diff --git a/.dockerignore b/.dockerignore
index ae0ad8fd77..8b378b5dab 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,27 +1,27 @@
*
-!rust-toolchain.toml
-!Cargo.toml
+# Files
!Cargo.lock
+!Cargo.toml
!Makefile
+!rust-toolchain.toml
+!scripts/combine_control_files.py
+!scripts/ninstall.sh
+!vm-cgconfig.conf
+# Directories
!.cargo/
!.config/
-!control_plane/
!compute_tools/
+!control_plane/
!libs/
+!neon_local/
!pageserver/
!pgxn/
!proxy/
-!safekeeper/
!s3_scrubber/
+!safekeeper/
!storage_broker/
!trace/
-!vendor/postgres-v14/
-!vendor/postgres-v15/
-!vendor/postgres-v16/
+!vendor/postgres-*/
!workspace_hack/
-!neon_local/
-!scripts/ninstall.sh
-!scripts/combine_control_files.py
-!vm-cgconfig.conf
diff --git a/.github/ISSUE_TEMPLATE/epic-template.md b/.github/ISSUE_TEMPLATE/epic-template.md
index 019e6e7345..c442f50fde 100644
--- a/.github/ISSUE_TEMPLATE/epic-template.md
+++ b/.github/ISSUE_TEMPLATE/epic-template.md
@@ -16,9 +16,9 @@ assignees: ''
## Implementation ideas
-
+## Tasks
```[tasklist]
-### Tasks
+- [ ] Example Task
```
diff --git a/.github/actionlint.yml b/.github/actionlint.yml
index 362480f256..cb36e2eee6 100644
--- a/.github/actionlint.yml
+++ b/.github/actionlint.yml
@@ -4,6 +4,8 @@ self-hosted-runner:
- dev
- gen3
- large
+ # Remove `macos-14` from the list after https://github.com/rhysd/actionlint/pull/392 is merged.
+ - macos-14
- small
- us-east-2
config-variables:
diff --git a/.github/actions/allure-report-generate/action.yml b/.github/actions/allure-report-generate/action.yml
index abdbba802e..1ecb5ecc7e 100644
--- a/.github/actions/allure-report-generate/action.yml
+++ b/.github/actions/allure-report-generate/action.yml
@@ -39,7 +39,7 @@ runs:
PR_NUMBER=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH" || true)
if [ "${PR_NUMBER}" != "null" ]; then
BRANCH_OR_PR=pr-${PR_NUMBER}
- elif [ "${GITHUB_REF_NAME}" = "main" ] || [ "${GITHUB_REF_NAME}" = "release" ]; then
+ elif [ "${GITHUB_REF_NAME}" = "main" ] || [ "${GITHUB_REF_NAME}" = "release" ] || [ "${GITHUB_REF_NAME}" = "release-proxy" ]; then
# Shortcut for special branches
BRANCH_OR_PR=${GITHUB_REF_NAME}
else
@@ -59,7 +59,7 @@ runs:
BUCKET: neon-github-public-dev
# TODO: We can replace with a special docker image with Java and Allure pre-installed
- - uses: actions/setup-java@v3
+ - uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
@@ -76,8 +76,8 @@ runs:
rm -f ${ALLURE_ZIP}
fi
env:
- ALLURE_VERSION: 2.24.0
- ALLURE_ZIP_SHA256: 60b1d6ce65d9ef24b23cf9c2c19fd736a123487c38e54759f1ed1a7a77353c90
+ ALLURE_VERSION: 2.27.0
+ ALLURE_ZIP_SHA256: b071858fb2fa542c65d8f152c5c40d26267b2dfb74df1f1608a589ecca38e777
# Potentially we could have several running build for the same key (for example, for the main branch), so we use improvised lock for this
- name: Acquire lock
@@ -179,22 +179,11 @@ runs:
aws s3 rm "s3://${BUCKET}/${LOCK_FILE}"
fi
- - name: Store Allure test stat in the DB
- if: ${{ !cancelled() && inputs.store-test-results-into-db == 'true' }}
- shell: bash -euxo pipefail {0}
- env:
- COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
- REPORT_JSON_URL: ${{ steps.generate-report.outputs.report-json-url }}
- run: |
- export DATABASE_URL=${REGRESS_TEST_RESULT_CONNSTR}
-
- ./scripts/pysync
-
- poetry run python3 scripts/ingest_regress_test_result.py \
- --revision ${COMMIT_SHA} \
- --reference ${GITHUB_REF} \
- --build-type unified \
- --ingest ${WORKDIR}/report/data/suites.json
+ - name: Cache poetry deps
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/pypoetry/virtualenvs
+ key: v2-${{ runner.os }}-python-deps-${{ hashFiles('poetry.lock') }}
- name: Store Allure test stat in the DB (new)
if: ${{ !cancelled() && inputs.store-test-results-into-db == 'true' }}
@@ -226,7 +215,7 @@ runs:
rm -rf ${WORKDIR}
fi
- - uses: actions/github-script@v6
+ - uses: actions/github-script@v7
if: always()
env:
REPORT_URL: ${{ steps.generate-report.outputs.report-url }}
diff --git a/.github/actions/allure-report-store/action.yml b/.github/actions/allure-report-store/action.yml
index 7ae9937d42..df4a6712ac 100644
--- a/.github/actions/allure-report-store/action.yml
+++ b/.github/actions/allure-report-store/action.yml
@@ -19,7 +19,7 @@ runs:
PR_NUMBER=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH" || true)
if [ "${PR_NUMBER}" != "null" ]; then
BRANCH_OR_PR=pr-${PR_NUMBER}
- elif [ "${GITHUB_REF_NAME}" = "main" ] || [ "${GITHUB_REF_NAME}" = "release" ]; then
+ elif [ "${GITHUB_REF_NAME}" = "main" ] || [ "${GITHUB_REF_NAME}" = "release" ] || [ "${GITHUB_REF_NAME}" = "release-proxy" ]; then
# Shortcut for special branches
BRANCH_OR_PR=${GITHUB_REF_NAME}
else
diff --git a/.github/actions/run-python-test-set/action.yml b/.github/actions/run-python-test-set/action.yml
index 8dfa6c465f..d9e543d4bb 100644
--- a/.github/actions/run-python-test-set/action.yml
+++ b/.github/actions/run-python-test-set/action.yml
@@ -44,6 +44,10 @@ inputs:
description: 'Postgres version to use for tests'
required: false
default: 'v14'
+ benchmark_durations:
+ description: 'benchmark durations JSON'
+ required: false
+ default: '{}'
runs:
using: "composite"
@@ -76,17 +80,16 @@ runs:
- name: Checkout
if: inputs.needs_postgres_source == 'true'
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 1
- name: Cache poetry deps
- id: cache_poetry
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/pypoetry/virtualenvs
- key: v1-${{ runner.os }}-python-deps-${{ hashFiles('poetry.lock') }}
+ key: v2-${{ runner.os }}-python-deps-${{ hashFiles('poetry.lock') }}
- name: Install Python deps
shell: bash -euxo pipefail {0}
@@ -160,7 +163,7 @@ runs:
# We use pytest-split plugin to run benchmarks in parallel on different CI runners
if [ "${TEST_SELECTION}" = "test_runner/performance" ] && [ "${{ inputs.build_type }}" != "remote" ]; then
mkdir -p $TEST_OUTPUT
- poetry run ./scripts/benchmark_durations.py "${TEST_RESULT_CONNSTR}" --days 10 --output "$TEST_OUTPUT/benchmark_durations.json"
+ echo '${{ inputs.benchmark_durations || '{}' }}' > $TEST_OUTPUT/benchmark_durations.json
EXTRA_PARAMS="--durations-path $TEST_OUTPUT/benchmark_durations.json $EXTRA_PARAMS"
fi
diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml
index 584828c1d0..f2736614bf 100644
--- a/.github/workflows/actionlint.yml
+++ b/.github/workflows/actionlint.yml
@@ -16,7 +16,14 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
+ check-permissions:
+ if: ${{ !contains(github.event.pull_request.labels.*.name, 'run-no-ci') }}
+ uses: ./.github/workflows/check-permissions.yml
+ with:
+ github-event-name: ${{ github.event_name}}
+
actionlint:
+ needs: [ check-permissions ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/approved-for-ci-run.yml b/.github/workflows/approved-for-ci-run.yml
index 5b21011b83..69c48d86b9 100644
--- a/.github/workflows/approved-for-ci-run.yml
+++ b/.github/workflows/approved-for-ci-run.yml
@@ -64,7 +64,7 @@ jobs:
steps:
- run: gh pr --repo "${GITHUB_REPOSITORY}" edit "${PR_NUMBER}" --remove-label "approved-for-ci-run"
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
ref: main
token: ${{ secrets.CI_ACCESS_TOKEN }}
@@ -93,6 +93,7 @@ jobs:
--body-file "body.md" \
--head "${BRANCH}" \
--base "main" \
+ --label "run-e2e-tests-in-draft" \
--draft
fi
diff --git a/.github/workflows/benchmarking.yml b/.github/workflows/benchmarking.yml
index 8bf12c31b1..2e56bf909f 100644
--- a/.github/workflows/benchmarking.yml
+++ b/.github/workflows/benchmarking.yml
@@ -62,11 +62,11 @@ jobs:
runs-on: [ self-hosted, us-east-2, x64 ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:pinned
options: --init
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Download Neon artifact
uses: ./.github/actions/download
@@ -214,14 +214,14 @@ jobs:
runs-on: [ self-hosted, us-east-2, x64 ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:pinned
options: --init
# Increase timeout to 8h, default timeout is 6h
timeout-minutes: 480
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Download Neon artifact
uses: ./.github/actions/download
@@ -362,11 +362,11 @@ jobs:
runs-on: [ self-hosted, us-east-2, x64 ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:pinned
options: --init
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Download Neon artifact
uses: ./.github/actions/download
@@ -461,11 +461,11 @@ jobs:
runs-on: [ self-hosted, us-east-2, x64 ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:pinned
options: --init
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Download Neon artifact
uses: ./.github/actions/download
@@ -558,11 +558,11 @@ jobs:
runs-on: [ self-hosted, us-east-2, x64 ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:pinned
options: --init
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Download Neon artifact
uses: ./.github/actions/download
diff --git a/.github/workflows/build-build-tools-image.yml b/.github/workflows/build-build-tools-image.yml
new file mode 100644
index 0000000000..251423e701
--- /dev/null
+++ b/.github/workflows/build-build-tools-image.yml
@@ -0,0 +1,105 @@
+name: Build build-tools image
+
+on:
+ workflow_call:
+ inputs:
+ image-tag:
+ description: "build-tools image tag"
+ required: true
+ type: string
+ outputs:
+ image-tag:
+ description: "build-tools tag"
+ value: ${{ inputs.image-tag }}
+ image:
+ description: "build-tools image"
+ value: neondatabase/build-tools:${{ inputs.image-tag }}
+
+defaults:
+ run:
+ shell: bash -euo pipefail {0}
+
+concurrency:
+ group: build-build-tools-image-${{ inputs.image-tag }}
+
+# No permission for GITHUB_TOKEN by default; the **minimal required** set of permissions should be granted in each job.
+permissions: {}
+
+jobs:
+ check-image:
+ uses: ./.github/workflows/check-build-tools-image.yml
+
+ # This job uses older version of GitHub Actions because it's run on gen2 runners, which don't support node 20 (for newer versions)
+ build-image:
+ needs: [ check-image ]
+ if: needs.check-image.outputs.found == 'false'
+
+ strategy:
+ matrix:
+ arch: [ x64, arm64 ]
+
+ runs-on: ${{ fromJson(format('["self-hosted", "dev", "{0}"]', matrix.arch)) }}
+
+ env:
+ IMAGE_TAG: ${{ inputs.image-tag }}
+
+ steps:
+ - name: Check `input.tag` is correct
+ env:
+ INPUTS_IMAGE_TAG: ${{ inputs.image-tag }}
+ CHECK_IMAGE_TAG : ${{ needs.check-image.outputs.image-tag }}
+ run: |
+ if [ "${INPUTS_IMAGE_TAG}" != "${CHECK_IMAGE_TAG}" ]; then
+ echo "'inputs.image-tag' (${INPUTS_IMAGE_TAG}) does not match the tag of the latest build-tools image 'inputs.image-tag' (${CHECK_IMAGE_TAG})"
+ exit 1
+ fi
+
+ - uses: actions/checkout@v3
+
+ # Use custom DOCKER_CONFIG directory to avoid conflicts with default settings
+ # The default value is ~/.docker
+ - name: Set custom docker config directory
+ run: |
+ mkdir -p /tmp/.docker-custom
+ echo DOCKER_CONFIG=/tmp/.docker-custom >> $GITHUB_ENV
+
+ - uses: docker/setup-buildx-action@v2
+
+ - uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
+
+ - uses: docker/build-push-action@v4
+ with:
+ context: .
+ provenance: false
+ push: true
+ pull: true
+ file: Dockerfile.build-tools
+ cache-from: type=registry,ref=neondatabase/build-tools:cache-${{ matrix.arch }}
+ cache-to: type=registry,ref=neondatabase/build-tools:cache-${{ matrix.arch }},mode=max
+ tags: neondatabase/build-tools:${{ inputs.image-tag }}-${{ matrix.arch }}
+
+ - name: Remove custom docker config directory
+ run: |
+ rm -rf /tmp/.docker-custom
+
+ merge-images:
+ needs: [ build-image ]
+ runs-on: ubuntu-latest
+
+ env:
+ IMAGE_TAG: ${{ inputs.image-tag }}
+
+ steps:
+ - uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
+
+ - name: Create multi-arch image
+ run: |
+ docker buildx imagetools create -t neondatabase/build-tools:${IMAGE_TAG} \
+ neondatabase/build-tools:${IMAGE_TAG}-x64 \
+ neondatabase/build-tools:${IMAGE_TAG}-arm64
diff --git a/.github/workflows/build_and_push_docker_image.yml b/.github/workflows/build_and_push_docker_image.yml
deleted file mode 100644
index e401b2f418..0000000000
--- a/.github/workflows/build_and_push_docker_image.yml
+++ /dev/null
@@ -1,105 +0,0 @@
-name: Build and Push Docker Image
-
-on:
- workflow_call:
- inputs:
- dockerfile-path:
- required: true
- type: string
- image-name:
- required: true
- type: string
- outputs:
- build-tools-tag:
- description: "tag generated for build tools"
- value: ${{ jobs.tag.outputs.build-tools-tag }}
-
-jobs:
- check-if-build-tools-dockerfile-changed:
- runs-on: ubuntu-latest
- outputs:
- docker_file_changed: ${{ steps.dockerfile.outputs.docker_file_changed }}
- steps:
- - name: Check if Dockerfile.buildtools has changed
- id: dockerfile
- run: |
- if [[ "$GITHUB_EVENT_NAME" != "pull_request" ]]; then
- echo "docker_file_changed=false" >> $GITHUB_OUTPUT
- exit
- fi
- updated_files=$(gh pr --repo neondatabase/neon diff ${{ github.event.pull_request.number }} --name-only)
- if [[ $updated_files == *"Dockerfile.buildtools"* ]]; then
- echo "docker_file_changed=true" >> $GITHUB_OUTPUT
- fi
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- tag:
- runs-on: ubuntu-latest
- needs: [ check-if-build-tools-dockerfile-changed ]
- outputs:
- build-tools-tag: ${{steps.buildtools-tag.outputs.image_tag}}
-
- steps:
- - name: Get buildtools tag
- env:
- DOCKERFILE_CHANGED: ${{ needs.check-if-build-tools-dockerfile-changed.outputs.docker_file_changed }}
- run: |
- if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "${DOCKERFILE_CHANGED}" == "true" ]]; then
- IMAGE_TAG=$GITHUB_RUN_ID
- else
- IMAGE_TAG=pinned
- fi
-
- echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT
- shell: bash
- id: buildtools-tag
-
- kaniko:
- if: needs.check-if-build-tools-dockerfile-changed.outputs.docker_file_changed == 'true'
- needs: [ tag, check-if-build-tools-dockerfile-changed ]
- runs-on: [ self-hosted, dev, x64 ]
- container: gcr.io/kaniko-project/executor:v1.7.0-debug
-
- steps:
- - name: Checkout
- uses: actions/checkout@v1
-
- - name: Configure ECR login
- run: echo "{\"credsStore\":\"ecr-login\"}" > /kaniko/.docker/config.json
-
- - name: Kaniko build
- run: /kaniko/executor --reproducible --snapshotMode=redo --skip-unused-stages --dockerfile ${{ inputs.dockerfile-path }} --cache=true --cache-repo 369495373322.dkr.ecr.eu-central-1.amazonaws.com/cache --destination 369495373322.dkr.ecr.eu-central-1.amazonaws.com/${{ inputs.image-name }}:${{ needs.tag.outputs.build-tools-tag }}-amd64
-
- kaniko-arm:
- if: needs.check-if-build-tools-dockerfile-changed.outputs.docker_file_changed == 'true'
- needs: [ tag, check-if-build-tools-dockerfile-changed ]
- runs-on: [ self-hosted, dev, arm64 ]
- container: gcr.io/kaniko-project/executor:v1.7.0-debug
-
- steps:
- - name: Checkout
- uses: actions/checkout@v1
-
- - name: Configure ECR login
- run: echo "{\"credsStore\":\"ecr-login\"}" > /kaniko/.docker/config.json
-
- - name: Kaniko build
- run: /kaniko/executor --reproducible --snapshotMode=redo --skip-unused-stages --dockerfile ${{ inputs.dockerfile-path }} --cache=true --cache-repo 369495373322.dkr.ecr.eu-central-1.amazonaws.com/cache --destination 369495373322.dkr.ecr.eu-central-1.amazonaws.com/${{ inputs.image-name }}:${{ needs.tag.outputs.build-tools-tag }}-arm64
-
- manifest:
- if: needs.check-if-build-tools-dockerfile-changed.outputs.docker_file_changed == 'true'
- name: 'manifest'
- runs-on: [ self-hosted, dev, x64 ]
- needs:
- - tag
- - kaniko
- - kaniko-arm
- - check-if-build-tools-dockerfile-changed
-
- steps:
- - name: Create manifest
- run: docker manifest create 369495373322.dkr.ecr.eu-central-1.amazonaws.com/${{ inputs.image-name }}:${{ needs.tag.outputs.build-tools-tag }} --amend 369495373322.dkr.ecr.eu-central-1.amazonaws.com/${{ inputs.image-name }}:${{ needs.tag.outputs.build-tools-tag }}-amd64 --amend 369495373322.dkr.ecr.eu-central-1.amazonaws.com/${{ inputs.image-name }}:${{ needs.tag.outputs.build-tools-tag }}-arm64
-
- - name: Push manifest
- run: docker manifest push 369495373322.dkr.ecr.eu-central-1.amazonaws.com/${{ inputs.image-name }}:${{ needs.tag.outputs.build-tools-tag }}
diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml
index 7445501f00..810c61de2d 100644
--- a/.github/workflows/build_and_test.yml
+++ b/.github/workflows/build_and_test.yml
@@ -5,6 +5,7 @@ on:
branches:
- main
- release
+ - release-proxy
pull_request:
defaults:
@@ -21,31 +22,15 @@ env:
COPT: '-Werror'
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_DEV }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY_DEV }}
- NEXTEST_RETRIES: 3
# A concurrency group that we use for e2e-tests runs, matches `concurrency.group` above with `github.repository` as a prefix
- E2E_CONCURRENCY_GROUP: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
+ E2E_CONCURRENCY_GROUP: ${{ github.repository }}-e2e-tests-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
jobs:
check-permissions:
- runs-on: ubuntu-latest
-
- steps:
- - name: Disallow PRs from forks
- if: |
- github.event_name == 'pull_request' &&
- github.event.pull_request.head.repo.full_name != github.repository
-
- run: |
- if [ "${{ contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association) }}" = "true" ]; then
- MESSAGE="Please create a PR from a branch of ${GITHUB_REPOSITORY} instead of a fork"
- else
- MESSAGE="The PR should be reviewed and labelled with 'approved-for-ci-run' to trigger a CI run"
- fi
-
- echo >&2 "We don't run CI for PRs from forks"
- echo >&2 "${MESSAGE}"
-
- exit 1
+ if: ${{ !contains(github.event.pull_request.labels.*.name, 'run-no-ci') }}
+ uses: ./.github/workflows/check-permissions.yml
+ with:
+ github-event-name: ${{ github.event_name}}
cancel-previous-e2e-tests:
needs: [ check-permissions ]
@@ -70,7 +55,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -83,6 +68,8 @@ jobs:
echo "tag=$(git rev-list --count HEAD)" >> $GITHUB_OUTPUT
elif [[ "$GITHUB_REF_NAME" == "release" ]]; then
echo "tag=release-$(git rev-list --count HEAD)" >> $GITHUB_OUTPUT
+ elif [[ "$GITHUB_REF_NAME" == "release-proxy" ]]; then
+ echo "tag=release-proxy-$(git rev-list --count HEAD)" >> $GITHUB_OUTPUT
else
echo "GITHUB_REF_NAME (value '$GITHUB_REF_NAME') is not set to either 'main' or 'release'"
echo "tag=$GITHUB_RUN_ID" >> $GITHUB_OUTPUT
@@ -90,34 +77,39 @@ jobs:
shell: bash
id: build-tag
- build-buildtools-image:
+ check-build-tools-image:
needs: [ check-permissions ]
- uses: ./.github/workflows/build_and_push_docker_image.yml
+ uses: ./.github/workflows/check-build-tools-image.yml
+
+ build-build-tools-image:
+ needs: [ check-build-tools-image ]
+ uses: ./.github/workflows/build-build-tools-image.yml
with:
- dockerfile-path: Dockerfile.buildtools
- image-name: build-tools
+ image-tag: ${{ needs.check-build-tools-image.outputs.image-tag }}
secrets: inherit
check-codestyle-python:
- needs: [ check-permissions, build-buildtools-image ]
+ needs: [ check-permissions, build-build-tools-image ]
runs-on: [ self-hosted, gen3, small ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: false
fetch-depth: 1
- name: Cache poetry deps
- id: cache_poetry
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/pypoetry/virtualenvs
- key: v1-codestyle-python-deps-${{ hashFiles('poetry.lock') }}
+ key: v2-${{ runner.os }}-python-deps-${{ hashFiles('poetry.lock') }}
- name: Install Python deps
run: ./scripts/pysync
@@ -132,15 +124,18 @@ jobs:
run: poetry run mypy .
check-codestyle-rust:
- needs: [ check-permissions, build-buildtools-image ]
- runs-on: [ self-hosted, gen3, large ]
+ needs: [ check-permissions, build-build-tools-image ]
+ runs-on: [ self-hosted, gen3, small ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 1
@@ -148,7 +143,7 @@ jobs:
# Disabled for now
# - name: Restore cargo deps cache
# id: cache_cargo
-# uses: actions/cache@v3
+# uses: actions/cache@v4
# with:
# path: |
# !~/.cargo/registry/src
@@ -199,10 +194,13 @@ jobs:
run: cargo deny check --hide-inclusion-graph
build-neon:
- needs: [ check-permissions, tag, build-buildtools-image ]
+ needs: [ check-permissions, tag, build-build-tools-image ]
runs-on: [ self-hosted, gen3, large ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
# Raise locked memory limit for tokio-epoll-uring.
# On 5.10 LTS kernels < 5.10.162 (and generally mainline kernels < 5.12),
# io_uring will account the memory of the CQ and SQ as locked.
@@ -233,7 +231,7 @@ jobs:
done
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 1
@@ -255,7 +253,7 @@ jobs:
done
if [ "${FAILED}" = "true" ]; then
- echo >&2 "Please update vendors/revisions.json if these changes are intentional"
+ echo >&2 "Please update vendor/revisions.json if these changes are intentional"
exit 1
fi
@@ -305,7 +303,7 @@ jobs:
# compressed crates.
# - name: Cache cargo deps
# id: cache_cargo
-# uses: actions/cache@v3
+# uses: actions/cache@v4
# with:
# path: |
# ~/.cargo/registry/
@@ -319,21 +317,21 @@ jobs:
- name: Cache postgres v14 build
id: cache_pg_14
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: pg_install/v14
key: v1-${{ runner.os }}-${{ matrix.build_type }}-pg-${{ steps.pg_v14_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
- name: Cache postgres v15 build
id: cache_pg_15
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: pg_install/v15
key: v1-${{ runner.os }}-${{ matrix.build_type }}-pg-${{ steps.pg_v15_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
- name: Cache postgres v16 build
id: cache_pg_16
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: pg_install/v16
key: v1-${{ runner.os }}-${{ matrix.build_type }}-pg-${{ steps.pg_v16_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
@@ -361,6 +359,8 @@ jobs:
${cov_prefix} mold -run cargo build $CARGO_FLAGS $CARGO_FEATURES --bins --tests
- name: Run rust tests
+ env:
+ NEXTEST_RETRIES: 3
run: |
for io_engine in std-fs tokio-epoll-uring ; do
NEON_PAGESERVER_UNIT_TEST_VIRTUAL_FILE_IOENGINE=$io_engine ${cov_prefix} cargo nextest run $CARGO_FLAGS $CARGO_FEATURES
@@ -438,10 +438,13 @@ jobs:
uses: ./.github/actions/save-coverage-data
regress-tests:
- needs: [ check-permissions, build-neon, build-buildtools-image, tag ]
+ needs: [ check-permissions, build-neon, build-build-tools-image, tag ]
runs-on: [ self-hosted, gen3, large ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
# for changed limits, see comments on `options:` earlier in this file
options: --init --shm-size=512mb --ulimit memlock=67108864:67108864
strategy:
@@ -451,7 +454,7 @@ jobs:
pg_version: [ v14, v15, v16 ]
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 1
@@ -472,16 +475,59 @@ jobs:
CHECK_ONDISK_DATA_COMPATIBILITY: nonempty
BUILD_TAG: ${{ needs.tag.outputs.build-tag }}
PAGESERVER_VIRTUAL_FILE_IO_ENGINE: tokio-epoll-uring
+ PAGESERVER_GET_VECTORED_IMPL: vectored
+ # Temporary disable this step until we figure out why it's so flaky
+ # Ref https://github.com/neondatabase/neon/issues/4540
- name: Merge and upload coverage data
- if: matrix.build_type == 'debug' && matrix.pg_version == 'v14'
+ if: |
+ false &&
+ matrix.build_type == 'debug' && matrix.pg_version == 'v14'
uses: ./.github/actions/save-coverage-data
- benchmarks:
- needs: [ check-permissions, build-neon, build-buildtools-image ]
+ get-benchmarks-durations:
+ outputs:
+ json: ${{ steps.get-benchmark-durations.outputs.json }}
+ needs: [ check-permissions, build-build-tools-image ]
runs-on: [ self-hosted, gen3, small ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
+ options: --init
+ if: github.ref_name == 'main' || contains(github.event.pull_request.labels.*.name, 'run-benchmarks')
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Cache poetry deps
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/pypoetry/virtualenvs
+ key: v1-${{ runner.os }}-python-deps-${{ hashFiles('poetry.lock') }}
+
+ - name: Install Python deps
+ run: ./scripts/pysync
+
+ - name: get benchmark durations
+ id: get-benchmark-durations
+ env:
+ TEST_RESULT_CONNSTR: ${{ secrets.REGRESS_TEST_RESULT_CONNSTR_NEW }}
+ run: |
+ poetry run ./scripts/benchmark_durations.py "${TEST_RESULT_CONNSTR}" \
+ --days 10 \
+ --output /tmp/benchmark_durations.json
+ echo "json=$(jq --compact-output '.' /tmp/benchmark_durations.json)" >> $GITHUB_OUTPUT
+
+ benchmarks:
+ needs: [ check-permissions, build-neon, build-build-tools-image, get-benchmarks-durations ]
+ runs-on: [ self-hosted, gen3, small ]
+ container:
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
# for changed limits, see comments on `options:` earlier in this file
options: --init --shm-size=512mb --ulimit memlock=67108864:67108864
if: github.ref_name == 'main' || contains(github.event.pull_request.labels.*.name, 'run-benchmarks')
@@ -489,11 +535,11 @@ jobs:
fail-fast: false
matrix:
# the amount of groups (N) should be reflected in `extra_params: --splits N ...`
- pytest_split_group: [ 1, 2, 3, 4 ]
+ pytest_split_group: [ 1, 2, 3, 4, 5 ]
build_type: [ release ]
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Pytest benchmarks
uses: ./.github/actions/run-python-test-set
@@ -502,7 +548,8 @@ jobs:
test_selection: performance
run_in_parallel: false
save_perf_report: ${{ github.ref_name == 'main' }}
- extra_params: --splits 4 --group ${{ matrix.pytest_split_group }}
+ extra_params: --splits 5 --group ${{ matrix.pytest_split_group }}
+ benchmark_durations: ${{ needs.get-benchmarks-durations.outputs.json }}
env:
VIP_VAP_ACCESS_TOKEN: "${{ secrets.VIP_VAP_ACCESS_TOKEN }}"
PERF_TEST_RESULT_CONNSTR: "${{ secrets.PERF_TEST_RESULT_CONNSTR }}"
@@ -512,16 +559,19 @@ jobs:
# while coverage is currently collected for the debug ones
create-test-report:
- needs: [ check-permissions, regress-tests, coverage-report, benchmarks, build-buildtools-image ]
+ needs: [ check-permissions, regress-tests, coverage-report, benchmarks, build-build-tools-image ]
if: ${{ !cancelled() && contains(fromJSON('["skipped", "success"]'), needs.check-permissions.result) }}
runs-on: [ self-hosted, gen3, small ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Create Allure report
if: ${{ !cancelled() }}
@@ -530,10 +580,9 @@ jobs:
with:
store-test-results-into-db: true
env:
- REGRESS_TEST_RESULT_CONNSTR: ${{ secrets.REGRESS_TEST_RESULT_CONNSTR }}
REGRESS_TEST_RESULT_CONNSTR_NEW: ${{ secrets.REGRESS_TEST_RESULT_CONNSTR_NEW }}
- - uses: actions/github-script@v6
+ - uses: actions/github-script@v7
if: ${{ !cancelled() }}
with:
# Retry script for 5XX server errors: https://github.com/actions/github-script#retries
@@ -559,10 +608,13 @@ jobs:
})
coverage-report:
- needs: [ check-permissions, regress-tests, build-buildtools-image ]
+ needs: [ check-permissions, regress-tests, build-build-tools-image ]
runs-on: [ self-hosted, gen3, small ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
strategy:
fail-fast: false
@@ -573,7 +625,7 @@ jobs:
coverage-json: ${{ steps.upload-coverage-report-new.outputs.summary-json }}
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
@@ -608,17 +660,6 @@ jobs:
--input-objects=/tmp/coverage/binaries.list \
--format=lcov
- - name: Upload coverage report
- id: upload-coverage-report
- env:
- BUCKET: neon-github-public-dev
- COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
- run: |
- aws s3 cp --only-show-errors --recursive /tmp/coverage/report s3://${BUCKET}/code-coverage/${COMMIT_SHA}
-
- REPORT_URL=https://${BUCKET}.s3.amazonaws.com/code-coverage/${COMMIT_SHA}/index.html
- echo "report-url=${REPORT_URL}" >> $GITHUB_OUTPUT
-
- name: Build coverage report NEW
id: upload-coverage-report-new
env:
@@ -653,23 +694,13 @@ jobs:
REPORT_URL=https://${BUCKET}.s3.amazonaws.com/code-coverage/${COMMIT_SHA}/lcov/summary.json
echo "summary-json=${REPORT_URL}" >> $GITHUB_OUTPUT
- - uses: actions/github-script@v6
+ - uses: actions/github-script@v7
env:
- REPORT_URL: ${{ steps.upload-coverage-report.outputs.report-url }}
REPORT_URL_NEW: ${{ steps.upload-coverage-report-new.outputs.report-url }}
COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
with:
script: |
- const { REPORT_URL, REPORT_URL_NEW, COMMIT_SHA } = process.env
-
- await github.rest.repos.createCommitStatus({
- owner: context.repo.owner,
- repo: context.repo.repo,
- sha: `${COMMIT_SHA}`,
- state: 'success',
- target_url: `${REPORT_URL}`,
- context: 'Code coverage report',
- })
+ const { REPORT_URL_NEW, COMMIT_SHA } = process.env
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
@@ -681,206 +712,146 @@ jobs:
})
trigger-e2e-tests:
+ if: ${{ !github.event.pull_request.draft || contains( github.event.pull_request.labels.*.name, 'run-e2e-tests-in-draft') || github.ref_name == 'main' || github.ref_name == 'release' || github.ref_name == 'release-proxy' }}
needs: [ check-permissions, promote-images, tag ]
- runs-on: [ self-hosted, gen3, small ]
- container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/base:pinned
- options: --init
- steps:
- - name: Set PR's status to pending and request a remote CI test
- run: |
- # For pull requests, GH Actions set "github.sha" variable to point at a fake merge commit
- # but we need to use a real sha of a latest commit in the PR's branch for the e2e job,
- # to place a job run status update later.
- COMMIT_SHA=${{ github.event.pull_request.head.sha }}
- # For non-PR kinds of runs, the above will produce an empty variable, pick the original sha value for those
- COMMIT_SHA=${COMMIT_SHA:-${{ github.sha }}}
-
- REMOTE_REPO="${{ github.repository_owner }}/cloud"
-
- curl -f -X POST \
- https://api.github.com/repos/${{ github.repository }}/statuses/$COMMIT_SHA \
- -H "Accept: application/vnd.github.v3+json" \
- --user "${{ secrets.CI_ACCESS_TOKEN }}" \
- --data \
- "{
- \"state\": \"pending\",
- \"context\": \"neon-cloud-e2e\",
- \"description\": \"[$REMOTE_REPO] Remote CI job is about to start\"
- }"
-
- curl -f -X POST \
- https://api.github.com/repos/$REMOTE_REPO/actions/workflows/testing.yml/dispatches \
- -H "Accept: application/vnd.github.v3+json" \
- --user "${{ secrets.CI_ACCESS_TOKEN }}" \
- --data \
- "{
- \"ref\": \"main\",
- \"inputs\": {
- \"ci_job_name\": \"neon-cloud-e2e\",
- \"commit_hash\": \"$COMMIT_SHA\",
- \"remote_repo\": \"${{ github.repository }}\",
- \"storage_image_tag\": \"${{ needs.tag.outputs.build-tag }}\",
- \"compute_image_tag\": \"${{ needs.tag.outputs.build-tag }}\",
- \"concurrency_group\": \"${{ env.E2E_CONCURRENCY_GROUP }}\"
- }
- }"
+ uses: ./.github/workflows/trigger-e2e-tests.yml
+ secrets: inherit
neon-image:
- needs: [ check-permissions, build-buildtools-image, tag ]
+ needs: [ check-permissions, build-build-tools-image, tag ]
runs-on: [ self-hosted, gen3, large ]
- container: gcr.io/kaniko-project/executor:v1.9.2-debug
- defaults:
- run:
- shell: sh -eu {0}
steps:
- name: Checkout
- uses: actions/checkout@v1 # v3 won't work with kaniko
+ uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
- - name: Configure ECR and Docker Hub login
+ # Use custom DOCKER_CONFIG directory to avoid conflicts with default settings
+ # The default value is ~/.docker
+ - name: Set custom docker config directory
run: |
- DOCKERHUB_AUTH=$(echo -n "${{ secrets.NEON_DOCKERHUB_USERNAME }}:${{ secrets.NEON_DOCKERHUB_PASSWORD }}" | base64)
- echo "::add-mask::${DOCKERHUB_AUTH}"
+ mkdir -p .docker-custom
+ echo DOCKER_CONFIG=$(pwd)/.docker-custom >> $GITHUB_ENV
+ - uses: docker/setup-buildx-action@v3
- cat <<-EOF > /kaniko/.docker/config.json
- {
- "auths": {
- "https://index.docker.io/v1/": {
- "auth": "${DOCKERHUB_AUTH}"
- }
- },
- "credHelpers": {
- "369495373322.dkr.ecr.eu-central-1.amazonaws.com": "ecr-login"
- }
- }
- EOF
+ - uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
- - name: Kaniko build neon
- run:
- /kaniko/executor --reproducible --snapshot-mode=redo --skip-unused-stages --cache=true
- --cache-repo 369495373322.dkr.ecr.eu-central-1.amazonaws.com/cache
- --context .
- --build-arg GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
- --build-arg BUILD_TAG=${{ needs.tag.outputs.build-tag }}
- --build-arg TAG=${{ needs.build-buildtools-image.outputs.build-tools-tag }}
- --build-arg REPOSITORY=369495373322.dkr.ecr.eu-central-1.amazonaws.com
- --destination 369495373322.dkr.ecr.eu-central-1.amazonaws.com/neon:${{needs.tag.outputs.build-tag}}
- --destination neondatabase/neon:${{needs.tag.outputs.build-tag}}
+ - uses: docker/login-action@v3
+ with:
+ registry: 369495373322.dkr.ecr.eu-central-1.amazonaws.com
+ username: ${{ secrets.AWS_ACCESS_KEY_DEV }}
+ password: ${{ secrets.AWS_SECRET_KEY_DEV }}
- # Cleanup script fails otherwise - rm: cannot remove '/nvme/actions-runner/_work/_temp/_github_home/.ecr': Permission denied
- - name: Cleanup ECR folder
- run: rm -rf ~/.ecr
+ - uses: docker/build-push-action@v5
+ with:
+ context: .
+ build-args: |
+ GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
+ BUILD_TAG=${{ needs.tag.outputs.build-tag }}
+ TAG=${{ needs.build-build-tools-image.outputs.image-tag }}
+ provenance: false
+ push: true
+ pull: true
+ file: Dockerfile
+ cache-from: type=registry,ref=neondatabase/neon:cache
+ cache-to: type=registry,ref=neondatabase/neon:cache,mode=max
+ tags: |
+ 369495373322.dkr.ecr.eu-central-1.amazonaws.com/neon:${{needs.tag.outputs.build-tag}}
+ neondatabase/neon:${{needs.tag.outputs.build-tag}}
- compute-tools-image:
- runs-on: [ self-hosted, gen3, large ]
- needs: [ check-permissions, build-buildtools-image, tag ]
- container: gcr.io/kaniko-project/executor:v1.9.2-debug
- defaults:
- run:
- shell: sh -eu {0}
-
- steps:
- - name: Checkout
- uses: actions/checkout@v1 # v3 won't work with kaniko
-
- - name: Configure ECR and Docker Hub login
+ - name: Remove custom docker config directory
+ if: always()
run: |
- DOCKERHUB_AUTH=$(echo -n "${{ secrets.NEON_DOCKERHUB_USERNAME }}:${{ secrets.NEON_DOCKERHUB_PASSWORD }}" | base64)
- echo "::add-mask::${DOCKERHUB_AUTH}"
-
- cat <<-EOF > /kaniko/.docker/config.json
- {
- "auths": {
- "https://index.docker.io/v1/": {
- "auth": "${DOCKERHUB_AUTH}"
- }
- },
- "credHelpers": {
- "369495373322.dkr.ecr.eu-central-1.amazonaws.com": "ecr-login"
- }
- }
- EOF
-
- - name: Kaniko build compute tools
- run:
- /kaniko/executor --reproducible --snapshot-mode=redo --skip-unused-stages --cache=true
- --cache-repo 369495373322.dkr.ecr.eu-central-1.amazonaws.com/cache
- --context .
- --build-arg GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
- --build-arg BUILD_TAG=${{needs.tag.outputs.build-tag}}
- --build-arg TAG=${{needs.build-buildtools-image.outputs.build-tools-tag}}
- --build-arg REPOSITORY=369495373322.dkr.ecr.eu-central-1.amazonaws.com
- --dockerfile Dockerfile.compute-tools
- --destination 369495373322.dkr.ecr.eu-central-1.amazonaws.com/compute-tools:${{needs.tag.outputs.build-tag}}
- --destination neondatabase/compute-tools:${{needs.tag.outputs.build-tag}}
-
- # Cleanup script fails otherwise - rm: cannot remove '/nvme/actions-runner/_work/_temp/_github_home/.ecr': Permission denied
- - name: Cleanup ECR folder
- run: rm -rf ~/.ecr
+ rm -rf .docker-custom
compute-node-image:
- needs: [ check-permissions, build-buildtools-image, tag ]
+ needs: [ check-permissions, build-build-tools-image, tag ]
runs-on: [ self-hosted, gen3, large ]
- container:
- image: gcr.io/kaniko-project/executor:v1.9.2-debug
- # Workaround for "Resolving download.osgeo.org (download.osgeo.org)... failed: Temporary failure in name resolution.""
- # Should be prevented by https://github.com/neondatabase/neon/issues/4281
- options: --add-host=download.osgeo.org:140.211.15.30
+
strategy:
fail-fast: false
matrix:
version: [ v14, v15, v16 ]
- defaults:
- run:
- shell: sh -eu {0}
steps:
- name: Checkout
- uses: actions/checkout@v1 # v3 won't work with kaniko
+ uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
- - name: Configure ECR and Docker Hub login
+ # Use custom DOCKER_CONFIG directory to avoid conflicts with default settings
+ # The default value is ~/.docker
+ - name: Set custom docker config directory
run: |
- DOCKERHUB_AUTH=$(echo -n "${{ secrets.NEON_DOCKERHUB_USERNAME }}:${{ secrets.NEON_DOCKERHUB_PASSWORD }}" | base64)
- echo "::add-mask::${DOCKERHUB_AUTH}"
+ mkdir -p .docker-custom
+ echo DOCKER_CONFIG=$(pwd)/.docker-custom >> $GITHUB_ENV
+ - uses: docker/setup-buildx-action@v3
+ with:
+ # Disable parallelism for docker buildkit.
+ # As we already build everything with `make -j$(nproc)`, running it in additional level of parallelisam blows up the Runner.
+ config-inline: |
+ [worker.oci]
+ max-parallelism = 1
- cat <<-EOF > /kaniko/.docker/config.json
- {
- "auths": {
- "https://index.docker.io/v1/": {
- "auth": "${DOCKERHUB_AUTH}"
- }
- },
- "credHelpers": {
- "369495373322.dkr.ecr.eu-central-1.amazonaws.com": "ecr-login"
- }
- }
- EOF
+ - uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
- - name: Kaniko build compute node with extensions
- run:
- /kaniko/executor --reproducible --snapshot-mode=redo --skip-unused-stages --cache=true
- --cache-repo 369495373322.dkr.ecr.eu-central-1.amazonaws.com/cache
- --context .
- --build-arg GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
- --build-arg PG_VERSION=${{ matrix.version }}
- --build-arg BUILD_TAG=${{needs.tag.outputs.build-tag}}
- --build-arg TAG=${{needs.build-buildtools-image.outputs.build-tools-tag}}
- --build-arg REPOSITORY=369495373322.dkr.ecr.eu-central-1.amazonaws.com
- --dockerfile Dockerfile.compute-node
- --destination 369495373322.dkr.ecr.eu-central-1.amazonaws.com/compute-node-${{ matrix.version }}:${{needs.tag.outputs.build-tag}}
- --destination neondatabase/compute-node-${{ matrix.version }}:${{needs.tag.outputs.build-tag}}
- --cleanup
+ - uses: docker/login-action@v3
+ with:
+ registry: 369495373322.dkr.ecr.eu-central-1.amazonaws.com
+ username: ${{ secrets.AWS_ACCESS_KEY_DEV }}
+ password: ${{ secrets.AWS_SECRET_KEY_DEV }}
- # Cleanup script fails otherwise - rm: cannot remove '/nvme/actions-runner/_work/_temp/_github_home/.ecr': Permission denied
- - name: Cleanup ECR folder
- run: rm -rf ~/.ecr
+ - name: Build compute-node image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ build-args: |
+ GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
+ PG_VERSION=${{ matrix.version }}
+ BUILD_TAG=${{ needs.tag.outputs.build-tag }}
+ TAG=${{ needs.build-build-tools-image.outputs.image-tag }}
+ provenance: false
+ push: true
+ pull: true
+ file: Dockerfile.compute-node
+ cache-from: type=registry,ref=neondatabase/compute-node-${{ matrix.version }}:cache
+ cache-to: type=registry,ref=neondatabase/compute-node-${{ matrix.version }}:cache,mode=max
+ tags: |
+ 369495373322.dkr.ecr.eu-central-1.amazonaws.com/compute-node-${{ matrix.version }}:${{needs.tag.outputs.build-tag}}
+ neondatabase/compute-node-${{ matrix.version }}:${{needs.tag.outputs.build-tag}}
+
+ - name: Build compute-tools image
+ # compute-tools are Postgres independent, so build it only once
+ if: ${{ matrix.version == 'v16' }}
+ uses: docker/build-push-action@v5
+ with:
+ target: compute-tools-image
+ context: .
+ build-args: |
+ GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
+ BUILD_TAG=${{ needs.tag.outputs.build-tag }}
+ TAG=${{ needs.build-build-tools-image.outputs.image-tag }}
+ provenance: false
+ push: true
+ pull: true
+ file: Dockerfile.compute-node
+ tags: |
+ 369495373322.dkr.ecr.eu-central-1.amazonaws.com/compute-tools:${{ needs.tag.outputs.build-tag }}
+ neondatabase/compute-tools:${{ needs.tag.outputs.build-tag }}
+
+ - name: Remove custom docker config directory
+ if: always()
+ run: |
+ rm -rf .docker-custom
vm-compute-node-image:
needs: [ check-permissions, tag, compute-node-image ]
@@ -893,7 +864,7 @@ jobs:
run:
shell: sh -eu {0}
env:
- VM_BUILDER_VERSION: v0.21.0
+ VM_BUILDER_VERSION: v0.23.2
steps:
- name: Checkout
@@ -924,12 +895,12 @@ jobs:
docker push 369495373322.dkr.ecr.eu-central-1.amazonaws.com/vm-compute-node-${{ matrix.version }}:${{needs.tag.outputs.build-tag}}
test-images:
- needs: [ check-permissions, tag, neon-image, compute-node-image, compute-tools-image ]
+ needs: [ check-permissions, tag, neon-image, compute-node-image ]
runs-on: [ self-hosted, gen3, small ]
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -958,7 +929,8 @@ jobs:
fi
- name: Verify docker-compose example
- run: env REPOSITORY=369495373322.dkr.ecr.eu-central-1.amazonaws.com TAG=${{needs.tag.outputs.build-tag}} ./docker-compose/docker_compose_test.sh
+ timeout-minutes: 20
+ run: env TAG=${{needs.tag.outputs.build-tag}} ./docker-compose/docker_compose_test.sh
- name: Print logs and clean up
if: always()
@@ -991,9 +963,7 @@ jobs:
crane pull 369495373322.dkr.ecr.eu-central-1.amazonaws.com/vm-compute-node-v16:${{needs.tag.outputs.build-tag}} vm-compute-node-v16
- name: Add latest tag to images
- if: |
- (github.ref_name == 'main' || github.ref_name == 'release') &&
- github.event_name != 'workflow_dispatch'
+ if: github.ref_name == 'main' || github.ref_name == 'release' || github.ref_name == 'release-proxy'
run: |
crane tag 369495373322.dkr.ecr.eu-central-1.amazonaws.com/neon:${{needs.tag.outputs.build-tag}} latest
crane tag 369495373322.dkr.ecr.eu-central-1.amazonaws.com/compute-tools:${{needs.tag.outputs.build-tag}} latest
@@ -1005,9 +975,7 @@ jobs:
crane tag 369495373322.dkr.ecr.eu-central-1.amazonaws.com/vm-compute-node-v16:${{needs.tag.outputs.build-tag}} latest
- name: Push images to production ECR
- if: |
- (github.ref_name == 'main' || github.ref_name == 'release') &&
- github.event_name != 'workflow_dispatch'
+ if: github.ref_name == 'main' || github.ref_name == 'release'|| github.ref_name == 'release-proxy'
run: |
crane copy 369495373322.dkr.ecr.eu-central-1.amazonaws.com/neon:${{needs.tag.outputs.build-tag}} 093970136003.dkr.ecr.eu-central-1.amazonaws.com/neon:latest
crane copy 369495373322.dkr.ecr.eu-central-1.amazonaws.com/compute-tools:${{needs.tag.outputs.build-tag}} 093970136003.dkr.ecr.eu-central-1.amazonaws.com/compute-tools:latest
@@ -1031,9 +999,7 @@ jobs:
crane push vm-compute-node-v16 neondatabase/vm-compute-node-v16:${{needs.tag.outputs.build-tag}}
- name: Push latest tags to Docker Hub
- if: |
- (github.ref_name == 'main' || github.ref_name == 'release') &&
- github.event_name != 'workflow_dispatch'
+ if: github.ref_name == 'main' || github.ref_name == 'release'|| github.ref_name == 'release-proxy'
run: |
crane tag neondatabase/neon:${{needs.tag.outputs.build-tag}} latest
crane tag neondatabase/compute-tools:${{needs.tag.outputs.build-tag}} latest
@@ -1123,7 +1089,7 @@ jobs:
deploy:
needs: [ check-permissions, promote-images, tag, regress-tests, trigger-custom-extensions-build-and-wait ]
- if: ( github.ref_name == 'main' || github.ref_name == 'release' ) && github.event_name != 'workflow_dispatch'
+ if: github.ref_name == 'main' || github.ref_name == 'release'|| github.ref_name == 'release-proxy'
runs-on: [ self-hosted, gen3, small ]
container: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/ansible:latest
@@ -1143,7 +1109,7 @@ jobs:
done
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: false
fetch-depth: 0
@@ -1158,15 +1124,27 @@ jobs:
# TODO: move deployPreprodRegion to release (`"$GITHUB_REF_NAME" == "release"` block), once Staging support different compute tag prefixes for different regions
gh workflow --repo neondatabase/aws run deploy-dev.yml --ref main -f branch=main -f dockerTag=${{needs.tag.outputs.build-tag}} -f deployPreprodRegion=true
elif [[ "$GITHUB_REF_NAME" == "release" ]]; then
- gh workflow --repo neondatabase/aws run deploy-prod.yml --ref main -f branch=main -f dockerTag=${{needs.tag.outputs.build-tag}}
+ gh workflow --repo neondatabase/aws run deploy-prod.yml --ref main \
+ -f deployPgSniRouter=false \
+ -f deployProxy=false \
+ -f deployStorage=true \
+ -f deployStorageBroker=true \
+ -f branch=main \
+ -f dockerTag=${{needs.tag.outputs.build-tag}}
+ elif [[ "$GITHUB_REF_NAME" == "release-proxy" ]]; then
+ gh workflow --repo neondatabase/aws run deploy-proxy-prod.yml --ref main \
+ -f deployPgSniRouter=true \
+ -f deployProxy=true \
+ -f branch=main \
+ -f dockerTag=${{needs.tag.outputs.build-tag}}
else
echo "GITHUB_REF_NAME (value '$GITHUB_REF_NAME') is not set to either 'main' or 'release'"
exit 1
fi
- name: Create git tag
- if: github.ref_name == 'release'
- uses: actions/github-script@v6
+ if: github.ref_name == 'release' || github.ref_name == 'release-proxy'
+ uses: actions/github-script@v7
with:
# Retry script for 5XX server errors: https://github.com/actions/github-script#retries
retries: 5
@@ -1178,9 +1156,10 @@ jobs:
sha: context.sha,
})
+ # TODO: check how GitHub releases looks for proxy releases and enable it if it's ok
- name: Create GitHub release
if: github.ref_name == 'release'
- uses: actions/github-script@v6
+ uses: actions/github-script@v7
with:
# Retry script for 5XX server errors: https://github.com/actions/github-script#retries
retries: 5
@@ -1229,3 +1208,11 @@ jobs:
time aws s3 cp --only-show-errors s3://${BUCKET}/${S3_KEY} s3://${BUCKET}/${PREFIX}/${FILENAME}
done
+
+ pin-build-tools-image:
+ needs: [ build-build-tools-image, promote-images, regress-tests ]
+ if: github.ref_name == 'main'
+ uses: ./.github/workflows/pin-build-tools-image.yml
+ with:
+ from-tag: ${{ needs.build-build-tools-image.outputs.image-tag }}
+ secrets: inherit
diff --git a/.github/workflows/check-build-tools-image.yml b/.github/workflows/check-build-tools-image.yml
new file mode 100644
index 0000000000..28646dfc19
--- /dev/null
+++ b/.github/workflows/check-build-tools-image.yml
@@ -0,0 +1,58 @@
+name: Check build-tools image
+
+on:
+ workflow_call:
+ outputs:
+ image-tag:
+ description: "build-tools image tag"
+ value: ${{ jobs.check-image.outputs.tag }}
+ found:
+ description: "Whether the image is found in the registry"
+ value: ${{ jobs.check-image.outputs.found }}
+
+defaults:
+ run:
+ shell: bash -euo pipefail {0}
+
+# No permission for GITHUB_TOKEN by default; the **minimal required** set of permissions should be granted in each job.
+permissions: {}
+
+jobs:
+ check-image:
+ runs-on: ubuntu-latest
+ outputs:
+ tag: ${{ steps.get-build-tools-tag.outputs.image-tag }}
+ found: ${{ steps.check-image.outputs.found }}
+
+ steps:
+ - name: Get build-tools image tag for the current commit
+ id: get-build-tools-tag
+ env:
+ COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ LAST_BUILD_TOOLS_SHA=$(
+ gh api \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ --method GET \
+ --field path=Dockerfile.build-tools \
+ --field sha=${COMMIT_SHA} \
+ --field per_page=1 \
+ --jq ".[0].sha" \
+ "/repos/${GITHUB_REPOSITORY}/commits"
+ )
+ echo "image-tag=${LAST_BUILD_TOOLS_SHA}" | tee -a $GITHUB_OUTPUT
+
+ - name: Check if such tag found in the registry
+ id: check-image
+ env:
+ IMAGE_TAG: ${{ steps.get-build-tools-tag.outputs.image-tag }}
+ run: |
+ if docker manifest inspect neondatabase/build-tools:${IMAGE_TAG}; then
+ found=true
+ else
+ found=false
+ fi
+
+ echo "found=${found}" | tee -a $GITHUB_OUTPUT
diff --git a/.github/workflows/check-permissions.yml b/.github/workflows/check-permissions.yml
new file mode 100644
index 0000000000..c3357c6cf8
--- /dev/null
+++ b/.github/workflows/check-permissions.yml
@@ -0,0 +1,36 @@
+name: Check Permissions
+
+on:
+ workflow_call:
+ inputs:
+ github-event-name:
+ required: true
+ type: string
+
+defaults:
+ run:
+ shell: bash -euo pipefail {0}
+
+# No permission for GITHUB_TOKEN by default; the **minimal required** set of permissions should be granted in each job.
+permissions: {}
+
+jobs:
+ check-permissions:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Disallow CI runs on PRs from forks
+ if: |
+ inputs.github-event-name == 'pull_request' &&
+ github.event.pull_request.head.repo.full_name != github.repository
+ run: |
+ if [ "${{ contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association) }}" = "true" ]; then
+ MESSAGE="Please create a PR from a branch of ${GITHUB_REPOSITORY} instead of a fork"
+ else
+ MESSAGE="The PR should be reviewed and labelled with 'approved-for-ci-run' to trigger a CI run"
+ fi
+
+ # TODO: use actions/github-script to post this message as a PR comment
+ echo >&2 "We don't run CI for PRs from forks"
+ echo >&2 "${MESSAGE}"
+
+ exit 1
diff --git a/.github/workflows/cleanup-caches-by-a-branch.yml b/.github/workflows/cleanup-caches-by-a-branch.yml
new file mode 100644
index 0000000000..d8c225dedb
--- /dev/null
+++ b/.github/workflows/cleanup-caches-by-a-branch.yml
@@ -0,0 +1,32 @@
+# A workflow from
+# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
+
+name: cleanup caches by a branch
+on:
+ pull_request:
+ types:
+ - closed
+
+jobs:
+ cleanup:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cleanup
+ run: |
+ gh extension install actions/gh-actions-cache
+
+ echo "Fetching list of cache key"
+ cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
+
+ ## Setting this to not fail the workflow while deleting cache keys.
+ set +e
+ echo "Deleting caches..."
+ for cacheKey in $cacheKeysForPR
+ do
+ gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
+ done
+ echo "Done"
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ REPO: ${{ github.repository }}
+ BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge
diff --git a/.github/workflows/neon_extra_builds.yml b/.github/workflows/neon_extra_builds.yml
index c6c2b7386a..5a2f9d6645 100644
--- a/.github/workflows/neon_extra_builds.yml
+++ b/.github/workflows/neon_extra_builds.yml
@@ -20,13 +20,31 @@ env:
COPT: '-Werror'
jobs:
+ check-permissions:
+ if: ${{ !contains(github.event.pull_request.labels.*.name, 'run-no-ci') }}
+ uses: ./.github/workflows/check-permissions.yml
+ with:
+ github-event-name: ${{ github.event_name}}
+
+ check-build-tools-image:
+ needs: [ check-permissions ]
+ uses: ./.github/workflows/check-build-tools-image.yml
+
+ build-build-tools-image:
+ needs: [ check-build-tools-image ]
+ uses: ./.github/workflows/build-build-tools-image.yml
+ with:
+ image-tag: ${{ needs.check-build-tools-image.outputs.image-tag }}
+ secrets: inherit
+
check-macos-build:
+ needs: [ check-permissions ]
if: |
contains(github.event.pull_request.labels.*.name, 'run-extra-build-macos') ||
contains(github.event.pull_request.labels.*.name, 'run-extra-build-*') ||
github.ref_name == 'main'
timeout-minutes: 90
- runs-on: macos-latest
+ runs-on: macos-14
env:
# Use release build only, to have less debug info around
@@ -57,24 +75,24 @@ jobs:
- name: Cache postgres v14 build
id: cache_pg_14
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: pg_install/v14
- key: v1-${{ runner.os }}-${{ env.BUILD_TYPE }}-pg-${{ steps.pg_v14_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
+ key: v1-${{ runner.os }}-${{ runner.arch }}-${{ env.BUILD_TYPE }}-pg-${{ steps.pg_v14_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
- name: Cache postgres v15 build
id: cache_pg_15
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: pg_install/v15
- key: v1-${{ runner.os }}-${{ env.BUILD_TYPE }}-pg-${{ steps.pg_v15_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
+ key: v1-${{ runner.os }}-${{ runner.arch }}-${{ env.BUILD_TYPE }}-pg-${{ steps.pg_v15_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
- name: Cache postgres v16 build
id: cache_pg_16
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: pg_install/v16
- key: v1-${{ runner.os }}-${{ env.BUILD_TYPE }}-pg-${{ steps.pg_v16_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
+ key: v1-${{ runner.os }}-${{ runner.arch }}-${{ env.BUILD_TYPE }}-pg-${{ steps.pg_v16_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
- name: Set extra env for macOS
run: |
@@ -82,14 +100,14 @@ jobs:
echo 'CPPFLAGS=-I/usr/local/opt/openssl@3/include' >> $GITHUB_ENV
- name: Cache cargo deps
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: |
~/.cargo/registry
!~/.cargo/registry/src
~/.cargo/git
target
- key: v1-${{ runner.os }}-cargo-${{ hashFiles('./Cargo.lock') }}-${{ hashFiles('./rust-toolchain.toml') }}-rust
+ key: v1-${{ runner.os }}-${{ runner.arch }}-cargo-${{ hashFiles('./Cargo.lock') }}-${{ hashFiles('./rust-toolchain.toml') }}-rust
- name: Build postgres v14
if: steps.cache_pg_14.outputs.cache-hit != 'true'
@@ -110,12 +128,13 @@ jobs:
run: make walproposer-lib -j$(sysctl -n hw.ncpu)
- name: Run cargo build
- run: cargo build --all --release
+ run: PQ_LIB_DIR=$(pwd)/pg_install/v16/lib cargo build --all --release
- name: Check that no warnings are produced
run: ./run_clippy.sh
check-linux-arm-build:
+ needs: [ check-permissions, build-build-tools-image ]
timeout-minutes: 90
runs-on: [ self-hosted, dev, arm64 ]
@@ -124,12 +143,15 @@ jobs:
# Hence keeping target/ (and general cache size) smaller
BUILD_TYPE: release
CARGO_FEATURES: --features testing
- CARGO_FLAGS: --locked --release
+ CARGO_FLAGS: --release
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_DEV }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY_DEV }}
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
steps:
@@ -171,21 +193,21 @@ jobs:
- name: Cache postgres v14 build
id: cache_pg_14
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: pg_install/v14
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ env.BUILD_TYPE }}-pg-${{ steps.pg_v14_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
- name: Cache postgres v15 build
id: cache_pg_15
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: pg_install/v15
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ env.BUILD_TYPE }}-pg-${{ steps.pg_v15_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
- name: Cache postgres v16 build
id: cache_pg_16
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: pg_install/v16
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ env.BUILD_TYPE }}-pg-${{ steps.pg_v16_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
@@ -210,18 +232,20 @@ jobs:
- name: Run cargo build
run: |
- mold -run cargo build $CARGO_FLAGS $CARGO_FEATURES --bins --tests
+ mold -run cargo build --locked $CARGO_FLAGS $CARGO_FEATURES --bins --tests
- name: Run cargo test
+ env:
+ NEXTEST_RETRIES: 3
run: |
- cargo test $CARGO_FLAGS $CARGO_FEATURES
+ cargo nextest run $CARGO_FEATURES
# Run separate tests for real S3
export ENABLE_REAL_S3_REMOTE_STORAGE=nonempty
export REMOTE_STORAGE_S3_BUCKET=neon-github-ci-tests
export REMOTE_STORAGE_S3_REGION=eu-central-1
# Avoid `$CARGO_FEATURES` since there's no `testing` feature in the e2e tests now
- cargo test $CARGO_FLAGS --package remote_storage --test test_real_s3
+ cargo nextest run --package remote_storage --test test_real_s3
# Run separate tests for real Azure Blob Storage
# XXX: replace region with `eu-central-1`-like region
@@ -231,14 +255,18 @@ jobs:
export REMOTE_STORAGE_AZURE_CONTAINER="${{ vars.REMOTE_STORAGE_AZURE_CONTAINER }}"
export REMOTE_STORAGE_AZURE_REGION="${{ vars.REMOTE_STORAGE_AZURE_REGION }}"
# Avoid `$CARGO_FEATURES` since there's no `testing` feature in the e2e tests now
- cargo test $CARGO_FLAGS --package remote_storage --test test_real_azure
+ cargo nextest run --package remote_storage --test test_real_azure
check-codestyle-rust-arm:
+ needs: [ check-permissions, build-build-tools-image ]
timeout-minutes: 90
runs-on: [ self-hosted, dev, arm64 ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
steps:
@@ -305,13 +333,17 @@ jobs:
run: cargo deny check
gather-rust-build-stats:
+ needs: [ check-permissions, build-build-tools-image ]
if: |
contains(github.event.pull_request.labels.*.name, 'run-extra-build-stats') ||
contains(github.event.pull_request.labels.*.name, 'run-extra-build-*') ||
github.ref_name == 'main'
runs-on: [ self-hosted, gen3, large ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
env:
@@ -352,7 +384,7 @@ jobs:
echo "report-url=${REPORT_URL}" >> $GITHUB_OUTPUT
- name: Publish build stats report
- uses: actions/github-script@v6
+ uses: actions/github-script@v7
env:
REPORT_URL: ${{ steps.upload-stats.outputs.report-url }}
SHA: ${{ github.event.pull_request.head.sha || github.sha }}
diff --git a/.github/workflows/pg_clients.yml b/.github/workflows/pg_clients.yml
index 224b7b4a6d..50e3227a74 100644
--- a/.github/workflows/pg_clients.yml
+++ b/.github/workflows/pg_clients.yml
@@ -28,7 +28,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
@@ -38,11 +38,10 @@ jobs:
uses: snok/install-poetry@v1
- name: Cache poetry deps
- id: cache_poetry
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/pypoetry/virtualenvs
- key: v1-${{ runner.os }}-python-deps-${{ hashFiles('poetry.lock') }}
+ key: v2-${{ runner.os }}-python-deps-ubunutu-latest-${{ hashFiles('poetry.lock') }}
- name: Install Python deps
shell: bash -euxo pipefail {0}
@@ -83,7 +82,7 @@ jobs:
# It will be fixed after switching to gen2 runner
- name: Upload python test logs
if: always()
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
retention-days: 7
name: python-test-pg_clients-${{ runner.os }}-stage-logs
diff --git a/.github/workflows/pin-build-tools-image.yml b/.github/workflows/pin-build-tools-image.yml
new file mode 100644
index 0000000000..c941692066
--- /dev/null
+++ b/.github/workflows/pin-build-tools-image.yml
@@ -0,0 +1,72 @@
+name: 'Pin build-tools image'
+
+on:
+ workflow_dispatch:
+ inputs:
+ from-tag:
+ description: 'Source tag'
+ required: true
+ type: string
+ workflow_call:
+ inputs:
+ from-tag:
+ description: 'Source tag'
+ required: true
+ type: string
+
+defaults:
+ run:
+ shell: bash -euo pipefail {0}
+
+concurrency:
+ group: pin-build-tools-image-${{ inputs.from-tag }}
+
+permissions: {}
+
+jobs:
+ tag-image:
+ runs-on: ubuntu-latest
+
+ env:
+ FROM_TAG: ${{ inputs.from-tag }}
+ TO_TAG: pinned
+
+ steps:
+ - name: Check if we really need to pin the image
+ id: check-manifests
+ run: |
+ docker manifest inspect neondatabase/build-tools:${FROM_TAG} > ${FROM_TAG}.json
+ docker manifest inspect neondatabase/build-tools:${TO_TAG} > ${TO_TAG}.json
+
+ if diff ${FROM_TAG}.json ${TO_TAG}.json; then
+ skip=true
+ else
+ skip=false
+ fi
+
+ echo "skip=${skip}" | tee -a $GITHUB_OUTPUT
+
+ - uses: docker/login-action@v3
+ if: steps.check-manifests.outputs.skip == 'false'
+ with:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
+
+ - name: Tag build-tools with `${{ env.TO_TAG }}` in Docker Hub
+ if: steps.check-manifests.outputs.skip == 'false'
+ run: |
+ docker buildx imagetools create -t neondatabase/build-tools:${TO_TAG} \
+ neondatabase/build-tools:${FROM_TAG}
+
+ - uses: docker/login-action@v3
+ if: steps.check-manifests.outputs.skip == 'false'
+ with:
+ registry: 369495373322.dkr.ecr.eu-central-1.amazonaws.com
+ username: ${{ secrets.AWS_ACCESS_KEY_DEV }}
+ password: ${{ secrets.AWS_SECRET_KEY_DEV }}
+
+ - name: Tag build-tools with `${{ env.TO_TAG }}` in ECR
+ if: steps.check-manifests.outputs.skip == 'false'
+ run: |
+ docker buildx imagetools create -t 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${TO_TAG} \
+ neondatabase/build-tools:${FROM_TAG}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ba37c5827a..b2c9a19588 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -2,12 +2,31 @@ name: Create Release Branch
on:
schedule:
- - cron: '0 6 * * 1'
+ # It should be kept in sync with if-condition in jobs
+ - cron: '0 6 * * MON' # Storage release
+ - cron: '0 6 * * THU' # Proxy release
workflow_dispatch:
+ inputs:
+ create-storage-release-branch:
+ type: boolean
+ description: 'Create Storage release PR'
+ required: false
+ create-proxy-release-branch:
+ type: boolean
+ description: 'Create Proxy release PR'
+ required: false
+
+# No permission for GITHUB_TOKEN by default; the **minimal required** set of permissions should be granted in each job.
+permissions: {}
+
+defaults:
+ run:
+ shell: bash -euo pipefail {0}
jobs:
- create_release_branch:
- runs-on: [ ubuntu-latest ]
+ create-storage-release-branch:
+ if: ${{ github.event.schedule == '0 6 * * MON' || format('{0}', inputs.create-storage-release-branch) == 'true' }}
+ runs-on: ubuntu-latest
permissions:
contents: write # for `git push`
@@ -18,27 +37,67 @@ jobs:
with:
ref: main
- - name: Get current date
- id: date
- run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
+ - name: Set environment variables
+ run: |
+ echo "RELEASE_DATE=$(date +'%Y-%m-%d')" | tee -a $GITHUB_ENV
+ echo "RELEASE_BRANCH=rc/$(date +'%Y-%m-%d')" | tee -a $GITHUB_ENV
- name: Create release branch
- run: git checkout -b releases/${{ steps.date.outputs.date }}
+ run: git checkout -b $RELEASE_BRANCH
- name: Push new branch
- run: git push origin releases/${{ steps.date.outputs.date }}
+ run: git push origin $RELEASE_BRANCH
- name: Create pull request into release
env:
GH_TOKEN: ${{ secrets.CI_ACCESS_TOKEN }}
run: |
cat << EOF > body.md
- ## Release ${{ steps.date.outputs.date }}
+ ## Release ${RELEASE_DATE}
- **Please merge this PR using 'Create a merge commit'!**
+ **Please merge this Pull Request using 'Create a merge commit' button**
EOF
- gh pr create --title "Release ${{ steps.date.outputs.date }}" \
+ gh pr create --title "Release ${RELEASE_DATE}" \
--body-file "body.md" \
- --head "releases/${{ steps.date.outputs.date }}" \
+ --head "${RELEASE_BRANCH}" \
--base "release"
+
+ create-proxy-release-branch:
+ if: ${{ github.event.schedule == '0 6 * * THU' || format('{0}', inputs.create-proxy-release-branch) == 'true' }}
+ runs-on: ubuntu-latest
+
+ permissions:
+ contents: write # for `git push`
+
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v4
+ with:
+ ref: main
+
+ - name: Set environment variables
+ run: |
+ echo "RELEASE_DATE=$(date +'%Y-%m-%d')" | tee -a $GITHUB_ENV
+ echo "RELEASE_BRANCH=rc/proxy/$(date +'%Y-%m-%d')" | tee -a $GITHUB_ENV
+
+ - name: Create release branch
+ run: git checkout -b $RELEASE_BRANCH
+
+ - name: Push new branch
+ run: git push origin $RELEASE_BRANCH
+
+ - name: Create pull request into release
+ env:
+ GH_TOKEN: ${{ secrets.CI_ACCESS_TOKEN }}
+ run: |
+ cat << EOF > body.md
+ ## Proxy release ${RELEASE_DATE}
+
+ **Please merge this Pull Request using 'Create a merge commit' button**
+ EOF
+
+ gh pr create --title "Proxy release ${RELEASE_DATE}" \
+ --body-file "body.md" \
+ --head "${RELEASE_BRANCH}" \
+ --base "release-proxy"
diff --git a/.github/workflows/trigger-e2e-tests.yml b/.github/workflows/trigger-e2e-tests.yml
new file mode 100644
index 0000000000..ae34cbffe0
--- /dev/null
+++ b/.github/workflows/trigger-e2e-tests.yml
@@ -0,0 +1,119 @@
+name: Trigger E2E Tests
+
+on:
+ pull_request:
+ types:
+ - ready_for_review
+ workflow_call:
+
+defaults:
+ run:
+ shell: bash -euxo pipefail {0}
+
+env:
+ # A concurrency group that we use for e2e-tests runs, matches `concurrency.group` above with `github.repository` as a prefix
+ E2E_CONCURRENCY_GROUP: ${{ github.repository }}-e2e-tests-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
+ AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_DEV }}
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY_DEV }}
+
+jobs:
+ cancel-previous-e2e-tests:
+ if: github.event_name == 'pull_request'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Cancel previous e2e-tests runs for this PR
+ env:
+ GH_TOKEN: ${{ secrets.CI_ACCESS_TOKEN }}
+ run: |
+ gh workflow --repo neondatabase/cloud \
+ run cancel-previous-in-concurrency-group.yml \
+ --field concurrency_group="${{ env.E2E_CONCURRENCY_GROUP }}"
+
+ tag:
+ runs-on: [ ubuntu-latest ]
+ outputs:
+ build-tag: ${{ steps.build-tag.outputs.tag }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Get build tag
+ env:
+ GH_TOKEN: ${{ secrets.CI_ACCESS_TOKEN }}
+ CURRENT_BRANCH: ${{ github.head_ref || github.ref_name }}
+ CURRENT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
+ run: |
+ if [[ "$GITHUB_REF_NAME" == "main" ]]; then
+ echo "tag=$(git rev-list --count HEAD)" | tee -a $GITHUB_OUTPUT
+ elif [[ "$GITHUB_REF_NAME" == "release" ]]; then
+ echo "tag=release-$(git rev-list --count HEAD)" | tee -a $GITHUB_OUTPUT
+ elif [[ "$GITHUB_REF_NAME" == "release-proxy" ]]; then
+ echo "tag=release-proxy-$(git rev-list --count HEAD)" >> $GITHUB_OUTPUT
+ else
+ echo "GITHUB_REF_NAME (value '$GITHUB_REF_NAME') is not set to either 'main' or 'release'"
+ BUILD_AND_TEST_RUN_ID=$(gh run list -b $CURRENT_BRANCH -c $CURRENT_SHA -w 'Build and Test' -L 1 --json databaseId --jq '.[].databaseId')
+ echo "tag=$BUILD_AND_TEST_RUN_ID" | tee -a $GITHUB_OUTPUT
+ fi
+ id: build-tag
+
+ trigger-e2e-tests:
+ needs: [ tag ]
+ runs-on: [ self-hosted, gen3, small ]
+ env:
+ TAG: ${{ needs.tag.outputs.build-tag }}
+ container:
+ image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/base:pinned
+ options: --init
+ steps:
+ - name: check if ecr image are present
+ run: |
+ for REPO in neon compute-tools compute-node-v14 vm-compute-node-v14 compute-node-v15 vm-compute-node-v15 compute-node-v16 vm-compute-node-v16; do
+ OUTPUT=$(aws ecr describe-images --repository-name ${REPO} --region eu-central-1 --query "imageDetails[?imageTags[?contains(@, '${TAG}')]]" --output text)
+ if [ "$OUTPUT" == "" ]; then
+ echo "$REPO with image tag $TAG not found" >> $GITHUB_OUTPUT
+ exit 1
+ fi
+ done
+
+ - name: Set PR's status to pending and request a remote CI test
+ run: |
+ # For pull requests, GH Actions set "github.sha" variable to point at a fake merge commit
+ # but we need to use a real sha of a latest commit in the PR's branch for the e2e job,
+ # to place a job run status update later.
+ COMMIT_SHA=${{ github.event.pull_request.head.sha }}
+ # For non-PR kinds of runs, the above will produce an empty variable, pick the original sha value for those
+ COMMIT_SHA=${COMMIT_SHA:-${{ github.sha }}}
+
+ REMOTE_REPO="${{ github.repository_owner }}/cloud"
+
+ curl -f -X POST \
+ https://api.github.com/repos/${{ github.repository }}/statuses/$COMMIT_SHA \
+ -H "Accept: application/vnd.github.v3+json" \
+ --user "${{ secrets.CI_ACCESS_TOKEN }}" \
+ --data \
+ "{
+ \"state\": \"pending\",
+ \"context\": \"neon-cloud-e2e\",
+ \"description\": \"[$REMOTE_REPO] Remote CI job is about to start\"
+ }"
+
+ curl -f -X POST \
+ https://api.github.com/repos/$REMOTE_REPO/actions/workflows/testing.yml/dispatches \
+ -H "Accept: application/vnd.github.v3+json" \
+ --user "${{ secrets.CI_ACCESS_TOKEN }}" \
+ --data \
+ "{
+ \"ref\": \"main\",
+ \"inputs\": {
+ \"ci_job_name\": \"neon-cloud-e2e\",
+ \"commit_hash\": \"$COMMIT_SHA\",
+ \"remote_repo\": \"${{ github.repository }}\",
+ \"storage_image_tag\": \"${TAG}\",
+ \"compute_image_tag\": \"${TAG}\",
+ \"concurrency_group\": \"${{ env.E2E_CONCURRENCY_GROUP }}\"
+ }
+ }"
diff --git a/.github/workflows/update_build_tools_image.yml b/.github/workflows/update_build_tools_image.yml
deleted file mode 100644
index 88bab797b7..0000000000
--- a/.github/workflows/update_build_tools_image.yml
+++ /dev/null
@@ -1,130 +0,0 @@
-name: 'Update build tools image tag'
-
-# This workflow it used to update tag of build tools in ECR.
-# The most common use case is adding/moving `pinned` tag to `${GITHUB_RUN_IT}` image.
-
-on:
- workflow_dispatch:
- inputs:
- from-tag:
- description: 'Source tag'
- required: true
- type: string
- to-tag:
- description: 'Destination tag'
- required: true
- type: string
- default: 'pinned'
-
-defaults:
- run:
- shell: bash -euo pipefail {0}
-
-env:
- AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_DEV }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY_DEV }}
-
-permissions: {}
-
-jobs:
- tag-image:
- runs-on: [ self-hosted, gen3, small ]
- container: golang:1.19-bullseye
-
- env:
- IMAGE: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools
- FROM_TAG: ${{ inputs.from-tag }}
- TO_TAG: ${{ inputs.to-tag }}
- outputs:
- next-digest-buildtools: ${{ steps.next-digest.outputs.next-digest-buildtools }}
- prev-digest-buildtools: ${{ steps.prev-digest.outputs.prev-digest-buildtools }}
-
- steps:
- - name: Install Crane & ECR helper
- run: |
- go install github.com/google/go-containerregistry/cmd/crane@a54d64203cffcbf94146e04069aae4a97f228ee2 # v0.16.1
- go install github.com/awslabs/amazon-ecr-credential-helper/ecr-login/cli/docker-credential-ecr-login@adf1bafd791ae7d4ff098108b1e91f36a4da5404 # v0.7.1
-
- - name: Configure ECR login
- run: |
- mkdir /github/home/.docker/
- echo "{\"credsStore\":\"ecr-login\"}" > /github/home/.docker/config.json
-
- - name: Get source image digest
- id: next-digest
- run: |
- NEXT_DIGEST=$(crane digest ${IMAGE}:${FROM_TAG} || true)
- if [ -z "${NEXT_DIGEST}" ]; then
- echo >&2 "Image ${IMAGE}:${FROM_TAG} does not exist"
- exit 1
- fi
-
- echo "Current ${IMAGE}@${FROM_TAG} image is ${IMAGE}@${NEXT_DIGEST}"
- echo "next-digest-buildtools=$NEXT_DIGEST" >> $GITHUB_OUTPUT
-
- - name: Get destination image digest (if already exists)
- id: prev-digest
- run: |
- PREV_DIGEST=$(crane digest ${IMAGE}:${TO_TAG} || true)
- if [ -z "${PREV_DIGEST}" ]; then
- echo >&2 "Image ${IMAGE}:${TO_TAG} does not exist (it's ok)"
- else
- echo >&2 "Current ${IMAGE}@${TO_TAG} image is ${IMAGE}@${PREV_DIGEST}"
-
- echo "prev-digest-buildtools=$PREV_DIGEST" >> $GITHUB_OUTPUT
- fi
-
- - name: Tag image
- run: |
- crane tag "${IMAGE}:${FROM_TAG}" "${TO_TAG}"
-
- rollback-tag-image:
- needs: tag-image
- if: ${{ !success() }}
-
- runs-on: [ self-hosted, gen3, small ]
- container: golang:1.19-bullseye
-
- env:
- IMAGE: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools
- FROM_TAG: ${{ inputs.from-tag }}
- TO_TAG: ${{ inputs.to-tag }}
-
- steps:
- - name: Install Crane & ECR helper
- run: |
- go install github.com/google/go-containerregistry/cmd/crane@a54d64203cffcbf94146e04069aae4a97f228ee2 # v0.16.1
- go install github.com/awslabs/amazon-ecr-credential-helper/ecr-login/cli/docker-credential-ecr-login@adf1bafd791ae7d4ff098108b1e91f36a4da5404 # v0.7.1
-
- - name: Configure ECR login
- run: |
- mkdir /github/home/.docker/
- echo "{\"credsStore\":\"ecr-login\"}" > /github/home/.docker/config.json
-
- - name: Restore previous tag if needed
- run: |
- NEXT_DIGEST="${{ needs.tag-image.outputs.next-digest-buildtools }}"
- PREV_DIGEST="${{ needs.tag-image.outputs.prev-digest-buildtools }}"
-
- if [ -z "${NEXT_DIGEST}" ]; then
- echo >&2 "Image ${IMAGE}:${FROM_TAG} does not exist, nothing to rollback"
- exit 0
- fi
-
- if [ -z "${PREV_DIGEST}" ]; then
- # I guess we should delete the tag here/untag the image, but crane does not support it
- # - https://github.com/google/go-containerregistry/issues/999
-
- echo >&2 "Image ${IMAGE}:${TO_TAG} did not exist, but it was created by the job, no need to rollback"
-
- exit 0
- fi
-
- CURRENT_DIGEST=$(crane digest "${IMAGE}:${TO_TAG}")
- if [ "${CURRENT_DIGEST}" == "${NEXT_DIGEST}" ]; then
- crane tag "${IMAGE}@${PREV_DIGEST}" "${TO_TAG}"
-
- echo >&2 "Successfully restored ${TO_TAG} tag from ${IMAGE}@${CURRENT_DIGEST} to ${IMAGE}@${PREV_DIGEST}"
- else
- echo >&2 "Image ${IMAGE}:${TO_TAG}@${CURRENT_DIGEST} is not required to be restored"
- fi
diff --git a/.gitignore b/.gitignore
index 3f4495c9e7..2c38cdcc59 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ test_output/
neon.iml
/.neon
/integration_tests/.neon
+compaction-suite-results.*
# Coverage
*.profraw
diff --git a/CODEOWNERS b/CODEOWNERS
index e384dc39f1..5b601f0566 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1,10 +1,10 @@
/compute_tools/ @neondatabase/control-plane @neondatabase/compute
-/control_plane/ @neondatabase/compute @neondatabase/storage
-/libs/pageserver_api/ @neondatabase/compute @neondatabase/storage
+/control_plane/attachment_service @neondatabase/storage
+/libs/pageserver_api/ @neondatabase/storage
/libs/postgres_ffi/ @neondatabase/compute
/libs/remote_storage/ @neondatabase/storage
/libs/safekeeper_api/ @neondatabase/safekeepers
-/libs/vm_monitor/ @neondatabase/autoscaling @neondatabase/compute
+/libs/vm_monitor/ @neondatabase/autoscaling
/pageserver/ @neondatabase/storage
/pgxn/ @neondatabase/compute
/proxy/ @neondatabase/proxy
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b318c295a3..164eb77f58 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -20,7 +20,7 @@ ln -s ../../pre-commit.py .git/hooks/pre-commit
This will run following checks on staged files before each commit:
- `rustfmt`
-- checks for python files, see [obligatory checks](/docs/sourcetree.md#obligatory-checks).
+- checks for Python files, see [obligatory checks](/docs/sourcetree.md#obligatory-checks).
There is also a separate script `./run_clippy.sh` that runs `cargo clippy` on the whole project
and `./scripts/reformat` that runs all formatting tools to ensure the project is up to date.
@@ -54,6 +54,9 @@ _An instruction for maintainers_
- If and only if it looks **safe** (i.e. it doesn't contain any malicious code which could expose secrets or harm the CI), then:
- Press the "Approve and run" button in GitHub UI
- Add the `approved-for-ci-run` label to the PR
+ - Currently draft PR will skip e2e test (only for internal contributors). After turning the PR 'Ready to Review' CI will trigger e2e test
+ - Add `run-e2e-tests-in-draft` label to run e2e test in draft PR (override above behaviour)
+ - The `approved-for-ci-run` workflow will add `run-e2e-tests-in-draft` automatically to run e2e test for external contributors
Repeat all steps after any change to the PR.
- When the changes are ready to get merged — merge the original PR (not the internal one)
@@ -71,16 +74,11 @@ We're using the following approach to make it work:
For details see [`approved-for-ci-run.yml`](.github/workflows/approved-for-ci-run.yml)
-## How do I add the "pinned" tag to an buildtools image?
-We use the `pinned` tag for `Dockerfile.buildtools` build images in our CI/CD setup, currently adding the `pinned` tag is a manual operation.
+## How do I make build-tools image "pinned"
-You can call it from GitHub UI: https://github.com/neondatabase/neon/actions/workflows/update_build_tools_image.yml,
-or using GitHub CLI:
+It's possible to update the `pinned` tag of the `build-tools` image using the `pin-build-tools-image.yml` workflow.
```bash
-gh workflow -R neondatabase/neon run update_build_tools_image.yml \
- -f from-tag=6254913013 \
- -f to-tag=pinned \
-
-# Default `-f to-tag` is `pinned`, so the parameter can be omitted.
-```
\ No newline at end of file
+gh workflow -R neondatabase/neon run pin-build-tools-image.yml \
+ -f from-tag=cc98d9b00d670f182c507ae3783342bd7e64c31e
+```
diff --git a/Cargo.lock b/Cargo.lock
index a9b9f9c7bf..e35fa564b9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -25,9 +25,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
-version = "0.8.5"
+version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd7d5a2cecb58716e47d67d5703a249964b14c7be1ec3cad3affc295b2d1c35d"
+checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f"
dependencies = [
"cfg-if",
"const-random",
@@ -241,7 +241,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -252,7 +252,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -275,18 +275,24 @@ name = "attachment_service"
version = "0.1.0"
dependencies = [
"anyhow",
+ "aws-config",
+ "aws-sdk-secretsmanager",
"camino",
"clap",
"control_plane",
+ "diesel",
+ "diesel_migrations",
"futures",
"git-version",
+ "humantime",
"hyper",
"metrics",
+ "once_cell",
"pageserver_api",
"pageserver_client",
- "postgres_backend",
"postgres_connection",
- "scopeguard",
+ "r2d2",
+ "reqwest",
"serde",
"serde_json",
"thiserror",
@@ -305,12 +311,11 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "aws-config"
-version = "1.0.1"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80c950a809d39bc9480207cb1cfc879ace88ea7e3a4392a8e9999e45d6e5692e"
+checksum = "8b30c39ebe61f75d1b3785362b1586b41991873c9ab3e317a9181c246fb71d82"
dependencies = [
"aws-credential-types",
- "aws-http",
"aws-runtime",
"aws-sdk-sso",
"aws-sdk-ssooidc",
@@ -325,7 +330,7 @@ dependencies = [
"bytes",
"fastrand 2.0.0",
"hex",
- "http",
+ "http 0.2.9",
"hyper",
"ring 0.17.6",
"time",
@@ -336,9 +341,9 @@ dependencies = [
[[package]]
name = "aws-credential-types"
-version = "1.0.1"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c1317e1a3514b103cf7d5828bbab3b4d30f56bd22d684f8568bc51b6cfbbb1c"
+checksum = "33cc49dcdd31c8b6e79850a179af4c367669150c7ac0135f176c61bec81a70f7"
dependencies = [
"aws-smithy-async",
"aws-smithy-runtime-api",
@@ -346,30 +351,13 @@ dependencies = [
"zeroize",
]
-[[package]]
-name = "aws-http"
-version = "0.60.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "361c4310fdce94328cc2d1ca0c8a48c13f43009c61d3367585685a50ca8c66b6"
-dependencies = [
- "aws-smithy-runtime-api",
- "aws-smithy-types",
- "aws-types",
- "bytes",
- "http",
- "http-body",
- "pin-project-lite",
- "tracing",
-]
-
[[package]]
name = "aws-runtime"
-version = "1.0.1"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ed7ef604a15fd0d4d9e43701295161ea6b504b63c44990ead352afea2bc15e9"
+checksum = "eb031bff99877c26c28895766f7bb8484a05e24547e370768d6cc9db514662aa"
dependencies = [
"aws-credential-types",
- "aws-http",
"aws-sigv4",
"aws-smithy-async",
"aws-smithy-eventstream",
@@ -377,21 +365,23 @@ dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
+ "bytes",
"fastrand 2.0.0",
- "http",
+ "http 0.2.9",
+ "http-body",
"percent-encoding",
+ "pin-project-lite",
"tracing",
"uuid",
]
[[package]]
name = "aws-sdk-s3"
-version = "1.4.0"
+version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9dcafc2fe52cc30b2d56685e2fa6a879ba50d79704594852112337a472ddbd24"
+checksum = "951f7730f51a2155c711c85c79f337fbc02a577fa99d2a0a8059acfce5392113"
dependencies = [
"aws-credential-types",
- "aws-http",
"aws-runtime",
"aws-sigv4",
"aws-smithy-async",
@@ -405,23 +395,22 @@ dependencies = [
"aws-smithy-xml",
"aws-types",
"bytes",
- "http",
+ "http 0.2.9",
"http-body",
"once_cell",
"percent-encoding",
- "regex",
+ "regex-lite",
"tracing",
"url",
]
[[package]]
-name = "aws-sdk-sso"
-version = "1.3.0"
+name = "aws-sdk-secretsmanager"
+version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0619ab97a5ca8982e7de073cdc66f93e5f6a1b05afc09e696bec1cb3607cd4df"
+checksum = "0a0b64e61e7d632d9df90a2e0f32630c68c24960cab1d27d848718180af883d3"
dependencies = [
"aws-credential-types",
- "aws-http",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http",
@@ -431,19 +420,42 @@ dependencies = [
"aws-smithy-types",
"aws-types",
"bytes",
- "http",
- "regex",
+ "fastrand 2.0.0",
+ "http 0.2.9",
+ "once_cell",
+ "regex-lite",
+ "tracing",
+]
+
+[[package]]
+name = "aws-sdk-sso"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f486420a66caad72635bc2ce0ff6581646e0d32df02aa39dc983bfe794955a5b"
+dependencies = [
+ "aws-credential-types",
+ "aws-runtime",
+ "aws-smithy-async",
+ "aws-smithy-http",
+ "aws-smithy-json",
+ "aws-smithy-runtime",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "aws-types",
+ "bytes",
+ "http 0.2.9",
+ "once_cell",
+ "regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-ssooidc"
-version = "1.3.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f04b9f5474cc0f35d829510b2ec8c21e352309b46bf9633c5a81fb9321e9b1c7"
+checksum = "39ddccf01d82fce9b4a15c8ae8608211ee7db8ed13a70b514bbfe41df3d24841"
dependencies = [
"aws-credential-types",
- "aws-http",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http",
@@ -453,19 +465,19 @@ dependencies = [
"aws-smithy-types",
"aws-types",
"bytes",
- "http",
- "regex",
+ "http 0.2.9",
+ "once_cell",
+ "regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-sts"
-version = "1.3.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5700da387716ccfc30b27f44b008f457e1baca5b0f05b6b95455778005e3432a"
+checksum = "1a591f8c7e6a621a501b2b5d2e88e1697fcb6274264523a6ad4d5959889a41ce"
dependencies = [
"aws-credential-types",
- "aws-http",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http",
@@ -476,16 +488,17 @@ dependencies = [
"aws-smithy-types",
"aws-smithy-xml",
"aws-types",
- "http",
- "regex",
+ "http 0.2.9",
+ "once_cell",
+ "regex-lite",
"tracing",
]
[[package]]
name = "aws-sigv4"
-version = "1.0.1"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "380adcc8134ad8bbdfeb2ace7626a869914ee266322965276cbc54066186d236"
+checksum = "c371c6b0ac54d4605eb6f016624fb5c7c2925d315fdf600ac1bf21b19d5f1742"
dependencies = [
"aws-credential-types",
"aws-smithy-eventstream",
@@ -497,11 +510,11 @@ dependencies = [
"form_urlencoded",
"hex",
"hmac",
- "http",
+ "http 0.2.9",
+ "http 1.0.0",
"once_cell",
"p256",
"percent-encoding",
- "regex",
"ring 0.17.6",
"sha2",
"subtle",
@@ -512,9 +525,9 @@ dependencies = [
[[package]]
name = "aws-smithy-async"
-version = "1.0.2"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e37ca17d25fe1e210b6d4bdf59b81caebfe99f986201a1228cb5061233b4b13"
+checksum = "72ee2d09cce0ef3ae526679b522835d63e75fb427aca5413cd371e490d52dcc6"
dependencies = [
"futures-util",
"pin-project-lite",
@@ -523,9 +536,9 @@ dependencies = [
[[package]]
name = "aws-smithy-checksums"
-version = "0.60.0"
+version = "0.60.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5a373ec01aede3dd066ec018c1bc4e8f5dd11b2c11c59c8eef1a5c68101f397"
+checksum = "be2acd1b9c6ae5859999250ed5a62423aedc5cf69045b844432de15fa2f31f2b"
dependencies = [
"aws-smithy-http",
"aws-smithy-types",
@@ -533,7 +546,7 @@ dependencies = [
"crc32c",
"crc32fast",
"hex",
- "http",
+ "http 0.2.9",
"http-body",
"md-5",
"pin-project-lite",
@@ -544,9 +557,9 @@ dependencies = [
[[package]]
name = "aws-smithy-eventstream"
-version = "0.60.0"
+version = "0.60.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c669e1e5fc0d79561bf7a122b118bd50c898758354fe2c53eb8f2d31507cbc3"
+checksum = "e6363078f927f612b970edf9d1903ef5cef9a64d1e8423525ebb1f0a1633c858"
dependencies = [
"aws-smithy-types",
"bytes",
@@ -555,9 +568,9 @@ dependencies = [
[[package]]
name = "aws-smithy-http"
-version = "0.60.0"
+version = "0.60.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b1de8aee22f67de467b2e3d0dd0fb30859dc53f579a63bd5381766b987db644"
+checksum = "dab56aea3cd9e1101a0a999447fb346afb680ab1406cebc44b32346e25b4117d"
dependencies = [
"aws-smithy-eventstream",
"aws-smithy-runtime-api",
@@ -565,7 +578,7 @@ dependencies = [
"bytes",
"bytes-utils",
"futures-core",
- "http",
+ "http 0.2.9",
"http-body",
"once_cell",
"percent-encoding",
@@ -576,18 +589,18 @@ dependencies = [
[[package]]
name = "aws-smithy-json"
-version = "0.60.0"
+version = "0.60.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a46dd338dc9576d6a6a5b5a19bd678dcad018ececee11cf28ecd7588bd1a55c"
+checksum = "fd3898ca6518f9215f62678870064398f00031912390efd03f1f6ef56d83aa8e"
dependencies = [
"aws-smithy-types",
]
[[package]]
name = "aws-smithy-query"
-version = "0.60.0"
+version = "0.60.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "feb5b8c7a86d4b6399169670723b7e6f21a39fc833a30f5c5a2f997608178129"
+checksum = "bda4b1dfc9810e35fba8a620e900522cd1bd4f9578c446e82f49d1ce41d2e9f9"
dependencies = [
"aws-smithy-types",
"urlencoding",
@@ -595,9 +608,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime"
-version = "1.0.2"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "273479291efc55e7b0bce985b139d86b6031adb8e50f65c1f712f20ba38f6388"
+checksum = "fafdab38f40ad7816e7da5dec279400dd505160780083759f01441af1bbb10ea"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
@@ -606,28 +619,28 @@ dependencies = [
"bytes",
"fastrand 2.0.0",
"h2",
- "http",
+ "http 0.2.9",
"http-body",
"hyper",
"hyper-rustls",
"once_cell",
"pin-project-lite",
"pin-utils",
- "rustls",
+ "rustls 0.21.9",
"tokio",
"tracing",
]
[[package]]
name = "aws-smithy-runtime-api"
-version = "1.0.2"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6cebff0d977b6b6feed2fd07db52aac58ba3ccaf26cdd49f1af4add5061bef9"
+checksum = "c18276dd28852f34b3bf501f4f3719781f4999a51c7bff1a5c6dc8c4529adc29"
dependencies = [
"aws-smithy-async",
"aws-smithy-types",
"bytes",
- "http",
+ "http 0.2.9",
"pin-project-lite",
"tokio",
"tracing",
@@ -636,15 +649,15 @@ dependencies = [
[[package]]
name = "aws-smithy-types"
-version = "1.0.2"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7f48b3f27ddb40ab19892a5abda331f403e3cb877965e4e51171447807104af"
+checksum = "bb3e134004170d3303718baa2a4eb4ca64ee0a1c0a7041dca31b38be0fb414f3"
dependencies = [
"base64-simd",
"bytes",
"bytes-utils",
"futures-core",
- "http",
+ "http 0.2.9",
"http-body",
"itoa",
"num-integer",
@@ -659,24 +672,24 @@ dependencies = [
[[package]]
name = "aws-smithy-xml"
-version = "0.60.0"
+version = "0.60.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ec40d74a67fd395bc3f6b4ccbdf1543672622d905ef3f979689aea5b730cb95"
+checksum = "8604a11b25e9ecaf32f9aa56b9fe253c5e2f606a3477f0071e96d3155a5ed218"
dependencies = [
"xmlparser",
]
[[package]]
name = "aws-types"
-version = "1.0.1"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8403fc56b1f3761e8efe45771ddc1165e47ec3417c68e68a4519b5cb030159ca"
+checksum = "789bbe008e65636fe1b6dbbb374c40c8960d1232b96af5ff4aec349f9c4accf4"
dependencies = [
"aws-credential-types",
"aws-smithy-async",
"aws-smithy-runtime-api",
"aws-smithy-types",
- "http",
+ "http 0.2.9",
"rustc_version",
"tracing",
]
@@ -693,7 +706,7 @@ dependencies = [
"bitflags 1.3.2",
"bytes",
"futures-util",
- "http",
+ "http 0.2.9",
"http-body",
"hyper",
"itoa",
@@ -725,7 +738,7 @@ dependencies = [
"async-trait",
"bytes",
"futures-util",
- "http",
+ "http 0.2.9",
"http-body",
"mime",
"rustversion",
@@ -894,6 +907,16 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+[[package]]
+name = "bcder"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c627747a6774aab38beb35990d88309481378558875a41da1a4b2e373c906ef0"
+dependencies = [
+ "bytes",
+ "smallvec",
+]
+
[[package]]
name = "bincode"
version = "1.3.3"
@@ -922,7 +945,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
- "syn 2.0.32",
+ "syn 2.0.52",
"which",
]
@@ -973,9 +996,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
-version = "1.4.0"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
dependencies = [
"serde",
]
@@ -1136,7 +1159,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -1145,16 +1168,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
-[[package]]
-name = "close_fds"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3bc416f33de9d59e79e57560f450d21ff8393adcf1cdfc3e6d8fb93d5f88a2ed"
-dependencies = [
- "cfg-if",
- "libc",
-]
-
[[package]]
name = "colorchoice"
version = "1.0.0"
@@ -1386,9 +1399,9 @@ dependencies = [
[[package]]
name = "crc32c"
-version = "0.6.3"
+version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dfea2db42e9927a3845fb268a10a72faed6d416065f77873f05e411457c363e"
+checksum = "89254598aa9b9fa608de44b3ae54c810f0f06d755e24c50177f1f8f31ff50ce2"
dependencies = [
"rustc_version",
]
@@ -1571,7 +1584,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -1582,7 +1595,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
dependencies = [
"darling_core",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -1624,6 +1637,16 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "der"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
+dependencies = [
+ "const-oid",
+ "zeroize",
+]
+
[[package]]
name = "der-parser"
version = "8.2.0"
@@ -1638,6 +1661,69 @@ dependencies = [
"rusticata-macros",
]
+[[package]]
+name = "desim"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "bytes",
+ "hex",
+ "parking_lot 0.12.1",
+ "rand 0.8.5",
+ "scopeguard",
+ "smallvec",
+ "tracing",
+ "utils",
+ "workspace_hack",
+]
+
+[[package]]
+name = "diesel"
+version = "2.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62c6fcf842f17f8c78ecf7c81d75c5ce84436b41ee07e03f490fbb5f5a8731d8"
+dependencies = [
+ "bitflags 2.4.1",
+ "byteorder",
+ "diesel_derives",
+ "itoa",
+ "pq-sys",
+ "r2d2",
+ "serde_json",
+]
+
+[[package]]
+name = "diesel_derives"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef8337737574f55a468005a83499da720f20c65586241ffea339db9ecdfd2b44"
+dependencies = [
+ "diesel_table_macro_syntax",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "diesel_migrations"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6036b3f0120c5961381b570ee20a02432d7e2d27ea60de9578799cf9156914ac"
+dependencies = [
+ "diesel",
+ "migrations_internals",
+ "migrations_macros",
+]
+
+[[package]]
+name = "diesel_table_macro_syntax"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5"
+dependencies = [
+ "syn 2.0.52",
+]
+
[[package]]
name = "digest"
version = "0.10.7"
@@ -1657,7 +1743,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -1681,10 +1767,10 @@ version = "0.14.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c"
dependencies = [
- "der",
+ "der 0.6.1",
"elliptic-curve",
"rfc6979",
- "signature",
+ "signature 1.6.4",
]
[[package]]
@@ -1701,7 +1787,7 @@ checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3"
dependencies = [
"base16ct",
"crypto-bigint 0.4.9",
- "der",
+ "der 0.6.1",
"digest",
"ff",
"generic-array",
@@ -1749,6 +1835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e875f1719c16de097dee81ed675e2d9bb63096823ed3f0ca827b7dea3028bbbb"
dependencies = [
"enumset_derive",
+ "serde",
]
[[package]]
@@ -1760,7 +1847,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -1966,9 +2053,9 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
"futures-sink",
@@ -1976,9 +2063,9 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-executor"
@@ -1993,9 +2080,9 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-lite"
@@ -2014,26 +2101,26 @@ dependencies = [
[[package]]
name = "futures-macro"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
name = "futures-sink"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-timer"
@@ -2043,9 +2130,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
[[package]]
name = "futures-util"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-channel",
"futures-core",
@@ -2149,7 +2236,7 @@ dependencies = [
"futures-core",
"futures-sink",
"futures-util",
- "http",
+ "http 0.2.9",
"indexmap 2.0.1",
"slab",
"tokio",
@@ -2199,11 +2286,11 @@ dependencies = [
[[package]]
name = "hashlink"
-version = "0.8.2"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0761a1b9491c4f2e3d66aa0f62d0fba0af9a0e2852e4d48ea506632a4b56e6aa"
+checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
- "hashbrown 0.13.2",
+ "hashbrown 0.14.0",
]
[[package]]
@@ -2300,6 +2387,17 @@ dependencies = [
"itoa",
]
+[[package]]
+name = "http"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
[[package]]
name = "http-body"
version = "0.4.5"
@@ -2307,7 +2405,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [
"bytes",
- "http",
+ "http 0.2.9",
"pin-project-lite",
]
@@ -2370,7 +2468,7 @@ dependencies = [
"futures-core",
"futures-util",
"h2",
- "http",
+ "http 0.2.9",
"http-body",
"httparse",
"httpdate",
@@ -2389,13 +2487,13 @@ version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7"
dependencies = [
- "http",
+ "http 0.2.9",
"hyper",
"log",
- "rustls",
+ "rustls 0.21.9",
"rustls-native-certs",
"tokio",
- "tokio-rustls",
+ "tokio-rustls 0.24.0",
]
[[package]]
@@ -2633,7 +2731,7 @@ checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4"
dependencies = [
"base64 0.21.1",
"js-sys",
- "pem 3.0.3",
+ "pem",
"ring 0.17.6",
"serde",
"serde_json",
@@ -2660,6 +2758,16 @@ dependencies = [
"libc",
]
+[[package]]
+name = "lasso"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4644821e1c3d7a560fe13d842d13f587c07348a1a05d3a797152d41c90c56df2"
+dependencies = [
+ "dashmap",
+ "hashbrown 0.13.2",
+]
+
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -2672,6 +2780,17 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+[[package]]
+name = "leaky-bucket"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eb491abd89e9794d50f93c8db610a29509123e3fbbc9c8c67a528e9391cd853"
+dependencies = [
+ "parking_lot 0.12.1",
+ "tokio",
+ "tracing",
+]
+
[[package]]
name = "libc"
version = "0.2.150"
@@ -2688,6 +2807,12 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "libm"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
@@ -2758,6 +2883,15 @@ version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+[[package]]
+name = "memoffset"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "memoffset"
version = "0.8.0"
@@ -2783,10 +2917,35 @@ dependencies = [
"chrono",
"libc",
"once_cell",
+ "procfs",
"prometheus",
+ "rand 0.8.5",
+ "rand_distr",
+ "twox-hash",
"workspace_hack",
]
+[[package]]
+name = "migrations_internals"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f23f71580015254b020e856feac3df5878c2c7a8812297edd6c0a485ac9dada"
+dependencies = [
+ "serde",
+ "toml",
+]
+
+[[package]]
+name = "migrations_macros"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cce3325ac70e67bbab5bd837a31cae01f1a6db64e0e744a33cb03a543469ef08"
+dependencies = [
+ "migrations_internals",
+ "proc-macro2",
+ "quote",
+]
+
[[package]]
name = "mime"
version = "0.3.17"
@@ -2820,9 +2979,9 @@ dependencies = [
[[package]]
name = "mio"
-version = "0.8.10"
+version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log",
@@ -2866,6 +3025,19 @@ dependencies = [
"libc",
]
+[[package]]
+name = "nix"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+ "memoffset 0.7.1",
+ "pin-utils",
+]
+
[[package]]
name = "nix"
version = "0.27.1"
@@ -2988,6 +3160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
+ "libm",
]
[[package]]
@@ -3018,7 +3191,7 @@ dependencies = [
"base64 0.13.1",
"chrono",
"getrandom 0.2.11",
- "http",
+ "http 0.2.9",
"rand 0.8.5",
"serde",
"serde_json",
@@ -3081,7 +3254,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -3120,7 +3293,7 @@ checksum = "c7594ec0e11d8e33faf03530a4c49af7064ebba81c1480e01be67d90b356508b"
dependencies = [
"async-trait",
"bytes",
- "http",
+ "http 0.2.9",
"opentelemetry_api",
"reqwest",
]
@@ -3133,7 +3306,7 @@ checksum = "7e5e5a5c4135864099f3faafbe939eb4d7f9b80ebf68a8448da961b32a7c1275"
dependencies = [
"async-trait",
"futures-core",
- "http",
+ "http 0.2.9",
"opentelemetry-http",
"opentelemetry-proto",
"opentelemetry-semantic-conventions",
@@ -3309,6 +3482,7 @@ name = "pageserver"
version = "0.1.0"
dependencies = [
"anyhow",
+ "arc-swap",
"async-compression",
"async-stream",
"async-trait",
@@ -3318,7 +3492,6 @@ dependencies = [
"camino-tempfile",
"chrono",
"clap",
- "close_fds",
"const_format",
"consumption_metrics",
"crc32c",
@@ -3337,6 +3510,7 @@ dependencies = [
"humantime-serde",
"hyper",
"itertools",
+ "leaky-bucket",
"md5",
"metrics",
"nix 0.27.1",
@@ -3344,6 +3518,7 @@ dependencies = [
"num_cpus",
"once_cell",
"pageserver_api",
+ "pageserver_compaction",
"pin-project-lite",
"postgres",
"postgres-protocol",
@@ -3394,10 +3569,13 @@ dependencies = [
"bincode",
"byteorder",
"bytes",
+ "chrono",
"const_format",
"enum-map",
"hex",
+ "humantime",
"humantime-serde",
+ "itertools",
"postgres_ffi",
"rand 0.8.5",
"serde",
@@ -3431,6 +3609,53 @@ dependencies = [
"workspace_hack",
]
+[[package]]
+name = "pageserver_compaction"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-compression",
+ "async-stream",
+ "async-trait",
+ "byteorder",
+ "bytes",
+ "chrono",
+ "clap",
+ "const_format",
+ "consumption_metrics",
+ "criterion",
+ "crossbeam-utils",
+ "either",
+ "fail",
+ "flate2",
+ "futures",
+ "git-version",
+ "hex",
+ "hex-literal",
+ "humantime",
+ "humantime-serde",
+ "itertools",
+ "metrics",
+ "once_cell",
+ "pageserver_api",
+ "pin-project-lite",
+ "rand 0.8.5",
+ "smallvec",
+ "svg_fmt",
+ "sync_wrapper",
+ "thiserror",
+ "tokio",
+ "tokio-io-timeout",
+ "tokio-util",
+ "tracing",
+ "tracing-error",
+ "tracing-subscriber",
+ "url",
+ "utils",
+ "walkdir",
+ "workspace_hack",
+]
+
[[package]]
name = "parking"
version = "2.1.1"
@@ -3511,7 +3736,7 @@ dependencies = [
"parquet",
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -3549,16 +3774,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
-[[package]]
-name = "pem"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b13fe415cdf3c8e44518e18a7c95a13431d9bdf6d15367d82b23c377fdd441a"
-dependencies = [
- "base64 0.21.1",
- "serde",
-]
-
[[package]]
name = "pem"
version = "3.0.3"
@@ -3620,7 +3835,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -3641,8 +3856,8 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba"
dependencies = [
- "der",
- "spki",
+ "der 0.6.1",
+ "spki 0.6.0",
]
[[package]]
@@ -3741,14 +3956,14 @@ dependencies = [
"futures",
"once_cell",
"pq_proto",
- "rustls",
- "rustls-pemfile",
+ "rustls 0.22.2",
+ "rustls-pemfile 2.1.1",
"serde",
"thiserror",
"tokio",
"tokio-postgres",
"tokio-postgres-rustls",
- "tokio-rustls",
+ "tokio-rustls 0.25.0",
"tracing",
"workspace_hack",
]
@@ -3795,6 +4010,15 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+[[package]]
+name = "pq-sys"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31c0052426df997c0cbd30789eb44ca097e3541717a7b8fa36b1c464ee7edebd"
+dependencies = [
+ "vcpkg",
+]
+
[[package]]
name = "pq_proto"
version = "0.1.0"
@@ -3804,6 +4028,7 @@ dependencies = [
"pin-project-lite",
"postgres-protocol",
"rand 0.8.5",
+ "serde",
"thiserror",
"tokio",
"tracing",
@@ -3827,7 +4052,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b69d39aab54d069e7f2fe8cb970493e7834601ca2d8c65fd7bbd183578080d1"
dependencies = [
"proc-macro2",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -3838,9 +4063,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
-version = "1.0.66"
+version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
@@ -3853,6 +4078,8 @@ checksum = "b1de8dacb0873f77e6aefc6d71e044761fcc68060290f5b1089fcdf84626bb69"
dependencies = [
"bitflags 1.3.2",
"byteorder",
+ "chrono",
+ "flate2",
"hex",
"lazy_static",
"rustix 0.36.16",
@@ -3943,6 +4170,7 @@ dependencies = [
"clap",
"consumption_metrics",
"dashmap",
+ "env_logger",
"futures",
"git-version",
"hashbrown 0.13.2",
@@ -3955,6 +4183,7 @@ dependencies = [
"hyper-tungstenite",
"ipnet",
"itertools",
+ "lasso",
"md5",
"metrics",
"native-tls",
@@ -3971,6 +4200,7 @@ dependencies = [
"pq_proto",
"prometheus",
"rand 0.8.5",
+ "rand_distr",
"rcgen",
"redis",
"regex",
@@ -3982,28 +4212,31 @@ dependencies = [
"routerify",
"rstest",
"rustc-hash",
- "rustls",
- "rustls-pemfile",
+ "rustls 0.22.2",
+ "rustls-pemfile 2.1.1",
"scopeguard",
"serde",
"serde_json",
"sha2",
+ "smallvec",
"smol_str",
"socket2 0.5.5",
"sync_wrapper",
"task-local-extensions",
"thiserror",
- "tls-listener",
+ "tikv-jemalloc-ctl",
+ "tikv-jemallocator",
"tokio",
"tokio-postgres",
"tokio-postgres-rustls",
- "tokio-rustls",
+ "tokio-rustls 0.25.0",
"tokio-util",
"tracing",
"tracing-opentelemetry",
"tracing-subscriber",
"tracing-utils",
"url",
+ "urlencoding",
"utils",
"uuid",
"walkdir",
@@ -4024,13 +4257,24 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.32"
+version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
+[[package]]
+name = "r2d2"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93"
+dependencies = [
+ "log",
+ "parking_lot 0.12.1",
+ "scheduled-thread-pool",
+]
+
[[package]]
name = "rand"
version = "0.7.3"
@@ -4093,6 +4337,16 @@ dependencies = [
"getrandom 0.2.11",
]
+[[package]]
+name = "rand_distr"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
+dependencies = [
+ "num-traits",
+ "rand 0.8.5",
+]
+
[[package]]
name = "rand_hc"
version = "0.2.0"
@@ -4126,12 +4380,12 @@ dependencies = [
[[package]]
name = "rcgen"
-version = "0.11.1"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4954fbc00dcd4d8282c987710e50ba513d351400dbdd00e803a05172a90d8976"
+checksum = "48406db8ac1f3cbc7dcdb56ec355343817958a356ff430259bb07baf7607e1e1"
dependencies = [
- "pem 2.0.1",
- "ring 0.16.20",
+ "pem",
+ "ring 0.17.6",
"time",
"yasna",
]
@@ -4149,15 +4403,15 @@ dependencies = [
"itoa",
"percent-encoding",
"pin-project-lite",
- "rustls",
+ "rustls 0.21.9",
"rustls-native-certs",
- "rustls-pemfile",
+ "rustls-pemfile 1.0.2",
"rustls-webpki 0.101.7",
"ryu",
"sha1_smol",
"socket2 0.4.9",
"tokio",
- "tokio-rustls",
+ "tokio-rustls 0.24.0",
"tokio-util",
"url",
]
@@ -4212,6 +4466,12 @@ dependencies = [
"regex-syntax 0.8.2",
]
+[[package]]
+name = "regex-lite"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e"
+
[[package]]
name = "regex-syntax"
version = "0.6.29"
@@ -4251,6 +4511,7 @@ dependencies = [
"futures",
"futures-util",
"http-types",
+ "humantime",
"hyper",
"itertools",
"metrics",
@@ -4262,6 +4523,7 @@ dependencies = [
"serde_json",
"test-context",
"tokio",
+ "tokio-stream",
"tokio-util",
"toml_edit",
"tracing",
@@ -4281,7 +4543,7 @@ dependencies = [
"futures-core",
"futures-util",
"h2",
- "http",
+ "http 0.2.9",
"http-body",
"hyper",
"hyper-rustls",
@@ -4295,14 +4557,14 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
- "rustls",
- "rustls-pemfile",
+ "rustls 0.21.9",
+ "rustls-pemfile 1.0.2",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
- "tokio-rustls",
+ "tokio-rustls 0.24.0",
"tokio-util",
"tower-service",
"url",
@@ -4322,7 +4584,7 @@ checksum = "4531c89d50effe1fac90d095c8b133c20c5c714204feee0bfc3fd158e784209d"
dependencies = [
"anyhow",
"async-trait",
- "http",
+ "http 0.2.9",
"reqwest",
"serde",
"task-local-extensions",
@@ -4340,7 +4602,7 @@ dependencies = [
"chrono",
"futures",
"getrandom 0.2.11",
- "http",
+ "http 0.2.9",
"hyper",
"parking_lot 0.11.2",
"reqwest",
@@ -4427,7 +4689,7 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "496c1d3718081c45ba9c31fbfc07417900aa96f4070ff90dc29961836b7a9945"
dependencies = [
- "http",
+ "http 0.2.9",
"hyper",
"lazy_static",
"percent-encoding",
@@ -4468,7 +4730,7 @@ dependencies = [
"regex",
"relative-path",
"rustc_version",
- "syn 2.0.32",
+ "syn 2.0.52",
"unicode-ident",
]
@@ -4552,6 +4814,20 @@ dependencies = [
"sct",
]
+[[package]]
+name = "rustls"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41"
+dependencies = [
+ "log",
+ "ring 0.17.6",
+ "rustls-pki-types",
+ "rustls-webpki 0.102.2",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "rustls-native-certs"
version = "0.6.2"
@@ -4559,7 +4835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
dependencies = [
"openssl-probe",
- "rustls-pemfile",
+ "rustls-pemfile 1.0.2",
"schannel",
"security-framework",
]
@@ -4573,6 +4849,22 @@ dependencies = [
"base64 0.21.1",
]
+[[package]]
+name = "rustls-pemfile"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab"
+dependencies = [
+ "base64 0.21.1",
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8"
+
[[package]]
name = "rustls-webpki"
version = "0.100.2"
@@ -4593,6 +4885,17 @@ dependencies = [
"untrusted 0.9.0",
]
+[[package]]
+name = "rustls-webpki"
+version = "0.102.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610"
+dependencies = [
+ "ring 0.17.6",
+ "rustls-pki-types",
+ "untrusted 0.9.0",
+]
+
[[package]]
name = "rustversion"
version = "1.0.12"
@@ -4635,7 +4938,7 @@ dependencies = [
"serde_with",
"thiserror",
"tokio",
- "tokio-rustls",
+ "tokio-rustls 0.25.0",
"tokio-stream",
"tracing",
"tracing-appender",
@@ -4659,6 +4962,7 @@ dependencies = [
"clap",
"const_format",
"crc32c",
+ "desim",
"fail",
"fs2",
"futures",
@@ -4674,6 +4978,7 @@ dependencies = [
"postgres_backend",
"postgres_ffi",
"pq_proto",
+ "rand 0.8.5",
"regex",
"remote_storage",
"reqwest",
@@ -4694,8 +4999,10 @@ dependencies = [
"tokio-util",
"toml_edit",
"tracing",
+ "tracing-subscriber",
"url",
"utils",
+ "walproposer",
"workspace_hack",
]
@@ -4728,6 +5035,15 @@ dependencies = [
"windows-sys 0.42.0",
]
+[[package]]
+name = "scheduled-thread-pool"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19"
+dependencies = [
+ "parking_lot 0.12.1",
+]
+
[[package]]
name = "scopeguard"
version = "1.1.0"
@@ -4757,7 +5073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928"
dependencies = [
"base16ct",
- "der",
+ "der 0.6.1",
"generic-array",
"pkcs8",
"subtle",
@@ -4801,7 +5117,7 @@ checksum = "2e95efd0cefa32028cdb9766c96de71d96671072f9fb494dc9fb84c0ef93e52b"
dependencies = [
"httpdate",
"reqwest",
- "rustls",
+ "rustls 0.21.9",
"sentry-backtrace",
"sentry-contexts",
"sentry-core",
@@ -4923,7 +5239,7 @@ checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -5004,7 +5320,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -5090,6 +5406,15 @@ dependencies = [
"rand_core 0.6.4",
]
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "rand_core 0.6.4",
+]
+
[[package]]
name = "simple_asn1"
version = "0.6.2"
@@ -5174,7 +5499,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b"
dependencies = [
"base64ct",
- "der",
+ "der 0.6.1",
+]
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der 0.7.8",
]
[[package]]
@@ -5260,9 +5595,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "svg_fmt"
-version = "0.4.1"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2"
+checksum = "f83ba502a3265efb76efb89b0a2f7782ad6f2675015d4ce37e4b547dda42b499"
[[package]]
name = "syn"
@@ -5277,9 +5612,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.32"
+version = "2.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
+checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
dependencies = [
"proc-macro2",
"quote",
@@ -5394,22 +5729,22 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.47"
+version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
+checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.47"
+version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
+checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -5433,6 +5768,37 @@ dependencies = [
"ordered-float 2.10.1",
]
+[[package]]
+name = "tikv-jemalloc-ctl"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "619bfed27d807b54f7f776b9430d4f8060e66ee138a28632ca898584d462c31c"
+dependencies = [
+ "libc",
+ "paste",
+ "tikv-jemalloc-sys",
+]
+
+[[package]]
+name = "tikv-jemalloc-sys"
+version = "0.5.4+5.3.0-patched"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "tikv-jemallocator"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965fe0c26be5c56c94e38ba547249074803efd52adfb66de62107d95aab3eaca"
+dependencies = [
+ "libc",
+ "tikv-jemalloc-sys",
+]
+
[[package]]
name = "time"
version = "0.3.21"
@@ -5497,25 +5863,11 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
-[[package]]
-name = "tls-listener"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81294c017957a1a69794f506723519255879e15a870507faf45dfed288b763dd"
-dependencies = [
- "futures-util",
- "hyper",
- "pin-project-lite",
- "thiserror",
- "tokio",
- "tokio-rustls",
-]
-
[[package]]
name = "tokio"
-version = "1.34.0"
+version = "1.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
+checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
dependencies = [
"backtrace",
"bytes",
@@ -5532,9 +5884,10 @@ dependencies = [
[[package]]
name = "tokio-epoll-uring"
version = "0.1.0"
-source = "git+https://github.com/neondatabase/tokio-epoll-uring.git?branch=main#0dd3a2f8bf3239d34a19719ef1a74146c093126f"
+source = "git+https://github.com/neondatabase/tokio-epoll-uring.git?branch=main#868d2c42b5d54ca82fead6e8f2f233b69a540d3e"
dependencies = [
"futures",
+ "nix 0.26.4",
"once_cell",
"scopeguard",
"thiserror",
@@ -5562,7 +5915,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -5600,16 +5953,17 @@ dependencies = [
[[package]]
name = "tokio-postgres-rustls"
-version = "0.10.0"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd5831152cb0d3f79ef5523b357319ba154795d64c7078b2daa95a803b54057f"
+checksum = "0ea13f22eda7127c827983bdaf0d7fff9df21c8817bab02815ac277a21143677"
dependencies = [
"futures",
- "ring 0.16.20",
- "rustls",
+ "ring 0.17.6",
+ "rustls 0.22.2",
"tokio",
"tokio-postgres",
- "tokio-rustls",
+ "tokio-rustls 0.25.0",
+ "x509-certificate",
]
[[package]]
@@ -5618,7 +5972,18 @@ version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5"
dependencies = [
- "rustls",
+ "rustls 0.21.9",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
+dependencies = [
+ "rustls 0.22.2",
+ "rustls-pki-types",
"tokio",
]
@@ -5738,7 +6103,7 @@ dependencies = [
"futures-core",
"futures-util",
"h2",
- "http",
+ "http 0.2.9",
"http-body",
"hyper",
"hyper-timeout",
@@ -5746,9 +6111,9 @@ dependencies = [
"pin-project",
"prost",
"rustls-native-certs",
- "rustls-pemfile",
+ "rustls-pemfile 1.0.2",
"tokio",
- "tokio-rustls",
+ "tokio-rustls 0.24.0",
"tokio-stream",
"tower",
"tower-layer",
@@ -5844,7 +6209,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -5953,7 +6318,7 @@ dependencies = [
"byteorder",
"bytes",
"data-encoding",
- "http",
+ "http 0.2.9",
"httparse",
"log",
"rand 0.8.5",
@@ -6060,7 +6425,7 @@ dependencies = [
"base64 0.21.1",
"log",
"once_cell",
- "rustls",
+ "rustls 0.21.9",
"rustls-webpki 0.100.2",
"url",
"webpki-roots 0.23.1",
@@ -6069,8 +6434,9 @@ dependencies = [
[[package]]
name = "uring-common"
version = "0.1.0"
-source = "git+https://github.com/neondatabase/tokio-epoll-uring.git?branch=main#0dd3a2f8bf3239d34a19719ef1a74146c093126f"
+source = "git+https://github.com/neondatabase/tokio-epoll-uring.git?branch=main#868d2c42b5d54ca82fead6e8f2f233b69a540d3e"
dependencies = [
+ "bytes",
"io-uring",
"libc",
]
@@ -6127,6 +6493,7 @@ dependencies = [
"hex-literal",
"hyper",
"jsonwebtoken",
+ "leaky-bucket",
"metrics",
"nix 0.27.1",
"once_cell",
@@ -6301,7 +6668,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
"wasm-bindgen-shared",
]
@@ -6335,7 +6702,7 @@ checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -6646,6 +7013,7 @@ dependencies = [
"futures-sink",
"futures-util",
"getrandom 0.2.11",
+ "hashbrown 0.13.2",
"hashbrown 0.14.0",
"hex",
"hmac",
@@ -6667,19 +7035,18 @@ dependencies = [
"regex-automata 0.4.3",
"regex-syntax 0.8.2",
"reqwest",
- "ring 0.16.20",
- "rustls",
+ "rustls 0.21.9",
"scopeguard",
"serde",
"serde_json",
"smallvec",
"subtle",
"syn 1.0.109",
- "syn 2.0.32",
+ "syn 2.0.52",
"time",
"time-macros",
"tokio",
- "tokio-rustls",
+ "tokio-rustls 0.24.0",
"tokio-util",
"toml_datetime",
"toml_edit",
@@ -6690,11 +7057,31 @@ dependencies = [
"tungstenite",
"url",
"uuid",
+ "zeroize",
"zstd",
"zstd-safe",
"zstd-sys",
]
+[[package]]
+name = "x509-certificate"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66534846dec7a11d7c50a74b7cdb208b9a581cad890b7866430d438455847c85"
+dependencies = [
+ "bcder",
+ "bytes",
+ "chrono",
+ "der 0.7.8",
+ "hex",
+ "pem",
+ "ring 0.17.6",
+ "signature 2.2.0",
+ "spki 0.7.3",
+ "thiserror",
+ "zeroize",
+]
+
[[package]]
name = "x509-parser"
version = "0.15.0"
@@ -6753,7 +7140,7 @@ checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.32",
+ "syn 2.0.52",
]
[[package]]
@@ -6761,6 +7148,20 @@ name = "zeroize"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
[[package]]
name = "zstd"
diff --git a/Cargo.toml b/Cargo.toml
index 0254ea24e1..3f98c1946c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,7 @@ members = [
"control_plane",
"control_plane/attachment_service",
"pageserver",
+ "pageserver/compaction",
"pageserver/ctl",
"pageserver/client",
"pageserver/pagebench",
@@ -18,6 +19,7 @@ members = [
"libs/pageserver_api",
"libs/postgres_ffi",
"libs/safekeeper_api",
+ "libs/desim",
"libs/utils",
"libs/consumption_metrics",
"libs/postgres_backend",
@@ -48,11 +50,12 @@ azure_storage_blobs = "0.18"
flate2 = "1.0.26"
async-stream = "0.3"
async-trait = "0.1"
-aws-config = { version = "1.0", default-features = false, features=["rustls"] }
-aws-sdk-s3 = "1.0"
-aws-smithy-async = { version = "1.0", default-features = false, features=["rt-tokio"] }
-aws-smithy-types = "1.0"
-aws-credential-types = "1.0"
+aws-config = { version = "1.1.4", default-features = false, features=["rustls"] }
+aws-sdk-s3 = "1.14"
+aws-sdk-secretsmanager = { version = "1.14.0" }
+aws-smithy-async = { version = "1.1.4", default-features = false, features=["rt-tokio"] }
+aws-smithy-types = "1.1.4"
+aws-credential-types = "1.1.4"
axum = { version = "0.6.20", features = ["ws"] }
base64 = "0.13.0"
bincode = "1.3"
@@ -64,7 +67,6 @@ camino = "1.1.6"
cfg-if = "1.0.0"
chrono = { version = "0.4", default-features = false, features = ["clock"] }
clap = { version = "4.0", features = ["derive"] }
-close_fds = "0.3.2"
comfy-table = "6.1"
const_format = "0.2"
crc32c = "0.6"
@@ -80,7 +82,7 @@ futures-core = "0.3"
futures-util = "0.3"
git-version = "0.3"
hashbrown = "0.13"
-hashlink = "0.8.1"
+hashlink = "0.8.4"
hdrhistogram = "7.5.2"
hex = "0.4"
hex-literal = "0.4"
@@ -95,6 +97,8 @@ inotify = "0.10.2"
ipnet = "2.9.0"
itertools = "0.10"
jsonwebtoken = "9"
+lasso = "0.7"
+leaky-bucket = "1.0.1"
libc = "0.2"
md5 = "0.7.0"
memoffset = "0.8"
@@ -112,6 +116,7 @@ parquet = { version = "49.0.0", default-features = false, features = ["zstd"] }
parquet_derive = "49.0.0"
pbkdf2 = { version = "0.12.1", features = ["simple", "std"] }
pin-project-lite = "0.2"
+procfs = "0.14"
prometheus = {version = "0.13", default_features=false, features = ["process"]} # removes protobuf dependency
prost = "0.11"
rand = "0.8"
@@ -124,8 +129,8 @@ reqwest-retry = "0.2.2"
routerify = "3"
rpds = "0.13"
rustc-hash = "1.1.0"
-rustls = "0.21"
-rustls-pemfile = "1"
+rustls = "0.22"
+rustls-pemfile = "2"
rustls-split = "0.3"
scopeguard = "1.1"
sysinfo = "0.29.2"
@@ -149,12 +154,13 @@ tar = "0.4"
task-local-extensions = "0.1.4"
test-context = "0.1"
thiserror = "1.0"
-tls-listener = { version = "0.7", features = ["rustls", "hyper-h1"] }
+tikv-jemallocator = "0.5"
+tikv-jemalloc-ctl = "0.5"
tokio = { version = "1.17", features = ["macros"] }
tokio-epoll-uring = { git = "https://github.com/neondatabase/tokio-epoll-uring.git" , branch = "main" }
tokio-io-timeout = "1.2.0"
-tokio-postgres-rustls = "0.10.0"
-tokio-rustls = "0.24"
+tokio-postgres-rustls = "0.11.0"
+tokio-rustls = "0.25"
tokio-stream = "0.1"
tokio-tar = "0.3"
tokio-test = "0.4.3"
@@ -166,7 +172,9 @@ tracing = "0.1"
tracing-error = "0.2.0"
tracing-opentelemetry = "0.20.0"
tracing-subscriber = { version = "0.3", default_features = false, features = ["smallvec", "fmt", "tracing-log", "std", "env-filter", "json"] }
+twox-hash = { version = "1.6.3", default-features = false }
url = "2.2"
+urlencoding = "2.1"
uuid = { version = "1.6.1", features = ["v4", "v7", "serde"] }
walkdir = "2.3.2"
webpki-roots = "0.25"
@@ -192,12 +200,14 @@ consumption_metrics = { version = "0.1", path = "./libs/consumption_metrics/" }
metrics = { version = "0.1", path = "./libs/metrics/" }
pageserver_api = { version = "0.1", path = "./libs/pageserver_api/" }
pageserver_client = { path = "./pageserver/client" }
+pageserver_compaction = { version = "0.1", path = "./pageserver/compaction/" }
postgres_backend = { version = "0.1", path = "./libs/postgres_backend/" }
postgres_connection = { version = "0.1", path = "./libs/postgres_connection/" }
postgres_ffi = { version = "0.1", path = "./libs/postgres_ffi/" }
pq_proto = { version = "0.1", path = "./libs/pq_proto/" }
remote_storage = { version = "0.1", path = "./libs/remote_storage/" }
safekeeper_api = { version = "0.1", path = "./libs/safekeeper_api" }
+desim = { version = "0.1", path = "./libs/desim" }
storage_broker = { version = "0.1", path = "./storage_broker/" } # Note: main broker code is inside the binary crate, so linking with the library shouldn't be heavy.
tenant_size_model = { version = "0.1", path = "./libs/tenant_size_model/" }
tracing-utils = { version = "0.1", path = "./libs/tracing-utils/" }
@@ -210,7 +220,7 @@ workspace_hack = { version = "0.1", path = "./workspace_hack/" }
## Build dependencies
criterion = "0.5.1"
-rcgen = "0.11"
+rcgen = "0.12"
rstest = "0.18"
camino-tempfile = "1.0.2"
tonic-build = "0.9"
diff --git a/Dockerfile b/Dockerfile
index 5d5fde4f14..5f82df3e18 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -47,12 +47,13 @@ COPY --chown=nonroot . .
# Show build caching stats to check if it was used in the end.
# Has to be the part of the same RUN since cachepot daemon is killed in the end of this RUN, losing the compilation stats.
RUN set -e \
- && mold -run cargo build \
+ && RUSTFLAGS="-Clinker=clang -Clink-arg=-fuse-ld=mold -Clink-arg=-Wl,--no-rosegment" cargo build \
--bin pg_sni_router \
--bin pageserver \
--bin pagectl \
--bin safekeeper \
--bin storage_broker \
+ --bin storage_controller \
--bin proxy \
--bin neon_local \
--locked --release \
@@ -80,6 +81,7 @@ COPY --from=build --chown=neon:neon /home/nonroot/target/release/pageserver
COPY --from=build --chown=neon:neon /home/nonroot/target/release/pagectl /usr/local/bin
COPY --from=build --chown=neon:neon /home/nonroot/target/release/safekeeper /usr/local/bin
COPY --from=build --chown=neon:neon /home/nonroot/target/release/storage_broker /usr/local/bin
+COPY --from=build --chown=neon:neon /home/nonroot/target/release/storage_controller /usr/local/bin
COPY --from=build --chown=neon:neon /home/nonroot/target/release/proxy /usr/local/bin
COPY --from=build --chown=neon:neon /home/nonroot/target/release/neon_local /usr/local/bin
@@ -98,6 +100,11 @@ RUN mkdir -p /data/.neon/ && chown -R neon:neon /data/.neon/ \
-c "listen_pg_addr='0.0.0.0:6400'" \
-c "listen_http_addr='0.0.0.0:9898'"
+# When running a binary that links with libpq, default to using our most recent postgres version. Binaries
+# that want a particular postgres version will select it explicitly: this is just a default.
+ENV LD_LIBRARY_PATH /usr/local/v16/lib
+
+
VOLUME ["/data"]
USER neon
EXPOSE 6400
diff --git a/Dockerfile.buildtools b/Dockerfile.build-tools
similarity index 99%
rename from Dockerfile.buildtools
rename to Dockerfile.build-tools
index 213aed1679..3a452fec32 100644
--- a/Dockerfile.buildtools
+++ b/Dockerfile.build-tools
@@ -111,7 +111,7 @@ USER nonroot:nonroot
WORKDIR /home/nonroot
# Python
-ENV PYTHON_VERSION=3.9.2 \
+ENV PYTHON_VERSION=3.9.18 \
PYENV_ROOT=/home/nonroot/.pyenv \
PATH=/home/nonroot/.pyenv/shims:/home/nonroot/.pyenv/bin:/home/nonroot/.poetry/bin:$PATH
RUN set -e \
@@ -135,7 +135,7 @@ WORKDIR /home/nonroot
# Rust
# Please keep the version of llvm (installed above) in sync with rust llvm (`rustc --version --verbose | grep LLVM`)
-ENV RUSTC_VERSION=1.75.0
+ENV RUSTC_VERSION=1.76.0
ENV RUSTUP_HOME="/home/nonroot/.rustup"
ENV PATH="/home/nonroot/.cargo/bin:${PATH}"
RUN curl -sSO https://static.rust-lang.org/rustup/dist/$(uname -m)-unknown-linux-gnu/rustup-init && whoami && \
diff --git a/Dockerfile.compute-node b/Dockerfile.compute-node
index 299c4097e8..c73b9ce5c9 100644
--- a/Dockerfile.compute-node
+++ b/Dockerfile.compute-node
@@ -520,8 +520,7 @@ RUN apt-get update && \
libboost-regex1.74-dev \
libboost-serialization1.74-dev \
libboost-system1.74-dev \
- libeigen3-dev \
- libfreetype6-dev
+ libeigen3-dev
ENV PATH "/usr/local/pgsql/bin/:/usr/local/pgsql/:$PATH"
RUN wget https://github.com/rdkit/rdkit/archive/refs/tags/Release_2023_03_3.tar.gz -O rdkit.tar.gz && \
@@ -547,6 +546,7 @@ RUN wget https://github.com/rdkit/rdkit/archive/refs/tags/Release_2023_03_3.tar.
-D PostgreSQL_LIBRARY_DIR=`pg_config --libdir` \
-D RDK_INSTALL_INTREE=OFF \
-D RDK_INSTALL_COMIC_FONTS=OFF \
+ -D RDK_BUILD_FREETYPE_SUPPORT=OFF \
-D CMAKE_BUILD_TYPE=Release \
. && \
make -j $(getconf _NPROCESSORS_ONLN) && \
@@ -639,8 +639,8 @@ FROM build-deps AS pg-anon-pg-build
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
ENV PATH "/usr/local/pgsql/bin/:$PATH"
-RUN wget https://gitlab.com/dalibo/postgresql_anonymizer/-/archive/1.1.0/postgresql_anonymizer-1.1.0.tar.gz -O pg_anon.tar.gz && \
- echo "08b09d2ff9b962f96c60db7e6f8e79cf7253eb8772516998fc35ece08633d3ad pg_anon.tar.gz" | sha256sum --check && \
+RUN wget https://github.com/neondatabase/postgresql_anonymizer/archive/refs/tags/neon_1.1.1.tar.gz -O pg_anon.tar.gz && \
+ echo "321ea8d5c1648880aafde850a2c576e4a9e7b9933a34ce272efc839328999fa9 pg_anon.tar.gz" | sha256sum --check && \
mkdir pg_anon-src && cd pg_anon-src && tar xvzf ../pg_anon.tar.gz --strip-components=1 -C . && \
find /usr/local/pgsql -type f | sed 's|^/usr/local/pgsql/||' > /before.txt &&\
make -j $(getconf _NPROCESSORS_ONLN) install PG_CONFIG=/usr/local/pgsql/bin/pg_config && \
@@ -769,6 +769,40 @@ RUN wget https://github.com/eulerto/wal2json/archive/refs/tags/wal2json_2_5.tar.
make -j $(getconf _NPROCESSORS_ONLN) && \
make -j $(getconf _NPROCESSORS_ONLN) install
+#########################################################################################
+#
+# Layer "pg_ivm"
+# compile pg_ivm extension
+#
+#########################################################################################
+FROM build-deps AS pg-ivm-build
+COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
+
+ENV PATH "/usr/local/pgsql/bin/:$PATH"
+RUN wget https://github.com/sraoss/pg_ivm/archive/refs/tags/v1.7.tar.gz -O pg_ivm.tar.gz && \
+ echo "ebfde04f99203c7be4b0e873f91104090e2e83e5429c32ac242d00f334224d5e pg_ivm.tar.gz" | sha256sum --check && \
+ mkdir pg_ivm-src && cd pg_ivm-src && tar xvzf ../pg_ivm.tar.gz --strip-components=1 -C . && \
+ make -j $(getconf _NPROCESSORS_ONLN) && \
+ make -j $(getconf _NPROCESSORS_ONLN) install && \
+ echo 'trusted = true' >> /usr/local/pgsql/share/extension/pg_ivm.control
+
+#########################################################################################
+#
+# Layer "pg_partman"
+# compile pg_partman extension
+#
+#########################################################################################
+FROM build-deps AS pg-partman-build
+COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
+
+ENV PATH "/usr/local/pgsql/bin/:$PATH"
+RUN wget https://github.com/pgpartman/pg_partman/archive/refs/tags/v5.0.1.tar.gz -O pg_partman.tar.gz && \
+ echo "75b541733a9659a6c90dbd40fccb904a630a32880a6e3044d0c4c5f4c8a65525 pg_partman.tar.gz" | sha256sum --check && \
+ mkdir pg_partman-src && cd pg_partman-src && tar xvzf ../pg_partman.tar.gz --strip-components=1 -C . && \
+ make -j $(getconf _NPROCESSORS_ONLN) && \
+ make -j $(getconf _NPROCESSORS_ONLN) install && \
+ echo 'trusted = true' >> /usr/local/pgsql/share/extension/pg_partman.control
+
#########################################################################################
#
# Layer "neon-pg-ext-build"
@@ -809,6 +843,9 @@ COPY --from=pg-roaringbitmap-pg-build /usr/local/pgsql/ /usr/local/pgsql/
COPY --from=pg-semver-pg-build /usr/local/pgsql/ /usr/local/pgsql/
COPY --from=pg-embedding-pg-build /usr/local/pgsql/ /usr/local/pgsql/
COPY --from=wal2json-pg-build /usr/local/pgsql /usr/local/pgsql
+COPY --from=pg-anon-pg-build /usr/local/pgsql/ /usr/local/pgsql/
+COPY --from=pg-ivm-build /usr/local/pgsql/ /usr/local/pgsql/
+COPY --from=pg-partman-build /usr/local/pgsql/ /usr/local/pgsql/
COPY pgxn/ pgxn/
RUN make -j $(getconf _NPROCESSORS_ONLN) \
@@ -819,6 +856,10 @@ RUN make -j $(getconf _NPROCESSORS_ONLN) \
PG_CONFIG=/usr/local/pgsql/bin/pg_config \
-C pgxn/neon_utils \
-s install && \
+ make -j $(getconf _NPROCESSORS_ONLN) \
+ PG_CONFIG=/usr/local/pgsql/bin/pg_config \
+ -C pgxn/neon_test_utils \
+ -s install && \
make -j $(getconf _NPROCESSORS_ONLN) \
PG_CONFIG=/usr/local/pgsql/bin/pg_config \
-C pgxn/neon_rmgr \
@@ -850,7 +891,17 @@ ENV BUILD_TAG=$BUILD_TAG
USER nonroot
# Copy entire project to get Cargo.* files with proper dependencies for the whole project
COPY --chown=nonroot . .
-RUN cd compute_tools && cargo build --locked --profile release-line-debug-size-lto
+RUN cd compute_tools && mold -run cargo build --locked --profile release-line-debug-size-lto
+
+#########################################################################################
+#
+# Final compute-tools image
+#
+#########################################################################################
+
+FROM debian:bullseye-slim AS compute-tools-image
+
+COPY --from=compute-tools /home/nonroot/target/release-line-debug-size-lto/compute_ctl /usr/local/bin/compute_ctl
#########################################################################################
#
@@ -901,7 +952,7 @@ COPY --from=compute-tools --chown=postgres /home/nonroot/target/release-line-deb
# libgeos, libgdal, libsfcgal1, libproj and libprotobuf-c1 for PostGIS
# libxml2, libxslt1.1 for xml2
# libzstd1 for zstd
-# libboost*, libfreetype6, and zlib1g for rdkit
+# libboost* for rdkit
# ca-certificates for communicating with s3 by compute_ctl
RUN apt update && \
apt install --no-install-recommends -y \
@@ -914,7 +965,6 @@ RUN apt update && \
libboost-serialization1.74.0 \
libboost-system1.74.0 \
libossp-uuid16 \
- libfreetype6 \
libgeos-c1v5 \
libgdal28 \
libproj19 \
@@ -926,7 +976,6 @@ RUN apt update && \
libcurl4-openssl-dev \
locales \
procps \
- zlib1g \
ca-certificates && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
diff --git a/Dockerfile.compute-tools b/Dockerfile.compute-tools
deleted file mode 100644
index cc305cc556..0000000000
--- a/Dockerfile.compute-tools
+++ /dev/null
@@ -1,32 +0,0 @@
-# First transient image to build compute_tools binaries
-# NB: keep in sync with rust image version in .github/workflows/build_and_test.yml
-ARG REPOSITORY=neondatabase
-ARG IMAGE=build-tools
-ARG TAG=pinned
-ARG BUILD_TAG
-
-FROM $REPOSITORY/$IMAGE:$TAG AS rust-build
-WORKDIR /home/nonroot
-
-# Enable https://github.com/paritytech/cachepot to cache Rust crates' compilation results in Docker builds.
-# Set up cachepot to use an AWS S3 bucket for cache results, to reuse it between `docker build` invocations.
-# cachepot falls back to local filesystem if S3 is misconfigured, not failing the build.
-ARG RUSTC_WRAPPER=cachepot
-ENV AWS_REGION=eu-central-1
-ENV CACHEPOT_S3_KEY_PREFIX=cachepot
-ARG CACHEPOT_BUCKET=neon-github-dev
-#ARG AWS_ACCESS_KEY_ID
-#ARG AWS_SECRET_ACCESS_KEY
-ARG BUILD_TAG
-ENV BUILD_TAG=$BUILD_TAG
-
-COPY . .
-
-RUN set -e \
- && mold -run cargo build -p compute_tools --locked --release \
- && cachepot -s
-
-# Final image that only has one binary
-FROM debian:bullseye-slim
-
-COPY --from=rust-build /home/nonroot/target/release/compute_ctl /usr/local/bin/compute_ctl
diff --git a/Makefile b/Makefile
index 004ca3fbcf..f13f080f1a 100644
--- a/Makefile
+++ b/Makefile
@@ -51,6 +51,8 @@ CARGO_BUILD_FLAGS += $(filter -j1,$(MAKEFLAGS))
CARGO_CMD_PREFIX += $(if $(filter n,$(MAKEFLAGS)),,+)
# Force cargo not to print progress bar
CARGO_CMD_PREFIX += CARGO_TERM_PROGRESS_WHEN=never CI=1
+# Set PQ_LIB_DIR to make sure `storage_controller` get linked with bundled libpq (through diesel)
+CARGO_CMD_PREFIX += PQ_LIB_DIR=$(POSTGRES_INSTALL_DIR)/v16/lib
#
# Top level Makefile to build Neon and PostgreSQL
@@ -157,8 +159,8 @@ neon-pg-ext-%: postgres-%
-C $(POSTGRES_INSTALL_DIR)/build/neon-utils-$* \
-f $(ROOT_PROJECT_DIR)/pgxn/neon_utils/Makefile install
-.PHONY: neon-pg-ext-clean-%
-neon-pg-ext-clean-%:
+.PHONY: neon-pg-clean-ext-%
+neon-pg-clean-ext-%:
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config \
-C $(POSTGRES_INSTALL_DIR)/build/neon-$* \
-f $(ROOT_PROJECT_DIR)/pgxn/neon/Makefile clean
@@ -174,10 +176,10 @@ neon-pg-ext-clean-%:
# Build walproposer as a static library. walproposer source code is located
# in the pgxn/neon directory.
-#
+#
# We also need to include libpgport.a and libpgcommon.a, because walproposer
# uses some functions from those libraries.
-#
+#
# Some object files are removed from libpgport.a and libpgcommon.a because
# they depend on openssl and other libraries that are not included in our
# Rust build.
@@ -214,11 +216,11 @@ neon-pg-ext: \
neon-pg-ext-v15 \
neon-pg-ext-v16
-.PHONY: neon-pg-ext-clean
-neon-pg-ext-clean: \
- neon-pg-ext-clean-v14 \
- neon-pg-ext-clean-v15 \
- neon-pg-ext-clean-v16
+.PHONY: neon-pg-clean-ext
+neon-pg-clean-ext: \
+ neon-pg-clean-ext-v14 \
+ neon-pg-clean-ext-v15 \
+ neon-pg-clean-ext-v16
# shorthand to build all Postgres versions
.PHONY: postgres
@@ -247,7 +249,7 @@ postgres-check: \
# This doesn't remove the effects of 'configure'.
.PHONY: clean
-clean: postgres-clean neon-pg-ext-clean
+clean: postgres-clean neon-pg-clean-ext
$(CARGO_CMD_PREFIX) cargo clean
# This removes everything
diff --git a/NOTICE b/NOTICE
index c13dc2f0b3..52fc751c41 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,5 +1,5 @@
Neon
-Copyright 2022 Neon Inc.
+Copyright 2022 - 2024 Neon Inc.
The PostgreSQL submodules in vendor/ are licensed under the PostgreSQL license.
See vendor/postgres-vX/COPYRIGHT for details.
diff --git a/README.md b/README.md
index 98af1edee6..c44ae695d6 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
Neon is a serverless open-source alternative to AWS Aurora Postgres. It separates storage and compute and substitutes the PostgreSQL storage layer by redistributing data across a cluster of nodes.
## Quick start
-Try the [Neon Free Tier](https://neon.tech/docs/introduction/technical-preview-free-tier/) to create a serverless Postgres instance. Then connect to it with your preferred Postgres client (psql, dbeaver, etc) or use the online [SQL Editor](https://neon.tech/docs/get-started-with-neon/query-with-neon-sql-editor/). See [Connect from any application](https://neon.tech/docs/connect/connect-from-any-app/) for connection instructions.
+Try the [Neon Free Tier](https://neon.tech/github) to create a serverless Postgres instance. Then connect to it with your preferred Postgres client (psql, dbeaver, etc) or use the online [SQL Editor](https://neon.tech/docs/get-started-with-neon/query-with-neon-sql-editor/). See [Connect from any application](https://neon.tech/docs/connect/connect-from-any-app/) for connection instructions.
Alternatively, compile and run the project [locally](#running-local-installation).
@@ -14,8 +14,8 @@ Alternatively, compile and run the project [locally](#running-local-installation
A Neon installation consists of compute nodes and the Neon storage engine. Compute nodes are stateless PostgreSQL nodes backed by the Neon storage engine.
The Neon storage engine consists of two major components:
-- Pageserver. Scalable storage backend for the compute nodes.
-- Safekeepers. The safekeepers form a redundant WAL service that received WAL from the compute node, and stores it durably until it has been processed by the pageserver and uploaded to cloud storage.
+- Pageserver: Scalable storage backend for the compute nodes.
+- Safekeepers: The safekeepers form a redundant WAL service that received WAL from the compute node, and stores it durably until it has been processed by the pageserver and uploaded to cloud storage.
See developer documentation in [SUMMARY.md](/docs/SUMMARY.md) for more information.
@@ -81,9 +81,9 @@ The project uses [rust toolchain file](./rust-toolchain.toml) to define the vers
This file is automatically picked up by [`rustup`](https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file) that installs (if absent) and uses the toolchain version pinned in the file.
-rustup users who want to build with another toolchain can use [`rustup override`](https://rust-lang.github.io/rustup/overrides.html#directory-overrides) command to set a specific toolchain for the project's directory.
+rustup users who want to build with another toolchain can use the [`rustup override`](https://rust-lang.github.io/rustup/overrides.html#directory-overrides) command to set a specific toolchain for the project's directory.
-non-rustup users most probably are not getting the same toolchain automatically from the file, so are responsible to manually verify their toolchain matches the version in the file.
+non-rustup users most probably are not getting the same toolchain automatically from the file, so are responsible to manually verify that their toolchain matches the version in the file.
Newer rustc versions most probably will work fine, yet older ones might not be supported due to some new features used by the project or the crates.
#### Building on Linux
@@ -124,7 +124,7 @@ make -j`sysctl -n hw.logicalcpu` -s
To run the `psql` client, install the `postgresql-client` package or modify `PATH` and `LD_LIBRARY_PATH` to include `pg_install/bin` and `pg_install/lib`, respectively.
To run the integration tests or Python scripts (not required to use the code), install
-Python (3.9 or higher), and install python3 packages using `./scripts/pysync` (requires [poetry>=1.3](https://python-poetry.org/)) in the project directory.
+Python (3.9 or higher), and install the python3 packages using `./scripts/pysync` (requires [poetry>=1.3](https://python-poetry.org/)) in the project directory.
#### Running neon database
@@ -166,7 +166,7 @@ Starting postgres at 'postgresql://cloud_admin@127.0.0.1:55432/postgres'
2. Now, it is possible to connect to postgres and run some queries:
```text
-> psql -p55432 -h 127.0.0.1 -U cloud_admin postgres
+> psql -p 55432 -h 127.0.0.1 -U cloud_admin postgres
postgres=# CREATE TABLE t(key int primary key, value text);
CREATE TABLE
postgres=# insert into t values(1,1);
@@ -205,7 +205,7 @@ Starting postgres at 'postgresql://cloud_admin@127.0.0.1:55434/postgres'
# this new postgres instance will have all the data from 'main' postgres,
# but all modifications would not affect data in original postgres
-> psql -p55434 -h 127.0.0.1 -U cloud_admin postgres
+> psql -p 55434 -h 127.0.0.1 -U cloud_admin postgres
postgres=# select * from t;
key | value
-----+-------
@@ -216,7 +216,7 @@ postgres=# insert into t values(2,2);
INSERT 0 1
# check that the new change doesn't affect the 'main' postgres
-> psql -p55432 -h 127.0.0.1 -U cloud_admin postgres
+> psql -p 55432 -h 127.0.0.1 -U cloud_admin postgres
postgres=# select * from t;
key | value
-----+-------
@@ -224,12 +224,18 @@ postgres=# select * from t;
(1 row)
```
-4. If you want to run tests afterward (see below), you must stop all the running of the pageserver, safekeeper, and postgres instances
+4. If you want to run tests afterwards (see below), you must stop all the running pageserver, safekeeper, and postgres instances
you have just started. You can terminate them all with one command:
```sh
> cargo neon stop
```
+More advanced usages can be found at [Control Plane and Neon Local](./control_plane/README.md).
+
+#### Handling build failures
+
+If you encounter errors during setting up the initial tenant, it's best to stop everything (`cargo neon stop`) and remove the `.neon` directory. Then fix the problems, and start the setup again.
+
## Running tests
Ensure your dependencies are installed as described [here](https://github.com/neondatabase/neon#dependency-installation-notes).
@@ -243,12 +249,28 @@ CARGO_BUILD_FLAGS="--features=testing" make
```
By default, this runs both debug and release modes, and all supported postgres versions. When
-testing locally, it is convenient to run just run one set of permutations, like this:
+testing locally, it is convenient to run just one set of permutations, like this:
```sh
DEFAULT_PG_VERSION=15 BUILD_TYPE=release ./scripts/pytest
```
+## Flamegraphs
+
+You may find yourself in need of flamegraphs for software in this repository.
+You can use [`flamegraph-rs`](https://github.com/flamegraph-rs/flamegraph) or the original [`flamegraph.pl`](https://github.com/brendangregg/FlameGraph). Your choice!
+
+>[!IMPORTANT]
+> If you're using `lld` or `mold`, you need the `--no-rosegment` linker argument.
+> It's a [general thing with Rust / lld / mold](https://crbug.com/919499#c16), not specific to this repository.
+> See [this PR for further instructions](https://github.com/neondatabase/neon/pull/6764).
+
+## Cleanup
+
+For cleaning up the source tree from build artifacts, run `make clean` in the source directory.
+
+For removing every artifact from build and configure steps, run `make distclean`, and also consider removing the cargo binaries in the `target` directory, as well as the database in the `.neon` directory. Note that removing the `.neon` directory will remove your database, with all data in it. You have been warned!
+
## Documentation
[docs](/docs) Contains a top-level overview of all available markdown documentation.
diff --git a/clippy.toml b/clippy.toml
index d788afc84d..5f7dc66152 100644
--- a/clippy.toml
+++ b/clippy.toml
@@ -3,3 +3,10 @@ disallowed-methods = [
# Allow this for now, to deny it later once we stop using Handle::block_on completely
# "tokio::runtime::Handle::block_on",
]
+
+disallowed-macros = [
+ # use std::pin::pin
+ "futures::pin_mut",
+ # cannot disallow this, because clippy finds used from tokio macros
+ #"tokio::pin",
+]
diff --git a/compute_tools/src/bin/compute_ctl.rs b/compute_tools/src/bin/compute_ctl.rs
index a7e10d0aee..117919786e 100644
--- a/compute_tools/src/bin/compute_ctl.rs
+++ b/compute_tools/src/bin/compute_ctl.rs
@@ -45,7 +45,6 @@ use std::{thread, time::Duration};
use anyhow::{Context, Result};
use chrono::Utc;
use clap::Arg;
-use nix::sys::signal::{kill, Signal};
use signal_hook::consts::{SIGQUIT, SIGTERM};
use signal_hook::{consts::SIGINT, iterator::Signals};
use tracing::{error, info};
@@ -53,7 +52,9 @@ use url::Url;
use compute_api::responses::ComputeStatus;
-use compute_tools::compute::{ComputeNode, ComputeState, ParsedSpec, PG_PID, SYNC_SAFEKEEPERS_PID};
+use compute_tools::compute::{
+ forward_termination_signal, ComputeNode, ComputeState, ParsedSpec, PG_PID,
+};
use compute_tools::configurator::launch_configurator;
use compute_tools::extension_server::get_pg_version;
use compute_tools::http::api::launch_http_server;
@@ -394,6 +395,15 @@ fn main() -> Result<()> {
info!("synced safekeepers at lsn {lsn}");
}
+ let mut state = compute.state.lock().unwrap();
+ if state.status == ComputeStatus::TerminationPending {
+ state.status = ComputeStatus::Terminated;
+ compute.state_changed.notify_all();
+ // we were asked to terminate gracefully, don't exit to avoid restart
+ delay_exit = true
+ }
+ drop(state);
+
if let Err(err) = compute.check_for_core_dumps() {
error!("error while checking for core dumps: {err:?}");
}
@@ -523,16 +533,7 @@ fn cli() -> clap::Command {
/// wait for termination which would be easy then.
fn handle_exit_signal(sig: i32) {
info!("received {sig} termination signal");
- let ss_pid = SYNC_SAFEKEEPERS_PID.load(Ordering::SeqCst);
- if ss_pid != 0 {
- let ss_pid = nix::unistd::Pid::from_raw(ss_pid as i32);
- kill(ss_pid, Signal::SIGTERM).ok();
- }
- let pg_pid = PG_PID.load(Ordering::SeqCst);
- if pg_pid != 0 {
- let pg_pid = nix::unistd::Pid::from_raw(pg_pid as i32);
- kill(pg_pid, Signal::SIGTERM).ok();
- }
+ forward_termination_signal();
exit(1);
}
diff --git a/compute_tools/src/compute.rs b/compute_tools/src/compute.rs
index 07e0abe6ff..0fa315682d 100644
--- a/compute_tools/src/compute.rs
+++ b/compute_tools/src/compute.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::env;
use std::fs;
use std::io::BufRead;
-use std::os::unix::fs::PermissionsExt;
+use std::os::unix::fs::{symlink, PermissionsExt};
use std::path::Path;
use std::process::{Command, Stdio};
use std::str::FromStr;
@@ -17,9 +17,9 @@ use chrono::{DateTime, Utc};
use futures::future::join_all;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
+use nix::unistd::Pid;
+use postgres::error::SqlState;
use postgres::{Client, NoTls};
-use tokio;
-use tokio_postgres;
use tracing::{debug, error, info, instrument, warn};
use utils::id::{TenantId, TimelineId};
use utils::lsn::Lsn;
@@ -28,6 +28,8 @@ use compute_api::responses::{ComputeMetrics, ComputeStatus};
use compute_api::spec::{ComputeFeature, ComputeMode, ComputeSpec};
use utils::measured_stream::MeasuredReader;
+use nix::sys::signal::{kill, Signal};
+
use remote_storage::{DownloadError, RemotePath};
use crate::checker::create_availability_check_data;
@@ -207,6 +209,7 @@ fn maybe_cgexec(cmd: &str) -> Command {
/// Create special neon_superuser role, that's a slightly nerfed version of a real superuser
/// that we give to customers
+#[instrument(skip_all)]
fn create_neon_superuser(spec: &ComputeSpec, client: &mut Client) -> Result<()> {
let roles = spec
.cluster
@@ -319,11 +322,12 @@ impl ComputeNode {
// Get basebackup from the libpq connection to pageserver using `connstr` and
// unarchive it to `pgdata` directory overriding all its previous content.
#[instrument(skip_all, fields(%lsn))]
- fn get_basebackup(&self, compute_state: &ComputeState, lsn: Lsn) -> Result<()> {
+ fn try_get_basebackup(&self, compute_state: &ComputeState, lsn: Lsn) -> Result<()> {
let spec = compute_state.pspec.as_ref().expect("spec must be set");
let start_time = Instant::now();
- let mut config = postgres::Config::from_str(&spec.pageserver_connstr)?;
+ let shard0_connstr = spec.pageserver_connstr.split(',').next().unwrap();
+ let mut config = postgres::Config::from_str(shard0_connstr)?;
// Use the storage auth token from the config file, if given.
// Note: this overrides any password set in the connection string.
@@ -390,6 +394,34 @@ impl ComputeNode {
Ok(())
}
+ // Gets the basebackup in a retry loop
+ #[instrument(skip_all, fields(%lsn))]
+ pub fn get_basebackup(&self, compute_state: &ComputeState, lsn: Lsn) -> Result<()> {
+ let mut retry_period_ms = 500.0;
+ let mut attempts = 0;
+ let max_attempts = 10;
+ loop {
+ let result = self.try_get_basebackup(compute_state, lsn);
+ match result {
+ Ok(_) => {
+ return result;
+ }
+ Err(ref e) if attempts < max_attempts => {
+ warn!(
+ "Failed to get basebackup: {} (attempt {}/{})",
+ e, attempts, max_attempts
+ );
+ std::thread::sleep(std::time::Duration::from_millis(retry_period_ms as u64));
+ retry_period_ms *= 1.5;
+ }
+ Err(_) => {
+ return result;
+ }
+ }
+ attempts += 1;
+ }
+ }
+
pub async fn check_safekeepers_synced_async(
&self,
compute_state: &ComputeState,
@@ -605,6 +637,48 @@ impl ComputeNode {
// Update pg_hba.conf received with basebackup.
update_pg_hba(pgdata_path)?;
+ // Place pg_dynshmem under /dev/shm. This allows us to use
+ // 'dynamic_shared_memory_type = mmap' so that the files are placed in
+ // /dev/shm, similar to how 'dynamic_shared_memory_type = posix' works.
+ //
+ // Why on earth don't we just stick to the 'posix' default, you might
+ // ask. It turns out that making large allocations with 'posix' doesn't
+ // work very well with autoscaling. The behavior we want is that:
+ //
+ // 1. You can make large DSM allocations, larger than the current RAM
+ // size of the VM, without errors
+ //
+ // 2. If the allocated memory is really used, the VM is scaled up
+ // automatically to accommodate that
+ //
+ // We try to make that possible by having swap in the VM. But with the
+ // default 'posix' DSM implementation, we fail step 1, even when there's
+ // plenty of swap available. PostgreSQL uses posix_fallocate() to create
+ // the shmem segment, which is really just a file in /dev/shm in Linux,
+ // but posix_fallocate() on tmpfs returns ENOMEM if the size is larger
+ // than available RAM.
+ //
+ // Using 'dynamic_shared_memory_type = mmap' works around that, because
+ // the Postgres 'mmap' DSM implementation doesn't use
+ // posix_fallocate(). Instead, it uses repeated calls to write(2) to
+ // fill the file with zeros. It's weird that that differs between
+ // 'posix' and 'mmap', but we take advantage of it. When the file is
+ // filled slowly with write(2), the kernel allows it to grow larger, as
+ // long as there's swap available.
+ //
+ // In short, using 'dynamic_shared_memory_type = mmap' allows us one DSM
+ // segment to be larger than currently available RAM. But because we
+ // don't want to store it on a real file, which the kernel would try to
+ // flush to disk, so symlink pg_dynshm to /dev/shm.
+ //
+ // We don't set 'dynamic_shared_memory_type = mmap' here, we let the
+ // control plane control that option. If 'mmap' is not used, this
+ // symlink doesn't affect anything.
+ //
+ // See https://github.com/neondatabase/autoscaling/issues/800
+ std::fs::remove_dir(pgdata_path.join("pg_dynshmem"))?;
+ symlink("/dev/shm/", pgdata_path.join("pg_dynshmem"))?;
+
match spec.mode {
ComputeMode::Primary => {}
ComputeMode::Replica | ComputeMode::Static(..) => {
@@ -649,8 +723,12 @@ impl ComputeNode {
// Stop it when it's ready
info!("waiting for postgres");
wait_for_postgres(&mut pg, Path::new(pgdata))?;
- pg.kill()?;
- info!("sent kill signal");
+ // SIGQUIT orders postgres to exit immediately. We don't want to SIGKILL
+ // it to avoid orphaned processes prowling around while datadir is
+ // wiped.
+ let pm_pid = Pid::from_raw(pg.id() as i32);
+ kill(pm_pid, Signal::SIGQUIT)?;
+ info!("sent SIGQUIT signal");
pg.wait()?;
info!("done prewarming");
@@ -691,6 +769,26 @@ impl ComputeNode {
Ok((pg, logs_handle))
}
+ /// Do post configuration of the already started Postgres. This function spawns a background thread to
+ /// configure the database after applying the compute spec. Currently, it upgrades the neon extension
+ /// version. In the future, it may upgrade all 3rd-party extensions.
+ #[instrument(skip_all)]
+ pub fn post_apply_config(&self) -> Result<()> {
+ let connstr = self.connstr.clone();
+ thread::spawn(move || {
+ let func = || {
+ let mut client = Client::connect(connstr.as_str(), NoTls)?;
+ handle_neon_extension_upgrade(&mut client)
+ .context("handle_neon_extension_upgrade")?;
+ Ok::<_, anyhow::Error>(())
+ };
+ if let Err(err) = func() {
+ error!("error while post_apply_config: {err:#}");
+ }
+ });
+ Ok(())
+ }
+
/// Do initial configuration of the already started Postgres.
#[instrument(skip_all)]
pub fn apply_config(&self, compute_state: &ComputeState) -> Result<()> {
@@ -702,27 +800,34 @@ impl ComputeNode {
// but we can create a new one and grant it all privileges.
let connstr = self.connstr.clone();
let mut client = match Client::connect(connstr.as_str(), NoTls) {
- Err(e) => {
- info!(
- "cannot connect to postgres: {}, retrying with `zenith_admin` username",
- e
- );
- let mut zenith_admin_connstr = connstr.clone();
+ Err(e) => match e.code() {
+ Some(&SqlState::INVALID_PASSWORD)
+ | Some(&SqlState::INVALID_AUTHORIZATION_SPECIFICATION) => {
+ // connect with zenith_admin if cloud_admin could not authenticate
+ info!(
+ "cannot connect to postgres: {}, retrying with `zenith_admin` username",
+ e
+ );
+ let mut zenith_admin_connstr = connstr.clone();
- zenith_admin_connstr
- .set_username("zenith_admin")
- .map_err(|_| anyhow::anyhow!("invalid connstr"))?;
+ zenith_admin_connstr
+ .set_username("zenith_admin")
+ .map_err(|_| anyhow::anyhow!("invalid connstr"))?;
- let mut client = Client::connect(zenith_admin_connstr.as_str(), NoTls)?;
- // Disable forwarding so that users don't get a cloud_admin role
- client.simple_query("SET neon.forward_ddl = false")?;
- client.simple_query("CREATE USER cloud_admin WITH SUPERUSER")?;
- client.simple_query("GRANT zenith_admin TO cloud_admin")?;
- drop(client);
+ let mut client =
+ Client::connect(zenith_admin_connstr.as_str(), NoTls)
+ .context("broken cloud_admin credential: tried connecting with cloud_admin but could not authenticate, and zenith_admin does not work either")?;
+ // Disable forwarding so that users don't get a cloud_admin role
+ client.simple_query("SET neon.forward_ddl = false")?;
+ client.simple_query("CREATE USER cloud_admin WITH SUPERUSER")?;
+ client.simple_query("GRANT zenith_admin TO cloud_admin")?;
+ drop(client);
- // reconnect with connstring with expected name
- Client::connect(connstr.as_str(), NoTls)?
- }
+ // reconnect with connstring with expected name
+ Client::connect(connstr.as_str(), NoTls)?
+ }
+ _ => return Err(e.into()),
+ },
Ok(client) => client,
};
@@ -736,7 +841,12 @@ impl ComputeNode {
handle_roles(spec, &mut client)?;
handle_databases(spec, &mut client)?;
handle_role_deletions(spec, connstr.as_str(), &mut client)?;
- handle_grants(spec, &mut client, connstr.as_str())?;
+ handle_grants(
+ spec,
+ &mut client,
+ connstr.as_str(),
+ self.has_feature(ComputeFeature::AnonExtension),
+ )?;
handle_extensions(spec, &mut client)?;
handle_extension_neon(&mut client)?;
create_availability_check_data(&mut client)?;
@@ -744,12 +854,11 @@ impl ComputeNode {
// 'Close' connection
drop(client);
- if self.has_feature(ComputeFeature::Migrations) {
- thread::spawn(move || {
- let mut client = Client::connect(connstr.as_str(), NoTls)?;
- handle_migrations(&mut client)
- });
- }
+ // Run migrations separately to not hold up cold starts
+ thread::spawn(move || {
+ let mut client = Client::connect(connstr.as_str(), NoTls)?;
+ handle_migrations(&mut client)
+ });
Ok(())
}
@@ -811,7 +920,12 @@ impl ComputeNode {
handle_roles(&spec, &mut client)?;
handle_databases(&spec, &mut client)?;
handle_role_deletions(&spec, self.connstr.as_str(), &mut client)?;
- handle_grants(&spec, &mut client, self.connstr.as_str())?;
+ handle_grants(
+ &spec,
+ &mut client,
+ self.connstr.as_str(),
+ self.has_feature(ComputeFeature::AnonExtension),
+ )?;
handle_extensions(&spec, &mut client)?;
handle_extension_neon(&mut client)?;
// We can skip handle_migrations here because a new migration can only appear
@@ -909,18 +1023,21 @@ impl ComputeNode {
let pg_process = self.start_postgres(pspec.storage_auth_token.clone())?;
let config_time = Utc::now();
- if pspec.spec.mode == ComputeMode::Primary && !pspec.spec.skip_pg_catalog_updates {
- let pgdata_path = Path::new(&self.pgdata);
- // temporarily reset max_cluster_size in config
- // to avoid the possibility of hitting the limit, while we are applying config:
- // creating new extensions, roles, etc...
- config::compute_ctl_temp_override_create(pgdata_path, "neon.max_cluster_size=-1")?;
- self.pg_reload_conf()?;
+ if pspec.spec.mode == ComputeMode::Primary {
+ if !pspec.spec.skip_pg_catalog_updates {
+ let pgdata_path = Path::new(&self.pgdata);
+ // temporarily reset max_cluster_size in config
+ // to avoid the possibility of hitting the limit, while we are applying config:
+ // creating new extensions, roles, etc...
+ config::compute_ctl_temp_override_create(pgdata_path, "neon.max_cluster_size=-1")?;
+ self.pg_reload_conf()?;
- self.apply_config(&compute_state)?;
+ self.apply_config(&compute_state)?;
- config::compute_ctl_temp_override_remove(pgdata_path)?;
- self.pg_reload_conf()?;
+ config::compute_ctl_temp_override_remove(pgdata_path)?;
+ self.pg_reload_conf()?;
+ }
+ self.post_apply_config()?;
}
let startup_end_time = Utc::now();
@@ -1241,3 +1358,17 @@ LIMIT 100",
Ok(remote_ext_metrics)
}
}
+
+pub fn forward_termination_signal() {
+ let ss_pid = SYNC_SAFEKEEPERS_PID.load(Ordering::SeqCst);
+ if ss_pid != 0 {
+ let ss_pid = nix::unistd::Pid::from_raw(ss_pid as i32);
+ kill(ss_pid, Signal::SIGTERM).ok();
+ }
+ let pg_pid = PG_PID.load(Ordering::SeqCst);
+ if pg_pid != 0 {
+ let pg_pid = nix::unistd::Pid::from_raw(pg_pid as i32);
+ // use 'immediate' shutdown (SIGQUIT): https://www.postgresql.org/docs/current/server-shutdown.html
+ kill(pg_pid, Signal::SIGQUIT).ok();
+ }
+}
diff --git a/compute_tools/src/config.rs b/compute_tools/src/config.rs
index a7ef8cea92..42b8480211 100644
--- a/compute_tools/src/config.rs
+++ b/compute_tools/src/config.rs
@@ -51,6 +51,9 @@ pub fn write_postgres_conf(
if let Some(s) = &spec.pageserver_connstring {
writeln!(file, "neon.pageserver_connstring={}", escape_conf_value(s))?;
}
+ if let Some(stripe_size) = spec.shard_stripe_size {
+ writeln!(file, "neon.stripe_size={stripe_size}")?;
+ }
if !spec.safekeeper_connstrings.is_empty() {
writeln!(
file,
@@ -79,6 +82,12 @@ pub fn write_postgres_conf(
ComputeMode::Replica => {
// hot_standby is 'on' by default, but let's be explicit
writeln!(file, "hot_standby=on")?;
+
+ // Inform the replica about the primary state
+ // Default is 'false'
+ if let Some(primary_is_running) = spec.primary_is_running {
+ writeln!(file, "neon.primary_is_running={}", primary_is_running)?;
+ }
}
}
diff --git a/compute_tools/src/extension_server.rs b/compute_tools/src/extension_server.rs
index 2cec12119f..ef1db73982 100644
--- a/compute_tools/src/extension_server.rs
+++ b/compute_tools/src/extension_server.rs
@@ -71,7 +71,7 @@ More specifically, here is an example ext_index.json
}
}
*/
-use anyhow::{self, Result};
+use anyhow::Result;
use anyhow::{bail, Context};
use bytes::Bytes;
use compute_api::spec::RemoteExtSpec;
diff --git a/compute_tools/src/http/api.rs b/compute_tools/src/http/api.rs
index fa2c4cff28..128783b477 100644
--- a/compute_tools/src/http/api.rs
+++ b/compute_tools/src/http/api.rs
@@ -5,6 +5,7 @@ use std::net::SocketAddr;
use std::sync::Arc;
use std::thread;
+use crate::compute::forward_termination_signal;
use crate::compute::{ComputeNode, ComputeState, ParsedSpec};
use compute_api::requests::ConfigurationRequest;
use compute_api::responses::{ComputeStatus, ComputeStatusResponse, GenericAPIError};
@@ -12,8 +13,6 @@ use compute_api::responses::{ComputeStatus, ComputeStatusResponse, GenericAPIErr
use anyhow::Result;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, Request, Response, Server, StatusCode};
-use num_cpus;
-use serde_json;
use tokio::task;
use tracing::{error, info, warn};
use tracing_utils::http::OtelName;
@@ -123,6 +122,17 @@ async fn routes(req: Request
, compute: &Arc) -> Response {
+ info!("serving /terminate POST request");
+ match handle_terminate_request(compute).await {
+ Ok(()) => Response::new(Body::empty()),
+ Err((msg, code)) => {
+ error!("error handling /terminate request: {msg}");
+ render_json_error(&msg, code)
+ }
+ }
+ }
+
// download extension files from remote extension storage on demand
(&Method::POST, route) if route.starts_with("/extension_server/") => {
info!("serving {:?} POST request", route);
@@ -297,6 +307,49 @@ fn render_json_error(e: &str, status: StatusCode) -> Response {
.unwrap()
}
+async fn handle_terminate_request(compute: &Arc) -> Result<(), (String, StatusCode)> {
+ {
+ let mut state = compute.state.lock().unwrap();
+ if state.status == ComputeStatus::Terminated {
+ return Ok(());
+ }
+ if state.status != ComputeStatus::Empty && state.status != ComputeStatus::Running {
+ let msg = format!(
+ "invalid compute status for termination request: {:?}",
+ state.status.clone()
+ );
+ return Err((msg, StatusCode::PRECONDITION_FAILED));
+ }
+ state.status = ComputeStatus::TerminationPending;
+ compute.state_changed.notify_all();
+ drop(state);
+ }
+ forward_termination_signal();
+ info!("sent signal and notified waiters");
+
+ // Spawn a blocking thread to wait for compute to become Terminated.
+ // This is needed to do not block the main pool of workers and
+ // be able to serve other requests while some particular request
+ // is waiting for compute to finish configuration.
+ let c = compute.clone();
+ task::spawn_blocking(move || {
+ let mut state = c.state.lock().unwrap();
+ while state.status != ComputeStatus::Terminated {
+ state = c.state_changed.wait(state).unwrap();
+ info!(
+ "waiting for compute to become Terminated, current status: {:?}",
+ state.status
+ );
+ }
+
+ Ok(())
+ })
+ .await
+ .unwrap()?;
+ info!("terminated Postgres");
+ Ok(())
+}
+
// Main Hyper HTTP server function that runs it and blocks waiting on it forever.
#[tokio::main]
async fn serve(port: u16, state: Arc) {
diff --git a/compute_tools/src/http/openapi_spec.yaml b/compute_tools/src/http/openapi_spec.yaml
index cedc6ece8f..d2ec54299f 100644
--- a/compute_tools/src/http/openapi_spec.yaml
+++ b/compute_tools/src/http/openapi_spec.yaml
@@ -168,6 +168,29 @@ paths:
schema:
$ref: "#/components/schemas/GenericError"
+ /terminate:
+ post:
+ tags:
+ - Terminate
+ summary: Terminate Postgres and wait for it to exit
+ description: ""
+ operationId: terminate
+ responses:
+ 200:
+ description: Result
+ 412:
+ description: "wrong state"
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/GenericError"
+ 500:
+ description: "Unexpected error"
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/GenericError"
+
components:
securitySchemes:
JWT:
diff --git a/compute_tools/src/monitor.rs b/compute_tools/src/monitor.rs
index f09bd02664..872a3f7750 100644
--- a/compute_tools/src/monitor.rs
+++ b/compute_tools/src/monitor.rs
@@ -138,6 +138,34 @@ fn watch_compute_activity(compute: &ComputeNode) {
}
}
//
+ // Don't suspend compute if there is an active logical replication subscription
+ //
+ // `where pid is not null` – to filter out read only computes and subscription on branches
+ //
+ let logical_subscriptions_query =
+ "select count(*) from pg_stat_subscription where pid is not null;";
+ match cli.query_one(logical_subscriptions_query, &[]) {
+ Ok(row) => match row.try_get::<&str, i64>("count") {
+ Ok(num_subscribers) => {
+ if num_subscribers > 0 {
+ compute.update_last_active(Some(Utc::now()));
+ continue;
+ }
+ }
+ Err(e) => {
+ warn!("failed to parse `pg_stat_subscription` count: {:?}", e);
+ continue;
+ }
+ },
+ Err(e) => {
+ warn!(
+ "failed to get list of active logical replication subscriptions: {:?}",
+ e
+ );
+ continue;
+ }
+ }
+ //
// Do not suspend compute if autovacuum is running
//
let autovacuum_count_query = "select count(*) from pg_stat_activity where backend_type = 'autovacuum worker'";
diff --git a/compute_tools/src/pg_helpers.rs b/compute_tools/src/pg_helpers.rs
index ce704385c6..5deb50d6b7 100644
--- a/compute_tools/src/pg_helpers.rs
+++ b/compute_tools/src/pg_helpers.rs
@@ -264,9 +264,10 @@ pub fn wait_for_postgres(pg: &mut Child, pgdata: &Path) -> Result<()> {
// case we miss some events for some reason. Not strictly necessary, but
// better safe than sorry.
let (tx, rx) = std::sync::mpsc::channel();
- let (mut watcher, rx): (Box, _) = match notify::recommended_watcher(move |res| {
+ let watcher_res = notify::recommended_watcher(move |res| {
let _ = tx.send(res);
- }) {
+ });
+ let (mut watcher, rx): (Box, _) = match watcher_res {
Ok(watcher) => (Box::new(watcher), rx),
Err(e) => {
match e.kind {
diff --git a/compute_tools/src/spec.rs b/compute_tools/src/spec.rs
index e87dc0b732..ba3a84cda8 100644
--- a/compute_tools/src/spec.rs
+++ b/compute_tools/src/spec.rs
@@ -302,9 +302,9 @@ pub fn handle_roles(spec: &ComputeSpec, client: &mut Client) -> Result<()> {
RoleAction::Create => {
// This branch only runs when roles are created through the console, so it is
// safe to add more permissions here. BYPASSRLS and REPLICATION are inherited
- // from neon_superuser.
+ // from neon_superuser. (NOTE: REPLICATION has been removed from here for now).
let mut query: String = format!(
- "CREATE ROLE {} INHERIT CREATEROLE CREATEDB BYPASSRLS REPLICATION IN ROLE neon_superuser",
+ "CREATE ROLE {} INHERIT CREATEROLE CREATEDB BYPASSRLS IN ROLE neon_superuser",
name.pg_quote()
);
info!("running role create query: '{}'", &query);
@@ -581,7 +581,12 @@ pub fn handle_databases(spec: &ComputeSpec, client: &mut Client) -> Result<()> {
/// Grant CREATE ON DATABASE to the database owner and do some other alters and grants
/// to allow users creating trusted extensions and re-creating `public` schema, for example.
#[instrument(skip_all)]
-pub fn handle_grants(spec: &ComputeSpec, client: &mut Client, connstr: &str) -> Result<()> {
+pub fn handle_grants(
+ spec: &ComputeSpec,
+ client: &mut Client,
+ connstr: &str,
+ enable_anon_extension: bool,
+) -> Result<()> {
info!("modifying database permissions");
let existing_dbs = get_existing_dbs(client)?;
@@ -650,6 +655,9 @@ pub fn handle_grants(spec: &ComputeSpec, client: &mut Client, connstr: &str) ->
// remove this code if possible. The worst thing that could happen is that
// user won't be able to use public schema in NEW databases created in the
// very OLD project.
+ //
+ // Also, alter default permissions so that relations created by extensions can be
+ // used by neon_superuser without permission issues.
let grant_query = "DO $$\n\
BEGIN\n\
IF EXISTS(\n\
@@ -668,6 +676,15 @@ pub fn handle_grants(spec: &ComputeSpec, client: &mut Client, connstr: &str) ->
GRANT CREATE ON SCHEMA public TO web_access;\n\
END IF;\n\
END IF;\n\
+ IF EXISTS(\n\
+ SELECT nspname\n\
+ FROM pg_catalog.pg_namespace\n\
+ WHERE nspname = 'public'\n\
+ )\n\
+ THEN\n\
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO neon_superuser WITH GRANT OPTION;\n\
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO neon_superuser WITH GRANT OPTION;\n\
+ END IF;\n\
END\n\
$$;"
.to_string();
@@ -678,6 +695,11 @@ pub fn handle_grants(spec: &ComputeSpec, client: &mut Client, connstr: &str) ->
inlinify(&grant_query)
);
db_client.simple_query(&grant_query)?;
+
+ // it is important to run this after all grants
+ if enable_anon_extension {
+ handle_extension_anon(spec, &db.owner, &mut db_client, false)?;
+ }
}
Ok(())
@@ -722,7 +744,17 @@ pub fn handle_extension_neon(client: &mut Client) -> Result<()> {
// - extension was just installed
// - extension was already installed and is up to date
let query = "ALTER EXTENSION neon UPDATE";
- info!("update neon extension schema with query: {}", query);
+ info!("update neon extension version with query: {}", query);
+ client.simple_query(query)?;
+
+ Ok(())
+}
+
+#[instrument(skip_all)]
+pub fn handle_neon_extension_upgrade(client: &mut Client) -> Result<()> {
+ info!("handle neon extension upgrade");
+ let query = "ALTER EXTENSION neon UPDATE";
+ info!("update neon extension version with query: {}", query);
client.simple_query(query)?;
Ok(())
@@ -758,6 +790,33 @@ BEGIN
END LOOP;
END $$;
"#,
+ r#"
+DO $$
+BEGIN
+ IF (SELECT setting::numeric >= 160000 FROM pg_settings WHERE name = 'server_version_num') THEN
+ EXECUTE 'GRANT pg_create_subscription TO neon_superuser';
+ END IF;
+END
+$$;"#,
+ "GRANT pg_monitor TO neon_superuser WITH ADMIN OPTION",
+ // Don't remove: these are some SQLs that we originally applied in migrations but turned out to execute somewhere else.
+ "",
+ "",
+ "",
+ "",
+ // Add new migrations below.
+ r#"
+DO $$
+DECLARE
+ role_name TEXT;
+BEGIN
+ FOR role_name IN SELECT rolname FROM pg_roles WHERE rolreplication IS TRUE
+ LOOP
+ RAISE NOTICE 'EXECUTING ALTER ROLE % NOREPLICATION', quote_ident(role_name);
+ EXECUTE 'ALTER ROLE ' || quote_ident(role_name) || ' NOREPLICATION';
+ END LOOP;
+END
+$$;"#,
];
let mut query = "CREATE SCHEMA IF NOT EXISTS neon_migration";
@@ -784,8 +843,13 @@ END $$;
client.simple_query(query)?;
while current_migration < migrations.len() {
- info!("Running migration:\n{}\n", migrations[current_migration]);
- client.simple_query(migrations[current_migration])?;
+ let migration = &migrations[current_migration];
+ if migration.is_empty() {
+ info!("Skip migration id={}", current_migration);
+ } else {
+ info!("Running migration:\n{}\n", migration);
+ client.simple_query(migration)?;
+ }
current_migration += 1;
}
let setval = format!(
@@ -801,5 +865,125 @@ END $$;
"Ran {} migrations",
(migrations.len() - starting_migration_id)
);
+
+ Ok(())
+}
+
+/// Connect to the database as superuser and pre-create anon extension
+/// if it is present in shared_preload_libraries
+#[instrument(skip_all)]
+pub fn handle_extension_anon(
+ spec: &ComputeSpec,
+ db_owner: &str,
+ db_client: &mut Client,
+ grants_only: bool,
+) -> Result<()> {
+ info!("handle extension anon");
+
+ if let Some(libs) = spec.cluster.settings.find("shared_preload_libraries") {
+ if libs.contains("anon") {
+ if !grants_only {
+ // check if extension is already initialized using anon.is_initialized()
+ let query = "SELECT anon.is_initialized()";
+ match db_client.query(query, &[]) {
+ Ok(rows) => {
+ if !rows.is_empty() {
+ let is_initialized: bool = rows[0].get(0);
+ if is_initialized {
+ info!("anon extension is already initialized");
+ return Ok(());
+ }
+ }
+ }
+ Err(e) => {
+ warn!(
+ "anon extension is_installed check failed with expected error: {}",
+ e
+ );
+ }
+ };
+
+ // Create anon extension if this compute needs it
+ // Users cannot create it themselves, because superuser is required.
+ let mut query = "CREATE EXTENSION IF NOT EXISTS anon CASCADE";
+ info!("creating anon extension with query: {}", query);
+ match db_client.query(query, &[]) {
+ Ok(_) => {}
+ Err(e) => {
+ error!("anon extension creation failed with error: {}", e);
+ return Ok(());
+ }
+ }
+
+ // check that extension is installed
+ query = "SELECT extname FROM pg_extension WHERE extname = 'anon'";
+ let rows = db_client.query(query, &[])?;
+ if rows.is_empty() {
+ error!("anon extension is not installed");
+ return Ok(());
+ }
+
+ // Initialize anon extension
+ // This also requires superuser privileges, so users cannot do it themselves.
+ query = "SELECT anon.init()";
+ match db_client.query(query, &[]) {
+ Ok(_) => {}
+ Err(e) => {
+ error!("anon.init() failed with error: {}", e);
+ return Ok(());
+ }
+ }
+ }
+
+ // check that extension is installed, if not bail early
+ let query = "SELECT extname FROM pg_extension WHERE extname = 'anon'";
+ match db_client.query(query, &[]) {
+ Ok(rows) => {
+ if rows.is_empty() {
+ error!("anon extension is not installed");
+ return Ok(());
+ }
+ }
+ Err(e) => {
+ error!("anon extension check failed with error: {}", e);
+ return Ok(());
+ }
+ };
+
+ let query = format!("GRANT ALL ON SCHEMA anon TO {}", db_owner);
+ info!("granting anon extension permissions with query: {}", query);
+ db_client.simple_query(&query)?;
+
+ // Grant permissions to db_owner to use anon extension functions
+ let query = format!("GRANT ALL ON ALL FUNCTIONS IN SCHEMA anon TO {}", db_owner);
+ info!("granting anon extension permissions with query: {}", query);
+ db_client.simple_query(&query)?;
+
+ // This is needed, because some functions are defined as SECURITY DEFINER.
+ // In Postgres SECURITY DEFINER functions are executed with the privileges
+ // of the owner.
+ // In anon extension this it is needed to access some GUCs, which are only accessible to
+ // superuser. But we've patched postgres to allow db_owner to access them as well.
+ // So we need to change owner of these functions to db_owner.
+ let query = format!("
+ SELECT 'ALTER FUNCTION '||nsp.nspname||'.'||p.proname||'('||pg_get_function_identity_arguments(p.oid)||') OWNER TO {};'
+ from pg_proc p
+ join pg_namespace nsp ON p.pronamespace = nsp.oid
+ where nsp.nspname = 'anon';", db_owner);
+
+ info!("change anon extension functions owner to db owner");
+ db_client.simple_query(&query)?;
+
+ // affects views as well
+ let query = format!("GRANT ALL ON ALL TABLES IN SCHEMA anon TO {}", db_owner);
+ info!("granting anon extension permissions with query: {}", query);
+ db_client.simple_query(&query)?;
+
+ let query = format!("GRANT ALL ON ALL SEQUENCES IN SCHEMA anon TO {}", db_owner);
+ info!("granting anon extension permissions with query: {}", query);
+ db_client.simple_query(&query)?;
+ }
+ }
+
Ok(())
}
diff --git a/control_plane/README.md b/control_plane/README.md
new file mode 100644
index 0000000000..827aba5c1f
--- /dev/null
+++ b/control_plane/README.md
@@ -0,0 +1,26 @@
+# Control Plane and Neon Local
+
+This crate contains tools to start a Neon development environment locally. This utility can be used with the `cargo neon` command.
+
+## Example: Start with Postgres 16
+
+To create and start a local development environment with Postgres 16, you will need to provide `--pg-version` flag to 3 of the start-up commands.
+
+```shell
+cargo neon init --pg-version 16
+cargo neon start
+cargo neon tenant create --set-default --pg-version 16
+cargo neon endpoint create main --pg-version 16
+cargo neon endpoint start main
+```
+
+## Example: Create Test User and Database
+
+By default, `cargo neon` starts an endpoint with `cloud_admin` and `postgres` database. If you want to have a role and a database similar to what we have on the cloud service, you can do it with the following commands when starting an endpoint.
+
+```shell
+cargo neon endpoint create main --pg-version 16 --update-catalog true
+cargo neon endpoint start main --create-test-user true
+```
+
+The first command creates `neon_superuser` and necessary roles. The second command creates `test` user and `neondb` database. You will see a connection string that connects you to the test user after running the second command.
diff --git a/control_plane/attachment_service/Cargo.toml b/control_plane/attachment_service/Cargo.toml
index 743dd806c4..a5fad7216c 100644
--- a/control_plane/attachment_service/Cargo.toml
+++ b/control_plane/attachment_service/Cargo.toml
@@ -4,17 +4,30 @@ version = "0.1.0"
edition.workspace = true
license.workspace = true
+[[bin]]
+name = "storage_controller"
+path = "src/main.rs"
+
+[features]
+default = []
+# Enables test-only APIs and behaviors
+testing = []
+
[dependencies]
anyhow.workspace = true
+aws-config.workspace = true
+aws-sdk-secretsmanager.workspace = true
camino.workspace = true
clap.workspace = true
futures.workspace = true
git-version.workspace = true
hyper.workspace = true
+humantime.workspace = true
+once_cell.workspace = true
pageserver_api.workspace = true
pageserver_client.workspace = true
postgres_connection.workspace = true
-scopeguard.workspace = true
+reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
@@ -22,9 +35,9 @@ tokio.workspace = true
tokio-util.workspace = true
tracing.workspace = true
-# TODO: remove this after DB persistence is added, it is only used for
-# a parsing function when loading pageservers from neon_local LocalEnv
-postgres_backend.workspace = true
+diesel = { version = "2.1.4", features = ["serde_json", "postgres", "r2d2"] }
+diesel_migrations = { version = "2.1.0" }
+r2d2 = { version = "0.8.10" }
utils = { path = "../../libs/utils/" }
metrics = { path = "../../libs/metrics/" }
diff --git a/control_plane/attachment_service/migrations/.keep b/control_plane/attachment_service/migrations/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/control_plane/attachment_service/migrations/00000000000000_diesel_initial_setup/down.sql b/control_plane/attachment_service/migrations/00000000000000_diesel_initial_setup/down.sql
new file mode 100644
index 0000000000..a9f5260911
--- /dev/null
+++ b/control_plane/attachment_service/migrations/00000000000000_diesel_initial_setup/down.sql
@@ -0,0 +1,6 @@
+-- This file was automatically created by Diesel to setup helper functions
+-- and other internal bookkeeping. This file is safe to edit, any future
+-- changes will be added to existing projects as new migrations.
+
+DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
+DROP FUNCTION IF EXISTS diesel_set_updated_at();
diff --git a/control_plane/attachment_service/migrations/00000000000000_diesel_initial_setup/up.sql b/control_plane/attachment_service/migrations/00000000000000_diesel_initial_setup/up.sql
new file mode 100644
index 0000000000..d68895b1a7
--- /dev/null
+++ b/control_plane/attachment_service/migrations/00000000000000_diesel_initial_setup/up.sql
@@ -0,0 +1,36 @@
+-- This file was automatically created by Diesel to setup helper functions
+-- and other internal bookkeeping. This file is safe to edit, any future
+-- changes will be added to existing projects as new migrations.
+
+
+
+
+-- Sets up a trigger for the given table to automatically set a column called
+-- `updated_at` whenever the row is modified (unless `updated_at` was included
+-- in the modified columns)
+--
+-- # Example
+--
+-- ```sql
+-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
+--
+-- SELECT diesel_manage_updated_at('users');
+-- ```
+CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
+BEGIN
+ EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
+ FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
+BEGIN
+ IF (
+ NEW IS DISTINCT FROM OLD AND
+ NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
+ ) THEN
+ NEW.updated_at := current_timestamp;
+ END IF;
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
diff --git a/control_plane/attachment_service/migrations/2024-01-07-211257_create_tenant_shards/down.sql b/control_plane/attachment_service/migrations/2024-01-07-211257_create_tenant_shards/down.sql
new file mode 100644
index 0000000000..b875b91c00
--- /dev/null
+++ b/control_plane/attachment_service/migrations/2024-01-07-211257_create_tenant_shards/down.sql
@@ -0,0 +1 @@
+DROP TABLE tenant_shards;
diff --git a/control_plane/attachment_service/migrations/2024-01-07-211257_create_tenant_shards/up.sql b/control_plane/attachment_service/migrations/2024-01-07-211257_create_tenant_shards/up.sql
new file mode 100644
index 0000000000..2ffdae6287
--- /dev/null
+++ b/control_plane/attachment_service/migrations/2024-01-07-211257_create_tenant_shards/up.sql
@@ -0,0 +1,13 @@
+CREATE TABLE tenant_shards (
+ tenant_id VARCHAR NOT NULL,
+ shard_number INTEGER NOT NULL,
+ shard_count INTEGER NOT NULL,
+ PRIMARY KEY(tenant_id, shard_number, shard_count),
+ shard_stripe_size INTEGER NOT NULL,
+ generation INTEGER NOT NULL,
+ generation_pageserver BIGINT NOT NULL,
+ placement_policy VARCHAR NOT NULL,
+ splitting SMALLINT NOT NULL,
+ -- config is JSON encoded, opaque to the database.
+ config TEXT NOT NULL
+);
\ No newline at end of file
diff --git a/control_plane/attachment_service/migrations/2024-01-07-212945_create_nodes/down.sql b/control_plane/attachment_service/migrations/2024-01-07-212945_create_nodes/down.sql
new file mode 100644
index 0000000000..ec303bc8cf
--- /dev/null
+++ b/control_plane/attachment_service/migrations/2024-01-07-212945_create_nodes/down.sql
@@ -0,0 +1 @@
+DROP TABLE nodes;
diff --git a/control_plane/attachment_service/migrations/2024-01-07-212945_create_nodes/up.sql b/control_plane/attachment_service/migrations/2024-01-07-212945_create_nodes/up.sql
new file mode 100644
index 0000000000..9be0880fa4
--- /dev/null
+++ b/control_plane/attachment_service/migrations/2024-01-07-212945_create_nodes/up.sql
@@ -0,0 +1,10 @@
+CREATE TABLE nodes (
+ node_id BIGINT PRIMARY KEY NOT NULL,
+
+ scheduling_policy VARCHAR NOT NULL,
+
+ listen_http_addr VARCHAR NOT NULL,
+ listen_http_port INTEGER NOT NULL,
+ listen_pg_addr VARCHAR NOT NULL,
+ listen_pg_port INTEGER NOT NULL
+);
\ No newline at end of file
diff --git a/control_plane/attachment_service/migrations/2024-02-29-094122_generations_null/down.sql b/control_plane/attachment_service/migrations/2024-02-29-094122_generations_null/down.sql
new file mode 100644
index 0000000000..503231f69d
--- /dev/null
+++ b/control_plane/attachment_service/migrations/2024-02-29-094122_generations_null/down.sql
@@ -0,0 +1,2 @@
+ALTER TABLE tenant_shards ALTER generation SET NOT NULL;
+ALTER TABLE tenant_shards ALTER generation_pageserver SET NOT NULL;
diff --git a/control_plane/attachment_service/migrations/2024-02-29-094122_generations_null/up.sql b/control_plane/attachment_service/migrations/2024-02-29-094122_generations_null/up.sql
new file mode 100644
index 0000000000..7e1e3cfe90
--- /dev/null
+++ b/control_plane/attachment_service/migrations/2024-02-29-094122_generations_null/up.sql
@@ -0,0 +1,4 @@
+
+
+ALTER TABLE tenant_shards ALTER generation DROP NOT NULL;
+ALTER TABLE tenant_shards ALTER generation_pageserver DROP NOT NULL;
\ No newline at end of file
diff --git a/control_plane/attachment_service/src/auth.rs b/control_plane/attachment_service/src/auth.rs
new file mode 100644
index 0000000000..ef47abf8c7
--- /dev/null
+++ b/control_plane/attachment_service/src/auth.rs
@@ -0,0 +1,9 @@
+use utils::auth::{AuthError, Claims, Scope};
+
+pub fn check_permission(claims: &Claims, required_scope: Scope) -> Result<(), AuthError> {
+ if claims.scope != required_scope {
+ return Err(AuthError("Scope mismatch. Permission denied".into()));
+ }
+
+ Ok(())
+}
diff --git a/control_plane/attachment_service/src/compute_hook.rs b/control_plane/attachment_service/src/compute_hook.rs
index 02617cd065..bebc62ac2f 100644
--- a/control_plane/attachment_service/src/compute_hook.rs
+++ b/control_plane/attachment_service/src/compute_hook.rs
@@ -1,72 +1,164 @@
-use std::collections::HashMap;
+use std::{collections::HashMap, time::Duration};
-use control_plane::endpoint::ComputeControlPlane;
+use control_plane::endpoint::{ComputeControlPlane, EndpointStatus};
use control_plane::local_env::LocalEnv;
-use pageserver_api::shard::{ShardCount, ShardIndex, TenantShardId};
+use hyper::{Method, StatusCode};
+use pageserver_api::shard::{ShardCount, ShardNumber, ShardStripeSize, TenantShardId};
use postgres_connection::parse_host_port;
-use utils::id::{NodeId, TenantId};
+use serde::{Deserialize, Serialize};
+use tokio_util::sync::CancellationToken;
+use utils::{
+ backoff::{self},
+ id::{NodeId, TenantId},
+};
-pub(super) struct ComputeHookTenant {
- shards: Vec<(ShardIndex, NodeId)>,
+use crate::service::Config;
+
+const BUSY_DELAY: Duration = Duration::from_secs(1);
+const SLOWDOWN_DELAY: Duration = Duration::from_secs(5);
+
+pub(crate) const API_CONCURRENCY: usize = 32;
+
+struct ShardedComputeHookTenant {
+ stripe_size: ShardStripeSize,
+ shard_count: ShardCount,
+ shards: Vec<(ShardNumber, NodeId)>,
+}
+
+enum ComputeHookTenant {
+ Unsharded(NodeId),
+ Sharded(ShardedComputeHookTenant),
}
impl ComputeHookTenant {
- pub(super) async fn maybe_reconfigure(&mut self, tenant_id: TenantId) -> anyhow::Result<()> {
- // Find the highest shard count and drop any shards that aren't
- // for that shard count.
- let shard_count = self.shards.iter().map(|(k, _v)| k.shard_count).max();
- let Some(shard_count) = shard_count else {
- // No shards, nothing to do.
- tracing::info!("ComputeHookTenant::maybe_reconfigure: no shards");
- return Ok(());
- };
+ /// Construct with at least one shard's information
+ fn new(tenant_shard_id: TenantShardId, stripe_size: ShardStripeSize, node_id: NodeId) -> Self {
+ if tenant_shard_id.shard_count.count() > 1 {
+ Self::Sharded(ShardedComputeHookTenant {
+ shards: vec![(tenant_shard_id.shard_number, node_id)],
+ stripe_size,
+ shard_count: tenant_shard_id.shard_count,
+ })
+ } else {
+ Self::Unsharded(node_id)
+ }
+ }
- self.shards.retain(|(k, _v)| k.shard_count == shard_count);
- self.shards
- .sort_by_key(|(shard, _node_id)| shard.shard_number);
-
- if self.shards.len() == shard_count.0 as usize || shard_count == ShardCount(0) {
- // We have pageservers for all the shards: proceed to reconfigure compute
- let env = match LocalEnv::load_config() {
- Ok(e) => e,
- Err(e) => {
- tracing::warn!(
- "Couldn't load neon_local config, skipping compute update ({e})"
- );
- return Ok(());
- }
- };
- let cplane = ComputeControlPlane::load(env.clone())
- .expect("Error loading compute control plane");
-
- let compute_pageservers = self
- .shards
- .iter()
- .map(|(_shard, node_id)| {
- let ps_conf = env
- .get_pageserver_conf(*node_id)
- .expect("Unknown pageserver");
- let (pg_host, pg_port) = parse_host_port(&ps_conf.listen_pg_addr)
- .expect("Unable to parse listen_pg_addr");
- (pg_host, pg_port.unwrap_or(5432))
- })
- .collect::>();
-
- for (endpoint_name, endpoint) in &cplane.endpoints {
- if endpoint.tenant_id == tenant_id && endpoint.status() == "running" {
- tracing::info!("🔁 Reconfiguring endpoint {}", endpoint_name,);
- endpoint.reconfigure(compute_pageservers.clone()).await?;
+ /// Set one shard's location. If stripe size or shard count have changed, Self is reset
+ /// and drops existing content.
+ fn update(
+ &mut self,
+ tenant_shard_id: TenantShardId,
+ stripe_size: ShardStripeSize,
+ node_id: NodeId,
+ ) {
+ match self {
+ Self::Unsharded(existing_node_id) if tenant_shard_id.shard_count.count() == 1 => {
+ *existing_node_id = node_id
+ }
+ Self::Sharded(sharded_tenant)
+ if sharded_tenant.stripe_size == stripe_size
+ && sharded_tenant.shard_count == tenant_shard_id.shard_count =>
+ {
+ if let Some(existing) = sharded_tenant
+ .shards
+ .iter()
+ .position(|s| s.0 == tenant_shard_id.shard_number)
+ {
+ sharded_tenant.shards.get_mut(existing).unwrap().1 = node_id;
+ } else {
+ sharded_tenant
+ .shards
+ .push((tenant_shard_id.shard_number, node_id));
+ sharded_tenant.shards.sort_by_key(|s| s.0)
}
}
- } else {
- tracing::info!(
- "ComputeHookTenant::maybe_reconfigure: not enough shards ({}/{})",
- self.shards.len(),
- shard_count.0
- );
+ _ => {
+ // Shard count changed: reset struct.
+ *self = Self::new(tenant_shard_id, stripe_size, node_id);
+ }
}
+ }
+}
- Ok(())
+#[derive(Serialize, Deserialize, Debug)]
+struct ComputeHookNotifyRequestShard {
+ node_id: NodeId,
+ shard_number: ShardNumber,
+}
+
+/// Request body that we send to the control plane to notify it of where a tenant is attached
+#[derive(Serialize, Deserialize, Debug)]
+struct ComputeHookNotifyRequest {
+ tenant_id: TenantId,
+ stripe_size: Option,
+ shards: Vec,
+}
+
+/// Error type for attempts to call into the control plane compute notification hook
+#[derive(thiserror::Error, Debug)]
+pub(crate) enum NotifyError {
+ // Request was not send successfully, e.g. transport error
+ #[error("Sending request: {0}")]
+ Request(#[from] reqwest::Error),
+ // Request could not be serviced right now due to ongoing Operation in control plane, but should be possible soon.
+ #[error("Control plane tenant busy")]
+ Busy,
+ // Explicit 429 response asking us to retry less frequently
+ #[error("Control plane overloaded")]
+ SlowDown,
+ // A 503 response indicates the control plane can't handle the request right now
+ #[error("Control plane unavailable (status {0})")]
+ Unavailable(StatusCode),
+ // API returned unexpected non-success status. We will retry, but log a warning.
+ #[error("Control plane returned unexpected status {0}")]
+ Unexpected(StatusCode),
+ // We shutdown while sending
+ #[error("Shutting down")]
+ ShuttingDown,
+ // A response indicates we will never succeed, such as 400 or 404
+ #[error("Non-retryable error {0}")]
+ Fatal(StatusCode),
+}
+
+impl ComputeHookTenant {
+ fn maybe_reconfigure(&self, tenant_id: TenantId) -> Option {
+ match self {
+ Self::Unsharded(node_id) => Some(ComputeHookNotifyRequest {
+ tenant_id,
+ shards: vec![ComputeHookNotifyRequestShard {
+ shard_number: ShardNumber(0),
+ node_id: *node_id,
+ }],
+ stripe_size: None,
+ }),
+ Self::Sharded(sharded_tenant)
+ if sharded_tenant.shards.len() == sharded_tenant.shard_count.count() as usize =>
+ {
+ Some(ComputeHookNotifyRequest {
+ tenant_id,
+ shards: sharded_tenant
+ .shards
+ .iter()
+ .map(|(shard_number, node_id)| ComputeHookNotifyRequestShard {
+ shard_number: *shard_number,
+ node_id: *node_id,
+ })
+ .collect(),
+ stripe_size: Some(sharded_tenant.stripe_size),
+ })
+ }
+ Self::Sharded(sharded_tenant) => {
+ // Sharded tenant doesn't yet have information for all its shards
+
+ tracing::info!(
+ "ComputeHookTenant::maybe_reconfigure: not enough shards ({}/{})",
+ sharded_tenant.shards.len(),
+ sharded_tenant.shard_count.count()
+ );
+ None
+ }
+ }
}
}
@@ -74,43 +166,297 @@ impl ComputeHookTenant {
/// mapping. It aggregates updates for the shards in a tenant, and when appropriate reconfigures
/// the compute connection string.
pub(super) struct ComputeHook {
+ config: Config,
state: tokio::sync::Mutex>,
+ authorization_header: Option,
}
impl ComputeHook {
- pub(super) fn new() -> Self {
+ pub(super) fn new(config: Config) -> Self {
+ let authorization_header = config
+ .control_plane_jwt_token
+ .clone()
+ .map(|jwt| format!("Bearer {}", jwt));
+
Self {
state: Default::default(),
+ config,
+ authorization_header,
}
}
+ /// For test environments: use neon_local's LocalEnv to update compute
+ async fn do_notify_local(
+ &self,
+ reconfigure_request: ComputeHookNotifyRequest,
+ ) -> anyhow::Result<()> {
+ let env = match LocalEnv::load_config() {
+ Ok(e) => e,
+ Err(e) => {
+ tracing::warn!("Couldn't load neon_local config, skipping compute update ({e})");
+ return Ok(());
+ }
+ };
+ let cplane =
+ ComputeControlPlane::load(env.clone()).expect("Error loading compute control plane");
+ let ComputeHookNotifyRequest {
+ tenant_id,
+ shards,
+ stripe_size,
+ } = reconfigure_request;
+
+ let compute_pageservers = shards
+ .into_iter()
+ .map(|shard| {
+ let ps_conf = env
+ .get_pageserver_conf(shard.node_id)
+ .expect("Unknown pageserver");
+ let (pg_host, pg_port) = parse_host_port(&ps_conf.listen_pg_addr)
+ .expect("Unable to parse listen_pg_addr");
+ (pg_host, pg_port.unwrap_or(5432))
+ })
+ .collect::>();
+
+ for (endpoint_name, endpoint) in &cplane.endpoints {
+ if endpoint.tenant_id == tenant_id && endpoint.status() == EndpointStatus::Running {
+ tracing::info!("Reconfiguring endpoint {}", endpoint_name,);
+ endpoint
+ .reconfigure(compute_pageservers.clone(), stripe_size)
+ .await?;
+ }
+ }
+
+ Ok(())
+ }
+
+ async fn do_notify_iteration(
+ &self,
+ client: &reqwest::Client,
+ url: &String,
+ reconfigure_request: &ComputeHookNotifyRequest,
+ cancel: &CancellationToken,
+ ) -> Result<(), NotifyError> {
+ let req = client.request(Method::PUT, url);
+ let req = if let Some(value) = &self.authorization_header {
+ req.header(reqwest::header::AUTHORIZATION, value)
+ } else {
+ req
+ };
+
+ tracing::info!(
+ "Sending notify request to {} ({:?})",
+ url,
+ reconfigure_request
+ );
+ let send_result = req.json(&reconfigure_request).send().await;
+ let response = match send_result {
+ Ok(r) => r,
+ Err(e) => return Err(e.into()),
+ };
+
+ // Treat all 2xx responses as success
+ if response.status() >= StatusCode::OK && response.status() < StatusCode::MULTIPLE_CHOICES {
+ if response.status() != StatusCode::OK {
+ // Non-200 2xx response: it doesn't make sense to retry, but this is unexpected, so
+ // log a warning.
+ tracing::warn!(
+ "Unexpected 2xx response code {} from control plane",
+ response.status()
+ );
+ }
+
+ return Ok(());
+ }
+
+ // Error response codes
+ match response.status() {
+ StatusCode::TOO_MANY_REQUESTS => {
+ // TODO: 429 handling should be global: set some state visible to other requests
+ // so that they will delay before starting, rather than all notifications trying
+ // once before backing off.
+ tokio::time::timeout(SLOWDOWN_DELAY, cancel.cancelled())
+ .await
+ .ok();
+ Err(NotifyError::SlowDown)
+ }
+ StatusCode::LOCKED => {
+ // Delay our retry if busy: the usual fast exponential backoff in backoff::retry
+ // is not appropriate
+ tokio::time::timeout(BUSY_DELAY, cancel.cancelled())
+ .await
+ .ok();
+ Err(NotifyError::Busy)
+ }
+ StatusCode::SERVICE_UNAVAILABLE
+ | StatusCode::GATEWAY_TIMEOUT
+ | StatusCode::BAD_GATEWAY => Err(NotifyError::Unavailable(response.status())),
+ StatusCode::BAD_REQUEST | StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => {
+ Err(NotifyError::Fatal(response.status()))
+ }
+ _ => Err(NotifyError::Unexpected(response.status())),
+ }
+ }
+
+ async fn do_notify(
+ &self,
+ url: &String,
+ reconfigure_request: ComputeHookNotifyRequest,
+ cancel: &CancellationToken,
+ ) -> Result<(), NotifyError> {
+ let client = reqwest::Client::new();
+ backoff::retry(
+ || self.do_notify_iteration(&client, url, &reconfigure_request, cancel),
+ |e| matches!(e, NotifyError::Fatal(_) | NotifyError::Unexpected(_)),
+ 3,
+ 10,
+ "Send compute notification",
+ cancel,
+ )
+ .await
+ .ok_or_else(|| NotifyError::ShuttingDown)
+ .and_then(|x| x)
+ }
+
+ /// Call this to notify the compute (postgres) tier of new pageservers to use
+ /// for a tenant. notify() is called by each shard individually, and this function
+ /// will decide whether an update to the tenant is sent. An update is sent on the
+ /// condition that:
+ /// - We know a pageserver for every shard.
+ /// - All the shards have the same shard_count (i.e. we are not mid-split)
+ ///
+ /// Cancellation token enables callers to drop out, e.g. if calling from a Reconciler
+ /// that is cancelled.
+ ///
+ /// This function is fallible, including in the case that the control plane is transiently
+ /// unavailable. A limited number of retries are done internally to efficiently hide short unavailability
+ /// periods, but we don't retry forever. The **caller** is responsible for handling failures and
+ /// ensuring that they eventually call again to ensure that the compute is eventually notified of
+ /// the proper pageserver nodes for a tenant.
+ #[tracing::instrument(skip_all, fields(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug(), node_id))]
pub(super) async fn notify(
&self,
tenant_shard_id: TenantShardId,
node_id: NodeId,
- ) -> anyhow::Result<()> {
- tracing::info!("ComputeHook::notify: {}->{}", tenant_shard_id, node_id);
+ stripe_size: ShardStripeSize,
+ cancel: &CancellationToken,
+ ) -> Result<(), NotifyError> {
let mut locked = self.state.lock().await;
- let entry = locked
- .entry(tenant_shard_id.tenant_id)
- .or_insert_with(|| ComputeHookTenant { shards: Vec::new() });
- let shard_index = ShardIndex {
- shard_count: tenant_shard_id.shard_count,
- shard_number: tenant_shard_id.shard_number,
+ use std::collections::hash_map::Entry;
+ let tenant = match locked.entry(tenant_shard_id.tenant_id) {
+ Entry::Vacant(e) => e.insert(ComputeHookTenant::new(
+ tenant_shard_id,
+ stripe_size,
+ node_id,
+ )),
+ Entry::Occupied(e) => {
+ let tenant = e.into_mut();
+ tenant.update(tenant_shard_id, stripe_size, node_id);
+ tenant
+ }
};
- let mut set = false;
- for (existing_shard, existing_node) in &mut entry.shards {
- if *existing_shard == shard_index {
- *existing_node = node_id;
- set = true;
- }
- }
- if !set {
- entry.shards.push((shard_index, node_id));
- }
+ let reconfigure_request = tenant.maybe_reconfigure(tenant_shard_id.tenant_id);
+ let Some(reconfigure_request) = reconfigure_request else {
+ // The tenant doesn't yet have pageservers for all its shards: we won't notify anything
+ // until it does.
+ tracing::info!("Tenant isn't yet ready to emit a notification");
+ return Ok(());
+ };
- entry.maybe_reconfigure(tenant_shard_id.tenant_id).await
+ if let Some(notify_url) = &self.config.compute_hook_url {
+ self.do_notify(notify_url, reconfigure_request, cancel)
+ .await
+ } else {
+ self.do_notify_local(reconfigure_request)
+ .await
+ .map_err(|e| {
+ // This path is for testing only, so munge the error into our prod-style error type.
+ tracing::error!("Local notification hook failed: {e}");
+ NotifyError::Fatal(StatusCode::INTERNAL_SERVER_ERROR)
+ })
+ }
+ }
+}
+
+#[cfg(test)]
+pub(crate) mod tests {
+ use pageserver_api::shard::{ShardCount, ShardNumber};
+ use utils::id::TenantId;
+
+ use super::*;
+
+ #[test]
+ fn tenant_updates() -> anyhow::Result<()> {
+ let tenant_id = TenantId::generate();
+ let mut tenant_state = ComputeHookTenant::new(
+ TenantShardId {
+ tenant_id,
+ shard_count: ShardCount::new(0),
+ shard_number: ShardNumber(0),
+ },
+ ShardStripeSize(12345),
+ NodeId(1),
+ );
+
+ // An unsharded tenant is always ready to emit a notification
+ assert!(tenant_state.maybe_reconfigure(tenant_id).is_some());
+ assert_eq!(
+ tenant_state
+ .maybe_reconfigure(tenant_id)
+ .unwrap()
+ .shards
+ .len(),
+ 1
+ );
+ assert!(tenant_state
+ .maybe_reconfigure(tenant_id)
+ .unwrap()
+ .stripe_size
+ .is_none());
+
+ // Writing the first shard of a multi-sharded situation (i.e. in a split)
+ // resets the tenant state and puts it in an non-notifying state (need to
+ // see all shards)
+ tenant_state.update(
+ TenantShardId {
+ tenant_id,
+ shard_count: ShardCount::new(2),
+ shard_number: ShardNumber(1),
+ },
+ ShardStripeSize(32768),
+ NodeId(1),
+ );
+ assert!(tenant_state.maybe_reconfigure(tenant_id).is_none());
+
+ // Writing the second shard makes it ready to notify
+ tenant_state.update(
+ TenantShardId {
+ tenant_id,
+ shard_count: ShardCount::new(2),
+ shard_number: ShardNumber(0),
+ },
+ ShardStripeSize(32768),
+ NodeId(1),
+ );
+
+ assert!(tenant_state.maybe_reconfigure(tenant_id).is_some());
+ assert_eq!(
+ tenant_state
+ .maybe_reconfigure(tenant_id)
+ .unwrap()
+ .shards
+ .len(),
+ 2
+ );
+ assert_eq!(
+ tenant_state
+ .maybe_reconfigure(tenant_id)
+ .unwrap()
+ .stripe_size,
+ Some(ShardStripeSize(32768))
+ );
+
+ Ok(())
}
}
diff --git a/control_plane/attachment_service/src/http.rs b/control_plane/attachment_service/src/http.rs
index 30f6dd66ee..27ba5bdb65 100644
--- a/control_plane/attachment_service/src/http.rs
+++ b/control_plane/attachment_service/src/http.rs
@@ -1,14 +1,19 @@
use crate::reconciler::ReconcileError;
-use crate::service::Service;
+use crate::service::{Service, STARTUP_RECONCILE_TIMEOUT};
use hyper::{Body, Request, Response};
use hyper::{StatusCode, Uri};
-use pageserver_api::models::{TenantCreateRequest, TimelineCreateRequest};
+use pageserver_api::models::{
+ TenantConfigRequest, TenantCreateRequest, TenantLocationConfigRequest, TenantShardSplitRequest,
+ TenantTimeTravelRequest, TimelineCreateRequest,
+};
use pageserver_api::shard::TenantShardId;
+use pageserver_client::mgmt_api;
use std::sync::Arc;
-use utils::auth::SwappableJwtAuth;
-use utils::http::endpoint::{auth_middleware, request_span};
-use utils::http::request::parse_request_param;
-use utils::id::TenantId;
+use std::time::{Duration, Instant};
+use utils::auth::{Scope, SwappableJwtAuth};
+use utils::http::endpoint::{auth_middleware, check_permission_with, request_span};
+use utils::http::request::{must_get_query_param, parse_request_param};
+use utils::id::{TenantId, TimelineId};
use utils::{
http::{
@@ -20,12 +25,12 @@ use utils::{
id::NodeId,
};
-use pageserver_api::control_api::{ReAttachRequest, ValidateRequest};
-
-use control_plane::attachment_service::{
- AttachHookRequest, InspectRequest, NodeConfigureRequest, NodeRegisterRequest,
- TenantShardMigrateRequest,
+use pageserver_api::controller_api::{
+ NodeConfigureRequest, NodeRegisterRequest, TenantShardMigrateRequest,
};
+use pageserver_api::upcall_api::{ReAttachRequest, ValidateRequest};
+
+use control_plane::storage_controller::{AttachHookRequest, InspectRequest};
/// State available to HTTP request handlers
#[derive(Clone)]
@@ -37,7 +42,7 @@ pub struct HttpState {
impl HttpState {
pub fn new(service: Arc, auth: Option>) -> Self {
- let allowlist_routes = ["/status"]
+ let allowlist_routes = ["/status", "/ready", "/metrics"]
.iter()
.map(|v| v.parse().unwrap())
.collect::>();
@@ -59,21 +64,18 @@ fn get_state(request: &Request) -> &HttpState {
/// Pageserver calls into this on startup, to learn which tenants it should attach
async fn handle_re_attach(mut req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::GenerationsApi)?;
+
let reattach_req = json_request::(&mut req).await?;
let state = get_state(&req);
- json_response(
- StatusCode::OK,
- state
- .service
- .re_attach(reattach_req)
- .await
- .map_err(ApiError::InternalServerError)?,
- )
+ json_response(StatusCode::OK, state.service.re_attach(reattach_req).await?)
}
/// Pageserver calls into this before doing deletions, to confirm that it still
/// holds the latest generation for the tenants with deletions enqueued
async fn handle_validate(mut req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::GenerationsApi)?;
+
let validate_req = json_request::(&mut req).await?;
let state = get_state(&req);
json_response(StatusCode::OK, state.service.validate(validate_req))
@@ -83,6 +85,8 @@ async fn handle_validate(mut req: Request) -> Result, ApiEr
/// (in the real control plane this is unnecessary, because the same program is managing
/// generation numbers and doing attachments).
async fn handle_attach_hook(mut req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let attach_req = json_request::(&mut req).await?;
let state = get_state(&req);
@@ -97,6 +101,8 @@ async fn handle_attach_hook(mut req: Request) -> Result, Ap
}
async fn handle_inspect(mut req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let inspect_req = json_request::(&mut req).await?;
let state = get_state(&req);
@@ -104,44 +110,272 @@ async fn handle_inspect(mut req: Request) -> Result, ApiErr
json_response(StatusCode::OK, state.service.inspect(inspect_req))
}
-async fn handle_tenant_create(mut req: Request) -> Result, ApiError> {
+async fn handle_tenant_create(
+ service: Arc,
+ mut req: Request,
+) -> Result, ApiError> {
+ check_permissions(&req, Scope::PageServerApi)?;
+
let create_req = json_request::(&mut req).await?;
- let state = get_state(&req);
+
json_response(
- StatusCode::OK,
- state.service.tenant_create(create_req).await?,
+ StatusCode::CREATED,
+ service.tenant_create(create_req).await?,
)
}
-async fn handle_tenant_timeline_create(mut req: Request) -> Result, ApiError> {
- let tenant_id: TenantId = parse_request_param(&req, "tenant_id")?;
- let create_req = json_request::(&mut req).await?;
+// For tenant and timeline deletions, which both implement an "initially return 202, then 404 once
+// we're done" semantic, we wrap with a retry loop to expose a simpler API upstream. This avoids
+// needing to track a "deleting" state for tenants.
+async fn deletion_wrapper(service: Arc, f: F) -> Result, ApiError>
+where
+ R: std::future::Future