diff --git a/.bazelrc b/.bazelrc index 58137d2eec548..736bc18131a91 100644 --- a/.bazelrc +++ b/.bazelrc @@ -94,11 +94,12 @@ build:debug --@rules_rust//:extra_rustc_flag="-Csplit-debuginfo=unpacked" # TODO(parkmycar): Enable this for macOS. `toolchains_llvm` defaults to ld64 which # doesn't support zlib compression. -build:linux --linkopt="-Wl,--compress-debug-sections=zstd" -build:linux --@rules_rust//:extra_rustc_flag="-Clink-arg=-Wl,--compress-debug-sections=zstd" -build:linux --linkopt="-Wl,-O3" +build:linux --linkopt="-Wl,--compress-debug-sections=zlib" +build:linux --@rules_rust//:extra_rustc_flag="-Clink-arg=-Wl,--compress-debug-sections=zlib" +# Specifying "-O2" uses level 6 zlib compression. +build:linux --linkopt="-Wl,-O2" build:linux --@rules_rust//:extra_rustc_flag="-Clink-arg=-Wl,-O2" -build:linux --copt="-gz=zstd" +build:linux --copt="-gz=zlib" # Match the DWARF version used by Rust # @@ -128,8 +129,7 @@ build --@rules_rust//:extra_rustc_flag="-Csymbol-mangling-version=v0" build --@rules_rust//:extra_rustc_flag="-Ccodegen-units=64" # Enabling pipelined builds allows dependent libraries to begin compiling with # just `.rmeta` instead of the full `.rlib`. -# TODO: Reenable when fixed, currently still sometimes fails, see for example: https://buildkite.com/materialize/test/builds/93445#0192d426-ff4e-4658-b48a-9473843ae116 -#build --@rules_rust//rust/settings:pipelined_compilation=True +build --@rules_rust//rust/settings:pipelined_compilation=True # `cargo check` like config, still experimental! # @@ -143,6 +143,8 @@ build:check --output_groups=build_metadata # # `/dev/shm` is a RAM backed temporary filesystem, it should speedup sandbox creation. build:ci --sandbox_base=/dev/shm +# Always enable verbose failures in CI, makes it easier to debug failures. +build:ci --verbose_failures # Release Build Configuration # diff --git a/.github/workflows/slack_notify_labeled.yml b/.github/workflows/slack_notify_labeled.yml index b3c81dd8a138e..b1efb09fc92cc 100644 --- a/.github/workflows/slack_notify_labeled.yml +++ b/.github/workflows/slack_notify_labeled.yml @@ -22,7 +22,7 @@ name: Slack Label Notifications on: - issues: + pull_request: types: - labeled @@ -30,30 +30,12 @@ jobs: notify: runs-on: ubuntu-latest steps: - - uses: actions-ecosystem/action-slack-notifier@fc778468d09c43a6f4d1b8cccaca59766656996a - if: ${{ github.event.label.name == 'P1' }} - with: - slack_token: ${{ secrets.SLACK_TOKEN }} - channel: github-p1 - message: | - `${{ github.event.label.name }}` label has been added to "${{ github.event.issue.title }}" (${{ github.event.issue.html_url }}) (assigned to: ${{ github.event.issue.assignee.login || 'unassigned' }}). - color: red - verbose: false - - uses: actions-ecosystem/action-slack-notifier@fc778468d09c43a6f4d1b8cccaca59766656996a - if: ${{ github.event.label.name == 'C-bug' }} - with: - slack_token: ${{ secrets.SLACK_TOKEN }} - channel: github-bugs - message: | - `${{ github.event.label.name }}` label has been added to "${{ github.event.issue.title }}" (${{ github.event.issue.html_url }}) (assigned to: ${{ github.event.issue.assignee.login || 'unassigned' }}). - color: red - verbose: false - uses: actions-ecosystem/action-slack-notifier@fc778468d09c43a6f4d1b8cccaca59766656996a if: ${{ github.event.label.name == 'release-blocker' }} with: slack_token: ${{ secrets.SLACK_TOKEN }} channel: release message: | - `${{ github.event.label.name }}` label has been added to "${{ github.event.issue.title }}" (${{ github.event.issue.html_url }}) (assigned to: ${{ github.event.issue.assignee.login || 'unassigned' }}). + `${{ github.event.label.name }}` label has been added to "${{ github.event.pull_request.title }}" (${{ github.event.pull_request.html_url }}) (assigned to: ${{ github.event.pull_request.assignee.login || 'unassigned' }}). color: red verbose: false diff --git a/.github/workflows/slack_notify_mz_catalog_server.yml b/.github/workflows/slack_notify_mz_catalog_server.yml deleted file mode 100644 index 0607f78cc82f5..0000000000000 --- a/.github/workflows/slack_notify_mz_catalog_server.yml +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright 2020 The Actions Ecosystem Authors -# Modifications Copyright Materialize, Inc. and contributors. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Portions of this file are derived from the README examples in the Action -# Slack Notifier project. The original source code was retrieved on -# January 5, 2022 from: -# -# https://github.com/actions-ecosystem/action-slack-notifier/blob/fc778468d09c43a6f4d1b8cccaca59766656996a/README.md - -# Send a notification to the #rnd-mz-catalog-server-council Slack channel when a change -# is made that adds or modifies an index on the mz_catalog_server cluster, or -# adds or modifies an object on which those indices depend. -# Also detects changes to catalog object retention. -# -# A notification is sent when all of these conditions are true: -# * A ready-to-review PR is (re-)opened, or a PR is moved from draft -# to ready-to-review. -# * The PR modifies the file 'test/sqllogictest/mz_catalog_server_index_accounting.slt', -# which is designed to detect new or modified mz_catalog_server indexes, OR -# * The PR changes the "is_retained_metrics..." field of any catalog object, -# as detected in the file 'src/catalog/src/builtin.rs'. - -name: Slack Mz Catalog Server Council Notifications - -on: - pull_request_target: - types: - - opened - - reopened - - ready_for_review - paths: - - src/catalog/src/builtin.rs - - test/sqllogictest/mz_catalog_server_index_accounting.slt - -jobs: - notify: - name: "Notify on mz_catalog_server index changes" - runs-on: ubuntu-latest - if: ${{ !github.event.pull_request.draft }} - steps: - - name: "Path filter" - id: filter - uses: dorny/paths-filter@v2 - with: - filters: | - builtin-rs: - - 'src/catalog/src/builtin.rs' - index-slt: - - 'test/sqllogictest/mz_catalog_server_index_accounting.slt' - - name: Checkout - uses: actions/checkout@v4 - - name: "Check Retained Metric Changes" - id: check-retain-metrics - if: steps.filter.outputs.builtin-rs == 'true' - run: | - # Check for the text "is_retained_metrics" modified in builtin.rs in the pull request - if git diff ${{ github.event.pull_request.base.sha }} -- 'src/catalog/src/builtin.rs' | grep -i 'is_retained_metrics'; then - echo "changed=true" >> $GITHUB_OUTPUT - fi - - name: "Push to Slack" - if: steps.filter.outputs.index-slt == 'true' || steps.check-retain-metrics.outputs.changed == 'true' - uses: actions-ecosystem/action-slack-notifier@fc778468d09c43a6f4d1b8cccaca59766656996a - with: - slack_token: ${{ secrets.SLACK_TOKEN }} - channel: rnd-mz-catalog-server-council - custom_payload: | - { - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "A new builtin index change is ready for review!" - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": ${{ toJSON(format('• *PR:* <{0}|{1}>', github.event.pull_request.html_url, github.event.pull_request.title)) }} - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "• *Author:* <${{ github.event.pull_request.user.html_url }}|${{ github.event.pull_request.user.login }}>" - } - } - ] - } diff --git a/.github/workflows/slack_notify_sql_parser.yml b/.github/workflows/slack_notify_sql_parser.yml index e051420c819fc..08e30d4e9e30e 100644 --- a/.github/workflows/slack_notify_sql_parser.yml +++ b/.github/workflows/slack_notify_sql_parser.yml @@ -39,6 +39,7 @@ on: - "src/sql-parser/**" - "src/sql-lexer/**" - "src/catalog/src/builtin.rs" + - test/sqllogictest/mz_catalog_server_index_accounting.slt jobs: notify: @@ -60,8 +61,20 @@ jobs: - 'src/catalog/src/builtin.rs' - '!**/Cargo.toml' - '!**/BUILD.bazel' + index-slt: + - 'test/sqllogictest/mz_catalog_server_index_accounting.slt' + - name: Checkout + uses: actions/checkout@v4 + - name: "Check Retained Metric Changes" + id: check-retain-metrics + if: steps.filter.outputs.builtin-rs == 'true' + run: | + # Check for the text "is_retained_metrics" modified in builtin.rs in the pull request + if git diff ${{ github.event.pull_request.base.sha }} -- 'src/catalog/src/builtin.rs' | grep -i 'is_retained_metrics'; then + echo "changed=true" >> $GITHUB_OUTPUT + fi - name: "Push to Slack" - if: steps.filter.outputs.sql-parser == 'true' || steps.filter.outputs.system-catalog == 'true' + if: steps.filter.outputs.sql-parser == 'true' || steps.filter.outputs.system-catalog == 'true' || steps.filter.outputs.index-slt == 'true' uses: actions-ecosystem/action-slack-notifier@fc778468d09c43a6f4d1b8cccaca59766656996a with: slack_token: ${{ secrets.SLACK_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index 64b662c22657a..cd5757ce37c06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4340,7 +4340,7 @@ dependencies = [ [[package]] name = "mz-balancerd" -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" dependencies = [ "anyhow", "async-trait", @@ -4484,7 +4484,7 @@ dependencies = [ [[package]] name = "mz-catalog-debug" -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" dependencies = [ "anyhow", "clap", @@ -4640,7 +4640,7 @@ dependencies = [ [[package]] name = "mz-clusterd" -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" dependencies = [ "anyhow", "axum", @@ -4920,7 +4920,7 @@ dependencies = [ [[package]] name = "mz-environmentd" -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" dependencies = [ "anyhow", "askama", @@ -5377,7 +5377,7 @@ dependencies = [ [[package]] name = "mz-materialized" -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" dependencies = [ "mz-clusterd", "mz-environmentd", @@ -5569,7 +5569,7 @@ dependencies = [ [[package]] name = "mz-orchestratord" -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" dependencies = [ "anyhow", "async-trait", @@ -5733,7 +5733,7 @@ dependencies = [ [[package]] name = "mz-persist-client" -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" dependencies = [ "anyhow", "arrayvec 0.7.4", @@ -6818,7 +6818,7 @@ dependencies = [ [[package]] name = "mz-testdrive" -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" dependencies = [ "anyhow", "arrow", @@ -11089,6 +11089,8 @@ dependencies = [ "globset", "hashbrown 0.14.5", "hyper 0.14.27", + "hyper 1.4.1", + "hyper-util", "indexmap 1.9.1", "insta", "k8s-openapi", diff --git a/LICENSE b/LICENSE index 7fd5683bed33c..660056dc3942f 100644 --- a/LICENSE +++ b/LICENSE @@ -13,7 +13,7 @@ Business Source License 1.1 Licensor: Materialize, Inc. -Licensed Work: Materialize Version 20241126 +Licensed Work: Materialize Version 20241204 The Licensed Work is © 2024 Materialize, Inc. Additional Use Grant: Within a single installation of Materialize, you @@ -32,7 +32,7 @@ Additional Use Grant: Within a single installation of Materialize, you whose definitions are controlled by such third parties. -Change Date: November 26, 2028 +Change Date: December 04, 2028 Change License: Apache License, Version 2.0 diff --git a/bin/ci-annotate-errors b/bin/ci-annotate-errors index 10bf5473a8b83..b15f1ebda6a98 100755 --- a/bin/ci-annotate-errors +++ b/bin/ci-annotate-errors @@ -25,6 +25,7 @@ else # keep only the remaining first line because there is a further line containing the mzcompose result summary HOSTNAME=$(echo "$OUTPUT" | head -n 1) else + # TODO: Remove when database-issues#8592 is fixed echo "Failed to get cloud hostname, using fallback value" HOSTNAME="7vifiksqeftxc6ld3r6zvc8n2.lb.us-east-1.aws.materialize.cloud" fi diff --git a/bin/ci-builder b/bin/ci-builder index e2aa654ee5ab1..75410e60d0792 100755 --- a/bin/ci-builder +++ b/bin/ci-builder @@ -103,7 +103,7 @@ fi # *inside* this image. See materialize.git.expand_globs in the Python code for # details on this computation. files=$(cat \ - <(git diff --name-only -z 4b825dc642cb6eb9a060e54bf8d69288fbee4904 ci/builder .bazelversion) \ + <(git diff --name-only -z 4b825dc642cb6eb9a060e54bf8d69288fbee4904 ci/builder .bazelversion WORKSPACE) \ <(git ls-files --others --exclude-standard -z ci/builder) \ | LC_ALL=C sort -z \ | xargs -0 "$shasum") diff --git a/bin/gen-completion b/bin/gen-completion index b0a260dd1296d..b98699fb98244 100755 --- a/bin/gen-completion +++ b/bin/gen-completion @@ -50,7 +50,7 @@ case "$cmd" in for name in "${python_autocompletions[@]}"; do for shell in "${supported_shells[@]}"; do if [ ! -f "$directory/../misc/completions/$shell/_$name" ]; then - printf "missing shell completion scripts. try running \`bin/completion generate\` and checking in the changes\n" + printf "missing shell completion scripts. try running \`bin/gen-completion generate\` and checking in the changes\n" exit 1 fi @@ -58,7 +58,7 @@ case "$cmd" in existing=$("$shasum" < "$directory"/../misc/completions/"$shell"/_"$name") if [[ "$latest" != "$existing" ]]; then - printf "shell completion scripts have uncommitted changes. try running \`bin/completion generate\` and checking in the changes\n" + printf "shell completion scripts have uncommitted changes. try running \`bin/gen-completion generate\` and checking in the changes\n" exit 1 fi done diff --git a/ci/builder/Dockerfile b/ci/builder/Dockerfile index f7926057d30b7..9d172eba6b0fa 100644 --- a/ci/builder/Dockerfile +++ b/ci/builder/Dockerfile @@ -203,10 +203,10 @@ RUN mkdir rust \ && cargo install --root /usr/local --version ="0.9.28" --locked cargo-hakari \ && cargo install --root /usr/local --version "=0.9.72" --locked cargo-nextest \ && cargo install --root /usr/local --version "=0.6.11" --locked cargo-llvm-cov \ - && cargo install --root /usr/local --version "=0.1.50" --features=vendored-openssl cargo-udeps \ - && cargo install --root /usr/local --version "=0.2.15" --no-default-features --features=s3,openssl/vendored sccache \ - && cargo install --root /usr/local --version "=0.3.6" cargo-binutils \ - && cargo install --root /usr/local --version "=0.13.0" wasm-pack + && cargo install --root /usr/local --version "=0.1.50" --locked --features=vendored-openssl cargo-udeps \ + && cargo install --root /usr/local --version "=0.2.15" --locked --no-default-features --features=s3,openssl/vendored sccache \ + && cargo install --root /usr/local --version "=0.3.6" --locked cargo-binutils \ + && cargo install --root /usr/local --version "=0.13.0" --locked wasm-pack # Link the system lld into the cross-compiling sysroot. @@ -273,7 +273,7 @@ RUN arch_bazel=$(echo "$ARCH_GCC" | sed -e "s/aarch64/arm64/" -e "s/amd64/x86_64 && if [ "$arch_bazel" = x86_64 ]; then echo '48ea0ff9d397a48add6369c261c5a4431fe6d5d5348cfb81411782fb80c388d3 /usr/local/bin/bazel' | sha256sum --check; fi \ && chmod +x /usr/local/bin/bazel -# Install KinD, kubectl, helm & helm-docs +# Install KinD, kubectl, helm, helm-docs & terraform RUN curl -fsSL https://kind.sigs.k8s.io/dl/v0.14.0/kind-linux-$ARCH_GO > /usr/local/bin/kind \ && chmod +x /usr/local/bin/kind \ @@ -305,6 +305,13 @@ RUN mkdir -p /usr/local/share/helm/plugins/unittest \ && tar -xf helm-unittest.tar.gz -C /usr/local/share/helm/plugins/unittest \ && rm helm-unittest.tar.gz +RUN curl -fsSL https://releases.hashicorp.com/terraform/1.9.8/terraform_1.9.8_linux_$ARCH_GO.zip > terraform.zip \ + && if [ $ARCH_GO = amd64 ]; then echo '186e0145f5e5f2eb97cbd785bc78f21bae4ef15119349f6ad4fa535b83b10df8 terraform.zip' | sha256sum --check; fi \ + && if [ $ARCH_GO = arm64 ]; then echo 'f85868798834558239f6148834884008f2722548f84034c9b0f62934b2d73ebb terraform.zip' | sha256sum --check; fi \ + && unzip terraform.zip terraform -d /usr/local/bin \ + && chmod +x /usr/local/bin/terraform \ + && rm terraform.zip + # Hardcode some known SSH hosts, or else SSH will ask whether the host is # trustworthy on the first connection. diff --git a/ci/builder/requirements.txt b/ci/builder/requirements.txt index 3b409f05a70b8..aa2d83960c6c4 100644 --- a/ci/builder/requirements.txt +++ b/ci/builder/requirements.txt @@ -19,7 +19,7 @@ ipywidgets==8.1.5 junit-xml==1.9 junitparser==3.2.0 jupyterlab==4.3.0 -jupyter-black==0.3.4 +jupyter-black==0.4.0 kubernetes==25.3.0 kubernetes-stubs==22.6.0.post1 launchdarkly-api==17.0.0 @@ -29,8 +29,8 @@ networkx==3.4.2 networkx-stubs==0.0.1 numpy==1.26.4 pandas==2.2.3 -pandas-stubs==2.2.3.241009 -parameterized==0.8.1 +pandas-stubs==2.2.3.241126 +parameterized==0.9.0 paramiko==3.5.0 pdoc==15.0.0 # We can revert back to standard pg8000 versions once https://github.com/tlocke/pg8000/pull/161 is released @@ -40,7 +40,7 @@ psutil==6.1.0 psycopg==3.2.3 psycopg-binary==3.2.3 pydantic==2.8.2 -pyelftools==0.29 +pyelftools==0.31 pyjwt==2.9.0 PyMySQL==1.1.1 pytest==8.3.3 @@ -51,7 +51,7 @@ ruamel.yaml==0.18.6 ruff==0.0.292 scipy==1.14.1 semver==3.0.0 -shtab==1.5.8 +shtab==1.7.1 sqlparse==0.5.0 toml==0.10.2 twine==5.1.1 @@ -71,5 +71,5 @@ fastavro==1.9.4 websocket-client==1.8.0 pyarrow-stubs==17.12 pyarrow==18.0.0 -minio==7.2.10 +minio==7.2.12 zstandard==0.23.0 diff --git a/ci/mkpipeline.py b/ci/mkpipeline.py index 5c8f6cd0c7dd2..d0cd70cb7214b 100644 --- a/ci/mkpipeline.py +++ b/ci/mkpipeline.py @@ -386,12 +386,29 @@ def switch_jobs_to_aws(pipeline: Any, priority: int) -> None: def visit(config: Any) -> None: if "agents" in config: agent = config["agents"].get("queue", None) - if agent == "hetzner-aarch64-4cpu-8gb": + if agent in ("hetzner-aarch64-4cpu-8gb", "hetzner-aarch64-2cpu-4gb"): config["agents"]["queue"] = "linux-aarch64-small" if agent == "hetzner-aarch64-8cpu-16gb": config["agents"]["queue"] = "linux-aarch64" if agent == "hetzner-aarch64-16cpu-32gb": config["agents"]["queue"] = "linux-aarch64-medium" + if agent in ( + "hetzner-x86-64-4cpu-8gb", + "hetzner-x86-64-2cpu-4gb", + "hetzner-x86-64-dedi-2cpu-8gb", + ): + config["agents"]["queue"] = "linux-x86-64-small" + if agent in ("hetzner-x86-64-8cpu-16gb", "hetzner-x86-64-dedi-4cpu-16gb"): + config["agents"]["queue"] = "linux-x86-64" + if agent in ("hetzner-x86-64-16cpu-32gb", "hetzner-x86-64-dedi-8cpu-32gb"): + config["agents"]["queue"] = "linux-x86-64-medium" + if agent == "hetzner-x86-64-dedi-16cpu-64gb": + config["agents"]["queue"] = "linux-x86-64-large" + if agent in ( + "hetzner-x86-64-dedi-32cpu-128gb", + "hetzner-x86-64-dedi-48cpu-192gb", + ): + config["agents"]["queue"] = "builder-linux-x86_64" for config in pipeline["steps"]: if "trigger" in config or "wait" in config: diff --git a/ci/nightly/pipeline.template.yml b/ci/nightly/pipeline.template.yml index aceac595f4423..d8bebe9313d24 100644 --- a/ci/nightly/pipeline.template.yml +++ b/ci/nightly/pipeline.template.yml @@ -73,8 +73,7 @@ steps: composition: cargo-test args: [--miri-full] agents: - # TODO(def-) Switch back to Hetzner - queue: builder-linux-aarch64-mem + queue: hetzner-aarch64-16cpu-32gb sanitizer: skip - group: Benchmarks @@ -82,11 +81,11 @@ steps: steps: - id: feature-benchmark label: "Feature benchmark against merge base or 'latest' %N" - depends_on: build-aarch64 + depends_on: build-x86_64 timeout_in_minutes: 720 parallelism: 8 agents: - queue: linux-aarch64-medium + queue: hetzner-x86-64-dedi-8cpu-32gb plugins: - ./ci/plugins/mzcompose: composition: feature-benchmark @@ -96,10 +95,11 @@ steps: - common-ancestor - id: scalability-benchmark-dml-dql label: "Scalability benchmark (read & write) against merge base or 'latest'" - depends_on: build-aarch64 + depends_on: build-x86_64 timeout_in_minutes: 240 agents: - queue: linux-aarch64 + # Larger instance is more stable in performance + queue: hetzner-x86-64-dedi-8cpu-32gb plugins: - ./ci/plugins/mzcompose: composition: scalability @@ -116,10 +116,11 @@ steps: - "256" - id: scalability-benchmark-ddl label: "Scalability benchmark (DDL) against merge base or 'latest'" - depends_on: build-aarch64 + depends_on: build-x86_64 timeout_in_minutes: 1200 agents: - queue: linux-aarch64 + # Larger instance is more stable in performance + queue: hetzner-x86-64-dedi-16cpu-64gb plugins: - ./ci/plugins/mzcompose: composition: scalability @@ -140,10 +141,11 @@ steps: - "64" - id: scalability-benchmark-connection label: "Scalability benchmark (connection) against merge base or 'latest'" - depends_on: build-aarch64 + depends_on: build-x86_64 timeout_in_minutes: 180 agents: - queue: linux-aarch64-medium + # Larger instance is more stable in performance + queue: hetzner-x86-64-dedi-16cpu-64gb plugins: - ./ci/plugins/mzcompose: composition: scalability @@ -162,10 +164,11 @@ steps: - "2048" - id: parallel-benchmark label: "Parallel Benchmark" - depends_on: build-aarch64 + depends_on: build-x86_64 timeout_in_minutes: 120 agents: - queue: linux-aarch64-medium + # Larger instance is more stable in performance + queue: hetzner-x86-64-dedi-16cpu-64gb plugins: - ./ci/plugins/mzcompose: composition: parallel-benchmark @@ -540,7 +543,8 @@ steps: plugins: - ./ci/plugins/mzcompose: composition: zippy - args: [--scenario=KafkaSources, --actions=10000, --cockroach-tag=latest, --max-execution-time=30m] + # TODO: Reenable --cockroach-tag=latest when https://github.com/cockroachdb/cockroach/issues/136678 is fixed + args: [--scenario=KafkaSources, --actions=10000, --max-execution-time=30m] - id: zippy-alter-connection label: "Zippy w/ alter connection" @@ -821,61 +825,49 @@ steps: composition: platform-checks args: [--scenario=UpgradeEntireMzFourVersions, "--seed=$BUILDKITE_JOB_ID"] - - id: checks-0dt-restart-entire-mz - label: "Checks 0dt restart of the entire Mz" - depends_on: build-aarch64 - timeout_in_minutes: 240 - agents: - # TODO(def-): Switch back to hetzner - queue: linux-aarch64-large - plugins: - - ./ci/plugins/mzcompose: - composition: platform-checks - args: [--scenario=ZeroDowntimeRestartEntireMz, "--seed=$BUILDKITE_JOB_ID"] - - id: checks-0dt-restart-entire-mz-forced-migrations - label: "Checks 0dt restart of the entire Mz with forced migrations" + label: "Checks 0dt restart of the entire Mz with forced migrations %N" depends_on: build-aarch64 - timeout_in_minutes: 240 + timeout_in_minutes: 60 + parallelism: 2 agents: - # TODO(def-): Switch back to hetzner - queue: linux-aarch64-large + queue: hetzner-aarch64-16cpu-32gb plugins: - ./ci/plugins/mzcompose: composition: platform-checks args: [--scenario=ZeroDowntimeRestartEntireMzForcedMigrations, "--seed=$BUILDKITE_JOB_ID"] - id: checks-0dt-upgrade-entire-mz - label: "Checks 0dt upgrade, whole-Mz restart" + label: "Checks 0dt upgrade, whole-Mz restart %N" depends_on: build-aarch64 - timeout_in_minutes: 240 + timeout_in_minutes: 60 + parallelism: 2 agents: - # TODO(def-): Switch back to hetzner - queue: linux-aarch64-large + queue: hetzner-aarch64-16cpu-32gb plugins: - ./ci/plugins/mzcompose: composition: platform-checks args: [--scenario=ZeroDowntimeUpgradeEntireMz, "--seed=$BUILDKITE_JOB_ID"] - id: checks-0dt-upgrade-entire-mz-two-versions - label: "Checks 0dt upgrade across two versions" + label: "Checks 0dt upgrade across two versions %N" depends_on: build-aarch64 - timeout_in_minutes: 240 + timeout_in_minutes: 60 + parallelism: 2 agents: - # TODO(def-): Switch back to hetzner - queue: linux-aarch64-large + queue: hetzner-aarch64-16cpu-32gb plugins: - ./ci/plugins/mzcompose: composition: platform-checks args: [--scenario=ZeroDowntimeUpgradeEntireMzTwoVersions, "--seed=$BUILDKITE_JOB_ID"] - id: checks-0dt-upgrade-entire-mz-four-versions - label: "Checks 0dt upgrade across four versions" + label: "Checks 0dt upgrade across four versions %N" depends_on: build-aarch64 - timeout_in_minutes: 240 + timeout_in_minutes: 60 + parallelism: 2 agents: - # TODO(def-): Switch back to hetzner - queue: linux-aarch64-large + queue: hetzner-aarch64-16cpu-32gb plugins: - ./ci/plugins/mzcompose: composition: platform-checks @@ -883,11 +875,10 @@ steps: - id: checks-0dt-bump-version label: "Checks 0dt upgrade to a bumped version" - depends_on: build-aarch64 + depends_on: build-x86_64 timeout_in_minutes: 240 agents: - # TODO(def-): Switch back to hetzner - queue: builder-linux-aarch64-mem + queue: hetzner-x86-64-dedi-32cpu-128gb plugins: - ./ci/plugins/mzcompose: composition: platform-checks @@ -1102,31 +1093,49 @@ steps: - ./ci/plugins/mzcompose: composition: launchdarkly - - id: cloud-canary - label: "Canary Deploy in Staging Cloud" - depends_on: build-aarch64 - timeout_in_minutes: 1200 - concurrency: 1 - concurrency_group: 'cloud-canary' - agents: - queue: linux-aarch64-small - plugins: - - ./ci/plugins/mzcompose: - composition: cloud-canary - branches: "main v*.*" + - group: E2E + key: e2e + steps: + - id: cloud-canary + label: "Canary Deploy in Staging Cloud" + depends_on: build-aarch64 + timeout_in_minutes: 1200 + concurrency: 1 + concurrency_group: 'cloud-canary' + agents: + queue: linux-aarch64-small + plugins: + - ./ci/plugins/mzcompose: + composition: cloud-canary + branches: "main v*.*" - - id: mz-e2e - label: "Mz E2E Test" - depends_on: build-aarch64 - timeout_in_minutes: 1200 - concurrency: 1 - concurrency_group: 'mz-e2e' - agents: - # Requires real Mz access, CONFLUENT_CLOUD_DEVEX_KAFKA_USERNAME, etc. - queue: linux-aarch64-small - plugins: - - ./ci/plugins/mzcompose: - composition: mz-e2e + - id: mz-e2e + label: "Mz E2E Test" + depends_on: build-aarch64 + timeout_in_minutes: 1200 + concurrency: 1 + concurrency_group: 'mz-e2e' + agents: + # Requires real Mz access, CONFLUENT_CLOUD_DEVEX_KAFKA_USERNAME, etc. + queue: linux-aarch64-small + plugins: + - ./ci/plugins/mzcompose: + composition: mz-e2e + + - id: terraform-aws + label: "Terraform + Helm Chart E2E on AWS" + artifact_paths: [test/terraform/aws/terraform.tfstate] + depends_on: build-aarch64 + timeout_in_minutes: 1200 + concurrency: 1 + concurrency_group: 'terraform' + agents: + queue: linux-aarch64-small + plugins: + - ./ci/plugins/scratch-aws-access: ~ + - ./ci/plugins/mzcompose: + composition: terraform + branches: "main v*.*" - group: "Output consistency" key: output-consistency @@ -1180,7 +1189,7 @@ steps: depends_on: build-aarch64 timeout_in_minutes: 58 agents: - queue: linux-aarch64-small + queue: hetzner-aarch64-4cpu-8gb plugins: - ./ci/plugins/mzcompose: composition: feature-flag-consistency @@ -1314,6 +1323,7 @@ steps: - id: rqg-window-functions label: "RQG window functions workload" depends_on: build-aarch64 + skip: "flaky until database-issues#8366 is fixed" timeout_in_minutes: 45 agents: queue: hetzner-aarch64-4cpu-8gb @@ -1517,12 +1527,11 @@ steps: - id: parallel-workload-0dt label: "Parallel Workload (0dt deploy)" - depends_on: build-aarch64 + depends_on: build-x86_64 artifact_paths: [parallel-workload-queries.log.zst] timeout_in_minutes: 90 agents: - # TODO(def-): Switch back to hetzner - queue: linux-aarch64-large + queue: hetzner-x86-64-dedi-16cpu-64gb plugins: - ./ci/plugins/mzcompose: composition: parallel-workload diff --git a/ci/plugins/cloudtest/hooks/command b/ci/plugins/cloudtest/hooks/command index 6216f5a2c2c3f..b8405dda6311d 100644 --- a/ci/plugins/cloudtest/hooks/command +++ b/ci/plugins/cloudtest/hooks/command @@ -42,6 +42,7 @@ ci_collapsed_heading ":docker: Purging all existing docker containers and volume sudo systemctl restart docker docker ps --all --quiet | xargs --no-run-if-empty docker rm --force --volumes killall -9 clusterd || true # There might be remaining processes from a previous cargo-test run +rm -rf ~/.kube # Remove potential state from E2E Terraform tests ci_collapsed_heading "kind: Increase system limits..." sudo sysctl fs.inotify.max_user_watches=524288 diff --git a/ci/plugins/mzcompose/hooks/pre-exit b/ci/plugins/mzcompose/hooks/pre-exit index 8ba86668a1ec5..5153e85197d18 100755 --- a/ci/plugins/mzcompose/hooks/pre-exit +++ b/ci/plugins/mzcompose/hooks/pre-exit @@ -85,7 +85,7 @@ timeout 300 buildkite-agent artifact upload "$artifacts_str" || true bin/ci-builder run stable bin/ci-annotate-errors --test-cmd="$(cat test_cmd)" --test-desc="$(cat test_desc)" "${artifacts[@]}" > ci-annotate-errors.log || CI_ANNOTATE_ERRORS_RESULT=$? buildkite-agent artifact upload "ci-annotate-errors.log" -if [ ! -s services.log ] && [ "$BUILDKITE_LABEL" != "Maelstrom coverage of persist" ] && [ "$BUILDKITE_LABEL" != "Long single-node Maelstrom coverage of persist" ] && [ "$BUILDKITE_LABEL" != "Maelstrom coverage of txn-wal" ] && [ "$BUILDKITE_LABEL" != "Mz E2E Test" ] && [ "$BUILDKITE_LABEL" != "Output consistency (version for DFR)" ] && [ "$BUILDKITE_LABEL" != "Output consistency (version for CTF)" ] && [ "$BUILDKITE_LABEL" != "QA Canary Environment Base Load" ] && [ "$BUILDKITE_LABEL" != "Parallel Benchmark against QA Canary Environment" ] && [ "$BUILDKITE_LABEL" != "Parallel Benchmark against QA Benchmarking Staging Environment" ]; then +if [ ! -s services.log ] && [ "$BUILDKITE_LABEL" != "Maelstrom coverage of persist" ] && [ "$BUILDKITE_LABEL" != "Long single-node Maelstrom coverage of persist" ] && [ "$BUILDKITE_LABEL" != "Maelstrom coverage of txn-wal" ] && [ "$BUILDKITE_LABEL" != "Mz E2E Test" ] && [ "$BUILDKITE_LABEL" != "Output consistency (version for DFR)" ] && [ "$BUILDKITE_LABEL" != "Output consistency (version for CTF)" ] && [ "$BUILDKITE_LABEL" != "QA Canary Environment Base Load" ] && [ "$BUILDKITE_LABEL" != "Parallel Benchmark against QA Canary Environment" ] && [ "$BUILDKITE_LABEL" != "Parallel Benchmark against QA Benchmarking Staging Environment" ] && [ "$BUILDKITE_LABEL" != "Terraform + Helm Chart E2E on AWS" ]; then echo "+++ services.log is empty, failing" exit 1 fi @@ -139,10 +139,6 @@ fi ci_unimportant_heading ":docker: Cleaning up after mzcompose" -run() { - bin/ci-builder run stable bin/mzcompose --mz-quiet --find "$BUILDKITE_PLUGIN_MZCOMPOSE_COMPOSITION" "$@" -} - # docker-compose kill may fail attempting to kill containers # that have just exited on their own because of the # "shared-fate" mechanism employed by Mz clusters diff --git a/ci/plugins/scratch-aws-access/README.md b/ci/plugins/scratch-aws-access/README.md index b654508212e38..8e72e51bc07ed 100644 --- a/ci/plugins/scratch-aws-access/README.md +++ b/ci/plugins/scratch-aws-access/README.md @@ -7,7 +7,7 @@ scratch AWS account. The following environment variables are set: * `AWS_SECRET_ACCESS_KEY` * `AWS_SESSION_TOKEN` -The credentials are valid for one hour. +The credentials are valid for 12 hours. ## Example diff --git a/ci/release-qualification/pipeline.template.yml b/ci/release-qualification/pipeline.template.yml index f003862a33e91..0701846c5dc18 100644 --- a/ci/release-qualification/pipeline.template.yml +++ b/ci/release-qualification/pipeline.template.yml @@ -166,11 +166,11 @@ steps: steps: - id: feature-benchmark-scale-plus-one label: "Feature benchmark against 'common-ancestor' with --scale=+1 %N" - depends_on: build-aarch64 + depends_on: build-x86_64 timeout_in_minutes: 2880 parallelism: 8 agents: - queue: linux-aarch64-medium + queue: hetzner-x86-64-dedi-16cpu-64gb plugins: - ./ci/plugins/mzcompose: composition: feature-benchmark @@ -178,10 +178,10 @@ steps: - id: long-parallel-benchmark label: "Long Parallel Benchmark" - depends_on: build-aarch64 + depends_on: build-x86_64 timeout_in_minutes: 1200 agents: - queue: linux-aarch64-medium + queue: hetzner-x86-64-dedi-8cpu-32gb plugins: - ./ci/plugins/mzcompose: composition: parallel-benchmark @@ -438,10 +438,9 @@ steps: args: [--scenario=KillClusterdStorage, "--seed=$BUILDKITE_JOB_ID"] - id: checks-restart-source-postgres - label: "Checks + restart source Postgres %N" + label: "Checks + restart source Postgres" depends_on: build-aarch64 timeout_in_minutes: 180 - parallelism: 2 agents: queue: hetzner-aarch64-16cpu-32gb plugins: @@ -474,6 +473,18 @@ steps: composition: platform-checks args: [--scenario=DropCreateDefaultReplica, "--seed=$BUILDKITE_JOB_ID"] + - id: checks-0dt-restart-entire-mz + label: "Checks 0dt restart of the entire Mz %N" + depends_on: build-aarch64 + timeout_in_minutes: 60 + parallelism: 2 + agents: + queue: hetzner-aarch64-16cpu-32gb + plugins: + - ./ci/plugins/mzcompose: + composition: platform-checks + args: [--scenario=ZeroDowntimeRestartEntireMz, "--seed=$BUILDKITE_JOB_ID"] + - id: limits label: "Product limits (finding new limits) %N" depends_on: build-aarch64 diff --git a/ci/test/docs-widgets/package-lock.json b/ci/test/docs-widgets/package-lock.json index bbbd8411d7c81..a6e64ec126234 100644 --- a/ci/test/docs-widgets/package-lock.json +++ b/ci/test/docs-widgets/package-lock.json @@ -11,9 +11,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -26,9 +26,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -41,9 +41,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -56,9 +56,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -71,9 +71,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -86,9 +86,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -101,9 +101,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -116,9 +116,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -131,9 +131,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -146,9 +146,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -161,9 +161,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -176,9 +176,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -191,9 +191,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -206,9 +206,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -221,9 +221,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -236,9 +236,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -251,9 +251,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -266,9 +266,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -281,9 +281,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -296,9 +296,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -311,9 +311,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -326,9 +326,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -341,9 +341,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -372,9 +372,9 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz", - "integrity": "sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.26.0.tgz", + "integrity": "sha512-gJNwtPDGEaOEgejbaseY6xMFu+CPltsc8/T+diUTTbOQLqD+bnrJq9ulH6WD69TqwqWmrfRAtUv30cCFZlbGTQ==", "cpu": [ "arm" ], @@ -384,9 +384,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz", - "integrity": "sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.26.0.tgz", + "integrity": "sha512-YJa5Gy8mEZgz5JquFruhJODMq3lTHWLm1fOy+HIANquLzfIOzE9RA5ie3JjCdVb9r46qfAQY/l947V0zfGJ0OQ==", "cpu": [ "arm64" ], @@ -396,9 +396,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz", - "integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.26.0.tgz", + "integrity": "sha512-ErTASs8YKbqTBoPLp/kA1B1Um5YSom8QAc4rKhg7b9tyyVqDBlQxy7Bf2wW7yIlPGPg2UODDQcbkTlruPzDosw==", "cpu": [ "arm64" ], @@ -408,9 +408,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz", - "integrity": "sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.26.0.tgz", + "integrity": "sha512-wbgkYDHcdWW+NqP2mnf2NOuEbOLzDblalrOWcPyY6+BRbVhliavon15UploG7PpBRQ2bZJnbmh8o3yLoBvDIHA==", "cpu": [ "x64" ], @@ -419,10 +419,46 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.26.0.tgz", + "integrity": "sha512-Y9vpjfp9CDkAG4q/uwuhZk96LP11fBz/bYdyg9oaHYhtGZp7NrbkQrj/66DYMMP2Yo/QPAsVHkV891KyO52fhg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.26.0.tgz", + "integrity": "sha512-A/jvfCZ55EYPsqeaAt/yDAG4q5tt1ZboWMHEvKAH9Zl92DWvMIbnZe/f/eOXze65aJaaKbL+YeM0Hz4kLQvdwg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz", - "integrity": "sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.26.0.tgz", + "integrity": "sha512-paHF1bMXKDuizaMODm2bBTjRiHxESWiIyIdMugKeLnjuS1TCS54MF5+Y5Dx8Ui/1RBPVRE09i5OUlaLnv8OGnA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.26.0.tgz", + "integrity": "sha512-cwxiHZU1GAs+TMxvgPfUDtVZjdBdTsQwVnNlzRXC5QzIJ6nhfB4I1ahKoe9yPmoaA/Vhf7m9dB1chGPpDRdGXg==", "cpu": [ "arm" ], @@ -432,9 +468,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz", - "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.26.0.tgz", + "integrity": "sha512-4daeEUQutGRCW/9zEo8JtdAgtJ1q2g5oHaoQaZbMSKaIWKDQwQ3Yx0/3jJNmpzrsScIPtx/V+1AfibLisb3AMQ==", "cpu": [ "arm64" ], @@ -444,9 +480,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz", - "integrity": "sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.26.0.tgz", + "integrity": "sha512-eGkX7zzkNxvvS05ROzJ/cO/AKqNvR/7t1jA3VZDi2vRniLKwAWxUr85fH3NsvtxU5vnUUKFHKh8flIBdlo2b3Q==", "cpu": [ "arm64" ], @@ -456,11 +492,11 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz", - "integrity": "sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.26.0.tgz", + "integrity": "sha512-Odp/lgHbW/mAqw/pU21goo5ruWsytP7/HCC/liOt0zcGG0llYWKrd10k9Fj0pdj3prQ63N5yQLCLiE7HTX+MYw==", "cpu": [ - "ppc64le" + "ppc64" ], "optional": true, "os": [ @@ -468,9 +504,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz", - "integrity": "sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.26.0.tgz", + "integrity": "sha512-MBR2ZhCTzUgVD0OJdTzNeF4+zsVogIR1U/FsyuFerwcqjZGvg2nYe24SAHp8O5sN8ZkRVbHwlYeHqcSQ8tcYew==", "cpu": [ "riscv64" ], @@ -480,9 +516,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz", - "integrity": "sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.26.0.tgz", + "integrity": "sha512-YYcg8MkbN17fMbRMZuxwmxWqsmQufh3ZJFxFGoHjrE7bv0X+T6l3glcdzd7IKLiwhT+PZOJCblpnNlz1/C3kGQ==", "cpu": [ "s390x" ], @@ -492,9 +528,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz", - "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.26.0.tgz", + "integrity": "sha512-ZuwpfjCwjPkAOxpjAEjabg6LRSfL7cAJb6gSQGZYjGhadlzKKywDkCUnJ+KEfrNY1jH5EEoSIKLCb572jSiglA==", "cpu": [ "x64" ], @@ -504,9 +540,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz", - "integrity": "sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.26.0.tgz", + "integrity": "sha512-+HJD2lFS86qkeF8kNu0kALtifMpPCZU80HvwztIKnYwym3KnA1os6nsX4BGSTLtS2QVAGG1P3guRgsYyMA0Yhg==", "cpu": [ "x64" ], @@ -516,9 +552,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz", - "integrity": "sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.26.0.tgz", + "integrity": "sha512-WUQzVFWPSw2uJzX4j6YEbMAiLbs0BUysgysh8s817doAYhR5ybqTI1wtKARQKo6cGop3pHnrUJPFCsXdoFaimQ==", "cpu": [ "arm64" ], @@ -528,9 +564,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz", - "integrity": "sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.26.0.tgz", + "integrity": "sha512-D4CxkazFKBfN1akAIY6ieyOqzoOoBV1OICxgUblWxff/pSjCA2khXlASUx7mK6W1oP4McqhgcCsu6QaLj3WMWg==", "cpu": [ "ia32" ], @@ -540,9 +576,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz", - "integrity": "sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.26.0.tgz", + "integrity": "sha512-2x8MO1rm4PGEP0xWbubJW5RtbNLk3puzAMaLQd3B3JHVw4KcHlmXcO+Wewx9zCoo7EUFiMlu/aZbCJ7VjMzAag==", "cpu": [ "x64" ], @@ -557,9 +593,9 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, "node_modules/@vitest/expect": { "version": "1.4.0", @@ -822,9 +858,9 @@ } }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -833,29 +869,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/estree-walker": { @@ -1250,9 +1286,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/pkg-types": { "version": "1.0.3", @@ -1265,9 +1301,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -1284,8 +1320,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -1333,11 +1369,11 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/rollup": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.2.tgz", - "integrity": "sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.26.0.tgz", + "integrity": "sha512-ilcl12hnWonG8f+NxU6BlgysVA0gvY2l8N0R84S1HcINbW20bvwuCngJkkInV6LXhwRpucsW5k1ovDwEdBVrNg==", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -1347,21 +1383,24 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.2", - "@rollup/rollup-android-arm64": "4.13.2", - "@rollup/rollup-darwin-arm64": "4.13.2", - "@rollup/rollup-darwin-x64": "4.13.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.2", - "@rollup/rollup-linux-arm64-gnu": "4.13.2", - "@rollup/rollup-linux-arm64-musl": "4.13.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2", - "@rollup/rollup-linux-riscv64-gnu": "4.13.2", - "@rollup/rollup-linux-s390x-gnu": "4.13.2", - "@rollup/rollup-linux-x64-gnu": "4.13.2", - "@rollup/rollup-linux-x64-musl": "4.13.2", - "@rollup/rollup-win32-arm64-msvc": "4.13.2", - "@rollup/rollup-win32-ia32-msvc": "4.13.2", - "@rollup/rollup-win32-x64-msvc": "4.13.2", + "@rollup/rollup-android-arm-eabi": "4.26.0", + "@rollup/rollup-android-arm64": "4.26.0", + "@rollup/rollup-darwin-arm64": "4.26.0", + "@rollup/rollup-darwin-x64": "4.26.0", + "@rollup/rollup-freebsd-arm64": "4.26.0", + "@rollup/rollup-freebsd-x64": "4.26.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.26.0", + "@rollup/rollup-linux-arm-musleabihf": "4.26.0", + "@rollup/rollup-linux-arm64-gnu": "4.26.0", + "@rollup/rollup-linux-arm64-musl": "4.26.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.26.0", + "@rollup/rollup-linux-riscv64-gnu": "4.26.0", + "@rollup/rollup-linux-s390x-gnu": "4.26.0", + "@rollup/rollup-linux-x64-gnu": "4.26.0", + "@rollup/rollup-linux-x64-musl": "4.26.0", + "@rollup/rollup-win32-arm64-msvc": "4.26.0", + "@rollup/rollup-win32-ia32-msvc": "4.26.0", + "@rollup/rollup-win32-x64-msvc": "4.26.0", "fsevents": "~2.3.2" } }, @@ -1422,9 +1461,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "engines": { "node": ">=0.10.0" } @@ -1543,13 +1582,13 @@ } }, "node_modules/vite": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz", - "integrity": "sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==", + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -1568,6 +1607,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -1585,6 +1625,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/ci/test/lint-main/checks/check-new-line.sh b/ci/test/lint-main/checks/check-new-line.sh index 20801af950855..2c5a6b90d6236 100755 --- a/ci/test/lint-main/checks/check-new-line.sh +++ b/ci/test/lint-main/checks/check-new-line.sh @@ -22,7 +22,7 @@ files=$(git_files "$@") # Only binary files are permitted to omit a trailing newline. If you're here to # exclude a text file that is missing its trailing newline, like an SVG, add # a trailing newline to the text file instead. -newline_files=$(grep -vE '\.(png|jpe?g|pb|avro|ico|so)$' <<< "$files") +newline_files=$(grep -vE '(_scratch|\.(png|jpe?g|pb|avro|ico|so))$' <<< "$files") try xargs misc/lint/trailing-newline.sh <<< "$newline_files" try xargs git --no-pager diff --check "$(git_empty_tree)" <<< "$newline_files" diff --git a/ci/test/pipeline.template.yml b/ci/test/pipeline.template.yml index bfc9db1845e66..a002ebfe8960c 100644 --- a/ci/test/pipeline.template.yml +++ b/ci/test/pipeline.template.yml @@ -170,7 +170,7 @@ steps: depends_on: [] timeout_in_minutes: 20 agents: - queue: linux-aarch64-small + queue: hetzner-aarch64-4cpu-8gb coverage: skip sanitizer: skip @@ -640,7 +640,7 @@ steps: - id: feature-benchmark-kafka-only label: "Feature benchmark (Kafka only)" - depends_on: build-aarch64 + depends_on: build-x86_64 timeout_in_minutes: 45 plugins: - ./ci/plugins/mzcompose: @@ -654,7 +654,7 @@ steps: ] coverage: skip agents: - queue: linux-aarch64-small + queue: hetzner-x86-64-dedi-4cpu-16gb # Fast tests closer to the end, doesn't matter as much if they have to wait # for an agent diff --git a/doc/user/assets/sass/_layout.scss b/doc/user/assets/sass/_layout.scss index c94d5b86395c1..b337bd0185fdb 100644 --- a/doc/user/assets/sass/_layout.scss +++ b/doc/user/assets/sass/_layout.scss @@ -31,7 +31,7 @@ // https://weblog.west-wind.com/posts/2016/Feb/15/Flexbox-Containers-PRE-tags-and-managing-Overflow min-width: 0; max-width: 840px; - padding: var(--xx-small) var(--small); + padding: var(--large) var(--small); @media (max-width: 1260px) { flex: 2.5; @@ -154,7 +154,7 @@ table.inline-headings { margin-left: auto; position: sticky; top: var(--nav-height); - padding: var(--x-small) var(--nano); + padding: var(--large) var(--small); overflow-y: auto; height: 100vh; display: flex; diff --git a/doc/user/config.toml b/doc/user/config.toml index cda412e9a614c..2a2fc09384741 100644 --- a/doc/user/config.toml +++ b/doc/user/config.toml @@ -7,6 +7,8 @@ disableKinds = ['taxonomy'] [params] repo = "//github.com/MaterializeInc/materialize" +bannerMessage = "Need to run Materialize in your own private or public cloud? Get early access to **self-managed Materialize**!" +bannerLink="https://materialize.com/blog/self-managed/?utm_medium=docs-banner&utm_source=documentation&utm_campaign=FY25_Blogs&sf_campaign=701TR00000NoNFXYA3&utm_term=&utm_content=" [frontmatter] publishDate = ["publishDate"] diff --git a/doc/user/content/ingest-data/mysql/amazon-aurora.md b/doc/user/content/ingest-data/mysql/amazon-aurora.md index a0b0064db4a19..eb288d4d7d4fd 100644 --- a/doc/user/content/ingest-data/mysql/amazon-aurora.md +++ b/doc/user/content/ingest-data/mysql/amazon-aurora.md @@ -8,8 +8,6 @@ menu: identifier: "mysql-amazon-aurora" --- -{{< public-preview />}} - This page shows you how to stream data from [Amazon Aurora MySQL](https://aws.amazon.com/rds/aurora/) to Materialize using the [MySQL source](/sql/create-source/mysql/). diff --git a/doc/user/content/ingest-data/mysql/amazon-rds.md b/doc/user/content/ingest-data/mysql/amazon-rds.md index 30874c55f29de..32b40f894f292 100644 --- a/doc/user/content/ingest-data/mysql/amazon-rds.md +++ b/doc/user/content/ingest-data/mysql/amazon-rds.md @@ -8,8 +8,6 @@ menu: identifier: "mysql-amazon-rds" --- -{{< public-preview />}} - This page shows you how to stream data from [Amazon RDS for MySQL](https://aws.amazon.com/rds/mysql/) to Materialize using the [MySQL source](/sql/create-source/mysql). diff --git a/doc/user/content/ingest-data/mysql/azure-db.md b/doc/user/content/ingest-data/mysql/azure-db.md index ed4ca6a2c8956..ee1417500daa9 100644 --- a/doc/user/content/ingest-data/mysql/azure-db.md +++ b/doc/user/content/ingest-data/mysql/azure-db.md @@ -8,8 +8,6 @@ menu: indentifier: "mysql-azure-db" --- -{{< public-preview />}} - This page shows you how to stream data from [Azure DB for MySQL](https://azure.microsoft.com/en-us/products/MySQL) to Materialize using the [MySQL source](/sql/create-source/mysql/). diff --git a/doc/user/content/ingest-data/mysql/google-cloud-sql.md b/doc/user/content/ingest-data/mysql/google-cloud-sql.md index 8fd54244cc67a..131e0e8692b40 100644 --- a/doc/user/content/ingest-data/mysql/google-cloud-sql.md +++ b/doc/user/content/ingest-data/mysql/google-cloud-sql.md @@ -8,8 +8,6 @@ menu: identifier: "mysql-google-cloudsql" --- -{{< public-preview />}} - This page shows you how to stream data from [Google Cloud SQL for MySQL](https://cloud.google.com/sql/MySQL) to Materialize using the[MySQL source](/sql/create-source/mysql/). diff --git a/doc/user/content/ingest-data/mysql/self-hosted.md b/doc/user/content/ingest-data/mysql/self-hosted.md index bd63c401719d1..49bd9d493a5a0 100644 --- a/doc/user/content/ingest-data/mysql/self-hosted.md +++ b/doc/user/content/ingest-data/mysql/self-hosted.md @@ -8,8 +8,6 @@ menu: identifier: "mysql-self-hosted" --- -{{< public-preview />}} - This page shows you how to stream data from a self-hosted MySQL database to Materialize using the [MySQL source](/sql/create-source/mysql/). diff --git a/doc/user/content/releases/previews.md b/doc/user/content/releases/previews.md index fce5b037223ff..4bc88bfcc40cf 100644 --- a/doc/user/content/releases/previews.md +++ b/doc/user/content/releases/previews.md @@ -76,12 +76,6 @@ For more information, see: - [`CREATE CLUSTER`](/sql/create-cluster). - [`ALTER CLUSTER`](/sql/alter-cluster). -### COMMENT ON - -Materialize adds the `COMMENT ON` command to allow commenting on an object. - -For more information, see [`COMMENT ON`](/sql/comment-on). - ### Support for Fivetran Materialize adds support for Fivetran to sync data into Materialize. @@ -186,18 +180,6 @@ your environment. Overview](/images/health-dashboard.png "Health Dashboard in the Materialize Console Environment Overview") -### Native connectors for MySQL - -Materialize provides native connectors for MySQL as a source. - -For more information, see: - -- [Ingest data: MySQL](/ingest-data/mysql/) - -- [Create Source: MySQL](/sql/create-source/mysql/) - -- [Create Connection: MySQL](/sql/create-connection/#mysql) - ### AWS Connection for IAM authentication Materialize supports the use of an AWS Connection to perform: diff --git a/doc/user/content/releases/v0.126.md b/doc/user/content/releases/v0.126.md index 67654adabf387..4134e217d5d63 100644 --- a/doc/user/content/releases/v0.126.md +++ b/doc/user/content/releases/v0.126.md @@ -2,6 +2,7 @@ title: "Materialize v0.126" date: 2024-12-04 released: false +patch: 2 _build: render: never --- diff --git a/doc/user/content/releases/v0.127.md b/doc/user/content/releases/v0.127.md new file mode 100644 index 0000000000000..9a88d6a25d92e --- /dev/null +++ b/doc/user/content/releases/v0.127.md @@ -0,0 +1,7 @@ +--- +title: "Materialize v0.127" +date: 2024-12-18 +released: false +_build: + render: never +--- diff --git a/doc/user/content/sql/comment-on.md b/doc/user/content/sql/comment-on.md index 17c92e78a0099..1f2d2923d176c 100644 --- a/doc/user/content/sql/comment-on.md +++ b/doc/user/content/sql/comment-on.md @@ -6,8 +6,6 @@ menu: parent: 'commands' --- -{{< public-preview />}} - `COMMENT ON ...` adds or updates the comment of an object. ## Syntax diff --git a/doc/user/content/sql/create-connection.md b/doc/user/content/sql/create-connection.md index f42705dbef693..b4acc4089deb3 100644 --- a/doc/user/content/sql/create-connection.md +++ b/doc/user/content/sql/create-connection.md @@ -625,8 +625,6 @@ CREATE CONNECTION csr_ssh TO CONFLUENT SCHEMA REGISTRY ( ### MySQL -{{< public-preview />}} - A MySQL connection establishes a link to a [MySQL] server. You can use MySQL connections to create [sources](/sql/create-source/mysql). diff --git a/doc/user/content/sql/create-source/mysql.md b/doc/user/content/sql/create-source/mysql.md index ca98a60f0e570..bd089132246cd 100644 --- a/doc/user/content/sql/create-source/mysql.md +++ b/doc/user/content/sql/create-source/mysql.md @@ -10,8 +10,6 @@ menu: weight: 20 --- -{{< public-preview />}} - {{% create-source/intro %}} Materialize supports MySQL (5.7+) as a real-time data source. To connect to a MySQL database, you first need to tweak its configuration to enable diff --git a/doc/user/content/sql/system-catalog/mz_internal.md b/doc/user/content/sql/system-catalog/mz_internal.md index 1d309845e539a..8c8bc7e86f3b8 100644 --- a/doc/user/content/sql/system-catalog/mz_internal.md +++ b/doc/user/content/sql/system-catalog/mz_internal.md @@ -68,6 +68,7 @@ granted the [`mz_monitor` role](/manage/access-control/manage-roles#builtin-role | `finished_at` | [`timestamp with time zone`] | The wall-clock time at which the statement finished executing. | | `finished_status` | [`text`] | The final status of the statement (e.g., `success`, `canceled`, `error`, or `aborted`). `aborted` means that Materialize exited before the statement finished executing. | | `error_message` | [`text`] | The error message, if the statement failed. | +| `result_size` | [`bigint`] | The size in bytes of the result, for statements that return rows. | | `rows_returned` | [`bigint`] | The number of rows returned, for statements that return rows. | | `execution_strategy` | [`text`] | For `SELECT` queries, the strategy for executing the query. `constant` means computed in the control plane without the involvement of a cluster, `fast-path` means read by a cluster directly from an in-memory index, and `standard` means computed by a temporary dataflow. | | `transaction_id` | [`uint8`] | The ID of the transaction that the statement was part of. Note that transaction IDs are only unique per session. | @@ -288,14 +289,14 @@ The `mz_pending_cluster_replicas` table lists the replicas that were created dur ## `mz_comments` -The `mz_comments` table stores optional comments (descriptions) for objects in the database. +The `mz_comments` table stores optional comments (i.e., descriptions) for objects in the database. | Field | Type | Meaning | | -------------- |-------------| -------- | | `id` | [`text`] | The ID of the object. Corresponds to [`mz_objects.id`](../mz_catalog/#mz_objects). | | `object_type` | [`text`] | The type of object the comment is associated with. | -| `object_sub_id`| [`integer`] | For a comment on a column of a relation, this is the column number. For all other object types this column is `NULL`. | +| `object_sub_id`| [`integer`] | For a comment on a column of a relation, the column number. `NULL` for other object types. | | `comment` | [`text`] | The comment itself. | ## `mz_compute_dependencies` @@ -420,7 +421,7 @@ usage. For example: the the reference documentation for [query optimization](/transform-data/optimization/#indexes) instead. - If a view is depended on by multiple objects that use very selective filters, - or a multiple projections that can be pushed into or even beyond the view, + or multiple projections that can be pushed into or even beyond the view, adding an index may increase resource usage. - If an index has been created to [enable delta joins](/transform-data/optimization/#optimize-multi-way-joins-with-delta-joins), removing it may lead to lower memory utilization, but the delta join @@ -1176,6 +1177,7 @@ and cannot be changed by users), the latter is used instead. | `finished_at` | [`timestamp with time zone`] | The time at which execution ended. | | `finished_status` | [`text`] | `'success'`, `'error'`, `'canceled'`, or `'aborted'`. `'aborted'` means that the database restarted (e.g., due to a crash or planned maintenance) before the query finished. | | `error_message` | [`text`] | The error returned when executing the statement, or `NULL` if it was successful, canceled or aborted. | +| `result_size` | [`bigint`] | The size in bytes of the result, for statements that return rows. | | `rows_returned` | [`int8`] | The number of rows returned by the statement, if it finished successfully and was of a kind of statement that can return rows, or `NULL` otherwise. | | `execution_strategy` | [`text`] | `'standard'`, `'fast-path'` `'constant'`, or `NULL`. `'standard'` means a dataflow was built on a cluster to compute the result. `'fast-path'` means a cluster read the result from an existing arrangement. `'constant'` means the result was computed in the serving layer, without involving a cluster. | --> diff --git a/doc/user/content/sql/system-catalog/mz_introspection.md b/doc/user/content/sql/system-catalog/mz_introspection.md index ade7e4602a985..46836ff521e16 100644 --- a/doc/user/content/sql/system-catalog/mz_introspection.md +++ b/doc/user/content/sql/system-catalog/mz_introspection.md @@ -222,6 +222,19 @@ The `mz_dataflow_channel_operators` view associates [dataflow] channels with the +## `mz_dataflow_global_ids` + +The `mz_dataflow_global_ids` view associates [dataflow] ids with global ids (ids of the form `u8` or `t5`). + + + +| Field | Type | Meaning | +|------------- | ------- | -------- | +| `id` | [`uint8`] | The dataflow ID. | +| `global_id` | [`text`] | A global ID associated with that dataflow. | + + + ## `mz_dataflow_operators` The `mz_dataflow_operators` view describes the [dataflow] operators in the system. @@ -292,6 +305,29 @@ through a hierarchical scheme for either aggregation or Top K computations. | `savings` | [`numeric`] | A conservative estimate of the amount of memory in bytes to be saved by applying the hint. | | `hint` | [`double precision`] | The hint value that will eliminate `to_cut` levels from the region's hierarchy. | +## `mz_lir_mapping` + +The `mz_lir_mapping` view describes the low-level internal representation (LIR) plan that corresponds to global ids. +LIR is a higher-level representation than dataflows; this view is used for profiling and debugging indices and materialized views. +Note that LIR is not a stable interface and may change at any time. +In particular, you should not attempt to parse `operator` descriptions. +LIR nodes are implemented by zero or more dataflow operators with sequential ids. +We use the range `[operator_id_start, operator_id_end)` to record this information. +If an LIR node was implemented without any dataflow operators, `operator_id_start` will be equal to `operator_id_end`. + + +| Field | Type | Meaning +| --------- | -------- | ----------- +| global_id | [`text`] | The global ID. +| lir_id | [`uint8`] | The LIR node ID. +| operator | [`text`] | The LIR operator, in the format `OperatorName INPUTS [OPTIONS]`. +| parent_lir_id | [`uint8`] | The parent of this LIR node. May be `NULL`. +| nesting | [`uint2`] | The nesting level of this LIR node. +| operator_id_start | [`uint8`] | The first dataflow operator ID implementing this LIR operator (inclusive). +| operator_id_end | [`uint8`] | The first dataflow operator ID _after_ this LIR operator (exclusive). + + + ## `mz_message_counts` The `mz_message_counts` view describes the messages and message batches sent and received over the [dataflow] channels in the system. diff --git a/doc/user/data/sql_funcs.yml b/doc/user/data/sql_funcs.yml index 28083fa3044cf..321d6f52a1907 100644 --- a/doc/user/data/sql_funcs.yml +++ b/doc/user/data/sql_funcs.yml @@ -37,7 +37,7 @@ - signature: 'bool_or(x: T) -> T' description: _NULL_ if all values of `x` are _NULL_, otherwise true if any values of `x` are true, otherwise false. - - signature: 'count(x: T) -> int' + - signature: 'count(x: T) -> bigint' description: Number of non-_NULL_ inputs. - signature: jsonb_agg(expression) -> jsonb diff --git a/doc/user/layouts/partials/banner.html b/doc/user/layouts/partials/banner.html index cdd43268a68bd..9ed5997f1ad67 100644 --- a/doc/user/layouts/partials/banner.html +++ b/doc/user/layouts/partials/banner.html @@ -4,7 +4,7 @@ {{ if .Site.Params.bannerLink }} {{ end }} - {{ .Site.Params.bannerMessage }} + {{ .Site.Params.bannerMessage | markdownify }} {{ if .Site.Params.bannerLink}} {{ end }} diff --git a/doc/user/layouts/partials/toc.html b/doc/user/layouts/partials/toc.html index 07ad5a0ea17c9..629f5ef996d09 100644 --- a/doc/user/layouts/partials/toc.html +++ b/doc/user/layouts/partials/toc.html @@ -59,7 +59,7 @@

On this page

const headings = $(".content h2, .content h3"); let offsets = headings.toArray().map((h) => ({ id: h.id, - offset: h.offsetTop - 65, // change to 75 if banner is displayed + offset: h.offsetTop - 75, // 75 if banner is displayed, 65 otherwise })); const cutoff = $(document).height() - $(window).height() - SLOP; const firstBad = offsets.findIndex((o) => o.offset > cutoff); diff --git a/misc/completions/bash/_scratch b/misc/completions/bash/_scratch index 435f56ab678c4..012b7bdfef9be 100644 --- a/misc/completions/bash/_scratch +++ b/misc/completions/bash/_scratch @@ -71,7 +71,7 @@ _shtab_replace_nonword() { # set default values (called for the initial parser & any subparsers) _set_parser_defaults() { local subparsers_var="${prefix}_subparsers[@]" - sub_parsers=${!subparsers_var} + sub_parsers=${!subparsers_var-} local current_option_strings_var="${prefix}_option_strings[@]" current_option_strings=${!current_option_strings_var} @@ -88,19 +88,19 @@ _set_new_action() { current_action="${prefix}_$(_shtab_replace_nonword $1)" local current_action_compgen_var=${current_action}_COMPGEN - current_action_compgen="${!current_action_compgen_var}" + current_action_compgen="${!current_action_compgen_var-}" local current_action_choices_var="${current_action}_choices[@]" - current_action_choices="${!current_action_choices_var}" + current_action_choices="${!current_action_choices_var-}" local current_action_nargs_var="${current_action}_nargs" - if [ -n "${!current_action_nargs_var}" ]; then + if [ -n "${!current_action_nargs_var-}" ]; then current_action_nargs="${!current_action_nargs_var}" else current_action_nargs=1 fi - current_action_args_start_index=$(( $word_index + 1 )) + current_action_args_start_index=$(( $word_index + 1 - $pos_only )) current_action_is_positional=$2 } @@ -114,10 +114,20 @@ _set_new_action() { # ${!x} -> ${hello} -> "world" _shtab_scratch() { local completing_word="${COMP_WORDS[COMP_CWORD]}" + local completed_positional_actions + local current_action + local current_action_args_start_index + local current_action_choices + local current_action_compgen + local current_action_is_positional + local current_action_nargs + local current_option_strings + local sub_parsers COMPREPLY=() - prefix=_shtab_scratch - word_index=0 + local prefix=_shtab_scratch + local word_index=0 + local pos_only=0 # "--" delimeter not encountered yet _set_parser_defaults word_index=1 @@ -126,26 +136,30 @@ _shtab_scratch() { while [ $word_index -ne $COMP_CWORD ]; do local this_word="${COMP_WORDS[$word_index]}" - if [[ -n $sub_parsers && " ${sub_parsers[@]} " =~ " ${this_word} " ]]; then - # valid subcommand: add it to the prefix & reset the current action - prefix="${prefix}_$(_shtab_replace_nonword $this_word)" - _set_parser_defaults - fi - - if [[ " ${current_option_strings[@]} " =~ " ${this_word} " ]]; then - # a new action should be acquired (due to recognised option string or - # no more input expected from current action); - # the next positional action can fill in here - _set_new_action $this_word false - fi - - if [[ "$current_action_nargs" != "*" ]] && \ - [[ "$current_action_nargs" != "+" ]] && \ - [[ "$current_action_nargs" != *"..." ]] && \ - (( $word_index + 1 - $current_action_args_start_index >= \ - $current_action_nargs )); then - $current_action_is_positional && let "completed_positional_actions += 1" - _set_new_action "pos_${completed_positional_actions}" true + if [[ $pos_only = 1 || " $this_word " != " -- " ]]; then + if [[ -n $sub_parsers && " ${sub_parsers[@]} " == *" ${this_word} "* ]]; then + # valid subcommand: add it to the prefix & reset the current action + prefix="${prefix}_$(_shtab_replace_nonword $this_word)" + _set_parser_defaults + fi + + if [[ " ${current_option_strings[@]} " == *" ${this_word} "* ]]; then + # a new action should be acquired (due to recognised option string or + # no more input expected from current action); + # the next positional action can fill in here + _set_new_action $this_word false + fi + + if [[ "$current_action_nargs" != "*" ]] && \ + [[ "$current_action_nargs" != "+" ]] && \ + [[ "$current_action_nargs" != *"..." ]] && \ + (( $word_index + 1 - $current_action_args_start_index - $pos_only >= \ + $current_action_nargs )); then + $current_action_is_positional && let "completed_positional_actions += 1" + _set_new_action "pos_${completed_positional_actions}" true + fi + else + pos_only=1 # "--" delimeter encountered fi let "word_index+=1" @@ -153,7 +167,7 @@ _shtab_scratch() { # Generate the completions - if [[ "${completing_word}" == -* ]]; then + if [[ $pos_only = 0 && "${completing_word}" == -* ]]; then # optional argument started: use option strings COMPREPLY=( $(compgen -W "${current_option_strings[*]}" -- "${completing_word}") ) else diff --git a/misc/completions/zsh/_scratch b/misc/completions/zsh/_scratch index c61b1042f0b78..2e113ae62166f 100644 --- a/misc/completions/zsh/_scratch +++ b/misc/completions/zsh/_scratch @@ -51,7 +51,7 @@ _shtab_scratch_destroy_options=( _shtab_scratch_forward_options=( "(- : *)"{-h,--help}"[show this help message and exit]" - ":The ID of the instance to connect to:" + ":The ID of the instance to connect to, or \'mine\' to specify your only live instance:" "(*)::The remote ports to forward locally:" ) @@ -69,17 +69,17 @@ _shtab_scratch_mine_options=( _shtab_scratch_push_options=( "(- : *)"{-h,--help}"[show this help message and exit]" "--rev[The git rev to checkout]:rev:" - ":The ID of the instance to connect to:" + ":The ID of the instance to connect to, or \'mine\' to specify your only live instance:" ) _shtab_scratch_sftp_options=( "(- : *)"{-h,--help}"[show this help message and exit]" - ":The ID of the instance to connect to:" + ":The ID of the instance to connect to, or \'mine\' to specify your only live instance:" ) _shtab_scratch_ssh_options=( "(- : *)"{-h,--help}"[show this help message and exit]" - ":The ID of the instance to connect to:" + ":The ID of the instance to connect to, or \'mine\' to specify your only live instance:" "(*)::The command to run via SSH, if any:" ) @@ -90,7 +90,7 @@ _shtab_scratch() { if ((${_shtab_scratch_options[(I)${(q)one_or_more}*]} + ${_shtab_scratch_options[(I)${(q)remainder}*]} == 0)); then # noqa: E501 _shtab_scratch_options+=(': :_shtab_scratch_commands' '*::: :->scratch') fi - _arguments -C $_shtab_scratch_options + _arguments -C -s $_shtab_scratch_options case $state in scratch) @@ -98,15 +98,15 @@ _shtab_scratch() { (( CURRENT += 1 )) curcontext="${curcontext%:*:*}:_shtab_scratch-$line[1]:" case $line[1] in - completion) _arguments -C $_shtab_scratch_completion_options ;; - create) _arguments -C $_shtab_scratch_create_options ;; - destroy) _arguments -C $_shtab_scratch_destroy_options ;; - forward) _arguments -C $_shtab_scratch_forward_options ;; - login) _arguments -C $_shtab_scratch_login_options ;; - mine) _arguments -C $_shtab_scratch_mine_options ;; - push) _arguments -C $_shtab_scratch_push_options ;; - sftp) _arguments -C $_shtab_scratch_sftp_options ;; - ssh) _arguments -C $_shtab_scratch_ssh_options ;; + completion) _arguments -C -s $_shtab_scratch_completion_options ;; + create) _arguments -C -s $_shtab_scratch_create_options ;; + destroy) _arguments -C -s $_shtab_scratch_destroy_options ;; + forward) _arguments -C -s $_shtab_scratch_forward_options ;; + login) _arguments -C -s $_shtab_scratch_login_options ;; + mine) _arguments -C -s $_shtab_scratch_mine_options ;; + push) _arguments -C -s $_shtab_scratch_push_options ;; + sftp) _arguments -C -s $_shtab_scratch_sftp_options ;; + ssh) _arguments -C -s $_shtab_scratch_ssh_options ;; esac esac } @@ -114,4 +114,12 @@ _shtab_scratch() { typeset -A opt_args -_shtab_scratch "$@" + +if [[ $zsh_eval_context[-1] == eval ]]; then + # eval/source/. command, register function for later + compdef _shtab_scratch -N scratch +else + # autoload from fpath, call function directly + _shtab_scratch "$@" +fi + diff --git a/misc/helm-charts/operator/Chart.yaml b/misc/helm-charts/operator/Chart.yaml index bbfd5c18970b3..1786b350c83b1 100644 --- a/misc/helm-charts/operator/Chart.yaml +++ b/misc/helm-charts/operator/Chart.yaml @@ -1,4 +1,3 @@ ---- # Copyright Materialize, Inc. and contributors. All rights reserved. # # Use of this software is governed by the Business Source License @@ -12,7 +11,7 @@ apiVersion: v2 name: materialize-operator description: Materialize Kubernetes Operator Helm Chart type: application -version: 25.1.0-beta.1 -appVersion: v0.125.2 +version: v25.1.0-beta.1 +appVersion: v0.127.0-dev.0 icon: https://materialize.com/favicon.ico home: https://materialize.com diff --git a/misc/helm-charts/operator/README.md b/misc/helm-charts/operator/README.md index fcfa5077213eb..01e4b903e465b 100644 --- a/misc/helm-charts/operator/README.md +++ b/misc/helm-charts/operator/README.md @@ -1,6 +1,6 @@ # Materialize Kubernetes Operator Helm Chart -![Version: 25.1.0-beta.1](https://img.shields.io/badge/Version-25.1.0--beta.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.125.2](https://img.shields.io/badge/AppVersion-v0.125.2-informational?style=flat-square) +![Version: v25.1.0-beta.1](https://img.shields.io/badge/Version-v25.1.0--beta.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.127.0-dev.0](https://img.shields.io/badge/AppVersion-v0.127.0--dev.0-informational?style=flat-square) Materialize Kubernetes Operator Helm Chart @@ -118,9 +118,10 @@ The following table lists the configurable parameters of the Materialize operato | `networkPolicies.ingress.cidrs[0]` | | ``"0.0.0.0/0"`` | | `networkPolicies.ingress.enabled` | | ``false`` | | `networkPolicies.internal.enabled` | | ``false`` | -| `observability.enabled` | | ``false`` | +| `observability.enabled` | | ``true`` | | `observability.podMetrics.enabled` | | ``false`` | -| `observability.prometheus.enabled` | | ``false`` | +| `observability.prometheus.scrapeAnnotations.enabled` | | ``true`` | +| `operator.args.enableInternalStatementLogging` | | ``true`` | | `operator.args.startupLogFilter` | | ``"INFO,mz_orchestratord=TRACE"`` | | `operator.cloudProvider.providers.aws.accountID` | | ``""`` | | `operator.cloudProvider.providers.aws.enabled` | | ``false`` | @@ -252,12 +253,13 @@ The following table lists the configurable parameters of the Materialize operato | `operator.features.createConsole` | | ``true`` | | `operator.image.pullPolicy` | | ``"IfNotPresent"`` | | `operator.image.repository` | | ``"materialize/orchestratord"`` | -| `operator.image.tag` | | ``"v0.125.2"`` | +| `operator.image.tag` | | ``"v0.127.0-dev.0"`` | | `operator.nodeSelector` | | ``{}`` | | `operator.resources.limits.memory` | | ``"512Mi"`` | | `operator.resources.requests.cpu` | | ``"100m"`` | | `operator.resources.requests.memory` | | ``"512Mi"`` | | `rbac.create` | | ``true`` | +| `rbac.enabled` | | ``true`` | | `serviceAccount.create` | | ``true`` | | `serviceAccount.name` | | ``"orchestratord"`` | | `storage.storageClass.allowVolumeExpansion` | | ``false`` | @@ -269,12 +271,16 @@ The following table lists the configurable parameters of the Materialize operato | `storage.storageClass.provisioner` | | ``""`` | | `storage.storageClass.reclaimPolicy` | | ``"Delete"`` | | `storage.storageClass.volumeBindingMode` | | ``"WaitForFirstConsumer"`` | +| `telemetry.enabled` | | ``true`` | +| `telemetry.segmentApiKey` | | ``"hMWi3sZ17KFMjn2sPWo9UJGpOQqiba4A"`` | +| `telemetry.segmentClientSide` | | ``true`` | +| `tls.defaultCertificateSpecs` | | ``{}`` | Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example: ```shell helm install my-materialize-operator \ - --set operator.image.tag=v0.125.2 \ + --set operator.image.tag=v0.127.0-dev.0 \ materialize/materialize-operator ``` @@ -309,7 +315,7 @@ metadata: name: 12345678-1234-1234-1234-123456789012 namespace: materialize-environment spec: - environmentdImageRef: materialize/environmentd:v0.125.2 + environmentdImageRef: materialize/environmentd:v0.127.0-dev.0 backendSecretName: materialize-backend environmentdResourceRequirements: limits: @@ -392,7 +398,7 @@ Or check the `Chart.yaml` file in the `misc/helm-charts/operator` directory: apiVersion: v2 name: materialize-operator # ... -version: 25.1.0-beta.1 +version: v25.1.0-beta.1 appVersion: v0.125.2 # Use this version for your Materialize instances ``` diff --git a/misc/helm-charts/operator/README.md.gotmpl b/misc/helm-charts/operator/README.md.gotmpl index 51903d14e4587..567c9fff6faad 100644 --- a/misc/helm-charts/operator/README.md.gotmpl +++ b/misc/helm-charts/operator/README.md.gotmpl @@ -245,7 +245,7 @@ Or check the `Chart.yaml` file in the `misc/helm-charts/operator` directory: apiVersion: v2 name: materialize-operator # ... -version: 25.1.0-beta.1 +version: v25.1.0-beta.1 appVersion: v0.125.2 # Use this version for your Materialize instances ``` diff --git a/misc/helm-charts/operator/templates/clusterrole.yaml b/misc/helm-charts/operator/templates/clusterrole.yaml index 3ef7aa9318206..531b74053e676 100644 --- a/misc/helm-charts/operator/templates/clusterrole.yaml +++ b/misc/helm-charts/operator/templates/clusterrole.yaml @@ -109,5 +109,16 @@ rules: verbs: - get - list +- apiGroups: ["cert-manager.io"] + resources: + - certificates + verbs: + - create + - update + - patch + - delete + - get + - list + - watch {{- end }} diff --git a/misc/helm-charts/operator/templates/deployment.yaml b/misc/helm-charts/operator/templates/deployment.yaml index b39701d873abc..8ffc32e196663 100644 --- a/misc/helm-charts/operator/templates/deployment.yaml +++ b/misc/helm-charts/operator/templates/deployment.yaml @@ -42,6 +42,9 @@ spec: {{- range $key, $value := include "materialize-operator.selectorLabels" . | fromYaml }} - "--orchestratord-pod-selector-labels={{ $key }}={{ $value }}" {{- end }} + {{- if .Values.operator.args.enableInternalStatementLogging }} + - "--enable-internal-statement-logging" + {{- end }} {{/* AWS Configuration */}} {{- if eq .Values.operator.cloudProvider.type "aws" }} @@ -70,6 +73,11 @@ spec: - "--console-image-tag-map={{ $key }}={{ $value }}" {{- end }} + {{/* Authentication */}} + {{- if not .Values.rbac.enabled }} + - "--disable-authentication" + {{- end }} + {{/* Cluster Configuration */}} {{- if .Values.operator.clusters }} {{- if .Values.operator.clusters.sizes }} @@ -123,12 +131,24 @@ spec: {{- end }} {{- end }} {{- end }} - + {{- if .Values.tls.defaultCertificateSpecs }} + - '--default-certificate-specs={{ toJson .Values.tls.defaultCertificateSpecs }}' + {{- end }} {{/* Observability */}} {{- if .Values.observability.enabled }} {{- if .Values.observability.podMetrics.enabled }} - "--collect-pod-metrics" {{- end }} + {{- if .Values.observability.prometheus.scrapeAnnotations.enabled }} + - "--enable-prometheus-scrape-annotations" + {{- end }} + {{- end }} + {{/* Telemetry */}} + {{- if .Values.telemetry.enabled }} + - "--segment-api-key={{ .Values.telemetry.segmentApiKey }}" + {{- if .Values.telemetry.segmentClientSide }} + - "--segment-client-side" + {{- end }} {{- end }} resources: {{- toYaml .Values.operator.resources | nindent 10 }} diff --git a/misc/helm-charts/operator/tests/deployment_test.yaml b/misc/helm-charts/operator/tests/deployment_test.yaml index a244fd4dac9e6..4e794687d3be0 100644 --- a/misc/helm-charts/operator/tests/deployment_test.yaml +++ b/misc/helm-charts/operator/tests/deployment_test.yaml @@ -9,155 +9,155 @@ suite: test deployment templates: - - deployment.yaml +- deployment.yaml tests: - - it: should create a deployment - asserts: - - isKind: - of: Deployment - - equal: - path: spec.template.spec.containers[0].image - value: materialize/orchestratord:v0.125.2 - - equal: - path: spec.template.spec.containers[0].imagePullPolicy - value: IfNotPresent - - equal: - path: spec.template.spec.serviceAccountName - value: orchestratord +- it: should create a deployment + asserts: + - isKind: + of: Deployment + - equal: + path: spec.template.spec.containers[0].image + value: materialize/orchestratord:v0.127.0-dev.0 + - equal: + path: spec.template.spec.containers[0].imagePullPolicy + value: IfNotPresent + - equal: + path: spec.template.spec.serviceAccountName + value: orchestratord - - it: should set ephemeral volume class when storage class is configured - set: - storage.storageClass.name: "my-storage-class" - asserts: - - contains: - path: spec.template.spec.containers[0].args - content: "--ephemeral-volume-class=my-storage-class" +- it: should set ephemeral volume class when storage class is configured + set: + storage.storageClass.name: "my-storage-class" + asserts: + - contains: + path: spec.template.spec.containers[0].args + content: "--ephemeral-volume-class=my-storage-class" - - it: should not set ephemeral volume class when storage class is not configured - set: - storage.storageClass.name: "" - asserts: - - notContains: - path: spec.template.spec.containers[0].args - content: "--ephemeral-volume-class" +- it: should not set ephemeral volume class when storage class is not configured + set: + storage.storageClass.name: "" + asserts: + - notContains: + path: spec.template.spec.containers[0].args + content: "--ephemeral-volume-class" - - it: should set correct base arguments - asserts: - - contains: - path: spec.template.spec.containers[0].args - content: "--startup-log-filter=INFO,mz_orchestratord=TRACE" +- it: should set correct base arguments + asserts: + - contains: + path: spec.template.spec.containers[0].args + content: "--startup-log-filter=INFO,mz_orchestratord=TRACE" - - it: should set resources correctly - asserts: - - equal: - path: spec.template.spec.containers[0].resources.requests.cpu - value: 100m - - equal: - path: spec.template.spec.containers[0].resources.requests.memory - value: 512Mi - - equal: - path: spec.template.spec.containers[0].resources.limits.memory - value: 512Mi +- it: should set resources correctly + asserts: + - equal: + path: spec.template.spec.containers[0].resources.requests.cpu + value: 100m + - equal: + path: spec.template.spec.containers[0].resources.requests.memory + value: 512Mi + - equal: + path: spec.template.spec.containers[0].resources.limits.memory + value: 512Mi - - it: should set disk limit to 0 when no storage class is configured - set: - storage.storageClass.name: "" - asserts: - - matchRegex: - path: spec.template.spec.containers[0].args[9] # Index of the environmentd-cluster-replica-sizes argument - pattern: disk_limit":"0" - - matchRegex: - path: spec.template.spec.containers[0].args[9] - pattern: is_cc":true +- it: should set disk limit to 0 when no storage class is configured + set: + storage.storageClass.name: "" + asserts: + - matchRegex: + path: spec.template.spec.containers[0].args[10] # Index of the environmentd-cluster-replica-sizes argument + pattern: disk_limit":"0" + - matchRegex: + path: spec.template.spec.containers[0].args[10] + pattern: is_cc":true - - it: should have a cluster with disk limit to 1552MiB when storage class is configured - set: - storage.storageClass.name: "my-storage-class" - asserts: - - matchRegex: - path: spec.template.spec.containers[0].args[9] - pattern: disk_limit":"1552MiB" - - matchRegex: - path: spec.template.spec.containers[0].args[9] - pattern: is_cc":true +- it: should have a cluster with disk limit to 1552MiB when storage class is configured + set: + storage.storageClass.name: "my-storage-class" + asserts: + - matchRegex: + path: spec.template.spec.containers[0].args[10] + pattern: disk_limit":"1552MiB" + - matchRegex: + path: spec.template.spec.containers[0].args[10] + pattern: is_cc":true - - it: should configure for AWS provider correctly - set: - operator.cloudProvider.type: "aws" - operator.cloudProvider.region: "us-east-1" - operator.cloudProvider.providers.aws: - enabled: true - accountID: "123456789012" - iam: - roles: - environment: "arn:aws:iam::123456789012:role/env-role" - connection: "arn:aws:iam::123456789012:role/conn-role" - asserts: - - contains: - path: spec.template.spec.containers[0].args - content: "--cloud-provider=aws" - - contains: - path: spec.template.spec.containers[0].args - content: "--region=us-east-1" - - contains: - path: spec.template.spec.containers[0].args - content: "--aws-account-id=123456789012" - - contains: - path: spec.template.spec.containers[0].args - content: "--environmentd-iam-role-arn=arn:aws:iam::123456789012:role/env-role" - - contains: - path: spec.template.spec.containers[0].args - content: "--environmentd-connection-role-arn=arn:aws:iam::123456789012:role/conn-role" +- it: should configure for AWS provider correctly + set: + operator.cloudProvider.type: "aws" + operator.cloudProvider.region: "us-east-1" + operator.cloudProvider.providers.aws: + enabled: true + accountID: "123456789012" + iam: + roles: + environment: "arn:aws:iam::123456789012:role/env-role" + connection: "arn:aws:iam::123456789012:role/conn-role" + asserts: + - contains: + path: spec.template.spec.containers[0].args + content: "--cloud-provider=aws" + - contains: + path: spec.template.spec.containers[0].args + content: "--region=us-east-1" + - contains: + path: spec.template.spec.containers[0].args + content: "--aws-account-id=123456789012" + - contains: + path: spec.template.spec.containers[0].args + content: "--environmentd-iam-role-arn=arn:aws:iam::123456789012:role/env-role" + - contains: + path: spec.template.spec.containers[0].args + content: "--environmentd-connection-role-arn=arn:aws:iam::123456789012:role/conn-role" - - it: should configure for generic provider correctly - set: - operator.cloudProvider.type: "generic" - operator.cloudProvider.region: "local" - asserts: - - contains: - path: spec.template.spec.containers[0].args - content: "--cloud-provider=generic" - - contains: - path: spec.template.spec.containers[0].args - content: "--region=local" - - notContains: - path: spec.template.spec.containers[0].args - content: "--aws-account-id" +- it: should configure for generic provider correctly + set: + operator.cloudProvider.type: "generic" + operator.cloudProvider.region: "local" + asserts: + - contains: + path: spec.template.spec.containers[0].args + content: "--cloud-provider=generic" + - contains: + path: spec.template.spec.containers[0].args + content: "--region=local" + - notContains: + path: spec.template.spec.containers[0].args + content: "--aws-account-id" # Feature Flag Tests - - it: should enable balancer creation when configured - set: - operator.features.createBalancers: true - asserts: - - contains: - path: spec.template.spec.containers[0].args - content: "--create-balancers" +- it: should enable balancer creation when configured + set: + operator.features.createBalancers: true + asserts: + - contains: + path: spec.template.spec.containers[0].args + content: "--create-balancers" - - it: should not enable balancer creation when disabled - set: - operator.features.createBalancers: false - asserts: - - notContains: - path: spec.template.spec.containers[0].args - content: "--create-balancers" +- it: should not enable balancer creation when disabled + set: + operator.features.createBalancers: false + asserts: + - notContains: + path: spec.template.spec.containers[0].args + content: "--create-balancers" - - it: should enable console creation when configured - set: - operator.features.createConsole: true - asserts: - - contains: - path: spec.template.spec.containers[0].args - content: "--create-console" +- it: should enable console creation when configured + set: + operator.features.createConsole: true + asserts: + - contains: + path: spec.template.spec.containers[0].args + content: "--create-console" - - it: should configure console image tag map override correctly - set: - operator.features.consoleImageTagMapOverride: - "v0.125.0": "25.1.0" - "v0.126.0": "25.2.0" - asserts: - - contains: - path: spec.template.spec.containers[0].args - content: "--console-image-tag-map=v0.125.0=25.1.0" - - contains: - path: spec.template.spec.containers[0].args - content: "--console-image-tag-map=v0.126.0=25.2.0" +- it: should configure console image tag map override correctly + set: + operator.features.consoleImageTagMapOverride: + "v0.125.0": "25.1.0" + "v0.126.0": "25.2.0" + asserts: + - contains: + path: spec.template.spec.containers[0].args + content: "--console-image-tag-map=v0.125.0=25.1.0" + - contains: + path: spec.template.spec.containers[0].args + content: "--console-image-tag-map=v0.126.0=25.2.0" diff --git a/misc/helm-charts/operator/values.yaml b/misc/helm-charts/operator/values.yaml index c97c34d05b4c5..145bc9b50e013 100644 --- a/misc/helm-charts/operator/values.yaml +++ b/misc/helm-charts/operator/values.yaml @@ -13,13 +13,14 @@ operator: # The Docker repository for the operator image repository: materialize/orchestratord # The tag/version of the operator image to be used - tag: v0.125.2 + tag: v0.127.0-dev.0 # Policy for pulling the image: "IfNotPresent" avoids unnecessary re-pulling of images pullPolicy: IfNotPresent args: # Log filtering settings for startup logs startupLogFilter: "INFO,mz_orchestratord=TRACE" + enableInternalStatementLogging: true features: # Flag to indicate whether to create balancerd pods for the environments @@ -214,6 +215,7 @@ clusterd: # RBAC (Role-Based Access Control) settings rbac: + enabled: true # Whether to create necessary RBAC roles and bindings create: true @@ -226,15 +228,21 @@ serviceAccount: # Observability settings (disabled in this case) observability: - enabled: false + enabled: true podMetrics: # Whether to enable the pod metrics scraper which populates the # Environment Overview Monitoring tab in the web console (requires # metrics-server to be installed) enabled: false prometheus: - # Whether to enable Prometheus integration for monitoring (disabled here) - enabled: false + scrapeAnnotations: + # Whether to annotate pods with common keys used for prometheus scraping. + enabled: true + +telemetry: + enabled: true + segmentApiKey: hMWi3sZ17KFMjn2sPWo9UJGpOQqiba4A + segmentClientSide: true # Network policies configuration networkPolicies: @@ -248,12 +256,31 @@ networkPolicies: ingress: enabled: false cidrs: - - 0.0.0.0/0 + - 0.0.0.0/0 # egress from Materialize pods to sources and sinks egress: enabled: false cidrs: - - 0.0.0.0/0 + - 0.0.0.0/0 + +tls: + defaultCertificateSpecs: {} + #balancerdExternal: + # dnsNames: + # - balancerd + # issuerRef: + # name: dns01 + # kind: ClusterIssuer + #consoleExternal: + # dnsNames: + # - console + # issuerRef: + # name: dns01 + # kind: ClusterIssuer + #internal: + # issuerRef: + # name: dns01 + # kind: ClusterIssuer # Namespace configuration namespace: diff --git a/misc/helm-charts/testing/environmentd.yaml b/misc/helm-charts/testing/environmentd.yaml index 431ae370a9dc9..7b1310f1d6e15 100644 --- a/misc/helm-charts/testing/environmentd.yaml +++ b/misc/helm-charts/testing/environmentd.yaml @@ -1,4 +1,3 @@ ---- # Source: materialize-environmentd/templates/secret.yaml # Copyright Materialize, Inc. and contributors. All rights reserved. # @@ -29,5 +28,21 @@ metadata: name: 12345678-1234-1234-1234-123456789012 namespace: materialize-environment spec: - environmentdImageRef: materialize/environmentd:v0.125.2 + environmentdImageRef: materialize/environmentd:v0.127.0-dev.0 backendSecretName: materialize-backend + #balancerdExternalCertificateSpec: + # dnsNames: + # - balancerd + # issuerRef: + # name: dns01 + # kind: ClusterIssuer + #consoleExternalCertificateSpec: + # dnsNames: + # - console + # issuerRef: + # name: dns01 + # kind: ClusterIssuer + #internalCertificateSpec: + # issuerRef: + # name: intermediate-ca + # kind: Issuer diff --git a/misc/python/materialize/buildkite_insights/costs/extract_stats.py b/misc/python/materialize/buildkite_insights/costs/extract_stats.py index d33b29c5885e3..ad457e7f46d68 100755 --- a/misc/python/materialize/buildkite_insights/costs/extract_stats.py +++ b/misc/python/materialize/buildkite_insights/costs/extract_stats.py @@ -65,9 +65,12 @@ "x86-64-4cpu-8gb": 0.0113, "x86-64-8cpu-16gb": 0.0273, "x86-64-16cpu-32gb": 0.0540, - "x86-64-16cpu-64gb": 0.1546, - "x86-64-32cpu-128gb": 0.3085, - "x86-64-48cpu-192gb": 0.4623, + "x86-64-dedi-2cpu-8gb": 0.0200, + "x86-64-dedi-4cpu-16gb": 0.0392, + "x86-64-dedi-8cpu-32gb": 0.0777, + "x86-64-dedi-16cpu-64gb": 0.1546, + "x86-64-dedi-32cpu-128gb": 0.3085, + "x86-64-dedi-48cpu-192gb": 0.4623, "x86-64": 0, # local experiments } diff --git a/misc/python/materialize/cli/ci_annotate_errors.py b/misc/python/materialize/cli/ci_annotate_errors.py index fdd7cd8986a6e..eee396590bff8 100644 --- a/misc/python/materialize/cli/ci_annotate_errors.py +++ b/misc/python/materialize/cli/ci_annotate_errors.py @@ -143,7 +143,7 @@ # Will print a separate panic line which will be handled and contains the relevant information (new style) | internal\ error:\ unexpected\ panic\ during\ query\ optimization # redpanda INFO logging - | larger\ sizes\ prevent\ running\ out\ of\ memory + | [Ll]arger\ sizes\ prevent\ running\ out\ of\ memory # Old versions won't support new parameters | (platform-checks|legacy-upgrade|upgrade-matrix|feature-benchmark)-materialized-.* \| .*cannot\ load\ unknown\ system\ parameter\ from\ catalog\ storage # Fencing warnings are OK in fencing/0dt tests diff --git a/misc/python/materialize/cli/scratch/forward.py b/misc/python/materialize/cli/scratch/forward.py index 8c23977ef9b12..327132b8fb3ed 100644 --- a/misc/python/materialize/cli/scratch/forward.py +++ b/misc/python/materialize/cli/scratch/forward.py @@ -9,21 +9,22 @@ import argparse -import boto3 - from materialize.cli.scratch import check_required_vars -from materialize.scratch import mssh +from materialize.scratch import get_instance, mssh def configure_parser(parser: argparse.ArgumentParser) -> None: check_required_vars() - parser.add_argument("instance", help="The ID of the instance to connect to") + parser.add_argument( + "instance", + help="The ID of the instance to connect to, or 'mine' to specify your only live instance", + ) parser.add_argument("ports", nargs="*", help="The remote ports to forward locally") def run(args: argparse.Namespace) -> None: - instance = boto3.resource("ec2").Instance(args.instance) + instance = get_instance(args.instance) ssh_args = [] for port in args.ports: ssh_args.extend(["-L", f"{port}:127.0.0.1:{port}"]) diff --git a/misc/python/materialize/cli/scratch/push.py b/misc/python/materialize/cli/scratch/push.py index 9cd4f236d8657..fbe3ded3544ab 100644 --- a/misc/python/materialize/cli/scratch/push.py +++ b/misc/python/materialize/cli/scratch/push.py @@ -9,20 +9,21 @@ import argparse -import boto3 - from materialize.cli.scratch import check_required_vars -from materialize.scratch import mkrepo +from materialize.scratch import get_instance, mkrepo def configure_parser(parser: argparse.ArgumentParser) -> None: check_required_vars() - parser.add_argument("instance", help="The ID of the instance to connect to") + parser.add_argument( + "instance", + help="The ID of the instance to connect to, or 'mine' to specify your only live instance", + ) parser.add_argument("--rev", help="The git rev to checkout", default="HEAD") def run(args: argparse.Namespace) -> None: - instance = boto3.resource("ec2").Instance(args.instance) + instance = get_instance(args.instance) mkrepo(instance, args.rev, init=False, force=True) diff --git a/misc/python/materialize/cli/scratch/sftp.py b/misc/python/materialize/cli/scratch/sftp.py index 7cb747b04d760..3c05fd96304bd 100644 --- a/misc/python/materialize/cli/scratch/sftp.py +++ b/misc/python/materialize/cli/scratch/sftp.py @@ -9,18 +9,19 @@ import argparse -import boto3 - from materialize.cli.scratch import check_required_vars -from materialize.scratch import msftp +from materialize.scratch import get_instance, msftp def configure_parser(parser: argparse.ArgumentParser) -> None: check_required_vars() - parser.add_argument("instance", help="The ID of the instance to connect to") + parser.add_argument( + "instance", + help="The ID of the instance to connect to, or 'mine' to specify your only live instance", + ) def run(args: argparse.Namespace) -> None: - instance = boto3.resource("ec2").Instance(args.instance) + instance = get_instance(args.instance) msftp(instance) diff --git a/misc/python/materialize/cli/scratch/ssh.py b/misc/python/materialize/cli/scratch/ssh.py index fa60512647f20..97d10b315dac2 100644 --- a/misc/python/materialize/cli/scratch/ssh.py +++ b/misc/python/materialize/cli/scratch/ssh.py @@ -9,21 +9,22 @@ import argparse -import boto3 - from materialize.cli.scratch import check_required_vars -from materialize.scratch import mssh +from materialize.scratch import get_instance, mssh def configure_parser(parser: argparse.ArgumentParser) -> None: check_required_vars() - parser.add_argument("instance", help="The ID of the instance to connect to") + parser.add_argument( + "instance", + help="The ID of the instance to connect to, or 'mine' to specify your only live instance", + ) parser.add_argument("command", nargs="*", help="The command to run via SSH, if any") def run(args: argparse.Namespace) -> None: # SSH will join together multiple arguments with spaces, so we don't lose # anything by doing the same. - instance = boto3.resource("ec2").Instance(args.instance) + instance = get_instance(args.instance) mssh(instance, " ".join(args.command)) diff --git a/misc/python/materialize/cloudtest/k8s/redpanda.py b/misc/python/materialize/cloudtest/k8s/redpanda.py index 013ccdc1765cf..621c446eae852 100644 --- a/misc/python/materialize/cloudtest/k8s/redpanda.py +++ b/misc/python/materialize/cloudtest/k8s/redpanda.py @@ -24,6 +24,7 @@ from materialize.cloudtest.k8s.api.k8s_deployment import K8sDeployment from materialize.cloudtest.k8s.api.k8s_resource import K8sResource from materialize.cloudtest.k8s.api.k8s_service import K8sService +from materialize.mzcompose.services.redpanda import REDPANDA_VERSION class RedpandaDeployment(K8sDeployment): @@ -31,7 +32,7 @@ def __init__(self, namespace: str, apply_node_selectors: bool) -> None: super().__init__(namespace) container = V1Container( name="redpanda", - image="vectorized/redpanda:v24.2.2", + image=f"redpandadata/redpanda:{REDPANDA_VERSION}", command=[ "/usr/bin/rpk", "redpanda", diff --git a/misc/python/materialize/feature_benchmark/scenarios/benchmark_main.py b/misc/python/materialize/feature_benchmark/scenarios/benchmark_main.py index c05691fd04bbf..5aceb2154dcc4 100644 --- a/misc/python/materialize/feature_benchmark/scenarios/benchmark_main.py +++ b/misc/python/materialize/feature_benchmark/scenarios/benchmark_main.py @@ -239,6 +239,10 @@ def init(self) -> Action: def benchmark(self) -> MeasurementSource: return Td( f""" +$ postgres-connect name=mz_system url=postgres://mz_system:materialize@${{testdrive.materialize-internal-sql-addr}} +$ postgres-execute connection=mz_system +ALTER SYSTEM SET max_result_size = 17179869184; + > DROP TABLE IF EXISTS t1; > CREATE TABLE t1 (f1 INTEGER) @@ -311,7 +315,10 @@ def benchmark(self) -> MeasurementSource: class InsertMultiRow(DML): - """Measure the time it takes for a single multi-row INSERT statement to return.""" + """Measure the time it takes for a single multi-row INSERT statement to return. + When `sequence_insert` calls `constant_optimizer`, it should be able to reach a constant. Otherwise, we run the full + logical optimizer, which makes this test show a regression. + """ SCALE = 4 # FATAL: request larger than 2.0 MB @@ -334,7 +341,7 @@ def benchmark(self) -> MeasurementSource: class Update(DML): """Measure the time it takes for an UPDATE statement to return to client""" - SCALE = 6 # TODO: Increase scale when database-issues#8766 is fixed + SCALE = 7 def init(self) -> list[Action]: return [ @@ -461,6 +468,10 @@ def init(self) -> Action: def benchmark(self) -> MeasurementSource: return Td( f""" +$ postgres-connect name=mz_system url=postgres://mz_system:materialize@${{testdrive.materialize-internal-sql-addr}} +$ postgres-execute connection=mz_system +ALTER SYSTEM SET max_result_size = 17179869184; + > DROP TABLE IF EXISTS t1; > CREATE TABLE t1 (f1 INTEGER) diff --git a/misc/python/materialize/feature_benchmark/scenarios/concurrency.py b/misc/python/materialize/feature_benchmark/scenarios/concurrency.py index e33e2030e17bb..5fc82ded8dbde 100644 --- a/misc/python/materialize/feature_benchmark/scenarios/concurrency.py +++ b/misc/python/materialize/feature_benchmark/scenarios/concurrency.py @@ -10,6 +10,7 @@ from materialize.feature_benchmark.action import Action, TdAction from materialize.feature_benchmark.measurement_source import MeasurementSource, Td from materialize.feature_benchmark.scenario import Scenario +from materialize.feature_benchmark.scenario_version import ScenarioVersion class Concurrency(Scenario): @@ -22,6 +23,9 @@ class ParallelIngestion(Concurrency): SOURCES = 10 FIXED_SCALE = True # Disk slowness in CRDB leading to CRDB going down + def version(self) -> ScenarioVersion: + return ScenarioVersion.create(1, 1, 0) + def shared(self) -> Action: return TdAction( self.schema() @@ -72,7 +76,7 @@ def benchmark(self) -> MeasurementSource: create_indexes = "\n".join( [ f""" -> CREATE DEFAULT INDEX ON s{s} +> CREATE DEFAULT INDEX ON s{s}_tbl """ for s in sources ] diff --git a/misc/python/materialize/mzcompose/__init__.py b/misc/python/materialize/mzcompose/__init__.py index 482068ce95e3b..dcc325ba862cf 100644 --- a/misc/python/materialize/mzcompose/__init__.py +++ b/misc/python/materialize/mzcompose/__init__.py @@ -146,6 +146,8 @@ def get_default_system_parameters( "persist_schema_register": ( "false" if version < MzVersion.parse_mz("v0.111.0-dev") else "true" ), + # 16 MiB - large enough to avoid a big perf hit, small enough to get more coverage... + "persist_blob_target_size": "16777216", "persist_schema_require": "true", "persist_stats_audit_percent": "100", "persist_txn_tables": "lazy", # removed, but keep value for older versions diff --git a/misc/python/materialize/mzcompose/service.py b/misc/python/materialize/mzcompose/service.py index ce937a221758d..416926be5542b 100644 --- a/misc/python/materialize/mzcompose/service.py +++ b/misc/python/materialize/mzcompose/service.py @@ -171,6 +171,9 @@ class ServiceConfig(TypedDict, total=False): stop_grace_period: str | None """Time to wait when stopping a container.""" + network_mode: str | None + """Network mode.""" + class Service: """A Docker Compose service in a `Composition`. diff --git a/misc/python/materialize/mzcompose/services/cockroach.py b/misc/python/materialize/mzcompose/services/cockroach.py index fdc9e866f8034..946ec16093d46 100644 --- a/misc/python/materialize/mzcompose/services/cockroach.py +++ b/misc/python/materialize/mzcompose/services/cockroach.py @@ -21,6 +21,7 @@ class Cockroach(Service): + # TODO: Bump version to >= v24.3.0 when https://github.com/cockroachdb/cockroach/issues/136678 is fixed DEFAULT_COCKROACH_TAG = "v24.2.0" def __init__( diff --git a/misc/python/materialize/mzcompose/services/redpanda.py b/misc/python/materialize/mzcompose/services/redpanda.py index 873d9818e9eb3..67c0859ae9a73 100644 --- a/misc/python/materialize/mzcompose/services/redpanda.py +++ b/misc/python/materialize/mzcompose/services/redpanda.py @@ -13,7 +13,7 @@ ServiceConfig, ) -REDPANDA_VERSION = "v24.2.7" +REDPANDA_VERSION = "v24.3.1" class Redpanda(Service): @@ -27,7 +27,7 @@ def __init__( ports: list[int] | None = None, ) -> None: if image is None: - image = f"vectorized/redpanda:{version}" + image = f"redpandadata/redpanda:{version}" if ports is None: ports = [9092, 8081] @@ -37,7 +37,7 @@ def __init__( aliases = ["kafka", "schema-registry"] # Most of these options are simply required when using Redpanda in Docker. - # See: https://vectorized.io/docs/quick-start-docker/#Single-command-for-a-1-node-cluster + # See: https://docs.redpanda.com/current/get-started/quick-start/#Single-command-for-a-1-node-cluster # The `enable_transactions` and `enable_idempotence` feature flags enable # features Materialize requires that are present by default in Apache Kafka # but not in Redpanda. diff --git a/misc/python/materialize/mzcompose/services/testdrive.py b/misc/python/materialize/mzcompose/services/testdrive.py index d6d6b36450527..1781ae8ae2fb6 100644 --- a/misc/python/materialize/mzcompose/services/testdrive.py +++ b/misc/python/materialize/mzcompose/services/testdrive.py @@ -15,6 +15,7 @@ from materialize.mzcompose import DEFAULT_MZ_VOLUMES, cluster_replica_size_map from materialize.mzcompose.service import ( Service, + ServiceConfig, ServiceDependency, ) from materialize.mzcompose.services.postgres import METADATA_STORE @@ -59,6 +60,7 @@ def __init__( metadata_store: str = METADATA_STORE, stop_grace_period: str = "120s", cluster_replica_size: dict[str, dict[str, Any]] | None = None, + network_mode: str | None = None, ) -> None: depends_graph: dict[str, ServiceDependency] = {} @@ -174,16 +176,20 @@ def __init__( entrypoint.extend(entrypoint_extra) + config: ServiceConfig = { + "depends_on": depends_graph, + "mzbuild": mzbuild, + "entrypoint": entrypoint, + "environment": environment, + "volumes": volumes, + "propagate_uid_gid": propagate_uid_gid, + "init": True, + "stop_grace_period": stop_grace_period, + } + if network_mode: + config["network_mode"] = network_mode + super().__init__( name=name, - config={ - "depends_on": depends_graph, - "mzbuild": mzbuild, - "entrypoint": entrypoint, - "environment": environment, - "volumes": volumes, - "propagate_uid_gid": propagate_uid_gid, - "init": True, - "stop_grace_period": stop_grace_period, - }, + config=config, ) diff --git a/misc/python/materialize/parallel_workload/action.py b/misc/python/materialize/parallel_workload/action.py index fc89c14e2ac25..d892875e46dbb 100644 --- a/misc/python/materialize/parallel_workload/action.py +++ b/misc/python/materialize/parallel_workload/action.py @@ -994,6 +994,10 @@ def __init__( BOOLEAN_FLAG_VALUES = ["TRUE", "FALSE"] self.flags_with_values: dict[str, list[str]] = dict() + self.flags_with_values["persist_blob_target_size"] = ( + # 1 MiB, 16 MiB, 128 MiB + ["1048576", "16777216", "134217728"] + ) for flag in ["catalog", "source", "snapshot", "txn"]: self.flags_with_values[f"persist_use_critical_since_{flag}"] = ( BOOLEAN_FLAG_VALUES diff --git a/misc/python/materialize/scratch.py b/misc/python/materialize/scratch.py index 5cde3e3d18459..5ce8f8ef0c8fe 100644 --- a/misc/python/materialize/scratch.py +++ b/misc/python/materialize/scratch.py @@ -24,6 +24,7 @@ from mypy_boto3_ec2.literals import InstanceTypeType from mypy_boto3_ec2.service_resource import Instance from mypy_boto3_ec2.type_defs import ( + FilterTypeDef, InstanceNetworkInterfaceSpecificationTypeDef, InstanceTypeDef, RunInstancesRequestRequestTypeDef, @@ -367,6 +368,32 @@ def whoami() -> str: return boto3.client("sts").get_caller_identity()["UserId"].split(":")[1] +def get_instance(name: str) -> Instance: + """ + Get an instance by instance id. The special name 'mine' resolves to a + unique running owned instance, if there is one; otherwise the name is + assumed to be an instance id. + :param name: The instance id or the special case 'mine'. + :return: The instance to which the name refers. + """ + if name == "mine": + filters: list[FilterTypeDef] = [ + {"Name": "tag:LaunchedBy", "Values": [whoami()]}, + {"Name": "instance-state-name", "Values": ["pending", "running"]}, + ] + instances = [i for i in boto3.resource("ec2").instances.filter(Filters=filters)] + if not instances: + raise RuntimeError("can't understand 'mine': no owned instance?") + if len(instances) > 1: + raise RuntimeError( + f"can't understand 'mine': too many owned instances ({', '.join(i.id for i in instances)})" + ) + instance = instances[0] + say(f"understanding 'mine' as unique owned instance {instance.id}") + return instance + return boto3.resource("ec2").Instance(name) + + def get_instances_by_tag(k: str, v: str) -> list[InstanceTypeDef]: return [ i diff --git a/misc/python/materialize/test_analytics/config/test_analytics_db_config.py b/misc/python/materialize/test_analytics/config/test_analytics_db_config.py index c7965d9648250..4a4cb34cdc852 100644 --- a/misc/python/materialize/test_analytics/config/test_analytics_db_config.py +++ b/misc/python/materialize/test_analytics/config/test_analytics_db_config.py @@ -9,7 +9,7 @@ import os -from materialize import buildkite +from materialize import buildkite, ui from materialize.mz_env_util import get_cloud_hostname from materialize.mzcompose.composition import Composition from materialize.test_analytics.config.mz_db_config import MzDbConfig @@ -20,7 +20,12 @@ def create_test_analytics_config(c: Composition) -> MzDbConfig: app_password = os.getenv("PRODUCTION_ANALYTICS_APP_PASSWORD") if app_password is not None: - hostname = get_cloud_hostname(c, app_password=app_password) + try: + hostname = get_cloud_hostname(c, app_password=app_password) + except ui.CommandFailureCausedUIError as e: + # TODO: Remove when database-issues#8592 is fixed + print(f"Failed to get cloud hostname ({e}), using fallback value") + hostname = "7vifiksqeftxc6ld3r6zvc8n2.lb.us-east-1.aws.materialize.cloud" else: hostname = "unknown" diff --git a/misc/python/materialize/version_ancestor_overrides.py b/misc/python/materialize/version_ancestor_overrides.py index 8f29fcf21dfd9..59fafdf775602 100644 --- a/misc/python/materialize/version_ancestor_overrides.py +++ b/misc/python/materialize/version_ancestor_overrides.py @@ -28,6 +28,28 @@ def get_ancestor_overrides_for_performance_regressions( min_ancestor_mz_version_per_commit = dict() + if scenario_class_name in ("KafkaUpsertUnique", "ParallelIngestion"): + # PR#30617 (storage/kafka: use separate consumer for metadata probing) + # adds 1s of delay to Kafka source startup + min_ancestor_mz_version_per_commit[ + "9f7b634e6824f73d0effcdfa86c2b8b1642a4784" + ] = MzVersion.parse_mz("v0.127.0") + if scenario_class_name == "InsertMultiRow": + # PR#30622 (Refactor how we run FoldConstants) increases wallclock + min_ancestor_mz_version_per_commit[ + "a558d6bdc4b29abf79457eaba52914a0d6c805b7" + ] = MzVersion.parse_mz("v0.127.0") + if scenario_class_name == "CrossJoin": + # PR#26745 (compute: MV sink refresh) increases wallclock + min_ancestor_mz_version_per_commit[ + "9485261eae85fb7f12a2884fdac464a68798dc8b" + ] = MzVersion.parse_mz("v0.126.0") + if "OptbenchTPCH" in scenario_class_name: + # PR#30602 (Replace ColumnKnowledge with EquivalencePropagation) increases wallclock + min_ancestor_mz_version_per_commit[ + "1bd45336f8335b3487153beb7ce57f6391a7cf9c" + ] = MzVersion.parse_mz("v0.126.0") + if "OptbenchTPCH" in scenario_class_name: # PR#30506 (Remove NonNullable transform) increases wallclock min_ancestor_mz_version_per_commit[ diff --git a/src/adapter/src/catalog.rs b/src/adapter/src/catalog.rs index bccf6580692bf..844f4741f1bee 100644 --- a/src/adapter/src/catalog.rs +++ b/src/adapter/src/catalog.rs @@ -518,14 +518,52 @@ impl Catalog { Fut: Future, { let persist_client = PersistClient::new_for_tests().await; - let environmentd_id = Uuid::new_v4(); + let organization_id = Uuid::new_v4(); let bootstrap_args = test_bootstrap_args(); - let catalog = Self::open_debug_catalog(persist_client, environmentd_id, &bootstrap_args) + let catalog = Self::open_debug_catalog(persist_client, organization_id, &bootstrap_args) .await .expect("can open debug catalog"); f(catalog).await } + /// Like [`Catalog::with_debug`], but the catalog created believes that bootstrap is still + /// in progress. + pub async fn with_debug_in_bootstrap(f: F) -> T + where + F: FnOnce(Catalog) -> Fut, + Fut: Future, + { + let persist_client = PersistClient::new_for_tests().await; + let organization_id = Uuid::new_v4(); + let bootstrap_args = test_bootstrap_args(); + let mut catalog = + Self::open_debug_catalog(persist_client.clone(), organization_id, &bootstrap_args) + .await + .expect("can open debug catalog"); + + // Replace `storage` in `catalog` with one that doesn't think bootstrap is over. + let now = SYSTEM_TIME.clone(); + let openable_storage = TestCatalogStateBuilder::new(persist_client) + .with_organization_id(organization_id) + .with_default_deploy_generation() + .build() + .await + .expect("can create durable catalog"); + let mut storage = openable_storage + .open(now().into(), &bootstrap_args) + .await + .expect("can open durable catalog") + .0; + // Drain updates. + let _ = storage + .sync_to_current_updates() + .await + .expect("can sync to current updates"); + catalog.storage = Arc::new(tokio::sync::Mutex::new(storage)); + + f(catalog).await + } + /// Opens a debug catalog. /// /// See [`Catalog::with_debug`]. @@ -541,7 +579,7 @@ impl Catalog { .with_default_deploy_generation() .build() .await?; - let storage = openable_storage.open(now().into(), bootstrap_args).await?; + let storage = openable_storage.open(now().into(), bootstrap_args).await?.0; let system_parameter_defaults = BTreeMap::default(); Self::open_debug_catalog_inner( persist_client, @@ -731,13 +769,17 @@ impl Catalog { commit_ts: mz_repr::Timestamp, ) -> Result<(CatalogItemId, GlobalId), Error> { use mz_ore::collections::CollectionExt; - self.storage() - .await - .allocate_system_ids(1, commit_ts) - .await - .maybe_terminate("allocating system ids") - .map(|ids| ids.into_element()) - .err_into() + + let mut storage = self.storage().await; + let mut txn = storage.transaction().await?; + let id = txn + .allocate_system_item_ids(1) + .maybe_terminate("allocating system ids")? + .into_element(); + // Drain transaction. + let _ = txn.get_and_commit_op_updates(); + txn.commit(commit_ts).await?; + Ok(id) } /// Get the next system item ID without allocating it. @@ -2649,7 +2691,7 @@ mod tests { assert_eq!( mz_sql::catalog::ObjectType::ClusterReplica, conn_catalog.get_object_type(&ObjectId::ClusterReplica(( - ClusterId::User(1), + ClusterId::user(1).expect("1 is a valid ID"), ReplicaId::User(1) ))) ); @@ -2671,7 +2713,7 @@ mod tests { assert_eq!( None, conn_catalog.get_privileges(&SystemObjectId::Object(ObjectId::ClusterReplica(( - ClusterId::User(1), + ClusterId::user(1).expect("1 is a valid ID"), ReplicaId::User(1), )))) ); diff --git a/src/adapter/src/catalog/open.rs b/src/adapter/src/catalog/open.rs index cbbd6a6499964..1a0ecc92fa73f 100644 --- a/src/adapter/src/catalog/open.rs +++ b/src/adapter/src/catalog/open.rs @@ -28,9 +28,7 @@ use mz_catalog::config::{ClusterReplicaSizeMap, StateConfig}; use mz_catalog::durable::objects::{ SystemObjectDescription, SystemObjectMapping, SystemObjectUniqueIdentifier, }; -use mz_catalog::durable::{ - ClusterVariant, ClusterVariantManaged, Transaction, SYSTEM_CLUSTER_ID_ALLOC_KEY, -}; +use mz_catalog::durable::{ClusterVariant, ClusterVariantManaged, Transaction}; use mz_catalog::expr_cache::{ ExpressionCacheConfig, ExpressionCacheHandle, GlobalExpressions, LocalExpressions, }; @@ -77,8 +75,11 @@ use crate::AdapterError; #[derive(Debug)] pub struct BuiltinMigrationMetadata { - // Used to drop objects on STORAGE nodes - pub previous_storage_collection_ids: BTreeSet, + /// Used to drop objects on STORAGE nodes. + /// + /// Note: These collections are only known by the storage controller, and not the + /// Catalog, thus we identify them by their [`GlobalId`]. + pub previous_storage_collection_ids: BTreeSet, // Used to update persisted on disk catalog state pub migrated_system_object_mappings: BTreeMap, pub introspection_source_index_updates: @@ -177,7 +178,7 @@ pub struct InitializeStateResult { /// An initialized [`CatalogState`]. pub state: CatalogState, /// A set of storage collections to drop (only used by legacy migrations). - pub storage_collections_to_drop: BTreeSet, + pub storage_collections_to_drop: BTreeSet, /// A set of new shards that may need to be initialized (only used by 0dt migration). pub migrated_storage_collections_0dt: BTreeSet, /// A set of new builtin items. @@ -198,7 +199,10 @@ pub struct OpenCatalogResult { /// An opened [`Catalog`]. pub catalog: Catalog, /// A set of storage collections to drop (only used by legacy migrations). - pub storage_collections_to_drop: BTreeSet, + /// + /// Note: These Collections will not be in the Catalog, and are only known about by + /// the storage controller, which is why we identify them by [`GlobalId`]. + pub storage_collections_to_drop: BTreeSet, /// A set of new shards that may need to be initialized (only used by 0dt migration). pub migrated_storage_collections_0dt: BTreeSet, /// A set of new builtin items. @@ -629,6 +633,8 @@ impl Catalog { ); } + catalog.storage().await.mark_bootstrap_complete(); + Ok(OpenCatalogResult { catalog, storage_collections_to_drop, @@ -652,17 +658,13 @@ impl Catalog { async fn initialize_storage_controller_state( &mut self, storage_controller: &mut dyn StorageController, - storage_collections_to_drop: BTreeSet, + storage_collections_to_drop: BTreeSet, ) -> Result<(), mz_catalog::durable::CatalogError> { let collections = self .entries() .filter(|entry| entry.item().is_storage_collection()) .flat_map(|entry| entry.global_ids()) .collect(); - let collections_to_drop = storage_collections_to_drop - .into_iter() - .flat_map(|item_id| self.get_entry(&item_id).global_ids()) - .collect(); // Clone the state so that any errors that occur do not leak any // transformations on error. @@ -672,7 +674,7 @@ impl Catalog { let mut txn = storage.transaction().await?; storage_controller - .initialize_state(&mut txn, collections, collections_to_drop) + .initialize_state(&mut txn, collections, storage_collections_to_drop) .await .map_err(mz_catalog::durable::DurableCatalogError::from)?; @@ -695,7 +697,7 @@ impl Catalog { config: mz_controller::ControllerConfig, envd_epoch: core::num::NonZeroI64, read_only: bool, - storage_collections_to_drop: BTreeSet, + storage_collections_to_drop: BTreeSet, ) -> Result, mz_catalog::durable::CatalogError> { let controller_start = Instant::now(); @@ -771,6 +773,10 @@ impl Catalog { let (new_item_id, new_global_id) = match id { CatalogItemId::System(_) => txn.allocate_system_item_ids(1)?.into_element(), + CatalogItemId::IntrospectionSourceIndex(id) => ( + CatalogItemId::IntrospectionSourceIndex(id), + GlobalId::IntrospectionSourceIndex(id), + ), CatalogItemId::User(_) => txn.allocate_user_item_ids(1)?.into_element(), _ => unreachable!("can't migrate id: {id}"), }; @@ -816,7 +822,7 @@ impl Catalog { if entry.item().is_storage_collection() { migration_metadata .previous_storage_collection_ids - .insert(id); + .extend(entry.global_ids()); } // Push drop commands. @@ -1221,10 +1227,7 @@ fn add_new_builtin_clusters_migration( if !cluster_names.contains(builtin_cluster.name) { let cluster_size = builtin_cluster_sizes.get_size(builtin_cluster.name)?; let cluster_allocation = cluster_sizes.get_allocation_by_name(&cluster_size)?; - let id = txn.get_and_increment_id(SYSTEM_CLUSTER_ID_ALLOC_KEY.to_string())?; - let id = ClusterId::System(id); txn.insert_system_cluster( - id, builtin_cluster.name, vec![], builtin_cluster.privileges.to_vec(), @@ -1264,13 +1267,9 @@ fn add_new_remove_old_builtin_introspection_source_migration( } } - let new_ids = txn.allocate_system_item_ids(usize_to_u64(new_logs.len()))?; - let new_entries = new_logs - .into_iter() - .zip_eq(new_ids) - .map(|(log, (item_id, gid))| (log, item_id, gid)); - - for (log, item_id, gid) in new_entries { + for log in new_logs { + let (item_id, gid) = + Transaction::allocate_introspection_source_index_id(&cluster.id, log.variant); new_indexes.push((cluster.id, log.name.to_string(), item_id, gid)); } @@ -1585,7 +1584,8 @@ mod builtin_migration_tests { item_id_mapping: &BTreeMap, global_id_mapping: &BTreeMap, global_id_gen: &mut Gen, - ) -> (String, ItemNamespace, CatalogItem) { + ) -> (String, ItemNamespace, CatalogItem, GlobalId) { + let global_id = GlobalId::User(global_id_gen.allocate_id()); let item = match self.item { SimplifiedItem::Table => CatalogItem::Table(Table { create_sql: Some("CREATE TABLE materialize.public.t (a INT)".to_string()), @@ -1593,12 +1593,7 @@ mod builtin_migration_tests { .with_column("a", ScalarType::Int32.nullable(true)) .with_key(vec![0]) .finish(), - collections: [( - RelationVersion::root(), - GlobalId::User(global_id_gen.allocate_id()), - )] - .into_iter() - .collect(), + collections: [(RelationVersion::root(), global_id)].into_iter().collect(), conn_id: None, resolved_ids: ResolvedIds::empty(), custom_logical_compaction_window: None, @@ -1621,7 +1616,7 @@ mod builtin_migration_tests { convert_names_to_ids(referenced_names, item_id_mapping, global_id_mapping); CatalogItem::MaterializedView(MaterializedView { - global_id: GlobalId::User(global_id_gen.allocate_id()), + global_id, create_sql: format!( "CREATE MATERIALIZED VIEW materialize.public.mv ({column_list}) AS SELECT * FROM {table_list}" ), @@ -1645,7 +1640,7 @@ mod builtin_migration_tests { .with_key(vec![0]) .finish(), resolved_ids: resolved_ids.into_iter().collect(), - cluster_id: ClusterId::User(1), + cluster_id: ClusterId::user(1).expect("1 is a valid ID"), non_null_assertions: vec![], custom_logical_compaction_window: None, refresh_schedule: None, @@ -1657,18 +1652,18 @@ mod builtin_migration_tests { let on_gid = global_id_mapping[&on]; CatalogItem::Index(Index { create_sql: format!("CREATE INDEX idx ON materialize.public.{on} (a)"), - global_id: GlobalId::User(global_id_gen.allocate_id()), + global_id, on: on_gid, keys: Default::default(), conn_id: None, resolved_ids: [(on_item_id, on_gid)].into_iter().collect(), - cluster_id: ClusterId::User(1), + cluster_id: ClusterId::user(1).expect("1 is a valid ID"), custom_logical_compaction_window: None, is_retained_metrics_object: false, }) } }; - (self.name, self.namespace, item) + (self.name, self.namespace, item, global_id) } } @@ -1687,9 +1682,9 @@ mod builtin_migration_tests { name: String, item: CatalogItem, item_namespace: ItemNamespace, - ) -> (CatalogItemId, GlobalId) { + ) -> CatalogItemId { let id_ts = catalog.storage().await.current_upper().await; - let (item_id, global_id) = match item_namespace { + let (item_id, _) = match item_namespace { ItemNamespace::User => catalog .allocate_user_id(id_ts) .await @@ -1732,7 +1727,7 @@ mod builtin_migration_tests { .await .expect("failed to transact"); - (item_id, global_id) + item_id } fn convert_names_to_ids( @@ -1757,8 +1752,22 @@ mod builtin_migration_tests { ids.into_iter().map(|id| name_lookup[&id].clone()).collect() } + fn convert_global_ids_to_names>( + ids: I, + global_id_lookup: &BTreeMap, + ) -> BTreeSet { + ids.into_iter() + .flat_map(|id_a| { + global_id_lookup + .iter() + .filter_map(move |(name, id_b)| (id_a == *id_b).then_some(name)) + }) + .cloned() + .collect() + } + async fn run_test_case(test_case: BuiltinMigrationTestCase) { - Catalog::with_debug(|mut catalog| async move { + Catalog::with_debug_in_bootstrap(|mut catalog| async move { let mut item_id_mapping = BTreeMap::new(); let mut name_mapping = BTreeMap::new(); @@ -1766,10 +1775,9 @@ mod builtin_migration_tests { let mut global_id_mapping = BTreeMap::new(); for entry in test_case.initial_state { - let (name, namespace, item) = + let (name, namespace, item, global_id) = entry.to_catalog_item(&item_id_mapping, &global_id_mapping, &mut global_id_gen); - let (item_id, global_id) = - add_item(&mut catalog, name.clone(), item, namespace).await; + let item_id = add_item(&mut catalog, name.clone(), item, namespace).await; item_id_mapping.insert(name.clone(), item_id); global_id_mapping.insert(name.clone(), global_id); @@ -1808,11 +1816,11 @@ mod builtin_migration_tests { }; assert_eq!( - convert_ids_to_names( + convert_global_ids_to_names( migration_metadata .previous_storage_collection_ids .into_iter(), - &name_mapping + &global_id_mapping ), test_case .expected_previous_storage_collection_names diff --git a/src/adapter/src/catalog/open/builtin_item_migration.rs b/src/adapter/src/catalog/open/builtin_item_migration.rs index bf80e115a7925..a88882b8634a3 100644 --- a/src/adapter/src/catalog/open/builtin_item_migration.rs +++ b/src/adapter/src/catalog/open/builtin_item_migration.rs @@ -47,7 +47,7 @@ pub(crate) struct BuiltinItemMigrationResult { /// A vec of updates to apply to the builtin tables. pub(crate) builtin_table_updates: Vec>, /// A set of storage collections to drop (only used by legacy migration). - pub(crate) storage_collections_to_drop: BTreeSet, + pub(crate) storage_collections_to_drop: BTreeSet, /// A set of new shards that may need to be initialized (only used by 0dt migration). pub(crate) migrated_storage_collections_0dt: BTreeSet, /// Some cleanup action to take once the migration has been made durable. diff --git a/src/adapter/src/catalog/state.rs b/src/adapter/src/catalog/state.rs index a7736d40e7a99..6ca5c83708126 100644 --- a/src/adapter/src/catalog/state.rs +++ b/src/adapter/src/catalog/state.rs @@ -87,7 +87,7 @@ use mz_storage_types::connections::ConnectionContext; use serde::Serialize; use timely::progress::Antichain; use tokio::sync::mpsc; -use tracing::{debug, info, warn}; +use tracing::{debug, warn}; // DO NOT add any more imports from `crate` outside of `crate::catalog`. use crate::catalog::{Catalog, ConnCatalog}; @@ -1145,7 +1145,7 @@ impl CatalogState { Some(local_expr) if local_expr.optimizer_features == optimizer_config.features => { - info!("local expression cache hit for {global_id:?}"); + debug!("local expression cache hit for {global_id:?}"); (view.expr, local_expr.local_mir) } Some(_) | None => { @@ -1195,18 +1195,17 @@ impl CatalogState { Some(local_expr) if local_expr.optimizer_features == optimizer_config.features => { - info!("local expression cache hit for {global_id:?}"); + debug!("local expression cache hit for {global_id:?}"); (materialized_view.expr, local_expr.local_mir) } Some(_) | None => { let optimizer_features = optimizer_config.features.clone(); - // Build an optimizer for this VIEW. // TODO(aalexandrov): ideally this should be a materialized_view::Optimizer. let mut optimizer = optimize::view::Optimizer::new(optimizer_config, None); let raw_expr = materialized_view.expr; let optimized_expr = match optimizer.optimize(raw_expr.clone()) { - Ok(optimzed_expr) => optimzed_expr, + Ok(optimized_expr) => optimized_expr, Err(err) => return Err((err.into(), cached_expr)), }; diff --git a/src/adapter/src/catalog/transact.rs b/src/adapter/src/catalog/transact.rs index 1f5abab102a26..0c51cd5ae91ab 100644 --- a/src/adapter/src/catalog/transact.rs +++ b/src/adapter/src/catalog/transact.rs @@ -32,7 +32,6 @@ use mz_catalog::memory::objects::{ use mz_catalog::SYSTEM_CONN_ID; use mz_controller::clusters::{ManagedReplicaLocation, ReplicaConfig, ReplicaLocation}; use mz_controller_types::{ClusterId, ReplicaId}; -use mz_ore::cast::usize_to_u64; use mz_ore::collections::HashSet; use mz_ore::instrument; use mz_ore::now::EpochMillis; @@ -903,12 +902,19 @@ impl Catalog { let privileges: Vec<_> = merge_mz_acl_items(owner_privileges.into_iter().chain(default_privileges)) .collect(); - let introspection_source_ids = - tx.allocate_system_item_ids(usize_to_u64(introspection_sources.len()))?; + let introspection_source_ids: Vec<_> = introspection_sources + .iter() + .map(|introspection_source| { + Transaction::allocate_introspection_source_index_id( + &id, + introspection_source.variant, + ) + }) + .collect(); let introspection_sources = introspection_sources .into_iter() - .zip_eq(introspection_source_ids.into_iter()) + .zip_eq(introspection_source_ids) .map(|(log, (item_id, gid))| (log, item_id, gid)) .collect(); diff --git a/src/adapter/src/client.rs b/src/adapter/src/client.rs index 8c9eefa759ea2..401c3350b5307 100644 --- a/src/adapter/src/client.rs +++ b/src/adapter/src/client.rs @@ -741,7 +741,6 @@ impl SessionClient { // Collect optimizer parameters. let optimizer_config = optimize::OptimizerConfig::from(conn_catalog.system_vars()); - // Build an optimizer for this VIEW. let mut optimizer = optimize::view::Optimizer::new(optimizer_config, None); let result: Result<_, AdapterError> = diff --git a/src/adapter/src/coord.rs b/src/adapter/src/coord.rs index a429a4dcf9466..92935ff0a2705 100644 --- a/src/adapter/src/coord.rs +++ b/src/adapter/src/coord.rs @@ -98,7 +98,7 @@ use mz_catalog::durable::OpenableDurableCatalogState; use mz_catalog::expr_cache::{GlobalExpressions, LocalExpressions}; use mz_catalog::memory::objects::{ CatalogEntry, CatalogItem, ClusterReplicaProcessStatus, ClusterVariantManaged, Connection, - DataSourceDesc, Table, TableDataSource, + DataSourceDesc, StateUpdate, Table, TableDataSource, }; use mz_cloud_resources::{CloudResourceController, VpcEndpointConfig, VpcEndpointEvent}; use mz_compute_client::as_of_selection; @@ -977,6 +977,7 @@ pub struct Config { pub controller_config: ControllerConfig, pub controller_envd_epoch: NonZeroI64, pub storage: Box, + pub audit_logs_handle: thread::JoinHandle>, pub timestamp_oracle_url: Option, pub unsafe_mode: bool, pub all_features: bool, @@ -1787,6 +1788,7 @@ impl Coordinator { mut builtin_table_updates: Vec, cached_global_exprs: BTreeMap, uncached_local_exprs: BTreeMap, + audit_logs_handle: std::thread::JoinHandle>, ) -> Result<(), AdapterError> { let bootstrap_start = Instant::now(); info!("startup: coordinator init: bootstrap beginning"); @@ -2248,12 +2250,27 @@ impl Coordinator { if self.controller.read_only() { info!("coordinator init: bootstrap: stashing builtin table updates while in read-only mode"); + let audit_join_start = Instant::now(); + info!("startup: coordinator init: bootstrap: join audit log deserialization beginning"); + let audit_log_updates = audit_logs_handle + .join() + .expect("unable to deserialize audit log"); + let audit_log_builtin_table_updates = self + .catalog() + .state() + .generate_builtin_table_updates(audit_log_updates); + builtin_table_updates.extend(audit_log_builtin_table_updates); + info!( + "startup: coordinator init: bootstrap: join audit log deserialization complete in {:?}", + audit_join_start.elapsed() + ); self.buffered_builtin_table_updates .as_mut() .expect("in read-only mode") .append(&mut builtin_table_updates); } else { - self.bootstrap_tables(&entries, builtin_table_updates).await; + self.bootstrap_tables(&entries, builtin_table_updates, audit_logs_handle) + .await; }; info!( "startup: coordinator init: bootstrap: generate builtin updates complete in {:?}", @@ -2301,7 +2318,8 @@ impl Coordinator { let id_too_large = match id { CatalogItemId::System(id) => *id >= next_system_item_id, CatalogItemId::User(id) => *id >= next_user_item_id, - CatalogItemId::Transient(_) => false, + CatalogItemId::IntrospectionSourceIndex(_) + | CatalogItemId::Transient(_) => false, }; if id_too_large { info!( @@ -2366,6 +2384,7 @@ impl Coordinator { &mut self, entries: &[CatalogEntry], mut builtin_table_updates: Vec, + audit_logs_handle: thread::JoinHandle>, ) { /// Smaller helper struct of metadata for bootstrapping tables. struct TableMetadata<'a> { @@ -2464,6 +2483,21 @@ impl Coordinator { builtin_table_updates.extend(retractions); } + let audit_join_start = Instant::now(); + info!("startup: coordinator init: bootstrap: join audit log deserialization beginning"); + let audit_log_updates = audit_logs_handle + .join() + .expect("unable to deserialize audit log"); + let audit_log_builtin_table_updates = self + .catalog() + .state() + .generate_builtin_table_updates(audit_log_updates); + builtin_table_updates.extend(audit_log_builtin_table_updates); + info!( + "startup: coordinator init: bootstrap: join audit log deserialization complete in {:?}", + audit_join_start.elapsed() + ); + // Now that the snapshots are complete, the appends must also be complete. table_fence_rx .await @@ -2752,7 +2786,7 @@ impl Coordinator { if global_expressions.optimizer_features == optimizer_config.features => { - info!("global expression cache hit for {global_id:?}"); + debug!("global expression cache hit for {global_id:?}"); ( global_expressions.global_mir, global_expressions.physical_plan, @@ -2837,7 +2871,7 @@ impl Coordinator { if global_expressions.optimizer_features == optimizer_config.features => { - info!("global expression cache hit for {global_id:?}"); + debug!("global expression cache hit for {global_id:?}"); ( global_expressions.global_mir, global_expressions.physical_plan, @@ -2930,7 +2964,7 @@ impl Coordinator { if global_expressions.optimizer_features == optimizer_config.features => { - info!("global expression cache hit for {global_id:?}"); + debug!("global expression cache hit for {global_id:?}"); ( global_expressions.global_mir, global_expressions.physical_plan, @@ -3624,7 +3658,7 @@ impl Coordinator { // An arbitrary compute instance ID to satisfy the function calls below. Note that // this only works because this function will never run. - let compute_instance = ComputeInstanceId::User(1); + let compute_instance = ComputeInstanceId::user(1).expect("1 is a valid ID"); let _: () = self.ship_dataflow(dataflow, compute_instance, None).await; } @@ -3681,6 +3715,7 @@ pub fn serve( controller_config, controller_envd_epoch, mut storage, + audit_logs_handle, timestamp_oracle_url, unsafe_mode, all_features, @@ -3995,7 +4030,7 @@ pub fn serve( storage_collections_to_drop, ) }) - .expect("failed to initialize storage_controller"); + .unwrap_or_terminate("failed to initialize storage_controller"); // Initializing the controller uses one or more timestamps, so push the boot timestamp up to the // current catalog upper. let catalog_upper = handle.block_on(catalog.current_upper()); @@ -4064,6 +4099,7 @@ pub fn serve( builtin_table_updates, cached_global_exprs, uncached_local_exprs, + audit_logs_handle, ) .await?; coord diff --git a/src/adapter/src/coord/message_handler.rs b/src/adapter/src/coord/message_handler.rs index fcc123918cf27..b3d34d24b5998 100644 --- a/src/adapter/src/coord/message_handler.rs +++ b/src/adapter/src/coord/message_handler.rs @@ -215,7 +215,6 @@ impl Coordinator { let CollectionMetadata { data_shard, remap_shard, - status_shard, // No wildcards, to improve the odds that the addition of a // new shard type results in a compiler error here. // @@ -227,7 +226,7 @@ impl Coordinator { relation_desc: _, txns_shard: _, } = collection_metadata; - [remap_shard, status_shard, Some(data_shard)].into_iter() + [remap_shard, Some(data_shard)].into_iter() }) .filter_map(|shard| shard) .collect(); diff --git a/src/adapter/src/coord/peek.rs b/src/adapter/src/coord/peek.rs index 4b3e1a2c212ea..01ecdd56b1c95 100644 --- a/src/adapter/src/coord/peek.rs +++ b/src/adapter/src/coord/peek.rs @@ -491,11 +491,13 @@ impl crate::coord::Coordinator { max_returned_query_size, &duration_histogram, ) { - Ok(rows) => { + Ok((rows, row_size_bytes)) => { + let result_size = u64::cast_from(row_size_bytes); let rows_returned = u64::cast_from(rows.count()); ( Ok(Self::send_immediate_rows(rows)), StatementEndedExecutionReason::Success { + result_size: Some(result_size), rows_returned: Some(rows_returned), execution_strategy: Some(StatementExecutionStrategy::Constant), }, @@ -651,7 +653,7 @@ impl crate::coord::Coordinator { max_returned_query_size, &duration_histogram, ) { - Ok(rows) => PeekResponseUnary::Rows(Box::new(rows)), + Ok((rows, _size_bytes)) => PeekResponseUnary::Rows(Box::new(rows)), Err(e) => PeekResponseUnary::Error(e), } } @@ -729,13 +731,17 @@ impl crate::coord::Coordinator { }) = self.remove_pending_peek(&uuid) { let reason = match notification { - PeekNotification::Success { rows: num_rows } => { + PeekNotification::Success { + rows: num_rows, + result_size, + } => { let strategy = if is_fast_path { StatementExecutionStrategy::FastPath } else { StatementExecutionStrategy::Standard }; StatementEndedExecutionReason::Success { + result_size: Some(result_size), rows_returned: Some(num_rows), execution_strategy: Some(strategy), } diff --git a/src/adapter/src/coord/sequencer.rs b/src/adapter/src/coord/sequencer.rs index a0f77143c9006..3ba9ff5920eaa 100644 --- a/src/adapter/src/coord/sequencer.rs +++ b/src/adapter/src/coord/sequencer.rs @@ -845,7 +845,7 @@ impl Coordinator { Some(max_returned_query_size), duration_histogram, ) { - Ok(rows) => Ok(Self::send_immediate_rows(rows)), + Ok((rows, _size_bytes)) => Ok(Self::send_immediate_rows(rows)), Err(e) => Err(AdapterError::ResultSize(e)), }; } diff --git a/src/adapter/src/coord/sequencer/inner.rs b/src/adapter/src/coord/sequencer/inner.rs index 20a27f4af61d5..6c8a3060e0ee7 100644 --- a/src/adapter/src/coord/sequencer/inner.rs +++ b/src/adapter/src/coord/sequencer/inner.rs @@ -2647,7 +2647,7 @@ impl Coordinator { // Collect optimizer parameters. let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config()); - // Build an optimizer for this VIEW. + // (`optimize::view::Optimizer` has a special case for constant queries.) let mut optimizer = optimize::view::Optimizer::new(optimizer_config, None); // HIR ⇒ MIR lowering and MIR ⇒ MIR optimization (local) diff --git a/src/adapter/src/coord/sequencer/inner/explain_timestamp.rs b/src/adapter/src/coord/sequencer/inner/explain_timestamp.rs index 3b2867b57730d..9b704d514c8fd 100644 --- a/src/adapter/src/coord/sequencer/inner/explain_timestamp.rs +++ b/src/adapter/src/coord/sequencer/inner/explain_timestamp.rs @@ -130,7 +130,6 @@ impl Coordinator { // Collect optimizer parameters. let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config()); - // Build an optimizer for this VIEW. let mut optimizer = optimize::view::Optimizer::new(optimizer_config, None); let span = Span::current(); diff --git a/src/adapter/src/coord/statement_logging.rs b/src/adapter/src/coord/statement_logging.rs index b998b87b547b5..ad18a172ea4c0 100644 --- a/src/adapter/src/coord/statement_logging.rs +++ b/src/adapter/src/coord/statement_logging.rs @@ -494,6 +494,8 @@ impl Coordinator { Datum::Null, // error_message Datum::Null, + // result_size + Datum::Null, // rows_returned Datum::Null, // execution_status @@ -559,23 +561,25 @@ impl Coordinator { let mut row = Row::default(); let mut packer = row.packer(); Self::pack_statement_execution_inner(began_record, &mut packer); - let (status, error_message, rows_returned, execution_strategy) = match &ended_record.reason - { - StatementEndedExecutionReason::Success { - rows_returned, - execution_strategy, - } => ( - "success", - None, - rows_returned.map(|rr| i64::try_from(rr).expect("must fit")), - execution_strategy.map(|es| es.name()), - ), - StatementEndedExecutionReason::Canceled => ("canceled", None, None, None), - StatementEndedExecutionReason::Errored { error } => { - ("error", Some(error.as_str()), None, None) - } - StatementEndedExecutionReason::Aborted => ("aborted", None, None, None), - }; + let (status, error_message, result_size, rows_returned, execution_strategy) = + match &ended_record.reason { + StatementEndedExecutionReason::Success { + result_size, + rows_returned, + execution_strategy, + } => ( + "success", + None, + result_size.map(|rs| i64::try_from(rs).expect("must fit")), + rows_returned.map(|rr| i64::try_from(rr).expect("must fit")), + execution_strategy.map(|es| es.name()), + ), + StatementEndedExecutionReason::Canceled => ("canceled", None, None, None, None), + StatementEndedExecutionReason::Errored { error } => { + ("error", Some(error.as_str()), None, None, None) + } + StatementEndedExecutionReason::Aborted => ("aborted", None, None, None, None), + }; packer.extend([ Datum::TimestampTz( to_datetime(ended_record.ended_at) @@ -584,6 +588,7 @@ impl Coordinator { ), status.into(), error_message.into(), + result_size.into(), rows_returned.into(), execution_strategy.into(), ]); diff --git a/src/adapter/src/coord/validity.rs b/src/adapter/src/coord/validity.rs index 4d2152b3614b7..d33b4e35fa1e8 100644 --- a/src/adapter/src/coord/validity.rs +++ b/src/adapter/src/coord/validity.rs @@ -255,7 +255,7 @@ mod tests { let PlanValidity::Checks { cluster_id, .. } = validity else { panic!(); }; - *cluster_id = Some(ClusterId::User(3)); + *cluster_id = Some(ClusterId::user(3).expect("3 is a valid ID")); }), Box::new(|res| { assert_contains!( diff --git a/src/adapter/src/explain.rs b/src/adapter/src/explain.rs index a9237ce5f2338..0f19a59eafa2f 100644 --- a/src/adapter/src/explain.rs +++ b/src/adapter/src/explain.rs @@ -90,7 +90,7 @@ where /// In the long term, this method and [`explain_dataflow`] should be unified. In /// order to do that, however, we first need to generalize the role /// [`DataflowMetainfo`] as a carrier of metainformation for the optimization -/// pass in general, and not for a specific strucutre representing an +/// pass in general, and not for a specific structure representing an /// intermediate result. pub(crate) fn explain_plan( mut plan: T, diff --git a/src/adapter/src/explain/mir.rs b/src/adapter/src/explain/mir.rs index 5598c58472618..2c62dd54e2538 100644 --- a/src/adapter/src/explain/mir.rs +++ b/src/adapter/src/explain/mir.rs @@ -12,8 +12,8 @@ //! The specialized [`Explain`] implementation for an [`MirRelationExpr`] //! wrapped in an [`Explainable`] newtype struct allows us to interpret more //! [`mz_repr::explain::ExplainConfig`] options. This is the case because -//! attribute derivation and let normalization are defined in [`mz_transform`] -//! and conssequently are not available for the default [`Explain`] +//! Analysis derivation and Let normalization are defined in [`mz_transform`] +//! and consequently are not available for the default [`Explain`] //! implementation for [`MirRelationExpr`] in [`mz_expr`]. use mz_compute_types::dataflows::DataflowDescription; diff --git a/src/adapter/src/optimize.rs b/src/adapter/src/optimize.rs index dcbab3afcca3f..4def51e0f455d 100644 --- a/src/adapter/src/optimize.rs +++ b/src/adapter/src/optimize.rs @@ -251,10 +251,6 @@ impl From<&OptimizerConfig> for mz_sql::plan::HirToMirConfig { Self { enable_new_outer_join_lowering: config.features.enable_new_outer_join_lowering, enable_variadic_left_join_lowering: config.features.enable_variadic_left_join_lowering, - enable_value_window_function_fusion: config - .features - .enable_value_window_function_fusion, - enable_window_aggregation_fusion: config.features.enable_window_aggregation_fusion, } } } @@ -363,6 +359,22 @@ fn optimize_mir_local( Ok::<_, OptimizerError>(expr) } +/// This is just a wrapper around [mz_transform::Optimizer::constant_optimizer], +/// running it, and tracing the result plan. +#[mz_ore::instrument(target = "optimizer", level = "debug", name = "constant")] +fn optimize_mir_constant( + expr: MirRelationExpr, + ctx: &mut TransformCtx, +) -> Result { + let optimizer = mz_transform::Optimizer::constant_optimizer(ctx); + let expr = optimizer.optimize(expr, ctx)?; + + // Trace the result of this phase. + mz_repr::explain::trace_plan(expr.as_inner()); + + Ok::<_, OptimizerError>(expr.0) +} + macro_rules! trace_plan { (at: $span:literal, $plan:expr) => { tracing::debug_span!(target: "optimizer", $span).in_scope(|| { diff --git a/src/adapter/src/optimize/view.rs b/src/adapter/src/optimize/view.rs index d67b0a9022e39..de4d496f11a52 100644 --- a/src/adapter/src/optimize/view.rs +++ b/src/adapter/src/optimize/view.rs @@ -7,7 +7,13 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0. -//! Optimizer implementation for `CREATE VIEW` statements. +//! An Optimizer that +//! 1. Optimistically calls `optimize_mir_constant`. +//! 2. Then, if we haven't arrived at a constant, it calls `optimize_mir_local`, i.e., the +//! logical optimizer. +//! +//! This is used for `CREATE VIEW` statements and in various other situations where no physical +//! optimization is needed, such as for `INSERT` statements. use std::time::Instant; @@ -18,7 +24,10 @@ use mz_transform::dataflow::DataflowMetainfo; use mz_transform::typecheck::{empty_context, SharedContext as TypecheckContext}; use mz_transform::TransformCtx; -use crate::optimize::{optimize_mir_local, trace_plan, Optimize, OptimizerConfig, OptimizerError}; +use crate::optimize::{ + optimize_mir_constant, optimize_mir_local, trace_plan, Optimize, OptimizerConfig, + OptimizerError, +}; pub struct Optimizer { /// A typechecking context to use throughout the optimizer pipeline. @@ -52,13 +61,24 @@ impl Optimize for Optimizer { trace_plan!(at: "raw", &expr); // HIR ⇒ MIR lowering and decorrelation - let expr = expr.lower(&self.config, self.metrics.as_ref())?; + let mut expr = expr.lower(&self.config, self.metrics.as_ref())?; - // MIR ⇒ MIR optimization (local) let mut df_meta = DataflowMetainfo::default(); let mut transform_ctx = TransformCtx::local(&self.config.features, &self.typecheck_ctx, &mut df_meta); - let expr = optimize_mir_local(expr, &mut transform_ctx)?; + + // First, we run a very simple optimizer pipeline, which only folds constants. This takes + // care of constant INSERTs. (This optimizer is also used for INSERTs, not just VIEWs.) + expr = optimize_mir_constant(expr, &mut transform_ctx)?; + + // MIR ⇒ MIR optimization (local) + let expr = if expr.as_const().is_some() { + // No need to optimize further, because we already have a constant. + OptimizedMirRelationExpr(expr) + } else { + // Call the real optimization. + optimize_mir_local(expr, &mut transform_ctx)? + }; if let Some(metrics) = &self.metrics { metrics.observe_e2e_optimization_time("view", time.elapsed()); diff --git a/src/adapter/src/statement_logging.rs b/src/adapter/src/statement_logging.rs index 3ecc68e140293..0835cd5eddb9f 100644 --- a/src/adapter/src/statement_logging.rs +++ b/src/adapter/src/statement_logging.rs @@ -10,7 +10,7 @@ use mz_controller_types::ClusterId; use mz_ore::cast::CastFrom; use mz_ore::now::EpochMillis; -use mz_repr::GlobalId; +use mz_repr::{GlobalId, RowIterator}; use mz_sql_parser::ast::StatementKind; use uuid::Uuid; @@ -89,6 +89,7 @@ impl StatementExecutionStrategy { #[derive(Clone, Debug)] pub enum StatementEndedExecutionReason { Success { + result_size: Option, rows_returned: Option, execution_strategy: Option, }, @@ -152,7 +153,12 @@ impl From<&ExecuteResponse> for StatementEndedExecutionReason { // NB [btv]: It's not clear that this combination // can ever actually happen. ExecuteResponse::SendingRowsImmediate { rows, .. } => { + // Note(parkmycar): It potentially feels bad here to iterate over the entire + // iterator _just_ to get the encoded result size. As noted above, it's not + // entirely clear this case ever happens, so the simplicity is worth it. + let result_size: usize = rows.box_clone().map(|row| row.byte_len()).sum(); StatementEndedExecutionReason::Success { + result_size: Some(u64::cast_from(result_size)), rows_returned: Some(u64::cast_from(rows.count())), execution_strategy: Some(StatementExecutionStrategy::Constant), } @@ -179,7 +185,14 @@ impl From<&ExecuteResponse> for StatementEndedExecutionReason { } ExecuteResponse::SendingRowsImmediate { rows, .. } => { + // Note(parkmycar): It potentially feels bad here to iterate over the entire + // iterator _just_ to get the encoded result size, the number of Rows returned here + // shouldn't be too large though. An alternative is to pre-compute some of the + // result size, but that would require always decoding Rows to handle projecting + // away columns, which has a negative impact for much larger response sizes. + let result_size: usize = rows.box_clone().map(|row| row.byte_len()).sum(); StatementEndedExecutionReason::Success { + result_size: Some(u64::cast_from(result_size)), rows_returned: Some(u64::cast_from(rows.count())), execution_strategy: Some(StatementExecutionStrategy::Constant), } @@ -233,6 +246,7 @@ impl From<&ExecuteResponse> for StatementEndedExecutionReason { | ExecuteResponse::Updated(_) | ExecuteResponse::ValidatedConnection { .. } => { StatementEndedExecutionReason::Success { + result_size: None, rows_returned: None, execution_strategy: None, } diff --git a/src/balancerd/BUILD.bazel b/src/balancerd/BUILD.bazel index a25630042fe64..d7c1f64b7ddc9 100644 --- a/src/balancerd/BUILD.bazel +++ b/src/balancerd/BUILD.bazel @@ -30,7 +30,7 @@ rust_library( proc_macro_deps = [] + all_crate_deps(proc_macro = True), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/alloc:mz_alloc", "//src/alloc-default:mz_alloc_default", @@ -70,7 +70,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/alloc:mz_alloc", "//src/alloc-default:mz_alloc_default", @@ -137,7 +137,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ ":mz_balancerd", "//src/alloc:mz_alloc", @@ -175,7 +175,7 @@ rust_binary( proc_macro_deps = [] + all_crate_deps(proc_macro = True), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ ":mz_balancerd", "//src/alloc:mz_alloc", diff --git a/src/balancerd/Cargo.toml b/src/balancerd/Cargo.toml index 5e32282ea825a..6fb96fc817b99 100644 --- a/src/balancerd/Cargo.toml +++ b/src/balancerd/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mz-balancerd" description = "Balancer service." -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" edition.workspace = true rust-version.workspace = true publish = false diff --git a/src/buf.yaml b/src/buf.yaml index 7d95c86b18247..d50cb186c1802 100644 --- a/src/buf.yaml +++ b/src/buf.yaml @@ -30,6 +30,8 @@ breaking: # reason: does currently not require backward-compatibility - catalog/protos/objects_v72.proto # reason: does currently not require backward-compatibility + - catalog/protos/objects_v73.proto + # reason: does currently not require backward-compatibility - cluster-client/src/client.proto # reason: does currently not require backward-compatibility - compute-client/src/logging.proto @@ -59,6 +61,8 @@ breaking: - storage-types/src/connections/aws.proto # reason: currently does not require backward-compatibility - storage-types/src/connections/string_or_secret.proto + # reason: remove after the commit that introduced it gets merged + - storage-types/src/errors.proto lint: use: - DEFAULT diff --git a/src/catalog-debug/BUILD.bazel b/src/catalog-debug/BUILD.bazel index 7b039795834b9..ae0c33eefcbcd 100644 --- a/src/catalog-debug/BUILD.bazel +++ b/src/catalog-debug/BUILD.bazel @@ -29,7 +29,7 @@ rust_binary( proc_macro_deps = [] + all_crate_deps(proc_macro = True), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/adapter:mz_adapter", "//src/build-info:mz_build_info", diff --git a/src/catalog-debug/Cargo.toml b/src/catalog-debug/Cargo.toml index 1eb0af06b4eb9..50407e1ac420d 100644 --- a/src/catalog-debug/Cargo.toml +++ b/src/catalog-debug/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mz-catalog-debug" description = "Durable metadata storage debug tool." -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" edition.workspace = true rust-version.workspace = true publish = false diff --git a/src/catalog-debug/src/main.rs b/src/catalog-debug/src/main.rs index bef65fab13cdc..f4588d5888687 100644 --- a/src/catalog-debug/src/main.rs +++ b/src/catalog-debug/src/main.rs @@ -551,7 +551,8 @@ async fn upgrade_check( cluster_replica_size_map: cluster_replica_sizes.clone(), }, ) - .await?; + .await? + .0; // If this upgrade has new builtin replicas, then we need to assign some size to it. It doesn't // really matter what size since it's not persisted, so we pick a random valid one. diff --git a/src/catalog/protos/hashes.json b/src/catalog/protos/hashes.json index 644d9f46bdd89..968d7a7e53a16 100644 --- a/src/catalog/protos/hashes.json +++ b/src/catalog/protos/hashes.json @@ -1,7 +1,7 @@ [ { "name": "objects.proto", - "md5": "2d781c72c4a56b13dfb1b4215f3614f0" + "md5": "65c8ec9661c8a207bc9eb5af098fa98f" }, { "name": "objects_v67.proto", @@ -26,5 +26,9 @@ { "name": "objects_v72.proto", "md5": "b21cb2b1b41649c78405731e53560d59" + }, + { + "name": "objects_v73.proto", + "md5": "d5d1a8c6b1aa8212245cfd343a3b8417" } ] diff --git a/src/catalog/protos/objects.proto b/src/catalog/protos/objects.proto index 42a9cfcbdbf07..37b099171dff3 100644 --- a/src/catalog/protos/objects.proto +++ b/src/catalog/protos/objects.proto @@ -78,12 +78,12 @@ message ClusterIntrospectionSourceIndexKey { } message ClusterIntrospectionSourceIndexValue { - // TODO(parkmycar): Ideally this is a SystemCatalogItemId but making this change panics 0dt + // TODO(parkmycar): Ideally this is a IntrospectionSourceCatalogItemId but making this change panics 0dt // upgrades if there were new builtin objects added since the older version of Materialize - // doesn't know how to read the new SystemCatalogItemId type. + // doesn't know how to read the new IntrospectionSourceCatalogItemId type. uint64 index_id = 1; uint32 oid = 2; - SystemGlobalId global_id = 3; + IntrospectionSourceIndexGlobalId global_id = 3; } message ClusterReplicaKey { @@ -307,6 +307,7 @@ message CatalogItemId { uint64 system = 1; uint64 user = 2; uint64 transient = 3; + uint64 introspection_source_index = 4; } } @@ -315,12 +316,18 @@ message SystemCatalogItemId { uint64 value = 1; } +/// A newtype wrapper for a `CatalogItemId` that is always in the "introspection source index" namespace. +message IntrospectionSourceIndexCatalogItemId { + uint64 value = 1; +} + message GlobalId { oneof value { uint64 system = 1; uint64 user = 2; uint64 transient = 3; Empty explain = 4; + uint64 introspection_source_index = 5; } } @@ -329,6 +336,11 @@ message SystemGlobalId { uint64 value = 1; } +/// A newtype wrapper for a `GlobalId` that is always in the "introspection source index" namespace. +message IntrospectionSourceIndexGlobalId { + uint64 value = 1; +} + message ClusterId { oneof value { uint64 system = 1; diff --git a/src/catalog/protos/objects_v73.proto b/src/catalog/protos/objects_v73.proto new file mode 100644 index 0000000000000..41f14a5b7ed24 --- /dev/null +++ b/src/catalog/protos/objects_v73.proto @@ -0,0 +1,1073 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +// This protobuf file defines the types we store in the Stash. +// +// Before and after modifying this file, make sure you have a snapshot of the before version, +// e.g. a copy of this file named 'objects_v{CATALOG_VERSION}.proto', and a snapshot of the file +// after your modifications, e.g. 'objects_v{CATALOG_VERSION + 1}.proto'. Then you can write a +// migration using these two files, and no matter how the types change in the future, we'll always +// have these snapshots to facilitate the migration. + +// buf breaking: ignore (does currently not require backward-compatibility) + +syntax = "proto3"; + +package objects_v73; + +message ConfigKey { + string key = 1; +} + +message ConfigValue { + uint64 value = 1; +} + +message SettingKey { + string name = 1; +} + +message SettingValue { + string value = 1; +} + +message IdAllocKey { + string name = 1; +} + +message IdAllocValue { + uint64 next_id = 1; +} + +message GidMappingKey { + string schema_name = 1; + CatalogItemType object_type = 2; + string object_name = 3; +} + +message GidMappingValue { + // TODO(parkmycar): Ideally this is a SystemCatalogItemId but making this change panics 0dt + // upgrades if there were new builtin objects added since the older version of Materialize + // doesn't know how to read the new SystemCatalogItemId type. + uint64 id = 1; + string fingerprint = 2; + SystemGlobalId global_id = 3; +} + +message ClusterKey { + ClusterId id = 1; +} + +message ClusterValue { + reserved 2; + string name = 1; + RoleId owner_id = 3; + repeated MzAclItem privileges = 4; + ClusterConfig config = 5; +} + +message ClusterIntrospectionSourceIndexKey { + ClusterId cluster_id = 1; + string name = 2; +} + +message ClusterIntrospectionSourceIndexValue { + // TODO(parkmycar): Ideally this is a IntrospectionSourceCatalogItemId but making this change panics 0dt + // upgrades if there were new builtin objects added since the older version of Materialize + // doesn't know how to read the new IntrospectionSourceCatalogItemId type. + uint64 index_id = 1; + uint32 oid = 2; + IntrospectionSourceIndexGlobalId global_id = 3; +} + +message ClusterReplicaKey { + ReplicaId id = 1; +} + +message ClusterReplicaValue { + ClusterId cluster_id = 1; + string name = 2; + ReplicaConfig config = 3; + RoleId owner_id = 4; +} + +message DatabaseKey { + DatabaseId id = 1; +} + +message DatabaseValue { + string name = 1; + RoleId owner_id = 2; + repeated MzAclItem privileges = 3; + uint32 oid = 4; +} + +message SchemaKey { + SchemaId id = 1; +} + +message SchemaValue { + DatabaseId database_id = 1; + string name = 2; + RoleId owner_id = 3; + repeated MzAclItem privileges = 4; + uint32 oid = 5; +} + +message ItemKey { + CatalogItemId gid = 1; +} + +message ItemValue { + SchemaId schema_id = 1; + string name = 2; + CatalogItem definition = 3; + RoleId owner_id = 4; + repeated MzAclItem privileges = 5; + uint32 oid = 6; + GlobalId global_id = 7; + repeated ItemVersion extra_versions = 8; +} + +message ItemVersion { + GlobalId global_id = 1; + Version version = 2; +} + +message RoleKey { + RoleId id = 1; +} + +message RoleValue { + string name = 1; + RoleAttributes attributes = 2; + RoleMembership membership = 3; + RoleVars vars = 4; + uint32 oid = 5; +} + +message NetworkPolicyKey { + NetworkPolicyId id = 1; +} + +message NetworkPolicyValue { + string name = 1; + repeated NetworkPolicyRule rules = 2; + RoleId owner_id = 3; + repeated MzAclItem privileges = 4; + uint32 oid = 5; +} + +message ServerConfigurationKey { + string name = 1; +} + +message ServerConfigurationValue { + string value = 1; +} + +message AuditLogKey { + oneof event { + AuditLogEventV1 v1 = 1; + } +} + +message CommentKey { + oneof object { + CatalogItemId table = 1; + CatalogItemId view = 2; + CatalogItemId materialized_view = 4; + CatalogItemId source = 5; + CatalogItemId sink = 6; + CatalogItemId index = 7; + CatalogItemId func = 8; + CatalogItemId connection = 9; + CatalogItemId type = 10; + CatalogItemId secret = 11; + CatalogItemId continual_task = 17; + RoleId role = 12; + DatabaseId database = 13; + ResolvedSchema schema = 14; + ClusterId cluster = 15; + ClusterReplicaId cluster_replica = 16; + NetworkPolicyId network_policy = 18; + } + oneof sub_component { + uint64 column_pos = 3; + } +} + +message CommentValue { + string comment = 1; +} + +message SourceReferencesKey { + CatalogItemId source = 1; +} + +message SourceReferencesValue { + repeated SourceReference references = 1; + EpochMillis updated_at = 2; +} + +message SourceReference { + string name = 1; + optional string namespace = 2; + repeated string columns = 3; +} + +message StorageCollectionMetadataKey { + GlobalId id = 1; +} + +// This value is stored transparently, however, it should only ever be +// manipulated by the storage controller. +message StorageCollectionMetadataValue { + string shard = 1; +} + +// This value is stored transparently, however, it should only ever be +// manipulated by the storage controller. +message UnfinalizedShardKey { + string shard = 1; +} + +// This value is stored transparently, however, it should only ever be +// manipulated by the storage controller. +message TxnWalShardValue { + string shard = 1; +} + +// ---- Common Types +// +// Note: Normally types like this would go in some sort of `common.proto` file, but we want to keep +// our proto definitions in a single file to make snapshotting easier, hence them living here. + +message Empty { + /* purposefully empty */ +} + +// In protobuf a "None" string is the same thing as an empty string. To get the same semantics of +// an `Option` from Rust, we need to wrap a string in a message. +message StringWrapper { + string inner = 1; +} + +message Duration { + uint64 secs = 1; + uint32 nanos = 2; +} + +message EpochMillis { + uint64 millis = 1; +} + +// Opaque timestamp type that is specific to Materialize. +message Timestamp { + uint64 internal = 1; +} + +message Version { + uint64 value = 2; +} + +enum CatalogItemType { + CATALOG_ITEM_TYPE_UNKNOWN = 0; + CATALOG_ITEM_TYPE_TABLE = 1; + CATALOG_ITEM_TYPE_SOURCE = 2; + CATALOG_ITEM_TYPE_SINK = 3; + CATALOG_ITEM_TYPE_VIEW = 4; + CATALOG_ITEM_TYPE_MATERIALIZED_VIEW = 5; + CATALOG_ITEM_TYPE_INDEX = 6; + CATALOG_ITEM_TYPE_TYPE = 7; + CATALOG_ITEM_TYPE_FUNC = 8; + CATALOG_ITEM_TYPE_SECRET = 9; + CATALOG_ITEM_TYPE_CONNECTION = 10; + CATALOG_ITEM_TYPE_CONTINUAL_TASK = 11; +} + +message CatalogItem { + message V1 { + string create_sql = 1; + } + + oneof value { + V1 v1 = 1; + } +} + +message CatalogItemId { + oneof value { + uint64 system = 1; + uint64 user = 2; + uint64 transient = 3; + uint64 introspection_source_index = 4; + } +} + +/// A newtype wrapper for a `CatalogItemId` that is always in the "system" namespace. +message SystemCatalogItemId { + uint64 value = 1; +} + +/// A newtype wrapper for a `CatalogItemId` that is always in the "introspection source index" namespace. +message IntrospectionSourceIndexCatalogItemId { + uint64 value = 1; +} + +message GlobalId { + oneof value { + uint64 system = 1; + uint64 user = 2; + uint64 transient = 3; + Empty explain = 4; + uint64 introspection_source_index = 5; + } +} + +/// A newtype wrapper for a `GlobalId` that is always in the "system" namespace. +message SystemGlobalId { + uint64 value = 1; +} + +/// A newtype wrapper for a `GlobalId` that is always in the "introspection source index" namespace. +message IntrospectionSourceIndexGlobalId { + uint64 value = 1; +} + +message ClusterId { + oneof value { + uint64 system = 1; + uint64 user = 2; + } +} + +message DatabaseId { + oneof value { + uint64 system = 1; + uint64 user = 2; + } +} + +message ResolvedDatabaseSpecifier { + oneof spec { + Empty ambient = 1; + DatabaseId id = 2; + } +} + +message SchemaId { + oneof value { + uint64 system = 1; + uint64 user = 2; + } +} + +message SchemaSpecifier { + oneof spec { + Empty temporary = 1; + SchemaId id = 2; + } +} + +message ResolvedSchema { + ResolvedDatabaseSpecifier database = 1; + SchemaSpecifier schema = 2; +} + +message ReplicaId { + oneof value { + uint64 system = 1; + uint64 user = 2; + } +} + +message ClusterReplicaId { + ClusterId cluster_id = 1; + ReplicaId replica_id = 2; +} + +message NetworkPolicyId { + oneof value { + uint64 system = 1; + uint64 user = 2; + } +} + +message ReplicaLogging { + bool log_logging = 1; + Duration interval = 2; +} + +message OptimizerFeatureOverride { + string name = 1; + string value = 2; +} + +message ClusterScheduleRefreshOptions { + Duration rehydration_time_estimate = 1; +} + +message ClusterSchedule { + oneof value { + Empty manual = 1; + ClusterScheduleRefreshOptions refresh = 2; + } +} + +message ClusterConfig { + message ManagedCluster { + string size = 1; + uint32 replication_factor = 2; + repeated string availability_zones = 3; + ReplicaLogging logging = 4; + bool disk = 6; + repeated OptimizerFeatureOverride optimizer_feature_overrides = 7; + ClusterSchedule schedule = 8; + } + + oneof variant { + Empty unmanaged = 1; + ManagedCluster managed = 2; + } + optional string workload_class = 3; +} + +message ReplicaConfig { + message UnmanagedLocation { + repeated string storagectl_addrs = 1; + repeated string storage_addrs = 2; + repeated string computectl_addrs = 3; + repeated string compute_addrs = 4; + uint64 workers = 5; + } + + message ManagedLocation { + string size = 1; + optional string availability_zone = 2; + bool disk = 4; + bool internal = 5; + optional string billed_as = 6; + bool pending = 7; + } + + oneof location { + UnmanagedLocation unmanaged = 1; + ManagedLocation managed = 2; + } + ReplicaLogging logging = 3; +} + +message RoleId { + oneof value { + uint64 system = 1; + uint64 user = 2; + Empty public = 3; + uint64 predefined = 4; + } +} + +message RoleAttributes { + bool inherit = 1; +} + +message RoleMembership { + message Entry { + RoleId key = 1; + RoleId value = 2; + } + + repeated Entry map = 1; +} + +message RoleVars { + message SqlSet { + repeated string entries = 1; + } + + message Entry { + string key = 1; + oneof val { + string flat = 2; + SqlSet sql_set = 3; + } + } + + repeated Entry entries = 1; +} + +message NetworkPolicyRule { + string name = 1; + oneof action { + Empty allow = 2; + } + oneof direction { + Empty ingress = 3; + } + string address = 4; +} + +message AclMode { + // A bit flag representing all the privileges that can be granted to a role. + uint64 bitflags = 1; +} + +message MzAclItem { + RoleId grantee = 1; + RoleId grantor = 2; + AclMode acl_mode = 3; +} + +enum ObjectType { + OBJECT_TYPE_UNKNOWN = 0; + OBJECT_TYPE_TABLE = 1; + OBJECT_TYPE_VIEW = 2; + OBJECT_TYPE_MATERIALIZED_VIEW = 3; + OBJECT_TYPE_SOURCE = 4; + OBJECT_TYPE_SINK = 5; + OBJECT_TYPE_INDEX = 6; + OBJECT_TYPE_TYPE = 7; + OBJECT_TYPE_ROLE = 8; + OBJECT_TYPE_CLUSTER = 9; + OBJECT_TYPE_CLUSTER_REPLICA = 10; + OBJECT_TYPE_SECRET = 11; + OBJECT_TYPE_CONNECTION = 12; + OBJECT_TYPE_DATABASE = 13; + OBJECT_TYPE_SCHEMA = 14; + OBJECT_TYPE_FUNC = 15; + OBJECT_TYPE_CONTINUAL_TASK = 16; + OBJECT_TYPE_NETWORK_POLICY = 17; +} + +message DefaultPrivilegesKey { + RoleId role_id = 1; + DatabaseId database_id = 2; + SchemaId schema_id = 3; + ObjectType object_type = 4; + RoleId grantee = 5; +} + +message DefaultPrivilegesValue { + AclMode privileges = 1; +} + +message SystemPrivilegesKey { + RoleId grantee = 1; + RoleId grantor = 2; +} + +message SystemPrivilegesValue { + AclMode acl_mode = 1; +} + +message AuditLogEventV1 { + enum EventType { + EVENT_TYPE_UNKNOWN = 0; + EVENT_TYPE_CREATE = 1; + EVENT_TYPE_DROP = 2; + EVENT_TYPE_ALTER = 3; + EVENT_TYPE_GRANT = 4; + EVENT_TYPE_REVOKE = 5; + EVENT_TYPE_COMMENT = 6; + } + + enum ObjectType { + OBJECT_TYPE_UNKNOWN = 0; + OBJECT_TYPE_CLUSTER = 1; + OBJECT_TYPE_CLUSTER_REPLICA = 2; + OBJECT_TYPE_CONNECTION = 3; + OBJECT_TYPE_DATABASE = 4; + OBJECT_TYPE_FUNC = 5; + OBJECT_TYPE_INDEX = 6; + OBJECT_TYPE_MATERIALIZED_VIEW = 7; + OBJECT_TYPE_ROLE = 8; + OBJECT_TYPE_SECRET = 9; + OBJECT_TYPE_SCHEMA = 10; + OBJECT_TYPE_SINK = 11; + OBJECT_TYPE_SOURCE = 12; + OBJECT_TYPE_TABLE = 13; + OBJECT_TYPE_TYPE = 14; + OBJECT_TYPE_VIEW = 15; + OBJECT_TYPE_SYSTEM = 16; + OBJECT_TYPE_CONTINUAL_TASK = 17; + OBJECT_TYPE_NETWORK_POLICY = 18; + } + + message IdFullNameV1 { + string id = 1; + FullNameV1 name = 2; + } + + message FullNameV1 { + string database = 1; + string schema = 2; + string item = 3; + } + + message IdNameV1 { + string id = 1; + string name = 2; + } + + message RenameClusterV1 { + string id = 1; + string old_name = 2; + string new_name = 3; + } + + message RenameClusterReplicaV1 { + string cluster_id = 1; + string replica_id = 2; + string old_name = 3; + string new_name = 4; + } + + message RenameItemV1 { + string id = 1; + FullNameV1 old_name = 2; + FullNameV1 new_name = 3; + } + + message CreateClusterReplicaV1 { + string cluster_id = 1; + string cluster_name = 2; + StringWrapper replica_id = 3; + string replica_name = 4; + string logical_size = 5; + bool disk = 6; + optional string billed_as = 7; + bool internal = 8; + } + + message CreateClusterReplicaV2 { + string cluster_id = 1; + string cluster_name = 2; + StringWrapper replica_id = 3; + string replica_name = 4; + string logical_size = 5; + bool disk = 6; + optional string billed_as = 7; + bool internal = 8; + CreateOrDropClusterReplicaReasonV1 reason = 9; + SchedulingDecisionsWithReasonsV1 scheduling_policies = 10; + } + + message CreateClusterReplicaV3 { + string cluster_id = 1; + string cluster_name = 2; + StringWrapper replica_id = 3; + string replica_name = 4; + string logical_size = 5; + bool disk = 6; + optional string billed_as = 7; + bool internal = 8; + CreateOrDropClusterReplicaReasonV1 reason = 9; + SchedulingDecisionsWithReasonsV2 scheduling_policies = 10; + } + + message DropClusterReplicaV1 { + string cluster_id = 1; + string cluster_name = 2; + StringWrapper replica_id = 3; + string replica_name = 4; + } + + message DropClusterReplicaV2 { + string cluster_id = 1; + string cluster_name = 2; + StringWrapper replica_id = 3; + string replica_name = 4; + CreateOrDropClusterReplicaReasonV1 reason = 5; + SchedulingDecisionsWithReasonsV1 scheduling_policies = 6; + } + + message DropClusterReplicaV3 { + string cluster_id = 1; + string cluster_name = 2; + StringWrapper replica_id = 3; + string replica_name = 4; + CreateOrDropClusterReplicaReasonV1 reason = 5; + SchedulingDecisionsWithReasonsV2 scheduling_policies = 6; + } + + message CreateOrDropClusterReplicaReasonV1 { + oneof reason { + Empty Manual = 1; + Empty Schedule = 2; + Empty System = 3; + } + } + + message SchedulingDecisionsWithReasonsV1 { + RefreshDecisionWithReasonV1 on_refresh = 1; + } + + message SchedulingDecisionsWithReasonsV2 { + RefreshDecisionWithReasonV2 on_refresh = 1; + } + + message RefreshDecisionWithReasonV1 { + oneof decision { + Empty On = 1; + Empty Off = 2; + } + repeated string objects_needing_refresh = 3; + string rehydration_time_estimate = 4; + } + + message RefreshDecisionWithReasonV2 { + oneof decision { + Empty On = 1; + Empty Off = 2; + } + repeated string objects_needing_refresh = 3; + repeated string objects_needing_compaction = 5; + string rehydration_time_estimate = 4; + } + + message CreateSourceSinkV1 { + string id = 1; + FullNameV1 name = 2; + StringWrapper size = 3; + } + + message CreateSourceSinkV2 { + string id = 1; + FullNameV1 name = 2; + StringWrapper size = 3; + string external_type = 4; + } + + message CreateSourceSinkV3 { + string id = 1; + FullNameV1 name = 2; + string external_type = 3; + } + + message CreateSourceSinkV4 { + string id = 1; + StringWrapper cluster_id = 2; + FullNameV1 name = 3; + string external_type = 4; + } + + message CreateIndexV1 { + string id = 1; + string cluster_id = 2; + FullNameV1 name = 3; + } + + message CreateMaterializedViewV1 { + string id = 1; + string cluster_id = 2; + FullNameV1 name = 3; + } + + message AlterSourceSinkV1 { + string id = 1; + FullNameV1 name = 2; + StringWrapper old_size = 3; + StringWrapper new_size = 4; + } + + message AlterSetClusterV1 { + string id = 1; + FullNameV1 name = 2; + StringWrapper old_cluster = 3; + StringWrapper new_cluster = 4; + } + + message GrantRoleV1 { + string role_id = 1; + string member_id = 2; + string grantor_id = 3; + } + + message GrantRoleV2 { + string role_id = 1; + string member_id = 2; + string grantor_id = 3; + string executed_by = 4; + } + + message RevokeRoleV1 { + string role_id = 1; + string member_id = 2; + } + + message RevokeRoleV2 { + string role_id = 1; + string member_id = 2; + string grantor_id = 3; + string executed_by = 4; + } + + message UpdatePrivilegeV1 { + string object_id = 1; + string grantee_id = 2; + string grantor_id = 3; + string privileges = 4; + } + + message AlterDefaultPrivilegeV1 { + string role_id = 1; + StringWrapper database_id = 2; + StringWrapper schema_id = 3; + string grantee_id = 4; + string privileges = 5; + } + + message UpdateOwnerV1 { + string object_id = 1; + string old_owner_id = 2; + string new_owner_id = 3; + } + + message SchemaV1 { + string id = 1; + string name = 2; + string database_name = 3; + } + + message SchemaV2 { + string id = 1; + string name = 2; + StringWrapper database_name = 3; + } + + message RenameSchemaV1 { + string id = 1; + optional string database_name = 2; + string old_name = 3; + string new_name = 4; + } + + message UpdateItemV1 { + string id = 1; + FullNameV1 name = 2; + } + + message AlterRetainHistoryV1 { + string id = 1; + optional string old_history = 2; + optional string new_history = 3; + } + + message ToNewIdV1 { + string id = 1; + string new_id = 2; + } + + message FromPreviousIdV1 { + string id = 1; + string previous_id = 2; + } + + message SetV1 { + string name = 1; + optional string value = 2; + } + + message RotateKeysV1 { + string id = 1; + string name = 2; + } + + uint64 id = 1; + EventType event_type = 2; + ObjectType object_type = 3; + StringWrapper user = 4; + EpochMillis occurred_at = 5; + + // next-id: 40 + oneof details { + CreateClusterReplicaV1 create_cluster_replica_v1 = 6; + CreateClusterReplicaV2 create_cluster_replica_v2 = 33; + CreateClusterReplicaV3 create_cluster_replica_v3 = 41; + DropClusterReplicaV1 drop_cluster_replica_v1 = 7; + DropClusterReplicaV2 drop_cluster_replica_v2 = 34; + DropClusterReplicaV3 drop_cluster_replica_v3 = 42; + CreateSourceSinkV1 create_source_sink_v1 = 8; + CreateSourceSinkV2 create_source_sink_v2 = 9; + AlterSourceSinkV1 alter_source_sink_v1 = 10; + AlterSetClusterV1 alter_set_cluster_v1 = 25; + GrantRoleV1 grant_role_v1 = 11; + GrantRoleV2 grant_role_v2 = 12; + RevokeRoleV1 revoke_role_v1 = 13; + RevokeRoleV2 revoke_role_v2 = 14; + UpdatePrivilegeV1 update_privilege_v1 = 22; + AlterDefaultPrivilegeV1 alter_default_privilege_v1 = 23; + UpdateOwnerV1 update_owner_v1 = 24; + IdFullNameV1 id_full_name_v1 = 15; + RenameClusterV1 rename_cluster_v1 = 20; + RenameClusterReplicaV1 rename_cluster_replica_v1 = 21; + RenameItemV1 rename_item_v1 = 16; + IdNameV1 id_name_v1 = 17; + SchemaV1 schema_v1 = 18; + SchemaV2 schema_v2 = 19; + RenameSchemaV1 rename_schema_v1 = 27; + UpdateItemV1 update_item_v1 = 26; + CreateSourceSinkV3 create_source_sink_v3 = 29; + AlterRetainHistoryV1 alter_retain_history_v1 = 30; + ToNewIdV1 to_new_id_v1 = 31; + FromPreviousIdV1 from_previous_id_v1 = 32; + SetV1 set_v1 = 35; + Empty reset_all_v1 = 36; + RotateKeysV1 rotate_keys_v1 = 37; + CreateSourceSinkV4 create_source_sink_v4 = 38; + CreateIndexV1 create_index_v1 = 39; + CreateMaterializedViewV1 create_materialized_view_v1 = 40; + } +} + +// Wrapper of key-values used by the persist implementation to serialize the catalog. +message StateUpdateKind { + reserved "Epoch"; + + message AuditLog { + AuditLogKey key = 1; + } + + message Cluster { + ClusterKey key = 1; + ClusterValue value = 2; + } + + message ClusterReplica { + ClusterReplicaKey key = 1; + ClusterReplicaValue value = 2; + } + + message Comment { + CommentKey key = 1; + CommentValue value = 2; + } + + message Config { + ConfigKey key = 1; + ConfigValue value = 2; + } + + message Database { + DatabaseKey key = 1; + DatabaseValue value = 2; + } + + message DefaultPrivileges { + DefaultPrivilegesKey key = 1; + DefaultPrivilegesValue value = 2; + } + + message FenceToken { + uint64 deploy_generation = 1; + int64 epoch = 2; + } + + message IdAlloc { + IdAllocKey key = 1; + IdAllocValue value = 2; + } + + message ClusterIntrospectionSourceIndex { + ClusterIntrospectionSourceIndexKey key = 1; + ClusterIntrospectionSourceIndexValue value = 2; + } + + message Item { + ItemKey key = 1; + ItemValue value = 2; + } + + message Role { + RoleKey key = 1; + RoleValue value = 2; + } + + message NetworkPolicy { + NetworkPolicyKey key = 1; + NetworkPolicyValue value = 2; + } + + message Schema { + SchemaKey key = 1; + SchemaValue value = 2; + } + + message Setting { + SettingKey key = 1; + SettingValue value = 2; + } + + message ServerConfiguration { + ServerConfigurationKey key = 1; + ServerConfigurationValue value = 2; + } + + message SourceReferences { + SourceReferencesKey key = 1; + SourceReferencesValue value = 2; + } + + message GidMapping { + GidMappingKey key = 1; + GidMappingValue value = 2; + } + + message SystemPrivileges { + SystemPrivilegesKey key = 1; + SystemPrivilegesValue value = 2; + } + + message StorageCollectionMetadata { + StorageCollectionMetadataKey key = 1; + StorageCollectionMetadataValue value = 2; + } + + message UnfinalizedShard { + UnfinalizedShardKey key = 1; + } + + message TxnWalShard { + TxnWalShardValue value = 1; + } + + reserved 15; + reserved "storage_usage"; + reserved 19; + reserved "timestamp"; + reserved 22; + reserved "persist_txn_shard"; + reserved 8; + reserved "epoch"; + + oneof kind { + AuditLog audit_log = 1; + Cluster cluster = 2; + ClusterReplica cluster_replica = 3; + Comment comment = 4; + Config config = 5; + Database database = 6; + DefaultPrivileges default_privileges = 7; + IdAlloc id_alloc = 9; + ClusterIntrospectionSourceIndex cluster_introspection_source_index = 10; + Item item = 11; + Role role = 12; + Schema schema = 13; + Setting setting = 14; + ServerConfiguration server_configuration = 16; + GidMapping gid_mapping = 17; + SystemPrivileges system_privileges = 18; + StorageCollectionMetadata storage_collection_metadata = 20; + UnfinalizedShard unfinalized_shard = 21; + TxnWalShard txn_wal_shard = 23; + SourceReferences source_references = 24; + FenceToken fence_token = 25; + NetworkPolicy network_policy = 26; + } +} diff --git a/src/catalog/src/builtin.rs b/src/catalog/src/builtin.rs index 963897ec0c76b..c11b4dbde547e 100644 --- a/src/catalog/src/builtin.rs +++ b/src/catalog/src/builtin.rs @@ -3016,7 +3016,7 @@ pub static MZ_STATEMENT_EXECUTION_HISTORY_REDACTED: LazyLock = Lazy SELECT id, prepared_statement_id, sample_rate, cluster_id, application_name, cluster_name, database_name, search_path, transaction_isolation, execution_timestamp, transaction_id, transient_index_id, mz_version, began_at, finished_at, finished_status, -rows_returned, execution_strategy +result_size, rows_returned, execution_strategy FROM mz_internal.mz_statement_execution_history", access: vec![SUPPORT_SELECT, ANALYTICS_SELECT, MONITOR_REDACTED_SELECT, MONITOR_SELECT], } @@ -3113,7 +3113,7 @@ pub static MZ_ACTIVITY_LOG_THINNED: LazyLock = LazyLock::new(|| { sql: " SELECT mseh.id AS execution_id, sample_rate, cluster_id, application_name, cluster_name, database_name, search_path, transaction_isolation, execution_timestamp, transient_index_id, params, mz_version, began_at, finished_at, finished_status, -error_message, rows_returned, execution_strategy, transaction_id, +error_message, result_size, rows_returned, execution_strategy, transaction_id, mpsh.id AS prepared_statement_id, sql_hash, mpsh.name AS prepared_statement_name, mpsh.session_id, prepared_at, statement_type, throttled_count, initial_application_name, authenticated_user @@ -3161,7 +3161,7 @@ pub static MZ_RECENT_ACTIVITY_LOG_REDACTED: LazyLock = LazyLock::ne sql: "SELECT mralt.execution_id, mralt.sample_rate, mralt.cluster_id, mralt.application_name, mralt.cluster_name, mralt.database_name, mralt.search_path, mralt.transaction_isolation, mralt.execution_timestamp, mralt.transient_index_id, mralt.params, mralt.mz_version, mralt.began_at, mralt.finished_at, - mralt.finished_status, mralt.rows_returned, mralt.execution_strategy, mralt.transaction_id, + mralt.finished_status, mralt.result_size, mralt.rows_returned, mralt.execution_strategy, mralt.transaction_id, mralt.prepared_statement_id, mralt.sql_hash, mralt.prepared_statement_name, mralt.session_id, mralt.prepared_at, mralt.statement_type, mralt.throttled_count, mralt.initial_application_name, mralt.authenticated_user, @@ -9136,6 +9136,7 @@ pub static BUILTINS_STATIC: LazyLock>> = LazyLock::ne Builtin::Log(&MZ_DATAFLOW_ADDRESSES_PER_WORKER), Builtin::Log(&MZ_DATAFLOW_OPERATOR_REACHABILITY_RAW), Builtin::Log(&MZ_COMPUTE_EXPORTS_PER_WORKER), + Builtin::Log(&MZ_COMPUTE_DATAFLOW_GLOBAL_IDS_PER_WORKER), Builtin::Log(&MZ_MESSAGE_COUNTS_RECEIVED_RAW), Builtin::Log(&MZ_MESSAGE_COUNTS_SENT_RAW), Builtin::Log(&MZ_MESSAGE_BATCH_COUNTS_RECEIVED_RAW), @@ -9231,6 +9232,7 @@ pub static BUILTINS_STATIC: LazyLock>> = LazyLock::ne Builtin::View(&MZ_DATAFLOW_ADDRESSES), Builtin::View(&MZ_DATAFLOW_CHANNELS), Builtin::View(&MZ_DATAFLOW_OPERATORS), + Builtin::View(&MZ_DATAFLOW_GLOBAL_IDS), Builtin::View(&MZ_DATAFLOW_OPERATOR_DATAFLOWS_PER_WORKER), Builtin::View(&MZ_DATAFLOW_OPERATOR_DATAFLOWS), Builtin::View(&MZ_OBJECT_TRANSITIVE_DEPENDENCIES), @@ -9410,6 +9412,8 @@ pub static BUILTINS_STATIC: LazyLock>> = LazyLock::ne Builtin::View(&MZ_COMPUTE_ERROR_COUNTS), Builtin::Source(&MZ_COMPUTE_ERROR_COUNTS_RAW_UNIFIED), Builtin::Source(&MZ_COMPUTE_HYDRATION_TIMES), + Builtin::Log(&MZ_COMPUTE_LIR_MAPPING_PER_WORKER), + Builtin::View(&MZ_LIR_MAPPING), Builtin::View(&MZ_COMPUTE_OPERATOR_HYDRATION_STATUSES), Builtin::Source(&MZ_CLUSTER_REPLICA_FRONTIERS), Builtin::View(&MZ_COMPUTE_HYDRATION_STATUSES), diff --git a/src/catalog/src/durable.rs b/src/catalog/src/durable.rs index 91298d251c6c9..123e92a1604db 100644 --- a/src/catalog/src/durable.rs +++ b/src/catalog/src/durable.rs @@ -16,13 +16,13 @@ use std::time::Duration; use async_trait::async_trait; use mz_audit_log::VersionedEvent; -use uuid::Uuid; - use mz_controller_types::ClusterId; use mz_ore::collections::CollectionExt; use mz_ore::metrics::MetricsRegistry; use mz_persist_client::PersistClient; use mz_repr::{CatalogItemId, GlobalId}; +use mz_sql::catalog::CatalogError as SqlCatalogError; +use uuid::Uuid; use crate::config::ClusterReplicaSizeMap; use crate::durable::debug::{DebugCatalogState, Trace}; @@ -99,11 +99,19 @@ pub trait OpenableDurableCatalogState: Debug + Send { /// - Catalog migrations fail. /// /// `initial_ts` is used as the initial timestamp for new environments. + /// + /// Also returns a handle to a thread that is deserializing all of the audit logs. async fn open_savepoint( mut self: Box, initial_ts: Timestamp, bootstrap_args: &BootstrapArgs, - ) -> Result, CatalogError>; + ) -> Result< + ( + Box, + std::thread::JoinHandle>, + ), + CatalogError, + >; /// Opens the catalog in read only mode. All mutating methods /// will return an error. @@ -120,11 +128,19 @@ pub trait OpenableDurableCatalogState: Debug + Send { /// needed. /// /// `initial_ts` is used as the initial timestamp for new environments. + /// + /// Also returns a handle to a thread that is deserializing all of the audit logs. async fn open( mut self: Box, initial_ts: Timestamp, bootstrap_args: &BootstrapArgs, - ) -> Result, CatalogError>; + ) -> Result< + ( + Box, + std::thread::JoinHandle>, + ), + CatalogError, + >; /// Opens the catalog for manual editing of the underlying data. This is helpful for /// fixing a corrupt catalog. @@ -202,6 +218,9 @@ pub trait ReadOnlyDurableCatalogState: Debug + Send { /// Politely releases all external resources that can only be released in an async context. async fn expire(self: Box); + /// Returns true if the system bootstrapping process is complete, false otherwise. + fn is_bootstrap_complete(&self) -> bool; + /// Get all audit log events. /// /// Results are guaranteed to be sorted by ID. @@ -273,6 +292,9 @@ pub trait DurableCatalogState: ReadOnlyDurableCatalogState { /// Returns true if the catalog is opened is savepoint mode, false otherwise. fn is_savepoint(&self) -> bool; + /// Marks the bootstrap process as complete. + fn mark_bootstrap_complete(&mut self); + /// Creates a new durable catalog state transaction. async fn transaction(&mut self) -> Result; @@ -313,23 +335,6 @@ pub trait DurableCatalogState: ReadOnlyDurableCatalogState { Ok(ids) } - /// Allocates and returns `amount` system [`CatalogItemId`]s. - /// - /// See [`Self::commit_transaction`] for details on `commit_ts`. - async fn allocate_system_ids( - &mut self, - amount: u64, - commit_ts: Timestamp, - ) -> Result, CatalogError> { - let id = self - .allocate_id(SYSTEM_ITEM_ALLOC_KEY, amount, commit_ts) - .await?; - Ok(id - .into_iter() - .map(|id| (CatalogItemId::System(id), GlobalId::System(id))) - .collect()) - } - /// Allocates and returns both a user [`CatalogItemId`] and [`GlobalId`]. /// /// See [`Self::commit_transaction`] for details on `commit_ts`. @@ -351,9 +356,9 @@ pub trait DurableCatalogState: ReadOnlyDurableCatalogState { ) -> Result { let id = self .allocate_id(USER_CLUSTER_ID_ALLOC_KEY, 1, commit_ts) - .await?; - let id = id.into_element(); - Ok(ClusterId::User(id)) + .await? + .into_element(); + Ok(ClusterId::user(id).ok_or(SqlCatalogError::IdExhaustion)?) } } diff --git a/src/catalog/src/durable/objects.rs b/src/catalog/src/durable/objects.rs index 5852b1042082c..e6bb869ce84fb 100644 --- a/src/catalog/src/durable/objects.rs +++ b/src/catalog/src/durable/objects.rs @@ -338,11 +338,11 @@ impl DurableType for IntrospectionSourceIndex { catalog_id: self .item_id .try_into() - .expect("cluster introspection source index mapping must be a System ID"), + .expect("cluster introspection source index mapping must be an Introspection Source Index ID"), global_id: self .index_id .try_into() - .expect("cluster introspection source index mapping must be a System ID"), + .expect("cluster introspection source index mapping must be a Introspection Source Index ID"), oid: self.oid, }, ) @@ -603,6 +603,7 @@ impl TryFrom for SystemCatalogItemId { fn try_from(val: CatalogItemId) -> Result { match val { CatalogItemId::System(x) => Ok(SystemCatalogItemId(x)), + CatalogItemId::IntrospectionSourceIndex(_) => Err("introspection_source_index"), CatalogItemId::User(_) => Err("user"), CatalogItemId::Transient(_) => Err("transient"), } @@ -615,6 +616,31 @@ impl From for CatalogItemId { } } +/// A newtype wrapper for [`CatalogItemId`] that is only for the "introspection source index" namespace. +#[derive(Debug, Copy, Clone, Ord, PartialOrd, PartialEq, Eq)] +pub struct IntrospectionSourceIndexCatalogItemId(u64); + +impl TryFrom for IntrospectionSourceIndexCatalogItemId { + type Error = &'static str; + + fn try_from(val: CatalogItemId) -> Result { + match val { + CatalogItemId::System(_) => Err("system"), + CatalogItemId::IntrospectionSourceIndex(x) => { + Ok(IntrospectionSourceIndexCatalogItemId(x)) + } + CatalogItemId::User(_) => Err("user"), + CatalogItemId::Transient(_) => Err("transient"), + } + } +} + +impl From for CatalogItemId { + fn from(val: IntrospectionSourceIndexCatalogItemId) -> Self { + CatalogItemId::IntrospectionSourceIndex(val.0) + } +} + /// A newtype wrapper for [`GlobalId`] that is only for the "system" namespace. #[derive(Debug, Copy, Clone, Ord, PartialOrd, PartialEq, Eq)] pub struct SystemGlobalId(u64); @@ -625,6 +651,7 @@ impl TryFrom for SystemGlobalId { fn try_from(val: GlobalId) -> Result { match val { GlobalId::System(x) => Ok(SystemGlobalId(x)), + GlobalId::IntrospectionSourceIndex(_) => Err("introspection_source_index"), GlobalId::User(_) => Err("user"), GlobalId::Transient(_) => Err("transient"), GlobalId::Explain => Err("explain"), @@ -638,6 +665,30 @@ impl From for GlobalId { } } +/// A newtype wrapper for [`GlobalId`] that is only for the "introspection source index" namespace. +#[derive(Debug, Copy, Clone, Ord, PartialOrd, PartialEq, Eq)] +pub struct IntrospectionSourceIndexGlobalId(u64); + +impl TryFrom for IntrospectionSourceIndexGlobalId { + type Error = &'static str; + + fn try_from(val: GlobalId) -> Result { + match val { + GlobalId::System(_) => Err("system"), + GlobalId::IntrospectionSourceIndex(x) => Ok(IntrospectionSourceIndexGlobalId(x)), + GlobalId::User(_) => Err("user"), + GlobalId::Transient(_) => Err("transient"), + GlobalId::Explain => Err("explain"), + } + } +} + +impl From for GlobalId { + fn from(val: IntrospectionSourceIndexGlobalId) -> Self { + GlobalId::IntrospectionSourceIndex(val.0) + } +} + #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)] pub struct SystemObjectDescription { pub schema_name: String, @@ -1164,8 +1215,8 @@ pub struct ClusterIntrospectionSourceIndexKey { #[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Ord)] pub struct ClusterIntrospectionSourceIndexValue { - pub(crate) catalog_id: SystemCatalogItemId, - pub(crate) global_id: SystemGlobalId, + pub(crate) catalog_id: IntrospectionSourceIndexCatalogItemId, + pub(crate) global_id: IntrospectionSourceIndexGlobalId, pub(crate) oid: u32, } diff --git a/src/catalog/src/durable/objects/serialization.rs b/src/catalog/src/durable/objects/serialization.rs index 323c33e05ba3d..329aab38a6302 100644 --- a/src/catalog/src/durable/objects/serialization.rs +++ b/src/catalog/src/durable/objects/serialization.rs @@ -50,8 +50,9 @@ use crate::durable::objects::{ AuditLogKey, ClusterIntrospectionSourceIndexKey, ClusterIntrospectionSourceIndexValue, ClusterKey, ClusterReplicaKey, ClusterReplicaValue, ClusterValue, CommentKey, CommentValue, ConfigKey, ConfigValue, DatabaseKey, DatabaseValue, DefaultPrivilegesKey, - DefaultPrivilegesValue, GidMappingKey, GidMappingValue, IdAllocKey, IdAllocValue, ItemKey, - ItemValue, NetworkPolicyKey, NetworkPolicyValue, RoleKey, RoleValue, SchemaKey, SchemaValue, + DefaultPrivilegesValue, GidMappingKey, GidMappingValue, IdAllocKey, IdAllocValue, + IntrospectionSourceIndexCatalogItemId, IntrospectionSourceIndexGlobalId, ItemKey, ItemValue, + NetworkPolicyKey, NetworkPolicyValue, RoleKey, RoleValue, SchemaKey, SchemaValue, ServerConfigurationKey, ServerConfigurationValue, SettingKey, SettingValue, SourceReference, SourceReferencesKey, SourceReferencesValue, StorageCollectionMetadataKey, StorageCollectionMetadataValue, SystemCatalogItemId, SystemGlobalId, SystemPrivilegesKey, @@ -419,7 +420,7 @@ impl RustType proto: proto::ClusterIntrospectionSourceIndexValue, ) -> Result { Ok(ClusterIntrospectionSourceIndexValue { - catalog_id: SystemCatalogItemId(proto.index_id), + catalog_id: IntrospectionSourceIndexCatalogItemId(proto.index_id), global_id: proto .global_id .into_rust_if_some("ClusterIntrospectionSourceIndexValue::global_id")?, @@ -1055,8 +1056,18 @@ impl RustType for StorageInstanceId { .value .ok_or_else(|| TryFromProtoError::missing_field("ClusterId::value"))?; let id = match value { - proto::cluster_id::Value::User(id) => StorageInstanceId::User(id), - proto::cluster_id::Value::System(id) => StorageInstanceId::System(id), + proto::cluster_id::Value::User(id) => StorageInstanceId::user(id).ok_or_else(|| { + TryFromProtoError::InvalidPersistState(format!( + "{id} is not a valid StorageInstanceId" + )) + })?, + proto::cluster_id::Value::System(id) => { + StorageInstanceId::system(id).ok_or_else(|| { + TryFromProtoError::InvalidPersistState(format!( + "{id} is not a valid StorageInstanceId" + )) + })? + } }; Ok(id) } @@ -1620,6 +1631,9 @@ impl RustType for CatalogItemId { proto::CatalogItemId { value: Some(match self { CatalogItemId::System(x) => proto::catalog_item_id::Value::System(*x), + CatalogItemId::IntrospectionSourceIndex(x) => { + proto::catalog_item_id::Value::IntrospectionSourceIndex(*x) + } CatalogItemId::User(x) => proto::catalog_item_id::Value::User(*x), CatalogItemId::Transient(x) => proto::catalog_item_id::Value::Transient(*x), }), @@ -1629,6 +1643,9 @@ impl RustType for CatalogItemId { fn from_proto(proto: proto::CatalogItemId) -> Result { match proto.value { Some(proto::catalog_item_id::Value::System(x)) => Ok(CatalogItemId::System(x)), + Some(proto::catalog_item_id::Value::IntrospectionSourceIndex(x)) => { + Ok(CatalogItemId::IntrospectionSourceIndex(x)) + } Some(proto::catalog_item_id::Value::User(x)) => Ok(CatalogItemId::User(x)), Some(proto::catalog_item_id::Value::Transient(x)) => Ok(CatalogItemId::Transient(x)), None => Err(TryFromProtoError::missing_field("CatalogItemId::kind")), @@ -1646,11 +1663,28 @@ impl RustType for SystemCatalogItemId { } } +impl RustType + for IntrospectionSourceIndexCatalogItemId +{ + fn into_proto(&self) -> proto::IntrospectionSourceIndexCatalogItemId { + proto::IntrospectionSourceIndexCatalogItemId { value: self.0 } + } + + fn from_proto( + proto: proto::IntrospectionSourceIndexCatalogItemId, + ) -> Result { + Ok(IntrospectionSourceIndexCatalogItemId(proto.value)) + } +} + impl RustType for GlobalId { fn into_proto(&self) -> proto::GlobalId { proto::GlobalId { value: Some(match self { GlobalId::System(x) => proto::global_id::Value::System(*x), + GlobalId::IntrospectionSourceIndex(x) => { + proto::global_id::Value::IntrospectionSourceIndex(*x) + } GlobalId::User(x) => proto::global_id::Value::User(*x), GlobalId::Transient(x) => proto::global_id::Value::Transient(*x), GlobalId::Explain => proto::global_id::Value::Explain(Default::default()), @@ -1661,6 +1695,9 @@ impl RustType for GlobalId { fn from_proto(proto: proto::GlobalId) -> Result { match proto.value { Some(proto::global_id::Value::System(x)) => Ok(GlobalId::System(x)), + Some(proto::global_id::Value::IntrospectionSourceIndex(x)) => { + Ok(GlobalId::IntrospectionSourceIndex(x)) + } Some(proto::global_id::Value::User(x)) => Ok(GlobalId::User(x)), Some(proto::global_id::Value::Transient(x)) => Ok(GlobalId::Transient(x)), Some(proto::global_id::Value::Explain(_)) => Ok(GlobalId::Explain), @@ -1679,6 +1716,18 @@ impl RustType for SystemGlobalId { } } +impl RustType for IntrospectionSourceIndexGlobalId { + fn into_proto(&self) -> proto::IntrospectionSourceIndexGlobalId { + proto::IntrospectionSourceIndexGlobalId { value: self.0 } + } + + fn from_proto( + proto: proto::IntrospectionSourceIndexGlobalId, + ) -> Result { + Ok(IntrospectionSourceIndexGlobalId(proto.value)) + } +} + impl RustType for VersionedEvent { fn into_proto(&self) -> proto::audit_log_key::Event { match self { diff --git a/src/catalog/src/durable/objects/state_update.rs b/src/catalog/src/durable/objects/state_update.rs index 2a39c72c8aff4..e87cee2f4a02e 100644 --- a/src/catalog/src/durable/objects/state_update.rs +++ b/src/catalog/src/durable/objects/state_update.rs @@ -332,6 +332,7 @@ impl StateUpdateKindJson { value: String::new(), }, ), + StateUpdateKind::AuditLog(proto::AuditLogKey { event: None }, ()), ] .into_iter() .map(|kind| { @@ -342,6 +343,18 @@ impl StateUpdateKindJson { }); DESERIALIZABLE_KINDS.contains(self.kind()) } + + /// Returns true if this is an audit log update. Otherwise, returns false. + pub(crate) fn is_audit_log(&self) -> bool { + // Construct a fake audit log so we can extract exactly what the kind field will serialize + // as. + static AUDIT_LOG_KIND: LazyLock = LazyLock::new(|| { + let audit_log = StateUpdateKind::AuditLog(proto::AuditLogKey { event: None }, ()); + let json_kind: StateUpdateKindJson = audit_log.into(); + json_kind.kind().to_string() + }); + &*AUDIT_LOG_KIND == self.kind() + } } /// Version of [`StateUpdateKind`] that is stored directly in persist. diff --git a/src/catalog/src/durable/persist.rs b/src/catalog/src/durable/persist.rs index 3bf88881afb55..c446e01477def 100644 --- a/src/catalog/src/durable/persist.rs +++ b/src/catalog/src/durable/persist.rs @@ -381,6 +381,8 @@ pub(crate) struct PersistHandle> { fenceable_token: FenceableToken, /// The semantic version of the current binary. catalog_content_version: semver::Version, + /// Flag to indicate if bootstrap is complete. + bootstrap_complete: bool, /// Metrics for the persist catalog. metrics: Arc, } @@ -904,7 +906,7 @@ impl ApplyUpdate for UnopenedCatalogStateInner { current_fence_token: &mut FenceableToken, _metrics: &Arc, ) -> Result>, FenceError> { - if update.kind.is_always_deserializable() { + if !update.kind.is_audit_log() && update.kind.is_always_deserializable() { let kind = TryInto::try_into(&update.kind).expect("kind is known to be deserializable"); match (kind, update.diff) { (StateUpdateKind::Config(key, value), 1) => { @@ -1072,6 +1074,7 @@ impl UnopenedPersistCatalogState { upper, fenceable_token: FenceableToken::new(deploy_generation), catalog_content_version: version, + bootstrap_complete: false, metrics, }; // If the snapshot is not consolidated, and we see multiple epoch values while applying the @@ -1115,7 +1118,13 @@ impl UnopenedPersistCatalogState { mode: Mode, initial_ts: Timestamp, bootstrap_args: &BootstrapArgs, - ) -> Result, CatalogError> { + ) -> Result< + ( + Box, + std::thread::JoinHandle>, + ), + CatalogError, + > { // It would be nice to use `initial_ts` here, but it comes from the system clock, not the // timestamp oracle. let mut commit_ts = self.upper; @@ -1195,6 +1204,35 @@ impl UnopenedPersistCatalogState { } soft_assert_ne_or_log!(self.upper, Timestamp::minimum()); + // Remove all audit log entries. + let (audit_logs, snapshot): (Vec<_>, Vec<_>) = self + .snapshot + .into_iter() + .partition(|(update, _, _)| update.is_audit_log()); + self.snapshot = snapshot; + + // Create thread to deserialize audit logs. + let audit_log_handle = std::thread::spawn(move || { + let updates: Vec<_> = audit_logs + .into_iter() + .map(|(kind, ts, diff)| { + assert_eq!( + diff, 1, + "audit log is append only: ({kind:?}, {ts:?}, {diff:?})" + ); + let diff = memory::objects::StateDiff::Addition; + + let kind = TryIntoStateUpdateKind::try_into(kind).expect("kind decoding error"); + let kind: Option = (&kind) + .try_into() + .expect("invalid persisted update: {update:#?}"); + let kind = kind.expect("audit log always produces im-memory updates"); + memory::objects::StateUpdate { kind, ts, diff } + }) + .collect(); + updates + }); + // Perform data migrations. if is_initialized && !read_only { commit_ts = upgrade(&mut self, commit_ts).await?; @@ -1218,6 +1256,7 @@ impl UnopenedPersistCatalogState { snapshot: Vec::new(), update_applier: CatalogStateInner::new(), catalog_content_version: self.catalog_content_version, + bootstrap_complete: false, metrics: self.metrics, }; catalog.metrics.collection_entries.reset(); @@ -1299,7 +1338,7 @@ impl UnopenedPersistCatalogState { }); } - Ok(Box::new(catalog)) + Ok((Box::new(catalog), audit_log_handle)) } /// Reports if the catalog state has been initialized. @@ -1363,7 +1402,13 @@ impl OpenableDurableCatalogState for UnopenedPersistCatalogState { mut self: Box, initial_ts: Timestamp, bootstrap_args: &BootstrapArgs, - ) -> Result, CatalogError> { + ) -> Result< + ( + Box, + std::thread::JoinHandle>, + ), + CatalogError, + > { self.open_inner(Mode::Savepoint, initial_ts, bootstrap_args) .boxed() .await @@ -1377,6 +1422,7 @@ impl OpenableDurableCatalogState for UnopenedPersistCatalogState { self.open_inner(Mode::Readonly, EpochMillis::MIN.into(), bootstrap_args) .boxed() .await + .map(|(catalog, _)| catalog) } #[mz_ore::instrument] @@ -1384,7 +1430,13 @@ impl OpenableDurableCatalogState for UnopenedPersistCatalogState { mut self: Box, initial_ts: Timestamp, bootstrap_args: &BootstrapArgs, - ) -> Result, CatalogError> { + ) -> Result< + ( + Box, + std::thread::JoinHandle>, + ), + CatalogError, + > { self.open_inner(Mode::Writable, initial_ts, bootstrap_args) .boxed() .await @@ -1577,6 +1629,10 @@ impl ReadOnlyDurableCatalogState for PersistCatalogState { self.expire().await } + fn is_bootstrap_complete(&self) -> bool { + self.bootstrap_complete + } + async fn get_audit_logs(&mut self) -> Result, CatalogError> { self.sync_to_current_upper().await?; let audit_logs: Vec<_> = self @@ -1680,6 +1736,10 @@ impl DurableCatalogState for PersistCatalogState { matches!(self.mode, Mode::Savepoint) } + fn mark_bootstrap_complete(&mut self) { + self.bootstrap_complete = true; + } + #[mz_ore::instrument(level = "debug")] async fn transaction(&mut self) -> Result { self.metrics.transactions_started.inc(); diff --git a/src/catalog/src/durable/persist/tests.rs b/src/catalog/src/durable/persist/tests.rs index 5274664dda274..cb1111b7863a5 100644 --- a/src/catalog/src/durable/persist/tests.rs +++ b/src/catalog/src/durable/persist/tests.rs @@ -51,7 +51,8 @@ async fn test_upgrade_shard() { let _persist_state = persist_openable_state .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .expect("failed to open persist catalog"); + .expect("failed to open persist catalog") + .0; assert_eq!( Some(first_version.clone()), @@ -111,7 +112,8 @@ async fn test_upgrade_shard() { let _persist_state = persist_openable_state .open_savepoint(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .expect("failed to open savepoint persist catalog"); + .expect("failed to open savepoint persist catalog") + .0; assert_eq!( Some(first_version.clone()), @@ -140,7 +142,8 @@ async fn test_upgrade_shard() { let _persist_state = persist_openable_state .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .expect("failed to open readonly persist catalog"); + .expect("failed to open readonly persist catalog") + .0; assert_eq!( Some(second_version), @@ -180,7 +183,8 @@ async fn test_version_regression() { let _persist_state = persist_openable_state .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .expect("failed to open persist catalog"); + .expect("failed to open persist catalog") + .0; assert_eq!( Some(first_version.clone()), @@ -201,7 +205,8 @@ async fn test_version_regression() { let _persist_state = persist_openable_state .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .expect("failed to open readonly persist catalog"); + .expect("failed to open readonly persist catalog") + .0; assert_eq!( Some(second_version.clone()), diff --git a/src/catalog/src/durable/transaction.rs b/src/catalog/src/durable/transaction.rs index 93735d78ca2df..2d34e0ae5f7d1 100644 --- a/src/catalog/src/durable/transaction.rs +++ b/src/catalog/src/durable/transaction.rs @@ -15,6 +15,7 @@ use anyhow::anyhow; use derivative::Derivative; use itertools::Itertools; use mz_audit_log::VersionedEvent; +use mz_compute_client::logging::{ComputeLog, DifferentialLog, LogVariant, TimelyLog}; use mz_controller_types::{ClusterId, ReplicaId}; use mz_ore::cast::{u64_to_usize, usize_to_u64}; use mz_ore::collections::{CollectionExt, HashSet}; @@ -60,9 +61,9 @@ use crate::durable::{ CatalogError, DefaultPrivilege, DurableCatalogError, DurableCatalogState, NetworkPolicy, Snapshot, SystemConfiguration, AUDIT_LOG_ID_ALLOC_KEY, BUILTIN_MIGRATION_SHARD_KEY, CATALOG_CONTENT_VERSION_KEY, DATABASE_ID_ALLOC_KEY, EXPRESSION_CACHE_SHARD_KEY, OID_ALLOC_KEY, - SCHEMA_ID_ALLOC_KEY, STORAGE_USAGE_ID_ALLOC_KEY, SYSTEM_ITEM_ALLOC_KEY, - SYSTEM_REPLICA_ID_ALLOC_KEY, USER_ITEM_ALLOC_KEY, USER_NETWORK_POLICY_ID_ALLOC_KEY, - USER_REPLICA_ID_ALLOC_KEY, USER_ROLE_ID_ALLOC_KEY, + SCHEMA_ID_ALLOC_KEY, STORAGE_USAGE_ID_ALLOC_KEY, SYSTEM_CLUSTER_ID_ALLOC_KEY, + SYSTEM_ITEM_ALLOC_KEY, SYSTEM_REPLICA_ID_ALLOC_KEY, USER_ITEM_ALLOC_KEY, + USER_NETWORK_POLICY_ID_ALLOC_KEY, USER_REPLICA_ID_ALLOC_KEY, USER_ROLE_ID_ALLOC_KEY, }; use crate::memory::objects::{StateDiff, StateUpdate, StateUpdateKind}; @@ -393,7 +394,6 @@ impl<'a> Transaction<'a> { /// Panics if any introspection source id is not a system id pub fn insert_system_cluster( &mut self, - cluster_id: ClusterId, cluster_name: &str, introspection_source_indexes: Vec<(&'static BuiltinLog, CatalogItemId, GlobalId)>, privileges: Vec, @@ -401,6 +401,8 @@ impl<'a> Transaction<'a> { config: ClusterConfig, temporary_oids: &HashSet, ) -> Result<(), CatalogError> { + let cluster_id = self.get_and_increment_id(SYSTEM_CLUSTER_ID_ALLOC_KEY.to_string())?; + let cluster_id = ClusterId::system(cluster_id).ok_or(SqlCatalogError::IdExhaustion)?; self.insert_cluster( cluster_id, cluster_name, @@ -700,6 +702,11 @@ impl<'a> Transaction<'a> { key: String, amount: u64, ) -> Result, CatalogError> { + assert!( + key != SYSTEM_ITEM_ALLOC_KEY || !self.durable_catalog.is_bootstrap_complete(), + "system item IDs cannot be allocated outside of bootstrap" + ); + let current_id = self .id_allocator .items() @@ -727,6 +734,10 @@ impl<'a> Transaction<'a> { &mut self, amount: u64, ) -> Result, CatalogError> { + assert!( + !self.durable_catalog.is_bootstrap_complete(), + "we can only allocate system item IDs during bootstrap" + ); Ok(self .get_and_increment_id_by(SYSTEM_ITEM_ALLOC_KEY.to_string(), amount)? .into_iter() @@ -735,6 +746,96 @@ impl<'a> Transaction<'a> { .collect()) } + /// Allocates an ID for an introspection source index. These IDs are deterministically derived + /// from the `cluster_id` and `log_variant`. + /// + /// Introspection source indexes are a special edge case of items. They are considered system + /// items, but they are the only system item that can be created by the user at any time. All + /// other system items can only be created by the system during the startup of an upgrade. + /// + /// Furthermore, all other system item IDs are allocated deterministically in the same order + /// during startup. Therefore, all read-only `environmentd` processes during an upgrade will + /// allocate the same system IDs to the same items, and due to the way catalog fencing works, + /// only one of them can successfully write the IDs down to the catalog. This removes the need + /// for `environmentd` processes to coordinate system IDs allocated during read-only mode. + /// + /// Since introspection IDs can be allocated at any time, read-only instances would either need + /// to coordinate across processes when allocating a new ID or allocate them deterministically. + /// We opted to allocate the IDs deterministically to avoid the overhead of coordination. + /// + /// Introspection source index IDs are 64 bit integers, with the following format (not to + /// scale): + /// + /// ------------------------------------------------------------- + /// | Cluster ID Variant | Cluster ID Inner Value | Log Variant | + /// |--------------------|------------------------|-------------| + /// | 8-bits | 48-bits | 8-bits | + /// ------------------------------------------------------------- + /// + /// Cluster ID Variant: A unique number indicating the variant of cluster the index belongs + /// to. + /// Cluster ID Inner Value: A per variant unique number indicating the cluster the index + /// belongs to. + /// Log Variant: A unique number indicating the log variant this index is on. + pub fn allocate_introspection_source_index_id( + cluster_id: &ClusterId, + log_variant: LogVariant, + ) -> (CatalogItemId, GlobalId) { + let cluster_variant: u8 = match cluster_id { + ClusterId::System(_) => 1, + ClusterId::User(_) => 2, + }; + let cluster_id: u64 = cluster_id.inner_id(); + const CLUSTER_ID_MASK: u64 = 0xFFFF << 48; + assert_eq!( + CLUSTER_ID_MASK & cluster_id, + 0, + "invalid cluster ID: {cluster_id}" + ); + let log_variant: u8 = match log_variant { + LogVariant::Timely(TimelyLog::Operates) => 1, + LogVariant::Timely(TimelyLog::Channels) => 2, + LogVariant::Timely(TimelyLog::Elapsed) => 3, + LogVariant::Timely(TimelyLog::Histogram) => 4, + LogVariant::Timely(TimelyLog::Addresses) => 5, + LogVariant::Timely(TimelyLog::Parks) => 6, + LogVariant::Timely(TimelyLog::MessagesSent) => 7, + LogVariant::Timely(TimelyLog::MessagesReceived) => 8, + LogVariant::Timely(TimelyLog::Reachability) => 9, + LogVariant::Timely(TimelyLog::BatchesSent) => 10, + LogVariant::Timely(TimelyLog::BatchesReceived) => 11, + LogVariant::Differential(DifferentialLog::ArrangementBatches) => 12, + LogVariant::Differential(DifferentialLog::ArrangementRecords) => 13, + LogVariant::Differential(DifferentialLog::Sharing) => 14, + LogVariant::Differential(DifferentialLog::BatcherRecords) => 15, + LogVariant::Differential(DifferentialLog::BatcherSize) => 16, + LogVariant::Differential(DifferentialLog::BatcherCapacity) => 17, + LogVariant::Differential(DifferentialLog::BatcherAllocations) => 18, + LogVariant::Compute(ComputeLog::DataflowCurrent) => 19, + LogVariant::Compute(ComputeLog::FrontierCurrent) => 20, + LogVariant::Compute(ComputeLog::PeekCurrent) => 21, + LogVariant::Compute(ComputeLog::PeekDuration) => 22, + LogVariant::Compute(ComputeLog::ImportFrontierCurrent) => 23, + LogVariant::Compute(ComputeLog::ArrangementHeapSize) => 24, + LogVariant::Compute(ComputeLog::ArrangementHeapCapacity) => 25, + LogVariant::Compute(ComputeLog::ArrangementHeapAllocations) => 26, + LogVariant::Compute(ComputeLog::ShutdownDuration) => 27, + LogVariant::Compute(ComputeLog::ErrorCount) => 28, + LogVariant::Compute(ComputeLog::HydrationTime) => 29, + LogVariant::Compute(ComputeLog::LirMapping) => 30, + LogVariant::Compute(ComputeLog::DataflowGlobal) => 31, + }; + + let mut id: u64 = u64::from(cluster_variant) << 56; + id |= cluster_id << 8; + id |= u64::from(log_variant); + + ( + CatalogItemId::IntrospectionSourceIndex(id), + GlobalId::IntrospectionSourceIndex(id), + ) + } + pub fn allocate_user_item_ids( &mut self, amount: u64, @@ -3542,13 +3643,15 @@ mod tests { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; let mut savepoint_state = state_builder .unwrap_build() .await .open_savepoint(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; let initial_snapshot = savepoint_state.sync_to_current_updates().await.unwrap(); assert!(!initial_snapshot.is_empty()); @@ -3578,4 +3681,56 @@ mod tests { assert_eq!(db_owner, db.owner_id); assert_eq!(db_privileges, db.privileges); } + + #[mz_ore::test] + fn test_allocate_introspection_source_index_id() { + let cluster_variant: u8 = 0b0000_0001; + let cluster_id_inner: u64 = + 0b0000_0000_1100_0101_1100_0011_1010_1101_0000_1011_1111_1001_0110_1010; + let timely_messages_received_log_variant: u8 = 0b0000_1000; + + let cluster_id = ClusterId::System(cluster_id_inner); + let log_variant = LogVariant::Timely(TimelyLog::MessagesReceived); + + let introspection_source_index_id: u64 = + 0b0000_0001_1100_0101_1100_0011_1010_1101_0000_1011_1111_1001_0110_1010_0000_1000; + + // Sanity check that `introspection_source_index_id` contains `cluster_variant`. + { + let mut cluster_variant_mask = 0xFF << 56; + cluster_variant_mask &= introspection_source_index_id; + cluster_variant_mask >>= 56; + assert_eq!(cluster_variant_mask, u64::from(cluster_variant)); + } + + // Sanity check that `introspection_source_index_id` contains `cluster_id_inner`. + { + let mut cluster_id_inner_mask = 0xFFFF_FFFF_FFFF << 8; + cluster_id_inner_mask &= introspection_source_index_id; + cluster_id_inner_mask >>= 8; + assert_eq!(cluster_id_inner_mask, cluster_id_inner); + } + + // Sanity check that `introspection_source_index_id` contains `timely_messages_received_log_variant`. + { + let mut log_variant_mask = 0xFF; + log_variant_mask &= introspection_source_index_id; + assert_eq!( + log_variant_mask, + u64::from(timely_messages_received_log_variant) + ); + } + + let (catalog_item_id, global_id) = + Transaction::allocate_introspection_source_index_id(&cluster_id, log_variant); + + assert_eq!( + catalog_item_id, + CatalogItemId::IntrospectionSourceIndex(introspection_source_index_id) + ); + assert_eq!( + global_id, + GlobalId::IntrospectionSourceIndex(introspection_source_index_id) + ); + } } diff --git a/src/catalog/src/durable/upgrade.rs b/src/catalog/src/durable/upgrade.rs index 33ed769a08458..19c8ac1de21a8 100644 --- a/src/catalog/src/durable/upgrade.rs +++ b/src/catalog/src/durable/upgrade.rs @@ -20,6 +20,7 @@ //! - Config //! - Setting //! - FenceToken +//! - AuditLog //! //! When you want to make a change to the `Catalog` you need to follow these steps: //! @@ -57,8 +58,6 @@ mod tests; use mz_ore::{soft_assert_eq_or_log, soft_assert_ne_or_log}; use mz_repr::Diff; -use timely::progress::Timestamp as TimelyTimestamp; - use paste::paste; #[cfg(test)] use proptest::prelude::*; @@ -66,6 +65,7 @@ use proptest::prelude::*; use proptest::strategy::ValueTree; #[cfg(test)] use proptest_derive::Arbitrary; +use timely::progress::Timestamp as TimelyTimestamp; use crate::durable::initialize::USER_VERSION_KEY; use crate::durable::objects::serialization::proto; @@ -180,14 +180,14 @@ macro_rules! objects { } } -objects!(v67, v68, v69, v70, v71, v72); +objects!(v67, v68, v69, v70, v71, v72, v73); /// The current version of the `Catalog`. /// /// We will initialize new `Catalog`es with this version, and migrate existing `Catalog`es to this /// version. Whenever the `Catalog` changes, e.g. the protobufs we serialize in the `Catalog` /// change, we need to bump this version. -pub const CATALOG_VERSION: u64 = 72; +pub const CATALOG_VERSION: u64 = 73; /// The minimum `Catalog` version number that we support migrating from. /// @@ -204,6 +204,7 @@ mod v68_to_v69; mod v69_to_v70; mod v70_to_v71; mod v71_to_v72; +mod v72_to_v73; /// Describes a single action to take during a migration from `V1` to `V2`. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -328,6 +329,15 @@ async fn run_upgrade( ) .await } + 72 => { + run_versioned_upgrade( + unopened_catalog_state, + version, + commit_ts, + v72_to_v73::upgrade, + ) + .await + } // Up-to-date, no migration needed! CATALOG_VERSION => Ok((CATALOG_VERSION, commit_ts)), @@ -367,6 +377,12 @@ async fn run_versioned_upgrade, +) -> Vec> { + let mut migrations = Vec::new(); + + for update in snapshot { + match update.kind { + // Attempting to duplicate the introspection source index ID allocation logic in this + // file would be extremely cumbersome and error-prone. Instead, we delete all existing + // introspection source indexes and let the builtin migration code recreate them with + // the new correct IDs. + Some(v72::state_update_kind::Kind::ClusterIntrospectionSourceIndex(_)) => { + let migration = MigrationAction::Delete(update); + migrations.push(migration); + } + _ => {} + } + } + + migrations +} diff --git a/src/catalog/src/expr_cache.rs b/src/catalog/src/expr_cache.rs index 9f58045ca5e97..5017c4c009e1a 100644 --- a/src/catalog/src/expr_cache.rs +++ b/src/catalog/src/expr_cache.rs @@ -492,9 +492,9 @@ mod tests { impl ArbitraryTimeout { // Number of attempts to generate a value before panicking. The maximum time spent // generating a value is `GENERATE_ATTEMPTS` * `TIMEOUT_SECS`. - const GENERATE_ATTEMPTS: u64 = 6; + const GENERATE_ATTEMPTS: u64 = 10; // Amount of time in seconds before we give up trying to generate a single value. - const TIMEOUT_SECS: u64 = 5; + const TIMEOUT_SECS: u64 = 10; fn new() -> Self { Self { diff --git a/src/catalog/tests/debug.rs b/src/catalog/tests/debug.rs index ebaf05a086a43..80f13f6ac8b29 100644 --- a/src/catalog/tests/debug.rs +++ b/src/catalog/tests/debug.rs @@ -176,7 +176,8 @@ async fn test_debug<'a>(state_builder: TestCatalogStateBuilder) { let _ = openable_state1 .open(NOW_ZERO().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Check epoch let mut openable_state2 = state_builder.clone().unwrap_build().await; @@ -347,7 +348,8 @@ async fn test_debug_edit_fencing<'a>(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; let mut debug_state = state_builder .clone() @@ -401,7 +403,8 @@ async fn test_debug_edit_fencing<'a>(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Now debug state should be fenced. let err = debug_state @@ -440,7 +443,8 @@ async fn test_debug_delete_fencing<'a>(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Drain state updates. let _ = state.sync_to_current_updates().await; @@ -496,7 +500,8 @@ async fn test_debug_delete_fencing<'a>(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Now debug state should be fenced. let err = debug_state @@ -551,7 +556,8 @@ async fn test_concurrent_debugs(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; let state_handle = mz_ore::task::spawn(|| "state", async move { // Eventually this state should get fenced by the edit below. let err = run_state(&mut state).await.unwrap_err(); @@ -583,7 +589,8 @@ async fn test_concurrent_debugs(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; let configs = state.snapshot().await.unwrap().configs; assert_eq!(configs.get(&key).unwrap(), &value); @@ -619,6 +626,7 @@ async fn test_concurrent_debugs(state_builder: TestCatalogStateBuilder) { .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await .unwrap() + .0 .snapshot() .await .unwrap() diff --git a/src/catalog/tests/open.rs b/src/catalog/tests/open.rs index 88e56a6409b20..1cf244111d463 100644 --- a/src/catalog/tests/open.rs +++ b/src/catalog/tests/open.rs @@ -141,7 +141,8 @@ async fn test_is_initialized(state_builder: TestCatalogStateBuilder) { let state = openable_state1 .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; state.expire().await; let mut openable_state2 = state_builder.unwrap_build().await; @@ -185,7 +186,8 @@ async fn test_get_deployment_generation(state_builder: TestCatalogStateBuilder) let state = openable_state .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; state.expire().await; } @@ -243,7 +245,8 @@ async fn test_open_savepoint(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; assert_eq!(state.epoch(), Epoch::new(2).expect("known to be non-zero")); Box::new(state).expire().await; } @@ -256,7 +259,8 @@ async fn test_open_savepoint(state_builder: TestCatalogStateBuilder) { .await .open_savepoint(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Drain initial updates. let _ = state .sync_to_current_updates() @@ -361,7 +365,8 @@ async fn test_open_savepoint(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Write should not have persisted. let db = state .snapshot() @@ -406,7 +411,8 @@ async fn test_open_read_only(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Drain initial updates. let _ = state .sync_to_current_updates() @@ -481,7 +487,8 @@ async fn test_open(state_builder: TestCatalogStateBuilder) { // Use `NOW_ZERO` for consistent timestamps in the snapshots. .open(NOW_ZERO().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; assert_eq!(state.epoch(), Epoch::new(2).expect("known to be non-zero")); // Check initial snapshot. @@ -513,7 +520,8 @@ async fn test_open(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; assert_eq!(state.epoch(), Epoch::new(3).expect("known to be non-zero")); assert_eq!(state.snapshot().await.unwrap(), snapshot); @@ -528,7 +536,8 @@ async fn test_open(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; assert_eq!(state.epoch(), Epoch::new(4).expect("known to be non-zero")); assert_eq!(state.snapshot().await.unwrap(), snapshot); @@ -560,7 +569,8 @@ async fn test_unopened_deploy_generation_fencing(state_builder: TestCatalogState .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // drain catalog updates. let _ = state.sync_to_current_updates().await.unwrap(); let mut txn = state.transaction().await.unwrap(); @@ -589,7 +599,8 @@ async fn test_unopened_deploy_generation_fencing(state_builder: TestCatalogState .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Unopened catalog should be fenced now with a deploy generation fence. let err = openable_state @@ -659,7 +670,8 @@ async fn test_opened_epoch_fencing(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Open catalog, which will bump the epoch. let _state = state_builder @@ -668,7 +680,8 @@ async fn test_opened_epoch_fencing(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Opened catalog should be fenced now with an epoch fence. let err = state.snapshot().await.unwrap_err(); @@ -707,7 +720,8 @@ async fn test_opened_deploy_generation_fencing(state_builder: TestCatalogStateBu .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Open catalog, which will bump the epoch AND deploy generation. let _state = state_builder @@ -717,7 +731,8 @@ async fn test_opened_deploy_generation_fencing(state_builder: TestCatalogStateBu .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Opened catalog should be fenced now with an epoch fence. let err = state.snapshot().await.unwrap_err(); @@ -760,7 +775,8 @@ async fn test_fencing_during_write(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Drain updates. let _ = state.sync_to_current_updates().await; let mut txn = state.transaction().await.unwrap(); @@ -774,7 +790,8 @@ async fn test_fencing_during_write(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Drain updates. let _ = state.sync_to_current_updates().await; @@ -800,7 +817,8 @@ async fn test_fencing_during_write(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Committing results in a deploy generation fence error. let commit_ts = txn.upper(); @@ -840,7 +858,8 @@ async fn test_persist_version_fencing() { let _persist_state = persist_openable_state .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; persist_cache.cfg.build_version = reader_version.clone(); let persist_client = persist_cache @@ -903,7 +922,8 @@ async fn test_concurrent_open(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; let state_handle = mz_ore::task::spawn(|| "state", async move { // Eventually this state should get fenced by the open below. let err = run_state(&mut state).await.unwrap_err(); @@ -920,7 +940,8 @@ async fn test_concurrent_open(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; state_handle.await.unwrap(); @@ -930,5 +951,6 @@ async fn test_concurrent_open(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; } diff --git a/src/catalog/tests/read-write.rs b/src/catalog/tests/read-write.rs index d5b90e62cf94b..58b845aff8e6a 100644 --- a/src/catalog/tests/read-write.rs +++ b/src/catalog/tests/read-write.rs @@ -44,7 +44,8 @@ async fn test_confirm_leadership(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; assert_ok!(state1.confirm_leadership().await); let mut state2 = state_builder @@ -52,7 +53,8 @@ async fn test_confirm_leadership(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; assert_ok!(state2.confirm_leadership().await); let err = state1.confirm_leadership().await.unwrap_err(); @@ -91,7 +93,8 @@ async fn test_allocate_id(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; let start_id = state.get_next_id(id_type).await.unwrap(); let commit_ts = state.current_upper().await; @@ -169,7 +172,8 @@ async fn test_audit_logs(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Drain initial updates. let _ = state .sync_to_current_updates() @@ -231,7 +235,8 @@ async fn test_items(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Drain initial updates. let _ = state .sync_to_current_updates() @@ -288,7 +293,8 @@ async fn test_schemas(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; // Drain initial updates. let _ = state .sync_to_current_updates() @@ -353,14 +359,16 @@ async fn test_non_writer_commits(state_builder: TestCatalogStateBuilder) { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; let mut savepoint_state = state_builder .clone() .unwrap_build() .await .open_savepoint(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); + .unwrap() + .0; let mut reader_state = state_builder .clone() .unwrap_build() diff --git a/src/cloud-resources/BUILD.bazel b/src/cloud-resources/BUILD.bazel index 2799ad0586fe8..04dd122c8650b 100644 --- a/src/cloud-resources/BUILD.bazel +++ b/src/cloud-resources/BUILD.bazel @@ -22,7 +22,12 @@ rust_library( proc_macro = True, ), compile_data = [], - crate_features = [], + crate_features = [ + "async-trait", + "default", + "mz-repr", + "vpc-endpoints", + ], data = [], proc_macro_deps = [] + all_crate_deps(proc_macro = True), rustc_env = {}, @@ -50,7 +55,12 @@ rust_test( ), compile_data = [], crate = ":mz_cloud_resources", - crate_features = [], + crate_features = [ + "async-trait", + "default", + "mz-repr", + "vpc-endpoints", + ], data = [], env = {}, proc_macro_deps = [] + all_crate_deps( diff --git a/src/cloud-resources/Cargo.toml b/src/cloud-resources/Cargo.toml index 33a9a61406374..ffdb1cd8c5e5c 100644 --- a/src/cloud-resources/Cargo.toml +++ b/src/cloud-resources/Cargo.toml @@ -11,13 +11,11 @@ workspace = true [dependencies] anyhow = "1.0.66" -async-trait = "0.1.68" -k8s-openapi = { version = "0.22.0", features = ["schemars", "v1_29"] } -kube = { version = "0.92.1", default-features = false, features = ["client", "derive", "openssl-tls", "ws"] } chrono = { version = "0.4.35", default-features = false } futures = "0.3.25" -mz-ore = { path = "../ore", features = [] } -mz-repr = { path = "../repr" } +k8s-openapi = { version = "0.22.0", features = ["schemars", "v1_29"] } +kube = { version = "0.92.1", default-features = false, features = ["client", "derive", "openssl-tls", "ws"] } +mz-ore = { path = "../ore", default-features = false } rand = "0.8.5" schemars = { version = "0.8", features = ["uuid1"] } semver = "1.0.16" @@ -25,7 +23,14 @@ serde = "1.0.152" serde_json = "1.0.125" tracing = "0.1.37" uuid = { version = "1.2", features = ["serde", "v4"] } -workspace-hack = { version = "0.0.0", path = "../workspace-hack" } +workspace-hack = { version = "0.0.0", path = "../workspace-hack", optional = true } + +async-trait = { version = "0.1.68", optional = true } +mz-repr = { path = "../repr", optional = true } + +[features] +default = ["workspace-hack", "vpc-endpoints"] +vpc-endpoints = ["async-trait", "mz-repr"] [package.metadata.cargo-udeps.ignore] normal = ["workspace-hack"] diff --git a/src/cloud-resources/src/crd.rs b/src/cloud-resources/src/crd.rs index e2bad24470eae..0e2d0e659362a 100644 --- a/src/cloud-resources/src/crd.rs +++ b/src/cloud-resources/src/crd.rs @@ -23,7 +23,9 @@ use tracing::{info, warn}; use mz_ore::retry::Retry; +pub mod gen; pub mod materialize; +#[cfg(feature = "vpc-endpoints")] pub mod vpc_endpoint; #[derive(Debug, Clone)] diff --git a/src/cloud-resources/src/crd/gen.rs b/src/cloud-resources/src/crd/gen.rs new file mode 100644 index 0000000000000..a3eca9f09d3ab --- /dev/null +++ b/src/cloud-resources/src/crd/gen.rs @@ -0,0 +1,10 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +pub mod cert_manager; diff --git a/src/cloud-resources/src/crd/gen/cert_manager.rs b/src/cloud-resources/src/crd/gen/cert_manager.rs new file mode 100644 index 0000000000000..1772c458ebab2 --- /dev/null +++ b/src/cloud-resources/src/crd/gen/cert_manager.rs @@ -0,0 +1,11 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +pub mod certificates; +pub mod issuers; diff --git a/src/cloud-resources/src/crd/gen/cert_manager/certificates.rs b/src/cloud-resources/src/crd/gen/cert_manager/certificates.rs new file mode 100644 index 0000000000000..350c2949aaa7a --- /dev/null +++ b/src/cloud-resources/src/crd/gen/cert_manager/certificates.rs @@ -0,0 +1,723 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +#![allow(rustdoc::all)] + +// WARNING: generated by kopium - manual changes will be overwritten +// kopium command: kopium certificates.cert-manager.io --docs --smart-derive-elision --derive Default --derive PartialEq --derive JsonSchema +// kopium version: 0.21.1 + +#[allow(unused_imports)] +mod prelude { + pub use k8s_openapi::apimachinery::pkg::apis::meta::v1::Condition; + pub use kube::CustomResource; + pub use schemars::JsonSchema; + pub use serde::{Deserialize, Serialize}; + pub use std::collections::BTreeMap; +} +use self::prelude::*; + +/// Specification of the desired state of the Certificate resource. +/// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status +#[derive(CustomResource, Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +#[kube( + group = "cert-manager.io", + version = "v1", + kind = "Certificate", + plural = "certificates" +)] +#[kube(namespaced)] +#[kube(status = "CertificateStatus")] +#[kube(schema = "disabled")] +#[kube(derive = "Default")] +#[kube(derive = "PartialEq")] +pub struct CertificateSpec { + /// Defines extra output formats of the private key and signed certificate chain + /// to be written to this Certificate's target Secret. + /// + /// This is a Beta Feature enabled by default. It can be disabled with the + /// `--feature-gates=AdditionalCertificateOutputFormats=false` option set on both + /// the controller and webhook components. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "additionalOutputFormats" + )] + pub additional_output_formats: Option>, + /// Requested common name X509 certificate subject attribute. + /// More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6 + /// NOTE: TLS clients will ignore this value when any subject alternative name is + /// set (see https://tools.ietf.org/html/rfc6125#section-6.4.4). + /// + /// Should have a length of 64 characters or fewer to avoid generating invalid CSRs. + /// Cannot be set if the `literalSubject` field is set. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "commonName" + )] + pub common_name: Option, + /// Requested DNS subject alternative names. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "dnsNames")] + pub dns_names: Option>, + /// Requested 'duration' (i.e. lifetime) of the Certificate. Note that the + /// issuer may choose to ignore the requested duration, just like any other + /// requested attribute. + /// + /// If unset, this defaults to 90 days. + /// Minimum accepted duration is 1 hour. + /// Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub duration: Option, + /// Requested email subject alternative names. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "emailAddresses" + )] + pub email_addresses: Option>, + /// Whether the KeyUsage and ExtKeyUsage extensions should be set in the encoded CSR. + /// + /// This option defaults to true, and should only be disabled if the target + /// issuer does not support CSRs with these X509 KeyUsage/ ExtKeyUsage extensions. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "encodeUsagesInRequest" + )] + pub encode_usages_in_request: Option, + /// Requested IP address subject alternative names. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "ipAddresses" + )] + pub ip_addresses: Option>, + /// Requested basic constraints isCA value. + /// The isCA value is used to set the `isCA` field on the created CertificateRequest + /// resources. Note that the issuer may choose to ignore the requested isCA value, just + /// like any other requested attribute. + /// + /// If true, this will automatically add the `cert sign` usage to the list + /// of requested `usages`. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "isCA")] + pub is_ca: Option, + /// Reference to the issuer responsible for issuing the certificate. + /// If the issuer is namespace-scoped, it must be in the same namespace + /// as the Certificate. If the issuer is cluster-scoped, it can be used + /// from any namespace. + /// + /// The `name` field of the reference must always be specified. + #[serde(rename = "issuerRef")] + pub issuer_ref: CertificateIssuerRef, + /// Additional keystore output formats to be stored in the Certificate's Secret. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub keystores: Option, + /// Requested X.509 certificate subject, represented using the LDAP "String + /// Representation of a Distinguished Name" [1]. + /// Important: the LDAP string format also specifies the order of the attributes + /// in the subject, this is important when issuing certs for LDAP authentication. + /// Example: `CN=foo,DC=corp,DC=example,DC=com` + /// More info [1]: https://datatracker.ietf.org/doc/html/rfc4514 + /// More info: https://github.com/cert-manager/cert-manager/issues/3203 + /// More info: https://github.com/cert-manager/cert-manager/issues/4424 + /// + /// Cannot be set if the `subject` or `commonName` field is set. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "literalSubject" + )] + pub literal_subject: Option, + /// x.509 certificate NameConstraint extension which MUST NOT be used in a non-CA certificate. + /// More Info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10 + /// + /// This is an Alpha Feature and is only enabled with the + /// `--feature-gates=NameConstraints=true` option set on both + /// the controller and webhook components. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "nameConstraints" + )] + pub name_constraints: Option, + /// `otherNames` is an escape hatch for SAN that allows any type. We currently restrict the support to string like otherNames, cf RFC 5280 p 37 + /// Any UTF8 String valued otherName can be passed with by setting the keys oid: x.x.x.x and UTF8Value: somevalue for `otherName`. + /// Most commonly this would be UPN set with oid: 1.3.6.1.4.1.311.20.2.3 + /// You should ensure that any OID passed is valid for the UTF8String type as we do not explicitly validate this. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "otherNames" + )] + pub other_names: Option>, + /// Private key options. These include the key algorithm and size, the used + /// encoding and the rotation policy. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "privateKey" + )] + pub private_key: Option, + /// How long before the currently issued certificate's expiry cert-manager should + /// renew the certificate. For example, if a certificate is valid for 60 minutes, + /// and `renewBefore=10m`, cert-manager will begin to attempt to renew the certificate + /// 50 minutes after it was issued (i.e. when there are 10 minutes remaining until + /// the certificate is no longer valid). + /// + /// NOTE: The actual lifetime of the issued certificate is used to determine the + /// renewal time. If an issuer returns a certificate with a different lifetime than + /// the one requested, cert-manager will use the lifetime of the issued certificate. + /// + /// If unset, this defaults to 1/3 of the issued certificate's lifetime. + /// Minimum accepted value is 5 minutes. + /// Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration. + /// Cannot be set if the `renewBeforePercentage` field is set. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "renewBefore" + )] + pub renew_before: Option, + /// `renewBeforePercentage` is like `renewBefore`, except it is a relative percentage + /// rather than an absolute duration. For example, if a certificate is valid for 60 + /// minutes, and `renewBeforePercentage=25`, cert-manager will begin to attempt to + /// renew the certificate 45 minutes after it was issued (i.e. when there are 15 + /// minutes (25%) remaining until the certificate is no longer valid). + /// + /// NOTE: The actual lifetime of the issued certificate is used to determine the + /// renewal time. If an issuer returns a certificate with a different lifetime than + /// the one requested, cert-manager will use the lifetime of the issued certificate. + /// + /// Value must be an integer in the range (0,100). The minimum effective + /// `renewBefore` derived from the `renewBeforePercentage` and `duration` fields is 5 + /// minutes. + /// Cannot be set if the `renewBefore` field is set. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "renewBeforePercentage" + )] + pub renew_before_percentage: Option, + /// The maximum number of CertificateRequest revisions that are maintained in + /// the Certificate's history. Each revision represents a single `CertificateRequest` + /// created by this Certificate, either when it was created, renewed, or Spec + /// was changed. Revisions will be removed by oldest first if the number of + /// revisions exceeds this number. + /// + /// If set, revisionHistoryLimit must be a value of `1` or greater. + /// If unset (`nil`), revisions will not be garbage collected. + /// Default value is `nil`. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "revisionHistoryLimit" + )] + pub revision_history_limit: Option, + /// Name of the Secret resource that will be automatically created and + /// managed by this Certificate resource. It will be populated with a + /// private key and certificate, signed by the denoted issuer. The Secret + /// resource lives in the same namespace as the Certificate resource. + #[serde(rename = "secretName")] + pub secret_name: String, + /// Defines annotations and labels to be copied to the Certificate's Secret. + /// Labels and annotations on the Secret will be changed as they appear on the + /// SecretTemplate when added or removed. SecretTemplate annotations are added + /// in conjunction with, and cannot overwrite, the base set of annotations + /// cert-manager sets on the Certificate's Secret. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "secretTemplate" + )] + pub secret_template: Option, + /// Requested set of X509 certificate subject attributes. + /// More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6 + /// + /// The common name attribute is specified separately in the `commonName` field. + /// Cannot be set if the `literalSubject` field is set. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub subject: Option, + /// Requested URI subject alternative names. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub uris: Option>, + /// Requested key usages and extended key usages. + /// These usages are used to set the `usages` field on the created CertificateRequest + /// resources. If `encodeUsagesInRequest` is unset or set to `true`, the usages + /// will additionally be encoded in the `request` field which contains the CSR blob. + /// + /// If unset, defaults to `digital signature` and `key encipherment`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub usages: Option>, +} + +/// CertificateAdditionalOutputFormat defines an additional output format of a +/// Certificate resource. These contain supplementary data formats of the signed +/// certificate chain and paired private key. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct CertificateAdditionalOutputFormats { + /// Type is the name of the format type that should be written to the + /// Certificate's target Secret. + #[serde(rename = "type")] + pub r#type: CertificateAdditionalOutputFormatsType, +} + +/// CertificateAdditionalOutputFormat defines an additional output format of a +/// Certificate resource. These contain supplementary data formats of the signed +/// certificate chain and paired private key. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub enum CertificateAdditionalOutputFormatsType { + #[serde(rename = "DER")] + Der, + #[serde(rename = "CombinedPEM")] + CombinedPem, +} + +/// Reference to the issuer responsible for issuing the certificate. +/// If the issuer is namespace-scoped, it must be in the same namespace +/// as the Certificate. If the issuer is cluster-scoped, it can be used +/// from any namespace. +/// +/// The `name` field of the reference must always be specified. +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificateIssuerRef { + /// Group of the resource being referred to. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub group: Option, + /// Kind of the resource being referred to. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub kind: Option, + /// Name of the resource being referred to. + pub name: String, +} + +/// Additional keystore output formats to be stored in the Certificate's Secret. +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificateKeystores { + /// JKS configures options for storing a JKS keystore in the + /// `spec.secretName` Secret resource. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub jks: Option, + /// PKCS12 configures options for storing a PKCS12 keystore in the + /// `spec.secretName` Secret resource. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub pkcs12: Option, +} + +/// JKS configures options for storing a JKS keystore in the +/// `spec.secretName` Secret resource. +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificateKeystoresJks { + /// Alias specifies the alias of the key in the keystore, required by the JKS format. + /// If not provided, the default alias `certificate` will be used. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub alias: Option, + /// Create enables JKS keystore creation for the Certificate. + /// If true, a file named `keystore.jks` will be created in the target + /// Secret resource, encrypted using the password stored in + /// `passwordSecretRef`. + /// The keystore file will be updated immediately. + /// If the issuer provided a CA certificate, a file named `truststore.jks` + /// will also be created in the target Secret resource, encrypted using the + /// password stored in `passwordSecretRef` + /// containing the issuing Certificate Authority + pub create: bool, + /// PasswordSecretRef is a reference to a key in a Secret resource + /// containing the password used to encrypt the JKS keystore. + #[serde(rename = "passwordSecretRef")] + pub password_secret_ref: CertificateKeystoresJksPasswordSecretRef, +} + +/// PasswordSecretRef is a reference to a key in a Secret resource +/// containing the password used to encrypt the JKS keystore. +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificateKeystoresJksPasswordSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// PKCS12 configures options for storing a PKCS12 keystore in the +/// `spec.secretName` Secret resource. +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificateKeystoresPkcs12 { + /// Create enables PKCS12 keystore creation for the Certificate. + /// If true, a file named `keystore.p12` will be created in the target + /// Secret resource, encrypted using the password stored in + /// `passwordSecretRef`. + /// The keystore file will be updated immediately. + /// If the issuer provided a CA certificate, a file named `truststore.p12` will + /// also be created in the target Secret resource, encrypted using the + /// password stored in `passwordSecretRef` containing the issuing Certificate + /// Authority + pub create: bool, + /// PasswordSecretRef is a reference to a key in a Secret resource + /// containing the password used to encrypt the PKCS12 keystore. + #[serde(rename = "passwordSecretRef")] + pub password_secret_ref: CertificateKeystoresPkcs12PasswordSecretRef, + /// Profile specifies the key and certificate encryption algorithms and the HMAC algorithm + /// used to create the PKCS12 keystore. Default value is `LegacyRC2` for backward compatibility. + /// + /// If provided, allowed values are: + /// `LegacyRC2`: Deprecated. Not supported by default in OpenSSL 3 or Java 20. + /// `LegacyDES`: Less secure algorithm. Use this option for maximal compatibility. + /// `Modern2023`: Secure algorithm. Use this option in case you have to always use secure algorithms + /// (eg. because of company policy). Please note that the security of the algorithm is not that important + /// in reality, because the unencrypted certificate and private key are also stored in the Secret. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub profile: Option, +} + +/// PasswordSecretRef is a reference to a key in a Secret resource +/// containing the password used to encrypt the PKCS12 keystore. +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificateKeystoresPkcs12PasswordSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// PKCS12 configures options for storing a PKCS12 keystore in the +/// `spec.secretName` Secret resource. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub enum CertificateKeystoresPkcs12Profile { + #[serde(rename = "LegacyRC2")] + LegacyRc2, + #[serde(rename = "LegacyDES")] + LegacyDes, + Modern2023, +} + +/// x.509 certificate NameConstraint extension which MUST NOT be used in a non-CA certificate. +/// More Info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10 +/// +/// This is an Alpha Feature and is only enabled with the +/// `--feature-gates=NameConstraints=true` option set on both +/// the controller and webhook components. +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificateNameConstraints { + /// if true then the name constraints are marked critical. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub critical: Option, + /// Excluded contains the constraints which must be disallowed. Any name matching a + /// restriction in the excluded field is invalid regardless + /// of information appearing in the permitted + #[serde(default, skip_serializing_if = "Option::is_none")] + pub excluded: Option, + /// Permitted contains the constraints in which the names must be located. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub permitted: Option, +} + +/// Excluded contains the constraints which must be disallowed. Any name matching a +/// restriction in the excluded field is invalid regardless +/// of information appearing in the permitted +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificateNameConstraintsExcluded { + /// DNSDomains is a list of DNS domains that are permitted or excluded. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "dnsDomains" + )] + pub dns_domains: Option>, + /// EmailAddresses is a list of Email Addresses that are permitted or excluded. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "emailAddresses" + )] + pub email_addresses: Option>, + /// IPRanges is a list of IP Ranges that are permitted or excluded. + /// This should be a valid CIDR notation. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "ipRanges")] + pub ip_ranges: Option>, + /// URIDomains is a list of URI domains that are permitted or excluded. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "uriDomains" + )] + pub uri_domains: Option>, +} + +/// Permitted contains the constraints in which the names must be located. +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificateNameConstraintsPermitted { + /// DNSDomains is a list of DNS domains that are permitted or excluded. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "dnsDomains" + )] + pub dns_domains: Option>, + /// EmailAddresses is a list of Email Addresses that are permitted or excluded. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "emailAddresses" + )] + pub email_addresses: Option>, + /// IPRanges is a list of IP Ranges that are permitted or excluded. + /// This should be a valid CIDR notation. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "ipRanges")] + pub ip_ranges: Option>, + /// URIDomains is a list of URI domains that are permitted or excluded. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "uriDomains" + )] + pub uri_domains: Option>, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificateOtherNames { + /// OID is the object identifier for the otherName SAN. + /// The object identifier must be expressed as a dotted string, for + /// example, "1.2.840.113556.1.4.221". + #[serde(default, skip_serializing_if = "Option::is_none")] + pub oid: Option, + /// utf8Value is the string value of the otherName SAN. + /// The utf8Value accepts any valid UTF8 string to set as value for the otherName SAN. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "utf8Value")] + pub utf8_value: Option, +} + +/// Private key options. These include the key algorithm and size, the used +/// encoding and the rotation policy. +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificatePrivateKey { + /// Algorithm is the private key algorithm of the corresponding private key + /// for this certificate. + /// + /// If provided, allowed values are either `RSA`, `ECDSA` or `Ed25519`. + /// If `algorithm` is specified and `size` is not provided, + /// key size of 2048 will be used for `RSA` key algorithm and + /// key size of 256 will be used for `ECDSA` key algorithm. + /// key size is ignored when using the `Ed25519` key algorithm. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub algorithm: Option, + /// The private key cryptography standards (PKCS) encoding for this + /// certificate's private key to be encoded in. + /// + /// If provided, allowed values are `PKCS1` and `PKCS8` standing for PKCS#1 + /// and PKCS#8, respectively. + /// Defaults to `PKCS1` if not specified. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub encoding: Option, + /// RotationPolicy controls how private keys should be regenerated when a + /// re-issuance is being processed. + /// + /// If set to `Never`, a private key will only be generated if one does not + /// already exist in the target `spec.secretName`. If one does exist but it + /// does not have the correct algorithm or size, a warning will be raised + /// to await user intervention. + /// If set to `Always`, a private key matching the specified requirements + /// will be generated whenever a re-issuance occurs. + /// Default is `Never` for backward compatibility. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "rotationPolicy" + )] + pub rotation_policy: Option, + /// Size is the key bit size of the corresponding private key for this certificate. + /// + /// If `algorithm` is set to `RSA`, valid values are `2048`, `4096` or `8192`, + /// and will default to `2048` if not specified. + /// If `algorithm` is set to `ECDSA`, valid values are `256`, `384` or `521`, + /// and will default to `256` if not specified. + /// If `algorithm` is set to `Ed25519`, Size is ignored. + /// No other values are allowed. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub size: Option, +} + +/// Private key options. These include the key algorithm and size, the used +/// encoding and the rotation policy. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub enum CertificatePrivateKeyAlgorithm { + #[serde(rename = "RSA")] + Rsa, + #[serde(rename = "ECDSA")] + Ecdsa, + Ed25519, +} + +/// Private key options. These include the key algorithm and size, the used +/// encoding and the rotation policy. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub enum CertificatePrivateKeyEncoding { + #[serde(rename = "PKCS1")] + Pkcs1, + #[serde(rename = "PKCS8")] + Pkcs8, +} + +/// Private key options. These include the key algorithm and size, the used +/// encoding and the rotation policy. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub enum CertificatePrivateKeyRotationPolicy { + Never, + Always, +} + +/// Defines annotations and labels to be copied to the Certificate's Secret. +/// Labels and annotations on the Secret will be changed as they appear on the +/// SecretTemplate when added or removed. SecretTemplate annotations are added +/// in conjunction with, and cannot overwrite, the base set of annotations +/// cert-manager sets on the Certificate's Secret. +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificateSecretTemplate { + /// Annotations is a key value map to be copied to the target Kubernetes Secret. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub annotations: Option>, + /// Labels is a key value map to be copied to the target Kubernetes Secret. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub labels: Option>, +} + +/// Requested set of X509 certificate subject attributes. +/// More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6 +/// +/// The common name attribute is specified separately in the `commonName` field. +/// Cannot be set if the `literalSubject` field is set. +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificateSubject { + /// Countries to be used on the Certificate. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub countries: Option>, + /// Cities to be used on the Certificate. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub localities: Option>, + /// Organizational Units to be used on the Certificate. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "organizationalUnits" + )] + pub organizational_units: Option>, + /// Organizations to be used on the Certificate. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub organizations: Option>, + /// Postal codes to be used on the Certificate. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "postalCodes" + )] + pub postal_codes: Option>, + /// State/Provinces to be used on the Certificate. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub provinces: Option>, + /// Serial number to be used on the Certificate. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "serialNumber" + )] + pub serial_number: Option, + /// Street addresses to be used on the Certificate. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "streetAddresses" + )] + pub street_addresses: Option>, +} + +/// Status of the Certificate. +/// This is set and managed automatically. +/// Read-only. +/// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] +pub struct CertificateStatus { + /// List of status conditions to indicate the status of certificates. + /// Known condition types are `Ready` and `Issuing`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub conditions: Option>, + /// The number of continuous failed issuance attempts up till now. This + /// field gets removed (if set) on a successful issuance and gets set to + /// 1 if unset and an issuance has failed. If an issuance has failed, the + /// delay till the next issuance will be calculated using formula + /// time.Hour * 2 ^ (failedIssuanceAttempts - 1). + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "failedIssuanceAttempts" + )] + pub failed_issuance_attempts: Option, + /// LastFailureTime is set only if the latest issuance for this + /// Certificate failed and contains the time of the failure. If an + /// issuance has failed, the delay till the next issuance will be + /// calculated using formula time.Hour * 2 ^ (failedIssuanceAttempts - + /// 1). If the latest issuance has succeeded this field will be unset. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "lastFailureTime" + )] + pub last_failure_time: Option, + /// The name of the Secret resource containing the private key to be used + /// for the next certificate iteration. + /// The keymanager controller will automatically set this field if the + /// `Issuing` condition is set to `True`. + /// It will automatically unset this field when the Issuing condition is + /// not set or False. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "nextPrivateKeySecretName" + )] + pub next_private_key_secret_name: Option, + /// The expiration time of the certificate stored in the secret named + /// by this resource in `spec.secretName`. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "notAfter")] + pub not_after: Option, + /// The time after which the certificate stored in the secret named + /// by this resource in `spec.secretName` is valid. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "notBefore")] + pub not_before: Option, + /// RenewalTime is the time at which the certificate will be next + /// renewed. + /// If not set, no upcoming renewal is scheduled. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "renewalTime" + )] + pub renewal_time: Option, + /// The current 'revision' of the certificate as issued. + /// + /// When a CertificateRequest resource is created, it will have the + /// `cert-manager.io/certificate-revision` set to one greater than the + /// current value of this field. + /// + /// Upon issuance, this field will be set to the value of the annotation + /// on the CertificateRequest resource used to issue the certificate. + /// + /// Persisting the value on the CertificateRequest resource allows the + /// certificates controller to know whether a request is part of an old + /// issuance or if it is part of the ongoing revision's issuance by + /// checking if the revision value in the annotation is greater than this + /// field. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub revision: Option, +} diff --git a/src/cloud-resources/src/crd/gen/cert_manager/issuers.rs b/src/cloud-resources/src/crd/gen/cert_manager/issuers.rs new file mode 100644 index 0000000000000..fedefae6471d2 --- /dev/null +++ b/src/cloud-resources/src/crd/gen/cert_manager/issuers.rs @@ -0,0 +1,3629 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +#![allow(rustdoc::all)] + +// WARNING: generated by kopium - manual changes will be overwritten +// kopium command: kopium issuers.cert-manager.io --docs --smart-derive-elision --derive Default +// kopium version: 0.21.1 + +#[allow(unused_imports)] +mod prelude { + pub use k8s_openapi::apimachinery::pkg::apis::meta::v1::Condition; + pub use kube::CustomResource; + pub use serde::{Deserialize, Serialize}; + pub use std::collections::BTreeMap; +} +use self::prelude::*; + +/// Desired state of the Issuer resource. +#[derive(CustomResource, Serialize, Deserialize, Clone, Debug, Default)] +#[kube( + group = "cert-manager.io", + version = "v1", + kind = "Issuer", + plural = "issuers" +)] +#[kube(namespaced)] +#[kube(status = "IssuerStatus")] +#[kube(schema = "disabled")] +#[kube(derive = "Default")] +pub struct IssuerSpec { + /// ACME configures this issuer to communicate with a RFC8555 (ACME) server + /// to obtain signed x509 certificates. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub acme: Option, + /// CA configures this issuer to sign certificates using a signing CA keypair + /// stored in a Secret resource. + /// This is used to build internal PKIs that are managed by cert-manager. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ca: Option, + /// SelfSigned configures this issuer to 'self sign' certificates using the + /// private key used to create the CertificateRequest object. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "selfSigned" + )] + pub self_signed: Option, + /// Vault configures this issuer to sign certificates using a HashiCorp Vault + /// PKI backend. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub vault: Option, + /// Venafi configures this issuer to sign certificates using a Venafi TPP + /// or Venafi Cloud policy zone. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub venafi: Option, +} + +/// ACME configures this issuer to communicate with a RFC8555 (ACME) server +/// to obtain signed x509 certificates. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcme { + /// Base64-encoded bundle of PEM CAs which can be used to validate the certificate + /// chain presented by the ACME server. + /// Mutually exclusive with SkipTLSVerify; prefer using CABundle to prevent various + /// kinds of security vulnerabilities. + /// If CABundle and SkipTLSVerify are unset, the system certificate bundle inside + /// the container is used to validate the TLS connection. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "caBundle")] + pub ca_bundle: Option, + /// Enables or disables generating a new ACME account key. + /// If true, the Issuer resource will *not* request a new account but will expect + /// the account key to be supplied via an existing secret. + /// If false, the cert-manager system will generate a new ACME account key + /// for the Issuer. + /// Defaults to false. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "disableAccountKeyGeneration" + )] + pub disable_account_key_generation: Option, + /// Email is the email address to be associated with the ACME account. + /// This field is optional, but it is strongly recommended to be set. + /// It will be used to contact you in case of issues with your account or + /// certificates, including expiry notification emails. + /// This field may be updated after the account is initially registered. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub email: Option, + /// Enables requesting a Not After date on certificates that matches the + /// duration of the certificate. This is not supported by all ACME servers + /// like Let's Encrypt. If set to true when the ACME server does not support + /// it, it will create an error on the Order. + /// Defaults to false. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "enableDurationFeature" + )] + pub enable_duration_feature: Option, + /// ExternalAccountBinding is a reference to a CA external account of the ACME + /// server. + /// If set, upon registration cert-manager will attempt to associate the given + /// external account credentials with the registered ACME account. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "externalAccountBinding" + )] + pub external_account_binding: Option, + /// PreferredChain is the chain to use if the ACME server outputs multiple. + /// PreferredChain is no guarantee that this one gets delivered by the ACME + /// endpoint. + /// For example, for Let's Encrypt's DST crosssign you would use: + /// "DST Root CA X3" or "ISRG Root X1" for the newer Let's Encrypt root CA. + /// This value picks the first certificate bundle in the combined set of + /// ACME default and alternative chains that has a root-most certificate with + /// this value as its issuer's commonname. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "preferredChain" + )] + pub preferred_chain: Option, + /// PrivateKey is the name of a Kubernetes Secret resource that will be used to + /// store the automatically generated ACME account private key. + /// Optionally, a `key` may be specified to select a specific entry within + /// the named Secret resource. + /// If `key` is not specified, a default of `tls.key` will be used. + #[serde(rename = "privateKeySecretRef")] + pub private_key_secret_ref: IssuerAcmePrivateKeySecretRef, + /// Server is the URL used to access the ACME server's 'directory' endpoint. + /// For example, for Let's Encrypt's staging endpoint, you would use: + /// "https://acme-staging-v02.api.letsencrypt.org/directory". + /// Only ACME v2 endpoints (i.e. RFC 8555) are supported. + pub server: String, + /// INSECURE: Enables or disables validation of the ACME server TLS certificate. + /// If true, requests to the ACME server will not have the TLS certificate chain + /// validated. + /// Mutually exclusive with CABundle; prefer using CABundle to prevent various + /// kinds of security vulnerabilities. + /// Only enable this option in development environments. + /// If CABundle and SkipTLSVerify are unset, the system certificate bundle inside + /// the container is used to validate the TLS connection. + /// Defaults to false. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "skipTLSVerify" + )] + pub skip_tls_verify: Option, + /// Solvers is a list of challenge solvers that will be used to solve + /// ACME challenges for the matching domains. + /// Solver configurations must be provided in order to obtain certificates + /// from an ACME server. + /// For more information, see: https://cert-manager.io/docs/configuration/acme/ + #[serde(default, skip_serializing_if = "Option::is_none")] + pub solvers: Option>, +} + +/// ExternalAccountBinding is a reference to a CA external account of the ACME +/// server. +/// If set, upon registration cert-manager will attempt to associate the given +/// external account credentials with the registered ACME account. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeExternalAccountBinding { + /// Deprecated: keyAlgorithm field exists for historical compatibility + /// reasons and should not be used. The algorithm is now hardcoded to HS256 + /// in golang/x/crypto/acme. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "keyAlgorithm" + )] + pub key_algorithm: Option, + /// keyID is the ID of the CA key that the External Account is bound to. + #[serde(rename = "keyID")] + pub key_id: String, + /// keySecretRef is a Secret Key Selector referencing a data item in a Kubernetes + /// Secret which holds the symmetric MAC key of the External Account Binding. + /// The `key` is the index string that is paired with the key data in the + /// Secret and should not be confused with the key data itself, or indeed with + /// the External Account Binding keyID above. + /// The secret key stored in the Secret **must** be un-padded, base64 URL + /// encoded data. + #[serde(rename = "keySecretRef")] + pub key_secret_ref: IssuerAcmeExternalAccountBindingKeySecretRef, +} + +/// ExternalAccountBinding is a reference to a CA external account of the ACME +/// server. +/// If set, upon registration cert-manager will attempt to associate the given +/// external account credentials with the registered ACME account. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum IssuerAcmeExternalAccountBindingKeyAlgorithm { + #[serde(rename = "HS256")] + Hs256, + #[serde(rename = "HS384")] + Hs384, + #[serde(rename = "HS512")] + Hs512, +} + +/// keySecretRef is a Secret Key Selector referencing a data item in a Kubernetes +/// Secret which holds the symmetric MAC key of the External Account Binding. +/// The `key` is the index string that is paired with the key data in the +/// Secret and should not be confused with the key data itself, or indeed with +/// the External Account Binding keyID above. +/// The secret key stored in the Secret **must** be un-padded, base64 URL +/// encoded data. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeExternalAccountBindingKeySecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// PrivateKey is the name of a Kubernetes Secret resource that will be used to +/// store the automatically generated ACME account private key. +/// Optionally, a `key` may be specified to select a specific entry within +/// the named Secret resource. +/// If `key` is not specified, a default of `tls.key` will be used. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmePrivateKeySecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// An ACMEChallengeSolver describes how to solve ACME challenges for the issuer it is part of. +/// A selector may be provided to use different solving strategies for different DNS names. +/// Only one of HTTP01 or DNS01 must be provided. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolvers { + /// Configures cert-manager to attempt to complete authorizations by + /// performing the DNS01 challenge flow. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dns01: Option, + /// Configures cert-manager to attempt to complete authorizations by + /// performing the HTTP01 challenge flow. + /// It is not possible to obtain certificates for wildcard domain names + /// (e.g. `*.example.com`) using the HTTP01 challenge mechanism. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub http01: Option, + /// Selector selects a set of DNSNames on the Certificate resource that + /// should be solved using this challenge solver. + /// If not specified, the solver will be treated as the 'default' solver + /// with the lowest priority, i.e. if any other solver has a more specific + /// match, it will be used instead. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub selector: Option, +} + +/// Configures cert-manager to attempt to complete authorizations by +/// performing the DNS01 challenge flow. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01 { + /// Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage + /// DNS01 challenge records. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "acmeDNS")] + pub acme_dns: Option, + /// Use the Akamai DNS zone management API to manage DNS01 challenge records. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub akamai: Option, + /// Use the Microsoft Azure DNS API to manage DNS01 challenge records. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "azureDNS")] + pub azure_dns: Option, + /// Use the Google Cloud DNS API to manage DNS01 challenge records. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "cloudDNS")] + pub cloud_dns: Option, + /// Use the Cloudflare API to manage DNS01 challenge records. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cloudflare: Option, + /// CNAMEStrategy configures how the DNS01 provider should handle CNAME + /// records when found in DNS zones. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "cnameStrategy" + )] + pub cname_strategy: Option, + /// Use the DigitalOcean DNS API to manage DNS01 challenge records. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub digitalocean: Option, + /// Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) + /// to manage DNS01 challenge records. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub rfc2136: Option, + /// Use the AWS Route53 API to manage DNS01 challenge records. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub route53: Option, + /// Configure an external webhook based DNS01 challenge solver to manage + /// DNS01 challenge records. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub webhook: Option, +} + +/// Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage +/// DNS01 challenge records. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01AcmeDns { + /// A reference to a specific 'key' within a Secret resource. + /// In some instances, `key` is a required field. + #[serde(rename = "accountSecretRef")] + pub account_secret_ref: IssuerAcmeSolversDns01AcmeDnsAccountSecretRef, + pub host: String, +} + +/// A reference to a specific 'key' within a Secret resource. +/// In some instances, `key` is a required field. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01AcmeDnsAccountSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Use the Akamai DNS zone management API to manage DNS01 challenge records. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01Akamai { + /// A reference to a specific 'key' within a Secret resource. + /// In some instances, `key` is a required field. + #[serde(rename = "accessTokenSecretRef")] + pub access_token_secret_ref: IssuerAcmeSolversDns01AkamaiAccessTokenSecretRef, + /// A reference to a specific 'key' within a Secret resource. + /// In some instances, `key` is a required field. + #[serde(rename = "clientSecretSecretRef")] + pub client_secret_secret_ref: IssuerAcmeSolversDns01AkamaiClientSecretSecretRef, + /// A reference to a specific 'key' within a Secret resource. + /// In some instances, `key` is a required field. + #[serde(rename = "clientTokenSecretRef")] + pub client_token_secret_ref: IssuerAcmeSolversDns01AkamaiClientTokenSecretRef, + #[serde(rename = "serviceConsumerDomain")] + pub service_consumer_domain: String, +} + +/// A reference to a specific 'key' within a Secret resource. +/// In some instances, `key` is a required field. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01AkamaiAccessTokenSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// A reference to a specific 'key' within a Secret resource. +/// In some instances, `key` is a required field. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01AkamaiClientSecretSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// A reference to a specific 'key' within a Secret resource. +/// In some instances, `key` is a required field. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01AkamaiClientTokenSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Use the Microsoft Azure DNS API to manage DNS01 challenge records. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01AzureDns { + /// Auth: Azure Service Principal: + /// The ClientID of the Azure Service Principal used to authenticate with Azure DNS. + /// If set, ClientSecret and TenantID must also be set. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "clientID")] + pub client_id: Option, + /// Auth: Azure Service Principal: + /// A reference to a Secret containing the password associated with the Service Principal. + /// If set, ClientID and TenantID must also be set. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "clientSecretSecretRef" + )] + pub client_secret_secret_ref: Option, + /// name of the Azure environment (default AzurePublicCloud) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub environment: Option, + /// name of the DNS zone that should be used + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "hostedZoneName" + )] + pub hosted_zone_name: Option, + /// Auth: Azure Workload Identity or Azure Managed Service Identity: + /// Settings to enable Azure Workload Identity or Azure Managed Service Identity + /// If set, ClientID, ClientSecret and TenantID must not be set. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "managedIdentity" + )] + pub managed_identity: Option, + /// resource group the DNS zone is located in + #[serde(rename = "resourceGroupName")] + pub resource_group_name: String, + /// ID of the Azure subscription + #[serde(rename = "subscriptionID")] + pub subscription_id: String, + /// Auth: Azure Service Principal: + /// The TenantID of the Azure Service Principal used to authenticate with Azure DNS. + /// If set, ClientID and ClientSecret must also be set. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "tenantID")] + pub tenant_id: Option, +} + +/// Auth: Azure Service Principal: +/// A reference to a Secret containing the password associated with the Service Principal. +/// If set, ClientID and TenantID must also be set. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01AzureDnsClientSecretSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Use the Microsoft Azure DNS API to manage DNS01 challenge records. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum IssuerAcmeSolversDns01AzureDnsEnvironment { + AzurePublicCloud, + AzureChinaCloud, + AzureGermanCloud, + #[serde(rename = "AzureUSGovernmentCloud")] + AzureUsGovernmentCloud, +} + +/// Auth: Azure Workload Identity or Azure Managed Service Identity: +/// Settings to enable Azure Workload Identity or Azure Managed Service Identity +/// If set, ClientID, ClientSecret and TenantID must not be set. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01AzureDnsManagedIdentity { + /// client ID of the managed identity, can not be used at the same time as resourceID + #[serde(default, skip_serializing_if = "Option::is_none", rename = "clientID")] + pub client_id: Option, + /// resource ID of the managed identity, can not be used at the same time as clientID + /// Cannot be used for Azure Managed Service Identity + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "resourceID" + )] + pub resource_id: Option, +} + +/// Use the Google Cloud DNS API to manage DNS01 challenge records. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01CloudDns { + /// HostedZoneName is an optional field that tells cert-manager in which + /// Cloud DNS zone the challenge record has to be created. + /// If left empty cert-manager will automatically choose a zone. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "hostedZoneName" + )] + pub hosted_zone_name: Option, + pub project: String, + /// A reference to a specific 'key' within a Secret resource. + /// In some instances, `key` is a required field. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "serviceAccountSecretRef" + )] + pub service_account_secret_ref: Option, +} + +/// A reference to a specific 'key' within a Secret resource. +/// In some instances, `key` is a required field. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01CloudDnsServiceAccountSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Use the Cloudflare API to manage DNS01 challenge records. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01Cloudflare { + /// API key to use to authenticate with Cloudflare. + /// Note: using an API token to authenticate is now the recommended method + /// as it allows greater control of permissions. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "apiKeySecretRef" + )] + pub api_key_secret_ref: Option, + /// API token used to authenticate with Cloudflare. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "apiTokenSecretRef" + )] + pub api_token_secret_ref: Option, + /// Email of the account, only required when using API key based authentication. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub email: Option, +} + +/// API key to use to authenticate with Cloudflare. +/// Note: using an API token to authenticate is now the recommended method +/// as it allows greater control of permissions. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01CloudflareApiKeySecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// API token used to authenticate with Cloudflare. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01CloudflareApiTokenSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Configures cert-manager to attempt to complete authorizations by +/// performing the DNS01 challenge flow. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum IssuerAcmeSolversDns01CnameStrategy { + None, + Follow, +} + +/// Use the DigitalOcean DNS API to manage DNS01 challenge records. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01Digitalocean { + /// A reference to a specific 'key' within a Secret resource. + /// In some instances, `key` is a required field. + #[serde(rename = "tokenSecretRef")] + pub token_secret_ref: IssuerAcmeSolversDns01DigitaloceanTokenSecretRef, +} + +/// A reference to a specific 'key' within a Secret resource. +/// In some instances, `key` is a required field. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01DigitaloceanTokenSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) +/// to manage DNS01 challenge records. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01Rfc2136 { + /// The IP address or hostname of an authoritative DNS server supporting + /// RFC2136 in the form host:port. If the host is an IPv6 address it must be + /// enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. + /// This field is required. + pub nameserver: String, + /// The TSIG Algorithm configured in the DNS supporting RFC2136. Used only + /// when ``tsigSecretSecretRef`` and ``tsigKeyName`` are defined. + /// Supported values are (case-insensitive): ``HMACMD5`` (default), + /// ``HMACSHA1``, ``HMACSHA256`` or ``HMACSHA512``. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "tsigAlgorithm" + )] + pub tsig_algorithm: Option, + /// The TSIG Key name configured in the DNS. + /// If ``tsigSecretSecretRef`` is defined, this field is required. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "tsigKeyName" + )] + pub tsig_key_name: Option, + /// The name of the secret containing the TSIG value. + /// If ``tsigKeyName`` is defined, this field is required. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "tsigSecretSecretRef" + )] + pub tsig_secret_secret_ref: Option, +} + +/// The name of the secret containing the TSIG value. +/// If ``tsigKeyName`` is defined, this field is required. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01Rfc2136TsigSecretSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Use the AWS Route53 API to manage DNS01 challenge records. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01Route53 { + /// The AccessKeyID is used for authentication. + /// Cannot be set when SecretAccessKeyID is set. + /// If neither the Access Key nor Key ID are set, we fall-back to using env + /// vars, shared credentials file or AWS Instance metadata, + /// see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "accessKeyID" + )] + pub access_key_id: Option, + /// The SecretAccessKey is used for authentication. If set, pull the AWS + /// access key ID from a key within a Kubernetes Secret. + /// Cannot be set when AccessKeyID is set. + /// If neither the Access Key nor Key ID are set, we fall-back to using env + /// vars, shared credentials file or AWS Instance metadata, + /// see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "accessKeyIDSecretRef" + )] + pub access_key_id_secret_ref: Option, + /// Auth configures how cert-manager authenticates. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub auth: Option, + /// If set, the provider will manage only this zone in Route53 and will not do a lookup using the route53:ListHostedZonesByName api call. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "hostedZoneID" + )] + pub hosted_zone_id: Option, + /// Override the AWS region. + /// + /// Route53 is a global service and does not have regional endpoints but the + /// region specified here (or via environment variables) is used as a hint to + /// help compute the correct AWS credential scope and partition when it + /// connects to Route53. See: + /// - [Amazon Route 53 endpoints and quotas](https://docs.aws.amazon.com/general/latest/gr/r53.html) + /// - [Global services](https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/global-services.html) + /// + /// If you omit this region field, cert-manager will use the region from + /// AWS_REGION and AWS_DEFAULT_REGION environment variables, if they are set + /// in the cert-manager controller Pod. + /// + /// The `region` field is not needed if you use [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). + /// Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + /// [Amazon EKS Pod Identity Webhook](https://github.com/aws/amazon-eks-pod-identity-webhook). + /// In this case this `region` field value is ignored. + /// + /// The `region` field is not needed if you use [EKS Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html). + /// Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + /// [Amazon EKS Pod Identity Agent](https://github.com/aws/eks-pod-identity-agent), + /// In this case this `region` field value is ignored. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub region: Option, + /// Role is a Role ARN which the Route53 provider will assume using either the explicit credentials AccessKeyID/SecretAccessKey + /// or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata + #[serde(default, skip_serializing_if = "Option::is_none")] + pub role: Option, + /// The SecretAccessKey is used for authentication. + /// If neither the Access Key nor Key ID are set, we fall-back to using env + /// vars, shared credentials file or AWS Instance metadata, + /// see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "secretAccessKeySecretRef" + )] + pub secret_access_key_secret_ref: Option, +} + +/// The SecretAccessKey is used for authentication. If set, pull the AWS +/// access key ID from a key within a Kubernetes Secret. +/// Cannot be set when AccessKeyID is set. +/// If neither the Access Key nor Key ID are set, we fall-back to using env +/// vars, shared credentials file or AWS Instance metadata, +/// see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01Route53AccessKeyIdSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Auth configures how cert-manager authenticates. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01Route53Auth { + /// Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + /// by passing a bound ServiceAccount token. + pub kubernetes: IssuerAcmeSolversDns01Route53AuthKubernetes, +} + +/// Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity +/// by passing a bound ServiceAccount token. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01Route53AuthKubernetes { + /// A reference to a service account that will be used to request a bound + /// token (also known as "projected token"). To use this field, you must + /// configure an RBAC rule to let cert-manager request a token. + #[serde(rename = "serviceAccountRef")] + pub service_account_ref: IssuerAcmeSolversDns01Route53AuthKubernetesServiceAccountRef, +} + +/// A reference to a service account that will be used to request a bound +/// token (also known as "projected token"). To use this field, you must +/// configure an RBAC rule to let cert-manager request a token. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01Route53AuthKubernetesServiceAccountRef { + /// TokenAudiences is an optional list of audiences to include in the + /// token passed to AWS. The default token consisting of the issuer's namespace + /// and name is always included. + /// If unset the audience defaults to `sts.amazonaws.com`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub audiences: Option>, + /// Name of the ServiceAccount used to request a token. + pub name: String, +} + +/// The SecretAccessKey is used for authentication. +/// If neither the Access Key nor Key ID are set, we fall-back to using env +/// vars, shared credentials file or AWS Instance metadata, +/// see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01Route53SecretAccessKeySecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Configure an external webhook based DNS01 challenge solver to manage +/// DNS01 challenge records. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversDns01Webhook { + /// Additional configuration that should be passed to the webhook apiserver + /// when challenges are processed. + /// This can contain arbitrary JSON data. + /// Secret values should not be specified in this stanza. + /// If secret values are needed (e.g. credentials for a DNS service), you + /// should use a SecretKeySelector to reference a Secret resource. + /// For details on the schema of this field, consult the webhook provider + /// implementation's documentation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub config: Option, + /// The API group name that should be used when POSTing ChallengePayload + /// resources to the webhook apiserver. + /// This should be the same as the GroupName specified in the webhook + /// provider implementation. + #[serde(rename = "groupName")] + pub group_name: String, + /// The name of the solver to use, as defined in the webhook provider + /// implementation. + /// This will typically be the name of the provider, e.g. 'cloudflare'. + #[serde(rename = "solverName")] + pub solver_name: String, +} + +/// Configures cert-manager to attempt to complete authorizations by +/// performing the HTTP01 challenge flow. +/// It is not possible to obtain certificates for wildcard domain names +/// (e.g. `*.example.com`) using the HTTP01 challenge mechanism. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01 { + /// The Gateway API is a sig-network community API that models service networking + /// in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will + /// create HTTPRoutes with the specified labels in the same namespace as the challenge. + /// This solver is experimental, and fields / behaviour may change in the future. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "gatewayHTTPRoute" + )] + pub gateway_http_route: Option, + /// The ingress based HTTP01 challenge solver will solve challenges by + /// creating or modifying Ingress resources in order to route requests for + /// '/.well-known/acme-challenge/XYZ' to 'challenge solver' pods that are + /// provisioned by cert-manager for each Challenge to be completed. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ingress: Option, +} + +/// The Gateway API is a sig-network community API that models service networking +/// in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will +/// create HTTPRoutes with the specified labels in the same namespace as the challenge. +/// This solver is experimental, and fields / behaviour may change in the future. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoute { + /// Custom labels that will be applied to HTTPRoutes created by cert-manager + /// while solving HTTP-01 challenges. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub labels: Option>, + /// When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. + /// cert-manager needs to know which parentRefs should be used when creating + /// the HTTPRoute. Usually, the parentRef references a Gateway. See: + /// https://gateway-api.sigs.k8s.io/api-types/httproute/#attaching-to-gateways + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "parentRefs" + )] + pub parent_refs: Option>, + /// Optional pod template used to configure the ACME challenge solver pods + /// used for HTTP01 challenges. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "podTemplate" + )] + pub pod_template: Option, + /// Optional service type for Kubernetes solver service. Supported values + /// are NodePort or ClusterIP. If unset, defaults to NodePort. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "serviceType" + )] + pub service_type: Option, +} + +/// ParentReference identifies an API object (usually a Gateway) that can be considered +/// a parent of this resource (usually a route). There are two kinds of parent resources +/// with "Core" support: +/// +/// * Gateway (Gateway conformance profile) +/// * Service (Mesh conformance profile, ClusterIP Services only) +/// +/// This API may be extended in the future to support additional kinds of parent +/// resources. +/// +/// The API object must be valid in the cluster; the Group and Kind must +/// be registered in the cluster for this reference to be valid. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRouteParentRefs { + /// Group is the group of the referent. + /// When unspecified, "gateway.networking.k8s.io" is inferred. + /// To set the core API group (such as for a "Service" kind referent), + /// Group must be explicitly set to "" (empty string). + /// + /// Support: Core + #[serde(default, skip_serializing_if = "Option::is_none")] + pub group: Option, + /// Kind is kind of the referent. + /// + /// There are two kinds of parent resources with "Core" support: + /// + /// * Gateway (Gateway conformance profile) + /// * Service (Mesh conformance profile, ClusterIP Services only) + /// + /// Support for other resources is Implementation-Specific. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub kind: Option, + /// Name is the name of the referent. + /// + /// Support: Core + pub name: String, + /// Namespace is the namespace of the referent. When unspecified, this refers + /// to the local namespace of the Route. + /// + /// Note that there are specific rules for ParentRefs which cross namespace + /// boundaries. Cross-namespace references are only valid if they are explicitly + /// allowed by something in the namespace they are referring to. For example: + /// Gateway has the AllowedRoutes field, and ReferenceGrant provides a + /// generic way to enable any other kind of cross-namespace reference. + /// + /// + /// ParentRefs from a Route to a Service in the same namespace are "producer" + /// routes, which apply default routing rules to inbound connections from + /// any namespace to the Service. + /// + /// ParentRefs from a Route to a Service in a different namespace are + /// "consumer" routes, and these routing rules are only applied to outbound + /// connections originating from the same namespace as the Route, for which + /// the intended destination of the connections are a Service targeted as a + /// ParentRef of the Route. + /// + /// + /// Support: Core + #[serde(default, skip_serializing_if = "Option::is_none")] + pub namespace: Option, + /// Port is the network port this Route targets. It can be interpreted + /// differently based on the type of parent resource. + /// + /// When the parent resource is a Gateway, this targets all listeners + /// listening on the specified port that also support this kind of Route(and + /// select this Route). It's not recommended to set `Port` unless the + /// networking behaviors specified in a Route must apply to a specific port + /// as opposed to a listener(s) whose port(s) may be changed. When both Port + /// and SectionName are specified, the name and port of the selected listener + /// must match both specified values. + /// + /// + /// When the parent resource is a Service, this targets a specific port in the + /// Service spec. When both Port (experimental) and SectionName are specified, + /// the name and port of the selected port must match both specified values. + /// + /// + /// Implementations MAY choose to support other parent resources. + /// Implementations supporting other types of parent resources MUST clearly + /// document how/if Port is interpreted. + /// + /// For the purpose of status, an attachment is considered successful as + /// long as the parent resource accepts it partially. For example, Gateway + /// listeners can restrict which Routes can attach to them by Route kind, + /// namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + /// from the referencing Route, the Route MUST be considered successfully + /// attached. If no Gateway listeners accept attachment from this Route, + /// the Route MUST be considered detached from the Gateway. + /// + /// Support: Extended + #[serde(default, skip_serializing_if = "Option::is_none")] + pub port: Option, + /// SectionName is the name of a section within the target resource. In the + /// following resources, SectionName is interpreted as the following: + /// + /// * Gateway: Listener name. When both Port (experimental) and SectionName + /// are specified, the name and port of the selected listener must match + /// both specified values. + /// * Service: Port name. When both Port (experimental) and SectionName + /// are specified, the name and port of the selected listener must match + /// both specified values. + /// + /// Implementations MAY choose to support attaching Routes to other resources. + /// If that is the case, they MUST clearly document how SectionName is + /// interpreted. + /// + /// When unspecified (empty string), this will reference the entire resource. + /// For the purpose of status, an attachment is considered successful if at + /// least one section in the parent resource accepts it. For example, Gateway + /// listeners can restrict which Routes can attach to them by Route kind, + /// namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + /// the referencing Route, the Route MUST be considered successfully + /// attached. If no Gateway listeners accept attachment from this Route, the + /// Route MUST be considered detached from the Gateway. + /// + /// Support: Core + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "sectionName" + )] + pub section_name: Option, +} + +/// Optional pod template used to configure the ACME challenge solver pods +/// used for HTTP01 challenges. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplate { + /// ObjectMeta overrides for the pod used to solve HTTP01 challenges. + /// Only the 'labels' and 'annotations' fields may be set. + /// If labels or annotations overlap with in-built values, the values here + /// will override the in-built values. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// PodSpec defines overrides for the HTTP01 challenge solver pod. + /// Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + /// All other fields will be ignored. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub spec: Option, +} + +/// ObjectMeta overrides for the pod used to solve HTTP01 challenges. +/// Only the 'labels' and 'annotations' fields may be set. +/// If labels or annotations overlap with in-built values, the values here +/// will override the in-built values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateMetadata { + /// Annotations that should be added to the created ACME HTTP01 solver pods. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub annotations: Option>, + /// Labels that should be added to the created ACME HTTP01 solver pods. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub labels: Option>, +} + +/// PodSpec defines overrides for the HTTP01 challenge solver pod. +/// Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. +/// All other fields will be ignored. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpec { + /// If specified, the pod's scheduling constraints + #[serde(default, skip_serializing_if = "Option::is_none")] + pub affinity: Option, + /// If specified, the pod's imagePullSecrets + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "imagePullSecrets" + )] + pub image_pull_secrets: + Option>, + /// NodeSelector is a selector which must be true for the pod to fit on a node. + /// Selector which must match a node's labels for the pod to be scheduled on that node. + /// More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "nodeSelector" + )] + pub node_selector: Option>, + /// If specified, the pod's priorityClassName. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "priorityClassName" + )] + pub priority_class_name: Option, + /// If specified, the pod's security context + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "securityContext" + )] + pub security_context: + Option, + /// If specified, the pod's service account + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "serviceAccountName" + )] + pub service_account_name: Option, + /// If specified, the pod's tolerations. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tolerations: Option>, +} + +/// If specified, the pod's scheduling constraints +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinity { + /// Describes node affinity scheduling rules for the pod. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "nodeAffinity" + )] + pub node_affinity: + Option, + /// Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "podAffinity" + )] + pub pod_affinity: + Option, + /// Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "podAntiAffinity" + )] + pub pod_anti_affinity: + Option, +} + +/// Describes node affinity scheduling rules for the pod. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityNodeAffinity { + /// The scheduler will prefer to schedule pods to nodes that satisfy + /// the affinity expressions specified by this field, but it may choose + /// a node that violates one or more of the expressions. The node that is + /// most preferred is the one with the greatest sum of weights, i.e. + /// for each node that meets all of the scheduling requirements (resource + /// request, requiredDuringScheduling affinity expressions, etc.), + /// compute a sum by iterating through the elements of this field and adding + /// "weight" to the sum if the node matches the corresponding matchExpressions; the + /// node(s) with the highest sum are the most preferred. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "preferredDuringSchedulingIgnoredDuringExecution")] + pub preferred_during_scheduling_ignored_during_execution: Option>, + /// If the affinity requirements specified by this field are not met at + /// scheduling time, the pod will not be scheduled onto the node. + /// If the affinity requirements specified by this field cease to be met + /// at some point during pod execution (e.g. due to an update), the system + /// may or may not try to eventually evict the pod from its node. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "requiredDuringSchedulingIgnoredDuringExecution")] + pub required_during_scheduling_ignored_during_execution: Option, +} + +/// An empty preferred scheduling term matches all objects with implicit weight 0 +/// (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecution { + /// A node selector term, associated with the corresponding weight. + pub preference: IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionPreference, + /// Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + pub weight: i32, +} + +/// A node selector term, associated with the corresponding weight. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionPreference { + /// A list of node selector requirements by node's labels. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// A list of node selector requirements by node's fields. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchFields")] + pub match_fields: Option>, +} + +/// A node selector requirement is a selector that contains values, a key, and an operator +/// that relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionPreferenceMatchExpressions +{ + /// The label key that the selector applies to. + pub key: String, + /// Represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + pub operator: String, + /// An array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. If the operator is Gt or Lt, the values + /// array must have a single element, which will be interpreted as an integer. + /// This array is replaced during a strategic merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// A node selector requirement is a selector that contains values, a key, and an operator +/// that relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionPreferenceMatchFields +{ + /// The label key that the selector applies to. + pub key: String, + /// Represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + pub operator: String, + /// An array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. If the operator is Gt or Lt, the values + /// array must have a single element, which will be interpreted as an integer. + /// This array is replaced during a strategic merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// If the affinity requirements specified by this field are not met at +/// scheduling time, the pod will not be scheduled onto the node. +/// If the affinity requirements specified by this field cease to be met +/// at some point during pod execution (e.g. due to an update), the system +/// may or may not try to eventually evict the pod from its node. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecution { + /// Required. A list of node selector terms. The terms are ORed. + #[serde(rename = "nodeSelectorTerms")] + pub node_selector_terms: Vec, +} + +/// A null or empty node selector term matches no objects. The requirements of +/// them are ANDed. +/// The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecutionNodeSelectorTerms { + /// A list of node selector requirements by node's labels. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// A list of node selector requirements by node's fields. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchFields")] + pub match_fields: Option>, +} + +/// A node selector requirement is a selector that contains values, a key, and an operator +/// that relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecutionNodeSelectorTermsMatchExpressions +{ + /// The label key that the selector applies to. + pub key: String, + /// Represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + pub operator: String, + /// An array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. If the operator is Gt or Lt, the values + /// array must have a single element, which will be interpreted as an integer. + /// This array is replaced during a strategic merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// A node selector requirement is a selector that contains values, a key, and an operator +/// that relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecutionNodeSelectorTermsMatchFields +{ + /// The label key that the selector applies to. + pub key: String, + /// Represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + pub operator: String, + /// An array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. If the operator is Gt or Lt, the values + /// array must have a single element, which will be interpreted as an integer. + /// This array is replaced during a strategic merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAffinity { + /// The scheduler will prefer to schedule pods to nodes that satisfy + /// the affinity expressions specified by this field, but it may choose + /// a node that violates one or more of the expressions. The node that is + /// most preferred is the one with the greatest sum of weights, i.e. + /// for each node that meets all of the scheduling requirements (resource + /// request, requiredDuringScheduling affinity expressions, etc.), + /// compute a sum by iterating through the elements of this field and adding + /// "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + /// node(s) with the highest sum are the most preferred. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "preferredDuringSchedulingIgnoredDuringExecution")] + pub preferred_during_scheduling_ignored_during_execution: Option>, + /// If the affinity requirements specified by this field are not met at + /// scheduling time, the pod will not be scheduled onto the node. + /// If the affinity requirements specified by this field cease to be met + /// at some point during pod execution (e.g. due to a pod label update), the + /// system may or may not try to eventually evict the pod from its node. + /// When there are multiple elements, the lists of nodes corresponding to each + /// podAffinityTerm are intersected, i.e. all terms must be satisfied. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "requiredDuringSchedulingIgnoredDuringExecution")] + pub required_during_scheduling_ignored_during_execution: Option>, +} + +/// The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecution { + /// Required. A pod affinity term, associated with the corresponding weight. + #[serde(rename = "podAffinityTerm")] + pub pod_affinity_term: IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTerm, + /// weight associated with matching the corresponding podAffinityTerm, + /// in the range 1-100. + pub weight: i32, +} + +/// Required. A pod affinity term, associated with the corresponding weight. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTerm { + /// A label query over a set of resources, in this case pods. + /// If it's null, this PodAffinityTerm matches with no Pods. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "labelSelector")] + pub label_selector: Option, + /// MatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both matchLabelKeys and labelSelector. + /// Also, matchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabelKeys")] + pub match_label_keys: Option>, + /// MismatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + /// Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "mismatchLabelKeys")] + pub mismatch_label_keys: Option>, + /// A label query over the set of namespaces that the term applies to. + /// The term is applied to the union of the namespaces selected by this field + /// and the ones listed in the namespaces field. + /// null selector and null or empty namespaces list means "this pod's namespace". + /// An empty selector ({}) matches all namespaces. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "namespaceSelector")] + pub namespace_selector: Option, + /// namespaces specifies a static list of namespace names that the term applies to. + /// The term is applied to the union of the namespaces listed in this field + /// and the ones selected by namespaceSelector. + /// null or empty namespaces list and null namespaceSelector means "this pod's namespace". + #[serde(default, skip_serializing_if = "Option::is_none")] + pub namespaces: Option>, + /// This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + /// the labelSelector in the specified namespaces, where co-located is defined as running on a node + /// whose value of the label with key topologyKey matches that of any node on which any of the + /// selected pods is running. + /// Empty topologyKey is not allowed. + #[serde(rename = "topologyKey")] + pub topology_key: String, +} + +/// A label query over a set of resources, in this case pods. +/// If it's null, this PodAffinityTerm matches with no Pods. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermLabelSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermLabelSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// A label query over the set of namespaces that the term applies to. +/// The term is applied to the union of the namespaces selected by this field +/// and the ones listed in the namespaces field. +/// null selector and null or empty namespaces list means "this pod's namespace". +/// An empty selector ({}) matches all namespaces. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermNamespaceSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermNamespaceSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// Defines a set of pods (namely those matching the labelSelector +/// relative to the given namespace(s)) that this pod should be +/// co-located (affinity) or not co-located (anti-affinity) with, +/// where co-located is defined as running on a node whose value of +/// the label with key matches that of any node on which +/// a pod of the set of pods is running +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAffinityRequiredDuringSchedulingIgnoredDuringExecution { + /// A label query over a set of resources, in this case pods. + /// If it's null, this PodAffinityTerm matches with no Pods. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "labelSelector")] + pub label_selector: Option, + /// MatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both matchLabelKeys and labelSelector. + /// Also, matchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabelKeys")] + pub match_label_keys: Option>, + /// MismatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + /// Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "mismatchLabelKeys")] + pub mismatch_label_keys: Option>, + /// A label query over the set of namespaces that the term applies to. + /// The term is applied to the union of the namespaces selected by this field + /// and the ones listed in the namespaces field. + /// null selector and null or empty namespaces list means "this pod's namespace". + /// An empty selector ({}) matches all namespaces. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "namespaceSelector")] + pub namespace_selector: Option, + /// namespaces specifies a static list of namespace names that the term applies to. + /// The term is applied to the union of the namespaces listed in this field + /// and the ones selected by namespaceSelector. + /// null or empty namespaces list and null namespaceSelector means "this pod's namespace". + #[serde(default, skip_serializing_if = "Option::is_none")] + pub namespaces: Option>, + /// This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + /// the labelSelector in the specified namespaces, where co-located is defined as running on a node + /// whose value of the label with key topologyKey matches that of any node on which any of the + /// selected pods is running. + /// Empty topologyKey is not allowed. + #[serde(rename = "topologyKey")] + pub topology_key: String, +} + +/// A label query over a set of resources, in this case pods. +/// If it's null, this PodAffinityTerm matches with no Pods. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAffinityRequiredDuringSchedulingIgnoredDuringExecutionLabelSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAffinityRequiredDuringSchedulingIgnoredDuringExecutionLabelSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// A label query over the set of namespaces that the term applies to. +/// The term is applied to the union of the namespaces selected by this field +/// and the ones listed in the namespaces field. +/// null selector and null or empty namespaces list means "this pod's namespace". +/// An empty selector ({}) matches all namespaces. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAffinityRequiredDuringSchedulingIgnoredDuringExecutionNamespaceSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAffinityRequiredDuringSchedulingIgnoredDuringExecutionNamespaceSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAntiAffinity { + /// The scheduler will prefer to schedule pods to nodes that satisfy + /// the anti-affinity expressions specified by this field, but it may choose + /// a node that violates one or more of the expressions. The node that is + /// most preferred is the one with the greatest sum of weights, i.e. + /// for each node that meets all of the scheduling requirements (resource + /// request, requiredDuringScheduling anti-affinity expressions, etc.), + /// compute a sum by iterating through the elements of this field and adding + /// "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + /// node(s) with the highest sum are the most preferred. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "preferredDuringSchedulingIgnoredDuringExecution")] + pub preferred_during_scheduling_ignored_during_execution: Option>, + /// If the anti-affinity requirements specified by this field are not met at + /// scheduling time, the pod will not be scheduled onto the node. + /// If the anti-affinity requirements specified by this field cease to be met + /// at some point during pod execution (e.g. due to a pod label update), the + /// system may or may not try to eventually evict the pod from its node. + /// When there are multiple elements, the lists of nodes corresponding to each + /// podAffinityTerm are intersected, i.e. all terms must be satisfied. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "requiredDuringSchedulingIgnoredDuringExecution")] + pub required_during_scheduling_ignored_during_execution: Option>, +} + +/// The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecution { + /// Required. A pod affinity term, associated with the corresponding weight. + #[serde(rename = "podAffinityTerm")] + pub pod_affinity_term: IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTerm, + /// weight associated with matching the corresponding podAffinityTerm, + /// in the range 1-100. + pub weight: i32, +} + +/// Required. A pod affinity term, associated with the corresponding weight. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTerm { + /// A label query over a set of resources, in this case pods. + /// If it's null, this PodAffinityTerm matches with no Pods. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "labelSelector")] + pub label_selector: Option, + /// MatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both matchLabelKeys and labelSelector. + /// Also, matchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabelKeys")] + pub match_label_keys: Option>, + /// MismatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + /// Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "mismatchLabelKeys")] + pub mismatch_label_keys: Option>, + /// A label query over the set of namespaces that the term applies to. + /// The term is applied to the union of the namespaces selected by this field + /// and the ones listed in the namespaces field. + /// null selector and null or empty namespaces list means "this pod's namespace". + /// An empty selector ({}) matches all namespaces. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "namespaceSelector")] + pub namespace_selector: Option, + /// namespaces specifies a static list of namespace names that the term applies to. + /// The term is applied to the union of the namespaces listed in this field + /// and the ones selected by namespaceSelector. + /// null or empty namespaces list and null namespaceSelector means "this pod's namespace". + #[serde(default, skip_serializing_if = "Option::is_none")] + pub namespaces: Option>, + /// This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + /// the labelSelector in the specified namespaces, where co-located is defined as running on a node + /// whose value of the label with key topologyKey matches that of any node on which any of the + /// selected pods is running. + /// Empty topologyKey is not allowed. + #[serde(rename = "topologyKey")] + pub topology_key: String, +} + +/// A label query over a set of resources, in this case pods. +/// If it's null, this PodAffinityTerm matches with no Pods. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermLabelSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermLabelSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// A label query over the set of namespaces that the term applies to. +/// The term is applied to the union of the namespaces selected by this field +/// and the ones listed in the namespaces field. +/// null selector and null or empty namespaces list means "this pod's namespace". +/// An empty selector ({}) matches all namespaces. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermNamespaceSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermNamespaceSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// Defines a set of pods (namely those matching the labelSelector +/// relative to the given namespace(s)) that this pod should be +/// co-located (affinity) or not co-located (anti-affinity) with, +/// where co-located is defined as running on a node whose value of +/// the label with key matches that of any node on which +/// a pod of the set of pods is running +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAntiAffinityRequiredDuringSchedulingIgnoredDuringExecution { + /// A label query over a set of resources, in this case pods. + /// If it's null, this PodAffinityTerm matches with no Pods. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "labelSelector")] + pub label_selector: Option, + /// MatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both matchLabelKeys and labelSelector. + /// Also, matchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabelKeys")] + pub match_label_keys: Option>, + /// MismatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + /// Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "mismatchLabelKeys")] + pub mismatch_label_keys: Option>, + /// A label query over the set of namespaces that the term applies to. + /// The term is applied to the union of the namespaces selected by this field + /// and the ones listed in the namespaces field. + /// null selector and null or empty namespaces list means "this pod's namespace". + /// An empty selector ({}) matches all namespaces. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "namespaceSelector")] + pub namespace_selector: Option, + /// namespaces specifies a static list of namespace names that the term applies to. + /// The term is applied to the union of the namespaces listed in this field + /// and the ones selected by namespaceSelector. + /// null or empty namespaces list and null namespaceSelector means "this pod's namespace". + #[serde(default, skip_serializing_if = "Option::is_none")] + pub namespaces: Option>, + /// This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + /// the labelSelector in the specified namespaces, where co-located is defined as running on a node + /// whose value of the label with key topologyKey matches that of any node on which any of the + /// selected pods is running. + /// Empty topologyKey is not allowed. + #[serde(rename = "topologyKey")] + pub topology_key: String, +} + +/// A label query over a set of resources, in this case pods. +/// If it's null, this PodAffinityTerm matches with no Pods. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAntiAffinityRequiredDuringSchedulingIgnoredDuringExecutionLabelSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAntiAffinityRequiredDuringSchedulingIgnoredDuringExecutionLabelSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// A label query over the set of namespaces that the term applies to. +/// The term is applied to the union of the namespaces selected by this field +/// and the ones listed in the namespaces field. +/// null selector and null or empty namespaces list means "this pod's namespace". +/// An empty selector ({}) matches all namespaces. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAntiAffinityRequiredDuringSchedulingIgnoredDuringExecutionNamespaceSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecAffinityPodAntiAffinityRequiredDuringSchedulingIgnoredDuringExecutionNamespaceSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// LocalObjectReference contains enough information to let you locate the +/// referenced object inside the same namespace. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecImagePullSecrets { + /// Name of the referent. + /// This field is effectively required, but due to backwards compatibility is + /// allowed to be empty. Instances of this type with an empty value here are + /// almost certainly wrong. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +/// If specified, the pod's security context +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecSecurityContext { + /// A special supplemental group that applies to all containers in a pod. + /// Some volume types allow the Kubelet to change the ownership of that volume + /// to be owned by the pod: + /// + /// 1. The owning GID will be the FSGroup + /// 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + /// 3. The permission bits are OR'd with rw-rw---- + /// + /// If unset, the Kubelet will not modify the ownership and permissions of any volume. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "fsGroup")] + pub fs_group: Option, + /// fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + /// before being exposed inside Pod. This field will only apply to + /// volume types which support fsGroup based ownership(and permissions). + /// It will have no effect on ephemeral volume types such as: secret, configmaps + /// and emptydir. + /// Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "fsGroupChangePolicy" + )] + pub fs_group_change_policy: Option, + /// The GID to run the entrypoint of the container process. + /// Uses runtime default if unset. + /// May also be set in SecurityContext. If set in both SecurityContext and + /// PodSecurityContext, the value specified in SecurityContext takes precedence + /// for that container. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "runAsGroup" + )] + pub run_as_group: Option, + /// Indicates that the container must run as a non-root user. + /// If true, the Kubelet will validate the image at runtime to ensure that it + /// does not run as UID 0 (root) and fail to start the container if it does. + /// If unset or false, no such validation will be performed. + /// May also be set in SecurityContext. If set in both SecurityContext and + /// PodSecurityContext, the value specified in SecurityContext takes precedence. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "runAsNonRoot" + )] + pub run_as_non_root: Option, + /// The UID to run the entrypoint of the container process. + /// Defaults to user specified in image metadata if unspecified. + /// May also be set in SecurityContext. If set in both SecurityContext and + /// PodSecurityContext, the value specified in SecurityContext takes precedence + /// for that container. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "runAsUser")] + pub run_as_user: Option, + /// The SELinux context to be applied to all containers. + /// If unspecified, the container runtime will allocate a random SELinux context for each + /// container. May also be set in SecurityContext. If set in + /// both SecurityContext and PodSecurityContext, the value specified in SecurityContext + /// takes precedence for that container. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "seLinuxOptions" + )] + pub se_linux_options: + Option, + /// The seccomp options to use by the containers in this pod. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "seccompProfile" + )] + pub seccomp_profile: + Option, + /// A list of groups applied to the first process run in each container, in addition + /// to the container's primary GID, the fsGroup (if specified), and group memberships + /// defined in the container image for the uid of the container process. If unspecified, + /// no additional groups are added to any container. Note that group memberships + /// defined in the container image for the uid of the container process are still effective, + /// even if they are not included in this list. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "supplementalGroups" + )] + pub supplemental_groups: Option>, + /// Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + /// sysctls (by the container runtime) might fail to launch. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sysctls: + Option>, +} + +/// The SELinux context to be applied to all containers. +/// If unspecified, the container runtime will allocate a random SELinux context for each +/// container. May also be set in SecurityContext. If set in +/// both SecurityContext and PodSecurityContext, the value specified in SecurityContext +/// takes precedence for that container. +/// Note that this field cannot be set when spec.os.name is windows. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecSecurityContextSeLinuxOptions { + /// Level is SELinux level label that applies to the container. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub level: Option, + /// Role is a SELinux role label that applies to the container. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub role: Option, + /// Type is a SELinux type label that applies to the container. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "type")] + pub r#type: Option, + /// User is a SELinux user label that applies to the container. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, +} + +/// The seccomp options to use by the containers in this pod. +/// Note that this field cannot be set when spec.os.name is windows. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecSecurityContextSeccompProfile { + /// localhostProfile indicates a profile defined in a file on the node should be used. + /// The profile must be preconfigured on the node to work. + /// Must be a descending path, relative to the kubelet's configured seccomp profile location. + /// Must be set if type is "Localhost". Must NOT be set for any other type. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "localhostProfile" + )] + pub localhost_profile: Option, + /// type indicates which kind of seccomp profile will be applied. + /// Valid options are: + /// + /// Localhost - a profile defined in a file on the node should be used. + /// RuntimeDefault - the container runtime default profile should be used. + /// Unconfined - no profile should be applied. + #[serde(rename = "type")] + pub r#type: String, +} + +/// Sysctl defines a kernel parameter to be set +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecSecurityContextSysctls { + /// Name of a property to set + pub name: String, + /// Value of a property to set + pub value: String, +} + +/// The pod this Toleration is attached to tolerates any taint that matches +/// the triple using the matching operator . +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01GatewayHttpRoutePodTemplateSpecTolerations { + /// Effect indicates the taint effect to match. Empty means match all taint effects. + /// When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub effect: Option, + /// Key is the taint key that the toleration applies to. Empty means match all taint keys. + /// If the key is empty, operator must be Exists; this combination means to match all values and all keys. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Operator represents a key's relationship to the value. + /// Valid operators are Exists and Equal. Defaults to Equal. + /// Exists is equivalent to wildcard for value, so that a pod can + /// tolerate all taints of a particular category. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub operator: Option, + /// TolerationSeconds represents the period of time the toleration (which must be + /// of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + /// it is not set, which means tolerate the taint forever (do not evict). Zero and + /// negative values will be treated as 0 (evict immediately) by the system. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "tolerationSeconds" + )] + pub toleration_seconds: Option, + /// Value is the taint value the toleration matches to. + /// If the operator is Exists, the value should be empty, otherwise just a regular string. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, +} + +/// The ingress based HTTP01 challenge solver will solve challenges by +/// creating or modifying Ingress resources in order to route requests for +/// '/.well-known/acme-challenge/XYZ' to 'challenge solver' pods that are +/// provisioned by cert-manager for each Challenge to be completed. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01Ingress { + /// This field configures the annotation `kubernetes.io/ingress.class` when + /// creating Ingress resources to solve ACME challenges that use this + /// challenge solver. Only one of `class`, `name` or `ingressClassName` may + /// be specified. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub class: Option, + /// This field configures the field `ingressClassName` on the created Ingress + /// resources used to solve ACME challenges that use this challenge solver. + /// This is the recommended way of configuring the ingress class. Only one of + /// `class`, `name` or `ingressClassName` may be specified. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "ingressClassName" + )] + pub ingress_class_name: Option, + /// Optional ingress template used to configure the ACME challenge solver + /// ingress used for HTTP01 challenges. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "ingressTemplate" + )] + pub ingress_template: Option, + /// The name of the ingress resource that should have ACME challenge solving + /// routes inserted into it in order to solve HTTP01 challenges. + /// This is typically used in conjunction with ingress controllers like + /// ingress-gce, which maintains a 1:1 mapping between external IPs and + /// ingress resources. Only one of `class`, `name` or `ingressClassName` may + /// be specified. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Optional pod template used to configure the ACME challenge solver pods + /// used for HTTP01 challenges. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "podTemplate" + )] + pub pod_template: Option, + /// Optional service type for Kubernetes solver service. Supported values + /// are NodePort or ClusterIP. If unset, defaults to NodePort. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "serviceType" + )] + pub service_type: Option, +} + +/// Optional ingress template used to configure the ACME challenge solver +/// ingress used for HTTP01 challenges. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressIngressTemplate { + /// ObjectMeta overrides for the ingress used to solve HTTP01 challenges. + /// Only the 'labels' and 'annotations' fields may be set. + /// If labels or annotations overlap with in-built values, the values here + /// will override the in-built values. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +/// ObjectMeta overrides for the ingress used to solve HTTP01 challenges. +/// Only the 'labels' and 'annotations' fields may be set. +/// If labels or annotations overlap with in-built values, the values here +/// will override the in-built values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressIngressTemplateMetadata { + /// Annotations that should be added to the created ACME HTTP01 solver ingress. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub annotations: Option>, + /// Labels that should be added to the created ACME HTTP01 solver ingress. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub labels: Option>, +} + +/// Optional pod template used to configure the ACME challenge solver pods +/// used for HTTP01 challenges. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplate { + /// ObjectMeta overrides for the pod used to solve HTTP01 challenges. + /// Only the 'labels' and 'annotations' fields may be set. + /// If labels or annotations overlap with in-built values, the values here + /// will override the in-built values. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// PodSpec defines overrides for the HTTP01 challenge solver pod. + /// Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + /// All other fields will be ignored. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub spec: Option, +} + +/// ObjectMeta overrides for the pod used to solve HTTP01 challenges. +/// Only the 'labels' and 'annotations' fields may be set. +/// If labels or annotations overlap with in-built values, the values here +/// will override the in-built values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateMetadata { + /// Annotations that should be added to the created ACME HTTP01 solver pods. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub annotations: Option>, + /// Labels that should be added to the created ACME HTTP01 solver pods. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub labels: Option>, +} + +/// PodSpec defines overrides for the HTTP01 challenge solver pod. +/// Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. +/// All other fields will be ignored. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpec { + /// If specified, the pod's scheduling constraints + #[serde(default, skip_serializing_if = "Option::is_none")] + pub affinity: Option, + /// If specified, the pod's imagePullSecrets + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "imagePullSecrets" + )] + pub image_pull_secrets: + Option>, + /// NodeSelector is a selector which must be true for the pod to fit on a node. + /// Selector which must match a node's labels for the pod to be scheduled on that node. + /// More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "nodeSelector" + )] + pub node_selector: Option>, + /// If specified, the pod's priorityClassName. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "priorityClassName" + )] + pub priority_class_name: Option, + /// If specified, the pod's security context + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "securityContext" + )] + pub security_context: Option, + /// If specified, the pod's service account + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "serviceAccountName" + )] + pub service_account_name: Option, + /// If specified, the pod's tolerations. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tolerations: Option>, +} + +/// If specified, the pod's scheduling constraints +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinity { + /// Describes node affinity scheduling rules for the pod. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "nodeAffinity" + )] + pub node_affinity: Option, + /// Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "podAffinity" + )] + pub pod_affinity: Option, + /// Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "podAntiAffinity" + )] + pub pod_anti_affinity: + Option, +} + +/// Describes node affinity scheduling rules for the pod. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityNodeAffinity { + /// The scheduler will prefer to schedule pods to nodes that satisfy + /// the affinity expressions specified by this field, but it may choose + /// a node that violates one or more of the expressions. The node that is + /// most preferred is the one with the greatest sum of weights, i.e. + /// for each node that meets all of the scheduling requirements (resource + /// request, requiredDuringScheduling affinity expressions, etc.), + /// compute a sum by iterating through the elements of this field and adding + /// "weight" to the sum if the node matches the corresponding matchExpressions; the + /// node(s) with the highest sum are the most preferred. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "preferredDuringSchedulingIgnoredDuringExecution")] + pub preferred_during_scheduling_ignored_during_execution: Option>, + /// If the affinity requirements specified by this field are not met at + /// scheduling time, the pod will not be scheduled onto the node. + /// If the affinity requirements specified by this field cease to be met + /// at some point during pod execution (e.g. due to an update), the system + /// may or may not try to eventually evict the pod from its node. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "requiredDuringSchedulingIgnoredDuringExecution")] + pub required_during_scheduling_ignored_during_execution: Option, +} + +/// An empty preferred scheduling term matches all objects with implicit weight 0 +/// (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecution { + /// A node selector term, associated with the corresponding weight. + pub preference: IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionPreference, + /// Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + pub weight: i32, +} + +/// A node selector term, associated with the corresponding weight. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionPreference { + /// A list of node selector requirements by node's labels. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// A list of node selector requirements by node's fields. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchFields")] + pub match_fields: Option>, +} + +/// A node selector requirement is a selector that contains values, a key, and an operator +/// that relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionPreferenceMatchExpressions +{ + /// The label key that the selector applies to. + pub key: String, + /// Represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + pub operator: String, + /// An array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. If the operator is Gt or Lt, the values + /// array must have a single element, which will be interpreted as an integer. + /// This array is replaced during a strategic merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// A node selector requirement is a selector that contains values, a key, and an operator +/// that relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecutionPreferenceMatchFields +{ + /// The label key that the selector applies to. + pub key: String, + /// Represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + pub operator: String, + /// An array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. If the operator is Gt or Lt, the values + /// array must have a single element, which will be interpreted as an integer. + /// This array is replaced during a strategic merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// If the affinity requirements specified by this field are not met at +/// scheduling time, the pod will not be scheduled onto the node. +/// If the affinity requirements specified by this field cease to be met +/// at some point during pod execution (e.g. due to an update), the system +/// may or may not try to eventually evict the pod from its node. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecution { + /// Required. A list of node selector terms. The terms are ORed. + #[serde(rename = "nodeSelectorTerms")] + pub node_selector_terms: Vec, +} + +/// A null or empty node selector term matches no objects. The requirements of +/// them are ANDed. +/// The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecutionNodeSelectorTerms { + /// A list of node selector requirements by node's labels. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// A list of node selector requirements by node's fields. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchFields")] + pub match_fields: Option>, +} + +/// A node selector requirement is a selector that contains values, a key, and an operator +/// that relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecutionNodeSelectorTermsMatchExpressions +{ + /// The label key that the selector applies to. + pub key: String, + /// Represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + pub operator: String, + /// An array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. If the operator is Gt or Lt, the values + /// array must have a single element, which will be interpreted as an integer. + /// This array is replaced during a strategic merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// A node selector requirement is a selector that contains values, a key, and an operator +/// that relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecutionNodeSelectorTermsMatchFields +{ + /// The label key that the selector applies to. + pub key: String, + /// Represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + pub operator: String, + /// An array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. If the operator is Gt or Lt, the values + /// array must have a single element, which will be interpreted as an integer. + /// This array is replaced during a strategic merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAffinity { + /// The scheduler will prefer to schedule pods to nodes that satisfy + /// the affinity expressions specified by this field, but it may choose + /// a node that violates one or more of the expressions. The node that is + /// most preferred is the one with the greatest sum of weights, i.e. + /// for each node that meets all of the scheduling requirements (resource + /// request, requiredDuringScheduling affinity expressions, etc.), + /// compute a sum by iterating through the elements of this field and adding + /// "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + /// node(s) with the highest sum are the most preferred. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "preferredDuringSchedulingIgnoredDuringExecution")] + pub preferred_during_scheduling_ignored_during_execution: Option>, + /// If the affinity requirements specified by this field are not met at + /// scheduling time, the pod will not be scheduled onto the node. + /// If the affinity requirements specified by this field cease to be met + /// at some point during pod execution (e.g. due to a pod label update), the + /// system may or may not try to eventually evict the pod from its node. + /// When there are multiple elements, the lists of nodes corresponding to each + /// podAffinityTerm are intersected, i.e. all terms must be satisfied. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "requiredDuringSchedulingIgnoredDuringExecution")] + pub required_during_scheduling_ignored_during_execution: Option>, +} + +/// The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecution { + /// Required. A pod affinity term, associated with the corresponding weight. + #[serde(rename = "podAffinityTerm")] + pub pod_affinity_term: IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTerm, + /// weight associated with matching the corresponding podAffinityTerm, + /// in the range 1-100. + pub weight: i32, +} + +/// Required. A pod affinity term, associated with the corresponding weight. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTerm { + /// A label query over a set of resources, in this case pods. + /// If it's null, this PodAffinityTerm matches with no Pods. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "labelSelector")] + pub label_selector: Option, + /// MatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both matchLabelKeys and labelSelector. + /// Also, matchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabelKeys")] + pub match_label_keys: Option>, + /// MismatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + /// Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "mismatchLabelKeys")] + pub mismatch_label_keys: Option>, + /// A label query over the set of namespaces that the term applies to. + /// The term is applied to the union of the namespaces selected by this field + /// and the ones listed in the namespaces field. + /// null selector and null or empty namespaces list means "this pod's namespace". + /// An empty selector ({}) matches all namespaces. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "namespaceSelector")] + pub namespace_selector: Option, + /// namespaces specifies a static list of namespace names that the term applies to. + /// The term is applied to the union of the namespaces listed in this field + /// and the ones selected by namespaceSelector. + /// null or empty namespaces list and null namespaceSelector means "this pod's namespace". + #[serde(default, skip_serializing_if = "Option::is_none")] + pub namespaces: Option>, + /// This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + /// the labelSelector in the specified namespaces, where co-located is defined as running on a node + /// whose value of the label with key topologyKey matches that of any node on which any of the + /// selected pods is running. + /// Empty topologyKey is not allowed. + #[serde(rename = "topologyKey")] + pub topology_key: String, +} + +/// A label query over a set of resources, in this case pods. +/// If it's null, this PodAffinityTerm matches with no Pods. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermLabelSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermLabelSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// A label query over the set of namespaces that the term applies to. +/// The term is applied to the union of the namespaces selected by this field +/// and the ones listed in the namespaces field. +/// null selector and null or empty namespaces list means "this pod's namespace". +/// An empty selector ({}) matches all namespaces. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermNamespaceSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermNamespaceSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// Defines a set of pods (namely those matching the labelSelector +/// relative to the given namespace(s)) that this pod should be +/// co-located (affinity) or not co-located (anti-affinity) with, +/// where co-located is defined as running on a node whose value of +/// the label with key matches that of any node on which +/// a pod of the set of pods is running +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAffinityRequiredDuringSchedulingIgnoredDuringExecution { + /// A label query over a set of resources, in this case pods. + /// If it's null, this PodAffinityTerm matches with no Pods. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "labelSelector")] + pub label_selector: Option, + /// MatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both matchLabelKeys and labelSelector. + /// Also, matchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabelKeys")] + pub match_label_keys: Option>, + /// MismatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + /// Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "mismatchLabelKeys")] + pub mismatch_label_keys: Option>, + /// A label query over the set of namespaces that the term applies to. + /// The term is applied to the union of the namespaces selected by this field + /// and the ones listed in the namespaces field. + /// null selector and null or empty namespaces list means "this pod's namespace". + /// An empty selector ({}) matches all namespaces. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "namespaceSelector")] + pub namespace_selector: Option, + /// namespaces specifies a static list of namespace names that the term applies to. + /// The term is applied to the union of the namespaces listed in this field + /// and the ones selected by namespaceSelector. + /// null or empty namespaces list and null namespaceSelector means "this pod's namespace". + #[serde(default, skip_serializing_if = "Option::is_none")] + pub namespaces: Option>, + /// This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + /// the labelSelector in the specified namespaces, where co-located is defined as running on a node + /// whose value of the label with key topologyKey matches that of any node on which any of the + /// selected pods is running. + /// Empty topologyKey is not allowed. + #[serde(rename = "topologyKey")] + pub topology_key: String, +} + +/// A label query over a set of resources, in this case pods. +/// If it's null, this PodAffinityTerm matches with no Pods. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAffinityRequiredDuringSchedulingIgnoredDuringExecutionLabelSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAffinityRequiredDuringSchedulingIgnoredDuringExecutionLabelSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// A label query over the set of namespaces that the term applies to. +/// The term is applied to the union of the namespaces selected by this field +/// and the ones listed in the namespaces field. +/// null selector and null or empty namespaces list means "this pod's namespace". +/// An empty selector ({}) matches all namespaces. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAffinityRequiredDuringSchedulingIgnoredDuringExecutionNamespaceSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAffinityRequiredDuringSchedulingIgnoredDuringExecutionNamespaceSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAntiAffinity { + /// The scheduler will prefer to schedule pods to nodes that satisfy + /// the anti-affinity expressions specified by this field, but it may choose + /// a node that violates one or more of the expressions. The node that is + /// most preferred is the one with the greatest sum of weights, i.e. + /// for each node that meets all of the scheduling requirements (resource + /// request, requiredDuringScheduling anti-affinity expressions, etc.), + /// compute a sum by iterating through the elements of this field and adding + /// "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + /// node(s) with the highest sum are the most preferred. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "preferredDuringSchedulingIgnoredDuringExecution")] + pub preferred_during_scheduling_ignored_during_execution: Option>, + /// If the anti-affinity requirements specified by this field are not met at + /// scheduling time, the pod will not be scheduled onto the node. + /// If the anti-affinity requirements specified by this field cease to be met + /// at some point during pod execution (e.g. due to a pod label update), the + /// system may or may not try to eventually evict the pod from its node. + /// When there are multiple elements, the lists of nodes corresponding to each + /// podAffinityTerm are intersected, i.e. all terms must be satisfied. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "requiredDuringSchedulingIgnoredDuringExecution")] + pub required_during_scheduling_ignored_during_execution: Option>, +} + +/// The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecution { + /// Required. A pod affinity term, associated with the corresponding weight. + #[serde(rename = "podAffinityTerm")] + pub pod_affinity_term: IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTerm, + /// weight associated with matching the corresponding podAffinityTerm, + /// in the range 1-100. + pub weight: i32, +} + +/// Required. A pod affinity term, associated with the corresponding weight. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTerm { + /// A label query over a set of resources, in this case pods. + /// If it's null, this PodAffinityTerm matches with no Pods. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "labelSelector")] + pub label_selector: Option, + /// MatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both matchLabelKeys and labelSelector. + /// Also, matchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabelKeys")] + pub match_label_keys: Option>, + /// MismatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + /// Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "mismatchLabelKeys")] + pub mismatch_label_keys: Option>, + /// A label query over the set of namespaces that the term applies to. + /// The term is applied to the union of the namespaces selected by this field + /// and the ones listed in the namespaces field. + /// null selector and null or empty namespaces list means "this pod's namespace". + /// An empty selector ({}) matches all namespaces. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "namespaceSelector")] + pub namespace_selector: Option, + /// namespaces specifies a static list of namespace names that the term applies to. + /// The term is applied to the union of the namespaces listed in this field + /// and the ones selected by namespaceSelector. + /// null or empty namespaces list and null namespaceSelector means "this pod's namespace". + #[serde(default, skip_serializing_if = "Option::is_none")] + pub namespaces: Option>, + /// This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + /// the labelSelector in the specified namespaces, where co-located is defined as running on a node + /// whose value of the label with key topologyKey matches that of any node on which any of the + /// selected pods is running. + /// Empty topologyKey is not allowed. + #[serde(rename = "topologyKey")] + pub topology_key: String, +} + +/// A label query over a set of resources, in this case pods. +/// If it's null, this PodAffinityTerm matches with no Pods. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermLabelSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermLabelSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// A label query over the set of namespaces that the term applies to. +/// The term is applied to the union of the namespaces selected by this field +/// and the ones listed in the namespaces field. +/// null selector and null or empty namespaces list means "this pod's namespace". +/// An empty selector ({}) matches all namespaces. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermNamespaceSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAntiAffinityPreferredDuringSchedulingIgnoredDuringExecutionPodAffinityTermNamespaceSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// Defines a set of pods (namely those matching the labelSelector +/// relative to the given namespace(s)) that this pod should be +/// co-located (affinity) or not co-located (anti-affinity) with, +/// where co-located is defined as running on a node whose value of +/// the label with key matches that of any node on which +/// a pod of the set of pods is running +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAntiAffinityRequiredDuringSchedulingIgnoredDuringExecution { + /// A label query over a set of resources, in this case pods. + /// If it's null, this PodAffinityTerm matches with no Pods. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "labelSelector")] + pub label_selector: Option, + /// MatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both matchLabelKeys and labelSelector. + /// Also, matchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabelKeys")] + pub match_label_keys: Option>, + /// MismatchLabelKeys is a set of pod label keys to select which pods will + /// be taken into consideration. The keys are used to lookup values from the + /// incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + /// to select the group of existing pods which pods will be taken into consideration + /// for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + /// pod labels will be ignored. The default value is empty. + /// The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + /// Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + /// This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + #[serde(default, skip_serializing_if = "Option::is_none", rename = "mismatchLabelKeys")] + pub mismatch_label_keys: Option>, + /// A label query over the set of namespaces that the term applies to. + /// The term is applied to the union of the namespaces selected by this field + /// and the ones listed in the namespaces field. + /// null selector and null or empty namespaces list means "this pod's namespace". + /// An empty selector ({}) matches all namespaces. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "namespaceSelector")] + pub namespace_selector: Option, + /// namespaces specifies a static list of namespace names that the term applies to. + /// The term is applied to the union of the namespaces listed in this field + /// and the ones selected by namespaceSelector. + /// null or empty namespaces list and null namespaceSelector means "this pod's namespace". + #[serde(default, skip_serializing_if = "Option::is_none")] + pub namespaces: Option>, + /// This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + /// the labelSelector in the specified namespaces, where co-located is defined as running on a node + /// whose value of the label with key topologyKey matches that of any node on which any of the + /// selected pods is running. + /// Empty topologyKey is not allowed. + #[serde(rename = "topologyKey")] + pub topology_key: String, +} + +/// A label query over a set of resources, in this case pods. +/// If it's null, this PodAffinityTerm matches with no Pods. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAntiAffinityRequiredDuringSchedulingIgnoredDuringExecutionLabelSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAntiAffinityRequiredDuringSchedulingIgnoredDuringExecutionLabelSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// A label query over the set of namespaces that the term applies to. +/// The term is applied to the union of the namespaces selected by this field +/// and the ones listed in the namespaces field. +/// null selector and null or empty namespaces list means "this pod's namespace". +/// An empty selector ({}) matches all namespaces. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAntiAffinityRequiredDuringSchedulingIgnoredDuringExecutionNamespaceSelector { + /// matchExpressions is a list of label selector requirements. The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + pub match_expressions: Option>, + /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + /// map is equivalent to an element of matchExpressions, whose key field is "key", the + /// operator is "In", and the values array contains only "value". The requirements are ANDed. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + pub match_labels: Option>, +} + +/// A label selector requirement is a selector that contains values, a key, and an operator that +/// relates the key and values. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecAffinityPodAntiAffinityRequiredDuringSchedulingIgnoredDuringExecutionNamespaceSelectorMatchExpressions +{ + /// key is the label key that the selector applies to. + pub key: String, + /// operator represents a key's relationship to a set of values. + /// Valid operators are In, NotIn, Exists and DoesNotExist. + pub operator: String, + /// values is an array of string values. If the operator is In or NotIn, + /// the values array must be non-empty. If the operator is Exists or DoesNotExist, + /// the values array must be empty. This array is replaced during a strategic + /// merge patch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub values: Option>, +} + +/// LocalObjectReference contains enough information to let you locate the +/// referenced object inside the same namespace. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecImagePullSecrets { + /// Name of the referent. + /// This field is effectively required, but due to backwards compatibility is + /// allowed to be empty. Instances of this type with an empty value here are + /// almost certainly wrong. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +/// If specified, the pod's security context +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecSecurityContext { + /// A special supplemental group that applies to all containers in a pod. + /// Some volume types allow the Kubelet to change the ownership of that volume + /// to be owned by the pod: + /// + /// 1. The owning GID will be the FSGroup + /// 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + /// 3. The permission bits are OR'd with rw-rw---- + /// + /// If unset, the Kubelet will not modify the ownership and permissions of any volume. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "fsGroup")] + pub fs_group: Option, + /// fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + /// before being exposed inside Pod. This field will only apply to + /// volume types which support fsGroup based ownership(and permissions). + /// It will have no effect on ephemeral volume types such as: secret, configmaps + /// and emptydir. + /// Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "fsGroupChangePolicy" + )] + pub fs_group_change_policy: Option, + /// The GID to run the entrypoint of the container process. + /// Uses runtime default if unset. + /// May also be set in SecurityContext. If set in both SecurityContext and + /// PodSecurityContext, the value specified in SecurityContext takes precedence + /// for that container. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "runAsGroup" + )] + pub run_as_group: Option, + /// Indicates that the container must run as a non-root user. + /// If true, the Kubelet will validate the image at runtime to ensure that it + /// does not run as UID 0 (root) and fail to start the container if it does. + /// If unset or false, no such validation will be performed. + /// May also be set in SecurityContext. If set in both SecurityContext and + /// PodSecurityContext, the value specified in SecurityContext takes precedence. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "runAsNonRoot" + )] + pub run_as_non_root: Option, + /// The UID to run the entrypoint of the container process. + /// Defaults to user specified in image metadata if unspecified. + /// May also be set in SecurityContext. If set in both SecurityContext and + /// PodSecurityContext, the value specified in SecurityContext takes precedence + /// for that container. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "runAsUser")] + pub run_as_user: Option, + /// The SELinux context to be applied to all containers. + /// If unspecified, the container runtime will allocate a random SELinux context for each + /// container. May also be set in SecurityContext. If set in + /// both SecurityContext and PodSecurityContext, the value specified in SecurityContext + /// takes precedence for that container. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "seLinuxOptions" + )] + pub se_linux_options: + Option, + /// The seccomp options to use by the containers in this pod. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "seccompProfile" + )] + pub seccomp_profile: + Option, + /// A list of groups applied to the first process run in each container, in addition + /// to the container's primary GID, the fsGroup (if specified), and group memberships + /// defined in the container image for the uid of the container process. If unspecified, + /// no additional groups are added to any container. Note that group memberships + /// defined in the container image for the uid of the container process are still effective, + /// even if they are not included in this list. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "supplementalGroups" + )] + pub supplemental_groups: Option>, + /// Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + /// sysctls (by the container runtime) might fail to launch. + /// Note that this field cannot be set when spec.os.name is windows. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sysctls: Option>, +} + +/// The SELinux context to be applied to all containers. +/// If unspecified, the container runtime will allocate a random SELinux context for each +/// container. May also be set in SecurityContext. If set in +/// both SecurityContext and PodSecurityContext, the value specified in SecurityContext +/// takes precedence for that container. +/// Note that this field cannot be set when spec.os.name is windows. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecSecurityContextSeLinuxOptions { + /// Level is SELinux level label that applies to the container. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub level: Option, + /// Role is a SELinux role label that applies to the container. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub role: Option, + /// Type is a SELinux type label that applies to the container. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "type")] + pub r#type: Option, + /// User is a SELinux user label that applies to the container. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, +} + +/// The seccomp options to use by the containers in this pod. +/// Note that this field cannot be set when spec.os.name is windows. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecSecurityContextSeccompProfile { + /// localhostProfile indicates a profile defined in a file on the node should be used. + /// The profile must be preconfigured on the node to work. + /// Must be a descending path, relative to the kubelet's configured seccomp profile location. + /// Must be set if type is "Localhost". Must NOT be set for any other type. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "localhostProfile" + )] + pub localhost_profile: Option, + /// type indicates which kind of seccomp profile will be applied. + /// Valid options are: + /// + /// Localhost - a profile defined in a file on the node should be used. + /// RuntimeDefault - the container runtime default profile should be used. + /// Unconfined - no profile should be applied. + #[serde(rename = "type")] + pub r#type: String, +} + +/// Sysctl defines a kernel parameter to be set +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecSecurityContextSysctls { + /// Name of a property to set + pub name: String, + /// Value of a property to set + pub value: String, +} + +/// The pod this Toleration is attached to tolerates any taint that matches +/// the triple using the matching operator . +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversHttp01IngressPodTemplateSpecTolerations { + /// Effect indicates the taint effect to match. Empty means match all taint effects. + /// When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub effect: Option, + /// Key is the taint key that the toleration applies to. Empty means match all taint keys. + /// If the key is empty, operator must be Exists; this combination means to match all values and all keys. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Operator represents a key's relationship to the value. + /// Valid operators are Exists and Equal. Defaults to Equal. + /// Exists is equivalent to wildcard for value, so that a pod can + /// tolerate all taints of a particular category. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub operator: Option, + /// TolerationSeconds represents the period of time the toleration (which must be + /// of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + /// it is not set, which means tolerate the taint forever (do not evict). Zero and + /// negative values will be treated as 0 (evict immediately) by the system. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "tolerationSeconds" + )] + pub toleration_seconds: Option, + /// Value is the taint value the toleration matches to. + /// If the operator is Exists, the value should be empty, otherwise just a regular string. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, +} + +/// Selector selects a set of DNSNames on the Certificate resource that +/// should be solved using this challenge solver. +/// If not specified, the solver will be treated as the 'default' solver +/// with the lowest priority, i.e. if any other solver has a more specific +/// match, it will be used instead. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerAcmeSolversSelector { + /// List of DNSNames that this solver will be used to solve. + /// If specified and a match is found, a dnsNames selector will take + /// precedence over a dnsZones selector. + /// If multiple solvers match with the same dnsNames value, the solver + /// with the most matching labels in matchLabels will be selected. + /// If neither has more matches, the solver defined earlier in the list + /// will be selected. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "dnsNames")] + pub dns_names: Option>, + /// List of DNSZones that this solver will be used to solve. + /// The most specific DNS zone match specified here will take precedence + /// over other DNS zone matches, so a solver specifying sys.example.com + /// will be selected over one specifying example.com for the domain + /// www.sys.example.com. + /// If multiple solvers match with the same dnsZones value, the solver + /// with the most matching labels in matchLabels will be selected. + /// If neither has more matches, the solver defined earlier in the list + /// will be selected. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "dnsZones")] + pub dns_zones: Option>, + /// A label selector that is used to refine the set of certificate's that + /// this challenge solver will apply to. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "matchLabels" + )] + pub match_labels: Option>, +} + +/// CA configures this issuer to sign certificates using a signing CA keypair +/// stored in a Secret resource. +/// This is used to build internal PKIs that are managed by cert-manager. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerCa { + /// The CRL distribution points is an X.509 v3 certificate extension which identifies + /// the location of the CRL from which the revocation of this certificate can be checked. + /// If not set, certificates will be issued without distribution points set. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "crlDistributionPoints" + )] + pub crl_distribution_points: Option>, + /// IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates + /// it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. + /// As an example, such a URL might be "http://ca.domain.com/ca.crt". + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "issuingCertificateURLs" + )] + pub issuing_certificate_ur_ls: Option>, + /// The OCSP server list is an X.509 v3 extension that defines a list of + /// URLs of OCSP responders. The OCSP responders can be queried for the + /// revocation status of an issued certificate. If not set, the + /// certificate will be issued with no OCSP servers set. For example, an + /// OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "ocspServers" + )] + pub ocsp_servers: Option>, + /// SecretName is the name of the secret used to sign Certificates issued + /// by this Issuer. + #[serde(rename = "secretName")] + pub secret_name: String, +} + +/// SelfSigned configures this issuer to 'self sign' certificates using the +/// private key used to create the CertificateRequest object. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerSelfSigned { + /// The CRL distribution points is an X.509 v3 certificate extension which identifies + /// the location of the CRL from which the revocation of this certificate can be checked. + /// If not set certificate will be issued without CDP. Values are strings. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "crlDistributionPoints" + )] + pub crl_distribution_points: Option>, +} + +/// Vault configures this issuer to sign certificates using a HashiCorp Vault +/// PKI backend. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVault { + /// Auth configures how cert-manager authenticates with the Vault server. + pub auth: IssuerVaultAuth, + /// Base64-encoded bundle of PEM CAs which will be used to validate the certificate + /// chain presented by Vault. Only used if using HTTPS to connect to Vault and + /// ignored for HTTP connections. + /// Mutually exclusive with CABundleSecretRef. + /// If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in + /// the cert-manager controller container is used to validate the TLS connection. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "caBundle")] + pub ca_bundle: Option, + /// Reference to a Secret containing a bundle of PEM-encoded CAs to use when + /// verifying the certificate chain presented by Vault when using HTTPS. + /// Mutually exclusive with CABundle. + /// If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in + /// the cert-manager controller container is used to validate the TLS connection. + /// If no key for the Secret is specified, cert-manager will default to 'ca.crt'. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "caBundleSecretRef" + )] + pub ca_bundle_secret_ref: Option, + /// Reference to a Secret containing a PEM-encoded Client Certificate to use when the + /// Vault server requires mTLS. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "clientCertSecretRef" + )] + pub client_cert_secret_ref: Option, + /// Reference to a Secret containing a PEM-encoded Client Private Key to use when the + /// Vault server requires mTLS. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "clientKeySecretRef" + )] + pub client_key_secret_ref: Option, + /// Name of the vault namespace. Namespaces is a set of features within Vault Enterprise that allows Vault environments to support Secure Multi-tenancy. e.g: "ns1" + /// More about namespaces can be found here https://www.vaultproject.io/docs/enterprise/namespaces + #[serde(default, skip_serializing_if = "Option::is_none")] + pub namespace: Option, + /// Path is the mount path of the Vault PKI backend's `sign` endpoint, e.g: + /// "my_pki_mount/sign/my-role-name". + pub path: String, + /// Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200". + pub server: String, +} + +/// Auth configures how cert-manager authenticates with the Vault server. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVaultAuth { + /// AppRole authenticates with Vault using the App Role auth mechanism, + /// with the role and secret stored in a Kubernetes Secret resource. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "appRole")] + pub app_role: Option, + /// ClientCertificate authenticates with Vault by presenting a client + /// certificate during the request's TLS handshake. + /// Works only when using HTTPS protocol. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "clientCertificate" + )] + pub client_certificate: Option, + /// Kubernetes authenticates with Vault by passing the ServiceAccount + /// token stored in the named Secret resource to the Vault server. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub kubernetes: Option, + /// TokenSecretRef authenticates with Vault by presenting a token. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "tokenSecretRef" + )] + pub token_secret_ref: Option, +} + +/// AppRole authenticates with Vault using the App Role auth mechanism, +/// with the role and secret stored in a Kubernetes Secret resource. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVaultAuthAppRole { + /// Path where the App Role authentication backend is mounted in Vault, e.g: + /// "approle" + pub path: String, + /// RoleID configured in the App Role authentication backend when setting + /// up the authentication backend in Vault. + #[serde(rename = "roleId")] + pub role_id: String, + /// Reference to a key in a Secret that contains the App Role secret used + /// to authenticate with Vault. + /// The `key` field must be specified and denotes which entry within the Secret + /// resource is used as the app role secret. + #[serde(rename = "secretRef")] + pub secret_ref: IssuerVaultAuthAppRoleSecretRef, +} + +/// Reference to a key in a Secret that contains the App Role secret used +/// to authenticate with Vault. +/// The `key` field must be specified and denotes which entry within the Secret +/// resource is used as the app role secret. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVaultAuthAppRoleSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// ClientCertificate authenticates with Vault by presenting a client +/// certificate during the request's TLS handshake. +/// Works only when using HTTPS protocol. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVaultAuthClientCertificate { + /// The Vault mountPath here is the mount path to use when authenticating with + /// Vault. For example, setting a value to `/v1/auth/foo`, will use the path + /// `/v1/auth/foo/login` to authenticate with Vault. If unspecified, the + /// default value "/v1/auth/cert" will be used. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "mountPath")] + pub mount_path: Option, + /// Name of the certificate role to authenticate against. + /// If not set, matching any certificate role, if available. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Reference to Kubernetes Secret of type "kubernetes.io/tls" (hence containing + /// tls.crt and tls.key) used to authenticate to Vault using TLS client + /// authentication. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "secretName" + )] + pub secret_name: Option, +} + +/// Kubernetes authenticates with Vault by passing the ServiceAccount +/// token stored in the named Secret resource to the Vault server. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVaultAuthKubernetes { + /// The Vault mountPath here is the mount path to use when authenticating with + /// Vault. For example, setting a value to `/v1/auth/foo`, will use the path + /// `/v1/auth/foo/login` to authenticate with Vault. If unspecified, the + /// default value "/v1/auth/kubernetes" will be used. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "mountPath")] + pub mount_path: Option, + /// A required field containing the Vault Role to assume. A Role binds a + /// Kubernetes ServiceAccount with a set of Vault policies. + pub role: String, + /// The required Secret field containing a Kubernetes ServiceAccount JWT used + /// for authenticating with Vault. Use of 'ambient credentials' is not + /// supported. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "secretRef")] + pub secret_ref: Option, + /// A reference to a service account that will be used to request a bound + /// token (also known as "projected token"). Compared to using "secretRef", + /// using this field means that you don't rely on statically bound tokens. To + /// use this field, you must configure an RBAC rule to let cert-manager + /// request a token. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "serviceAccountRef" + )] + pub service_account_ref: Option, +} + +/// The required Secret field containing a Kubernetes ServiceAccount JWT used +/// for authenticating with Vault. Use of 'ambient credentials' is not +/// supported. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVaultAuthKubernetesSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// A reference to a service account that will be used to request a bound +/// token (also known as "projected token"). Compared to using "secretRef", +/// using this field means that you don't rely on statically bound tokens. To +/// use this field, you must configure an RBAC rule to let cert-manager +/// request a token. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVaultAuthKubernetesServiceAccountRef { + /// TokenAudiences is an optional list of extra audiences to include in the token passed to Vault. The default token + /// consisting of the issuer's namespace and name is always included. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub audiences: Option>, + /// Name of the ServiceAccount used to request a token. + pub name: String, +} + +/// TokenSecretRef authenticates with Vault by presenting a token. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVaultAuthTokenSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Reference to a Secret containing a bundle of PEM-encoded CAs to use when +/// verifying the certificate chain presented by Vault when using HTTPS. +/// Mutually exclusive with CABundle. +/// If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in +/// the cert-manager controller container is used to validate the TLS connection. +/// If no key for the Secret is specified, cert-manager will default to 'ca.crt'. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVaultCaBundleSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Reference to a Secret containing a PEM-encoded Client Certificate to use when the +/// Vault server requires mTLS. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVaultClientCertSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Reference to a Secret containing a PEM-encoded Client Private Key to use when the +/// Vault server requires mTLS. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVaultClientKeySecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Venafi configures this issuer to sign certificates using a Venafi TPP +/// or Venafi Cloud policy zone. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVenafi { + /// Cloud specifies the Venafi cloud configuration settings. + /// Only one of TPP or Cloud may be specified. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cloud: Option, + /// TPP specifies Trust Protection Platform configuration settings. + /// Only one of TPP or Cloud may be specified. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tpp: Option, + /// Zone is the Venafi Policy Zone to use for this issuer. + /// All requests made to the Venafi platform will be restricted by the named + /// zone policy. + /// This field is required. + pub zone: String, +} + +/// Cloud specifies the Venafi cloud configuration settings. +/// Only one of TPP or Cloud may be specified. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVenafiCloud { + /// APITokenSecretRef is a secret key selector for the Venafi Cloud API token. + #[serde(rename = "apiTokenSecretRef")] + pub api_token_secret_ref: IssuerVenafiCloudApiTokenSecretRef, + /// URL is the base URL for Venafi Cloud. + /// Defaults to "https://api.venafi.cloud/v1". + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url: Option, +} + +/// APITokenSecretRef is a secret key selector for the Venafi Cloud API token. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVenafiCloudApiTokenSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// TPP specifies Trust Protection Platform configuration settings. +/// Only one of TPP or Cloud may be specified. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVenafiTpp { + /// Base64-encoded bundle of PEM CAs which will be used to validate the certificate + /// chain presented by the TPP server. Only used if using HTTPS; ignored for HTTP. + /// If undefined, the certificate bundle in the cert-manager controller container + /// is used to validate the chain. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "caBundle")] + pub ca_bundle: Option, + /// Reference to a Secret containing a base64-encoded bundle of PEM CAs + /// which will be used to validate the certificate chain presented by the TPP server. + /// Only used if using HTTPS; ignored for HTTP. Mutually exclusive with CABundle. + /// If neither CABundle nor CABundleSecretRef is defined, the certificate bundle in + /// the cert-manager controller container is used to validate the TLS connection. + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "caBundleSecretRef" + )] + pub ca_bundle_secret_ref: Option, + /// CredentialsRef is a reference to a Secret containing the Venafi TPP API credentials. + /// The secret must contain the key 'access-token' for the Access Token Authentication, + /// or two keys, 'username' and 'password' for the API Keys Authentication. + #[serde(rename = "credentialsRef")] + pub credentials_ref: IssuerVenafiTppCredentialsRef, + /// URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, + /// for example: "https://tpp.example.com/vedsdk". + pub url: String, +} + +/// Reference to a Secret containing a base64-encoded bundle of PEM CAs +/// which will be used to validate the certificate chain presented by the TPP server. +/// Only used if using HTTPS; ignored for HTTP. Mutually exclusive with CABundle. +/// If neither CABundle nor CABundleSecretRef is defined, the certificate bundle in +/// the cert-manager controller container is used to validate the TLS connection. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVenafiTppCaBundleSecretRef { + /// The key of the entry in the Secret resource's `data` field to be used. + /// Some instances of this field may be defaulted, in others it may be + /// required. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// CredentialsRef is a reference to a Secret containing the Venafi TPP API credentials. +/// The secret must contain the key 'access-token' for the Access Token Authentication, +/// or two keys, 'username' and 'password' for the API Keys Authentication. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerVenafiTppCredentialsRef { + /// Name of the resource being referred to. + /// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + pub name: String, +} + +/// Status of the Issuer. This is set and managed automatically. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerStatus { + /// ACME specific status options. + /// This field should only be set if the Issuer is configured to use an ACME + /// server to issue certificates. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub acme: Option, + /// List of status conditions to indicate the status of a CertificateRequest. + /// Known condition types are `Ready`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub conditions: Option>, +} + +/// ACME specific status options. +/// This field should only be set if the Issuer is configured to use an ACME +/// server to issue certificates. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct IssuerStatusAcme { + /// LastPrivateKeyHash is a hash of the private key associated with the latest + /// registered ACME account, in order to track changes made to registered account + /// associated with the Issuer + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "lastPrivateKeyHash" + )] + pub last_private_key_hash: Option, + /// LastRegisteredEmail is the email associated with the latest registered + /// ACME account, in order to track changes made to registered account + /// associated with the Issuer + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "lastRegisteredEmail" + )] + pub last_registered_email: Option, + /// URI is the unique account identifier, which can also be used to retrieve + /// account details from the CA + #[serde(default, skip_serializing_if = "Option::is_none")] + pub uri: Option, +} diff --git a/src/cloud-resources/src/crd/materialize.rs b/src/cloud-resources/src/crd/materialize.rs index 9e19823f2112b..2b3719a0e543a 100644 --- a/src/cloud-resources/src/crd/materialize.rs +++ b/src/cloud-resources/src/crd/materialize.rs @@ -17,7 +17,6 @@ use k8s_openapi::{ }, }; use kube::{api::ObjectMeta, CustomResource, Resource, ResourceExt}; - use rand::distributions::Uniform; use rand::Rng; use schemars::JsonSchema; @@ -25,12 +24,39 @@ use semver::Version; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use crate::crd::gen::cert_manager::certificates::{ + CertificateIssuerRef, CertificateSecretTemplate, +}; + pub const LAST_KNOWN_ACTIVE_GENERATION_ANNOTATION: &str = "materialize.cloud/last-known-active-generation"; pub mod v1alpha1 { + use super::*; + // This is intentionally a subset of the fields of a Certificate. + // We do not want customers to configure options that may conflict with + // things we override or expand in our code. + #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize, JsonSchema)] + #[serde(rename_all = "camelCase")] + pub struct MaterializeCertSpec { + // Additional DNS names the cert will be valid for. + pub dns_names: Option>, + // Duration the certificate will be requested for. + // Value must be in units accepted by Go time.ParseDuration + // https://golang.org/pkg/time/#ParseDuration. + pub duration: Option, + // Duration before expiration the certificate will be renewed. + // Value must be in units accepted by Go time.ParseDuration + // https://golang.org/pkg/time/#ParseDuration. + pub renew_before: Option, + // Reference to an Issuer or ClusterIssuer that will generate the certificate. + pub issuer_ref: Option, + // Additional annotations and labels to include in the Certificate object. + pub secret_template: Option, + } + #[derive( CustomResource, Clone, Debug, Default, PartialEq, Deserialize, Serialize, JsonSchema, )] @@ -109,6 +135,20 @@ pub mod v1alpha1 { // will populate any defaults. #[serde(default = "Uuid::new_v4")] pub environment_id: Uuid, + + // The configuration for generating an x509 certificate using cert-manager for balancerd + // to present to incoming connections. + // The dns_names and issuer_ref fields are required. + pub balancerd_external_certificate_spec: Option, + // The configuration for generating an x509 certificate using cert-manager for the console + // to present to incoming connections. + // The dns_names and issuer_ref fields are required. + // Not yet implemented. + pub console_external_certificate_spec: Option, + // The cert-manager Issuer or ClusterIssuer to use for database internal communication. + // The issuer_ref field is required. + // This currently is only used for environmentd, but will eventually support clusterd. + pub internal_certificate_spec: Option, } impl Materialize { @@ -144,6 +184,14 @@ pub mod v1alpha1 { self.name_prefixed("environmentd") } + pub fn environmentd_service_internal_fqdn(&self) -> String { + format!( + "{}.{}.svc.cluster.local", + self.environmentd_service_name(), + self.meta().namespace.as_ref().unwrap() + ) + } + pub fn environmentd_generation_service_name(&self, generation: u64) -> String { self.name_prefixed(&format!("environmentd-{generation}")) } @@ -152,6 +200,14 @@ pub mod v1alpha1 { "balancerd".to_owned() } + pub fn environmentd_certificate_name(&self) -> String { + self.name_prefixed("environmentd-external") + } + + pub fn environmentd_certificate_secret_name(&self) -> String { + self.name_prefixed("environmentd-tls") + } + pub fn balancerd_deployment_name(&self) -> String { self.name_prefixed("balancerd") } @@ -164,6 +220,14 @@ pub mod v1alpha1 { "console".to_owned() } + pub fn balancerd_external_certificate_name(&self) -> String { + self.name_prefixed("balancerd-external") + } + + pub fn balancerd_external_certificate_secret_name(&self) -> String { + self.name_prefixed("balancerd-external-tls") + } + pub fn console_deployment_name(&self) -> String { self.name_prefixed("console") } @@ -172,6 +236,14 @@ pub mod v1alpha1 { self.name_prefixed("console") } + pub fn console_external_certificate_name(&self) -> String { + self.name_prefixed("console-external") + } + + pub fn console_external_certificate_secret_name(&self) -> String { + self.name_prefixed("console-external-tls") + } + pub fn persist_pubsub_service_name(&self, generation: u64) -> String { self.name_prefixed(&format!("persist-pubsub-{generation}")) } diff --git a/src/cloud-resources/src/lib.rs b/src/cloud-resources/src/lib.rs index 5ec637d974a3d..e615c48ade48c 100644 --- a/src/cloud-resources/src/lib.rs +++ b/src/cloud-resources/src/lib.rs @@ -10,139 +10,11 @@ //! Abstractions for management of cloud resources that have no equivalent when running //! locally, like AWS PrivateLink endpoints. -use std::collections::BTreeMap; -use std::fmt::{self, Debug}; -use std::str::FromStr; -use std::sync::Arc; - -use async_trait::async_trait; -use chrono::{DateTime, Utc}; -use futures::stream::BoxStream; -use mz_repr::CatalogItemId; -use mz_repr::Row; -use serde::{Deserialize, Serialize}; - -use crate::crd::vpc_endpoint::v1::{VpcEndpointState, VpcEndpointStatus}; - pub mod crd; - -/// A prefix for an [external ID] to use for all AWS AssumeRole operations. It -/// should be concatenanted with a non-user-provided suffix identifying the -/// source or sink. The ID used for the suffix should never be reused if the -/// source or sink is deleted. -/// -/// **WARNING:** it is critical for security that this ID is **not** provided by -/// end users of Materialize. It must be provided by the operator of the -/// Materialize service. -/// -/// This type protects against accidental construction of an -/// `AwsExternalIdPrefix` through the use of an unwieldy and overly descriptive -/// constructor method name. -/// -/// [external ID]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct AwsExternalIdPrefix(String); - -impl AwsExternalIdPrefix { - /// Creates a new AWS external ID prefix from a command-line argument or - /// an environment variable. - /// - /// **WARNING:** it is critical for security that this ID is **not** - /// provided by end users of Materialize. It must be provided by the - /// operator of the Materialize service. - /// - pub fn new_from_cli_argument_or_environment_variable( - aws_external_id_prefix: &str, - ) -> AwsExternalIdPrefix { - AwsExternalIdPrefix(aws_external_id_prefix.into()) - } -} - -impl fmt::Display for AwsExternalIdPrefix { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(&self.0) - } -} - -/// Configures a VPC endpoint. -pub struct VpcEndpointConfig { - /// The name of the service to connect to. - pub aws_service_name: String, - /// The IDs of the availability zones in which the service is available. - pub availability_zone_ids: Vec, -} - -#[async_trait] -pub trait CloudResourceController: CloudResourceReader { - /// Creates or updates the specified `VpcEndpoint` Kubernetes object. - async fn ensure_vpc_endpoint( - &self, - id: CatalogItemId, - vpc_endpoint: VpcEndpointConfig, - ) -> Result<(), anyhow::Error>; - - /// Deletes the specified `VpcEndpoint` Kubernetes object. - async fn delete_vpc_endpoint(&self, id: CatalogItemId) -> Result<(), anyhow::Error>; - - /// Lists existing `VpcEndpoint` Kubernetes objects. - async fn list_vpc_endpoints( - &self, - ) -> Result, anyhow::Error>; - - /// Lists existing `VpcEndpoint` Kubernetes objects. - async fn watch_vpc_endpoints(&self) -> BoxStream<'static, VpcEndpointEvent>; - - /// Returns a reader for the resources managed by this controller. - fn reader(&self) -> Arc; -} - -#[async_trait] -pub trait CloudResourceReader: Debug + Send + Sync { - /// Reads the specified `VpcEndpoint` Kubernetes object. - async fn read(&self, id: CatalogItemId) -> Result; -} - -/// Returns the name to use for the VPC endpoint with the given ID. -pub fn vpc_endpoint_name(id: CatalogItemId) -> String { - // This is part of the contract with the VpcEndpointController in the - // cloud infrastructure layer. - format!("connection-{id}") -} - -/// Attempts to return the ID used to create the given VPC endpoint name. -pub fn id_from_vpc_endpoint_name(vpc_endpoint_name: &str) -> Option { - vpc_endpoint_name - .split_once('-') - .and_then(|(_, id_str)| CatalogItemId::from_str(id_str).ok()) -} - -/// Returns the host to use for the VPC endpoint with the given ID and -/// optionally in the given availability zone. -pub fn vpc_endpoint_host(id: CatalogItemId, availability_zone: Option<&str>) -> String { - let name = vpc_endpoint_name(id); - // This naming scheme is part of the contract with the VpcEndpointController - // in the cloud infrastructure layer. - match availability_zone { - Some(az) => format!("{name}-{az}"), - None => name, - } -} - -#[derive(Debug)] -pub struct VpcEndpointEvent { - pub connection_id: CatalogItemId, - pub status: VpcEndpointState, - pub time: DateTime, -} - -impl From for Row { - fn from(value: VpcEndpointEvent) -> Self { - use mz_repr::Datum; - - Row::pack_slice(&[ - Datum::TimestampTz(value.time.try_into().expect("must fit")), - Datum::String(&value.connection_id.to_string()), - Datum::String(&value.status.to_string()), - ]) - } -} +#[cfg(feature = "vpc-endpoints")] +pub mod vpc_endpoint; +#[cfg(feature = "vpc-endpoints")] +pub use vpc_endpoint::{ + id_from_vpc_endpoint_name, vpc_endpoint_host, vpc_endpoint_name, AwsExternalIdPrefix, + CloudResourceController, CloudResourceReader, VpcEndpointConfig, VpcEndpointEvent, +}; diff --git a/src/cloud-resources/src/vpc_endpoint.rs b/src/cloud-resources/src/vpc_endpoint.rs new file mode 100644 index 0000000000000..4e63c2be54717 --- /dev/null +++ b/src/cloud-resources/src/vpc_endpoint.rs @@ -0,0 +1,143 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +use std::collections::BTreeMap; +use std::fmt::{self, Debug}; +use std::str::FromStr; +use std::sync::Arc; + +use async_trait::async_trait; +use chrono::{DateTime, Utc}; +use futures::stream::BoxStream; +use mz_repr::CatalogItemId; +use mz_repr::Row; +use serde::{Deserialize, Serialize}; + +use crate::crd::vpc_endpoint::v1::{VpcEndpointState, VpcEndpointStatus}; + +/// A prefix for an [external ID] to use for all AWS AssumeRole operations. It +/// should be concatenanted with a non-user-provided suffix identifying the +/// source or sink. The ID used for the suffix should never be reused if the +/// source or sink is deleted. +/// +/// **WARNING:** it is critical for security that this ID is **not** provided by +/// end users of Materialize. It must be provided by the operator of the +/// Materialize service. +/// +/// This type protects against accidental construction of an +/// `AwsExternalIdPrefix` through the use of an unwieldy and overly descriptive +/// constructor method name. +/// +/// [external ID]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct AwsExternalIdPrefix(String); + +impl AwsExternalIdPrefix { + /// Creates a new AWS external ID prefix from a command-line argument or + /// an environment variable. + /// + /// **WARNING:** it is critical for security that this ID is **not** + /// provided by end users of Materialize. It must be provided by the + /// operator of the Materialize service. + /// + pub fn new_from_cli_argument_or_environment_variable( + aws_external_id_prefix: &str, + ) -> AwsExternalIdPrefix { + AwsExternalIdPrefix(aws_external_id_prefix.into()) + } +} + +impl fmt::Display for AwsExternalIdPrefix { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&self.0) + } +} + +/// Configures a VPC endpoint. +pub struct VpcEndpointConfig { + /// The name of the service to connect to. + pub aws_service_name: String, + /// The IDs of the availability zones in which the service is available. + pub availability_zone_ids: Vec, +} + +#[async_trait] +pub trait CloudResourceController: CloudResourceReader { + /// Creates or updates the specified `VpcEndpoint` Kubernetes object. + async fn ensure_vpc_endpoint( + &self, + id: CatalogItemId, + vpc_endpoint: VpcEndpointConfig, + ) -> Result<(), anyhow::Error>; + + /// Deletes the specified `VpcEndpoint` Kubernetes object. + async fn delete_vpc_endpoint(&self, id: CatalogItemId) -> Result<(), anyhow::Error>; + + /// Lists existing `VpcEndpoint` Kubernetes objects. + async fn list_vpc_endpoints( + &self, + ) -> Result, anyhow::Error>; + + /// Lists existing `VpcEndpoint` Kubernetes objects. + async fn watch_vpc_endpoints(&self) -> BoxStream<'static, VpcEndpointEvent>; + + /// Returns a reader for the resources managed by this controller. + fn reader(&self) -> Arc; +} + +#[async_trait] +pub trait CloudResourceReader: Debug + Send + Sync { + /// Reads the specified `VpcEndpoint` Kubernetes object. + async fn read(&self, id: CatalogItemId) -> Result; +} + +/// Returns the name to use for the VPC endpoint with the given ID. +pub fn vpc_endpoint_name(id: CatalogItemId) -> String { + // This is part of the contract with the VpcEndpointController in the + // cloud infrastructure layer. + format!("connection-{id}") +} + +/// Attempts to return the ID used to create the given VPC endpoint name. +pub fn id_from_vpc_endpoint_name(vpc_endpoint_name: &str) -> Option { + vpc_endpoint_name + .split_once('-') + .and_then(|(_, id_str)| CatalogItemId::from_str(id_str).ok()) +} + +/// Returns the host to use for the VPC endpoint with the given ID and +/// optionally in the given availability zone. +pub fn vpc_endpoint_host(id: CatalogItemId, availability_zone: Option<&str>) -> String { + let name = vpc_endpoint_name(id); + // This naming scheme is part of the contract with the VpcEndpointController + // in the cloud infrastructure layer. + match availability_zone { + Some(az) => format!("{name}-{az}"), + None => name, + } +} + +#[derive(Debug)] +pub struct VpcEndpointEvent { + pub connection_id: CatalogItemId, + pub status: VpcEndpointState, + pub time: DateTime, +} + +impl From for Row { + fn from(value: VpcEndpointEvent) -> Self { + use mz_repr::Datum; + + Row::pack_slice(&[ + Datum::TimestampTz(value.time.try_into().expect("must fit")), + Datum::String(&value.connection_id.to_string()), + Datum::String(&value.status.to_string()), + ]) + } +} diff --git a/src/cluster-client/src/lib.rs b/src/cluster-client/src/lib.rs index 5c5aad384a677..e4784254339ad 100644 --- a/src/cluster-client/src/lib.rs +++ b/src/cluster-client/src/lib.rs @@ -20,6 +20,7 @@ use anyhow::bail; use serde::{Deserialize, Serialize}; pub mod client; +pub mod metrics; /// A function that computes the lag between the given time and wallclock time. pub type WallclockLagFn = Arc Duration + Send + Sync>; diff --git a/src/cluster-client/src/metrics.rs b/src/cluster-client/src/metrics.rs new file mode 100644 index 0000000000000..58806ba1da462 --- /dev/null +++ b/src/cluster-client/src/metrics.rs @@ -0,0 +1,134 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +//! Metrics shared by both compute and storage. + +use std::time::Duration; + +use mz_ore::metric; +use mz_ore::metrics::{ + CounterVec, DeleteOnDropCounter, DeleteOnDropGauge, GaugeVec, IntCounterVec, MetricsRegistry, +}; +use mz_ore::stats::SlidingMinMax; +use prometheus::core::{AtomicF64, AtomicU64}; + +/// Controller metrics. +#[derive(Debug, Clone)] +pub struct ControllerMetrics { + dataflow_wallclock_lag_seconds: GaugeVec, + dataflow_wallclock_lag_seconds_sum: CounterVec, + dataflow_wallclock_lag_seconds_count: IntCounterVec, +} + +impl ControllerMetrics { + /// Create a metrics instance registered into the given registry. + pub fn new(metrics_registry: &MetricsRegistry) -> Self { + Self { + // The next three metrics immitate a summary metric type. The `prometheus` crate lacks + // support for summaries, so we roll our own. Note that we also only expose the 0- and + // the 1-quantile, i.e., minimum and maximum lag values. + dataflow_wallclock_lag_seconds: metrics_registry.register(metric!( + name: "mz_dataflow_wallclock_lag_seconds", + help: "A summary of the second-by-second lag of the dataflow frontier relative \ + to wallclock time, aggregated over the last minute.", + var_labels: ["instance_id", "replica_id", "collection_id", "quantile"], + )), + dataflow_wallclock_lag_seconds_sum: metrics_registry.register(metric!( + name: "mz_dataflow_wallclock_lag_seconds_sum", + help: "The total sum of dataflow wallclock lag measurements.", + var_labels: ["instance_id", "replica_id", "collection_id"], + )), + dataflow_wallclock_lag_seconds_count: metrics_registry.register(metric!( + name: "mz_dataflow_wallclock_lag_seconds_count", + help: "The total count of dataflow wallclock lag measurements.", + var_labels: ["instance_id", "replica_id", "collection_id"], + )), + } + } + + /// Return an object that tracks wallclock lag metrics for the given collection on the given + /// cluster and replica. + pub fn wallclock_lag_metrics( + &self, + collection_id: String, + instance_id: Option, + replica_id: Option, + ) -> WallclockLagMetrics { + let labels = vec![ + instance_id.unwrap_or_default(), + replica_id.unwrap_or_default(), + collection_id, + ]; + + let labels_with_quantile = |quantile: &str| { + labels + .iter() + .cloned() + .chain([quantile.to_string()]) + .collect() + }; + + let wallclock_lag_seconds_min = self + .dataflow_wallclock_lag_seconds + .get_delete_on_drop_metric(labels_with_quantile("0")); + let wallclock_lag_seconds_max = self + .dataflow_wallclock_lag_seconds + .get_delete_on_drop_metric(labels_with_quantile("1")); + let wallclock_lag_seconds_sum = self + .dataflow_wallclock_lag_seconds_sum + .get_delete_on_drop_metric(labels.clone()); + let wallclock_lag_seconds_count = self + .dataflow_wallclock_lag_seconds_count + .get_delete_on_drop_metric(labels); + let wallclock_lag_minmax = SlidingMinMax::new(60); + + WallclockLagMetrics { + wallclock_lag_seconds_min, + wallclock_lag_seconds_max, + wallclock_lag_seconds_sum, + wallclock_lag_seconds_count, + wallclock_lag_minmax, + } + } +} + +/// Metrics tracking frontier wallclock lag for a collection. +#[derive(Debug)] +pub struct WallclockLagMetrics { + /// Gauge tracking minimum dataflow wallclock lag. + wallclock_lag_seconds_min: DeleteOnDropGauge<'static, AtomicF64, Vec>, + /// Gauge tracking maximum dataflow wallclock lag. + wallclock_lag_seconds_max: DeleteOnDropGauge<'static, AtomicF64, Vec>, + /// Counter tracking the total sum of dataflow wallclock lag. + wallclock_lag_seconds_sum: DeleteOnDropCounter<'static, AtomicF64, Vec>, + /// Counter tracking the total count of dataflow wallclock lag measurements. + wallclock_lag_seconds_count: DeleteOnDropCounter<'static, AtomicU64, Vec>, + + /// State maintaining minimum and maximum wallclock lag. + wallclock_lag_minmax: SlidingMinMax, +} + +impl WallclockLagMetrics { + /// Observe a new wallclock lag measurement. + pub fn observe(&mut self, lag: Duration) { + let lag_secs = lag.as_secs_f32(); + + self.wallclock_lag_minmax.add_sample(lag_secs); + + let (&min, &max) = self + .wallclock_lag_minmax + .get() + .expect("just added a sample"); + + self.wallclock_lag_seconds_min.set(min.into()); + self.wallclock_lag_seconds_max.set(max.into()); + self.wallclock_lag_seconds_sum.inc_by(lag_secs.into()); + self.wallclock_lag_seconds_count.inc(); + } +} diff --git a/src/clusterd/BUILD.bazel b/src/clusterd/BUILD.bazel index ee81e4b67367b..e5c2f3945ed0f 100644 --- a/src/clusterd/BUILD.bazel +++ b/src/clusterd/BUILD.bazel @@ -31,7 +31,7 @@ rust_library( proc_macro_deps = [] + all_crate_deps(proc_macro = True), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/alloc:mz_alloc", "//src/alloc-default:mz_alloc_default", @@ -80,7 +80,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/alloc:mz_alloc", "//src/alloc-default:mz_alloc_default", @@ -153,7 +153,7 @@ rust_binary( proc_macro_deps = [] + all_crate_deps(proc_macro = True), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ ":mz_clusterd", "//src/alloc:mz_alloc", diff --git a/src/clusterd/Cargo.toml b/src/clusterd/Cargo.toml index d382a0aa16e63..f0e22e296976a 100644 --- a/src/clusterd/Cargo.toml +++ b/src/clusterd/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mz-clusterd" description = "Materialize's cluster server." -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" edition.workspace = true rust-version.workspace = true publish = false diff --git a/src/compute-client/src/controller.rs b/src/compute-client/src/controller.rs index f6d346c84369e..538b12e64b6db 100644 --- a/src/compute-client/src/controller.rs +++ b/src/compute-client/src/controller.rs @@ -38,6 +38,7 @@ use futures::stream::FuturesUnordered; use futures::{FutureExt, StreamExt}; use mz_build_info::BuildInfo; use mz_cluster_client::client::ClusterReplicaLocation; +use mz_cluster_client::metrics::ControllerMetrics; use mz_cluster_client::{ReplicaId, WallclockLagFn}; use mz_compute_types::dataflows::DataflowDescription; use mz_compute_types::dyncfgs::COMPUTE_REPLICA_EXPIRATION_OFFSET; @@ -130,6 +131,8 @@ pub enum PeekNotification { Success { /// Number of rows in the returned peek result. rows: u64, + /// Size of the returned peek result in bytes. + result_size: u64, }, /// Error of an unsuccessful peek, including the reason for the error. Error(String), @@ -144,6 +147,7 @@ impl PeekNotification { match peek_response { PeekResponse::Rows(rows) => Self::Success { rows: u64::cast_from(rows.count(offset, limit)), + result_size: u64::cast_from(rows.byte_len()), }, PeekResponse::Error(err) => Self::Error(err.clone()), PeekResponse::Canceled => Self::Canceled, @@ -236,7 +240,8 @@ impl ComputeController { storage_collections: StorageCollections, envd_epoch: NonZeroI64, read_only: bool, - metrics_registry: MetricsRegistry, + metrics_registry: &MetricsRegistry, + controller_metrics: ControllerMetrics, now: NowFn, wallclock_lag: WallclockLagFn, ) -> Self { @@ -288,6 +293,8 @@ impl ComputeController { } }); + let metrics = ComputeControllerMetrics::new(metrics_registry, controller_metrics); + Self { instances: BTreeMap::new(), instance_workload_classes, @@ -299,7 +306,7 @@ impl ComputeController { arrangement_exert_proportionality: 16, stashed_response: None, envd_epoch, - metrics: ComputeControllerMetrics::new(metrics_registry), + metrics, now, wallclock_lag, dyncfg: Arc::new(mz_dyncfgs::all_dyncfgs()), diff --git a/src/compute-client/src/controller/instance.rs b/src/compute-client/src/controller/instance.rs index 2420cadafdc98..5a7038d097ee0 100644 --- a/src/compute-client/src/controller/instance.rs +++ b/src/compute-client/src/controller/instance.rs @@ -564,7 +564,7 @@ impl Instance { } if let Some(metrics) = &mut collection.metrics { - metrics.observe_wallclock_lag(lag); + metrics.wallclock_lag.observe(lag); }; } } diff --git a/src/compute-client/src/metrics.rs b/src/compute-client/src/metrics.rs index 77ea4a20abd8d..60b8310f7ca4d 100644 --- a/src/compute-client/src/metrics.rs +++ b/src/compute-client/src/metrics.rs @@ -13,16 +13,17 @@ use std::borrow::Borrow; use std::sync::Arc; use std::time::Duration; +use mz_cluster_client::metrics::{ControllerMetrics, WallclockLagMetrics}; use mz_cluster_client::ReplicaId; use mz_compute_types::ComputeInstanceId; use mz_ore::cast::CastFrom; use mz_ore::metric; use mz_ore::metrics::raw::UIntGaugeVec; use mz_ore::metrics::{ - CounterVec, DeleteOnDropCounter, DeleteOnDropGauge, DeleteOnDropHistogram, GaugeVec, - HistogramVec, IntCounterVec, MetricVecExt, MetricsRegistry, + DeleteOnDropCounter, DeleteOnDropGauge, DeleteOnDropHistogram, GaugeVec, HistogramVec, + IntCounterVec, MetricVecExt, MetricsRegistry, }; -use mz_ore::stats::{histogram_seconds_buckets, SlidingMinMax}; +use mz_ore::stats::histogram_seconds_buckets; use mz_repr::GlobalId; use mz_service::codec::StatsCollector; use prometheus::core::{AtomicF64, AtomicU64}; @@ -30,7 +31,6 @@ use prometheus::core::{AtomicF64, AtomicU64}; use crate::protocol::command::{ComputeCommand, ProtoComputeCommand}; use crate::protocol::response::{PeekResponse, ProtoComputeResponse}; -type Counter = DeleteOnDropCounter<'static, AtomicF64, Vec>; pub(crate) type IntCounter = DeleteOnDropCounter<'static, AtomicU64, Vec>; type Gauge = DeleteOnDropGauge<'static, AtomicF64, Vec>; /// TODO(database-issues#7533): Add documentation. @@ -68,14 +68,14 @@ pub struct ComputeControllerMetrics { // dataflows dataflow_initial_output_duration_seconds: GaugeVec, - dataflow_wallclock_lag_seconds: GaugeVec, - dataflow_wallclock_lag_seconds_sum: CounterVec, - dataflow_wallclock_lag_seconds_count: IntCounterVec, + + /// Metrics shared with the storage controller. + shared: ControllerMetrics, } impl ComputeControllerMetrics { /// Create a metrics instance registered into the given registry. - pub fn new(metrics_registry: MetricsRegistry) -> Self { + pub fn new(metrics_registry: &MetricsRegistry, shared: ControllerMetrics) -> Self { ComputeControllerMetrics { commands_total: metrics_registry.register(metric!( name: "mz_compute_commands_total", @@ -174,25 +174,7 @@ impl ComputeControllerMetrics { var_labels: ["instance_id", "replica_id", "collection_id"], )), - // The next three metrics immitate a summary metric type. The `prometheus` crate lacks - // support for summaries, so we roll our own. Note that we also only expose the 0- and - // the 1-quantile, i.e., minimum and maximum lag values. - dataflow_wallclock_lag_seconds: metrics_registry.register(metric!( - name: "mz_dataflow_wallclock_lag_seconds", - help: "A summary of the second-by-second lag of the dataflow frontier relative \ - to wallclock time, aggregated over the last minute.", - var_labels: ["instance_id", "replica_id", "collection_id", "quantile"], - )), - dataflow_wallclock_lag_seconds_sum: metrics_registry.register(metric!( - name: "mz_dataflow_wallclock_lag_seconds_sum", - help: "The total sum of dataflow wallclock lag measurements.", - var_labels: ["instance_id", "replica_id", "collection_id"], - )), - dataflow_wallclock_lag_seconds_count: metrics_registry.register(metric!( - name: "mz_dataflow_wallclock_lag_seconds_count", - help: "The total count of dataflow wallclock lag measurements.", - var_labels: ["instance_id", "replica_id", "collection_id"], - )), + shared, } } @@ -418,44 +400,20 @@ impl ReplicaMetrics { collection_id.to_string(), ]; - let labels_with_quantile = |quantile: &str| { - labels - .iter() - .cloned() - .chain([quantile.to_string()]) - .collect() - }; - let initial_output_duration_seconds = self .metrics .dataflow_initial_output_duration_seconds .get_delete_on_drop_metric(labels.clone()); - let wallclock_lag_seconds_min = self - .metrics - .dataflow_wallclock_lag_seconds - .get_delete_on_drop_metric(labels_with_quantile("0")); - let wallclock_lag_seconds_max = self - .metrics - .dataflow_wallclock_lag_seconds - .get_delete_on_drop_metric(labels_with_quantile("1")); - let wallclock_lag_seconds_sum = self - .metrics - .dataflow_wallclock_lag_seconds_sum - .get_delete_on_drop_metric(labels.clone()); - let wallclock_lag_seconds_count = self - .metrics - .dataflow_wallclock_lag_seconds_count - .get_delete_on_drop_metric(labels); - let wallclock_lag_minmax = SlidingMinMax::new(60); + let wallclock_lag = self.metrics.shared.wallclock_lag_metrics( + collection_id.to_string(), + Some(self.instance_id.to_string()), + Some(self.replica_id.to_string()), + ); Some(ReplicaCollectionMetrics { initial_output_duration_seconds, - wallclock_lag_seconds_min, - wallclock_lag_seconds_max, - wallclock_lag_seconds_sum, - wallclock_lag_seconds_count, - wallclock_lag_minmax, + wallclock_lag, }) } } @@ -484,35 +442,8 @@ impl StatsCollector for ReplicaMetric pub(crate) struct ReplicaCollectionMetrics { /// Gauge tracking dataflow hydration time. pub initial_output_duration_seconds: Gauge, - /// Gauge tracking minimum dataflow wallclock lag. - wallclock_lag_seconds_min: Gauge, - /// Gauge tracking maximum dataflow wallclock lag. - wallclock_lag_seconds_max: Gauge, - /// Counter tracking the total sum of dataflow wallclock lag. - wallclock_lag_seconds_sum: Counter, - /// Counter tracking the total count of dataflow wallclock lag measurements. - wallclock_lag_seconds_count: IntCounter, - - /// State maintaining minimum and maximum wallclock lag. - wallclock_lag_minmax: SlidingMinMax, -} - -impl ReplicaCollectionMetrics { - pub fn observe_wallclock_lag(&mut self, lag: Duration) { - let lag_secs = lag.as_secs_f32(); - - self.wallclock_lag_minmax.add_sample(lag_secs); - - let (&min, &max) = self - .wallclock_lag_minmax - .get() - .expect("just added a sample"); - - self.wallclock_lag_seconds_min.set(min.into()); - self.wallclock_lag_seconds_max.set(max.into()); - self.wallclock_lag_seconds_sum.inc_by(lag_secs.into()); - self.wallclock_lag_seconds_count.inc(); - } + /// Metrics tracking dataflow wallclock lag. + pub wallclock_lag: WallclockLagMetrics, } /// Metrics keyed by `ComputeCommand` type. diff --git a/src/compute-types/src/plan/lowering.rs b/src/compute-types/src/plan/lowering.rs index eae882ecd91ac..97ecceea80e6d 100644 --- a/src/compute-types/src/plan/lowering.rs +++ b/src/compute-types/src/plan/lowering.rs @@ -37,8 +37,6 @@ pub(super) struct Context { debug_info: LirDebugInfo, /// Whether to enable fusion of MFPs in reductions. enable_reduce_mfp_fusion: bool, - /// Whether to fuse `Reduce` with `FlatMap UnnestList` for better window function performance. - enable_reduce_unnest_list_fusion: bool, } impl Context { @@ -51,7 +49,6 @@ impl Context { id: GlobalId::Transient(0), }, enable_reduce_mfp_fusion: features.enable_reduce_mfp_fusion, - enable_reduce_unnest_list_fusion: features.enable_reduce_unnest_list_fusion, } } @@ -418,9 +415,6 @@ impl Context { // it would be very hard to hunt down all these parts. (For example, key inference // infers the group key as a unique key.) let fused_with_reduce = 'fusion: { - if !self.enable_reduce_unnest_list_fusion { - break 'fusion None; - } if !matches!(func, TableFunc::UnnestList { .. }) { break 'fusion None; } @@ -692,10 +686,9 @@ This is not expected to cause incorrect results, but could indicate a performanc monotonic, expected_group_size, } => { - if self.enable_reduce_unnest_list_fusion - && aggregates - .iter() - .any(|agg| agg.func.can_fuse_with_unnest_list()) + if aggregates + .iter() + .any(|agg| agg.func.can_fuse_with_unnest_list()) { // This case should have been handled at the `MirRelationExpr::FlatMap` case // above. But that has a pretty complicated pattern matching, so it's not diff --git a/src/controller/src/lib.rs b/src/controller/src/lib.rs index 90afb94bd5fff..2171bedd717a3 100644 --- a/src/controller/src/lib.rs +++ b/src/controller/src/lib.rs @@ -30,6 +30,7 @@ use std::time::Duration; use futures::future::BoxFuture; use mz_build_info::BuildInfo; +use mz_cluster_client::metrics::ControllerMetrics; use mz_cluster_client::{ReplicaId, WallclockLagFn}; use mz_compute_client::controller::{ ComputeController, ComputeControllerResponse, ComputeControllerTimestamp, PeekNotification, @@ -649,6 +650,8 @@ where Duration::from(lag_ts) }); + let controller_metrics = ControllerMetrics::new(&config.metrics_registry); + let txns_metrics = Arc::new(TxnMetrics::new(&config.metrics_registry)); let collections_ctl = storage_collections::StorageCollectionsImpl::new( config.persist_location.clone(), @@ -675,7 +678,8 @@ where Arc::clone(&txns_metrics), envd_epoch, read_only, - config.metrics_registry.clone(), + &config.metrics_registry, + controller_metrics.clone(), config.connection_context, storage_txn, Arc::clone(&collections_ctl), @@ -688,7 +692,8 @@ where storage_collections, envd_epoch, read_only, - config.metrics_registry.clone(), + &config.metrics_registry, + controller_metrics, config.now.clone(), wallclock_lag, ); diff --git a/src/durable-cache/src/lib.rs b/src/durable-cache/src/lib.rs index 3b2448327a117..ab1a30d755b17 100644 --- a/src/durable-cache/src/lib.rs +++ b/src/durable-cache/src/lib.rs @@ -131,46 +131,76 @@ impl DurableCache { async fn sync_to(&mut self, progress: Option) -> u64 { let progress = progress.expect("cache shard should not be closed"); + let mut updates: BTreeMap<_, Vec<_>> = BTreeMap::new(); + while self.local_progress < progress { let events = self.subscribe.fetch_next().await; for event in events { match event { - ListenEvent::Updates(mut x) => { - consolidate_updates(&mut x); - x.sort_by(|(_, ts1, d1), (_, ts2, d2)| ts1.cmp(ts2).then(d1.cmp(d2))); - for ((k, v), t, d) in x { - let encoded_key = k.unwrap(); - let encoded_val = v.unwrap(); - let (decoded_key, decoded_val) = C::decode(&encoded_key, &encoded_val); - let val = LocalVal { - encoded_key, - decoded_val, - encoded_val, - }; - - if d == 1 { - self.local - .expect_insert(decoded_key, val, "duplicate cache entry"); - } else if d == -1 { - let prev = self - .local - .expect_remove(&decoded_key, "entry does not exist"); - assert_eq!(val, prev, "removed val does not match expected val"); - } else { - panic!( - "unexpected diff: (({:?}, {:?}), {}, {})", - decoded_key, val.decoded_val, t, d - ); - } + ListenEvent::Updates(batch_updates) => { + debug!("syncing updates {batch_updates:?}"); + for update in batch_updates { + updates.entry(update.1).or_default().push(update); } } ListenEvent::Progress(x) => { + debug!("synced up to {x:?}"); self.local_progress = - x.into_option().expect("cache shard should not be closed") + x.into_option().expect("cache shard should not be closed"); + // Apply updates in batches of complete timestamps so that we don't attempt + // to apply a subset of the updates from a timestamp. + while let Some((ts, mut updates)) = updates.pop_first() { + assert!( + ts < self.local_progress, + "expected {} < {}", + ts, + self.local_progress + ); + assert!( + updates.iter().all(|(_, update_ts, _)| ts == *update_ts), + "all updates should be for time {ts}, updates: {updates:?}" + ); + + consolidate_updates(&mut updates); + updates.sort_by(|(_, _, d1), (_, _, d2)| d1.cmp(d2)); + for ((k, v), t, d) in updates { + let encoded_key = k.unwrap(); + let encoded_val = v.unwrap(); + let (decoded_key, decoded_val) = + C::decode(&encoded_key, &encoded_val); + let val = LocalVal { + encoded_key, + decoded_val, + encoded_val, + }; + + if d == 1 { + self.local.expect_insert( + decoded_key, + val, + "duplicate cache entry", + ); + } else if d == -1 { + let prev = self + .local + .expect_remove(&decoded_key, "entry does not exist"); + assert_eq!( + val, prev, + "removed val does not match expected val" + ); + } else { + panic!( + "unexpected diff: (({:?}, {:?}), {}, {})", + decoded_key, val.decoded_val, t, d + ); + } + } + } } } } } + assert_eq!(updates, BTreeMap::new(), "all updates should be applied"); progress } diff --git a/src/environmentd/BUILD.bazel b/src/environmentd/BUILD.bazel index 20c0e2348d794..478bd3e2707f2 100644 --- a/src/environmentd/BUILD.bazel +++ b/src/environmentd/BUILD.bazel @@ -43,7 +43,7 @@ rust_library( proc_macro_deps = [] + all_crate_deps(proc_macro = True), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ ":mz_environmentd_build_script", "//src/adapter:mz_adapter", @@ -116,7 +116,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/adapter:mz_adapter", "//src/adapter-types:mz_adapter_types", @@ -251,7 +251,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/adapter:mz_adapter", "//src/adapter-types:mz_adapter_types", @@ -319,7 +319,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/adapter:mz_adapter", "//src/adapter-types:mz_adapter_types", @@ -387,7 +387,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/adapter:mz_adapter", "//src/adapter-types:mz_adapter_types", @@ -455,7 +455,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/adapter:mz_adapter", "//src/adapter-types:mz_adapter_types", @@ -523,7 +523,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/adapter:mz_adapter", "//src/adapter-types:mz_adapter_types", @@ -591,7 +591,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/adapter:mz_adapter", "//src/adapter-types:mz_adapter_types", @@ -659,7 +659,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/adapter:mz_adapter", "//src/adapter-types:mz_adapter_types", @@ -721,7 +721,7 @@ rust_binary( proc_macro_deps = [] + all_crate_deps(proc_macro = True), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ ":mz_environmentd", "//src/adapter:mz_adapter", diff --git a/src/environmentd/Cargo.toml b/src/environmentd/Cargo.toml index e37dd60e645a1..437674c500e9b 100644 --- a/src/environmentd/Cargo.toml +++ b/src/environmentd/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mz-environmentd" description = "Manages a single Materialize environment." -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" authors = ["Materialize, Inc."] license = "proprietary" edition.workspace = true diff --git a/src/environmentd/src/deployment/preflight.rs b/src/environmentd/src/deployment/preflight.rs index a66eded46cdf7..3734b25ce83f7 100644 --- a/src/environmentd/src/deployment/preflight.rs +++ b/src/environmentd/src/deployment/preflight.rs @@ -75,7 +75,7 @@ pub async fn preflight_legacy( .open_savepoint(boot_ts.clone(), &bootstrap_args) .await { - Ok(adapter_storage) => Box::new(adapter_storage).expire().await, + Ok((adapter_storage, _)) => Box::new(adapter_storage).expire().await, Err(CatalogError::Durable(e)) if e.can_recover_with_write_mode() => { // This is theoretically possible if catalog implementation A is // initialized, implementation B is uninitialized, and we are going to diff --git a/src/environmentd/src/environmentd/main.rs b/src/environmentd/src/environmentd/main.rs index e7fd62925913a..0ec55ba85351f 100644 --- a/src/environmentd/src/environmentd/main.rs +++ b/src/environmentd/src/environmentd/main.rs @@ -162,30 +162,6 @@ pub struct Args { default_value = "127.0.0.1:6879" )] internal_persist_pubsub_listen_addr: SocketAddr, - /// The address on which to listen for SQL connections from the balancers. - /// - /// Connections to this address are not subject to encryption. - /// Care should be taken to not expose this address to the public internet - /// or other unauthorized parties. - #[clap( - long, - value_name = "HOST:PORT", - env = "BALANCER_SQL_LISTEN_ADDR", - default_value = "127.0.0.1:6880" - )] - balancer_sql_listen_addr: SocketAddr, - /// The address on which to listen for trusted HTTP connections. - /// - /// Connections to this address are not subject to encryption. - /// Care should be taken to not expose this address to the public internet - /// or other unauthorized parties. - #[clap( - long, - value_name = "HOST:PORT", - env = "BALANCER_HTTP_LISTEN_ADDR", - default_value = "127.0.0.1:6881" - )] - balancer_http_listen_addr: SocketAddr, /// Enable cross-origin resource sharing (CORS) for HTTP requests from the /// specified origin. /// @@ -280,6 +256,12 @@ pub struct Args { /// Whether to enable pod metrics collection. #[clap(long, env = "ORCHESTRATOR_KUBERNETES_DISABLE_POD_METRICS_COLLECTION")] orchestrator_kubernetes_disable_pod_metrics_collection: bool, + /// Whether to annotate pods for prometheus service discovery. + #[clap( + long, + env = "ORCHESTRATOR_KUBERNETES_ENABLE_PROMETHEUS_SCRAPE_ANNOTATIONS" + )] + orchestrator_kubernetes_enable_prometheus_scrape_annotations: bool, #[clap(long, env = "ORCHESTRATOR_PROCESS_WRAPPER")] orchestrator_process_wrapper: Option, /// Where the process orchestrator should store secrets. @@ -767,6 +749,8 @@ fn run(mut args: Args) -> Result<(), anyhow::Error> { name_prefix: args.orchestrator_kubernetes_name_prefix.clone(), collect_pod_metrics: !args .orchestrator_kubernetes_disable_pod_metrics_collection, + enable_prometheus_scrape_annotations: args + .orchestrator_kubernetes_enable_prometheus_scrape_annotations, })) .context("creating kubernetes orchestrator")?, ); @@ -1002,8 +986,6 @@ fn run(mut args: Args) -> Result<(), anyhow::Error> { let listeners = Listeners::bind(ListenersConfig { sql_listen_addr: args.sql_listen_addr, http_listen_addr: args.http_listen_addr, - balancer_sql_listen_addr: args.balancer_sql_listen_addr, - balancer_http_listen_addr: args.balancer_http_listen_addr, internal_sql_listen_addr: args.internal_sql_listen_addr, internal_http_listen_addr: args.internal_http_listen_addr, }) @@ -1109,14 +1091,6 @@ fn run(mut args: Args) -> Result<(), anyhow::Error> { " Internal HTTP address: {}", server.internal_http_local_addr() ); - println!( - " Balancerd SQL address: {}", - server.balancer_sql_local_addr() - ); - println!( - " Balancerd HTTP address: {}", - server.balancer_http_local_addr() - ); println!( " Internal Persist PubSub address: {}", args.internal_persist_pubsub_listen_addr diff --git a/src/environmentd/src/http/sql.rs b/src/environmentd/src/http/sql.rs index 42b2b8a8b2657..9761c593e9cdd 100644 --- a/src/environmentd/src/http/sql.rs +++ b/src/environmentd/src/http/sql.rs @@ -900,6 +900,7 @@ impl ResultSender for WebSocket { } let mut datum_vec = mz_repr::DatumVec::new(); + let mut result_size: usize = 0; let mut rows_returned = 0; loop { let res = match await_rows(self, client, rx.recv()).await { @@ -929,6 +930,7 @@ impl ResultSender for WebSocket { rows_returned += rows.count(); while let Some(row) = rows.next() { + result_size += row.byte_len(); let datums = datum_vec.borrow_with(row); let types = &desc.typ().column_types; if let Err(e) = send_ws_response( @@ -977,6 +979,7 @@ impl ResultSender for WebSocket { vec![WebSocketResponse::CommandComplete(tag)], Some(( StatementEndedExecutionReason::Success { + result_size: Some(u64::cast_from(result_size)), rows_returned: Some(u64::cast_from(rows_returned)), execution_strategy: Some( StatementExecutionStrategy::Standard, diff --git a/src/environmentd/src/lib.rs b/src/environmentd/src/lib.rs index f9c213c25b03d..d154996685c5c 100644 --- a/src/environmentd/src/lib.rs +++ b/src/environmentd/src/lib.rs @@ -193,11 +193,6 @@ pub struct ListenersConfig { pub sql_listen_addr: SocketAddr, /// The IP address and port to listen for HTTP connections on. pub http_listen_addr: SocketAddr, - /// The IP address and port to listen on for pgwire connections from the cloud - /// balancer pods. - pub balancer_sql_listen_addr: SocketAddr, - /// The IP address and port to listen for HTTP connections from the cloud balancer pods. - pub balancer_http_listen_addr: SocketAddr, /// The IP address and port to listen for pgwire connections from the cloud /// system on. pub internal_sql_listen_addr: SocketAddr, @@ -219,8 +214,6 @@ pub struct Listeners { // Drop order matters for these fields. sql: (ListenerHandle, Pin>), http: (ListenerHandle, Pin>), - balancer_sql: (ListenerHandle, Pin>), - balancer_http: (ListenerHandle, Pin>), internal_sql: (ListenerHandle, Pin>), internal_http: (ListenerHandle, Pin>), } @@ -241,23 +234,17 @@ impl Listeners { ListenersConfig { sql_listen_addr, http_listen_addr, - balancer_sql_listen_addr, - balancer_http_listen_addr, internal_sql_listen_addr, internal_http_listen_addr, }: ListenersConfig, ) -> Result { let sql = mz_server_core::listen(&sql_listen_addr).await?; let http = mz_server_core::listen(&http_listen_addr).await?; - let balancer_sql = mz_server_core::listen(&balancer_sql_listen_addr).await?; - let balancer_http = mz_server_core::listen(&balancer_http_listen_addr).await?; let internal_sql = mz_server_core::listen(&internal_sql_listen_addr).await?; let internal_http = mz_server_core::listen(&internal_http_listen_addr).await?; Ok(Listeners { sql, http, - balancer_sql, - balancer_http, internal_sql, internal_http, }) @@ -269,8 +256,6 @@ impl Listeners { Listeners::bind(ListenersConfig { sql_listen_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0), http_listen_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0), - balancer_sql_listen_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0), - balancer_http_listen_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0), internal_sql_listen_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0), internal_http_listen_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0), }) @@ -289,8 +274,6 @@ impl Listeners { let Listeners { sql: (sql_listener, sql_conns), http: (http_listener, http_conns), - balancer_sql: (balancer_sql_listener, balancer_sql_conns), - balancer_http: (balancer_http_listener, balancer_http_conns), internal_sql: (internal_sql_listener, internal_sql_conns), internal_http: (internal_http_listener, internal_http_conns), } = self; @@ -561,10 +544,10 @@ impl Listeners { }; // Load the adapter durable storage. - let adapter_storage = if read_only { + let (adapter_storage, audit_logs_handle) = if read_only { // TODO: behavior of migrations when booting in savepoint mode is // not well defined. - let adapter_storage = openable_adapter_storage + let (adapter_storage, audit_logs_handle) = openable_adapter_storage .open_savepoint(boot_ts, &bootstrap_args) .await?; @@ -572,9 +555,9 @@ impl Listeners { // because we are by definition not the leader if we are in // read-only mode. - adapter_storage + (adapter_storage, audit_logs_handle) } else { - let adapter_storage = openable_adapter_storage + let (adapter_storage, audit_logs_handle) = openable_adapter_storage .open(boot_ts, &bootstrap_args) .await?; @@ -583,7 +566,7 @@ impl Listeners { // fenced out all other environments using the adapter storage. deployment_state.set_is_leader(); - adapter_storage + (adapter_storage, audit_logs_handle) }; info!( @@ -633,6 +616,7 @@ impl Listeners { controller_config: config.controller, controller_envd_epoch: envd_epoch, storage: adapter_storage, + audit_logs_handle, timestamp_oracle_url: config.timestamp_oracle_url, unsafe_mode: config.unsafe_mode, all_features: config.all_features, @@ -761,50 +745,6 @@ impl Listeners { }) }); - // Launch HTTP server exposed to balancers - task::spawn(|| "balancer_http_server", { - let balancer_http_server = HttpServer::new(HttpConfig { - source: "balancer", - // TODO(Alex): implement self-signed TLS for all internal connections - tls: None, - frontegg: config.frontegg.clone(), - adapter_client: adapter_client.clone(), - allowed_origin: config.cors_allowed_origin, - active_connection_counter: active_connection_counter.clone(), - helm_chart_version: config.helm_chart_version.clone(), - concurrent_webhook_req: webhook_concurrency_limit.semaphore(), - metrics: http_metrics, - }); - mz_server_core::serve(ServeConfig { - conns: balancer_http_conns, - server: balancer_http_server, - // `environmentd` does not currently need to dynamically - // configure graceful termination behavior. - dyncfg: None, - }) - }); - - // Launch SQL server exposed to balancers - task::spawn(|| "balancer_sql_server", { - let balancer_sql_server = mz_pgwire::Server::new(mz_pgwire::Config { - label: "balancer_pgwire", - tls: None, - adapter_client: adapter_client.clone(), - frontegg: config.frontegg.clone(), - metrics, - internal: false, - active_connection_counter: active_connection_counter.clone(), - helm_chart_version: config.helm_chart_version.clone(), - }); - mz_server_core::serve(ServeConfig { - conns: balancer_sql_conns, - server: balancer_sql_server, - // `environmentd` does not currently need to dynamically - // configure graceful termination behavior. - dyncfg: None, - }) - }); - // Start telemetry reporting loop. if let Some(segment_client) = segment_client { telemetry::start_reporting(telemetry::Config { @@ -840,8 +780,6 @@ impl Listeners { Ok(Server { sql_listener, http_listener, - balancer_sql_listener, - balancer_http_listener, internal_sql_listener, internal_http_listener, _adapter_handle: adapter_handle, @@ -884,8 +822,6 @@ pub struct Server { // Drop order matters for these fields. sql_listener: ListenerHandle, http_listener: ListenerHandle, - balancer_sql_listener: ListenerHandle, - balancer_http_listener: ListenerHandle, internal_sql_listener: ListenerHandle, internal_http_listener: ListenerHandle, _adapter_handle: mz_adapter::Handle, @@ -900,14 +836,6 @@ impl Server { self.http_listener.local_addr() } - pub fn balancer_sql_local_addr(&self) -> SocketAddr { - self.balancer_sql_listener.local_addr() - } - - pub fn balancer_http_local_addr(&self) -> SocketAddr { - self.balancer_http_listener.local_addr() - } - pub fn internal_sql_local_addr(&self) -> SocketAddr { self.internal_sql_listener.local_addr() } diff --git a/src/environmentd/tests/server.rs b/src/environmentd/tests/server.rs index 48197312f24b4..af0ae0fb4409e 100644 --- a/src/environmentd/tests/server.rs +++ b/src/environmentd/tests/server.rs @@ -357,6 +357,7 @@ fn test_statement_logging_basic() { error_message: Option, prepared_at: DateTime, execution_strategy: Option, + result_size: Option, rows_returned: Option, execution_timestamp: Option, } @@ -376,6 +377,7 @@ fn test_statement_logging_basic() { mseh.error_message, mpsh.prepared_at, mseh.execution_strategy, + mseh.result_size, mseh.rows_returned, mseh.execution_timestamp FROM @@ -412,8 +414,9 @@ ORDER BY mseh.began_at", error_message: r.get(4), prepared_at: r.get(5), execution_strategy: r.get(6), - rows_returned: r.get(7), - execution_timestamp: r.get::<_, Option>(8).map(|UInt8(val)| val), + result_size: r.get(7), + rows_returned: r.get(8), + execution_timestamp: r.get::<_, Option>(9).map(|UInt8(val)| val), }) .collect::>(), Err(rows) => { @@ -453,18 +456,21 @@ ORDER BY mseh.began_at", } } } + assert!(sl_results[0].result_size.unwrap_or(0) > 0); assert_eq!(sl_results[0].rows_returned, Some(1)); assert_eq!(sl_results[0].finished_status, "success"); assert_eq!( sl_results[0].execution_strategy.as_ref().unwrap(), "constant" ); + assert!(sl_results[1].result_size.unwrap_or(0) > 0); assert_eq!(sl_results[1].rows_returned, Some(10001)); assert_eq!(sl_results[1].finished_status, "success"); assert_eq!( sl_results[1].execution_strategy.as_ref().unwrap(), "standard" ); + assert!(sl_results[2].result_size.unwrap_or(0) > 0); assert_eq!(sl_results[2].rows_returned, Some(10001)); assert_eq!(sl_results[2].finished_status, "success"); assert_eq!( @@ -477,6 +483,7 @@ ORDER BY mseh.began_at", .as_ref() .unwrap() .contains("division by zero")); + assert_none!(sl_results[3].result_size); assert_none!(sl_results[3].rows_returned); } diff --git a/src/environmentd/tests/testdata/http/ws b/src/environmentd/tests/testdata/http/ws index bee3e9252cb92..f8121b4e3b15a 100644 --- a/src/environmentd/tests/testdata/http/ws +++ b/src/environmentd/tests/testdata/http/ws @@ -402,7 +402,7 @@ ws-text ws-text {"query": "SELECT 1 FROM mz_sources LIMIT 1"} ---- -{"type":"Notice","payload":{"message":"{\n \"plans\": {\n \"raw\": {\n \"text\": \"Finish limit=1 output=[#0]\\n Project (#15)\\n Map (1)\\n Get mz_catalog.mz_sources\\n\\nTarget cluster: mz_catalog_server\\n\",\n \"json\": {\n \"Project\": {\n \"input\": {\n \"Map\": {\n \"input\": {\n \"Get\": {\n \"id\": {\n \"Global\": {\n \"System\": 460\n }\n },\n \"typ\": {\n \"column_types\": [\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"Oid\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": {\n \"Array\": \"MzAclItem\"\n },\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n }\n ],\n \"keys\": [\n [\n 0\n ],\n [\n 1\n ]\n ]\n }\n }\n },\n \"scalars\": [\n {\n \"Literal\": [\n {\n \"data\": [\n 45,\n 1\n ]\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n }\n ]\n }\n },\n \"outputs\": [\n 15\n ]\n }\n }\n },\n \"optimized\": {\n \"global\": {\n \"text\": \"t72:\\n Finish limit=1 output=[#0]\\n ArrangeBy keys=[[#0]]\\n ReadGlobalFromSameDataflow t71\\n\\nt71:\\n Project (#15)\\n Map (1)\\n ReadIndex on=mz_sources mz_sources_ind=[*** full scan ***]\\n\\nTarget cluster: mz_catalog_server\\n\",\n \"json\": {\n \"plans\": [\n {\n \"id\": \"t72\",\n \"plan\": {\n \"ArrangeBy\": {\n \"input\": {\n \"Get\": {\n \"id\": {\n \"Global\": {\n \"Transient\": 71\n }\n },\n \"typ\": {\n \"column_types\": [\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ],\n \"keys\": []\n },\n \"access_strategy\": \"SameDataflow\"\n }\n },\n \"keys\": [\n [\n {\n \"Column\": 0\n }\n ]\n ]\n }\n }\n },\n {\n \"id\": \"t71\",\n \"plan\": {\n \"Project\": {\n \"input\": {\n \"Map\": {\n \"input\": {\n \"Get\": {\n \"id\": {\n \"Global\": {\n \"System\": 460\n }\n },\n \"typ\": {\n \"column_types\": [\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"Oid\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": {\n \"Array\": \"MzAclItem\"\n },\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n }\n ],\n \"keys\": [\n [\n 0\n ],\n [\n 1\n ]\n ]\n },\n \"access_strategy\": {\n \"Index\": [\n [\n {\n \"System\": 732\n },\n \"FullScan\"\n ]\n ]\n }\n }\n },\n \"scalars\": [\n {\n \"Literal\": [\n {\n \"Ok\": {\n \"data\": [\n 45,\n 1\n ]\n }\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n }\n ]\n }\n },\n \"outputs\": [\n 15\n ]\n }\n }\n }\n ],\n \"sources\": []\n }\n },\n \"fast_path\": {\n \"text\": \"Explained Query (fast path):\\n Finish limit=1 output=[#0]\\n Project (#15)\\n Map (1)\\n ReadIndex on=mz_catalog.mz_sources mz_sources_ind=[*** full scan ***]\\n\\nTarget cluster: mz_catalog_server\\n\",\n \"json\": {\n \"plans\": [\n {\n \"id\": \"Explained Query (fast path)\",\n \"plan\": {\n \"PeekExisting\": [\n {\n \"System\": 460\n },\n {\n \"System\": 732\n },\n null,\n {\n \"mfp\": {\n \"expressions\": [\n {\n \"Literal\": [\n {\n \"Ok\": {\n \"data\": [\n 45,\n 1\n ]\n }\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n }\n ],\n \"predicates\": [],\n \"projection\": [\n 15\n ],\n \"input_arity\": 15\n }\n }\n ]\n }\n }\n ],\n \"sources\": []\n }\n }\n }\n },\n \"insights\": {\n \"imports\": {\n \"s732\": {\n \"name\": {\n \"schema\": \"mz_catalog\",\n \"item\": \"mz_sources_ind\"\n },\n \"type\": \"compute\"\n }\n },\n \"fast_path_clusters\": {},\n \"fast_path_limit\": null,\n \"persist_count\": []\n },\n \"cluster\": {\n \"name\": \"mz_catalog_server\",\n \"id\": {\n \"System\": 2\n }\n },\n \"redacted_sql\": \"SELECT '' FROM [s460 AS mz_catalog.mz_sources] LIMIT ''\"\n}","code":"MZ001","severity":"notice"}} +{"type":"Notice","payload":{"message":"{\n \"plans\": {\n \"raw\": {\n \"text\": \"Finish limit=1 output=[#0]\\n Project (#15)\\n Map (1)\\n Get mz_catalog.mz_sources\\n\\nTarget cluster: mz_catalog_server\\n\",\n \"json\": {\n \"Project\": {\n \"input\": {\n \"Map\": {\n \"input\": {\n \"Get\": {\n \"id\": {\n \"Global\": {\n \"System\": 461\n }\n },\n \"typ\": {\n \"column_types\": [\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"Oid\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": {\n \"Array\": \"MzAclItem\"\n },\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n }\n ],\n \"keys\": [\n [\n 0\n ],\n [\n 1\n ]\n ]\n }\n }\n },\n \"scalars\": [\n {\n \"Literal\": [\n {\n \"data\": [\n 45,\n 1\n ]\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n }\n ]\n }\n },\n \"outputs\": [\n 15\n ]\n }\n }\n },\n \"optimized\": {\n \"global\": {\n \"text\": \"t72:\\n Finish limit=1 output=[#0]\\n ArrangeBy keys=[[#0]]\\n ReadGlobalFromSameDataflow t71\\n\\nt71:\\n Project (#15)\\n Map (1)\\n ReadIndex on=mz_sources mz_sources_ind=[*** full scan ***]\\n\\nTarget cluster: mz_catalog_server\\n\",\n \"json\": {\n \"plans\": [\n {\n \"id\": \"t72\",\n \"plan\": {\n \"ArrangeBy\": {\n \"input\": {\n \"Get\": {\n \"id\": {\n \"Global\": {\n \"Transient\": 71\n }\n },\n \"typ\": {\n \"column_types\": [\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ],\n \"keys\": []\n },\n \"access_strategy\": \"SameDataflow\"\n }\n },\n \"keys\": [\n [\n {\n \"Column\": 0\n }\n ]\n ]\n }\n }\n },\n {\n \"id\": \"t71\",\n \"plan\": {\n \"Project\": {\n \"input\": {\n \"Map\": {\n \"input\": {\n \"Get\": {\n \"id\": {\n \"Global\": {\n \"System\": 461\n }\n },\n \"typ\": {\n \"column_types\": [\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"Oid\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": {\n \"Array\": \"MzAclItem\"\n },\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n }\n ],\n \"keys\": [\n [\n 0\n ],\n [\n 1\n ]\n ]\n },\n \"access_strategy\": {\n \"Index\": [\n [\n {\n \"System\": 736\n },\n \"FullScan\"\n ]\n ]\n }\n }\n },\n \"scalars\": [\n {\n \"Literal\": [\n {\n \"Ok\": {\n \"data\": [\n 45,\n 1\n ]\n }\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n }\n ]\n }\n },\n \"outputs\": [\n 15\n ]\n }\n }\n }\n ],\n \"sources\": []\n }\n },\n \"fast_path\": {\n \"text\": \"Explained Query (fast path):\\n Finish limit=1 output=[#0]\\n Project (#15)\\n Map (1)\\n ReadIndex on=mz_catalog.mz_sources mz_sources_ind=[*** full scan ***]\\n\\nTarget cluster: mz_catalog_server\\n\",\n \"json\": {\n \"plans\": [\n {\n \"id\": \"Explained Query (fast path)\",\n \"plan\": {\n \"PeekExisting\": [\n {\n \"System\": 461\n },\n {\n \"System\": 736\n },\n null,\n {\n \"mfp\": {\n \"expressions\": [\n {\n \"Literal\": [\n {\n \"Ok\": {\n \"data\": [\n 45,\n 1\n ]\n }\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n }\n ],\n \"predicates\": [],\n \"projection\": [\n 15\n ],\n \"input_arity\": 15\n }\n }\n ]\n }\n }\n ],\n \"sources\": []\n }\n }\n }\n },\n \"insights\": {\n \"imports\": {\n \"s736\": {\n \"name\": {\n \"schema\": \"mz_catalog\",\n \"item\": \"mz_sources_ind\"\n },\n \"type\": \"compute\"\n }\n },\n \"fast_path_clusters\": {},\n \"fast_path_limit\": null,\n \"persist_count\": []\n },\n \"cluster\": {\n \"name\": \"mz_catalog_server\",\n \"id\": {\n \"System\": 2\n }\n },\n \"redacted_sql\": \"SELECT '' FROM [s461 AS mz_catalog.mz_sources] LIMIT ''\"\n}","code":"MZ001","severity":"notice"}} {"type":"CommandStarting","payload":{"has_rows":true,"is_streaming":false}} {"type":"Rows","payload":{"columns":[{"name":"?column?","type_oid":23,"type_len":4,"type_mod":-1}]}} {"type":"Row","payload":["1"]} @@ -412,7 +412,7 @@ ws-text ws-text {"query": "SELECT 1 / 0 FROM mz_sources LIMIT 1"} ---- -{"type":"Notice","payload":{"message":"{\n \"plans\": {\n \"raw\": {\n \"text\": \"Finish limit=1 output=[#0]\\n Project (#15)\\n Map ((1 / 0))\\n Get mz_catalog.mz_sources\\n\\nTarget cluster: mz_catalog_server\\n\",\n \"json\": {\n \"Project\": {\n \"input\": {\n \"Map\": {\n \"input\": {\n \"Get\": {\n \"id\": {\n \"Global\": {\n \"System\": 460\n }\n },\n \"typ\": {\n \"column_types\": [\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"Oid\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": {\n \"Array\": \"MzAclItem\"\n },\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n }\n ],\n \"keys\": [\n [\n 0\n ],\n [\n 1\n ]\n ]\n }\n }\n },\n \"scalars\": [\n {\n \"CallBinary\": {\n \"func\": \"DivInt32\",\n \"expr1\": {\n \"Literal\": [\n {\n \"data\": [\n 45,\n 1\n ]\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n },\n \"expr2\": {\n \"Literal\": [\n {\n \"data\": [\n 44\n ]\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n }\n }\n }\n ]\n }\n },\n \"outputs\": [\n 15\n ]\n }\n }\n },\n \"optimized\": {\n \"global\": {\n \"text\": \"t75:\\n Finish limit=1 output=[#0]\\n ArrangeBy keys=[[#0]]\\n ReadGlobalFromSameDataflow t74\\n\\nt74:\\n Project (#15)\\n Map (error(\\\"division by zero\\\"))\\n ReadIndex on=mz_sources mz_sources_ind=[*** full scan ***]\\n\\nTarget cluster: mz_catalog_server\\n\",\n \"json\": {\n \"plans\": [\n {\n \"id\": \"t75\",\n \"plan\": {\n \"ArrangeBy\": {\n \"input\": {\n \"Get\": {\n \"id\": {\n \"Global\": {\n \"Transient\": 74\n }\n },\n \"typ\": {\n \"column_types\": [\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ],\n \"keys\": []\n },\n \"access_strategy\": \"SameDataflow\"\n }\n },\n \"keys\": [\n [\n {\n \"Column\": 0\n }\n ]\n ]\n }\n }\n },\n {\n \"id\": \"t74\",\n \"plan\": {\n \"Project\": {\n \"input\": {\n \"Map\": {\n \"input\": {\n \"Get\": {\n \"id\": {\n \"Global\": {\n \"System\": 460\n }\n },\n \"typ\": {\n \"column_types\": [\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"Oid\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": {\n \"Array\": \"MzAclItem\"\n },\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n }\n ],\n \"keys\": [\n [\n 0\n ],\n [\n 1\n ]\n ]\n },\n \"access_strategy\": {\n \"Index\": [\n [\n {\n \"System\": 732\n },\n \"FullScan\"\n ]\n ]\n }\n }\n },\n \"scalars\": [\n {\n \"Literal\": [\n {\n \"Err\": \"DivisionByZero\"\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n }\n ]\n }\n },\n \"outputs\": [\n 15\n ]\n }\n }\n }\n ],\n \"sources\": []\n }\n },\n \"fast_path\": {\n \"text\": \"Explained Query (fast path):\\n Finish limit=1 output=[#0]\\n Project (#15)\\n Map (error(\\\"division by zero\\\"))\\n ReadIndex on=mz_catalog.mz_sources mz_sources_ind=[*** full scan ***]\\n\\nTarget cluster: mz_catalog_server\\n\",\n \"json\": {\n \"plans\": [\n {\n \"id\": \"Explained Query (fast path)\",\n \"plan\": {\n \"PeekExisting\": [\n {\n \"System\": 460\n },\n {\n \"System\": 732\n },\n null,\n {\n \"mfp\": {\n \"expressions\": [\n {\n \"Literal\": [\n {\n \"Err\": \"DivisionByZero\"\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n }\n ],\n \"predicates\": [],\n \"projection\": [\n 15\n ],\n \"input_arity\": 15\n }\n }\n ]\n }\n }\n ],\n \"sources\": []\n }\n }\n }\n },\n \"insights\": {\n \"imports\": {\n \"s732\": {\n \"name\": {\n \"schema\": \"mz_catalog\",\n \"item\": \"mz_sources_ind\"\n },\n \"type\": \"compute\"\n }\n },\n \"fast_path_clusters\": {},\n \"fast_path_limit\": null,\n \"persist_count\": []\n },\n \"cluster\": {\n \"name\": \"mz_catalog_server\",\n \"id\": {\n \"System\": 2\n }\n },\n \"redacted_sql\": \"SELECT '' / '' FROM [s460 AS mz_catalog.mz_sources] LIMIT ''\"\n}","code":"MZ001","severity":"notice"}} +{"type":"Notice","payload":{"message":"{\n \"plans\": {\n \"raw\": {\n \"text\": \"Finish limit=1 output=[#0]\\n Project (#15)\\n Map ((1 / 0))\\n Get mz_catalog.mz_sources\\n\\nTarget cluster: mz_catalog_server\\n\",\n \"json\": {\n \"Project\": {\n \"input\": {\n \"Map\": {\n \"input\": {\n \"Get\": {\n \"id\": {\n \"Global\": {\n \"System\": 461\n }\n },\n \"typ\": {\n \"column_types\": [\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"Oid\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": {\n \"Array\": \"MzAclItem\"\n },\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n }\n ],\n \"keys\": [\n [\n 0\n ],\n [\n 1\n ]\n ]\n }\n }\n },\n \"scalars\": [\n {\n \"CallBinary\": {\n \"func\": \"DivInt32\",\n \"expr1\": {\n \"Literal\": [\n {\n \"data\": [\n 45,\n 1\n ]\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n },\n \"expr2\": {\n \"Literal\": [\n {\n \"data\": [\n 44\n ]\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n }\n }\n }\n ]\n }\n },\n \"outputs\": [\n 15\n ]\n }\n }\n },\n \"optimized\": {\n \"global\": {\n \"text\": \"t75:\\n Finish limit=1 output=[#0]\\n ArrangeBy keys=[[#0]]\\n ReadGlobalFromSameDataflow t74\\n\\nt74:\\n Project (#15)\\n Map (error(\\\"division by zero\\\"))\\n ReadIndex on=mz_sources mz_sources_ind=[*** full scan ***]\\n\\nTarget cluster: mz_catalog_server\\n\",\n \"json\": {\n \"plans\": [\n {\n \"id\": \"t75\",\n \"plan\": {\n \"ArrangeBy\": {\n \"input\": {\n \"Get\": {\n \"id\": {\n \"Global\": {\n \"Transient\": 74\n }\n },\n \"typ\": {\n \"column_types\": [\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ],\n \"keys\": []\n },\n \"access_strategy\": \"SameDataflow\"\n }\n },\n \"keys\": [\n [\n {\n \"Column\": 0\n }\n ]\n ]\n }\n }\n },\n {\n \"id\": \"t74\",\n \"plan\": {\n \"Project\": {\n \"input\": {\n \"Map\": {\n \"input\": {\n \"Get\": {\n \"id\": {\n \"Global\": {\n \"System\": 461\n }\n },\n \"typ\": {\n \"column_types\": [\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"Oid\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": false\n },\n {\n \"scalar_type\": {\n \"Array\": \"MzAclItem\"\n },\n \"nullable\": false\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n },\n {\n \"scalar_type\": \"String\",\n \"nullable\": true\n }\n ],\n \"keys\": [\n [\n 0\n ],\n [\n 1\n ]\n ]\n },\n \"access_strategy\": {\n \"Index\": [\n [\n {\n \"System\": 736\n },\n \"FullScan\"\n ]\n ]\n }\n }\n },\n \"scalars\": [\n {\n \"Literal\": [\n {\n \"Err\": \"DivisionByZero\"\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n }\n ]\n }\n },\n \"outputs\": [\n 15\n ]\n }\n }\n }\n ],\n \"sources\": []\n }\n },\n \"fast_path\": {\n \"text\": \"Explained Query (fast path):\\n Finish limit=1 output=[#0]\\n Project (#15)\\n Map (error(\\\"division by zero\\\"))\\n ReadIndex on=mz_catalog.mz_sources mz_sources_ind=[*** full scan ***]\\n\\nTarget cluster: mz_catalog_server\\n\",\n \"json\": {\n \"plans\": [\n {\n \"id\": \"Explained Query (fast path)\",\n \"plan\": {\n \"PeekExisting\": [\n {\n \"System\": 461\n },\n {\n \"System\": 736\n },\n null,\n {\n \"mfp\": {\n \"expressions\": [\n {\n \"Literal\": [\n {\n \"Err\": \"DivisionByZero\"\n },\n {\n \"scalar_type\": \"Int32\",\n \"nullable\": false\n }\n ]\n }\n ],\n \"predicates\": [],\n \"projection\": [\n 15\n ],\n \"input_arity\": 15\n }\n }\n ]\n }\n }\n ],\n \"sources\": []\n }\n }\n }\n },\n \"insights\": {\n \"imports\": {\n \"s736\": {\n \"name\": {\n \"schema\": \"mz_catalog\",\n \"item\": \"mz_sources_ind\"\n },\n \"type\": \"compute\"\n }\n },\n \"fast_path_clusters\": {},\n \"fast_path_limit\": null,\n \"persist_count\": []\n },\n \"cluster\": {\n \"name\": \"mz_catalog_server\",\n \"id\": {\n \"System\": 2\n }\n },\n \"redacted_sql\": \"SELECT '' / '' FROM [s461 AS mz_catalog.mz_sources] LIMIT ''\"\n}","code":"MZ001","severity":"notice"}} {"type":"CommandStarting","payload":{"has_rows":false,"is_streaming":false}} {"type":"Error","payload":{"message":"division by zero","code":"XX000"}} {"type":"ReadyForQuery","payload":"I"} diff --git a/src/expr-parser/src/parser.rs b/src/expr-parser/src/parser.rs index 7400a0a618ced..e7d63ab8b3cb2 100644 --- a/src/expr-parser/src/parser.rs +++ b/src/expr-parser/src/parser.rs @@ -120,12 +120,12 @@ mod relation { let constant = input.parse::()?; let parse_typ = |input: ParseStream| -> syn::Result { - let attrs = attributes::parse_attributes(input)?; - let Some(column_types) = attrs.types else { - let msg = "Missing expected `types` attribute for Constant line"; + let analyses = analyses::parse_analyses(input)?; + let Some(column_types) = analyses.types else { + let msg = "Missing expected `types` analyses for Constant line"; Err(Error::new(input.span(), msg))? }; - let keys = attrs.keys.unwrap_or_default(); + let keys = analyses.keys.unwrap_or_default(); Ok(RelationType { column_types, keys }) }; @@ -200,13 +200,13 @@ mod relation { if recursive { let (mut ids, mut values, mut limits) = (vec![], vec![], vec![]); - for (id, attrs, value) in ctes.into_iter().rev() { + for (id, analyses, value) in ctes.into_iter().rev() { let typ = { - let Some(column_types) = attrs.types else { - let msg = format!("`let {}` needs a `types` attribute", id); + let Some(column_types) = analyses.types else { + let msg = format!("`let {}` needs a `types` analyses", id); Err(Error::new(with.span(), msg))? }; - let keys = attrs.keys.unwrap_or_default(); + let keys = analyses.keys.unwrap_or_default(); RelationType { column_types, keys } }; @@ -251,7 +251,7 @@ mod relation { fn parse_cte( ctx: CtxRef, input: ParseStream, - ) -> syn::Result<(LocalId, attributes::Attributes, MirRelationExpr)> { + ) -> syn::Result<(LocalId, analyses::Analyses, MirRelationExpr)> { let cte = input.parse::()?; let ident = input.parse::()?; @@ -259,12 +259,12 @@ mod relation { input.parse::()?; - let attrs = attributes::parse_attributes(input)?; + let analyses = analyses::parse_analyses(input)?; let parse_value = ParseChildren::new(input, cte.span().start()); let value = parse_value.parse_one(ctx, parse_expr)?; - Ok((id, attrs, value)) + Ok((id, analyses, value)) } fn parse_project(ctx: CtxRef, input: ParseStream) -> Result { @@ -953,7 +953,7 @@ mod scalar { let typ = if input.eat(kw::null) { packer.push(Datum::Null); input.parse::()?; - attributes::parse_scalar_type(input)?.nullable(true) + analyses::parse_scalar_type(input)?.nullable(true) } else { match input.parse::()? { syn::Lit::Str(l) => { @@ -1237,21 +1237,21 @@ mod row { } } -mod attributes { +mod analyses { use mz_repr::{ColumnType, ScalarType}; use super::*; #[derive(Default)] - pub struct Attributes { + pub struct Analyses { pub types: Option>, pub keys: Option>>, } - pub fn parse_attributes(input: ParseStream) -> syn::Result { - let mut attributes = Attributes::default(); + pub fn parse_analyses(input: ParseStream) -> syn::Result { + let mut analyses = Analyses::default(); - // Attributes are optional, appearing after a `//` at the end of the + // Analyses are optional, appearing after a `//` at the end of the // line. However, since the syn lexer eats comments, we assume that `//` // was replaced with `::` upfront. if input.eat(syn::Token![::]) { @@ -1260,7 +1260,7 @@ mod attributes { let (start, end) = (inner.span().start(), inner.span().end()); if start.line != end.line { - let msg = "attributes should not span more than one line".to_string(); + let msg = "analyses should not span more than one line".to_string(); Err(Error::new(inner.span(), msg))? } @@ -1270,17 +1270,17 @@ mod attributes { "types" => { inner.parse::()?; let value = inner.parse::()?.value(); - attributes.types = Some(parse_types.parse_str(&value)?); + analyses.types = Some(parse_types.parse_str(&value)?); } // TODO: support keys key => { - let msg = format!("unexpected attribute type `{}`", key); + let msg = format!("unexpected analysis type `{}`", key); Err(Error::new(inner.span(), msg))?; } } } } - Ok(attributes) + Ok(analyses) } fn parse_types(input: ParseStream) -> syn::Result> { @@ -1394,7 +1394,7 @@ mod def { input.parse::()?; let column_name = input.parse::()?.to_string(); input.parse::()?; - let column_type = attributes::parse_column_type(input)?; + let column_type = analyses::parse_column_type(input)?; Ok((column_name, column_type)) } diff --git a/src/expr/src/explain/text.rs b/src/expr/src/explain/text.rs index cd50ca085ba71..bb4c712a903ea 100644 --- a/src/expr/src/explain/text.rs +++ b/src/expr/src/explain/text.rs @@ -18,7 +18,7 @@ use mz_ore::soft_assert_eq_or_log; use mz_ore::str::{closure_to_display, separated, Indent, IndentLike, StrExt}; use mz_repr::explain::text::DisplayText; use mz_repr::explain::{ - CompactScalars, ExprHumanizer, HumanizedAttributes, IndexUsageType, Indices, + CompactScalars, ExprHumanizer, HumanizedAnalyses, IndexUsageType, Indices, PlanRenderingContext, RenderingContext, ScalarOps, }; use mz_repr::{Datum, Diff, GlobalId, Row}; @@ -46,8 +46,10 @@ where if let Some(finishing) = &self.context.finishing { if ctx.config.humanized_exprs { - let attrs = ctx.annotations.get(&self.plan.plan); - let cols = attrs.map(|attrs| attrs.column_names.clone()).flatten(); + let analyses = ctx.annotations.get(&self.plan.plan); + let cols = analyses + .map(|analyses| analyses.column_names.clone()) + .flatten(); mode.expr(finishing, cols.as_ref()).fmt_text(f, &mut ctx)?; } else { mode.expr(finishing, None).fmt_text(f, &mut ctx)?; @@ -112,8 +114,10 @@ where // If present, a RowSetFinishing always applies to the first rendered plan. Some(finishing) if no == 0 => { if ctx.config.humanized_exprs { - let attrs = ctx.annotations.get(plan.plan); - let cols = attrs.map(|attrs| attrs.column_names.clone()).flatten(); + let analyses = ctx.annotations.get(plan.plan); + let cols = analyses + .map(|analyses| analyses.column_names.clone()) + .flatten(); mode.expr(finishing, cols.as_ref()).fmt_text(f, ctx)?; } else { mode.expr(finishing, None).fmt_text(f, ctx)?; @@ -295,7 +299,7 @@ impl MirRelationExpr { Ok(rows) => { if !rows.is_empty() { write!(f, "{}Constant", ctx.indent)?; - self.fmt_attributes(f, ctx)?; + self.fmt_analyses(f, ctx)?; ctx.indented(|ctx| { fmt_text_constant_rows( f, @@ -306,7 +310,7 @@ impl MirRelationExpr { })?; } else { write!(f, "{}Constant ", ctx.indent)?; - self.fmt_attributes(f, ctx)?; + self.fmt_analyses(f, ctx)?; } } Err(err) => { @@ -338,11 +342,11 @@ impl MirRelationExpr { Ok(()) })?; write!(f, "{}Return", ctx.indent)?; - self.fmt_attributes(f, ctx)?; + self.fmt_analyses(f, ctx)?; ctx.indented(|ctx| head.fmt_text(f, ctx))?; } else { write!(f, "{}Return", ctx.indent)?; - self.fmt_attributes(f, ctx)?; + self.fmt_analyses(f, ctx)?; ctx.indented(|ctx| head.fmt_text(f, ctx))?; writeln!(f, "{}With", ctx.indent)?; ctx.indented(|ctx| { @@ -379,7 +383,7 @@ impl MirRelationExpr { unreachable!(); // We exclude this case in `as_explain_single_plan`. } else { write!(f, "{}Return", ctx.indent)?; - self.fmt_attributes(f, ctx)?; + self.fmt_analyses(f, ctx)?; ctx.indented(|ctx| head.fmt_text(f, ctx))?; write!(f, "{}With Mutually Recursive", ctx.indent)?; if let Some(limit) = all_limits_same { @@ -475,7 +479,7 @@ impl MirRelationExpr { } } } - self.fmt_attributes(f, ctx)?; + self.fmt_analyses(f, ctx)?; } Project { outputs, input } => { FmtNode { @@ -483,7 +487,7 @@ impl MirRelationExpr { let outputs = mode.seq(outputs, self.column_names(ctx)); let outputs = CompactScalars(outputs); write!(f, "{}Project ({})", ctx.indent, outputs)?; - self.fmt_attributes(f, ctx) + self.fmt_analyses(f, ctx) }, fmt_children: |f, ctx| input.fmt_text(f, ctx), } @@ -495,7 +499,7 @@ impl MirRelationExpr { let scalars = mode.seq(scalars, self.column_names(ctx)); let scalars = CompactScalars(scalars); write!(f, "{}Map ({})", ctx.indent, scalars)?; - self.fmt_attributes(f, ctx) + self.fmt_analyses(f, ctx) }, fmt_children: |f, ctx| input.fmt_text(f, ctx), } @@ -507,7 +511,7 @@ impl MirRelationExpr { let exprs = mode.seq(exprs, input.column_names(ctx)); let exprs = CompactScalars(exprs); write!(f, "{}FlatMap {}({})", ctx.indent, func, exprs)?; - self.fmt_attributes(f, ctx) + self.fmt_analyses(f, ctx) }, fmt_children: |f, ctx| input.fmt_text(f, ctx), } @@ -524,7 +528,7 @@ impl MirRelationExpr { let predicates = separated(" AND ", predicates); write!(f, "{}Filter {}", ctx.indent, predicates)?; } - self.fmt_attributes(f, ctx) + self.fmt_analyses(f, ctx) }, fmt_children: |f, ctx| input.fmt_text(f, ctx), } @@ -557,7 +561,7 @@ impl MirRelationExpr { write!(f, " type={}", name)?; } - self.fmt_attributes(f, ctx)?; + self.fmt_analyses(f, ctx)?; if ctx.config.join_impls { let input_name = &|pos: usize| -> String { @@ -712,7 +716,7 @@ impl MirRelationExpr { Some(literal_constraints.clone()), cse_id, )?; - self.fmt_attributes(f, ctx)?; + self.fmt_analyses(f, ctx)?; } Reduce { group_key, @@ -749,7 +753,7 @@ impl MirRelationExpr { if let Some(expected_group_size) = expected_group_size { write!(f, " exp_group_size={}", expected_group_size)?; } - self.fmt_attributes(f, ctx) + self.fmt_analyses(f, ctx) }, fmt_children: |f, ctx| input.fmt_text(f, ctx), } @@ -794,7 +798,7 @@ impl MirRelationExpr { if let Some(expected_group_size) = expected_group_size { write!(f, " exp_group_size={}", expected_group_size)?; } - self.fmt_attributes(f, ctx) + self.fmt_analyses(f, ctx) }, fmt_children: |f, ctx| input.fmt_text(f, ctx), } @@ -804,7 +808,7 @@ impl MirRelationExpr { FmtNode { fmt_root: |f, ctx| { write!(f, "{}Negate", ctx.indent)?; - self.fmt_attributes(f, ctx) + self.fmt_analyses(f, ctx) }, fmt_children: |f, ctx| input.fmt_text(f, ctx), } @@ -814,7 +818,7 @@ impl MirRelationExpr { FmtNode { fmt_root: |f, ctx| { write!(f, "{}Threshold", ctx.indent)?; - self.fmt_attributes(f, ctx) + self.fmt_analyses(f, ctx) }, fmt_children: |f, ctx| input.fmt_text(f, ctx), } @@ -822,7 +826,7 @@ impl MirRelationExpr { } Union { base, inputs } => { write!(f, "{}Union", ctx.indent)?; - self.fmt_attributes(f, ctx)?; + self.fmt_analyses(f, ctx)?; ctx.indented(|ctx| { base.fmt_text(f, ctx)?; for input in inputs.iter() { @@ -843,7 +847,7 @@ impl MirRelationExpr { let keys = separated("], [", keys); write!(f, " keys=[[{}]]", keys)?; - self.fmt_attributes(f, ctx) + self.fmt_analyses(f, ctx) }, fmt_children: |f, ctx| input.fmt_text(f, ctx), } @@ -854,16 +858,16 @@ impl MirRelationExpr { Ok(()) } - fn fmt_attributes( + fn fmt_analyses( &self, f: &mut fmt::Formatter<'_>, ctx: &PlanRenderingContext<'_, MirRelationExpr>, ) -> fmt::Result { - if ctx.config.requires_attributes() { - if let Some(attrs) = ctx.annotations.get(self) { - writeln!(f, " {}", HumanizedAttributes::new(attrs, ctx)) + if ctx.config.requires_analyses() { + if let Some(analyses) = ctx.annotations.get(self) { + writeln!(f, " {}", HumanizedAnalyses::new(analyses, ctx)) } else { - writeln!(f, " // error: no attrs for subtree in map") + writeln!(f, " // error: no analyses for subtree in map") } } else { writeln!(f) @@ -876,8 +880,8 @@ impl MirRelationExpr { ) -> Option<&Vec> { if !ctx.config.humanized_exprs { None - } else if let Some(attrs) = ctx.annotations.get(self) { - attrs.column_names.as_ref() + } else if let Some(analyses) = ctx.annotations.get(self) { + analyses.column_names.as_ref() } else { None } diff --git a/src/expr/src/relation.rs b/src/expr/src/relation.rs index 33ea7f5c01684..2c2035a7fd1dc 100644 --- a/src/expr/src/relation.rs +++ b/src/expr/src/relation.rs @@ -3524,13 +3524,16 @@ impl RowSetFinishing { impl RowSetFinishing { /// Applies finishing actions to a [`RowCollection`], and reports the total /// time it took to run. + /// + /// Returns a [`SortedRowCollectionIter`] that contains all of the response data, as + /// well as the size of the response in bytes. pub fn finish( &self, rows: RowCollection, max_result_size: u64, max_returned_query_size: Option, duration_histogram: &Histogram, - ) -> Result { + ) -> Result<(SortedRowCollectionIter, usize), String> { let now = Instant::now(); let result = self.finish_inner(rows, max_result_size, max_returned_query_size); let duration = now.elapsed(); @@ -3545,7 +3548,7 @@ impl RowSetFinishing { rows: RowCollection, max_result_size: u64, max_returned_query_size: Option, - ) -> Result { + ) -> Result<(SortedRowCollectionIter, usize), String> { // How much additional memory is required to make a sorted view. let sorted_view_mem = rows.entries().saturating_mul(std::mem::size_of::()); let required_memory = rows.byte_len().saturating_add(sorted_view_mem); @@ -3568,19 +3571,24 @@ impl RowSetFinishing { iter = iter.with_limit(limit); }; + // TODO(parkmycar): Re-think how we can calculate the total response size without + // having to iterate through the entire collection of Rows, while still + // respecting the LIMIT, OFFSET, and projections. + // + // Note: It feels a bit bad always calculating the response size, but we almost + // always need it to either check the `max_returned_query_size`, or for reporting + // in the query history. + let response_size: usize = iter.clone().map(|row| row.data().len()).sum(); + // Bail if we would end up returning more data to the client than they can support. if let Some(max) = max_returned_query_size { - // TODO(parkmycar): Re-implement out LIMIT and OFFSET logic so we can calculate - // the remaining bytes via `(Row, Diff)` tuples. - let remaining_bytes: usize = iter.clone().map(|row| row.data().len()).sum(); - - if remaining_bytes > usize::cast_from(max) { + if response_size > usize::cast_from(max) { let max_bytes = ByteSize::b(max); return Err(format!("result exceeds max size of {max_bytes}")); } } - Ok(iter) + Ok((iter, response_size)) } } diff --git a/src/expr/src/row/collection.rs b/src/expr/src/row/collection.rs index 2b090fa232e7d..f36abc74a64c7 100644 --- a/src/expr/src/row/collection.rs +++ b/src/expr/src/row/collection.rs @@ -34,7 +34,7 @@ pub struct RowCollection { /// Contiguous blob of encoded Rows. encoded: Bytes, /// Metadata about an individual Row in the blob. - metadata: Vec, + metadata: Arc<[EncodedRowMetadata]>, /// Ends of non-empty, sorted runs of rows in index into `metadata`. runs: Vec, } @@ -82,7 +82,7 @@ impl RowCollection { RowCollection { encoded: Bytes::from(encoded), - metadata, + metadata: metadata.into(), runs, } } @@ -104,7 +104,7 @@ impl RowCollection { }); let self_len = self.metadata.len(); - self.metadata.extend(mapped_metas); + self.metadata = self.metadata.iter().cloned().chain(mapped_metas).collect(); self.encoded = Bytes::from(new_bytes); self.runs.extend(other.runs.iter().map(|f| f + self_len)); } @@ -454,6 +454,10 @@ impl RowIterator for SortedRowCollectionIter { fn count(&self) -> usize { self.collection.collection.count(self.offset, self.limit) } + + fn box_clone(&self) -> Box { + Box::new(self.clone()) + } } impl IntoRowIterator for SortedRowCollection { @@ -538,7 +542,7 @@ mod tests { RowCollection { encoded: Bytes::from(encoded), - metadata, + metadata: metadata.into(), runs, } } diff --git a/src/kafka-util/src/client.rs b/src/kafka-util/src/client.rs index feb4651f28093..ca7f00a2ce02a 100644 --- a/src/kafka-util/src/client.rs +++ b/src/kafka-util/src/client.rs @@ -206,6 +206,7 @@ impl FromStr for MzKafkaError { Ok(Self::UnsupportedBrokerVersion) } else if s.contains("Disconnected while requesting ApiVersion") || s.contains("Broker transport failure") + || s.contains("Connection refused") { Ok(Self::BrokerTransportFailure) } else if Regex::new(r"(\d+)/\1 brokers are down") diff --git a/src/materialized/BUILD.bazel b/src/materialized/BUILD.bazel index 0fefbcd0ac78c..2378b4c842fff 100644 --- a/src/materialized/BUILD.bazel +++ b/src/materialized/BUILD.bazel @@ -29,7 +29,7 @@ rust_binary( proc_macro_deps = [] + all_crate_deps(proc_macro = True), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/clusterd:mz_clusterd", "//src/environmentd:mz_environmentd", diff --git a/src/materialized/Cargo.toml b/src/materialized/Cargo.toml index fc5d5be24d085..fe07f73a63d55 100644 --- a/src/materialized/Cargo.toml +++ b/src/materialized/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mz-materialized" description = "Materialize's unified binary." -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" edition.workspace = true rust-version.workspace = true publish = false diff --git a/src/orchestrator-kubernetes/src/lib.rs b/src/orchestrator-kubernetes/src/lib.rs index 0fb05c18c4293..f7815e65d3f50 100644 --- a/src/orchestrator-kubernetes/src/lib.rs +++ b/src/orchestrator-kubernetes/src/lib.rs @@ -97,6 +97,8 @@ pub struct KubernetesOrchestratorConfig { pub name_prefix: Option, /// Whether we should attempt to collect metrics from kubernetes pub collect_pod_metrics: bool, + /// Whether to annotate pods for prometheus service discovery. + pub enable_prometheus_scrape_annotations: bool, } impl KubernetesOrchestratorConfig { @@ -820,7 +822,7 @@ impl NamespacedOrchestrator for NamespacedKubernetesOrchestrator { None }; - let pod_annotations = btreemap! { + let mut pod_annotations = btreemap! { // Prevent the cluster-autoscaler (or karpenter) from evicting these pods in attempts to scale down // and terminate nodes. // This will cost us more money, but should give us better uptime. @@ -832,6 +834,19 @@ impl NamespacedOrchestrator for NamespacedKubernetesOrchestrator { // It's called do-not-disrupt in newer versions of karpenter, so adding for forward/backward compatibility "karpenter.sh/do-not-disrupt".to_owned() => "true".to_string(), }; + if self.config.enable_prometheus_scrape_annotations { + if let Some(internal_http_port) = ports_in + .iter() + .find(|port| port.name == "internal-http") + .map(|port| port.port_hint.to_string()) + { + // Enable prometheus scrape discovery + pod_annotations.insert("prometheus.io/scrape".to_owned(), "true".to_string()); + pod_annotations.insert("prometheus.io/port".to_owned(), internal_http_port); + pod_annotations.insert("prometheus.io/path".to_owned(), "/metrics".to_string()); + pod_annotations.insert("prometheus.io/scheme".to_owned(), "http".to_string()); + } + } let default_node_selector = if disk { vec![("materialize.cloud/disk".to_string(), disk.to_string())] diff --git a/src/orchestratord/BUILD.bazel b/src/orchestratord/BUILD.bazel index b081d9ee5379d..4947ff7be4767 100644 --- a/src/orchestratord/BUILD.bazel +++ b/src/orchestratord/BUILD.bazel @@ -30,7 +30,7 @@ rust_library( proc_macro_deps = [] + all_crate_deps(proc_macro = True), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/alloc:mz_alloc", "//src/alloc-default:mz_alloc_default", @@ -69,7 +69,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/alloc:mz_alloc", "//src/alloc-default:mz_alloc_default", @@ -124,7 +124,7 @@ rust_binary( proc_macro_deps = [] + all_crate_deps(proc_macro = True), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ ":mz_orchestratord", "//src/alloc:mz_alloc", diff --git a/src/orchestratord/Cargo.toml b/src/orchestratord/Cargo.toml index bb69e586c8f81..b0b1173f013b5 100644 --- a/src/orchestratord/Cargo.toml +++ b/src/orchestratord/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mz-orchestratord" description = "Kubernetes operator for Materialize regions" -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" edition.workspace = true rust-version.workspace = true publish = false diff --git a/src/orchestratord/src/controller/materialize.rs b/src/orchestratord/src/controller/materialize.rs index 33d862ef48254..3c1a57f07cc28 100644 --- a/src/orchestratord/src/controller/materialize.rs +++ b/src/orchestratord/src/controller/materialize.rs @@ -10,16 +10,20 @@ use std::{ collections::BTreeSet, fmt::Display, + str::FromStr, sync::{Arc, Mutex}, }; use http::HeaderValue; use k8s_openapi::apimachinery::pkg::apis::meta::v1::{Condition, Time}; use kube::{api::PostParams, runtime::controller::Action, Api, Client, Resource, ResourceExt}; +use serde::Deserialize; use tracing::{debug, trace}; use crate::metrics::Metrics; -use mz_cloud_resources::crd::materialize::v1alpha1::{Materialize, MaterializeStatus}; +use mz_cloud_resources::crd::materialize::v1alpha1::{ + Materialize, MaterializeCertSpec, MaterializeStatus, +}; use mz_orchestrator_kubernetes::KubernetesImagePullPolicy; use mz_orchestrator_tracing::TracingCliArgs; use mz_ore::{cast::CastFrom, cli::KeyValueArg, instrument}; @@ -27,6 +31,7 @@ use mz_sql::catalog::CloudProvider; mod console; mod resources; +mod tls; #[derive(clap::Parser)] pub struct Args { @@ -39,13 +44,20 @@ pub struct Args { #[clap(long)] create_console: bool, #[clap(long)] - enable_tls: bool, - #[clap(long)] helm_chart_version: Option, #[clap(long, default_value = "kubernetes")] secrets_controller: String, #[clap(long)] collect_pod_metrics: bool, + #[clap(long)] + enable_prometheus_scrape_annotations: bool, + #[clap(long)] + disable_authentication: bool, + + #[clap(long)] + segment_api_key: Option, + #[clap(long)] + segment_client_side: bool, #[clap(long)] console_image_tag_default: String, @@ -61,6 +73,8 @@ pub struct Args { scheduler_name: Option, #[clap(long)] enable_security_context: bool, + #[clap(long)] + enable_internal_statement_logging: bool, #[clap(long)] orchestratord_pod_selector_labels: Vec>, @@ -112,10 +126,6 @@ pub struct Args { environmentd_internal_http_host_override: Option, #[clap(long, default_value = "6879")] environmentd_internal_persist_pubsub_port: i32, - #[clap(long, default_value = "6880")] - environmentd_balancer_sql_port: i32, - #[clap(long, default_value = "6881")] - environmentd_balancer_http_port: i32, #[clap(long, default_value = "6875")] balancerd_sql_port: i32, @@ -126,6 +136,25 @@ pub struct Args { #[clap(long, default_value = "9000")] console_http_port: i32, + + #[clap(long, default_value = "{}")] + default_certificate_specs: DefaultCertificateSpecs, +} + +#[derive(Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct DefaultCertificateSpecs { + balancerd_external: Option, + console_external: Option, + internal: Option, +} + +impl FromStr for DefaultCertificateSpecs { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } } #[derive(clap::Parser)] @@ -201,8 +230,6 @@ impl Context { ); } - assert!(!config.enable_tls, "--enable-tls is not yet implemented"); - Self { config, tracing, diff --git a/src/orchestratord/src/controller/materialize/console.rs b/src/orchestratord/src/controller/materialize/console.rs index 1d6284235cbfe..b7188d7ba8f19 100644 --- a/src/orchestratord/src/controller/materialize/console.rs +++ b/src/orchestratord/src/controller/materialize/console.rs @@ -24,9 +24,15 @@ use k8s_openapi::{ }; use kube::{api::ObjectMeta, Api, Client}; use maplit::btreemap; +use tracing::trace; -use crate::k8s::apply_resource; -use mz_cloud_resources::crd::materialize::v1alpha1::Materialize; +use crate::{ + controller::materialize::tls::{create_certificate, issuer_ref_defined}, + k8s::apply_resource, +}; +use mz_cloud_resources::crd::{ + gen::cert_manager::certificates::Certificate, materialize::v1alpha1::Materialize, +}; // corresponds to the `EXPOSE` line in the console dockerfile const CONSOLE_IMAGE_HTTP_PORT: i32 = 8080; @@ -35,6 +41,7 @@ pub struct Resources { network_policies: Vec, console_deployment: Box, console_service: Box, + console_external_certificate: Box>, } impl Resources { @@ -46,10 +53,13 @@ impl Resources { console_image_ref, )); let console_service = Box::new(create_console_service_object(config, mz)); + let console_external_certificate = + Box::new(create_console_external_certificate(config, mz)); Self { network_policies, console_deployment, console_service, + console_external_certificate, } } @@ -57,13 +67,23 @@ impl Resources { let network_policy_api: Api = Api::namespaced(client.clone(), namespace); let deployment_api: Api = Api::namespaced(client.clone(), namespace); let service_api: Api = Api::namespaced(client.clone(), namespace); + let certificate_api: Api = Api::namespaced(client.clone(), namespace); for network_policy in &self.network_policies { apply_resource(&network_policy_api, network_policy).await?; } + + trace!("creating new console deployment"); apply_resource(&deployment_api, &self.console_deployment).await?; + + trace!("creating new console service"); apply_resource(&service_api, &self.console_service).await?; + if let Some(certificate) = &*self.console_external_certificate { + trace!("creating new console external certificate"); + apply_resource(&certificate_api, certificate).await?; + } + Ok(()) } } @@ -114,6 +134,20 @@ fn create_network_policies(config: &super::Args, mz: &Materialize) -> Vec Option { + create_certificate( + config.default_certificate_specs.console_external.clone(), + mz, + mz.spec.console_external_certificate_spec.clone(), + mz.console_external_certificate_name(), + mz.console_external_certificate_secret_name(), + None, + ) +} + fn create_console_deployment_object( config: &super::Args, mz: &Materialize, @@ -143,24 +177,35 @@ fn create_console_deployment_object( ..Default::default() }; + let scheme = if issuer_ref_defined( + &config.default_certificate_specs.balancerd_external, + &mz.spec.balancerd_external_certificate_spec, + ) { + "https" + } else { + "http" + }; let env = vec![EnvVar { name: "MZ_ENDPOINT".to_string(), value: Some(format!( - "http://{}.{}.svc.cluster.local:{}", - // TODO: this should talk to balancerd eventually, but for now we - // need to bypass auth which requires using the internal port - mz.environmentd_service_name(), + "{}://{}.{}.svc.cluster.local:{}", + scheme, + mz.balancerd_service_name(), mz.namespace(), - config.environmentd_internal_http_port, + config.balancerd_http_port, )), ..Default::default() }]; - if config.enable_tls { - unimplemented!(); - } else { - // currently the docker image just doesn't implement tls - } + //if issuer_ref_defined( + // &config.default_certificate_specs.console_external, + // &mz.spec.console_external_certificate_spec, + //) { + // // TODO define volumes, volume_mounts, and any arg changes + // unimplemented!(); + //} else { + // // currently the docker image just doesn't implement tls + //} let security_context = if config.enable_security_context { // Since we want to adhere to the most restrictive security context, all diff --git a/src/orchestratord/src/controller/materialize/resources.rs b/src/orchestratord/src/controller/materialize/resources.rs index 367a935acd7ea..ab733e028dcbb 100644 --- a/src/orchestratord/src/controller/materialize/resources.rs +++ b/src/orchestratord/src/controller/materialize/resources.rs @@ -20,8 +20,8 @@ use k8s_openapi::{ Capabilities, Container, ContainerPort, EnvVar, EnvVarSource, EphemeralVolumeSource, HTTPGetAction, PersistentVolumeClaimSpec, PersistentVolumeClaimTemplate, Pod, PodSecurityContext, PodSpec, PodTemplateSpec, Probe, SeccompProfile, SecretKeySelector, - SecurityContext, Service, ServiceAccount, ServicePort, ServiceSpec, TCPSocketAction, - Toleration, Volume, VolumeMount, VolumeResourceRequirements, + SecretVolumeSource, SecurityContext, Service, ServiceAccount, ServicePort, ServiceSpec, + TCPSocketAction, Toleration, Volume, VolumeMount, VolumeResourceRequirements, }, networking::v1::{ IPBlock, NetworkPolicy, NetworkPolicyEgressRule, NetworkPolicyIngressRule, @@ -39,7 +39,9 @@ use sha2::{Digest, Sha256}; use tracing::trace; use super::matching_image_from_environmentd_image_ref; +use crate::controller::materialize::tls::{create_certificate, issuer_ref_defined}; use crate::k8s::{apply_resource, delete_resource, get_resource}; +use mz_cloud_resources::crd::gen::cert_manager::certificates::Certificate; use mz_cloud_resources::crd::materialize::v1alpha1::Materialize; use mz_environmentd::DeploymentStatus; use mz_orchestrator_tracing::TracingCliArgs; @@ -56,7 +58,9 @@ pub struct Resources { public_service: Box, generation_service: Box, persist_pubsub_service: Box, + environmentd_certificate: Box>, environmentd_statefulset: Box, + balancerd_external_certificate: Box>, balancerd_deployment: Option>, balancerd_service: Option>, } @@ -79,9 +83,12 @@ impl Resources { let generation_service = Box::new(create_generation_service_object(config, mz, generation)); let persist_pubsub_service = Box::new(create_persist_pubsub_service(config, mz, generation)); + let environmentd_certificate = Box::new(create_environmentd_certificate(config, mz)); let environmentd_statefulset = Box::new(create_environmentd_statefulset_object( config, tracing, mz, generation, )); + let balancerd_external_certificate = + Box::new(create_balancerd_external_certificate(config, mz)); let balancerd_deployment = config .create_balancers .then(|| Box::new(create_balancerd_deployment_object(config, mz))); @@ -98,7 +105,9 @@ impl Resources { public_service, generation_service, persist_pubsub_service, + environmentd_certificate, environmentd_statefulset, + balancerd_external_certificate, balancerd_deployment, balancerd_service, } @@ -120,6 +129,7 @@ impl Resources { let role_binding_api: Api = Api::namespaced(client.clone(), namespace); let statefulset_api: Api = Api::namespaced(client.clone(), namespace); let pod_api: Api = Api::namespaced(client.clone(), namespace); + let certificate_api: Api = Api::namespaced(client.clone(), namespace); for policy in &self.environmentd_network_policies { trace!("applying network policy {}", policy.name_unchecked()); @@ -141,6 +151,16 @@ impl Resources { trace!("creating persist pubsub service"); apply_resource(&service_api, &*self.persist_pubsub_service).await?; + if let Some(certificate) = &*self.balancerd_external_certificate { + trace!("creating new balancerd external certificate"); + apply_resource(&certificate_api, certificate).await?; + } + + if let Some(certificate) = &*self.environmentd_certificate { + trace!("creating new environmentd certificate"); + apply_resource(&certificate_api, certificate).await?; + } + trace!("creating new environmentd statefulset"); apply_resource(&statefulset_api, &*self.environmentd_statefulset).await?; @@ -668,18 +688,6 @@ fn create_base_service_object( name: Some("internal-http".to_string()), ..Default::default() }, - ServicePort { - port: config.environmentd_balancer_sql_port, - protocol: Some("TCP".to_string()), - name: Some("balancer-sql".to_string()), - ..Default::default() - }, - ServicePort { - port: config.environmentd_balancer_http_port, - protocol: Some("TCP".to_string()), - name: Some("balancer-http".to_string()), - ..Default::default() - }, ]; let selector = btreemap! {"materialize.cloud/name".to_string() => mz.environmentd_statefulset_name(generation)}; @@ -724,6 +732,20 @@ fn create_persist_pubsub_service( } } +fn create_environmentd_certificate(config: &super::Args, mz: &Materialize) -> Option { + create_certificate( + config.default_certificate_specs.internal.clone(), + mz, + mz.spec.internal_certificate_spec.clone(), + mz.environmentd_certificate_name(), + mz.environmentd_certificate_secret_name(), + Some(vec![ + mz.environmentd_service_name(), + mz.environmentd_service_internal_fqdn(), + ]), + ) +} + fn create_environmentd_statefulset_object( config: &super::Args, tracing: &TracingCliArgs, @@ -849,14 +871,6 @@ fn create_environmentd_statefulset_object( "--internal-http-listen-addr=0.0.0.0:{}", config.environmentd_internal_http_port ), - format!( - "--balancer-sql-listen-addr=0.0.0.0:{}", - config.environmentd_balancer_sql_port - ), - format!( - "--balancer-http-listen-addr=0.0.0.0:{}", - config.environmentd_balancer_http_port - ), ]); args.extend( @@ -872,13 +886,14 @@ fn create_environmentd_statefulset_object( )); if !config.cloud_provider.is_cloud() { - args.push("--system-parameter-default=cluster_enable_topology_spread=false".into()) + args.push("--system-parameter-default=cluster_enable_topology_spread=false".into()); } - if config.enable_tls { - unimplemented!(); - } else { - args.push("--tls-mode=disable".to_string()); + if config.enable_internal_statement_logging { + args.push("--system-parameter-default=enable_internal_statement_logging=true".into()); + } + if config.disable_authentication { + args.push("--system-parameter-default=enable_rbac_checks=false".into()); } // Add persist arguments. @@ -964,8 +979,43 @@ fn create_environmentd_statefulset_object( ]); } - let mut volumes = None; - let mut volume_mounts = None; + if let Some(segment_api_key) = &config.segment_api_key { + args.push(format!("--segment-api-key={}", segment_api_key)); + if config.segment_client_side { + args.push("--segment-client-side".into()); + } + } + + let mut volumes = Vec::new(); + let mut volume_mounts = Vec::new(); + if issuer_ref_defined( + &config.default_certificate_specs.internal, + &mz.spec.internal_certificate_spec, + ) { + volumes.push(Volume { + name: "certificate".to_owned(), + secret: Some(SecretVolumeSource { + default_mode: Some(0o400), + secret_name: Some(mz.environmentd_certificate_secret_name()), + items: None, + optional: Some(false), + }), + ..Default::default() + }); + volume_mounts.push(VolumeMount { + name: "certificate".to_owned(), + mount_path: "/etc/materialized".to_owned(), + read_only: Some(true), + ..Default::default() + }); + args.extend([ + "--tls-mode=require".into(), + "--tls-cert=/etc/materialized/tls.crt".into(), + "--tls-key=/etc/materialized/tls.key".into(), + ]); + } else { + args.push("--tls-mode=disable".to_string()); + } if let Some(ephemeral_volume_class) = &config.ephemeral_volume_class { args.extend([ format!( @@ -974,7 +1024,7 @@ fn create_environmentd_statefulset_object( ), "--scratch-directory=/scratch".to_string(), ]); - volumes = Some(vec![Volume { + volumes.push(Volume { name: "scratch".to_string(), ephemeral: Some(EphemeralVolumeSource { volume_claim_template: Some(PersistentVolumeClaimTemplate { @@ -995,12 +1045,12 @@ fn create_environmentd_statefulset_object( ..Default::default() }), ..Default::default() - }]); - volume_mounts = Some(vec![VolumeMount { + }); + volume_mounts.push(VolumeMount { name: "scratch".to_string(), mount_path: "/scratch".to_string(), ..Default::default() - }]); + }); } // The `materialize` user used by clusterd always has gid 999. args.push("--orchestrator-kubernetes-service-fs-group=999".to_string()); @@ -1036,6 +1086,9 @@ fn create_environmentd_statefulset_object( if !config.collect_pod_metrics { args.push("--orchestrator-kubernetes-disable-pod-metrics-collection".into()); } + if config.enable_prometheus_scrape_annotations { + args.push("--orchestrator-kubernetes-enable-prometheus-scrape-annotations".into()); + } // Add user-specified extra arguments. if let Some(extra_args) = &mz.spec.environmentd_extra_args { @@ -1084,11 +1137,6 @@ fn create_environmentd_statefulset_object( name: Some("internal-sql".to_owned()), ..Default::default() }, - ContainerPort { - container_port: config.environmentd_balancer_sql_port, - name: Some("balancer-sql".to_owned()), - ..Default::default() - }, ContainerPort { container_port: config.environmentd_http_port, name: Some("http".to_owned()), @@ -1099,11 +1147,6 @@ fn create_environmentd_statefulset_object( name: Some("internal-http".to_owned()), ..Default::default() }, - ContainerPort { - container_port: config.environmentd_balancer_http_port, - name: Some("balancer-http".to_owned()), - ..Default::default() - }, ContainerPort { container_port: config.environmentd_internal_persist_pubsub_port, name: Some("persist-pubsub".to_owned()), @@ -1118,7 +1161,7 @@ fn create_environmentd_statefulset_object( ports: Some(ports), args: Some(args), env: Some(env), - volume_mounts, + volume_mounts: Some(volume_mounts), liveness_probe: Some(probe.clone()), readiness_probe: Some(probe), resources: mz.spec.environmentd_resource_requirements.clone(), @@ -1137,7 +1180,7 @@ fn create_environmentd_statefulset_object( ); pod_template_labels.insert("app".to_owned(), "environmentd".to_string()); - let annotations = btreemap! { + let mut pod_template_annotations = btreemap! { // We can re-enable eviction once we have HA "cluster-autoscaler.kubernetes.io/safe-to-evict".to_owned() => "false".to_string(), @@ -1146,6 +1189,15 @@ fn create_environmentd_statefulset_object( "karpenter.sh/do-not-disrupt".to_owned() => "true".to_string(), "materialize.cloud/generation".to_owned() => generation.to_string(), }; + if config.enable_prometheus_scrape_annotations { + pod_template_annotations.insert("prometheus.io/scrape".to_owned(), "true".to_string()); + pod_template_annotations.insert( + "prometheus.io/port".to_owned(), + config.environmentd_internal_http_port.to_string(), + ); + pod_template_annotations.insert("prometheus.io/path".to_owned(), "/metrics".to_string()); + pod_template_annotations.insert("prometheus.io/scheme".to_owned(), "http".to_string()); + } let tolerations = Some(vec![ // When the node becomes `NotReady` it indicates there is a problem with the node, @@ -1172,7 +1224,7 @@ fn create_environmentd_statefulset_object( // by the statefulset, not the materialize instance metadata: Some(ObjectMeta { labels: Some(pod_template_labels), - annotations: Some(annotations), // This is inserted into later, do not delete. + annotations: Some(pod_template_annotations), // This is inserted into later, do not delete. ..Default::default() }), spec: Some(PodSpec { @@ -1186,7 +1238,7 @@ fn create_environmentd_statefulset_object( ), scheduler_name: config.scheduler_name.clone(), service_account_name: Some(mz.service_account_name()), - volumes, + volumes: Some(volumes), security_context: Some(PodSecurityContext { fs_group: Some(999), run_as_user: Some(999), @@ -1253,6 +1305,20 @@ fn create_environmentd_statefulset_object( } } +fn create_balancerd_external_certificate( + config: &super::Args, + mz: &Materialize, +) -> Option { + create_certificate( + config.default_certificate_specs.balancerd_external.clone(), + mz, + mz.spec.balancerd_external_certificate_spec.clone(), + mz.balancerd_external_certificate_name(), + mz.balancerd_external_certificate_secret_name(), + None, + ) +} + fn create_balancerd_deployment_object(config: &super::Args, mz: &Materialize) -> Deployment { let security_context = if config.enable_security_context { // Since we want to adhere to the most restrictive security context, all @@ -1275,6 +1341,16 @@ fn create_balancerd_deployment_object(config: &super::Args, mz: &Materialize) -> None }; + let pod_template_annotations = if config.enable_prometheus_scrape_annotations { + Some(btreemap! { + "prometheus.io/scrape".to_owned() => "true".to_string(), + "prometheus.io/port".to_owned() => config.balancerd_internal_http_port.to_string(), + "prometheus.io/path".to_owned() => "/metrics".to_string(), + "prometheus.io/scheme".to_owned() => "http".to_string(), + }) + } else { + None + }; let mut pod_template_labels = mz.default_labels(); pod_template_labels.insert( "materialize.cloud/name".to_owned(), @@ -1316,18 +1392,50 @@ fn create_balancerd_deployment_object(config: &super::Args, mz: &Materialize) -> "--https-resolver-template={}.{}.svc.cluster.local:{}", mz.environmentd_service_name(), mz.namespace(), - config.environmentd_balancer_http_port + config.environmentd_http_port ), format!( "--static-resolver-addr={}.{}.svc.cluster.local:{}", mz.environmentd_service_name(), mz.namespace(), - config.environmentd_balancer_sql_port + config.environmentd_sql_port ), ]; - if config.enable_tls { - unimplemented!(); + if issuer_ref_defined( + &config.default_certificate_specs.internal, + &mz.spec.internal_certificate_spec, + ) { + args.push("--internal-tls".to_owned()) + } + + let mut volumes = Vec::new(); + let mut volume_mounts = Vec::new(); + if issuer_ref_defined( + &config.default_certificate_specs.balancerd_external, + &mz.spec.balancerd_external_certificate_spec, + ) { + volumes.push(Volume { + name: "external-certificate".to_owned(), + secret: Some(SecretVolumeSource { + default_mode: Some(0o400), + secret_name: Some(mz.balancerd_external_certificate_secret_name()), + items: None, + optional: Some(false), + }), + ..Default::default() + }); + volume_mounts.push(VolumeMount { + name: "external-certificate".to_owned(), + mount_path: "/etc/external_tls".to_owned(), + read_only: Some(true), + ..Default::default() + }); + args.extend([ + "--tls-mode=require".into(), + "--tls-cert=/etc/external_tls/tls.crt".into(), + "--tls-key=/etc/external_tls/tls.key".into(), + ]); } else { args.push("--tls-mode=disable".to_string()); } @@ -1386,6 +1494,7 @@ fn create_balancerd_deployment_object(config: &super::Args, mz: &Materialize) -> liveness_probe: Some(liveness_probe), resources: mz.spec.balancerd_resource_requirements.clone(), security_context: security_context.clone(), + volume_mounts: Some(volume_mounts), ..Default::default() }; @@ -1409,6 +1518,7 @@ fn create_balancerd_deployment_object(config: &super::Args, mz: &Materialize) -> // not using managed_resource_meta because the pod should be // owned by the deployment, not the materialize instance metadata: Some(ObjectMeta { + annotations: pod_template_annotations, labels: Some(pod_template_labels), ..Default::default() }), @@ -1429,6 +1539,7 @@ fn create_balancerd_deployment_object(config: &super::Args, mz: &Materialize) -> }), scheduler_name: config.scheduler_name.clone(), service_account_name: Some(mz.service_account_name()), + volumes: Some(volumes), ..Default::default() }), }, diff --git a/src/orchestratord/src/controller/materialize/tls.rs b/src/orchestratord/src/controller/materialize/tls.rs new file mode 100644 index 0000000000000..406b7250442eb --- /dev/null +++ b/src/orchestratord/src/controller/materialize/tls.rs @@ -0,0 +1,81 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +use mz_cloud_resources::crd::gen::cert_manager::certificates::{ + Certificate, CertificatePrivateKey, CertificatePrivateKeyAlgorithm, + CertificatePrivateKeyEncoding, CertificatePrivateKeyRotationPolicy, CertificateSpec, +}; +use mz_cloud_resources::crd::materialize::v1alpha1::{Materialize, MaterializeCertSpec}; + +pub fn create_certificate( + default_spec: Option, + mz: &Materialize, + mz_cert_spec: Option, + cert_name: String, + secret_name: String, + additional_dns_names: Option>, +) -> Option { + let default_spec = default_spec.unwrap_or_else(MaterializeCertSpec::default); + let mz_cert_spec = mz_cert_spec.unwrap_or_else(MaterializeCertSpec::default); + let Some(issuer_ref) = mz_cert_spec.issuer_ref.or(default_spec.issuer_ref) else { + return None; + }; + let mut secret_template = mz_cert_spec + .secret_template + .or(default_spec.secret_template) + .unwrap_or_default(); + secret_template.labels = Some( + secret_template + .labels + .unwrap_or_default() + .into_iter() + .chain(mz.default_labels()) + .collect(), + ); + let mut dns_names = mz_cert_spec + .dns_names + .or(default_spec.dns_names) + .unwrap_or_default(); + if let Some(names) = additional_dns_names { + dns_names.extend(names); + } + Some(Certificate { + metadata: mz.managed_resource_meta(cert_name), + spec: CertificateSpec { + dns_names: Some(dns_names), + duration: mz_cert_spec.duration.or(default_spec.duration), + issuer_ref, + private_key: Some(CertificatePrivateKey { + algorithm: Some(CertificatePrivateKeyAlgorithm::Ed25519), + encoding: Some(CertificatePrivateKeyEncoding::Pkcs8), + rotation_policy: Some(CertificatePrivateKeyRotationPolicy::Always), + size: None, + }), + renew_before: mz_cert_spec.renew_before.or(default_spec.renew_before), + secret_name, + secret_template: Some(secret_template), + ..Default::default() + }, + status: None, + }) +} + +pub fn issuer_ref_defined( + defaults: &Option, + overrides: &Option, +) -> bool { + overrides + .as_ref() + .and_then(|spec| spec.issuer_ref.as_ref()) + .is_some() + || defaults + .as_ref() + .and_then(|spec| spec.issuer_ref.as_ref()) + .is_some() +} diff --git a/src/persist-client/BUILD.bazel b/src/persist-client/BUILD.bazel index 17f63e25cb8bb..678f21d890ad5 100644 --- a/src/persist-client/BUILD.bazel +++ b/src/persist-client/BUILD.bazel @@ -29,7 +29,7 @@ rust_library( proc_macro_deps = ["//src/persist-proc:mz_persist_proc"] + all_crate_deps(proc_macro = True), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ ":mz_persist_client_build_script", "//src/build-info:mz_build_info", @@ -68,7 +68,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/build-info:mz_build_info", "//src/dyncfg:mz_dyncfg", diff --git a/src/persist-client/Cargo.toml b/src/persist-client/Cargo.toml index 784545f7ffed1..0baee83bb7a11 100644 --- a/src/persist-client/Cargo.toml +++ b/src/persist-client/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mz-persist-client" description = "Client for Materialize pTVC durability system" -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" edition.workspace = true rust-version.workspace = true publish = false diff --git a/src/persist-client/src/batch.rs b/src/persist-client/src/batch.rs index 6d8d56de8cc70..36c388cbde285 100644 --- a/src/persist-client/src/batch.rs +++ b/src/persist-client/src/batch.rs @@ -1098,6 +1098,11 @@ impl BatchParts { let blob = Arc::clone(&blob); let metrics = Arc::clone(&metrics); let writer_key = cfg.writer_key.clone(); + // Don't spill "unordered" runs to S3, since we'll split them up into many single-element + // runs below. + let run_length_limit = (order == RunOrder::Unordered) + .then_some(usize::MAX) + .unwrap_or(cfg.run_length_limit); let merge_fn = move |parts| { let blob = Arc::clone(&blob); let writer_key = writer_key.clone(); @@ -1124,7 +1129,7 @@ impl BatchParts { ); Pending::new(handle) }; - WritingRuns::Ordered(order, MergeTree::new(cfg.run_length_limit, merge_fn)) + WritingRuns::Ordered(order, MergeTree::new(run_length_limit, merge_fn)) }; BatchParts { cfg, diff --git a/src/persist-client/src/internal/compact.rs b/src/persist-client/src/internal/compact.rs index da9cf687d2a89..455566e941e5d 100644 --- a/src/persist-client/src/internal/compact.rs +++ b/src/persist-client/src/internal/compact.rs @@ -7,9 +7,11 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0. +use std::borrow::Cow; use std::collections::VecDeque; use std::fmt::Debug; use std::marker::PhantomData; +use std::pin::pin; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -17,7 +19,7 @@ use anyhow::anyhow; use differential_dataflow::difference::Semigroup; use differential_dataflow::lattice::Lattice; use differential_dataflow::trace::Description; -use futures_util::TryFutureExt; +use futures_util::{StreamExt, TryFutureExt}; use mz_dyncfg::Config; use mz_ore::cast::CastFrom; use mz_ore::error::ErrorExt; @@ -505,8 +507,10 @@ where let mut all_run_meta = vec![]; let mut len = 0; + let ordered_runs = + Self::order_runs(&req, cfg.batch.preferred_order, &*blob, &*metrics).await?; for (runs, run_chunk_max_memory_usage) in - Self::chunk_runs(&req, &cfg, metrics.as_ref(), run_reserved_memory_bytes) + Self::chunk_runs(&ordered_runs, &cfg, &*metrics, run_reserved_memory_bytes) { metrics.compaction.chunks_compacted.inc(); metrics @@ -587,7 +591,7 @@ where /// were written with a different target size than this build. Uses [Self::order_runs] to /// determine the order in which runs are selected. fn chunk_runs<'a>( - req: &'a CompactReq, + ordered_runs: &'a [(&'a Description, &'a RunMeta, Cow<'a, [RunPart]>)], cfg: &CompactConfig, metrics: &Metrics, run_reserved_memory_bytes: usize, @@ -595,8 +599,7 @@ where Vec<(&'a Description, &'a RunMeta, &'a [RunPart])>, usize, )> { - let ordered_runs = Self::order_runs(req, cfg.batch.preferred_order); - let mut ordered_runs = ordered_runs.iter().peekable(); + let mut ordered_runs = ordered_runs.into_iter().peekable(); let mut chunks = vec![]; let mut current_chunk = vec![]; @@ -607,7 +610,7 @@ where .map(|x| x.max_part_bytes()) .max() .unwrap_or(cfg.batch.blob_target_size); - current_chunk.push((*desc, *meta, *run)); + current_chunk.push((*desc, *meta, &**run)); current_chunk_max_memory_usage += run_greatest_part_size; if let Some((_next_desc, _next_meta, next_run)) = ordered_runs.peek() { @@ -664,10 +667,12 @@ where /// b1 runs=[C] output=[A, C, D, B, E, F] /// b2 runs=[D, E, F] /// ``` - fn order_runs( - req: &CompactReq, + async fn order_runs<'a>( + req: &'a CompactReq, target_order: RunOrder, - ) -> Vec<(&Description, &RunMeta, &[RunPart])> { + blob: &'a dyn Blob, + metrics: &'a Metrics, + ) -> anyhow::Result, &'a RunMeta, Cow<'a, [RunPart]>)>> { let total_number_of_runs = req .inputs .iter() @@ -686,26 +691,33 @@ where if let Some((meta, run)) = runs.next() { let same_order = meta.order.unwrap_or(RunOrder::Codec) == target_order; if same_order { - ordered_runs.push((desc, meta, run)); + ordered_runs.push((desc, meta, Cow::Borrowed(run))); } else { - // The downstream consolidation step will handle a length-N run that's not in - // the desired order by splitting it up into N length-1 runs. This preserves + // The downstream consolidation step will handle a long run that's not in + // the desired order by splitting it up into many single-element runs. This preserves // correctness, but it means that we may end up needing to iterate through // many more parts concurrently than expected, increasing memory use. Instead, - // we break up those runs before they're grouped together to be passed to - // consolidation. + // we break up those runs into individual batch parts, fetching hollow runs as + // necessary, before they're grouped together to be passed to consolidation. // The downside is that this breaks the usual property that compaction produces // fewer runs than it takes in. This should generally be resolved by future // runs of compaction. for part in run { - ordered_runs.push((desc, meta, std::slice::from_ref(part))); + let mut batch_parts = pin!(part.part_stream(req.shard_id, blob, metrics)); + while let Some(part) = batch_parts.next().await { + ordered_runs.push(( + desc, + meta, + Cow::Owned(vec![RunPart::Single(part?.into_owned())]), + )); + } } } batch_runs.push_back((desc, runs)); } } - ordered_runs + Ok(ordered_runs) } /// Compacts runs together. If the input runs are sorted, a single run will be created as output. diff --git a/src/pgwire/src/protocol.rs b/src/pgwire/src/protocol.rs index 5828574e5561f..57a30cc12bbfc 100644 --- a/src/pgwire/src/protocol.rs +++ b/src/pgwire/src/protocol.rs @@ -421,8 +421,13 @@ struct StateMachine<'a, A> { } enum SendRowsEndedReason { - Success { rows_returned: u64 }, - Errored { error: String }, + Success { + result_size: u64, + rows_returned: u64, + }, + Errored { + error: String, + }, Canceled, } @@ -1083,21 +1088,28 @@ where Ok((ok, SendRowsEndedReason::Canceled)) => { (Ok(ok), StatementEndedExecutionReason::Canceled) } - // NOTE: For now the `rows_returned` in - // fetches is a bit confusing. We record - // `Some(n)` for the first fetch, where `n` is - // the number of rows returned by the inner + // NOTE: For now the values for `result_size` and + // `rows_returned` in fetches are a bit confusing. + // We record `Some(n)` for the first fetch, where `n` is + // the number of bytes/rows returned by the inner // execute (regardless of how many rows the - // fetch fetched) , and `None` for subsequent fetches. + // fetch fetched), and `None` for subsequent fetches. // - // This arguably makes sense since the rows + // This arguably makes sense since the size/rows // returned measures how much work the compute // layer had to do to satisfy the query, but // we should revisit it if/when we start // logging the inner execute separately. - Ok((ok, SendRowsEndedReason::Success { rows_returned: _ })) => ( + Ok(( + ok, + SendRowsEndedReason::Success { + result_size: _, + rows_returned: _, + }, + )) => ( Ok(ok), StatementEndedExecutionReason::Success { + result_size: None, rows_returned: None, execution_strategy: None, }, @@ -1124,6 +1136,7 @@ where self.adapter_client.retire_execute( outer_ctx_extra, StatementEndedExecutionReason::Success { + result_size: None, rows_returned: None, execution_strategy: None, }, @@ -1577,9 +1590,16 @@ where Ok((ok, SendRowsEndedReason::Canceled)) => { (Ok(ok), StatementEndedExecutionReason::Canceled) } - Ok((ok, SendRowsEndedReason::Success { rows_returned })) => ( + Ok(( + ok, + SendRowsEndedReason::Success { + result_size, + rows_returned, + }, + )) => ( Ok(ok), StatementEndedExecutionReason::Success { + result_size: Some(result_size), rows_returned: Some(rows_returned), execution_strategy: None, }, @@ -1620,9 +1640,16 @@ where // We consider that to be a cancelation, rather than a query error. (Err(e), StatementEndedExecutionReason::Canceled) } - Ok((state, SendRowsEndedReason::Success { rows_returned })) => ( + Ok(( + state, + SendRowsEndedReason::Success { + result_size, + rows_returned, + }, + )) => ( Ok(state), StatementEndedExecutionReason::Success { + result_size: Some(result_size), rows_returned: Some(rows_returned), execution_strategy: None, }, @@ -1829,6 +1856,7 @@ where ); let mut total_sent_rows = 0; + let mut total_sent_bytes = 0; // want_rows is the maximum number of rows the client wants. let mut want_rows = match max_rows { ExecuteCount::All => usize::MAX, @@ -1881,16 +1909,27 @@ where // Send a portion of the rows. let mut sent_rows = 0; + let mut sent_bytes = 0; let messages = (&mut batch_rows) + // TODO(parkmycar): This is a fair bit of juggling between iterator types + // to count the total number of bytes. Alternatively we could track the + // total sent bytes in this .map(...) call, but having side effects in map + // is a code smell. .map(|row| { + let row_len = row.byte_len(); let values = mz_pgrepr::values_from_row(row, row_desc.typ()); - BackendMessage::DataRow(values) + (row_len, BackendMessage::DataRow(values)) + }) + .inspect(|(row_len, _)| { + sent_bytes += row_len; + sent_rows += 1 }) - .inspect(|_| sent_rows += 1) + .map(|(_row_len, row)| row) .take(want_rows); self.send_all(messages).await?; total_sent_rows += sent_rows; + total_sent_bytes += sent_bytes; want_rows -= sent_rows; // If we have sent the number of requested rows, put the remainder of the batch @@ -1947,6 +1986,7 @@ where Ok(( State::Ready, SendRowsEndedReason::Success { + result_size: u64::cast_from(total_sent_bytes), rows_returned: u64::cast_from(total_sent_rows), }, )) @@ -2008,6 +2048,7 @@ where } let mut count = 0; + let mut total_sent_bytes = 0; loop { tokio::select! { e = self.conn.wait_closed() => return Err(e), @@ -2029,6 +2070,7 @@ where Some(PeekResponseUnary::Rows(mut rows)) => { count += rows.count(); while let Some(row) = rows.next() { + total_sent_bytes += row.byte_len(); encode_fn(row, typ, &mut out)?; self.send(BackendMessage::CopyData(mem::take(&mut out))) .await?; @@ -2058,6 +2100,7 @@ where Ok(( State::Ready, SendRowsEndedReason::Success { + result_size: u64::cast_from(total_sent_bytes), rows_returned: u64::cast_from(count), }, )) diff --git a/src/repr/src/catalog_item_id.proto b/src/repr/src/catalog_item_id.proto index 4ab5dbde85e51..4645c37c05789 100644 --- a/src/repr/src/catalog_item_id.proto +++ b/src/repr/src/catalog_item_id.proto @@ -18,5 +18,6 @@ message ProtoCatalogItemId { uint64 system = 1; uint64 user = 2; uint64 transient = 3; + uint64 introspection_source_index = 4; } } diff --git a/src/repr/src/catalog_item_id.rs b/src/repr/src/catalog_item_id.rs index 0f99b3d92b749..0fc64304b9338 100644 --- a/src/repr/src/catalog_item_id.rs +++ b/src/repr/src/catalog_item_id.rs @@ -36,6 +36,8 @@ include!(concat!(env!("OUT_DIR"), "/mz_repr.catalog_item_id.rs")); pub enum CatalogItemId { /// System namespace. System(u64), + /// Introspection Source Index namespace. + IntrospectionSourceIndex(u64), /// User namespace. User(u64), /// Transient item. @@ -45,7 +47,10 @@ pub enum CatalogItemId { impl CatalogItemId { /// Reports whether this ID is in the system namespace. pub fn is_system(&self) -> bool { - matches!(self, CatalogItemId::System(_)) + matches!( + self, + CatalogItemId::System(_) | CatalogItemId::IntrospectionSourceIndex(_) + ) } /// Reports whether this ID is in the user namespace. @@ -62,17 +67,27 @@ impl CatalogItemId { impl FromStr for CatalogItemId { type Err = Error; - fn from_str(s: &str) -> Result { + fn from_str(mut s: &str) -> Result { if s.len() < 2 { return Err(anyhow!("couldn't parse id {}", s)); } - let val: u64 = s[1..].parse()?; - match s.chars().next().unwrap() { - 's' => Ok(CatalogItemId::System(val)), - 'u' => Ok(CatalogItemId::User(val)), - 't' => Ok(CatalogItemId::Transient(val)), - _ => Err(anyhow!("couldn't parse id {}", s)), - } + let tag = s.chars().next().unwrap(); + s = &s[1..]; + let variant = match tag { + 's' => { + if Some('i') == s.chars().next() { + s = &s[1..]; + CatalogItemId::IntrospectionSourceIndex + } else { + CatalogItemId::System + } + } + 'u' => CatalogItemId::User, + 't' => CatalogItemId::Transient, + _ => return Err(anyhow!("couldn't parse id {}", s)), + }; + let val: u64 = s.parse()?; + Ok(variant(val)) } } @@ -80,6 +95,7 @@ impl fmt::Display for CatalogItemId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { CatalogItemId::System(id) => write!(f, "s{}", id), + CatalogItemId::IntrospectionSourceIndex(id) => write!(f, "si{}", id), CatalogItemId::User(id) => write!(f, "u{}", id), CatalogItemId::Transient(id) => write!(f, "t{}", id), } @@ -92,6 +108,7 @@ impl RustType for CatalogItemId { ProtoCatalogItemId { kind: Some(match self { CatalogItemId::System(x) => System(*x), + CatalogItemId::IntrospectionSourceIndex(x) => IntrospectionSourceIndex(*x), CatalogItemId::User(x) => User(*x), CatalogItemId::Transient(x) => Transient(*x), }), @@ -102,6 +119,7 @@ impl RustType for CatalogItemId { use proto_catalog_item_id::Kind::*; match proto.kind { Some(System(x)) => Ok(CatalogItemId::System(x)), + Some(IntrospectionSourceIndex(x)) => Ok(CatalogItemId::IntrospectionSourceIndex(x)), Some(User(x)) => Ok(CatalogItemId::User(x)), Some(Transient(x)) => Ok(CatalogItemId::Transient(x)), None => Err(TryFromProtoError::missing_field("ProtoCatalogItemId::kind")), @@ -111,12 +129,9 @@ impl RustType for CatalogItemId { #[cfg(test)] mod tests { - use mz_proto::ProtoType; use proptest::prelude::*; - use prost::Message; use super::*; - use crate::GlobalId; #[mz_ore::test] fn proptest_catalog_item_id_roundtrips() { @@ -130,31 +145,4 @@ mod tests { testcase(id); }) } - - #[mz_ore::test] - fn proptest_catalog_item_id_global_id_wire_compat() { - fn testcase(og: GlobalId) { - let bytes = og.into_proto().encode_to_vec(); - let proto = ProtoCatalogItemId::decode(&bytes[..]).expect("valid"); - let rnd = proto.into_rust().expect("valid proto"); - - match (og, rnd) { - (GlobalId::User(x), CatalogItemId::User(y)) => assert_eq!(x, y), - (GlobalId::System(x), CatalogItemId::System(y)) => assert_eq!(x, y), - (GlobalId::Transient(x), CatalogItemId::Transient(y)) => assert_eq!(x, y), - (gid, item) => panic!("{gid:?} turned into {item:?}"), - } - } - - let strat = proptest::arbitrary::any::().prop_flat_map(|inner| { - proptest::strategy::Union::new(vec![ - Just(GlobalId::User(inner)), - Just(GlobalId::System(inner)), - Just(GlobalId::Transient(inner)), - ]) - }); - proptest!(|(id in strat)| { - testcase(id) - }); - } } diff --git a/src/repr/src/explain.rs b/src/repr/src/explain.rs index c209cca4bec5a..39fe0f75346c3 100644 --- a/src/repr/src/explain.rs +++ b/src/repr/src/explain.rs @@ -154,22 +154,34 @@ impl From for ExplainError { /// A set of options for controlling the output of [`Explain`] implementations. #[derive(Clone, Debug)] pub struct ExplainConfig { - /// Show the number of columns. + // Analyses: + // (These are shown only if the Analysis is supported by the backing IR.) + /// Show the `SubtreeSize` Analysis in the explanation. + pub subtree_size: bool, + /// Show the number of columns, i.e., the `Arity` Analysis. pub arity: bool, - /// Show cardinality information. + /// Show the types, i.e., the `RelationType` Analysis. + pub types: bool, + /// Show the sets of unique keys, i.e., the `UniqueKeys` Analysis. + pub keys: bool, + /// Show the `NonNegative` Analysis. + pub non_negative: bool, + /// Show the `Cardinality` Analysis. pub cardinality: bool, - /// Show inferred column names. + /// Show the `ColumnNames` Analysis. pub column_names: bool, + /// Show the `Equivalences` Analysis. + pub equivalences: bool, + // TODO: add an option to show the `Monotonic` Analysis. This is non-trivial, because this + // Analysis needs the set of monotonic GlobalIds, which are cumbersome to pass around. + + // Other display options: /// Render implemented MIR `Join` nodes in a way which reflects the implementation. pub join_impls: bool, /// Use inferred column names when rendering scalar and aggregate expressions. pub humanized_exprs: bool, - /// Show the sets of unique keys. - pub keys: bool, /// Restrict output trees to linear chains. Ignored if `raw_plans` is set. pub linear_chains: bool, - /// Show the `non_negative` in the explanation if it is supported by the backing IR. - pub non_negative: bool, /// Show the slow path plan even if a fast path plan was created. Useful for debugging. /// Enforced if `timing` is set. pub no_fast_path: bool, @@ -183,14 +195,11 @@ pub struct ExplainConfig { pub raw_syntax: bool, /// Anonymize literals in the plan. pub redacted: bool, - /// Show the `subtree_size` attribute in the explanation if it is supported by the backing IR. - pub subtree_size: bool, /// Print optimization timings. pub timing: bool, - /// Show the `type` attribute in the explanation. - pub types: bool, /// Show MFP pushdown information. pub filter_pushdown: bool, + /// Optimizer feature flags. pub features: OptimizerFeatureOverrides, } @@ -217,13 +226,14 @@ impl Default for ExplainConfig { subtree_size: false, timing: false, types: false, + equivalences: false, features: Default::default(), } } } impl ExplainConfig { - pub fn requires_attributes(&self) -> bool { + pub fn requires_analyses(&self) -> bool { self.subtree_size || self.non_negative || self.arity @@ -231,6 +241,7 @@ impl ExplainConfig { || self.keys || self.cardinality || self.column_names + || self.equivalences } } @@ -378,7 +389,7 @@ impl<'a> AsRef<&'a dyn ExprHumanizer> for RenderingContext<'a> { pub struct PlanRenderingContext<'a, T> { pub indent: Indent, pub humanizer: &'a dyn ExprHumanizer, - pub annotations: BTreeMap<&'a T, Attributes>, + pub annotations: BTreeMap<&'a T, Analyses>, pub config: &'a ExplainConfig, } @@ -386,7 +397,7 @@ impl<'a, T> PlanRenderingContext<'a, T> { pub fn new( indent: Indent, humanizer: &'a dyn ExprHumanizer, - annotations: BTreeMap<&'a T, Attributes>, + annotations: BTreeMap<&'a T, Analyses>, config: &'a ExplainConfig, ) -> PlanRenderingContext<'a, T> { PlanRenderingContext { @@ -607,16 +618,16 @@ pub trait ScalarOps { } /// A somewhat ad-hoc way to keep carry a plan with a set -/// of attributes derived for each node in that plan. +/// of analyses derived for each node in that plan. #[allow(missing_debug_implementations)] pub struct AnnotatedPlan<'a, T> { pub plan: &'a T, - pub annotations: BTreeMap<&'a T, Attributes>, + pub annotations: BTreeMap<&'a T, Analyses>, } -/// A container for derived attributes. +/// A container for derived analyses. #[derive(Clone, Default, Debug)] -pub struct Attributes { +pub struct Analyses { pub non_negative: Option, pub subtree_size: Option, pub arity: Option, @@ -624,50 +635,51 @@ pub struct Attributes { pub keys: Option>>, pub cardinality: Option, pub column_names: Option>, + pub equivalences: Option, } #[derive(Debug, Clone)] -pub struct HumanizedAttributes<'a> { - attrs: &'a Attributes, +pub struct HumanizedAnalyses<'a> { + analyses: &'a Analyses, humanizer: &'a dyn ExprHumanizer, config: &'a ExplainConfig, } -impl<'a> HumanizedAttributes<'a> { - pub fn new(attrs: &'a Attributes, ctx: &PlanRenderingContext<'a, T>) -> Self { +impl<'a> HumanizedAnalyses<'a> { + pub fn new(analyses: &'a Analyses, ctx: &PlanRenderingContext<'a, T>) -> Self { Self { - attrs, + analyses, humanizer: ctx.humanizer, config: ctx.config, } } } -impl<'a> fmt::Display for HumanizedAttributes<'a> { - // Attribute rendering is guarded by the ExplainConfig flag for each - // attribute. This is needed because we might have derived attributes that +impl<'a> Display for HumanizedAnalyses<'a> { + // Analysis rendering is guarded by the ExplainConfig flag for each + // Analysis. This is needed because we might have derived Analysis that // are not explicitly requested (such as column_names), in which case we // don't want to display them. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut builder = f.debug_struct("//"); if self.config.subtree_size { - let subtree_size = self.attrs.subtree_size.expect("subtree_size"); + let subtree_size = self.analyses.subtree_size.expect("subtree_size"); builder.field("subtree_size", &subtree_size); } if self.config.non_negative { - let non_negative = self.attrs.non_negative.expect("non_negative"); + let non_negative = self.analyses.non_negative.expect("non_negative"); builder.field("non_negative", &non_negative); } if self.config.arity { - let arity = self.attrs.arity.expect("arity"); + let arity = self.analyses.arity.expect("arity"); builder.field("arity", &arity); } if self.config.types { - let types = match self.attrs.types.as_ref().expect("types") { + let types = match self.analyses.types.as_ref().expect("types") { Some(types) => { let types = types .into_iter() @@ -683,7 +695,7 @@ impl<'a> fmt::Display for HumanizedAttributes<'a> { if self.config.keys { let keys = self - .attrs + .analyses .keys .as_ref() .expect("keys") @@ -694,12 +706,12 @@ impl<'a> fmt::Display for HumanizedAttributes<'a> { } if self.config.cardinality { - let cardinality = self.attrs.cardinality.as_ref().expect("cardinality"); + let cardinality = self.analyses.cardinality.as_ref().expect("cardinality"); builder.field("cardinality", cardinality); } if self.config.column_names { - let column_names = self.attrs.column_names.as_ref().expect("column_names"); + let column_names = self.analyses.column_names.as_ref().expect("column_names"); let column_names = column_names.into_iter().enumerate().map(|(i, c)| { if c.is_empty() { Cow::Owned(format!("#{i}")) @@ -711,6 +723,11 @@ impl<'a> fmt::Display for HumanizedAttributes<'a> { builder.field("column_names", &column_names); } + if self.config.equivalences { + let equivs = self.analyses.equivalences.as_ref().expect("equivalences"); + builder.field("equivs", equivs); + } + builder.finish() } } @@ -933,6 +950,7 @@ mod tests { raw_plans: false, raw_syntax: false, subtree_size: false, + equivalences: false, timing: true, types: false, features: Default::default(), diff --git a/src/repr/src/global_id.proto b/src/repr/src/global_id.proto index 8c9731dafcde6..2cd66b23a4e8b 100644 --- a/src/repr/src/global_id.proto +++ b/src/repr/src/global_id.proto @@ -19,5 +19,6 @@ message ProtoGlobalId { uint64 user = 2; uint64 transient = 3; google.protobuf.Empty explain = 4; + uint64 introspection_source_index = 5; } } diff --git a/src/repr/src/global_id.rs b/src/repr/src/global_id.rs index 5fc60312fdd21..b570a245b549f 100644 --- a/src/repr/src/global_id.rs +++ b/src/repr/src/global_id.rs @@ -54,6 +54,8 @@ include!(concat!(env!("OUT_DIR"), "/mz_repr.global_id.rs")); pub enum GlobalId { /// System namespace. System(u64), + /// Introspection Source Index namespace. + IntrospectionSourceIndex(u64), /// User namespace. User(u64), /// Transient namespace. @@ -62,10 +64,17 @@ pub enum GlobalId { Explain, } +// `GlobalId`s are serialized often, so it would be nice to try and keep them small. If this assert +// fails, then there isn't any correctness issues just potential performance issues. +static_assertions::assert_eq_size!(GlobalId, [u8; 16]); + impl GlobalId { /// Reports whether this ID is in the system namespace. pub fn is_system(&self) -> bool { - matches!(self, GlobalId::System(_)) + matches!( + self, + GlobalId::System(_) | GlobalId::IntrospectionSourceIndex(_) + ) } /// Reports whether this ID is in the user namespace. @@ -82,20 +91,30 @@ impl GlobalId { impl FromStr for GlobalId { type Err = Error; - fn from_str(s: &str) -> Result { + fn from_str(mut s: &str) -> Result { if s.len() < 2 { return Err(anyhow!("couldn't parse id {}", s)); } if s == "Explained Query" { return Ok(GlobalId::Explain); } - let val: u64 = s[1..].parse()?; - match s.chars().next().unwrap() { - 's' => Ok(GlobalId::System(val)), - 'u' => Ok(GlobalId::User(val)), - 't' => Ok(GlobalId::Transient(val)), - _ => Err(anyhow!("couldn't parse id {}", s)), - } + let tag = s.chars().next().unwrap(); + s = &s[1..]; + let variant = match tag { + 's' => { + if Some('i') == s.chars().next() { + s = &s[1..]; + GlobalId::IntrospectionSourceIndex + } else { + GlobalId::System + } + } + 'u' => GlobalId::User, + 't' => GlobalId::Transient, + _ => return Err(anyhow!("couldn't parse id {}", s)), + }; + let val: u64 = s.parse()?; + Ok(variant(val)) } } @@ -103,6 +122,7 @@ impl fmt::Display for GlobalId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { GlobalId::System(id) => write!(f, "s{}", id), + GlobalId::IntrospectionSourceIndex(id) => write!(f, "si{}", id), GlobalId::User(id) => write!(f, "u{}", id), GlobalId::Transient(id) => write!(f, "t{}", id), GlobalId::Explain => write!(f, "Explained Query"), @@ -116,6 +136,7 @@ impl RustType for GlobalId { ProtoGlobalId { kind: Some(match self { GlobalId::System(x) => System(*x), + GlobalId::IntrospectionSourceIndex(x) => IntrospectionSourceIndex(*x), GlobalId::User(x) => User(*x), GlobalId::Transient(x) => Transient(*x), GlobalId::Explain => Explain(()), @@ -127,6 +148,7 @@ impl RustType for GlobalId { use proto_global_id::Kind::*; match proto.kind { Some(System(x)) => Ok(GlobalId::System(x)), + Some(IntrospectionSourceIndex(x)) => Ok(GlobalId::IntrospectionSourceIndex(x)), Some(User(x)) => Ok(GlobalId::User(x)), Some(Transient(x)) => Ok(GlobalId::Transient(x)), Some(Explain(_)) => Ok(GlobalId::Explain), diff --git a/src/repr/src/optimize.rs b/src/repr/src/optimize.rs index 97c9c4f82f44e..364cfdd502e1c 100644 --- a/src/repr/src/optimize.rs +++ b/src/repr/src/optimize.rs @@ -116,12 +116,6 @@ optimizer_feature_flags!({ // Reoptimize imported views when building and optimizing a // `DataflowDescription` in the global MIR optimization phase. reoptimize_imported_views: bool, - // Enables the value window function fusion optimization. - enable_value_window_function_fusion: bool, - // See the feature flag of the same name. - enable_reduce_unnest_list_fusion: bool, - // See the feature flag of the same name. - enable_window_aggregation_fusion: bool, // See the feature flag of the same name. enable_reduce_reduction: bool, }); diff --git a/src/repr/src/row.rs b/src/repr/src/row.rs index fc130574d92d9..127d7609230e1 100644 --- a/src/repr/src/row.rs +++ b/src/repr/src/row.rs @@ -457,6 +457,11 @@ impl RowRef { } } + /// Return the byte length of this [`RowRef`]. + pub fn byte_len(&self) -> usize { + self.0.len() + } + /// For debugging only. pub fn data(&self) -> &[u8] { &self.0 diff --git a/src/repr/src/row/iter.rs b/src/repr/src/row/iter.rs index 9b84902945ed5..762c5f04811b6 100644 --- a/src/repr/src/row/iter.rs +++ b/src/repr/src/row/iter.rs @@ -10,6 +10,7 @@ //! Defines a "lending iterator" for [`Row`] use std::fmt::Debug; +use std::sync::Arc; use crate::row::{Row, RowRef}; @@ -41,6 +42,9 @@ pub trait RowIterator: Debug { /// will not change the value returned from this method. fn count(&self) -> usize; + /// Returns a clone of `self` as a `Box`. + fn box_clone(&self) -> Box; + /// Maps the returned [`RowRef`]s from this [`RowIterator`]. fn map(self, f: F) -> MappedRowIterator where @@ -66,6 +70,10 @@ impl RowIterator for Box { fn count(&self) -> usize { (**self).count() } + + fn box_clone(&self) -> Box { + (**self).box_clone() + } } impl RowIterator for &mut I { @@ -80,6 +88,10 @@ impl RowIterator for &mut I { fn count(&self) -> usize { (**self).count() } + + fn box_clone(&self) -> Box { + (**self).box_clone() + } } #[derive(Debug)] @@ -114,9 +126,9 @@ impl IntoRowIterator for T { } /// A [`RowIterator`] for a single [`Row`]. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SingleRowIter { - row: Row, + row: Arc, finished: bool, } @@ -141,6 +153,10 @@ impl RowIterator for SingleRowIter { fn count(&self) -> usize { 1 } + + fn box_clone(&self) -> Box { + Box::new(self.clone()) + } } impl IntoRowIterator for Row { @@ -148,7 +164,7 @@ impl IntoRowIterator for Row { fn into_row_iter(self) -> Self::Iter { SingleRowIter { - row: self, + row: Arc::new(self), finished: false, } } @@ -157,7 +173,7 @@ impl IntoRowIterator for Row { /// A [`RowIterator`] for a [`Vec`] of [`Row`]s. #[derive(Debug, Clone)] pub struct VecRowIter { - rows: Vec, + rows: Arc<[Row]>, index: usize, } @@ -176,6 +192,10 @@ impl RowIterator for VecRowIter { fn count(&self) -> usize { self.rows.len() } + + fn box_clone(&self) -> Box { + Box::new(self.clone()) + } } impl IntoRowIterator for Vec { @@ -183,7 +203,7 @@ impl IntoRowIterator for Vec { fn into_row_iter(self) -> Self::Iter { VecRowIter { - rows: self, + rows: self.into(), index: 0, } } diff --git a/src/repr/src/stats2.rs b/src/repr/src/stats2.rs index 0a0ed6ed57878..4048c93eef9e2 100644 --- a/src/repr/src/stats2.rs +++ b/src/repr/src/stats2.rs @@ -581,6 +581,7 @@ mod tests { use uuid::Uuid; #[mz_ore::test] + #[cfg_attr(miri, ignore)] // slow fn proptest_uuid_sort_order() { fn test(mut og: Vec) { let mut as_bytes: Vec<_> = og.iter().map(|u| u.as_bytes().clone()).collect(); diff --git a/src/sql-lexer/src/keywords.txt b/src/sql-lexer/src/keywords.txt index 958083e6e39e9..21d848f64464f 100644 --- a/src/sql-lexer/src/keywords.txt +++ b/src/sql-lexer/src/keywords.txt @@ -151,6 +151,7 @@ End Endpoint Enforced Envelope +Equivalences Error Errors Escape diff --git a/src/sql-parser/src/ast/defs/statement.rs b/src/sql-parser/src/ast/defs/statement.rs index aaaa352fe0bf1..4452d9f1d3543 100644 --- a/src/sql-parser/src/ast/defs/statement.rs +++ b/src/sql-parser/src/ast/defs/statement.rs @@ -2328,9 +2328,6 @@ pub enum ClusterFeatureName { EnableEagerDeltaJoins, EnableVariadicLeftJoinLowering, EnableLetrecFixpointAnalysis, - EnableValueWindowFunctionFusion, - EnableReduceUnnestListFusion, - EnableWindowAggregationFusion, } impl WithOptionName for ClusterFeatureName { @@ -2345,10 +2342,7 @@ impl WithOptionName for ClusterFeatureName { | Self::EnableNewOuterJoinLowering | Self::EnableEagerDeltaJoins | Self::EnableVariadicLeftJoinLowering - | Self::EnableLetrecFixpointAnalysis - | Self::EnableValueWindowFunctionFusion - | Self::EnableReduceUnnestListFusion - | Self::EnableWindowAggregationFusion => false, + | Self::EnableLetrecFixpointAnalysis => false, } } } @@ -3889,14 +3883,12 @@ pub enum ExplainPlanOptionName { SubtreeSize, Timing, Types, + Equivalences, ReoptimizeImportedViews, EnableNewOuterJoinLowering, EnableEagerDeltaJoins, EnableVariadicLeftJoinLowering, EnableLetrecFixpointAnalysis, - EnableValueWindowFunctionFusion, - EnableReduceUnnestListFusion, - EnableWindowAggregationFusion, } impl WithOptionName for ExplainPlanOptionName { @@ -3926,14 +3918,12 @@ impl WithOptionName for ExplainPlanOptionName { | Self::SubtreeSize | Self::Timing | Self::Types + | Self::Equivalences | Self::ReoptimizeImportedViews | Self::EnableNewOuterJoinLowering | Self::EnableEagerDeltaJoins | Self::EnableVariadicLeftJoinLowering - | Self::EnableLetrecFixpointAnalysis - | Self::EnableValueWindowFunctionFusion - | Self::EnableReduceUnnestListFusion - | Self::EnableWindowAggregationFusion => false, + | Self::EnableLetrecFixpointAnalysis => false, } } } diff --git a/src/sql-parser/tests/testdata/explain b/src/sql-parser/tests/testdata/explain index 295568931c86e..ab4a27ae87833 100644 --- a/src/sql-parser/tests/testdata/explain +++ b/src/sql-parser/tests/testdata/explain @@ -305,3 +305,10 @@ EXPLAIN FILTER PUSHDOWN FOR MATERIALIZED VIEW whatever EXPLAIN FILTER PUSHDOWN FOR MATERIALIZED VIEW whatever => ExplainPushdown(ExplainPushdownStatement { explainee: MaterializedView(Name(UnresolvedItemName([Ident("whatever")]))) }) + +parse-statement +EXPLAIN WITH (ARITY, EQUIVALENCES, HUMANIZED EXPRESSIONS) CREATE MATERIALIZED VIEW mv AS SELECT 665 +---- +EXPLAIN WITH (ARITY, EQUIVALENCES, HUMANIZED EXPRESSIONS) CREATE MATERIALIZED VIEW mv AS SELECT 665 +=> +ExplainPlan(ExplainPlanStatement { stage: None, with_options: [ExplainPlanOption { name: Arity, value: None }, ExplainPlanOption { name: Equivalences, value: None }, ExplainPlanOption { name: HumanizedExpressions, value: None }], format: None, explainee: CreateMaterializedView(CreateMaterializedViewStatement { if_exists: Error, name: UnresolvedItemName([Ident("mv")]), columns: [], in_cluster: None, query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None, with_options: [] }, false) }) diff --git a/src/sql/src/names.rs b/src/sql/src/names.rs index ca182ff120ef4..30a79ca61e6a0 100644 --- a/src/sql/src/names.rs +++ b/src/sql/src/names.rs @@ -1867,7 +1867,7 @@ impl<'a> Fold for NameResolver<'a> { ResolvedClusterName { // The ID is arbitrary here; we just need some dummy // value to return. - id: ClusterId::System(0), + id: ClusterId::system(0).expect("0 is a valid ID"), print_name: None, } } @@ -1883,7 +1883,7 @@ impl<'a> Fold for NameResolver<'a> { ResolvedClusterName { // The ID is arbitrary here; we just need some dummy // value to return. - id: ClusterId::System(0), + id: ClusterId::system(0).expect("0 is a valid ID"), print_name: None, } } @@ -2026,7 +2026,7 @@ impl<'a> Fold for NameResolver<'a> { ResolvedObjectName::ClusterReplica(ResolvedClusterReplicaName { // The ID is arbitrary here; we just need some dummy // value to return. - cluster_id: ClusterId::System(0), + cluster_id: ClusterId::system(0).expect("0 is a valid ID"), replica_id: ReplicaId::System(0), }) } diff --git a/src/sql/src/plan/lowering.rs b/src/sql/src/plan/lowering.rs index 2097509b34a3b..af1bdbac760eb 100644 --- a/src/sql/src/plan/lowering.rs +++ b/src/sql/src/plan/lowering.rs @@ -136,10 +136,6 @@ pub struct Config { pub enable_new_outer_join_lowering: bool, /// Enable outer join lowering implemented in database-issues#7561. pub enable_variadic_left_join_lowering: bool, - /// See the feature flag of the same name. - pub enable_value_window_function_fusion: bool, - /// See the feature flag of the same name. - pub enable_window_aggregation_fusion: bool, } impl From<&SystemVars> for Config { @@ -147,8 +143,6 @@ impl From<&SystemVars> for Config { Self { enable_new_outer_join_lowering: vars.enable_new_outer_join_lowering(), enable_variadic_left_join_lowering: vars.enable_variadic_left_join_lowering(), - enable_value_window_function_fusion: vars.enable_value_window_function_fusion(), - enable_window_aggregation_fusion: vars.enable_window_aggregation_fusion(), } } } diff --git a/src/sql/src/plan/statement/ddl.rs b/src/sql/src/plan/statement/ddl.rs index 0c75cf2d513aa..2bb01b9d4db6e 100644 --- a/src/sql/src/plan/statement/ddl.rs +++ b/src/sql/src/plan/statement/ddl.rs @@ -4414,10 +4414,7 @@ generate_extracted_config!( (EnableEagerDeltaJoins, Option, Default(None)), (EnableNewOuterJoinLowering, Option, Default(None)), (EnableVariadicLeftJoinLowering, Option, Default(None)), - (EnableLetrecFixpointAnalysis, Option, Default(None)), - (EnableValueWindowFunctionFusion, Option, Default(None)), - (EnableReduceUnnestListFusion, Option, Default(None)), - (EnableWindowAggregationFusion, Option, Default(None)) + (EnableLetrecFixpointAnalysis, Option, Default(None)) ); /// Convert a [`CreateClusterStatement`] into a [`Plan`]. @@ -4554,9 +4551,6 @@ pub fn plan_create_cluster_inner( enable_new_outer_join_lowering, enable_variadic_left_join_lowering, enable_letrec_fixpoint_analysis, - enable_value_window_function_fusion, - enable_reduce_unnest_list_fusion, - enable_window_aggregation_fusion, seen: _, } = ClusterFeatureExtracted::try_from(features)?; let optimizer_feature_overrides = OptimizerFeatureOverrides { @@ -4565,9 +4559,6 @@ pub fn plan_create_cluster_inner( enable_new_outer_join_lowering, enable_variadic_left_join_lowering, enable_letrec_fixpoint_analysis, - enable_value_window_function_fusion, - enable_reduce_unnest_list_fusion, - enable_window_aggregation_fusion, ..Default::default() }; @@ -4662,9 +4653,6 @@ pub fn unplan_create_cluster( enable_new_outer_join_lowering, enable_variadic_left_join_lowering, enable_letrec_fixpoint_analysis, - enable_value_window_function_fusion, - enable_reduce_unnest_list_fusion, - enable_window_aggregation_fusion, enable_reduce_reduction: _, } = optimizer_feature_overrides; let features_extracted = ClusterFeatureExtracted { @@ -4675,9 +4663,6 @@ pub fn unplan_create_cluster( enable_new_outer_join_lowering, enable_variadic_left_join_lowering, enable_letrec_fixpoint_analysis, - enable_value_window_function_fusion, - enable_reduce_unnest_list_fusion, - enable_window_aggregation_fusion, }; let features = features_extracted.into_values(scx.catalog); let availability_zones = if availability_zones.is_empty() { diff --git a/src/sql/src/plan/statement/dml.rs b/src/sql/src/plan/statement/dml.rs index affb4cb71de85..9ee68720d7a22 100644 --- a/src/sql/src/plan/statement/dml.rs +++ b/src/sql/src/plan/statement/dml.rs @@ -364,14 +364,12 @@ generate_extracted_config!( (SubtreeSize, bool, Default(false)), (Timing, bool, Default(false)), (Types, bool, Default(false)), + (Equivalences, bool, Default(false)), (ReoptimizeImportedViews, Option, Default(None)), (EnableNewOuterJoinLowering, Option, Default(None)), (EnableEagerDeltaJoins, Option, Default(None)), (EnableVariadicLeftJoinLowering, Option, Default(None)), - (EnableLetrecFixpointAnalysis, Option, Default(None)), - (EnableValueWindowFunctionFusion, Option, Default(None)), - (EnableReduceUnnestListFusion, Option, Default(None)), - (EnableWindowAggregationFusion, Option, Default(None)) + (EnableLetrecFixpointAnalysis, Option, Default(None)) ); impl TryFrom for ExplainConfig { @@ -409,6 +407,7 @@ impl TryFrom for ExplainConfig { raw_syntax: v.raw_syntax, redacted: v.redacted, subtree_size: v.subtree_size, + equivalences: v.equivalences, timing: v.timing, types: v.types, // The ones that are initialized with `Default::default()` are not wired up to EXPLAIN. @@ -422,9 +421,6 @@ impl TryFrom for ExplainConfig { enable_cardinality_estimates: Default::default(), persist_fast_path_limit: Default::default(), reoptimize_imported_views: v.reoptimize_imported_views, - enable_value_window_function_fusion: v.enable_value_window_function_fusion, - enable_reduce_unnest_list_fusion: v.enable_reduce_unnest_list_fusion, - enable_window_aggregation_fusion: v.enable_window_aggregation_fusion, enable_reduce_reduction: Default::default(), }, }) diff --git a/src/sql/src/plan/transform_expr.rs b/src/sql/src/plan/transform_expr.rs index 1f047f18a0e0b..524b49e28ab08 100644 --- a/src/sql/src/plan/transform_expr.rs +++ b/src/sql/src/plan/transform_expr.rs @@ -353,14 +353,8 @@ fn column_type( /// columns in the group.) pub fn fuse_window_functions( root: &mut HirRelationExpr, - context: &crate::plan::lowering::Context, + _context: &crate::plan::lowering::Context, ) -> Result<(), RecursionLimitError> { - if !(context.config.enable_value_window_function_fusion - || context.config.enable_window_aggregation_fusion) - { - return Ok(()); - } - impl HirScalarExpr { /// Similar to `MirScalarExpr::support`, but adapted to `HirScalarExpr` in a special way: it /// considers column references that target the root level. @@ -589,7 +583,6 @@ pub fn fuse_window_functions( // encounter these, because we just do one pass, but it's better to be // robust against future code changes.) !matches!(func, ValueWindowFunc::Fused(..)) - && context.config.enable_value_window_function_fusion } HirScalarExpr::Windowing(WindowExpr { func: @@ -598,10 +591,7 @@ pub fn fuse_window_functions( .. }), .. - }) => { - !matches!(func, AggregateFunc::FusedWindowAgg { .. }) - && context.config.enable_window_aggregation_fusion - } + }) => !matches!(func, AggregateFunc::FusedWindowAgg { .. }), _ => false, } }; diff --git a/src/sql/src/session/vars/definitions.rs b/src/sql/src/session/vars/definitions.rs index f9631513c2b8f..a345ecde62de1 100644 --- a/src/sql/src/session/vars/definitions.rs +++ b/src/sql/src/session/vars/definitions.rs @@ -2176,24 +2176,6 @@ feature_flags!( default: false, enable_for_item_parsing: false, }, - { - name: enable_value_window_function_fusion, - desc: "Enables the value window function fusion optimization", - default: true, - enable_for_item_parsing: false, - }, - { - name: enable_window_aggregation_fusion, - desc: "Enables the window aggregation fusion optimization", - default: true, - enable_for_item_parsing: false, - }, - { - name: enable_reduce_unnest_list_fusion, - desc: "Enables fusing `Reduce` with `FlatMap UnnestList` for better window function performance", - default: true, - enable_for_item_parsing: false, - }, { name: enable_continual_task_create, desc: "CREATE CONTINUAL TASK", @@ -2230,9 +2212,6 @@ impl From<&super::SystemVars> for OptimizerFeatures { enable_variadic_left_join_lowering: vars.enable_variadic_left_join_lowering(), enable_letrec_fixpoint_analysis: vars.enable_letrec_fixpoint_analysis(), enable_cardinality_estimates: vars.enable_cardinality_estimates(), - enable_value_window_function_fusion: vars.enable_value_window_function_fusion(), - enable_reduce_unnest_list_fusion: vars.enable_reduce_unnest_list_fusion(), - enable_window_aggregation_fusion: vars.enable_window_aggregation_fusion(), enable_reduce_reduction: vars.enable_reduce_reduction(), persist_fast_path_limit: vars.persist_fast_path_limit(), reoptimize_imported_views: false, diff --git a/src/storage-client/src/controller.rs b/src/storage-client/src/controller.rs index ad298698ab741..6458e318d6530 100644 --- a/src/storage-client/src/controller.rs +++ b/src/storage-client/src/controller.rs @@ -29,6 +29,7 @@ use std::time::Duration; use async_trait::async_trait; use differential_dataflow::lattice::Lattice; use mz_cluster_client::client::ClusterReplicaLocation; +use mz_cluster_client::metrics::WallclockLagMetrics; use mz_cluster_client::ReplicaId; use mz_ore::collections::CollectionExt; use mz_persist_client::read::{Cursor, ReadHandle}; @@ -814,6 +815,8 @@ pub struct ExportState { /// Maximum frontier wallclock lag since the last introspection update. pub wallclock_lag_max: Duration, + /// Frontier wallclock lag metrics tracked for this collection. + pub wallclock_lag_metrics: WallclockLagMetrics, } impl ExportState { @@ -821,6 +824,7 @@ impl ExportState { description: ExportDescription, read_hold: ReadHold, read_policy: ReadPolicy, + wallclock_lag_metrics: WallclockLagMetrics, ) -> Self { Self { description, @@ -828,6 +832,7 @@ impl ExportState { read_policy, write_frontier: Antichain::from_elem(Timestamp::minimum()), wallclock_lag_max: Default::default(), + wallclock_lag_metrics, } } diff --git a/src/storage-client/src/healthcheck.rs b/src/storage-client/src/healthcheck.rs index 82c0f0a463ac5..5339fbd82960b 100644 --- a/src/storage-client/src/healthcheck.rs +++ b/src/storage-client/src/healthcheck.rs @@ -96,6 +96,7 @@ pub static MZ_STATEMENT_EXECUTION_HISTORY_DESC: LazyLock = LazyLoc ) .with_column("finished_status", ScalarType::String.nullable(true)) .with_column("error_message", ScalarType::String.nullable(true)) + .with_column("result_size", ScalarType::Int64.nullable(true)) .with_column("rows_returned", ScalarType::Int64.nullable(true)) .with_column("execution_strategy", ScalarType::String.nullable(true)) .finish() diff --git a/src/storage-client/src/metrics.rs b/src/storage-client/src/metrics.rs index b21b7860be3b1..cb6fbf2cfdffc 100644 --- a/src/storage-client/src/metrics.rs +++ b/src/storage-client/src/metrics.rs @@ -11,6 +11,7 @@ use std::sync::Arc; +use mz_cluster_client::metrics::{ControllerMetrics, WallclockLagMetrics}; use mz_cluster_client::ReplicaId; use mz_ore::cast::{CastFrom, TryCastFrom}; use mz_ore::metric; @@ -19,6 +20,7 @@ use mz_ore::metrics::{ MetricsRegistry, UIntGaugeVec, }; use mz_ore::stats::HISTOGRAM_BYTE_BUCKETS; +use mz_repr::GlobalId; use mz_service::codec::StatsCollector; use mz_storage_types::instances::StorageInstanceId; use prometheus::core::AtomicU64; @@ -35,10 +37,13 @@ pub struct StorageControllerMetrics { startup_prepared_statements_kept: prometheus::IntGauge, regressed_offset_known: IntCounterVec, history_command_count: UIntGaugeVec, + + /// Metrics shared with the compute controller. + shared: ControllerMetrics, } impl StorageControllerMetrics { - pub fn new(metrics_registry: MetricsRegistry) -> Self { + pub fn new(metrics_registry: &MetricsRegistry, shared: ControllerMetrics) -> Self { Self { messages_sent_bytes: metrics_registry.register(metric!( name: "mz_storage_messages_sent_bytes", @@ -66,6 +71,8 @@ impl StorageControllerMetrics { help: "The number of commands in the controller's command history.", var_labels: ["instance_id", "command_type"], )), + + shared, } } @@ -77,6 +84,15 @@ impl StorageControllerMetrics { .get_delete_on_drop_metric(vec![id.to_string()]) } + pub fn wallclock_lag_metrics( + &self, + id: GlobalId, + instance_id: Option, + ) -> WallclockLagMetrics { + self.shared + .wallclock_lag_metrics(id.to_string(), instance_id.map(|x| x.to_string()), None) + } + pub fn for_instance(&self, id: StorageInstanceId) -> InstanceMetrics { InstanceMetrics { instance_id: id, diff --git a/src/storage-client/src/storage_collections.rs b/src/storage-client/src/storage_collections.rs index 8d4853437d488..40cefa92ca6fe 100644 --- a/src/storage-client/src/storage_collections.rs +++ b/src/storage-client/src/storage_collections.rs @@ -1401,11 +1401,6 @@ where Ok(shard) }; - let status_shard = match description.status_collection_id { - Some(status_collection_id) => Some(get_shard(status_collection_id)?), - None => None, - }; - let remap_shard = match &description.data_source { // Only ingestions can have remap shards. DataSource::Ingestion(IngestionDescription { @@ -1431,7 +1426,6 @@ where persist_location: self.persist_location.clone(), remap_shard, data_shard, - status_shard, relation_desc: description.desc.clone(), txns_shard, }; @@ -1455,54 +1449,70 @@ where .map(|data: Result<_, StorageError>| { let register_ts = register_ts.clone(); async move { - let (id, description, metadata) = data?; - - // should be replaced with real introspection - // (https://github.com/MaterializeInc/database-issues/issues/4078) - // but for now, it's helpful to have this mapping written down - // somewhere - debug!( - "mapping GlobalId={} to remap shard ({:?}), data shard ({}), status shard ({:?})", - id, metadata.remap_shard, metadata.data_shard, metadata.status_shard - ); + let (id, description, metadata) = data?; + + // should be replaced with real introspection + // (https://github.com/MaterializeInc/database-issues/issues/4078) + // but for now, it's helpful to have this mapping written down + // somewhere + debug!( + "mapping GlobalId={} to remap shard ({:?}), data shard ({})", + id, metadata.remap_shard, metadata.data_shard + ); - let (write, mut since_handle) = this - .open_data_handles( - &id, - metadata.data_shard, - description.since.as_ref(), - metadata.relation_desc.clone(), - persist_client, - ) - .await; + let (write, mut since_handle) = this + .open_data_handles( + &id, + metadata.data_shard, + description.since.as_ref(), + metadata.relation_desc.clone(), + persist_client, + ) + .await; - // Present tables as springing into existence at the register_ts - // by advancing the since. Otherwise, we could end up in a - // situation where a table with a long compaction window appears - // to exist before the environment (and this the table) existed. - // - // We could potentially also do the same thing for other - // sources, in particular storage's internal sources and perhaps - // others, but leave them for now. - match description.data_source { - DataSource::Introspection(_) - | DataSource::IngestionExport { .. } - | DataSource::Webhook - | DataSource::Ingestion(_) - | DataSource::Progress - | DataSource::Other => {}, - DataSource::Table => { - let register_ts = register_ts.expect("caller should have provided a register_ts when creating a table"); - if since_handle.since().elements() == &[T::minimum()] && !migrated_storage_collections.contains(&id) { - debug!("advancing {} to initial since of {:?}", id, register_ts); - let token = since_handle.opaque(); - let _ = since_handle.compare_and_downgrade_since(&token, (&token, &Antichain::from_elem(register_ts.clone()))).await; + // Present tables as springing into existence at the register_ts + // by advancing the since. Otherwise, we could end up in a + // situation where a table with a long compaction window appears + // to exist before the environment (and this the table) existed. + // + // We could potentially also do the same thing for other + // sources, in particular storage's internal sources and perhaps + // others, but leave them for now. + match description.data_source { + DataSource::Introspection(_) + | DataSource::IngestionExport { .. } + | DataSource::Webhook + | DataSource::Ingestion(_) + | DataSource::Progress + | DataSource::Other => {} + DataSource::Table => { + let register_ts = register_ts.expect( + "caller should have provided a register_ts when creating a table", + ); + if since_handle.since().elements() == &[T::minimum()] + && !migrated_storage_collections.contains(&id) + { + debug!("advancing {} to initial since of {:?}", id, register_ts); + let token = since_handle.opaque(); + let _ = since_handle + .compare_and_downgrade_since( + &token, + (&token, &Antichain::from_elem(register_ts.clone())), + ) + .await; + } } } - } - Ok::<_, StorageError>((id, description, write, since_handle, metadata)) - }}) + Ok::<_, StorageError>(( + id, + description, + write, + since_handle, + metadata, + )) + } + }) // Poll each future for each collection concurrently, maximum of 50 at a time. .buffer_unordered(50) // HERE BE DRAGONS: diff --git a/src/storage-controller/src/history.rs b/src/storage-controller/src/history.rs index 170b4d333a604..9d8f8732e8225 100644 --- a/src/storage-controller/src/history.rs +++ b/src/storage-controller/src/history.rs @@ -203,6 +203,7 @@ impl CommandHistory { mod tests { use std::str::FromStr; + use mz_cluster_client::metrics::ControllerMetrics; use mz_ore::metrics::MetricsRegistry; use mz_ore::url::SensitiveUrl; use mz_persist_types::PersistLocation; @@ -231,8 +232,9 @@ mod tests { fn history() -> CommandHistory { let registry = MetricsRegistry::new(); - let metrics = StorageControllerMetrics::new(registry) - .for_instance(StorageInstanceId::System(0)) + let controller_metrics = ControllerMetrics::new(®istry); + let metrics = StorageControllerMetrics::new(®istry, controller_metrics) + .for_instance(StorageInstanceId::system(0).expect("0 is a valid ID")) .for_history(); CommandHistory::new(metrics) @@ -256,7 +258,6 @@ mod tests { }, remap_shard: Default::default(), data_shard: Default::default(), - status_shard: Default::default(), relation_desc: RelationDesc::new( RelationType { column_types: Default::default(), @@ -299,7 +300,6 @@ mod tests { }, remap_shard: Default::default(), data_shard: Default::default(), - status_shard: Default::default(), relation_desc: RelationDesc::new( RelationType { column_types: Default::default(), @@ -310,7 +310,7 @@ mod tests { txns_shard: Default::default(), }, source_exports, - instance_id: StorageInstanceId::System(0), + instance_id: StorageInstanceId::system(0).expect("0 is a valid ID"), remap_collection_id: GlobalId::User(remap_collection_id), } } @@ -371,7 +371,6 @@ mod tests { }, remap_shard: Default::default(), data_shard: Default::default(), - status_shard: Default::default(), relation_desc: RelationDesc::new( RelationType { column_types: Default::default(), diff --git a/src/storage-controller/src/lib.rs b/src/storage-controller/src/lib.rs index deb180b81972e..b29cf4dd9d8a4 100644 --- a/src/storage-controller/src/lib.rs +++ b/src/storage-controller/src/lib.rs @@ -27,6 +27,7 @@ use futures::StreamExt; use itertools::Itertools; use mz_build_info::BuildInfo; use mz_cluster_client::client::ClusterReplicaLocation; +use mz_cluster_client::metrics::{ControllerMetrics, WallclockLagMetrics}; use mz_cluster_client::{ReplicaId, WallclockLagFn}; use mz_controller_types::dyncfgs::{ENABLE_0DT_DEPLOYMENT_SOURCES, WALLCLOCK_LAG_REFRESH_INTERVAL}; use mz_ore::collections::CollectionExt; @@ -551,11 +552,6 @@ where Ok(shard) }; - let status_shard = match description.status_collection_id { - Some(status_collection_id) => Some(get_shard(status_collection_id)?), - None => None, - }; - let remap_shard = match &description.data_source { // Only ingestions can have remap shards. DataSource::Ingestion(IngestionDescription { @@ -580,7 +576,6 @@ where persist_location: self.persist_location.clone(), remap_shard, data_shard, - status_shard, relation_desc: description.desc.clone(), txns_shard, }; @@ -604,26 +599,27 @@ where let mut to_register: Vec<_> = futures::stream::iter(enriched_with_metadata) .map(|data: Result<_, StorageError>| { async move { - let (id, description, metadata) = data?; + let (id, description, metadata) = data?; - // should be replaced with real introspection (https://github.com/MaterializeInc/database-issues/issues/4078) - // but for now, it's helpful to have this mapping written down somewhere - debug!( - "mapping GlobalId={} to remap shard ({:?}), data shard ({}), status shard ({:?})", - id, metadata.remap_shard, metadata.data_shard, metadata.status_shard - ); + // should be replaced with real introspection (https://github.com/MaterializeInc/database-issues/issues/4078) + // but for now, it's helpful to have this mapping written down somewhere + debug!( + "mapping GlobalId={} to remap shard ({:?}), data shard ({})", + id, metadata.remap_shard, metadata.data_shard + ); - let write = this - .open_data_handles( - &id, - metadata.data_shard, - metadata.relation_desc.clone(), - persist_client, - ) - .await; + let write = this + .open_data_handles( + &id, + metadata.data_shard, + metadata.relation_desc.clone(), + persist_client, + ) + .await; - Ok::<_, StorageError>((id, description, write, metadata)) - }}) + Ok::<_, StorageError>((id, description, write, metadata)) + } + }) // Poll each future for each collection concurrently, maximum of 50 at a time. .buffer_unordered(50) // HERE BE DRAGONS: @@ -660,7 +656,9 @@ where let mut new_source_statistic_entries = BTreeSet::new(); let mut new_webhook_statistic_entries = BTreeSet::new(); - for (id, mut description, write, metadata) in to_register { + for (id, description, write, metadata) in to_register { + let mut data_source = description.data_source; + to_execute.insert(id); new_collections.insert(id); @@ -668,7 +666,7 @@ where // This is done in an awkward spot to appease the borrow checker. // TODO(database-issues#8620): This will be removed once sources no longer export // to primary collections and only export to explicit SourceExports (tables). - if let DataSource::Ingestion(ingestion) = &mut description.data_source { + if let DataSource::Ingestion(ingestion) = &mut data_source { if let Some(export) = ingestion.desc.primary_source_export() { ingestion.source_exports.insert(id, export); } @@ -677,8 +675,7 @@ where let write_frontier = write.upper(); // Determine if this collection has another dependency. - let storage_dependencies = - self.determine_collection_dependencies(id, &description.data_source)?; + let storage_dependencies = self.determine_collection_dependencies(id, &data_source)?; let dependency_read_holds = self .storage_collections @@ -723,17 +720,15 @@ where ); } - let mut collection_state = CollectionState { - data_source: description.data_source.clone(), - collection_metadata: metadata.clone(), - extra_state: CollectionStateExtra::None, - wallclock_lag_max: Default::default(), - }; - - // Install the collection state in the appropriate spot. - match &collection_state.data_source { + // Perform data source-specific setup. + let mut extra_state = CollectionStateExtra::None; + let mut maybe_instance_id = None; + match &data_source { DataSource::Introspection(typ) => { - debug!(data_source = ?collection_state.data_source, meta = ?metadata, "registering {} with persist monotonic worker", id); + debug!( + ?data_source, meta = ?metadata, + "registering {id} with persist monotonic worker", + ); // We always register the collection with the collection manager, // regardless of read-only mode. The CollectionManager itself is // aware of read-only mode and will not attempt to write before told @@ -745,11 +740,12 @@ where write, persist_client.clone(), )?; - self.collections.insert(id, collection_state); } DataSource::Webhook => { - debug!(data_source = ?collection_state.data_source, meta = ?metadata, "registering {} with persist monotonic worker", id); - self.collections.insert(id, collection_state); + debug!( + ?data_source, meta = ?metadata, + "registering {id} with persist monotonic worker", + ); new_source_statistic_entries.insert(id); // This collection of statistics is periodically aggregated into // `source_statistics`. @@ -767,7 +763,10 @@ where details, data_config, } => { - debug!(data_source = ?collection_state.data_source, meta = ?metadata, "not registering {} with a controller persist worker", id); + debug!( + ?data_source, meta = ?metadata, + "not registering {id} with a controller persist worker", + ); // Adjust the source to contain this export. let ingestion_state = self .collections @@ -810,22 +809,29 @@ where hydrated: false, }; - collection_state.extra_state = CollectionStateExtra::Ingestion(ingestion_state); + extra_state = CollectionStateExtra::Ingestion(ingestion_state); + maybe_instance_id = Some(instance_id); - self.collections.insert(id, collection_state); new_source_statistic_entries.insert(id); } DataSource::Table => { - debug!(data_source = ?collection_state.data_source, meta = ?metadata, "registering {} with persist table worker", id); - self.collections.insert(id, collection_state); + debug!( + ?data_source, meta = ?metadata, + "registering {id} with persist table worker", + ); table_registers.push((id, write)); } DataSource::Progress | DataSource::Other => { - debug!(data_source = ?collection_state.data_source, meta = ?metadata, "not registering {} with a controller persist worker", id); - self.collections.insert(id, collection_state); + debug!( + ?data_source, meta = ?metadata, + "not registering {id} with a controller persist worker", + ); } DataSource::Ingestion(ingestion_desc) => { - debug!(?ingestion_desc, meta = ?metadata, "not registering {} with a controller persist worker", id); + debug!( + ?data_source, meta = ?metadata, + "not registering {id} with a controller persist worker", + ); let mut dependency_since = Antichain::from_elem(T::minimum()); for read_hold in dependency_read_holds.iter() { @@ -842,12 +848,23 @@ where hydrated: false, }; - collection_state.extra_state = CollectionStateExtra::Ingestion(ingestion_state); + extra_state = CollectionStateExtra::Ingestion(ingestion_state); + maybe_instance_id = Some(ingestion_desc.instance_id); - self.collections.insert(id, collection_state); new_source_statistic_entries.insert(id); } } + + let wallclock_lag_metrics = self.metrics.wallclock_lag_metrics(id, maybe_instance_id); + let collection_state = CollectionState { + data_source, + collection_metadata: metadata, + extra_state, + wallclock_lag_max: Default::default(), + wallclock_lag_metrics, + }; + + self.collections.insert(id, collection_state); } { @@ -1243,10 +1260,17 @@ where "create_exports: creating sink" ); - self.exports.insert( - id, - ExportState::new(description.clone(), read_hold, read_policy), + let wallclock_lag_metrics = self + .metrics + .wallclock_lag_metrics(id, Some(description.instance_id)); + + let export_state = ExportState::new( + description.clone(), + read_hold, + read_policy, + wallclock_lag_metrics, ); + self.exports.insert(id, export_state); // Just like with `new_source_statistic_entries`, we can probably // `insert` here, but in the interest of safety, never override @@ -1294,12 +1318,17 @@ where return Err(StorageError::ReadBeforeSince(from_id)); } + let wallclock_lag_metrics = self + .metrics + .wallclock_lag_metrics(id, Some(new_description.instance_id)); + let new_export = ExportState { description: new_description.clone(), read_hold, read_policy: cur_export.read_policy.clone(), write_frontier: cur_export.write_frontier.clone(), wallclock_lag_max: Default::default(), + wallclock_lag_metrics, }; *cur_export = new_export; @@ -2376,7 +2405,8 @@ where txns_metrics: Arc, envd_epoch: NonZeroI64, read_only: bool, - metrics_registry: MetricsRegistry, + metrics_registry: &MetricsRegistry, + controller_metrics: ControllerMetrics, connection_context: ConnectionContext, txn: &dyn StorageTxn, storage_collections: Arc + Send + Sync>, @@ -2444,6 +2474,8 @@ where let (instance_response_tx, instance_response_rx) = mpsc::unbounded_channel(); + let metrics = StorageControllerMetrics::new(metrics_registry, controller_metrics); + Self { build_info, collections: BTreeMap::default(), @@ -2474,7 +2506,7 @@ where internal_response_queue: rx, persist_location, persist: persist_clients, - metrics: StorageControllerMetrics::new(metrics_registry), + metrics, recorded_frontiers: BTreeMap::new(), recorded_replica_frontiers: BTreeMap::new(), wallclock_lag, @@ -3403,7 +3435,8 @@ where .differential_append(id, replica_updates); } - /// Update introspection with the current wallclock lag values. + /// Refresh the `WallclockLagHistory` introspection and the `wallclock_lag_*_seconds` metrics + /// with the current lag values. /// /// We measure the lag of write frontiers behind the wallclock time every second and track the /// maximum over 60 measurements (i.e., one minute). Every minute, we emit a new lag event to @@ -3411,7 +3444,7 @@ where /// /// This method is invoked by `ComputeController::maintain`, which we expect to be called once /// per second during normal operation. - fn update_wallclock_lag_introspection(&mut self) { + fn refresh_wallclock_lag(&mut self) { let refresh_introspection = !self.read_only && self.wallclock_lag_last_refresh.elapsed() >= WALLCLOCK_LAG_REFRESH_INTERVAL.get(self.config.config_set()); @@ -3447,6 +3480,8 @@ where let row = pack_row(id, lag); updates.push((row, 1)); } + + collection.wallclock_lag_metrics.observe(lag); } let active_exports = self.exports.iter_mut().filter(|(_id, e)| !e.is_dropped()); @@ -3459,6 +3494,8 @@ where let row = pack_row(*id, lag); updates.push((row, 1)); } + + export.wallclock_lag_metrics.observe(lag); } if let Some(updates) = introspection_updates { @@ -3473,7 +3510,7 @@ where /// for tasks that need to run periodically, such as state cleanup or updating of metrics. fn maintain(&mut self) { self.update_frontier_introspection(); - self.update_wallclock_lag_introspection(); + self.refresh_wallclock_lag(); } } @@ -3658,6 +3695,8 @@ struct CollectionState { /// Maximum frontier wallclock lag since the last introspection update. wallclock_lag_max: Duration, + /// Frontier wallclock lag metrics tracked for this collection. + wallclock_lag_metrics: WallclockLagMetrics, } /// Additional state that the controller maintains for select collection types. diff --git a/src/storage-types/BUILD.bazel b/src/storage-types/BUILD.bazel index 7bbfed06e8319..fc8f88dff561e 100644 --- a/src/storage-types/BUILD.bazel +++ b/src/storage-types/BUILD.bazel @@ -156,7 +156,6 @@ filegroup( "src/errors.proto", "src/instances.proto", "src/parameters.proto", - "src/shim.proto", "src/sinks.proto", "src/sources.proto", "src/sources/encoding.proto", diff --git a/src/storage-types/build.rs b/src/storage-types/build.rs index fed28b9c405d5..c04f9541fe8f3 100644 --- a/src/storage-types/build.rs +++ b/src/storage-types/build.rs @@ -59,7 +59,6 @@ fn main() { "storage-types/src/connections/aws.proto", "storage-types/src/connections/string_or_secret.proto", "storage-types/src/connections.proto", - "storage-types/src/shim.proto", "storage-types/src/errors.proto", "storage-types/src/instances.proto", "storage-types/src/parameters.proto", diff --git a/src/storage-types/src/collections.proto b/src/storage-types/src/collections.proto index c6ece95f015ed..b2aabae046a94 100644 --- a/src/storage-types/src/collections.proto +++ b/src/storage-types/src/collections.proto @@ -19,6 +19,7 @@ message GlobalId { uint64 user = 2; uint64 transient = 3; google.protobuf.Empty explain = 4; + uint64 introspection_source_index = 5; } } diff --git a/src/storage-types/src/collections.rs b/src/storage-types/src/collections.rs index 9ad3366530bbc..059ca1a8e5746 100644 --- a/src/storage-types/src/collections.rs +++ b/src/storage-types/src/collections.rs @@ -22,6 +22,9 @@ impl RustType for RustGlobalId { GlobalId { value: Some(match self { RustGlobalId::System(x) => global_id::Value::System(*x), + RustGlobalId::IntrospectionSourceIndex(x) => { + global_id::Value::IntrospectionSourceIndex(*x) + } RustGlobalId::User(x) => global_id::Value::User(*x), RustGlobalId::Transient(x) => global_id::Value::Transient(*x), RustGlobalId::Explain => global_id::Value::Explain(Default::default()), @@ -32,6 +35,9 @@ impl RustType for RustGlobalId { fn from_proto(proto: GlobalId) -> Result { match proto.value { Some(global_id::Value::System(x)) => Ok(RustGlobalId::System(x)), + Some(global_id::Value::IntrospectionSourceIndex(x)) => { + Ok(RustGlobalId::IntrospectionSourceIndex(x)) + } Some(global_id::Value::User(x)) => Ok(RustGlobalId::User(x)), Some(global_id::Value::Transient(x)) => Ok(RustGlobalId::Transient(x)), Some(global_id::Value::Explain(_)) => Ok(RustGlobalId::Explain), diff --git a/src/storage-types/src/controller.proto b/src/storage-types/src/controller.proto index acf34507f9db6..c1489ffba112e 100644 --- a/src/storage-types/src/controller.proto +++ b/src/storage-types/src/controller.proto @@ -16,11 +16,11 @@ package mz_storage_types.controller; import "repr/src/relation_and_scalar.proto"; message ProtoCollectionMetadata { + reserved 5; string blob_uri = 1; string consensus_uri = 2; string data_shard = 3; optional string remap_shard = 4; - optional string status_shard = 5; optional string txns_shard = 7; mz_repr.relation_and_scalar.ProtoRelationDesc relation_desc = 6; diff --git a/src/storage-types/src/controller.rs b/src/storage-types/src/controller.rs index 2c2e27ec80c50..47d840dd67e4b 100644 --- a/src/storage-types/src/controller.rs +++ b/src/storage-types/src/controller.rs @@ -42,8 +42,6 @@ pub struct CollectionMetadata { pub remap_shard: Option, /// The persist shard containing the contents of this storage collection. pub data_shard: ShardId, - /// The persist shard containing the status updates for this storage collection. - pub status_shard: Option, /// The `RelationDesc` that describes the contents of the `data_shard`. pub relation_desc: RelationDesc, /// The shard id of the txn-wal shard, if `self.data_shard` is managed @@ -64,7 +62,6 @@ impl crate::AlterCompatible for CollectionMetadata { persist_location: _, remap_shard, data_shard, - status_shard, relation_desc, txns_shard, } = self; @@ -72,7 +69,6 @@ impl crate::AlterCompatible for CollectionMetadata { let compatibility_checks = [ (remap_shard == &other.remap_shard, "remap_shard"), (data_shard == &other.data_shard, "data_shard"), - (status_shard == &other.status_shard, "status_shard"), (relation_desc == &other.relation_desc, "relation_desc"), (txns_shard == &other.txns_shard, "txns_shard"), ]; @@ -100,7 +96,6 @@ impl RustType for CollectionMetadata { consensus_uri: self.persist_location.consensus_uri.to_string_unredacted(), data_shard: self.data_shard.to_string(), remap_shard: self.remap_shard.map(|s| s.to_string()), - status_shard: self.status_shard.map(|s| s.to_string()), relation_desc: Some(self.relation_desc.into_proto()), txns_shard: self.txns_shard.map(|x| x.to_string()), } @@ -120,10 +115,6 @@ impl RustType for CollectionMetadata { .data_shard .parse() .map_err(TryFromProtoError::InvalidShardId)?, - status_shard: value - .status_shard - .map(|s| s.parse().map_err(TryFromProtoError::InvalidShardId)) - .transpose()?, relation_desc: value .relation_desc .into_rust_if_some("ProtoCollectionMetadata::relation_desc")?, diff --git a/src/storage-types/src/errors.proto b/src/storage-types/src/errors.proto index 7779fb45f1025..9a257b67c00ba 100644 --- a/src/storage-types/src/errors.proto +++ b/src/storage-types/src/errors.proto @@ -8,12 +8,12 @@ // by the Apache License, Version 2.0. syntax = "proto3"; +// buf breaking: ignore (remove after the commit that introduced it gets merged) package mz_storage_types.errors; import "expr/src/scalar.proto"; import "repr/src/row.proto"; -import "storage-types/src/shim.proto"; message ProtoDecodeError { ProtoDecodeErrorKind kind = 1; @@ -46,11 +46,6 @@ message ProtoUpsertValueError { mz_repr.row.ProtoRow for_key = 2; } -message ProtoUpsertValueErrorLegacy { - ProtoDataflowError inner = 1; - mz_repr.row.ProtoRow for_key = 2; -} - message ProtoUpsertNullKeyError { reserved 1; } @@ -58,7 +53,7 @@ message ProtoUpsertNullKeyError { message ProtoUpsertError { oneof kind { ProtoDecodeError key_decode = 1; - mz_storage_types.shim.ProtoUpsertValueErrorShim value = 2; + ProtoUpsertValueError value = 2; ProtoUpsertNullKeyError null_key = 3; } } diff --git a/src/storage-types/src/errors.rs b/src/storage-types/src/errors.rs index 37c6927279855..db1df11fa20b8 100644 --- a/src/storage-types/src/errors.rs +++ b/src/storage-types/src/errors.rs @@ -23,8 +23,6 @@ use rdkafka::error::KafkaError; use serde::{Deserialize, Serialize}; use tracing::warn; -use crate::shim::ProtoUpsertValueErrorShim; - include!(concat!(env!("OUT_DIR"), "/mz_storage_types.errors.rs")); /// The underlying data was not decodable in the format we expected: eg. @@ -177,83 +175,33 @@ pub struct UpsertValueError { pub inner: DecodeError, /// The (good) key associated with the errored value. pub for_key: Row, - /// Whether this upsert error got decoded from a legacy format - /// Do not touch this field unless your name is Petros - pub is_legacy_dont_touch_it: bool, } -impl RustType for UpsertValueError { - fn into_proto(&self) -> ProtoUpsertValueErrorShim { - if self.is_legacy_dont_touch_it { - let inner = ProtoDataflowError { - kind: Some(proto_dataflow_error::Kind::DecodeError( - self.inner.into_proto(), - )), - }; - let proto = ProtoUpsertValueErrorLegacy { - inner: Some(inner), - for_key: Some(self.for_key.into_proto()), - }; - ProtoUpsertValueErrorShim::Legacy(Box::new(proto)) - } else { - let proto = ProtoUpsertValueError { - inner: Some(self.inner.into_proto()), - for_key: Some(self.for_key.into_proto()), - }; - ProtoUpsertValueErrorShim::New(proto) +impl RustType for UpsertValueError { + fn into_proto(&self) -> ProtoUpsertValueError { + ProtoUpsertValueError { + inner: Some(self.inner.into_proto()), + for_key: Some(self.for_key.into_proto()), } } - fn from_proto(proto: ProtoUpsertValueErrorShim) -> Result { - Ok(match proto { - ProtoUpsertValueErrorShim::New(new) => UpsertValueError { - inner: new - .inner - .into_rust_if_some("ProtoUpsertValueError::inner")?, - for_key: new - .for_key - .into_rust_if_some("ProtoUpsertValueError::for_key")?, - is_legacy_dont_touch_it: false, - }, - ProtoUpsertValueErrorShim::Legacy(legacy) => { - let legacy_inner = match legacy.inner { - Some(inner) => inner, - None => { - return Err(TryFromProtoError::missing_field( - "ProtoUpsertValueError::inner", - )) - } - }; - let inner = DataflowError::from_proto(legacy_inner)?; - let inner = match inner { - DataflowError::DecodeError(inner) => *inner, - _ => panic!("invalid legacy upsert error"), - }; - UpsertValueError { - inner, - for_key: legacy - .for_key - .into_rust_if_some("ProtoUpsertValueError::for_key")?, - is_legacy_dont_touch_it: true, - } - } + fn from_proto(proto: ProtoUpsertValueError) -> Result { + Ok(UpsertValueError { + inner: proto + .inner + .into_rust_if_some("ProtoUpsertValueError::inner")?, + for_key: proto + .for_key + .into_rust_if_some("ProtoUpsertValueError::for_key")?, }) } } impl Display for UpsertValueError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let UpsertValueError { - inner, - for_key, - is_legacy_dont_touch_it, - } = self; + let UpsertValueError { inner, for_key } = self; write!(f, "{inner}, decoded key: {for_key:?}")?; - if *is_legacy_dont_touch_it { - f.write_str(", legacy: true") - } else { - Ok(()) - } + Ok(()) } } @@ -850,7 +798,6 @@ mod columnation { UpsertError::Value(err) => UpsertError::Value(UpsertValueError { inner: self.copy_decode_error(&err.inner), for_key: self.row_region.copy(&err.for_key), - is_legacy_dont_touch_it: err.is_legacy_dont_touch_it, }), UpsertError::NullKey(err) => UpsertError::NullKey(*err), }; diff --git a/src/storage-types/src/instances.rs b/src/storage-types/src/instances.rs index 75c4114eb210a..ad4c8bbd5995a 100644 --- a/src/storage-types/src/instances.rs +++ b/src/storage-types/src/instances.rs @@ -14,15 +14,15 @@ use std::str::FromStr; use anyhow::bail; use mz_proto::{RustType, TryFromProtoError}; -use proptest_derive::Arbitrary; +use proptest::prelude::{Arbitrary, Strategy}; +use proptest::strategy::BoxedStrategy; use serde::{Deserialize, Serialize}; +use tracing::error; include!(concat!(env!("OUT_DIR"), "/mz_storage_types.instances.rs")); /// Identifier of a storage instance. -#[derive( - Arbitrary, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, -)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] pub enum StorageInstanceId { /// A system storage instance. System(u64), @@ -31,6 +31,33 @@ pub enum StorageInstanceId { } impl StorageInstanceId { + /// Creates a new `StorageInstanceId` in the system namespace. The top 16 bits of `id` must be + /// 0, because this ID is packed into 48 bits of + /// [`mz_repr::GlobalId::IntrospectionSourceIndex`]. + pub fn system(id: u64) -> Option { + Self::new(id, Self::System) + } + + /// Creates a new `StorageInstanceId` in the user namespace. The top 16 bits of `id` must be + /// 0, because this ID is packed into 48 bits of + /// [`mz_repr::GlobalId::IntrospectionSourceIndex`]. + pub fn user(id: u64) -> Option { + Self::new(id, Self::User) + } + + fn new(id: u64, variant: fn(u64) -> Self) -> Option { + const MASK: u64 = 0xFFFF << 48; + const WARN_MASK: u64 = 1 << 47; + if MASK & id == 0 { + if WARN_MASK & id != 0 { + error!("{WARN_MASK} or more `StorageInstanceId`s allocated, we will run out soon"); + } + Some(variant(id)) + } else { + None + } + } + pub fn inner_id(&self) -> u64 { match self { StorageInstanceId::System(id) | StorageInstanceId::User(id) => *id, @@ -85,11 +112,36 @@ impl RustType for StorageInstanceId { fn from_proto(proto: ProtoStorageInstanceId) -> Result { use proto_storage_instance_id::Kind::*; match proto.kind { - Some(System(x)) => Ok(StorageInstanceId::System(x)), - Some(User(x)) => Ok(StorageInstanceId::User(x)), + Some(System(x)) => StorageInstanceId::system(x).ok_or_else(|| { + TryFromProtoError::InvalidPersistState(format!( + "{x} is not a valid StorageInstanceId" + )) + }), + Some(User(x)) => StorageInstanceId::user(x).ok_or_else(|| { + TryFromProtoError::InvalidPersistState(format!( + "{x} is not a valid StorageInstanceId" + )) + }), None => Err(TryFromProtoError::missing_field( "ProtoStorageInstanceId::kind", )), } } } + +impl Arbitrary for StorageInstanceId { + type Parameters = (); + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + const UPPER_BOUND: u64 = 1 << 47; + (0..2, 0..UPPER_BOUND) + .prop_map(|(variant, id)| match variant { + 0 => StorageInstanceId::System(id), + 1 => StorageInstanceId::User(id), + _ => unreachable!(), + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/src/storage-types/src/lib.rs b/src/storage-types/src/lib.rs index 77799e3fb9252..6526e81e6fc2f 100644 --- a/src/storage-types/src/lib.rs +++ b/src/storage-types/src/lib.rs @@ -19,7 +19,6 @@ pub mod instances; pub mod parameters; pub mod read_holds; pub mod read_policy; -pub mod shim; pub mod sinks; pub mod sources; pub mod stats; diff --git a/src/storage-types/src/shim.proto b/src/storage-types/src/shim.proto deleted file mode 100644 index 053411b07cb7d..0000000000000 --- a/src/storage-types/src/shim.proto +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright Materialize, Inc. and contributors. All rights reserved. -// -// Use of this software is governed by the Business Source License -// included in the LICENSE file. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0. - -syntax = "proto3"; - -package mz_storage_types.shim; - -// This is just a shim message to try and get prost to generate a reference to -// this shim as part of the `prost::Message` implementation for the -// ProtoUpsertError type. -// -// The actual definition of `ProtoUpsertValueErrorShim` and its implementation -// of `prost::Message` happens manually in `shim.rs` -message ProtoUpsertValueErrorShim {} diff --git a/src/storage-types/src/shim.rs b/src/storage-types/src/shim.rs deleted file mode 100644 index 01911e83b9572..0000000000000 --- a/src/storage-types/src/shim.rs +++ /dev/null @@ -1,545 +0,0 @@ -// Copyright Materialize, Inc. and contributors. All rights reserved. -// -// Use of this software is governed by the Business Source License -// included in the LICENSE file. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0. - -use bytes::{Buf, BufMut}; - -use super::errors::{proto_dataflow_error, ProtoUpsertValueError, ProtoUpsertValueErrorLegacy}; - -#[derive(Clone, Debug, PartialEq)] -pub enum ProtoUpsertValueErrorShim { - New(ProtoUpsertValueError), - Legacy(Box), -} - -impl Default for ProtoUpsertValueErrorShim { - fn default() -> Self { - Self::New(ProtoUpsertValueError::default()) - } -} - -impl prost::Message for ProtoUpsertValueErrorShim { - fn encode_raw(&self, buf: &mut impl BufMut) - where - Self: Sized, - { - match self { - Self::New(new) => new.encode_raw(buf), - Self::Legacy(legacy) => legacy.encode_raw(buf), - } - } - - fn merge_field( - &mut self, - tag: u32, - wire_type: prost::encoding::WireType, - buf: &mut impl Buf, - ctx: prost::encoding::DecodeContext, - ) -> Result<(), prost::DecodeError> - where - Self: Sized, - { - if tag == 1 { - // This is the problematic tag that changed types. Here we need to do some type - // "inference" to figure out if we're decoding the new or the legacy format and update - // the variant of self accordingly. - - // We need `merge_field` two times but we can only read the provided buffer once, so we - // make a local copy. This would normally be tricky with only the `Buf` bound but we - // know that we only get here as a result of a `Codec::decode` call, which has a &[u8] - // argument that is what B will be. This means that the `B: Buf` generic type is backed - // by a contiguous buffer and therefore calling `Buf::chunk` will give us all the data. - let data = buf.chunk().to_vec(); - assert_eq!(data.len(), buf.remaining()); - - let mut new_buf = &*data; - let mut new_proto = ProtoUpsertValueError::default(); - let new_merge = new_proto.merge_field(tag, wire_type, &mut new_buf, ctx.clone()); - - let mut legacy_buf = &*data; - let mut legacy_proto = ProtoUpsertValueErrorLegacy::default(); - let legacy_merge = - legacy_proto.merge_field(tag, wire_type, &mut legacy_buf, ctx.clone()); - - // Let's see what happened - let (is_legacy, ret) = match (new_merge, legacy_merge) { - (Ok(()), Err(_)) => (false, Ok(())), - (Err(_), Ok(_)) => (true, Ok(())), - // If both succeeded we need to look deeper into the abyss and use heuristics - (Ok(()), Ok(())) => { - let maybe_new = match &new_proto.inner { - Some(decode_err) => decode_err.raw.is_some(), - None => false, - }; - - let maybe_legacy = match &legacy_proto.inner { - Some(inner) => match &inner.kind { - Some(proto_dataflow_error::Kind::DecodeError(decode_err)) => { - decode_err.raw.is_some() - } - _ => false, - }, - None => false, - }; - - if maybe_new == maybe_legacy { - panic!("could not figure out the variant: {new_proto:?} {legacy_proto:?}"); - } - - (maybe_legacy && !maybe_new, Ok(())) - } - // If both failed we will arbitrarily pick the new variant. This is not expected to - // happen - (Err(err), Err(_)) => (false, Err(err)), - }; - if is_legacy { - fail::fail_point!("reject_legacy_upsert_errors"); - *self = Self::Legacy(Box::new(legacy_proto)); - buf.advance(data.len() - legacy_buf.len()); - } else { - *self = Self::New(new_proto); - buf.advance(data.len() - new_buf.len()); - } - ret - } else { - // For all other tags we simply continue with what we have since the determination has - // already happened. - match self { - Self::New(new) => new.merge_field(tag, wire_type, buf, ctx), - Self::Legacy(legacy) => legacy.merge_field(tag, wire_type, buf, ctx), - } - } - } - - fn encoded_len(&self) -> usize { - match self { - Self::New(new) => new.encoded_len(), - Self::Legacy(legacy) => legacy.encoded_len(), - } - } - - fn clear(&mut self) { - match self { - Self::New(new) => new.clear(), - Self::Legacy(legacy) => legacy.clear(), - } - } -} - -#[cfg(test)] -mod test { - use mz_proto::RustType; - use mz_repr::{Datum, Row}; - use prost::Message; - - use crate::errors::{proto_dataflow_error, ProtoDataflowError}; - use crate::errors::{DecodeError, DecodeErrorKind}; - - use super::*; - - #[mz_ore::test] - fn sanity_check() { - // Create an assortment of different decode errors to make sure we can correctly tell apart - // the two protobuf serializations. We are especially interested in pathological cases - // (empty strings/empty vectors) which might trip the protobuf decoder up. - let decode_errors = vec![ - // Empty Text kind plus zeros - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0; 1], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0; 2], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0; 4], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0; 16], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0; 32], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0; 64], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0; 128], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0; 256], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0; 512], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0; 1024], - }, - // None-empty Text kind plus zeros - DecodeError { - kind: DecodeErrorKind::Text("foo".into()), - raw: vec![], - }, - DecodeError { - kind: DecodeErrorKind::Text("foo".into()), - raw: vec![0; 1], - }, - DecodeError { - kind: DecodeErrorKind::Text("foo".into()), - raw: vec![0; 2], - }, - DecodeError { - kind: DecodeErrorKind::Text("foo".into()), - raw: vec![0; 4], - }, - DecodeError { - kind: DecodeErrorKind::Text("foo".into()), - raw: vec![0; 16], - }, - DecodeError { - kind: DecodeErrorKind::Text("foo".into()), - raw: vec![0; 32], - }, - DecodeError { - kind: DecodeErrorKind::Text("foo".into()), - raw: vec![0; 64], - }, - DecodeError { - kind: DecodeErrorKind::Text("foo".into()), - raw: vec![0; 128], - }, - DecodeError { - kind: DecodeErrorKind::Text("foo".into()), - raw: vec![0; 256], - }, - DecodeError { - kind: DecodeErrorKind::Text("foo".into()), - raw: vec![0; 512], - }, - DecodeError { - kind: DecodeErrorKind::Text("foo".into()), - raw: vec![0; 1024], - }, - // Empty Text kind plus 0x42 - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0x42; 1], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0x42; 2], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0x42; 4], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0x42; 16], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0x42; 32], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0x42; 64], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0x42; 128], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0x42; 256], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0x42; 512], - }, - DecodeError { - kind: DecodeErrorKind::Text("".into()), - raw: vec![0x42; 1024], - }, - // None-empty Text kind plus 0x42 - DecodeError { - kind: DecodeErrorKind::Text("fooooo".into()), - raw: vec![], - }, - DecodeError { - kind: DecodeErrorKind::Text("fooooo".into()), - raw: vec![0x42; 1], - }, - DecodeError { - kind: DecodeErrorKind::Text("fooooo".into()), - raw: vec![0x42; 2], - }, - DecodeError { - kind: DecodeErrorKind::Text("fooooo".into()), - raw: vec![0x42; 4], - }, - DecodeError { - kind: DecodeErrorKind::Text("fooooo".into()), - raw: vec![0x42; 16], - }, - DecodeError { - kind: DecodeErrorKind::Text("fooooo".into()), - raw: vec![0x42; 32], - }, - DecodeError { - kind: DecodeErrorKind::Text("fooooo".into()), - raw: vec![0x42; 64], - }, - DecodeError { - kind: DecodeErrorKind::Text("fooooo".into()), - raw: vec![0x42; 128], - }, - DecodeError { - kind: DecodeErrorKind::Text("fooooo".into()), - raw: vec![0x42; 256], - }, - DecodeError { - kind: DecodeErrorKind::Text("fooooo".into()), - raw: vec![0x42; 512], - }, - DecodeError { - kind: DecodeErrorKind::Text("fooooo".into()), - raw: vec![0x42; 1024], - }, - // Empty Bytes kind plus zeros - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0; 1], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0; 2], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0; 4], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0; 16], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0; 32], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0; 64], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0; 128], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0; 256], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0; 512], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0; 1024], - }, - // None-empty Bytes kind plus zeros - DecodeError { - kind: DecodeErrorKind::Bytes("foo".into()), - raw: vec![], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("foo".into()), - raw: vec![0; 1], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("foo".into()), - raw: vec![0; 2], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("foo".into()), - raw: vec![0; 4], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("foo".into()), - raw: vec![0; 16], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("foo".into()), - raw: vec![0; 32], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("foo".into()), - raw: vec![0; 64], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("foo".into()), - raw: vec![0; 128], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("foo".into()), - raw: vec![0; 256], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("foo".into()), - raw: vec![0; 512], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("foo".into()), - raw: vec![0; 1024], - }, - // Empty Bytes kind plus 0x42 - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0x42; 1], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0x42; 2], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0x42; 4], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0x42; 16], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0x42; 32], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0x42; 64], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0x42; 128], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0x42; 256], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0x42; 512], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("".into()), - raw: vec![0x42; 1024], - }, - // None-empty Bytes kind plus 0x42 - DecodeError { - kind: DecodeErrorKind::Bytes("i love protobuf".into()), - raw: vec![], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("i love protobuf".into()), - raw: vec![0x42; 1], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("i love protobuf".into()), - raw: vec![0x42; 2], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("i love protobuf".into()), - raw: vec![0x42; 4], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("i love protobuf".into()), - raw: vec![0x42; 16], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("i love protobuf".into()), - raw: vec![0x42; 32], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("i love protobuf".into()), - raw: vec![0x42; 64], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("i love protobuf".into()), - raw: vec![0x42; 128], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("i love protobuf".into()), - raw: vec![0x42; 256], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("i love protobuf".into()), - raw: vec![0x42; 512], - }, - DecodeError { - kind: DecodeErrorKind::Bytes("i love protobuf".into()), - raw: vec![0x42; 1024], - }, - ]; - - // Now we go through each one of them, encode it in two different ways using the normal - // Proto structs, which is how Materialize 0.68.1 and 0.69.1 would do it, and then attemp - // to decode them with the shim. The shim should correctly understand if it is reading a - // new or legacy format. - - // We don't care about the key, the whole determination happens during processing of the - // firs tag, which is the inner error. - let key = Row::pack([Datum::String("the key")]); - for err in decode_errors { - let inner = ProtoDataflowError { - kind: Some(proto_dataflow_error::Kind::DecodeError(err.into_proto())), - }; - let legacy_proto = ProtoUpsertValueErrorLegacy { - inner: Some(inner), - for_key: Some(key.into_proto()), - }; - let legacy_bytes = legacy_proto.encode_to_vec(); - let legacy_shim = ProtoUpsertValueErrorShim::decode(&*legacy_bytes).unwrap(); - assert_eq!( - legacy_shim, - ProtoUpsertValueErrorShim::Legacy(Box::new(legacy_proto)) - ); - - let new_proto = ProtoUpsertValueError { - inner: Some(err.into_proto()), - for_key: Some(key.into_proto()), - }; - let new_bytes = new_proto.encode_to_vec(); - let new_shim = ProtoUpsertValueErrorShim::decode(&*new_bytes).unwrap(); - assert_eq!(new_shim, ProtoUpsertValueErrorShim::New(new_proto)); - } - } -} diff --git a/src/storage/src/metrics/upsert.rs b/src/storage/src/metrics/upsert.rs index 837af0be10ad9..a64feeffb0ba5 100644 --- a/src/storage/src/metrics/upsert.rs +++ b/src/storage/src/metrics/upsert.rs @@ -49,9 +49,6 @@ pub(crate) struct UpsertMetricDefs { pub(crate) multi_put_latency: HistogramVec, pub(crate) multi_put_size: IntCounterVec, - /// The number of legacy errors encountered during rehydration - pub(crate) legacy_value_errors: UIntGaugeVec, - // These are used by `rocksdb`. pub(crate) rocksdb_multi_get_latency: HistogramVec, pub(crate) rocksdb_multi_get_size: IntCounterVec, @@ -247,12 +244,6 @@ impl UpsertMetricDefs { var_labels: ["source_id", "worker_id"], )), rocksdb_shared, - legacy_value_errors: registry.register(metric!( - name: "mz_storage_upsert_legacy_value_errors", - help: "The total number of legacy errors encountered during \ - rehydration for this source", - var_labels: ["source_id", "worker_id"], - )), } } @@ -387,8 +378,6 @@ pub struct UpsertMetrics { pub(crate) multi_get_result_count: DeleteOnDropCounter<'static, AtomicU64, Vec>, pub(crate) multi_put_size: DeleteOnDropCounter<'static, AtomicU64, Vec>, - pub(crate) legacy_value_errors: DeleteOnDropGauge<'static, AtomicU64, Vec>, - pub(crate) shared: Arc, pub(crate) rocksdb_shared: Arc, pub(crate) rocksdb_instance_metrics: Arc, @@ -452,10 +441,6 @@ impl UpsertMetrics { .multi_put_size .get_delete_on_drop_metric(vec![source_id_s.clone(), worker_id.clone()]), - legacy_value_errors: defs - .legacy_value_errors - .get_delete_on_drop_metric(vec![source_id_s.clone(), worker_id.clone()]), - shared: defs.shared(&source_id), rocksdb_shared: defs.rocksdb_shared(&source_id), rocksdb_instance_metrics: Arc::new(RocksDBInstanceMetrics { diff --git a/src/storage/src/render/sources.rs b/src/storage/src/render/sources.rs index 3542958457c01..74fe3f45a6b93 100644 --- a/src/storage/src/render/sources.rs +++ b/src/storage/src/render/sources.rs @@ -592,7 +592,6 @@ fn upsert_commands( _ => Some(Err(UpsertError::Value(UpsertValueError { for_key: key_row, inner, - is_legacy_dont_touch_it: false, }))), } } diff --git a/src/storage/src/sink/kafka.rs b/src/storage/src/sink/kafka.rs index b0482bb001d84..94e0d9c60a7d0 100644 --- a/src/storage/src/sink/kafka.rs +++ b/src/storage/src/sink/kafka.rs @@ -230,6 +230,8 @@ struct TransactionalProducer { staged_bytes: u64, /// The timeout to use for network operations. socket_timeout: Duration, + /// The timeout to use for committing transactions. + transaction_timeout: Duration, } impl TransactionalProducer { @@ -341,6 +343,7 @@ impl TransactionalProducer { staged_messages: 0, staged_bytes: 0, socket_timeout: timeout_config.socket_timeout, + transaction_timeout: timeout_config.transaction_timeout, }; let timeout = timeout_config.socket_timeout; @@ -499,7 +502,7 @@ impl TransactionalProducer { fail::fail_point!("kafka_sink_commit_transaction"); - let timeout = self.socket_timeout; + let timeout = self.transaction_timeout; match self .spawn_blocking(move |p| p.commit_transaction(timeout)) .await diff --git a/src/storage/src/source/kafka.rs b/src/storage/src/source/kafka.rs index dab645f6b31bc..feaa3feb38969 100644 --- a/src/storage/src/source/kafka.rs +++ b/src/storage/src/source/kafka.rs @@ -15,6 +15,7 @@ use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; +use anyhow::bail; use chrono::{DateTime, NaiveDateTime}; use differential_dataflow::AsCollection; use futures::StreamExt; @@ -41,7 +42,6 @@ use mz_timely_util::antichain::AntichainExt; use mz_timely_util::builder_async::{OperatorBuilder as AsyncOperatorBuilder, PressOnDropButton}; use mz_timely_util::containers::stack::AccountedStackBuilder; use mz_timely_util::order::Partitioned; -use rdkafka::client::Client; use rdkafka::consumer::base_consumer::PartitionQueue; use rdkafka::consumer::{BaseConsumer, Consumer, ConsumerContext}; use rdkafka::error::KafkaError; @@ -99,8 +99,7 @@ pub struct KafkaSourceReader { /// thread. progress_statistics: Arc>, /// The last partition info we received. For each partition we also fetch the high watermark. - partition_info: - Arc)>>>, + partition_info: Arc)>>>, /// A handle to the spawned metadata thread // Drop order is important here, we want the thread to be unparked after the `partition_info` // Arc has been dropped, so that the unpacked thread notices it and exits immediately @@ -129,17 +128,10 @@ struct PartitionCapability { progress: Capability, } -/// Represents the low and high watermark offsets of a Kafka partition. -#[derive(Debug)] -struct WatermarkOffsets { - /// The offset of the earliest message in the topic/partition. If no messages have been written - /// to the topic, the low watermark offset is set to 0. The low watermark will also be 0 if one - /// message has been written to the partition (with offset 0). - low: u64, - /// The high watermark offset, which is the offset of the latest message in the topic/partition - /// available for consumption + 1. - high: u64, -} +/// The high watermark offsets of a Kafka partition. +/// +/// This is the offset of the latest message in the topic/partition available for consumption + 1. +type HighWatermark = u64; /// Processes `resume_uppers` stream updates, committing them upstream and /// storing them in the `progress_statistics` to be emitted later. @@ -324,7 +316,8 @@ impl SourceRender for KafkaSourceConnection { let (stats_tx, stats_rx) = crossbeam_channel::unbounded(); let health_status = Arc::new(Mutex::new(Default::default())); let notificator = Arc::new(Notify::new()); - let consumer: Result, _> = connection + + let reader_consumer: Result, _> = connection .create_with_context( &config.config, GlueConsumerContext { @@ -363,37 +356,63 @@ impl SourceRender for KafkaSourceConnection { ) .await; - let consumer = match consumer { - Ok(consumer) => Arc::new(consumer), - Err(e) => { - let update = HealthStatusUpdate::halting( - format!( - "failed creating kafka consumer: {}", - e.display_with_causes() - ), - None, - ); - for (output, update) in outputs.iter().repeat_clone(update) { - health_output.give( - &health_cap, - HealthStatusMessage { - index: output.output_index, - namespace: if matches!(e, ContextCreationError::Ssh(_)) { - StatusNamespace::Ssh - } else { - Self::STATUS_NAMESPACE.clone() - }, - update, - }, + // Consumers use a single connection to talk to the upstream, so if we'd use the + // same consumer in the reader and the metadata thread, metadata probes issued by + // the latter could be delayed by data fetches issued by the former. We avoid that + // by giving the metadata thread its own consumer. + let metadata_consumer: Result, _> = connection + .create_with_context( + &config.config, + MzClientContext::default(), + &btreemap! { + // Use the user-configured topic metadata refresh + // interval. + "topic.metadata.refresh.interval.ms" => + topic_metadata_refresh_interval + .as_millis() + .to_string(), + // Allow Kafka monitoring tools to identify this + // consumer. + "client.id" => format!("{client_id}-metadata"), + }, + InTask::Yes, + ) + .await; + + let (reader_consumer, metadata_consumer) = + match (reader_consumer, metadata_consumer) { + (Ok(r), Ok(m)) => (r, m), + (Err(e), _) | (_, Err(e)) => { + let update = HealthStatusUpdate::halting( + format!( + "failed creating kafka consumer: {}", + e.display_with_causes() + ), + None, ); + for (output, update) in outputs.iter().repeat_clone(update) { + health_output.give( + &health_cap, + HealthStatusMessage { + index: output.output_index, + namespace: if matches!(e, ContextCreationError::Ssh(_)) { + StatusNamespace::Ssh + } else { + Self::STATUS_NAMESPACE.clone() + }, + update, + }, + ); + } + // IMPORTANT: wedge forever until the `SuspendAndRestart` is processed. + // Returning would incorrectly present to the remap operator as progress to the + // empty frontier which would be incorrectly recorded to the remap shard. + std::future::pending::<()>().await; + unreachable!("pending future never returns"); } - // IMPORTANT: wedge forever until the `SuspendAndRestart` is processed. - // Returning would incorrectly present to the remap operator as progress to the - // empty frontier which would be incorrectly recorded to the remap shard. - std::future::pending::<()>().await; - unreachable!("pending future never returns"); - } - }; + }; + + let reader_consumer = Arc::new(reader_consumer); // Note that we wait for this AFTER we downgrade to the source `resume_upper`. This // allows downstream operators (namely, the `reclock_operator`) to downgrade to the @@ -410,7 +429,6 @@ impl SourceRender for KafkaSourceConnection { let metadata_thread_handle = { let partition_info = Arc::downgrade(&partition_info); let topic = topic.clone(); - let consumer = Arc::clone(&consumer); // We want a fairly low ceiling on our polling frequency, since we rely // on this heartbeat to determine the health of our Kafka connection. @@ -439,7 +457,7 @@ impl SourceRender for KafkaSourceConnection { let probe_ts = mz_repr::Timestamp::try_from((now_fn)()).expect("must fit"); let result = fetch_partition_info( - consumer.client(), + &metadata_consumer, &topic, config .config @@ -479,7 +497,7 @@ impl SourceRender for KafkaSourceConnection { )); let ssh_status = - consumer.client().context().tunnel_status(); + metadata_consumer.client().context().tunnel_status(); let ssh_status = match ssh_status { SshTunnelStatus::Running => { Some(HealthStatusUpdate::running()) @@ -516,7 +534,7 @@ impl SourceRender for KafkaSourceConnection { source_name: config.name.clone(), id: config.id, partition_consumers: Vec::new(), - consumer: Arc::clone(&consumer), + consumer: Arc::clone(&reader_consumer), worker_id: config.worker_id, worker_count: config.worker_count, last_offsets: outputs @@ -540,7 +558,7 @@ impl SourceRender for KafkaSourceConnection { let offset_committer = KafkaResumeUpperProcessor { config: config.clone(), topic_name: topic.clone(), - consumer, + consumer: reader_consumer, progress_statistics: Arc::clone(&reader.progress_statistics), }; @@ -585,7 +603,7 @@ impl SourceRender for KafkaSourceConnection { let mut prev_offset_known = None; let mut prev_offset_committed = None; - let mut prev_pid_info: Option> = None; + let mut prev_pid_info: Option> = None; let mut snapshot_total = None; let max_wait_time = @@ -636,15 +654,15 @@ impl SourceRender for KafkaSourceConnection { // The second heuristic is whether the high watermark regressed if let Some(prev_pid_info) = prev_pid_info { - for (pid, prev_watermarks) in prev_pid_info { - let watermarks = &partitions[&pid]; - if !(prev_watermarks.high <= watermarks.high) { + for (pid, prev_high_watermark) in prev_pid_info { + let high_watermark = partitions[&pid]; + if !(prev_high_watermark <= high_watermark) { let err = DataflowError::SourceError(Box::new(SourceError { error: SourceErrorDetails::Other( format!( "topic was recreated: high watermark of \ - partition {pid} regressed from {} to {}", - prev_watermarks.high, watermarks.high + partition {pid} regressed from {} to {}", + prev_high_watermark, high_watermark ) .into(), ), @@ -668,13 +686,13 @@ impl SourceRender for KafkaSourceConnection { probe_ts, upstream_frontier: Antichain::from_elem(future_ts), }; - for (&pid, watermarks) in &partitions { + for (&pid, &high_watermark) in &partitions { probe.upstream_frontier.insert(Partitioned::new_singleton( RangeBound::exact(pid), - MzOffset::from(watermarks.high), + MzOffset::from(high_watermark), )); if responsible_for_pid(&config, pid) { - upstream_stat += watermarks.high; + upstream_stat += high_watermark; reader.ensure_partition(pid); if let Entry::Vacant(entry) = reader.partition_capabilities.entry(pid) @@ -683,14 +701,13 @@ impl SourceRender for KafkaSourceConnection { Some(&offset) => offset.try_into().unwrap(), None => 0u64, }; - let start_offset = std::cmp::max(start_offset, watermarks.low); let part_since_ts = Partitioned::new_singleton( RangeBound::exact(pid), MzOffset::from(start_offset), ); let part_upper_ts = Partitioned::new_singleton( RangeBound::exact(pid), - MzOffset::from(watermarks.high), + MzOffset::from(high_watermark), ); // This is the moment at which we have discovered a new partition @@ -1522,24 +1539,33 @@ mod tests { } } -/// Fetches the list of partitions and their corresponding high watermark -fn fetch_partition_info( - client: &Client, +/// Fetches the list of partitions and their corresponding high watermark. +fn fetch_partition_info( + consumer: &BaseConsumer, topic: &str, fetch_timeout: Duration, -) -> Result, anyhow::Error> { - let pids = get_partitions(client, topic, fetch_timeout)?; - - let mut result = BTreeMap::new(); +) -> Result, anyhow::Error> { + let pids = get_partitions(consumer.client(), topic, fetch_timeout)?; + let mut offset_requests = TopicPartitionList::with_capacity(pids.len()); for pid in pids { - let (low, high) = client.fetch_watermarks(topic, pid, fetch_timeout)?; - let watermarks = WatermarkOffsets { - low: low.try_into().expect("invalid negative offset"), - high: high.try_into().expect("invalid negative offset"), + offset_requests.add_partition_offset(topic, pid, Offset::End)?; + } + + let offset_responses = consumer.offsets_for_times(offset_requests, fetch_timeout)?; + + let mut result = BTreeMap::new(); + for entry in offset_responses.elements() { + let offset = match entry.offset() { + Offset::Offset(offset) => offset, + offset => bail!("unexpected high watermark offset: {offset:?}"), }; - result.insert(pid, watermarks); + + let pid = entry.partition(); + let watermark = offset.try_into().expect("invalid negative offset"); + result.insert(pid, watermark); } + Ok(result) } diff --git a/src/storage/src/source/postgres/replication.rs b/src/storage/src/source/postgres/replication.rs index 93c4abd9e899e..f2a772beb0364 100644 --- a/src/storage/src/source/postgres/replication.rs +++ b/src/storage/src/source/postgres/replication.rs @@ -76,7 +76,7 @@ use std::rc::Rc; use std::str::FromStr; use std::sync::Arc; use std::sync::LazyLock; -use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use bytes::Bytes; use differential_dataflow::AsCollection; @@ -172,11 +172,8 @@ pub(crate) fn render>( PostgresFlavor::Yugabyte => None, }; - let mut rewind_input = builder.new_input_for( - rewind_stream, - Exchange::new(move |_| slot_reader), - &data_output, - ); + let mut rewind_input = + builder.new_disconnected_input(rewind_stream, Exchange::new(move |_| slot_reader)); let mut slot_ready_input = builder.new_disconnected_input(slot_ready_stream, Pipeline); let mut output_uppers = table_info .iter() @@ -313,14 +310,12 @@ pub(crate) fn render>( let Some(resume_lsn) = resume_upper.into_option() else { return Ok(()); }; - data_cap_set.downgrade([&resume_lsn]); upper_cap_set.downgrade([&resume_lsn]); - trace!(%id, "timely-{worker_id} replication \ - reader started lsn={}", resume_lsn); + trace!(%id, "timely-{worker_id} replication reader started lsn={resume_lsn}"); let mut rewinds = BTreeMap::new(); while let Some(event) = rewind_input.next().await { - if let AsyncEvent::Data(cap, data) = event { + if let AsyncEvent::Data(_, data) = event { for req in data { if resume_lsn > req.snapshot_lsn + 1 { let err = DefiniteError::SlotCompactedPastResumePoint( @@ -351,7 +346,7 @@ pub(crate) fn render>( ); return Ok(()); } - rewinds.insert(req.output_index, (cap.clone(), req)); + rewinds.insert(req.output_index, req); } } } @@ -366,7 +361,7 @@ pub(crate) fn render>( &connection.publication_details.slot, &connection.publication_details.timeline_id, &connection.publication, - *data_cap_set[0].time(), + resume_lsn, committed_uppers.as_mut(), &stats_output, &stats_cap[0], @@ -408,7 +403,7 @@ pub(crate) fn render>( // if we're about to yield, which is checked at the bottom of the loop. This avoids // creating excessive progress tracking traffic when there are multiple small // transactions ready to go. - let mut data_upper = *data_cap_set[0].time(); + let mut data_upper = resume_lsn; // A stash of reusable vectors to convert from bytes::Bytes based data, which is not // compatible with `columnation`, to Vec data that is. let mut col_temp: Vec> = vec![]; @@ -469,10 +464,10 @@ pub(crate) fn render>( Err(err) => Err(err.into()), }; let mut data = (oid, output_index, event); - if let Some((data_cap, req)) = rewinds.get(&output_index) { + if let Some(req) = rewinds.get(&output_index) { if commit_lsn <= req.snapshot_lsn { let update = (data, MzOffset::from(0), -diff); - data_output.give_fueled(data_cap, &update).await; + data_output.give_fueled(&data_cap_set[0], &update).await; data = update.0; } } @@ -502,14 +497,14 @@ pub(crate) fn render>( let will_yield = stream.as_mut().peek().now_or_never().is_none(); if will_yield { + trace!(%id, "timely-{worker_id} yielding at lsn={data_upper}"); + rewinds.retain(|_, req| data_upper <= req.snapshot_lsn); + // As long as there are pending rewinds we can't downgrade our data capability + // since we must be able to produce data at offset 0. + if rewinds.is_empty() { + data_cap_set.downgrade([&data_upper]); + } upper_cap_set.downgrade([&data_upper]); - data_cap_set.downgrade([&data_upper]); - trace!( - %id, - "timely-{worker_id} yielding at lsn={}", - data_upper - ); - rewinds.retain(|_, (_, req)| data_cap_set[0].time() <= &req.snapshot_lsn); } } // We never expect the replication stream to gracefully end @@ -627,13 +622,31 @@ async fn raw_stream<'a>( let row = simple_query_opt(&metadata_client, "SHOW wal_sender_timeout;") .await? .unwrap(); - let wal_sender_timeout: &str = row.get("wal_sender_timeout").unwrap(); + let wal_sender_timeout = match row.get("wal_sender_timeout") { + // When this parameter is zero the timeout mechanism is disabled + Some("0") => None, + Some(value) => Some( + mz_repr::adt::interval::Interval::from_str(value) + .unwrap() + .duration() + .unwrap(), + ), + None => panic!("ubiquitous parameter missing"), + }; - let timeout = mz_repr::adt::interval::Interval::from_str(wal_sender_timeout) - .unwrap() - .duration() - .unwrap(); - let feedback_interval = timeout.checked_div(2).unwrap(); + // This interval controls the cadence at which we send back status updates and, crucially, + // request PrimaryKeepAlive messages. PrimaryKeepAlive messages drive the frontier forward in + // the absence of data updates and we don't want a large `wal_sender_timeout` value to slow us + // down. For this reason the feedback interval is set to one second, or less if the + // wal_sender_timeout is less than 2 seconds. + let feedback_interval = match wal_sender_timeout { + Some(t) => std::cmp::min(Duration::from_secs(1), t.checked_div(2).unwrap()), + None => Duration::from_secs(1), + }; + + let mut feedback_timer = tokio::time::interval(feedback_interval); + // 'Delay' ensures we always tick at least 'feedback_interval'. + feedback_timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); // Postgres will return all transactions that commit *at or after* after the provided LSN, // following the timely upper semantics. @@ -711,28 +724,39 @@ async fn raw_stream<'a>( Err(err)?; } - let mut last_feedback = Instant::now(); loop { - let send_feedback = tokio::select! { + tokio::select! { Some(next_message) = stream.next() => match next_message { Ok(ReplicationMessage::XLogData(data)) => { yield ReplicationMessage::XLogData(data); - Ok(last_feedback.elapsed() > feedback_interval) + Ok(()) } Ok(ReplicationMessage::PrimaryKeepAlive(keepalive)) => { - let send_feedback = keepalive.reply() == 1; yield ReplicationMessage::PrimaryKeepAlive(keepalive); - Ok(send_feedback) + Ok(()) } Err(err) => Err(err.into()), _ => Err(TransientError::UnknownReplicationMessage), }, + _ = feedback_timer.tick() => { + let ts: i64 = PG_EPOCH.elapsed().unwrap().as_micros().try_into().unwrap(); + let lsn = PgLsn::from(last_committed_upper.offset); + trace!("timely-{} ({}) sending keepalive {lsn:?}", config.worker_id, config.id); + // Postgres only sends PrimaryKeepAlive messages when *it* wants a reply, which + // happens when out status update is late. Since we send them proactively this + // may never happen. It is therefore *crucial* that we set the last parameter + // (the reply flag) to 1 here. This will cause the upstream server to send us a + // PrimaryKeepAlive message promptly which will give us frontier advancement + // information in the absence of data updates. + let res = stream.as_mut().standby_status_update(lsn, lsn, lsn, ts, 1).await; + res.map_err(|e| e.into()) + }, Some(upper) = uppers.next() => match upper.into_option() { Some(lsn) => { last_committed_upper = std::cmp::max(last_committed_upper, lsn); - Ok(true) + Ok(()) } - None => Ok(false), + None => Ok(()), }, Ok(()) = probe_rx.changed() => match &*probe_rx.borrow() { Some(Ok(probe)) => { @@ -749,22 +773,13 @@ async fn raw_stream<'a>( ); } probe_output.give(probe_cap, probe.clone()); - Ok(false) + Ok(()) }, Some(Err(err)) => Err(anyhow::anyhow!("{err}").into()), - None => Ok(false), + None => Ok(()), }, else => return }?; - if send_feedback { - let ts: i64 = PG_EPOCH.elapsed().unwrap().as_micros().try_into().unwrap(); - let lsn = PgLsn::from(last_committed_upper.offset); - stream - .as_mut() - .standby_status_update(lsn, lsn, lsn, ts, 0) - .await?; - last_feedback = Instant::now(); - } } }); Ok(Ok(stream)) diff --git a/src/storage/src/source/postgres/snapshot.rs b/src/storage/src/source/postgres/snapshot.rs index fad5025c47b04..0ac8b2d5b47a8 100644 --- a/src/storage/src/source/postgres/snapshot.rs +++ b/src/storage/src/source/postgres/snapshot.rs @@ -309,7 +309,6 @@ pub(crate) fn render>( %id, "timely-{worker_id} exporting snapshot info {snapshot_info:?}"); snapshot_handle.give(&snapshot_cap_set[0], snapshot_info); - *slot_ready_cap_set = CapabilitySet::new(); client } else { @@ -321,6 +320,7 @@ pub(crate) fn render>( ) .await? }; + *slot_ready_cap_set = CapabilitySet::new(); // Configure statement_timeout based on param. We want to be able to // override the server value here in case it's set too low, diff --git a/src/storage/src/source/reclock.rs b/src/storage/src/source/reclock.rs index fcde8b67a0aa1..2425b86bf0654 100644 --- a/src/storage/src/source/reclock.rs +++ b/src/storage/src/source/reclock.rs @@ -669,7 +669,6 @@ mod tests { }, remap_shard: Some(shard), data_shard: ShardId::new(), - status_shard: None, relation_desc: RelationDesc::empty(), txns_shard: None, }; diff --git a/src/storage/src/storage_state/async_storage_worker.rs b/src/storage/src/storage_state/async_storage_worker.rs index f1782202f2c39..557ec1037cf4d 100644 --- a/src/storage/src/storage_state/async_storage_worker.rs +++ b/src/storage/src/storage_state/async_storage_worker.rs @@ -248,8 +248,6 @@ impl AsyncStorageWorker { persist_location, remap_shard, data_shard, - // The status shard only contains non-definite status updates - status_shard: _, relation_desc, txns_shard, } = &export.storage_metadata; diff --git a/src/storage/src/upsert.rs b/src/storage/src/upsert.rs index 78b5470ac3df6..bb7789b6a4c6e 100644 --- a/src/storage/src/upsert.rs +++ b/src/storage/src/upsert.rs @@ -14,14 +14,12 @@ use std::fmt::Debug; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use differential_dataflow::consolidation; use differential_dataflow::hashable::Hashable; use differential_dataflow::{AsCollection, Collection}; use futures::future::FutureExt; use futures::StreamExt; use indexmap::map::Entry; use itertools::Itertools; -use mz_ore::cast::CastFrom; use mz_ore::error::ErrorExt; use mz_repr::{Datum, DatumVec, Diff, Row}; use mz_rocksdb::ValueIterator; @@ -781,7 +779,6 @@ where let mut snapshot_upper = Antichain::from_elem(Timestamp::minimum()); let mut stash = vec![]; - let mut legacy_errors_to_correct = vec![]; let mut error_emitter = (&mut health_output, &health_cap); @@ -813,20 +810,6 @@ where }; } - for (_, value, diff) in events.iter_mut() { - if let Err(UpsertError::Value(ref mut err)) = value { - // If we receive a legacy error in the snapshot we will keep a note of it but - // insert a non-legacy error in our state. This is so that if this error is - // ever retracted we will correctly retract the non-legacy version because by - // that time we will have emitted the error correction, which happens before - // processing any of the new source input. - if err.is_legacy_dont_touch_it { - legacy_errors_to_correct.push((err.clone(), diff.clone())); - err.is_legacy_dont_touch_it = false; - } - } - } - match state .consolidate_chunk( events.drain(..), @@ -872,33 +855,6 @@ where output_cap.downgrade(ts); } - // Now it's time to emit the error corrections. It doesn't matter at what timestamp we emit - // them at because all they do is change the representation. The error count at any - // timestamp remains constant. - upsert_metrics - .legacy_value_errors - .set(u64::cast_from(legacy_errors_to_correct.len())); - if !legacy_errors_to_correct.is_empty() { - tracing::error!( - "unexpected legacy error representation. Found {} occurences", - legacy_errors_to_correct.len() - ); - } - consolidation::consolidate(&mut legacy_errors_to_correct); - for (mut error, diff) in legacy_errors_to_correct { - assert!( - error.is_legacy_dont_touch_it, - "attempted to correct non-legacy error" - ); - tracing::info!("correcting legacy error {error:?} with diff {diff}"); - let time = output_cap.time().clone(); - let retraction = Err(UpsertError::Value(error.clone())); - error.is_legacy_dont_touch_it = false; - let insertion = Err(UpsertError::Value(error)); - output_handle.give(&output_cap, (retraction, time.clone(), -diff)); - output_handle.give(&output_cap, (insertion, time, diff)); - } - tracing::info!( "timely-{} upsert source {} finished rehydration", source_config.worker_id, diff --git a/src/storage/src/upsert_continual_feedback.rs b/src/storage/src/upsert_continual_feedback.rs index daefc9c069129..60ca105ea7bf7 100644 --- a/src/storage/src/upsert_continual_feedback.rs +++ b/src/storage/src/upsert_continual_feedback.rs @@ -17,12 +17,10 @@ use std::collections::HashMap; use std::fmt::Debug; use std::sync::Arc; -use differential_dataflow::consolidation; use differential_dataflow::hashable::Hashable; use differential_dataflow::{AsCollection, Collection}; use indexmap::map::Entry; use itertools::Itertools; -use mz_ore::cast::CastFrom; use mz_ore::vec::VecExt; use mz_repr::{Diff, Row}; use mz_storage_types::errors::{DataflowError, EnvelopeError, UpsertError}; @@ -224,7 +222,6 @@ where let mut output_updates = vec![]; let mut error_emitter = (&mut health_output, &health_cap); - let mut legacy_errors_to_correct = vec![]; loop { @@ -275,26 +272,9 @@ where ?persist_upper, "ingesting persist snapshot chunk"); - let persist_stash_iter = persist_stash.drain(..); - - // Also collect any legacy errors as they're flying by. - let persist_stash_iter = persist_stash_iter.map(|(key, mut val, _ts, diff)| { - if let Err(UpsertError::Value(ref mut err)) = val { - // If we receive a legacy error in the snapshot we - // will keep a note of it but insert a non-legacy - // error in our state. This is so that if this error - // is ever retracted we will correctly retract the - // non-legacy version because by that time we will - // have emitted the error correction, which happens - // before processing any of the new source input. - if err.is_legacy_dont_touch_it { - legacy_errors_to_correct.push((err.clone(), diff.clone())); - err.is_legacy_dont_touch_it = false; - } - } - - (key, val, diff) - }); + let persist_stash_iter = persist_stash + .drain(..) + .map(|(key, val, _ts, diff)| (key, val, diff)); match state .consolidate_chunk( @@ -321,36 +301,6 @@ where if last_rehydration_chunk { hydrating = false; - // Now it's time to emit the error corrections. It doesn't matter at what timestamp we emit - // them at because all they do is change the representation. The error count at any - // timestamp remains constant. - upsert_metrics - .legacy_value_errors - .set(u64::cast_from(legacy_errors_to_correct.len())); - if !legacy_errors_to_correct.is_empty() { - tracing::error!( - "unexpected legacy error representation. Found {} occurences", - legacy_errors_to_correct.len() - ); - } - consolidation::consolidate(&mut legacy_errors_to_correct); - for (mut error, diff) in legacy_errors_to_correct.drain(..) { - assert!( - error.is_legacy_dont_touch_it, - "attempted to correct non-legacy error" - ); - tracing::info!( - worker_id = %source_config.worker_id, - source_id = %source_config.id, - "correcting legacy error {error:?} with diff {diff}"); - let time = output_cap.time().clone(); - let retraction = Err(UpsertError::Value(error.clone())); - error.is_legacy_dont_touch_it = false; - let insertion = Err(UpsertError::Value(error)); - output_handle.give(&output_cap, (retraction, time.clone(), -diff)); - output_handle.give(&output_cap, (insertion, time, diff)); - } - tracing::info!( worker_id = %source_config.worker_id, source_id = %source_config.id, diff --git a/src/testdrive/BUILD.bazel b/src/testdrive/BUILD.bazel index f8fc1bcf74fed..8122ba780a13b 100644 --- a/src/testdrive/BUILD.bazel +++ b/src/testdrive/BUILD.bazel @@ -29,7 +29,7 @@ rust_library( proc_macro_deps = [] + all_crate_deps(proc_macro = True), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ ":mz_testdrive_build_script", "//src/adapter:mz_adapter", @@ -74,7 +74,7 @@ rust_test( ), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ "//src/adapter:mz_adapter", "//src/avro:mz_avro", @@ -163,7 +163,7 @@ rust_binary( proc_macro_deps = [] + all_crate_deps(proc_macro = True), rustc_env = {}, rustc_flags = [], - version = "0.126.0-dev.0", + version = "0.127.0-dev.0", deps = [ ":mz_testdrive", "//src/adapter:mz_adapter", diff --git a/src/testdrive/Cargo.toml b/src/testdrive/Cargo.toml index 5d3fd13b17313..09f3bce4fe6ae 100644 --- a/src/testdrive/Cargo.toml +++ b/src/testdrive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mz-testdrive" description = "Integration test driver for Materialize." -version = "0.126.0-dev.0" +version = "0.127.0-dev.0" edition.workspace = true rust-version.workspace = true publish = false diff --git a/src/transform/src/analysis.rs b/src/transform/src/analysis.rs index 337badbebb926..d750d9ade27f4 100644 --- a/src/transform/src/analysis.rs +++ b/src/transform/src/analysis.rs @@ -102,14 +102,18 @@ pub mod common { impl Derived { /// Return the analysis results derived so far. - pub fn results(&self) -> Option<&[A::Value]> { + /// + /// # Panics + /// + /// This method panics if `A` was not installed as a required analysis. + pub fn results(&self) -> &[A::Value] { let type_id = TypeId::of::>(); if let Some(bundle) = self.analyses.get(&type_id) { if let Some(bundle) = bundle.as_any().downcast_ref::>() { - return Some(&bundle.results[..]); + return &bundle.results[..]; } } - None + panic!("Analysis {:?} missing", std::any::type_name::()); } /// Bindings from local identifiers to result offsets for analysis values. pub fn bindings(&self) -> &BTreeMap { @@ -127,7 +131,7 @@ pub mod common { start: usize, count: usize, ) -> impl Iterator + 'a { - let sizes = self.results::().expect("SubtreeSize missing"); + let sizes = self.results::(); let offset = 1; (0..count).scan(offset, move |offset, _| { let result = start - *offset; @@ -141,10 +145,7 @@ pub mod common { DerivedView { derived: self, lower: 0, - upper: self - .results::() - .expect("SubtreeSize missing") - .len(), + upper: self.results::().len(), } } } @@ -166,7 +167,7 @@ pub mod common { impl<'a> DerivedView<'a> { /// The value associated with the expression. pub fn value(&self) -> Option<&'a A::Value> { - self.results::().and_then(|slice| slice.last()) + self.results::().last() } /// The post-order traversal index for the expression. @@ -185,16 +186,18 @@ pub mod common { self.derived .bindings .get(&id) - .and_then(|index| self.derived.results::().and_then(|r| r.get(*index))) + .and_then(|index| self.derived.results::().get(*index)) } /// The results for expression and its children. /// /// The results for the expression itself will be the last element. - pub fn results(&self) -> Option<&'a [A::Value]> { - self.derived - .results::() - .map(|slice| &slice[self.lower..self.upper]) + /// + /// # Panics + /// + /// This method panics if `A` was not installed as a required analysis. + pub fn results(&self) -> &'a [A::Value] { + &self.derived.results::()[self.lower..self.upper] } /// Bindings from local identifiers to result offsets for analysis values. @@ -221,7 +224,7 @@ pub mod common { // Repeatedly read out the last element, then peel off that many elements. // Each extracted slice corresponds to a child of the current expression. // We should end cleanly with an empty slice, otherwise there is an issue. - let sizes = self.results::().expect("SubtreeSize missing"); + let sizes = self.results::(); let sizes = &sizes[..sizes.len() - 1]; let offset = self.lower; @@ -718,7 +721,7 @@ mod unique_keys { keys } _ => { - let arity = depends.results::().unwrap(); + let arity = depends.results::(); expr.keys_with_input_keys( offsets.iter().map(|o| arity[*o]), offsets.iter().map(|o| &results[*o]), @@ -1196,16 +1199,18 @@ mod column_names { } mod explain { - //! Derived attributes framework and definitions. + //! Derived Analysis framework and definitions. use std::collections::BTreeMap; use mz_expr::explain::ExplainContext; use mz_expr::MirRelationExpr; use mz_ore::stack::RecursionLimitError; - use mz_repr::explain::{AnnotatedPlan, Attributes}; + use mz_repr::explain::{Analyses, AnnotatedPlan}; + + use crate::analysis::equivalences::Equivalences; - // Attributes should have shortened paths when exported. + // Analyses should have shortened paths when exported. use super::DerivedBuilder; impl<'c> From<&ExplainContext<'c>> for DerivedBuilder<'c> { @@ -1234,24 +1239,27 @@ mod explain { if context.config.column_names || context.config.humanized_exprs { builder.require(super::ColumnNames); } + if context.config.equivalences { + builder.require(Equivalences); + } builder } } /// Produce an [`AnnotatedPlan`] wrapping the given [`MirRelationExpr`] along - /// with [`Attributes`] derived from the given context configuration. + /// with [`Analyses`] derived from the given context configuration. pub fn annotate_plan<'a>( plan: &'a MirRelationExpr, context: &'a ExplainContext, ) -> Result, RecursionLimitError> { - let mut annotations = BTreeMap::<&MirRelationExpr, Attributes>::default(); + let mut annotations = BTreeMap::<&MirRelationExpr, Analyses>::default(); let config = context.config; - // We want to annotate the plan with attributes in the following cases: - // 1. An attribute was explicitly requested in the ExplainConfig. + // We want to annotate the plan with analyses in the following cases: + // 1. An Analysis was explicitly requested in the ExplainConfig. // 2. Humanized expressions were requested in the ExplainConfig (in which - // case we need to derive the ColumnNames attribute). - if config.requires_attributes() || config.humanized_exprs { + // case we need to derive the ColumnNames Analysis). + if config.requires_analyses() || config.humanized_exprs { // get the annotation keys let subtree_refs = plan.post_order_vec(); // get the annotation values @@ -1261,76 +1269,86 @@ mod explain { if config.subtree_size { for (expr, subtree_size) in std::iter::zip( subtree_refs.iter(), - derived.results::().unwrap().into_iter(), + derived.results::().into_iter(), ) { - let attrs = annotations.entry(expr).or_default(); - attrs.subtree_size = Some(*subtree_size); + let analyses = annotations.entry(expr).or_default(); + analyses.subtree_size = Some(*subtree_size); } } if config.non_negative { for (expr, non_negative) in std::iter::zip( subtree_refs.iter(), - derived.results::().unwrap().into_iter(), + derived.results::().into_iter(), ) { - let attrs = annotations.entry(expr).or_default(); - attrs.non_negative = Some(*non_negative); + let analyses = annotations.entry(expr).or_default(); + analyses.non_negative = Some(*non_negative); } } if config.arity { for (expr, arity) in std::iter::zip( subtree_refs.iter(), - derived.results::().unwrap().into_iter(), + derived.results::().into_iter(), ) { - let attrs = annotations.entry(expr).or_default(); - attrs.arity = Some(*arity); + let analyses = annotations.entry(expr).or_default(); + analyses.arity = Some(*arity); } } if config.types { for (expr, types) in std::iter::zip( subtree_refs.iter(), - derived - .results::() - .unwrap() - .into_iter(), + derived.results::().into_iter(), ) { - let attrs = annotations.entry(expr).or_default(); - attrs.types = Some(types.clone()); + let analyses = annotations.entry(expr).or_default(); + analyses.types = Some(types.clone()); } } if config.keys { for (expr, keys) in std::iter::zip( subtree_refs.iter(), - derived.results::().unwrap().into_iter(), + derived.results::().into_iter(), ) { - let attrs = annotations.entry(expr).or_default(); - attrs.keys = Some(keys.clone()); + let analyses = annotations.entry(expr).or_default(); + analyses.keys = Some(keys.clone()); } } if config.cardinality { for (expr, card) in std::iter::zip( subtree_refs.iter(), - derived.results::().unwrap().into_iter(), + derived.results::().into_iter(), ) { - let attrs = annotations.entry(expr).or_default(); - attrs.cardinality = Some(card.to_string()); + let analyses = annotations.entry(expr).or_default(); + analyses.cardinality = Some(card.to_string()); } } if config.column_names || config.humanized_exprs { for (expr, column_names) in std::iter::zip( subtree_refs.iter(), - derived.results::().unwrap().into_iter(), + derived.results::().into_iter(), ) { - let attrs = annotations.entry(expr).or_default(); + let analyses = annotations.entry(expr).or_default(); let value = column_names .iter() .map(|column_name| column_name.humanize(context.humanizer)) .collect(); - attrs.column_names = Some(value); + analyses.column_names = Some(value); + } + } + + if config.equivalences { + for (expr, equivs) in std::iter::zip( + subtree_refs.iter(), + derived.results::().into_iter(), + ) { + let analyses = annotations.entry(expr).or_default(); + analyses.equivalences = Some(match equivs.as_ref() { + Some(equivs) => equivs.to_string(), + None => "".to_string(), + }); } } } @@ -1339,7 +1357,7 @@ mod explain { } } -/// Definition and helper structs for the [`Cardinality`] attribute. +/// Definition and helper structs for the [`Cardinality`] Analysis. mod cardinality { use std::collections::{BTreeMap, BTreeSet}; @@ -1793,18 +1811,9 @@ mod cardinality { ) -> Self::Value { use MirRelationExpr::*; - let sizes = depends - .as_view() - .results::() - .expect("SubtreeSize analysis results missing"); - let arity = depends - .as_view() - .results::() - .expect("Arity analysis results missing"); - let keys = depends - .as_view() - .results::() - .expect("UniqueKeys analysis results missing"); + let sizes = depends.as_view().results::(); + let arity = depends.as_view().results::(); + let keys = depends.as_view().results::(); match expr { Constant { rows, .. } => { @@ -1842,7 +1851,7 @@ mod cardinality { } Filter { predicates, .. } => { let input = results[index - 1]; - let keys = depends.results::().expect("UniqueKeys missing"); + let keys = depends.results::(); let keys = &keys[index - 1]; self.filter(predicates, keys, input) } diff --git a/src/transform/src/analysis/equivalences.rs b/src/transform/src/analysis/equivalences.rs index e8575b01515d8..dabd1e96608f2 100644 --- a/src/transform/src/analysis/equivalences.rs +++ b/src/transform/src/analysis/equivalences.rs @@ -16,8 +16,10 @@ //! equivalences classes, each a list of equivalent expressions. use std::collections::BTreeMap; +use std::fmt::Formatter; use mz_expr::{Id, MirRelationExpr, MirScalarExpr}; +use mz_ore::str::{bracketed, separated}; use mz_repr::{ColumnType, Datum}; use crate::analysis::{Analysis, Lattice}; @@ -145,7 +147,7 @@ impl Analysis for Equivalences { // introduce equivalences for new columns and expressions that define them. let mut equivalences = results.get(index - 1).unwrap().clone(); if let Some(equivalences) = &mut equivalences { - let input_arity = depends.results::().unwrap()[index - 1]; + let input_arity = depends.results::()[index - 1]; for (pos, expr) in scalars.iter().enumerate() { equivalences .classes @@ -175,7 +177,7 @@ impl Analysis for Equivalences { .collect::>(); children.reverse(); - let arity = depends.results::().unwrap(); + let arity = depends.results::(); let mut columns = 0; let mut result = Some(EquivalenceClasses::default()); for child in children.into_iter() { @@ -204,7 +206,7 @@ impl Analysis for Equivalences { aggregates, .. } => { - let input_arity = depends.results::().unwrap()[index - 1]; + let input_arity = depends.results::()[index - 1]; let mut equivalences = results.get(index - 1).unwrap().clone(); if let Some(equivalences) = &mut equivalences { // Introduce keys column equivalences as if a map, then project to those columns. @@ -266,7 +268,7 @@ impl Analysis for Equivalences { MirRelationExpr::ArrangeBy { .. } => results.get(index - 1).unwrap().clone(), }; - let expr_type = depends.results::().unwrap()[index].as_ref(); + let expr_type = depends.results::()[index].as_ref(); equivalences .as_mut() .map(|e| e.minimize(expr_type.map(|x| &x[..]))); @@ -320,7 +322,7 @@ pub struct EquivalenceClasses { /// The first element should be the "canonical" simplest element, that any other element /// can be replaced by. /// These classes are unified whenever possible, to minimize the number of classes. - /// They are only guaranteed to form an equivalence relation after a call to `minimimize`, + /// They are only guaranteed to form an equivalence relation after a call to `minimize`, /// which refreshes both `self.classes` and `self.remap`. pub classes: Vec>, @@ -336,6 +338,17 @@ pub struct EquivalenceClasses { remap: BTreeMap, } +impl std::fmt::Display for EquivalenceClasses { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + // Only show `classes`. + let classes = self + .classes + .iter() + .map(|class| format!("{}", bracketed("[", "]", separated(", ", class)))); + write!(f, "{}", bracketed("[", "]", separated(", ", classes))) + } +} + impl EquivalenceClasses { /// Comparator function for the complexity of scalar expressions. Simpler expressions are /// smaller. Can be used when we need to decide which of several equivalent expressions to use. diff --git a/src/transform/src/equivalence_propagation.rs b/src/transform/src/equivalence_propagation.rs index cf685dbc5a76c..e54666e946aae 100644 --- a/src/transform/src/equivalence_propagation.rs +++ b/src/transform/src/equivalence_propagation.rs @@ -529,8 +529,13 @@ impl EquivalencePropagation { .expect("RelationType required"); let reducer = input_equivalences.reducer(); if let Some(expr) = limit { + let old_expr = expr.clone(); reducer.reduce_expr(expr); + let acceptable_sub = literal_domination(&old_expr, expr); expr.reduce(input_types.as_ref().unwrap()); + if !acceptable_sub && !literal_domination(&old_expr, expr) { + expr.clone_from(&old_expr); + } } } diff --git a/src/transform/src/lib.rs b/src/transform/src/lib.rs index bfba72c0c8e96..5f05a68b8c632 100644 --- a/src/transform/src/lib.rs +++ b/src/transform/src/lib.rs @@ -552,9 +552,11 @@ pub fn fuse_and_collapse_fixpoint() -> Fixpoint { } } -/// Does constant folding idempotently. This needs to call `FoldConstants` together with -/// `NormalizeLets` in a fixpoint loop, because currently `FoldConstants` doesn't inline CTEs, so -/// these two need to alternate until fixpoint. +/// Does constant folding to a fixpoint: An expression all of whose leaves are constants, of size +/// small enough to be inlined and folded should reach a single `MirRelationExpr::Constant`. +/// +/// This needs to call `FoldConstants` together with `NormalizeLets` in a fixpoint loop, because +/// currently `FoldConstants` doesn't inline CTEs, so these two need to alternate until fixpoint. /// /// Also note that `FoldConstants` can break the normalized form by removing all references to a /// Let. @@ -819,6 +821,15 @@ impl Optimizer { } } + /// Builds a tiny optimizer, which just folds constants. For more details, see + /// [fold_constants_fixpoint]. + pub fn constant_optimizer(_ctx: &mut TransformCtx) -> Self { + Self { + name: "fast_path_optimizer", + transforms: vec![Box::new(fold_constants_fixpoint())], + } + } + /// Optimizes the supplied relation expression. /// /// These optimizations are performed with no information about available arrangements, diff --git a/src/transform/tests/test_runner.rs b/src/transform/tests/test_runner.rs index 1460ef34c64b6..53bc76efa8798 100644 --- a/src/transform/tests/test_runner.rs +++ b/src/transform/tests/test_runner.rs @@ -141,6 +141,7 @@ mod tests { raw_plans: false, raw_syntax: false, subtree_size: false, + equivalences: false, timing: false, types: format_contains("types"), ..ExplainConfig::default() diff --git a/src/workspace-hack/Cargo.toml b/src/workspace-hack/Cargo.toml index 1ea94b0420e7a..5181ae105d9c4 100644 --- a/src/workspace-hack/Cargo.toml +++ b/src/workspace-hack/Cargo.toml @@ -57,7 +57,9 @@ futures-util = { version = "0.3.30", features = ["channel", "io", "sink"] } getrandom = { version = "0.2.10", default-features = false, features = ["std"] } globset = { version = "0.4.14", features = ["serde1"] } hashbrown = { version = "0.14.5", features = ["raw"] } -hyper = { version = "0.14.27", features = ["client", "http1", "http2", "stream", "tcp"] } +hyper-582f2526e08bb6a0 = { package = "hyper", version = "0.14.27", features = ["client", "http1", "http2", "stream", "tcp"] } +hyper-dff4ba8e3ae991db = { package = "hyper", version = "1.4.1", features = ["client", "http1", "http2", "server"] } +hyper-util = { version = "0.1.6", features = ["client-legacy", "server-auto", "service"] } indexmap = { version = "1.9.1", default-features = false, features = ["std"] } insta = { version = "1.33.0", features = ["json"] } k8s-openapi = { version = "0.22.0", default-features = false, features = ["schemars", "v1_29"] } @@ -82,6 +84,7 @@ num-bigint = { version = "0.4.3" } num-integer = { version = "0.1.46", features = ["i128"] } num-traits = { version = "0.2.15", features = ["i128", "libm"] } openssl = { version = "0.10.66", features = ["vendored"] } +openssl-sys = { version = "0.9.103", default-features = false, features = ["vendored"] } ordered-float = { version = "4.2.0", features = ["serde"] } parking_lot = { version = "0.12.1", features = ["send_guard"] } parquet = { version = "51.0.0", default-features = false, features = ["arrow", "brotli", "flate2", "lz4", "snap", "zstd"] } @@ -184,7 +187,9 @@ futures-util = { version = "0.3.30", features = ["channel", "io", "sink"] } getrandom = { version = "0.2.10", default-features = false, features = ["std"] } globset = { version = "0.4.14", features = ["serde1"] } hashbrown = { version = "0.14.5", features = ["raw"] } -hyper = { version = "0.14.27", features = ["client", "http1", "http2", "stream", "tcp"] } +hyper-582f2526e08bb6a0 = { package = "hyper", version = "0.14.27", features = ["client", "http1", "http2", "stream", "tcp"] } +hyper-dff4ba8e3ae991db = { package = "hyper", version = "1.4.1", features = ["client", "http1", "http2", "server"] } +hyper-util = { version = "0.1.6", features = ["client-legacy", "server-auto", "service"] } indexmap = { version = "1.9.1", default-features = false, features = ["std"] } insta = { version = "1.33.0", features = ["json"] } k8s-openapi = { version = "0.22.0", default-features = false, features = ["schemars", "v1_29"] } @@ -209,6 +214,7 @@ num-bigint = { version = "0.4.3" } num-integer = { version = "0.1.46", features = ["i128"] } num-traits = { version = "0.2.15", features = ["i128", "libm"] } openssl = { version = "0.10.66", features = ["vendored"] } +openssl-sys = { version = "0.9.103", default-features = false, features = ["vendored"] } ordered-float = { version = "4.2.0", features = ["serde"] } parking_lot = { version = "0.12.1", features = ["send_guard"] } parquet = { version = "51.0.0", default-features = false, features = ["arrow", "brotli", "flate2", "lz4", "snap", "zstd"] } @@ -271,25 +277,23 @@ zstd-sys = { version = "2.0.9", features = ["std"] } [target.x86_64-unknown-linux-gnu.dependencies] bitflags = { version = "2.4.1", default-features = false, features = ["std"] } camino = { version = "1.1.7", default-features = false, features = ["serde1"] } -hyper = { version = "0.14.27", default-features = false, features = ["runtime"] } +hyper-582f2526e08bb6a0 = { package = "hyper", version = "0.14.27", default-features = false, features = ["runtime"] } native-tls = { version = "0.2.11", default-features = false, features = ["vendored"] } -openssl-sys = { version = "0.9.103", default-features = false, features = ["vendored"] } pathdiff = { version = "0.2.1", default-features = false, features = ["camino"] } ring = { version = "0.17.7", features = ["std"] } [target.x86_64-unknown-linux-gnu.build-dependencies] bitflags = { version = "2.4.1", default-features = false, features = ["std"] } camino = { version = "1.1.7", default-features = false, features = ["serde1"] } -hyper = { version = "0.14.27", default-features = false, features = ["runtime"] } +hyper-582f2526e08bb6a0 = { package = "hyper", version = "0.14.27", default-features = false, features = ["runtime"] } native-tls = { version = "0.2.11", default-features = false, features = ["vendored"] } -openssl-sys = { version = "0.9.103", default-features = false, features = ["vendored"] } pathdiff = { version = "0.2.1", default-features = false, features = ["camino"] } ring = { version = "0.17.7", features = ["std"] } [target.x86_64-apple-darwin.dependencies] bitflags = { version = "2.4.1", default-features = false, features = ["std"] } camino = { version = "1.1.7", default-features = false, features = ["serde1"] } -hyper = { version = "0.14.27", default-features = false, features = ["runtime"] } +hyper-582f2526e08bb6a0 = { package = "hyper", version = "0.14.27", default-features = false, features = ["runtime"] } native-tls = { version = "0.2.11", default-features = false, features = ["vendored"] } pathdiff = { version = "0.2.1", default-features = false, features = ["camino"] } ring = { version = "0.17.7", features = ["std"] } @@ -298,7 +302,7 @@ security-framework = { version = "2.7.0", features = ["alpn"] } [target.x86_64-apple-darwin.build-dependencies] bitflags = { version = "2.4.1", default-features = false, features = ["std"] } camino = { version = "1.1.7", default-features = false, features = ["serde1"] } -hyper = { version = "0.14.27", default-features = false, features = ["runtime"] } +hyper-582f2526e08bb6a0 = { package = "hyper", version = "0.14.27", default-features = false, features = ["runtime"] } native-tls = { version = "0.2.11", default-features = false, features = ["vendored"] } pathdiff = { version = "0.2.1", default-features = false, features = ["camino"] } ring = { version = "0.17.7", features = ["std"] } diff --git a/test/0dt/mzcompose.py b/test/0dt/mzcompose.py index 83d01e9b59a2c..8f73067136885 100644 --- a/test/0dt/mzcompose.py +++ b/test/0dt/mzcompose.py @@ -962,7 +962,7 @@ def workflow_kafka_source_rehydration(c: Composition) -> None: duration = time.time() - start_time assert result[0][0] == count * repeats, f"Wrong result: {result}" assert ( - duration < 2 + duration < 4 ), f"Took {duration}s to SELECT on Kafka source after 0dt upgrade, is it hydrated?" @@ -1076,7 +1076,7 @@ def workflow_pg_source_rehydration(c: Composition) -> None: duration = time.time() - start_time assert result[0][0] == count * repeats, f"Wrong result: {result}" assert ( - duration < 2 + duration < 4 ), f"Took {duration}s to SELECT on Postgres source after 0dt upgrade, is it hydrated?" @@ -1191,7 +1191,7 @@ def workflow_mysql_source_rehydration(c: Composition) -> None: duration = time.time() - start_time assert result[0][0] == count * repeats, f"Wrong result: {result}" assert ( - duration < 2 + duration < 4 ), f"Took {duration}s to SELECT on MySQL source after 0dt upgrade, is it hydrated?" diff --git a/test/balancerd/mzcompose.py b/test/balancerd/mzcompose.py index 9c2faec0794c4..055563be957a9 100644 --- a/test/balancerd/mzcompose.py +++ b/test/balancerd/mzcompose.py @@ -196,17 +196,35 @@ def workflow_plaintext(c: Composition) -> None: """Test plaintext internal connections""" c.down(destroy_volumes=True) with c.override( + Materialized( + options=[ + # Enable TLS on the public port to verify that balancerd is connecting to the balancerd + # port. + "--tls-mode=disable", + f"--frontegg-tenant={TENANT_ID}", + "--frontegg-jwk-file=/secrets/frontegg-mock.crt", + f"--frontegg-api-token-url={FRONTEGG_URL}/identity/resources/auth/v1/api-token", + f"--frontegg-admin-role={ADMIN_ROLE}", + ], + # We do not do anything interesting on the Mz side + # to justify the extra restarts + sanity_restart=False, + depends_on=["test-certs"], + volumes_extra=[ + "secrets:/secrets", + ], + ), Balancerd( command=[ "service", "--pgwire-listen-addr=0.0.0.0:6875", "--https-listen-addr=0.0.0.0:6876", "--internal-http-listen-addr=0.0.0.0:6878", - "--frontegg-resolver-template=materialized:6880", + "--frontegg-resolver-template=materialized:6875", "--frontegg-jwk-file=/secrets/frontegg-mock.crt", f"--frontegg-api-token-url={FRONTEGG_URL}/identity/resources/auth/v1/api-token", f"--frontegg-admin-role={ADMIN_ROLE}", - "--https-resolver-template=materialized:6881", + "--https-resolver-template=materialized:6876", "--tls-key=/secrets/balancerd.key", "--tls-cert=/secrets/balancerd.crt", "--default-config=balancerd_inject_proxy_protocol_header_http=true", diff --git a/test/cluster/blue-green-deployment/deploy.td b/test/cluster/blue-green-deployment/deploy.td index c7b29a1c113b8..7139778e881d3 100644 --- a/test/cluster/blue-green-deployment/deploy.td +++ b/test/cluster/blue-green-deployment/deploy.td @@ -52,7 +52,7 @@ ), -- Collect ready dataflows. -- For a dataflow to be ready it must be hydrated and caught up. - -- We define a dataflow to be caught up if its local lag is less than 2 seconds. + -- We define a dataflow to be caught up if its local lag is less than 4 seconds. ready_dataflows AS ( SELECT id FROM dataflows d @@ -62,7 +62,7 @@ LEFT JOIN mz_internal.mz_materialization_lag l ON (l.object_id = d.id) WHERE h.hydrated AND - (l.local_lag <= '2s' OR l.local_lag IS NULL) + (l.local_lag <= '4s' OR l.local_lag IS NULL) ), -- Collect dataflows that are not yet ready. pending_dataflows AS ( diff --git a/test/cluster/blue-green-deployment/setup.td b/test/cluster/blue-green-deployment/setup.td index 81266fa70fd6b..db2ab4f5aab7b 100644 --- a/test/cluster/blue-green-deployment/setup.td +++ b/test/cluster/blue-green-deployment/setup.td @@ -88,7 +88,7 @@ ALTER SYSTEM SET enable_unorchestrated_cluster_replicas = true; ), -- Collect ready dataflows. -- For a dataflow to be ready it must be hydrated and caught up. - -- We define a dataflow to be caught up if its local lag is less than 2 seconds. + -- We define a dataflow to be caught up if its local lag is less than 4 seconds. ready_dataflows AS ( SELECT id FROM dataflows d @@ -98,7 +98,7 @@ ALTER SYSTEM SET enable_unorchestrated_cluster_replicas = true; LEFT JOIN mz_internal.mz_materialization_lag l ON (l.object_id = d.id) WHERE h.hydrated AND - (l.local_lag <= '2s' OR l.local_lag IS NULL) + (l.local_lag <= '4s' OR l.local_lag IS NULL) ), -- Collect dataflows that are not yet ready. pending_dataflows AS ( diff --git a/test/cluster/mzcompose.py b/test/cluster/mzcompose.py index 48bb7fc028ab0..3c31f695025e4 100644 --- a/test/cluster/mzcompose.py +++ b/test/cluster/mzcompose.py @@ -2877,17 +2877,17 @@ def fetch_metrics() -> Metrics: # mz_optimizer_e2e_optimization_time_seconds time = metrics.get_e2e_optimization_time("view") - assert 0 < time < 1, f"got {time}" + assert 0 < time < 10, f"got {time}" time = metrics.get_e2e_optimization_time("index") - assert 0 < time < 1, f"got {time}" + assert 0 < time < 10, f"got {time}" time = metrics.get_e2e_optimization_time("materialized_view") - assert 0 < time < 1, f"got {time}" + assert 0 < time < 10, f"got {time}" time = metrics.get_e2e_optimization_time("peek:fast_path") - assert 0 < time < 1, f"got {time}" + assert 0 < time < 10, f"got {time}" time = metrics.get_e2e_optimization_time("peek:slow_path") - assert 0 < time < 1, f"got {time}" + assert 0 < time < 10, f"got {time}" time = metrics.get_e2e_optimization_time("subscribe") - assert 0 < time < 1, f"got {time}" + assert 0 < time < 10, f"got {time}" def workflow_test_metrics_retention_across_restart(c: Composition) -> None: diff --git a/test/cluster/statement-logging/statement-logging.td b/test/cluster/statement-logging/statement-logging.td index 9fd9476224923..728614e961bbb 100644 --- a/test/cluster/statement-logging/statement-logging.td +++ b/test/cluster/statement-logging/statement-logging.td @@ -110,7 +110,7 @@ serializable # (2) We don't have a way to log errors that happen during statement preparation, # but only during statement execution. # (3) SQL-level statements always use a prepared statement with a useless name beginning with 's' -# (4) `FETCH` has the somewhat confusing behavior of reporting the `rows_returned` +# (4) `FETCH` has the somewhat confusing behavior of reporting the `result_size` and `rows_returned` # of the *total* result set in the first `FETCH`, and null thereafter. # # All of these (except (3), which is just how the postgres library TD uses works) are working as designed. @@ -121,31 +121,31 @@ serializable > WITH all_stmts AS (SELECT mseh.*, mpsh.*, mst.sql, mst.redacted_sql FROM mz_internal.mz_statement_execution_history mseh RIGHT JOIN mz_internal.mz_prepared_statement_history mpsh ON mseh.prepared_statement_id = mpsh.id RIGHT JOIN (SELECT DISTINCT sql_hash, sql, redacted_sql FROM mz_internal.mz_sql_text) mst ON mpsh.sql_hash = mst.sql_hash), test_begin AS (SELECT began_at FROM all_stmts WHERE sql = 'SELECT ''beginning real test!''' ORDER BY began_at DESC LIMIT 1) - SELECT c.name, all_stmts.cluster_name, all_stmts.application_name, all_stmts.sample_rate, all_stmts.params, all_stmts.finished_status, all_stmts.error_message, all_stmts.rows_returned, all_stmts.execution_strategy, all_stmts.name LIKE 's%', all_stmts.sql, all_stmts.transaction_isolation, all_stmts.transient_index_id ~ '^t[0-9]+$', + SELECT c.name, all_stmts.cluster_name, all_stmts.application_name, all_stmts.sample_rate, all_stmts.params, all_stmts.finished_status, all_stmts.error_message, all_stmts.result_size, all_stmts.rows_returned, all_stmts.execution_strategy, all_stmts.name LIKE 's%', all_stmts.sql, all_stmts.transaction_isolation, all_stmts.transient_index_id ~ '^t[0-9]+$', all_stmts.database_name, all_stmts.search_path::text FROM all_stmts, test_begin LEFT JOIN mz_clusters c ON c.id = all_stmts.cluster_id WHERE all_stmts.began_at >= test_begin.began_at AND all_stmts.sql NOT LIKE '%sduiahsdfuoiahsdf%' - my_app 1 {} success true "SET transaction_isolation TO serializable" "strict serializable" materialize {public} - c my_app 1 {} success 1 standard true "SELECT count(*) FROM t" "strict serializable" true materialize {public} - c my_app 1 {} success true "DROP CLUSTER c" "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} error "Evaluation error: division by zero" true "SELECT f/0 FROM t" "strict serializable" true materialize {public} -quickstart quickstart my_app 1 {} success 1 constant true "EXECUTE p('hello world')" "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} success 1 fast-path true "SELECT * FROM t" "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} success 1 standard true "SELECT count(*) FROM t" "strict serializable" true materialize {public} -quickstart quickstart my_app 1 {} success 2 constant true "FETCH c" "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} success true BEGIN "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} success true COMMIT "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} success true "CREATE CLUSTER c REPLICAS (r1 (size '1'))" "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} success true "CREATE DEFAULT INDEX i ON t" "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} success true "CREATE TABLE t(f int)" "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} success true "DECLARE c CURSOR FOR VALUES (1), (2)" "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} success true "FETCH c" "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} success true "FETCH c" "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} success true "INSERT INTO t VALUES (1)" "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} success true "PREPARE p AS values ($1)" "strict serializable" materialize {public} -quickstart quickstart my_app 1 {} success true "SET cluster TO c" "strict serializable" materialize {public} -mz_catalog_server mz_catalog_server my_app 1 {} success 1 constant true "SELECT 'beginning real test!'" "strict serializable" materialize {public} -mz_catalog_server mz_catalog_server my_app 1 {} success 1 constant true "SELECT 'serializable'" serializable materialize {public} -mz_catalog_server mz_catalog_server my_app 1 {} success 1 standard true "SELECT count(*) > 0 FROM mz_internal.mz_cluster_replica_metrics" "strict serializable" true materialize {public} + my_app 1 {} success true "SET transaction_isolation TO serializable" "strict serializable" materialize {public} + c my_app 1 {} success 18 1 standard true "SELECT count(*) FROM t" "strict serializable" true materialize {public} + c my_app 1 {} success true "DROP CLUSTER c" "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} error "Evaluation error: division by zero" true "SELECT f/0 FROM t" "strict serializable" true materialize {public} +quickstart quickstart my_app 1 {} success 13 1 constant true "EXECUTE p('hello world')" "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} success 18 1 fast-path true "SELECT * FROM t" "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} success 18 1 standard true "SELECT count(*) FROM t" "strict serializable" true materialize {public} +quickstart quickstart my_app 1 {} success 4 2 constant true "FETCH c" "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} success true BEGIN "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} success true COMMIT "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} success true "CREATE CLUSTER c REPLICAS (r1 (size '1'))" "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} success true "CREATE DEFAULT INDEX i ON t" "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} success true "CREATE TABLE t(f int)" "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} success true "DECLARE c CURSOR FOR VALUES (1), (2)" "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} success true "FETCH c" "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} success true "FETCH c" "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} success true "INSERT INTO t VALUES (1)" "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} success true "PREPARE p AS values ($1)" "strict serializable" materialize {public} +quickstart quickstart my_app 1 {} success true "SET cluster TO c" "strict serializable" materialize {public} +mz_catalog_server mz_catalog_server my_app 1 {} success 22 1 constant true "SELECT 'beginning real test!'" "strict serializable" materialize {public} +mz_catalog_server mz_catalog_server my_app 1 {} success 14 1 constant true "SELECT 'serializable'" serializable materialize {public} +mz_catalog_server mz_catalog_server my_app 1 {} success 17 1 standard true "SELECT count(*) > 0 FROM mz_internal.mz_cluster_replica_metrics" "strict serializable" true materialize {public} > WITH all_stmts AS (SELECT mseh.id, mseh.began_at, mst.sql FROM mz_internal.mz_statement_execution_history mseh JOIN mz_internal.mz_prepared_statement_history mpsh ON mseh.prepared_statement_id = mpsh.id JOIN (SELECT DISTINCT sql, sql_hash, redacted_sql FROM mz_internal.mz_sql_text) mst ON mpsh.sql_hash = mst.sql_hash), test_begin AS (SELECT began_at FROM all_stmts WHERE sql = 'SELECT ''beginning real test!''' ORDER BY began_at DESC LIMIT 1) @@ -292,14 +292,14 @@ ROLLBACK > SELECT * FROM t_offset_limit ORDER BY a DESC LIMIT 0; -> SELECT rows_returned FROM mz_internal.mz_recent_activity_log WHERE sql = 'SELECT * FROM t_offset_limit ORDER BY a DESC OFFSET 4'; -1 +> SELECT result_size, rows_returned FROM mz_internal.mz_recent_activity_log WHERE sql = 'SELECT * FROM t_offset_limit ORDER BY a DESC OFFSET 4'; +90 1 -> SELECT rows_returned FROM mz_internal.mz_recent_activity_log WHERE sql = 'SELECT * FROM t_offset_limit ORDER BY a DESC LIMIT 2'; -2 +> SELECT result_size, rows_returned FROM mz_internal.mz_recent_activity_log WHERE sql = 'SELECT * FROM t_offset_limit ORDER BY a DESC LIMIT 2'; +54 2 -> SELECT rows_returned FROM mz_internal.mz_recent_activity_log WHERE sql = 'SELECT * FROM t_offset_limit ORDER BY a DESC OFFSET 100'; -0 +> SELECT result_size, rows_returned FROM mz_internal.mz_recent_activity_log WHERE sql = 'SELECT * FROM t_offset_limit ORDER BY a DESC OFFSET 100'; +90 0 -> SELECT rows_returned FROM mz_internal.mz_recent_activity_log WHERE sql = 'SELECT * FROM t_offset_limit ORDER BY a DESC LIMIT 0'; -0 +> SELECT result_size, rows_returned FROM mz_internal.mz_recent_activity_log WHERE sql = 'SELECT * FROM t_offset_limit ORDER BY a DESC LIMIT 0'; +0 0 diff --git a/test/feature-benchmark/mzcompose.py b/test/feature-benchmark/mzcompose.py index 4113525e93de6..7e99041aae9f9 100644 --- a/test/feature-benchmark/mzcompose.py +++ b/test/feature-benchmark/mzcompose.py @@ -104,7 +104,7 @@ # -FEATURE_BENCHMARK_FRAMEWORK_VERSION = "1.4.0" +FEATURE_BENCHMARK_FRAMEWORK_VERSION = "1.5.0" def make_filter(args: argparse.Namespace) -> Filter: diff --git a/test/kafka-matrix/mzcompose.py b/test/kafka-matrix/mzcompose.py index 3b4078a1c0357..b08deb550520c 100644 --- a/test/kafka-matrix/mzcompose.py +++ b/test/kafka-matrix/mzcompose.py @@ -28,7 +28,8 @@ "v23.1.21", "v23.2.29", "v23.3.21", - "v24.1.17", + "v24.1.18", + "v24.2.12", REDPANDA_VERSION, "latest", ] diff --git a/test/limits/mzcompose.py b/test/limits/mzcompose.py index 49ff5f39a7e5c..edc9f87ca3b7f 100644 --- a/test/limits/mzcompose.py +++ b/test/limits/mzcompose.py @@ -532,7 +532,7 @@ def body(cls) -> None: class KafkaSinks(Generator): COUNT = min(Generator.COUNT, 50) # $ kafka-verify-data is slow - MAX_COUNT = 3200 # Too long-running with 6400 sinks + MAX_COUNT = 1600 # Too long-running with 3200 sinks @classmethod def body(cls) -> None: @@ -1414,6 +1414,8 @@ def body(cls) -> None: class RowsJoinOneToMany(Generator): COUNT = 10_000_000 + MAX_COUNT = 160_000_000 # Too long-running with 320_000_000 + @classmethod def body(cls) -> None: print( @@ -1426,6 +1428,8 @@ def body(cls) -> None: class RowsJoinCross(Generator): COUNT = 1_000_000 + MAX_COUNT = 256_000_000 # Too long-running with 512_000_000 + @classmethod def body(cls) -> None: print( diff --git a/test/parallel-benchmark/mzcompose.py b/test/parallel-benchmark/mzcompose.py index aed4dcff08d76..d464220dcfc6c 100644 --- a/test/parallel-benchmark/mzcompose.py +++ b/test/parallel-benchmark/mzcompose.py @@ -62,7 +62,7 @@ from materialize.util import PgConnInfo, all_subclasses, parse_pg_conn_string from materialize.version_list import resolve_ancestor_image_tag -PARALLEL_BENCHMARK_FRAMEWORK_VERSION = "1.1.0" +PARALLEL_BENCHMARK_FRAMEWORK_VERSION = "1.2.0" def known_regression(scenario: str, other_tag: str) -> bool: diff --git a/test/replica-isolation/mzcompose.py b/test/replica-isolation/mzcompose.py index d9b2a2a377a1d..e44ecce2cdfde 100644 --- a/test/replica-isolation/mzcompose.py +++ b/test/replica-isolation/mzcompose.py @@ -101,9 +101,11 @@ def _log_contains_id(log: str, the_id: str) -> bool: @staticmethod def _format_id(iid: str) -> str: - if iid.startswith("s"): + if iid.startswith("si"): + return "IntrospectionSourceIndex(" + iid[2:] + ")" + elif iid.startswith("s"): return "System(" + iid[1:] + ")" - if iid.startswith("u"): + elif iid.startswith("u"): return "User(" + iid[1:] + ")" raise RuntimeError(f"Unexpected iid: {iid}") diff --git a/test/scalability/mzcompose.py b/test/scalability/mzcompose.py index 1b7aefbb92f48..1904bf19e57e7 100644 --- a/test/scalability/mzcompose.py +++ b/test/scalability/mzcompose.py @@ -92,7 +92,7 @@ ] DEFAULT_REGRESSION_THRESHOLD = 0.2 -SCALABILITY_FRAMEWORK_VERSION = "1.4.0" +SCALABILITY_FRAMEWORK_VERSION = "1.5.0" INCLUDE_ZERO_IN_Y_AXIS = True diff --git a/test/sqllogictest/autogenerated/mz_internal.slt b/test/sqllogictest/autogenerated/mz_internal.slt index 25dce0a490ac3..b7527e8804c50 100644 --- a/test/sqllogictest/autogenerated/mz_internal.slt +++ b/test/sqllogictest/autogenerated/mz_internal.slt @@ -48,19 +48,20 @@ SELECT position, name, type FROM objects WHERE schema = 'mz_internal' AND object 14 finished_at timestamp␠with␠time␠zone 15 finished_status text 16 error_message text -17 rows_returned bigint -18 execution_strategy text -19 transaction_id uint8 -20 prepared_statement_id uuid -21 sql_hash bytea -22 prepared_statement_name text -23 session_id uuid -24 prepared_at timestamp␠with␠time␠zone -25 statement_type text -26 throttled_count uint8 -27 initial_application_name text -28 authenticated_user text -29 sql text +17 result_size bigint +18 rows_returned bigint +19 execution_strategy text +20 transaction_id uint8 +21 prepared_statement_id uuid +22 sql_hash bytea +23 prepared_statement_name text +24 session_id uuid +25 prepared_at timestamp␠with␠time␠zone +26 statement_type text +27 throttled_count uint8 +28 initial_application_name text +29 authenticated_user text +30 sql text query ITT SELECT position, name, type FROM objects WHERE schema = 'mz_internal' AND object = 'mz_aws_connections' ORDER BY position diff --git a/test/sqllogictest/autogenerated/mz_introspection.slt b/test/sqllogictest/autogenerated/mz_introspection.slt index 9805b1b825070..b90f36fc63a42 100644 --- a/test/sqllogictest/autogenerated/mz_introspection.slt +++ b/test/sqllogictest/autogenerated/mz_introspection.slt @@ -126,6 +126,12 @@ SELECT position, name, type FROM objects WHERE schema = 'mz_introspection' AND o 4 to_operator_id uint8 5 to_operator_address list +query ITT +SELECT position, name, type FROM objects WHERE schema = 'mz_introspection' AND object = 'mz_dataflow_global_ids' ORDER BY position +---- +1 id uint8 +2 global_id text + query ITT SELECT position, name, type FROM objects WHERE schema = 'mz_introspection' AND object = 'mz_dataflow_operators' ORDER BY position ---- @@ -164,6 +170,17 @@ SELECT position, name, type FROM objects WHERE schema = 'mz_introspection' AND o 7 savings numeric 8 hint double␠precision +query ITT +SELECT position, name, type FROM objects WHERE schema = 'mz_introspection' AND object = 'mz_lir_mapping' ORDER BY position +---- +1 global_id text +2 lir_id uint8 +3 operator text +4 parent_lir_id uint8 +5 nesting uint2 +6 operator_id_start uint8 +7 operator_id_end uint8 + query ITT SELECT position, name, type FROM objects WHERE schema = 'mz_introspection' AND object = 'mz_message_counts' ORDER BY position ---- @@ -235,6 +252,7 @@ mz_arrangement_sharing_per_worker mz_arrangement_sharing_raw mz_arrangement_sizes mz_arrangement_sizes_per_worker +mz_compute_dataflow_global_ids_per_worker mz_compute_error_counts mz_compute_error_counts_per_worker mz_compute_error_counts_raw @@ -245,6 +263,7 @@ mz_compute_frontiers_per_worker mz_compute_hydration_times_per_worker mz_compute_import_frontiers mz_compute_import_frontiers_per_worker +mz_compute_lir_mapping_per_worker mz_compute_operator_durations_histogram mz_compute_operator_durations_histogram_per_worker mz_compute_operator_durations_histogram_raw @@ -255,6 +274,7 @@ mz_dataflow_channel_operators mz_dataflow_channel_operators_per_worker mz_dataflow_channels mz_dataflow_channels_per_worker +mz_dataflow_global_ids mz_dataflow_operator_dataflows mz_dataflow_operator_dataflows_per_worker mz_dataflow_operator_parents @@ -270,6 +290,7 @@ mz_dataflow_shutdown_durations_histogram_raw mz_dataflows mz_dataflows_per_worker mz_expected_group_size_advice +mz_lir_mapping mz_message_batch_counts_received_raw mz_message_batch_counts_sent_raw mz_message_counts diff --git a/test/sqllogictest/cluster.slt b/test/sqllogictest/cluster.slt index 8c32bbc093e64..9e8ca980ca70b 100644 --- a/test/sqllogictest/cluster.slt +++ b/test/sqllogictest/cluster.slt @@ -210,6 +210,8 @@ bar mz_arrangement_records_raw mz_arrangement_records_raw_u7_primary_idx 1 o bar mz_arrangement_records_raw mz_arrangement_records_raw_u7_primary_idx 2 worker_id NULL false bar mz_arrangement_sharing_raw mz_arrangement_sharing_raw_u7_primary_idx 1 operator_id NULL false bar mz_arrangement_sharing_raw mz_arrangement_sharing_raw_u7_primary_idx 2 worker_id NULL false +bar mz_compute_dataflow_global_ids_per_worker mz_compute_dataflow_global_ids_per_worker_u7_primary_idx 1 id NULL false +bar mz_compute_dataflow_global_ids_per_worker mz_compute_dataflow_global_ids_per_worker_u7_primary_idx 2 worker_id NULL false bar mz_compute_error_counts_raw mz_compute_error_counts_raw_u7_primary_idx 1 export_id NULL false bar mz_compute_error_counts_raw mz_compute_error_counts_raw_u7_primary_idx 2 worker_id NULL false bar mz_compute_exports_per_worker mz_compute_exports_per_worker_u7_primary_idx 1 export_id NULL false @@ -221,6 +223,9 @@ bar mz_compute_hydration_times_per_worker mz_compute_hydration_times_per_worke bar mz_compute_import_frontiers_per_worker mz_compute_import_frontiers_per_worker_u7_primary_idx 1 export_id NULL false bar mz_compute_import_frontiers_per_worker mz_compute_import_frontiers_per_worker_u7_primary_idx 2 import_id NULL false bar mz_compute_import_frontiers_per_worker mz_compute_import_frontiers_per_worker_u7_primary_idx 3 worker_id NULL false +bar mz_compute_lir_mapping_per_worker mz_compute_lir_mapping_per_worker_u7_primary_idx 1 global_id NULL false +bar mz_compute_lir_mapping_per_worker mz_compute_lir_mapping_per_worker_u7_primary_idx 2 lir_id NULL false +bar mz_compute_lir_mapping_per_worker mz_compute_lir_mapping_per_worker_u7_primary_idx 3 worker_id NULL false bar mz_compute_operator_durations_histogram_raw mz_compute_operator_durations_histogram_raw_u7_primary_idx 1 id NULL false bar mz_compute_operator_durations_histogram_raw mz_compute_operator_durations_histogram_raw_u7_primary_idx 2 worker_id NULL false bar mz_compute_operator_durations_histogram_raw mz_compute_operator_durations_histogram_raw_u7_primary_idx 3 duration_ns NULL false @@ -407,7 +412,7 @@ DROP CLUSTER foo, foo2, foo3, foo4 CASCADE query I SELECT COUNT(name) FROM mz_indexes WHERE cluster_id = 'u1'; ---- -29 +31 query I SELECT COUNT(name) FROM mz_indexes WHERE cluster_id <> 'u1' AND cluster_id NOT LIKE 's%'; @@ -420,7 +425,7 @@ CREATE CLUSTER test REPLICAS (foo (SIZE '1')); query I SELECT COUNT(name) FROM mz_indexes; ---- -271 +285 statement ok DROP CLUSTER test CASCADE @@ -428,7 +433,7 @@ DROP CLUSTER test CASCADE query T SELECT COUNT(name) FROM mz_indexes; ---- -242 +254 simple conn=mz_system,user=mz_system ALTER CLUSTER quickstart OWNER TO materialize diff --git a/test/sqllogictest/explain/optimized_plan_as_text.slt b/test/sqllogictest/explain/optimized_plan_as_text.slt index 879af6203f45e..27184c7e8b765 100644 --- a/test/sqllogictest/explain/optimized_plan_as_text.slt +++ b/test/sqllogictest/explain/optimized_plan_as_text.slt @@ -1577,3 +1577,42 @@ Source materialize.public.t4 Target cluster: no_replicas EOF + +statement ok +CREATE TABLE t5( + x int, + y int NOT NULL, + z int +); + +statement ok +CREATE TABLE t6( + a int NOT NULL, + b int +); + +# WITH(EQUIVALENCES) +query T multiline +EXPLAIN WITH(EQUIVALENCES) +SELECT * +FROM t5, t6 +WHERE x = a AND b IN (8,9); +---- +Explained Query: + Project (#0..=#2, #0, #4) // { equivs: "[[#0, #3], [false, (#0) IS NULL, (#1) IS NULL], [true, ((#4 = 8) OR (#4 = 9))]]" } + Join on=(#0 = #3) type=differential // { equivs: "[[#0, #3], [false, (#0) IS NULL, (#1) IS NULL], [true, ((#4 = 8) OR (#4 = 9))]]" } + ArrangeBy keys=[[#0]] // { equivs: "[[false, (#0) IS NULL, (#1) IS NULL]]" } + Filter (#0) IS NOT NULL // { equivs: "[[false, (#0) IS NULL, (#1) IS NULL]]" } + ReadStorage materialize.public.t5 // { equivs: "[[false, (#1) IS NULL]]" } + ArrangeBy keys=[[#0]] // { equivs: "[[false, (#0) IS NULL], [true, ((#1 = 8) OR (#1 = 9))]]" } + Filter ((#1 = 8) OR (#1 = 9)) // { equivs: "[[false, (#0) IS NULL], [true, ((#1 = 8) OR (#1 = 9))]]" } + ReadStorage materialize.public.t6 // { equivs: "[[false, (#0) IS NULL]]" } + +Source materialize.public.t5 + filter=((#0) IS NOT NULL) +Source materialize.public.t6 + filter=(((#1 = 8) OR (#1 = 9))) + +Target cluster: no_replicas + +EOF diff --git a/test/sqllogictest/id.slt b/test/sqllogictest/id.slt index 65feb1f651b72..ee29c67246899 100644 --- a/test/sqllogictest/id.slt +++ b/test/sqllogictest/id.slt @@ -54,9 +54,12 @@ SELECT y.a FROM [u1 AS materialize.public.y] statement error invalid id SELECT y.a FROM [u6 AS materialize.public.y] -statement error invalid digit +statement error couldn't parse id SELECT y.a FROM [xx AS materialize.public.y] +statement error invalid digit +SELECT y.a FROM [ux AS materialize.public.y] + statement ok CREATE VIEW foo AS SELECT * FROM x diff --git a/test/sqllogictest/information_schema_tables.slt b/test/sqllogictest/information_schema_tables.slt index 04758cd6a92ec..4d439cf53f0e6 100644 --- a/test/sqllogictest/information_schema_tables.slt +++ b/test/sqllogictest/information_schema_tables.slt @@ -825,6 +825,10 @@ mz_arrangement_sizes_per_worker VIEW materialize mz_introspection +mz_compute_dataflow_global_ids_per_worker +SOURCE +materialize +mz_introspection mz_compute_error_counts VIEW materialize @@ -865,6 +869,10 @@ mz_compute_import_frontiers_per_worker SOURCE materialize mz_introspection +mz_compute_lir_mapping_per_worker +SOURCE +materialize +mz_introspection mz_compute_operator_durations_histogram VIEW materialize @@ -905,6 +913,10 @@ mz_dataflow_channels_per_worker SOURCE materialize mz_introspection +mz_dataflow_global_ids +VIEW +materialize +mz_introspection mz_dataflow_operator_dataflows VIEW materialize @@ -965,6 +977,10 @@ mz_expected_group_size_advice VIEW materialize mz_introspection +mz_lir_mapping +VIEW +materialize +mz_introspection mz_message_batch_counts_received_raw SOURCE materialize diff --git a/test/sqllogictest/introspection/attribution_sources.slt b/test/sqllogictest/introspection/attribution_sources.slt new file mode 100644 index 0000000000000..eb82c2d4dd717 --- /dev/null +++ b/test/sqllogictest/introspection/attribution_sources.slt @@ -0,0 +1,208 @@ +# Copyright Materialize, Inc. and contributors. All rights reserved. +# +# Use of this software is governed by the Business Source License +# included in the LICENSE file at the root of this repository. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0. + +# Ensure attribution sources function (are created, dropped appropriately) + +mode cockroach + +# VIEW + INDEX + +statement ok +CREATE TABLE t(x INT NOT NULL, y INT, z TEXT); + +statement ok +CREATE VIEW v AS + SELECT t1.x AS x, t1.z AS z1, t2.z AS z2 + FROM t AS t1, t AS t2 + WHERE t1.x = t2.y; + +statement ok +CREATE INDEX v_idx_x ON v(x); + +# let the introspection sources update +statement ok +SELECT mz_unsafe.mz_sleep(2) + +query IT +SELECT id, global_id FROM mz_internal.mz_dataflow_global_ids ORDER BY id, global_id; +---- +8 u2 +8 u3 + +query TI +SELECT global_id, lir_id FROM mz_internal.mz_lir_mapping ORDER BY global_id, lir_id DESC; +---- +u2 5 +u2 4 +u2 3 +u2 2 +u2 1 +u3 7 +u3 6 + +## attribution queries + +# omitting sum(duration_ns) as duration, sum(count) as count +query TIIT +SELECT global_id, lir_id, parent_lir_id, REPEAT(' ', nesting * 2) || operator AS operator + FROM mz_internal.mz_lir_mapping mlm + LEFT JOIN mz_introspection.mz_compute_operator_durations_histogram mcodh + ON (mlm.operator_id_start <= mcodh.id AND mcodh.id < mlm.operator_id_end) +GROUP BY global_id, lir_id, operator, parent_lir_id, nesting +ORDER BY global_id, lir_id DESC; +---- +u2 5 NULL Join::Differential␠2»␠4 +u2 4 5 ␠␠Arrange␠3 +u2 3 4 ␠␠␠␠Get::Collection␠u1 +u2 2 5 ␠␠Arrange␠1 +u2 1 2 ␠␠␠␠Get::Collection␠u1 +u3 7 NULL Arrange␠6 +u3 6 7 ␠␠Get::PassArrangements␠u2 + +# omitting pg_size_pretty(sum(size)) as size +query TIIT +SELECT global_id, lir_id, parent_lir_id, repeat(' ', nesting * 2) || operator AS operator + FROM mz_internal.mz_lir_mapping mlm + LEFT JOIN mz_introspection.mz_arrangement_sizes mas + ON (mlm.operator_id_start <= mas.operator_id AND mas.operator_id < mlm.operator_id_end) +GROUP BY global_id, lir_id, operator, parent_lir_id, nesting +ORDER BY global_id, lir_id DESC; +---- +u2 5 NULL Join::Differential␠2»␠4 +u2 4 5 ␠␠Arrange␠3 +u2 3 4 ␠␠␠␠Get::Collection␠u1 +u2 2 5 ␠␠Arrange␠1 +u2 1 2 ␠␠␠␠Get::Collection␠u1 +u3 7 NULL Arrange␠6 +u3 6 7 ␠␠Get::PassArrangements␠u2 + +statement ok +DROP TABLE t CASCADE; + +# we need the dataflow to actually drop to see the updates +statement ok +SELECT mz_unsafe.mz_sleep(3) + +query I +SELECT COUNT(*) FROM mz_internal.mz_dataflow_global_ids; +---- +0 + +query I +SELECT COUNT(*) FROM mz_internal.mz_lir_mapping; +---- +0 + +# MATERIALIZED VIEW + +statement ok +CREATE TABLE u(x INT NOT NULL, y INT, z TEXT); + +statement ok +CREATE MATERIALIZED VIEW w AS + SELECT t1.x AS x, t1.z AS z1, t2.z AS z2 + FROM u AS t1, u AS t2 + WHERE t1.x = t2.y; + +# let the introspection sources update +statement ok +SELECT mz_unsafe.mz_sleep(2) + +query IT +SELECT id, global_id FROM mz_internal.mz_dataflow_global_ids ORDER BY id, global_id; +---- +13 t59 + + +query TI +SELECT global_id, lir_id FROM mz_internal.mz_lir_mapping ORDER BY global_id, lir_id DESC; +---- +t59 5 +t59 4 +t59 3 +t59 2 +t59 1 + +## attribution queries + +# omitting sum(duration_ns) as duration, sum(count) as count +query TIIT +SELECT global_id, lir_id, parent_lir_id, REPEAT(' ', nesting * 2) || operator AS operator + FROM mz_internal.mz_lir_mapping mlm + LEFT JOIN mz_introspection.mz_compute_operator_durations_histogram mcodh + ON (mlm.operator_id_start <= mcodh.id AND mcodh.id < mlm.operator_id_end) +GROUP BY global_id, lir_id, operator, parent_lir_id, nesting +ORDER BY global_id, lir_id DESC; +---- +t59 5 NULL Join::Differential␠2»␠4 +t59 4 5 ␠␠Arrange␠3 +t59 3 4 ␠␠␠␠Get::Collection␠u4 +t59 2 5 ␠␠Arrange␠1 +t59 1 2 ␠␠␠␠Get::Collection␠u4 + +# omitting pg_size_pretty(sum(size)) as size +query TIIT +SELECT global_id, lir_id, parent_lir_id, REPEAT(' ', nesting * 2) || operator AS operator + FROM mz_internal.mz_lir_mapping mlm + LEFT JOIN mz_introspection.mz_arrangement_sizes mas + ON (mlm.operator_id_start <= mas.operator_id AND mas.operator_id < mlm.operator_id_end) +GROUP BY global_id, lir_id, operator, parent_lir_id, nesting +ORDER BY global_id, lir_id DESC; +---- +t59 5 NULL Join::Differential␠2»␠4 +t59 4 5 ␠␠Arrange␠3 +t59 3 4 ␠␠␠␠Get::Collection␠u4 +t59 2 5 ␠␠Arrange␠1 +t59 1 2 ␠␠␠␠Get::Collection␠u4 + +statement ok +DROP TABLE u CASCADE; + +# we need the dataflow to actually drop to see the updates +statement ok +SELECT mz_unsafe.mz_sleep(3) + +query I +SELECT COUNT(*) FROM mz_internal.mz_dataflow_global_ids; +---- +0 + +query I +SELECT COUNT(*) FROM mz_internal.mz_lir_mapping; +---- +0 + +# ATTRIBUTING TOP K HINTS + +statement ok +CREATE TABLE t(x INT NOT NULL, y INT, z TEXT); + +statement ok +CREATE VIEW v2 AS SELECT DISTINCT ON(x, y) * FROM t ORDER BY x, y; + +statement ok +CREATE INDEX v2_idx_x ON v2(x); + +statement ok +SELECT mz_unsafe.mz_sleep(2) + +query TIITIIIT +SELECT mlm.global_id AS global_id, lir_id, parent_lir_id, REPEAT(' ', nesting * 2) || operator AS operator, levels, to_cut, savings, hint + FROM mz_internal.mz_lir_mapping mlm + JOIN mz_introspection.mz_dataflow_global_ids mdgi + ON (mlm.global_id = mdgi.global_id) + LEFT JOIN mz_introspection.mz_expected_group_size_advice megsa + ON (megsa.dataflow_id = mdgi.id AND + mlm.operator_id_start <= megsa.region_id AND megsa.region_id < mlm.operator_id_end) +ORDER BY mlm.global_id, lir_id DESC; +---- +u7 2 NULL TopK::Basic␠1 8 7 3584 15.000 +u7 1 2 ␠␠Get::PassArrangements␠u6 NULL NULL NULL NULL +u8 4 NULL Arrange␠3 NULL NULL NULL NULL +u8 3 4 ␠␠Get::PassArrangements␠u7 NULL NULL NULL NULL diff --git a/test/sqllogictest/mz_catalog_server_index_accounting.slt b/test/sqllogictest/mz_catalog_server_index_accounting.slt index 4f6adee71c260..0bfdfcd0cc11a 100644 --- a/test/sqllogictest/mz_catalog_server_index_accounting.slt +++ b/test/sqllogictest/mz_catalog_server_index_accounting.slt @@ -37,92 +37,94 @@ mz_arrangement_heap_capacity_raw_s2_primary_idx CREATE␠INDEX␠"mz_arrangemen mz_arrangement_heap_size_raw_s2_primary_idx CREATE␠INDEX␠"mz_arrangement_heap_size_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_arrangement_heap_size_raw"␠("operator_id",␠"worker_id") mz_arrangement_records_raw_s2_primary_idx CREATE␠INDEX␠"mz_arrangement_records_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_arrangement_records_raw"␠("operator_id",␠"worker_id") mz_arrangement_sharing_raw_s2_primary_idx CREATE␠INDEX␠"mz_arrangement_sharing_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_arrangement_sharing_raw"␠("operator_id",␠"worker_id") -mz_cluster_replica_history_ind CREATE␠INDEX␠"mz_cluster_replica_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s577␠AS␠"mz_internal"."mz_cluster_replica_history"]␠("dropped_at") -mz_cluster_replica_metrics_history_ind CREATE␠INDEX␠"mz_cluster_replica_metrics_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s491␠AS␠"mz_internal"."mz_cluster_replica_metrics_history"]␠("replica_id") -mz_cluster_replica_metrics_ind CREATE␠INDEX␠"mz_cluster_replica_metrics_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s490␠AS␠"mz_internal"."mz_cluster_replica_metrics"]␠("replica_id") -mz_cluster_replica_name_history_ind CREATE␠INDEX␠"mz_cluster_replica_name_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s578␠AS␠"mz_internal"."mz_cluster_replica_name_history"]␠("id") -mz_cluster_replica_sizes_ind CREATE␠INDEX␠"mz_cluster_replica_sizes_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s492␠AS␠"mz_catalog"."mz_cluster_replica_sizes"]␠("size") -mz_cluster_replica_status_history_ind CREATE␠INDEX␠"mz_cluster_replica_status_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s494␠AS␠"mz_internal"."mz_cluster_replica_status_history"]␠("replica_id") -mz_cluster_replica_statuses_ind CREATE␠INDEX␠"mz_cluster_replica_statuses_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s493␠AS␠"mz_internal"."mz_cluster_replica_statuses"]␠("replica_id") -mz_cluster_replicas_ind CREATE␠INDEX␠"mz_cluster_replicas_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s489␠AS␠"mz_catalog"."mz_cluster_replicas"]␠("id") -mz_clusters_ind CREATE␠INDEX␠"mz_clusters_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s483␠AS␠"mz_catalog"."mz_clusters"]␠("id") -mz_columns_ind CREATE␠INDEX␠"mz_columns_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s456␠AS␠"mz_catalog"."mz_columns"]␠("name") -mz_comments_ind CREATE␠INDEX␠"mz_comments_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s506␠AS␠"mz_internal"."mz_comments"]␠("id") -mz_compute_dependencies_ind CREATE␠INDEX␠"mz_compute_dependencies_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s698␠AS␠"mz_internal"."mz_compute_dependencies"]␠("dependency_id") +mz_cluster_replica_history_ind CREATE␠INDEX␠"mz_cluster_replica_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s579␠AS␠"mz_internal"."mz_cluster_replica_history"]␠("dropped_at") +mz_cluster_replica_metrics_history_ind CREATE␠INDEX␠"mz_cluster_replica_metrics_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s492␠AS␠"mz_internal"."mz_cluster_replica_metrics_history"]␠("replica_id") +mz_cluster_replica_metrics_ind CREATE␠INDEX␠"mz_cluster_replica_metrics_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s491␠AS␠"mz_internal"."mz_cluster_replica_metrics"]␠("replica_id") +mz_cluster_replica_name_history_ind CREATE␠INDEX␠"mz_cluster_replica_name_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s580␠AS␠"mz_internal"."mz_cluster_replica_name_history"]␠("id") +mz_cluster_replica_sizes_ind CREATE␠INDEX␠"mz_cluster_replica_sizes_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s493␠AS␠"mz_catalog"."mz_cluster_replica_sizes"]␠("size") +mz_cluster_replica_status_history_ind CREATE␠INDEX␠"mz_cluster_replica_status_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s495␠AS␠"mz_internal"."mz_cluster_replica_status_history"]␠("replica_id") +mz_cluster_replica_statuses_ind CREATE␠INDEX␠"mz_cluster_replica_statuses_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s494␠AS␠"mz_internal"."mz_cluster_replica_statuses"]␠("replica_id") +mz_cluster_replicas_ind CREATE␠INDEX␠"mz_cluster_replicas_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s490␠AS␠"mz_catalog"."mz_cluster_replicas"]␠("id") +mz_clusters_ind CREATE␠INDEX␠"mz_clusters_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s484␠AS␠"mz_catalog"."mz_clusters"]␠("id") +mz_columns_ind CREATE␠INDEX␠"mz_columns_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s457␠AS␠"mz_catalog"."mz_columns"]␠("name") +mz_comments_ind CREATE␠INDEX␠"mz_comments_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s507␠AS␠"mz_internal"."mz_comments"]␠("id") +mz_compute_dataflow_global_ids_per_worker_s2_primary_idx CREATE␠INDEX␠"mz_compute_dataflow_global_ids_per_worker_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_compute_dataflow_global_ids_per_worker"␠("id",␠"worker_id") +mz_compute_dependencies_ind CREATE␠INDEX␠"mz_compute_dependencies_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s700␠AS␠"mz_internal"."mz_compute_dependencies"]␠("dependency_id") mz_compute_error_counts_raw_s2_primary_idx CREATE␠INDEX␠"mz_compute_error_counts_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_compute_error_counts_raw"␠("export_id",␠"worker_id") mz_compute_exports_per_worker_s2_primary_idx CREATE␠INDEX␠"mz_compute_exports_per_worker_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_compute_exports_per_worker"␠("export_id",␠"worker_id") mz_compute_frontiers_per_worker_s2_primary_idx CREATE␠INDEX␠"mz_compute_frontiers_per_worker_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_compute_frontiers_per_worker"␠("export_id",␠"worker_id") mz_compute_hydration_times_per_worker_s2_primary_idx CREATE␠INDEX␠"mz_compute_hydration_times_per_worker_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_compute_hydration_times_per_worker"␠("export_id",␠"worker_id") mz_compute_import_frontiers_per_worker_s2_primary_idx CREATE␠INDEX␠"mz_compute_import_frontiers_per_worker_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_compute_import_frontiers_per_worker"␠("export_id",␠"import_id",␠"worker_id") +mz_compute_lir_mapping_per_worker_s2_primary_idx CREATE␠INDEX␠"mz_compute_lir_mapping_per_worker_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_compute_lir_mapping_per_worker"␠("global_id",␠"lir_id",␠"worker_id") mz_compute_operator_durations_histogram_raw_s2_primary_idx CREATE␠INDEX␠"mz_compute_operator_durations_histogram_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_compute_operator_durations_histogram_raw"␠("id",␠"worker_id",␠"duration_ns") -mz_connections_ind CREATE␠INDEX␠"mz_connections_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s487␠AS␠"mz_catalog"."mz_connections"]␠("schema_id") -mz_console_cluster_utilization_overview_ind CREATE␠INDEX␠"mz_console_cluster_utilization_overview_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s702␠AS␠"mz_internal"."mz_console_cluster_utilization_overview"]␠("cluster_id") -mz_continual_tasks_ind CREATE␠INDEX␠"mz_continual_tasks_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s509␠AS␠"mz_internal"."mz_continual_tasks"]␠("id") -mz_databases_ind CREATE␠INDEX␠"mz_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s454␠AS␠"mz_catalog"."mz_databases"]␠("name") +mz_connections_ind CREATE␠INDEX␠"mz_connections_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s488␠AS␠"mz_catalog"."mz_connections"]␠("schema_id") +mz_console_cluster_utilization_overview_ind CREATE␠INDEX␠"mz_console_cluster_utilization_overview_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s704␠AS␠"mz_internal"."mz_console_cluster_utilization_overview"]␠("cluster_id") +mz_continual_tasks_ind CREATE␠INDEX␠"mz_continual_tasks_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s510␠AS␠"mz_internal"."mz_continual_tasks"]␠("id") +mz_databases_ind CREATE␠INDEX␠"mz_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s455␠AS␠"mz_catalog"."mz_databases"]␠("name") mz_dataflow_addresses_per_worker_s2_primary_idx CREATE␠INDEX␠"mz_dataflow_addresses_per_worker_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_dataflow_addresses_per_worker"␠("id",␠"worker_id") mz_dataflow_channels_per_worker_s2_primary_idx CREATE␠INDEX␠"mz_dataflow_channels_per_worker_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_dataflow_channels_per_worker"␠("id",␠"worker_id") mz_dataflow_operator_reachability_raw_s2_primary_idx CREATE␠INDEX␠"mz_dataflow_operator_reachability_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_dataflow_operator_reachability_raw"␠("address",␠"port",␠"worker_id",␠"update_type",␠"time") mz_dataflow_operators_per_worker_s2_primary_idx CREATE␠INDEX␠"mz_dataflow_operators_per_worker_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_dataflow_operators_per_worker"␠("id",␠"worker_id") mz_dataflow_shutdown_durations_histogram_raw_s2_primary_idx CREATE␠INDEX␠"mz_dataflow_shutdown_durations_histogram_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_dataflow_shutdown_durations_histogram_raw"␠("worker_id",␠"duration_ns") -mz_frontiers_ind CREATE␠INDEX␠"mz_frontiers_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s692␠AS␠"mz_internal"."mz_frontiers"]␠("object_id") -mz_indexes_ind CREATE␠INDEX␠"mz_indexes_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s457␠AS␠"mz_catalog"."mz_indexes"]␠("id") -mz_kafka_sources_ind CREATE␠INDEX␠"mz_kafka_sources_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s452␠AS␠"mz_catalog"."mz_kafka_sources"]␠("id") -mz_materialized_views_ind CREATE␠INDEX␠"mz_materialized_views_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s468␠AS␠"mz_catalog"."mz_materialized_views"]␠("id") +mz_frontiers_ind CREATE␠INDEX␠"mz_frontiers_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s694␠AS␠"mz_internal"."mz_frontiers"]␠("object_id") +mz_indexes_ind CREATE␠INDEX␠"mz_indexes_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s458␠AS␠"mz_catalog"."mz_indexes"]␠("id") +mz_kafka_sources_ind CREATE␠INDEX␠"mz_kafka_sources_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s453␠AS␠"mz_catalog"."mz_kafka_sources"]␠("id") +mz_materialized_views_ind CREATE␠INDEX␠"mz_materialized_views_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s469␠AS␠"mz_catalog"."mz_materialized_views"]␠("id") mz_message_batch_counts_received_raw_s2_primary_idx CREATE␠INDEX␠"mz_message_batch_counts_received_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_message_batch_counts_received_raw"␠("channel_id",␠"from_worker_id",␠"to_worker_id") mz_message_batch_counts_sent_raw_s2_primary_idx CREATE␠INDEX␠"mz_message_batch_counts_sent_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_message_batch_counts_sent_raw"␠("channel_id",␠"from_worker_id",␠"to_worker_id") mz_message_counts_received_raw_s2_primary_idx CREATE␠INDEX␠"mz_message_counts_received_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_message_counts_received_raw"␠("channel_id",␠"from_worker_id",␠"to_worker_id") mz_message_counts_sent_raw_s2_primary_idx CREATE␠INDEX␠"mz_message_counts_sent_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_message_counts_sent_raw"␠("channel_id",␠"from_worker_id",␠"to_worker_id") -mz_notices_ind CREATE␠INDEX␠"mz_notices_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s776␠AS␠"mz_internal"."mz_notices"]␠("id") -mz_object_dependencies_ind CREATE␠INDEX␠"mz_object_dependencies_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s453␠AS␠"mz_internal"."mz_object_dependencies"]␠("object_id") -mz_object_history_ind CREATE␠INDEX␠"mz_object_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s517␠AS␠"mz_internal"."mz_object_history"]␠("id") -mz_object_lifetimes_ind CREATE␠INDEX␠"mz_object_lifetimes_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s518␠AS␠"mz_internal"."mz_object_lifetimes"]␠("id") -mz_object_transitive_dependencies_ind CREATE␠INDEX␠"mz_object_transitive_dependencies_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s530␠AS␠"mz_internal"."mz_object_transitive_dependencies"]␠("object_id") -mz_objects_ind CREATE␠INDEX␠"mz_objects_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s514␠AS␠"mz_catalog"."mz_objects"]␠("schema_id") +mz_notices_ind CREATE␠INDEX␠"mz_notices_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s780␠AS␠"mz_internal"."mz_notices"]␠("id") +mz_object_dependencies_ind CREATE␠INDEX␠"mz_object_dependencies_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s454␠AS␠"mz_internal"."mz_object_dependencies"]␠("object_id") +mz_object_history_ind CREATE␠INDEX␠"mz_object_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s518␠AS␠"mz_internal"."mz_object_history"]␠("id") +mz_object_lifetimes_ind CREATE␠INDEX␠"mz_object_lifetimes_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s519␠AS␠"mz_internal"."mz_object_lifetimes"]␠("id") +mz_object_transitive_dependencies_ind CREATE␠INDEX␠"mz_object_transitive_dependencies_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s532␠AS␠"mz_internal"."mz_object_transitive_dependencies"]␠("object_id") +mz_objects_ind CREATE␠INDEX␠"mz_objects_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s515␠AS␠"mz_catalog"."mz_objects"]␠("schema_id") mz_peek_durations_histogram_raw_s2_primary_idx CREATE␠INDEX␠"mz_peek_durations_histogram_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_peek_durations_histogram_raw"␠("worker_id",␠"type",␠"duration_ns") -mz_recent_activity_log_thinned_ind CREATE␠INDEX␠"mz_recent_activity_log_thinned_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s676␠AS␠"mz_internal"."mz_recent_activity_log_thinned"]␠("sql_hash") -mz_recent_sql_text_ind CREATE␠INDEX␠"mz_recent_sql_text_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s672␠AS␠"mz_internal"."mz_recent_sql_text"]␠("sql_hash") -mz_recent_storage_usage_ind CREATE␠INDEX␠"mz_recent_storage_usage_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s768␠AS␠"mz_catalog"."mz_recent_storage_usage"]␠("object_id") -mz_roles_ind CREATE␠INDEX␠"mz_roles_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s476␠AS␠"mz_catalog"."mz_roles"]␠("id") +mz_recent_activity_log_thinned_ind CREATE␠INDEX␠"mz_recent_activity_log_thinned_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s678␠AS␠"mz_internal"."mz_recent_activity_log_thinned"]␠("sql_hash") +mz_recent_sql_text_ind CREATE␠INDEX␠"mz_recent_sql_text_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s674␠AS␠"mz_internal"."mz_recent_sql_text"]␠("sql_hash") +mz_recent_storage_usage_ind CREATE␠INDEX␠"mz_recent_storage_usage_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s772␠AS␠"mz_catalog"."mz_recent_storage_usage"]␠("object_id") +mz_roles_ind CREATE␠INDEX␠"mz_roles_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s477␠AS␠"mz_catalog"."mz_roles"]␠("id") mz_scheduling_elapsed_raw_s2_primary_idx CREATE␠INDEX␠"mz_scheduling_elapsed_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_scheduling_elapsed_raw"␠("id",␠"worker_id") mz_scheduling_parks_histogram_raw_s2_primary_idx CREATE␠INDEX␠"mz_scheduling_parks_histogram_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_scheduling_parks_histogram_raw"␠("worker_id",␠"slept_for_ns",␠"requested_ns") -mz_schemas_ind CREATE␠INDEX␠"mz_schemas_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s455␠AS␠"mz_catalog"."mz_schemas"]␠("database_id") -mz_secrets_ind CREATE␠INDEX␠"mz_secrets_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s486␠AS␠"mz_catalog"."mz_secrets"]␠("name") -mz_show_all_objects_ind CREATE␠INDEX␠"mz_show_all_objects_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s561␠AS␠"mz_internal"."mz_show_all_objects"]␠("schema_id") -mz_show_cluster_replicas_ind CREATE␠INDEX␠"mz_show_cluster_replicas_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s711␠AS␠"mz_internal"."mz_show_cluster_replicas"]␠("cluster") -mz_show_clusters_ind CREATE␠INDEX␠"mz_show_clusters_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s563␠AS␠"mz_internal"."mz_show_clusters"]␠("name") -mz_show_columns_ind CREATE␠INDEX␠"mz_show_columns_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s562␠AS␠"mz_internal"."mz_show_columns"]␠("id") -mz_show_connections_ind CREATE␠INDEX␠"mz_show_connections_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s571␠AS␠"mz_internal"."mz_show_connections"]␠("schema_id") -mz_show_databases_ind CREATE␠INDEX␠"mz_show_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s565␠AS␠"mz_internal"."mz_show_databases"]␠("name") -mz_show_indexes_ind CREATE␠INDEX␠"mz_show_indexes_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s575␠AS␠"mz_internal"."mz_show_indexes"]␠("schema_id") -mz_show_materialized_views_ind CREATE␠INDEX␠"mz_show_materialized_views_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s574␠AS␠"mz_internal"."mz_show_materialized_views"]␠("schema_id") -mz_show_roles_ind CREATE␠INDEX␠"mz_show_roles_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s570␠AS␠"mz_internal"."mz_show_roles"]␠("name") -mz_show_schemas_ind CREATE␠INDEX␠"mz_show_schemas_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s566␠AS␠"mz_internal"."mz_show_schemas"]␠("database_id") -mz_show_secrets_ind CREATE␠INDEX␠"mz_show_secrets_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s564␠AS␠"mz_internal"."mz_show_secrets"]␠("schema_id") -mz_show_sinks_ind CREATE␠INDEX␠"mz_show_sinks_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s573␠AS␠"mz_internal"."mz_show_sinks"]␠("schema_id") -mz_show_sources_ind CREATE␠INDEX␠"mz_show_sources_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s572␠AS␠"mz_internal"."mz_show_sources"]␠("schema_id") -mz_show_tables_ind CREATE␠INDEX␠"mz_show_tables_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s567␠AS␠"mz_internal"."mz_show_tables"]␠("schema_id") -mz_show_types_ind CREATE␠INDEX␠"mz_show_types_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s569␠AS␠"mz_internal"."mz_show_types"]␠("schema_id") -mz_show_views_ind CREATE␠INDEX␠"mz_show_views_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s568␠AS␠"mz_internal"."mz_show_views"]␠("schema_id") -mz_sink_statistics_ind CREATE␠INDEX␠"mz_sink_statistics_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s689␠AS␠"mz_internal"."mz_sink_statistics"]␠("id") -mz_sink_status_history_ind CREATE␠INDEX␠"mz_sink_status_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s661␠AS␠"mz_internal"."mz_sink_status_history"]␠("sink_id") -mz_sink_statuses_ind CREATE␠INDEX␠"mz_sink_statuses_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s662␠AS␠"mz_internal"."mz_sink_statuses"]␠("id") -mz_sinks_ind CREATE␠INDEX␠"mz_sinks_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s466␠AS␠"mz_catalog"."mz_sinks"]␠("id") -mz_source_statistics_ind CREATE␠INDEX␠"mz_source_statistics_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s687␠AS␠"mz_internal"."mz_source_statistics"]␠("id") -mz_source_statistics_with_history_ind CREATE␠INDEX␠"mz_source_statistics_with_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s685␠AS␠"mz_internal"."mz_source_statistics_with_history"]␠("id") -mz_source_status_history_ind CREATE␠INDEX␠"mz_source_status_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s663␠AS␠"mz_internal"."mz_source_status_history"]␠("source_id") -mz_source_statuses_ind CREATE␠INDEX␠"mz_source_statuses_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s680␠AS␠"mz_internal"."mz_source_statuses"]␠("id") -mz_sources_ind CREATE␠INDEX␠"mz_sources_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s460␠AS␠"mz_catalog"."mz_sources"]␠("id") -mz_tables_ind CREATE␠INDEX␠"mz_tables_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s459␠AS␠"mz_catalog"."mz_tables"]␠("schema_id") -mz_types_ind CREATE␠INDEX␠"mz_types_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s470␠AS␠"mz_catalog"."mz_types"]␠("schema_id") -mz_views_ind CREATE␠INDEX␠"mz_views_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s467␠AS␠"mz_catalog"."mz_views"]␠("schema_id") -mz_wallclock_global_lag_recent_history_ind CREATE␠INDEX␠"mz_wallclock_global_lag_recent_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s696␠AS␠"mz_internal"."mz_wallclock_global_lag_recent_history"]␠("object_id") -mz_webhook_sources_ind CREATE␠INDEX␠"mz_webhook_sources_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s507␠AS␠"mz_internal"."mz_webhook_sources"]␠("id") -pg_attrdef_all_databases_ind CREATE␠INDEX␠"pg_attrdef_all_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s603␠AS␠"mz_internal"."pg_attrdef_all_databases"]␠("oid",␠"adrelid",␠"adnum",␠"adbin",␠"adsrc") -pg_attribute_all_databases_ind CREATE␠INDEX␠"pg_attribute_all_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s596␠AS␠"mz_internal"."pg_attribute_all_databases"]␠("attrelid",␠"attname",␠"atttypid",␠"attlen",␠"attnum",␠"atttypmod",␠"attnotnull",␠"atthasdef",␠"attidentity",␠"attgenerated",␠"attisdropped",␠"attcollation",␠"database_name",␠"pg_type_database_name") -pg_class_all_databases_ind CREATE␠INDEX␠"pg_class_all_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s584␠AS␠"mz_internal"."pg_class_all_databases"]␠("relname") -pg_description_all_databases_ind CREATE␠INDEX␠"pg_description_all_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s593␠AS␠"mz_internal"."pg_description_all_databases"]␠("objoid",␠"classoid",␠"objsubid",␠"description",␠"oid_database_name",␠"class_database_name") -pg_namespace_all_databases_ind CREATE␠INDEX␠"pg_namespace_all_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s581␠AS␠"mz_internal"."pg_namespace_all_databases"]␠("nspname") -pg_type_all_databases_ind CREATE␠INDEX␠"pg_type_all_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s590␠AS␠"mz_internal"."pg_type_all_databases"]␠("oid") +mz_schemas_ind CREATE␠INDEX␠"mz_schemas_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s456␠AS␠"mz_catalog"."mz_schemas"]␠("database_id") +mz_secrets_ind CREATE␠INDEX␠"mz_secrets_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s487␠AS␠"mz_catalog"."mz_secrets"]␠("name") +mz_show_all_objects_ind CREATE␠INDEX␠"mz_show_all_objects_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s563␠AS␠"mz_internal"."mz_show_all_objects"]␠("schema_id") +mz_show_cluster_replicas_ind CREATE␠INDEX␠"mz_show_cluster_replicas_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s715␠AS␠"mz_internal"."mz_show_cluster_replicas"]␠("cluster") +mz_show_clusters_ind CREATE␠INDEX␠"mz_show_clusters_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s565␠AS␠"mz_internal"."mz_show_clusters"]␠("name") +mz_show_columns_ind CREATE␠INDEX␠"mz_show_columns_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s564␠AS␠"mz_internal"."mz_show_columns"]␠("id") +mz_show_connections_ind CREATE␠INDEX␠"mz_show_connections_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s573␠AS␠"mz_internal"."mz_show_connections"]␠("schema_id") +mz_show_databases_ind CREATE␠INDEX␠"mz_show_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s567␠AS␠"mz_internal"."mz_show_databases"]␠("name") +mz_show_indexes_ind CREATE␠INDEX␠"mz_show_indexes_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s577␠AS␠"mz_internal"."mz_show_indexes"]␠("schema_id") +mz_show_materialized_views_ind CREATE␠INDEX␠"mz_show_materialized_views_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s576␠AS␠"mz_internal"."mz_show_materialized_views"]␠("schema_id") +mz_show_roles_ind CREATE␠INDEX␠"mz_show_roles_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s572␠AS␠"mz_internal"."mz_show_roles"]␠("name") +mz_show_schemas_ind CREATE␠INDEX␠"mz_show_schemas_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s568␠AS␠"mz_internal"."mz_show_schemas"]␠("database_id") +mz_show_secrets_ind CREATE␠INDEX␠"mz_show_secrets_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s566␠AS␠"mz_internal"."mz_show_secrets"]␠("schema_id") +mz_show_sinks_ind CREATE␠INDEX␠"mz_show_sinks_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s575␠AS␠"mz_internal"."mz_show_sinks"]␠("schema_id") +mz_show_sources_ind CREATE␠INDEX␠"mz_show_sources_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s574␠AS␠"mz_internal"."mz_show_sources"]␠("schema_id") +mz_show_tables_ind CREATE␠INDEX␠"mz_show_tables_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s569␠AS␠"mz_internal"."mz_show_tables"]␠("schema_id") +mz_show_types_ind CREATE␠INDEX␠"mz_show_types_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s571␠AS␠"mz_internal"."mz_show_types"]␠("schema_id") +mz_show_views_ind CREATE␠INDEX␠"mz_show_views_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s570␠AS␠"mz_internal"."mz_show_views"]␠("schema_id") +mz_sink_statistics_ind CREATE␠INDEX␠"mz_sink_statistics_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s691␠AS␠"mz_internal"."mz_sink_statistics"]␠("id") +mz_sink_status_history_ind CREATE␠INDEX␠"mz_sink_status_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s663␠AS␠"mz_internal"."mz_sink_status_history"]␠("sink_id") +mz_sink_statuses_ind CREATE␠INDEX␠"mz_sink_statuses_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s664␠AS␠"mz_internal"."mz_sink_statuses"]␠("id") +mz_sinks_ind CREATE␠INDEX␠"mz_sinks_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s467␠AS␠"mz_catalog"."mz_sinks"]␠("id") +mz_source_statistics_ind CREATE␠INDEX␠"mz_source_statistics_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s689␠AS␠"mz_internal"."mz_source_statistics"]␠("id") +mz_source_statistics_with_history_ind CREATE␠INDEX␠"mz_source_statistics_with_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s687␠AS␠"mz_internal"."mz_source_statistics_with_history"]␠("id") +mz_source_status_history_ind CREATE␠INDEX␠"mz_source_status_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s665␠AS␠"mz_internal"."mz_source_status_history"]␠("source_id") +mz_source_statuses_ind CREATE␠INDEX␠"mz_source_statuses_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s682␠AS␠"mz_internal"."mz_source_statuses"]␠("id") +mz_sources_ind CREATE␠INDEX␠"mz_sources_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s461␠AS␠"mz_catalog"."mz_sources"]␠("id") +mz_tables_ind CREATE␠INDEX␠"mz_tables_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s460␠AS␠"mz_catalog"."mz_tables"]␠("schema_id") +mz_types_ind CREATE␠INDEX␠"mz_types_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s471␠AS␠"mz_catalog"."mz_types"]␠("schema_id") +mz_views_ind CREATE␠INDEX␠"mz_views_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s468␠AS␠"mz_catalog"."mz_views"]␠("schema_id") +mz_wallclock_global_lag_recent_history_ind CREATE␠INDEX␠"mz_wallclock_global_lag_recent_history_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s698␠AS␠"mz_internal"."mz_wallclock_global_lag_recent_history"]␠("object_id") +mz_webhook_sources_ind CREATE␠INDEX␠"mz_webhook_sources_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s508␠AS␠"mz_internal"."mz_webhook_sources"]␠("id") +pg_attrdef_all_databases_ind CREATE␠INDEX␠"pg_attrdef_all_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s605␠AS␠"mz_internal"."pg_attrdef_all_databases"]␠("oid",␠"adrelid",␠"adnum",␠"adbin",␠"adsrc") +pg_attribute_all_databases_ind CREATE␠INDEX␠"pg_attribute_all_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s598␠AS␠"mz_internal"."pg_attribute_all_databases"]␠("attrelid",␠"attname",␠"atttypid",␠"attlen",␠"attnum",␠"atttypmod",␠"attnotnull",␠"atthasdef",␠"attidentity",␠"attgenerated",␠"attisdropped",␠"attcollation",␠"database_name",␠"pg_type_database_name") +pg_class_all_databases_ind CREATE␠INDEX␠"pg_class_all_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s586␠AS␠"mz_internal"."pg_class_all_databases"]␠("relname") +pg_description_all_databases_ind CREATE␠INDEX␠"pg_description_all_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s595␠AS␠"mz_internal"."pg_description_all_databases"]␠("objoid",␠"classoid",␠"objsubid",␠"description",␠"oid_database_name",␠"class_database_name") +pg_namespace_all_databases_ind CREATE␠INDEX␠"pg_namespace_all_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s583␠AS␠"mz_internal"."pg_namespace_all_databases"]␠("nspname") +pg_type_all_databases_ind CREATE␠INDEX␠"pg_type_all_databases_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s592␠AS␠"mz_internal"."pg_type_all_databases"]␠("oid") # Record all transitive dependencies (tables, sources, views, mvs) of indexes on # the mz_catalog_server cluster. @@ -161,6 +163,7 @@ mz_activity_log_thinned params mz_activity_log_thinned prepared_at mz_activity_log_thinned prepared_statement_id mz_activity_log_thinned prepared_statement_name +mz_activity_log_thinned result_size mz_activity_log_thinned rows_returned mz_activity_log_thinned sample_rate mz_activity_log_thinned search_path @@ -273,6 +276,9 @@ mz_comments comment mz_comments id mz_comments object_sub_id mz_comments object_type +mz_compute_dataflow_global_ids_per_worker global_id +mz_compute_dataflow_global_ids_per_worker id +mz_compute_dataflow_global_ids_per_worker worker_id mz_compute_dependencies dependency_id mz_compute_dependencies object_id mz_compute_error_counts_raw count @@ -298,6 +304,14 @@ mz_compute_import_frontiers_per_worker export_id mz_compute_import_frontiers_per_worker import_id mz_compute_import_frontiers_per_worker time mz_compute_import_frontiers_per_worker worker_id +mz_compute_lir_mapping_per_worker global_id +mz_compute_lir_mapping_per_worker lir_id +mz_compute_lir_mapping_per_worker nesting +mz_compute_lir_mapping_per_worker operator +mz_compute_lir_mapping_per_worker operator_id_end +mz_compute_lir_mapping_per_worker operator_id_start +mz_compute_lir_mapping_per_worker parent_lir_id +mz_compute_lir_mapping_per_worker worker_id mz_compute_operator_durations_histogram_raw duration_ns mz_compute_operator_durations_histogram_raw id mz_compute_operator_durations_histogram_raw worker_id @@ -497,6 +511,7 @@ mz_recent_activity_log_thinned params mz_recent_activity_log_thinned prepared_at mz_recent_activity_log_thinned prepared_statement_id mz_recent_activity_log_thinned prepared_statement_name +mz_recent_activity_log_thinned result_size mz_recent_activity_log_thinned rows_returned mz_recent_activity_log_thinned sample_rate mz_recent_activity_log_thinned search_path @@ -738,6 +753,7 @@ mz_statement_execution_history id mz_statement_execution_history mz_version mz_statement_execution_history params mz_statement_execution_history prepared_statement_id +mz_statement_execution_history result_size mz_statement_execution_history rows_returned mz_statement_execution_history sample_rate mz_statement_execution_history search_path diff --git a/test/sqllogictest/oid.slt b/test/sqllogictest/oid.slt index ccfee8dc83033..79ab0a04aec60 100644 --- a/test/sqllogictest/oid.slt +++ b/test/sqllogictest/oid.slt @@ -1149,3 +1149,7 @@ SELECT oid, name FROM mz_objects WHERE id LIKE 's%' AND oid < 20000 ORDER BY oid 17041 mz_network_policy_rules 17042 mz_index_advice 17043 mz_show_network_policies +17044 mz_compute_lir_mapping_per_worker +17045 mz_compute_dataflow_global_ids_per_worker +17046 mz_lir_mapping +17047 mz_dataflow_global_ids diff --git a/test/sqllogictest/pg_catalog_user.slt b/test/sqllogictest/pg_catalog_user.slt index 703c246f740b5..06f94c639473a 100644 --- a/test/sqllogictest/pg_catalog_user.slt +++ b/test/sqllogictest/pg_catalog_user.slt @@ -21,7 +21,7 @@ CREATE ROLE "materialize@foocorp.io" query TIBBBBTTT rowsort SELECT usename, usesysid, usecreatedb, usesuper, userepl, usebypassrls, passwd, valuntil, useconfig FROM pg_user; ---- -materialize@foocorp.io 20178 false NULL false false ******** NULL NULL +materialize@foocorp.io 20190 false NULL false false ******** NULL NULL mz_support 16662 false NULL false false ******** NULL NULL mz_system 16661 true true false false ******** NULL NULL diff --git a/test/sqllogictest/regclass.slt b/test/sqllogictest/regclass.slt index 84a67095de2c9..0267ea8995f5d 100644 --- a/test/sqllogictest/regclass.slt +++ b/test/sqllogictest/regclass.slt @@ -35,12 +35,12 @@ CREATE MATERIALIZED VIEW s.m AS SELECT * FROM s.t; query T SELECT 't'::regclass::oid::int ---- -20178 +20190 query T SELECT 's.t'::regclass::oid::int ---- -20179 +20191 query T SELECT 't'::regclass = 's.t'::regclass @@ -73,7 +73,7 @@ t query T SELECT 't'::regclass::oid::int ---- -20179 +20191 query T SELECT 'public.t'::regclass::text; @@ -101,12 +101,12 @@ d.public.t query T SELECT 'm'::regclass::oid::int ---- -20183 +20195 query T SELECT 's.m'::regclass::oid::int ---- -20184 +20196 query T SELECT 'm'::regclass = 's.m'::regclass @@ -296,7 +296,7 @@ true query T SELECT 'materialize.public.t'::regclass::oid::int ---- -20178 +20190 query error db error: ERROR: relation "t" does not exist SELECT 't'::regclass::oid::int diff --git a/test/sqllogictest/regtype.slt b/test/sqllogictest/regtype.slt index f8d336c220c55..f6a434f7e0d20 100644 --- a/test/sqllogictest/regtype.slt +++ b/test/sqllogictest/regtype.slt @@ -142,12 +142,12 @@ CREATE TYPE d.public.t AS LIST (ELEMENT TYPE = int4); query T SELECT 't'::regtype::oid::int ---- -20179 +20191 query T SELECT 's.t'::regtype::oid::int ---- -20180 +20192 query T SELECT 't'::regtype = 's.t'::regtype diff --git a/test/sqllogictest/window_funcs.slt b/test/sqllogictest/window_funcs.slt index b48c207077b89..cac1be204fe60 100644 --- a/test/sqllogictest/window_funcs.slt +++ b/test/sqllogictest/window_funcs.slt @@ -12,11 +12,6 @@ mode cockroach -simple conn=mz_system,user=mz_system -ALTER SYSTEM SET enable_reduce_unnest_list_fusion = true ----- -COMPLETE 0 - statement ok CREATE TABLE t(x string); @@ -7584,60 +7579,6 @@ ORDER BY x,y; 15 16 1111 240 2222 31 14 31 17 18 1111 306 2222 35 16 35 -simple conn=mz_system,user=mz_system -ALTER SYSTEM SET enable_value_window_function_fusion = false ----- -COMPLETE 0 - -simple conn=mz_system,user=mz_system -ALTER SYSTEM SET enable_window_aggregation_fusion = false ----- -COMPLETE 0 - -query T multiline -EXPLAIN -SELECT - *, - lag(x) OVER (), - lag(y) OVER (), - sum(x) OVER (), - min(x) OVER () -FROM t7 -ORDER BY x,y; ----- -Explained Query: - Finish order_by=[#0 asc nulls_last, #1 asc nulls_last] output=[#0..=#5] - Project (#3, #4, #8, #7, #6, #5) - Map (record_get[1](#1), record_get[0](#2), record_get[1](#2), record_get[3](#2), record_get[5](#2), record_get[7](#2), record_get[0](#1)) - FlatMap unnest_list(#0) - Reduce aggregates=[lag[order_by=[]](row(row(row(record_get[0](record_get[1](#0)), record_get[1](record_get[1](#0)), record_get[2](record_get[1](#0)), record_get[3](record_get[1](#0)), record_get[4](record_get[1](#0)), record_get[5](record_get[1](#0)), record_get[0](#0), record_get[0](#0)), row(record_get[0](record_get[1](#0)), 1, null))))] - Project (#1) - FlatMap unnest_list(#0) - Reduce aggregates=[lag[order_by=[]](row(row(row(record_get[0](record_get[1](#0)), record_get[1](record_get[1](#0)), record_get[2](record_get[1](#0)), record_get[3](record_get[1](#0)), record_get[0](#0), record_get[0](#0)), row(record_get[1](record_get[1](#0)), 1, null))))] - Project (#1) - FlatMap unnest_list(#0) - Reduce aggregates=[window_agg[sum order_by=[]](row(row(row(record_get[0](record_get[1](#0)), record_get[1](record_get[1](#0)), record_get[0](#0), record_get[0](#0)), record_get[0](record_get[1](#0)))))] - Project (#1) - FlatMap unnest_list(#0) - Reduce aggregates=[window_agg[min order_by=[]](row(row(row(#0, #1), #0)))] - ReadStorage materialize.public.t7 - -Source materialize.public.t7 - -Target cluster: quickstart - -EOF - -simple conn=mz_system,user=mz_system -ALTER SYSTEM SET enable_value_window_function_fusion = true ----- -COMPLETE 0 - -simple conn=mz_system,user=mz_system -ALTER SYSTEM SET enable_window_aggregation_fusion = true ----- -COMPLETE 0 - query T multiline EXPLAIN SELECT @@ -7706,7 +7647,8 @@ ORDER BY x,y,l; 13 14 11 15 16 13 -## Check some LIR plans that the optimization guarded by `enable_reduce_unnest_list_fusion` happens. +## Check some LIR plans that the optimization of fusing `Reduce` with `FlatMap UnnestList` happens. +## https://github.com/MaterializeInc/materialize/pull/29554 ## These should show `fused_unnest_list=true`. # Simple situation @@ -7893,192 +7835,6 @@ Target cluster: quickstart EOF -## Run some tests also with `enable_reduce_unnest_list_fusion` disabled. -simple conn=mz_system,user=mz_system -ALTER SYSTEM SET enable_reduce_unnest_list_fusion = false ----- -COMPLETE 0 - -# This is a new test. -query IIIIIIIIIIIIII -SELECT - *, - first_value( - lag(x*x,y,1111) OVER (PARTITION BY x ORDER BY y) - ) OVER (PARTITION BY x ORDER BY y), - x*y, - last_value( - lag(x*x,y,2222) OVER (PARTITION BY x ORDER BY y) - ) OVER (PARTITION BY x ORDER BY y+y), - last_value(x+y) OVER (PARTITION BY x ORDER BY y+y), - lag(y) OVER (ORDER BY x), - row_number() OVER (ORDER BY x), - rank() OVER (ORDER BY x), - dense_rank() OVER (ORDER BY x), - sum(y+3) OVER (ORDER BY x), - min(y+4) OVER (PARTITION BY x%2 ORDER BY x), - x+y, - sum(y+5) OVER (ORDER BY x) -FROM t7 -ORDER BY x,y; ----- -1 2 1111 2 2222 3 NULL 1 1 1 5 6 3 7 -3 NULL NULL NULL NULL NULL 2 2 2 2 5 6 NULL 7 -5 6 1111 30 2222 11 NULL 3 3 3 14 6 11 18 -7 8 1111 56 2222 15 6 4 4 4 25 6 15 31 -9 NULL NULL NULL NULL NULL 8 5 5 5 25 6 NULL 31 -10 -50 1111 -500 2222 -40 NULL 6 6 6 -59 -46 -40 -49 -10 -40 1111 -400 2222 -30 -50 7 6 6 -59 -46 -30 -49 -11 NULL NULL NULL NULL NULL -40 8 8 7 -59 6 NULL -49 -13 14 1111 182 2222 27 NULL 9 9 8 -42 6 27 -30 -15 16 1111 240 2222 31 14 10 10 9 -23 6 31 -9 -17 18 1111 306 2222 35 16 11 11 10 -2 6 35 14 - -# These are copied from above. -query III -SELECT * -FROM ( - SELECT *, lag(x) OVER (ORDER BY x) AS l - FROM t7 -) -WHERE l/2 < 7 -ORDER BY x,y,l; ----- -3 NULL 1 -5 6 3 -7 8 5 -9 NULL 7 -10 -50 9 -10 -40 10 -11 NULL 10 -13 14 11 -15 16 13 - -query IRRB -WITH - simple AS ( - SELECT - x%5 AS x5_simple, - avg(y) OVER (PARTITION BY x%5) AS avg_simple - FROM t7 - ), - complicated AS ( -- array_agg, then do an unnest and global agg in a subquery in a SELECT - WITH - array_agg AS ( - SELECT - x%5 AS x5, - array_agg(y) OVER (PARTITION BY x%5) AS l - FROM t7 - ) - SELECT - x5 AS x5_complicated, - ( - SELECT avg(uy) - FROM unnest(l) AS uy - ) AS avg_complicated - FROM array_agg - ) -SELECT DISTINCT - x5_simple, - avg_simple, - avg_complicated, - avg_simple = avg_complicated -FROM simple, complicated -WHERE x5_simple = x5_complicated -ORDER BY x5_simple; ----- -0 -17 -17 true -1 2 2 true -2 13 13 true -3 14 14 true -4 NULL NULL NULL - -query IIIIIIIIIIIIIIIIIIII -select - x, - y, - lag(y) over (partition by x%4 order by x) as lag1, - lag(y) respect nulls over (partition by x%4 order by x) as lag1_resp, - lag(y) ignore nulls over (partition by x%4 order by x) as lag1_ign, - lead(y) over (partition by x%4 order by x) as lead1, - lead(y) ignore nulls over (partition by x%4 order by x) as lead1_ign, - lag(y, 2) over (partition by x%4 order by x) as lag2, - lag(y, 2) ignore nulls over (partition by x%4 order by x) as lag2_ign, - lead(y, 2) over (partition by x%4 order by x) as lead2, - lead(y, 2) ignore nulls over (partition by x%4 order by x) as lead2_ign, - lag(y, 2, -1) ignore nulls over (partition by x%4 order by x) as lag2_ign_def, - lead(y, 2, -1) ignore nulls over (partition by x%4 order by x) as lead2_ign_def, - lag(y, 2, 16) ignore nulls over (partition by x%4 order by x) as lag2_ign_def16, - lead(y, 2, 16) ignore nulls over (partition by x%4 order by x) as lead2_ign_def16, - lag(y, -1, 100) ignore nulls over (partition by x%4 order by x) as lag_neg_offset, - lead(y, -2, 100) ignore nulls over (partition by x%4 order by x) as lead_neg_offset, - lag(y, y/5+1) ignore nulls over (partition by x%4 order by x) as lag_dynamic_offset, - lead(y, y/5+1) ignore nulls over (partition by x%4 order by x) as lead_dynamic_offset, - lag(y, null) ignore nulls over (partition by x%4 order by x) as lag_literal_null_offset -from t6 -order by x%4, x; ----- -1 2 NULL NULL NULL 6 6 NULL NULL NULL 14 -1 14 16 14 6 100 NULL 6 NULL -5 6 2 2 2 NULL 14 NULL NULL 14 18 -1 18 16 18 14 100 NULL 18 NULL -9 NULL 6 6 6 14 14 2 2 18 18 2 18 2 18 14 2 NULL NULL NULL -13 14 NULL NULL 6 18 18 6 2 NULL NULL 2 -1 2 16 18 2 NULL NULL NULL -17 18 14 14 14 NULL NULL NULL 6 NULL NULL 6 -1 6 16 100 6 NULL NULL NULL -3 NULL NULL NULL NULL 8 8 NULL NULL NULL 16 -1 16 16 16 8 100 NULL NULL NULL -7 8 NULL NULL NULL NULL 16 NULL NULL 16 NULL -1 -1 16 16 16 100 NULL NULL NULL -11 NULL 8 8 8 16 16 NULL NULL NULL NULL -1 -1 16 16 16 100 NULL NULL NULL -15 16 NULL NULL 8 NULL NULL 8 NULL NULL NULL -1 -1 16 16 100 100 NULL NULL NULL - -# Run the same test with also `enable_value_window_function_fusion` disabled. -simple conn=mz_system,user=mz_system -ALTER SYSTEM SET enable_value_window_function_fusion = false ----- -COMPLETE 0 - -query IIIIIIIIIIIIIIIIIIII -select - x, - y, - lag(y) over (partition by x%4 order by x) as lag1, - lag(y) respect nulls over (partition by x%4 order by x) as lag1_resp, - lag(y) ignore nulls over (partition by x%4 order by x) as lag1_ign, - lead(y) over (partition by x%4 order by x) as lead1, - lead(y) ignore nulls over (partition by x%4 order by x) as lead1_ign, - lag(y, 2) over (partition by x%4 order by x) as lag2, - lag(y, 2) ignore nulls over (partition by x%4 order by x) as lag2_ign, - lead(y, 2) over (partition by x%4 order by x) as lead2, - lead(y, 2) ignore nulls over (partition by x%4 order by x) as lead2_ign, - lag(y, 2, -1) ignore nulls over (partition by x%4 order by x) as lag2_ign_def, - lead(y, 2, -1) ignore nulls over (partition by x%4 order by x) as lead2_ign_def, - lag(y, 2, 16) ignore nulls over (partition by x%4 order by x) as lag2_ign_def16, - lead(y, 2, 16) ignore nulls over (partition by x%4 order by x) as lead2_ign_def16, - lag(y, -1, 100) ignore nulls over (partition by x%4 order by x) as lag_neg_offset, - lead(y, -2, 100) ignore nulls over (partition by x%4 order by x) as lead_neg_offset, - lag(y, y/5+1) ignore nulls over (partition by x%4 order by x) as lag_dynamic_offset, - lead(y, y/5+1) ignore nulls over (partition by x%4 order by x) as lead_dynamic_offset, - lag(y, null) ignore nulls over (partition by x%4 order by x) as lag_literal_null_offset -from t6 -order by x%4, x; ----- -1 2 NULL NULL NULL 6 6 NULL NULL NULL 14 -1 14 16 14 6 100 NULL 6 NULL -5 6 2 2 2 NULL 14 NULL NULL 14 18 -1 18 16 18 14 100 NULL 18 NULL -9 NULL 6 6 6 14 14 2 2 18 18 2 18 2 18 14 2 NULL NULL NULL -13 14 NULL NULL 6 18 18 6 2 NULL NULL 2 -1 2 16 18 2 NULL NULL NULL -17 18 14 14 14 NULL NULL NULL 6 NULL NULL 6 -1 6 16 100 6 NULL NULL NULL -3 NULL NULL NULL NULL 8 8 NULL NULL NULL 16 -1 16 16 16 8 100 NULL NULL NULL -7 8 NULL NULL NULL NULL 16 NULL NULL 16 NULL -1 -1 16 16 16 100 NULL NULL NULL -11 NULL 8 8 8 16 16 NULL NULL NULL NULL -1 -1 16 16 16 100 NULL NULL NULL -15 16 NULL NULL 8 NULL NULL 8 NULL NULL NULL -1 -1 16 16 100 100 NULL NULL NULL - -simple conn=mz_system,user=mz_system -ALTER SYSTEM SET enable_value_window_function_fusion = true ----- -COMPLETE 0 - -simple conn=mz_system,user=mz_system -ALTER SYSTEM SET enable_reduce_unnest_list_fusion = true ----- -COMPLETE 0 - ## Window functions on big relations. statement ok diff --git a/test/ssh-connection/kafka-sink.td b/test/ssh-connection/kafka-sink.td index 8afe94e40a2b3..96dd79aea060c 100644 --- a/test/ssh-connection/kafka-sink.td +++ b/test/ssh-connection/kafka-sink.td @@ -9,6 +9,7 @@ $ postgres-execute connection=postgres://mz_system:materialize@${testdrive.materialize-internal-sql-addr} ALTER SYSTEM SET enable_create_source_denylist_with_options = true +ALTER SYSTEM SET kafka_transaction_timeout = '60s' # Test creating a Kafka sink using ssh. diff --git a/test/terraform/.gitignore b/test/terraform/.gitignore new file mode 100644 index 0000000000000..1c9078693e921 --- /dev/null +++ b/test/terraform/.gitignore @@ -0,0 +1,4 @@ +.terraform.lock.hcl +.terraform +terraform.tfstate +terraform.tfstate.backup diff --git a/test/terraform/aws/main.tf b/test/terraform/aws/main.tf new file mode 100644 index 0000000000000..799b790523fde --- /dev/null +++ b/test/terraform/aws/main.tf @@ -0,0 +1,102 @@ +# Copyright Materialize, Inc. and contributors. All rights reserved. +# +# Use of this software is governed by the Business Source License +# included in the LICENSE file at the root of this repository. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0. + +provider "aws" { + region = "us-east-1" +} + +module "materialize_infrastructure" { + source = "git::https://github.com/MaterializeInc/terraform-aws-materialize.git?ref=v0.1.3" + + # Basic settings + environment = "dev" + vpc_name = "terraform-aws-test-vpc" + cluster_name = "terraform-aws-test-cluster" + mz_iam_service_account_name = "terraform-aws-test-user" + mz_iam_role_name = "terraform-aws-test-s3-role" + + # VPC Configuration + vpc_cidr = "10.0.0.0/16" + availability_zones = ["us-east-1a", "us-east-1b"] + private_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"] + public_subnet_cidrs = ["10.0.101.0/24", "10.0.102.0/24"] + single_nat_gateway = true + + # EKS Configuration + cluster_version = "1.31" + node_group_instance_types = ["c7a.2xlarge"] + node_group_desired_size = 2 + node_group_min_size = 1 + node_group_max_size = 3 + node_group_capacity_type = "ON_DEMAND" + + # Storage Configuration + bucket_name = "terraform-aws-test-storage-${random_id.suffix.hex}" + enable_bucket_versioning = true + enable_bucket_encryption = true + bucket_force_destroy = true + + # Database Configuration + database_password = "someRANDOMpasswordNOTsecure" + db_identifier = "terraform-aws-test-metadata-db" + postgres_version = "15" + db_instance_class = "db.t3.micro" + db_allocated_storage = 20 + database_name = "materialize" + database_username = "materialize" + db_multi_az = false + + # Basic monitoring + enable_monitoring = true + metrics_retention_days = 7 + + # Tags + tags = { + Environment = "dev" + Project = "terraform-aws-test" + Terraform = "true" + } +} + +# Generate random suffix for unique S3 bucket name +resource "random_id" "suffix" { + byte_length = 4 +} + +# outputs.tf +output "eks_cluster_endpoint" { + description = "EKS cluster endpoint" + value = module.materialize_infrastructure.eks_cluster_endpoint +} + +output "database_endpoint" { + description = "RDS instance endpoint" + value = module.materialize_infrastructure.database_endpoint +} + +output "s3_bucket_name" { + description = "Name of the S3 bucket" + value = module.materialize_infrastructure.s3_bucket_name +} + +output "materialize_s3_role_arn" { + description = "The ARN of the IAM role for Materialize" + value = module.materialize_infrastructure.materialize_s3_role_arn +} + +output "metadata_backend_url" { + description = "PostgreSQL connection URL in the format required by Materialize" + value = module.materialize_infrastructure.metadata_backend_url + sensitive = true +} + +output "persist_backend_url" { + description = "S3 connection URL in the format required by Materialize using IRSA" + value = module.materialize_infrastructure.persist_backend_url +} diff --git a/test/terraform/aws/simple.tf b/test/terraform/aws/simple.tf new file mode 100644 index 0000000000000..6bb8cfcfdaa25 --- /dev/null +++ b/test/terraform/aws/simple.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.75.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + } +} diff --git a/test/terraform/mzcompose b/test/terraform/mzcompose new file mode 100755 index 0000000000000..1f866645dabc8 --- /dev/null +++ b/test/terraform/mzcompose @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Copyright Materialize, Inc. and contributors. All rights reserved. +# +# Use of this software is governed by the Business Source License +# included in the LICENSE file at the root of this repository. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0. +# +# mzcompose — runs Docker Compose with Materialize customizations. + +exec "$(dirname "$0")"/../../bin/pyactivate -m materialize.cli.mzcompose "$@" diff --git a/test/terraform/mzcompose.py b/test/terraform/mzcompose.py new file mode 100644 index 0000000000000..f2070eae8c777 --- /dev/null +++ b/test/terraform/mzcompose.py @@ -0,0 +1,472 @@ +# Copyright Materialize, Inc. and contributors. All rights reserved. # +# Use of this software is governed by the Business Source License +# included in the LICENSE file at the root of this repository. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0. + +""" +Tests the mz command line tool against a real Cloud instance +""" + +import argparse +import os +import signal +import subprocess +import time +from collections.abc import Sequence +from pathlib import Path +from textwrap import dedent +from typing import IO + +import psycopg +import yaml + +from materialize import MZ_ROOT, ci_util, git, spawn +from materialize.mzcompose.composition import ( + Composition, + WorkflowArgumentParser, +) +from materialize.mzcompose.services.testdrive import Testdrive + +SERVICES = [ + Testdrive(), # Overridden below +] + + +def run_ignore_error( + args: Sequence[Path | str], + cwd: Path | None = None, + stdin: None | int | IO[bytes] | bytes = None, +): + try: + spawn.runv(args, cwd=cwd, stdin=stdin) + except subprocess.CalledProcessError: + pass + + +def workflow_default(c: Composition, parser: WorkflowArgumentParser) -> None: + """To run locally use `aws sso login` first.""" + parser.add_argument( + "--setup", + default=True, + action=argparse.BooleanOptionalAction, + help="Run setup steps", + ) + parser.add_argument( + "--cleanup", + default=True, + action=argparse.BooleanOptionalAction, + help="Destroy the region at the end of the workflow.", + ) + parser.add_argument( + "--tag", + type=str, + help="Custom version tag to use", + ) + + args = parser.parse_args() + + tag = args.tag or f"v{ci_util.get_mz_version()}--pr.g{git.rev_parse('HEAD')}" + materialize_environment = None + port_forward_process = None + + path = MZ_ROOT / "test" / "terraform" / "aws" + try: + if args.setup: + print("--- Setup") + spawn.runv(["terraform", "init"], cwd=path) + spawn.runv(["terraform", "validate"], cwd=path) + spawn.runv(["terraform", "plan"], cwd=path) + spawn.runv(["terraform", "apply", "-auto-approve"], cwd=path) + + metadata_backend_url = spawn.capture( + ["terraform", "output", "-raw", "metadata_backend_url"], cwd=path + ).strip() + persist_backend_url = spawn.capture( + ["terraform", "output", "-raw", "persist_backend_url"], cwd=path + ).strip() + materialize_s3_role_arn = spawn.capture( + ["terraform", "output", "-raw", "materialize_s3_role_arn"], cwd=path + ).strip() + + spawn.runv( + [ + "aws", + "eks", + "update-kubeconfig", + "--name", + "terraform-aws-test-cluster", + "--region", + "us-east-1", + ] + ) + + spawn.runv(["kubectl", "get", "nodes"]) + # Not working yet? + # spawn.runv( + # ["helm", "repo", "add", "openebs", "https://openebs.github.io/openebs"] + # ) + # spawn.runv(["helm", "repo", "update"]) + # spawn.runv( + # [ + # "helm", + # "install", + # "openebs", + # "--namespace", + # "openebs", + # "openebs/openebs", + # "--set", + # "engines.replicated.mayastor.enabled=false", + # "--create-namespace", + # ] + # ) + # spawn.runv( + # ["kubectl", "get", "pods", "-n", "openebs", "-l", "role=openebs-lvm"] + # ) + + aws_account_id = spawn.capture( + [ + "aws", + "sts", + "get-caller-identity", + "--query", + "Account", + "--output", + "text", + ] + ).strip() + public_ip_address = spawn.capture( + ["curl", "http://checkip.amazonaws.com"] + ).strip() + + materialize_values = { + "operator": { + "image": {"tag": tag}, + "cloudProvider": { + "type": "aws", + "region": "us-east-1", + "providers": { + "aws": { + "enabled": True, + "accountID": aws_account_id, + "iam": { + "roles": {"environment": materialize_s3_role_arn} + }, + } + }, + }, + }, + "namespace": {"create": True, "name": "materialize"}, + "networkPolicies": { + "enabled": True, + "egress": {"enabled": True, "cidrs": ["0.0.0.0/0"]}, + "ingress": {"enabled": True, "cidrs": [f"{public_ip_address}/24"]}, + "internal": {"enabled": True}, + }, + } + + spawn.runv( + [ + "helm", + "install", + "materialize-operator", + "misc/helm-charts/operator", + "-f", + "-", + ], + cwd=MZ_ROOT, + stdin=yaml.dump(materialize_values).encode(), + ) + for i in range(60): + try: + spawn.runv( + ["kubectl", "get", "pods", "-n", "materialize"], + cwd=path, + ) + status = spawn.capture( + [ + "kubectl", + "get", + "pods", + "-n", + "materialize", + "-o", + "jsonpath={.items[0].status.phase}", + ], + cwd=path, + ) + if status == "Running": + break + except subprocess.CalledProcessError: + time.sleep(1) + else: + raise ValueError("Never completed") + + spawn.runv(["kubectl", "create", "namespace", "materialize-environment"]) + + materialize_backend_secret = { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "materialize-backend", + "namespace": "materialize-environment", + }, + "stringData": { + "metadata_backend_url": metadata_backend_url, + "persist_backend_url": persist_backend_url, + }, + } + + spawn.runv( + ["kubectl", "apply", "-f", "-"], + cwd=path, + stdin=yaml.dump(materialize_backend_secret).encode(), + ) + + materialize_environment = { + "apiVersion": "materialize.cloud/v1alpha1", + "kind": "Materialize", + "metadata": { + "name": "12345678-1234-1234-1234-123456789012", + "namespace": "materialize-environment", + }, + "spec": { + "environmentdImageRef": f"materialize/environmentd:{tag}", + "environmentdResourceRequirements": { + "limits": {"memory": "4Gi"}, + "requests": {"cpu": "2", "memory": "4Gi"}, + }, + "balancerdResourceRequirements": { + "limits": {"memory": "256Mi"}, + "requests": {"cpu": "100m", "memory": "256Mi"}, + }, + "backendSecretName": "materialize-backend", + }, + } + + spawn.runv( + ["kubectl", "apply", "-f", "-"], + cwd=path, + stdin=yaml.dump(materialize_environment).encode(), + ) + for i in range(60): + try: + spawn.runv( + [ + "kubectl", + "get", + "materializes", + "-n", + "materialize-environment", + ], + cwd=path, + ) + break + except subprocess.CalledProcessError: + time.sleep(1) + else: + raise ValueError("Never completed") + for i in range(60): + try: + spawn.runv( + ["kubectl", "get", "pods", "-n", "materialize-environment"], + cwd=path, + ) + status = spawn.capture( + [ + "kubectl", + "get", + "pods", + "-n", + "materialize-environment", + "-o", + "jsonpath={.items[0].status.phase}", + ], + cwd=path, + ) + if status == "Running": + break + except subprocess.CalledProcessError: + time.sleep(1) + else: + raise ValueError("Never completed") + else: + spawn.runv( + [ + "aws", + "eks", + "update-kubeconfig", + "--name", + "terraform-aws-test-cluster", + "--region", + "us-east-1", + ] + ) + + print("--- Running tests") + environmentd_name = spawn.capture( + [ + "kubectl", + "get", + "pods", + "-l", + "app=environmentd", + "-n", + "materialize-environment", + "-o", + "jsonpath={.items[*].metadata.name}", + ], + cwd=path, + ) + # These sleeps are a bit unclean, we should instead only consider + # environmentd as running when it is has started listening on its ports + time.sleep(30) + port_forward_process = subprocess.Popen( + [ + "kubectl", + "port-forward", + f"pod/{environmentd_name}", + "-n", + "materialize-environment", + "6875:6875", + "6876:6876", + "6877:6877", + "6878:6878", + ], + preexec_fn=os.setpgrp, + ) + time.sleep(30) + + with c.override( + Testdrive( + materialize_url="postgres://materialize@127.0.0.1:6875/materialize", + materialize_url_internal="postgres://mz_system:materialize@127.0.0.1:6877/materialize", + materialize_use_https=True, + no_consistency_checks=True, + network_mode="host", + volume_workdir="../testdrive:/workdir", + # For full testdrive support we'll need: + # kafka_url=... + # schema_registry_url=... + # aws_endpoint=... + ) + ): + c.up("testdrive", persistent=True) + c.testdrive( + dedent( + """ + > SELECT 1 + 1 + """ + ) + ) + + with psycopg.connect( + "postgres://materialize@127.0.0.1:6875/materialize" + ) as conn: + with conn.cursor() as cur: + cur.execute("SELECT 1") + results = cur.fetchall() + assert results == [(1,)], results + cur.execute("SELECT mz_version()") + version = cur.fetchall()[0][0] + assert version.startswith(tag.split("--")[0] + " ") + with open( + MZ_ROOT / "misc" / "helm-charts" / "operator" / "Chart.yaml" + ) as f: + content = yaml.load(f, Loader=yaml.Loader) + helm_chart_version = content["version"] + assert version.endswith(f", helm chart: {helm_chart_version})") + + c.run_testdrive_files( + "array.td", + "char-varchar-distinct.td", + "char-varchar-joins.td", + "char-varchar-multibyte.td", + "constants.td", + "coordinator-multiplicities.td", + "create-views.td", + "date_func.td", + "decimal-distinct.td", + "decimal-join.td", + "decimal-order.td", + "decimal-overflow.td", + "decimal-sum.td", + "decimal-zero.td", + "delete-using.td", + "drop.td", + "duplicate-table-names.td", + "failpoints.td", + "fetch-tail-as-of.td", + "fetch-tail-large-diff.td", + "fetch-tail-limit-timeout.td", + "fetch-tail-timestamp-zero.td", + "fetch-timeout.td", + "float_sum.td", + "get-started.td", + "github-11563.td", + "github-1947.td", + "github-3281.td", + "github-5502.td", + "github-5774.td", + "github-5873.td", + "github-5983.td", + "github-5984.td", + "github-6335.td", + "github-6744.td", + "github-6950.td", + "github-7171.td", + "github-7191.td", + "github-795.td", + "joins.td", + "jsonb.td", + "list.td", + "logging.td", + "map.td", + "multijoins.td", + "numeric-sum.td", + "numeric.td", + "oid.td", + "orms.td", + "pg-catalog.td", + "runtime-errors.td", + "search_path.td", + "self-test.td", + "string.td", + "subquery-scalar-errors.td", + "system-functions.td", + "test-skip-if.td", + "type_char_quoted.td", + "version.td", + ) + finally: + if port_forward_process: + os.killpg(os.getpgid(port_forward_process.pid), signal.SIGTERM) + + if args.cleanup: + print("--- Cleaning up") + if materialize_environment: + run_ignore_error( + ["kubectl", "delete", "-f", "-"], + cwd=path, + stdin=yaml.dump(materialize_environment).encode(), + ) + run_ignore_error( + [ + "kubectl", + "delete", + "materialize.materialize.cloud/12345678-1234-1234-1234-123456789012", + "-n" "materialize-environment", + ] + ) + run_ignore_error( + ["kubectl", "delete", "namespace", "materialize-environment"] + ) + run_ignore_error( + ["helm", "uninstall", "materialize-operator"], + cwd=path, + ) + run_ignore_error(["kubectl", "delete", "namespace", "materialize"]) + spawn.runv(["terraform", "destroy", "-auto-approve"], cwd=path) diff --git a/test/testdrive-old-kafka-src-syntax/hydration-status.td b/test/testdrive-old-kafka-src-syntax/hydration-status.td index d79339904b3c7..8aa5331869dea 100644 --- a/test/testdrive-old-kafka-src-syntax/hydration-status.td +++ b/test/testdrive-old-kafka-src-syntax/hydration-status.td @@ -30,17 +30,17 @@ # Introspection subscribes do not show up because the hydration introspection # relations exclude transient dataflow. -> SELECT DISTINCT left(h.object_id, 1), h.hydrated +> SELECT DISTINCT left(h.object_id, 2), h.hydrated FROM mz_internal.mz_compute_hydration_statuses h JOIN mz_cluster_replicas r ON (r.id = h.replica_id) WHERE r.name LIKE 'hydrated_test%'; -s true +si true -> SELECT DISTINCT left(h.object_id, 1), h.hydrated +> SELECT DISTINCT left(h.object_id, 2), h.hydrated FROM mz_internal.mz_hydration_statuses h JOIN mz_cluster_replicas r ON (r.id = h.replica_id) WHERE r.name LIKE 'hydrated_test%'; -s true +si true # No operator-level hydration status logging for introspection dataflows. > SELECT DISTINCT left(h.object_id, 1), h.hydrated diff --git a/test/testdrive-old-kafka-src-syntax/indexes.td b/test/testdrive-old-kafka-src-syntax/indexes.td index b16708bbe6a97..0547741dd4e7e 100644 --- a/test/testdrive-old-kafka-src-syntax/indexes.td +++ b/test/testdrive-old-kafka-src-syntax/indexes.td @@ -303,11 +303,13 @@ mz_clusters_ind mz_clusters mz_columns_ind mz_columns mz_catalog_server {name} "" mz_comments_ind mz_comments mz_catalog_server {id} "" mz_compute_dependencies_ind mz_compute_dependencies mz_catalog_server {dependency_id} "" +mz_compute_dataflow_global_ids_per_worker_s2_primary_idx mz_compute_dataflow_global_ids_per_worker mz_catalog_server {id,worker_id} "" mz_compute_error_counts_raw_s2_primary_idx mz_compute_error_counts_raw mz_catalog_server {export_id,worker_id} "" mz_compute_exports_per_worker_s2_primary_idx mz_compute_exports_per_worker mz_catalog_server {export_id,worker_id} "" mz_compute_frontiers_per_worker_s2_primary_idx mz_compute_frontiers_per_worker mz_catalog_server {export_id,worker_id} "" mz_compute_hydration_times_per_worker_s2_primary_idx mz_compute_hydration_times_per_worker mz_catalog_server {export_id,worker_id} "" mz_compute_import_frontiers_per_worker_s2_primary_idx mz_compute_import_frontiers_per_worker mz_catalog_server {export_id,import_id,worker_id} "" +mz_compute_lir_mapping_per_worker_s2_primary_idx mz_compute_lir_mapping_per_worker mz_catalog_server {global_id,lir_id,worker_id} "" mz_compute_operator_durations_histogram_raw_s2_primary_idx mz_compute_operator_durations_histogram_raw mz_catalog_server {id,worker_id,duration_ns} "" mz_connections_ind mz_connections mz_catalog_server {schema_id} "" mz_console_cluster_utilization_overview_ind mz_console_cluster_utilization_overview mz_catalog_server {cluster_id} "" diff --git a/test/testdrive/catalog.td b/test/testdrive/catalog.td index 801712c53dfef..044f37c5aa0bb 100644 --- a/test/testdrive/catalog.td +++ b/test/testdrive/catalog.td @@ -715,11 +715,13 @@ mz_arrangement_heap_capacity_raw log "" mz_arrangement_heap_size_raw log "" mz_arrangement_records_raw log "" mz_arrangement_sharing_raw log "" +mz_compute_dataflow_global_ids_per_worker log "" mz_compute_error_counts_raw log "" mz_compute_exports_per_worker log "" mz_compute_frontiers_per_worker log "" mz_compute_hydration_times_per_worker log "" mz_compute_import_frontiers_per_worker log "" +mz_compute_lir_mapping_per_worker log "" mz_compute_operator_durations_histogram_raw log "" mz_dataflow_addresses_per_worker log "" mz_dataflow_channels_per_worker log "" @@ -754,6 +756,7 @@ mz_dataflow_arrangement_sizes "" mz_dataflow_channel_operators "" mz_dataflow_channel_operators_per_worker "" mz_dataflow_channels "" +mz_dataflow_global_ids "" mz_dataflow_operator_dataflows "" mz_dataflow_operator_dataflows_per_worker "" mz_dataflow_operator_parents "" @@ -766,6 +769,7 @@ mz_dataflow_shutdown_durations_histogram_per_worker "" mz_dataflows "" mz_dataflows_per_worker "" mz_expected_group_size_advice "" +mz_lir_mapping "" mz_message_counts "" mz_message_counts_per_worker "" mz_peek_durations_histogram "" @@ -796,7 +800,7 @@ test_table "" # There is one entry in mz_indexes for each field_number/expression of the index. > SELECT COUNT(id) FROM mz_indexes WHERE id LIKE 's%' -242 +254 # Create a second schema with the same table name as above > CREATE SCHEMA tester2 diff --git a/test/testdrive/distinct-arrangements.td b/test/testdrive/distinct-arrangements.td index 51ed3ed963f6e..8e2bfba1625ab 100644 --- a/test/testdrive/distinct-arrangements.td +++ b/test/testdrive/distinct-arrangements.td @@ -740,8 +740,8 @@ ReduceMinsMaxes "ArrangeBy[[Column(15)]]-errors" 1 "ArrangeBy[[Column(2)]]" 20 "ArrangeBy[[Column(2)]]-errors" 7 -"ArrangeBy[[Column(20)]]" 1 -"ArrangeBy[[Column(20)]]-errors" 1 +"ArrangeBy[[Column(21)]]" 1 +"ArrangeBy[[Column(21)]]-errors" 1 "ArrangeBy[[Column(3)]]" 6 "ArrangeBy[[Column(3)]]-errors" 2 "ArrangeBy[[Column(4)]]" 5 @@ -781,8 +781,10 @@ ReduceMinsMaxes "Arrange Compute(ArrangementHeapCapacity)" "Arrange Compute(ArrangementHeapSize)" "Arrange Compute(DataflowCurrent)" +"Arrange Compute(DataflowGlobal)" "Arrange Compute(ErrorCount)" "Arrange Compute(FrontierCurrent)" +"Arrange Compute(LirMapping)" "Arrange Compute(HydrationTime)" "Arrange Compute(ImportFrontierCurrent)" "Arrange Compute(PeekCurrent)" diff --git a/test/testdrive/divergent-dataflow-cancellation.td b/test/testdrive/divergent-dataflow-cancellation.td index 6672101715380..eaed19d1eb818 100644 --- a/test/testdrive/divergent-dataflow-cancellation.td +++ b/test/testdrive/divergent-dataflow-cancellation.td @@ -88,13 +88,13 @@ contains: canceling statement due to statement timeout > SELECT count(*) FROM mz_introspection.mz_compute_exports_per_worker WHERE worker_id = 0 -29 +31 # One frontier for each introspection arrangement. > SELECT count(*) FROM mz_introspection.mz_compute_frontiers_per_worker WHERE worker_id = 0 -29 +31 > SELECT count(*) FROM mz_introspection.mz_compute_import_frontiers_per_worker 0 diff --git a/test/testdrive/hydration-status.td b/test/testdrive/hydration-status.td index d79339904b3c7..8aa5331869dea 100644 --- a/test/testdrive/hydration-status.td +++ b/test/testdrive/hydration-status.td @@ -30,17 +30,17 @@ # Introspection subscribes do not show up because the hydration introspection # relations exclude transient dataflow. -> SELECT DISTINCT left(h.object_id, 1), h.hydrated +> SELECT DISTINCT left(h.object_id, 2), h.hydrated FROM mz_internal.mz_compute_hydration_statuses h JOIN mz_cluster_replicas r ON (r.id = h.replica_id) WHERE r.name LIKE 'hydrated_test%'; -s true +si true -> SELECT DISTINCT left(h.object_id, 1), h.hydrated +> SELECT DISTINCT left(h.object_id, 2), h.hydrated FROM mz_internal.mz_hydration_statuses h JOIN mz_cluster_replicas r ON (r.id = h.replica_id) WHERE r.name LIKE 'hydrated_test%'; -s true +si true # No operator-level hydration status logging for introspection dataflows. > SELECT DISTINCT left(h.object_id, 1), h.hydrated diff --git a/test/testdrive/indexes.td b/test/testdrive/indexes.td index 4e7799538fcf5..620c8a3de8d61 100644 --- a/test/testdrive/indexes.td +++ b/test/testdrive/indexes.td @@ -320,11 +320,13 @@ mz_clusters_ind mz_clusters mz_columns_ind mz_columns mz_catalog_server {name} "" mz_comments_ind mz_comments mz_catalog_server {id} "" mz_compute_dependencies_ind mz_compute_dependencies mz_catalog_server {dependency_id} "" +mz_compute_dataflow_global_ids_per_worker_s2_primary_idx mz_compute_dataflow_global_ids_per_worker mz_catalog_server {id,worker_id} "" mz_compute_error_counts_raw_s2_primary_idx mz_compute_error_counts_raw mz_catalog_server {export_id,worker_id} "" mz_compute_exports_per_worker_s2_primary_idx mz_compute_exports_per_worker mz_catalog_server {export_id,worker_id} "" mz_compute_frontiers_per_worker_s2_primary_idx mz_compute_frontiers_per_worker mz_catalog_server {export_id,worker_id} "" mz_compute_hydration_times_per_worker_s2_primary_idx mz_compute_hydration_times_per_worker mz_catalog_server {export_id,worker_id} "" mz_compute_import_frontiers_per_worker_s2_primary_idx mz_compute_import_frontiers_per_worker mz_catalog_server {export_id,import_id,worker_id} "" +mz_compute_lir_mapping_per_worker_s2_primary_idx mz_compute_lir_mapping_per_worker mz_catalog_server {global_id,lir_id,worker_id} "" mz_compute_operator_durations_histogram_raw_s2_primary_idx mz_compute_operator_durations_histogram_raw mz_catalog_server {id,worker_id,duration_ns} "" mz_connections_ind mz_connections mz_catalog_server {schema_id} "" mz_console_cluster_utilization_overview_ind mz_console_cluster_utilization_overview mz_catalog_server {cluster_id} "" diff --git a/test/upsert/autospill/03-rocksdb.td b/test/upsert/autospill/03-rocksdb.td index 555957952b0d2..542e419c71a06 100644 --- a/test/upsert/autospill/03-rocksdb.td +++ b/test/upsert/autospill/03-rocksdb.td @@ -22,7 +22,8 @@ fish:AREALLYBIGFISHAREALLYBIGFISHAREALLYBIGFISHAREALLYBIGFISH 3 > SELECT - SUM(u.bytes_indexed) > 0, + -- TODO: Reenable when database-issues#8802 is fixed + -- SUM(u.bytes_indexed) > 0, -- This + the assertion below that ensures the byte count goes to 0 tests -- that we correctly transition the `bytes_indexed` count to the -- in-rocksdb. In the past, we would accidentally SUM the previous and @@ -34,7 +35,7 @@ fish:AREALLYBIGFISHAREALLYBIGFISHAREALLYBIGFISHAREALLYBIGFISH WHERE t.name = 'autospill_tbl' GROUP BY t.name ORDER BY t.name -true true 3 +true 3 $ set-from-sql var=previous-bytes-2 SELECT @@ -56,11 +57,12 @@ animal: # bytes_indexed will still have tombstones, but should be smaller than when # there are full values. > SELECT - SUM(u.bytes_indexed) < ${previous-bytes-2}, + -- TODO: Reenable when database-issues#8802 is fixed + -- SUM(u.bytes_indexed) < ${previous-bytes-2}, SUM(u.records_indexed) FROM mz_tables t JOIN mz_internal.mz_source_statistics_raw u ON t.id = u.id WHERE t.name = 'autospill_tbl' GROUP BY t.name ORDER BY t.name -true 0 +0