mirror of
https://github.com/neondatabase/neon.git
synced 2026-08-16 19:18:22 +00:00
Compare commits
291 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef737e7d7c | |||
| 5c934efb29 | |||
| 5c9c3b3317 | |||
| 921a4f2009 | |||
| eb93c3e3c6 | |||
| 7a7ab2a1d1 | |||
| ff526a1051 | |||
| 9a2456bea5 | |||
| a456e818af | |||
| 3e6fdb0aa6 | |||
| f8d3f86f58 | |||
| f67a8a173e | |||
| 2288efae66 | |||
| 4fedcbc0ac | |||
| eb830fa547 | |||
| a203f9829a | |||
| 42ab34dc36 | |||
| 30b877074c | |||
| f18cc808f0 | |||
| d14d8271b8 | |||
| fecb707b19 | |||
| 296c9190b2 | |||
| a5fe67f361 | |||
| ee7bb1a667 | |||
| 9bba31bf68 | |||
| 380d167b7c | |||
| cb991fba42 | |||
| 4566b12a22 | |||
| 63ca084696 | |||
| 379259bdd7 | |||
| 3300207523 | |||
| a0a7733b5a | |||
| f4245403b3 | |||
| a8db7ebffb | |||
| 154f6dc59c | |||
| 15f633922a | |||
| c34d36d8a2 | |||
| cec0543b51 | |||
| 8aa9540a05 | |||
| b91f821e8b | |||
| 44ea17b7b2 | |||
| 1b7339b53e | |||
| 3593fe195a | |||
| c5aaf1ae21 | |||
| 13b5e7b26f | |||
| dcdfe80bf0 | |||
| 8630d37f5e | |||
| 2fc77c836b | |||
| 2c6b327be6 | |||
| be5bbaecad | |||
| d33b3c7457 | |||
| ffeede085e | |||
| bdca5b500b | |||
| f4b03ddd7b | |||
| 08b19f001c | |||
| 1a45b2ec90 | |||
| 13e38a58a1 | |||
| 2edd59aefb | |||
| 0b639ba608 | |||
| 28f604d628 | |||
| fe0ddb7169 | |||
| 4bbabc092a | |||
| 12c26243fc | |||
| 2f71eda00f | |||
| 5ec82105cc | |||
| 78a6daa874 | |||
| 5c0de4ee8c | |||
| bc6a756f1c | |||
| 8f3351fa91 | |||
| e7d18bc188 | |||
| 4ee0da0a20 | |||
| 7049003cf7 | |||
| 3915995530 | |||
| 5ea0bb2d4f | |||
| aac1f8efb1 | |||
| 43dbded8c8 | |||
| c848b995b2 | |||
| 4dee2bfd82 | |||
| 09ff22a4d4 | |||
| 8223c1ba9d | |||
| 3dad4698ec | |||
| 81e7218c27 | |||
| a06c560ad0 | |||
| 477ab12b69 | |||
| f9b05a42d7 | |||
| 29d73e1404 | |||
| 8a042fb8ed | |||
| f72115d0a9 | |||
| 7458d031b1 | |||
| 38384c37ac | |||
| 2b2a547671 | |||
| 59e393aef3 | |||
| f51ed4a2c4 | |||
| 4f16ab3f56 | |||
| 18796fd1dd | |||
| 2f3fc7cb57 | |||
| e65d5f7369 | |||
| 55aef2993d | |||
| 1eef961f09 | |||
| fc10bb9438 | |||
| 4b5c75b52f | |||
| ca9d8761ff | |||
| b568189f7b | |||
| b94a5ce119 | |||
| 7ed4530618 | |||
| 3a44774227 | |||
| b2705cfee6 | |||
| 225267b3ae | |||
| d378726e38 | |||
| 436a117c15 | |||
| cc699f6f85 | |||
| 495112ca50 | |||
| 46158ee63f | |||
| 305fe61ac1 | |||
| f95fdf5b44 | |||
| a852bc5e39 | |||
| b96983a31c | |||
| 3ed28661b1 | |||
| 03e604e432 | |||
| 4db934407a | |||
| 95e1011cd6 | |||
| 1bc1eae5e8 | |||
| e12d4f356a | |||
| 3415b90e88 | |||
| e01c8f238c | |||
| 45607cbe0c | |||
| 8b4fbefc29 | |||
| a9a51c038b | |||
| 44121cc175 | |||
| 0429a0db16 | |||
| d6beb3ffbb | |||
| efd7e52812 | |||
| 0f879a2e8f | |||
| 8e7ce42229 | |||
| 5ec8881c0b | |||
| b254dce8a1 | |||
| 3815e3b2b5 | |||
| bbcd70eab3 | |||
| 0934ce9bce | |||
| 4932963bac | |||
| 6d73cfa608 | |||
| d2d9946bab | |||
| daa402f35a | |||
| 5f3532970e | |||
| 2e681e0ef8 | |||
| 8e216a3a59 | |||
| d0a4ae3e8f | |||
| a384d7d501 | |||
| 66f53d9d34 | |||
| 2af9380962 | |||
| 620d50432c | |||
| 1d43f3bee8 | |||
| c746678bbc | |||
| 9bb4688c54 | |||
| 47553dbaf9 | |||
| e50b914a8e | |||
| e33e109403 | |||
| 0ee15002fc | |||
| 4c7956fa56 | |||
| 5a82182c48 | |||
| 37e181af8a | |||
| 6f4198c78a | |||
| cc1664ef93 | |||
| ebb6e26a64 | |||
| ebc12a388c | |||
| abc1efd5a6 | |||
| 6fa1562b57 | |||
| 10afac87e7 | |||
| 72b3c9cd11 | |||
| 232f2447d4 | |||
| a2d2108e6a | |||
| 33c0d5e2f4 | |||
| 605fb04f89 | |||
| fd1e8ec257 | |||
| be23eae3b6 | |||
| 6f70885e11 | |||
| f755979102 | |||
| 1d49eefbbb | |||
| 6c77638ea1 | |||
| 517a3d0d86 | |||
| 27ca1e21be | |||
| 1dc01c9bed | |||
| 7c4c36f5ac | |||
| a2d623696c | |||
| aa75722010 | |||
| 6c6de6382a | |||
| 158d84ea30 | |||
| 4dd9ca7b04 | |||
| 552249607d | |||
| a29772bf6e | |||
| 0efff1db26 | |||
| 5eecde461d | |||
| 85164422d0 | |||
| 6c3aba7c44 | |||
| 68a175d545 | |||
| 5e2c444525 | |||
| 8d711229c1 | |||
| 0e490f3be7 | |||
| 7e41ef1bec | |||
| 7916aa26e0 | |||
| 52ab8f3e65 | |||
| 3d822dbbde | |||
| af46b5286f | |||
| 47f7efee06 | |||
| 868c38f522 | |||
| c8b2ac93cf | |||
| b2954d16ff | |||
| 79485e7c3a | |||
| eaf1ab21c4 | |||
| 6508f4e5c1 | |||
| a298d2c29b | |||
| 8b197de7ff | |||
| 15d079cd41 | |||
| dc1625cd8e | |||
| a6d4de25cd | |||
| ec1452a559 | |||
| 1950ccfe33 | |||
| 2ca6665f4a | |||
| fa954671b2 | |||
| 6f4ffdb48b | |||
| 3f676df3d5 | |||
| 20f4febce1 | |||
| 762905cf8d | |||
| 830ef35ed3 | |||
| d8d62fb7cb | |||
| e6a404c66d | |||
| 7e711ede44 | |||
| e95f2f9a67 | |||
| 5a045e7d52 | |||
| 67fbc0582e | |||
| 3af6b3a2bf | |||
| 04013929cb | |||
| 83069f6ca1 | |||
| 7d4f662fbf | |||
| a5cac52e26 | |||
| dfa055f4be | |||
| a4c76740c0 | |||
| f2e96b2323 | |||
| dee73f0cb4 | |||
| edf51688bc | |||
| 4a8f3508f9 | |||
| 48052477b4 | |||
| d81353b2d1 | |||
| 143500dc4f | |||
| 1a5f7ce6ad | |||
| 01ccb34118 | |||
| f669e18477 | |||
| 632cde7f13 | |||
| 118e13438d | |||
| fc136eec8f | |||
| 818e5130f1 | |||
| c243521ae5 | |||
| 5303c71589 | |||
| d146897415 | |||
| d63815fa40 | |||
| 385324ee8a | |||
| 8a68d463f6 | |||
| 3046c307da | |||
| e83f1d8ba5 | |||
| 8917676e86 | |||
| 43acabd4c2 | |||
| db24ba95d1 | |||
| 1dce65308d | |||
| ad88ec9257 | |||
| 60dfdf39c7 | |||
| 3d5e2bf685 | |||
| 54fdcfdfa8 | |||
| 28e882a80f | |||
| 24038033bf | |||
| 1b935b1958 | |||
| 3f16ca2c18 | |||
| 67b94c5992 | |||
| e38193c530 | |||
| 21949137ed | |||
| 02f94edb60 | |||
| 58327ef74d | |||
| 73be6bb736 | |||
| 40d7583906 | |||
| 7a68699abb | |||
| f42d44342d | |||
| d759fcb8bd | |||
| 76f95f06d8 | |||
| 7efd4554ab | |||
| 3c7235669a | |||
| 6dd84041a1 | |||
| df7e301a54 | |||
| 470c7d5e0e | |||
| 4d99b6ff4d | |||
| 590301df08 | |||
| c511786548 | |||
| fe31baf985 |
@@ -33,6 +33,7 @@ workspace-members = [
|
|||||||
"compute_api",
|
"compute_api",
|
||||||
"consumption_metrics",
|
"consumption_metrics",
|
||||||
"desim",
|
"desim",
|
||||||
|
"json",
|
||||||
"metrics",
|
"metrics",
|
||||||
"pageserver_api",
|
"pageserver_api",
|
||||||
"postgres_backend",
|
"postgres_backend",
|
||||||
|
|||||||
+2
-1
@@ -4,6 +4,7 @@
|
|||||||
!Cargo.lock
|
!Cargo.lock
|
||||||
!Cargo.toml
|
!Cargo.toml
|
||||||
!Makefile
|
!Makefile
|
||||||
|
!postgres.mk
|
||||||
!rust-toolchain.toml
|
!rust-toolchain.toml
|
||||||
!scripts/ninstall.sh
|
!scripts/ninstall.sh
|
||||||
!docker-compose/run-tests.sh
|
!docker-compose/run-tests.sh
|
||||||
@@ -26,4 +27,4 @@
|
|||||||
!storage_controller/
|
!storage_controller/
|
||||||
!vendor/postgres-*/
|
!vendor/postgres-*/
|
||||||
!workspace_hack/
|
!workspace_hack/
|
||||||
!build_tools/patches
|
!build-tools/patches
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ self-hosted-runner:
|
|||||||
- small-metal
|
- small-metal
|
||||||
- small-arm64
|
- small-arm64
|
||||||
- unit-perf
|
- unit-perf
|
||||||
|
- unit-perf-aws-arm
|
||||||
- us-east-2
|
- us-east-2
|
||||||
config-variables:
|
config-variables:
|
||||||
- AWS_ECR_REGION
|
- AWS_ECR_REGION
|
||||||
@@ -30,6 +31,7 @@ config-variables:
|
|||||||
- NEON_PROD_AWS_ACCOUNT_ID
|
- NEON_PROD_AWS_ACCOUNT_ID
|
||||||
- PGREGRESS_PG16_PROJECT_ID
|
- PGREGRESS_PG16_PROJECT_ID
|
||||||
- PGREGRESS_PG17_PROJECT_ID
|
- PGREGRESS_PG17_PROJECT_ID
|
||||||
|
- PREWARM_PGBENCH_SIZE
|
||||||
- REMOTE_STORAGE_AZURE_CONTAINER
|
- REMOTE_STORAGE_AZURE_CONTAINER
|
||||||
- REMOTE_STORAGE_AZURE_REGION
|
- REMOTE_STORAGE_AZURE_REGION
|
||||||
- SLACK_CICD_CHANNEL_ID
|
- SLACK_CICD_CHANNEL_ID
|
||||||
|
|||||||
@@ -176,7 +176,11 @@ runs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $BUILD_TYPE == "debug" && $RUNNER_ARCH == 'X64' ]]; then
|
if [[ $BUILD_TYPE == "debug" && $RUNNER_ARCH == 'X64' ]]; then
|
||||||
cov_prefix=(scripts/coverage "--profraw-prefix=$GITHUB_JOB" --dir=/tmp/coverage run)
|
# We don't use code coverage for regression tests (the step is disabled),
|
||||||
|
# so there's no need to collect it.
|
||||||
|
# Ref https://github.com/neondatabase/neon/issues/4540
|
||||||
|
# cov_prefix=(scripts/coverage "--profraw-prefix=$GITHUB_JOB" --dir=/tmp/coverage run)
|
||||||
|
cov_prefix=()
|
||||||
else
|
else
|
||||||
cov_prefix=()
|
cov_prefix=()
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
default: 1
|
default: 1
|
||||||
type: number
|
type: number
|
||||||
|
rerun-failed:
|
||||||
|
description: 'rerun failed tests to ignore flaky tests'
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -99,11 +104,10 @@ jobs:
|
|||||||
|
|
||||||
# Set some environment variables used by all the steps.
|
# Set some environment variables used by all the steps.
|
||||||
#
|
#
|
||||||
# CARGO_FLAGS is extra options to pass to "cargo build", "cargo test" etc.
|
# CARGO_FLAGS is extra options to pass to all "cargo" subcommands.
|
||||||
# It also includes --features, if any
|
|
||||||
#
|
#
|
||||||
# CARGO_FEATURES is passed to "cargo metadata". It is separate from CARGO_FLAGS,
|
# CARGO_PROFILE is passed to "cargo build", "cargo test" etc, but not to
|
||||||
# because "cargo metadata" doesn't accept --release or --debug options
|
# "cargo metadata", because it doesn't accept --release or --debug options.
|
||||||
#
|
#
|
||||||
# We run tests with addtional features, that are turned off by default (e.g. in release builds), see
|
# We run tests with addtional features, that are turned off by default (e.g. in release builds), see
|
||||||
# corresponding Cargo.toml files for their descriptions.
|
# corresponding Cargo.toml files for their descriptions.
|
||||||
@@ -112,16 +116,16 @@ jobs:
|
|||||||
ARCH: ${{ inputs.arch }}
|
ARCH: ${{ inputs.arch }}
|
||||||
SANITIZERS: ${{ inputs.sanitizers }}
|
SANITIZERS: ${{ inputs.sanitizers }}
|
||||||
run: |
|
run: |
|
||||||
CARGO_FEATURES="--features testing"
|
CARGO_FLAGS="--locked --features testing"
|
||||||
if [[ $BUILD_TYPE == "debug" && $ARCH == 'x64' ]]; then
|
if [[ $BUILD_TYPE == "debug" && $ARCH == 'x64' ]]; then
|
||||||
cov_prefix="scripts/coverage --profraw-prefix=$GITHUB_JOB --dir=/tmp/coverage run"
|
cov_prefix="scripts/coverage --profraw-prefix=$GITHUB_JOB --dir=/tmp/coverage run"
|
||||||
CARGO_FLAGS="--locked"
|
CARGO_PROFILE=""
|
||||||
elif [[ $BUILD_TYPE == "debug" ]]; then
|
elif [[ $BUILD_TYPE == "debug" ]]; then
|
||||||
cov_prefix=""
|
cov_prefix=""
|
||||||
CARGO_FLAGS="--locked"
|
CARGO_PROFILE=""
|
||||||
elif [[ $BUILD_TYPE == "release" ]]; then
|
elif [[ $BUILD_TYPE == "release" ]]; then
|
||||||
cov_prefix=""
|
cov_prefix=""
|
||||||
CARGO_FLAGS="--locked --release"
|
CARGO_PROFILE="--release"
|
||||||
fi
|
fi
|
||||||
if [[ $SANITIZERS == 'enabled' ]]; then
|
if [[ $SANITIZERS == 'enabled' ]]; then
|
||||||
make_vars="WITH_SANITIZERS=yes"
|
make_vars="WITH_SANITIZERS=yes"
|
||||||
@@ -131,8 +135,8 @@ jobs:
|
|||||||
{
|
{
|
||||||
echo "cov_prefix=${cov_prefix}"
|
echo "cov_prefix=${cov_prefix}"
|
||||||
echo "make_vars=${make_vars}"
|
echo "make_vars=${make_vars}"
|
||||||
echo "CARGO_FEATURES=${CARGO_FEATURES}"
|
|
||||||
echo "CARGO_FLAGS=${CARGO_FLAGS}"
|
echo "CARGO_FLAGS=${CARGO_FLAGS}"
|
||||||
|
echo "CARGO_PROFILE=${CARGO_PROFILE}"
|
||||||
echo "CARGO_HOME=${GITHUB_WORKSPACE}/.cargo"
|
echo "CARGO_HOME=${GITHUB_WORKSPACE}/.cargo"
|
||||||
} >> $GITHUB_ENV
|
} >> $GITHUB_ENV
|
||||||
|
|
||||||
@@ -146,7 +150,7 @@ jobs:
|
|||||||
secretKey: ${{ secrets.HETZNER_CACHE_SECRET_KEY }}
|
secretKey: ${{ secrets.HETZNER_CACHE_SECRET_KEY }}
|
||||||
use-fallback: false
|
use-fallback: false
|
||||||
path: pg_install/v14
|
path: pg_install/v14
|
||||||
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ inputs.build-type }}-pg-${{ steps.pg_v14_rev.outputs.pg_rev }}-bookworm-${{ hashFiles('Makefile', 'build-tools.Dockerfile') }}
|
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ inputs.build-type }}-pg-${{ steps.pg_v14_rev.outputs.pg_rev }}-bookworm-${{ hashFiles('Makefile', 'build-tools/Dockerfile') }}
|
||||||
|
|
||||||
- name: Cache postgres v15 build
|
- name: Cache postgres v15 build
|
||||||
id: cache_pg_15
|
id: cache_pg_15
|
||||||
@@ -158,7 +162,7 @@ jobs:
|
|||||||
secretKey: ${{ secrets.HETZNER_CACHE_SECRET_KEY }}
|
secretKey: ${{ secrets.HETZNER_CACHE_SECRET_KEY }}
|
||||||
use-fallback: false
|
use-fallback: false
|
||||||
path: pg_install/v15
|
path: pg_install/v15
|
||||||
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ inputs.build-type }}-pg-${{ steps.pg_v15_rev.outputs.pg_rev }}-bookworm-${{ hashFiles('Makefile', 'build-tools.Dockerfile') }}
|
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ inputs.build-type }}-pg-${{ steps.pg_v15_rev.outputs.pg_rev }}-bookworm-${{ hashFiles('Makefile', 'build-tools/Dockerfile') }}
|
||||||
|
|
||||||
- name: Cache postgres v16 build
|
- name: Cache postgres v16 build
|
||||||
id: cache_pg_16
|
id: cache_pg_16
|
||||||
@@ -170,7 +174,7 @@ jobs:
|
|||||||
secretKey: ${{ secrets.HETZNER_CACHE_SECRET_KEY }}
|
secretKey: ${{ secrets.HETZNER_CACHE_SECRET_KEY }}
|
||||||
use-fallback: false
|
use-fallback: false
|
||||||
path: pg_install/v16
|
path: pg_install/v16
|
||||||
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ inputs.build-type }}-pg-${{ steps.pg_v16_rev.outputs.pg_rev }}-bookworm-${{ hashFiles('Makefile', 'build-tools.Dockerfile') }}
|
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ inputs.build-type }}-pg-${{ steps.pg_v16_rev.outputs.pg_rev }}-bookworm-${{ hashFiles('Makefile', 'build-tools/Dockerfile') }}
|
||||||
|
|
||||||
- name: Cache postgres v17 build
|
- name: Cache postgres v17 build
|
||||||
id: cache_pg_17
|
id: cache_pg_17
|
||||||
@@ -182,36 +186,20 @@ jobs:
|
|||||||
secretKey: ${{ secrets.HETZNER_CACHE_SECRET_KEY }}
|
secretKey: ${{ secrets.HETZNER_CACHE_SECRET_KEY }}
|
||||||
use-fallback: false
|
use-fallback: false
|
||||||
path: pg_install/v17
|
path: pg_install/v17
|
||||||
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ inputs.build-type }}-pg-${{ steps.pg_v17_rev.outputs.pg_rev }}-bookworm-${{ hashFiles('Makefile', 'build-tools.Dockerfile') }}
|
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ inputs.build-type }}-pg-${{ steps.pg_v17_rev.outputs.pg_rev }}-bookworm-${{ hashFiles('Makefile', 'build-tools/Dockerfile') }}
|
||||||
|
|
||||||
- name: Build postgres v14
|
- name: Build all
|
||||||
if: steps.cache_pg_14.outputs.cache-hit != 'true'
|
# Note: the Makefile picks up BUILD_TYPE and CARGO_PROFILE from the env variables
|
||||||
run: mold -run make ${make_vars} postgres-v14 -j$(nproc)
|
run: mold -run make ${make_vars} all -j$(nproc) CARGO_BUILD_FLAGS="$CARGO_FLAGS"
|
||||||
|
|
||||||
- name: Build postgres v15
|
|
||||||
if: steps.cache_pg_15.outputs.cache-hit != 'true'
|
|
||||||
run: mold -run make ${make_vars} postgres-v15 -j$(nproc)
|
|
||||||
|
|
||||||
- name: Build postgres v16
|
|
||||||
if: steps.cache_pg_16.outputs.cache-hit != 'true'
|
|
||||||
run: mold -run make ${make_vars} postgres-v16 -j$(nproc)
|
|
||||||
|
|
||||||
- name: Build postgres v17
|
|
||||||
if: steps.cache_pg_17.outputs.cache-hit != 'true'
|
|
||||||
run: mold -run make ${make_vars} postgres-v17 -j$(nproc)
|
|
||||||
|
|
||||||
- name: Build neon extensions
|
|
||||||
run: mold -run make ${make_vars} neon-pg-ext -j$(nproc)
|
|
||||||
|
|
||||||
- name: Build walproposer-lib
|
- name: Build walproposer-lib
|
||||||
run: mold -run make ${make_vars} walproposer-lib -j$(nproc)
|
run: mold -run make ${make_vars} walproposer-lib -j$(nproc)
|
||||||
|
|
||||||
- name: Run cargo build
|
- name: Build unit tests
|
||||||
env:
|
if: inputs.sanitizers != 'enabled'
|
||||||
WITH_TESTS: ${{ inputs.sanitizers != 'enabled' && '--tests' || '' }}
|
|
||||||
run: |
|
run: |
|
||||||
export ASAN_OPTIONS=detect_leaks=0
|
export ASAN_OPTIONS=detect_leaks=0
|
||||||
${cov_prefix} mold -run cargo build $CARGO_FLAGS $CARGO_FEATURES --bins ${WITH_TESTS}
|
${cov_prefix} mold -run cargo build $CARGO_FLAGS $CARGO_PROFILE --tests
|
||||||
|
|
||||||
# Do install *before* running rust tests because they might recompile the
|
# Do install *before* running rust tests because they might recompile the
|
||||||
# binaries with different features/flags.
|
# binaries with different features/flags.
|
||||||
@@ -223,7 +211,7 @@ jobs:
|
|||||||
# Install target binaries
|
# Install target binaries
|
||||||
mkdir -p /tmp/neon/bin/
|
mkdir -p /tmp/neon/bin/
|
||||||
binaries=$(
|
binaries=$(
|
||||||
${cov_prefix} cargo metadata $CARGO_FEATURES --format-version=1 --no-deps |
|
${cov_prefix} cargo metadata $CARGO_FLAGS --format-version=1 --no-deps |
|
||||||
jq -r '.packages[].targets[] | select(.kind | index("bin")) | .name'
|
jq -r '.packages[].targets[] | select(.kind | index("bin")) | .name'
|
||||||
)
|
)
|
||||||
for bin in $binaries; do
|
for bin in $binaries; do
|
||||||
@@ -240,7 +228,7 @@ jobs:
|
|||||||
mkdir -p /tmp/neon/test_bin/
|
mkdir -p /tmp/neon/test_bin/
|
||||||
|
|
||||||
test_exe_paths=$(
|
test_exe_paths=$(
|
||||||
${cov_prefix} cargo test $CARGO_FLAGS $CARGO_FEATURES --message-format=json --no-run |
|
${cov_prefix} cargo test $CARGO_FLAGS $CARGO_PROFILE --message-format=json --no-run |
|
||||||
jq -r '.executable | select(. != null)'
|
jq -r '.executable | select(. != null)'
|
||||||
)
|
)
|
||||||
for bin in $test_exe_paths; do
|
for bin in $test_exe_paths; do
|
||||||
@@ -274,10 +262,10 @@ jobs:
|
|||||||
export LD_LIBRARY_PATH
|
export LD_LIBRARY_PATH
|
||||||
|
|
||||||
#nextest does not yet support running doctests
|
#nextest does not yet support running doctests
|
||||||
${cov_prefix} cargo test --doc $CARGO_FLAGS $CARGO_FEATURES
|
${cov_prefix} cargo test --doc $CARGO_FLAGS $CARGO_PROFILE
|
||||||
|
|
||||||
# run all non-pageserver tests
|
# run all non-pageserver tests
|
||||||
${cov_prefix} cargo nextest run $CARGO_FLAGS $CARGO_FEATURES -E '!package(pageserver)'
|
${cov_prefix} cargo nextest run $CARGO_FLAGS $CARGO_PROFILE -E '!package(pageserver)'
|
||||||
|
|
||||||
# run pageserver tests
|
# run pageserver tests
|
||||||
# (When developing new pageserver features gated by config fields, we commonly make the rust
|
# (When developing new pageserver features gated by config fields, we commonly make the rust
|
||||||
@@ -286,13 +274,13 @@ jobs:
|
|||||||
# pageserver tests from non-pageserver tests cuts down the time it takes for this CI step.)
|
# pageserver tests from non-pageserver tests cuts down the time it takes for this CI step.)
|
||||||
NEON_PAGESERVER_UNIT_TEST_VIRTUAL_FILE_IOENGINE=tokio-epoll-uring \
|
NEON_PAGESERVER_UNIT_TEST_VIRTUAL_FILE_IOENGINE=tokio-epoll-uring \
|
||||||
${cov_prefix} \
|
${cov_prefix} \
|
||||||
cargo nextest run $CARGO_FLAGS $CARGO_FEATURES -E 'package(pageserver)'
|
cargo nextest run $CARGO_FLAGS $CARGO_PROFILE -E 'package(pageserver)'
|
||||||
|
|
||||||
# Run separate tests for real S3
|
# Run separate tests for real S3
|
||||||
export ENABLE_REAL_S3_REMOTE_STORAGE=nonempty
|
export ENABLE_REAL_S3_REMOTE_STORAGE=nonempty
|
||||||
export REMOTE_STORAGE_S3_BUCKET=neon-github-ci-tests
|
export REMOTE_STORAGE_S3_BUCKET=neon-github-ci-tests
|
||||||
export REMOTE_STORAGE_S3_REGION=eu-central-1
|
export REMOTE_STORAGE_S3_REGION=eu-central-1
|
||||||
${cov_prefix} cargo nextest run $CARGO_FLAGS $CARGO_FEATURES -E 'package(remote_storage)' -E 'test(test_real_s3)'
|
${cov_prefix} cargo nextest run $CARGO_FLAGS $CARGO_PROFILE -E 'package(remote_storage)' -E 'test(test_real_s3)'
|
||||||
|
|
||||||
# Run separate tests for real Azure Blob Storage
|
# Run separate tests for real Azure Blob Storage
|
||||||
# XXX: replace region with `eu-central-1`-like region
|
# XXX: replace region with `eu-central-1`-like region
|
||||||
@@ -301,17 +289,17 @@ jobs:
|
|||||||
export AZURE_STORAGE_ACCESS_KEY="${{ secrets.AZURE_STORAGE_ACCESS_KEY_DEV }}"
|
export AZURE_STORAGE_ACCESS_KEY="${{ secrets.AZURE_STORAGE_ACCESS_KEY_DEV }}"
|
||||||
export REMOTE_STORAGE_AZURE_CONTAINER="${{ vars.REMOTE_STORAGE_AZURE_CONTAINER }}"
|
export REMOTE_STORAGE_AZURE_CONTAINER="${{ vars.REMOTE_STORAGE_AZURE_CONTAINER }}"
|
||||||
export REMOTE_STORAGE_AZURE_REGION="${{ vars.REMOTE_STORAGE_AZURE_REGION }}"
|
export REMOTE_STORAGE_AZURE_REGION="${{ vars.REMOTE_STORAGE_AZURE_REGION }}"
|
||||||
${cov_prefix} cargo nextest run $CARGO_FLAGS $CARGO_FEATURES -E 'package(remote_storage)' -E 'test(test_real_azure)'
|
${cov_prefix} cargo nextest run $CARGO_FLAGS $CARGO_PROFILE -E 'package(remote_storage)' -E 'test(test_real_azure)'
|
||||||
|
|
||||||
- name: Install postgres binaries
|
- name: Install postgres binaries
|
||||||
run: |
|
run: |
|
||||||
# Use tar to copy files matching the pattern, preserving the paths in the destionation
|
# Use tar to copy files matching the pattern, preserving the paths in the destionation
|
||||||
tar c \
|
tar c \
|
||||||
pg_install/v* \
|
pg_install/v* \
|
||||||
pg_install/build/*/src/test/regress/*.so \
|
build/*/src/test/regress/*.so \
|
||||||
pg_install/build/*/src/test/regress/pg_regress \
|
build/*/src/test/regress/pg_regress \
|
||||||
pg_install/build/*/src/test/isolation/isolationtester \
|
build/*/src/test/isolation/isolationtester \
|
||||||
pg_install/build/*/src/test/isolation/pg_isolation_regress \
|
build/*/src/test/isolation/pg_isolation_regress \
|
||||||
| tar x -C /tmp/neon
|
| tar x -C /tmp/neon
|
||||||
|
|
||||||
- name: Upload Neon artifact
|
- name: Upload Neon artifact
|
||||||
@@ -379,7 +367,7 @@ jobs:
|
|||||||
- name: Pytest regression tests
|
- name: Pytest regression tests
|
||||||
continue-on-error: ${{ matrix.lfc_state == 'with-lfc' && inputs.build-type == 'debug' }}
|
continue-on-error: ${{ matrix.lfc_state == 'with-lfc' && inputs.build-type == 'debug' }}
|
||||||
uses: ./.github/actions/run-python-test-set
|
uses: ./.github/actions/run-python-test-set
|
||||||
timeout-minutes: ${{ inputs.sanitizers != 'enabled' && 75 || 180 }}
|
timeout-minutes: ${{ (inputs.build-type == 'release' && inputs.sanitizers != 'enabled') && 75 || 180 }}
|
||||||
with:
|
with:
|
||||||
build_type: ${{ inputs.build-type }}
|
build_type: ${{ inputs.build-type }}
|
||||||
test_selection: regress
|
test_selection: regress
|
||||||
@@ -387,14 +375,14 @@ jobs:
|
|||||||
run_with_real_s3: true
|
run_with_real_s3: true
|
||||||
real_s3_bucket: neon-github-ci-tests
|
real_s3_bucket: neon-github-ci-tests
|
||||||
real_s3_region: eu-central-1
|
real_s3_region: eu-central-1
|
||||||
rerun_failed: ${{ inputs.test-run-count == 1 }}
|
rerun_failed: ${{ inputs.rerun-failed }}
|
||||||
pg_version: ${{ matrix.pg_version }}
|
pg_version: ${{ matrix.pg_version }}
|
||||||
sanitizers: ${{ inputs.sanitizers }}
|
sanitizers: ${{ inputs.sanitizers }}
|
||||||
aws-oidc-role-arn: ${{ vars.DEV_AWS_OIDC_ROLE_ARN }}
|
aws-oidc-role-arn: ${{ vars.DEV_AWS_OIDC_ROLE_ARN }}
|
||||||
# `--session-timeout` is equal to (timeout-minutes - 10 minutes) * 60 seconds.
|
# `--session-timeout` is equal to (timeout-minutes - 10 minutes) * 60 seconds.
|
||||||
# Attempt to stop tests gracefully to generate test reports
|
# Attempt to stop tests gracefully to generate test reports
|
||||||
# until they are forcibly stopped by the stricter `timeout-minutes` limit.
|
# until they are forcibly stopped by the stricter `timeout-minutes` limit.
|
||||||
extra_params: --session-timeout=${{ inputs.sanitizers != 'enabled' && 3000 || 10200 }} --count=${{ inputs.test-run-count }}
|
extra_params: --session-timeout=${{ (inputs.build-type == 'release' && inputs.sanitizers != 'enabled') && 3000 || 10200 }} --count=${{ inputs.test-run-count }}
|
||||||
${{ inputs.test-selection != '' && format('-k "{0}"', inputs.test-selection) || '' }}
|
${{ inputs.test-selection != '' && format('-k "{0}"', inputs.test-selection) || '' }}
|
||||||
env:
|
env:
|
||||||
TEST_RESULT_CONNSTR: ${{ secrets.REGRESS_TEST_RESULT_CONNSTR_NEW }}
|
TEST_RESULT_CONNSTR: ${{ secrets.REGRESS_TEST_RESULT_CONNSTR_NEW }}
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ jobs:
|
|||||||
--ignore test_runner/performance/test_cumulative_statistics_persistence.py
|
--ignore test_runner/performance/test_cumulative_statistics_persistence.py
|
||||||
--ignore test_runner/performance/test_perf_many_relations.py
|
--ignore test_runner/performance/test_perf_many_relations.py
|
||||||
--ignore test_runner/performance/test_perf_oltp_large_tenant.py
|
--ignore test_runner/performance/test_perf_oltp_large_tenant.py
|
||||||
|
--ignore test_runner/performance/test_lfc_prewarm.py
|
||||||
env:
|
env:
|
||||||
BENCHMARK_CONNSTR: ${{ steps.create-neon-project.outputs.dsn }}
|
BENCHMARK_CONNSTR: ${{ steps.create-neon-project.outputs.dsn }}
|
||||||
VIP_VAP_ACCESS_TOKEN: "${{ secrets.VIP_VAP_ACCESS_TOKEN }}"
|
VIP_VAP_ACCESS_TOKEN: "${{ secrets.VIP_VAP_ACCESS_TOKEN }}"
|
||||||
@@ -410,6 +411,77 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||||
|
|
||||||
|
prewarm-test:
|
||||||
|
if: ${{ github.event.inputs.run_only_pgvector_tests == 'false' || github.event.inputs.run_only_pgvector_tests == null }}
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
statuses: write
|
||||||
|
id-token: write # aws-actions/configure-aws-credentials
|
||||||
|
env:
|
||||||
|
PGBENCH_SIZE: ${{ vars.PREWARM_PGBENCH_SIZE }}
|
||||||
|
POSTGRES_DISTRIB_DIR: /tmp/neon/pg_install
|
||||||
|
DEFAULT_PG_VERSION: 17
|
||||||
|
TEST_OUTPUT: /tmp/test_output
|
||||||
|
BUILD_TYPE: remote
|
||||||
|
SAVE_PERF_REPORT: ${{ github.event.inputs.save_perf_report || ( github.ref_name == 'main' ) }}
|
||||||
|
PLATFORM: "neon-staging"
|
||||||
|
|
||||||
|
runs-on: [ self-hosted, us-east-2, x64 ]
|
||||||
|
container:
|
||||||
|
image: ghcr.io/neondatabase/build-tools:pinned-bookworm
|
||||||
|
credentials:
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
options: --init
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
|
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
|
||||||
|
with:
|
||||||
|
aws-region: eu-central-1
|
||||||
|
role-to-assume: ${{ vars.DEV_AWS_OIDC_ROLE_ARN }}
|
||||||
|
role-duration-seconds: 18000 # 5 hours
|
||||||
|
|
||||||
|
- name: Download Neon artifact
|
||||||
|
uses: ./.github/actions/download
|
||||||
|
with:
|
||||||
|
name: neon-${{ runner.os }}-${{ runner.arch }}-release-artifact
|
||||||
|
path: /tmp/neon/
|
||||||
|
prefix: latest
|
||||||
|
aws-oidc-role-arn: ${{ vars.DEV_AWS_OIDC_ROLE_ARN }}
|
||||||
|
|
||||||
|
- name: Run prewarm benchmark
|
||||||
|
uses: ./.github/actions/run-python-test-set
|
||||||
|
with:
|
||||||
|
build_type: ${{ env.BUILD_TYPE }}
|
||||||
|
test_selection: performance/test_lfc_prewarm.py
|
||||||
|
run_in_parallel: false
|
||||||
|
save_perf_report: ${{ env.SAVE_PERF_REPORT }}
|
||||||
|
extra_params: -m remote_cluster --timeout 5400
|
||||||
|
pg_version: ${{ env.DEFAULT_PG_VERSION }}
|
||||||
|
aws-oidc-role-arn: ${{ vars.DEV_AWS_OIDC_ROLE_ARN }}
|
||||||
|
env:
|
||||||
|
VIP_VAP_ACCESS_TOKEN: "${{ secrets.VIP_VAP_ACCESS_TOKEN }}"
|
||||||
|
PERF_TEST_RESULT_CONNSTR: "${{ secrets.PERF_TEST_RESULT_CONNSTR }}"
|
||||||
|
NEON_API_KEY: ${{ secrets.NEON_STAGING_API_KEY }}
|
||||||
|
|
||||||
|
- name: Create Allure report
|
||||||
|
id: create-allure-report
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: ./.github/actions/allure-report-generate
|
||||||
|
with:
|
||||||
|
store-test-results-into-db: true
|
||||||
|
aws-oidc-role-arn: ${{ vars.DEV_AWS_OIDC_ROLE_ARN }}
|
||||||
|
env:
|
||||||
|
REGRESS_TEST_RESULT_CONNSTR_NEW: ${{ secrets.REGRESS_TEST_RESULT_CONNSTR_NEW }}
|
||||||
|
|
||||||
generate-matrices:
|
generate-matrices:
|
||||||
if: ${{ github.event.inputs.run_only_pgvector_tests == 'false' || github.event.inputs.run_only_pgvector_tests == null }}
|
if: ${{ github.event.inputs.run_only_pgvector_tests == 'false' || github.event.inputs.run_only_pgvector_tests == null }}
|
||||||
# Create matrices for the benchmarking jobs, so we run benchmarks on rds only once a week (on Saturday)
|
# Create matrices for the benchmarking jobs, so we run benchmarks on rds only once a week (on Saturday)
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
ARCHS: ${{ inputs.archs || '["x64","arm64"]' }}
|
ARCHS: ${{ inputs.archs || '["x64","arm64"]' }}
|
||||||
DEBIANS: ${{ inputs.debians || '["bullseye","bookworm"]' }}
|
DEBIANS: ${{ inputs.debians || '["bullseye","bookworm"]' }}
|
||||||
IMAGE_TAG: |
|
IMAGE_TAG: |
|
||||||
${{ hashFiles('build-tools.Dockerfile',
|
${{ hashFiles('build-tools/Dockerfile',
|
||||||
'.github/workflows/build-build-tools-image.yml') }}
|
'.github/workflows/build-build-tools-image.yml') }}
|
||||||
run: |
|
run: |
|
||||||
echo "archs=${ARCHS}" | tee -a ${GITHUB_OUTPUT}
|
echo "archs=${ARCHS}" | tee -a ${GITHUB_OUTPUT}
|
||||||
@@ -144,7 +144,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
|
- uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
|
||||||
with:
|
with:
|
||||||
file: build-tools.Dockerfile
|
file: build-tools/Dockerfile
|
||||||
context: .
|
context: .
|
||||||
provenance: false
|
provenance: false
|
||||||
push: true
|
push: true
|
||||||
|
|||||||
@@ -32,161 +32,14 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-pgxn:
|
make-all:
|
||||||
if: |
|
|
||||||
inputs.pg_versions != '[]' || inputs.rebuild_everything ||
|
|
||||||
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: 30
|
|
||||||
runs-on: macos-15
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
postgres-version: ${{ inputs.rebuild_everything && fromJSON('["v14", "v15", "v16", "v17"]') || fromJSON(inputs.pg_versions) }}
|
|
||||||
env:
|
|
||||||
# Use release build only, to have less debug info around
|
|
||||||
# Hence keeping target/ (and general cache size) smaller
|
|
||||||
BUILD_TYPE: release
|
|
||||||
steps:
|
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
|
|
||||||
- name: Checkout main repo
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
|
|
||||||
- name: Set pg ${{ matrix.postgres-version }} for caching
|
|
||||||
id: pg_rev
|
|
||||||
run: echo pg_rev=$(git rev-parse HEAD:vendor/postgres-${{ matrix.postgres-version }}) | tee -a "${GITHUB_OUTPUT}"
|
|
||||||
|
|
||||||
- name: Cache postgres ${{ matrix.postgres-version }} build
|
|
||||||
id: cache_pg
|
|
||||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
|
||||||
with:
|
|
||||||
path: pg_install/${{ matrix.postgres-version }}
|
|
||||||
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ env.BUILD_TYPE }}-pg-${{ matrix.postgres-version }}-${{ steps.pg_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
|
|
||||||
|
|
||||||
- name: Checkout submodule vendor/postgres-${{ matrix.postgres-version }}
|
|
||||||
if: steps.cache_pg.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
git submodule init vendor/postgres-${{ matrix.postgres-version }}
|
|
||||||
git submodule update --depth 1 --recursive
|
|
||||||
|
|
||||||
- name: Install build dependencies
|
|
||||||
if: steps.cache_pg.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
brew install flex bison openssl protobuf icu4c
|
|
||||||
|
|
||||||
- name: Set extra env for macOS
|
|
||||||
if: steps.cache_pg.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
echo 'LDFLAGS=-L/usr/local/opt/openssl@3/lib' >> $GITHUB_ENV
|
|
||||||
echo 'CPPFLAGS=-I/usr/local/opt/openssl@3/include' >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Build Postgres ${{ matrix.postgres-version }}
|
|
||||||
if: steps.cache_pg.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
make postgres-${{ matrix.postgres-version }} -j$(sysctl -n hw.ncpu)
|
|
||||||
|
|
||||||
- name: Build Neon Pg Ext ${{ matrix.postgres-version }}
|
|
||||||
if: steps.cache_pg.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
make "neon-pg-ext-${{ matrix.postgres-version }}" -j$(sysctl -n hw.ncpu)
|
|
||||||
|
|
||||||
- name: Get postgres headers ${{ matrix.postgres-version }}
|
|
||||||
if: steps.cache_pg.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
make postgres-headers-${{ matrix.postgres-version }} -j$(sysctl -n hw.ncpu)
|
|
||||||
|
|
||||||
- name: Upload "pg_install/${{ matrix.postgres-version }}" artifact
|
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
||||||
with:
|
|
||||||
name: pg_install--${{ matrix.postgres-version }}
|
|
||||||
path: pg_install/${{ matrix.postgres-version }}
|
|
||||||
# The artifact is supposed to be used by the next job in the same workflow,
|
|
||||||
# so there’s no need to store it for too long.
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
build-walproposer-lib:
|
|
||||||
if: |
|
|
||||||
inputs.pg_versions != '[]' || inputs.rebuild_everything ||
|
|
||||||
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: 30
|
|
||||||
runs-on: macos-15
|
|
||||||
needs: [build-pgxn]
|
|
||||||
env:
|
|
||||||
# Use release build only, to have less debug info around
|
|
||||||
# Hence keeping target/ (and general cache size) smaller
|
|
||||||
BUILD_TYPE: release
|
|
||||||
steps:
|
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
|
||||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
|
|
||||||
- name: Checkout main repo
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
|
|
||||||
- name: Set pg v17 for caching
|
|
||||||
id: pg_rev
|
|
||||||
run: echo pg_rev=$(git rev-parse HEAD:vendor/postgres-v17) | tee -a "${GITHUB_OUTPUT}"
|
|
||||||
|
|
||||||
- name: Download "pg_install/v17" artifact
|
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
||||||
with:
|
|
||||||
name: pg_install--v17
|
|
||||||
path: pg_install/v17
|
|
||||||
|
|
||||||
- name: Cache walproposer-lib
|
|
||||||
id: cache_walproposer_lib
|
|
||||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
|
||||||
with:
|
|
||||||
path: pg_install/build/walproposer-lib
|
|
||||||
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ env.BUILD_TYPE }}-walproposer_lib-v17-${{ steps.pg_rev.outputs.pg_rev }}-${{ hashFiles('Makefile') }}
|
|
||||||
|
|
||||||
- name: Checkout submodule vendor/postgres-v17
|
|
||||||
if: steps.cache_walproposer_lib.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
git submodule init vendor/postgres-v17
|
|
||||||
git submodule update --depth 1 --recursive
|
|
||||||
|
|
||||||
- name: Install build dependencies
|
|
||||||
if: steps.cache_walproposer_lib.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
brew install flex bison openssl protobuf icu4c
|
|
||||||
|
|
||||||
- name: Set extra env for macOS
|
|
||||||
if: steps.cache_walproposer_lib.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
echo 'LDFLAGS=-L/usr/local/opt/openssl@3/lib' >> $GITHUB_ENV
|
|
||||||
echo 'CPPFLAGS=-I/usr/local/opt/openssl@3/include' >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Build walproposer-lib (only for v17)
|
|
||||||
if: steps.cache_walproposer_lib.outputs.cache-hit != 'true'
|
|
||||||
run:
|
|
||||||
make walproposer-lib -j$(sysctl -n hw.ncpu)
|
|
||||||
|
|
||||||
- name: Upload "pg_install/build/walproposer-lib" artifact
|
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
||||||
with:
|
|
||||||
name: pg_install--build--walproposer-lib
|
|
||||||
path: pg_install/build/walproposer-lib
|
|
||||||
# The artifact is supposed to be used by the next job in the same workflow,
|
|
||||||
# so there’s no need to store it for too long.
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
cargo-build:
|
|
||||||
if: |
|
if: |
|
||||||
inputs.pg_versions != '[]' || inputs.rebuild_rust_code || inputs.rebuild_everything ||
|
inputs.pg_versions != '[]' || inputs.rebuild_rust_code || inputs.rebuild_everything ||
|
||||||
contains(github.event.pull_request.labels.*.name, 'run-extra-build-macos') ||
|
contains(github.event.pull_request.labels.*.name, 'run-extra-build-macos') ||
|
||||||
contains(github.event.pull_request.labels.*.name, 'run-extra-build-*') ||
|
contains(github.event.pull_request.labels.*.name, 'run-extra-build-*') ||
|
||||||
github.ref_name == 'main'
|
github.ref_name == 'main'
|
||||||
timeout-minutes: 30
|
timeout-minutes: 60
|
||||||
runs-on: macos-15
|
runs-on: macos-15
|
||||||
needs: [build-pgxn, build-walproposer-lib]
|
|
||||||
env:
|
env:
|
||||||
# Use release build only, to have less debug info around
|
# Use release build only, to have less debug info around
|
||||||
# Hence keeping target/ (and general cache size) smaller
|
# Hence keeping target/ (and general cache size) smaller
|
||||||
@@ -202,41 +55,53 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Download "pg_install/v14" artifact
|
- name: Install build dependencies
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
||||||
with:
|
|
||||||
name: pg_install--v14
|
|
||||||
path: pg_install/v14
|
|
||||||
|
|
||||||
- name: Download "pg_install/v15" artifact
|
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
||||||
with:
|
|
||||||
name: pg_install--v15
|
|
||||||
path: pg_install/v15
|
|
||||||
|
|
||||||
- name: Download "pg_install/v16" artifact
|
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
||||||
with:
|
|
||||||
name: pg_install--v16
|
|
||||||
path: pg_install/v16
|
|
||||||
|
|
||||||
- name: Download "pg_install/v17" artifact
|
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
||||||
with:
|
|
||||||
name: pg_install--v17
|
|
||||||
path: pg_install/v17
|
|
||||||
|
|
||||||
- name: Download "pg_install/build/walproposer-lib" artifact
|
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
||||||
with:
|
|
||||||
name: pg_install--build--walproposer-lib
|
|
||||||
path: pg_install/build/walproposer-lib
|
|
||||||
|
|
||||||
# `actions/download-artifact` doesn't preserve permissions:
|
|
||||||
# https://github.com/actions/download-artifact?tab=readme-ov-file#permission-loss
|
|
||||||
- name: Make pg_install/v*/bin/* executable
|
|
||||||
run: |
|
run: |
|
||||||
chmod +x pg_install/v*/bin/*
|
brew install flex bison openssl protobuf icu4c
|
||||||
|
|
||||||
|
- name: Set extra env for macOS
|
||||||
|
run: |
|
||||||
|
echo 'LDFLAGS=-L/usr/local/opt/openssl@3/lib' >> $GITHUB_ENV
|
||||||
|
echo 'CPPFLAGS=-I/usr/local/opt/openssl@3/include' >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Restore "pg_install/" cache
|
||||||
|
id: cache_pg
|
||||||
|
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||||
|
with:
|
||||||
|
path: pg_install
|
||||||
|
key: v1-${{ runner.os }}-${{ runner.arch }}-${{ env.BUILD_TYPE }}-pg-install-v14-${{ hashFiles('Makefile', 'postgres.mk', 'vendor/revisions.json') }}
|
||||||
|
|
||||||
|
- name: Checkout vendor/postgres submodules
|
||||||
|
if: steps.cache_pg.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
git submodule init
|
||||||
|
git submodule update --depth 1 --recursive
|
||||||
|
|
||||||
|
- name: Build Postgres
|
||||||
|
if: steps.cache_pg.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
make postgres -j$(sysctl -n hw.ncpu)
|
||||||
|
|
||||||
|
# This isn't strictly necessary, but it makes the cached and non-cached builds more similar,
|
||||||
|
# When pg_install is restored from cache, there is no 'build/' directory. By removing it
|
||||||
|
# in a non-cached build too, we enforce that the rest of the steps don't depend on it,
|
||||||
|
# so that we notice any build caching bugs earlier.
|
||||||
|
- name: Remove build artifacts
|
||||||
|
if: steps.cache_pg.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
rm -rf build
|
||||||
|
|
||||||
|
# Explicitly update the rust toolchain before running 'make'. The parallel make build can
|
||||||
|
# invoke 'cargo build' more than once in parallel, for different crates. That's OK, 'cargo'
|
||||||
|
# does its own locking to prevent concurrent builds from stepping on each other's
|
||||||
|
# toes. However, it will first try to update the toolchain, and that step is not locked the
|
||||||
|
# same way. To avoid two toolchain updates running in parallel and stepping on each other's
|
||||||
|
# toes, ensure that the toolchain is up-to-date beforehand.
|
||||||
|
- name: Update rust toolchain
|
||||||
|
run: |
|
||||||
|
rustup --version &&
|
||||||
|
rustup update &&
|
||||||
|
rustup show
|
||||||
|
|
||||||
- name: Cache cargo deps
|
- name: Cache cargo deps
|
||||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||||
@@ -248,17 +113,12 @@ jobs:
|
|||||||
target
|
target
|
||||||
key: v1-${{ runner.os }}-${{ runner.arch }}-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: Install build dependencies
|
# Build the neon-specific postgres extensions, and all the Rust bits.
|
||||||
run: |
|
#
|
||||||
brew install flex bison openssl protobuf icu4c
|
# Pass PG_INSTALL_CACHED=1 because PostgreSQL was already built and cached
|
||||||
|
# separately.
|
||||||
- name: Set extra env for macOS
|
- name: Build all
|
||||||
run: |
|
run: PG_INSTALL_CACHED=1 BUILD_TYPE=release make -j$(sysctl -n hw.ncpu) all
|
||||||
echo 'LDFLAGS=-L/usr/local/opt/openssl@3/lib' >> $GITHUB_ENV
|
|
||||||
echo 'CPPFLAGS=-I/usr/local/opt/openssl@3/include' >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Run cargo build
|
|
||||||
run: cargo build --all --release -j$(sysctl -n hw.ncpu)
|
|
||||||
|
|
||||||
- name: Check that no warnings are produced
|
- name: Check that no warnings are produced
|
||||||
run: ./run_clippy.sh
|
run: ./run_clippy.sh
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ jobs:
|
|||||||
test-cfg: ${{ inputs.pg-versions }}
|
test-cfg: ${{ inputs.pg-versions }}
|
||||||
test-selection: ${{ inputs.test-selection }}
|
test-selection: ${{ inputs.test-selection }}
|
||||||
test-run-count: ${{ fromJson(inputs.run-count) }}
|
test-run-count: ${{ fromJson(inputs.run-count) }}
|
||||||
|
rerun-failed: false
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
create-test-report:
|
create-test-report:
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Check for file changes
|
- name: Check for file changes
|
||||||
uses: step-security/paths-filter@v3
|
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||||
id: files-changed
|
id: files-changed
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -87,6 +87,24 @@ jobs:
|
|||||||
uses: ./.github/workflows/build-build-tools-image.yml
|
uses: ./.github/workflows/build-build-tools-image.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
|
lint-openapi-spec:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: [ meta, check-permissions ]
|
||||||
|
# We do need to run this in `.*-rc-pr` because of hotfixes.
|
||||||
|
if: ${{ contains(fromJSON('["pr", "push-main", "storage-rc-pr", "proxy-rc-pr", "compute-rc-pr"]'), needs.meta.outputs.run-kind) }}
|
||||||
|
steps:
|
||||||
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
|
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- run: make lint-openapi-spec
|
||||||
|
|
||||||
check-codestyle-python:
|
check-codestyle-python:
|
||||||
needs: [ meta, check-permissions, build-build-tools-image ]
|
needs: [ meta, check-permissions, build-build-tools-image ]
|
||||||
# No need to run on `main` because we this in the merge queue. We do need to run this in `.*-rc-pr` because of hotfixes.
|
# No need to run on `main` because we this in the merge queue. We do need to run this in `.*-rc-pr` because of hotfixes.
|
||||||
@@ -199,6 +217,28 @@ jobs:
|
|||||||
build-tools-image: ${{ needs.build-build-tools-image.outputs.image }}-bookworm
|
build-tools-image: ${{ needs.build-build-tools-image.outputs.image }}-bookworm
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
|
validate-compute-manifest:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: [ meta, check-permissions ]
|
||||||
|
# We do need to run this in `.*-rc-pr` because of hotfixes.
|
||||||
|
if: ${{ contains(fromJSON('["pr", "push-main", "storage-rc-pr", "proxy-rc-pr", "compute-rc-pr"]'), needs.meta.outputs.run-kind) }}
|
||||||
|
steps:
|
||||||
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
|
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
node-version: '24'
|
||||||
|
|
||||||
|
- name: Validate manifest against schema
|
||||||
|
run: |
|
||||||
|
make -C compute manifest-schema-validation
|
||||||
|
|
||||||
build-and-test-locally:
|
build-and-test-locally:
|
||||||
needs: [ meta, build-build-tools-image ]
|
needs: [ meta, build-build-tools-image ]
|
||||||
# We do need to run this in `.*-rc-pr` because of hotfixes.
|
# We do need to run this in `.*-rc-pr` because of hotfixes.
|
||||||
@@ -284,14 +324,14 @@ jobs:
|
|||||||
statuses: write
|
statuses: write
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: [ self-hosted, unit-perf ]
|
runs-on: [ self-hosted, unit-perf-aws-arm ]
|
||||||
container:
|
container:
|
||||||
image: ${{ needs.build-build-tools-image.outputs.image }}-bookworm
|
image: ${{ needs.build-build-tools-image.outputs.image }}-bookworm
|
||||||
credentials:
|
credentials:
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
# for changed limits, see comments on `options:` earlier in this file
|
# for changed limits, see comments on `options:` earlier in this file
|
||||||
options: --init --shm-size=512mb --ulimit memlock=67108864:67108864
|
options: --init --shm-size=512mb --ulimit memlock=67108864:67108864 --ulimit nofile=65536:65536 --security-opt seccomp=unconfined
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -648,7 +688,7 @@ jobs:
|
|||||||
ghcr.io/neondatabase/neon:${{ needs.meta.outputs.build-tag }}-bookworm-arm64
|
ghcr.io/neondatabase/neon:${{ needs.meta.outputs.build-tag }}-bookworm-arm64
|
||||||
|
|
||||||
compute-node-image-arch:
|
compute-node-image-arch:
|
||||||
needs: [ check-permissions, build-build-tools-image, meta ]
|
needs: [ check-permissions, meta ]
|
||||||
if: ${{ contains(fromJSON('["push-main", "pr", "compute-rc-pr"]'), needs.meta.outputs.run-kind) }}
|
if: ${{ contains(fromJSON('["push-main", "pr", "compute-rc-pr"]'), needs.meta.outputs.run-kind) }}
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write # aws-actions/configure-aws-credentials
|
id-token: write # aws-actions/configure-aws-credentials
|
||||||
@@ -721,7 +761,6 @@ jobs:
|
|||||||
GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
|
GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
|
||||||
PG_VERSION=${{ matrix.version.pg }}
|
PG_VERSION=${{ matrix.version.pg }}
|
||||||
BUILD_TAG=${{ needs.meta.outputs.release-tag || needs.meta.outputs.build-tag }}
|
BUILD_TAG=${{ needs.meta.outputs.release-tag || needs.meta.outputs.build-tag }}
|
||||||
TAG=${{ needs.build-build-tools-image.outputs.image-tag }}-${{ matrix.version.debian }}
|
|
||||||
DEBIAN_VERSION=${{ matrix.version.debian }}
|
DEBIAN_VERSION=${{ matrix.version.debian }}
|
||||||
provenance: false
|
provenance: false
|
||||||
push: true
|
push: true
|
||||||
@@ -741,7 +780,6 @@ jobs:
|
|||||||
GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
|
GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
|
||||||
PG_VERSION=${{ matrix.version.pg }}
|
PG_VERSION=${{ matrix.version.pg }}
|
||||||
BUILD_TAG=${{ needs.meta.outputs.release-tag || needs.meta.outputs.build-tag }}
|
BUILD_TAG=${{ needs.meta.outputs.release-tag || needs.meta.outputs.build-tag }}
|
||||||
TAG=${{ needs.build-build-tools-image.outputs.image-tag }}-${{ matrix.version.debian }}
|
|
||||||
DEBIAN_VERSION=${{ matrix.version.debian }}
|
DEBIAN_VERSION=${{ matrix.version.debian }}
|
||||||
provenance: false
|
provenance: false
|
||||||
push: true
|
push: true
|
||||||
@@ -966,6 +1004,7 @@ jobs:
|
|||||||
- name: Verify docker-compose example and test extensions
|
- name: Verify docker-compose example and test extensions
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
env:
|
env:
|
||||||
|
PARALLEL_COMPUTES: 3
|
||||||
TAG: >-
|
TAG: >-
|
||||||
${{
|
${{
|
||||||
needs.meta.outputs.run-kind == 'compute-rc-pr'
|
needs.meta.outputs.run-kind == 'compute-rc-pr'
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
name: Build and Test Fully
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# * is a special character in YAML so you have to quote this string
|
||||||
|
# ┌───────────── minute (0 - 59)
|
||||||
|
# │ ┌───────────── hour (0 - 23)
|
||||||
|
# │ │ ┌───────────── day of the month (1 - 31)
|
||||||
|
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||||
|
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||||
|
- cron: '0 3 * * *' # run once a day, timezone is utc
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash -euxo pipefail {0}
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
# Allow only one workflow per any non-`main` branch.
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
COPT: '-Werror'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tag:
|
||||||
|
runs-on: [ self-hosted, small ]
|
||||||
|
container: ${{ vars.NEON_DEV_AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_ECR_REGION }}.amazonaws.com/base:pinned
|
||||||
|
outputs:
|
||||||
|
build-tag: ${{steps.build-tag.outputs.tag}}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Need `fetch-depth: 0` to count the number of commits in the branch
|
||||||
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
|
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Get build tag
|
||||||
|
run: |
|
||||||
|
echo run:$GITHUB_RUN_ID
|
||||||
|
echo ref:$GITHUB_REF_NAME
|
||||||
|
echo rev:$(git rev-list --count HEAD)
|
||||||
|
if [[ "$GITHUB_REF_NAME" == "main" ]]; then
|
||||||
|
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
|
||||||
|
elif [[ "$GITHUB_REF_NAME" == "release-compute" ]]; then
|
||||||
|
echo "tag=release-compute-$(git rev-list --count HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "GITHUB_REF_NAME (value '$GITHUB_REF_NAME') is not set to either 'main' or 'release', 'release-proxy', 'release-compute'"
|
||||||
|
echo "tag=$GITHUB_RUN_ID" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
id: build-tag
|
||||||
|
|
||||||
|
build-build-tools-image:
|
||||||
|
uses: ./.github/workflows/build-build-tools-image.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
build-and-test-locally:
|
||||||
|
needs: [ tag, build-build-tools-image ]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
arch: [ x64, arm64 ]
|
||||||
|
build-type: [ debug, release ]
|
||||||
|
uses: ./.github/workflows/_build-and-test-locally.yml
|
||||||
|
with:
|
||||||
|
arch: ${{ matrix.arch }}
|
||||||
|
build-tools-image: ${{ needs.build-build-tools-image.outputs.image }}-bookworm
|
||||||
|
build-tag: ${{ needs.tag.outputs.build-tag }}
|
||||||
|
build-type: ${{ matrix.build-type }}
|
||||||
|
rerun-failed: false
|
||||||
|
test-cfg: '[{"pg_version":"v14", "lfc_state": "with-lfc"},
|
||||||
|
{"pg_version":"v15", "lfc_state": "with-lfc"},
|
||||||
|
{"pg_version":"v16", "lfc_state": "with-lfc"},
|
||||||
|
{"pg_version":"v17", "lfc_state": "with-lfc"},
|
||||||
|
{"pg_version":"v14", "lfc_state": "without-lfc"},
|
||||||
|
{"pg_version":"v15", "lfc_state": "without-lfc"},
|
||||||
|
{"pg_version":"v16", "lfc_state": "without-lfc"},
|
||||||
|
{"pg_version":"v17", "lfc_state": "withouts-lfc"}]'
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
|
||||||
|
create-test-report:
|
||||||
|
needs: [ build-and-test-locally, build-build-tools-image ]
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
permissions:
|
||||||
|
id-token: write # aws-actions/configure-aws-credentials
|
||||||
|
statuses: write
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
outputs:
|
||||||
|
report-url: ${{ steps.create-allure-report.outputs.report-url }}
|
||||||
|
|
||||||
|
runs-on: [ self-hosted, small ]
|
||||||
|
container:
|
||||||
|
image: ${{ needs.build-build-tools-image.outputs.image }}-bookworm
|
||||||
|
credentials:
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
options: --init
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
|
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Create Allure report
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
id: create-allure-report
|
||||||
|
uses: ./.github/actions/allure-report-generate
|
||||||
|
with:
|
||||||
|
store-test-results-into-db: true
|
||||||
|
aws-oidc-role-arn: ${{ vars.DEV_AWS_OIDC_ROLE_ARN }}
|
||||||
|
env:
|
||||||
|
REGRESS_TEST_RESULT_CONNSTR_NEW: ${{ secrets.REGRESS_TEST_RESULT_CONNSTR_NEW }}
|
||||||
|
|
||||||
|
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
with:
|
||||||
|
# Retry script for 5XX server errors: https://github.com/actions/github-script#retries
|
||||||
|
retries: 5
|
||||||
|
script: |
|
||||||
|
const report = {
|
||||||
|
reportUrl: "${{ steps.create-allure-report.outputs.report-url }}",
|
||||||
|
reportJsonUrl: "${{ steps.create-allure-report.outputs.report-json-url }}",
|
||||||
|
}
|
||||||
|
|
||||||
|
const coverage = {}
|
||||||
|
|
||||||
|
const script = require("./scripts/comment-test-report.js")
|
||||||
|
await script({
|
||||||
|
github,
|
||||||
|
context,
|
||||||
|
fetch,
|
||||||
|
report,
|
||||||
|
coverage,
|
||||||
|
})
|
||||||
@@ -79,6 +79,7 @@ jobs:
|
|||||||
build-tools-image: ${{ needs.build-build-tools-image.outputs.image }}-bookworm
|
build-tools-image: ${{ needs.build-build-tools-image.outputs.image }}-bookworm
|
||||||
build-tag: ${{ needs.tag.outputs.build-tag }}
|
build-tag: ${{ needs.tag.outputs.build-tag }}
|
||||||
build-type: ${{ matrix.build-type }}
|
build-type: ${{ matrix.build-type }}
|
||||||
|
rerun-failed: false
|
||||||
test-cfg: '[{"pg_version":"v17"}]'
|
test-cfg: '[{"pg_version":"v17"}]'
|
||||||
sanitizers: enabled
|
sanitizers: enabled
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|||||||
@@ -33,11 +33,19 @@ jobs:
|
|||||||
fail-fast: false # allow other variants to continue even if one fails
|
fail-fast: false # allow other variants to continue even if one fails
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
# test only read-only custom scripts in new branch without database maintenance
|
||||||
|
- target: new_branch
|
||||||
|
custom_scripts: select_any_webhook_with_skew.sql@300 select_recent_webhook.sql@397 select_prefetch_webhook.sql@3
|
||||||
|
test_maintenance: false
|
||||||
|
# test all custom scripts in new branch with database maintenance
|
||||||
- target: new_branch
|
- target: new_branch
|
||||||
custom_scripts: insert_webhooks.sql@200 select_any_webhook_with_skew.sql@300 select_recent_webhook.sql@397 select_prefetch_webhook.sql@3 IUD_one_transaction.sql@100
|
custom_scripts: insert_webhooks.sql@200 select_any_webhook_with_skew.sql@300 select_recent_webhook.sql@397 select_prefetch_webhook.sql@3 IUD_one_transaction.sql@100
|
||||||
|
test_maintenance: true
|
||||||
|
# test all custom scripts in reuse branch with database maintenance
|
||||||
- target: reuse_branch
|
- target: reuse_branch
|
||||||
custom_scripts: insert_webhooks.sql@200 select_any_webhook_with_skew.sql@300 select_recent_webhook.sql@397 select_prefetch_webhook.sql@3 IUD_one_transaction.sql@100
|
custom_scripts: insert_webhooks.sql@200 select_any_webhook_with_skew.sql@300 select_recent_webhook.sql@397 select_prefetch_webhook.sql@3 IUD_one_transaction.sql@100
|
||||||
max-parallel: 1 # we want to run each stripe size sequentially to be able to compare the results
|
test_maintenance: true
|
||||||
|
max-parallel: 1 # we want to run each benchmark sequentially to not have noisy neighbors on shared storage (PS, SK)
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
statuses: write
|
statuses: write
|
||||||
@@ -145,6 +153,7 @@ jobs:
|
|||||||
PERF_TEST_RESULT_CONNSTR: "${{ secrets.PERF_TEST_RESULT_CONNSTR }}"
|
PERF_TEST_RESULT_CONNSTR: "${{ secrets.PERF_TEST_RESULT_CONNSTR }}"
|
||||||
|
|
||||||
- name: Benchmark database maintenance
|
- name: Benchmark database maintenance
|
||||||
|
if: ${{ matrix.test_maintenance }}
|
||||||
uses: ./.github/actions/run-python-test-set
|
uses: ./.github/actions/run-python-test-set
|
||||||
with:
|
with:
|
||||||
build_type: ${{ env.BUILD_TYPE }}
|
build_type: ${{ env.BUILD_TYPE }}
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
name: large oltp growth
|
||||||
|
# workflow to grow the reuse branch of large oltp benchmark continuously (about 16 GB per run)
|
||||||
|
|
||||||
|
on:
|
||||||
|
# uncomment to run on push for debugging your PR
|
||||||
|
# push:
|
||||||
|
# branches: [ bodobolero/increase_large_oltp_workload ]
|
||||||
|
|
||||||
|
schedule:
|
||||||
|
# * is a special character in YAML so you have to quote this string
|
||||||
|
# ┌───────────── minute (0 - 59)
|
||||||
|
# │ ┌───────────── hour (0 - 23)
|
||||||
|
# │ │ ┌───────────── day of the month (1 - 31)
|
||||||
|
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||||
|
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||||
|
- cron: '0 6 * * *' # 06:00 UTC
|
||||||
|
- cron: '0 8 * * *' # 08:00 UTC
|
||||||
|
- cron: '0 10 * * *' # 10:00 UTC
|
||||||
|
- cron: '0 12 * * *' # 12:00 UTC
|
||||||
|
- cron: '0 14 * * *' # 14:00 UTC
|
||||||
|
- cron: '0 16 * * *' # 16:00 UTC
|
||||||
|
workflow_dispatch: # adds ability to run this manually
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash -euxo pipefail {0}
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
# Allow only one workflow globally because we need dedicated resources which only exist once
|
||||||
|
group: large-oltp-growth
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
oltp:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false # allow other variants to continue even if one fails
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
# for now only grow the reuse branch, not the other branches.
|
||||||
|
- target: reuse_branch
|
||||||
|
custom_scripts:
|
||||||
|
- grow_action_blocks.sql
|
||||||
|
- grow_action_kwargs.sql
|
||||||
|
- grow_device_fingerprint_event.sql
|
||||||
|
- grow_edges.sql
|
||||||
|
- grow_hotel_rate_mapping.sql
|
||||||
|
- grow_ocr_pipeline_results_version.sql
|
||||||
|
- grow_priceline_raw_response.sql
|
||||||
|
- grow_relabled_transactions.sql
|
||||||
|
- grow_state_values.sql
|
||||||
|
- grow_values.sql
|
||||||
|
- grow_vertices.sql
|
||||||
|
- update_accounting_coding_body_tracking_category_selection.sql
|
||||||
|
- update_action_blocks.sql
|
||||||
|
- update_action_kwargs.sql
|
||||||
|
- update_denormalized_approval_workflow.sql
|
||||||
|
- update_device_fingerprint_event.sql
|
||||||
|
- update_edges.sql
|
||||||
|
- update_heron_transaction_enriched_log.sql
|
||||||
|
- update_heron_transaction_enrichment_requests.sql
|
||||||
|
- update_hotel_rate_mapping.sql
|
||||||
|
- update_incoming_webhooks.sql
|
||||||
|
- update_manual_transaction.sql
|
||||||
|
- update_ml_receipt_matching_log.sql
|
||||||
|
- update_ocr_pipeine_results_version.sql
|
||||||
|
- update_orc_pipeline_step_results.sql
|
||||||
|
- update_orc_pipeline_step_results_version.sql
|
||||||
|
- update_priceline_raw_response.sql
|
||||||
|
- update_quickbooks_transactions.sql
|
||||||
|
- update_raw_finicity_transaction.sql
|
||||||
|
- update_relabeled_transactions.sql
|
||||||
|
- update_state_values.sql
|
||||||
|
- update_stripe_authorization_event_log.sql
|
||||||
|
- update_transaction.sql
|
||||||
|
- update_values.sql
|
||||||
|
- update_vertices.sql
|
||||||
|
max-parallel: 1 # we want to run each growth workload sequentially (for now there is just one)
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
statuses: write
|
||||||
|
id-token: write # aws-actions/configure-aws-credentials
|
||||||
|
env:
|
||||||
|
TEST_PG_BENCH_DURATIONS_MATRIX: "1h"
|
||||||
|
TEST_PGBENCH_CUSTOM_SCRIPTS: ${{ join(matrix.custom_scripts, ' ') }}
|
||||||
|
POSTGRES_DISTRIB_DIR: /tmp/neon/pg_install
|
||||||
|
PG_VERSION: 16 # pre-determined by pre-determined project
|
||||||
|
TEST_OUTPUT: /tmp/test_output
|
||||||
|
BUILD_TYPE: remote
|
||||||
|
PLATFORM: ${{ matrix.target }}
|
||||||
|
|
||||||
|
runs-on: [ self-hosted, us-east-2, x64 ]
|
||||||
|
container:
|
||||||
|
image: ghcr.io/neondatabase/build-tools:pinned-bookworm
|
||||||
|
credentials:
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
options: --init
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
|
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Configure AWS credentials # necessary to download artefacts
|
||||||
|
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
|
||||||
|
with:
|
||||||
|
aws-region: eu-central-1
|
||||||
|
role-to-assume: ${{ vars.DEV_AWS_OIDC_ROLE_ARN }}
|
||||||
|
role-duration-seconds: 18000 # 5 hours is currently max associated with IAM role
|
||||||
|
|
||||||
|
- name: Download Neon artifact
|
||||||
|
uses: ./.github/actions/download
|
||||||
|
with:
|
||||||
|
name: neon-${{ runner.os }}-${{ runner.arch }}-release-artifact
|
||||||
|
path: /tmp/neon/
|
||||||
|
prefix: latest
|
||||||
|
aws-oidc-role-arn: ${{ vars.DEV_AWS_OIDC_ROLE_ARN }}
|
||||||
|
|
||||||
|
- name: Set up Connection String
|
||||||
|
id: set-up-connstr
|
||||||
|
run: |
|
||||||
|
case "${{ matrix.target }}" in
|
||||||
|
reuse_branch)
|
||||||
|
CONNSTR=${{ secrets.BENCHMARK_LARGE_OLTP_REUSE_CONNSTR }}
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo >&2 "Unknown target=${{ matrix.target }}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CONNSTR_WITHOUT_POOLER="${CONNSTR//-pooler/}"
|
||||||
|
|
||||||
|
echo "connstr=${CONNSTR}" >> $GITHUB_OUTPUT
|
||||||
|
echo "connstr_without_pooler=${CONNSTR_WITHOUT_POOLER}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: pgbench with custom-scripts
|
||||||
|
uses: ./.github/actions/run-python-test-set
|
||||||
|
with:
|
||||||
|
build_type: ${{ env.BUILD_TYPE }}
|
||||||
|
test_selection: performance
|
||||||
|
run_in_parallel: false
|
||||||
|
save_perf_report: true
|
||||||
|
extra_params: -m remote_cluster --timeout 7200 -k test_perf_oltp_large_tenant_growth
|
||||||
|
pg_version: ${{ env.PG_VERSION }}
|
||||||
|
aws-oidc-role-arn: ${{ vars.DEV_AWS_OIDC_ROLE_ARN }}
|
||||||
|
env:
|
||||||
|
BENCHMARK_CONNSTR: ${{ steps.set-up-connstr.outputs.connstr }}
|
||||||
|
VIP_VAP_ACCESS_TOKEN: "${{ secrets.VIP_VAP_ACCESS_TOKEN }}"
|
||||||
|
PERF_TEST_RESULT_CONNSTR: "${{ secrets.PERF_TEST_RESULT_CONNSTR }}"
|
||||||
|
|
||||||
|
- name: Create Allure report
|
||||||
|
id: create-allure-report
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: ./.github/actions/allure-report-generate
|
||||||
|
with:
|
||||||
|
aws-oidc-role-arn: ${{ vars.DEV_AWS_OIDC_ROLE_ARN }}
|
||||||
|
|
||||||
|
- name: Post to a Slack channel
|
||||||
|
if: ${{ github.event.schedule && failure() }}
|
||||||
|
uses: slackapi/slack-github-action@fcfb566f8b0aab22203f066d80ca1d7e4b5d05b3 # v1.27.1
|
||||||
|
with:
|
||||||
|
channel-id: "C06KHQVQ7U3" # on-call-qa-staging-stream
|
||||||
|
slack-message: |
|
||||||
|
Periodic large oltp tenant growth increase: ${{ job.status }}
|
||||||
|
<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|GitHub Run>
|
||||||
|
<${{ steps.create-allure-report.outputs.report-url }}|Allure report>
|
||||||
|
env:
|
||||||
|
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Check for Postgres changes
|
- name: Check for Postgres changes
|
||||||
uses: step-security/paths-filter@v3
|
uses: dorny/paths-filter@1441771bbfdd59dcd748680ee64ebd8faab1a242 #v3
|
||||||
id: files_changed
|
id: files_changed
|
||||||
with:
|
with:
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Periodic pagebench performance test on unit-perf hetzner runner
|
name: Periodic pagebench performance test on unit-perf-aws-arm runners
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
statuses: write
|
statuses: write
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: [ self-hosted, unit-perf ]
|
runs-on: [ self-hosted, unit-perf-aws-arm ]
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/neondatabase/build-tools:pinned-bookworm
|
image: ghcr.io/neondatabase/build-tools:pinned-bookworm
|
||||||
credentials:
|
credentials:
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
|
- uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5
|
||||||
id: python-src
|
id: python-src
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
poetry.lock
|
poetry.lock
|
||||||
pyproject.toml
|
pyproject.toml
|
||||||
|
|
||||||
- uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
|
- uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5
|
||||||
id: rust-src
|
id: rust-src
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
name: Periodic proxy performance test on unit-perf-aws-arm runners
|
||||||
|
|
||||||
|
on:
|
||||||
|
push: # TODO: remove after testing
|
||||||
|
branches:
|
||||||
|
- test-proxy-bench # Runs on pushes to branches starting with test-proxy-bench
|
||||||
|
# schedule:
|
||||||
|
# * is a special character in YAML so you have to quote this string
|
||||||
|
# ┌───────────── minute (0 - 59)
|
||||||
|
# │ ┌───────────── hour (0 - 23)
|
||||||
|
# │ │ ┌───────────── day of the month (1 - 31)
|
||||||
|
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||||
|
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||||
|
# - cron: '0 5 * * *' # Runs at 5 UTC once a day
|
||||||
|
workflow_dispatch: # adds an ability to run this manually
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash -euo pipefail {0}
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run_periodic_proxybench_test:
|
||||||
|
permissions:
|
||||||
|
id-token: write # aws-actions/configure-aws-credentials
|
||||||
|
statuses: write
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: [self-hosted, unit-perf-aws-arm]
|
||||||
|
timeout-minutes: 60 # 1h timeout
|
||||||
|
container:
|
||||||
|
image: ghcr.io/neondatabase/build-tools:pinned-bookworm
|
||||||
|
credentials:
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
options: --init
|
||||||
|
steps:
|
||||||
|
- name: Checkout proxy-bench Repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: neondatabase/proxy-bench
|
||||||
|
path: proxy-bench
|
||||||
|
|
||||||
|
- name: Set up the environment which depends on $RUNNER_TEMP on nvme drive
|
||||||
|
id: set-env
|
||||||
|
shell: bash -euxo pipefail {0}
|
||||||
|
run: |
|
||||||
|
PROXY_BENCH_PATH=$(realpath ./proxy-bench)
|
||||||
|
{
|
||||||
|
echo "PROXY_BENCH_PATH=$PROXY_BENCH_PATH"
|
||||||
|
echo "NEON_DIR=${RUNNER_TEMP}/neon"
|
||||||
|
echo "TEST_OUTPUT=${PROXY_BENCH_PATH}/test_output"
|
||||||
|
echo ""
|
||||||
|
} >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Run proxy-bench
|
||||||
|
run: ${PROXY_BENCH_PATH}/run.sh
|
||||||
|
|
||||||
|
- name: Ingest Bench Results # neon repo script
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
mkdir -p $TEST_OUTPUT
|
||||||
|
python $NEON_DIR/scripts/proxy_bench_results_ingest.py --out $TEST_OUTPUT
|
||||||
|
|
||||||
|
- name: Push Metrics to Proxy perf database
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
PERF_TEST_RESULT_CONNSTR: "${{ secrets.PROXY_TEST_RESULT_CONNSTR }}"
|
||||||
|
REPORT_FROM: $TEST_OUTPUT
|
||||||
|
run: $NEON_DIR/scripts/generate_and_push_perf_report.sh
|
||||||
|
|
||||||
|
- name: Docker cleanup
|
||||||
|
if: always()
|
||||||
|
run: docker compose down
|
||||||
|
|
||||||
|
- name: Notify Failure
|
||||||
|
if: failure()
|
||||||
|
run: echo "Proxy bench job failed" && exit 1
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
/artifact_cache
|
/artifact_cache
|
||||||
|
/build
|
||||||
/pg_install
|
/pg_install
|
||||||
/target
|
/target
|
||||||
/tmp_check
|
/tmp_check
|
||||||
/tmp_check_cli
|
/tmp_check_cli
|
||||||
__pycache__/
|
__pycache__/
|
||||||
test_output/
|
test_output/
|
||||||
|
neon_previous/
|
||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
*.swp
|
*.swp
|
||||||
@@ -13,6 +15,7 @@ neon.iml
|
|||||||
/.neon
|
/.neon
|
||||||
/integration_tests/.neon
|
/integration_tests/.neon
|
||||||
compaction-suite-results.*
|
compaction-suite-results.*
|
||||||
|
docker-compose/docker-compose-parallel.yml
|
||||||
|
|
||||||
# Coverage
|
# Coverage
|
||||||
*.profraw
|
*.profraw
|
||||||
|
|||||||
+4
-4
@@ -1,16 +1,16 @@
|
|||||||
[submodule "vendor/postgres-v14"]
|
[submodule "vendor/postgres-v14"]
|
||||||
path = vendor/postgres-v14
|
path = vendor/postgres-v14
|
||||||
url = https://github.com/neondatabase/postgres.git
|
url = ../postgres.git
|
||||||
branch = REL_14_STABLE_neon
|
branch = REL_14_STABLE_neon
|
||||||
[submodule "vendor/postgres-v15"]
|
[submodule "vendor/postgres-v15"]
|
||||||
path = vendor/postgres-v15
|
path = vendor/postgres-v15
|
||||||
url = https://github.com/neondatabase/postgres.git
|
url = ../postgres.git
|
||||||
branch = REL_15_STABLE_neon
|
branch = REL_15_STABLE_neon
|
||||||
[submodule "vendor/postgres-v16"]
|
[submodule "vendor/postgres-v16"]
|
||||||
path = vendor/postgres-v16
|
path = vendor/postgres-v16
|
||||||
url = https://github.com/neondatabase/postgres.git
|
url = ../postgres.git
|
||||||
branch = REL_16_STABLE_neon
|
branch = REL_16_STABLE_neon
|
||||||
[submodule "vendor/postgres-v17"]
|
[submodule "vendor/postgres-v17"]
|
||||||
path = vendor/postgres-v17
|
path = vendor/postgres-v17
|
||||||
url = https://github.com/neondatabase/postgres.git
|
url = ../postgres.git
|
||||||
branch = REL_17_STABLE_neon
|
branch = REL_17_STABLE_neon
|
||||||
|
|||||||
Generated
+173
-24
@@ -753,6 +753,7 @@ dependencies = [
|
|||||||
"axum",
|
"axum",
|
||||||
"axum-core",
|
"axum-core",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"form_urlencoded",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"headers",
|
"headers",
|
||||||
"http 1.1.0",
|
"http 1.1.0",
|
||||||
@@ -761,6 +762,8 @@ dependencies = [
|
|||||||
"mime",
|
"mime",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_html_form",
|
||||||
|
"serde_path_to_error",
|
||||||
"tower 0.5.2",
|
"tower 0.5.2",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
@@ -900,12 +903,6 @@ version = "0.13.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.20.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.7"
|
version = "0.21.7"
|
||||||
@@ -1086,6 +1083,25 @@ version = "0.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cbindgen"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "975982cdb7ad6a142be15bdf84aea7ec6a9e5d4d797c004d43185b24cfe4e684"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"heck",
|
||||||
|
"indexmap 2.9.0",
|
||||||
|
"log",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"syn 2.0.100",
|
||||||
|
"tempfile",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.16"
|
version = "1.2.16"
|
||||||
@@ -1270,6 +1286,15 @@ dependencies = [
|
|||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "communicator"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"cbindgen",
|
||||||
|
"neon-shmem",
|
||||||
|
"workspace_hack",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "compute_api"
|
name = "compute_api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -1282,6 +1307,7 @@ dependencies = [
|
|||||||
"remote_storage",
|
"remote_storage",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"url",
|
||||||
"utils",
|
"utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1297,7 +1323,7 @@ dependencies = [
|
|||||||
"aws-smithy-types",
|
"aws-smithy-types",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
"base64 0.13.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"camino",
|
"camino",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -1307,6 +1333,7 @@ dependencies = [
|
|||||||
"fail",
|
"fail",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
|
"hostname-validator",
|
||||||
"http 1.1.0",
|
"http 1.1.0",
|
||||||
"indexmap 2.9.0",
|
"indexmap 2.9.0",
|
||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
@@ -1319,8 +1346,11 @@ dependencies = [
|
|||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"opentelemetry_sdk",
|
"opentelemetry_sdk",
|
||||||
"p256 0.13.2",
|
"p256 0.13.2",
|
||||||
|
"pageserver_page_api",
|
||||||
"postgres",
|
"postgres",
|
||||||
|
"postgres-types",
|
||||||
"postgres_initdb",
|
"postgres_initdb",
|
||||||
|
"postgres_versioninfo",
|
||||||
"regex",
|
"regex",
|
||||||
"remote_storage",
|
"remote_storage",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -1337,6 +1367,7 @@ dependencies = [
|
|||||||
"tokio-postgres",
|
"tokio-postgres",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
"tonic 0.13.1",
|
||||||
"tower 0.5.2",
|
"tower 0.5.2",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tower-otel",
|
"tower-otel",
|
||||||
@@ -1423,7 +1454,7 @@ name = "control_plane"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.13.1",
|
"base64 0.22.1",
|
||||||
"camino",
|
"camino",
|
||||||
"clap",
|
"clap",
|
||||||
"comfy-table",
|
"comfy-table",
|
||||||
@@ -2055,6 +2086,7 @@ dependencies = [
|
|||||||
"axum-extra",
|
"axum-extra",
|
||||||
"camino",
|
"camino",
|
||||||
"camino-tempfile",
|
"camino-tempfile",
|
||||||
|
"clap",
|
||||||
"futures",
|
"futures",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
@@ -2769,6 +2801,12 @@ dependencies = [
|
|||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hostname-validator"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
@@ -3452,6 +3490,15 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "json"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"futures",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "json-structural-diff"
|
name = "json-structural-diff"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -4247,7 +4294,9 @@ dependencies = [
|
|||||||
"humantime-serde",
|
"humantime-serde",
|
||||||
"pageserver_api",
|
"pageserver_api",
|
||||||
"pageserver_client",
|
"pageserver_client",
|
||||||
|
"pageserver_client_grpc",
|
||||||
"pageserver_page_api",
|
"pageserver_page_api",
|
||||||
|
"pprof",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4257,6 +4306,7 @@ dependencies = [
|
|||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tonic 0.13.1",
|
"tonic 0.13.1",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"url",
|
||||||
"utils",
|
"utils",
|
||||||
"workspace_hack",
|
"workspace_hack",
|
||||||
]
|
]
|
||||||
@@ -4275,6 +4325,7 @@ dependencies = [
|
|||||||
"pageserver_api",
|
"pageserver_api",
|
||||||
"postgres_ffi",
|
"postgres_ffi",
|
||||||
"remote_storage",
|
"remote_storage",
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"svg_fmt",
|
"svg_fmt",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
@@ -4292,6 +4343,7 @@ dependencies = [
|
|||||||
"arc-swap",
|
"arc-swap",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
|
"base64 0.22.1",
|
||||||
"bincode",
|
"bincode",
|
||||||
"bit_field",
|
"bit_field",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
@@ -4336,6 +4388,7 @@ dependencies = [
|
|||||||
"postgres_backend",
|
"postgres_backend",
|
||||||
"postgres_connection",
|
"postgres_connection",
|
||||||
"postgres_ffi",
|
"postgres_ffi",
|
||||||
|
"postgres_ffi_types",
|
||||||
"postgres_initdb",
|
"postgres_initdb",
|
||||||
"posthog_client_lite",
|
"posthog_client_lite",
|
||||||
"pprof",
|
"pprof",
|
||||||
@@ -4405,7 +4458,9 @@ dependencies = [
|
|||||||
"nix 0.30.1",
|
"nix 0.30.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"postgres_backend",
|
"postgres_backend",
|
||||||
"postgres_ffi",
|
"postgres_ffi_types",
|
||||||
|
"postgres_versioninfo",
|
||||||
|
"posthog_client_lite",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"remote_storage",
|
"remote_storage",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -4416,6 +4471,7 @@ dependencies = [
|
|||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
|
"tracing",
|
||||||
"tracing-utils",
|
"tracing-utils",
|
||||||
"utils",
|
"utils",
|
||||||
]
|
]
|
||||||
@@ -4429,6 +4485,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"http-utils",
|
"http-utils",
|
||||||
"pageserver_api",
|
"pageserver_api",
|
||||||
|
"postgres_versioninfo",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
@@ -4440,6 +4497,26 @@ dependencies = [
|
|||||||
"workspace_hack",
|
"workspace_hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pageserver_client_grpc"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"arc-swap",
|
||||||
|
"bytes",
|
||||||
|
"compute_api",
|
||||||
|
"futures",
|
||||||
|
"pageserver_api",
|
||||||
|
"pageserver_page_api",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tokio-util",
|
||||||
|
"tonic 0.13.1",
|
||||||
|
"tracing",
|
||||||
|
"utils",
|
||||||
|
"workspace_hack",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pageserver_compaction"
|
name = "pageserver_compaction"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -4467,11 +4544,18 @@ dependencies = [
|
|||||||
name = "pageserver_page_api"
|
name = "pageserver_page_api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"futures",
|
||||||
"pageserver_api",
|
"pageserver_api",
|
||||||
"postgres_ffi",
|
"postgres_ffi_types",
|
||||||
"prost 0.13.5",
|
"prost 0.13.5",
|
||||||
|
"prost-types 0.13.5",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
"tonic 0.13.1",
|
"tonic 0.13.1",
|
||||||
"tonic-build",
|
"tonic-build",
|
||||||
"utils",
|
"utils",
|
||||||
@@ -4814,7 +4898,7 @@ dependencies = [
|
|||||||
name = "postgres-protocol2"
|
name = "postgres-protocol2"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.20.0",
|
"base64 0.22.1",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
@@ -4891,6 +4975,8 @@ dependencies = [
|
|||||||
"memoffset 0.9.0",
|
"memoffset 0.9.0",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"postgres",
|
"postgres",
|
||||||
|
"postgres_ffi_types",
|
||||||
|
"postgres_versioninfo",
|
||||||
"pprof",
|
"pprof",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4899,17 +4985,37 @@ dependencies = [
|
|||||||
"utils",
|
"utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postgres_ffi_types"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"workspace_hack",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "postgres_initdb"
|
name = "postgres_initdb"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"camino",
|
"camino",
|
||||||
|
"postgres_versioninfo",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"workspace_hack",
|
"workspace_hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postgres_versioninfo"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"serde",
|
||||||
|
"serde_repr",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"workspace_hack",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "posthog_client_lite"
|
name = "posthog_client_lite"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -5122,7 +5228,7 @@ dependencies = [
|
|||||||
"petgraph",
|
"petgraph",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"prost 0.13.5",
|
"prost 0.13.5",
|
||||||
"prost-types 0.13.3",
|
"prost-types 0.13.5",
|
||||||
"regex",
|
"regex",
|
||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
@@ -5165,9 +5271,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-types"
|
name = "prost-types"
|
||||||
version = "0.13.3"
|
version = "0.13.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670"
|
checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"prost 0.13.5",
|
"prost 0.13.5",
|
||||||
]
|
]
|
||||||
@@ -5184,9 +5290,10 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"atomic-take",
|
"atomic-take",
|
||||||
"aws-config",
|
"aws-config",
|
||||||
|
"aws-credential-types",
|
||||||
"aws-sdk-iam",
|
"aws-sdk-iam",
|
||||||
"aws-sigv4",
|
"aws-sigv4",
|
||||||
"base64 0.13.1",
|
"base64 0.22.1",
|
||||||
"bstr",
|
"bstr",
|
||||||
"bytes",
|
"bytes",
|
||||||
"camino",
|
"camino",
|
||||||
@@ -5196,6 +5303,7 @@ dependencies = [
|
|||||||
"clashmap",
|
"clashmap",
|
||||||
"compute_api",
|
"compute_api",
|
||||||
"consumption_metrics",
|
"consumption_metrics",
|
||||||
|
"criterion",
|
||||||
"ecdsa 0.16.9",
|
"ecdsa 0.16.9",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@@ -5223,6 +5331,7 @@ dependencies = [
|
|||||||
"itoa",
|
"itoa",
|
||||||
"jose-jwa",
|
"jose-jwa",
|
||||||
"jose-jwk",
|
"jose-jwk",
|
||||||
|
"json",
|
||||||
"lasso",
|
"lasso",
|
||||||
"measured",
|
"measured",
|
||||||
"metrics",
|
"metrics",
|
||||||
@@ -5603,6 +5712,8 @@ dependencies = [
|
|||||||
"azure_identity",
|
"azure_identity",
|
||||||
"azure_storage",
|
"azure_storage",
|
||||||
"azure_storage_blobs",
|
"azure_storage_blobs",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"camino",
|
"camino",
|
||||||
"camino-tempfile",
|
"camino-tempfile",
|
||||||
@@ -6101,6 +6212,8 @@ dependencies = [
|
|||||||
"postgres-protocol",
|
"postgres-protocol",
|
||||||
"postgres_backend",
|
"postgres_backend",
|
||||||
"postgres_ffi",
|
"postgres_ffi",
|
||||||
|
"postgres_ffi_types",
|
||||||
|
"postgres_versioninfo",
|
||||||
"pprof",
|
"pprof",
|
||||||
"pq_proto",
|
"pq_proto",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
@@ -6144,7 +6257,8 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"const_format",
|
"const_format",
|
||||||
"pageserver_api",
|
"pageserver_api",
|
||||||
"postgres_ffi",
|
"postgres_ffi_types",
|
||||||
|
"postgres_versioninfo",
|
||||||
"pq_proto",
|
"pq_proto",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -6421,6 +6535,19 @@ dependencies = [
|
|||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_html_form"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d2de91cf02bbc07cde38891769ccd5d4f073d22a40683aa4bc7a95781aaa2c4"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"indexmap 2.9.0",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.125"
|
version = "1.0.125"
|
||||||
@@ -6454,6 +6581,17 @@ dependencies = [
|
|||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_repr"
|
||||||
|
version = "0.1.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.100",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "0.6.6"
|
version = "0.6.6"
|
||||||
@@ -6477,15 +6615,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "2.3.3"
|
version = "3.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe"
|
checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.1",
|
"base64 0.22.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
|
"indexmap 2.9.0",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with_macros",
|
"serde_with_macros",
|
||||||
"time",
|
"time",
|
||||||
@@ -6493,9 +6633,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with_macros"
|
name = "serde_with_macros"
|
||||||
version = "2.3.3"
|
version = "3.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f"
|
checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -6746,6 +6886,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clashmap",
|
"clashmap",
|
||||||
|
"compute_api",
|
||||||
"control_plane",
|
"control_plane",
|
||||||
"cron",
|
"cron",
|
||||||
"diesel",
|
"diesel",
|
||||||
@@ -6757,6 +6898,7 @@ dependencies = [
|
|||||||
"hex",
|
"hex",
|
||||||
"http-utils",
|
"http-utils",
|
||||||
"humantime",
|
"humantime",
|
||||||
|
"humantime-serde",
|
||||||
"hyper 0.14.30",
|
"hyper 0.14.30",
|
||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
"json-structural-diff",
|
"json-structural-diff",
|
||||||
@@ -6767,6 +6909,7 @@ dependencies = [
|
|||||||
"pageserver_api",
|
"pageserver_api",
|
||||||
"pageserver_client",
|
"pageserver_client",
|
||||||
"postgres_connection",
|
"postgres_connection",
|
||||||
|
"posthog_client_lite",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -6853,6 +6996,7 @@ dependencies = [
|
|||||||
"pageserver_api",
|
"pageserver_api",
|
||||||
"pageserver_client",
|
"pageserver_client",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"safekeeper_api",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"storage_controller_client",
|
"storage_controller_client",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -7422,6 +7566,7 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7545,6 +7690,7 @@ dependencies = [
|
|||||||
"axum",
|
"axum",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"flate2",
|
||||||
"h2 0.4.4",
|
"h2 0.4.4",
|
||||||
"http 1.1.0",
|
"http 1.1.0",
|
||||||
"http-body 1.0.0",
|
"http-body 1.0.0",
|
||||||
@@ -7564,6 +7710,7 @@ dependencies = [
|
|||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7575,7 +7722,7 @@ dependencies = [
|
|||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"prost-build 0.13.3",
|
"prost-build 0.13.3",
|
||||||
"prost-types 0.13.3",
|
"prost-types 0.13.5",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
@@ -7587,7 +7734,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "f9687bd5bfeafebdded2356950f278bba8226f0b32109537c4253406e09aafe1"
|
checksum = "f9687bd5bfeafebdded2356950f278bba8226f0b32109537c4253406e09aafe1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"prost 0.13.5",
|
"prost 0.13.5",
|
||||||
"prost-types 0.13.3",
|
"prost-types 0.13.5",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tonic 0.13.1",
|
"tonic 0.13.1",
|
||||||
@@ -8143,6 +8290,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"pageserver_api",
|
"pageserver_api",
|
||||||
"postgres_ffi",
|
"postgres_ffi",
|
||||||
|
"postgres_ffi_types",
|
||||||
"pprof",
|
"pprof",
|
||||||
"prost 0.13.5",
|
"prost 0.13.5",
|
||||||
"remote_storage",
|
"remote_storage",
|
||||||
@@ -8566,7 +8714,6 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-core",
|
"axum-core",
|
||||||
"base64 0.13.1",
|
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"base64ct",
|
"base64ct",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -8588,8 +8735,10 @@ dependencies = [
|
|||||||
"fail",
|
"fail",
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
"futures-executor",
|
"futures-executor",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
"getrandom 0.2.11",
|
"getrandom 0.2.11",
|
||||||
|
|||||||
+18
-6
@@ -8,6 +8,7 @@ members = [
|
|||||||
"pageserver/compaction",
|
"pageserver/compaction",
|
||||||
"pageserver/ctl",
|
"pageserver/ctl",
|
||||||
"pageserver/client",
|
"pageserver/client",
|
||||||
|
"pageserver/client_grpc",
|
||||||
"pageserver/pagebench",
|
"pageserver/pagebench",
|
||||||
"pageserver/page_api",
|
"pageserver/page_api",
|
||||||
"proxy",
|
"proxy",
|
||||||
@@ -22,6 +23,8 @@ members = [
|
|||||||
"libs/http-utils",
|
"libs/http-utils",
|
||||||
"libs/pageserver_api",
|
"libs/pageserver_api",
|
||||||
"libs/postgres_ffi",
|
"libs/postgres_ffi",
|
||||||
|
"libs/postgres_ffi_types",
|
||||||
|
"libs/postgres_versioninfo",
|
||||||
"libs/safekeeper_api",
|
"libs/safekeeper_api",
|
||||||
"libs/desim",
|
"libs/desim",
|
||||||
"libs/neon-shmem",
|
"libs/neon-shmem",
|
||||||
@@ -40,10 +43,12 @@ members = [
|
|||||||
"libs/walproposer",
|
"libs/walproposer",
|
||||||
"libs/wal_decoder",
|
"libs/wal_decoder",
|
||||||
"libs/postgres_initdb",
|
"libs/postgres_initdb",
|
||||||
|
"libs/proxy/json",
|
||||||
"libs/proxy/postgres-protocol2",
|
"libs/proxy/postgres-protocol2",
|
||||||
"libs/proxy/postgres-types2",
|
"libs/proxy/postgres-types2",
|
||||||
"libs/proxy/tokio-postgres2",
|
"libs/proxy/tokio-postgres2",
|
||||||
"endpoint_storage",
|
"endpoint_storage",
|
||||||
|
"pgxn/neon/communicator",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
@@ -71,8 +76,8 @@ aws-credential-types = "1.2.0"
|
|||||||
aws-sigv4 = { version = "1.2", features = ["sign-http"] }
|
aws-sigv4 = { version = "1.2", features = ["sign-http"] }
|
||||||
aws-types = "1.3"
|
aws-types = "1.3"
|
||||||
axum = { version = "0.8.1", features = ["ws"] }
|
axum = { version = "0.8.1", features = ["ws"] }
|
||||||
axum-extra = { version = "0.10.0", features = ["typed-header"] }
|
axum-extra = { version = "0.10.0", features = ["typed-header", "query"] }
|
||||||
base64 = "0.13.0"
|
base64 = "0.22"
|
||||||
bincode = "1.3"
|
bincode = "1.3"
|
||||||
bindgen = "0.71"
|
bindgen = "0.71"
|
||||||
bit_field = "0.10.2"
|
bit_field = "0.10.2"
|
||||||
@@ -150,6 +155,7 @@ pprof = { version = "0.14", features = ["criterion", "flamegraph", "frame-pointe
|
|||||||
procfs = "0.16"
|
procfs = "0.16"
|
||||||
prometheus = {version = "0.13", default-features=false, features = ["process"]} # removes protobuf dependency
|
prometheus = {version = "0.13", default-features=false, features = ["process"]} # removes protobuf dependency
|
||||||
prost = "0.13.5"
|
prost = "0.13.5"
|
||||||
|
prost-types = "0.13.5"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
redis = { version = "0.29.2", features = ["tokio-rustls-comp", "keep-alive"] }
|
redis = { version = "0.29.2", features = ["tokio-rustls-comp", "keep-alive"] }
|
||||||
regex = "1.10.2"
|
regex = "1.10.2"
|
||||||
@@ -171,8 +177,9 @@ sentry = { version = "0.37", default-features = false, features = ["backtrace",
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
serde_path_to_error = "0.1"
|
serde_path_to_error = "0.1"
|
||||||
serde_with = { version = "2.0", features = [ "base64" ] }
|
serde_with = { version = "3", features = [ "base64" ] }
|
||||||
serde_assert = "0.5.0"
|
serde_assert = "0.5.0"
|
||||||
|
serde_repr = "0.1.20"
|
||||||
sha2 = "0.10.2"
|
sha2 = "0.10.2"
|
||||||
signal-hook = "0.3"
|
signal-hook = "0.3"
|
||||||
smallvec = "1.11"
|
smallvec = "1.11"
|
||||||
@@ -194,12 +201,12 @@ tokio-epoll-uring = { git = "https://github.com/neondatabase/tokio-epoll-uring.g
|
|||||||
tokio-io-timeout = "1.2.0"
|
tokio-io-timeout = "1.2.0"
|
||||||
tokio-postgres-rustls = "0.12.0"
|
tokio-postgres-rustls = "0.12.0"
|
||||||
tokio-rustls = { version = "0.26.0", default-features = false, features = ["tls12", "ring"]}
|
tokio-rustls = { version = "0.26.0", default-features = false, features = ["tls12", "ring"]}
|
||||||
tokio-stream = "0.1"
|
tokio-stream = { version = "0.1", features = ["sync"] }
|
||||||
tokio-tar = "0.3"
|
tokio-tar = "0.3"
|
||||||
tokio-util = { version = "0.7.10", features = ["io", "rt"] }
|
tokio-util = { version = "0.7.10", features = ["io", "io-util", "rt"] }
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
toml_edit = "0.22"
|
toml_edit = "0.22"
|
||||||
tonic = { version = "0.13.1", default-features = false, features = ["channel", "codegen", "prost", "router", "server", "tls-ring", "tls-native-roots"] }
|
tonic = { version = "0.13.1", default-features = false, features = ["channel", "codegen", "gzip", "prost", "router", "server", "tls-ring", "tls-native-roots", "zstd"] }
|
||||||
tonic-reflection = { version = "0.13.1", features = ["server"] }
|
tonic-reflection = { version = "0.13.1", features = ["server"] }
|
||||||
tower = { version = "0.5.2", default-features = false }
|
tower = { version = "0.5.2", default-features = false }
|
||||||
tower-http = { version = "0.6.2", features = ["auth", "request-id", "trace"] }
|
tower-http = { version = "0.6.2", features = ["auth", "request-id", "trace"] }
|
||||||
@@ -251,14 +258,18 @@ desim = { version = "0.1", path = "./libs/desim" }
|
|||||||
endpoint_storage = { version = "0.0.1", path = "./endpoint_storage/" }
|
endpoint_storage = { version = "0.0.1", path = "./endpoint_storage/" }
|
||||||
http-utils = { version = "0.1", path = "./libs/http-utils/" }
|
http-utils = { version = "0.1", path = "./libs/http-utils/" }
|
||||||
metrics = { version = "0.1", path = "./libs/metrics/" }
|
metrics = { version = "0.1", path = "./libs/metrics/" }
|
||||||
|
neon-shmem = { version = "0.1", path = "./libs/neon-shmem/" }
|
||||||
pageserver = { path = "./pageserver" }
|
pageserver = { path = "./pageserver" }
|
||||||
pageserver_api = { version = "0.1", path = "./libs/pageserver_api/" }
|
pageserver_api = { version = "0.1", path = "./libs/pageserver_api/" }
|
||||||
pageserver_client = { path = "./pageserver/client" }
|
pageserver_client = { path = "./pageserver/client" }
|
||||||
|
pageserver_client_grpc = { path = "./pageserver/client_grpc" }
|
||||||
pageserver_compaction = { version = "0.1", path = "./pageserver/compaction/" }
|
pageserver_compaction = { version = "0.1", path = "./pageserver/compaction/" }
|
||||||
pageserver_page_api = { path = "./pageserver/page_api" }
|
pageserver_page_api = { path = "./pageserver/page_api" }
|
||||||
postgres_backend = { version = "0.1", path = "./libs/postgres_backend/" }
|
postgres_backend = { version = "0.1", path = "./libs/postgres_backend/" }
|
||||||
postgres_connection = { version = "0.1", path = "./libs/postgres_connection/" }
|
postgres_connection = { version = "0.1", path = "./libs/postgres_connection/" }
|
||||||
postgres_ffi = { version = "0.1", path = "./libs/postgres_ffi/" }
|
postgres_ffi = { version = "0.1", path = "./libs/postgres_ffi/" }
|
||||||
|
postgres_ffi_types = { version = "0.1", path = "./libs/postgres_ffi_types/" }
|
||||||
|
postgres_versioninfo = { version = "0.1", path = "./libs/postgres_versioninfo/" }
|
||||||
postgres_initdb = { path = "./libs/postgres_initdb" }
|
postgres_initdb = { path = "./libs/postgres_initdb" }
|
||||||
posthog_client_lite = { version = "0.1", path = "./libs/posthog_client_lite" }
|
posthog_client_lite = { version = "0.1", path = "./libs/posthog_client_lite" }
|
||||||
pq_proto = { version = "0.1", path = "./libs/pq_proto/" }
|
pq_proto = { version = "0.1", path = "./libs/pq_proto/" }
|
||||||
@@ -278,6 +289,7 @@ walproposer = { version = "0.1", path = "./libs/walproposer/" }
|
|||||||
workspace_hack = { version = "0.1", path = "./workspace_hack/" }
|
workspace_hack = { version = "0.1", path = "./workspace_hack/" }
|
||||||
|
|
||||||
## Build dependencies
|
## Build dependencies
|
||||||
|
cbindgen = "0.29.0"
|
||||||
criterion = "0.5.1"
|
criterion = "0.5.1"
|
||||||
rcgen = "0.13"
|
rcgen = "0.13"
|
||||||
rstest = "0.18"
|
rstest = "0.18"
|
||||||
|
|||||||
+46
-30
@@ -5,8 +5,6 @@
|
|||||||
ARG REPOSITORY=ghcr.io/neondatabase
|
ARG REPOSITORY=ghcr.io/neondatabase
|
||||||
ARG IMAGE=build-tools
|
ARG IMAGE=build-tools
|
||||||
ARG TAG=pinned
|
ARG TAG=pinned
|
||||||
ARG DEFAULT_PG_VERSION=17
|
|
||||||
ARG STABLE_PG_VERSION=16
|
|
||||||
ARG DEBIAN_VERSION=bookworm
|
ARG DEBIAN_VERSION=bookworm
|
||||||
ARG DEBIAN_FLAVOR=${DEBIAN_VERSION}-slim
|
ARG DEBIAN_FLAVOR=${DEBIAN_VERSION}-slim
|
||||||
|
|
||||||
@@ -32,7 +30,18 @@ ARG BASE_IMAGE_SHA=debian:${DEBIAN_FLAVOR}
|
|||||||
ARG BASE_IMAGE_SHA=${BASE_IMAGE_SHA/debian:bookworm-slim/debian@$BOOKWORM_SLIM_SHA}
|
ARG BASE_IMAGE_SHA=${BASE_IMAGE_SHA/debian:bookworm-slim/debian@$BOOKWORM_SLIM_SHA}
|
||||||
ARG BASE_IMAGE_SHA=${BASE_IMAGE_SHA/debian:bullseye-slim/debian@$BULLSEYE_SLIM_SHA}
|
ARG BASE_IMAGE_SHA=${BASE_IMAGE_SHA/debian:bullseye-slim/debian@$BULLSEYE_SLIM_SHA}
|
||||||
|
|
||||||
# Build Postgres
|
# Naive way:
|
||||||
|
#
|
||||||
|
# 1. COPY . .
|
||||||
|
# 1. make neon-pg-ext
|
||||||
|
# 2. cargo build <storage binaries>
|
||||||
|
#
|
||||||
|
# But to enable docker to cache intermediate layers, we perform a few preparatory steps:
|
||||||
|
#
|
||||||
|
# - Build all postgres versions, depending on just the contents of vendor/
|
||||||
|
# - Use cargo chef to build all rust dependencies
|
||||||
|
|
||||||
|
# 1. Build all postgres versions
|
||||||
FROM $REPOSITORY/$IMAGE:$TAG AS pg-build
|
FROM $REPOSITORY/$IMAGE:$TAG AS pg-build
|
||||||
WORKDIR /home/nonroot
|
WORKDIR /home/nonroot
|
||||||
|
|
||||||
@@ -40,17 +49,15 @@ COPY --chown=nonroot vendor/postgres-v14 vendor/postgres-v14
|
|||||||
COPY --chown=nonroot vendor/postgres-v15 vendor/postgres-v15
|
COPY --chown=nonroot vendor/postgres-v15 vendor/postgres-v15
|
||||||
COPY --chown=nonroot vendor/postgres-v16 vendor/postgres-v16
|
COPY --chown=nonroot vendor/postgres-v16 vendor/postgres-v16
|
||||||
COPY --chown=nonroot vendor/postgres-v17 vendor/postgres-v17
|
COPY --chown=nonroot vendor/postgres-v17 vendor/postgres-v17
|
||||||
COPY --chown=nonroot pgxn pgxn
|
|
||||||
COPY --chown=nonroot Makefile Makefile
|
COPY --chown=nonroot Makefile Makefile
|
||||||
|
COPY --chown=nonroot postgres.mk postgres.mk
|
||||||
COPY --chown=nonroot scripts/ninstall.sh scripts/ninstall.sh
|
COPY --chown=nonroot scripts/ninstall.sh scripts/ninstall.sh
|
||||||
|
|
||||||
ENV BUILD_TYPE=release
|
ENV BUILD_TYPE=release
|
||||||
RUN set -e \
|
RUN set -e \
|
||||||
&& mold -run make -j $(nproc) -s neon-pg-ext \
|
&& mold -run make -j $(nproc) -s postgres
|
||||||
&& rm -rf pg_install/build \
|
|
||||||
&& tar -C pg_install -czf /home/nonroot/postgres_install.tar.gz .
|
|
||||||
|
|
||||||
# Prepare cargo-chef recipe
|
# 2. Prepare cargo-chef recipe
|
||||||
FROM $REPOSITORY/$IMAGE:$TAG AS plan
|
FROM $REPOSITORY/$IMAGE:$TAG AS plan
|
||||||
WORKDIR /home/nonroot
|
WORKDIR /home/nonroot
|
||||||
|
|
||||||
@@ -58,26 +65,22 @@ COPY --chown=nonroot . .
|
|||||||
|
|
||||||
RUN cargo chef prepare --recipe-path recipe.json
|
RUN cargo chef prepare --recipe-path recipe.json
|
||||||
|
|
||||||
# Build neon binaries
|
# Main build image
|
||||||
FROM $REPOSITORY/$IMAGE:$TAG AS build
|
FROM $REPOSITORY/$IMAGE:$TAG AS build
|
||||||
WORKDIR /home/nonroot
|
WORKDIR /home/nonroot
|
||||||
ARG GIT_VERSION=local
|
ARG GIT_VERSION=local
|
||||||
ARG BUILD_TAG
|
ARG BUILD_TAG
|
||||||
ARG STABLE_PG_VERSION
|
|
||||||
|
|
||||||
COPY --from=pg-build /home/nonroot/pg_install/v14/include/postgresql/server pg_install/v14/include/postgresql/server
|
|
||||||
COPY --from=pg-build /home/nonroot/pg_install/v15/include/postgresql/server pg_install/v15/include/postgresql/server
|
|
||||||
COPY --from=pg-build /home/nonroot/pg_install/v16/include/postgresql/server pg_install/v16/include/postgresql/server
|
|
||||||
COPY --from=pg-build /home/nonroot/pg_install/v17/include/postgresql/server pg_install/v17/include/postgresql/server
|
|
||||||
COPY --from=pg-build /home/nonroot/pg_install/v16/lib pg_install/v16/lib
|
|
||||||
COPY --from=pg-build /home/nonroot/pg_install/v17/lib pg_install/v17/lib
|
|
||||||
COPY --from=plan /home/nonroot/recipe.json recipe.json
|
|
||||||
|
|
||||||
ARG ADDITIONAL_RUSTFLAGS=""
|
ARG ADDITIONAL_RUSTFLAGS=""
|
||||||
|
|
||||||
|
# 3. Build cargo dependencies. Note that this step doesn't depend on anything else than
|
||||||
|
# `recipe.json`, so the layer can be reused as long as none of the dependencies change.
|
||||||
|
COPY --from=plan /home/nonroot/recipe.json recipe.json
|
||||||
RUN set -e \
|
RUN set -e \
|
||||||
&& RUSTFLAGS="-Clinker=clang -Clink-arg=-fuse-ld=mold -Clink-arg=-Wl,--no-rosegment -Cforce-frame-pointers=yes ${ADDITIONAL_RUSTFLAGS}" cargo chef cook --locked --release --recipe-path recipe.json
|
&& RUSTFLAGS="-Clinker=clang -Clink-arg=-fuse-ld=mold -Clink-arg=-Wl,--no-rosegment -Cforce-frame-pointers=yes ${ADDITIONAL_RUSTFLAGS}" cargo chef cook --locked --release --recipe-path recipe.json
|
||||||
|
|
||||||
|
# Perform the main build. We reuse the Postgres build artifacts from the intermediate 'pg-build'
|
||||||
|
# layer, and the cargo dependencies built in the previous step.
|
||||||
|
COPY --chown=nonroot --from=pg-build /home/nonroot/pg_install/ pg_install
|
||||||
COPY --chown=nonroot . .
|
COPY --chown=nonroot . .
|
||||||
|
|
||||||
RUN set -e \
|
RUN set -e \
|
||||||
@@ -92,12 +95,11 @@ RUN set -e \
|
|||||||
--bin endpoint_storage \
|
--bin endpoint_storage \
|
||||||
--bin neon_local \
|
--bin neon_local \
|
||||||
--bin storage_scrubber \
|
--bin storage_scrubber \
|
||||||
--locked --release
|
--locked --release \
|
||||||
|
&& mold -run make -j $(nproc) -s neon-pg-ext
|
||||||
|
|
||||||
# Build final image
|
# Assemble the final image
|
||||||
#
|
|
||||||
FROM $BASE_IMAGE_SHA
|
FROM $BASE_IMAGE_SHA
|
||||||
ARG DEFAULT_PG_VERSION
|
|
||||||
WORKDIR /data
|
WORKDIR /data
|
||||||
|
|
||||||
RUN set -e \
|
RUN set -e \
|
||||||
@@ -107,9 +109,20 @@ RUN set -e \
|
|||||||
libreadline-dev \
|
libreadline-dev \
|
||||||
libseccomp-dev \
|
libseccomp-dev \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
# System postgres for use with client libraries (e.g. in storage controller)
|
|
||||||
postgresql-15 \
|
|
||||||
openssl \
|
openssl \
|
||||||
|
unzip \
|
||||||
|
curl \
|
||||||
|
&& ARCH=$(uname -m) \
|
||||||
|
&& if [ "$ARCH" = "x86_64" ]; then \
|
||||||
|
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"; \
|
||||||
|
elif [ "$ARCH" = "aarch64" ]; then \
|
||||||
|
curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"; \
|
||||||
|
else \
|
||||||
|
echo "Unsupported architecture: $ARCH" && exit 1; \
|
||||||
|
fi \
|
||||||
|
&& unzip awscliv2.zip \
|
||||||
|
&& ./aws/install \
|
||||||
|
&& rm -rf aws awscliv2.zip \
|
||||||
&& rm -f /etc/apt/apt.conf.d/80-retries \
|
&& rm -f /etc/apt/apt.conf.d/80-retries \
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
|
||||||
&& useradd -d /data neon \
|
&& useradd -d /data neon \
|
||||||
@@ -125,12 +138,15 @@ COPY --from=build --chown=neon:neon /home/nonroot/target/release/proxy
|
|||||||
COPY --from=build --chown=neon:neon /home/nonroot/target/release/endpoint_storage /usr/local/bin
|
COPY --from=build --chown=neon:neon /home/nonroot/target/release/endpoint_storage /usr/local/bin
|
||||||
COPY --from=build --chown=neon:neon /home/nonroot/target/release/neon_local /usr/local/bin
|
COPY --from=build --chown=neon:neon /home/nonroot/target/release/neon_local /usr/local/bin
|
||||||
COPY --from=build --chown=neon:neon /home/nonroot/target/release/storage_scrubber /usr/local/bin
|
COPY --from=build --chown=neon:neon /home/nonroot/target/release/storage_scrubber /usr/local/bin
|
||||||
|
COPY --from=build /home/nonroot/pg_install/v14 /usr/local/v14/
|
||||||
|
COPY --from=build /home/nonroot/pg_install/v15 /usr/local/v15/
|
||||||
|
COPY --from=build /home/nonroot/pg_install/v16 /usr/local/v16/
|
||||||
|
COPY --from=build /home/nonroot/pg_install/v17 /usr/local/v17/
|
||||||
|
|
||||||
COPY --from=pg-build /home/nonroot/pg_install/v14 /usr/local/v14/
|
# Deprecated: Old deployment scripts use this tarball which contains all the Postgres binaries.
|
||||||
COPY --from=pg-build /home/nonroot/pg_install/v15 /usr/local/v15/
|
# That's obsolete, since all the same files are also present under /usr/local/v*. But to keep the
|
||||||
COPY --from=pg-build /home/nonroot/pg_install/v16 /usr/local/v16/
|
# old scripts working for now, create the tarball.
|
||||||
COPY --from=pg-build /home/nonroot/pg_install/v17 /usr/local/v17/
|
RUN tar -C /usr/local -cvzf /data/postgres_install.tar.gz v14 v15 v16 v17
|
||||||
COPY --from=pg-build /home/nonroot/postgres_install.tar.gz /data/
|
|
||||||
|
|
||||||
# By default, pageserver uses `.neon/` working directory in WORKDIR, so create one and fill it with the dummy config.
|
# By default, pageserver uses `.neon/` working directory in WORKDIR, so create one and fill it with the dummy config.
|
||||||
# Now, when `docker run ... pageserver` is run, it can start without errors, yet will have some default dummy values.
|
# Now, when `docker run ... pageserver` is run, it can start without errors, yet will have some default dummy values.
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
ROOT_PROJECT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
ROOT_PROJECT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||||
|
|
||||||
# Where to install Postgres, default is ./pg_install, maybe useful for package managers
|
# Where to install Postgres, default is ./pg_install, maybe useful for package
|
||||||
|
# managers.
|
||||||
POSTGRES_INSTALL_DIR ?= $(ROOT_PROJECT_DIR)/pg_install/
|
POSTGRES_INSTALL_DIR ?= $(ROOT_PROJECT_DIR)/pg_install/
|
||||||
|
|
||||||
|
# Supported PostgreSQL versions
|
||||||
|
POSTGRES_VERSIONS = v17 v16 v15 v14
|
||||||
|
|
||||||
|
# CARGO_BUILD_FLAGS: Extra flags to pass to `cargo build`. `--locked`
|
||||||
|
# and `--features testing` are popular examples.
|
||||||
|
#
|
||||||
|
# CARGO_PROFILE: Set to override the cargo profile to use. By default,
|
||||||
|
# it is derived from BUILD_TYPE.
|
||||||
|
|
||||||
|
# All intermediate build artifacts are stored here.
|
||||||
|
BUILD_DIR := build
|
||||||
|
|
||||||
ICU_PREFIX_DIR := /usr/local/icu
|
ICU_PREFIX_DIR := /usr/local/icu
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -16,12 +29,19 @@ ifeq ($(BUILD_TYPE),release)
|
|||||||
PG_CONFIGURE_OPTS = --enable-debug --with-openssl
|
PG_CONFIGURE_OPTS = --enable-debug --with-openssl
|
||||||
PG_CFLAGS += -O2 -g3 $(CFLAGS)
|
PG_CFLAGS += -O2 -g3 $(CFLAGS)
|
||||||
PG_LDFLAGS = $(LDFLAGS)
|
PG_LDFLAGS = $(LDFLAGS)
|
||||||
# Unfortunately, `--profile=...` is a nightly feature
|
CARGO_PROFILE ?= --profile=release
|
||||||
CARGO_BUILD_FLAGS += --release
|
# NEON_CARGO_ARTIFACT_TARGET_DIR is the directory where `cargo build` places
|
||||||
|
# the final build artifacts. There is unfortunately no easy way of changing
|
||||||
|
# it to a fully predictable path, nor to extract the path with a simple
|
||||||
|
# command. See https://github.com/rust-lang/cargo/issues/9661 and
|
||||||
|
# https://github.com/rust-lang/cargo/issues/6790.
|
||||||
|
NEON_CARGO_ARTIFACT_TARGET_DIR = $(ROOT_PROJECT_DIR)/target/release
|
||||||
else ifeq ($(BUILD_TYPE),debug)
|
else ifeq ($(BUILD_TYPE),debug)
|
||||||
PG_CONFIGURE_OPTS = --enable-debug --with-openssl --enable-cassert --enable-depend
|
PG_CONFIGURE_OPTS = --enable-debug --with-openssl --enable-cassert --enable-depend
|
||||||
PG_CFLAGS += -O0 -g3 $(CFLAGS)
|
PG_CFLAGS += -O0 -g3 $(CFLAGS)
|
||||||
PG_LDFLAGS = $(LDFLAGS)
|
PG_LDFLAGS = $(LDFLAGS)
|
||||||
|
CARGO_PROFILE ?= --profile=dev
|
||||||
|
NEON_CARGO_ARTIFACT_TARGET_DIR = $(ROOT_PROJECT_DIR)/target/debug
|
||||||
else
|
else
|
||||||
$(error Bad build type '$(BUILD_TYPE)', see Makefile for options)
|
$(error Bad build type '$(BUILD_TYPE)', see Makefile for options)
|
||||||
endif
|
endif
|
||||||
@@ -85,141 +105,32 @@ CACHEDIR_TAG_CONTENTS := "Signature: 8a477f597d28d172789f06886806bc55"
|
|||||||
# Top level Makefile to build Neon and PostgreSQL
|
# Top level Makefile to build Neon and PostgreSQL
|
||||||
#
|
#
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: neon postgres neon-pg-ext
|
all: neon postgres-install neon-pg-ext
|
||||||
|
|
||||||
### Neon Rust bits
|
### Neon Rust bits
|
||||||
#
|
#
|
||||||
# The 'postgres_ffi' depends on the Postgres headers.
|
# The 'postgres_ffi' crate depends on the Postgres headers.
|
||||||
.PHONY: neon
|
.PHONY: neon
|
||||||
neon: postgres-headers walproposer-lib cargo-target-dir
|
neon: postgres-headers-install walproposer-lib cargo-target-dir
|
||||||
+@echo "Compiling Neon"
|
+@echo "Compiling Neon"
|
||||||
$(CARGO_CMD_PREFIX) cargo build $(CARGO_BUILD_FLAGS)
|
$(CARGO_CMD_PREFIX) cargo build $(CARGO_BUILD_FLAGS) $(CARGO_PROFILE)
|
||||||
|
|
||||||
.PHONY: cargo-target-dir
|
.PHONY: cargo-target-dir
|
||||||
cargo-target-dir:
|
cargo-target-dir:
|
||||||
# https://github.com/rust-lang/cargo/issues/14281
|
# https://github.com/rust-lang/cargo/issues/14281
|
||||||
mkdir -p target
|
mkdir -p target
|
||||||
test -e target/CACHEDIR.TAG || echo "$(CACHEDIR_TAG_CONTENTS)" > target/CACHEDIR.TAG
|
test -e target/CACHEDIR.TAG || echo "$(CACHEDIR_TAG_CONTENTS)" > target/CACHEDIR.TAG
|
||||||
|
|
||||||
### PostgreSQL parts
|
|
||||||
# Some rules are duplicated for Postgres v14 and 15. We may want to refactor
|
|
||||||
# to avoid the duplication in the future, but it's tolerable for now.
|
|
||||||
#
|
|
||||||
$(POSTGRES_INSTALL_DIR)/build/%/config.status:
|
|
||||||
|
|
||||||
mkdir -p $(POSTGRES_INSTALL_DIR)
|
|
||||||
test -e $(POSTGRES_INSTALL_DIR)/CACHEDIR.TAG || echo "$(CACHEDIR_TAG_CONTENTS)" > $(POSTGRES_INSTALL_DIR)/CACHEDIR.TAG
|
|
||||||
|
|
||||||
+@echo "Configuring Postgres $* build"
|
|
||||||
@test -s $(ROOT_PROJECT_DIR)/vendor/postgres-$*/configure || { \
|
|
||||||
echo "\nPostgres submodule not found in $(ROOT_PROJECT_DIR)/vendor/postgres-$*/, execute "; \
|
|
||||||
echo "'git submodule update --init --recursive --depth 2 --progress .' in project root.\n"; \
|
|
||||||
exit 1; }
|
|
||||||
mkdir -p $(POSTGRES_INSTALL_DIR)/build/$*
|
|
||||||
|
|
||||||
VERSION=$*; \
|
|
||||||
EXTRA_VERSION=$$(cd $(ROOT_PROJECT_DIR)/vendor/postgres-$$VERSION && git rev-parse HEAD); \
|
|
||||||
(cd $(POSTGRES_INSTALL_DIR)/build/$$VERSION && \
|
|
||||||
env PATH="$(EXTRA_PATH_OVERRIDES):$$PATH" $(ROOT_PROJECT_DIR)/vendor/postgres-$$VERSION/configure \
|
|
||||||
CFLAGS='$(PG_CFLAGS)' LDFLAGS='$(PG_LDFLAGS)' \
|
|
||||||
$(PG_CONFIGURE_OPTS) --with-extra-version=" ($$EXTRA_VERSION)" \
|
|
||||||
--prefix=$(abspath $(POSTGRES_INSTALL_DIR))/$$VERSION > configure.log)
|
|
||||||
|
|
||||||
# nicer alias to run 'configure'
|
|
||||||
# Note: I've been unable to use templates for this part of our configuration.
|
|
||||||
# I'm not sure why it wouldn't work, but this is the only place (apart from
|
|
||||||
# the "build-all-versions" entry points) where direct mention of PostgreSQL
|
|
||||||
# versions is used.
|
|
||||||
.PHONY: postgres-configure-v17
|
|
||||||
postgres-configure-v17: $(POSTGRES_INSTALL_DIR)/build/v17/config.status
|
|
||||||
.PHONY: postgres-configure-v16
|
|
||||||
postgres-configure-v16: $(POSTGRES_INSTALL_DIR)/build/v16/config.status
|
|
||||||
.PHONY: postgres-configure-v15
|
|
||||||
postgres-configure-v15: $(POSTGRES_INSTALL_DIR)/build/v15/config.status
|
|
||||||
.PHONY: postgres-configure-v14
|
|
||||||
postgres-configure-v14: $(POSTGRES_INSTALL_DIR)/build/v14/config.status
|
|
||||||
|
|
||||||
# Install the PostgreSQL header files into $(POSTGRES_INSTALL_DIR)/<version>/include
|
|
||||||
.PHONY: postgres-headers-%
|
|
||||||
postgres-headers-%: postgres-configure-%
|
|
||||||
+@echo "Installing PostgreSQL $* headers"
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$*/src/include MAKELEVEL=0 install
|
|
||||||
|
|
||||||
# Compile and install PostgreSQL
|
|
||||||
.PHONY: postgres-%
|
|
||||||
postgres-%: postgres-configure-% \
|
|
||||||
postgres-headers-% # to prevent `make install` conflicts with neon's `postgres-headers`
|
|
||||||
+@echo "Compiling PostgreSQL $*"
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$* MAKELEVEL=0 install
|
|
||||||
+@echo "Compiling libpq $*"
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$*/src/interfaces/libpq install
|
|
||||||
+@echo "Compiling pg_prewarm $*"
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$*/contrib/pg_prewarm install
|
|
||||||
+@echo "Compiling pg_buffercache $*"
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$*/contrib/pg_buffercache install
|
|
||||||
+@echo "Compiling pg_visibility $*"
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$*/contrib/pg_visibility install
|
|
||||||
+@echo "Compiling pageinspect $*"
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$*/contrib/pageinspect install
|
|
||||||
+@echo "Compiling pg_trgm $*"
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$*/contrib/pg_trgm install
|
|
||||||
+@echo "Compiling amcheck $*"
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$*/contrib/amcheck install
|
|
||||||
+@echo "Compiling test_decoding $*"
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$*/contrib/test_decoding install
|
|
||||||
|
|
||||||
.PHONY: postgres-clean-%
|
|
||||||
postgres-clean-%:
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$* MAKELEVEL=0 clean
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$*/contrib/pg_buffercache clean
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$*/contrib/pageinspect clean
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$*/src/interfaces/libpq clean
|
|
||||||
|
|
||||||
.PHONY: postgres-check-%
|
|
||||||
postgres-check-%: postgres-%
|
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$* MAKELEVEL=0 check
|
|
||||||
|
|
||||||
.PHONY: neon-pg-ext-%
|
.PHONY: neon-pg-ext-%
|
||||||
neon-pg-ext-%: postgres-%
|
neon-pg-ext-%: postgres-install-% cargo-target-dir
|
||||||
+@echo "Compiling neon $*"
|
+@echo "Compiling neon-specific Postgres extensions for $*"
|
||||||
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-$*
|
mkdir -p $(BUILD_DIR)/pgxn-$*
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config COPT='$(COPT)' \
|
$(MAKE) PG_CONFIG="$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config" COPT='$(COPT)' \
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-$* \
|
NEON_CARGO_ARTIFACT_TARGET_DIR="$(NEON_CARGO_ARTIFACT_TARGET_DIR)" \
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon/Makefile install
|
CARGO_BUILD_FLAGS="$(CARGO_BUILD_FLAGS)" \
|
||||||
+@echo "Compiling neon_walredo $*"
|
CARGO_PROFILE="$(CARGO_PROFILE)" \
|
||||||
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-walredo-$*
|
-C $(BUILD_DIR)/pgxn-$*\
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config COPT='$(COPT)' \
|
-f $(ROOT_PROJECT_DIR)/pgxn/Makefile install
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-walredo-$* \
|
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon_walredo/Makefile install
|
|
||||||
+@echo "Compiling neon_rmgr $*"
|
|
||||||
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-rmgr-$*
|
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config COPT='$(COPT)' \
|
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-rmgr-$* \
|
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon_rmgr/Makefile install
|
|
||||||
+@echo "Compiling neon_test_utils $*"
|
|
||||||
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-test-utils-$*
|
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config COPT='$(COPT)' \
|
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-test-utils-$* \
|
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon_test_utils/Makefile install
|
|
||||||
+@echo "Compiling neon_utils $*"
|
|
||||||
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-utils-$*
|
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config COPT='$(COPT)' \
|
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-utils-$* \
|
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon_utils/Makefile install
|
|
||||||
|
|
||||||
.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
|
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config \
|
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-walredo-$* \
|
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon_walredo/Makefile clean
|
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config \
|
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-test-utils-$* \
|
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon_test_utils/Makefile clean
|
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config \
|
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-utils-$* \
|
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon_utils/Makefile clean
|
|
||||||
|
|
||||||
# Build walproposer as a static library. walproposer source code is located
|
# Build walproposer as a static library. walproposer source code is located
|
||||||
# in the pgxn/neon directory.
|
# in the pgxn/neon directory.
|
||||||
@@ -233,15 +144,15 @@ neon-pg-clean-ext-%:
|
|||||||
.PHONY: walproposer-lib
|
.PHONY: walproposer-lib
|
||||||
walproposer-lib: neon-pg-ext-v17
|
walproposer-lib: neon-pg-ext-v17
|
||||||
+@echo "Compiling walproposer-lib"
|
+@echo "Compiling walproposer-lib"
|
||||||
mkdir -p $(POSTGRES_INSTALL_DIR)/build/walproposer-lib
|
mkdir -p $(BUILD_DIR)/walproposer-lib
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/v17/bin/pg_config COPT='$(COPT)' \
|
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/v17/bin/pg_config COPT='$(COPT)' \
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/walproposer-lib \
|
-C $(BUILD_DIR)/walproposer-lib \
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon/Makefile walproposer-lib
|
-f $(ROOT_PROJECT_DIR)/pgxn/neon/Makefile walproposer-lib
|
||||||
cp $(POSTGRES_INSTALL_DIR)/v17/lib/libpgport.a $(POSTGRES_INSTALL_DIR)/build/walproposer-lib
|
cp $(POSTGRES_INSTALL_DIR)/v17/lib/libpgport.a $(BUILD_DIR)/walproposer-lib
|
||||||
cp $(POSTGRES_INSTALL_DIR)/v17/lib/libpgcommon.a $(POSTGRES_INSTALL_DIR)/build/walproposer-lib
|
cp $(POSTGRES_INSTALL_DIR)/v17/lib/libpgcommon.a $(BUILD_DIR)/walproposer-lib
|
||||||
$(AR) d $(POSTGRES_INSTALL_DIR)/build/walproposer-lib/libpgport.a \
|
$(AR) d $(BUILD_DIR)/walproposer-lib/libpgport.a \
|
||||||
pg_strong_random.o
|
pg_strong_random.o
|
||||||
$(AR) d $(POSTGRES_INSTALL_DIR)/build/walproposer-lib/libpgcommon.a \
|
$(AR) d $(BUILD_DIR)/walproposer-lib/libpgcommon.a \
|
||||||
checksum_helper.o \
|
checksum_helper.o \
|
||||||
cryptohash_openssl.o \
|
cryptohash_openssl.o \
|
||||||
hmac_openssl.o \
|
hmac_openssl.o \
|
||||||
@@ -249,69 +160,18 @@ walproposer-lib: neon-pg-ext-v17
|
|||||||
parse_manifest.o \
|
parse_manifest.o \
|
||||||
scram-common.o
|
scram-common.o
|
||||||
ifeq ($(UNAME_S),Linux)
|
ifeq ($(UNAME_S),Linux)
|
||||||
$(AR) d $(POSTGRES_INSTALL_DIR)/build/walproposer-lib/libpgcommon.a \
|
$(AR) d $(BUILD_DIR)/walproposer-lib/libpgcommon.a \
|
||||||
pg_crc32c.o
|
pg_crc32c.o
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: walproposer-lib-clean
|
# Shorthand to call neon-pg-ext-% target for all Postgres versions
|
||||||
walproposer-lib-clean:
|
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/v17/bin/pg_config \
|
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/walproposer-lib \
|
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon/Makefile clean
|
|
||||||
|
|
||||||
.PHONY: neon-pg-ext
|
.PHONY: neon-pg-ext
|
||||||
neon-pg-ext: \
|
neon-pg-ext: $(foreach pg_version,$(POSTGRES_VERSIONS),neon-pg-ext-$(pg_version))
|
||||||
neon-pg-ext-v14 \
|
|
||||||
neon-pg-ext-v15 \
|
|
||||||
neon-pg-ext-v16 \
|
|
||||||
neon-pg-ext-v17
|
|
||||||
|
|
||||||
.PHONY: neon-pg-clean-ext
|
|
||||||
neon-pg-clean-ext: \
|
|
||||||
neon-pg-clean-ext-v14 \
|
|
||||||
neon-pg-clean-ext-v15 \
|
|
||||||
neon-pg-clean-ext-v16 \
|
|
||||||
neon-pg-clean-ext-v17
|
|
||||||
|
|
||||||
# shorthand to build all Postgres versions
|
|
||||||
.PHONY: postgres
|
|
||||||
postgres: \
|
|
||||||
postgres-v14 \
|
|
||||||
postgres-v15 \
|
|
||||||
postgres-v16 \
|
|
||||||
postgres-v17
|
|
||||||
|
|
||||||
.PHONY: postgres-headers
|
|
||||||
postgres-headers: \
|
|
||||||
postgres-headers-v14 \
|
|
||||||
postgres-headers-v15 \
|
|
||||||
postgres-headers-v16 \
|
|
||||||
postgres-headers-v17
|
|
||||||
|
|
||||||
.PHONY: postgres-clean
|
|
||||||
postgres-clean: \
|
|
||||||
postgres-clean-v14 \
|
|
||||||
postgres-clean-v15 \
|
|
||||||
postgres-clean-v16 \
|
|
||||||
postgres-clean-v17
|
|
||||||
|
|
||||||
.PHONY: postgres-check
|
|
||||||
postgres-check: \
|
|
||||||
postgres-check-v14 \
|
|
||||||
postgres-check-v15 \
|
|
||||||
postgres-check-v16 \
|
|
||||||
postgres-check-v17
|
|
||||||
|
|
||||||
# This doesn't remove the effects of 'configure'.
|
|
||||||
.PHONY: clean
|
|
||||||
clean: postgres-clean neon-pg-clean-ext
|
|
||||||
$(MAKE) -C compute clean
|
|
||||||
$(CARGO_CMD_PREFIX) cargo clean
|
|
||||||
|
|
||||||
# This removes everything
|
# This removes everything
|
||||||
.PHONY: distclean
|
.PHONY: distclean
|
||||||
distclean:
|
distclean:
|
||||||
$(RM) -r $(POSTGRES_INSTALL_DIR)
|
$(RM) -r $(POSTGRES_INSTALL_DIR) $(BUILD_DIR)
|
||||||
$(CARGO_CMD_PREFIX) cargo clean
|
$(CARGO_CMD_PREFIX) cargo clean
|
||||||
|
|
||||||
.PHONY: fmt
|
.PHONY: fmt
|
||||||
@@ -320,7 +180,7 @@ fmt:
|
|||||||
|
|
||||||
postgres-%-pg-bsd-indent: postgres-%
|
postgres-%-pg-bsd-indent: postgres-%
|
||||||
+@echo "Compiling pg_bsd_indent"
|
+@echo "Compiling pg_bsd_indent"
|
||||||
$(MAKE) -C $(POSTGRES_INSTALL_DIR)/build/$*/src/tools/pg_bsd_indent/
|
$(MAKE) -C $(BUILD_DIR)/$*/src/tools/pg_bsd_indent/
|
||||||
|
|
||||||
# Create typedef list for the core. Note that generally it should be combined with
|
# Create typedef list for the core. Note that generally it should be combined with
|
||||||
# buildfarm one to cover platform specific stuff.
|
# buildfarm one to cover platform specific stuff.
|
||||||
@@ -339,7 +199,7 @@ postgres-%-pgindent: postgres-%-pg-bsd-indent postgres-%-typedefs.list
|
|||||||
cat $(ROOT_PROJECT_DIR)/vendor/postgres-$*/src/tools/pgindent/typedefs.list |\
|
cat $(ROOT_PROJECT_DIR)/vendor/postgres-$*/src/tools/pgindent/typedefs.list |\
|
||||||
cat - postgres-$*-typedefs.list | sort | uniq > postgres-$*-typedefs-full.list
|
cat - postgres-$*-typedefs.list | sort | uniq > postgres-$*-typedefs-full.list
|
||||||
+@echo note: you might want to run it on selected files/dirs instead.
|
+@echo note: you might want to run it on selected files/dirs instead.
|
||||||
INDENT=$(POSTGRES_INSTALL_DIR)/build/$*/src/tools/pg_bsd_indent/pg_bsd_indent \
|
INDENT=$(BUILD_DIR)/$*/src/tools/pg_bsd_indent/pg_bsd_indent \
|
||||||
$(ROOT_PROJECT_DIR)/vendor/postgres-$*/src/tools/pgindent/pgindent --typedefs postgres-$*-typedefs-full.list \
|
$(ROOT_PROJECT_DIR)/vendor/postgres-$*/src/tools/pgindent/pgindent --typedefs postgres-$*-typedefs-full.list \
|
||||||
$(ROOT_PROJECT_DIR)/vendor/postgres-$*/src/ \
|
$(ROOT_PROJECT_DIR)/vendor/postgres-$*/src/ \
|
||||||
--excludes $(ROOT_PROJECT_DIR)/vendor/postgres-$*/src/tools/pgindent/exclude_file_patterns
|
--excludes $(ROOT_PROJECT_DIR)/vendor/postgres-$*/src/tools/pgindent/exclude_file_patterns
|
||||||
@@ -350,12 +210,37 @@ postgres-%-pgindent: postgres-%-pg-bsd-indent postgres-%-typedefs.list
|
|||||||
neon-pgindent: postgres-v17-pg-bsd-indent neon-pg-ext-v17
|
neon-pgindent: postgres-v17-pg-bsd-indent neon-pg-ext-v17
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/v17/bin/pg_config COPT='$(COPT)' \
|
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/v17/bin/pg_config COPT='$(COPT)' \
|
||||||
FIND_TYPEDEF=$(ROOT_PROJECT_DIR)/vendor/postgres-v17/src/tools/find_typedef \
|
FIND_TYPEDEF=$(ROOT_PROJECT_DIR)/vendor/postgres-v17/src/tools/find_typedef \
|
||||||
INDENT=$(POSTGRES_INSTALL_DIR)/build/v17/src/tools/pg_bsd_indent/pg_bsd_indent \
|
INDENT=$(BUILD_DIR)/v17/src/tools/pg_bsd_indent/pg_bsd_indent \
|
||||||
PGINDENT_SCRIPT=$(ROOT_PROJECT_DIR)/vendor/postgres-v17/src/tools/pgindent/pgindent \
|
PGINDENT_SCRIPT=$(ROOT_PROJECT_DIR)/vendor/postgres-v17/src/tools/pgindent/pgindent \
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-v17 \
|
-C $(BUILD_DIR)/neon-v17 \
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon/Makefile pgindent
|
-f $(ROOT_PROJECT_DIR)/pgxn/neon/Makefile pgindent
|
||||||
|
|
||||||
|
|
||||||
.PHONY: setup-pre-commit-hook
|
.PHONY: setup-pre-commit-hook
|
||||||
setup-pre-commit-hook:
|
setup-pre-commit-hook:
|
||||||
ln -s -f $(ROOT_PROJECT_DIR)/pre-commit.py .git/hooks/pre-commit
|
ln -s -f $(ROOT_PROJECT_DIR)/pre-commit.py .git/hooks/pre-commit
|
||||||
|
|
||||||
|
.PHONY: lint-openapi-spec
|
||||||
|
lint-openapi-spec:
|
||||||
|
# operation-2xx-response: pageserver timeline delete returns 404 on success
|
||||||
|
find . -iname "openapi_spec.y*ml" -exec\
|
||||||
|
docker run --rm -v ${PWD}:/spec ghcr.io/redocly/cli:1.34.4\
|
||||||
|
--skip-rule=operation-operationId --skip-rule=operation-summary --extends=minimal\
|
||||||
|
--skip-rule=no-server-example.com --skip-rule=operation-2xx-response\
|
||||||
|
lint {} \+
|
||||||
|
|
||||||
|
# Targets for building PostgreSQL are defined in postgres.mk.
|
||||||
|
#
|
||||||
|
# But if the caller has indicated that PostgreSQL is already
|
||||||
|
# installed, by setting the PG_INSTALL_CACHED variable, skip it.
|
||||||
|
ifdef PG_INSTALL_CACHED
|
||||||
|
postgres-install: skip-install
|
||||||
|
$(foreach pg_version,$(POSTGRES_VERSIONS),postgres-install-$(pg_version)): skip-install
|
||||||
|
postgres-headers-install:
|
||||||
|
+@echo "Skipping installation of PostgreSQL headers because PG_INSTALL_CACHED is set"
|
||||||
|
skip-install:
|
||||||
|
+@echo "Skipping PostgreSQL installation because PG_INSTALL_CACHED is set"
|
||||||
|
|
||||||
|
else
|
||||||
|
include postgres.mk
|
||||||
|
endif
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries && \
|
|||||||
echo -e "retry_connrefused=on\ntimeout=15\ntries=5\nretry-on-host-error=on\n" > /root/.wgetrc && \
|
echo -e "retry_connrefused=on\ntimeout=15\ntries=5\nretry-on-host-error=on\n" > /root/.wgetrc && \
|
||||||
echo -e "--retry-connrefused\n--connect-timeout 15\n--retry 5\n--max-time 300\n" > /root/.curlrc
|
echo -e "--retry-connrefused\n--connect-timeout 15\n--retry 5\n--max-time 300\n" > /root/.curlrc
|
||||||
|
|
||||||
COPY build_tools/patches/pgcopydbv017.patch /pgcopydbv017.patch
|
COPY build-tools/patches/pgcopydbv017.patch /pgcopydbv017.patch
|
||||||
|
|
||||||
RUN if [ "${DEBIAN_VERSION}" = "bookworm" ]; then \
|
RUN if [ "${DEBIAN_VERSION}" = "bookworm" ]; then \
|
||||||
set -e && \
|
set -e && \
|
||||||
@@ -165,6 +165,7 @@ RUN curl -fsSL \
|
|||||||
&& rm sql_exporter.tar.gz
|
&& rm sql_exporter.tar.gz
|
||||||
|
|
||||||
# protobuf-compiler (protoc)
|
# protobuf-compiler (protoc)
|
||||||
|
# Keep the version the same as in compute/compute-node.Dockerfile
|
||||||
ENV PROTOC_VERSION=25.1
|
ENV PROTOC_VERSION=25.1
|
||||||
RUN curl -fsSL "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-$(uname -m | sed 's/aarch64/aarch_64/g').zip" -o "protoc.zip" \
|
RUN curl -fsSL "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-$(uname -m | sed 's/aarch64/aarch_64/g').zip" -o "protoc.zip" \
|
||||||
&& unzip -q protoc.zip -d protoc \
|
&& unzip -q protoc.zip -d protoc \
|
||||||
@@ -179,7 +180,7 @@ RUN curl -sL "https://github.com/peak/s5cmd/releases/download/v${S5CMD_VERSION}/
|
|||||||
&& mv s5cmd /usr/local/bin/s5cmd
|
&& mv s5cmd /usr/local/bin/s5cmd
|
||||||
|
|
||||||
# LLVM
|
# LLVM
|
||||||
ENV LLVM_VERSION=19
|
ENV LLVM_VERSION=20
|
||||||
RUN curl -fsSL 'https://apt.llvm.org/llvm-snapshot.gpg.key' | apt-key add - \
|
RUN curl -fsSL 'https://apt.llvm.org/llvm-snapshot.gpg.key' | apt-key add - \
|
||||||
&& echo "deb http://apt.llvm.org/${DEBIAN_VERSION}/ llvm-toolchain-${DEBIAN_VERSION}-${LLVM_VERSION} main" > /etc/apt/sources.list.d/llvm.stable.list \
|
&& echo "deb http://apt.llvm.org/${DEBIAN_VERSION}/ llvm-toolchain-${DEBIAN_VERSION}-${LLVM_VERSION} main" > /etc/apt/sources.list.d/llvm.stable.list \
|
||||||
&& apt update \
|
&& apt update \
|
||||||
@@ -292,7 +293,7 @@ WORKDIR /home/nonroot
|
|||||||
|
|
||||||
# Rust
|
# Rust
|
||||||
# Please keep the version of llvm (installed above) in sync with rust llvm (`rustc --version --verbose | grep LLVM`)
|
# Please keep the version of llvm (installed above) in sync with rust llvm (`rustc --version --verbose | grep LLVM`)
|
||||||
ENV RUSTC_VERSION=1.87.0
|
ENV RUSTC_VERSION=1.88.0
|
||||||
ENV RUSTUP_HOME="/home/nonroot/.rustup"
|
ENV RUSTUP_HOME="/home/nonroot/.rustup"
|
||||||
ENV PATH="/home/nonroot/.cargo/bin:${PATH}"
|
ENV PATH="/home/nonroot/.cargo/bin:${PATH}"
|
||||||
ARG RUSTFILT_VERSION=0.2.1
|
ARG RUSTFILT_VERSION=0.2.1
|
||||||
+5
-2
@@ -1,9 +1,12 @@
|
|||||||
disallowed-methods = [
|
disallowed-methods = [
|
||||||
"tokio::task::block_in_place",
|
"tokio::task::block_in_place",
|
||||||
|
|
||||||
# Allow this for now, to deny it later once we stop using Handle::block_on completely
|
# Allow this for now, to deny it later once we stop using Handle::block_on completely
|
||||||
# "tokio::runtime::Handle::block_on",
|
# "tokio::runtime::Handle::block_on",
|
||||||
# use tokio_epoll_uring_ext instead
|
|
||||||
"tokio_epoll_uring::thread_local_system",
|
# tokio-epoll-uring:
|
||||||
|
# - allow-invalid because the method doesn't exist on macOS
|
||||||
|
{ path = "tokio_epoll_uring::thread_local_system", replacement = "tokio_epoll_uring_ext module inside pageserver crate", allow-invalid = true }
|
||||||
]
|
]
|
||||||
|
|
||||||
disallowed-macros = [
|
disallowed-macros = [
|
||||||
|
|||||||
@@ -3,3 +3,6 @@ etc/neon_collector.yml
|
|||||||
etc/neon_collector_autoscaling.yml
|
etc/neon_collector_autoscaling.yml
|
||||||
etc/sql_exporter.yml
|
etc/sql_exporter.yml
|
||||||
etc/sql_exporter_autoscaling.yml
|
etc/sql_exporter_autoscaling.yml
|
||||||
|
|
||||||
|
# Node.js dependencies
|
||||||
|
node_modules/
|
||||||
|
|||||||
+10
-2
@@ -22,7 +22,7 @@ sql_exporter.yml: $(jsonnet_files)
|
|||||||
--output-file etc/$@ \
|
--output-file etc/$@ \
|
||||||
--tla-str collector_name=neon_collector \
|
--tla-str collector_name=neon_collector \
|
||||||
--tla-str collector_file=neon_collector.yml \
|
--tla-str collector_file=neon_collector.yml \
|
||||||
--tla-str 'connection_string=postgresql://cloud_admin@127.0.0.1:5432/postgres?sslmode=disable&application_name=sql_exporter' \
|
--tla-str 'connection_string=postgresql://cloud_admin@127.0.0.1:5432/postgres?sslmode=disable&application_name=sql_exporter&pgaudit.log=none' \
|
||||||
etc/sql_exporter.jsonnet
|
etc/sql_exporter.jsonnet
|
||||||
|
|
||||||
sql_exporter_autoscaling.yml: $(jsonnet_files)
|
sql_exporter_autoscaling.yml: $(jsonnet_files)
|
||||||
@@ -30,7 +30,7 @@ sql_exporter_autoscaling.yml: $(jsonnet_files)
|
|||||||
--output-file etc/$@ \
|
--output-file etc/$@ \
|
||||||
--tla-str collector_name=neon_collector_autoscaling \
|
--tla-str collector_name=neon_collector_autoscaling \
|
||||||
--tla-str collector_file=neon_collector_autoscaling.yml \
|
--tla-str collector_file=neon_collector_autoscaling.yml \
|
||||||
--tla-str 'connection_string=postgresql://cloud_admin@127.0.0.1:5432/postgres?sslmode=disable&application_name=sql_exporter_autoscaling' \
|
--tla-str 'connection_string=postgresql://cloud_admin@127.0.0.1:5432/postgres?sslmode=disable&application_name=sql_exporter_autoscaling&pgaudit.log=none' \
|
||||||
etc/sql_exporter.jsonnet
|
etc/sql_exporter.jsonnet
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
@@ -48,3 +48,11 @@ jsonnetfmt-test:
|
|||||||
.PHONY: jsonnetfmt-format
|
.PHONY: jsonnetfmt-format
|
||||||
jsonnetfmt-format:
|
jsonnetfmt-format:
|
||||||
jsonnetfmt --in-place $(jsonnet_files)
|
jsonnetfmt --in-place $(jsonnet_files)
|
||||||
|
|
||||||
|
.PHONY: manifest-schema-validation
|
||||||
|
manifest-schema-validation: node_modules
|
||||||
|
node_modules/.bin/jsonschema validate -d https://json-schema.org/draft/2020-12/schema manifest.schema.json manifest.yaml
|
||||||
|
|
||||||
|
node_modules: package.json
|
||||||
|
npm install
|
||||||
|
touch node_modules
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#
|
#
|
||||||
# build-tools: This contains Rust compiler toolchain and other tools needed at compile
|
# build-tools: This contains Rust compiler toolchain and other tools needed at compile
|
||||||
# time. This is also used for the storage builds. This image is defined in
|
# time. This is also used for the storage builds. This image is defined in
|
||||||
# build-tools.Dockerfile.
|
# build-tools/Dockerfile.
|
||||||
#
|
#
|
||||||
# build-deps: Contains C compiler, other build tools, and compile-time dependencies
|
# build-deps: Contains C compiler, other build tools, and compile-time dependencies
|
||||||
# needed to compile PostgreSQL and most extensions. (Some extensions need
|
# needed to compile PostgreSQL and most extensions. (Some extensions need
|
||||||
@@ -77,9 +77,6 @@
|
|||||||
# build_and_test.yml github workflow for how that's done.
|
# build_and_test.yml github workflow for how that's done.
|
||||||
|
|
||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
ARG REPOSITORY=ghcr.io/neondatabase
|
|
||||||
ARG IMAGE=build-tools
|
|
||||||
ARG TAG=pinned
|
|
||||||
ARG BUILD_TAG
|
ARG BUILD_TAG
|
||||||
ARG DEBIAN_VERSION=bookworm
|
ARG DEBIAN_VERSION=bookworm
|
||||||
ARG DEBIAN_FLAVOR=${DEBIAN_VERSION}-slim
|
ARG DEBIAN_FLAVOR=${DEBIAN_VERSION}-slim
|
||||||
@@ -118,6 +115,9 @@ ARG EXTENSIONS=all
|
|||||||
FROM $BASE_IMAGE_SHA AS build-deps
|
FROM $BASE_IMAGE_SHA AS build-deps
|
||||||
ARG DEBIAN_VERSION
|
ARG DEBIAN_VERSION
|
||||||
|
|
||||||
|
# Keep in sync with build-tools/Dockerfile
|
||||||
|
ENV PROTOC_VERSION=25.1
|
||||||
|
|
||||||
# Use strict mode for bash to catch errors early
|
# Use strict mode for bash to catch errors early
|
||||||
SHELL ["/bin/bash", "-euo", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-euo", "pipefail", "-c"]
|
||||||
|
|
||||||
@@ -149,8 +149,17 @@ RUN case $DEBIAN_VERSION in \
|
|||||||
ninja-build git autoconf automake libtool build-essential bison flex libreadline-dev \
|
ninja-build git autoconf automake libtool build-essential bison flex libreadline-dev \
|
||||||
zlib1g-dev libxml2-dev libcurl4-openssl-dev libossp-uuid-dev wget ca-certificates pkg-config libssl-dev \
|
zlib1g-dev libxml2-dev libcurl4-openssl-dev libossp-uuid-dev wget ca-certificates pkg-config libssl-dev \
|
||||||
libicu-dev libxslt1-dev liblz4-dev libzstd-dev zstd curl unzip g++ \
|
libicu-dev libxslt1-dev liblz4-dev libzstd-dev zstd curl unzip g++ \
|
||||||
|
libclang-dev \
|
||||||
|
jsonnet \
|
||||||
$VERSION_INSTALLS \
|
$VERSION_INSTALLS \
|
||||||
&& apt clean && rm -rf /var/lib/apt/lists/*
|
&& apt clean && rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& useradd -ms /bin/bash nonroot -b /home \
|
||||||
|
# Install protoc from binary release, since Debian's versions are too old.
|
||||||
|
&& curl -fsSL "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-$(uname -m | sed 's/aarch64/aarch_64/g').zip" -o "protoc.zip" \
|
||||||
|
&& unzip -q protoc.zip -d protoc \
|
||||||
|
&& mv protoc/bin/protoc /usr/local/bin/protoc \
|
||||||
|
&& mv protoc/include/google /usr/local/include/google \
|
||||||
|
&& rm -rf protoc.zip protoc
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
#
|
#
|
||||||
@@ -171,9 +180,6 @@ RUN cd postgres && \
|
|||||||
eval $CONFIGURE_CMD && \
|
eval $CONFIGURE_CMD && \
|
||||||
make MAKELEVEL=0 -j $(getconf _NPROCESSORS_ONLN) -s install && \
|
make MAKELEVEL=0 -j $(getconf _NPROCESSORS_ONLN) -s install && \
|
||||||
make MAKELEVEL=0 -j $(getconf _NPROCESSORS_ONLN) -s -C contrib/ install && \
|
make MAKELEVEL=0 -j $(getconf _NPROCESSORS_ONLN) -s -C contrib/ install && \
|
||||||
# Install headers
|
|
||||||
make MAKELEVEL=0 -j $(getconf _NPROCESSORS_ONLN) -s -C src/include install && \
|
|
||||||
make MAKELEVEL=0 -j $(getconf _NPROCESSORS_ONLN) -s -C src/interfaces/libpq install && \
|
|
||||||
# Enable some of contrib extensions
|
# Enable some of contrib extensions
|
||||||
echo 'trusted = true' >> /usr/local/pgsql/share/extension/autoinc.control && \
|
echo 'trusted = true' >> /usr/local/pgsql/share/extension/autoinc.control && \
|
||||||
echo 'trusted = true' >> /usr/local/pgsql/share/extension/dblink.control && \
|
echo 'trusted = true' >> /usr/local/pgsql/share/extension/dblink.control && \
|
||||||
@@ -1057,17 +1063,10 @@ RUN make -j $(getconf _NPROCESSORS_ONLN) && \
|
|||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
#
|
#
|
||||||
# Layer "pg build with nonroot user and cargo installed"
|
# Layer "build-deps with Rust toolchain installed"
|
||||||
# This layer is base and common for layers with `pgrx`
|
|
||||||
#
|
#
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
FROM pg-build AS pg-build-nonroot-with-cargo
|
FROM build-deps AS build-deps-with-cargo
|
||||||
ARG PG_VERSION
|
|
||||||
|
|
||||||
RUN apt update && \
|
|
||||||
apt install --no-install-recommends --no-install-suggests -y curl libclang-dev && \
|
|
||||||
apt clean && rm -rf /var/lib/apt/lists/* && \
|
|
||||||
useradd -ms /bin/bash nonroot -b /home
|
|
||||||
|
|
||||||
ENV HOME=/home/nonroot
|
ENV HOME=/home/nonroot
|
||||||
ENV PATH="/home/nonroot/.cargo/bin:$PATH"
|
ENV PATH="/home/nonroot/.cargo/bin:$PATH"
|
||||||
@@ -1082,13 +1081,29 @@ RUN curl -sSO https://static.rust-lang.org/rustup/dist/$(uname -m)-unknown-linux
|
|||||||
./rustup-init -y --no-modify-path --profile minimal --default-toolchain stable && \
|
./rustup-init -y --no-modify-path --profile minimal --default-toolchain stable && \
|
||||||
rm rustup-init
|
rm rustup-init
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
#
|
||||||
|
# Layer "pg-build with Rust toolchain installed"
|
||||||
|
# This layer is base and common for layers with `pgrx`
|
||||||
|
#
|
||||||
|
#########################################################################################
|
||||||
|
FROM pg-build AS pg-build-with-cargo
|
||||||
|
ARG PG_VERSION
|
||||||
|
|
||||||
|
ENV HOME=/home/nonroot
|
||||||
|
ENV PATH="/home/nonroot/.cargo/bin:$PATH"
|
||||||
|
USER nonroot
|
||||||
|
WORKDIR /home/nonroot
|
||||||
|
|
||||||
|
COPY --from=build-deps-with-cargo /home/nonroot /home/nonroot
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
#
|
#
|
||||||
# Layer "rust extensions"
|
# Layer "rust extensions"
|
||||||
# This layer is used to build `pgrx` deps
|
# This layer is used to build `pgrx` deps
|
||||||
#
|
#
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
FROM pg-build-nonroot-with-cargo AS rust-extensions-build
|
FROM pg-build-with-cargo AS rust-extensions-build
|
||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
|
|
||||||
RUN case "${PG_VERSION:?}" in \
|
RUN case "${PG_VERSION:?}" in \
|
||||||
@@ -1110,7 +1125,7 @@ USER root
|
|||||||
# and eventually get merged with `rust-extensions-build`
|
# and eventually get merged with `rust-extensions-build`
|
||||||
#
|
#
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
FROM pg-build-nonroot-with-cargo AS rust-extensions-build-pgrx12
|
FROM pg-build-with-cargo AS rust-extensions-build-pgrx12
|
||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
|
|
||||||
RUN cargo install --locked --version 0.12.9 cargo-pgrx && \
|
RUN cargo install --locked --version 0.12.9 cargo-pgrx && \
|
||||||
@@ -1127,7 +1142,7 @@ USER root
|
|||||||
# and eventually get merged with `rust-extensions-build`
|
# and eventually get merged with `rust-extensions-build`
|
||||||
#
|
#
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
FROM pg-build-nonroot-with-cargo AS rust-extensions-build-pgrx14
|
FROM pg-build-with-cargo AS rust-extensions-build-pgrx14
|
||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
|
|
||||||
RUN cargo install --locked --version 0.14.1 cargo-pgrx && \
|
RUN cargo install --locked --version 0.14.1 cargo-pgrx && \
|
||||||
@@ -1144,10 +1159,12 @@ USER root
|
|||||||
|
|
||||||
FROM build-deps AS pgrag-src
|
FROM build-deps AS pgrag-src
|
||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
|
|
||||||
WORKDIR /ext-src
|
WORKDIR /ext-src
|
||||||
|
COPY compute/patches/onnxruntime.patch .
|
||||||
|
|
||||||
RUN wget https://github.com/microsoft/onnxruntime/archive/refs/tags/v1.18.1.tar.gz -O onnxruntime.tar.gz && \
|
RUN wget https://github.com/microsoft/onnxruntime/archive/refs/tags/v1.18.1.tar.gz -O onnxruntime.tar.gz && \
|
||||||
mkdir onnxruntime-src && cd onnxruntime-src && tar xzf ../onnxruntime.tar.gz --strip-components=1 -C . && \
|
mkdir onnxruntime-src && cd onnxruntime-src && tar xzf ../onnxruntime.tar.gz --strip-components=1 -C . && \
|
||||||
|
patch -p1 < /ext-src/onnxruntime.patch && \
|
||||||
echo "#nothing to test here" > neon-test.sh
|
echo "#nothing to test here" > neon-test.sh
|
||||||
|
|
||||||
RUN wget https://github.com/neondatabase-labs/pgrag/archive/refs/tags/v0.1.2.tar.gz -O pgrag.tar.gz && \
|
RUN wget https://github.com/neondatabase-labs/pgrag/archive/refs/tags/v0.1.2.tar.gz -O pgrag.tar.gz && \
|
||||||
@@ -1162,7 +1179,7 @@ COPY --from=pgrag-src /ext-src/ /ext-src/
|
|||||||
# Install it using virtual environment, because Python 3.11 (the default version on Debian 12 (Bookworm)) complains otherwise
|
# Install it using virtual environment, because Python 3.11 (the default version on Debian 12 (Bookworm)) complains otherwise
|
||||||
WORKDIR /ext-src/onnxruntime-src
|
WORKDIR /ext-src/onnxruntime-src
|
||||||
RUN apt update && apt install --no-install-recommends --no-install-suggests -y \
|
RUN apt update && apt install --no-install-recommends --no-install-suggests -y \
|
||||||
python3 python3-pip python3-venv protobuf-compiler && \
|
python3 python3-pip python3-venv && \
|
||||||
apt clean && rm -rf /var/lib/apt/lists/* && \
|
apt clean && rm -rf /var/lib/apt/lists/* && \
|
||||||
python3 -m venv venv && \
|
python3 -m venv venv && \
|
||||||
. venv/bin/activate && \
|
. venv/bin/activate && \
|
||||||
@@ -1555,29 +1572,31 @@ RUN make -j $(getconf _NPROCESSORS_ONLN) && \
|
|||||||
FROM build-deps AS pgaudit-src
|
FROM build-deps AS pgaudit-src
|
||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
WORKDIR /ext-src
|
WORKDIR /ext-src
|
||||||
|
COPY "compute/patches/pgaudit-parallel_workers-${PG_VERSION}.patch" .
|
||||||
RUN case "${PG_VERSION}" in \
|
RUN case "${PG_VERSION}" in \
|
||||||
"v14") \
|
"v14") \
|
||||||
export PGAUDIT_VERSION=1.6.2 \
|
export PGAUDIT_VERSION=1.6.3 \
|
||||||
export PGAUDIT_CHECKSUM=1f350d70a0cbf488c0f2b485e3a5c9b11f78ad9e3cbb95ef6904afa1eb3187eb \
|
export PGAUDIT_CHECKSUM=37a8f5a7cc8d9188e536d15cf0fdc457fcdab2547caedb54442c37f124110919 \
|
||||||
;; \
|
;; \
|
||||||
"v15") \
|
"v15") \
|
||||||
export PGAUDIT_VERSION=1.7.0 \
|
export PGAUDIT_VERSION=1.7.1 \
|
||||||
export PGAUDIT_CHECKSUM=8f4a73e451c88c567e516e6cba7dc1e23bc91686bb6f1f77f8f3126d428a8bd8 \
|
export PGAUDIT_CHECKSUM=e9c8e6e092d82b2f901d72555ce0fe7780552f35f8985573796cd7e64b09d4ec \
|
||||||
;; \
|
;; \
|
||||||
"v16") \
|
"v16") \
|
||||||
export PGAUDIT_VERSION=16.0 \
|
export PGAUDIT_VERSION=16.1 \
|
||||||
export PGAUDIT_CHECKSUM=d53ef985f2d0b15ba25c512c4ce967dce07b94fd4422c95bd04c4c1a055fe738 \
|
export PGAUDIT_CHECKSUM=3bae908ab70ba0c6f51224009dbcfff1a97bd6104c6273297a64292e1b921fee \
|
||||||
;; \
|
;; \
|
||||||
"v17") \
|
"v17") \
|
||||||
export PGAUDIT_VERSION=17.0 \
|
export PGAUDIT_VERSION=17.1 \
|
||||||
export PGAUDIT_CHECKSUM=7d0d08d030275d525f36cd48b38c6455f1023da863385badff0cec44965bfd8c \
|
export PGAUDIT_CHECKSUM=9c5f37504d393486cc75d2ced83f75f5899be64fa85f689d6babb833b4361e6c \
|
||||||
;; \
|
;; \
|
||||||
*) \
|
*) \
|
||||||
echo "pgaudit is not supported on this PostgreSQL version" && exit 1;; \
|
echo "pgaudit is not supported on this PostgreSQL version" && exit 1;; \
|
||||||
esac && \
|
esac && \
|
||||||
wget https://github.com/pgaudit/pgaudit/archive/refs/tags/${PGAUDIT_VERSION}.tar.gz -O pgaudit.tar.gz && \
|
wget https://github.com/pgaudit/pgaudit/archive/refs/tags/${PGAUDIT_VERSION}.tar.gz -O pgaudit.tar.gz && \
|
||||||
echo "${PGAUDIT_CHECKSUM} pgaudit.tar.gz" | sha256sum --check && \
|
echo "${PGAUDIT_CHECKSUM} pgaudit.tar.gz" | sha256sum --check && \
|
||||||
mkdir pgaudit-src && cd pgaudit-src && tar xzf ../pgaudit.tar.gz --strip-components=1 -C .
|
mkdir pgaudit-src && cd pgaudit-src && tar xzf ../pgaudit.tar.gz --strip-components=1 -C . && \
|
||||||
|
patch -p1 < "/ext-src/pgaudit-parallel_workers-${PG_VERSION}.patch"
|
||||||
|
|
||||||
FROM pg-build AS pgaudit-build
|
FROM pg-build AS pgaudit-build
|
||||||
COPY --from=pgaudit-src /ext-src/ /ext-src/
|
COPY --from=pgaudit-src /ext-src/ /ext-src/
|
||||||
@@ -1617,22 +1636,14 @@ RUN make install USE_PGXS=1 -j $(getconf _NPROCESSORS_ONLN)
|
|||||||
# compile neon extensions
|
# compile neon extensions
|
||||||
#
|
#
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
FROM pg-build AS neon-ext-build
|
FROM pg-build-with-cargo AS neon-ext-build
|
||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
|
|
||||||
COPY pgxn/ pgxn/
|
USER root
|
||||||
RUN make -j $(getconf _NPROCESSORS_ONLN) \
|
COPY . .
|
||||||
-C pgxn/neon \
|
|
||||||
-s install && \
|
RUN make -j $(getconf _NPROCESSORS_ONLN) -C pgxn -s install-compute \
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) \
|
BUILD_TYPE=release CARGO_BUILD_FLAGS="--locked --release" NEON_CARGO_ARTIFACT_TARGET_DIR="$(pwd)/target/release"
|
||||||
-C pgxn/neon_utils \
|
|
||||||
-s install && \
|
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) \
|
|
||||||
-C pgxn/neon_test_utils \
|
|
||||||
-s install && \
|
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) \
|
|
||||||
-C pgxn/neon_rmgr \
|
|
||||||
-s install
|
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
#
|
#
|
||||||
@@ -1722,7 +1733,7 @@ FROM extensions-${EXTENSIONS} AS neon-pg-ext-build
|
|||||||
# Compile the Neon-specific `compute_ctl`, `fast_import`, and `local_proxy` binaries
|
# Compile the Neon-specific `compute_ctl`, `fast_import`, and `local_proxy` binaries
|
||||||
#
|
#
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
FROM $REPOSITORY/$IMAGE:$TAG AS compute-tools
|
FROM build-deps-with-cargo AS compute-tools
|
||||||
ARG BUILD_TAG
|
ARG BUILD_TAG
|
||||||
ENV BUILD_TAG=$BUILD_TAG
|
ENV BUILD_TAG=$BUILD_TAG
|
||||||
|
|
||||||
@@ -1732,7 +1743,7 @@ COPY --chown=nonroot . .
|
|||||||
RUN --mount=type=cache,uid=1000,target=/home/nonroot/.cargo/registry \
|
RUN --mount=type=cache,uid=1000,target=/home/nonroot/.cargo/registry \
|
||||||
--mount=type=cache,uid=1000,target=/home/nonroot/.cargo/git \
|
--mount=type=cache,uid=1000,target=/home/nonroot/.cargo/git \
|
||||||
--mount=type=cache,uid=1000,target=/home/nonroot/target \
|
--mount=type=cache,uid=1000,target=/home/nonroot/target \
|
||||||
mold -run cargo build --locked --profile release-line-debug-size-lto --bin compute_ctl --bin fast_import --bin local_proxy && \
|
cargo build --locked --profile release-line-debug-size-lto --bin compute_ctl --bin fast_import --bin local_proxy && \
|
||||||
mkdir target-bin && \
|
mkdir target-bin && \
|
||||||
cp target/release-line-debug-size-lto/compute_ctl \
|
cp target/release-line-debug-size-lto/compute_ctl \
|
||||||
target/release-line-debug-size-lto/fast_import \
|
target/release-line-debug-size-lto/fast_import \
|
||||||
@@ -1779,7 +1790,7 @@ RUN set -e \
|
|||||||
#########################################################################################
|
#########################################################################################
|
||||||
FROM build-deps AS exporters
|
FROM build-deps AS exporters
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
# Keep sql_exporter version same as in build-tools.Dockerfile and
|
# Keep sql_exporter version same as in build-tools/Dockerfile and
|
||||||
# test_runner/regress/test_compute_metrics.py
|
# test_runner/regress/test_compute_metrics.py
|
||||||
# See comment on the top of the file regading `echo`, `-e` and `\n`
|
# See comment on the top of the file regading `echo`, `-e` and `\n`
|
||||||
RUN if [ "$TARGETARCH" = "amd64" ]; then\
|
RUN if [ "$TARGETARCH" = "amd64" ]; then\
|
||||||
@@ -1826,10 +1837,11 @@ RUN rm /usr/local/pgsql/lib/lib*.a
|
|||||||
# Preprocess the sql_exporter configuration files
|
# Preprocess the sql_exporter configuration files
|
||||||
#
|
#
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
FROM $REPOSITORY/$IMAGE:$TAG AS sql_exporter_preprocessor
|
FROM build-deps AS sql_exporter_preprocessor
|
||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
|
|
||||||
USER nonroot
|
USER nonroot
|
||||||
|
WORKDIR /home/nonroot
|
||||||
|
|
||||||
COPY --chown=nonroot compute compute
|
COPY --chown=nonroot compute compute
|
||||||
|
|
||||||
@@ -1903,10 +1915,10 @@ RUN cd /ext-src/pg_repack-src && patch -p1 </ext-src/pg_repack.patch && rm -f /e
|
|||||||
|
|
||||||
COPY --chmod=755 docker-compose/run-tests.sh /run-tests.sh
|
COPY --chmod=755 docker-compose/run-tests.sh /run-tests.sh
|
||||||
RUN echo /usr/local/pgsql/lib > /etc/ld.so.conf.d/00-neon.conf && /sbin/ldconfig
|
RUN echo /usr/local/pgsql/lib > /etc/ld.so.conf.d/00-neon.conf && /sbin/ldconfig
|
||||||
RUN apt-get update && apt-get install -y libtap-parser-sourcehandler-pgtap-perl jq \
|
RUN apt-get update && apt-get install -y libtap-parser-sourcehandler-pgtap-perl jq parallel \
|
||||||
&& apt clean && rm -rf /ext-src/*.tar.gz /ext-src/*.patch /var/lib/apt/lists/*
|
&& apt clean && rm -rf /ext-src/*.tar.gz /ext-src/*.patch /var/lib/apt/lists/*
|
||||||
ENV PATH=/usr/local/pgsql/bin:$PATH
|
ENV PATH=/usr/local/pgsql/bin:$PATH
|
||||||
ENV PGHOST=compute
|
ENV PGHOST=compute1
|
||||||
ENV PGPORT=55433
|
ENV PGPORT=55433
|
||||||
ENV PGUSER=cloud_admin
|
ENV PGUSER=cloud_admin
|
||||||
ENV PGDATABASE=postgres
|
ENV PGDATABASE=postgres
|
||||||
@@ -1976,7 +1988,7 @@ RUN apt update && \
|
|||||||
locales \
|
locales \
|
||||||
lsof \
|
lsof \
|
||||||
procps \
|
procps \
|
||||||
rsyslog \
|
rsyslog-gnutls \
|
||||||
screen \
|
screen \
|
||||||
tcpdump \
|
tcpdump \
|
||||||
$VERSION_INSTALLS && \
|
$VERSION_INSTALLS && \
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
import 'sql_exporter/compute_logical_snapshot_files.libsonnet',
|
import 'sql_exporter/compute_logical_snapshot_files.libsonnet',
|
||||||
import 'sql_exporter/compute_logical_snapshots_bytes.libsonnet',
|
import 'sql_exporter/compute_logical_snapshots_bytes.libsonnet',
|
||||||
import 'sql_exporter/compute_max_connections.libsonnet',
|
import 'sql_exporter/compute_max_connections.libsonnet',
|
||||||
|
import 'sql_exporter/compute_pg_oldest_frozen_xid_age.libsonnet',
|
||||||
|
import 'sql_exporter/compute_pg_oldest_mxid_age.libsonnet',
|
||||||
import 'sql_exporter/compute_receive_lsn.libsonnet',
|
import 'sql_exporter/compute_receive_lsn.libsonnet',
|
||||||
import 'sql_exporter/compute_subscriptions_count.libsonnet',
|
import 'sql_exporter/compute_subscriptions_count.libsonnet',
|
||||||
import 'sql_exporter/connection_counts.libsonnet',
|
import 'sql_exporter/connection_counts.libsonnet',
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ unix_socket_dir=/tmp/
|
|||||||
unix_socket_mode=0777
|
unix_socket_mode=0777
|
||||||
; required for pgbouncer_exporter
|
; required for pgbouncer_exporter
|
||||||
ignore_startup_parameters=extra_float_digits
|
ignore_startup_parameters=extra_float_digits
|
||||||
|
; pidfile for graceful termination
|
||||||
|
pidfile=/tmp/pgbouncer.pid
|
||||||
|
|
||||||
;; Disable connection logging. It produces a lot of logs that no one looks at,
|
;; Disable connection logging. It produces a lot of logs that no one looks at,
|
||||||
;; and we can get similar log entries from the proxy too. We had incidents in
|
;; and we can get similar log entries from the proxy too. We had incidents in
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
metric_name: 'compute_pg_oldest_frozen_xid_age',
|
||||||
|
type: 'gauge',
|
||||||
|
help: 'Age of oldest XIDs that have not been frozen by VACUUM. An indicator of how long it has been since VACUUM last ran.',
|
||||||
|
key_labels: [
|
||||||
|
'database_name',
|
||||||
|
],
|
||||||
|
value_label: 'metric',
|
||||||
|
values: [
|
||||||
|
'frozen_xid_age',
|
||||||
|
],
|
||||||
|
query: importstr 'sql_exporter/compute_pg_oldest_frozen_xid_age.sql',
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
SELECT datname database_name,
|
||||||
|
age(datfrozenxid) frozen_xid_age
|
||||||
|
FROM pg_database
|
||||||
|
ORDER BY frozen_xid_age DESC LIMIT 10;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
metric_name: 'compute_pg_oldest_mxid_age',
|
||||||
|
type: 'gauge',
|
||||||
|
help: 'Age of oldest MXIDs that have not been replaced by VACUUM. An indicator of how long it has been since VACUUM last ran.',
|
||||||
|
key_labels: [
|
||||||
|
'database_name',
|
||||||
|
],
|
||||||
|
value_label: 'metric',
|
||||||
|
values: [
|
||||||
|
'min_mxid_age',
|
||||||
|
],
|
||||||
|
query: importstr 'sql_exporter/compute_pg_oldest_mxid_age.sql',
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
SELECT datname database_name,
|
||||||
|
mxid_age(datminmxid) min_mxid_age
|
||||||
|
FROM pg_database
|
||||||
|
ORDER BY min_mxid_age DESC LIMIT 10;
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"title": "Neon Compute Manifest Schema",
|
||||||
|
"description": "Schema for Neon compute node configuration manifest",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"pg_settings": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"common": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"client_connection_check_interval": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Check for client disconnection interval in milliseconds"
|
||||||
|
},
|
||||||
|
"effective_io_concurrency": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Effective IO concurrency setting"
|
||||||
|
},
|
||||||
|
"fsync": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["on", "off"],
|
||||||
|
"description": "Whether to force fsync to disk"
|
||||||
|
},
|
||||||
|
"hot_standby": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["on", "off"],
|
||||||
|
"description": "Whether hot standby is enabled"
|
||||||
|
},
|
||||||
|
"idle_in_transaction_session_timeout": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Timeout for idle transactions in milliseconds"
|
||||||
|
},
|
||||||
|
"listen_addresses": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Addresses to listen on"
|
||||||
|
},
|
||||||
|
"log_connections": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["on", "off"],
|
||||||
|
"description": "Whether to log connections"
|
||||||
|
},
|
||||||
|
"log_disconnections": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["on", "off"],
|
||||||
|
"description": "Whether to log disconnections"
|
||||||
|
},
|
||||||
|
"log_temp_files": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Size threshold for logging temporary files in KB"
|
||||||
|
},
|
||||||
|
"log_error_verbosity": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["terse", "verbose", "default"],
|
||||||
|
"description": "Error logging verbosity level"
|
||||||
|
},
|
||||||
|
"log_min_error_statement": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Minimum error level for statement logging"
|
||||||
|
},
|
||||||
|
"maintenance_io_concurrency": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Maintenance IO concurrency setting"
|
||||||
|
},
|
||||||
|
"max_connections": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Maximum number of connections"
|
||||||
|
},
|
||||||
|
"max_replication_flush_lag": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Maximum replication flush lag"
|
||||||
|
},
|
||||||
|
"max_replication_slots": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Maximum number of replication slots"
|
||||||
|
},
|
||||||
|
"max_replication_write_lag": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Maximum replication write lag"
|
||||||
|
},
|
||||||
|
"max_wal_senders": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Maximum number of WAL senders"
|
||||||
|
},
|
||||||
|
"max_wal_size": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Maximum WAL size"
|
||||||
|
},
|
||||||
|
"neon.unstable_extensions": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "List of unstable extensions"
|
||||||
|
},
|
||||||
|
"neon.protocol_version": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Neon protocol version"
|
||||||
|
},
|
||||||
|
"password_encryption": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Password encryption method"
|
||||||
|
},
|
||||||
|
"restart_after_crash": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["on", "off"],
|
||||||
|
"description": "Whether to restart after crash"
|
||||||
|
},
|
||||||
|
"superuser_reserved_connections": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Number of reserved connections for superuser"
|
||||||
|
},
|
||||||
|
"synchronous_standby_names": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Names of synchronous standby servers"
|
||||||
|
},
|
||||||
|
"wal_keep_size": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "WAL keep size"
|
||||||
|
},
|
||||||
|
"wal_level": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "WAL level"
|
||||||
|
},
|
||||||
|
"wal_log_hints": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["on", "off"],
|
||||||
|
"description": "Whether to log hints in WAL"
|
||||||
|
},
|
||||||
|
"wal_sender_timeout": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "WAL sender timeout in milliseconds"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"client_connection_check_interval",
|
||||||
|
"effective_io_concurrency",
|
||||||
|
"fsync",
|
||||||
|
"hot_standby",
|
||||||
|
"idle_in_transaction_session_timeout",
|
||||||
|
"listen_addresses",
|
||||||
|
"log_connections",
|
||||||
|
"log_disconnections",
|
||||||
|
"log_temp_files",
|
||||||
|
"log_error_verbosity",
|
||||||
|
"log_min_error_statement",
|
||||||
|
"maintenance_io_concurrency",
|
||||||
|
"max_connections",
|
||||||
|
"max_replication_flush_lag",
|
||||||
|
"max_replication_slots",
|
||||||
|
"max_replication_write_lag",
|
||||||
|
"max_wal_senders",
|
||||||
|
"max_wal_size",
|
||||||
|
"neon.unstable_extensions",
|
||||||
|
"neon.protocol_version",
|
||||||
|
"password_encryption",
|
||||||
|
"restart_after_crash",
|
||||||
|
"superuser_reserved_connections",
|
||||||
|
"synchronous_standby_names",
|
||||||
|
"wal_keep_size",
|
||||||
|
"wal_level",
|
||||||
|
"wal_log_hints",
|
||||||
|
"wal_sender_timeout"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"replica": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"hot_standby": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["on", "off"],
|
||||||
|
"description": "Whether hot standby is enabled for replicas"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["hot_standby"]
|
||||||
|
},
|
||||||
|
"per_version": {
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^1[4-7]$": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"common": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"io_combine_limit": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "IO combine limit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"replica": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"recovery_prefetch": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["on", "off"],
|
||||||
|
"description": "Whether to enable recovery prefetch for PostgreSQL replicas"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["common", "replica", "per_version"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["pg_settings"]
|
||||||
|
}
|
||||||
@@ -105,17 +105,17 @@ pg_settings:
|
|||||||
# Neon hot standby ignores pages that are not in the shared_buffers
|
# Neon hot standby ignores pages that are not in the shared_buffers
|
||||||
recovery_prefetch: "off"
|
recovery_prefetch: "off"
|
||||||
16:
|
16:
|
||||||
common:
|
common: {}
|
||||||
replica:
|
replica:
|
||||||
# prefetching of blocks referenced in WAL doesn't make sense for us
|
# prefetching of blocks referenced in WAL doesn't make sense for us
|
||||||
# Neon hot standby ignores pages that are not in the shared_buffers
|
# Neon hot standby ignores pages that are not in the shared_buffers
|
||||||
recovery_prefetch: "off"
|
recovery_prefetch: "off"
|
||||||
15:
|
15:
|
||||||
common:
|
common: {}
|
||||||
replica:
|
replica:
|
||||||
# prefetching of blocks referenced in WAL doesn't make sense for us
|
# prefetching of blocks referenced in WAL doesn't make sense for us
|
||||||
# Neon hot standby ignores pages that are not in the shared_buffers
|
# Neon hot standby ignores pages that are not in the shared_buffers
|
||||||
recovery_prefetch: "off"
|
recovery_prefetch: "off"
|
||||||
14:
|
14:
|
||||||
common:
|
common: {}
|
||||||
replica:
|
replica: {}
|
||||||
|
|||||||
Generated
+37
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "neon-compute",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "neon-compute",
|
||||||
|
"dependencies": {
|
||||||
|
"@sourcemeta/jsonschema": "9.3.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sourcemeta/jsonschema": {
|
||||||
|
"version": "9.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sourcemeta/jsonschema/-/jsonschema-9.3.4.tgz",
|
||||||
|
"integrity": "sha512-hkujfkZAIGXUs4U//We9faZW8LZ4/H9LqagRYsFSulH/VLcKPNhZyCTGg7AhORuzm27zqENvKpnX4g2FzudYFw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64",
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "AGPL-3.0",
|
||||||
|
"os": [
|
||||||
|
"darwin",
|
||||||
|
"linux",
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"jsonschema": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sourcemeta"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "neon-compute",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@sourcemeta/jsonschema": "9.3.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
diff --git a/sql/anon.sql b/sql/anon.sql
|
diff --git a/sql/anon.sql b/sql/anon.sql
|
||||||
index 0cdc769..f6cc950 100644
|
index 0cdc769..b450327 100644
|
||||||
--- a/sql/anon.sql
|
--- a/sql/anon.sql
|
||||||
+++ b/sql/anon.sql
|
+++ b/sql/anon.sql
|
||||||
@@ -1141,3 +1141,8 @@ $$
|
@@ -1141,3 +1141,15 @@ $$
|
||||||
-- TODO : https://en.wikipedia.org/wiki/L-diversity
|
-- TODO : https://en.wikipedia.org/wiki/L-diversity
|
||||||
|
|
||||||
-- TODO : https://en.wikipedia.org/wiki/T-closeness
|
-- TODO : https://en.wikipedia.org/wiki/T-closeness
|
||||||
@@ -11,6 +11,13 @@ index 0cdc769..f6cc950 100644
|
|||||||
+
|
+
|
||||||
+GRANT ALL ON SCHEMA anon to neon_superuser;
|
+GRANT ALL ON SCHEMA anon to neon_superuser;
|
||||||
+GRANT ALL ON ALL TABLES IN SCHEMA anon TO neon_superuser;
|
+GRANT ALL ON ALL TABLES IN SCHEMA anon TO neon_superuser;
|
||||||
|
+
|
||||||
|
+DO $$
|
||||||
|
+BEGIN
|
||||||
|
+ IF current_setting('server_version_num')::int >= 150000 THEN
|
||||||
|
+ GRANT SET ON PARAMETER anon.transparent_dynamic_masking TO neon_superuser;
|
||||||
|
+ END IF;
|
||||||
|
+END $$;
|
||||||
diff --git a/sql/init.sql b/sql/init.sql
|
diff --git a/sql/init.sql b/sql/init.sql
|
||||||
index 7da6553..9b6164b 100644
|
index 7da6553..9b6164b 100644
|
||||||
--- a/sql/init.sql
|
--- a/sql/init.sql
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
diff --git a/cmake/deps.txt b/cmake/deps.txt
|
||||||
|
index d213b09034..229de2ebf0 100644
|
||||||
|
--- a/cmake/deps.txt
|
||||||
|
+++ b/cmake/deps.txt
|
||||||
|
@@ -22,7 +22,9 @@ dlpack;https://github.com/dmlc/dlpack/archive/refs/tags/v0.6.zip;4d565dd2e5b3132
|
||||||
|
# it contains changes on top of 3.4.0 which are required to fix build issues.
|
||||||
|
# Until the 3.4.1 release this is the best option we have.
|
||||||
|
# Issue link: https://gitlab.com/libeigen/eigen/-/issues/2744
|
||||||
|
-eigen;https://gitlab.com/libeigen/eigen/-/archive/e7248b26a1ed53fa030c5c459f7ea095dfd276ac/eigen-e7248b26a1ed53fa030c5c459f7ea095dfd276ac.zip;be8be39fdbc6e60e94fa7870b280707069b5b81a
|
||||||
|
+# Moved to github mirror to avoid gitlab issues.Add commentMore actions
|
||||||
|
+# Issue link: https://github.com/bazelbuild/bazel-central-registry/issues/4355
|
||||||
|
+eigen;https://github.com/eigen-mirror/eigen/archive/e7248b26a1ed53fa030c5c459f7ea095dfd276ac/eigen-e7248b26a1ed53fa030c5c459f7ea095dfd276ac.zip;61418a349000ba7744a3ad03cf5071f22ebf860a
|
||||||
|
flatbuffers;https://github.com/google/flatbuffers/archive/refs/tags/v23.5.26.zip;59422c3b5e573dd192fead2834d25951f1c1670c
|
||||||
|
fp16;https://github.com/Maratyszcza/FP16/archive/0a92994d729ff76a58f692d3028ca1b64b145d91.zip;b985f6985a05a1c03ff1bb71190f66d8f98a1494
|
||||||
|
fxdiv;https://github.com/Maratyszcza/FXdiv/archive/63058eff77e11aa15bf531df5dd34395ec3017c8.zip;a5658f4036402dbca7cebee32be57fb8149811e1
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
commit 7220bb3a3f23fa27207d77562dcc286f9a123313
|
||||||
|
Author: Tristan Partin <tristan.partin@databricks.com>
|
||||||
|
Date: 2025-06-23 02:09:31 +0000
|
||||||
|
|
||||||
|
Disable logging in parallel workers
|
||||||
|
|
||||||
|
When a query uses parallel workers, pgaudit will log the same query for
|
||||||
|
every parallel worker. This is undesireable since it can result in log
|
||||||
|
amplification for queries that use parallel workers.
|
||||||
|
|
||||||
|
Signed-off-by: Tristan Partin <tristan.partin@databricks.com>
|
||||||
|
|
||||||
|
diff --git a/expected/pgaudit.out b/expected/pgaudit.out
|
||||||
|
index baa8011..a601375 100644
|
||||||
|
--- a/expected/pgaudit.out
|
||||||
|
+++ b/expected/pgaudit.out
|
||||||
|
@@ -2563,6 +2563,37 @@ COMMIT;
|
||||||
|
NOTICE: AUDIT: SESSION,12,4,MISC,COMMIT,,,COMMIT;,<not logged>
|
||||||
|
DROP TABLE part_test;
|
||||||
|
NOTICE: AUDIT: SESSION,13,1,DDL,DROP TABLE,,,DROP TABLE part_test;,<not logged>
|
||||||
|
+--
|
||||||
|
+-- Test logging in parallel workers
|
||||||
|
+SET pgaudit.log = 'read';
|
||||||
|
+SET pgaudit.log_client = on;
|
||||||
|
+SET pgaudit.log_level = 'notice';
|
||||||
|
+-- Force parallel execution for testing
|
||||||
|
+SET max_parallel_workers_per_gather = 2;
|
||||||
|
+SET parallel_tuple_cost = 0;
|
||||||
|
+SET parallel_setup_cost = 0;
|
||||||
|
+SET min_parallel_table_scan_size = 0;
|
||||||
|
+SET min_parallel_index_scan_size = 0;
|
||||||
|
+-- Create table with enough data to trigger parallel execution
|
||||||
|
+CREATE TABLE parallel_test (id int, data text);
|
||||||
|
+INSERT INTO parallel_test SELECT generate_series(1, 1000), 'test data';
|
||||||
|
+SELECT count(*) FROM parallel_test;
|
||||||
|
+NOTICE: AUDIT: SESSION,14,1,READ,SELECT,,,SELECT count(*) FROM parallel_test;,<not logged>
|
||||||
|
+ count
|
||||||
|
+-------
|
||||||
|
+ 1000
|
||||||
|
+(1 row)
|
||||||
|
+
|
||||||
|
+-- Cleanup parallel test
|
||||||
|
+DROP TABLE parallel_test;
|
||||||
|
+RESET max_parallel_workers_per_gather;
|
||||||
|
+RESET parallel_tuple_cost;
|
||||||
|
+RESET parallel_setup_cost;
|
||||||
|
+RESET min_parallel_table_scan_size;
|
||||||
|
+RESET min_parallel_index_scan_size;
|
||||||
|
+RESET pgaudit.log;
|
||||||
|
+RESET pgaudit.log_client;
|
||||||
|
+RESET pgaudit.log_level;
|
||||||
|
-- Cleanup
|
||||||
|
-- Set client_min_messages up to warning to avoid noise
|
||||||
|
SET client_min_messages = 'warning';
|
||||||
|
diff --git a/pgaudit.c b/pgaudit.c
|
||||||
|
index 5e6fd38..ac9ded2 100644
|
||||||
|
--- a/pgaudit.c
|
||||||
|
+++ b/pgaudit.c
|
||||||
|
@@ -11,6 +11,7 @@
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "access/htup_details.h"
|
||||||
|
+#include "access/parallel.h"
|
||||||
|
#include "access/sysattr.h"
|
||||||
|
#include "access/xact.h"
|
||||||
|
#include "access/relation.h"
|
||||||
|
@@ -1303,7 +1304,7 @@ pgaudit_ExecutorStart_hook(QueryDesc *queryDesc, int eflags)
|
||||||
|
{
|
||||||
|
AuditEventStackItem *stackItem = NULL;
|
||||||
|
|
||||||
|
- if (!internalStatement)
|
||||||
|
+ if (!internalStatement && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* Push the audit even onto the stack */
|
||||||
|
stackItem = stack_push();
|
||||||
|
@@ -1384,7 +1385,7 @@ pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, bool abort)
|
||||||
|
|
||||||
|
/* Log DML if the audit role is valid or session logging is enabled */
|
||||||
|
if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
|
||||||
|
- !IsAbortedTransactionBlockState())
|
||||||
|
+ !IsAbortedTransactionBlockState() && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* If auditLogRows is on, wait for rows processed to be set */
|
||||||
|
if (auditLogRows && auditEventStack != NULL)
|
||||||
|
@@ -1438,7 +1439,7 @@ pgaudit_ExecutorRun_hook(QueryDesc *queryDesc, ScanDirection direction, uint64 c
|
||||||
|
else
|
||||||
|
standard_ExecutorRun(queryDesc, direction, count, execute_once);
|
||||||
|
|
||||||
|
- if (auditLogRows && !internalStatement)
|
||||||
|
+ if (auditLogRows && !internalStatement && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* Find an item from the stack by the query memory context */
|
||||||
|
stackItem = stack_find_context(queryDesc->estate->es_query_cxt);
|
||||||
|
@@ -1458,7 +1459,7 @@ pgaudit_ExecutorEnd_hook(QueryDesc *queryDesc)
|
||||||
|
AuditEventStackItem *stackItem = NULL;
|
||||||
|
AuditEventStackItem *auditEventStackFull = NULL;
|
||||||
|
|
||||||
|
- if (auditLogRows && !internalStatement)
|
||||||
|
+ if (auditLogRows && !internalStatement && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* Find an item from the stack by the query memory context */
|
||||||
|
stackItem = stack_find_context(queryDesc->estate->es_query_cxt);
|
||||||
|
diff --git a/sql/pgaudit.sql b/sql/pgaudit.sql
|
||||||
|
index cc1374a..1870a60 100644
|
||||||
|
--- a/sql/pgaudit.sql
|
||||||
|
+++ b/sql/pgaudit.sql
|
||||||
|
@@ -1612,6 +1612,36 @@ COMMIT;
|
||||||
|
|
||||||
|
DROP TABLE part_test;
|
||||||
|
|
||||||
|
+--
|
||||||
|
+-- Test logging in parallel workers
|
||||||
|
+SET pgaudit.log = 'read';
|
||||||
|
+SET pgaudit.log_client = on;
|
||||||
|
+SET pgaudit.log_level = 'notice';
|
||||||
|
+
|
||||||
|
+-- Force parallel execution for testing
|
||||||
|
+SET max_parallel_workers_per_gather = 2;
|
||||||
|
+SET parallel_tuple_cost = 0;
|
||||||
|
+SET parallel_setup_cost = 0;
|
||||||
|
+SET min_parallel_table_scan_size = 0;
|
||||||
|
+SET min_parallel_index_scan_size = 0;
|
||||||
|
+
|
||||||
|
+-- Create table with enough data to trigger parallel execution
|
||||||
|
+CREATE TABLE parallel_test (id int, data text);
|
||||||
|
+INSERT INTO parallel_test SELECT generate_series(1, 1000), 'test data';
|
||||||
|
+
|
||||||
|
+SELECT count(*) FROM parallel_test;
|
||||||
|
+
|
||||||
|
+-- Cleanup parallel test
|
||||||
|
+DROP TABLE parallel_test;
|
||||||
|
+RESET max_parallel_workers_per_gather;
|
||||||
|
+RESET parallel_tuple_cost;
|
||||||
|
+RESET parallel_setup_cost;
|
||||||
|
+RESET min_parallel_table_scan_size;
|
||||||
|
+RESET min_parallel_index_scan_size;
|
||||||
|
+RESET pgaudit.log;
|
||||||
|
+RESET pgaudit.log_client;
|
||||||
|
+RESET pgaudit.log_level;
|
||||||
|
+
|
||||||
|
-- Cleanup
|
||||||
|
-- Set client_min_messages up to warning to avoid noise
|
||||||
|
SET client_min_messages = 'warning';
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
commit 29dc2847f6255541992f18faf8a815dfab79631a
|
||||||
|
Author: Tristan Partin <tristan.partin@databricks.com>
|
||||||
|
Date: 2025-06-23 02:09:31 +0000
|
||||||
|
|
||||||
|
Disable logging in parallel workers
|
||||||
|
|
||||||
|
When a query uses parallel workers, pgaudit will log the same query for
|
||||||
|
every parallel worker. This is undesireable since it can result in log
|
||||||
|
amplification for queries that use parallel workers.
|
||||||
|
|
||||||
|
Signed-off-by: Tristan Partin <tristan.partin@databricks.com>
|
||||||
|
|
||||||
|
diff --git a/expected/pgaudit.out b/expected/pgaudit.out
|
||||||
|
index b22560b..73f0327 100644
|
||||||
|
--- a/expected/pgaudit.out
|
||||||
|
+++ b/expected/pgaudit.out
|
||||||
|
@@ -2563,6 +2563,37 @@ COMMIT;
|
||||||
|
NOTICE: AUDIT: SESSION,12,4,MISC,COMMIT,,,COMMIT;,<not logged>
|
||||||
|
DROP TABLE part_test;
|
||||||
|
NOTICE: AUDIT: SESSION,13,1,DDL,DROP TABLE,,,DROP TABLE part_test;,<not logged>
|
||||||
|
+--
|
||||||
|
+-- Test logging in parallel workers
|
||||||
|
+SET pgaudit.log = 'read';
|
||||||
|
+SET pgaudit.log_client = on;
|
||||||
|
+SET pgaudit.log_level = 'notice';
|
||||||
|
+-- Force parallel execution for testing
|
||||||
|
+SET max_parallel_workers_per_gather = 2;
|
||||||
|
+SET parallel_tuple_cost = 0;
|
||||||
|
+SET parallel_setup_cost = 0;
|
||||||
|
+SET min_parallel_table_scan_size = 0;
|
||||||
|
+SET min_parallel_index_scan_size = 0;
|
||||||
|
+-- Create table with enough data to trigger parallel execution
|
||||||
|
+CREATE TABLE parallel_test (id int, data text);
|
||||||
|
+INSERT INTO parallel_test SELECT generate_series(1, 1000), 'test data';
|
||||||
|
+SELECT count(*) FROM parallel_test;
|
||||||
|
+NOTICE: AUDIT: SESSION,14,1,READ,SELECT,,,SELECT count(*) FROM parallel_test;,<not logged>
|
||||||
|
+ count
|
||||||
|
+-------
|
||||||
|
+ 1000
|
||||||
|
+(1 row)
|
||||||
|
+
|
||||||
|
+-- Cleanup parallel test
|
||||||
|
+DROP TABLE parallel_test;
|
||||||
|
+RESET max_parallel_workers_per_gather;
|
||||||
|
+RESET parallel_tuple_cost;
|
||||||
|
+RESET parallel_setup_cost;
|
||||||
|
+RESET min_parallel_table_scan_size;
|
||||||
|
+RESET min_parallel_index_scan_size;
|
||||||
|
+RESET pgaudit.log;
|
||||||
|
+RESET pgaudit.log_client;
|
||||||
|
+RESET pgaudit.log_level;
|
||||||
|
-- Cleanup
|
||||||
|
-- Set client_min_messages up to warning to avoid noise
|
||||||
|
SET client_min_messages = 'warning';
|
||||||
|
diff --git a/pgaudit.c b/pgaudit.c
|
||||||
|
index 5e6fd38..ac9ded2 100644
|
||||||
|
--- a/pgaudit.c
|
||||||
|
+++ b/pgaudit.c
|
||||||
|
@@ -11,6 +11,7 @@
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "access/htup_details.h"
|
||||||
|
+#include "access/parallel.h"
|
||||||
|
#include "access/sysattr.h"
|
||||||
|
#include "access/xact.h"
|
||||||
|
#include "access/relation.h"
|
||||||
|
@@ -1303,7 +1304,7 @@ pgaudit_ExecutorStart_hook(QueryDesc *queryDesc, int eflags)
|
||||||
|
{
|
||||||
|
AuditEventStackItem *stackItem = NULL;
|
||||||
|
|
||||||
|
- if (!internalStatement)
|
||||||
|
+ if (!internalStatement && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* Push the audit even onto the stack */
|
||||||
|
stackItem = stack_push();
|
||||||
|
@@ -1384,7 +1385,7 @@ pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, bool abort)
|
||||||
|
|
||||||
|
/* Log DML if the audit role is valid or session logging is enabled */
|
||||||
|
if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
|
||||||
|
- !IsAbortedTransactionBlockState())
|
||||||
|
+ !IsAbortedTransactionBlockState() && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* If auditLogRows is on, wait for rows processed to be set */
|
||||||
|
if (auditLogRows && auditEventStack != NULL)
|
||||||
|
@@ -1438,7 +1439,7 @@ pgaudit_ExecutorRun_hook(QueryDesc *queryDesc, ScanDirection direction, uint64 c
|
||||||
|
else
|
||||||
|
standard_ExecutorRun(queryDesc, direction, count, execute_once);
|
||||||
|
|
||||||
|
- if (auditLogRows && !internalStatement)
|
||||||
|
+ if (auditLogRows && !internalStatement && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* Find an item from the stack by the query memory context */
|
||||||
|
stackItem = stack_find_context(queryDesc->estate->es_query_cxt);
|
||||||
|
@@ -1458,7 +1459,7 @@ pgaudit_ExecutorEnd_hook(QueryDesc *queryDesc)
|
||||||
|
AuditEventStackItem *stackItem = NULL;
|
||||||
|
AuditEventStackItem *auditEventStackFull = NULL;
|
||||||
|
|
||||||
|
- if (auditLogRows && !internalStatement)
|
||||||
|
+ if (auditLogRows && !internalStatement && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* Find an item from the stack by the query memory context */
|
||||||
|
stackItem = stack_find_context(queryDesc->estate->es_query_cxt);
|
||||||
|
diff --git a/sql/pgaudit.sql b/sql/pgaudit.sql
|
||||||
|
index 8052426..7f0667b 100644
|
||||||
|
--- a/sql/pgaudit.sql
|
||||||
|
+++ b/sql/pgaudit.sql
|
||||||
|
@@ -1612,6 +1612,36 @@ COMMIT;
|
||||||
|
|
||||||
|
DROP TABLE part_test;
|
||||||
|
|
||||||
|
+--
|
||||||
|
+-- Test logging in parallel workers
|
||||||
|
+SET pgaudit.log = 'read';
|
||||||
|
+SET pgaudit.log_client = on;
|
||||||
|
+SET pgaudit.log_level = 'notice';
|
||||||
|
+
|
||||||
|
+-- Force parallel execution for testing
|
||||||
|
+SET max_parallel_workers_per_gather = 2;
|
||||||
|
+SET parallel_tuple_cost = 0;
|
||||||
|
+SET parallel_setup_cost = 0;
|
||||||
|
+SET min_parallel_table_scan_size = 0;
|
||||||
|
+SET min_parallel_index_scan_size = 0;
|
||||||
|
+
|
||||||
|
+-- Create table with enough data to trigger parallel execution
|
||||||
|
+CREATE TABLE parallel_test (id int, data text);
|
||||||
|
+INSERT INTO parallel_test SELECT generate_series(1, 1000), 'test data';
|
||||||
|
+
|
||||||
|
+SELECT count(*) FROM parallel_test;
|
||||||
|
+
|
||||||
|
+-- Cleanup parallel test
|
||||||
|
+DROP TABLE parallel_test;
|
||||||
|
+RESET max_parallel_workers_per_gather;
|
||||||
|
+RESET parallel_tuple_cost;
|
||||||
|
+RESET parallel_setup_cost;
|
||||||
|
+RESET min_parallel_table_scan_size;
|
||||||
|
+RESET min_parallel_index_scan_size;
|
||||||
|
+RESET pgaudit.log;
|
||||||
|
+RESET pgaudit.log_client;
|
||||||
|
+RESET pgaudit.log_level;
|
||||||
|
+
|
||||||
|
-- Cleanup
|
||||||
|
-- Set client_min_messages up to warning to avoid noise
|
||||||
|
SET client_min_messages = 'warning';
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
commit cc708dde7ef2af2a8120d757102d2e34c0463a0f
|
||||||
|
Author: Tristan Partin <tristan.partin@databricks.com>
|
||||||
|
Date: 2025-06-23 02:09:31 +0000
|
||||||
|
|
||||||
|
Disable logging in parallel workers
|
||||||
|
|
||||||
|
When a query uses parallel workers, pgaudit will log the same query for
|
||||||
|
every parallel worker. This is undesireable since it can result in log
|
||||||
|
amplification for queries that use parallel workers.
|
||||||
|
|
||||||
|
Signed-off-by: Tristan Partin <tristan.partin@databricks.com>
|
||||||
|
|
||||||
|
diff --git a/expected/pgaudit.out b/expected/pgaudit.out
|
||||||
|
index 8772054..9b66ac6 100644
|
||||||
|
--- a/expected/pgaudit.out
|
||||||
|
+++ b/expected/pgaudit.out
|
||||||
|
@@ -2556,6 +2556,37 @@ DROP SERVER fdw_server;
|
||||||
|
NOTICE: AUDIT: SESSION,11,1,DDL,DROP SERVER,,,DROP SERVER fdw_server;,<not logged>
|
||||||
|
DROP EXTENSION postgres_fdw;
|
||||||
|
NOTICE: AUDIT: SESSION,12,1,DDL,DROP EXTENSION,,,DROP EXTENSION postgres_fdw;,<not logged>
|
||||||
|
+--
|
||||||
|
+-- Test logging in parallel workers
|
||||||
|
+SET pgaudit.log = 'read';
|
||||||
|
+SET pgaudit.log_client = on;
|
||||||
|
+SET pgaudit.log_level = 'notice';
|
||||||
|
+-- Force parallel execution for testing
|
||||||
|
+SET max_parallel_workers_per_gather = 2;
|
||||||
|
+SET parallel_tuple_cost = 0;
|
||||||
|
+SET parallel_setup_cost = 0;
|
||||||
|
+SET min_parallel_table_scan_size = 0;
|
||||||
|
+SET min_parallel_index_scan_size = 0;
|
||||||
|
+-- Create table with enough data to trigger parallel execution
|
||||||
|
+CREATE TABLE parallel_test (id int, data text);
|
||||||
|
+INSERT INTO parallel_test SELECT generate_series(1, 1000), 'test data';
|
||||||
|
+SELECT count(*) FROM parallel_test;
|
||||||
|
+NOTICE: AUDIT: SESSION,13,1,READ,SELECT,,,SELECT count(*) FROM parallel_test;,<not logged>
|
||||||
|
+ count
|
||||||
|
+-------
|
||||||
|
+ 1000
|
||||||
|
+(1 row)
|
||||||
|
+
|
||||||
|
+-- Cleanup parallel test
|
||||||
|
+DROP TABLE parallel_test;
|
||||||
|
+RESET max_parallel_workers_per_gather;
|
||||||
|
+RESET parallel_tuple_cost;
|
||||||
|
+RESET parallel_setup_cost;
|
||||||
|
+RESET min_parallel_table_scan_size;
|
||||||
|
+RESET min_parallel_index_scan_size;
|
||||||
|
+RESET pgaudit.log;
|
||||||
|
+RESET pgaudit.log_client;
|
||||||
|
+RESET pgaudit.log_level;
|
||||||
|
-- Cleanup
|
||||||
|
-- Set client_min_messages up to warning to avoid noise
|
||||||
|
SET client_min_messages = 'warning';
|
||||||
|
diff --git a/pgaudit.c b/pgaudit.c
|
||||||
|
index 004d1f9..f061164 100644
|
||||||
|
--- a/pgaudit.c
|
||||||
|
+++ b/pgaudit.c
|
||||||
|
@@ -11,6 +11,7 @@
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "access/htup_details.h"
|
||||||
|
+#include "access/parallel.h"
|
||||||
|
#include "access/sysattr.h"
|
||||||
|
#include "access/xact.h"
|
||||||
|
#include "access/relation.h"
|
||||||
|
@@ -1339,7 +1340,7 @@ pgaudit_ExecutorStart_hook(QueryDesc *queryDesc, int eflags)
|
||||||
|
{
|
||||||
|
AuditEventStackItem *stackItem = NULL;
|
||||||
|
|
||||||
|
- if (!internalStatement)
|
||||||
|
+ if (!internalStatement && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* Push the audit even onto the stack */
|
||||||
|
stackItem = stack_push();
|
||||||
|
@@ -1420,7 +1421,7 @@ pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, List *permInfos, bool abort)
|
||||||
|
|
||||||
|
/* Log DML if the audit role is valid or session logging is enabled */
|
||||||
|
if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
|
||||||
|
- !IsAbortedTransactionBlockState())
|
||||||
|
+ !IsAbortedTransactionBlockState() && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* If auditLogRows is on, wait for rows processed to be set */
|
||||||
|
if (auditLogRows && auditEventStack != NULL)
|
||||||
|
@@ -1475,7 +1476,7 @@ pgaudit_ExecutorRun_hook(QueryDesc *queryDesc, ScanDirection direction, uint64 c
|
||||||
|
else
|
||||||
|
standard_ExecutorRun(queryDesc, direction, count, execute_once);
|
||||||
|
|
||||||
|
- if (auditLogRows && !internalStatement)
|
||||||
|
+ if (auditLogRows && !internalStatement && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* Find an item from the stack by the query memory context */
|
||||||
|
stackItem = stack_find_context(queryDesc->estate->es_query_cxt);
|
||||||
|
@@ -1495,7 +1496,7 @@ pgaudit_ExecutorEnd_hook(QueryDesc *queryDesc)
|
||||||
|
AuditEventStackItem *stackItem = NULL;
|
||||||
|
AuditEventStackItem *auditEventStackFull = NULL;
|
||||||
|
|
||||||
|
- if (auditLogRows && !internalStatement)
|
||||||
|
+ if (auditLogRows && !internalStatement && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* Find an item from the stack by the query memory context */
|
||||||
|
stackItem = stack_find_context(queryDesc->estate->es_query_cxt);
|
||||||
|
diff --git a/sql/pgaudit.sql b/sql/pgaudit.sql
|
||||||
|
index 6aae88b..de6d7fd 100644
|
||||||
|
--- a/sql/pgaudit.sql
|
||||||
|
+++ b/sql/pgaudit.sql
|
||||||
|
@@ -1631,6 +1631,36 @@ DROP USER MAPPING FOR regress_user1 SERVER fdw_server;
|
||||||
|
DROP SERVER fdw_server;
|
||||||
|
DROP EXTENSION postgres_fdw;
|
||||||
|
|
||||||
|
+--
|
||||||
|
+-- Test logging in parallel workers
|
||||||
|
+SET pgaudit.log = 'read';
|
||||||
|
+SET pgaudit.log_client = on;
|
||||||
|
+SET pgaudit.log_level = 'notice';
|
||||||
|
+
|
||||||
|
+-- Force parallel execution for testing
|
||||||
|
+SET max_parallel_workers_per_gather = 2;
|
||||||
|
+SET parallel_tuple_cost = 0;
|
||||||
|
+SET parallel_setup_cost = 0;
|
||||||
|
+SET min_parallel_table_scan_size = 0;
|
||||||
|
+SET min_parallel_index_scan_size = 0;
|
||||||
|
+
|
||||||
|
+-- Create table with enough data to trigger parallel execution
|
||||||
|
+CREATE TABLE parallel_test (id int, data text);
|
||||||
|
+INSERT INTO parallel_test SELECT generate_series(1, 1000), 'test data';
|
||||||
|
+
|
||||||
|
+SELECT count(*) FROM parallel_test;
|
||||||
|
+
|
||||||
|
+-- Cleanup parallel test
|
||||||
|
+DROP TABLE parallel_test;
|
||||||
|
+RESET max_parallel_workers_per_gather;
|
||||||
|
+RESET parallel_tuple_cost;
|
||||||
|
+RESET parallel_setup_cost;
|
||||||
|
+RESET min_parallel_table_scan_size;
|
||||||
|
+RESET min_parallel_index_scan_size;
|
||||||
|
+RESET pgaudit.log;
|
||||||
|
+RESET pgaudit.log_client;
|
||||||
|
+RESET pgaudit.log_level;
|
||||||
|
+
|
||||||
|
-- Cleanup
|
||||||
|
-- Set client_min_messages up to warning to avoid noise
|
||||||
|
SET client_min_messages = 'warning';
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
commit 8d02e4c6c5e1e8676251b0717a46054267091cb4
|
||||||
|
Author: Tristan Partin <tristan.partin@databricks.com>
|
||||||
|
Date: 2025-06-23 02:09:31 +0000
|
||||||
|
|
||||||
|
Disable logging in parallel workers
|
||||||
|
|
||||||
|
When a query uses parallel workers, pgaudit will log the same query for
|
||||||
|
every parallel worker. This is undesireable since it can result in log
|
||||||
|
amplification for queries that use parallel workers.
|
||||||
|
|
||||||
|
Signed-off-by: Tristan Partin <tristan.partin@databricks.com>
|
||||||
|
|
||||||
|
diff --git a/expected/pgaudit.out b/expected/pgaudit.out
|
||||||
|
index d696287..4b1059a 100644
|
||||||
|
--- a/expected/pgaudit.out
|
||||||
|
+++ b/expected/pgaudit.out
|
||||||
|
@@ -2568,6 +2568,37 @@ DROP SERVER fdw_server;
|
||||||
|
NOTICE: AUDIT: SESSION,11,1,DDL,DROP SERVER,,,DROP SERVER fdw_server,<not logged>
|
||||||
|
DROP EXTENSION postgres_fdw;
|
||||||
|
NOTICE: AUDIT: SESSION,12,1,DDL,DROP EXTENSION,,,DROP EXTENSION postgres_fdw,<not logged>
|
||||||
|
+--
|
||||||
|
+-- Test logging in parallel workers
|
||||||
|
+SET pgaudit.log = 'read';
|
||||||
|
+SET pgaudit.log_client = on;
|
||||||
|
+SET pgaudit.log_level = 'notice';
|
||||||
|
+-- Force parallel execution for testing
|
||||||
|
+SET max_parallel_workers_per_gather = 2;
|
||||||
|
+SET parallel_tuple_cost = 0;
|
||||||
|
+SET parallel_setup_cost = 0;
|
||||||
|
+SET min_parallel_table_scan_size = 0;
|
||||||
|
+SET min_parallel_index_scan_size = 0;
|
||||||
|
+-- Create table with enough data to trigger parallel execution
|
||||||
|
+CREATE TABLE parallel_test (id int, data text);
|
||||||
|
+INSERT INTO parallel_test SELECT generate_series(1, 1000), 'test data';
|
||||||
|
+SELECT count(*) FROM parallel_test;
|
||||||
|
+NOTICE: AUDIT: SESSION,13,1,READ,SELECT,,,SELECT count(*) FROM parallel_test,<not logged>
|
||||||
|
+ count
|
||||||
|
+-------
|
||||||
|
+ 1000
|
||||||
|
+(1 row)
|
||||||
|
+
|
||||||
|
+-- Cleanup parallel test
|
||||||
|
+DROP TABLE parallel_test;
|
||||||
|
+RESET max_parallel_workers_per_gather;
|
||||||
|
+RESET parallel_tuple_cost;
|
||||||
|
+RESET parallel_setup_cost;
|
||||||
|
+RESET min_parallel_table_scan_size;
|
||||||
|
+RESET min_parallel_index_scan_size;
|
||||||
|
+RESET pgaudit.log;
|
||||||
|
+RESET pgaudit.log_client;
|
||||||
|
+RESET pgaudit.log_level;
|
||||||
|
-- Cleanup
|
||||||
|
-- Set client_min_messages up to warning to avoid noise
|
||||||
|
SET client_min_messages = 'warning';
|
||||||
|
diff --git a/pgaudit.c b/pgaudit.c
|
||||||
|
index 1764af1..0e48875 100644
|
||||||
|
--- a/pgaudit.c
|
||||||
|
+++ b/pgaudit.c
|
||||||
|
@@ -11,6 +11,7 @@
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "access/htup_details.h"
|
||||||
|
+#include "access/parallel.h"
|
||||||
|
#include "access/sysattr.h"
|
||||||
|
#include "access/xact.h"
|
||||||
|
#include "access/relation.h"
|
||||||
|
@@ -1406,7 +1407,7 @@ pgaudit_ExecutorStart_hook(QueryDesc *queryDesc, int eflags)
|
||||||
|
{
|
||||||
|
AuditEventStackItem *stackItem = NULL;
|
||||||
|
|
||||||
|
- if (!internalStatement)
|
||||||
|
+ if (!internalStatement && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* Push the audit event onto the stack */
|
||||||
|
stackItem = stack_push();
|
||||||
|
@@ -1489,7 +1490,7 @@ pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, List *permInfos, bool abort)
|
||||||
|
|
||||||
|
/* Log DML if the audit role is valid or session logging is enabled */
|
||||||
|
if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
|
||||||
|
- !IsAbortedTransactionBlockState())
|
||||||
|
+ !IsAbortedTransactionBlockState() && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* If auditLogRows is on, wait for rows processed to be set */
|
||||||
|
if (auditLogRows && auditEventStack != NULL)
|
||||||
|
@@ -1544,7 +1545,7 @@ pgaudit_ExecutorRun_hook(QueryDesc *queryDesc, ScanDirection direction, uint64 c
|
||||||
|
else
|
||||||
|
standard_ExecutorRun(queryDesc, direction, count, execute_once);
|
||||||
|
|
||||||
|
- if (auditLogRows && !internalStatement)
|
||||||
|
+ if (auditLogRows && !internalStatement && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* Find an item from the stack by the query memory context */
|
||||||
|
stackItem = stack_find_context(queryDesc->estate->es_query_cxt);
|
||||||
|
@@ -1564,7 +1565,7 @@ pgaudit_ExecutorEnd_hook(QueryDesc *queryDesc)
|
||||||
|
AuditEventStackItem *stackItem = NULL;
|
||||||
|
AuditEventStackItem *auditEventStackFull = NULL;
|
||||||
|
|
||||||
|
- if (auditLogRows && !internalStatement)
|
||||||
|
+ if (auditLogRows && !internalStatement && !IsParallelWorker())
|
||||||
|
{
|
||||||
|
/* Find an item from the stack by the query memory context */
|
||||||
|
stackItem = stack_find_context(queryDesc->estate->es_query_cxt);
|
||||||
|
diff --git a/sql/pgaudit.sql b/sql/pgaudit.sql
|
||||||
|
index e161f01..c873098 100644
|
||||||
|
--- a/sql/pgaudit.sql
|
||||||
|
+++ b/sql/pgaudit.sql
|
||||||
|
@@ -1637,6 +1637,36 @@ DROP USER MAPPING FOR regress_user1 SERVER fdw_server;
|
||||||
|
DROP SERVER fdw_server;
|
||||||
|
DROP EXTENSION postgres_fdw;
|
||||||
|
|
||||||
|
+--
|
||||||
|
+-- Test logging in parallel workers
|
||||||
|
+SET pgaudit.log = 'read';
|
||||||
|
+SET pgaudit.log_client = on;
|
||||||
|
+SET pgaudit.log_level = 'notice';
|
||||||
|
+
|
||||||
|
+-- Force parallel execution for testing
|
||||||
|
+SET max_parallel_workers_per_gather = 2;
|
||||||
|
+SET parallel_tuple_cost = 0;
|
||||||
|
+SET parallel_setup_cost = 0;
|
||||||
|
+SET min_parallel_table_scan_size = 0;
|
||||||
|
+SET min_parallel_index_scan_size = 0;
|
||||||
|
+
|
||||||
|
+-- Create table with enough data to trigger parallel execution
|
||||||
|
+CREATE TABLE parallel_test (id int, data text);
|
||||||
|
+INSERT INTO parallel_test SELECT generate_series(1, 1000), 'test data';
|
||||||
|
+
|
||||||
|
+SELECT count(*) FROM parallel_test;
|
||||||
|
+
|
||||||
|
+-- Cleanup parallel test
|
||||||
|
+DROP TABLE parallel_test;
|
||||||
|
+RESET max_parallel_workers_per_gather;
|
||||||
|
+RESET parallel_tuple_cost;
|
||||||
|
+RESET parallel_setup_cost;
|
||||||
|
+RESET min_parallel_table_scan_size;
|
||||||
|
+RESET min_parallel_index_scan_size;
|
||||||
|
+RESET pgaudit.log;
|
||||||
|
+RESET pgaudit.log_client;
|
||||||
|
+RESET pgaudit.log_level;
|
||||||
|
+
|
||||||
|
-- Cleanup
|
||||||
|
-- Set client_min_messages up to warning to avoid noise
|
||||||
|
SET client_min_messages = 'warning';
|
||||||
@@ -26,7 +26,7 @@ commands:
|
|||||||
- name: postgres-exporter
|
- name: postgres-exporter
|
||||||
user: nobody
|
user: nobody
|
||||||
sysvInitAction: respawn
|
sysvInitAction: respawn
|
||||||
shell: 'DATA_SOURCE_NAME="user=cloud_admin sslmode=disable dbname=postgres application_name=postgres-exporter" /bin/postgres_exporter --config.file=/etc/postgres_exporter.yml'
|
shell: 'DATA_SOURCE_NAME="user=cloud_admin sslmode=disable dbname=postgres application_name=postgres-exporter pgaudit.log=none" /bin/postgres_exporter --config.file=/etc/postgres_exporter.yml'
|
||||||
- name: pgbouncer-exporter
|
- name: pgbouncer-exporter
|
||||||
user: postgres
|
user: postgres
|
||||||
sysvInitAction: respawn
|
sysvInitAction: respawn
|
||||||
@@ -59,7 +59,7 @@ files:
|
|||||||
# the rules use ALL as the hostname. Avoid the pointless lookups and the "unable to
|
# the rules use ALL as the hostname. Avoid the pointless lookups and the "unable to
|
||||||
# resolve host" log messages that they generate.
|
# resolve host" log messages that they generate.
|
||||||
Defaults !fqdn
|
Defaults !fqdn
|
||||||
|
|
||||||
# Allow postgres user (which is what compute_ctl runs as) to run /neonvm/bin/resize-swap
|
# Allow postgres user (which is what compute_ctl runs as) to run /neonvm/bin/resize-swap
|
||||||
# and /neonvm/bin/set-disk-quota as root without requiring entering a password (NOPASSWD),
|
# and /neonvm/bin/set-disk-quota as root without requiring entering a password (NOPASSWD),
|
||||||
# regardless of hostname (ALL)
|
# regardless of hostname (ALL)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ commands:
|
|||||||
- name: postgres-exporter
|
- name: postgres-exporter
|
||||||
user: nobody
|
user: nobody
|
||||||
sysvInitAction: respawn
|
sysvInitAction: respawn
|
||||||
shell: 'DATA_SOURCE_NAME="user=cloud_admin sslmode=disable dbname=postgres application_name=postgres-exporter" /bin/postgres_exporter --config.file=/etc/postgres_exporter.yml'
|
shell: 'DATA_SOURCE_NAME="user=cloud_admin sslmode=disable dbname=postgres application_name=postgres-exporter pgaudit.log=none" /bin/postgres_exporter --config.file=/etc/postgres_exporter.yml'
|
||||||
- name: pgbouncer-exporter
|
- name: pgbouncer-exporter
|
||||||
user: postgres
|
user: postgres
|
||||||
sysvInitAction: respawn
|
sysvInitAction: respawn
|
||||||
@@ -59,7 +59,7 @@ files:
|
|||||||
# the rules use ALL as the hostname. Avoid the pointless lookups and the "unable to
|
# the rules use ALL as the hostname. Avoid the pointless lookups and the "unable to
|
||||||
# resolve host" log messages that they generate.
|
# resolve host" log messages that they generate.
|
||||||
Defaults !fqdn
|
Defaults !fqdn
|
||||||
|
|
||||||
# Allow postgres user (which is what compute_ctl runs as) to run /neonvm/bin/resize-swap
|
# Allow postgres user (which is what compute_ctl runs as) to run /neonvm/bin/resize-swap
|
||||||
# and /neonvm/bin/set-disk-quota as root without requiring entering a password (NOPASSWD),
|
# and /neonvm/bin/set-disk-quota as root without requiring entering a password (NOPASSWD),
|
||||||
# regardless of hostname (ALL)
|
# regardless of hostname (ALL)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ fail.workspace = true
|
|||||||
flate2.workspace = true
|
flate2.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
http.workspace = true
|
http.workspace = true
|
||||||
|
hostname-validator = "1.1"
|
||||||
indexmap.workspace = true
|
indexmap.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
jsonwebtoken.workspace = true
|
jsonwebtoken.workspace = true
|
||||||
@@ -38,6 +39,7 @@ once_cell.workspace = true
|
|||||||
opentelemetry.workspace = true
|
opentelemetry.workspace = true
|
||||||
opentelemetry_sdk.workspace = true
|
opentelemetry_sdk.workspace = true
|
||||||
p256 = { version = "0.13", features = ["pem"] }
|
p256 = { version = "0.13", features = ["pem"] }
|
||||||
|
pageserver_page_api.workspace = true
|
||||||
postgres.workspace = true
|
postgres.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
reqwest = { workspace = true, features = ["json"] }
|
reqwest = { workspace = true, features = ["json"] }
|
||||||
@@ -53,6 +55,7 @@ tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }
|
|||||||
tokio-postgres.workspace = true
|
tokio-postgres.workspace = true
|
||||||
tokio-util.workspace = true
|
tokio-util.workspace = true
|
||||||
tokio-stream.workspace = true
|
tokio-stream.workspace = true
|
||||||
|
tonic.workspace = true
|
||||||
tower-otel.workspace = true
|
tower-otel.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
tracing-opentelemetry.workspace = true
|
tracing-opentelemetry.workspace = true
|
||||||
@@ -63,7 +66,8 @@ url.workspace = true
|
|||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
walkdir.workspace = true
|
walkdir.workspace = true
|
||||||
x509-cert.workspace = true
|
x509-cert.workspace = true
|
||||||
|
postgres-types.workspace = true
|
||||||
|
postgres_versioninfo.workspace = true
|
||||||
postgres_initdb.workspace = true
|
postgres_initdb.workspace = true
|
||||||
compute_api.workspace = true
|
compute_api.workspace = true
|
||||||
utils.workspace = true
|
utils.workspace = true
|
||||||
|
|||||||
@@ -46,11 +46,14 @@ stateDiagram-v2
|
|||||||
Configuration --> Failed : Failed to configure the compute
|
Configuration --> Failed : Failed to configure the compute
|
||||||
Configuration --> Running : Compute has been configured
|
Configuration --> Running : Compute has been configured
|
||||||
Empty --> Init : Compute spec is immediately available
|
Empty --> Init : Compute spec is immediately available
|
||||||
Empty --> TerminationPending : Requested termination
|
Empty --> TerminationPendingFast : Requested termination
|
||||||
|
Empty --> TerminationPendingImmediate : Requested termination
|
||||||
Init --> Failed : Failed to start Postgres
|
Init --> Failed : Failed to start Postgres
|
||||||
Init --> Running : Started Postgres
|
Init --> Running : Started Postgres
|
||||||
Running --> TerminationPending : Requested termination
|
Running --> TerminationPendingFast : Requested termination
|
||||||
TerminationPending --> Terminated : Terminated compute
|
Running --> TerminationPendingImmediate : Requested termination
|
||||||
|
TerminationPendingFast --> Terminated compute with 30s delay for cplane to inspect status
|
||||||
|
TerminationPendingImmediate --> Terminated : Terminated compute immediately
|
||||||
Failed --> [*] : Compute exited
|
Failed --> [*] : Compute exited
|
||||||
Terminated --> [*] : Compute exited
|
Terminated --> [*] : Compute exited
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -36,6 +36,8 @@
|
|||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::AtomicU64;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -124,6 +126,10 @@ struct Cli {
|
|||||||
/// Interval in seconds for collecting installed extensions statistics
|
/// Interval in seconds for collecting installed extensions statistics
|
||||||
#[arg(long, default_value = "3600")]
|
#[arg(long, default_value = "3600")]
|
||||||
pub installed_extensions_collection_interval: u64,
|
pub installed_extensions_collection_interval: u64,
|
||||||
|
|
||||||
|
/// Run in development mode, skipping VM-specific operations like process termination
|
||||||
|
#[arg(long, action = clap::ArgAction::SetTrue)]
|
||||||
|
pub dev: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
@@ -159,7 +165,7 @@ fn main() -> Result<()> {
|
|||||||
.build()?;
|
.build()?;
|
||||||
let _rt_guard = runtime.enter();
|
let _rt_guard = runtime.enter();
|
||||||
|
|
||||||
runtime.block_on(init())?;
|
runtime.block_on(init(cli.dev))?;
|
||||||
|
|
||||||
// enable core dumping for all child processes
|
// enable core dumping for all child processes
|
||||||
setrlimit(Resource::CORE, rlimit::INFINITY, rlimit::INFINITY)?;
|
setrlimit(Resource::CORE, rlimit::INFINITY, rlimit::INFINITY)?;
|
||||||
@@ -186,7 +192,9 @@ fn main() -> Result<()> {
|
|||||||
cgroup: cli.cgroup,
|
cgroup: cli.cgroup,
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
vm_monitor_addr: cli.vm_monitor_addr,
|
vm_monitor_addr: cli.vm_monitor_addr,
|
||||||
installed_extensions_collection_interval: cli.installed_extensions_collection_interval,
|
installed_extensions_collection_interval: Arc::new(AtomicU64::new(
|
||||||
|
cli.installed_extensions_collection_interval,
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
config,
|
config,
|
||||||
)?;
|
)?;
|
||||||
@@ -198,13 +206,13 @@ fn main() -> Result<()> {
|
|||||||
deinit_and_exit(exit_code);
|
deinit_and_exit(exit_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn init() -> Result<()> {
|
async fn init(dev_mode: bool) -> Result<()> {
|
||||||
init_tracing_and_logging(DEFAULT_LOG_LEVEL).await?;
|
init_tracing_and_logging(DEFAULT_LOG_LEVEL).await?;
|
||||||
|
|
||||||
let mut signals = Signals::new([SIGINT, SIGTERM, SIGQUIT])?;
|
let mut signals = Signals::new([SIGINT, SIGTERM, SIGQUIT])?;
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
for sig in signals.forever() {
|
for sig in signals.forever() {
|
||||||
handle_exit_signal(sig);
|
handle_exit_signal(sig, dev_mode);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -263,9 +271,9 @@ fn deinit_and_exit(exit_code: Option<i32>) -> ! {
|
|||||||
/// When compute_ctl is killed, send also termination signal to sync-safekeepers
|
/// When compute_ctl is killed, send also termination signal to sync-safekeepers
|
||||||
/// to prevent leakage. TODO: it is better to convert compute_ctl to async and
|
/// to prevent leakage. TODO: it is better to convert compute_ctl to async and
|
||||||
/// wait for termination which would be easy then.
|
/// wait for termination which would be easy then.
|
||||||
fn handle_exit_signal(sig: i32) {
|
fn handle_exit_signal(sig: i32, dev_mode: bool) {
|
||||||
info!("received {sig} termination signal");
|
info!("received {sig} termination signal");
|
||||||
forward_termination_signal();
|
forward_termination_signal(dev_mode);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ use anyhow::{Context, bail};
|
|||||||
use aws_config::BehaviorVersion;
|
use aws_config::BehaviorVersion;
|
||||||
use camino::{Utf8Path, Utf8PathBuf};
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use compute_tools::extension_server::{PostgresMajorVersion, get_pg_version};
|
use compute_tools::extension_server::get_pg_version;
|
||||||
use nix::unistd::Pid;
|
use nix::unistd::Pid;
|
||||||
use std::ops::Not;
|
use std::ops::Not;
|
||||||
use tracing::{Instrument, error, info, info_span, warn};
|
use tracing::{Instrument, error, info, info_span, warn};
|
||||||
@@ -179,12 +179,8 @@ impl PostgresProcess {
|
|||||||
.await
|
.await
|
||||||
.context("create pgdata directory")?;
|
.context("create pgdata directory")?;
|
||||||
|
|
||||||
let pg_version = match get_pg_version(self.pgbin.as_ref()) {
|
let pg_version = get_pg_version(self.pgbin.as_ref());
|
||||||
PostgresMajorVersion::V14 => 14,
|
|
||||||
PostgresMajorVersion::V15 => 15,
|
|
||||||
PostgresMajorVersion::V16 => 16,
|
|
||||||
PostgresMajorVersion::V17 => 17,
|
|
||||||
};
|
|
||||||
postgres_initdb::do_run_initdb(postgres_initdb::RunInitdbArgs {
|
postgres_initdb::do_run_initdb(postgres_initdb::RunInitdbArgs {
|
||||||
superuser: initdb_user,
|
superuser: initdb_user,
|
||||||
locale: DEFAULT_LOCALE, // XXX: this shouldn't be hard-coded,
|
locale: DEFAULT_LOCALE, // XXX: this shouldn't be hard-coded,
|
||||||
@@ -486,10 +482,8 @@ async fn cmd_pgdata(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let superuser = "cloud_admin";
|
let superuser = "cloud_admin";
|
||||||
let destination_connstring = format!(
|
let destination_connstring =
|
||||||
"host=localhost port={} user={} dbname=neondb",
|
format!("host=localhost port={pg_port} user={superuser} dbname=neondb");
|
||||||
pg_port, superuser
|
|
||||||
);
|
|
||||||
|
|
||||||
let pgdata_dir = workdir.join("pgdata");
|
let pgdata_dir = workdir.join("pgdata");
|
||||||
let mut proc = PostgresProcess::new(pgdata_dir.clone(), pg_bin_dir.clone(), pg_lib_dir.clone());
|
let mut proc = PostgresProcess::new(pgdata_dir.clone(), pg_bin_dir.clone(), pg_lib_dir.clone());
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ impl clap::builder::TypedValueParser for S3Uri {
|
|||||||
S3Uri::from_str(value_str).map_err(|e| {
|
S3Uri::from_str(value_str).map_err(|e| {
|
||||||
clap::Error::raw(
|
clap::Error::raw(
|
||||||
clap::error::ErrorKind::InvalidValue,
|
clap::error::ErrorKind::InvalidValue,
|
||||||
format!("Failed to parse S3 URI: {}", e),
|
format!("Failed to parse S3 URI: {e}"),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub async fn get_dbs_and_roles(compute: &Arc<ComputeNode>) -> anyhow::Result<Cat
|
|||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
if let Err(e) = connection.await {
|
if let Err(e) = connection.await {
|
||||||
eprintln!("connection error: {}", e);
|
eprintln!("connection error: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ pub async fn get_database_schema(
|
|||||||
_ => {
|
_ => {
|
||||||
let mut lines = stderr_reader.lines();
|
let mut lines = stderr_reader.lines();
|
||||||
if let Some(line) = lines.next_line().await? {
|
if let Some(line) = lines.next_line().await? {
|
||||||
if line.contains(&format!("FATAL: database \"{}\" does not exist", dbname)) {
|
if line.contains(&format!("FATAL: database \"{dbname}\" does not exist")) {
|
||||||
return Err(SchemaDumpError::DatabaseDoesNotExist);
|
return Err(SchemaDumpError::DatabaseDoesNotExist);
|
||||||
}
|
}
|
||||||
warn!("pg_dump stderr: {}", line)
|
warn!("pg_dump stderr: {}", line)
|
||||||
|
|||||||
+395
-82
@@ -3,10 +3,10 @@ use chrono::{DateTime, Utc};
|
|||||||
use compute_api::privilege::Privilege;
|
use compute_api::privilege::Privilege;
|
||||||
use compute_api::responses::{
|
use compute_api::responses::{
|
||||||
ComputeConfig, ComputeCtlConfig, ComputeMetrics, ComputeStatus, LfcOffloadState,
|
ComputeConfig, ComputeCtlConfig, ComputeMetrics, ComputeStatus, LfcOffloadState,
|
||||||
LfcPrewarmState, TlsConfig,
|
LfcPrewarmState, PromoteState, TlsConfig,
|
||||||
};
|
};
|
||||||
use compute_api::spec::{
|
use compute_api::spec::{
|
||||||
ComputeAudit, ComputeFeature, ComputeMode, ComputeSpec, ExtVersion, PgIdent,
|
ComputeAudit, ComputeFeature, ComputeMode, ComputeSpec, ExtVersion, PageserverProtocol, PgIdent,
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
@@ -15,26 +15,28 @@ use itertools::Itertools;
|
|||||||
use nix::sys::signal::{Signal, kill};
|
use nix::sys::signal::{Signal, kill};
|
||||||
use nix::unistd::Pid;
|
use nix::unistd::Pid;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use pageserver_page_api::{self as page_api, BaseBackupCompression};
|
||||||
use postgres;
|
use postgres;
|
||||||
use postgres::NoTls;
|
use postgres::NoTls;
|
||||||
use postgres::error::SqlState;
|
use postgres::error::SqlState;
|
||||||
use remote_storage::{DownloadError, RemotePath};
|
use remote_storage::{DownloadError, RemotePath};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::os::unix::fs::{PermissionsExt, symlink};
|
use std::os::unix::fs::{PermissionsExt, symlink};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
|
||||||
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
use tokio::spawn;
|
use tokio::{spawn, sync::watch, task::JoinHandle, time};
|
||||||
use tracing::{Instrument, debug, error, info, instrument, warn};
|
use tracing::{Instrument, debug, error, info, instrument, warn};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use utils::id::{TenantId, TimelineId};
|
use utils::id::{TenantId, TimelineId};
|
||||||
use utils::lsn::Lsn;
|
use utils::lsn::Lsn;
|
||||||
use utils::measured_stream::MeasuredReader;
|
use utils::measured_stream::MeasuredReader;
|
||||||
|
use utils::pid_file;
|
||||||
|
use utils::shard::{ShardCount, ShardIndex, ShardNumber};
|
||||||
|
|
||||||
use crate::configurator::launch_configurator;
|
use crate::configurator::launch_configurator;
|
||||||
use crate::disk_quota::set_disk_quota;
|
use crate::disk_quota::set_disk_quota;
|
||||||
@@ -44,6 +46,7 @@ use crate::lsn_lease::launch_lsn_lease_bg_task_for_static;
|
|||||||
use crate::metrics::COMPUTE_CTL_UP;
|
use crate::metrics::COMPUTE_CTL_UP;
|
||||||
use crate::monitor::launch_monitor;
|
use crate::monitor::launch_monitor;
|
||||||
use crate::pg_helpers::*;
|
use crate::pg_helpers::*;
|
||||||
|
use crate::pgbouncer::*;
|
||||||
use crate::rsyslog::{
|
use crate::rsyslog::{
|
||||||
PostgresLogsRsyslogConfig, configure_audit_rsyslog, configure_postgres_logs_export,
|
PostgresLogsRsyslogConfig, configure_audit_rsyslog, configure_postgres_logs_export,
|
||||||
launch_pgaudit_gc,
|
launch_pgaudit_gc,
|
||||||
@@ -67,6 +70,7 @@ pub static BUILD_TAG: Lazy<String> = Lazy::new(|| {
|
|||||||
.unwrap_or(BUILD_TAG_DEFAULT)
|
.unwrap_or(BUILD_TAG_DEFAULT)
|
||||||
.to_string()
|
.to_string()
|
||||||
});
|
});
|
||||||
|
const DEFAULT_INSTALLED_EXTENSIONS_COLLECTION_INTERVAL: u64 = 3600;
|
||||||
|
|
||||||
/// Static configuration params that don't change after startup. These mostly
|
/// Static configuration params that don't change after startup. These mostly
|
||||||
/// come from the CLI args, or are derived from them.
|
/// come from the CLI args, or are derived from them.
|
||||||
@@ -100,9 +104,11 @@ pub struct ComputeNodeParams {
|
|||||||
pub remote_ext_base_url: Option<Url>,
|
pub remote_ext_base_url: Option<Url>,
|
||||||
|
|
||||||
/// Interval for installed extensions collection
|
/// Interval for installed extensions collection
|
||||||
pub installed_extensions_collection_interval: u64,
|
pub installed_extensions_collection_interval: Arc<AtomicU64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TaskHandle = Mutex<Option<JoinHandle<()>>>;
|
||||||
|
|
||||||
/// Compute node info shared across several `compute_ctl` threads.
|
/// Compute node info shared across several `compute_ctl` threads.
|
||||||
pub struct ComputeNode {
|
pub struct ComputeNode {
|
||||||
pub params: ComputeNodeParams,
|
pub params: ComputeNodeParams,
|
||||||
@@ -123,6 +129,10 @@ pub struct ComputeNode {
|
|||||||
// key: ext_archive_name, value: started download time, download_completed?
|
// key: ext_archive_name, value: started download time, download_completed?
|
||||||
pub ext_download_progress: RwLock<HashMap<String, (DateTime<Utc>, bool)>>,
|
pub ext_download_progress: RwLock<HashMap<String, (DateTime<Utc>, bool)>>,
|
||||||
pub compute_ctl_config: ComputeCtlConfig,
|
pub compute_ctl_config: ComputeCtlConfig,
|
||||||
|
|
||||||
|
/// Handle to the extension stats collection task
|
||||||
|
extension_stats_task: TaskHandle,
|
||||||
|
lfc_offload_task: TaskHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
// store some metrics about download size that might impact startup time
|
// store some metrics about download size that might impact startup time
|
||||||
@@ -161,6 +171,11 @@ pub struct ComputeState {
|
|||||||
pub lfc_prewarm_state: LfcPrewarmState,
|
pub lfc_prewarm_state: LfcPrewarmState,
|
||||||
pub lfc_offload_state: LfcOffloadState,
|
pub lfc_offload_state: LfcOffloadState,
|
||||||
|
|
||||||
|
/// WAL flush LSN that is set after terminating Postgres and syncing safekeepers if
|
||||||
|
/// mode == ComputeMode::Primary. None otherwise
|
||||||
|
pub terminate_flush_lsn: Option<Lsn>,
|
||||||
|
pub promote_state: Option<watch::Receiver<PromoteState>>,
|
||||||
|
|
||||||
pub metrics: ComputeMetrics,
|
pub metrics: ComputeMetrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +191,8 @@ impl ComputeState {
|
|||||||
metrics: ComputeMetrics::default(),
|
metrics: ComputeMetrics::default(),
|
||||||
lfc_prewarm_state: LfcPrewarmState::default(),
|
lfc_prewarm_state: LfcPrewarmState::default(),
|
||||||
lfc_offload_state: LfcOffloadState::default(),
|
lfc_offload_state: LfcOffloadState::default(),
|
||||||
|
terminate_flush_lsn: None,
|
||||||
|
promote_state: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,10 +228,50 @@ pub struct ParsedSpec {
|
|||||||
pub pageserver_connstr: String,
|
pub pageserver_connstr: String,
|
||||||
pub safekeeper_connstrings: Vec<String>,
|
pub safekeeper_connstrings: Vec<String>,
|
||||||
pub storage_auth_token: Option<String>,
|
pub storage_auth_token: Option<String>,
|
||||||
pub endpoint_storage_addr: Option<SocketAddr>,
|
/// k8s dns name and port
|
||||||
|
pub endpoint_storage_addr: Option<String>,
|
||||||
pub endpoint_storage_token: Option<String>,
|
pub endpoint_storage_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ParsedSpec {
|
||||||
|
pub fn validate(&self) -> Result<(), String> {
|
||||||
|
// Only Primary nodes are using safekeeper_connstrings, and at the moment
|
||||||
|
// this method only validates that part of the specs.
|
||||||
|
if self.spec.mode != ComputeMode::Primary {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// While it seems like a good idea to check for an odd number of entries in
|
||||||
|
// the safekeepers connection string, changes to the list of safekeepers might
|
||||||
|
// incur appending a new server to a list of 3, in which case a list of 4
|
||||||
|
// entries is okay in production.
|
||||||
|
//
|
||||||
|
// Still we want unique entries, and at least one entry in the vector
|
||||||
|
if self.safekeeper_connstrings.is_empty() {
|
||||||
|
return Err(String::from("safekeeper_connstrings is empty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for uniqueness of the connection strings in the set
|
||||||
|
let mut connstrings = self.safekeeper_connstrings.clone();
|
||||||
|
|
||||||
|
connstrings.sort();
|
||||||
|
let mut previous = &connstrings[0];
|
||||||
|
|
||||||
|
for current in connstrings.iter().skip(1) {
|
||||||
|
// duplicate entry?
|
||||||
|
if current == previous {
|
||||||
|
return Err(format!(
|
||||||
|
"duplicate entry in safekeeper_connstrings: {current}!",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
previous = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<ComputeSpec> for ParsedSpec {
|
impl TryFrom<ComputeSpec> for ParsedSpec {
|
||||||
type Error = String;
|
type Error = String;
|
||||||
fn try_from(spec: ComputeSpec) -> Result<Self, String> {
|
fn try_from(spec: ComputeSpec) -> Result<Self, String> {
|
||||||
@@ -244,6 +301,7 @@ impl TryFrom<ComputeSpec> for ParsedSpec {
|
|||||||
} else {
|
} else {
|
||||||
spec.safekeeper_connstrings.clone()
|
spec.safekeeper_connstrings.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let storage_auth_token = spec.storage_auth_token.clone();
|
let storage_auth_token = spec.storage_auth_token.clone();
|
||||||
let tenant_id: TenantId = if let Some(tenant_id) = spec.tenant_id {
|
let tenant_id: TenantId = if let Some(tenant_id) = spec.tenant_id {
|
||||||
tenant_id
|
tenant_id
|
||||||
@@ -266,19 +324,16 @@ impl TryFrom<ComputeSpec> for ParsedSpec {
|
|||||||
.or(Err("invalid timeline id"))?
|
.or(Err("invalid timeline id"))?
|
||||||
};
|
};
|
||||||
|
|
||||||
let endpoint_storage_addr: Option<SocketAddr> = spec
|
let endpoint_storage_addr: Option<String> = spec
|
||||||
.endpoint_storage_addr
|
.endpoint_storage_addr
|
||||||
.clone()
|
.clone()
|
||||||
.or_else(|| spec.cluster.settings.find("neon.endpoint_storage_addr"))
|
.or_else(|| spec.cluster.settings.find("neon.endpoint_storage_addr"));
|
||||||
.unwrap_or_default()
|
|
||||||
.parse()
|
|
||||||
.ok();
|
|
||||||
let endpoint_storage_token = spec
|
let endpoint_storage_token = spec
|
||||||
.endpoint_storage_token
|
.endpoint_storage_token
|
||||||
.clone()
|
.clone()
|
||||||
.or_else(|| spec.cluster.settings.find("neon.endpoint_storage_token"));
|
.or_else(|| spec.cluster.settings.find("neon.endpoint_storage_token"));
|
||||||
|
|
||||||
Ok(ParsedSpec {
|
let res = ParsedSpec {
|
||||||
spec,
|
spec,
|
||||||
pageserver_connstr,
|
pageserver_connstr,
|
||||||
safekeeper_connstrings,
|
safekeeper_connstrings,
|
||||||
@@ -287,7 +342,11 @@ impl TryFrom<ComputeSpec> for ParsedSpec {
|
|||||||
timeline_id,
|
timeline_id,
|
||||||
endpoint_storage_addr,
|
endpoint_storage_addr,
|
||||||
endpoint_storage_token,
|
endpoint_storage_token,
|
||||||
})
|
};
|
||||||
|
|
||||||
|
// Now check validity of the parsed specification
|
||||||
|
res.validate()?;
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +373,7 @@ fn maybe_cgexec(cmd: &str) -> Command {
|
|||||||
|
|
||||||
struct PostgresHandle {
|
struct PostgresHandle {
|
||||||
postgres: std::process::Child,
|
postgres: std::process::Child,
|
||||||
log_collector: tokio::task::JoinHandle<Result<()>>,
|
log_collector: JoinHandle<Result<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PostgresHandle {
|
impl PostgresHandle {
|
||||||
@@ -328,7 +387,7 @@ struct StartVmMonitorResult {
|
|||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
token: tokio_util::sync::CancellationToken,
|
token: tokio_util::sync::CancellationToken,
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
vm_monitor: Option<tokio::task::JoinHandle<Result<()>>>,
|
vm_monitor: Option<JoinHandle<Result<()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComputeNode {
|
impl ComputeNode {
|
||||||
@@ -354,14 +413,11 @@ impl ComputeNode {
|
|||||||
// that can affect `compute_ctl` and prevent it from properly configuring the database schema.
|
// that can affect `compute_ctl` and prevent it from properly configuring the database schema.
|
||||||
// Unset them via connection string options before connecting to the database.
|
// Unset them via connection string options before connecting to the database.
|
||||||
// N.B. keep it in sync with `ZENITH_OPTIONS` in `get_maintenance_client()`.
|
// N.B. keep it in sync with `ZENITH_OPTIONS` in `get_maintenance_client()`.
|
||||||
//
|
const EXTRA_OPTIONS: &str = "-c role=cloud_admin -c default_transaction_read_only=off -c search_path=public -c statement_timeout=0 -c pgaudit.log=none";
|
||||||
// TODO(ololobus): we currently pass `-c default_transaction_read_only=off` from control plane
|
|
||||||
// as well. After rolling out this code, we can remove this parameter from control plane.
|
|
||||||
// In the meantime, double-passing is fine, the last value is applied.
|
|
||||||
// See: <https://github.com/neondatabase/cloud/blob/133dd8c4dbbba40edfbad475bf6a45073ca63faf/goapp/controlplane/internal/pkg/compute/provisioner/provisioner_common.go#L70>
|
|
||||||
const EXTRA_OPTIONS: &str = "-c role=cloud_admin -c default_transaction_read_only=off -c search_path=public -c statement_timeout=0";
|
|
||||||
let options = match conn_conf.get_options() {
|
let options = match conn_conf.get_options() {
|
||||||
Some(options) => format!("{} {}", options, EXTRA_OPTIONS),
|
// Allow the control plane to override any options set by the
|
||||||
|
// compute
|
||||||
|
Some(options) => format!("{EXTRA_OPTIONS} {options}"),
|
||||||
None => EXTRA_OPTIONS.to_string(),
|
None => EXTRA_OPTIONS.to_string(),
|
||||||
};
|
};
|
||||||
conn_conf.options(&options);
|
conn_conf.options(&options);
|
||||||
@@ -381,6 +437,8 @@ impl ComputeNode {
|
|||||||
state_changed: Condvar::new(),
|
state_changed: Condvar::new(),
|
||||||
ext_download_progress: RwLock::new(HashMap::new()),
|
ext_download_progress: RwLock::new(HashMap::new()),
|
||||||
compute_ctl_config: config.compute_ctl_config,
|
compute_ctl_config: config.compute_ctl_config,
|
||||||
|
extension_stats_task: Mutex::new(None),
|
||||||
|
lfc_offload_task: Mutex::new(None),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,6 +526,9 @@ impl ComputeNode {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.terminate_extension_stats_task();
|
||||||
|
this.terminate_lfc_offload_task();
|
||||||
|
|
||||||
// Terminate the vm_monitor so it releases the file watcher on
|
// Terminate the vm_monitor so it releases the file watcher on
|
||||||
// /sys/fs/cgroup/neon-postgres.
|
// /sys/fs/cgroup/neon-postgres.
|
||||||
// Note: the vm-monitor only runs on linux because it requires cgroups.
|
// Note: the vm-monitor only runs on linux because it requires cgroups.
|
||||||
@@ -489,12 +550,21 @@ impl ComputeNode {
|
|||||||
// Reap the postgres process
|
// Reap the postgres process
|
||||||
delay_exit |= this.cleanup_after_postgres_exit()?;
|
delay_exit |= this.cleanup_after_postgres_exit()?;
|
||||||
|
|
||||||
|
// /terminate returns LSN. If we don't sleep at all, connection will break and we
|
||||||
|
// won't get result. If we sleep too much, tests will take significantly longer
|
||||||
|
// and Github Action run will error out
|
||||||
|
let sleep_duration = if delay_exit {
|
||||||
|
Duration::from_secs(30)
|
||||||
|
} else {
|
||||||
|
Duration::from_millis(300)
|
||||||
|
};
|
||||||
|
|
||||||
// If launch failed, keep serving HTTP requests for a while, so the cloud
|
// If launch failed, keep serving HTTP requests for a while, so the cloud
|
||||||
// control plane can get the actual error.
|
// control plane can get the actual error.
|
||||||
if delay_exit {
|
if delay_exit {
|
||||||
info!("giving control plane 30s to collect the error before shutdown");
|
info!("giving control plane 30s to collect the error before shutdown");
|
||||||
std::thread::sleep(Duration::from_secs(30));
|
|
||||||
}
|
}
|
||||||
|
std::thread::sleep(sleep_duration);
|
||||||
Ok(exit_code)
|
Ok(exit_code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,10 +765,15 @@ impl ComputeNode {
|
|||||||
// Configure and start rsyslog for compliance audit logging
|
// Configure and start rsyslog for compliance audit logging
|
||||||
match pspec.spec.audit_log_level {
|
match pspec.spec.audit_log_level {
|
||||||
ComputeAudit::Hipaa | ComputeAudit::Extended | ComputeAudit::Full => {
|
ComputeAudit::Hipaa | ComputeAudit::Extended | ComputeAudit::Full => {
|
||||||
let remote_endpoint =
|
let remote_tls_endpoint =
|
||||||
|
std::env::var("AUDIT_LOGGING_TLS_ENDPOINT").unwrap_or("".to_string());
|
||||||
|
let remote_plain_endpoint =
|
||||||
std::env::var("AUDIT_LOGGING_ENDPOINT").unwrap_or("".to_string());
|
std::env::var("AUDIT_LOGGING_ENDPOINT").unwrap_or("".to_string());
|
||||||
if remote_endpoint.is_empty() {
|
|
||||||
anyhow::bail!("AUDIT_LOGGING_ENDPOINT is empty");
|
if remote_plain_endpoint.is_empty() && remote_tls_endpoint.is_empty() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"AUDIT_LOGGING_ENDPOINT and AUDIT_LOGGING_TLS_ENDPOINT are both empty"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let log_directory_path = Path::new(&self.params.pgdata).join("log");
|
let log_directory_path = Path::new(&self.params.pgdata).join("log");
|
||||||
@@ -714,7 +789,8 @@ impl ComputeNode {
|
|||||||
log_directory_path.clone(),
|
log_directory_path.clone(),
|
||||||
endpoint_id,
|
endpoint_id,
|
||||||
project_id,
|
project_id,
|
||||||
&remote_endpoint,
|
&remote_plain_endpoint,
|
||||||
|
&remote_tls_endpoint,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Launch a background task to clean up the audit logs
|
// Launch a background task to clean up the audit logs
|
||||||
@@ -781,12 +857,15 @@ impl ComputeNode {
|
|||||||
// Log metrics so that we can search for slow operations in logs
|
// Log metrics so that we can search for slow operations in logs
|
||||||
info!(?metrics, postmaster_pid = %postmaster_pid, "compute start finished");
|
info!(?metrics, postmaster_pid = %postmaster_pid, "compute start finished");
|
||||||
|
|
||||||
// Spawn the extension stats background task
|
|
||||||
self.spawn_extension_stats_task();
|
self.spawn_extension_stats_task();
|
||||||
|
|
||||||
if pspec.spec.autoprewarm {
|
if pspec.spec.autoprewarm {
|
||||||
self.prewarm_lfc();
|
info!("autoprewarming on startup as requested");
|
||||||
|
self.prewarm_lfc(None);
|
||||||
}
|
}
|
||||||
|
if let Some(seconds) = pspec.spec.offload_lfc_interval_seconds {
|
||||||
|
self.spawn_lfc_offload_task(Duration::from_secs(seconds.into()));
|
||||||
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -866,20 +945,31 @@ impl ComputeNode {
|
|||||||
// Maybe sync safekeepers again, to speed up next startup
|
// Maybe sync safekeepers again, to speed up next startup
|
||||||
let compute_state = self.state.lock().unwrap().clone();
|
let compute_state = self.state.lock().unwrap().clone();
|
||||||
let pspec = compute_state.pspec.as_ref().expect("spec must be set");
|
let pspec = compute_state.pspec.as_ref().expect("spec must be set");
|
||||||
if matches!(pspec.spec.mode, compute_api::spec::ComputeMode::Primary) {
|
let lsn = if matches!(pspec.spec.mode, compute_api::spec::ComputeMode::Primary) {
|
||||||
info!("syncing safekeepers on shutdown");
|
info!("syncing safekeepers on shutdown");
|
||||||
let storage_auth_token = pspec.storage_auth_token.clone();
|
let storage_auth_token = pspec.storage_auth_token.clone();
|
||||||
let lsn = self.sync_safekeepers(storage_auth_token)?;
|
let lsn = self.sync_safekeepers(storage_auth_token)?;
|
||||||
info!("synced safekeepers at lsn {lsn}");
|
info!(%lsn, "synced safekeepers");
|
||||||
}
|
Some(lsn)
|
||||||
|
} else {
|
||||||
|
info!("not primary, not syncing safekeepers");
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let mut delay_exit = false;
|
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
if state.status == ComputeStatus::TerminationPending {
|
state.terminate_flush_lsn = lsn;
|
||||||
|
|
||||||
|
let delay_exit = state.status == ComputeStatus::TerminationPendingFast;
|
||||||
|
if state.status == ComputeStatus::TerminationPendingFast
|
||||||
|
|| state.status == ComputeStatus::TerminationPendingImmediate
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
"Changing compute status from {} to {}",
|
||||||
|
state.status,
|
||||||
|
ComputeStatus::Terminated
|
||||||
|
);
|
||||||
state.status = ComputeStatus::Terminated;
|
state.status = ComputeStatus::Terminated;
|
||||||
self.state_changed.notify_all();
|
self.state_changed.notify_all();
|
||||||
// we were asked to terminate gracefully, don't exit to avoid restart
|
|
||||||
delay_exit = true
|
|
||||||
}
|
}
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
@@ -936,13 +1026,103 @@ impl ComputeNode {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get basebackup from the libpq connection to pageserver using `connstr` and
|
/// Fetches a basebackup from the Pageserver using the compute state's Pageserver connstring and
|
||||||
// unarchive it to `pgdata` directory overriding all its previous content.
|
/// unarchives it to `pgdata` directory, replacing any existing contents.
|
||||||
#[instrument(skip_all, fields(%lsn))]
|
#[instrument(skip_all, fields(%lsn))]
|
||||||
fn try_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 spec = compute_state.pspec.as_ref().expect("spec must be set");
|
||||||
let start_time = Instant::now();
|
|
||||||
|
|
||||||
|
let shard0_connstr = spec.pageserver_connstr.split(',').next().unwrap();
|
||||||
|
let started = Instant::now();
|
||||||
|
|
||||||
|
let (connected, size) = match PageserverProtocol::from_connstring(shard0_connstr)? {
|
||||||
|
PageserverProtocol::Libpq => self.try_get_basebackup_libpq(spec, lsn)?,
|
||||||
|
PageserverProtocol::Grpc => self.try_get_basebackup_grpc(spec, lsn)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.fix_zenith_signal_neon_signal()?;
|
||||||
|
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
state.metrics.pageserver_connect_micros =
|
||||||
|
connected.duration_since(started).as_micros() as u64;
|
||||||
|
state.metrics.basebackup_bytes = size as u64;
|
||||||
|
state.metrics.basebackup_ms = started.elapsed().as_millis() as u64;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move the Zenith signal file to Neon signal file location.
|
||||||
|
/// This makes Compute compatible with older PageServers that don't yet
|
||||||
|
/// know about the Zenith->Neon rename.
|
||||||
|
fn fix_zenith_signal_neon_signal(&self) -> Result<()> {
|
||||||
|
let datadir = Path::new(&self.params.pgdata);
|
||||||
|
|
||||||
|
let neonsig = datadir.join("neon.signal");
|
||||||
|
|
||||||
|
if neonsig.is_file() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let zenithsig = datadir.join("zenith.signal");
|
||||||
|
|
||||||
|
if zenithsig.is_file() {
|
||||||
|
fs::copy(zenithsig, neonsig)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetches a basebackup via gRPC. The connstring must use grpc://. Returns the timestamp when
|
||||||
|
/// the connection was established, and the (compressed) size of the basebackup.
|
||||||
|
fn try_get_basebackup_grpc(&self, spec: &ParsedSpec, lsn: Lsn) -> Result<(Instant, usize)> {
|
||||||
|
let shard0_connstr = spec
|
||||||
|
.pageserver_connstr
|
||||||
|
.split(',')
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
let shard_index = match spec.pageserver_connstr.split(',').count() as u8 {
|
||||||
|
0 | 1 => ShardIndex::unsharded(),
|
||||||
|
count => ShardIndex::new(ShardNumber(0), ShardCount(count)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (reader, connected) = tokio::runtime::Handle::current().block_on(async move {
|
||||||
|
let mut client = page_api::Client::connect(
|
||||||
|
shard0_connstr,
|
||||||
|
spec.tenant_id,
|
||||||
|
spec.timeline_id,
|
||||||
|
shard_index,
|
||||||
|
spec.storage_auth_token.clone(),
|
||||||
|
None, // NB: base backups use payload compression
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let connected = Instant::now();
|
||||||
|
let reader = client
|
||||||
|
.get_base_backup(page_api::GetBaseBackupRequest {
|
||||||
|
lsn: (lsn != Lsn(0)).then_some(lsn),
|
||||||
|
compression: BaseBackupCompression::Gzip,
|
||||||
|
replica: spec.spec.mode != ComputeMode::Primary,
|
||||||
|
full: false,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
anyhow::Ok((reader, connected))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut reader = MeasuredReader::new(tokio_util::io::SyncIoBridge::new(reader));
|
||||||
|
|
||||||
|
// Set `ignore_zeros` so that unpack() reads the entire stream and doesn't just stop at the
|
||||||
|
// end-of-archive marker. If the server errors, the tar::Builder drop handler will write an
|
||||||
|
// end-of-archive marker before the error is emitted, and we would not see the error.
|
||||||
|
let mut ar = tar::Archive::new(flate2::read::GzDecoder::new(&mut reader));
|
||||||
|
ar.set_ignore_zeros(true);
|
||||||
|
ar.unpack(&self.params.pgdata)?;
|
||||||
|
|
||||||
|
Ok((connected, reader.get_byte_count()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetches a basebackup via libpq. The connstring must use postgresql://. Returns the timestamp
|
||||||
|
/// when the connection was established, and the (compressed) size of the basebackup.
|
||||||
|
fn try_get_basebackup_libpq(&self, spec: &ParsedSpec, lsn: Lsn) -> Result<(Instant, usize)> {
|
||||||
let shard0_connstr = spec.pageserver_connstr.split(',').next().unwrap();
|
let shard0_connstr = spec.pageserver_connstr.split(',').next().unwrap();
|
||||||
let mut config = postgres::Config::from_str(shard0_connstr)?;
|
let mut config = postgres::Config::from_str(shard0_connstr)?;
|
||||||
|
|
||||||
@@ -956,16 +1136,14 @@ impl ComputeNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.application_name("compute_ctl");
|
config.application_name("compute_ctl");
|
||||||
if let Some(spec) = &compute_state.pspec {
|
config.options(&format!(
|
||||||
config.options(&format!(
|
"-c neon.compute_mode={}",
|
||||||
"-c neon.compute_mode={}",
|
spec.spec.mode.to_type_str()
|
||||||
spec.spec.mode.to_type_str()
|
));
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to pageserver
|
// Connect to pageserver
|
||||||
let mut client = config.connect(NoTls)?;
|
let mut client = config.connect(NoTls)?;
|
||||||
let pageserver_connect_micros = start_time.elapsed().as_micros() as u64;
|
let connected = Instant::now();
|
||||||
|
|
||||||
let basebackup_cmd = match lsn {
|
let basebackup_cmd = match lsn {
|
||||||
Lsn(0) => {
|
Lsn(0) => {
|
||||||
@@ -1002,16 +1180,13 @@ impl ComputeNode {
|
|||||||
// Set `ignore_zeros` so that unpack() reads all the Copy data and
|
// Set `ignore_zeros` so that unpack() reads all the Copy data and
|
||||||
// doesn't stop at the end-of-archive marker. Otherwise, if the server
|
// doesn't stop at the end-of-archive marker. Otherwise, if the server
|
||||||
// sends an Error after finishing the tarball, we will not notice it.
|
// sends an Error after finishing the tarball, we will not notice it.
|
||||||
|
// The tar::Builder drop handler will write an end-of-archive marker
|
||||||
|
// before emitting the error, and we would not see it otherwise.
|
||||||
let mut ar = tar::Archive::new(flate2::read::GzDecoder::new(&mut bufreader));
|
let mut ar = tar::Archive::new(flate2::read::GzDecoder::new(&mut bufreader));
|
||||||
ar.set_ignore_zeros(true);
|
ar.set_ignore_zeros(true);
|
||||||
ar.unpack(&self.params.pgdata)?;
|
ar.unpack(&self.params.pgdata)?;
|
||||||
|
|
||||||
// Report metrics
|
Ok((connected, measured_reader.get_byte_count()))
|
||||||
let mut state = self.state.lock().unwrap();
|
|
||||||
state.metrics.pageserver_connect_micros = pageserver_connect_micros;
|
|
||||||
state.metrics.basebackup_bytes = measured_reader.get_byte_count() as u64;
|
|
||||||
state.metrics.basebackup_ms = start_time.elapsed().as_millis() as u64;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the basebackup in a retry loop
|
// Gets the basebackup in a retry loop
|
||||||
@@ -1064,7 +1239,7 @@ impl ComputeNode {
|
|||||||
let sk_configs = sk_connstrs.into_iter().map(|connstr| {
|
let sk_configs = sk_connstrs.into_iter().map(|connstr| {
|
||||||
// Format connstr
|
// Format connstr
|
||||||
let id = connstr.clone();
|
let id = connstr.clone();
|
||||||
let connstr = format!("postgresql://no_user@{}", connstr);
|
let connstr = format!("postgresql://no_user@{connstr}");
|
||||||
let options = format!(
|
let options = format!(
|
||||||
"-c timeline_id={} tenant_id={}",
|
"-c timeline_id={} tenant_id={}",
|
||||||
pspec.timeline_id, pspec.tenant_id
|
pspec.timeline_id, pspec.tenant_id
|
||||||
@@ -1111,9 +1286,7 @@ impl ComputeNode {
|
|||||||
|
|
||||||
// In case of error, log and fail the check, but don't crash.
|
// In case of error, log and fail the check, but don't crash.
|
||||||
// We're playing it safe because these errors could be transient
|
// We're playing it safe because these errors could be transient
|
||||||
// and we don't yet retry. Also being careful here allows us to
|
// and we don't yet retry.
|
||||||
// be backwards compatible with safekeepers that don't have the
|
|
||||||
// TIMELINE_STATUS API yet.
|
|
||||||
if responses.len() < quorum {
|
if responses.len() < quorum {
|
||||||
error!(
|
error!(
|
||||||
"failed sync safekeepers check {:?} {:?} {:?}",
|
"failed sync safekeepers check {:?} {:?} {:?}",
|
||||||
@@ -1427,7 +1600,7 @@ impl ComputeNode {
|
|||||||
let (mut client, connection) = conf.connect(NoTls).await?;
|
let (mut client, connection) = conf.connect(NoTls).await?;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = connection.await {
|
if let Err(e) = connection.await {
|
||||||
eprintln!("connection error: {}", e);
|
eprintln!("connection error: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1548,6 +1721,8 @@ impl ComputeNode {
|
|||||||
tls_config = self.compute_ctl_config.tls.clone();
|
tls_config = self.compute_ctl_config.tls.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.update_installed_extensions_collection_interval(&spec);
|
||||||
|
|
||||||
let max_concurrent_connections = self.max_service_connections(compute_state, &spec);
|
let max_concurrent_connections = self.max_service_connections(compute_state, &spec);
|
||||||
|
|
||||||
// Merge-apply spec & changes to PostgreSQL state.
|
// Merge-apply spec & changes to PostgreSQL state.
|
||||||
@@ -1570,7 +1745,7 @@ impl ComputeNode {
|
|||||||
Ok((mut client, connection)) => {
|
Ok((mut client, connection)) => {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = connection.await {
|
if let Err(e) = connection.await {
|
||||||
eprintln!("connection error: {}", e);
|
eprintln!("connection error: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if let Err(e) = handle_migrations(&mut client).await {
|
if let Err(e) = handle_migrations(&mut client).await {
|
||||||
@@ -1612,6 +1787,8 @@ impl ComputeNode {
|
|||||||
|
|
||||||
let tls_config = self.tls_config(&spec);
|
let tls_config = self.tls_config(&spec);
|
||||||
|
|
||||||
|
self.update_installed_extensions_collection_interval(&spec);
|
||||||
|
|
||||||
if let Some(ref pgbouncer_settings) = spec.pgbouncer_settings {
|
if let Some(ref pgbouncer_settings) = spec.pgbouncer_settings {
|
||||||
info!("tuning pgbouncer");
|
info!("tuning pgbouncer");
|
||||||
|
|
||||||
@@ -1655,6 +1832,8 @@ impl ComputeNode {
|
|||||||
tls_config,
|
tls_config,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
self.pg_reload_conf()?;
|
||||||
|
|
||||||
if !spec.skip_pg_catalog_updates {
|
if !spec.skip_pg_catalog_updates {
|
||||||
let max_concurrent_connections = spec.reconfigure_concurrency;
|
let max_concurrent_connections = spec.reconfigure_concurrency;
|
||||||
// Temporarily reset max_cluster_size in config
|
// Temporarily reset max_cluster_size in config
|
||||||
@@ -1674,10 +1853,9 @@ impl ComputeNode {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
self.pg_reload_conf()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pg_reload_conf()?;
|
|
||||||
|
|
||||||
let unknown_op = "unknown".to_string();
|
let unknown_op = "unknown".to_string();
|
||||||
let op_id = spec.operation_uuid.as_ref().unwrap_or(&unknown_op);
|
let op_id = spec.operation_uuid.as_ref().unwrap_or(&unknown_op);
|
||||||
info!(
|
info!(
|
||||||
@@ -1750,7 +1928,8 @@ impl ComputeNode {
|
|||||||
|
|
||||||
// exit loop
|
// exit loop
|
||||||
ComputeStatus::Failed
|
ComputeStatus::Failed
|
||||||
| ComputeStatus::TerminationPending
|
| ComputeStatus::TerminationPendingFast
|
||||||
|
| ComputeStatus::TerminationPendingImmediate
|
||||||
| ComputeStatus::Terminated => break 'cert_update,
|
| ComputeStatus::Terminated => break 'cert_update,
|
||||||
|
|
||||||
// wait
|
// wait
|
||||||
@@ -1874,7 +2053,7 @@ impl ComputeNode {
|
|||||||
let (client, connection) = connect_result.unwrap();
|
let (client, connection) = connect_result.unwrap();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = connection.await {
|
if let Err(e) = connection.await {
|
||||||
eprintln!("connection error: {}", e);
|
eprintln!("connection error: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let result = client
|
let result = client
|
||||||
@@ -2043,7 +2222,7 @@ LIMIT 100",
|
|||||||
db_client
|
db_client
|
||||||
.simple_query(&query)
|
.simple_query(&query)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to execute query: {}", query))?;
|
.with_context(|| format!("Failed to execute query: {query}"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -2070,7 +2249,7 @@ LIMIT 100",
|
|||||||
let version: Option<ExtVersion> = db_client
|
let version: Option<ExtVersion> = db_client
|
||||||
.query_opt(version_query, &[&ext_name])
|
.query_opt(version_query, &[&ext_name])
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to execute query: {}", version_query))?
|
.with_context(|| format!("Failed to execute query: {version_query}"))?
|
||||||
.map(|row| row.get(0));
|
.map(|row| row.get(0));
|
||||||
|
|
||||||
// sanitize the inputs as postgres idents.
|
// sanitize the inputs as postgres idents.
|
||||||
@@ -2085,14 +2264,14 @@ LIMIT 100",
|
|||||||
db_client
|
db_client
|
||||||
.simple_query(&query)
|
.simple_query(&query)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to execute query: {}", query))?;
|
.with_context(|| format!("Failed to execute query: {query}"))?;
|
||||||
} else {
|
} else {
|
||||||
let query =
|
let query =
|
||||||
format!("CREATE EXTENSION IF NOT EXISTS {ext_name} WITH VERSION {quoted_version}");
|
format!("CREATE EXTENSION IF NOT EXISTS {ext_name} WITH VERSION {quoted_version}");
|
||||||
db_client
|
db_client
|
||||||
.simple_query(&query)
|
.simple_query(&query)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to execute query: {}", query))?;
|
.with_context(|| format!("Failed to execute query: {query}"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ext_version)
|
Ok(ext_version)
|
||||||
@@ -2216,24 +2395,84 @@ LIMIT 100",
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_extension_stats_task(&self) {
|
pub fn spawn_extension_stats_task(&self) {
|
||||||
|
self.terminate_extension_stats_task();
|
||||||
|
|
||||||
let conf = self.tokio_conn_conf.clone();
|
let conf = self.tokio_conn_conf.clone();
|
||||||
let installed_extensions_collection_interval =
|
let atomic_interval = self.params.installed_extensions_collection_interval.clone();
|
||||||
self.params.installed_extensions_collection_interval;
|
let mut installed_extensions_collection_interval =
|
||||||
tokio::spawn(async move {
|
2 * atomic_interval.load(std::sync::atomic::Ordering::SeqCst);
|
||||||
// An initial sleep is added to ensure that two collections don't happen at the same time.
|
info!(
|
||||||
// The first collection happens during compute startup.
|
"[NEON_EXT_SPAWN] Spawning background installed extensions worker with Timeout: {}",
|
||||||
tokio::time::sleep(tokio::time::Duration::from_secs(
|
installed_extensions_collection_interval
|
||||||
installed_extensions_collection_interval,
|
);
|
||||||
))
|
let handle = tokio::spawn(async move {
|
||||||
.await;
|
|
||||||
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(
|
|
||||||
installed_extensions_collection_interval,
|
|
||||||
));
|
|
||||||
loop {
|
loop {
|
||||||
interval.tick().await;
|
info!(
|
||||||
|
"[NEON_EXT_INT_SLEEP]: Interval: {}",
|
||||||
|
installed_extensions_collection_interval
|
||||||
|
);
|
||||||
|
// Sleep at the start of the loop to ensure that two collections don't happen at the same time.
|
||||||
|
// The first collection happens during compute startup.
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(
|
||||||
|
installed_extensions_collection_interval,
|
||||||
|
))
|
||||||
|
.await;
|
||||||
let _ = installed_extensions(conf.clone()).await;
|
let _ = installed_extensions(conf.clone()).await;
|
||||||
|
// Acquire a read lock on the compute spec and then update the interval if necessary
|
||||||
|
installed_extensions_collection_interval = std::cmp::max(
|
||||||
|
installed_extensions_collection_interval,
|
||||||
|
2 * atomic_interval.load(std::sync::atomic::Ordering::SeqCst),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Store the new task handle
|
||||||
|
*self.extension_stats_task.lock().unwrap() = Some(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn terminate_extension_stats_task(&self) {
|
||||||
|
if let Some(h) = self.extension_stats_task.lock().unwrap().take() {
|
||||||
|
h.abort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_lfc_offload_task(self: &Arc<Self>, interval: Duration) {
|
||||||
|
self.terminate_lfc_offload_task();
|
||||||
|
let secs = interval.as_secs();
|
||||||
|
info!("spawning lfc offload worker with {secs}s interval");
|
||||||
|
let this = self.clone();
|
||||||
|
let handle = spawn(async move {
|
||||||
|
let mut interval = time::interval(interval);
|
||||||
|
interval.tick().await; // returns immediately
|
||||||
|
loop {
|
||||||
|
interval.tick().await;
|
||||||
|
this.offload_lfc_async().await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*self.lfc_offload_task.lock().unwrap() = Some(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn terminate_lfc_offload_task(&self) {
|
||||||
|
if let Some(h) = self.lfc_offload_task.lock().unwrap().take() {
|
||||||
|
h.abort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_installed_extensions_collection_interval(&self, spec: &ComputeSpec) {
|
||||||
|
// Update the interval for collecting installed extensions statistics
|
||||||
|
// If the value is -1, we never suspend so set the value to default collection.
|
||||||
|
// If the value is 0, it means default, we will just continue to use the default.
|
||||||
|
if spec.suspend_timeout_seconds == -1 || spec.suspend_timeout_seconds == 0 {
|
||||||
|
self.params.installed_extensions_collection_interval.store(
|
||||||
|
DEFAULT_INSTALLED_EXTENSIONS_COLLECTION_INTERVAL,
|
||||||
|
std::sync::atomic::Ordering::SeqCst,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.params.installed_extensions_collection_interval.store(
|
||||||
|
spec.suspend_timeout_seconds as u64,
|
||||||
|
std::sync::atomic::Ordering::SeqCst,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2246,17 +2485,73 @@ pub async fn installed_extensions(conf: tokio_postgres::Config) -> Result<()> {
|
|||||||
serde_json::to_string(&extensions).expect("failed to serialize extensions list")
|
serde_json::to_string(&extensions).expect("failed to serialize extensions list")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(err) => error!("could not get installed extensions: {err:?}"),
|
Err(err) => error!("could not get installed extensions: {err}"),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn forward_termination_signal() {
|
pub fn forward_termination_signal(dev_mode: bool) {
|
||||||
let ss_pid = SYNC_SAFEKEEPERS_PID.load(Ordering::SeqCst);
|
let ss_pid = SYNC_SAFEKEEPERS_PID.load(Ordering::SeqCst);
|
||||||
if ss_pid != 0 {
|
if ss_pid != 0 {
|
||||||
let ss_pid = nix::unistd::Pid::from_raw(ss_pid as i32);
|
let ss_pid = nix::unistd::Pid::from_raw(ss_pid as i32);
|
||||||
kill(ss_pid, Signal::SIGTERM).ok();
|
kill(ss_pid, Signal::SIGTERM).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !dev_mode {
|
||||||
|
// Terminate pgbouncer with SIGKILL
|
||||||
|
match pid_file::read(PGBOUNCER_PIDFILE.into()) {
|
||||||
|
Ok(pid_file::PidFileRead::LockedByOtherProcess(pid)) => {
|
||||||
|
info!("sending SIGKILL to pgbouncer process pid: {}", pid);
|
||||||
|
if let Err(e) = kill(pid, Signal::SIGKILL) {
|
||||||
|
error!("failed to terminate pgbouncer: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// pgbouncer does not lock the pid file, so we read and kill the process directly
|
||||||
|
Ok(pid_file::PidFileRead::NotHeldByAnyProcess(_)) => {
|
||||||
|
if let Ok(pid_str) = std::fs::read_to_string(PGBOUNCER_PIDFILE) {
|
||||||
|
if let Ok(pid) = pid_str.trim().parse::<i32>() {
|
||||||
|
info!(
|
||||||
|
"sending SIGKILL to pgbouncer process pid: {} (from unlocked pid file)",
|
||||||
|
pid
|
||||||
|
);
|
||||||
|
if let Err(e) = kill(Pid::from_raw(pid), Signal::SIGKILL) {
|
||||||
|
error!("failed to terminate pgbouncer: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("pgbouncer pid file exists but process not running");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(pid_file::PidFileRead::NotExist) => {
|
||||||
|
info!("pgbouncer pid file not found, process may not be running");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("error reading pgbouncer pid file: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminate local_proxy
|
||||||
|
match pid_file::read("/etc/local_proxy/pid".into()) {
|
||||||
|
Ok(pid_file::PidFileRead::LockedByOtherProcess(pid)) => {
|
||||||
|
info!("sending SIGTERM to local_proxy process pid: {}", pid);
|
||||||
|
if let Err(e) = kill(pid, Signal::SIGTERM) {
|
||||||
|
error!("failed to terminate local_proxy: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(pid_file::PidFileRead::NotHeldByAnyProcess(_)) => {
|
||||||
|
info!("local_proxy PID file exists but process not running");
|
||||||
|
}
|
||||||
|
Ok(pid_file::PidFileRead::NotExist) => {
|
||||||
|
info!("local_proxy PID file not found, process may not be running");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("error reading local_proxy PID file: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("Skipping pgbouncer and local_proxy termination because in dev mode");
|
||||||
|
}
|
||||||
|
|
||||||
let pg_pid = PG_PID.load(Ordering::SeqCst);
|
let pg_pid = PG_PID.load(Ordering::SeqCst);
|
||||||
if pg_pid != 0 {
|
if pg_pid != 0 {
|
||||||
let pg_pid = nix::unistd::Pid::from_raw(pg_pid as i32);
|
let pg_pid = nix::unistd::Pid::from_raw(pg_pid as i32);
|
||||||
@@ -2289,3 +2584,21 @@ impl<T: 'static> JoinSetExt<T> for tokio::task::JoinSet<T> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn duplicate_safekeeper_connstring() {
|
||||||
|
let file = File::open("tests/cluster_spec.json").unwrap();
|
||||||
|
let spec: ComputeSpec = serde_json::from_reader(file).unwrap();
|
||||||
|
|
||||||
|
match ParsedSpec::try_from(spec.clone()) {
|
||||||
|
Ok(_p) => panic!("Failed to detect duplicate entry"),
|
||||||
|
Err(e) => assert!(e.starts_with("duplicate entry in safekeeper_connstrings:")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use compute_api::responses::LfcOffloadState;
|
|||||||
use compute_api::responses::LfcPrewarmState;
|
use compute_api::responses::LfcPrewarmState;
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
use std::mem::replace;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::{io::AsyncReadExt, spawn};
|
use tokio::{io::AsyncReadExt, spawn};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
@@ -25,11 +26,16 @@ struct EndpointStoragePair {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const KEY: &str = "lfc_state";
|
const KEY: &str = "lfc_state";
|
||||||
impl TryFrom<&crate::compute::ParsedSpec> for EndpointStoragePair {
|
impl EndpointStoragePair {
|
||||||
type Error = anyhow::Error;
|
/// endpoint_id is set to None while prewarming from other endpoint, see replica promotion
|
||||||
fn try_from(pspec: &crate::compute::ParsedSpec) -> Result<Self, Self::Error> {
|
/// If not None, takes precedence over pspec.spec.endpoint_id
|
||||||
let Some(ref endpoint_id) = pspec.spec.endpoint_id else {
|
fn from_spec_and_endpoint(
|
||||||
bail!("pspec.endpoint_id missing")
|
pspec: &crate::compute::ParsedSpec,
|
||||||
|
endpoint_id: Option<String>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let endpoint_id = endpoint_id.as_ref().or(pspec.spec.endpoint_id.as_ref());
|
||||||
|
let Some(ref endpoint_id) = endpoint_id else {
|
||||||
|
bail!("pspec.endpoint_id missing, other endpoint_id not provided")
|
||||||
};
|
};
|
||||||
let Some(ref base_uri) = pspec.endpoint_storage_addr else {
|
let Some(ref base_uri) = pspec.endpoint_storage_addr else {
|
||||||
bail!("pspec.endpoint_storage_addr missing")
|
bail!("pspec.endpoint_storage_addr missing")
|
||||||
@@ -64,7 +70,7 @@ impl ComputeNode {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let row = match client
|
let row = match client
|
||||||
.query_one("select * from get_prewarm_info()", &[])
|
.query_one("select * from neon.get_prewarm_info()", &[])
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(row) => row,
|
Ok(row) => row,
|
||||||
@@ -83,25 +89,24 @@ impl ComputeNode {
|
|||||||
self.state.lock().unwrap().lfc_offload_state.clone()
|
self.state.lock().unwrap().lfc_offload_state.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns false if there is a prewarm request ongoing, true otherwise
|
/// If there is a prewarm request ongoing, return false, true otherwise
|
||||||
pub fn prewarm_lfc(self: &Arc<Self>) -> bool {
|
pub fn prewarm_lfc(self: &Arc<Self>, from_endpoint: Option<String>) -> bool {
|
||||||
crate::metrics::LFC_PREWARM_REQUESTS.inc();
|
|
||||||
{
|
{
|
||||||
let state = &mut self.state.lock().unwrap().lfc_prewarm_state;
|
let state = &mut self.state.lock().unwrap().lfc_prewarm_state;
|
||||||
if let LfcPrewarmState::Prewarming =
|
if let LfcPrewarmState::Prewarming = replace(state, LfcPrewarmState::Prewarming) {
|
||||||
std::mem::replace(state, LfcPrewarmState::Prewarming)
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
crate::metrics::LFC_PREWARMS.inc();
|
||||||
|
|
||||||
let cloned = self.clone();
|
let cloned = self.clone();
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let Err(err) = cloned.prewarm_impl().await else {
|
let Err(err) = cloned.prewarm_impl(from_endpoint).await else {
|
||||||
cloned.state.lock().unwrap().lfc_prewarm_state = LfcPrewarmState::Completed;
|
cloned.state.lock().unwrap().lfc_prewarm_state = LfcPrewarmState::Completed;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
error!(%err);
|
crate::metrics::LFC_PREWARM_ERRORS.inc();
|
||||||
|
error!(%err, "prewarming lfc");
|
||||||
cloned.state.lock().unwrap().lfc_prewarm_state = LfcPrewarmState::Failed {
|
cloned.state.lock().unwrap().lfc_prewarm_state = LfcPrewarmState::Failed {
|
||||||
error: err.to_string(),
|
error: err.to_string(),
|
||||||
};
|
};
|
||||||
@@ -109,13 +114,14 @@ impl ComputeNode {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn endpoint_storage_pair(&self) -> Result<EndpointStoragePair> {
|
/// from_endpoint: None for endpoint managed by this compute_ctl
|
||||||
|
fn endpoint_storage_pair(&self, from_endpoint: Option<String>) -> Result<EndpointStoragePair> {
|
||||||
let state = self.state.lock().unwrap();
|
let state = self.state.lock().unwrap();
|
||||||
state.pspec.as_ref().unwrap().try_into()
|
EndpointStoragePair::from_spec_and_endpoint(state.pspec.as_ref().unwrap(), from_endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prewarm_impl(&self) -> Result<()> {
|
async fn prewarm_impl(&self, from_endpoint: Option<String>) -> Result<()> {
|
||||||
let EndpointStoragePair { url, token } = self.endpoint_storage_pair()?;
|
let EndpointStoragePair { url, token } = self.endpoint_storage_pair(from_endpoint)?;
|
||||||
info!(%url, "requesting LFC state from endpoint storage");
|
info!(%url, "requesting LFC state from endpoint storage");
|
||||||
|
|
||||||
let request = Client::new().get(&url).bearer_auth(token);
|
let request = Client::new().get(&url).bearer_auth(token);
|
||||||
@@ -140,47 +146,57 @@ impl ComputeNode {
|
|||||||
ComputeNode::get_maintenance_client(&self.tokio_conn_conf)
|
ComputeNode::get_maintenance_client(&self.tokio_conn_conf)
|
||||||
.await
|
.await
|
||||||
.context("connecting to postgres")?
|
.context("connecting to postgres")?
|
||||||
.query_one("select prewarm_local_cache($1)", &[&uncompressed])
|
.query_one("select neon.prewarm_local_cache($1)", &[&uncompressed])
|
||||||
.await
|
.await
|
||||||
.context("loading LFC state into postgres")
|
.context("loading LFC state into postgres")
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns false if there is an offload request ongoing, true otherwise
|
/// If offload request is ongoing, return false, true otherwise
|
||||||
pub fn offload_lfc(self: &Arc<Self>) -> bool {
|
pub fn offload_lfc(self: &Arc<Self>) -> bool {
|
||||||
crate::metrics::LFC_OFFLOAD_REQUESTS.inc();
|
|
||||||
{
|
{
|
||||||
let state = &mut self.state.lock().unwrap().lfc_offload_state;
|
let state = &mut self.state.lock().unwrap().lfc_offload_state;
|
||||||
if let LfcOffloadState::Offloading =
|
if replace(state, LfcOffloadState::Offloading) == LfcOffloadState::Offloading {
|
||||||
std::mem::replace(state, LfcOffloadState::Offloading)
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let cloned = self.clone();
|
let cloned = self.clone();
|
||||||
spawn(async move {
|
spawn(async move { cloned.offload_lfc_with_state_update().await });
|
||||||
let Err(err) = cloned.offload_lfc_impl().await else {
|
|
||||||
cloned.state.lock().unwrap().lfc_offload_state = LfcOffloadState::Completed;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
error!(%err);
|
|
||||||
cloned.state.lock().unwrap().lfc_offload_state = LfcOffloadState::Failed {
|
|
||||||
error: err.to_string(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn offload_lfc_async(self: &Arc<Self>) {
|
||||||
|
{
|
||||||
|
let state = &mut self.state.lock().unwrap().lfc_offload_state;
|
||||||
|
if replace(state, LfcOffloadState::Offloading) == LfcOffloadState::Offloading {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.offload_lfc_with_state_update().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn offload_lfc_with_state_update(&self) {
|
||||||
|
crate::metrics::LFC_OFFLOADS.inc();
|
||||||
|
let Err(err) = self.offload_lfc_impl().await else {
|
||||||
|
self.state.lock().unwrap().lfc_offload_state = LfcOffloadState::Completed;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
crate::metrics::LFC_OFFLOAD_ERRORS.inc();
|
||||||
|
error!(%err, "offloading lfc");
|
||||||
|
self.state.lock().unwrap().lfc_offload_state = LfcOffloadState::Failed {
|
||||||
|
error: err.to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async fn offload_lfc_impl(&self) -> Result<()> {
|
async fn offload_lfc_impl(&self) -> Result<()> {
|
||||||
let EndpointStoragePair { url, token } = self.endpoint_storage_pair()?;
|
let EndpointStoragePair { url, token } = self.endpoint_storage_pair(None)?;
|
||||||
info!(%url, "requesting LFC state from postgres");
|
info!(%url, "requesting LFC state from postgres");
|
||||||
|
|
||||||
let mut compressed = Vec::new();
|
let mut compressed = Vec::new();
|
||||||
ComputeNode::get_maintenance_client(&self.tokio_conn_conf)
|
ComputeNode::get_maintenance_client(&self.tokio_conn_conf)
|
||||||
.await
|
.await
|
||||||
.context("connecting to postgres")?
|
.context("connecting to postgres")?
|
||||||
.query_one("select get_local_cache_state()", &[])
|
.query_one("select neon.get_local_cache_state()", &[])
|
||||||
.await
|
.await
|
||||||
.context("querying LFC state")?
|
.context("querying LFC state")?
|
||||||
.try_get::<usize, &[u8]>(0)
|
.try_get::<usize, &[u8]>(0)
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
use crate::compute::ComputeNode;
|
||||||
|
use anyhow::{Context, Result, bail};
|
||||||
|
use compute_api::{
|
||||||
|
responses::{LfcPrewarmState, PromoteState, SafekeepersLsn},
|
||||||
|
spec::ComputeMode,
|
||||||
|
};
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
use tokio::time::sleep;
|
||||||
|
use utils::lsn::Lsn;
|
||||||
|
|
||||||
|
impl ComputeNode {
|
||||||
|
/// Returns only when promote fails or succeeds. If a network error occurs
|
||||||
|
/// and http client disconnects, this does not stop promotion, and subsequent
|
||||||
|
/// calls block until promote finishes.
|
||||||
|
/// Called by control plane on secondary after primary endpoint is terminated
|
||||||
|
pub async fn promote(self: &Arc<Self>, safekeepers_lsn: SafekeepersLsn) -> PromoteState {
|
||||||
|
let cloned = self.clone();
|
||||||
|
let start_promotion = || {
|
||||||
|
let (tx, rx) = tokio::sync::watch::channel(PromoteState::NotPromoted);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
tx.send(match cloned.promote_impl(safekeepers_lsn).await {
|
||||||
|
Ok(_) => PromoteState::Completed,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!(%err, "promoting");
|
||||||
|
PromoteState::Failed {
|
||||||
|
error: err.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
rx
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut task;
|
||||||
|
// self.state is unlocked after block ends so we lock it in promote_impl
|
||||||
|
// and task.changed() is reached
|
||||||
|
{
|
||||||
|
task = self
|
||||||
|
.state
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.promote_state
|
||||||
|
.get_or_insert_with(start_promotion)
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
task.changed().await.expect("promote sender dropped");
|
||||||
|
task.borrow().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Why do we have to supply safekeepers?
|
||||||
|
// For secondary we use primary_connection_conninfo so safekeepers field is empty
|
||||||
|
async fn promote_impl(&self, safekeepers_lsn: SafekeepersLsn) -> Result<()> {
|
||||||
|
{
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
let mode = &state.pspec.as_ref().unwrap().spec.mode;
|
||||||
|
if *mode != ComputeMode::Replica {
|
||||||
|
bail!("{} is not replica", mode.to_type_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't need to query Postgres so not self.lfc_prewarm_state()
|
||||||
|
match &state.lfc_prewarm_state {
|
||||||
|
LfcPrewarmState::NotPrewarmed | LfcPrewarmState::Prewarming => {
|
||||||
|
bail!("prewarm not requested or pending")
|
||||||
|
}
|
||||||
|
LfcPrewarmState::Failed { error } => {
|
||||||
|
tracing::warn!(%error, "replica prewarm failed")
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = ComputeNode::get_maintenance_client(&self.tokio_conn_conf)
|
||||||
|
.await
|
||||||
|
.context("connecting to postgres")?;
|
||||||
|
|
||||||
|
let primary_lsn = safekeepers_lsn.wal_flush_lsn;
|
||||||
|
let mut last_wal_replay_lsn: Lsn = Lsn::INVALID;
|
||||||
|
const RETRIES: i32 = 20;
|
||||||
|
for i in 0..=RETRIES {
|
||||||
|
let row = client
|
||||||
|
.query_one("SELECT pg_last_wal_replay_lsn()", &[])
|
||||||
|
.await
|
||||||
|
.context("getting last replay lsn")?;
|
||||||
|
let lsn: u64 = row.get::<usize, postgres_types::PgLsn>(0).into();
|
||||||
|
last_wal_replay_lsn = lsn.into();
|
||||||
|
if last_wal_replay_lsn >= primary_lsn {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tracing::info!("Try {i}, replica lsn {last_wal_replay_lsn}, primary lsn {primary_lsn}");
|
||||||
|
sleep(Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
if last_wal_replay_lsn < primary_lsn {
|
||||||
|
bail!("didn't catch up with primary in {RETRIES} retries");
|
||||||
|
}
|
||||||
|
|
||||||
|
// using $1 doesn't work with ALTER SYSTEM SET
|
||||||
|
let safekeepers_sql = format!(
|
||||||
|
"ALTER SYSTEM SET neon.safekeepers='{}'",
|
||||||
|
safekeepers_lsn.safekeepers
|
||||||
|
);
|
||||||
|
client
|
||||||
|
.query(&safekeepers_sql, &[])
|
||||||
|
.await
|
||||||
|
.context("setting safekeepers")?;
|
||||||
|
client
|
||||||
|
.query("SELECT pg_reload_conf()", &[])
|
||||||
|
.await
|
||||||
|
.context("reloading postgres config")?;
|
||||||
|
let row = client
|
||||||
|
.query_one("SELECT * FROM pg_promote()", &[])
|
||||||
|
.await
|
||||||
|
.context("pg_promote")?;
|
||||||
|
if !row.get::<usize, bool>(0) {
|
||||||
|
bail!("pg_promote() returned false");
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = ComputeNode::get_maintenance_client(&self.tokio_conn_conf)
|
||||||
|
.await
|
||||||
|
.context("connecting to postgres")?;
|
||||||
|
let row = client
|
||||||
|
.query_one("SHOW transaction_read_only", &[])
|
||||||
|
.await
|
||||||
|
.context("getting transaction_read_only")?;
|
||||||
|
if row.get::<usize, &str>(0) == "on" {
|
||||||
|
bail!("replica in read only mode after promotion");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
state.pspec.as_mut().unwrap().spec.mode = ComputeMode::Primary;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,7 +51,7 @@ pub fn write_postgres_conf(
|
|||||||
|
|
||||||
// Write the postgresql.conf content from the spec file as is.
|
// Write the postgresql.conf content from the spec file as is.
|
||||||
if let Some(conf) = &spec.cluster.postgresql_conf {
|
if let Some(conf) = &spec.cluster.postgresql_conf {
|
||||||
writeln!(file, "{}", conf)?;
|
writeln!(file, "{conf}")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add options for connecting to storage
|
// Add options for connecting to storage
|
||||||
@@ -70,7 +70,7 @@ pub fn write_postgres_conf(
|
|||||||
);
|
);
|
||||||
// If generation is given, prepend sk list with g#number:
|
// If generation is given, prepend sk list with g#number:
|
||||||
if let Some(generation) = spec.safekeepers_generation {
|
if let Some(generation) = spec.safekeepers_generation {
|
||||||
write!(neon_safekeepers_value, "g#{}:", generation)?;
|
write!(neon_safekeepers_value, "g#{generation}:")?;
|
||||||
}
|
}
|
||||||
neon_safekeepers_value.push_str(&spec.safekeeper_connstrings.join(","));
|
neon_safekeepers_value.push_str(&spec.safekeeper_connstrings.join(","));
|
||||||
writeln!(
|
writeln!(
|
||||||
@@ -109,8 +109,8 @@ pub fn write_postgres_conf(
|
|||||||
tls::update_key_path_blocking(pgdata_path, tls_config);
|
tls::update_key_path_blocking(pgdata_path, tls_config);
|
||||||
|
|
||||||
// these are the default, but good to be explicit.
|
// these are the default, but good to be explicit.
|
||||||
writeln!(file, "ssl_cert_file = '{}'", SERVER_CRT)?;
|
writeln!(file, "ssl_cert_file = '{SERVER_CRT}'")?;
|
||||||
writeln!(file, "ssl_key_file = '{}'", SERVER_KEY)?;
|
writeln!(file, "ssl_key_file = '{SERVER_KEY}'")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locales
|
// Locales
|
||||||
@@ -191,8 +191,7 @@ pub fn write_postgres_conf(
|
|||||||
}
|
}
|
||||||
writeln!(
|
writeln!(
|
||||||
file,
|
file,
|
||||||
"shared_preload_libraries='{}{}'",
|
"shared_preload_libraries='{libs}{extra_shared_preload_libraries}'"
|
||||||
libs, extra_shared_preload_libraries
|
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
// Typically, this should be unreacheable,
|
// Typically, this should be unreacheable,
|
||||||
@@ -244,8 +243,7 @@ pub fn write_postgres_conf(
|
|||||||
}
|
}
|
||||||
writeln!(
|
writeln!(
|
||||||
file,
|
file,
|
||||||
"shared_preload_libraries='{}{}'",
|
"shared_preload_libraries='{libs}{extra_shared_preload_libraries}'"
|
||||||
libs, extra_shared_preload_libraries
|
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
// Typically, this should be unreacheable,
|
// Typically, this should be unreacheable,
|
||||||
@@ -263,7 +261,7 @@ pub fn write_postgres_conf(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(file, "neon.extension_server_port={}", extension_server_port)?;
|
writeln!(file, "neon.extension_server_port={extension_server_port}")?;
|
||||||
|
|
||||||
if spec.drop_subscriptions_before_start {
|
if spec.drop_subscriptions_before_start {
|
||||||
writeln!(file, "neon.disable_logical_replication_subscribers=true")?;
|
writeln!(file, "neon.disable_logical_replication_subscribers=true")?;
|
||||||
@@ -291,7 +289,7 @@ where
|
|||||||
{
|
{
|
||||||
let path = pgdata_path.join("compute_ctl_temp_override.conf");
|
let path = pgdata_path.join("compute_ctl_temp_override.conf");
|
||||||
let mut file = File::create(path)?;
|
let mut file = File::create(path)?;
|
||||||
write!(file, "{}", options)?;
|
write!(file, "{options}")?;
|
||||||
|
|
||||||
let res = exec();
|
let res = exec();
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,13 @@ input(type="imfile" File="{log_directory}/*.log"
|
|||||||
startmsg.regex="^[[:digit:]]{{4}}-[[:digit:]]{{2}}-[[:digit:]]{{2}} [[:digit:]]{{2}}:[[:digit:]]{{2}}:[[:digit:]]{{2}}.[[:digit:]]{{3}} GMT,")
|
startmsg.regex="^[[:digit:]]{{4}}-[[:digit:]]{{2}}-[[:digit:]]{{2}} [[:digit:]]{{2}}:[[:digit:]]{{2}}:[[:digit:]]{{2}}.[[:digit:]]{{3}} GMT,")
|
||||||
|
|
||||||
# the directory to store rsyslog state files
|
# the directory to store rsyslog state files
|
||||||
global(workDirectory="/var/log/rsyslog")
|
global(
|
||||||
|
workDirectory="/var/log/rsyslog"
|
||||||
|
DefaultNetstreamDriverCAFile="/etc/ssl/certs/ca-certificates.crt"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Whether the remote syslog receiver uses tls
|
||||||
|
set $.remote_syslog_tls = "{remote_syslog_tls}";
|
||||||
|
|
||||||
# Construct json, endpoint_id and project_id as additional metadata
|
# Construct json, endpoint_id and project_id as additional metadata
|
||||||
set $.json_log!endpoint_id = "{endpoint_id}";
|
set $.json_log!endpoint_id = "{endpoint_id}";
|
||||||
@@ -21,5 +27,29 @@ set $.json_log!msg = $msg;
|
|||||||
template(name="PgAuditLog" type="string"
|
template(name="PgAuditLog" type="string"
|
||||||
string="<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% - - - - %$.json_log%")
|
string="<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% - - - - %$.json_log%")
|
||||||
|
|
||||||
# Forward to remote syslog receiver (@@<hostname>:<port>;format
|
# Forward to remote syslog receiver (over TLS)
|
||||||
local5.info @@{remote_endpoint};PgAuditLog
|
if ( $syslogtag == 'pgaudit_log' ) then {{
|
||||||
|
if ( $.remote_syslog_tls == 'true' ) then {{
|
||||||
|
action(type="omfwd" target="{remote_syslog_host}" port="{remote_syslog_port}" protocol="tcp"
|
||||||
|
template="PgAuditLog"
|
||||||
|
queue.type="linkedList"
|
||||||
|
queue.size="1000"
|
||||||
|
action.ResumeRetryCount="10"
|
||||||
|
StreamDriver="gtls"
|
||||||
|
StreamDriverMode="1"
|
||||||
|
StreamDriverAuthMode="x509/name"
|
||||||
|
StreamDriverPermittedPeers="{remote_syslog_host}"
|
||||||
|
StreamDriver.CheckExtendedKeyPurpose="on"
|
||||||
|
StreamDriver.PermitExpiredCerts="off"
|
||||||
|
)
|
||||||
|
stop
|
||||||
|
}} else {{
|
||||||
|
action(type="omfwd" target="{remote_syslog_host}" port="{remote_syslog_port}" protocol="tcp"
|
||||||
|
template="PgAuditLog"
|
||||||
|
queue.type="linkedList"
|
||||||
|
queue.size="1000"
|
||||||
|
action.ResumeRetryCount="10"
|
||||||
|
)
|
||||||
|
stop
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|||||||
@@ -74,9 +74,11 @@ More specifically, here is an example ext_index.json
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
|
use crate::metrics::{REMOTE_EXT_REQUESTS_TOTAL, UNKNOWN_HTTP_STATUS};
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use compute_api::spec::RemoteExtSpec;
|
use compute_api::spec::RemoteExtSpec;
|
||||||
|
use postgres_versioninfo::PgMajorVersion;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use remote_storage::*;
|
use remote_storage::*;
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
@@ -86,8 +88,6 @@ use tracing::log::warn;
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
use zstd::stream::read::Decoder;
|
use zstd::stream::read::Decoder;
|
||||||
|
|
||||||
use crate::metrics::{REMOTE_EXT_REQUESTS_TOTAL, UNKNOWN_HTTP_STATUS};
|
|
||||||
|
|
||||||
fn get_pg_config(argument: &str, pgbin: &str) -> String {
|
fn get_pg_config(argument: &str, pgbin: &str) -> String {
|
||||||
// gives the result of `pg_config [argument]`
|
// gives the result of `pg_config [argument]`
|
||||||
// where argument is a flag like `--version` or `--sharedir`
|
// where argument is a flag like `--version` or `--sharedir`
|
||||||
@@ -106,7 +106,7 @@ fn get_pg_config(argument: &str, pgbin: &str) -> String {
|
|||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_pg_version(pgbin: &str) -> PostgresMajorVersion {
|
pub fn get_pg_version(pgbin: &str) -> PgMajorVersion {
|
||||||
// pg_config --version returns a (platform specific) human readable string
|
// pg_config --version returns a (platform specific) human readable string
|
||||||
// such as "PostgreSQL 15.4". We parse this to v14/v15/v16 etc.
|
// such as "PostgreSQL 15.4". We parse this to v14/v15/v16 etc.
|
||||||
let human_version = get_pg_config("--version", pgbin);
|
let human_version = get_pg_config("--version", pgbin);
|
||||||
@@ -114,25 +114,11 @@ pub fn get_pg_version(pgbin: &str) -> PostgresMajorVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_pg_version_string(pgbin: &str) -> String {
|
pub fn get_pg_version_string(pgbin: &str) -> String {
|
||||||
match get_pg_version(pgbin) {
|
get_pg_version(pgbin).v_str()
|
||||||
PostgresMajorVersion::V14 => "v14",
|
|
||||||
PostgresMajorVersion::V15 => "v15",
|
|
||||||
PostgresMajorVersion::V16 => "v16",
|
|
||||||
PostgresMajorVersion::V17 => "v17",
|
|
||||||
}
|
|
||||||
.to_owned()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
fn parse_pg_version(human_version: &str) -> PgMajorVersion {
|
||||||
pub enum PostgresMajorVersion {
|
use PgMajorVersion::*;
|
||||||
V14,
|
|
||||||
V15,
|
|
||||||
V16,
|
|
||||||
V17,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_pg_version(human_version: &str) -> PostgresMajorVersion {
|
|
||||||
use PostgresMajorVersion::*;
|
|
||||||
// Normal releases have version strings like "PostgreSQL 15.4". But there
|
// Normal releases have version strings like "PostgreSQL 15.4". But there
|
||||||
// are also pre-release versions like "PostgreSQL 17devel" or "PostgreSQL
|
// are also pre-release versions like "PostgreSQL 17devel" or "PostgreSQL
|
||||||
// 16beta2" or "PostgreSQL 17rc1". And with the --with-extra-version
|
// 16beta2" or "PostgreSQL 17rc1". And with the --with-extra-version
|
||||||
@@ -143,10 +129,10 @@ fn parse_pg_version(human_version: &str) -> PostgresMajorVersion {
|
|||||||
.captures(human_version)
|
.captures(human_version)
|
||||||
{
|
{
|
||||||
Some(captures) if captures.len() == 2 => match &captures["major"] {
|
Some(captures) if captures.len() == 2 => match &captures["major"] {
|
||||||
"14" => return V14,
|
"14" => return PG14,
|
||||||
"15" => return V15,
|
"15" => return PG15,
|
||||||
"16" => return V16,
|
"16" => return PG16,
|
||||||
"17" => return V17,
|
"17" => return PG17,
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -310,10 +296,7 @@ async fn download_extension_tar(remote_ext_base_url: &Url, ext_path: &str) -> Re
|
|||||||
async fn do_extension_server_request(uri: Url) -> Result<Bytes, (String, String)> {
|
async fn do_extension_server_request(uri: Url) -> Result<Bytes, (String, String)> {
|
||||||
let resp = reqwest::get(uri).await.map_err(|e| {
|
let resp = reqwest::get(uri).await.map_err(|e| {
|
||||||
(
|
(
|
||||||
format!(
|
format!("could not perform remote extensions server request: {e:?}"),
|
||||||
"could not perform remote extensions server request: {:?}",
|
|
||||||
e
|
|
||||||
),
|
|
||||||
UNKNOWN_HTTP_STATUS.to_string(),
|
UNKNOWN_HTTP_STATUS.to_string(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
@@ -323,7 +306,7 @@ async fn do_extension_server_request(uri: Url) -> Result<Bytes, (String, String)
|
|||||||
StatusCode::OK => match resp.bytes().await {
|
StatusCode::OK => match resp.bytes().await {
|
||||||
Ok(resp) => Ok(resp),
|
Ok(resp) => Ok(resp),
|
||||||
Err(e) => Err((
|
Err(e) => Err((
|
||||||
format!("could not read remote extensions server response: {:?}", e),
|
format!("could not read remote extensions server response: {e:?}"),
|
||||||
// It's fine to return and report error with status as 200 OK,
|
// It's fine to return and report error with status as 200 OK,
|
||||||
// because we still failed to read the response.
|
// because we still failed to read the response.
|
||||||
status.to_string(),
|
status.to_string(),
|
||||||
@@ -334,10 +317,7 @@ async fn do_extension_server_request(uri: Url) -> Result<Bytes, (String, String)
|
|||||||
status.to_string(),
|
status.to_string(),
|
||||||
)),
|
)),
|
||||||
_ => Err((
|
_ => Err((
|
||||||
format!(
|
format!("unexpected remote extensions server response status code: {status}"),
|
||||||
"unexpected remote extensions server response status code: {}",
|
|
||||||
status
|
|
||||||
),
|
|
||||||
status.to_string(),
|
status.to_string(),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
@@ -349,25 +329,25 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_pg_version() {
|
fn test_parse_pg_version() {
|
||||||
use super::PostgresMajorVersion::*;
|
use postgres_versioninfo::PgMajorVersion::*;
|
||||||
assert_eq!(parse_pg_version("PostgreSQL 15.4"), V15);
|
assert_eq!(parse_pg_version("PostgreSQL 15.4"), PG15);
|
||||||
assert_eq!(parse_pg_version("PostgreSQL 15.14"), V15);
|
assert_eq!(parse_pg_version("PostgreSQL 15.14"), PG15);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_pg_version("PostgreSQL 15.4 (Ubuntu 15.4-0ubuntu0.23.04.1)"),
|
parse_pg_version("PostgreSQL 15.4 (Ubuntu 15.4-0ubuntu0.23.04.1)"),
|
||||||
V15
|
PG15
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(parse_pg_version("PostgreSQL 14.15"), V14);
|
assert_eq!(parse_pg_version("PostgreSQL 14.15"), PG14);
|
||||||
assert_eq!(parse_pg_version("PostgreSQL 14.0"), V14);
|
assert_eq!(parse_pg_version("PostgreSQL 14.0"), PG14);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_pg_version("PostgreSQL 14.9 (Debian 14.9-1.pgdg120+1"),
|
parse_pg_version("PostgreSQL 14.9 (Debian 14.9-1.pgdg120+1"),
|
||||||
V14
|
PG14
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(parse_pg_version("PostgreSQL 16devel"), V16);
|
assert_eq!(parse_pg_version("PostgreSQL 16devel"), PG16);
|
||||||
assert_eq!(parse_pg_version("PostgreSQL 16beta1"), V16);
|
assert_eq!(parse_pg_version("PostgreSQL 16beta1"), PG16);
|
||||||
assert_eq!(parse_pg_version("PostgreSQL 16rc2"), V16);
|
assert_eq!(parse_pg_version("PostgreSQL 16rc2"), PG16);
|
||||||
assert_eq!(parse_pg_version("PostgreSQL 16extra"), V16);
|
assert_eq!(parse_pg_version("PostgreSQL 16extra"), PG16);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -83,6 +83,87 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/DbsAndRoles"
|
$ref: "#/components/schemas/DbsAndRoles"
|
||||||
|
|
||||||
|
/promote:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Promotion
|
||||||
|
summary: Promote secondary replica to primary
|
||||||
|
description: ""
|
||||||
|
operationId: promoteReplica
|
||||||
|
requestBody:
|
||||||
|
description: Promote requests data
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/SafekeepersLsn"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Promote succeeded or wasn't started
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/PromoteState"
|
||||||
|
500:
|
||||||
|
description: Promote failed
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/PromoteState"
|
||||||
|
|
||||||
|
/lfc/prewarm:
|
||||||
|
post:
|
||||||
|
summary: Request LFC Prewarm
|
||||||
|
parameters:
|
||||||
|
- name: from_endpoint
|
||||||
|
in: query
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: ""
|
||||||
|
operationId: lfcPrewarm
|
||||||
|
responses:
|
||||||
|
202:
|
||||||
|
description: LFC prewarm started
|
||||||
|
429:
|
||||||
|
description: LFC prewarm ongoing
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Prewarm
|
||||||
|
summary: Get LFC prewarm state
|
||||||
|
description: ""
|
||||||
|
operationId: getLfcPrewarmState
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Prewarm state
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/LfcPrewarmState"
|
||||||
|
|
||||||
|
/lfc/offload:
|
||||||
|
post:
|
||||||
|
summary: Request LFC offload
|
||||||
|
description: ""
|
||||||
|
operationId: lfcOffload
|
||||||
|
responses:
|
||||||
|
202:
|
||||||
|
description: LFC offload started
|
||||||
|
429:
|
||||||
|
description: LFC offload ongoing
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Prewarm
|
||||||
|
summary: Get LFC offloading state
|
||||||
|
description: ""
|
||||||
|
operationId: getLfcOffloadState
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Offload state
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/LfcOffloadState"
|
||||||
|
|
||||||
/database_schema:
|
/database_schema:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -290,9 +371,28 @@ paths:
|
|||||||
summary: Terminate Postgres and wait for it to exit
|
summary: Terminate Postgres and wait for it to exit
|
||||||
description: ""
|
description: ""
|
||||||
operationId: terminate
|
operationId: terminate
|
||||||
|
parameters:
|
||||||
|
- name: mode
|
||||||
|
in: query
|
||||||
|
description: "Terminate mode: fast (wait 30s before returning) and immediate"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum: ["fast", "immediate"]
|
||||||
|
default: fast
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: Result
|
description: Result
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/TerminateResponse"
|
||||||
|
201:
|
||||||
|
description: Result if compute is already terminated
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/TerminateResponse"
|
||||||
412:
|
412:
|
||||||
description: "wrong state"
|
description: "wrong state"
|
||||||
content:
|
content:
|
||||||
@@ -335,15 +435,6 @@ components:
|
|||||||
total_startup_ms:
|
total_startup_ms:
|
||||||
type: integer
|
type: integer
|
||||||
|
|
||||||
Info:
|
|
||||||
type: object
|
|
||||||
description: Information about VM/Pod.
|
|
||||||
required:
|
|
||||||
- num_cpus
|
|
||||||
properties:
|
|
||||||
num_cpus:
|
|
||||||
type: integer
|
|
||||||
|
|
||||||
DbsAndRoles:
|
DbsAndRoles:
|
||||||
type: object
|
type: object
|
||||||
description: Databases and Roles
|
description: Databases and Roles
|
||||||
@@ -458,11 +549,14 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- empty
|
- empty
|
||||||
- init
|
|
||||||
- failed
|
|
||||||
- running
|
|
||||||
- configuration_pending
|
- configuration_pending
|
||||||
|
- init
|
||||||
|
- running
|
||||||
- configuration
|
- configuration
|
||||||
|
- failed
|
||||||
|
- termination_pending_fast
|
||||||
|
- termination_pending_immediate
|
||||||
|
- terminated
|
||||||
example: running
|
example: running
|
||||||
|
|
||||||
ExtensionInstallRequest:
|
ExtensionInstallRequest:
|
||||||
@@ -497,25 +591,69 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
example: "1.0.0"
|
example: "1.0.0"
|
||||||
|
|
||||||
InstalledExtensions:
|
SafekeepersLsn:
|
||||||
type: object
|
type: object
|
||||||
|
required:
|
||||||
|
- safekeepers
|
||||||
|
- wal_flush_lsn
|
||||||
properties:
|
properties:
|
||||||
extensions:
|
safekeepers:
|
||||||
description: Contains list of installed extensions.
|
description: Primary replica safekeepers
|
||||||
type: array
|
type: string
|
||||||
items:
|
wal_flush_lsn:
|
||||||
type: object
|
description: Primary last WAL flush LSN
|
||||||
properties:
|
type: string
|
||||||
extname:
|
|
||||||
type: string
|
LfcPrewarmState:
|
||||||
version:
|
type: object
|
||||||
type: string
|
required:
|
||||||
items:
|
- status
|
||||||
type: string
|
- total
|
||||||
n_databases:
|
- prewarmed
|
||||||
type: integer
|
- skipped
|
||||||
owned_by_superuser:
|
properties:
|
||||||
type: integer
|
status:
|
||||||
|
description: Lfc prewarm status
|
||||||
|
enum: [not_prewarmed, prewarming, completed, failed]
|
||||||
|
type: string
|
||||||
|
error:
|
||||||
|
description: Lfc prewarm error, if any
|
||||||
|
type: string
|
||||||
|
total:
|
||||||
|
description: Total pages processed
|
||||||
|
type: integer
|
||||||
|
prewarmed:
|
||||||
|
description: Total pages prewarmed
|
||||||
|
type: integer
|
||||||
|
skipped:
|
||||||
|
description: Pages processed but not prewarmed
|
||||||
|
type: integer
|
||||||
|
|
||||||
|
LfcOffloadState:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
description: Lfc offload status
|
||||||
|
enum: [not_offloaded, offloading, completed, failed]
|
||||||
|
type: string
|
||||||
|
error:
|
||||||
|
description: Lfc offload error, if any
|
||||||
|
type: string
|
||||||
|
|
||||||
|
PromoteState:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
description: Promote result
|
||||||
|
enum: [not_promoted, completed, failed]
|
||||||
|
type: string
|
||||||
|
error:
|
||||||
|
description: Promote error, if any
|
||||||
|
type: string
|
||||||
|
|
||||||
SetRoleGrantsRequest:
|
SetRoleGrantsRequest:
|
||||||
type: object
|
type: object
|
||||||
@@ -544,6 +682,17 @@ components:
|
|||||||
description: Role name.
|
description: Role name.
|
||||||
example: "neon"
|
example: "neon"
|
||||||
|
|
||||||
|
TerminateResponse:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- lsn
|
||||||
|
properties:
|
||||||
|
lsn:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: "last WAL flush LSN"
|
||||||
|
example: "0/028F10D8"
|
||||||
|
|
||||||
SetRoleGrantsResponse:
|
SetRoleGrantsResponse:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ pub(in crate::http) async fn configure(
|
|||||||
|
|
||||||
if state.status == ComputeStatus::Failed {
|
if state.status == ComputeStatus::Failed {
|
||||||
let err = state.error.as_ref().map_or("unknown error", |x| x);
|
let err = state.error.as_ref().map_or("unknown error", |x| x);
|
||||||
let msg = format!("compute configuration failed: {:?}", err);
|
let msg = format!("compute configuration failed: {err:?}");
|
||||||
return Err(msg);
|
return Err(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::compute_prewarm::LfcPrewarmStateWithProgress;
|
|||||||
use crate::http::JsonResponse;
|
use crate::http::JsonResponse;
|
||||||
use axum::response::{IntoResponse, Response};
|
use axum::response::{IntoResponse, Response};
|
||||||
use axum::{Json, http::StatusCode};
|
use axum::{Json, http::StatusCode};
|
||||||
|
use axum_extra::extract::OptionalQuery;
|
||||||
use compute_api::responses::LfcOffloadState;
|
use compute_api::responses::LfcOffloadState;
|
||||||
type Compute = axum::extract::State<std::sync::Arc<crate::compute::ComputeNode>>;
|
type Compute = axum::extract::State<std::sync::Arc<crate::compute::ComputeNode>>;
|
||||||
|
|
||||||
@@ -16,8 +17,16 @@ pub(in crate::http) async fn offload_state(compute: Compute) -> Json<LfcOffloadS
|
|||||||
Json(compute.lfc_offload_state())
|
Json(compute.lfc_offload_state())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::http) async fn prewarm(compute: Compute) -> Response {
|
#[derive(serde::Deserialize)]
|
||||||
if compute.prewarm_lfc() {
|
pub struct PrewarmQuery {
|
||||||
|
pub from_endpoint: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::http) async fn prewarm(
|
||||||
|
compute: Compute,
|
||||||
|
OptionalQuery(query): OptionalQuery<PrewarmQuery>,
|
||||||
|
) -> Response {
|
||||||
|
if compute.prewarm_lfc(query.map(|q| q.from_endpoint)) {
|
||||||
StatusCode::ACCEPTED.into_response()
|
StatusCode::ACCEPTED.into_response()
|
||||||
} else {
|
} else {
|
||||||
JsonResponse::error(
|
JsonResponse::error(
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ pub(in crate::http) mod insights;
|
|||||||
pub(in crate::http) mod lfc;
|
pub(in crate::http) mod lfc;
|
||||||
pub(in crate::http) mod metrics;
|
pub(in crate::http) mod metrics;
|
||||||
pub(in crate::http) mod metrics_json;
|
pub(in crate::http) mod metrics_json;
|
||||||
|
pub(in crate::http) mod promote;
|
||||||
pub(in crate::http) mod status;
|
pub(in crate::http) mod status;
|
||||||
pub(in crate::http) mod terminate;
|
pub(in crate::http) mod terminate;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
use crate::http::JsonResponse;
|
||||||
|
use axum::Form;
|
||||||
|
use http::StatusCode;
|
||||||
|
|
||||||
|
pub(in crate::http) async fn promote(
|
||||||
|
compute: axum::extract::State<std::sync::Arc<crate::compute::ComputeNode>>,
|
||||||
|
Form(safekeepers_lsn): Form<compute_api::responses::SafekeepersLsn>,
|
||||||
|
) -> axum::response::Response {
|
||||||
|
let state = compute.promote(safekeepers_lsn).await;
|
||||||
|
if let compute_api::responses::PromoteState::Failed { error } = state {
|
||||||
|
return JsonResponse::error(StatusCode::INTERNAL_SERVER_ERROR, error);
|
||||||
|
}
|
||||||
|
JsonResponse::success(StatusCode::OK, state)
|
||||||
|
}
|
||||||
@@ -1,32 +1,42 @@
|
|||||||
use std::sync::Arc;
|
use crate::compute::{ComputeNode, forward_termination_signal};
|
||||||
|
use crate::http::JsonResponse;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::response::{IntoResponse, Response};
|
use axum::response::Response;
|
||||||
use compute_api::responses::ComputeStatus;
|
use axum_extra::extract::OptionalQuery;
|
||||||
|
use compute_api::responses::{ComputeStatus, TerminateMode, TerminateResponse};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::sync::Arc;
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::compute::{ComputeNode, forward_termination_signal};
|
#[derive(Deserialize, Default)]
|
||||||
use crate::http::JsonResponse;
|
pub struct TerminateQuery {
|
||||||
|
mode: TerminateMode,
|
||||||
|
}
|
||||||
|
|
||||||
/// Terminate the compute.
|
/// Terminate the compute.
|
||||||
pub(in crate::http) async fn terminate(State(compute): State<Arc<ComputeNode>>) -> Response {
|
pub(in crate::http) async fn terminate(
|
||||||
|
State(compute): State<Arc<ComputeNode>>,
|
||||||
|
OptionalQuery(terminate): OptionalQuery<TerminateQuery>,
|
||||||
|
) -> Response {
|
||||||
|
let mode = terminate.unwrap_or_default().mode;
|
||||||
{
|
{
|
||||||
let mut state = compute.state.lock().unwrap();
|
let mut state = compute.state.lock().unwrap();
|
||||||
if state.status == ComputeStatus::Terminated {
|
if state.status == ComputeStatus::Terminated {
|
||||||
return StatusCode::CREATED.into_response();
|
let response = TerminateResponse {
|
||||||
|
lsn: state.terminate_flush_lsn,
|
||||||
|
};
|
||||||
|
return JsonResponse::success(StatusCode::CREATED, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !matches!(state.status, ComputeStatus::Empty | ComputeStatus::Running) {
|
if !matches!(state.status, ComputeStatus::Empty | ComputeStatus::Running) {
|
||||||
return JsonResponse::invalid_status(state.status);
|
return JsonResponse::invalid_status(state.status);
|
||||||
}
|
}
|
||||||
|
state.set_status(mode.into(), &compute.state_changed);
|
||||||
state.set_status(ComputeStatus::TerminationPending, &compute.state_changed);
|
|
||||||
drop(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
forward_termination_signal();
|
forward_termination_signal(false);
|
||||||
info!("sent signal and notified waiters");
|
info!("sent signal and notified waiters");
|
||||||
|
|
||||||
// Spawn a blocking thread to wait for compute to become Terminated.
|
// Spawn a blocking thread to wait for compute to become Terminated.
|
||||||
@@ -34,7 +44,7 @@ pub(in crate::http) async fn terminate(State(compute): State<Arc<ComputeNode>>)
|
|||||||
// be able to serve other requests while some particular request
|
// be able to serve other requests while some particular request
|
||||||
// is waiting for compute to finish configuration.
|
// is waiting for compute to finish configuration.
|
||||||
let c = compute.clone();
|
let c = compute.clone();
|
||||||
task::spawn_blocking(move || {
|
let lsn = task::spawn_blocking(move || {
|
||||||
let mut state = c.state.lock().unwrap();
|
let mut state = c.state.lock().unwrap();
|
||||||
while state.status != ComputeStatus::Terminated {
|
while state.status != ComputeStatus::Terminated {
|
||||||
state = c.state_changed.wait(state).unwrap();
|
state = c.state_changed.wait(state).unwrap();
|
||||||
@@ -44,11 +54,10 @@ pub(in crate::http) async fn terminate(State(compute): State<Arc<ComputeNode>>)
|
|||||||
state.status
|
state.status
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
state.terminate_flush_lsn
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
info!("terminated Postgres");
|
info!("terminated Postgres");
|
||||||
|
JsonResponse::success(StatusCode::OK, TerminateResponse { lsn })
|
||||||
StatusCode::OK.into_response()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use super::{
|
|||||||
middleware::authorize::Authorize,
|
middleware::authorize::Authorize,
|
||||||
routes::{
|
routes::{
|
||||||
check_writability, configure, database_schema, dbs_and_roles, extension_server, extensions,
|
check_writability, configure, database_schema, dbs_and_roles, extension_server, extensions,
|
||||||
grants, insights, lfc, metrics, metrics_json, status, terminate,
|
grants, insights, lfc, metrics, metrics_json, promote, status, terminate,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::compute::ComputeNode;
|
use crate::compute::ComputeNode;
|
||||||
@@ -87,6 +87,7 @@ impl From<&Server> for Router<Arc<ComputeNode>> {
|
|||||||
let authenticated_router = Router::<Arc<ComputeNode>>::new()
|
let authenticated_router = Router::<Arc<ComputeNode>>::new()
|
||||||
.route("/lfc/prewarm", get(lfc::prewarm_state).post(lfc::prewarm))
|
.route("/lfc/prewarm", get(lfc::prewarm_state).post(lfc::prewarm))
|
||||||
.route("/lfc/offload", get(lfc::offload_state).post(lfc::offload))
|
.route("/lfc/offload", get(lfc::offload_state).post(lfc::offload))
|
||||||
|
.route("/promote", post(promote::promote))
|
||||||
.route("/check_writability", post(check_writability::is_writable))
|
.route("/check_writability", post(check_writability::is_writable))
|
||||||
.route("/configure", post(configure::configure))
|
.route("/configure", post(configure::configure))
|
||||||
.route("/database_schema", get(database_schema::get_schema_dump))
|
.route("/database_schema", get(database_schema::get_schema_dump))
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use compute_api::responses::{InstalledExtension, InstalledExtensions};
|
use compute_api::responses::{InstalledExtension, InstalledExtensions};
|
||||||
|
use tokio_postgres::error::Error as PostgresError;
|
||||||
use tokio_postgres::{Client, Config, NoTls};
|
use tokio_postgres::{Client, Config, NoTls};
|
||||||
|
|
||||||
use crate::metrics::INSTALLED_EXTENSIONS;
|
use crate::metrics::INSTALLED_EXTENSIONS;
|
||||||
@@ -10,7 +11,7 @@ use crate::metrics::INSTALLED_EXTENSIONS;
|
|||||||
/// and to make database listing query here more explicit.
|
/// and to make database listing query here more explicit.
|
||||||
///
|
///
|
||||||
/// Limit the number of databases to 500 to avoid excessive load.
|
/// Limit the number of databases to 500 to avoid excessive load.
|
||||||
async fn list_dbs(client: &mut Client) -> Result<Vec<String>> {
|
async fn list_dbs(client: &mut Client) -> Result<Vec<String>, PostgresError> {
|
||||||
// `pg_database.datconnlimit = -2` means that the database is in the
|
// `pg_database.datconnlimit = -2` means that the database is in the
|
||||||
// invalid state
|
// invalid state
|
||||||
let databases = client
|
let databases = client
|
||||||
@@ -37,13 +38,15 @@ async fn list_dbs(client: &mut Client) -> Result<Vec<String>> {
|
|||||||
/// Same extension can be installed in multiple databases with different versions,
|
/// Same extension can be installed in multiple databases with different versions,
|
||||||
/// so we report a separate metric (number of databases where it is installed)
|
/// so we report a separate metric (number of databases where it is installed)
|
||||||
/// for each extension version.
|
/// for each extension version.
|
||||||
pub async fn get_installed_extensions(mut conf: Config) -> Result<InstalledExtensions> {
|
pub async fn get_installed_extensions(
|
||||||
|
mut conf: Config,
|
||||||
|
) -> Result<InstalledExtensions, PostgresError> {
|
||||||
conf.application_name("compute_ctl:get_installed_extensions");
|
conf.application_name("compute_ctl:get_installed_extensions");
|
||||||
let databases: Vec<String> = {
|
let databases: Vec<String> = {
|
||||||
let (mut client, connection) = conf.connect(NoTls).await?;
|
let (mut client, connection) = conf.connect(NoTls).await?;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = connection.await {
|
if let Err(e) = connection.await {
|
||||||
eprintln!("connection error: {}", e);
|
eprintln!("connection error: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,7 +60,7 @@ pub async fn get_installed_extensions(mut conf: Config) -> Result<InstalledExten
|
|||||||
let (client, connection) = conf.connect(NoTls).await?;
|
let (client, connection) = conf.connect(NoTls).await?;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = connection.await {
|
if let Err(e) = connection.await {
|
||||||
eprintln!("connection error: {}", e);
|
eprintln!("connection error: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub mod logger;
|
|||||||
pub mod catalog;
|
pub mod catalog;
|
||||||
pub mod compute;
|
pub mod compute;
|
||||||
pub mod compute_prewarm;
|
pub mod compute_prewarm;
|
||||||
|
pub mod compute_promote;
|
||||||
pub mod disk_quota;
|
pub mod disk_quota;
|
||||||
pub mod extension_server;
|
pub mod extension_server;
|
||||||
pub mod installed_extensions;
|
pub mod installed_extensions;
|
||||||
@@ -22,6 +23,7 @@ mod migration;
|
|||||||
pub mod monitor;
|
pub mod monitor;
|
||||||
pub mod params;
|
pub mod params;
|
||||||
pub mod pg_helpers;
|
pub mod pg_helpers;
|
||||||
|
pub mod pgbouncer;
|
||||||
pub mod rsyslog;
|
pub mod rsyslog;
|
||||||
pub mod spec;
|
pub mod spec;
|
||||||
mod spec_apply;
|
mod spec_apply;
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ use std::thread;
|
|||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use compute_api::spec::ComputeMode;
|
use compute_api::spec::{ComputeMode, PageserverProtocol};
|
||||||
|
use itertools::Itertools as _;
|
||||||
|
use pageserver_page_api as page_api;
|
||||||
use postgres::{NoTls, SimpleQueryMessage};
|
use postgres::{NoTls, SimpleQueryMessage};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
use utils::id::{TenantId, TimelineId};
|
use utils::id::{TenantId, TimelineId};
|
||||||
@@ -76,25 +78,17 @@ fn acquire_lsn_lease_with_retry(
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Note: List of pageservers is dynamic, need to re-read configs before each attempt.
|
// Note: List of pageservers is dynamic, need to re-read configs before each attempt.
|
||||||
let configs = {
|
let (connstrings, auth) = {
|
||||||
let state = compute.state.lock().unwrap();
|
let state = compute.state.lock().unwrap();
|
||||||
|
|
||||||
let spec = state.pspec.as_ref().expect("spec must be set");
|
let spec = state.pspec.as_ref().expect("spec must be set");
|
||||||
|
(
|
||||||
let conn_strings = spec.pageserver_connstr.split(',');
|
spec.pageserver_connstr.clone(),
|
||||||
|
spec.storage_auth_token.clone(),
|
||||||
conn_strings
|
)
|
||||||
.map(|connstr| {
|
|
||||||
let mut config = postgres::Config::from_str(connstr).expect("Invalid connstr");
|
|
||||||
if let Some(storage_auth_token) = &spec.storage_auth_token {
|
|
||||||
config.password(storage_auth_token.clone());
|
|
||||||
}
|
|
||||||
config
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = try_acquire_lsn_lease(tenant_id, timeline_id, lsn, &configs);
|
let result =
|
||||||
|
try_acquire_lsn_lease(&connstrings, auth.as_deref(), tenant_id, timeline_id, lsn);
|
||||||
match result {
|
match result {
|
||||||
Ok(Some(res)) => {
|
Ok(Some(res)) => {
|
||||||
return Ok(res);
|
return Ok(res);
|
||||||
@@ -116,68 +110,104 @@ fn acquire_lsn_lease_with_retry(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to acquire an LSN lease through PS page_service API.
|
/// Tries to acquire LSN leases on all Pageserver shards.
|
||||||
fn try_acquire_lsn_lease(
|
fn try_acquire_lsn_lease(
|
||||||
|
connstrings: &str,
|
||||||
|
auth: Option<&str>,
|
||||||
tenant_id: TenantId,
|
tenant_id: TenantId,
|
||||||
timeline_id: TimelineId,
|
timeline_id: TimelineId,
|
||||||
lsn: Lsn,
|
lsn: Lsn,
|
||||||
configs: &[postgres::Config],
|
|
||||||
) -> Result<Option<SystemTime>> {
|
) -> Result<Option<SystemTime>> {
|
||||||
fn get_valid_until(
|
let connstrings = connstrings.split(',').collect_vec();
|
||||||
config: &postgres::Config,
|
let shard_count = connstrings.len();
|
||||||
tenant_shard_id: TenantShardId,
|
let mut leases = Vec::new();
|
||||||
timeline_id: TimelineId,
|
|
||||||
lsn: Lsn,
|
for (shard_number, &connstring) in connstrings.iter().enumerate() {
|
||||||
) -> Result<Option<SystemTime>> {
|
let tenant_shard_id = match shard_count {
|
||||||
let mut client = config.connect(NoTls)?;
|
0 | 1 => TenantShardId::unsharded(tenant_id),
|
||||||
let cmd = format!("lease lsn {} {} {} ", tenant_shard_id, timeline_id, lsn);
|
shard_count => TenantShardId {
|
||||||
let res = client.simple_query(&cmd)?;
|
tenant_id,
|
||||||
let msg = match res.first() {
|
shard_number: ShardNumber(shard_number as u8),
|
||||||
Some(msg) => msg,
|
shard_count: ShardCount::new(shard_count as u8),
|
||||||
None => bail!("empty response"),
|
},
|
||||||
};
|
|
||||||
let row = match msg {
|
|
||||||
SimpleQueryMessage::Row(row) => row,
|
|
||||||
_ => bail!("error parsing lsn lease response"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Note: this will be None if a lease is explicitly not granted.
|
let lease = match PageserverProtocol::from_connstring(connstring)? {
|
||||||
let valid_until_str = row.get("valid_until");
|
PageserverProtocol::Libpq => {
|
||||||
|
acquire_lsn_lease_libpq(connstring, auth, tenant_shard_id, timeline_id, lsn)?
|
||||||
let valid_until = valid_until_str.map(|s| {
|
}
|
||||||
SystemTime::UNIX_EPOCH
|
PageserverProtocol::Grpc => {
|
||||||
.checked_add(Duration::from_millis(u128::from_str(s).unwrap() as u64))
|
acquire_lsn_lease_grpc(connstring, auth, tenant_shard_id, timeline_id, lsn)?
|
||||||
.expect("Time larger than max SystemTime could handle")
|
}
|
||||||
});
|
};
|
||||||
Ok(valid_until)
|
leases.push(lease);
|
||||||
}
|
}
|
||||||
|
|
||||||
let shard_count = configs.len();
|
Ok(leases.into_iter().min().flatten())
|
||||||
|
}
|
||||||
|
|
||||||
let valid_until = if shard_count > 1 {
|
/// Acquires an LSN lease on a single shard, using the libpq API. The connstring must use a
|
||||||
configs
|
/// postgresql:// scheme.
|
||||||
.iter()
|
fn acquire_lsn_lease_libpq(
|
||||||
.enumerate()
|
connstring: &str,
|
||||||
.map(|(shard_number, config)| {
|
auth: Option<&str>,
|
||||||
let tenant_shard_id = TenantShardId {
|
tenant_shard_id: TenantShardId,
|
||||||
tenant_id,
|
timeline_id: TimelineId,
|
||||||
shard_count: ShardCount::new(shard_count as u8),
|
lsn: Lsn,
|
||||||
shard_number: ShardNumber(shard_number as u8),
|
) -> Result<Option<SystemTime>> {
|
||||||
};
|
let mut config = postgres::Config::from_str(connstring)?;
|
||||||
get_valid_until(config, tenant_shard_id, timeline_id, lsn)
|
if let Some(auth) = auth {
|
||||||
})
|
config.password(auth);
|
||||||
.collect::<Result<Vec<Option<SystemTime>>>>()?
|
}
|
||||||
.into_iter()
|
let mut client = config.connect(NoTls)?;
|
||||||
.min()
|
let cmd = format!("lease lsn {tenant_shard_id} {timeline_id} {lsn} ");
|
||||||
.unwrap()
|
let res = client.simple_query(&cmd)?;
|
||||||
} else {
|
let msg = match res.first() {
|
||||||
get_valid_until(
|
Some(msg) => msg,
|
||||||
&configs[0],
|
None => bail!("empty response"),
|
||||||
TenantShardId::unsharded(tenant_id),
|
};
|
||||||
timeline_id,
|
let row = match msg {
|
||||||
lsn,
|
SimpleQueryMessage::Row(row) => row,
|
||||||
)?
|
_ => bail!("error parsing lsn lease response"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Note: this will be None if a lease is explicitly not granted.
|
||||||
|
let valid_until_str = row.get("valid_until");
|
||||||
|
|
||||||
|
let valid_until = valid_until_str.map(|s| {
|
||||||
|
SystemTime::UNIX_EPOCH
|
||||||
|
.checked_add(Duration::from_millis(u128::from_str(s).unwrap() as u64))
|
||||||
|
.expect("Time larger than max SystemTime could handle")
|
||||||
|
});
|
||||||
Ok(valid_until)
|
Ok(valid_until)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Acquires an LSN lease on a single shard, using the gRPC API. The connstring must use a
|
||||||
|
/// grpc:// scheme.
|
||||||
|
fn acquire_lsn_lease_grpc(
|
||||||
|
connstring: &str,
|
||||||
|
auth: Option<&str>,
|
||||||
|
tenant_shard_id: TenantShardId,
|
||||||
|
timeline_id: TimelineId,
|
||||||
|
lsn: Lsn,
|
||||||
|
) -> Result<Option<SystemTime>> {
|
||||||
|
tokio::runtime::Handle::current().block_on(async move {
|
||||||
|
let mut client = page_api::Client::connect(
|
||||||
|
connstring.to_string(),
|
||||||
|
tenant_shard_id.tenant_id,
|
||||||
|
timeline_id,
|
||||||
|
tenant_shard_id.to_index(),
|
||||||
|
auth.map(String::from),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let req = page_api::LeaseLsnRequest { lsn };
|
||||||
|
match client.lease_lsn(req).await {
|
||||||
|
Ok(expires) => Ok(Some(expires)),
|
||||||
|
// Lease couldn't be acquired because the LSN has been garbage collected.
|
||||||
|
Err(err) if err.code() == tonic::Code::FailedPrecondition => Ok(None),
|
||||||
|
Err(err) => Err(err.into()),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -97,20 +97,34 @@ pub(crate) static PG_TOTAL_DOWNTIME_MS: Lazy<GenericCounter<AtomicU64>> = Lazy::
|
|||||||
.expect("failed to define a metric")
|
.expect("failed to define a metric")
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Needed as neon.file_cache_prewarm_batch == 0 doesn't mean we never tried to prewarm.
|
pub(crate) static LFC_PREWARMS: Lazy<IntCounter> = Lazy::new(|| {
|
||||||
/// On the other hand, LFC_PREWARMED_PAGES is excessive as we can GET /lfc/prewarm
|
|
||||||
pub(crate) static LFC_PREWARM_REQUESTS: Lazy<IntCounter> = Lazy::new(|| {
|
|
||||||
register_int_counter!(
|
register_int_counter!(
|
||||||
"compute_ctl_lfc_prewarm_requests_total",
|
"compute_ctl_lfc_prewarms_total",
|
||||||
"Total number of LFC prewarm requests made by compute_ctl",
|
"Total number of LFC prewarms requested by compute_ctl or autoprewarm option",
|
||||||
)
|
)
|
||||||
.expect("failed to define a metric")
|
.expect("failed to define a metric")
|
||||||
});
|
});
|
||||||
|
|
||||||
pub(crate) static LFC_OFFLOAD_REQUESTS: Lazy<IntCounter> = Lazy::new(|| {
|
pub(crate) static LFC_PREWARM_ERRORS: Lazy<IntCounter> = Lazy::new(|| {
|
||||||
register_int_counter!(
|
register_int_counter!(
|
||||||
"compute_ctl_lfc_offload_requests_total",
|
"compute_ctl_lfc_prewarm_errors_total",
|
||||||
"Total number of LFC offload requests made by compute_ctl",
|
"Total number of LFC prewarm errors",
|
||||||
|
)
|
||||||
|
.expect("failed to define a metric")
|
||||||
|
});
|
||||||
|
|
||||||
|
pub(crate) static LFC_OFFLOADS: Lazy<IntCounter> = Lazy::new(|| {
|
||||||
|
register_int_counter!(
|
||||||
|
"compute_ctl_lfc_offloads_total",
|
||||||
|
"Total number of LFC offloads requested by compute_ctl or lfc_offload_period_seconds option",
|
||||||
|
)
|
||||||
|
.expect("failed to define a metric")
|
||||||
|
});
|
||||||
|
|
||||||
|
pub(crate) static LFC_OFFLOAD_ERRORS: Lazy<IntCounter> = Lazy::new(|| {
|
||||||
|
register_int_counter!(
|
||||||
|
"compute_ctl_lfc_offload_errors_total",
|
||||||
|
"Total number of LFC offload errors",
|
||||||
)
|
)
|
||||||
.expect("failed to define a metric")
|
.expect("failed to define a metric")
|
||||||
});
|
});
|
||||||
@@ -124,7 +138,9 @@ pub fn collect() -> Vec<MetricFamily> {
|
|||||||
metrics.extend(AUDIT_LOG_DIR_SIZE.collect());
|
metrics.extend(AUDIT_LOG_DIR_SIZE.collect());
|
||||||
metrics.extend(PG_CURR_DOWNTIME_MS.collect());
|
metrics.extend(PG_CURR_DOWNTIME_MS.collect());
|
||||||
metrics.extend(PG_TOTAL_DOWNTIME_MS.collect());
|
metrics.extend(PG_TOTAL_DOWNTIME_MS.collect());
|
||||||
metrics.extend(LFC_PREWARM_REQUESTS.collect());
|
metrics.extend(LFC_PREWARMS.collect());
|
||||||
metrics.extend(LFC_OFFLOAD_REQUESTS.collect());
|
metrics.extend(LFC_PREWARM_ERRORS.collect());
|
||||||
|
metrics.extend(LFC_OFFLOADS.collect());
|
||||||
|
metrics.extend(LFC_OFFLOAD_ERRORS.collect());
|
||||||
metrics
|
metrics
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
-- On December 8th, 2023, an engineering escalation (INC-110) was opened after
|
||||||
|
-- it was found that BYPASSRLS was being applied to all roles.
|
||||||
|
--
|
||||||
|
-- PR that introduced the issue: https://github.com/neondatabase/neon/pull/5657
|
||||||
|
-- Subsequent commit on main: https://github.com/neondatabase/neon/commit/ad99fa5f0393e2679e5323df653c508ffa0ac072
|
||||||
|
--
|
||||||
|
-- NOBYPASSRLS and INHERIT are the defaults for a Postgres role, but because it
|
||||||
|
-- isn't easy to know if a Postgres cluster is affected by the issue, we need to
|
||||||
|
-- keep the migration around for a long time, if not indefinitely, so any
|
||||||
|
-- cluster can be fixed.
|
||||||
|
--
|
||||||
|
-- Branching is the gift that keeps on giving...
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
DECLARE
|
DECLARE
|
||||||
role_name text;
|
role_name text;
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
GRANT pg_signal_backend TO neon_superuser WITH ADMIN OPTION;
|
||||||
@@ -7,13 +7,17 @@ BEGIN
|
|||||||
INTO monitor
|
INTO monitor
|
||||||
FROM pg_auth_members
|
FROM pg_auth_members
|
||||||
WHERE roleid = 'pg_monitor'::regrole
|
WHERE roleid = 'pg_monitor'::regrole
|
||||||
AND member = 'pg_monitor'::regrole;
|
AND member = 'neon_superuser'::regrole;
|
||||||
|
|
||||||
IF NOT monitor.member THEN
|
IF monitor IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'no entry in pg_auth_members for neon_superuser and pg_monitor';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF monitor.admin IS NULL OR NOT monitor.member THEN
|
||||||
RAISE EXCEPTION 'neon_superuser is not a member of pg_monitor';
|
RAISE EXCEPTION 'neon_superuser is not a member of pg_monitor';
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
IF NOT monitor.admin THEN
|
IF monitor.admin IS NULL OR NOT monitor.admin THEN
|
||||||
RAISE EXCEPTION 'neon_superuser cannot grant pg_monitor';
|
RAISE EXCEPTION 'neon_superuser cannot grant pg_monitor';
|
||||||
END IF;
|
END IF;
|
||||||
END $$;
|
END $$;
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
signal_backend record;
|
||||||
|
BEGIN
|
||||||
|
SELECT pg_has_role('neon_superuser', 'pg_signal_backend', 'member') AS member,
|
||||||
|
admin_option AS admin
|
||||||
|
INTO signal_backend
|
||||||
|
FROM pg_auth_members
|
||||||
|
WHERE roleid = 'pg_signal_backend'::regrole
|
||||||
|
AND member = 'neon_superuser'::regrole;
|
||||||
|
|
||||||
|
IF signal_backend IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'no entry in pg_auth_members for neon_superuser and pg_signal_backend';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF signal_backend.member IS NULL OR NOT signal_backend.member THEN
|
||||||
|
RAISE EXCEPTION 'neon_superuser is not a member of pg_signal_backend';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF signal_backend.admin IS NULL OR NOT signal_backend.admin THEN
|
||||||
|
RAISE EXCEPTION 'neon_superuser cannot grant pg_signal_backend';
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
@@ -83,7 +83,10 @@ impl ComputeMonitor {
|
|||||||
let compute_status = self.compute.get_status();
|
let compute_status = self.compute.get_status();
|
||||||
if matches!(
|
if matches!(
|
||||||
compute_status,
|
compute_status,
|
||||||
ComputeStatus::Terminated | ComputeStatus::TerminationPending | ComputeStatus::Failed
|
ComputeStatus::Terminated
|
||||||
|
| ComputeStatus::TerminationPendingFast
|
||||||
|
| ComputeStatus::TerminationPendingImmediate
|
||||||
|
| ComputeStatus::Failed
|
||||||
) {
|
) {
|
||||||
info!(
|
info!(
|
||||||
"compute is in {} status, stopping compute monitor",
|
"compute is in {} status, stopping compute monitor",
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ pub fn escape_literal(s: &str) -> String {
|
|||||||
let res = s.replace('\'', "''").replace('\\', "\\\\");
|
let res = s.replace('\'', "''").replace('\\', "\\\\");
|
||||||
|
|
||||||
if res.contains('\\') {
|
if res.contains('\\') {
|
||||||
format!("E'{}'", res)
|
format!("E'{res}'")
|
||||||
} else {
|
} else {
|
||||||
format!("'{}'", res)
|
format!("'{res}'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ pub fn escape_literal(s: &str) -> String {
|
|||||||
/// with `'{}'` is not required, as it returns a ready-to-use config string.
|
/// with `'{}'` is not required, as it returns a ready-to-use config string.
|
||||||
pub fn escape_conf_value(s: &str) -> String {
|
pub fn escape_conf_value(s: &str) -> String {
|
||||||
let res = s.replace('\'', "''").replace('\\', "\\\\");
|
let res = s.replace('\'', "''").replace('\\', "\\\\");
|
||||||
format!("'{}'", res)
|
format!("'{res}'")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait GenericOptionExt {
|
pub trait GenericOptionExt {
|
||||||
@@ -446,7 +446,7 @@ pub async fn tune_pgbouncer(
|
|||||||
let mut pgbouncer_connstr =
|
let mut pgbouncer_connstr =
|
||||||
"host=localhost port=6432 dbname=pgbouncer user=postgres sslmode=disable".to_string();
|
"host=localhost port=6432 dbname=pgbouncer user=postgres sslmode=disable".to_string();
|
||||||
if let Ok(pass) = std::env::var("PGBOUNCER_PASSWORD") {
|
if let Ok(pass) = std::env::var("PGBOUNCER_PASSWORD") {
|
||||||
pgbouncer_connstr.push_str(format!(" password={}", pass).as_str());
|
pgbouncer_connstr.push_str(format!(" password={pass}").as_str());
|
||||||
}
|
}
|
||||||
pgbouncer_connstr
|
pgbouncer_connstr
|
||||||
};
|
};
|
||||||
@@ -464,7 +464,7 @@ pub async fn tune_pgbouncer(
|
|||||||
Ok((client, connection)) => {
|
Ok((client, connection)) => {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = connection.await {
|
if let Err(e) = connection.await {
|
||||||
eprintln!("connection error: {}", e);
|
eprintln!("connection error: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break client;
|
break client;
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
pub const PGBOUNCER_PIDFILE: &str = "/tmp/pgbouncer.pid";
|
||||||
@@ -4,8 +4,10 @@ use std::path::Path;
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{fs::OpenOptions, io::Write};
|
use std::{fs::OpenOptions, io::Write};
|
||||||
|
use url::{Host, Url};
|
||||||
|
|
||||||
use anyhow::{Context, Result, anyhow};
|
use anyhow::{Context, Result, anyhow};
|
||||||
|
use hostname_validator;
|
||||||
use tracing::{error, info, instrument, warn};
|
use tracing::{error, info, instrument, warn};
|
||||||
|
|
||||||
const POSTGRES_LOGS_CONF_PATH: &str = "/etc/rsyslog.d/postgres_logs.conf";
|
const POSTGRES_LOGS_CONF_PATH: &str = "/etc/rsyslog.d/postgres_logs.conf";
|
||||||
@@ -82,18 +84,84 @@ fn restart_rsyslog() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_audit_syslog_address(
|
||||||
|
remote_plain_endpoint: &str,
|
||||||
|
remote_tls_endpoint: &str,
|
||||||
|
) -> Result<(String, u16, String)> {
|
||||||
|
let tls;
|
||||||
|
let remote_endpoint = if !remote_tls_endpoint.is_empty() {
|
||||||
|
tls = "true".to_string();
|
||||||
|
remote_tls_endpoint
|
||||||
|
} else {
|
||||||
|
tls = "false".to_string();
|
||||||
|
remote_plain_endpoint
|
||||||
|
};
|
||||||
|
// Urlify the remote_endpoint, so parsing can be done with url::Url.
|
||||||
|
let url_str = format!("http://{remote_endpoint}");
|
||||||
|
let url = Url::parse(&url_str).map_err(|err| {
|
||||||
|
anyhow!("Error parsing {remote_endpoint}, expected host:port, got {err:?}")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let is_valid = url.scheme() == "http"
|
||||||
|
&& url.path() == "/"
|
||||||
|
&& url.query().is_none()
|
||||||
|
&& url.fragment().is_none()
|
||||||
|
&& url.username() == ""
|
||||||
|
&& url.password().is_none();
|
||||||
|
|
||||||
|
if !is_valid {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Invalid address format {remote_endpoint}, expected host:port"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let host = match url.host() {
|
||||||
|
Some(Host::Domain(h)) if hostname_validator::is_valid(h) => h.to_string(),
|
||||||
|
Some(Host::Ipv4(ip4)) => ip4.to_string(),
|
||||||
|
Some(Host::Ipv6(ip6)) => ip6.to_string(),
|
||||||
|
_ => return Err(anyhow!("Invalid host")),
|
||||||
|
};
|
||||||
|
let port = url
|
||||||
|
.port()
|
||||||
|
.ok_or_else(|| anyhow!("Invalid port in {remote_endpoint}"))?;
|
||||||
|
|
||||||
|
Ok((host, port, tls))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_audit_rsyslog_config(
|
||||||
|
log_directory: String,
|
||||||
|
endpoint_id: &str,
|
||||||
|
project_id: &str,
|
||||||
|
remote_syslog_host: &str,
|
||||||
|
remote_syslog_port: u16,
|
||||||
|
remote_syslog_tls: &str,
|
||||||
|
) -> String {
|
||||||
|
format!(
|
||||||
|
include_str!("config_template/compute_audit_rsyslog_template.conf"),
|
||||||
|
log_directory = log_directory,
|
||||||
|
endpoint_id = endpoint_id,
|
||||||
|
project_id = project_id,
|
||||||
|
remote_syslog_host = remote_syslog_host,
|
||||||
|
remote_syslog_port = remote_syslog_port,
|
||||||
|
remote_syslog_tls = remote_syslog_tls
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn configure_audit_rsyslog(
|
pub fn configure_audit_rsyslog(
|
||||||
log_directory: String,
|
log_directory: String,
|
||||||
endpoint_id: &str,
|
endpoint_id: &str,
|
||||||
project_id: &str,
|
project_id: &str,
|
||||||
remote_endpoint: &str,
|
remote_endpoint: &str,
|
||||||
|
remote_tls_endpoint: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let config_content: String = format!(
|
let (remote_syslog_host, remote_syslog_port, remote_syslog_tls) =
|
||||||
include_str!("config_template/compute_audit_rsyslog_template.conf"),
|
parse_audit_syslog_address(remote_endpoint, remote_tls_endpoint).unwrap();
|
||||||
log_directory = log_directory,
|
let config_content = generate_audit_rsyslog_config(
|
||||||
endpoint_id = endpoint_id,
|
log_directory,
|
||||||
project_id = project_id,
|
endpoint_id,
|
||||||
remote_endpoint = remote_endpoint
|
project_id,
|
||||||
|
&remote_syslog_host,
|
||||||
|
remote_syslog_port,
|
||||||
|
&remote_syslog_tls,
|
||||||
);
|
);
|
||||||
|
|
||||||
info!("rsyslog config_content: {}", config_content);
|
info!("rsyslog config_content: {}", config_content);
|
||||||
@@ -258,6 +326,8 @@ pub fn launch_pgaudit_gc(log_directory: String) {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::rsyslog::PostgresLogsRsyslogConfig;
|
use crate::rsyslog::PostgresLogsRsyslogConfig;
|
||||||
|
|
||||||
|
use super::{generate_audit_rsyslog_config, parse_audit_syslog_address};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_postgres_logs_config() {
|
fn test_postgres_logs_config() {
|
||||||
{
|
{
|
||||||
@@ -287,4 +357,146 @@ mod tests {
|
|||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_audit_syslog_address() {
|
||||||
|
{
|
||||||
|
// host:port format (plaintext)
|
||||||
|
let parsed = parse_audit_syslog_address("collector.host.tld:5555", "");
|
||||||
|
assert!(parsed.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
parsed.unwrap(),
|
||||||
|
(
|
||||||
|
String::from("collector.host.tld"),
|
||||||
|
5555,
|
||||||
|
String::from("false")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// host:port format with ipv4 ip address (plaintext)
|
||||||
|
let parsed = parse_audit_syslog_address("10.0.0.1:5555", "");
|
||||||
|
assert!(parsed.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
parsed.unwrap(),
|
||||||
|
(String::from("10.0.0.1"), 5555, String::from("false"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// host:port format with ipv6 ip address (plaintext)
|
||||||
|
let parsed =
|
||||||
|
parse_audit_syslog_address("[7e60:82ed:cb2e:d617:f904:f395:aaca:e252]:5555", "");
|
||||||
|
assert_eq!(
|
||||||
|
parsed.unwrap(),
|
||||||
|
(
|
||||||
|
String::from("7e60:82ed:cb2e:d617:f904:f395:aaca:e252"),
|
||||||
|
5555,
|
||||||
|
String::from("false")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Only TLS host:port defined
|
||||||
|
let parsed = parse_audit_syslog_address("", "tls.host.tld:5556");
|
||||||
|
assert_eq!(
|
||||||
|
parsed.unwrap(),
|
||||||
|
(String::from("tls.host.tld"), 5556, String::from("true"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// tls host should take precedence, when both defined
|
||||||
|
let parsed = parse_audit_syslog_address("plaintext.host.tld:5555", "tls.host.tld:5556");
|
||||||
|
assert_eq!(
|
||||||
|
parsed.unwrap(),
|
||||||
|
(String::from("tls.host.tld"), 5556, String::from("true"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// host without port (plaintext)
|
||||||
|
let parsed = parse_audit_syslog_address("collector.host.tld", "");
|
||||||
|
assert!(parsed.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// port without host
|
||||||
|
let parsed = parse_audit_syslog_address(":5555", "");
|
||||||
|
assert!(parsed.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// valid host with invalid port
|
||||||
|
let parsed = parse_audit_syslog_address("collector.host.tld:90001", "");
|
||||||
|
assert!(parsed.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// invalid hostname with valid port
|
||||||
|
let parsed = parse_audit_syslog_address("-collector.host.tld:5555", "");
|
||||||
|
assert!(parsed.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// parse error
|
||||||
|
let parsed = parse_audit_syslog_address("collector.host.tld:::5555", "");
|
||||||
|
assert!(parsed.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate_audit_rsyslog_config() {
|
||||||
|
{
|
||||||
|
// plaintext version
|
||||||
|
let log_directory = "/tmp/log".to_string();
|
||||||
|
let endpoint_id = "ep-test-endpoint-id";
|
||||||
|
let project_id = "test-project-id";
|
||||||
|
let remote_syslog_host = "collector.host.tld";
|
||||||
|
let remote_syslog_port = 5555;
|
||||||
|
let remote_syslog_tls = "false";
|
||||||
|
|
||||||
|
let conf_str = generate_audit_rsyslog_config(
|
||||||
|
log_directory,
|
||||||
|
endpoint_id,
|
||||||
|
project_id,
|
||||||
|
remote_syslog_host,
|
||||||
|
remote_syslog_port,
|
||||||
|
remote_syslog_tls,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(conf_str.contains(r#"set $.remote_syslog_tls = "false";"#));
|
||||||
|
assert!(conf_str.contains(r#"type="omfwd""#));
|
||||||
|
assert!(conf_str.contains(r#"target="collector.host.tld""#));
|
||||||
|
assert!(conf_str.contains(r#"port="5555""#));
|
||||||
|
assert!(conf_str.contains(r#"StreamDriverPermittedPeers="collector.host.tld""#));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// TLS version
|
||||||
|
let log_directory = "/tmp/log".to_string();
|
||||||
|
let endpoint_id = "ep-test-endpoint-id";
|
||||||
|
let project_id = "test-project-id";
|
||||||
|
let remote_syslog_host = "collector.host.tld";
|
||||||
|
let remote_syslog_port = 5556;
|
||||||
|
let remote_syslog_tls = "true";
|
||||||
|
|
||||||
|
let conf_str = generate_audit_rsyslog_config(
|
||||||
|
log_directory,
|
||||||
|
endpoint_id,
|
||||||
|
project_id,
|
||||||
|
remote_syslog_host,
|
||||||
|
remote_syslog_port,
|
||||||
|
remote_syslog_tls,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(conf_str.contains(r#"set $.remote_syslog_tls = "true";"#));
|
||||||
|
assert!(conf_str.contains(r#"type="omfwd""#));
|
||||||
|
assert!(conf_str.contains(r#"target="collector.host.tld""#));
|
||||||
|
assert!(conf_str.contains(r#"port="5556""#));
|
||||||
|
assert!(conf_str.contains(r#"StreamDriverPermittedPeers="collector.host.tld""#));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ fn do_control_plane_request(
|
|||||||
) -> Result<ControlPlaneConfigResponse, (bool, String, String)> {
|
) -> Result<ControlPlaneConfigResponse, (bool, String, String)> {
|
||||||
let resp = reqwest::blocking::Client::new()
|
let resp = reqwest::blocking::Client::new()
|
||||||
.get(uri)
|
.get(uri)
|
||||||
.header("Authorization", format!("Bearer {}", jwt))
|
.header("Authorization", format!("Bearer {jwt}"))
|
||||||
.send()
|
.send()
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
(
|
(
|
||||||
true,
|
true,
|
||||||
format!("could not perform request to control plane: {:?}", e),
|
format!("could not perform request to control plane: {e:?}"),
|
||||||
UNKNOWN_HTTP_STATUS.to_string(),
|
UNKNOWN_HTTP_STATUS.to_string(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
@@ -39,7 +39,7 @@ fn do_control_plane_request(
|
|||||||
Ok(spec_resp) => Ok(spec_resp),
|
Ok(spec_resp) => Ok(spec_resp),
|
||||||
Err(e) => Err((
|
Err(e) => Err((
|
||||||
true,
|
true,
|
||||||
format!("could not deserialize control plane response: {:?}", e),
|
format!("could not deserialize control plane response: {e:?}"),
|
||||||
status.to_string(),
|
status.to_string(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
@@ -62,7 +62,7 @@ fn do_control_plane_request(
|
|||||||
// or some internal failure happened. Doesn't make much sense to retry in this case.
|
// or some internal failure happened. Doesn't make much sense to retry in this case.
|
||||||
_ => Err((
|
_ => Err((
|
||||||
false,
|
false,
|
||||||
format!("unexpected control plane response status code: {}", status),
|
format!("unexpected control plane response status code: {status}"),
|
||||||
status.to_string(),
|
status.to_string(),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
@@ -197,6 +197,7 @@ pub async fn handle_migrations(client: &mut Client) -> Result<()> {
|
|||||||
include_str!(
|
include_str!(
|
||||||
"./migrations/0011-grant_pg_show_replication_origin_status_to_neon_superuser.sql"
|
"./migrations/0011-grant_pg_show_replication_origin_status_to_neon_superuser.sql"
|
||||||
),
|
),
|
||||||
|
include_str!("./migrations/0012-grant_pg_signal_backend_to_neon_superuser.sql"),
|
||||||
];
|
];
|
||||||
|
|
||||||
MigrationRunner::new(client, &migrations)
|
MigrationRunner::new(client, &migrations)
|
||||||
|
|||||||
@@ -933,56 +933,53 @@ async fn get_operations<'a>(
|
|||||||
PerDatabasePhase::DeleteDBRoleReferences => {
|
PerDatabasePhase::DeleteDBRoleReferences => {
|
||||||
let ctx = ctx.read().await;
|
let ctx = ctx.read().await;
|
||||||
|
|
||||||
let operations =
|
let operations = spec
|
||||||
spec.delta_operations
|
.delta_operations
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter(|op| op.action == "delete_role")
|
.filter(|op| op.action == "delete_role")
|
||||||
.filter_map(move |op| {
|
.filter_map(move |op| {
|
||||||
if db.is_owned_by(&op.name) {
|
if db.is_owned_by(&op.name) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if !ctx.roles.contains_key(&op.name) {
|
if !ctx.roles.contains_key(&op.name) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let quoted = op.name.pg_quote();
|
let quoted = op.name.pg_quote();
|
||||||
let new_owner = match &db {
|
let new_owner = match &db {
|
||||||
DB::SystemDB => PgIdent::from("cloud_admin").pg_quote(),
|
DB::SystemDB => PgIdent::from("cloud_admin").pg_quote(),
|
||||||
DB::UserDB(db) => db.owner.pg_quote(),
|
DB::UserDB(db) => db.owner.pg_quote(),
|
||||||
};
|
};
|
||||||
let (escaped_role, outer_tag) = op.name.pg_quote_dollar();
|
let (escaped_role, outer_tag) = op.name.pg_quote_dollar();
|
||||||
|
|
||||||
Some(vec![
|
Some(vec![
|
||||||
// This will reassign all dependent objects to the db owner
|
// This will reassign all dependent objects to the db owner
|
||||||
Operation {
|
Operation {
|
||||||
query: format!(
|
query: format!("REASSIGN OWNED BY {quoted} TO {new_owner}",),
|
||||||
"REASSIGN OWNED BY {} TO {}",
|
comment: None,
|
||||||
quoted, new_owner,
|
},
|
||||||
),
|
// Revoke some potentially blocking privileges (Neon-specific currently)
|
||||||
comment: None,
|
Operation {
|
||||||
},
|
query: format!(
|
||||||
// Revoke some potentially blocking privileges (Neon-specific currently)
|
include_str!("sql/pre_drop_role_revoke_privileges.sql"),
|
||||||
Operation {
|
// N.B. this has to be properly dollar-escaped with `pg_quote_dollar()`
|
||||||
query: format!(
|
role_name = escaped_role,
|
||||||
include_str!("sql/pre_drop_role_revoke_privileges.sql"),
|
outer_tag = outer_tag,
|
||||||
// N.B. this has to be properly dollar-escaped with `pg_quote_dollar()`
|
),
|
||||||
role_name = escaped_role,
|
comment: None,
|
||||||
outer_tag = outer_tag,
|
},
|
||||||
),
|
// This now will only drop privileges of the role
|
||||||
comment: None,
|
// TODO: this is obviously not 100% true because of the above case,
|
||||||
},
|
// there could be still some privileges that are not revoked. Maybe this
|
||||||
// This now will only drop privileges of the role
|
// only drops privileges that were granted *by this* role, not *to this* role,
|
||||||
// TODO: this is obviously not 100% true because of the above case,
|
// but this has to be checked.
|
||||||
// there could be still some privileges that are not revoked. Maybe this
|
Operation {
|
||||||
// only drops privileges that were granted *by this* role, not *to this* role,
|
query: format!("DROP OWNED BY {quoted}"),
|
||||||
// but this has to be checked.
|
comment: None,
|
||||||
Operation {
|
},
|
||||||
query: format!("DROP OWNED BY {}", quoted),
|
])
|
||||||
comment: None,
|
})
|
||||||
},
|
.flatten();
|
||||||
])
|
|
||||||
})
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
Ok(Box::new(operations))
|
Ok(Box::new(operations))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ pub async fn ping_safekeeper(
|
|||||||
let (client, conn) = config.connect(tokio_postgres::NoTls).await?;
|
let (client, conn) = config.connect(tokio_postgres::NoTls).await?;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = conn.await {
|
if let Err(e) = conn.await {
|
||||||
eprintln!("connection error: {}", e);
|
eprintln!("connection error: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
### Test files
|
||||||
|
|
||||||
|
The file `cluster_spec.json` has been copied over from libs/compute_api
|
||||||
|
tests, with some edits:
|
||||||
|
|
||||||
|
- the neon.safekeepers setting contains a duplicate value
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
{
|
||||||
|
"format_version": 1.0,
|
||||||
|
|
||||||
|
"timestamp": "2021-05-23T18:25:43.511Z",
|
||||||
|
"operation_uuid": "0f657b36-4b0f-4a2d-9c2e-1dcd615e7d8b",
|
||||||
|
"suspend_timeout_seconds": 3600,
|
||||||
|
|
||||||
|
"cluster": {
|
||||||
|
"cluster_id": "test-cluster-42",
|
||||||
|
"name": "Zenith Test",
|
||||||
|
"state": "restarted",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"name": "postgres",
|
||||||
|
"encrypted_password": "6b1d16b78004bbd51fa06af9eda75972",
|
||||||
|
"options": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "alexk",
|
||||||
|
"encrypted_password": null,
|
||||||
|
"options": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "zenith \"new\"",
|
||||||
|
"encrypted_password": "5b1d16b78004bbd51fa06af9eda75972",
|
||||||
|
"options": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "zen",
|
||||||
|
"encrypted_password": "9b1d16b78004bbd51fa06af9eda75972"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "\"name\";\\n select 1;",
|
||||||
|
"encrypted_password": "5b1d16b78004bbd51fa06af9eda75972"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MyRole",
|
||||||
|
"encrypted_password": "5b1d16b78004bbd51fa06af9eda75972"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"databases": [
|
||||||
|
{
|
||||||
|
"name": "DB2",
|
||||||
|
"owner": "alexk",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "LC_COLLATE",
|
||||||
|
"value": "C",
|
||||||
|
"vartype": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LC_CTYPE",
|
||||||
|
"value": "C",
|
||||||
|
"vartype": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TEMPLATE",
|
||||||
|
"value": "template0",
|
||||||
|
"vartype": "enum"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "zenith",
|
||||||
|
"owner": "MyRole"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "zen",
|
||||||
|
"owner": "zen"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"name": "fsync",
|
||||||
|
"value": "off",
|
||||||
|
"vartype": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wal_level",
|
||||||
|
"value": "logical",
|
||||||
|
"vartype": "enum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hot_standby",
|
||||||
|
"value": "on",
|
||||||
|
"vartype": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "prewarm_lfc_on_startup",
|
||||||
|
"value": "off",
|
||||||
|
"vartype": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "neon.safekeepers",
|
||||||
|
"value": "127.0.0.1:6502,127.0.0.1:6503,127.0.0.1:6501,127.0.0.1:6502",
|
||||||
|
"vartype": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wal_log_hints",
|
||||||
|
"value": "on",
|
||||||
|
"vartype": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "log_connections",
|
||||||
|
"value": "on",
|
||||||
|
"vartype": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "shared_buffers",
|
||||||
|
"value": "32768",
|
||||||
|
"vartype": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "port",
|
||||||
|
"value": "55432",
|
||||||
|
"vartype": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "max_connections",
|
||||||
|
"value": "100",
|
||||||
|
"vartype": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "max_wal_senders",
|
||||||
|
"value": "10",
|
||||||
|
"vartype": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "listen_addresses",
|
||||||
|
"value": "0.0.0.0",
|
||||||
|
"vartype": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wal_sender_timeout",
|
||||||
|
"value": "0",
|
||||||
|
"vartype": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "password_encryption",
|
||||||
|
"value": "md5",
|
||||||
|
"vartype": "enum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maintenance_work_mem",
|
||||||
|
"value": "65536",
|
||||||
|
"vartype": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "max_parallel_workers",
|
||||||
|
"value": "8",
|
||||||
|
"vartype": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "max_worker_processes",
|
||||||
|
"value": "8",
|
||||||
|
"vartype": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "neon.tenant_id",
|
||||||
|
"value": "b0554b632bd4d547a63b86c3630317e8",
|
||||||
|
"vartype": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "max_replication_slots",
|
||||||
|
"value": "10",
|
||||||
|
"vartype": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "neon.timeline_id",
|
||||||
|
"value": "2414a61ffc94e428f14b5758fe308e13",
|
||||||
|
"vartype": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "shared_preload_libraries",
|
||||||
|
"value": "neon",
|
||||||
|
"vartype": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "synchronous_standby_names",
|
||||||
|
"value": "walproposer",
|
||||||
|
"vartype": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "neon.pageserver_connstring",
|
||||||
|
"value": "host=127.0.0.1 port=6400",
|
||||||
|
"vartype": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "test.escaping",
|
||||||
|
"value": "here's a backslash \\ and a quote ' and a double-quote \" hooray",
|
||||||
|
"vartype": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"delta_operations": [
|
||||||
|
{
|
||||||
|
"action": "delete_db",
|
||||||
|
"name": "zenith_test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "rename_db",
|
||||||
|
"name": "DB",
|
||||||
|
"new_name": "DB2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "delete_role",
|
||||||
|
"name": "zenith2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "rename_role",
|
||||||
|
"name": "zenith new",
|
||||||
|
"new_name": "zenith \"new\""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"remote_extensions": {
|
||||||
|
"library_index": {
|
||||||
|
"postgis-3": "postgis",
|
||||||
|
"libpgrouting-3.4": "postgis",
|
||||||
|
"postgis_raster-3": "postgis",
|
||||||
|
"postgis_sfcgal-3": "postgis",
|
||||||
|
"postgis_topology-3": "postgis",
|
||||||
|
"address_standardizer-3": "postgis"
|
||||||
|
},
|
||||||
|
"extension_data": {
|
||||||
|
"postgis": {
|
||||||
|
"archive_path": "5834329303/v15/extensions/postgis.tar.zst",
|
||||||
|
"control_data": {
|
||||||
|
"postgis.control": "# postgis extension\ncomment = ''PostGIS geometry and geography spatial types and functions''\ndefault_version = ''3.3.2''\nmodule_pathname = ''$libdir/postgis-3''\nrelocatable = false\ntrusted = true\n",
|
||||||
|
"pgrouting.control": "# pgRouting Extension\ncomment = ''pgRouting Extension''\ndefault_version = ''3.4.2''\nmodule_pathname = ''$libdir/libpgrouting-3.4''\nrelocatable = true\nrequires = ''plpgsql''\nrequires = ''postgis''\ntrusted = true\n",
|
||||||
|
"postgis_raster.control": "# postgis_raster extension\ncomment = ''PostGIS raster types and functions''\ndefault_version = ''3.3.2''\nmodule_pathname = ''$libdir/postgis_raster-3''\nrelocatable = false\nrequires = postgis\ntrusted = true\n",
|
||||||
|
"postgis_sfcgal.control": "# postgis topology extension\ncomment = ''PostGIS SFCGAL functions''\ndefault_version = ''3.3.2''\nrelocatable = true\nrequires = postgis\ntrusted = true\n",
|
||||||
|
"postgis_topology.control": "# postgis topology extension\ncomment = ''PostGIS topology spatial types and functions''\ndefault_version = ''3.3.2''\nrelocatable = false\nschema = topology\nrequires = postgis\ntrusted = true\n",
|
||||||
|
"address_standardizer.control": "# address_standardizer extension\ncomment = ''Used to parse an address into constituent elements. Generally used to support geocoding address normalization step.''\ndefault_version = ''3.3.2''\nrelocatable = true\ntrusted = true\n",
|
||||||
|
"postgis_tiger_geocoder.control": "# postgis tiger geocoder extension\ncomment = ''PostGIS tiger geocoder and reverse geocoder''\ndefault_version = ''3.3.2''\nrelocatable = false\nschema = tiger\nrequires = ''postgis,fuzzystrmatch''\nsuperuser= false\ntrusted = true\n",
|
||||||
|
"address_standardizer_data_us.control": "# address standardizer us dataset\ncomment = ''Address Standardizer US dataset example''\ndefault_version = ''3.3.2''\nrelocatable = true\ntrusted = true\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"custom_extensions": [],
|
||||||
|
"public_extensions": ["postgis"]
|
||||||
|
},
|
||||||
|
"pgbouncer_settings": {
|
||||||
|
"default_pool_size": "42",
|
||||||
|
"pool_mode": "session"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ mod pg_helpers_tests {
|
|||||||
wal_level = logical
|
wal_level = logical
|
||||||
hot_standby = on
|
hot_standby = on
|
||||||
autoprewarm = off
|
autoprewarm = off
|
||||||
|
offload_lfc_interval_seconds = 20
|
||||||
neon.safekeepers = '127.0.0.1:6502,127.0.0.1:6503,127.0.0.1:6501'
|
neon.safekeepers = '127.0.0.1:6502,127.0.0.1:6503,127.0.0.1:6501'
|
||||||
wal_log_hints = on
|
wal_log_hints = on
|
||||||
log_connections = on
|
log_connections = on
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ use std::time::Duration;
|
|||||||
use anyhow::{Context, Result, anyhow, bail};
|
use anyhow::{Context, Result, anyhow, bail};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use compute_api::requests::ComputeClaimsScope;
|
use compute_api::requests::ComputeClaimsScope;
|
||||||
use compute_api::spec::ComputeMode;
|
use compute_api::spec::{ComputeMode, PageserverProtocol};
|
||||||
use control_plane::broker::StorageBroker;
|
use control_plane::broker::StorageBroker;
|
||||||
use control_plane::endpoint::ComputeControlPlane;
|
use control_plane::endpoint::{ComputeControlPlane, EndpointTerminateMode};
|
||||||
use control_plane::endpoint_storage::{ENDPOINT_STORAGE_DEFAULT_ADDR, EndpointStorage};
|
use control_plane::endpoint_storage::{ENDPOINT_STORAGE_DEFAULT_ADDR, EndpointStorage};
|
||||||
use control_plane::local_env;
|
use control_plane::local_env;
|
||||||
use control_plane::local_env::{
|
use control_plane::local_env::{
|
||||||
@@ -48,7 +48,7 @@ use postgres_connection::parse_host_port;
|
|||||||
use safekeeper_api::membership::{SafekeeperGeneration, SafekeeperId};
|
use safekeeper_api::membership::{SafekeeperGeneration, SafekeeperId};
|
||||||
use safekeeper_api::{
|
use safekeeper_api::{
|
||||||
DEFAULT_HTTP_LISTEN_PORT as DEFAULT_SAFEKEEPER_HTTP_PORT,
|
DEFAULT_HTTP_LISTEN_PORT as DEFAULT_SAFEKEEPER_HTTP_PORT,
|
||||||
DEFAULT_PG_LISTEN_PORT as DEFAULT_SAFEKEEPER_PG_PORT,
|
DEFAULT_PG_LISTEN_PORT as DEFAULT_SAFEKEEPER_PG_PORT, PgMajorVersion, PgVersionId,
|
||||||
};
|
};
|
||||||
use storage_broker::DEFAULT_LISTEN_ADDR as DEFAULT_BROKER_ADDR;
|
use storage_broker::DEFAULT_LISTEN_ADDR as DEFAULT_BROKER_ADDR;
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
@@ -64,7 +64,9 @@ const DEFAULT_PAGESERVER_ID: NodeId = NodeId(1);
|
|||||||
const DEFAULT_BRANCH_NAME: &str = "main";
|
const DEFAULT_BRANCH_NAME: &str = "main";
|
||||||
project_git_version!(GIT_VERSION);
|
project_git_version!(GIT_VERSION);
|
||||||
|
|
||||||
const DEFAULT_PG_VERSION: u32 = 17;
|
#[allow(dead_code)]
|
||||||
|
const DEFAULT_PG_VERSION: PgMajorVersion = PgMajorVersion::PG17;
|
||||||
|
const DEFAULT_PG_VERSION_NUM: &str = "17";
|
||||||
|
|
||||||
const DEFAULT_PAGESERVER_CONTROL_PLANE_API: &str = "http://127.0.0.1:1234/upcall/v1/";
|
const DEFAULT_PAGESERVER_CONTROL_PLANE_API: &str = "http://127.0.0.1:1234/upcall/v1/";
|
||||||
|
|
||||||
@@ -167,9 +169,9 @@ struct TenantCreateCmdArgs {
|
|||||||
#[clap(short = 'c')]
|
#[clap(short = 'c')]
|
||||||
config: Vec<String>,
|
config: Vec<String>,
|
||||||
|
|
||||||
#[arg(default_value_t = DEFAULT_PG_VERSION)]
|
#[arg(default_value = DEFAULT_PG_VERSION_NUM)]
|
||||||
#[clap(long, help = "Postgres version to use for the initial timeline")]
|
#[clap(long, help = "Postgres version to use for the initial timeline")]
|
||||||
pg_version: u32,
|
pg_version: PgMajorVersion,
|
||||||
|
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
@@ -290,9 +292,9 @@ struct TimelineCreateCmdArgs {
|
|||||||
#[clap(long, help = "Human-readable alias for the new timeline")]
|
#[clap(long, help = "Human-readable alias for the new timeline")]
|
||||||
branch_name: String,
|
branch_name: String,
|
||||||
|
|
||||||
#[arg(default_value_t = DEFAULT_PG_VERSION)]
|
#[arg(default_value = DEFAULT_PG_VERSION_NUM)]
|
||||||
#[clap(long, help = "Postgres version")]
|
#[clap(long, help = "Postgres version")]
|
||||||
pg_version: u32,
|
pg_version: PgMajorVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
@@ -322,9 +324,9 @@ struct TimelineImportCmdArgs {
|
|||||||
#[clap(long, help = "Lsn the basebackup ends at")]
|
#[clap(long, help = "Lsn the basebackup ends at")]
|
||||||
end_lsn: Option<Lsn>,
|
end_lsn: Option<Lsn>,
|
||||||
|
|
||||||
#[arg(default_value_t = DEFAULT_PG_VERSION)]
|
#[arg(default_value = DEFAULT_PG_VERSION_NUM)]
|
||||||
#[clap(long, help = "Postgres version of the backup being imported")]
|
#[clap(long, help = "Postgres version of the backup being imported")]
|
||||||
pg_version: u32,
|
pg_version: PgMajorVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Subcommand)]
|
#[derive(clap::Subcommand)]
|
||||||
@@ -601,9 +603,17 @@ struct EndpointCreateCmdArgs {
|
|||||||
)]
|
)]
|
||||||
config_only: bool,
|
config_only: bool,
|
||||||
|
|
||||||
#[arg(default_value_t = DEFAULT_PG_VERSION)]
|
#[arg(default_value = DEFAULT_PG_VERSION_NUM)]
|
||||||
#[clap(long, help = "Postgres version")]
|
#[clap(long, help = "Postgres version")]
|
||||||
pg_version: u32,
|
pg_version: PgMajorVersion,
|
||||||
|
|
||||||
|
/// Use gRPC to communicate with Pageservers, by generating grpc:// connstrings.
|
||||||
|
///
|
||||||
|
/// Specified on creation such that it's retained across reconfiguration and restarts.
|
||||||
|
///
|
||||||
|
/// NB: not yet supported by computes.
|
||||||
|
#[clap(long)]
|
||||||
|
grpc: bool,
|
||||||
|
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
@@ -664,6 +674,23 @@ struct EndpointStartCmdArgs {
|
|||||||
#[clap(short = 't', long, value_parser= humantime::parse_duration, help = "timeout until we fail the command")]
|
#[clap(short = 't', long, value_parser= humantime::parse_duration, help = "timeout until we fail the command")]
|
||||||
#[arg(default_value = "90s")]
|
#[arg(default_value = "90s")]
|
||||||
start_timeout: Duration,
|
start_timeout: Duration,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "Download LFC cache from endpoint storage on endpoint startup",
|
||||||
|
default_value = "false"
|
||||||
|
)]
|
||||||
|
autoprewarm: bool,
|
||||||
|
|
||||||
|
#[clap(long, help = "Upload LFC cache to endpoint storage periodically")]
|
||||||
|
offload_lfc_interval_seconds: Option<std::num::NonZeroU64>,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "Run in development mode, skipping VM-specific operations like process termination",
|
||||||
|
action = clap::ArgAction::SetTrue
|
||||||
|
)]
|
||||||
|
dev: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
@@ -696,10 +723,9 @@ struct EndpointStopCmdArgs {
|
|||||||
)]
|
)]
|
||||||
destroy: bool,
|
destroy: bool,
|
||||||
|
|
||||||
#[clap(long, help = "Postgres shutdown mode, passed to \"pg_ctl -m <mode>\"")]
|
#[clap(long, help = "Postgres shutdown mode")]
|
||||||
#[arg(value_parser(["smart", "fast", "immediate"]))]
|
#[clap(default_value = "fast")]
|
||||||
#[arg(default_value = "fast")]
|
mode: EndpointTerminateMode,
|
||||||
mode: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
@@ -905,7 +931,7 @@ fn print_timeline(
|
|||||||
br_sym = "┗━";
|
br_sym = "┗━";
|
||||||
}
|
}
|
||||||
|
|
||||||
print!("{} @{}: ", br_sym, ancestor_lsn);
|
print!("{br_sym} @{ancestor_lsn}: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally print a timeline id and name with new line
|
// Finally print a timeline id and name with new line
|
||||||
@@ -1281,7 +1307,7 @@ async fn handle_timeline(cmd: &TimelineCmd, env: &mut local_env::LocalEnv) -> Re
|
|||||||
},
|
},
|
||||||
new_members: None,
|
new_members: None,
|
||||||
};
|
};
|
||||||
let pg_version = args.pg_version * 10000;
|
let pg_version = PgVersionId::from(args.pg_version);
|
||||||
let req = safekeeper_api::models::TimelineCreateRequest {
|
let req = safekeeper_api::models::TimelineCreateRequest {
|
||||||
tenant_id,
|
tenant_id,
|
||||||
timeline_id,
|
timeline_id,
|
||||||
@@ -1451,6 +1477,7 @@ async fn handle_endpoint(subcmd: &EndpointCmd, env: &local_env::LocalEnv) -> Res
|
|||||||
args.internal_http_port,
|
args.internal_http_port,
|
||||||
args.pg_version,
|
args.pg_version,
|
||||||
mode,
|
mode,
|
||||||
|
args.grpc,
|
||||||
!args.update_catalog,
|
!args.update_catalog,
|
||||||
false,
|
false,
|
||||||
)?;
|
)?;
|
||||||
@@ -1491,13 +1518,20 @@ async fn handle_endpoint(subcmd: &EndpointCmd, env: &local_env::LocalEnv) -> Res
|
|||||||
|
|
||||||
let (pageservers, stripe_size) = if let Some(pageserver_id) = pageserver_id {
|
let (pageservers, stripe_size) = if let Some(pageserver_id) = pageserver_id {
|
||||||
let conf = env.get_pageserver_conf(pageserver_id).unwrap();
|
let conf = env.get_pageserver_conf(pageserver_id).unwrap();
|
||||||
let parsed = parse_host_port(&conf.listen_pg_addr).expect("Bad config");
|
// Use gRPC if requested.
|
||||||
(
|
let pageserver = if endpoint.grpc {
|
||||||
vec![(parsed.0, parsed.1.unwrap_or(5432))],
|
let grpc_addr = conf.listen_grpc_addr.as_ref().expect("bad config");
|
||||||
// If caller is telling us what pageserver to use, this is not a tenant which is
|
let (host, port) = parse_host_port(grpc_addr)?;
|
||||||
// full managed by storage controller, therefore not sharded.
|
let port = port.unwrap_or(DEFAULT_PAGESERVER_GRPC_PORT);
|
||||||
DEFAULT_STRIPE_SIZE,
|
(PageserverProtocol::Grpc, host, port)
|
||||||
)
|
} else {
|
||||||
|
let (host, port) = parse_host_port(&conf.listen_pg_addr)?;
|
||||||
|
let port = port.unwrap_or(5432);
|
||||||
|
(PageserverProtocol::Libpq, host, port)
|
||||||
|
};
|
||||||
|
// If caller is telling us what pageserver to use, this is not a tenant which is
|
||||||
|
// fully managed by storage controller, therefore not sharded.
|
||||||
|
(vec![pageserver], DEFAULT_STRIPE_SIZE)
|
||||||
} else {
|
} else {
|
||||||
// Look up the currently attached location of the tenant, and its striping metadata,
|
// Look up the currently attached location of the tenant, and its striping metadata,
|
||||||
// to pass these on to postgres.
|
// to pass these on to postgres.
|
||||||
@@ -1516,11 +1550,20 @@ async fn handle_endpoint(subcmd: &EndpointCmd, env: &local_env::LocalEnv) -> Res
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
anyhow::Ok((
|
let pageserver = if endpoint.grpc {
|
||||||
Host::parse(&shard.listen_pg_addr)
|
(
|
||||||
.expect("Storage controller reported bad hostname"),
|
PageserverProtocol::Grpc,
|
||||||
shard.listen_pg_port,
|
Host::parse(&shard.listen_grpc_addr.expect("no gRPC address"))?,
|
||||||
))
|
shard.listen_grpc_port.expect("no gRPC port"),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
PageserverProtocol::Libpq,
|
||||||
|
Host::parse(&shard.listen_pg_addr)?,
|
||||||
|
shard.listen_pg_port,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
anyhow::Ok(pageserver)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -1552,21 +1595,24 @@ async fn handle_endpoint(subcmd: &EndpointCmd, env: &local_env::LocalEnv) -> Res
|
|||||||
let endpoint_storage_token = env.generate_auth_token(&claims)?;
|
let endpoint_storage_token = env.generate_auth_token(&claims)?;
|
||||||
let endpoint_storage_addr = env.endpoint_storage.listen_addr.to_string();
|
let endpoint_storage_addr = env.endpoint_storage.listen_addr.to_string();
|
||||||
|
|
||||||
|
let args = control_plane::endpoint::EndpointStartArgs {
|
||||||
|
auth_token,
|
||||||
|
endpoint_storage_token,
|
||||||
|
endpoint_storage_addr,
|
||||||
|
safekeepers_generation,
|
||||||
|
safekeepers,
|
||||||
|
pageservers,
|
||||||
|
remote_ext_base_url: remote_ext_base_url.clone(),
|
||||||
|
shard_stripe_size: stripe_size.0 as usize,
|
||||||
|
create_test_user: args.create_test_user,
|
||||||
|
start_timeout: args.start_timeout,
|
||||||
|
autoprewarm: args.autoprewarm,
|
||||||
|
offload_lfc_interval_seconds: args.offload_lfc_interval_seconds,
|
||||||
|
dev: args.dev,
|
||||||
|
};
|
||||||
|
|
||||||
println!("Starting existing endpoint {endpoint_id}...");
|
println!("Starting existing endpoint {endpoint_id}...");
|
||||||
endpoint
|
endpoint.start(args).await?;
|
||||||
.start(
|
|
||||||
&auth_token,
|
|
||||||
endpoint_storage_token,
|
|
||||||
endpoint_storage_addr,
|
|
||||||
safekeepers_generation,
|
|
||||||
safekeepers,
|
|
||||||
pageservers,
|
|
||||||
remote_ext_base_url.as_ref(),
|
|
||||||
stripe_size.0 as usize,
|
|
||||||
args.create_test_user,
|
|
||||||
args.start_timeout,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
EndpointCmd::Reconfigure(args) => {
|
EndpointCmd::Reconfigure(args) => {
|
||||||
let endpoint_id = &args.endpoint_id;
|
let endpoint_id = &args.endpoint_id;
|
||||||
@@ -1575,11 +1621,19 @@ async fn handle_endpoint(subcmd: &EndpointCmd, env: &local_env::LocalEnv) -> Res
|
|||||||
.get(endpoint_id.as_str())
|
.get(endpoint_id.as_str())
|
||||||
.with_context(|| format!("postgres endpoint {endpoint_id} is not found"))?;
|
.with_context(|| format!("postgres endpoint {endpoint_id} is not found"))?;
|
||||||
let pageservers = if let Some(ps_id) = args.endpoint_pageserver_id {
|
let pageservers = if let Some(ps_id) = args.endpoint_pageserver_id {
|
||||||
let pageserver = PageServerNode::from_env(env, env.get_pageserver_conf(ps_id)?);
|
let conf = env.get_pageserver_conf(ps_id)?;
|
||||||
vec![(
|
// Use gRPC if requested.
|
||||||
pageserver.pg_connection_config.host().clone(),
|
let pageserver = if endpoint.grpc {
|
||||||
pageserver.pg_connection_config.port(),
|
let grpc_addr = conf.listen_grpc_addr.as_ref().expect("bad config");
|
||||||
)]
|
let (host, port) = parse_host_port(grpc_addr)?;
|
||||||
|
let port = port.unwrap_or(DEFAULT_PAGESERVER_GRPC_PORT);
|
||||||
|
(PageserverProtocol::Grpc, host, port)
|
||||||
|
} else {
|
||||||
|
let (host, port) = parse_host_port(&conf.listen_pg_addr)?;
|
||||||
|
let port = port.unwrap_or(5432);
|
||||||
|
(PageserverProtocol::Libpq, host, port)
|
||||||
|
};
|
||||||
|
vec![pageserver]
|
||||||
} else {
|
} else {
|
||||||
let storage_controller = StorageController::from_env(env);
|
let storage_controller = StorageController::from_env(env);
|
||||||
storage_controller
|
storage_controller
|
||||||
@@ -1588,18 +1642,30 @@ async fn handle_endpoint(subcmd: &EndpointCmd, env: &local_env::LocalEnv) -> Res
|
|||||||
.shards
|
.shards
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|shard| {
|
.map(|shard| {
|
||||||
(
|
// Use gRPC if requested.
|
||||||
Host::parse(&shard.listen_pg_addr)
|
if endpoint.grpc {
|
||||||
.expect("Storage controller reported malformed host"),
|
(
|
||||||
shard.listen_pg_port,
|
PageserverProtocol::Grpc,
|
||||||
)
|
Host::parse(&shard.listen_grpc_addr.expect("no gRPC address"))
|
||||||
|
.expect("bad hostname"),
|
||||||
|
shard.listen_grpc_port.expect("no gRPC port"),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
PageserverProtocol::Libpq,
|
||||||
|
Host::parse(&shard.listen_pg_addr).expect("bad hostname"),
|
||||||
|
shard.listen_pg_port,
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
// If --safekeepers argument is given, use only the listed
|
// If --safekeepers argument is given, use only the listed
|
||||||
// safekeeper nodes; otherwise all from the env.
|
// safekeeper nodes; otherwise all from the env.
|
||||||
let safekeepers = parse_safekeepers(&args.safekeepers)?;
|
let safekeepers = parse_safekeepers(&args.safekeepers)?;
|
||||||
endpoint.reconfigure(pageservers, None, safekeepers).await?;
|
endpoint
|
||||||
|
.reconfigure(Some(pageservers), None, safekeepers, None)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
EndpointCmd::Stop(args) => {
|
EndpointCmd::Stop(args) => {
|
||||||
let endpoint_id = &args.endpoint_id;
|
let endpoint_id = &args.endpoint_id;
|
||||||
@@ -1607,7 +1673,10 @@ async fn handle_endpoint(subcmd: &EndpointCmd, env: &local_env::LocalEnv) -> Res
|
|||||||
.endpoints
|
.endpoints
|
||||||
.get(endpoint_id)
|
.get(endpoint_id)
|
||||||
.with_context(|| format!("postgres endpoint {endpoint_id} is not found"))?;
|
.with_context(|| format!("postgres endpoint {endpoint_id} is not found"))?;
|
||||||
endpoint.stop(&args.mode, args.destroy)?;
|
match endpoint.stop(args.mode, args.destroy).await?.lsn {
|
||||||
|
Some(lsn) => println!("{lsn}"),
|
||||||
|
None => println!("null"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
EndpointCmd::GenerateJwt(args) => {
|
EndpointCmd::GenerateJwt(args) => {
|
||||||
let endpoint = {
|
let endpoint = {
|
||||||
@@ -1689,7 +1758,7 @@ async fn handle_pageserver(subcmd: &PageserverCmd, env: &local_env::LocalEnv) ->
|
|||||||
StopMode::Immediate => true,
|
StopMode::Immediate => true,
|
||||||
};
|
};
|
||||||
if let Err(e) = get_pageserver(env, args.pageserver_id)?.stop(immediate) {
|
if let Err(e) = get_pageserver(env, args.pageserver_id)?.stop(immediate) {
|
||||||
eprintln!("pageserver stop failed: {}", e);
|
eprintln!("pageserver stop failed: {e}");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1698,7 +1767,7 @@ async fn handle_pageserver(subcmd: &PageserverCmd, env: &local_env::LocalEnv) ->
|
|||||||
let pageserver = get_pageserver(env, args.pageserver_id)?;
|
let pageserver = get_pageserver(env, args.pageserver_id)?;
|
||||||
//TODO what shutdown strategy should we use here?
|
//TODO what shutdown strategy should we use here?
|
||||||
if let Err(e) = pageserver.stop(false) {
|
if let Err(e) = pageserver.stop(false) {
|
||||||
eprintln!("pageserver stop failed: {}", e);
|
eprintln!("pageserver stop failed: {e}");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1715,7 +1784,7 @@ async fn handle_pageserver(subcmd: &PageserverCmd, env: &local_env::LocalEnv) ->
|
|||||||
{
|
{
|
||||||
Ok(_) => println!("Page server is up and running"),
|
Ok(_) => println!("Page server is up and running"),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Page server is not available: {}", err);
|
eprintln!("Page server is not available: {err}");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1752,7 +1821,7 @@ async fn handle_storage_controller(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
if let Err(e) = svc.stop(stop_args).await {
|
if let Err(e) = svc.stop(stop_args).await {
|
||||||
eprintln!("stop failed: {}", e);
|
eprintln!("stop failed: {e}");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1774,7 +1843,7 @@ async fn handle_safekeeper(subcmd: &SafekeeperCmd, env: &local_env::LocalEnv) ->
|
|||||||
let safekeeper = get_safekeeper(env, args.id)?;
|
let safekeeper = get_safekeeper(env, args.id)?;
|
||||||
|
|
||||||
if let Err(e) = safekeeper.start(&args.extra_opt, &args.start_timeout).await {
|
if let Err(e) = safekeeper.start(&args.extra_opt, &args.start_timeout).await {
|
||||||
eprintln!("safekeeper start failed: {}", e);
|
eprintln!("safekeeper start failed: {e}");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1786,7 +1855,7 @@ async fn handle_safekeeper(subcmd: &SafekeeperCmd, env: &local_env::LocalEnv) ->
|
|||||||
StopMode::Immediate => true,
|
StopMode::Immediate => true,
|
||||||
};
|
};
|
||||||
if let Err(e) = safekeeper.stop(immediate) {
|
if let Err(e) = safekeeper.stop(immediate) {
|
||||||
eprintln!("safekeeper stop failed: {}", e);
|
eprintln!("safekeeper stop failed: {e}");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1799,12 +1868,12 @@ async fn handle_safekeeper(subcmd: &SafekeeperCmd, env: &local_env::LocalEnv) ->
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = safekeeper.stop(immediate) {
|
if let Err(e) = safekeeper.stop(immediate) {
|
||||||
eprintln!("safekeeper stop failed: {}", e);
|
eprintln!("safekeeper stop failed: {e}");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = safekeeper.start(&args.extra_opt, &args.start_timeout).await {
|
if let Err(e) = safekeeper.start(&args.extra_opt, &args.start_timeout).await {
|
||||||
eprintln!("safekeeper start failed: {}", e);
|
eprintln!("safekeeper start failed: {e}");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2039,11 +2108,16 @@ async fn handle_stop_all(args: &StopCmdArgs, env: &local_env::LocalEnv) -> Resul
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn try_stop_all(env: &local_env::LocalEnv, immediate: bool) {
|
async fn try_stop_all(env: &local_env::LocalEnv, immediate: bool) {
|
||||||
|
let mode = if immediate {
|
||||||
|
EndpointTerminateMode::Immediate
|
||||||
|
} else {
|
||||||
|
EndpointTerminateMode::Fast
|
||||||
|
};
|
||||||
// Stop all endpoints
|
// Stop all endpoints
|
||||||
match ComputeControlPlane::load(env.clone()) {
|
match ComputeControlPlane::load(env.clone()) {
|
||||||
Ok(cplane) => {
|
Ok(cplane) => {
|
||||||
for (_k, node) in cplane.endpoints {
|
for (_k, node) in cplane.endpoints {
|
||||||
if let Err(e) = node.stop(if immediate { "immediate" } else { "fast" }, false) {
|
if let Err(e) = node.stop(mode, false).await {
|
||||||
eprintln!("postgres stop failed: {e:#}");
|
eprintln!("postgres stop failed: {e:#}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2055,7 +2129,7 @@ async fn try_stop_all(env: &local_env::LocalEnv, immediate: bool) {
|
|||||||
|
|
||||||
let storage = EndpointStorage::from_env(env);
|
let storage = EndpointStorage::from_env(env);
|
||||||
if let Err(e) = storage.stop(immediate) {
|
if let Err(e) = storage.stop(immediate) {
|
||||||
eprintln!("endpoint_storage stop failed: {:#}", e);
|
eprintln!("endpoint_storage stop failed: {e:#}");
|
||||||
}
|
}
|
||||||
|
|
||||||
for ps_conf in &env.pageservers {
|
for ps_conf in &env.pageservers {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ impl StorageBroker {
|
|||||||
pub async fn start(&self, retry_timeout: &Duration) -> anyhow::Result<()> {
|
pub async fn start(&self, retry_timeout: &Duration) -> anyhow::Result<()> {
|
||||||
let broker = &self.env.broker;
|
let broker = &self.env.broker;
|
||||||
|
|
||||||
print!("Starting neon broker at {}", broker.client_url());
|
println!("Starting neon broker at {}", broker.client_url());
|
||||||
|
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
|
|
||||||
|
|||||||
+152
-81
@@ -32,11 +32,13 @@
|
|||||||
//! config.json - passed to `compute_ctl`
|
//! config.json - passed to `compute_ctl`
|
||||||
//! pgdata/
|
//! pgdata/
|
||||||
//! postgresql.conf - copy of postgresql.conf created by `compute_ctl`
|
//! postgresql.conf - copy of postgresql.conf created by `compute_ctl`
|
||||||
//! zenith.signal
|
//! neon.signal
|
||||||
|
//! zenith.signal - copy of neon.signal, for backward compatibility
|
||||||
//! <other PostgreSQL files>
|
//! <other PostgreSQL files>
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@@ -45,15 +47,18 @@ use std::sync::Arc;
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use anyhow::{Context, Result, anyhow, bail};
|
use anyhow::{Context, Result, anyhow, bail};
|
||||||
|
use base64::Engine;
|
||||||
|
use base64::prelude::BASE64_URL_SAFE_NO_PAD;
|
||||||
use compute_api::requests::{
|
use compute_api::requests::{
|
||||||
COMPUTE_AUDIENCE, ComputeClaims, ComputeClaimsScope, ConfigurationRequest,
|
COMPUTE_AUDIENCE, ComputeClaims, ComputeClaimsScope, ConfigurationRequest,
|
||||||
};
|
};
|
||||||
use compute_api::responses::{
|
use compute_api::responses::{
|
||||||
ComputeConfig, ComputeCtlConfig, ComputeStatus, ComputeStatusResponse, TlsConfig,
|
ComputeConfig, ComputeCtlConfig, ComputeStatus, ComputeStatusResponse, TerminateResponse,
|
||||||
|
TlsConfig,
|
||||||
};
|
};
|
||||||
use compute_api::spec::{
|
use compute_api::spec::{
|
||||||
Cluster, ComputeAudit, ComputeFeature, ComputeMode, ComputeSpec, Database, PgIdent,
|
Cluster, ComputeAudit, ComputeFeature, ComputeMode, ComputeSpec, Database, PageserverProtocol,
|
||||||
RemoteExtSpec, Role,
|
PgIdent, RemoteExtSpec, Role,
|
||||||
};
|
};
|
||||||
use jsonwebtoken::jwk::{
|
use jsonwebtoken::jwk::{
|
||||||
AlgorithmParameters, CommonParameters, EllipticCurve, Jwk, JwkSet, KeyAlgorithm, KeyOperations,
|
AlgorithmParameters, CommonParameters, EllipticCurve, Jwk, JwkSet, KeyAlgorithm, KeyOperations,
|
||||||
@@ -63,6 +68,7 @@ use nix::sys::signal::{Signal, kill};
|
|||||||
use pageserver_api::shard::ShardStripeSize;
|
use pageserver_api::shard::ShardStripeSize;
|
||||||
use pem::Pem;
|
use pem::Pem;
|
||||||
use reqwest::header::CONTENT_TYPE;
|
use reqwest::header::CONTENT_TYPE;
|
||||||
|
use safekeeper_api::PgMajorVersion;
|
||||||
use safekeeper_api::membership::SafekeeperGeneration;
|
use safekeeper_api::membership::SafekeeperGeneration;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
@@ -74,7 +80,6 @@ use utils::id::{NodeId, TenantId, TimelineId};
|
|||||||
|
|
||||||
use crate::local_env::LocalEnv;
|
use crate::local_env::LocalEnv;
|
||||||
use crate::postgresql_conf::PostgresConf;
|
use crate::postgresql_conf::PostgresConf;
|
||||||
use crate::storage_controller::StorageController;
|
|
||||||
|
|
||||||
// contents of a endpoint.json file
|
// contents of a endpoint.json file
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
|
||||||
@@ -86,7 +91,8 @@ pub struct EndpointConf {
|
|||||||
pg_port: u16,
|
pg_port: u16,
|
||||||
external_http_port: u16,
|
external_http_port: u16,
|
||||||
internal_http_port: u16,
|
internal_http_port: u16,
|
||||||
pg_version: u32,
|
pg_version: PgMajorVersion,
|
||||||
|
grpc: bool,
|
||||||
skip_pg_catalog_updates: bool,
|
skip_pg_catalog_updates: bool,
|
||||||
reconfigure_concurrency: usize,
|
reconfigure_concurrency: usize,
|
||||||
drop_subscriptions_before_start: bool,
|
drop_subscriptions_before_start: bool,
|
||||||
@@ -164,7 +170,7 @@ impl ComputeControlPlane {
|
|||||||
public_key_use: Some(PublicKeyUse::Signature),
|
public_key_use: Some(PublicKeyUse::Signature),
|
||||||
key_operations: Some(vec![KeyOperations::Verify]),
|
key_operations: Some(vec![KeyOperations::Verify]),
|
||||||
key_algorithm: Some(KeyAlgorithm::EdDSA),
|
key_algorithm: Some(KeyAlgorithm::EdDSA),
|
||||||
key_id: Some(base64::encode_config(key_hash, base64::URL_SAFE_NO_PAD)),
|
key_id: Some(BASE64_URL_SAFE_NO_PAD.encode(key_hash)),
|
||||||
x509_url: None::<String>,
|
x509_url: None::<String>,
|
||||||
x509_chain: None::<Vec<String>>,
|
x509_chain: None::<Vec<String>>,
|
||||||
x509_sha1_fingerprint: None::<String>,
|
x509_sha1_fingerprint: None::<String>,
|
||||||
@@ -173,7 +179,7 @@ impl ComputeControlPlane {
|
|||||||
algorithm: AlgorithmParameters::OctetKeyPair(OctetKeyPairParameters {
|
algorithm: AlgorithmParameters::OctetKeyPair(OctetKeyPairParameters {
|
||||||
key_type: OctetKeyPairType::OctetKeyPair,
|
key_type: OctetKeyPairType::OctetKeyPair,
|
||||||
curve: EllipticCurve::Ed25519,
|
curve: EllipticCurve::Ed25519,
|
||||||
x: base64::encode_config(public_key, base64::URL_SAFE_NO_PAD),
|
x: BASE64_URL_SAFE_NO_PAD.encode(public_key),
|
||||||
}),
|
}),
|
||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
@@ -188,8 +194,9 @@ impl ComputeControlPlane {
|
|||||||
pg_port: Option<u16>,
|
pg_port: Option<u16>,
|
||||||
external_http_port: Option<u16>,
|
external_http_port: Option<u16>,
|
||||||
internal_http_port: Option<u16>,
|
internal_http_port: Option<u16>,
|
||||||
pg_version: u32,
|
pg_version: PgMajorVersion,
|
||||||
mode: ComputeMode,
|
mode: ComputeMode,
|
||||||
|
grpc: bool,
|
||||||
skip_pg_catalog_updates: bool,
|
skip_pg_catalog_updates: bool,
|
||||||
drop_subscriptions_before_start: bool,
|
drop_subscriptions_before_start: bool,
|
||||||
) -> Result<Arc<Endpoint>> {
|
) -> Result<Arc<Endpoint>> {
|
||||||
@@ -224,6 +231,7 @@ impl ComputeControlPlane {
|
|||||||
// we also skip catalog updates in the cloud.
|
// we also skip catalog updates in the cloud.
|
||||||
skip_pg_catalog_updates,
|
skip_pg_catalog_updates,
|
||||||
drop_subscriptions_before_start,
|
drop_subscriptions_before_start,
|
||||||
|
grpc,
|
||||||
reconfigure_concurrency: 1,
|
reconfigure_concurrency: 1,
|
||||||
features: vec![],
|
features: vec![],
|
||||||
cluster: None,
|
cluster: None,
|
||||||
@@ -242,6 +250,7 @@ impl ComputeControlPlane {
|
|||||||
internal_http_port,
|
internal_http_port,
|
||||||
pg_port,
|
pg_port,
|
||||||
pg_version,
|
pg_version,
|
||||||
|
grpc,
|
||||||
skip_pg_catalog_updates,
|
skip_pg_catalog_updates,
|
||||||
drop_subscriptions_before_start,
|
drop_subscriptions_before_start,
|
||||||
reconfigure_concurrency: 1,
|
reconfigure_concurrency: 1,
|
||||||
@@ -296,6 +305,8 @@ pub struct Endpoint {
|
|||||||
pub tenant_id: TenantId,
|
pub tenant_id: TenantId,
|
||||||
pub timeline_id: TimelineId,
|
pub timeline_id: TimelineId,
|
||||||
pub mode: ComputeMode,
|
pub mode: ComputeMode,
|
||||||
|
/// If true, the endpoint should use gRPC to communicate with Pageservers.
|
||||||
|
pub grpc: bool,
|
||||||
|
|
||||||
// port and address of the Postgres server and `compute_ctl`'s HTTP APIs
|
// port and address of the Postgres server and `compute_ctl`'s HTTP APIs
|
||||||
pub pg_address: SocketAddr,
|
pub pg_address: SocketAddr,
|
||||||
@@ -303,7 +314,7 @@ pub struct Endpoint {
|
|||||||
pub internal_http_address: SocketAddr,
|
pub internal_http_address: SocketAddr,
|
||||||
|
|
||||||
// postgres major version in the format: 14, 15, etc.
|
// postgres major version in the format: 14, 15, etc.
|
||||||
pg_version: u32,
|
pg_version: PgMajorVersion,
|
||||||
|
|
||||||
// These are not part of the endpoint as such, but the environment
|
// These are not part of the endpoint as such, but the environment
|
||||||
// the endpoint runs in.
|
// the endpoint runs in.
|
||||||
@@ -331,18 +342,54 @@ pub enum EndpointStatus {
|
|||||||
RunningNoPidfile,
|
RunningNoPidfile,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for EndpointStatus {
|
impl Display for EndpointStatus {
|
||||||
fn fmt(&self, writer: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, writer: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
let s = match self {
|
writer.write_str(match self {
|
||||||
Self::Running => "running",
|
Self::Running => "running",
|
||||||
Self::Stopped => "stopped",
|
Self::Stopped => "stopped",
|
||||||
Self::Crashed => "crashed",
|
Self::Crashed => "crashed",
|
||||||
Self::RunningNoPidfile => "running, no pidfile",
|
Self::RunningNoPidfile => "running, no pidfile",
|
||||||
};
|
})
|
||||||
write!(writer, "{}", s)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Copy, clap::ValueEnum)]
|
||||||
|
pub enum EndpointTerminateMode {
|
||||||
|
#[default]
|
||||||
|
/// Use pg_ctl stop -m fast
|
||||||
|
Fast,
|
||||||
|
/// Use pg_ctl stop -m immediate
|
||||||
|
Immediate,
|
||||||
|
/// Use /terminate?mode=immediate
|
||||||
|
ImmediateTerminate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for EndpointTerminateMode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match &self {
|
||||||
|
EndpointTerminateMode::Fast => "fast",
|
||||||
|
EndpointTerminateMode::Immediate => "immediate",
|
||||||
|
EndpointTerminateMode::ImmediateTerminate => "immediate-terminate",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EndpointStartArgs {
|
||||||
|
pub auth_token: Option<String>,
|
||||||
|
pub endpoint_storage_token: String,
|
||||||
|
pub endpoint_storage_addr: String,
|
||||||
|
pub safekeepers_generation: Option<SafekeeperGeneration>,
|
||||||
|
pub safekeepers: Vec<NodeId>,
|
||||||
|
pub pageservers: Vec<(PageserverProtocol, Host, u16)>,
|
||||||
|
pub remote_ext_base_url: Option<String>,
|
||||||
|
pub shard_stripe_size: usize,
|
||||||
|
pub create_test_user: bool,
|
||||||
|
pub start_timeout: Duration,
|
||||||
|
pub autoprewarm: bool,
|
||||||
|
pub offload_lfc_interval_seconds: Option<std::num::NonZeroU64>,
|
||||||
|
pub dev: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl Endpoint {
|
impl Endpoint {
|
||||||
fn from_dir_entry(entry: std::fs::DirEntry, env: &LocalEnv) -> Result<Endpoint> {
|
fn from_dir_entry(entry: std::fs::DirEntry, env: &LocalEnv) -> Result<Endpoint> {
|
||||||
if !entry.file_type()?.is_dir() {
|
if !entry.file_type()?.is_dir() {
|
||||||
@@ -378,6 +425,7 @@ impl Endpoint {
|
|||||||
mode: conf.mode,
|
mode: conf.mode,
|
||||||
tenant_id: conf.tenant_id,
|
tenant_id: conf.tenant_id,
|
||||||
pg_version: conf.pg_version,
|
pg_version: conf.pg_version,
|
||||||
|
grpc: conf.grpc,
|
||||||
skip_pg_catalog_updates: conf.skip_pg_catalog_updates,
|
skip_pg_catalog_updates: conf.skip_pg_catalog_updates,
|
||||||
reconfigure_concurrency: conf.reconfigure_concurrency,
|
reconfigure_concurrency: conf.reconfigure_concurrency,
|
||||||
drop_subscriptions_before_start: conf.drop_subscriptions_before_start,
|
drop_subscriptions_before_start: conf.drop_subscriptions_before_start,
|
||||||
@@ -416,7 +464,7 @@ impl Endpoint {
|
|||||||
conf.append("max_connections", "100");
|
conf.append("max_connections", "100");
|
||||||
conf.append("wal_level", "logical");
|
conf.append("wal_level", "logical");
|
||||||
// wal_sender_timeout is the maximum time to wait for WAL replication.
|
// wal_sender_timeout is the maximum time to wait for WAL replication.
|
||||||
// It also defines how often the walreciever will send a feedback message to the wal sender.
|
// It also defines how often the walreceiver will send a feedback message to the wal sender.
|
||||||
conf.append("wal_sender_timeout", "5s");
|
conf.append("wal_sender_timeout", "5s");
|
||||||
conf.append("listen_addresses", &self.pg_address.ip().to_string());
|
conf.append("listen_addresses", &self.pg_address.ip().to_string());
|
||||||
conf.append("port", &self.pg_address.port().to_string());
|
conf.append("port", &self.pg_address.port().to_string());
|
||||||
@@ -504,7 +552,7 @@ impl Endpoint {
|
|||||||
conf.append("hot_standby", "on");
|
conf.append("hot_standby", "on");
|
||||||
// prefetching of blocks referenced in WAL doesn't make sense for us
|
// prefetching of blocks referenced in WAL doesn't make sense for us
|
||||||
// Neon hot standby ignores pages that are not in the shared_buffers
|
// Neon hot standby ignores pages that are not in the shared_buffers
|
||||||
if self.pg_version >= 15 {
|
if self.pg_version >= PgMajorVersion::PG15 {
|
||||||
conf.append("recovery_prefetch", "off");
|
conf.append("recovery_prefetch", "off");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -606,10 +654,10 @@ impl Endpoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_pageserver_connstr(pageservers: &[(Host, u16)]) -> String {
|
fn build_pageserver_connstr(pageservers: &[(PageserverProtocol, Host, u16)]) -> String {
|
||||||
pageservers
|
pageservers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(host, port)| format!("postgresql://no_user@{host}:{port}"))
|
.map(|(scheme, host, port)| format!("{scheme}://no_user@{host}:{port}"))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(",")
|
.join(",")
|
||||||
}
|
}
|
||||||
@@ -646,20 +694,7 @@ impl Endpoint {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
pub async fn start(&self, args: EndpointStartArgs) -> Result<()> {
|
||||||
pub async fn start(
|
|
||||||
&self,
|
|
||||||
auth_token: &Option<String>,
|
|
||||||
endpoint_storage_token: String,
|
|
||||||
endpoint_storage_addr: String,
|
|
||||||
safekeepers_generation: Option<SafekeeperGeneration>,
|
|
||||||
safekeepers: Vec<NodeId>,
|
|
||||||
pageservers: Vec<(Host, u16)>,
|
|
||||||
remote_ext_base_url: Option<&String>,
|
|
||||||
shard_stripe_size: usize,
|
|
||||||
create_test_user: bool,
|
|
||||||
start_timeout: Duration,
|
|
||||||
) -> Result<()> {
|
|
||||||
if self.status() == EndpointStatus::Running {
|
if self.status() == EndpointStatus::Running {
|
||||||
anyhow::bail!("The endpoint is already running");
|
anyhow::bail!("The endpoint is already running");
|
||||||
}
|
}
|
||||||
@@ -672,10 +707,10 @@ impl Endpoint {
|
|||||||
std::fs::remove_dir_all(self.pgdata())?;
|
std::fs::remove_dir_all(self.pgdata())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pageserver_connstring = Self::build_pageserver_connstr(&pageservers);
|
let pageserver_connstring = Self::build_pageserver_connstr(&args.pageservers);
|
||||||
assert!(!pageserver_connstring.is_empty());
|
assert!(!pageserver_connstring.is_empty());
|
||||||
|
|
||||||
let safekeeper_connstrings = self.build_safekeepers_connstrs(safekeepers)?;
|
let safekeeper_connstrings = self.build_safekeepers_connstrs(args.safekeepers)?;
|
||||||
|
|
||||||
// check for file remote_extensions_spec.json
|
// check for file remote_extensions_spec.json
|
||||||
// if it is present, read it and pass to compute_ctl
|
// if it is present, read it and pass to compute_ctl
|
||||||
@@ -703,7 +738,7 @@ impl Endpoint {
|
|||||||
cluster_id: None, // project ID: not used
|
cluster_id: None, // project ID: not used
|
||||||
name: None, // project name: not used
|
name: None, // project name: not used
|
||||||
state: None,
|
state: None,
|
||||||
roles: if create_test_user {
|
roles: if args.create_test_user {
|
||||||
vec![Role {
|
vec![Role {
|
||||||
name: PgIdent::from_str("test").unwrap(),
|
name: PgIdent::from_str("test").unwrap(),
|
||||||
encrypted_password: None,
|
encrypted_password: None,
|
||||||
@@ -712,7 +747,7 @@ impl Endpoint {
|
|||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
},
|
},
|
||||||
databases: if create_test_user {
|
databases: if args.create_test_user {
|
||||||
vec![Database {
|
vec![Database {
|
||||||
name: PgIdent::from_str("neondb").unwrap(),
|
name: PgIdent::from_str("neondb").unwrap(),
|
||||||
owner: PgIdent::from_str("test").unwrap(),
|
owner: PgIdent::from_str("test").unwrap(),
|
||||||
@@ -734,20 +769,22 @@ impl Endpoint {
|
|||||||
endpoint_id: Some(self.endpoint_id.clone()),
|
endpoint_id: Some(self.endpoint_id.clone()),
|
||||||
mode: self.mode,
|
mode: self.mode,
|
||||||
pageserver_connstring: Some(pageserver_connstring),
|
pageserver_connstring: Some(pageserver_connstring),
|
||||||
safekeepers_generation: safekeepers_generation.map(|g| g.into_inner()),
|
safekeepers_generation: args.safekeepers_generation.map(|g| g.into_inner()),
|
||||||
safekeeper_connstrings,
|
safekeeper_connstrings,
|
||||||
storage_auth_token: auth_token.clone(),
|
storage_auth_token: args.auth_token.clone(),
|
||||||
remote_extensions,
|
remote_extensions,
|
||||||
pgbouncer_settings: None,
|
pgbouncer_settings: None,
|
||||||
shard_stripe_size: Some(shard_stripe_size),
|
shard_stripe_size: Some(args.shard_stripe_size),
|
||||||
local_proxy_config: None,
|
local_proxy_config: None,
|
||||||
reconfigure_concurrency: self.reconfigure_concurrency,
|
reconfigure_concurrency: self.reconfigure_concurrency,
|
||||||
drop_subscriptions_before_start: self.drop_subscriptions_before_start,
|
drop_subscriptions_before_start: self.drop_subscriptions_before_start,
|
||||||
audit_log_level: ComputeAudit::Disabled,
|
audit_log_level: ComputeAudit::Disabled,
|
||||||
logs_export_host: None::<String>,
|
logs_export_host: None::<String>,
|
||||||
endpoint_storage_addr: Some(endpoint_storage_addr),
|
endpoint_storage_addr: Some(args.endpoint_storage_addr),
|
||||||
endpoint_storage_token: Some(endpoint_storage_token),
|
endpoint_storage_token: Some(args.endpoint_storage_token),
|
||||||
autoprewarm: false,
|
autoprewarm: args.autoprewarm,
|
||||||
|
offload_lfc_interval_seconds: args.offload_lfc_interval_seconds,
|
||||||
|
suspend_timeout_seconds: -1, // Only used in neon_local.
|
||||||
};
|
};
|
||||||
|
|
||||||
// this strange code is needed to support respec() in tests
|
// this strange code is needed to support respec() in tests
|
||||||
@@ -758,7 +795,7 @@ impl Endpoint {
|
|||||||
debug!("spec.cluster {:?}", spec.cluster);
|
debug!("spec.cluster {:?}", spec.cluster);
|
||||||
|
|
||||||
// fill missing fields again
|
// fill missing fields again
|
||||||
if create_test_user {
|
if args.create_test_user {
|
||||||
spec.cluster.roles.push(Role {
|
spec.cluster.roles.push(Role {
|
||||||
name: PgIdent::from_str("test").unwrap(),
|
name: PgIdent::from_str("test").unwrap(),
|
||||||
encrypted_password: None,
|
encrypted_password: None,
|
||||||
@@ -792,10 +829,10 @@ impl Endpoint {
|
|||||||
|
|
||||||
// Launch compute_ctl
|
// Launch compute_ctl
|
||||||
let conn_str = self.connstr("cloud_admin", "postgres");
|
let conn_str = self.connstr("cloud_admin", "postgres");
|
||||||
println!("Starting postgres node at '{}'", conn_str);
|
println!("Starting postgres node at '{conn_str}'");
|
||||||
if create_test_user {
|
if args.create_test_user {
|
||||||
let conn_str = self.connstr("test", "neondb");
|
let conn_str = self.connstr("test", "neondb");
|
||||||
println!("Also at '{}'", conn_str);
|
println!("Also at '{conn_str}'");
|
||||||
}
|
}
|
||||||
let mut cmd = Command::new(self.env.neon_distrib_dir.join("compute_ctl"));
|
let mut cmd = Command::new(self.env.neon_distrib_dir.join("compute_ctl"));
|
||||||
cmd.args([
|
cmd.args([
|
||||||
@@ -825,8 +862,12 @@ impl Endpoint {
|
|||||||
.stderr(logfile.try_clone()?)
|
.stderr(logfile.try_clone()?)
|
||||||
.stdout(logfile);
|
.stdout(logfile);
|
||||||
|
|
||||||
if let Some(remote_ext_base_url) = remote_ext_base_url {
|
if let Some(remote_ext_base_url) = args.remote_ext_base_url {
|
||||||
cmd.args(["--remote-ext-base-url", remote_ext_base_url]);
|
cmd.args(["--remote-ext-base-url", &remote_ext_base_url]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.dev {
|
||||||
|
cmd.arg("--dev");
|
||||||
}
|
}
|
||||||
|
|
||||||
let child = cmd.spawn()?;
|
let child = cmd.spawn()?;
|
||||||
@@ -857,10 +898,11 @@ impl Endpoint {
|
|||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
match state.status {
|
match state.status {
|
||||||
ComputeStatus::Init => {
|
ComputeStatus::Init => {
|
||||||
if Instant::now().duration_since(start_at) > start_timeout {
|
let timeout = args.start_timeout;
|
||||||
|
if Instant::now().duration_since(start_at) > timeout {
|
||||||
bail!(
|
bail!(
|
||||||
"compute startup timed out {:?}; still in Init state",
|
"compute startup timed out {:?}; still in Init state",
|
||||||
start_timeout
|
timeout
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// keep retrying
|
// keep retrying
|
||||||
@@ -881,17 +923,18 @@ impl Endpoint {
|
|||||||
ComputeStatus::Empty
|
ComputeStatus::Empty
|
||||||
| ComputeStatus::ConfigurationPending
|
| ComputeStatus::ConfigurationPending
|
||||||
| ComputeStatus::Configuration
|
| ComputeStatus::Configuration
|
||||||
| ComputeStatus::TerminationPending
|
| ComputeStatus::TerminationPendingFast
|
||||||
|
| ComputeStatus::TerminationPendingImmediate
|
||||||
| ComputeStatus::Terminated => {
|
| ComputeStatus::Terminated => {
|
||||||
bail!("unexpected compute status: {:?}", state.status)
|
bail!("unexpected compute status: {:?}", state.status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if Instant::now().duration_since(start_at) > start_timeout {
|
if Instant::now().duration_since(start_at) > args.start_timeout {
|
||||||
return Err(e).context(format!(
|
return Err(e).context(format!(
|
||||||
"timed out {:?} waiting to connect to compute_ctl HTTP",
|
"timed out {:?} waiting to connect to compute_ctl HTTP",
|
||||||
start_timeout,
|
args.start_timeout
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -930,7 +973,7 @@ impl Endpoint {
|
|||||||
// reqwest does not export its error construction utility functions, so let's craft the message ourselves
|
// reqwest does not export its error construction utility functions, so let's craft the message ourselves
|
||||||
let url = response.url().to_owned();
|
let url = response.url().to_owned();
|
||||||
let msg = match response.text().await {
|
let msg = match response.text().await {
|
||||||
Ok(err_body) => format!("Error: {}", err_body),
|
Ok(err_body) => format!("Error: {err_body}"),
|
||||||
Err(_) => format!("Http error ({}) at {}.", status.as_u16(), url),
|
Err(_) => format!("Http error ({}) at {}.", status.as_u16(), url),
|
||||||
};
|
};
|
||||||
Err(anyhow::anyhow!(msg))
|
Err(anyhow::anyhow!(msg))
|
||||||
@@ -939,9 +982,10 @@ impl Endpoint {
|
|||||||
|
|
||||||
pub async fn reconfigure(
|
pub async fn reconfigure(
|
||||||
&self,
|
&self,
|
||||||
mut pageservers: Vec<(Host, u16)>,
|
pageservers: Option<Vec<(PageserverProtocol, Host, u16)>>,
|
||||||
stripe_size: Option<ShardStripeSize>,
|
stripe_size: Option<ShardStripeSize>,
|
||||||
safekeepers: Option<Vec<NodeId>>,
|
safekeepers: Option<Vec<NodeId>>,
|
||||||
|
safekeeper_generation: Option<SafekeeperGeneration>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (mut spec, compute_ctl_config) = {
|
let (mut spec, compute_ctl_config) = {
|
||||||
let config_path = self.endpoint_path().join("config.json");
|
let config_path = self.endpoint_path().join("config.json");
|
||||||
@@ -954,34 +998,24 @@ impl Endpoint {
|
|||||||
let postgresql_conf = self.read_postgresql_conf()?;
|
let postgresql_conf = self.read_postgresql_conf()?;
|
||||||
spec.cluster.postgresql_conf = Some(postgresql_conf);
|
spec.cluster.postgresql_conf = Some(postgresql_conf);
|
||||||
|
|
||||||
// If we weren't given explicit pageservers, query the storage controller
|
// If pageservers are not specified, don't change them.
|
||||||
if pageservers.is_empty() {
|
if let Some(pageservers) = pageservers {
|
||||||
let storage_controller = StorageController::from_env(&self.env);
|
anyhow::ensure!(!pageservers.is_empty(), "no pageservers provided");
|
||||||
let locate_result = storage_controller.tenant_locate(self.tenant_id).await?;
|
|
||||||
pageservers = locate_result
|
|
||||||
.shards
|
|
||||||
.into_iter()
|
|
||||||
.map(|shard| {
|
|
||||||
(
|
|
||||||
Host::parse(&shard.listen_pg_addr)
|
|
||||||
.expect("Storage controller reported bad hostname"),
|
|
||||||
shard.listen_pg_port,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
let pageserver_connstr = Self::build_pageserver_connstr(&pageservers);
|
let pageserver_connstr = Self::build_pageserver_connstr(&pageservers);
|
||||||
assert!(!pageserver_connstr.is_empty());
|
spec.pageserver_connstring = Some(pageserver_connstr);
|
||||||
spec.pageserver_connstring = Some(pageserver_connstr);
|
if stripe_size.is_some() {
|
||||||
if stripe_size.is_some() {
|
spec.shard_stripe_size = stripe_size.map(|s| s.0 as usize);
|
||||||
spec.shard_stripe_size = stripe_size.map(|s| s.0 as usize);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If safekeepers are not specified, don't change them.
|
// If safekeepers are not specified, don't change them.
|
||||||
if let Some(safekeepers) = safekeepers {
|
if let Some(safekeepers) = safekeepers {
|
||||||
let safekeeper_connstrings = self.build_safekeepers_connstrs(safekeepers)?;
|
let safekeeper_connstrings = self.build_safekeepers_connstrs(safekeepers)?;
|
||||||
spec.safekeeper_connstrings = safekeeper_connstrings;
|
spec.safekeeper_connstrings = safekeeper_connstrings;
|
||||||
|
if let Some(g) = safekeeper_generation {
|
||||||
|
spec.safekeepers_generation = Some(g.into_inner());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = reqwest::Client::builder()
|
let client = reqwest::Client::builder()
|
||||||
@@ -1012,15 +1046,52 @@ impl Endpoint {
|
|||||||
} else {
|
} else {
|
||||||
let url = response.url().to_owned();
|
let url = response.url().to_owned();
|
||||||
let msg = match response.text().await {
|
let msg = match response.text().await {
|
||||||
Ok(err_body) => format!("Error: {}", err_body),
|
Ok(err_body) => format!("Error: {err_body}"),
|
||||||
Err(_) => format!("Http error ({}) at {}.", status.as_u16(), url),
|
Err(_) => format!("Http error ({}) at {}.", status.as_u16(), url),
|
||||||
};
|
};
|
||||||
Err(anyhow::anyhow!(msg))
|
Err(anyhow::anyhow!(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(&self, mode: &str, destroy: bool) -> Result<()> {
|
pub async fn reconfigure_pageservers(
|
||||||
self.pg_ctl(&["-m", mode, "stop"], &None)?;
|
&self,
|
||||||
|
pageservers: Vec<(PageserverProtocol, Host, u16)>,
|
||||||
|
stripe_size: Option<ShardStripeSize>,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.reconfigure(Some(pageservers), stripe_size, None, None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn reconfigure_safekeepers(
|
||||||
|
&self,
|
||||||
|
safekeepers: Vec<NodeId>,
|
||||||
|
generation: SafekeeperGeneration,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.reconfigure(None, None, Some(safekeepers), Some(generation))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn stop(
|
||||||
|
&self,
|
||||||
|
mode: EndpointTerminateMode,
|
||||||
|
destroy: bool,
|
||||||
|
) -> Result<TerminateResponse> {
|
||||||
|
// pg_ctl stop is fast but doesn't allow us to collect LSN. /terminate is
|
||||||
|
// slow, and test runs time out. Solution: special mode "immediate-terminate"
|
||||||
|
// which uses /terminate
|
||||||
|
let response = if let EndpointTerminateMode::ImmediateTerminate = mode {
|
||||||
|
let ip = self.external_http_address.ip();
|
||||||
|
let port = self.external_http_address.port();
|
||||||
|
let url = format!("http://{ip}:{port}/terminate?mode=immediate");
|
||||||
|
let token = self.generate_jwt(Some(ComputeClaimsScope::Admin))?;
|
||||||
|
let request = reqwest::Client::new().post(url).bearer_auth(token);
|
||||||
|
let response = request.send().await.context("/terminate")?;
|
||||||
|
let text = response.text().await.context("/terminate result")?;
|
||||||
|
serde_json::from_str(&text).with_context(|| format!("deserializing {text}"))?
|
||||||
|
} else {
|
||||||
|
self.pg_ctl(&["-m", &mode.to_string(), "stop"], &None)?;
|
||||||
|
TerminateResponse { lsn: None }
|
||||||
|
};
|
||||||
|
|
||||||
// Also wait for the compute_ctl process to die. It might have some
|
// Also wait for the compute_ctl process to die. It might have some
|
||||||
// cleanup work to do after postgres stops, like syncing safekeepers,
|
// cleanup work to do after postgres stops, like syncing safekeepers,
|
||||||
@@ -1030,7 +1101,7 @@ impl Endpoint {
|
|||||||
// waiting. Sometimes we do *not* want this cleanup: tests intentionally
|
// waiting. Sometimes we do *not* want this cleanup: tests intentionally
|
||||||
// do stop when majority of safekeepers is down, so sync-safekeepers
|
// do stop when majority of safekeepers is down, so sync-safekeepers
|
||||||
// would hang otherwise. This could be a separate flag though.
|
// would hang otherwise. This could be a separate flag though.
|
||||||
let send_sigterm = destroy || mode == "immediate";
|
let send_sigterm = destroy || !matches!(mode, EndpointTerminateMode::Fast);
|
||||||
self.wait_for_compute_ctl_to_exit(send_sigterm)?;
|
self.wait_for_compute_ctl_to_exit(send_sigterm)?;
|
||||||
if destroy {
|
if destroy {
|
||||||
println!(
|
println!(
|
||||||
@@ -1039,7 +1110,7 @@ impl Endpoint {
|
|||||||
);
|
);
|
||||||
std::fs::remove_dir_all(self.endpoint_path())?;
|
std::fs::remove_dir_all(self.endpoint_path())?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn connstr(&self, user: &str, db_name: &str) -> String {
|
pub fn connstr(&self, user: &str, db_name: &str) -> String {
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ use std::{env, fs};
|
|||||||
|
|
||||||
use anyhow::{Context, bail};
|
use anyhow::{Context, bail};
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
|
use pageserver_api::config::PostHogConfig;
|
||||||
use pem::Pem;
|
use pem::Pem;
|
||||||
use postgres_backend::AuthType;
|
use postgres_backend::AuthType;
|
||||||
use reqwest::{Certificate, Url};
|
use reqwest::{Certificate, Url};
|
||||||
|
use safekeeper_api::PgMajorVersion;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use utils::auth::encode_from_key_file;
|
use utils::auth::encode_from_key_file;
|
||||||
use utils::id::{NodeId, TenantId, TenantTimelineId, TimelineId};
|
use utils::id::{NodeId, TenantId, TenantTimelineId, TimelineId};
|
||||||
@@ -209,6 +211,15 @@ pub struct NeonStorageControllerConf {
|
|||||||
pub use_https_safekeeper_api: bool,
|
pub use_https_safekeeper_api: bool,
|
||||||
|
|
||||||
pub use_local_compute_notifications: bool,
|
pub use_local_compute_notifications: bool,
|
||||||
|
|
||||||
|
pub timeline_safekeeper_count: Option<usize>,
|
||||||
|
|
||||||
|
pub posthog_config: Option<PostHogConfig>,
|
||||||
|
|
||||||
|
pub kick_secondary_downloads: Option<bool>,
|
||||||
|
|
||||||
|
#[serde(with = "humantime_serde")]
|
||||||
|
pub shard_split_request_timeout: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NeonStorageControllerConf {
|
impl NeonStorageControllerConf {
|
||||||
@@ -236,9 +247,13 @@ impl Default for NeonStorageControllerConf {
|
|||||||
heartbeat_interval: Self::DEFAULT_HEARTBEAT_INTERVAL,
|
heartbeat_interval: Self::DEFAULT_HEARTBEAT_INTERVAL,
|
||||||
long_reconcile_threshold: None,
|
long_reconcile_threshold: None,
|
||||||
use_https_pageserver_api: false,
|
use_https_pageserver_api: false,
|
||||||
timelines_onto_safekeepers: false,
|
timelines_onto_safekeepers: true,
|
||||||
use_https_safekeeper_api: false,
|
use_https_safekeeper_api: false,
|
||||||
use_local_compute_notifications: true,
|
use_local_compute_notifications: true,
|
||||||
|
timeline_safekeeper_count: None,
|
||||||
|
posthog_config: None,
|
||||||
|
kick_secondary_downloads: None,
|
||||||
|
shard_split_request_timeout: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,7 +269,7 @@ impl Default for EndpointStorageConf {
|
|||||||
impl NeonBroker {
|
impl NeonBroker {
|
||||||
pub fn client_url(&self) -> Url {
|
pub fn client_url(&self) -> Url {
|
||||||
let url = if let Some(addr) = self.listen_https_addr {
|
let url = if let Some(addr) = self.listen_https_addr {
|
||||||
format!("https://{}", addr)
|
format!("https://{addr}")
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!(
|
||||||
"http://{}",
|
"http://{}",
|
||||||
@@ -418,25 +433,21 @@ impl LocalEnv {
|
|||||||
self.pg_distrib_dir.clone()
|
self.pg_distrib_dir.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pg_distrib_dir(&self, pg_version: u32) -> anyhow::Result<PathBuf> {
|
pub fn pg_distrib_dir(&self, pg_version: PgMajorVersion) -> anyhow::Result<PathBuf> {
|
||||||
let path = self.pg_distrib_dir.clone();
|
let path = self.pg_distrib_dir.clone();
|
||||||
|
|
||||||
#[allow(clippy::manual_range_patterns)]
|
Ok(path.join(pg_version.v_str()))
|
||||||
match pg_version {
|
|
||||||
14 | 15 | 16 | 17 => Ok(path.join(format!("v{pg_version}"))),
|
|
||||||
_ => bail!("Unsupported postgres version: {}", pg_version),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pg_dir(&self, pg_version: u32, dir_name: &str) -> anyhow::Result<PathBuf> {
|
pub fn pg_dir(&self, pg_version: PgMajorVersion, dir_name: &str) -> anyhow::Result<PathBuf> {
|
||||||
Ok(self.pg_distrib_dir(pg_version)?.join(dir_name))
|
Ok(self.pg_distrib_dir(pg_version)?.join(dir_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pg_bin_dir(&self, pg_version: u32) -> anyhow::Result<PathBuf> {
|
pub fn pg_bin_dir(&self, pg_version: PgMajorVersion) -> anyhow::Result<PathBuf> {
|
||||||
self.pg_dir(pg_version, "bin")
|
self.pg_dir(pg_version, "bin")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pg_lib_dir(&self, pg_version: u32) -> anyhow::Result<PathBuf> {
|
pub fn pg_lib_dir(&self, pg_version: PgMajorVersion) -> anyhow::Result<PathBuf> {
|
||||||
self.pg_dir(pg_version, "lib")
|
self.pg_dir(pg_version, "lib")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -727,7 +738,7 @@ impl LocalEnv {
|
|||||||
let config_toml_path = dentry.path().join("pageserver.toml");
|
let config_toml_path = dentry.path().join("pageserver.toml");
|
||||||
let config_toml: PageserverConfigTomlSubset = toml_edit::de::from_str(
|
let config_toml: PageserverConfigTomlSubset = toml_edit::de::from_str(
|
||||||
&std::fs::read_to_string(&config_toml_path)
|
&std::fs::read_to_string(&config_toml_path)
|
||||||
.with_context(|| format!("read {:?}", config_toml_path))?,
|
.with_context(|| format!("read {config_toml_path:?}"))?,
|
||||||
)
|
)
|
||||||
.context("parse pageserver.toml")?;
|
.context("parse pageserver.toml")?;
|
||||||
let identity_toml_path = dentry.path().join("identity.toml");
|
let identity_toml_path = dentry.path().join("identity.toml");
|
||||||
@@ -737,7 +748,7 @@ impl LocalEnv {
|
|||||||
}
|
}
|
||||||
let identity_toml: IdentityTomlSubset = toml_edit::de::from_str(
|
let identity_toml: IdentityTomlSubset = toml_edit::de::from_str(
|
||||||
&std::fs::read_to_string(&identity_toml_path)
|
&std::fs::read_to_string(&identity_toml_path)
|
||||||
.with_context(|| format!("read {:?}", identity_toml_path))?,
|
.with_context(|| format!("read {identity_toml_path:?}"))?,
|
||||||
)
|
)
|
||||||
.context("parse identity.toml")?;
|
.context("parse identity.toml")?;
|
||||||
let PageserverConfigTomlSubset {
|
let PageserverConfigTomlSubset {
|
||||||
|
|||||||
@@ -16,11 +16,13 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use anyhow::{Context, bail};
|
use anyhow::{Context, bail};
|
||||||
use camino::Utf8PathBuf;
|
use camino::Utf8PathBuf;
|
||||||
|
use pageserver_api::config::{DEFAULT_GRPC_LISTEN_PORT, DEFAULT_HTTP_LISTEN_PORT};
|
||||||
use pageserver_api::models::{self, TenantInfo, TimelineInfo};
|
use pageserver_api::models::{self, TenantInfo, TimelineInfo};
|
||||||
use pageserver_api::shard::TenantShardId;
|
use pageserver_api::shard::TenantShardId;
|
||||||
use pageserver_client::mgmt_api;
|
use pageserver_client::mgmt_api;
|
||||||
use postgres_backend::AuthType;
|
use postgres_backend::AuthType;
|
||||||
use postgres_connection::{PgConnectionConfig, parse_host_port};
|
use postgres_connection::{PgConnectionConfig, parse_host_port};
|
||||||
|
use safekeeper_api::PgMajorVersion;
|
||||||
use utils::auth::{Claims, Scope};
|
use utils::auth::{Claims, Scope};
|
||||||
use utils::id::{NodeId, TenantId, TimelineId};
|
use utils::id::{NodeId, TenantId, TimelineId};
|
||||||
use utils::lsn::Lsn;
|
use utils::lsn::Lsn;
|
||||||
@@ -120,7 +122,7 @@ impl PageServerNode {
|
|||||||
.env
|
.env
|
||||||
.generate_auth_token(&Claims::new(None, Scope::GenerationsApi))
|
.generate_auth_token(&Claims::new(None, Scope::GenerationsApi))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
overrides.push(format!("control_plane_api_token='{}'", jwt_token));
|
overrides.push(format!("control_plane_api_token='{jwt_token}'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !conf.other.contains_key("remote_storage") {
|
if !conf.other.contains_key("remote_storage") {
|
||||||
@@ -252,9 +254,10 @@ impl PageServerNode {
|
|||||||
// the storage controller
|
// the storage controller
|
||||||
let metadata_path = datadir.join("metadata.json");
|
let metadata_path = datadir.join("metadata.json");
|
||||||
|
|
||||||
let (_http_host, http_port) =
|
let http_host = "localhost".to_string();
|
||||||
|
let (_, http_port) =
|
||||||
parse_host_port(&self.conf.listen_http_addr).expect("Unable to parse listen_http_addr");
|
parse_host_port(&self.conf.listen_http_addr).expect("Unable to parse listen_http_addr");
|
||||||
let http_port = http_port.unwrap_or(9898);
|
let http_port = http_port.unwrap_or(DEFAULT_HTTP_LISTEN_PORT);
|
||||||
|
|
||||||
let https_port = match self.conf.listen_https_addr.as_ref() {
|
let https_port = match self.conf.listen_https_addr.as_ref() {
|
||||||
Some(https_addr) => {
|
Some(https_addr) => {
|
||||||
@@ -265,6 +268,13 @@ impl PageServerNode {
|
|||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (mut grpc_host, mut grpc_port) = (None, None);
|
||||||
|
if let Some(grpc_addr) = &self.conf.listen_grpc_addr {
|
||||||
|
let (_, port) = parse_host_port(grpc_addr).expect("Unable to parse listen_grpc_addr");
|
||||||
|
grpc_host = Some("localhost".to_string());
|
||||||
|
grpc_port = Some(port.unwrap_or(DEFAULT_GRPC_LISTEN_PORT));
|
||||||
|
}
|
||||||
|
|
||||||
// Intentionally hand-craft JSON: this acts as an implicit format compat test
|
// Intentionally hand-craft JSON: this acts as an implicit format compat test
|
||||||
// in case the pageserver-side structure is edited, and reflects the real life
|
// in case the pageserver-side structure is edited, and reflects the real life
|
||||||
// situation: the metadata is written by some other script.
|
// situation: the metadata is written by some other script.
|
||||||
@@ -273,7 +283,9 @@ impl PageServerNode {
|
|||||||
serde_json::to_vec(&pageserver_api::config::NodeMetadata {
|
serde_json::to_vec(&pageserver_api::config::NodeMetadata {
|
||||||
postgres_host: "localhost".to_string(),
|
postgres_host: "localhost".to_string(),
|
||||||
postgres_port: self.pg_connection_config.port(),
|
postgres_port: self.pg_connection_config.port(),
|
||||||
http_host: "localhost".to_string(),
|
grpc_host,
|
||||||
|
grpc_port,
|
||||||
|
http_host,
|
||||||
http_port,
|
http_port,
|
||||||
https_port,
|
https_port,
|
||||||
other: HashMap::from([(
|
other: HashMap::from([(
|
||||||
@@ -291,7 +303,7 @@ impl PageServerNode {
|
|||||||
async fn start_node(&self, retry_timeout: &Duration) -> anyhow::Result<()> {
|
async fn start_node(&self, retry_timeout: &Duration) -> anyhow::Result<()> {
|
||||||
// TODO: using a thread here because start_process() is not async but we need to call check_status()
|
// TODO: using a thread here because start_process() is not async but we need to call check_status()
|
||||||
let datadir = self.repo_path();
|
let datadir = self.repo_path();
|
||||||
print!(
|
println!(
|
||||||
"Starting pageserver node {} at '{}' in {:?}, retrying for {:?}",
|
"Starting pageserver node {} at '{}' in {:?}, retrying for {:?}",
|
||||||
self.conf.id,
|
self.conf.id,
|
||||||
self.pg_connection_config.raw_address(),
|
self.pg_connection_config.raw_address(),
|
||||||
@@ -440,6 +452,12 @@ impl PageServerNode {
|
|||||||
.map(|x| x.parse::<usize>())
|
.map(|x| x.parse::<usize>())
|
||||||
.transpose()
|
.transpose()
|
||||||
.context("Failed to parse 'image_creation_threshold' as non zero integer")?,
|
.context("Failed to parse 'image_creation_threshold' as non zero integer")?,
|
||||||
|
// HADRON
|
||||||
|
image_layer_force_creation_period: settings
|
||||||
|
.remove("image_layer_force_creation_period")
|
||||||
|
.map(humantime::parse_duration)
|
||||||
|
.transpose()
|
||||||
|
.context("Failed to parse 'image_layer_force_creation_period' as duration")?,
|
||||||
image_layer_creation_check_threshold: settings
|
image_layer_creation_check_threshold: settings
|
||||||
.remove("image_layer_creation_check_threshold")
|
.remove("image_layer_creation_check_threshold")
|
||||||
.map(|x| x.parse::<u8>())
|
.map(|x| x.parse::<u8>())
|
||||||
@@ -596,7 +614,7 @@ impl PageServerNode {
|
|||||||
timeline_id: TimelineId,
|
timeline_id: TimelineId,
|
||||||
base: (Lsn, PathBuf),
|
base: (Lsn, PathBuf),
|
||||||
pg_wal: Option<(Lsn, PathBuf)>,
|
pg_wal: Option<(Lsn, PathBuf)>,
|
||||||
pg_version: u32,
|
pg_version: PgMajorVersion,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
// Init base reader
|
// Init base reader
|
||||||
let (start_lsn, base_tarfile_path) = base;
|
let (start_lsn, base_tarfile_path) = base;
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ impl SafekeeperNode {
|
|||||||
extra_opts: &[String],
|
extra_opts: &[String],
|
||||||
retry_timeout: &Duration,
|
retry_timeout: &Duration,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
print!(
|
println!(
|
||||||
"Starting safekeeper at '{}' in '{}', retrying for {:?}",
|
"Starting safekeeper at '{}' in '{}', retrying for {:?}",
|
||||||
self.pg_connection_config.raw_address(),
|
self.pg_connection_config.raw_address(),
|
||||||
self.datadir_path().display(),
|
self.datadir_path().display(),
|
||||||
@@ -143,7 +143,7 @@ impl SafekeeperNode {
|
|||||||
let id_string = id.to_string();
|
let id_string = id.to_string();
|
||||||
// TODO: add availability_zone to the config.
|
// TODO: add availability_zone to the config.
|
||||||
// Right now we just specify any value here and use it to check metrics in tests.
|
// Right now we just specify any value here and use it to check metrics in tests.
|
||||||
let availability_zone = format!("sk-{}", id_string);
|
let availability_zone = format!("sk-{id_string}");
|
||||||
|
|
||||||
let mut args = vec![
|
let mut args = vec![
|
||||||
"-D".to_owned(),
|
"-D".to_owned(),
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ use std::str::FromStr;
|
|||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use crate::background_process;
|
||||||
|
use crate::local_env::{LocalEnv, NeonStorageControllerConf};
|
||||||
use camino::{Utf8Path, Utf8PathBuf};
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
use hyper0::Uri;
|
use hyper0::Uri;
|
||||||
use nix::unistd::Pid;
|
use nix::unistd::Pid;
|
||||||
@@ -22,6 +24,7 @@ use pageserver_client::mgmt_api::ResponseErrorMessageExt;
|
|||||||
use pem::Pem;
|
use pem::Pem;
|
||||||
use postgres_backend::AuthType;
|
use postgres_backend::AuthType;
|
||||||
use reqwest::{Method, Response};
|
use reqwest::{Method, Response};
|
||||||
|
use safekeeper_api::PgMajorVersion;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
@@ -31,9 +34,6 @@ use utils::auth::{Claims, Scope, encode_from_key_file};
|
|||||||
use utils::id::{NodeId, TenantId};
|
use utils::id::{NodeId, TenantId};
|
||||||
use whoami::username;
|
use whoami::username;
|
||||||
|
|
||||||
use crate::background_process;
|
|
||||||
use crate::local_env::{LocalEnv, NeonStorageControllerConf};
|
|
||||||
|
|
||||||
pub struct StorageController {
|
pub struct StorageController {
|
||||||
env: LocalEnv,
|
env: LocalEnv,
|
||||||
private_key: Option<Pem>,
|
private_key: Option<Pem>,
|
||||||
@@ -48,7 +48,7 @@ pub struct StorageController {
|
|||||||
|
|
||||||
const COMMAND: &str = "storage_controller";
|
const COMMAND: &str = "storage_controller";
|
||||||
|
|
||||||
const STORAGE_CONTROLLER_POSTGRES_VERSION: u32 = 16;
|
const STORAGE_CONTROLLER_POSTGRES_VERSION: PgMajorVersion = PgMajorVersion::PG16;
|
||||||
|
|
||||||
const DB_NAME: &str = "storage_controller";
|
const DB_NAME: &str = "storage_controller";
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ impl StorageController {
|
|||||||
fn storage_controller_instance_dir(&self, instance_id: u8) -> PathBuf {
|
fn storage_controller_instance_dir(&self, instance_id: u8) -> PathBuf {
|
||||||
self.env
|
self.env
|
||||||
.base_data_dir
|
.base_data_dir
|
||||||
.join(format!("storage_controller_{}", instance_id))
|
.join(format!("storage_controller_{instance_id}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pid_file(&self, instance_id: u8) -> Utf8PathBuf {
|
fn pid_file(&self, instance_id: u8) -> Utf8PathBuf {
|
||||||
@@ -184,9 +184,15 @@ impl StorageController {
|
|||||||
/// to other versions if that one isn't found. Some automated tests create circumstances
|
/// to other versions if that one isn't found. Some automated tests create circumstances
|
||||||
/// where only one version is available in pg_distrib_dir, such as `test_remote_extensions`.
|
/// where only one version is available in pg_distrib_dir, such as `test_remote_extensions`.
|
||||||
async fn get_pg_dir(&self, dir_name: &str) -> anyhow::Result<Utf8PathBuf> {
|
async fn get_pg_dir(&self, dir_name: &str) -> anyhow::Result<Utf8PathBuf> {
|
||||||
let prefer_versions = [STORAGE_CONTROLLER_POSTGRES_VERSION, 16, 15, 14];
|
const PREFER_VERSIONS: [PgMajorVersion; 5] = [
|
||||||
|
STORAGE_CONTROLLER_POSTGRES_VERSION,
|
||||||
|
PgMajorVersion::PG16,
|
||||||
|
PgMajorVersion::PG15,
|
||||||
|
PgMajorVersion::PG14,
|
||||||
|
PgMajorVersion::PG17,
|
||||||
|
];
|
||||||
|
|
||||||
for v in prefer_versions {
|
for v in PREFER_VERSIONS {
|
||||||
let path = Utf8PathBuf::from_path_buf(self.env.pg_dir(v, dir_name)?).unwrap();
|
let path = Utf8PathBuf::from_path_buf(self.env.pg_dir(v, dir_name)?).unwrap();
|
||||||
if tokio::fs::try_exists(&path).await? {
|
if tokio::fs::try_exists(&path).await? {
|
||||||
return Ok(path);
|
return Ok(path);
|
||||||
@@ -220,7 +226,7 @@ impl StorageController {
|
|||||||
"-d",
|
"-d",
|
||||||
DB_NAME,
|
DB_NAME,
|
||||||
"-p",
|
"-p",
|
||||||
&format!("{}", postgres_port),
|
&format!("{postgres_port}"),
|
||||||
];
|
];
|
||||||
let pg_lib_dir = self.get_pg_lib_dir().await.unwrap();
|
let pg_lib_dir = self.get_pg_lib_dir().await.unwrap();
|
||||||
let envs = [
|
let envs = [
|
||||||
@@ -263,7 +269,7 @@ impl StorageController {
|
|||||||
"-h",
|
"-h",
|
||||||
"localhost",
|
"localhost",
|
||||||
"-p",
|
"-p",
|
||||||
&format!("{}", postgres_port),
|
&format!("{postgres_port}"),
|
||||||
"-U",
|
"-U",
|
||||||
&username(),
|
&username(),
|
||||||
"-O",
|
"-O",
|
||||||
@@ -425,7 +431,7 @@ impl StorageController {
|
|||||||
// from `LocalEnv`'s config file (`.neon/config`).
|
// from `LocalEnv`'s config file (`.neon/config`).
|
||||||
tokio::fs::write(
|
tokio::fs::write(
|
||||||
&pg_data_path.join("postgresql.conf"),
|
&pg_data_path.join("postgresql.conf"),
|
||||||
format!("port = {}\nfsync=off\n", postgres_port),
|
format!("port = {postgres_port}\nfsync=off\n"),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -477,7 +483,7 @@ impl StorageController {
|
|||||||
self.setup_database(postgres_port).await?;
|
self.setup_database(postgres_port).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let database_url = format!("postgresql://localhost:{}/{DB_NAME}", postgres_port);
|
let database_url = format!("postgresql://localhost:{postgres_port}/{DB_NAME}");
|
||||||
|
|
||||||
// We support running a startup SQL script to fiddle with the database before we launch storcon.
|
// We support running a startup SQL script to fiddle with the database before we launch storcon.
|
||||||
// This is used by the test suite.
|
// This is used by the test suite.
|
||||||
@@ -508,7 +514,7 @@ impl StorageController {
|
|||||||
drop(client);
|
drop(client);
|
||||||
conn.await??;
|
conn.await??;
|
||||||
|
|
||||||
let addr = format!("{}:{}", host, listen_port);
|
let addr = format!("{host}:{listen_port}");
|
||||||
let address_for_peers = Uri::builder()
|
let address_for_peers = Uri::builder()
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.authority(addr.clone())
|
.authority(addr.clone())
|
||||||
@@ -557,6 +563,10 @@ impl StorageController {
|
|||||||
args.push("--use-local-compute-notifications".to_string());
|
args.push("--use-local-compute-notifications".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(value) = self.config.kick_secondary_downloads {
|
||||||
|
args.push(format!("--kick-secondary-downloads={value}"));
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ssl_ca_file) = self.env.ssl_ca_cert_path() {
|
if let Some(ssl_ca_file) = self.env.ssl_ca_cert_path() {
|
||||||
args.push(format!("--ssl-ca-file={}", ssl_ca_file.to_str().unwrap()));
|
args.push(format!("--ssl-ca-file={}", ssl_ca_file.to_str().unwrap()));
|
||||||
}
|
}
|
||||||
@@ -628,17 +638,43 @@ impl StorageController {
|
|||||||
args.push("--timelines-onto-safekeepers".to_string());
|
args.push("--timelines-onto-safekeepers".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Starting storage controller");
|
// neon_local is used in test environments where we often have less than 3 safekeepers.
|
||||||
|
if self.config.timeline_safekeeper_count.is_some() || self.env.safekeepers.len() < 3 {
|
||||||
|
let sk_cnt = self
|
||||||
|
.config
|
||||||
|
.timeline_safekeeper_count
|
||||||
|
.unwrap_or(self.env.safekeepers.len());
|
||||||
|
|
||||||
|
args.push(format!("--timeline-safekeeper-count={sk_cnt}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(duration) = self.config.shard_split_request_timeout {
|
||||||
|
args.push(format!(
|
||||||
|
"--shard-split-request-timeout={}",
|
||||||
|
humantime::Duration::from(duration)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut envs = vec![
|
||||||
|
("LD_LIBRARY_PATH".to_owned(), pg_lib_dir.to_string()),
|
||||||
|
("DYLD_LIBRARY_PATH".to_owned(), pg_lib_dir.to_string()),
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Some(posthog_config) = &self.config.posthog_config {
|
||||||
|
envs.push((
|
||||||
|
"POSTHOG_CONFIG".to_string(),
|
||||||
|
serde_json::to_string(posthog_config)?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Starting storage controller at {scheme}://{host}:{listen_port}");
|
||||||
|
|
||||||
background_process::start_process(
|
background_process::start_process(
|
||||||
COMMAND,
|
COMMAND,
|
||||||
&instance_dir,
|
&instance_dir,
|
||||||
&self.env.storage_controller_bin(),
|
&self.env.storage_controller_bin(),
|
||||||
args,
|
args,
|
||||||
vec![
|
envs,
|
||||||
("LD_LIBRARY_PATH".to_owned(), pg_lib_dir.to_string()),
|
|
||||||
("DYLD_LIBRARY_PATH".to_owned(), pg_lib_dir.to_string()),
|
|
||||||
],
|
|
||||||
background_process::InitialPidFile::Create(self.pid_file(start_args.instance_id)),
|
background_process::InitialPidFile::Create(self.pid_file(start_args.instance_id)),
|
||||||
&start_args.start_timeout,
|
&start_args.start_timeout,
|
||||||
|| async {
|
|| async {
|
||||||
@@ -802,9 +838,9 @@ impl StorageController {
|
|||||||
builder = builder.json(&body)
|
builder = builder.json(&body)
|
||||||
}
|
}
|
||||||
if let Some(private_key) = &self.private_key {
|
if let Some(private_key) = &self.private_key {
|
||||||
println!("Getting claims for path {}", path);
|
println!("Getting claims for path {path}");
|
||||||
if let Some(required_claims) = Self::get_claims_for_path(&path)? {
|
if let Some(required_claims) = Self::get_claims_for_path(&path)? {
|
||||||
println!("Got claims {:?} for path {}", required_claims, path);
|
println!("Got claims {required_claims:?} for path {path}");
|
||||||
let jwt_token = encode_from_key_file(&required_claims, private_key)?;
|
let jwt_token = encode_from_key_file(&required_claims, private_key)?;
|
||||||
builder = builder.header(
|
builder = builder.header(
|
||||||
reqwest::header::AUTHORIZATION,
|
reqwest::header::AUTHORIZATION,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ humantime.workspace = true
|
|||||||
pageserver_api.workspace = true
|
pageserver_api.workspace = true
|
||||||
pageserver_client.workspace = true
|
pageserver_client.workspace = true
|
||||||
reqwest.workspace = true
|
reqwest.workspace = true
|
||||||
|
safekeeper_api.workspace=true
|
||||||
serde_json = { workspace = true, features = ["raw_value"] }
|
serde_json = { workspace = true, features = ["raw_value"] }
|
||||||
storage_controller_client.workspace = true
|
storage_controller_client.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use pageserver_api::controller_api::{
|
|||||||
PlacementPolicy, SafekeeperDescribeResponse, SafekeeperSchedulingPolicyRequest,
|
PlacementPolicy, SafekeeperDescribeResponse, SafekeeperSchedulingPolicyRequest,
|
||||||
ShardSchedulingPolicy, ShardsPreferredAzsRequest, ShardsPreferredAzsResponse,
|
ShardSchedulingPolicy, ShardsPreferredAzsRequest, ShardsPreferredAzsResponse,
|
||||||
SkSchedulingPolicy, TenantCreateRequest, TenantDescribeResponse, TenantPolicyRequest,
|
SkSchedulingPolicy, TenantCreateRequest, TenantDescribeResponse, TenantPolicyRequest,
|
||||||
TenantShardMigrateRequest, TenantShardMigrateResponse,
|
TenantShardMigrateRequest, TenantShardMigrateResponse, TimelineSafekeeperMigrateRequest,
|
||||||
};
|
};
|
||||||
use pageserver_api::models::{
|
use pageserver_api::models::{
|
||||||
EvictionPolicy, EvictionPolicyLayerAccessThreshold, ShardParameters, TenantConfig,
|
EvictionPolicy, EvictionPolicyLayerAccessThreshold, ShardParameters, TenantConfig,
|
||||||
@@ -21,6 +21,7 @@ use pageserver_api::models::{
|
|||||||
use pageserver_api::shard::{ShardStripeSize, TenantShardId};
|
use pageserver_api::shard::{ShardStripeSize, TenantShardId};
|
||||||
use pageserver_client::mgmt_api::{self};
|
use pageserver_client::mgmt_api::{self};
|
||||||
use reqwest::{Certificate, Method, StatusCode, Url};
|
use reqwest::{Certificate, Method, StatusCode, Url};
|
||||||
|
use safekeeper_api::models::TimelineLocateResponse;
|
||||||
use storage_controller_client::control_api::Client;
|
use storage_controller_client::control_api::Client;
|
||||||
use utils::id::{NodeId, TenantId, TimelineId};
|
use utils::id::{NodeId, TenantId, TimelineId};
|
||||||
|
|
||||||
@@ -36,6 +37,10 @@ enum Command {
|
|||||||
listen_pg_addr: String,
|
listen_pg_addr: String,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
listen_pg_port: u16,
|
listen_pg_port: u16,
|
||||||
|
#[arg(long)]
|
||||||
|
listen_grpc_addr: Option<String>,
|
||||||
|
#[arg(long)]
|
||||||
|
listen_grpc_port: Option<u16>,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
listen_http_addr: String,
|
listen_http_addr: String,
|
||||||
@@ -61,10 +66,31 @@ enum Command {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
scheduling: Option<NodeSchedulingPolicy>,
|
scheduling: Option<NodeSchedulingPolicy>,
|
||||||
},
|
},
|
||||||
|
/// Exists for backup usage and will be removed in future.
|
||||||
|
/// Use [`Command::NodeStartDelete`] instead, if possible.
|
||||||
NodeDelete {
|
NodeDelete {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
},
|
},
|
||||||
|
/// Start deletion of the specified pageserver.
|
||||||
|
NodeStartDelete {
|
||||||
|
#[arg(long)]
|
||||||
|
node_id: NodeId,
|
||||||
|
},
|
||||||
|
/// Cancel deletion of the specified pageserver and wait for `timeout`
|
||||||
|
/// for the operation to be canceled. May be retried.
|
||||||
|
NodeCancelDelete {
|
||||||
|
#[arg(long)]
|
||||||
|
node_id: NodeId,
|
||||||
|
#[arg(long)]
|
||||||
|
timeout: humantime::Duration,
|
||||||
|
},
|
||||||
|
/// Delete a tombstone of node from the storage controller.
|
||||||
|
/// This is used when we want to allow the node to be re-registered.
|
||||||
|
NodeDeleteTombstone {
|
||||||
|
#[arg(long)]
|
||||||
|
node_id: NodeId,
|
||||||
|
},
|
||||||
/// Modify a tenant's policies in the storage controller
|
/// Modify a tenant's policies in the storage controller
|
||||||
TenantPolicy {
|
TenantPolicy {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
@@ -82,6 +108,8 @@ enum Command {
|
|||||||
},
|
},
|
||||||
/// List nodes known to the storage controller
|
/// List nodes known to the storage controller
|
||||||
Nodes {},
|
Nodes {},
|
||||||
|
/// List soft deleted nodes known to the storage controller
|
||||||
|
NodeTombstones {},
|
||||||
/// List tenants known to the storage controller
|
/// List tenants known to the storage controller
|
||||||
Tenants {
|
Tenants {
|
||||||
/// If this field is set, it will list the tenants on a specific node
|
/// If this field is set, it will list the tenants on a specific node
|
||||||
@@ -252,6 +280,23 @@ enum Command {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
concurrency: Option<usize>,
|
concurrency: Option<usize>,
|
||||||
},
|
},
|
||||||
|
/// Locate safekeepers for a timeline from the storcon DB.
|
||||||
|
TimelineLocate {
|
||||||
|
#[arg(long)]
|
||||||
|
tenant_id: TenantId,
|
||||||
|
#[arg(long)]
|
||||||
|
timeline_id: TimelineId,
|
||||||
|
},
|
||||||
|
/// Migrate a timeline to a new set of safekeepers
|
||||||
|
TimelineSafekeeperMigrate {
|
||||||
|
#[arg(long)]
|
||||||
|
tenant_id: TenantId,
|
||||||
|
#[arg(long)]
|
||||||
|
timeline_id: TimelineId,
|
||||||
|
/// Example: --new-sk-set 1,2,3
|
||||||
|
#[arg(long, required = true, value_delimiter = ',')]
|
||||||
|
new_sk_set: Vec<NodeId>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@@ -410,6 +455,8 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
node_id,
|
node_id,
|
||||||
listen_pg_addr,
|
listen_pg_addr,
|
||||||
listen_pg_port,
|
listen_pg_port,
|
||||||
|
listen_grpc_addr,
|
||||||
|
listen_grpc_port,
|
||||||
listen_http_addr,
|
listen_http_addr,
|
||||||
listen_http_port,
|
listen_http_port,
|
||||||
listen_https_port,
|
listen_https_port,
|
||||||
@@ -423,10 +470,13 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
node_id,
|
node_id,
|
||||||
listen_pg_addr,
|
listen_pg_addr,
|
||||||
listen_pg_port,
|
listen_pg_port,
|
||||||
|
listen_grpc_addr,
|
||||||
|
listen_grpc_port,
|
||||||
listen_http_addr,
|
listen_http_addr,
|
||||||
listen_http_port,
|
listen_http_port,
|
||||||
listen_https_port,
|
listen_https_port,
|
||||||
availability_zone_id: AvailabilityZone(availability_zone_id),
|
availability_zone_id: AvailabilityZone(availability_zone_id),
|
||||||
|
node_ip_addr: None,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -633,7 +683,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
response
|
response
|
||||||
.new_shards
|
.new_shards
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| format!("{:?}", s))
|
.map(|s| format!("{s:?}"))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(",")
|
.join(",")
|
||||||
);
|
);
|
||||||
@@ -755,8 +805,8 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
println!("Tenant {tenant_id}");
|
println!("Tenant {tenant_id}");
|
||||||
let mut table = comfy_table::Table::new();
|
let mut table = comfy_table::Table::new();
|
||||||
table.add_row(["Policy", &format!("{:?}", policy)]);
|
table.add_row(["Policy", &format!("{policy:?}")]);
|
||||||
table.add_row(["Stripe size", &format!("{:?}", stripe_size)]);
|
table.add_row(["Stripe size", &format!("{stripe_size:?}")]);
|
||||||
table.add_row(["Config", &serde_json::to_string_pretty(&config).unwrap()]);
|
table.add_row(["Config", &serde_json::to_string_pretty(&config).unwrap()]);
|
||||||
println!("{table}");
|
println!("{table}");
|
||||||
println!("Shards:");
|
println!("Shards:");
|
||||||
@@ -773,7 +823,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let secondary = shard
|
let secondary = shard
|
||||||
.node_secondary
|
.node_secondary
|
||||||
.iter()
|
.iter()
|
||||||
.map(|n| format!("{}", n))
|
.map(|n| format!("{n}"))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(",");
|
.join(",");
|
||||||
|
|
||||||
@@ -847,7 +897,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Make it obvious to the user that since they've omitted an AZ, we're clearing it
|
// Make it obvious to the user that since they've omitted an AZ, we're clearing it
|
||||||
eprintln!("Clearing preferred AZ for tenant {}", tenant_id);
|
eprintln!("Clearing preferred AZ for tenant {tenant_id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct a request that modifies all the tenant's shards
|
// Construct a request that modifies all the tenant's shards
|
||||||
@@ -896,10 +946,76 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
Command::NodeDelete { node_id } => {
|
Command::NodeDelete { node_id } => {
|
||||||
|
eprintln!("Warning: This command is obsolete and will be removed in a future version");
|
||||||
|
eprintln!("Use `NodeStartDelete` instead, if possible");
|
||||||
storcon_client
|
storcon_client
|
||||||
.dispatch::<(), ()>(Method::DELETE, format!("control/v1/node/{node_id}"), None)
|
.dispatch::<(), ()>(Method::DELETE, format!("control/v1/node/{node_id}"), None)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
Command::NodeStartDelete { node_id } => {
|
||||||
|
storcon_client
|
||||||
|
.dispatch::<(), ()>(
|
||||||
|
Method::PUT,
|
||||||
|
format!("control/v1/node/{node_id}/delete"),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
println!("Delete started for {node_id}");
|
||||||
|
}
|
||||||
|
Command::NodeCancelDelete { node_id, timeout } => {
|
||||||
|
storcon_client
|
||||||
|
.dispatch::<(), ()>(
|
||||||
|
Method::DELETE,
|
||||||
|
format!("control/v1/node/{node_id}/delete"),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("Waiting for node {node_id} to quiesce on scheduling policy ...");
|
||||||
|
|
||||||
|
let final_policy =
|
||||||
|
wait_for_scheduling_policy(storcon_client, node_id, *timeout, |sched| {
|
||||||
|
!matches!(sched, NodeSchedulingPolicy::Deleting)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Delete was cancelled for node {node_id}. Schedulling policy is now {final_policy:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Command::NodeDeleteTombstone { node_id } => {
|
||||||
|
storcon_client
|
||||||
|
.dispatch::<(), ()>(
|
||||||
|
Method::DELETE,
|
||||||
|
format!("debug/v1/tombstone/{node_id}"),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Command::NodeTombstones {} => {
|
||||||
|
let mut resp = storcon_client
|
||||||
|
.dispatch::<(), Vec<NodeDescribeResponse>>(
|
||||||
|
Method::GET,
|
||||||
|
"debug/v1/tombstone".to_string(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
resp.sort_by(|a, b| a.listen_http_addr.cmp(&b.listen_http_addr));
|
||||||
|
|
||||||
|
let mut table = comfy_table::Table::new();
|
||||||
|
table.set_header(["Id", "Hostname", "AZ", "Scheduling", "Availability"]);
|
||||||
|
for node in resp {
|
||||||
|
table.add_row([
|
||||||
|
format!("{}", node.id),
|
||||||
|
node.listen_http_addr,
|
||||||
|
node.availability_zone_id,
|
||||||
|
format!("{:?}", node.scheduling),
|
||||||
|
format!("{:?}", node.availability),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
println!("{table}");
|
||||||
|
}
|
||||||
Command::TenantSetTimeBasedEviction {
|
Command::TenantSetTimeBasedEviction {
|
||||||
tenant_id,
|
tenant_id,
|
||||||
period,
|
period,
|
||||||
@@ -1085,8 +1201,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
Err((tenant_shard_id, from, to, error)) => {
|
Err((tenant_shard_id, from, to, error)) => {
|
||||||
failure += 1;
|
failure += 1;
|
||||||
println!(
|
println!(
|
||||||
"Failed to migrate {} from node {} to node {}: {}",
|
"Failed to migrate {tenant_shard_id} from node {from} to node {to}: {error}"
|
||||||
tenant_shard_id, from, to, error
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1228,8 +1343,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
concurrency,
|
concurrency,
|
||||||
} => {
|
} => {
|
||||||
let mut path = format!(
|
let mut path = format!(
|
||||||
"/v1/tenant/{}/timeline/{}/download_heatmap_layers",
|
"v1/tenant/{tenant_shard_id}/timeline/{timeline_id}/download_heatmap_layers",
|
||||||
tenant_shard_id, timeline_id,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(c) = concurrency {
|
if let Some(c) = concurrency {
|
||||||
@@ -1240,6 +1354,41 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.dispatch::<(), ()>(Method::POST, path, None)
|
.dispatch::<(), ()>(Method::POST, path, None)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
Command::TimelineLocate {
|
||||||
|
tenant_id,
|
||||||
|
timeline_id,
|
||||||
|
} => {
|
||||||
|
let path = format!("debug/v1/tenant/{tenant_id}/timeline/{timeline_id}/locate");
|
||||||
|
|
||||||
|
let resp = storcon_client
|
||||||
|
.dispatch::<(), TimelineLocateResponse>(Method::GET, path, None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let sk_set = resp.sk_set.iter().map(|id| id.0 as i64).collect::<Vec<_>>();
|
||||||
|
let new_sk_set = resp
|
||||||
|
.new_sk_set
|
||||||
|
.as_ref()
|
||||||
|
.map(|ids| ids.iter().map(|id| id.0 as i64).collect::<Vec<_>>());
|
||||||
|
|
||||||
|
println!("generation = {}", resp.generation);
|
||||||
|
println!("sk_set = {sk_set:?}");
|
||||||
|
println!("new_sk_set = {new_sk_set:?}");
|
||||||
|
}
|
||||||
|
Command::TimelineSafekeeperMigrate {
|
||||||
|
tenant_id,
|
||||||
|
timeline_id,
|
||||||
|
new_sk_set,
|
||||||
|
} => {
|
||||||
|
let path = format!("v1/tenant/{tenant_id}/timeline/{timeline_id}/safekeeper_migrate");
|
||||||
|
|
||||||
|
storcon_client
|
||||||
|
.dispatch::<_, ()>(
|
||||||
|
Method::POST,
|
||||||
|
path,
|
||||||
|
Some(TimelineSafekeeperMigrateRequest { new_sk_set }),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1254,8 +1403,7 @@ async fn watch_tenant_shard(
|
|||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if let Some(until_migrated_to) = until_migrated_to {
|
if let Some(until_migrated_to) = until_migrated_to {
|
||||||
println!(
|
println!(
|
||||||
"Waiting for tenant shard {} to be migrated to node {}",
|
"Waiting for tenant shard {tenant_shard_id} to be migrated to node {until_migrated_to}"
|
||||||
tenant_shard_id, until_migrated_to
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1278,7 +1426,7 @@ async fn watch_tenant_shard(
|
|||||||
"attached: {} secondary: {} {}",
|
"attached: {} secondary: {} {}",
|
||||||
shard
|
shard
|
||||||
.node_attached
|
.node_attached
|
||||||
.map(|n| format!("{}", n))
|
.map(|n| format!("{n}"))
|
||||||
.unwrap_or("none".to_string()),
|
.unwrap_or("none".to_string()),
|
||||||
shard
|
shard
|
||||||
.node_secondary
|
.node_secondary
|
||||||
@@ -1292,15 +1440,12 @@ async fn watch_tenant_shard(
|
|||||||
"(reconciler idle)"
|
"(reconciler idle)"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
println!("{}", summary);
|
println!("{summary}");
|
||||||
|
|
||||||
// Maybe drop out if we finished migration
|
// Maybe drop out if we finished migration
|
||||||
if let Some(until_migrated_to) = until_migrated_to {
|
if let Some(until_migrated_to) = until_migrated_to {
|
||||||
if shard.node_attached == Some(until_migrated_to) && !shard.is_reconciling {
|
if shard.node_attached == Some(until_migrated_to) && !shard.is_reconciling {
|
||||||
println!(
|
println!("Tenant shard {tenant_shard_id} is now on node {until_migrated_to}");
|
||||||
"Tenant shard {} is now on node {}",
|
|
||||||
tenant_shard_id, until_migrated_to
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,14 +54,16 @@ else
|
|||||||
printf '%s\n' "${result}" | jq .
|
printf '%s\n' "${result}" | jq .
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Check if a timeline present"
|
if [[ "${RUN_PARALLEL:-false}" != "true" ]]; then
|
||||||
PARAMS=(
|
echo "Check if a timeline present"
|
||||||
-X GET
|
PARAMS=(
|
||||||
-H "Content-Type: application/json"
|
-X GET
|
||||||
"http://pageserver:9898/v1/tenant/${tenant_id}/timeline"
|
-H "Content-Type: application/json"
|
||||||
)
|
"http://pageserver:9898/v1/tenant/${tenant_id}/timeline"
|
||||||
timeline_id=$(curl "${PARAMS[@]}" | jq -r .[0].timeline_id)
|
)
|
||||||
if [[ -z "${timeline_id}" || "${timeline_id}" = null ]]; then
|
timeline_id=$(curl "${PARAMS[@]}" | jq -r .[0].timeline_id)
|
||||||
|
fi
|
||||||
|
if [[ -z "${timeline_id:-}" || "${timeline_id:-}" = null ]]; then
|
||||||
generate_id timeline_id
|
generate_id timeline_id
|
||||||
PARAMS=(
|
PARAMS=(
|
||||||
-sbf
|
-sbf
|
||||||
@@ -95,3 +97,4 @@ echo "Start compute node"
|
|||||||
-b /usr/local/bin/postgres \
|
-b /usr/local/bin/postgres \
|
||||||
--compute-id "compute-${RANDOM}" \
|
--compute-id "compute-${RANDOM}" \
|
||||||
--config "${CONFIG_FILE}"
|
--config "${CONFIG_FILE}"
|
||||||
|
--dev
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
"timestamp": "2022-10-12T18:00:00.000Z",
|
"timestamp": "2022-10-12T18:00:00.000Z",
|
||||||
"operation_uuid": "0f657b36-4b0f-4a2d-9c2e-1dcd615e7d8c",
|
"operation_uuid": "0f657b36-4b0f-4a2d-9c2e-1dcd615e7d8c",
|
||||||
|
"suspend_timeout_seconds": -1,
|
||||||
|
|
||||||
"cluster": {
|
"cluster": {
|
||||||
"cluster_id": "docker_compose",
|
"cluster_id": "docker_compose",
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ services:
|
|||||||
- "storage_broker"
|
- "storage_broker"
|
||||||
- "--listen-addr=0.0.0.0:50051"
|
- "--listen-addr=0.0.0.0:50051"
|
||||||
|
|
||||||
compute:
|
compute1:
|
||||||
restart: always
|
restart: always
|
||||||
build:
|
build:
|
||||||
context: ./compute_wrapper/
|
context: ./compute_wrapper/
|
||||||
@@ -152,6 +152,7 @@ services:
|
|||||||
- TAG=${COMPUTE_TAG:-${TAG:-latest}}
|
- TAG=${COMPUTE_TAG:-${TAG:-latest}}
|
||||||
- http_proxy=${http_proxy:-}
|
- http_proxy=${http_proxy:-}
|
||||||
- https_proxy=${https_proxy:-}
|
- https_proxy=${https_proxy:-}
|
||||||
|
image: built-compute
|
||||||
environment:
|
environment:
|
||||||
- PG_VERSION=${PG_VERSION:-16}
|
- PG_VERSION=${PG_VERSION:-16}
|
||||||
- TENANT_ID=${TENANT_ID:-}
|
- TENANT_ID=${TENANT_ID:-}
|
||||||
@@ -166,6 +167,11 @@ services:
|
|||||||
- 3080:3080 # http endpoints
|
- 3080:3080 # http endpoints
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- "/shell/compute.sh"
|
- "/shell/compute.sh"
|
||||||
|
# Ad an alias for compute1 for compatibility
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases:
|
||||||
|
- compute
|
||||||
depends_on:
|
depends_on:
|
||||||
- safekeeper1
|
- safekeeper1
|
||||||
- safekeeper2
|
- safekeeper2
|
||||||
@@ -174,15 +180,20 @@ services:
|
|||||||
|
|
||||||
compute_is_ready:
|
compute_is_ready:
|
||||||
image: postgres:latest
|
image: postgres:latest
|
||||||
|
environment:
|
||||||
|
- PARALLEL_COMPUTES=1
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- "/bin/bash"
|
- "/bin/sh"
|
||||||
- "-c"
|
- "-c"
|
||||||
command:
|
command:
|
||||||
- "until pg_isready -h compute -p 55433 -U cloud_admin ; do
|
- "for i in $(seq 1 $${PARALLEL_COMPUTES}); do
|
||||||
echo 'Waiting to start compute...' && sleep 1;
|
until pg_isready -h compute$$i -p 55433 -U cloud_admin ; do
|
||||||
done"
|
sleep 1;
|
||||||
|
done;
|
||||||
|
done;
|
||||||
|
echo All computes are started"
|
||||||
depends_on:
|
depends_on:
|
||||||
- compute
|
- compute1
|
||||||
|
|
||||||
neon-test-extensions:
|
neon-test-extensions:
|
||||||
profiles: ["test-extensions"]
|
profiles: ["test-extensions"]
|
||||||
@@ -196,4 +207,4 @@ services:
|
|||||||
command:
|
command:
|
||||||
- sleep 3600
|
- sleep 3600
|
||||||
depends_on:
|
depends_on:
|
||||||
- compute
|
- compute1
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# A basic test to ensure Docker images are built correctly.
|
# A basic test to ensure Docker images are built correctly.
|
||||||
# Build a wrapper around the compute, start all services and runs a simple SQL query.
|
# Build a wrapper around the compute, start all services and runs a simple SQL query.
|
||||||
@@ -13,9 +13,36 @@
|
|||||||
#
|
#
|
||||||
set -eux -o pipefail
|
set -eux -o pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "${0}")"
|
||||||
export COMPOSE_FILE='docker-compose.yml'
|
export COMPOSE_FILE='docker-compose.yml'
|
||||||
export COMPOSE_PROFILES=test-extensions
|
export COMPOSE_PROFILES=test-extensions
|
||||||
cd "$(dirname "${0}")"
|
export PARALLEL_COMPUTES=${PARALLEL_COMPUTES:-1}
|
||||||
|
READY_MESSAGE="All computes are started"
|
||||||
|
COMPUTES=()
|
||||||
|
for i in $(seq 1 "${PARALLEL_COMPUTES}"); do
|
||||||
|
COMPUTES+=("compute${i}")
|
||||||
|
done
|
||||||
|
CURRENT_TMPDIR=$(mktemp -d)
|
||||||
|
trap 'rm -rf ${CURRENT_TMPDIR} docker-compose-parallel.yml' EXIT
|
||||||
|
if [[ ${PARALLEL_COMPUTES} -gt 1 ]]; then
|
||||||
|
export COMPOSE_FILE=docker-compose-parallel.yml
|
||||||
|
cp docker-compose.yml docker-compose-parallel.yml
|
||||||
|
# Replace the environment variable PARALLEL_COMPUTES with the actual value
|
||||||
|
yq eval -i ".services.compute_is_ready.environment |= map(select(. | test(\"^PARALLEL_COMPUTES=\") | not)) + [\"PARALLEL_COMPUTES=${PARALLEL_COMPUTES}\"]" ${COMPOSE_FILE}
|
||||||
|
for i in $(seq 2 "${PARALLEL_COMPUTES}"); do
|
||||||
|
# Duplicate compute1 as compute${i} for parallel execution
|
||||||
|
yq eval -i ".services.compute${i} = .services.compute1" ${COMPOSE_FILE}
|
||||||
|
# We don't need these sections, so delete them
|
||||||
|
yq eval -i "(del .services.compute${i}.build) | (del .services.compute${i}.ports) | (del .services.compute${i}.networks)" ${COMPOSE_FILE}
|
||||||
|
# Let the compute 1 be the only dependence
|
||||||
|
yq eval -i ".services.compute${i}.depends_on = [\"compute1\"]" ${COMPOSE_FILE}
|
||||||
|
# Set RUN_PARALLEL=true for compute2. They will generate tenant_id and timeline_id to avoid using the same as other computes
|
||||||
|
yq eval -i ".services.compute${i}.environment += [\"RUN_PARALLEL=true\"]" ${COMPOSE_FILE}
|
||||||
|
# Remove TENANT_ID and TIMELINE_ID from the environment variables of the generated computes
|
||||||
|
# They will create new TENANT_ID and TIMELINE_ID anyway.
|
||||||
|
yq eval -i ".services.compute${i}.environment |= map(select(. | (test(\"^TENANT_ID=\") or test(\"^TIMELINE_ID=\")) | not))" ${COMPOSE_FILE}
|
||||||
|
done
|
||||||
|
fi
|
||||||
PSQL_OPTION="-h localhost -U cloud_admin -p 55433 -d postgres"
|
PSQL_OPTION="-h localhost -U cloud_admin -p 55433 -d postgres"
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
@@ -27,11 +54,11 @@ function cleanup() {
|
|||||||
|
|
||||||
for pg_version in ${TEST_VERSION_ONLY-14 15 16 17}; do
|
for pg_version in ${TEST_VERSION_ONLY-14 15 16 17}; do
|
||||||
pg_version=${pg_version/v/}
|
pg_version=${pg_version/v/}
|
||||||
echo "clean up containers if exists"
|
echo "clean up containers if exist"
|
||||||
cleanup
|
cleanup
|
||||||
PG_TEST_VERSION=$((pg_version < 16 ? 16 : pg_version))
|
PG_TEST_VERSION=$((pg_version < 16 ? 16 : pg_version))
|
||||||
PG_VERSION=${pg_version} PG_TEST_VERSION=${PG_TEST_VERSION} docker compose up --quiet-pull --build -d
|
PG_VERSION=${pg_version} PG_TEST_VERSION=${PG_TEST_VERSION} docker compose build compute1
|
||||||
|
PG_VERSION=${pg_version} PG_TEST_VERSION=${PG_TEST_VERSION} docker compose up --quiet-pull -d
|
||||||
echo "wait until the compute is ready. timeout after 60s. "
|
echo "wait until the compute is ready. timeout after 60s. "
|
||||||
cnt=0
|
cnt=0
|
||||||
while sleep 3; do
|
while sleep 3; do
|
||||||
@@ -41,45 +68,50 @@ for pg_version in ${TEST_VERSION_ONLY-14 15 16 17}; do
|
|||||||
echo "timeout before the compute is ready."
|
echo "timeout before the compute is ready."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if docker compose logs "compute_is_ready" | grep -q "accepting connections"; then
|
if docker compose logs compute_is_ready | grep -q "${READY_MESSAGE}"; then
|
||||||
echo "OK. The compute is ready to connect."
|
echo "OK. The compute is ready to connect."
|
||||||
echo "execute simple queries."
|
echo "execute simple queries."
|
||||||
docker compose exec compute /bin/bash -c "psql ${PSQL_OPTION} -c 'SELECT 1'"
|
for compute in "${COMPUTES[@]}"; do
|
||||||
|
docker compose exec "${compute}" /bin/bash -c "psql ${PSQL_OPTION} -c 'SELECT 1'"
|
||||||
|
done
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ ${pg_version} -ge 16 ]]; then
|
if [[ ${pg_version} -ge 16 ]]; then
|
||||||
# This is required for the pg_hint_plan test, to prevent flaky log message causing the test to fail
|
mkdir "${CURRENT_TMPDIR}"/{pg_hint_plan-src,file_fdw,postgis-src}
|
||||||
# It cannot be moved to Dockerfile now because the database directory is created after the start of the container
|
docker compose cp neon-test-extensions:/ext-src/postgis-src/raster/test "${CURRENT_TMPDIR}/postgis-src/test"
|
||||||
echo Adding dummy config
|
docker compose cp neon-test-extensions:/ext-src/postgis-src/regress/00-regress-install "${CURRENT_TMPDIR}/postgis-src/00-regress-install"
|
||||||
docker compose exec compute touch /var/db/postgres/compute/compute_ctl_temp_override.conf
|
docker compose cp neon-test-extensions:/ext-src/pg_hint_plan-src/data "${CURRENT_TMPDIR}/pg_hint_plan-src/data"
|
||||||
# Prepare for the PostGIS test
|
docker compose cp neon-test-extensions:/postgres/contrib/file_fdw/data "${CURRENT_TMPDIR}/file_fdw/data"
|
||||||
docker compose exec compute mkdir -p /tmp/pgis_reg/pgis_reg_tmp
|
|
||||||
TMPDIR=$(mktemp -d)
|
for compute in "${COMPUTES[@]}"; do
|
||||||
docker compose cp neon-test-extensions:/ext-src/postgis-src/raster/test "${TMPDIR}"
|
# This is required for the pg_hint_plan test, to prevent flaky log message causing the test to fail
|
||||||
docker compose cp neon-test-extensions:/ext-src/postgis-src/regress/00-regress-install "${TMPDIR}"
|
# It cannot be moved to Dockerfile now because the database directory is created after the start of the container
|
||||||
docker compose exec compute mkdir -p /ext-src/postgis-src/raster /ext-src/postgis-src/regress /ext-src/postgis-src/regress/00-regress-install
|
echo Adding dummy config on "${compute}"
|
||||||
docker compose cp "${TMPDIR}/test" compute:/ext-src/postgis-src/raster/test
|
docker compose exec "${compute}" touch /var/db/postgres/compute/compute_ctl_temp_override.conf
|
||||||
docker compose cp "${TMPDIR}/00-regress-install" compute:/ext-src/postgis-src/regress
|
# Prepare for the PostGIS test
|
||||||
rm -rf "${TMPDIR}"
|
docker compose exec "${compute}" mkdir -p /tmp/pgis_reg/pgis_reg_tmp /ext-src/postgis-src/raster /ext-src/postgis-src/regress /ext-src/postgis-src/regress/00-regress-install
|
||||||
# The following block copies the files for the pg_hintplan test to the compute node for the extension test in an isolated docker-compose environment
|
docker compose cp "${CURRENT_TMPDIR}/postgis-src/test" "${compute}":/ext-src/postgis-src/raster/test
|
||||||
TMPDIR=$(mktemp -d)
|
docker compose cp "${CURRENT_TMPDIR}/postgis-src/00-regress-install" "${compute}":/ext-src/postgis-src/regress
|
||||||
docker compose cp neon-test-extensions:/ext-src/pg_hint_plan-src/data "${TMPDIR}/data"
|
# The following block copies the files for the pg_hintplan test to the compute node for the extension test in an isolated docker-compose environment
|
||||||
docker compose cp "${TMPDIR}/data" compute:/ext-src/pg_hint_plan-src/
|
docker compose cp "${CURRENT_TMPDIR}/pg_hint_plan-src/data" "${compute}":/ext-src/pg_hint_plan-src/
|
||||||
rm -rf "${TMPDIR}"
|
# The following block does the same for the contrib/file_fdw test
|
||||||
# The following block does the same for the contrib/file_fdw test
|
docker compose cp "${CURRENT_TMPDIR}/file_fdw/data" "${compute}":/postgres/contrib/file_fdw/data
|
||||||
TMPDIR=$(mktemp -d)
|
done
|
||||||
docker compose cp neon-test-extensions:/postgres/contrib/file_fdw/data "${TMPDIR}/data"
|
|
||||||
docker compose cp "${TMPDIR}/data" compute:/postgres/contrib/file_fdw/data
|
|
||||||
rm -rf "${TMPDIR}"
|
|
||||||
# Apply patches
|
# Apply patches
|
||||||
docker compose exec -T neon-test-extensions bash -c "(cd /postgres && patch -p1)" <"../compute/patches/contrib_pg${pg_version}.patch"
|
docker compose exec -T neon-test-extensions bash -c "(cd /postgres && patch -p1)" <"../compute/patches/contrib_pg${pg_version}.patch"
|
||||||
# We are running tests now
|
# We are running tests now
|
||||||
rm -f testout.txt testout_contrib.txt
|
rm -f testout.txt testout_contrib.txt
|
||||||
|
# We want to run the longest tests first to better utilize parallelization and reduce overall test time.
|
||||||
|
# Tests listed in the RUN_FIRST variable will be run before others.
|
||||||
|
# If parallelization is not used, this environment variable will be ignored.
|
||||||
|
|
||||||
docker compose exec -e USE_PGXS=1 -e SKIP=timescaledb-src,rdkit-src,pg_jsonschema-src,kq_imcx-src,wal2json_2_5-src,rag_jina_reranker_v1_tiny_en-src,rag_bge_small_en_v15-src \
|
docker compose exec -e USE_PGXS=1 -e SKIP=timescaledb-src,rdkit-src,pg_jsonschema-src,kq_imcx-src,wal2json_2_5-src,rag_jina_reranker_v1_tiny_en-src,rag_bge_small_en_v15-src \
|
||||||
|
-e RUN_FIRST=hll-src,postgis-src,pgtap-src -e PARALLEL_COMPUTES="${PARALLEL_COMPUTES}" \
|
||||||
neon-test-extensions /run-tests.sh /ext-src | tee testout.txt && EXT_SUCCESS=1 || EXT_SUCCESS=0
|
neon-test-extensions /run-tests.sh /ext-src | tee testout.txt && EXT_SUCCESS=1 || EXT_SUCCESS=0
|
||||||
docker compose exec -e SKIP=start-scripts,postgres_fdw,ltree_plpython,jsonb_plpython,jsonb_plperl,hstore_plpython,hstore_plperl,dblink,bool_plperl \
|
docker compose exec -e SKIP=start-scripts,postgres_fdw,ltree_plpython,jsonb_plpython,jsonb_plperl,hstore_plpython,hstore_plperl,dblink,bool_plperl \
|
||||||
|
-e PARALLEL_COMPUTES="${PARALLEL_COMPUTES}" \
|
||||||
neon-test-extensions /run-tests.sh /postgres/contrib | tee testout_contrib.txt && CONTRIB_SUCCESS=1 || CONTRIB_SUCCESS=0
|
neon-test-extensions /run-tests.sh /postgres/contrib | tee testout_contrib.txt && CONTRIB_SUCCESS=1 || CONTRIB_SUCCESS=0
|
||||||
if [[ ${EXT_SUCCESS} -eq 0 || ${CONTRIB_SUCCESS} -eq 0 ]]; then
|
if [[ ${EXT_SUCCESS} -eq 0 || ${CONTRIB_SUCCESS} -eq 0 ]]; then
|
||||||
CONTRIB_FAILED=
|
CONTRIB_FAILED=
|
||||||
|
|||||||
+47
-22
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
if [[ -v BENCHMARK_CONNSTR ]]; then
|
if [[ -v BENCHMARK_CONNSTR ]]; then
|
||||||
@@ -26,8 +26,9 @@ if [[ -v BENCHMARK_CONNSTR ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
REGULAR_USER=false
|
REGULAR_USER=false
|
||||||
while getopts r arg; do
|
PARALLEL_COMPUTES=${PARALLEL_COMPUTES:-1}
|
||||||
case $arg in
|
while getopts pr arg; do
|
||||||
|
case ${arg} in
|
||||||
r)
|
r)
|
||||||
REGULAR_USER=true
|
REGULAR_USER=true
|
||||||
shift $((OPTIND-1))
|
shift $((OPTIND-1))
|
||||||
@@ -41,26 +42,49 @@ extdir=${1}
|
|||||||
|
|
||||||
cd "${extdir}" || exit 2
|
cd "${extdir}" || exit 2
|
||||||
FAILED=
|
FAILED=
|
||||||
LIST=$( (echo -e "${SKIP//","/"\n"}"; ls) | sort | uniq -u)
|
export FAILED_FILE=/tmp/failed
|
||||||
for d in ${LIST}; do
|
rm -f ${FAILED_FILE}
|
||||||
[ -d "${d}" ] || continue
|
mapfile -t LIST < <( (echo -e "${SKIP//","/"\n"}"; ls) | sort | uniq -u)
|
||||||
if ! psql -w -c "select 1" >/dev/null; then
|
if [[ ${PARALLEL_COMPUTES} -gt 1 ]]; then
|
||||||
FAILED="${d} ${FAILED}"
|
# Avoid errors if RUN_FIRST is not defined
|
||||||
break
|
RUN_FIRST=${RUN_FIRST:-}
|
||||||
fi
|
# Move entries listed in the RUN_FIRST variable to the beginning
|
||||||
if [[ ${REGULAR_USER} = true ]] && [ -f "${d}"/regular-test.sh ]; then
|
ORDERED_LIST=$(printf "%s\n" "${LIST[@]}" | grep -x -Ff <(echo -e "${RUN_FIRST//,/$'\n'}"); printf "%s\n" "${LIST[@]}" | grep -vx -Ff <(echo -e "${RUN_FIRST//,/$'\n'}"))
|
||||||
"${d}/regular-test.sh" || FAILED="${d} ${FAILED}"
|
parallel -j"${PARALLEL_COMPUTES}" "[[ -d {} ]] || exit 0
|
||||||
continue
|
export PGHOST=compute{%}
|
||||||
fi
|
if ! psql -c 'select 1'>/dev/null; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo Running on \${PGHOST}
|
||||||
|
if [[ -f ${extdir}/{}/neon-test.sh ]]; then
|
||||||
|
echo Running from script
|
||||||
|
${extdir}/{}/neon-test.sh || echo {} >> ${FAILED_FILE};
|
||||||
|
else
|
||||||
|
echo Running using make;
|
||||||
|
USE_PGXS=1 make -C {} installcheck || echo {} >> ${FAILED_FILE};
|
||||||
|
fi" ::: ${ORDERED_LIST}
|
||||||
|
[[ ! -f ${FAILED_FILE} ]] && exit 0
|
||||||
|
else
|
||||||
|
for d in "${LIST[@]}"; do
|
||||||
|
[ -d "${d}" ] || continue
|
||||||
|
if ! psql -w -c "select 1" >/dev/null; then
|
||||||
|
FAILED="${d} ${FAILED}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [[ ${REGULAR_USER} = true ]] && [ -f "${d}"/regular-test.sh ]; then
|
||||||
|
"${d}/regular-test.sh" || FAILED="${d} ${FAILED}"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -f "${d}/neon-test.sh" ]; then
|
if [ -f "${d}/neon-test.sh" ]; then
|
||||||
"${d}/neon-test.sh" || FAILED="${d} ${FAILED}"
|
"${d}/neon-test.sh" || FAILED="${d} ${FAILED}"
|
||||||
else
|
else
|
||||||
USE_PGXS=1 make -C "${d}" installcheck || FAILED="${d} ${FAILED}"
|
USE_PGXS=1 make -C "${d}" installcheck || FAILED="${d} ${FAILED}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
[ -z "${FAILED}" ] && exit 0
|
[[ -z ${FAILED} ]] && exit 0
|
||||||
for d in ${FAILED}; do
|
fi
|
||||||
|
for d in ${FAILED} $([[ ! -f ${FAILED_FILE} ]] || cat ${FAILED_FILE}); do
|
||||||
cat "$(find $d -name regression.diffs)"
|
cat "$(find $d -name regression.diffs)"
|
||||||
done
|
done
|
||||||
for postgis_diff in /tmp/pgis_reg/*_diff; do
|
for postgis_diff in /tmp/pgis_reg/*_diff; do
|
||||||
@@ -68,4 +92,5 @@ for postgis_diff in /tmp/pgis_reg/*_diff; do
|
|||||||
cat "${postgis_diff}"
|
cat "${postgis_diff}"
|
||||||
done
|
done
|
||||||
echo "${FAILED}"
|
echo "${FAILED}"
|
||||||
|
cat ${FAILED_FILE}
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
set -eux -o pipefail
|
set -eux -o pipefail
|
||||||
cd "$(dirname "${0}")"
|
cd "$(dirname "${0}")"
|
||||||
# Takes a variable name as argument. The result is stored in that variable.
|
# Takes a variable name as argument. The result is stored in that variable.
|
||||||
@@ -60,8 +60,8 @@ function check_timeline() {
|
|||||||
# Restarts the compute node with the required compute tag and timeline.
|
# Restarts the compute node with the required compute tag and timeline.
|
||||||
# Accepts the tag for the compute node and the timeline as parameters.
|
# Accepts the tag for the compute node and the timeline as parameters.
|
||||||
function restart_compute() {
|
function restart_compute() {
|
||||||
docker compose down compute compute_is_ready
|
docker compose down compute1 compute_is_ready
|
||||||
COMPUTE_TAG=${1} TENANT_ID=${tenant_id} TIMELINE_ID=${2} docker compose up --quiet-pull -d --build compute compute_is_ready
|
COMPUTE_TAG=${1} TENANT_ID=${tenant_id} TIMELINE_ID=${2} docker compose up --quiet-pull -d --build compute1 compute_is_ready
|
||||||
wait_for_ready
|
wait_for_ready
|
||||||
check_timeline ${2}
|
check_timeline ${2}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user