mirror of
https://github.com/neondatabase/neon.git
synced 2026-08-16 19:18:22 +00:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fe8b93ab9d | |||
| 7e3e7f1cca | |||
| 0b0ed662d9 | |||
| 50bd65769f | |||
| 90534b1745 | |||
| 99d52df475 | |||
| ab5bbb445b | |||
| 5ef805e12c | |||
| 091a175a3e | |||
| 326cd80f0d | |||
| 6baf1aae33 | |||
| 184935619e | |||
| b2ecbf3e80 | |||
| 53147b51f9 | |||
| 006d9dfb6b | |||
| 1f7904c917 | |||
| 07c714343f | |||
| 264c34dfb7 | |||
| 9dd80b9b4c | |||
| c2623ffef4 | |||
| 426b1c5f08 | |||
| 306094a87d | |||
| d3464584a6 | |||
| 878135fe9c | |||
| 75434060a5 | |||
| 721803a0e7 | |||
| 108a211917 | |||
| 72ef0e0fa1 | |||
| eb23d355a9 | |||
| bee04b8a69 | |||
| 63e7fab990 | |||
| a181392738 | |||
| fc7397122c | |||
| cc599e23c1 | |||
| 54d1185789 | |||
| 8a138db8b7 | |||
| 211970f0e0 | |||
| f87f5a383e | |||
| e8ae37652b | |||
| 5bd8e2363a | |||
| 618680c299 | |||
| baf27ba6a3 | |||
| 16417d919d | |||
| 18b97150b2 | |||
| 17c59ed786 | |||
| d7b960c9b5 | |||
| 2ff6d2b6b5 | |||
| 30f7fbc88d | |||
| 09f2000f91 | |||
| e553ca9e4f | |||
| 0a80dbce83 | |||
| e763256448 | |||
| 129d4480bb | |||
| 776df963ba | |||
| 11dc5feb36 | |||
| dbbe57a837 | |||
| cc29def544 | |||
| 912d47ec02 | |||
| 6eba29c732 | |||
| 99d4c1877b | |||
| 2272dc8a48 | |||
| 323bd018cd | |||
| ad267d849f | |||
| 8cd7b5bf54 | |||
| 47c3c9a413 |
@@ -5,9 +5,7 @@
|
|||||||
!Cargo.toml
|
!Cargo.toml
|
||||||
!Makefile
|
!Makefile
|
||||||
!rust-toolchain.toml
|
!rust-toolchain.toml
|
||||||
!scripts/combine_control_files.py
|
|
||||||
!scripts/ninstall.sh
|
!scripts/ninstall.sh
|
||||||
!vm-cgconfig.conf
|
|
||||||
!docker-compose/run-tests.sh
|
!docker-compose/run-tests.sh
|
||||||
|
|
||||||
# Directories
|
# Directories
|
||||||
@@ -17,15 +15,12 @@
|
|||||||
!compute_tools/
|
!compute_tools/
|
||||||
!control_plane/
|
!control_plane/
|
||||||
!libs/
|
!libs/
|
||||||
!neon_local/
|
|
||||||
!pageserver/
|
!pageserver/
|
||||||
!patches/
|
|
||||||
!pgxn/
|
!pgxn/
|
||||||
!proxy/
|
!proxy/
|
||||||
!storage_scrubber/
|
!storage_scrubber/
|
||||||
!safekeeper/
|
!safekeeper/
|
||||||
!storage_broker/
|
!storage_broker/
|
||||||
!storage_controller/
|
!storage_controller/
|
||||||
!trace/
|
|
||||||
!vendor/postgres-*/
|
!vendor/postgres-*/
|
||||||
!workspace_hack/
|
!workspace_hack/
|
||||||
|
|||||||
@@ -218,6 +218,9 @@ runs:
|
|||||||
name: compatibility-snapshot-${{ runner.arch }}-${{ inputs.build_type }}-pg${{ inputs.pg_version }}
|
name: compatibility-snapshot-${{ runner.arch }}-${{ inputs.build_type }}-pg${{ inputs.pg_version }}
|
||||||
# Directory is created by test_compatibility.py::test_create_snapshot, keep the path in sync with the test
|
# Directory is created by test_compatibility.py::test_create_snapshot, keep the path in sync with the test
|
||||||
path: /tmp/test_output/compatibility_snapshot_pg${{ inputs.pg_version }}/
|
path: /tmp/test_output/compatibility_snapshot_pg${{ inputs.pg_version }}/
|
||||||
|
# The lack of compatibility snapshot shouldn't fail the job
|
||||||
|
# (for example if we didn't run the test for non build-and-test workflow)
|
||||||
|
skip-if-does-not-exist: true
|
||||||
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ inputs:
|
|||||||
path:
|
path:
|
||||||
description: "A directory or file to upload"
|
description: "A directory or file to upload"
|
||||||
required: true
|
required: true
|
||||||
|
skip-if-does-not-exist:
|
||||||
|
description: "Allow to skip if path doesn't exist, fail otherwise"
|
||||||
|
default: false
|
||||||
|
required: false
|
||||||
prefix:
|
prefix:
|
||||||
description: "S3 prefix. Default is '${GITHUB_SHA}/${GITHUB_RUN_ID}/${GITHUB_RUN_ATTEMPT}'"
|
description: "S3 prefix. Default is '${GITHUB_SHA}/${GITHUB_RUN_ID}/${GITHUB_RUN_ATTEMPT}'"
|
||||||
required: false
|
required: false
|
||||||
@@ -15,10 +19,12 @@ runs:
|
|||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare artifact
|
- name: Prepare artifact
|
||||||
|
id: prepare-artifact
|
||||||
shell: bash -euxo pipefail {0}
|
shell: bash -euxo pipefail {0}
|
||||||
env:
|
env:
|
||||||
SOURCE: ${{ inputs.path }}
|
SOURCE: ${{ inputs.path }}
|
||||||
ARCHIVE: /tmp/uploads/${{ inputs.name }}.tar.zst
|
ARCHIVE: /tmp/uploads/${{ inputs.name }}.tar.zst
|
||||||
|
SKIP_IF_DOES_NOT_EXIST: ${{ inputs.skip-if-does-not-exist }}
|
||||||
run: |
|
run: |
|
||||||
mkdir -p $(dirname $ARCHIVE)
|
mkdir -p $(dirname $ARCHIVE)
|
||||||
|
|
||||||
@@ -33,14 +39,22 @@ runs:
|
|||||||
elif [ -f ${SOURCE} ]; then
|
elif [ -f ${SOURCE} ]; then
|
||||||
time tar -cf ${ARCHIVE} --zstd ${SOURCE}
|
time tar -cf ${ARCHIVE} --zstd ${SOURCE}
|
||||||
elif ! ls ${SOURCE} > /dev/null 2>&1; then
|
elif ! ls ${SOURCE} > /dev/null 2>&1; then
|
||||||
echo >&2 "${SOURCE} does not exist"
|
if [ "${SKIP_IF_DOES_NOT_EXIST}" = "true" ]; then
|
||||||
exit 2
|
echo 'SKIPPED=true' >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo >&2 "${SOURCE} does not exist"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo >&2 "${SOURCE} is neither a directory nor a file, do not know how to handle it"
|
echo >&2 "${SOURCE} is neither a directory nor a file, do not know how to handle it"
|
||||||
exit 3
|
exit 3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo 'SKIPPED=false' >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
|
if: ${{ steps.prepare-artifact.outputs.SKIPPED == 'false' }}
|
||||||
shell: bash -euxo pipefail {0}
|
shell: bash -euxo pipefail {0}
|
||||||
env:
|
env:
|
||||||
SOURCE: ${{ inputs.path }}
|
SOURCE: ${{ inputs.path }}
|
||||||
|
|||||||
@@ -193,16 +193,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
# Disabled for now
|
- name: Cache cargo deps
|
||||||
# - name: Restore cargo deps cache
|
uses: actions/cache@v4
|
||||||
# id: cache_cargo
|
with:
|
||||||
# uses: actions/cache@v4
|
path: |
|
||||||
# with:
|
~/.cargo/registry
|
||||||
# path: |
|
!~/.cargo/registry/src
|
||||||
# !~/.cargo/registry/src
|
~/.cargo/git
|
||||||
# ~/.cargo/git/
|
target
|
||||||
# target/
|
key: v1-${{ runner.os }}-${{ runner.arch }}-cargo-${{ hashFiles('./Cargo.lock') }}-${{ hashFiles('./rust-toolchain.toml') }}-rust
|
||||||
# key: v1-${{ runner.os }}-${{ runner.arch }}-cargo-clippy-${{ hashFiles('rust-toolchain.toml') }}-${{ hashFiles('Cargo.lock') }}
|
|
||||||
|
|
||||||
# Some of our rust modules use FFI and need those to be checked
|
# Some of our rust modules use FFI and need those to be checked
|
||||||
- name: Get postgres headers
|
- name: Get postgres headers
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: Report Workflow Stats
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows:
|
||||||
|
- Add `external` label to issues and PRs created by external users
|
||||||
|
- Benchmarking
|
||||||
|
- Build and Test
|
||||||
|
- Build and Test Locally
|
||||||
|
- Build build-tools image
|
||||||
|
- Check Permissions
|
||||||
|
- Check build-tools image
|
||||||
|
- Check neon with extra platform builds
|
||||||
|
- Cloud Regression Test
|
||||||
|
- Create Release Branch
|
||||||
|
- Handle `approved-for-ci-run` label
|
||||||
|
- Lint GitHub Workflows
|
||||||
|
- Notify Slack channel about upcoming release
|
||||||
|
- Periodic pagebench performance test on dedicated EC2 machine in eu-central-1 region
|
||||||
|
- Pin build-tools image
|
||||||
|
- Prepare benchmarking databases by restoring dumps
|
||||||
|
- Push images to ACR
|
||||||
|
- Test Postgres client libraries
|
||||||
|
- Trigger E2E Tests
|
||||||
|
- cleanup caches by a branch
|
||||||
|
types: [completed]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
gh-workflow-stats:
|
||||||
|
name: Github Workflow Stats
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
steps:
|
||||||
|
- name: Export GH Workflow Stats
|
||||||
|
uses: neondatabase/gh-workflow-stats-action@v0.1.4
|
||||||
|
with:
|
||||||
|
DB_URI: ${{ secrets.GH_REPORT_STATS_DB_RW_CONNSTR }}
|
||||||
|
DB_TABLE: "gh_workflow_stats_neon"
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GH_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
/compute_tools/ @neondatabase/control-plane @neondatabase/compute
|
/compute_tools/ @neondatabase/control-plane @neondatabase/compute
|
||||||
/storage_controller @neondatabase/storage
|
/storage_controller @neondatabase/storage
|
||||||
|
/storage_scrubber @neondatabase/storage
|
||||||
/libs/pageserver_api/ @neondatabase/storage
|
/libs/pageserver_api/ @neondatabase/storage
|
||||||
/libs/postgres_ffi/ @neondatabase/compute @neondatabase/storage
|
/libs/postgres_ffi/ @neondatabase/compute @neondatabase/storage
|
||||||
/libs/remote_storage/ @neondatabase/storage
|
/libs/remote_storage/ @neondatabase/storage
|
||||||
|
|||||||
Generated
+113
-169
@@ -666,34 +666,6 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "axum"
|
|
||||||
version = "0.6.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"axum-core 0.3.4",
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"bytes",
|
|
||||||
"futures-util",
|
|
||||||
"http 0.2.9",
|
|
||||||
"http-body 0.4.5",
|
|
||||||
"hyper 0.14.30",
|
|
||||||
"itoa",
|
|
||||||
"matchit 0.7.0",
|
|
||||||
"memchr",
|
|
||||||
"mime",
|
|
||||||
"percent-encoding",
|
|
||||||
"pin-project-lite",
|
|
||||||
"rustversion",
|
|
||||||
"serde",
|
|
||||||
"sync_wrapper 0.1.2",
|
|
||||||
"tower",
|
|
||||||
"tower-layer",
|
|
||||||
"tower-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.7.5"
|
version = "0.7.5"
|
||||||
@@ -701,7 +673,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
|
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum-core 0.4.5",
|
"axum-core",
|
||||||
"base64 0.21.1",
|
"base64 0.21.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@@ -731,23 +703,6 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "axum-core"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"bytes",
|
|
||||||
"futures-util",
|
|
||||||
"http 0.2.9",
|
|
||||||
"http-body 0.4.5",
|
|
||||||
"mime",
|
|
||||||
"rustversion",
|
|
||||||
"tower-layer",
|
|
||||||
"tower-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-core"
|
name = "axum-core"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
@@ -971,7 +926,7 @@ dependencies = [
|
|||||||
"clang-sys",
|
"clang-sys",
|
||||||
"itertools 0.12.1",
|
"itertools 0.12.1",
|
||||||
"log",
|
"log",
|
||||||
"prettyplease 0.2.17",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"regex",
|
"regex",
|
||||||
@@ -1865,6 +1820,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
|
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base16ct 0.2.0",
|
"base16ct 0.2.0",
|
||||||
|
"base64ct",
|
||||||
"crypto-bigint 0.5.5",
|
"crypto-bigint 0.5.5",
|
||||||
"digest",
|
"digest",
|
||||||
"ff 0.13.0",
|
"ff 0.13.0",
|
||||||
@@ -1874,6 +1830,8 @@ dependencies = [
|
|||||||
"pkcs8 0.10.2",
|
"pkcs8 0.10.2",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"sec1 0.7.3",
|
"sec1 0.7.3",
|
||||||
|
"serde_json",
|
||||||
|
"serdect",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -2454,15 +2412,6 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "home"
|
|
||||||
version = "0.5.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.52.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hostname"
|
name = "hostname"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -2657,14 +2606,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-timeout"
|
name = "hyper-timeout"
|
||||||
version = "0.4.1"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
|
checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hyper 0.14.30",
|
"hyper 1.4.1",
|
||||||
|
"hyper-util",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-io-timeout",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3470,7 +3420,7 @@ dependencies = [
|
|||||||
"opentelemetry-http",
|
"opentelemetry-http",
|
||||||
"opentelemetry-proto",
|
"opentelemetry-proto",
|
||||||
"opentelemetry_sdk",
|
"opentelemetry_sdk",
|
||||||
"prost 0.13.3",
|
"prost",
|
||||||
"reqwest 0.12.4",
|
"reqwest 0.12.4",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
@@ -3483,8 +3433,8 @@ checksum = "30ee9f20bff9c984511a02f082dc8ede839e4a9bf15cc2487c8d6fea5ad850d9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"opentelemetry_sdk",
|
"opentelemetry_sdk",
|
||||||
"prost 0.13.3",
|
"prost",
|
||||||
"tonic 0.12.3",
|
"tonic",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4090,6 +4040,8 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
"postgres-protocol",
|
"postgres-protocol",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4178,16 +4130,6 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "prettyplease"
|
|
||||||
version = "0.1.25"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"syn 1.0.109",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -4258,16 +4200,6 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "prost"
|
|
||||||
version = "0.11.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"prost-derive 0.11.9",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost"
|
name = "prost"
|
||||||
version = "0.13.3"
|
version = "0.13.3"
|
||||||
@@ -4275,42 +4207,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f"
|
checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"prost-derive 0.13.3",
|
"prost-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-build"
|
name = "prost-build"
|
||||||
version = "0.11.9"
|
version = "0.13.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270"
|
checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"heck 0.4.1",
|
"heck 0.5.0",
|
||||||
"itertools 0.10.5",
|
"itertools 0.12.1",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"multimap",
|
"multimap",
|
||||||
|
"once_cell",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
"prettyplease 0.1.25",
|
"prettyplease",
|
||||||
"prost 0.11.9",
|
"prost",
|
||||||
"prost-types",
|
"prost-types",
|
||||||
"regex",
|
"regex",
|
||||||
"syn 1.0.109",
|
"syn 2.0.52",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"which",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "prost-derive"
|
|
||||||
version = "0.11.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"itertools 0.10.5",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4328,11 +4246,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-types"
|
name = "prost-types"
|
||||||
version = "0.11.9"
|
version = "0.13.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13"
|
checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"prost 0.11.9",
|
"prost",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5094,6 +5012,21 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.23.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebbbdb961df0ad3f2652da8f3fdc4b36122f568f968f45ad3316f26c025c677b"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"rustls-webpki 0.102.2",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-native-certs"
|
name = "rustls-native-certs"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@@ -5119,6 +5052,19 @@ dependencies = [
|
|||||||
"security-framework",
|
"security-framework",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-native-certs"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a"
|
||||||
|
dependencies = [
|
||||||
|
"openssl-probe",
|
||||||
|
"rustls-pemfile 2.1.1",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pemfile"
|
name = "rustls-pemfile"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -5194,6 +5140,7 @@ dependencies = [
|
|||||||
"fail",
|
"fail",
|
||||||
"futures",
|
"futures",
|
||||||
"hex",
|
"hex",
|
||||||
|
"http 1.1.0",
|
||||||
"humantime",
|
"humantime",
|
||||||
"hyper 0.14.30",
|
"hyper 0.14.30",
|
||||||
"metrics",
|
"metrics",
|
||||||
@@ -5314,6 +5261,7 @@ dependencies = [
|
|||||||
"der 0.7.8",
|
"der 0.7.8",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
"pkcs8 0.10.2",
|
"pkcs8 0.10.2",
|
||||||
|
"serdect",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -5568,6 +5516,16 @@ dependencies = [
|
|||||||
"syn 2.0.52",
|
"syn 2.0.52",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serdect"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177"
|
||||||
|
dependencies = [
|
||||||
|
"base16ct 0.2.0",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
@@ -5750,19 +5708,22 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
|
"bytes",
|
||||||
"clap",
|
"clap",
|
||||||
"const_format",
|
"const_format",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"http-body-util",
|
||||||
"humantime",
|
"humantime",
|
||||||
"hyper 0.14.30",
|
"hyper 1.4.1",
|
||||||
|
"hyper-util",
|
||||||
"metrics",
|
"metrics",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot 0.12.1",
|
"parking_lot 0.12.1",
|
||||||
"prost 0.11.9",
|
"prost",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tonic 0.9.2",
|
"tonic",
|
||||||
"tonic-build",
|
"tonic-build",
|
||||||
"tracing",
|
"tracing",
|
||||||
"utils",
|
"utils",
|
||||||
@@ -6306,6 +6267,17 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-rustls"
|
||||||
|
version = "0.26.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
|
||||||
|
dependencies = [
|
||||||
|
"rustls 0.23.7",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
@@ -6397,29 +6369,30 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tonic"
|
name = "tonic"
|
||||||
version = "0.9.2"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a"
|
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-stream",
|
"async-stream",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum 0.6.20",
|
"axum",
|
||||||
"base64 0.21.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"h2 0.4.4",
|
||||||
"futures-util",
|
"http 1.1.0",
|
||||||
"h2 0.3.26",
|
"http-body 1.0.0",
|
||||||
"http 0.2.9",
|
"http-body-util",
|
||||||
"http-body 0.4.5",
|
"hyper 1.4.1",
|
||||||
"hyper 0.14.30",
|
|
||||||
"hyper-timeout",
|
"hyper-timeout",
|
||||||
|
"hyper-util",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"prost 0.11.9",
|
"prost",
|
||||||
"rustls-native-certs 0.6.2",
|
"rustls-native-certs 0.8.0",
|
||||||
"rustls-pemfile 1.0.2",
|
"rustls-pemfile 2.1.1",
|
||||||
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.0",
|
"tokio-rustls 0.26.0",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
@@ -6428,37 +6401,17 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tonic"
|
name = "tonic-build"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
|
checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"prettyplease",
|
||||||
"base64 0.22.1",
|
|
||||||
"bytes",
|
|
||||||
"http 1.1.0",
|
|
||||||
"http-body 1.0.0",
|
|
||||||
"http-body-util",
|
|
||||||
"percent-encoding",
|
|
||||||
"pin-project",
|
|
||||||
"prost 0.13.3",
|
|
||||||
"tokio-stream",
|
|
||||||
"tower-layer",
|
|
||||||
"tower-service",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tonic-build"
|
|
||||||
version = "0.9.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07"
|
|
||||||
dependencies = [
|
|
||||||
"prettyplease 0.1.25",
|
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"prost-build",
|
"prost-build",
|
||||||
|
"prost-types",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 2.0.52",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6864,7 +6817,7 @@ name = "vm_monitor"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum 0.7.5",
|
"axum",
|
||||||
"cgroups-rs",
|
"cgroups-rs",
|
||||||
"clap",
|
"clap",
|
||||||
"futures",
|
"futures",
|
||||||
@@ -7095,18 +7048,6 @@ dependencies = [
|
|||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "which"
|
|
||||||
version = "4.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
"home",
|
|
||||||
"once_cell",
|
|
||||||
"rustix",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "whoami"
|
name = "whoami"
|
||||||
version = "1.5.1"
|
version = "1.5.1"
|
||||||
@@ -7335,9 +7276,10 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"axum",
|
||||||
|
"axum-core",
|
||||||
"base64 0.21.1",
|
"base64 0.21.1",
|
||||||
"base64ct",
|
"base64ct",
|
||||||
"bitflags 2.4.1",
|
|
||||||
"bytes",
|
"bytes",
|
||||||
"camino",
|
"camino",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -7365,7 +7307,6 @@ dependencies = [
|
|||||||
"hyper 1.4.1",
|
"hyper 1.4.1",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"itertools 0.10.5",
|
|
||||||
"itertools 0.12.1",
|
"itertools 0.12.1",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -7377,15 +7318,16 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parquet",
|
"parquet",
|
||||||
|
"postgres-types",
|
||||||
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"prost 0.11.9",
|
"prost",
|
||||||
"quote",
|
"quote",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
"regex-automata 0.4.3",
|
"regex-automata 0.4.3",
|
||||||
"regex-syntax 0.8.2",
|
"regex-syntax 0.8.2",
|
||||||
"reqwest 0.12.4",
|
"reqwest 0.12.4",
|
||||||
"rustls 0.21.11",
|
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -7401,9 +7343,11 @@ dependencies = [
|
|||||||
"time",
|
"time",
|
||||||
"time-macros",
|
"time-macros",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.0",
|
"tokio-postgres",
|
||||||
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"toml_edit",
|
"toml_edit",
|
||||||
|
"tonic",
|
||||||
"tower",
|
"tower",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
|
|||||||
+3
-3
@@ -130,7 +130,7 @@ pbkdf2 = { version = "0.12.1", features = ["simple", "std"] }
|
|||||||
pin-project-lite = "0.2"
|
pin-project-lite = "0.2"
|
||||||
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.11"
|
prost = "0.13"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
redis = { version = "0.25.2", features = ["tokio-rustls-comp", "keep-alive"] }
|
redis = { version = "0.25.2", features = ["tokio-rustls-comp", "keep-alive"] }
|
||||||
regex = "1.10.2"
|
regex = "1.10.2"
|
||||||
@@ -178,7 +178,7 @@ tokio-tar = "0.3"
|
|||||||
tokio-util = { version = "0.7.10", features = ["io", "rt"] }
|
tokio-util = { version = "0.7.10", features = ["io", "rt"] }
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
toml_edit = "0.22"
|
toml_edit = "0.22"
|
||||||
tonic = {version = "0.9", features = ["tls", "tls-roots"]}
|
tonic = {version = "0.12.3", features = ["tls", "tls-roots"]}
|
||||||
tower-service = "0.3.2"
|
tower-service = "0.3.2"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-error = "0.2"
|
tracing-error = "0.2"
|
||||||
@@ -246,7 +246,7 @@ criterion = "0.5.1"
|
|||||||
rcgen = "0.12"
|
rcgen = "0.12"
|
||||||
rstest = "0.18"
|
rstest = "0.18"
|
||||||
camino-tempfile = "1.0.2"
|
camino-tempfile = "1.0.2"
|
||||||
tonic-build = "0.9"
|
tonic-build = "0.12"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
|
||||||
|
|||||||
@@ -168,27 +168,27 @@ postgres-check-%: postgres-%
|
|||||||
neon-pg-ext-%: postgres-%
|
neon-pg-ext-%: postgres-%
|
||||||
+@echo "Compiling neon $*"
|
+@echo "Compiling neon $*"
|
||||||
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-$*
|
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-$*
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config CFLAGS='$(PG_CFLAGS) $(COPT)' \
|
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config COPT='$(COPT)' \
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-$* \
|
-C $(POSTGRES_INSTALL_DIR)/build/neon-$* \
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon/Makefile install
|
-f $(ROOT_PROJECT_DIR)/pgxn/neon/Makefile install
|
||||||
+@echo "Compiling neon_walredo $*"
|
+@echo "Compiling neon_walredo $*"
|
||||||
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-walredo-$*
|
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-walredo-$*
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config CFLAGS='$(PG_CFLAGS) $(COPT)' \
|
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config COPT='$(COPT)' \
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-walredo-$* \
|
-C $(POSTGRES_INSTALL_DIR)/build/neon-walredo-$* \
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon_walredo/Makefile install
|
-f $(ROOT_PROJECT_DIR)/pgxn/neon_walredo/Makefile install
|
||||||
+@echo "Compiling neon_rmgr $*"
|
+@echo "Compiling neon_rmgr $*"
|
||||||
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-rmgr-$*
|
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-rmgr-$*
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config CFLAGS='$(PG_CFLAGS) $(COPT)' \
|
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config COPT='$(COPT)' \
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-rmgr-$* \
|
-C $(POSTGRES_INSTALL_DIR)/build/neon-rmgr-$* \
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon_rmgr/Makefile install
|
-f $(ROOT_PROJECT_DIR)/pgxn/neon_rmgr/Makefile install
|
||||||
+@echo "Compiling neon_test_utils $*"
|
+@echo "Compiling neon_test_utils $*"
|
||||||
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-test-utils-$*
|
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-test-utils-$*
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config CFLAGS='$(PG_CFLAGS) $(COPT)' \
|
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config COPT='$(COPT)' \
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-test-utils-$* \
|
-C $(POSTGRES_INSTALL_DIR)/build/neon-test-utils-$* \
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon_test_utils/Makefile install
|
-f $(ROOT_PROJECT_DIR)/pgxn/neon_test_utils/Makefile install
|
||||||
+@echo "Compiling neon_utils $*"
|
+@echo "Compiling neon_utils $*"
|
||||||
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-utils-$*
|
mkdir -p $(POSTGRES_INSTALL_DIR)/build/neon-utils-$*
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config CFLAGS='$(PG_CFLAGS) $(COPT)' \
|
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/$*/bin/pg_config COPT='$(COPT)' \
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/neon-utils-$* \
|
-C $(POSTGRES_INSTALL_DIR)/build/neon-utils-$* \
|
||||||
-f $(ROOT_PROJECT_DIR)/pgxn/neon_utils/Makefile install
|
-f $(ROOT_PROJECT_DIR)/pgxn/neon_utils/Makefile install
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ neon-pg-clean-ext-%:
|
|||||||
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 $(POSTGRES_INSTALL_DIR)/build/walproposer-lib
|
||||||
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/v17/bin/pg_config CFLAGS='$(PG_CFLAGS) $(COPT)' \
|
$(MAKE) PG_CONFIG=$(POSTGRES_INSTALL_DIR)/v17/bin/pg_config COPT='$(COPT)' \
|
||||||
-C $(POSTGRES_INSTALL_DIR)/build/walproposer-lib \
|
-C $(POSTGRES_INSTALL_DIR)/build/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 $(POSTGRES_INSTALL_DIR)/build/walproposer-lib
|
||||||
@@ -333,7 +333,7 @@ postgres-%-pgindent: postgres-%-pg-bsd-indent postgres-%-typedefs.list
|
|||||||
# Indent pxgn/neon.
|
# Indent pxgn/neon.
|
||||||
.PHONY: neon-pgindent
|
.PHONY: neon-pgindent
|
||||||
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 CFLAGS='$(PG_CFLAGS) $(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=$(POSTGRES_INSTALL_DIR)/build/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 \
|
||||||
|
|||||||
+106
-56
@@ -109,13 +109,30 @@ RUN apt update && \
|
|||||||
libcgal-dev libgdal-dev libgmp-dev libmpfr-dev libopenscenegraph-dev libprotobuf-c-dev \
|
libcgal-dev libgdal-dev libgmp-dev libmpfr-dev libopenscenegraph-dev libprotobuf-c-dev \
|
||||||
protobuf-c-compiler xsltproc
|
protobuf-c-compiler xsltproc
|
||||||
|
|
||||||
|
|
||||||
|
# Postgis 3.5.0 requires SFCGAL 1.4+
|
||||||
|
#
|
||||||
|
# It would be nice to update all versions together, but we must solve the SFCGAL dependency first.
|
||||||
# SFCGAL > 1.3 requires CGAL > 5.2, Bullseye's libcgal-dev is 5.2
|
# SFCGAL > 1.3 requires CGAL > 5.2, Bullseye's libcgal-dev is 5.2
|
||||||
RUN case "${PG_VERSION}" in "v17") \
|
# and also we must check backward compatibility with older versions of PostGIS.
|
||||||
mkdir -p /sfcgal && \
|
#
|
||||||
echo "Postgis doensn't yet support PG17 (needs 3.4.3, if not higher)" && exit 0;; \
|
# Use new version only for v17
|
||||||
|
RUN case "${PG_VERSION}" in \
|
||||||
|
"v17") \
|
||||||
|
export SFCGAL_VERSION=1.4.1 \
|
||||||
|
export SFCGAL_CHECKSUM=1800c8a26241588f11cddcf433049e9b9aea902e923414d2ecef33a3295626c3 \
|
||||||
|
;; \
|
||||||
|
"v14" | "v15" | "v16") \
|
||||||
|
export SFCGAL_VERSION=1.3.10 \
|
||||||
|
export SFCGAL_CHECKSUM=4e39b3b2adada6254a7bdba6d297bb28e1a9835a9f879b74f37e2dab70203232 \
|
||||||
|
;; \
|
||||||
|
*) \
|
||||||
|
echo "unexpected PostgreSQL version" && exit 1 \
|
||||||
|
;; \
|
||||||
esac && \
|
esac && \
|
||||||
wget https://gitlab.com/Oslandia/SFCGAL/-/archive/v1.3.10/SFCGAL-v1.3.10.tar.gz -O SFCGAL.tar.gz && \
|
mkdir -p /sfcgal && \
|
||||||
echo "4e39b3b2adada6254a7bdba6d297bb28e1a9835a9f879b74f37e2dab70203232 SFCGAL.tar.gz" | sha256sum --check && \
|
wget https://gitlab.com/sfcgal/SFCGAL/-/archive/v${SFCGAL_VERSION}/SFCGAL-v${SFCGAL_VERSION}.tar.gz -O SFCGAL.tar.gz && \
|
||||||
|
echo "${SFCGAL_CHECKSUM} SFCGAL.tar.gz" | sha256sum --check && \
|
||||||
mkdir sfcgal-src && cd sfcgal-src && tar xzf ../SFCGAL.tar.gz --strip-components=1 -C . && \
|
mkdir sfcgal-src && cd sfcgal-src && tar xzf ../SFCGAL.tar.gz --strip-components=1 -C . && \
|
||||||
cmake -DCMAKE_BUILD_TYPE=Release . && make -j $(getconf _NPROCESSORS_ONLN) && \
|
cmake -DCMAKE_BUILD_TYPE=Release . && make -j $(getconf _NPROCESSORS_ONLN) && \
|
||||||
DESTDIR=/sfcgal make install -j $(getconf _NPROCESSORS_ONLN) && \
|
DESTDIR=/sfcgal make install -j $(getconf _NPROCESSORS_ONLN) && \
|
||||||
@@ -123,15 +140,27 @@ RUN case "${PG_VERSION}" in "v17") \
|
|||||||
|
|
||||||
ENV PATH="/usr/local/pgsql/bin:$PATH"
|
ENV PATH="/usr/local/pgsql/bin:$PATH"
|
||||||
|
|
||||||
RUN case "${PG_VERSION}" in "v17") \
|
# Postgis 3.5.0 supports v17
|
||||||
echo "Postgis doensn't yet support PG17 (needs 3.4.3, if not higher)" && exit 0;; \
|
RUN case "${PG_VERSION}" in \
|
||||||
|
"v17") \
|
||||||
|
export POSTGIS_VERSION=3.5.0 \
|
||||||
|
export POSTGIS_CHECKSUM=ca698a22cc2b2b3467ac4e063b43a28413f3004ddd505bdccdd74c56a647f510 \
|
||||||
|
;; \
|
||||||
|
"v14" | "v15" | "v16") \
|
||||||
|
export POSTGIS_VERSION=3.3.3 \
|
||||||
|
export POSTGIS_CHECKSUM=74eb356e3f85f14233791013360881b6748f78081cc688ff9d6f0f673a762d13 \
|
||||||
|
;; \
|
||||||
|
*) \
|
||||||
|
echo "unexpected PostgreSQL version" && exit 1 \
|
||||||
|
;; \
|
||||||
esac && \
|
esac && \
|
||||||
wget https://download.osgeo.org/postgis/source/postgis-3.3.3.tar.gz -O postgis.tar.gz && \
|
wget https://download.osgeo.org/postgis/source/postgis-${POSTGIS_VERSION}.tar.gz -O postgis.tar.gz && \
|
||||||
echo "74eb356e3f85f14233791013360881b6748f78081cc688ff9d6f0f673a762d13 postgis.tar.gz" | sha256sum --check && \
|
echo "${POSTGIS_CHECKSUM} postgis.tar.gz" | sha256sum --check && \
|
||||||
mkdir postgis-src && cd postgis-src && tar xzf ../postgis.tar.gz --strip-components=1 -C . && \
|
mkdir postgis-src && cd postgis-src && tar xzf ../postgis.tar.gz --strip-components=1 -C . && \
|
||||||
find /usr/local/pgsql -type f | sed 's|^/usr/local/pgsql/||' > /before.txt &&\
|
find /usr/local/pgsql -type f | sed 's|^/usr/local/pgsql/||' > /before.txt &&\
|
||||||
./autogen.sh && \
|
./autogen.sh && \
|
||||||
./configure --with-sfcgal=/usr/local/bin/sfcgal-config && \
|
./configure --with-sfcgal=/usr/local/bin/sfcgal-config && \
|
||||||
|
make -j $(getconf _NPROCESSORS_ONLN) && \
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) install && \
|
make -j $(getconf _NPROCESSORS_ONLN) install && \
|
||||||
cd extensions/postgis && \
|
cd extensions/postgis && \
|
||||||
make clean && \
|
make clean && \
|
||||||
@@ -152,11 +181,27 @@ RUN case "${PG_VERSION}" in "v17") \
|
|||||||
cp /usr/local/pgsql/share/extension/address_standardizer.control /extensions/postgis && \
|
cp /usr/local/pgsql/share/extension/address_standardizer.control /extensions/postgis && \
|
||||||
cp /usr/local/pgsql/share/extension/address_standardizer_data_us.control /extensions/postgis
|
cp /usr/local/pgsql/share/extension/address_standardizer_data_us.control /extensions/postgis
|
||||||
|
|
||||||
RUN case "${PG_VERSION}" in "v17") \
|
# Uses versioned libraries, i.e. libpgrouting-3.4
|
||||||
echo "v17 extensions are not supported yet. Quit" && exit 0;; \
|
# and may introduce function signature changes between releases
|
||||||
|
# i.e. release 3.5.0 has new signature for pg_dijkstra function
|
||||||
|
#
|
||||||
|
# Use new version only for v17
|
||||||
|
# last release v3.6.2 - Mar 30, 2024
|
||||||
|
RUN case "${PG_VERSION}" in \
|
||||||
|
"v17") \
|
||||||
|
export PGROUTING_VERSION=3.6.2 \
|
||||||
|
export PGROUTING_CHECKSUM=f4a1ed79d6f714e52548eca3bb8e5593c6745f1bde92eb5fb858efd8984dffa2 \
|
||||||
|
;; \
|
||||||
|
"v14" | "v15" | "v16") \
|
||||||
|
export PGROUTING_VERSION=3.4.2 \
|
||||||
|
export PGROUTING_CHECKSUM=cac297c07d34460887c4f3b522b35c470138760fe358e351ad1db4edb6ee306e \
|
||||||
|
;; \
|
||||||
|
*) \
|
||||||
|
echo "unexpected PostgreSQL version" && exit 1 \
|
||||||
|
;; \
|
||||||
esac && \
|
esac && \
|
||||||
wget https://github.com/pgRouting/pgrouting/archive/v3.4.2.tar.gz -O pgrouting.tar.gz && \
|
wget https://github.com/pgRouting/pgrouting/archive/v${PGROUTING_VERSION}.tar.gz -O pgrouting.tar.gz && \
|
||||||
echo "cac297c07d34460887c4f3b522b35c470138760fe358e351ad1db4edb6ee306e pgrouting.tar.gz" | sha256sum --check && \
|
echo "${PGROUTING_CHECKSUM} pgrouting.tar.gz" | sha256sum --check && \
|
||||||
mkdir pgrouting-src && cd pgrouting-src && tar xzf ../pgrouting.tar.gz --strip-components=1 -C . && \
|
mkdir pgrouting-src && cd pgrouting-src && tar xzf ../pgrouting.tar.gz --strip-components=1 -C . && \
|
||||||
mkdir build && cd build && \
|
mkdir build && cd build && \
|
||||||
cmake -DCMAKE_BUILD_TYPE=Release .. && \
|
cmake -DCMAKE_BUILD_TYPE=Release .. && \
|
||||||
@@ -215,10 +260,9 @@ FROM build-deps AS h3-pg-build
|
|||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
||||||
|
|
||||||
RUN case "${PG_VERSION}" in "v17") \
|
# not version-specific
|
||||||
mkdir -p /h3/usr/ && \
|
# last release v4.1.0 - Jan 18, 2023
|
||||||
echo "v17 extensions are not supported yet. Quit" && exit 0;; \
|
RUN mkdir -p /h3/usr/ && \
|
||||||
esac && \
|
|
||||||
wget https://github.com/uber/h3/archive/refs/tags/v4.1.0.tar.gz -O h3.tar.gz && \
|
wget https://github.com/uber/h3/archive/refs/tags/v4.1.0.tar.gz -O h3.tar.gz && \
|
||||||
echo "ec99f1f5974846bde64f4513cf8d2ea1b8d172d2218ab41803bf6a63532272bc h3.tar.gz" | sha256sum --check && \
|
echo "ec99f1f5974846bde64f4513cf8d2ea1b8d172d2218ab41803bf6a63532272bc h3.tar.gz" | sha256sum --check && \
|
||||||
mkdir h3-src && cd h3-src && tar xzf ../h3.tar.gz --strip-components=1 -C . && \
|
mkdir h3-src && cd h3-src && tar xzf ../h3.tar.gz --strip-components=1 -C . && \
|
||||||
@@ -229,10 +273,9 @@ RUN case "${PG_VERSION}" in "v17") \
|
|||||||
cp -R /h3/usr / && \
|
cp -R /h3/usr / && \
|
||||||
rm -rf build
|
rm -rf build
|
||||||
|
|
||||||
RUN case "${PG_VERSION}" in "v17") \
|
# not version-specific
|
||||||
echo "v17 extensions are not supported yet. Quit" && exit 0;; \
|
# last release v4.1.3 - Jul 26, 2023
|
||||||
esac && \
|
RUN wget https://github.com/zachasme/h3-pg/archive/refs/tags/v4.1.3.tar.gz -O h3-pg.tar.gz && \
|
||||||
wget https://github.com/zachasme/h3-pg/archive/refs/tags/v4.1.3.tar.gz -O h3-pg.tar.gz && \
|
|
||||||
echo "5c17f09a820859ffe949f847bebf1be98511fb8f1bd86f94932512c00479e324 h3-pg.tar.gz" | sha256sum --check && \
|
echo "5c17f09a820859ffe949f847bebf1be98511fb8f1bd86f94932512c00479e324 h3-pg.tar.gz" | sha256sum --check && \
|
||||||
mkdir h3-pg-src && cd h3-pg-src && tar xzf ../h3-pg.tar.gz --strip-components=1 -C . && \
|
mkdir h3-pg-src && cd h3-pg-src && tar xzf ../h3-pg.tar.gz --strip-components=1 -C . && \
|
||||||
export PATH="/usr/local/pgsql/bin:$PATH" && \
|
export PATH="/usr/local/pgsql/bin:$PATH" && \
|
||||||
@@ -251,11 +294,10 @@ FROM build-deps AS unit-pg-build
|
|||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
||||||
|
|
||||||
RUN case "${PG_VERSION}" in "v17") \
|
# not version-specific
|
||||||
echo "v17 extensions are not supported yet. Quit" && exit 0;; \
|
# last release 7.9 - Sep 15, 2024
|
||||||
esac && \
|
RUN wget https://github.com/df7cb/postgresql-unit/archive/refs/tags/7.9.tar.gz -O postgresql-unit.tar.gz && \
|
||||||
wget https://github.com/df7cb/postgresql-unit/archive/refs/tags/7.7.tar.gz -O postgresql-unit.tar.gz && \
|
echo "e46de6245dcc8b2c2ecf29873dbd43b2b346773f31dd5ce4b8315895a052b456 postgresql-unit.tar.gz" | sha256sum --check && \
|
||||||
echo "411d05beeb97e5a4abf17572bfcfbb5a68d98d1018918feff995f6ee3bb03e79 postgresql-unit.tar.gz" | sha256sum --check && \
|
|
||||||
mkdir postgresql-unit-src && cd postgresql-unit-src && tar xzf ../postgresql-unit.tar.gz --strip-components=1 -C . && \
|
mkdir postgresql-unit-src && cd postgresql-unit-src && tar xzf ../postgresql-unit.tar.gz --strip-components=1 -C . && \
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) PG_CONFIG=/usr/local/pgsql/bin/pg_config && \
|
make -j $(getconf _NPROCESSORS_ONLN) PG_CONFIG=/usr/local/pgsql/bin/pg_config && \
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) install PG_CONFIG=/usr/local/pgsql/bin/pg_config && \
|
make -j $(getconf _NPROCESSORS_ONLN) install PG_CONFIG=/usr/local/pgsql/bin/pg_config && \
|
||||||
@@ -302,12 +344,10 @@ FROM build-deps AS pgjwt-pg-build
|
|||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
||||||
|
|
||||||
# 9742dab1b2f297ad3811120db7b21451bca2d3c9 made on 13/11/2021
|
# not version-specific
|
||||||
RUN case "${PG_VERSION}" in "v17") \
|
# doesn't use releases, last commit f3d82fd - Mar 2, 2023
|
||||||
echo "v17 extensions are not supported yet. Quit" && exit 0;; \
|
RUN wget https://github.com/michelp/pgjwt/archive/f3d82fd30151e754e19ce5d6a06c71c20689ce3d.tar.gz -O pgjwt.tar.gz && \
|
||||||
esac && \
|
echo "dae8ed99eebb7593b43013f6532d772b12dfecd55548d2673f2dfd0163f6d2b9 pgjwt.tar.gz" | sha256sum --check && \
|
||||||
wget https://github.com/michelp/pgjwt/archive/9742dab1b2f297ad3811120db7b21451bca2d3c9.tar.gz -O pgjwt.tar.gz && \
|
|
||||||
echo "cfdefb15007286f67d3d45510f04a6a7a495004be5b3aecb12cda667e774203f pgjwt.tar.gz" | sha256sum --check && \
|
|
||||||
mkdir pgjwt-src && cd pgjwt-src && tar xzf ../pgjwt.tar.gz --strip-components=1 -C . && \
|
mkdir pgjwt-src && cd pgjwt-src && tar xzf ../pgjwt.tar.gz --strip-components=1 -C . && \
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) install PG_CONFIG=/usr/local/pgsql/bin/pg_config && \
|
make -j $(getconf _NPROCESSORS_ONLN) install PG_CONFIG=/usr/local/pgsql/bin/pg_config && \
|
||||||
echo 'trusted = true' >> /usr/local/pgsql/share/extension/pgjwt.control
|
echo 'trusted = true' >> /usr/local/pgsql/share/extension/pgjwt.control
|
||||||
@@ -342,10 +382,9 @@ FROM build-deps AS pg-hashids-pg-build
|
|||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
||||||
|
|
||||||
RUN case "${PG_VERSION}" in "v17") \
|
# not version-specific
|
||||||
echo "v17 extensions are not supported yet. Quit" && exit 0;; \
|
# last release v1.2.1 -Jan 12, 2018
|
||||||
esac && \
|
RUN wget https://github.com/iCyberon/pg_hashids/archive/refs/tags/v1.2.1.tar.gz -O pg_hashids.tar.gz && \
|
||||||
wget https://github.com/iCyberon/pg_hashids/archive/refs/tags/v1.2.1.tar.gz -O pg_hashids.tar.gz && \
|
|
||||||
echo "74576b992d9277c92196dd8d816baa2cc2d8046fe102f3dcd7f3c3febed6822a pg_hashids.tar.gz" | sha256sum --check && \
|
echo "74576b992d9277c92196dd8d816baa2cc2d8046fe102f3dcd7f3c3febed6822a pg_hashids.tar.gz" | sha256sum --check && \
|
||||||
mkdir pg_hashids-src && cd pg_hashids-src && tar xzf ../pg_hashids.tar.gz --strip-components=1 -C . && \
|
mkdir pg_hashids-src && cd pg_hashids-src && tar xzf ../pg_hashids.tar.gz --strip-components=1 -C . && \
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) PG_CONFIG=/usr/local/pgsql/bin/pg_config USE_PGXS=1 && \
|
make -j $(getconf _NPROCESSORS_ONLN) PG_CONFIG=/usr/local/pgsql/bin/pg_config USE_PGXS=1 && \
|
||||||
@@ -405,10 +444,9 @@ FROM build-deps AS ip4r-pg-build
|
|||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
||||||
|
|
||||||
RUN case "${PG_VERSION}" in "v17") \
|
# not version-specific
|
||||||
echo "v17 extensions are not supported yet. Quit" && exit 0;; \
|
# last release v2.4.2 - Jul 29, 2023
|
||||||
esac && \
|
RUN wget https://github.com/RhodiumToad/ip4r/archive/refs/tags/2.4.2.tar.gz -O ip4r.tar.gz && \
|
||||||
wget https://github.com/RhodiumToad/ip4r/archive/refs/tags/2.4.2.tar.gz -O ip4r.tar.gz && \
|
|
||||||
echo "0f7b1f159974f49a47842a8ab6751aecca1ed1142b6d5e38d81b064b2ead1b4b ip4r.tar.gz" | sha256sum --check && \
|
echo "0f7b1f159974f49a47842a8ab6751aecca1ed1142b6d5e38d81b064b2ead1b4b ip4r.tar.gz" | sha256sum --check && \
|
||||||
mkdir ip4r-src && cd ip4r-src && tar xzf ../ip4r.tar.gz --strip-components=1 -C . && \
|
mkdir ip4r-src && cd ip4r-src && tar xzf ../ip4r.tar.gz --strip-components=1 -C . && \
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) PG_CONFIG=/usr/local/pgsql/bin/pg_config && \
|
make -j $(getconf _NPROCESSORS_ONLN) PG_CONFIG=/usr/local/pgsql/bin/pg_config && \
|
||||||
@@ -425,10 +463,9 @@ FROM build-deps AS prefix-pg-build
|
|||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
||||||
|
|
||||||
RUN case "${PG_VERSION}" in "v17") \
|
# not version-specific
|
||||||
echo "v17 extensions are not supported yet. Quit" && exit 0;; \
|
# last release v1.2.10 - Jul 5, 2023
|
||||||
esac && \
|
RUN wget https://github.com/dimitri/prefix/archive/refs/tags/v1.2.10.tar.gz -O prefix.tar.gz && \
|
||||||
wget https://github.com/dimitri/prefix/archive/refs/tags/v1.2.10.tar.gz -O prefix.tar.gz && \
|
|
||||||
echo "4342f251432a5f6fb05b8597139d3ccde8dcf87e8ca1498e7ee931ca057a8575 prefix.tar.gz" | sha256sum --check && \
|
echo "4342f251432a5f6fb05b8597139d3ccde8dcf87e8ca1498e7ee931ca057a8575 prefix.tar.gz" | sha256sum --check && \
|
||||||
mkdir prefix-src && cd prefix-src && tar xzf ../prefix.tar.gz --strip-components=1 -C . && \
|
mkdir prefix-src && cd prefix-src && tar xzf ../prefix.tar.gz --strip-components=1 -C . && \
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) PG_CONFIG=/usr/local/pgsql/bin/pg_config && \
|
make -j $(getconf _NPROCESSORS_ONLN) PG_CONFIG=/usr/local/pgsql/bin/pg_config && \
|
||||||
@@ -445,10 +482,9 @@ FROM build-deps AS hll-pg-build
|
|||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
||||||
|
|
||||||
RUN case "${PG_VERSION}" in "v17") \
|
# not version-specific
|
||||||
echo "v17 extensions are not supported yet. Quit" && exit 0;; \
|
# last release v2.18 - Aug 29, 2023
|
||||||
esac && \
|
RUN wget https://github.com/citusdata/postgresql-hll/archive/refs/tags/v2.18.tar.gz -O hll.tar.gz && \
|
||||||
wget https://github.com/citusdata/postgresql-hll/archive/refs/tags/v2.18.tar.gz -O hll.tar.gz && \
|
|
||||||
echo "e2f55a6f4c4ab95ee4f1b4a2b73280258c5136b161fe9d059559556079694f0e hll.tar.gz" | sha256sum --check && \
|
echo "e2f55a6f4c4ab95ee4f1b4a2b73280258c5136b161fe9d059559556079694f0e hll.tar.gz" | sha256sum --check && \
|
||||||
mkdir hll-src && cd hll-src && tar xzf ../hll.tar.gz --strip-components=1 -C . && \
|
mkdir hll-src && cd hll-src && tar xzf ../hll.tar.gz --strip-components=1 -C . && \
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) PG_CONFIG=/usr/local/pgsql/bin/pg_config && \
|
make -j $(getconf _NPROCESSORS_ONLN) PG_CONFIG=/usr/local/pgsql/bin/pg_config && \
|
||||||
@@ -659,11 +695,10 @@ FROM build-deps AS pg-roaringbitmap-pg-build
|
|||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
||||||
|
|
||||||
|
# not version-specific
|
||||||
|
# last release v0.5.4 - Jun 28, 2022
|
||||||
ENV PATH="/usr/local/pgsql/bin/:$PATH"
|
ENV PATH="/usr/local/pgsql/bin/:$PATH"
|
||||||
RUN case "${PG_VERSION}" in "v17") \
|
RUN wget https://github.com/ChenHuajun/pg_roaringbitmap/archive/refs/tags/v0.5.4.tar.gz -O pg_roaringbitmap.tar.gz && \
|
||||||
echo "v17 extensions is not supported yet by pg_roaringbitmap. Quit" && exit 0;; \
|
|
||||||
esac && \
|
|
||||||
wget https://github.com/ChenHuajun/pg_roaringbitmap/archive/refs/tags/v0.5.4.tar.gz -O pg_roaringbitmap.tar.gz && \
|
|
||||||
echo "b75201efcb1c2d1b014ec4ae6a22769cc7a224e6e406a587f5784a37b6b5a2aa pg_roaringbitmap.tar.gz" | sha256sum --check && \
|
echo "b75201efcb1c2d1b014ec4ae6a22769cc7a224e6e406a587f5784a37b6b5a2aa pg_roaringbitmap.tar.gz" | sha256sum --check && \
|
||||||
mkdir pg_roaringbitmap-src && cd pg_roaringbitmap-src && tar xzf ../pg_roaringbitmap.tar.gz --strip-components=1 -C . && \
|
mkdir pg_roaringbitmap-src && cd pg_roaringbitmap-src && tar xzf ../pg_roaringbitmap.tar.gz --strip-components=1 -C . && \
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) && \
|
make -j $(getconf _NPROCESSORS_ONLN) && \
|
||||||
@@ -680,12 +715,27 @@ FROM build-deps AS pg-semver-pg-build
|
|||||||
ARG PG_VERSION
|
ARG PG_VERSION
|
||||||
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
COPY --from=pg-build /usr/local/pgsql/ /usr/local/pgsql/
|
||||||
|
|
||||||
|
# Release 0.40.0 breaks backward compatibility with previous versions
|
||||||
|
# see release note https://github.com/theory/pg-semver/releases/tag/v0.40.0
|
||||||
|
# Use new version only for v17
|
||||||
|
#
|
||||||
|
# last release v0.40.0 - Jul 22, 2024
|
||||||
ENV PATH="/usr/local/pgsql/bin/:$PATH"
|
ENV PATH="/usr/local/pgsql/bin/:$PATH"
|
||||||
RUN case "${PG_VERSION}" in "v17") \
|
RUN case "${PG_VERSION}" in \
|
||||||
echo "v17 is not supported yet by pg_semver. Quit" && exit 0;; \
|
"v17") \
|
||||||
|
export SEMVER_VERSION=0.40.0 \
|
||||||
|
export SEMVER_CHECKSUM=3e50bcc29a0e2e481e7b6d2bc937cadc5f5869f55d983b5a1aafeb49f5425cfc \
|
||||||
|
;; \
|
||||||
|
"v14" | "v15" | "v16") \
|
||||||
|
export SEMVER_VERSION=0.32.1 \
|
||||||
|
export SEMVER_CHECKSUM=fbdaf7512026d62eec03fad8687c15ed509b6ba395bff140acd63d2e4fbe25d7 \
|
||||||
|
;; \
|
||||||
|
*) \
|
||||||
|
echo "unexpected PostgreSQL version" && exit 1 \
|
||||||
|
;; \
|
||||||
esac && \
|
esac && \
|
||||||
wget https://github.com/theory/pg-semver/archive/refs/tags/v0.32.1.tar.gz -O pg_semver.tar.gz && \
|
wget https://github.com/theory/pg-semver/archive/refs/tags/v${SEMVER_VERSION}.tar.gz -O pg_semver.tar.gz && \
|
||||||
echo "fbdaf7512026d62eec03fad8687c15ed509b6ba395bff140acd63d2e4fbe25d7 pg_semver.tar.gz" | sha256sum --check && \
|
echo "${SEMVER_CHECKSUM} pg_semver.tar.gz" | sha256sum --check && \
|
||||||
mkdir pg_semver-src && cd pg_semver-src && tar xzf ../pg_semver.tar.gz --strip-components=1 -C . && \
|
mkdir pg_semver-src && cd pg_semver-src && tar xzf ../pg_semver.tar.gz --strip-components=1 -C . && \
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) && \
|
make -j $(getconf _NPROCESSORS_ONLN) && \
|
||||||
make -j $(getconf _NPROCESSORS_ONLN) install && \
|
make -j $(getconf _NPROCESSORS_ONLN) install && \
|
||||||
|
|||||||
@@ -402,8 +402,7 @@ fn start_postgres(
|
|||||||
) -> Result<(Option<PostgresHandle>, StartPostgresResult)> {
|
) -> Result<(Option<PostgresHandle>, StartPostgresResult)> {
|
||||||
// We got all we need, update the state.
|
// We got all we need, update the state.
|
||||||
let mut state = compute.state.lock().unwrap();
|
let mut state = compute.state.lock().unwrap();
|
||||||
state.status = ComputeStatus::Init;
|
state.set_status(ComputeStatus::Init, &compute.state_changed);
|
||||||
compute.state_changed.notify_all();
|
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"running compute with features: {:?}",
|
"running compute with features: {:?}",
|
||||||
|
|||||||
@@ -109,6 +109,18 @@ impl ComputeState {
|
|||||||
metrics: ComputeMetrics::default(),
|
metrics: ComputeMetrics::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_status(&mut self, status: ComputeStatus, state_changed: &Condvar) {
|
||||||
|
let prev = self.status;
|
||||||
|
info!("Changing compute status from {} to {}", prev, status);
|
||||||
|
self.status = status;
|
||||||
|
state_changed.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_failed_status(&mut self, err: anyhow::Error, state_changed: &Condvar) {
|
||||||
|
self.error = Some(format!("{err:?}"));
|
||||||
|
self.set_status(ComputeStatus::Failed, state_changed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ComputeState {
|
impl Default for ComputeState {
|
||||||
@@ -303,15 +315,12 @@ impl ComputeNode {
|
|||||||
|
|
||||||
pub fn set_status(&self, status: ComputeStatus) {
|
pub fn set_status(&self, status: ComputeStatus) {
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
state.status = status;
|
state.set_status(status, &self.state_changed);
|
||||||
self.state_changed.notify_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_failed_status(&self, err: anyhow::Error) {
|
pub fn set_failed_status(&self, err: anyhow::Error) {
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
state.error = Some(format!("{err:?}"));
|
state.set_failed_status(err, &self.state_changed);
|
||||||
state.status = ComputeStatus::Failed;
|
|
||||||
self.state_changed.notify_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_status(&self) -> ComputeStatus {
|
pub fn get_status(&self) -> ComputeStatus {
|
||||||
@@ -1475,6 +1484,28 @@ LIMIT 100",
|
|||||||
info!("Pageserver config changed");
|
info!("Pageserver config changed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gather info about installed extensions
|
||||||
|
pub fn get_installed_extensions(&self) -> Result<()> {
|
||||||
|
let connstr = self.connstr.clone();
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("failed to create runtime");
|
||||||
|
let result = rt
|
||||||
|
.block_on(crate::installed_extensions::get_installed_extensions(
|
||||||
|
connstr,
|
||||||
|
))
|
||||||
|
.expect("failed to get installed extensions");
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string(&result).expect("failed to serialize extensions list")
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn forward_termination_signal() {
|
pub fn forward_termination_signal() {
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ fn configurator_main_loop(compute: &Arc<ComputeNode>) {
|
|||||||
// Re-check the status after waking up
|
// Re-check the status after waking up
|
||||||
if state.status == ComputeStatus::ConfigurationPending {
|
if state.status == ComputeStatus::ConfigurationPending {
|
||||||
info!("got configuration request");
|
info!("got configuration request");
|
||||||
state.status = ComputeStatus::Configuration;
|
state.set_status(ComputeStatus::Configuration, &compute.state_changed);
|
||||||
compute.state_changed.notify_all();
|
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
let mut new_status = ComputeStatus::Failed;
|
let mut new_status = ComputeStatus::Failed;
|
||||||
|
|||||||
@@ -165,6 +165,32 @@ async fn routes(req: Request<Body>, compute: &Arc<ComputeNode>) -> Response<Body
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the list of installed extensions
|
||||||
|
// currently only used in python tests
|
||||||
|
// TODO: call it from cplane
|
||||||
|
(&Method::GET, "/installed_extensions") => {
|
||||||
|
info!("serving /installed_extensions GET request");
|
||||||
|
let status = compute.get_status();
|
||||||
|
if status != ComputeStatus::Running {
|
||||||
|
let msg = format!(
|
||||||
|
"invalid compute status for extensions request: {:?}",
|
||||||
|
status
|
||||||
|
);
|
||||||
|
error!(msg);
|
||||||
|
return Response::new(Body::from(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
let connstr = compute.connstr.clone();
|
||||||
|
let res = crate::installed_extensions::get_installed_extensions(connstr).await;
|
||||||
|
match res {
|
||||||
|
Ok(res) => render_json(Body::from(serde_json::to_string(&res).unwrap())),
|
||||||
|
Err(e) => render_json_error(
|
||||||
|
&format!("could not get list of installed extensions: {}", e),
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// download extension files from remote extension storage on demand
|
// download extension files from remote extension storage on demand
|
||||||
(&Method::POST, route) if route.starts_with("/extension_server/") => {
|
(&Method::POST, route) if route.starts_with("/extension_server/") => {
|
||||||
info!("serving {:?} POST request", route);
|
info!("serving {:?} POST request", route);
|
||||||
@@ -288,8 +314,7 @@ async fn handle_configure_request(
|
|||||||
return Err((msg, StatusCode::PRECONDITION_FAILED));
|
return Err((msg, StatusCode::PRECONDITION_FAILED));
|
||||||
}
|
}
|
||||||
state.pspec = Some(parsed_spec);
|
state.pspec = Some(parsed_spec);
|
||||||
state.status = ComputeStatus::ConfigurationPending;
|
state.set_status(ComputeStatus::ConfigurationPending, &compute.state_changed);
|
||||||
compute.state_changed.notify_all();
|
|
||||||
drop(state);
|
drop(state);
|
||||||
info!("set new spec and notified waiters");
|
info!("set new spec and notified waiters");
|
||||||
}
|
}
|
||||||
@@ -362,15 +387,15 @@ async fn handle_terminate_request(compute: &Arc<ComputeNode>) -> Result<(), (Str
|
|||||||
}
|
}
|
||||||
if state.status != ComputeStatus::Empty && state.status != ComputeStatus::Running {
|
if state.status != ComputeStatus::Empty && state.status != ComputeStatus::Running {
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"invalid compute status for termination request: {:?}",
|
"invalid compute status for termination request: {}",
|
||||||
state.status.clone()
|
state.status
|
||||||
);
|
);
|
||||||
return Err((msg, StatusCode::PRECONDITION_FAILED));
|
return Err((msg, StatusCode::PRECONDITION_FAILED));
|
||||||
}
|
}
|
||||||
state.status = ComputeStatus::TerminationPending;
|
state.set_status(ComputeStatus::TerminationPending, &compute.state_changed);
|
||||||
compute.state_changed.notify_all();
|
|
||||||
drop(state);
|
drop(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
forward_termination_signal();
|
forward_termination_signal();
|
||||||
info!("sent signal and notified waiters");
|
info!("sent signal and notified waiters");
|
||||||
|
|
||||||
@@ -384,7 +409,8 @@ async fn handle_terminate_request(compute: &Arc<ComputeNode>) -> Result<(), (Str
|
|||||||
while state.status != ComputeStatus::Terminated {
|
while state.status != ComputeStatus::Terminated {
|
||||||
state = c.state_changed.wait(state).unwrap();
|
state = c.state_changed.wait(state).unwrap();
|
||||||
info!(
|
info!(
|
||||||
"waiting for compute to become Terminated, current status: {:?}",
|
"waiting for compute to become {}, current status: {:?}",
|
||||||
|
ComputeStatus::Terminated,
|
||||||
state.status
|
state.status
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,20 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/ComputeInsights"
|
$ref: "#/components/schemas/ComputeInsights"
|
||||||
|
|
||||||
|
/installed_extensions:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Info
|
||||||
|
summary: Get installed extensions.
|
||||||
|
description: ""
|
||||||
|
operationId: getInstalledExtensions
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: List of installed extensions
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/InstalledExtensions"
|
||||||
/info:
|
/info:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -395,6 +409,24 @@ components:
|
|||||||
- configuration
|
- configuration
|
||||||
example: running
|
example: running
|
||||||
|
|
||||||
|
InstalledExtensions:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
extensions:
|
||||||
|
description: Contains list of installed extensions.
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
extname:
|
||||||
|
type: string
|
||||||
|
versions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
n_databases:
|
||||||
|
type: integer
|
||||||
|
|
||||||
#
|
#
|
||||||
# Errors
|
# Errors
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
use compute_api::responses::{InstalledExtension, InstalledExtensions};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use postgres::{Client, NoTls};
|
||||||
|
use tokio::task;
|
||||||
|
|
||||||
|
/// We don't reuse get_existing_dbs() just for code clarity
|
||||||
|
/// and to make database listing query here more explicit.
|
||||||
|
///
|
||||||
|
/// Limit the number of databases to 500 to avoid excessive load.
|
||||||
|
fn list_dbs(client: &mut Client) -> Result<Vec<String>> {
|
||||||
|
// `pg_database.datconnlimit = -2` means that the database is in the
|
||||||
|
// invalid state
|
||||||
|
let databases = client
|
||||||
|
.query(
|
||||||
|
"SELECT datname FROM pg_catalog.pg_database
|
||||||
|
WHERE datallowconn
|
||||||
|
AND datconnlimit <> - 2
|
||||||
|
LIMIT 500",
|
||||||
|
&[],
|
||||||
|
)?
|
||||||
|
.iter()
|
||||||
|
.map(|row| {
|
||||||
|
let db: String = row.get("datname");
|
||||||
|
db
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(databases)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connect to every database (see list_dbs above) and get the list of installed extensions.
|
||||||
|
/// Same extension can be installed in multiple databases with different versions,
|
||||||
|
/// we only keep the highest and lowest version across all databases.
|
||||||
|
pub async fn get_installed_extensions(connstr: Url) -> Result<InstalledExtensions> {
|
||||||
|
let mut connstr = connstr.clone();
|
||||||
|
|
||||||
|
task::spawn_blocking(move || {
|
||||||
|
let mut client = Client::connect(connstr.as_str(), NoTls)?;
|
||||||
|
let databases: Vec<String> = list_dbs(&mut client)?;
|
||||||
|
|
||||||
|
let mut extensions_map: HashMap<String, InstalledExtension> = HashMap::new();
|
||||||
|
for db in databases.iter() {
|
||||||
|
connstr.set_path(db);
|
||||||
|
let mut db_client = Client::connect(connstr.as_str(), NoTls)?;
|
||||||
|
let extensions: Vec<(String, String)> = db_client
|
||||||
|
.query(
|
||||||
|
"SELECT extname, extversion FROM pg_catalog.pg_extension;",
|
||||||
|
&[],
|
||||||
|
)?
|
||||||
|
.iter()
|
||||||
|
.map(|row| (row.get("extname"), row.get("extversion")))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for (extname, v) in extensions.iter() {
|
||||||
|
let version = v.to_string();
|
||||||
|
extensions_map
|
||||||
|
.entry(extname.to_string())
|
||||||
|
.and_modify(|e| {
|
||||||
|
e.versions.insert(version.clone());
|
||||||
|
// count the number of databases where the extension is installed
|
||||||
|
e.n_databases += 1;
|
||||||
|
})
|
||||||
|
.or_insert(InstalledExtension {
|
||||||
|
extname: extname.to_string(),
|
||||||
|
versions: HashSet::from([version.clone()]),
|
||||||
|
n_databases: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(InstalledExtensions {
|
||||||
|
extensions: extensions_map.values().cloned().collect(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ pub mod catalog;
|
|||||||
pub mod compute;
|
pub mod compute;
|
||||||
pub mod disk_quota;
|
pub mod disk_quota;
|
||||||
pub mod extension_server;
|
pub mod extension_server;
|
||||||
|
pub mod installed_extensions;
|
||||||
pub mod local_proxy;
|
pub mod local_proxy;
|
||||||
pub mod lsn_lease;
|
pub mod lsn_lease;
|
||||||
mod migration;
|
mod migration;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@@ -189,6 +190,15 @@ pub fn handle_roles(spec: &ComputeSpec, client: &mut Client) -> Result<()> {
|
|||||||
let mut xact = client.transaction()?;
|
let mut xact = client.transaction()?;
|
||||||
let existing_roles: Vec<Role> = get_existing_roles(&mut xact)?;
|
let existing_roles: Vec<Role> = get_existing_roles(&mut xact)?;
|
||||||
|
|
||||||
|
let mut jwks_roles = HashSet::new();
|
||||||
|
if let Some(local_proxy) = &spec.local_proxy_config {
|
||||||
|
for jwks_setting in local_proxy.jwks.iter().flatten() {
|
||||||
|
for role_name in &jwks_setting.role_names {
|
||||||
|
jwks_roles.insert(role_name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Print a list of existing Postgres roles (only in debug mode)
|
// Print a list of existing Postgres roles (only in debug mode)
|
||||||
if span_enabled!(Level::INFO) {
|
if span_enabled!(Level::INFO) {
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
@@ -308,6 +318,9 @@ pub fn handle_roles(spec: &ComputeSpec, client: &mut Client) -> Result<()> {
|
|||||||
"CREATE ROLE {} INHERIT CREATEROLE CREATEDB BYPASSRLS REPLICATION IN ROLE neon_superuser",
|
"CREATE ROLE {} INHERIT CREATEROLE CREATEDB BYPASSRLS REPLICATION IN ROLE neon_superuser",
|
||||||
name.pg_quote()
|
name.pg_quote()
|
||||||
);
|
);
|
||||||
|
if jwks_roles.contains(name.as_str()) {
|
||||||
|
query = format!("CREATE ROLE {}", name.pg_quote());
|
||||||
|
}
|
||||||
info!("running role create query: '{}'", &query);
|
info!("running role create query: '{}'", &query);
|
||||||
query.push_str(&role.to_pg_options());
|
query.push_str(&role.to_pg_options());
|
||||||
xact.execute(query.as_str(), &[])?;
|
xact.execute(query.as_str(), &[])?;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
//! Structs representing the JSON formats used in the compute_ctl's HTTP API.
|
//! Structs representing the JSON formats used in the compute_ctl's HTTP API.
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize, Serializer};
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
|
|
||||||
@@ -58,6 +61,21 @@ pub enum ComputeStatus {
|
|||||||
Terminated,
|
Terminated,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for ComputeStatus {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ComputeStatus::Empty => f.write_str("empty"),
|
||||||
|
ComputeStatus::ConfigurationPending => f.write_str("configuration-pending"),
|
||||||
|
ComputeStatus::Init => f.write_str("init"),
|
||||||
|
ComputeStatus::Running => f.write_str("running"),
|
||||||
|
ComputeStatus::Configuration => f.write_str("configuration"),
|
||||||
|
ComputeStatus::Failed => f.write_str("failed"),
|
||||||
|
ComputeStatus::TerminationPending => f.write_str("termination-pending"),
|
||||||
|
ComputeStatus::Terminated => f.write_str("terminated"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn rfc3339_serialize<S>(x: &Option<DateTime<Utc>>, s: S) -> Result<S::Ok, S::Error>
|
fn rfc3339_serialize<S>(x: &Option<DateTime<Utc>>, s: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
@@ -138,3 +156,15 @@ pub enum ControlPlaneComputeStatus {
|
|||||||
// should be able to start with provided spec.
|
// should be able to start with provided spec.
|
||||||
Attached,
|
Attached,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Serialize)]
|
||||||
|
pub struct InstalledExtension {
|
||||||
|
pub extname: String,
|
||||||
|
pub versions: HashSet<String>,
|
||||||
|
pub n_databases: u32, // Number of databases using this extension
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Serialize)]
|
||||||
|
pub struct InstalledExtensions {
|
||||||
|
pub extensions: Vec<InstalledExtension>,
|
||||||
|
}
|
||||||
|
|||||||
@@ -104,8 +104,7 @@ pub struct ConfigToml {
|
|||||||
pub image_compression: ImageCompressionAlgorithm,
|
pub image_compression: ImageCompressionAlgorithm,
|
||||||
pub ephemeral_bytes_per_memory_kb: usize,
|
pub ephemeral_bytes_per_memory_kb: usize,
|
||||||
pub l0_flush: Option<crate::models::L0FlushConfig>,
|
pub l0_flush: Option<crate::models::L0FlushConfig>,
|
||||||
pub virtual_file_direct_io: crate::models::virtual_file::DirectIoMode,
|
pub virtual_file_io_mode: Option<crate::models::virtual_file::IoMode>,
|
||||||
pub io_buffer_alignment: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
@@ -388,10 +387,7 @@ impl Default for ConfigToml {
|
|||||||
image_compression: (DEFAULT_IMAGE_COMPRESSION),
|
image_compression: (DEFAULT_IMAGE_COMPRESSION),
|
||||||
ephemeral_bytes_per_memory_kb: (DEFAULT_EPHEMERAL_BYTES_PER_MEMORY_KB),
|
ephemeral_bytes_per_memory_kb: (DEFAULT_EPHEMERAL_BYTES_PER_MEMORY_KB),
|
||||||
l0_flush: None,
|
l0_flush: None,
|
||||||
virtual_file_direct_io: crate::models::virtual_file::DirectIoMode::default(),
|
virtual_file_io_mode: None,
|
||||||
|
|
||||||
io_buffer_alignment: DEFAULT_IO_BUFFER_ALIGNMENT,
|
|
||||||
|
|
||||||
tenant_config: TenantConfigToml::default(),
|
tenant_config: TenantConfigToml::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -972,8 +972,6 @@ pub struct TopTenantShardsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod virtual_file {
|
pub mod virtual_file {
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Copy,
|
Copy,
|
||||||
Clone,
|
Clone,
|
||||||
@@ -994,50 +992,45 @@ pub mod virtual_file {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Direct IO modes for a pageserver.
|
/// Direct IO modes for a pageserver.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize, Default)]
|
#[derive(
|
||||||
#[serde(tag = "mode", rename_all = "kebab-case", deny_unknown_fields)]
|
Copy,
|
||||||
pub enum DirectIoMode {
|
Clone,
|
||||||
/// Direct IO disabled (uses usual buffered IO).
|
PartialEq,
|
||||||
#[default]
|
Eq,
|
||||||
Disabled,
|
Hash,
|
||||||
/// Direct IO disabled (performs checks and perf simulations).
|
strum_macros::EnumString,
|
||||||
Evaluate {
|
strum_macros::Display,
|
||||||
/// Alignment check level
|
serde_with::DeserializeFromStr,
|
||||||
alignment_check: DirectIoAlignmentCheckLevel,
|
serde_with::SerializeDisplay,
|
||||||
/// Latency padded for performance simulation.
|
Debug,
|
||||||
latency_padding: DirectIoLatencyPadding,
|
)]
|
||||||
},
|
#[strum(serialize_all = "kebab-case")]
|
||||||
/// Direct IO enabled.
|
#[repr(u8)]
|
||||||
Enabled {
|
pub enum IoMode {
|
||||||
/// Actions to perform on alignment error.
|
/// Uses buffered IO.
|
||||||
on_alignment_error: DirectIoOnAlignmentErrorAction,
|
Buffered,
|
||||||
},
|
/// Uses direct IO, error out if the operation fails.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
Direct,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize, Default)]
|
impl IoMode {
|
||||||
#[serde(rename_all = "kebab-case")]
|
pub const fn preferred() -> Self {
|
||||||
pub enum DirectIoAlignmentCheckLevel {
|
Self::Buffered
|
||||||
#[default]
|
}
|
||||||
Error,
|
|
||||||
Log,
|
|
||||||
None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize, Default)]
|
impl TryFrom<u8> for IoMode {
|
||||||
#[serde(rename_all = "kebab-case")]
|
type Error = u8;
|
||||||
pub enum DirectIoOnAlignmentErrorAction {
|
|
||||||
Error,
|
|
||||||
#[default]
|
|
||||||
FallbackToBuffered,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize, Default)]
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
#[serde(tag = "type", rename_all = "kebab-case")]
|
Ok(match value {
|
||||||
pub enum DirectIoLatencyPadding {
|
v if v == (IoMode::Buffered as u8) => IoMode::Buffered,
|
||||||
/// Pad virtual file operations with IO to a fake file.
|
#[cfg(target_os = "linux")]
|
||||||
FakeFileRW { path: PathBuf },
|
v if v == (IoMode::Direct as u8) => IoMode::Direct,
|
||||||
#[default]
|
x => return Err(x),
|
||||||
None,
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -496,26 +496,12 @@ impl RemoteStorage for AzureBlobStorage {
|
|||||||
builder = builder.if_match(IfMatchCondition::NotMatch(etag.to_string()))
|
builder = builder.if_match(IfMatchCondition::NotMatch(etag.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.download_for_builder(builder, cancel).await
|
if let Some((start, end)) = opts.byte_range() {
|
||||||
}
|
builder = builder.range(match end {
|
||||||
|
Some(end) => Range::Range(start..end),
|
||||||
async fn download_byte_range(
|
None => Range::RangeFrom(start..),
|
||||||
&self,
|
});
|
||||||
from: &RemotePath,
|
}
|
||||||
start_inclusive: u64,
|
|
||||||
end_exclusive: Option<u64>,
|
|
||||||
cancel: &CancellationToken,
|
|
||||||
) -> Result<Download, DownloadError> {
|
|
||||||
let blob_client = self.client.blob_client(self.relative_path_to_name(from));
|
|
||||||
|
|
||||||
let mut builder = blob_client.get();
|
|
||||||
|
|
||||||
let range: Range = if let Some(end_exclusive) = end_exclusive {
|
|
||||||
(start_inclusive..end_exclusive).into()
|
|
||||||
} else {
|
|
||||||
(start_inclusive..).into()
|
|
||||||
};
|
|
||||||
builder = builder.range(range);
|
|
||||||
|
|
||||||
self.download_for_builder(builder, cancel).await
|
self.download_for_builder(builder, cancel).await
|
||||||
}
|
}
|
||||||
|
|||||||
+122
-58
@@ -19,7 +19,8 @@ mod simulate_failures;
|
|||||||
mod support;
|
mod support;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap, fmt::Debug, num::NonZeroU32, pin::Pin, sync::Arc, time::SystemTime,
|
collections::HashMap, fmt::Debug, num::NonZeroU32, ops::Bound, pin::Pin, sync::Arc,
|
||||||
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
@@ -162,11 +163,60 @@ pub struct Listing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Options for downloads. The default value is a plain GET.
|
/// Options for downloads. The default value is a plain GET.
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DownloadOpts {
|
pub struct DownloadOpts {
|
||||||
/// If given, returns [`DownloadError::Unmodified`] if the object still has
|
/// If given, returns [`DownloadError::Unmodified`] if the object still has
|
||||||
/// the same ETag (using If-None-Match).
|
/// the same ETag (using If-None-Match).
|
||||||
pub etag: Option<Etag>,
|
pub etag: Option<Etag>,
|
||||||
|
/// The start of the byte range to download, or unbounded.
|
||||||
|
pub byte_start: Bound<u64>,
|
||||||
|
/// The end of the byte range to download, or unbounded. Must be after the
|
||||||
|
/// start bound.
|
||||||
|
pub byte_end: Bound<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DownloadOpts {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
etag: Default::default(),
|
||||||
|
byte_start: Bound::Unbounded,
|
||||||
|
byte_end: Bound::Unbounded,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DownloadOpts {
|
||||||
|
/// Returns the byte range with inclusive start and exclusive end, or None
|
||||||
|
/// if unbounded.
|
||||||
|
pub fn byte_range(&self) -> Option<(u64, Option<u64>)> {
|
||||||
|
if self.byte_start == Bound::Unbounded && self.byte_end == Bound::Unbounded {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let start = match self.byte_start {
|
||||||
|
Bound::Excluded(i) => i + 1,
|
||||||
|
Bound::Included(i) => i,
|
||||||
|
Bound::Unbounded => 0,
|
||||||
|
};
|
||||||
|
let end = match self.byte_end {
|
||||||
|
Bound::Excluded(i) => Some(i),
|
||||||
|
Bound::Included(i) => Some(i + 1),
|
||||||
|
Bound::Unbounded => None,
|
||||||
|
};
|
||||||
|
if let Some(end) = end {
|
||||||
|
assert!(start < end, "range end {end} at or before start {start}");
|
||||||
|
}
|
||||||
|
Some((start, end))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the byte range as an RFC 2616 Range header value with inclusive
|
||||||
|
/// bounds, or None if unbounded.
|
||||||
|
pub fn byte_range_header(&self) -> Option<String> {
|
||||||
|
self.byte_range()
|
||||||
|
.map(|(start, end)| (start, end.map(|end| end - 1))) // make end inclusive
|
||||||
|
.map(|(start, end)| match end {
|
||||||
|
Some(end) => format!("bytes={start}-{end}"),
|
||||||
|
None => format!("bytes={start}-"),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Storage (potentially remote) API to manage its state.
|
/// Storage (potentially remote) API to manage its state.
|
||||||
@@ -257,21 +307,6 @@ pub trait RemoteStorage: Send + Sync + 'static {
|
|||||||
cancel: &CancellationToken,
|
cancel: &CancellationToken,
|
||||||
) -> Result<Download, DownloadError>;
|
) -> Result<Download, DownloadError>;
|
||||||
|
|
||||||
/// Streams a given byte range of the remote storage entry contents.
|
|
||||||
///
|
|
||||||
/// The returned download stream will obey initial timeout and cancellation signal by erroring
|
|
||||||
/// on whichever happens first. Only one of the reasons will fail the stream, which is usually
|
|
||||||
/// enough for `tokio::io::copy_buf` usage. If needed the error can be filtered out.
|
|
||||||
///
|
|
||||||
/// Returns the metadata, if any was stored with the file previously.
|
|
||||||
async fn download_byte_range(
|
|
||||||
&self,
|
|
||||||
from: &RemotePath,
|
|
||||||
start_inclusive: u64,
|
|
||||||
end_exclusive: Option<u64>,
|
|
||||||
cancel: &CancellationToken,
|
|
||||||
) -> Result<Download, DownloadError>;
|
|
||||||
|
|
||||||
/// Delete a single path from remote storage.
|
/// Delete a single path from remote storage.
|
||||||
///
|
///
|
||||||
/// If the operation fails because of timeout or cancellation, the root cause of the error will be
|
/// If the operation fails because of timeout or cancellation, the root cause of the error will be
|
||||||
@@ -425,33 +460,6 @@ impl<Other: RemoteStorage> GenericRemoteStorage<Arc<Other>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn download_byte_range(
|
|
||||||
&self,
|
|
||||||
from: &RemotePath,
|
|
||||||
start_inclusive: u64,
|
|
||||||
end_exclusive: Option<u64>,
|
|
||||||
cancel: &CancellationToken,
|
|
||||||
) -> Result<Download, DownloadError> {
|
|
||||||
match self {
|
|
||||||
Self::LocalFs(s) => {
|
|
||||||
s.download_byte_range(from, start_inclusive, end_exclusive, cancel)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Self::AwsS3(s) => {
|
|
||||||
s.download_byte_range(from, start_inclusive, end_exclusive, cancel)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Self::AzureBlob(s) => {
|
|
||||||
s.download_byte_range(from, start_inclusive, end_exclusive, cancel)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Self::Unreliable(s) => {
|
|
||||||
s.download_byte_range(from, start_inclusive, end_exclusive, cancel)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [`RemoteStorage::delete`]
|
/// See [`RemoteStorage::delete`]
|
||||||
pub async fn delete(
|
pub async fn delete(
|
||||||
&self,
|
&self,
|
||||||
@@ -573,20 +581,6 @@ impl GenericRemoteStorage {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Downloads the storage object into the `to_path` provided.
|
|
||||||
/// `byte_range` could be specified to dowload only a part of the file, if needed.
|
|
||||||
pub async fn download_storage_object(
|
|
||||||
&self,
|
|
||||||
byte_range: Option<(u64, Option<u64>)>,
|
|
||||||
from: &RemotePath,
|
|
||||||
cancel: &CancellationToken,
|
|
||||||
) -> Result<Download, DownloadError> {
|
|
||||||
match byte_range {
|
|
||||||
Some((start, end)) => self.download_byte_range(from, start, end, cancel).await,
|
|
||||||
None => self.download(from, &DownloadOpts::default(), cancel).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The name of the bucket/container/etc.
|
/// The name of the bucket/container/etc.
|
||||||
pub fn bucket_name(&self) -> Option<&str> {
|
pub fn bucket_name(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
@@ -660,6 +654,76 @@ impl ConcurrencyLimiter {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
/// DownloadOpts::byte_range() should generate (inclusive, exclusive) ranges
|
||||||
|
/// with optional end bound, or None when unbounded.
|
||||||
|
#[test]
|
||||||
|
fn download_opts_byte_range() {
|
||||||
|
// Consider using test_case or a similar table-driven test framework.
|
||||||
|
let cases = [
|
||||||
|
// (byte_start, byte_end, expected)
|
||||||
|
(Bound::Unbounded, Bound::Unbounded, None),
|
||||||
|
(Bound::Unbounded, Bound::Included(7), Some((0, Some(8)))),
|
||||||
|
(Bound::Unbounded, Bound::Excluded(7), Some((0, Some(7)))),
|
||||||
|
(Bound::Included(3), Bound::Unbounded, Some((3, None))),
|
||||||
|
(Bound::Included(3), Bound::Included(7), Some((3, Some(8)))),
|
||||||
|
(Bound::Included(3), Bound::Excluded(7), Some((3, Some(7)))),
|
||||||
|
(Bound::Excluded(3), Bound::Unbounded, Some((4, None))),
|
||||||
|
(Bound::Excluded(3), Bound::Included(7), Some((4, Some(8)))),
|
||||||
|
(Bound::Excluded(3), Bound::Excluded(7), Some((4, Some(7)))),
|
||||||
|
// 1-sized ranges are fine, 0 aren't and will panic (separate test).
|
||||||
|
(Bound::Included(3), Bound::Included(3), Some((3, Some(4)))),
|
||||||
|
(Bound::Included(3), Bound::Excluded(4), Some((3, Some(4)))),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (byte_start, byte_end, expect) in cases {
|
||||||
|
let opts = DownloadOpts {
|
||||||
|
byte_start,
|
||||||
|
byte_end,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let result = opts.byte_range();
|
||||||
|
assert_eq!(
|
||||||
|
result, expect,
|
||||||
|
"byte_start={byte_start:?} byte_end={byte_end:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check generated HTTP header, which uses an inclusive range.
|
||||||
|
let expect_header = expect.map(|(start, end)| match end {
|
||||||
|
Some(end) => format!("bytes={start}-{}", end - 1), // inclusive end
|
||||||
|
None => format!("bytes={start}-"),
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
opts.byte_range_header(),
|
||||||
|
expect_header,
|
||||||
|
"byte_start={byte_start:?} byte_end={byte_end:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DownloadOpts::byte_range() zero-sized byte range should panic.
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn download_opts_byte_range_zero() {
|
||||||
|
DownloadOpts {
|
||||||
|
byte_start: Bound::Included(3),
|
||||||
|
byte_end: Bound::Excluded(3),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.byte_range();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DownloadOpts::byte_range() negative byte range should panic.
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn download_opts_byte_range_negative() {
|
||||||
|
DownloadOpts {
|
||||||
|
byte_start: Bound::Included(3),
|
||||||
|
byte_end: Bound::Included(2),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.byte_range();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_object_name() {
|
fn test_object_name() {
|
||||||
let k = RemotePath::new(Utf8Path::new("a/b/c")).unwrap();
|
let k = RemotePath::new(Utf8Path::new("a/b/c")).unwrap();
|
||||||
|
|||||||
@@ -506,54 +506,7 @@ impl RemoteStorage for LocalFs {
|
|||||||
return Err(DownloadError::Unmodified);
|
return Err(DownloadError::Unmodified);
|
||||||
}
|
}
|
||||||
|
|
||||||
let source = ReaderStream::new(
|
let mut file = fs::OpenOptions::new()
|
||||||
fs::OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.open(&target_path)
|
|
||||||
.await
|
|
||||||
.with_context(|| {
|
|
||||||
format!("Failed to open source file {target_path:?} to use in the download")
|
|
||||||
})
|
|
||||||
.map_err(DownloadError::Other)?,
|
|
||||||
);
|
|
||||||
|
|
||||||
let metadata = self
|
|
||||||
.read_storage_metadata(&target_path)
|
|
||||||
.await
|
|
||||||
.map_err(DownloadError::Other)?;
|
|
||||||
|
|
||||||
let cancel_or_timeout = crate::support::cancel_or_timeout(self.timeout, cancel.clone());
|
|
||||||
let source = crate::support::DownloadStream::new(cancel_or_timeout, source);
|
|
||||||
|
|
||||||
Ok(Download {
|
|
||||||
metadata,
|
|
||||||
last_modified: file_metadata
|
|
||||||
.modified()
|
|
||||||
.map_err(|e| DownloadError::Other(anyhow::anyhow!(e).context("Reading mtime")))?,
|
|
||||||
etag,
|
|
||||||
download_stream: Box::pin(source),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn download_byte_range(
|
|
||||||
&self,
|
|
||||||
from: &RemotePath,
|
|
||||||
start_inclusive: u64,
|
|
||||||
end_exclusive: Option<u64>,
|
|
||||||
cancel: &CancellationToken,
|
|
||||||
) -> Result<Download, DownloadError> {
|
|
||||||
if let Some(end_exclusive) = end_exclusive {
|
|
||||||
if end_exclusive <= start_inclusive {
|
|
||||||
return Err(DownloadError::Other(anyhow::anyhow!("Invalid range, start ({start_inclusive}) is not less than end_exclusive ({end_exclusive:?})")));
|
|
||||||
};
|
|
||||||
if start_inclusive == end_exclusive.saturating_sub(1) {
|
|
||||||
return Err(DownloadError::Other(anyhow::anyhow!("Invalid range, start ({start_inclusive}) and end_exclusive ({end_exclusive:?}) difference is zero bytes")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let target_path = from.with_base(&self.storage_root);
|
|
||||||
let file_metadata = file_metadata(&target_path).await?;
|
|
||||||
let mut source = tokio::fs::OpenOptions::new()
|
|
||||||
.read(true)
|
.read(true)
|
||||||
.open(&target_path)
|
.open(&target_path)
|
||||||
.await
|
.await
|
||||||
@@ -562,31 +515,29 @@ impl RemoteStorage for LocalFs {
|
|||||||
})
|
})
|
||||||
.map_err(DownloadError::Other)?;
|
.map_err(DownloadError::Other)?;
|
||||||
|
|
||||||
let len = source
|
let mut take = file_metadata.len();
|
||||||
.metadata()
|
if let Some((start, end)) = opts.byte_range() {
|
||||||
.await
|
if start > 0 {
|
||||||
.context("query file length")
|
file.seek(io::SeekFrom::Start(start))
|
||||||
.map_err(DownloadError::Other)?
|
.await
|
||||||
.len();
|
.context("Failed to seek to the range start in a local storage file")
|
||||||
|
.map_err(DownloadError::Other)?;
|
||||||
|
}
|
||||||
|
if let Some(end) = end {
|
||||||
|
take = end - start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
source
|
let source = ReaderStream::new(file.take(take));
|
||||||
.seek(io::SeekFrom::Start(start_inclusive))
|
|
||||||
.await
|
|
||||||
.context("Failed to seek to the range start in a local storage file")
|
|
||||||
.map_err(DownloadError::Other)?;
|
|
||||||
|
|
||||||
let metadata = self
|
let metadata = self
|
||||||
.read_storage_metadata(&target_path)
|
.read_storage_metadata(&target_path)
|
||||||
.await
|
.await
|
||||||
.map_err(DownloadError::Other)?;
|
.map_err(DownloadError::Other)?;
|
||||||
|
|
||||||
let source = source.take(end_exclusive.unwrap_or(len) - start_inclusive);
|
|
||||||
let source = ReaderStream::new(source);
|
|
||||||
|
|
||||||
let cancel_or_timeout = crate::support::cancel_or_timeout(self.timeout, cancel.clone());
|
let cancel_or_timeout = crate::support::cancel_or_timeout(self.timeout, cancel.clone());
|
||||||
let source = crate::support::DownloadStream::new(cancel_or_timeout, source);
|
let source = crate::support::DownloadStream::new(cancel_or_timeout, source);
|
||||||
|
|
||||||
let etag = mock_etag(&file_metadata);
|
|
||||||
Ok(Download {
|
Ok(Download {
|
||||||
metadata,
|
metadata,
|
||||||
last_modified: file_metadata
|
last_modified: file_metadata
|
||||||
@@ -688,7 +639,7 @@ mod fs_tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use camino_tempfile::tempdir;
|
use camino_tempfile::tempdir;
|
||||||
use std::{collections::HashMap, io::Write};
|
use std::{collections::HashMap, io::Write, ops::Bound};
|
||||||
|
|
||||||
async fn read_and_check_metadata(
|
async fn read_and_check_metadata(
|
||||||
storage: &LocalFs,
|
storage: &LocalFs,
|
||||||
@@ -804,10 +755,12 @@ mod fs_tests {
|
|||||||
let (first_part_local, second_part_local) = uploaded_bytes.split_at(3);
|
let (first_part_local, second_part_local) = uploaded_bytes.split_at(3);
|
||||||
|
|
||||||
let first_part_download = storage
|
let first_part_download = storage
|
||||||
.download_byte_range(
|
.download(
|
||||||
&upload_target,
|
&upload_target,
|
||||||
0,
|
&DownloadOpts {
|
||||||
Some(first_part_local.len() as u64),
|
byte_end: Bound::Excluded(first_part_local.len() as u64),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
&cancel,
|
&cancel,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -823,10 +776,15 @@ mod fs_tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let second_part_download = storage
|
let second_part_download = storage
|
||||||
.download_byte_range(
|
.download(
|
||||||
&upload_target,
|
&upload_target,
|
||||||
first_part_local.len() as u64,
|
&DownloadOpts {
|
||||||
Some((first_part_local.len() + second_part_local.len()) as u64),
|
byte_start: Bound::Included(first_part_local.len() as u64),
|
||||||
|
byte_end: Bound::Excluded(
|
||||||
|
(first_part_local.len() + second_part_local.len()) as u64,
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
&cancel,
|
&cancel,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -842,7 +800,14 @@ mod fs_tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let suffix_bytes = storage
|
let suffix_bytes = storage
|
||||||
.download_byte_range(&upload_target, 13, None, &cancel)
|
.download(
|
||||||
|
&upload_target,
|
||||||
|
&DownloadOpts {
|
||||||
|
byte_start: Bound::Included(13),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&cancel,
|
||||||
|
)
|
||||||
.await?
|
.await?
|
||||||
.download_stream;
|
.download_stream;
|
||||||
let suffix_bytes = aggregate(suffix_bytes).await?;
|
let suffix_bytes = aggregate(suffix_bytes).await?;
|
||||||
@@ -850,7 +815,7 @@ mod fs_tests {
|
|||||||
assert_eq!(upload_name, suffix);
|
assert_eq!(upload_name, suffix);
|
||||||
|
|
||||||
let all_bytes = storage
|
let all_bytes = storage
|
||||||
.download_byte_range(&upload_target, 0, None, &cancel)
|
.download(&upload_target, &DownloadOpts::default(), &cancel)
|
||||||
.await?
|
.await?
|
||||||
.download_stream;
|
.download_stream;
|
||||||
let all_bytes = aggregate(all_bytes).await?;
|
let all_bytes = aggregate(all_bytes).await?;
|
||||||
@@ -861,48 +826,26 @@ mod fs_tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn download_file_range_negative() -> anyhow::Result<()> {
|
#[should_panic(expected = "at or before start")]
|
||||||
let (storage, cancel) = create_storage()?;
|
async fn download_file_range_negative() {
|
||||||
|
let (storage, cancel) = create_storage().unwrap();
|
||||||
let upload_name = "upload_1";
|
let upload_name = "upload_1";
|
||||||
let upload_target = upload_dummy_file(&storage, upload_name, None, &cancel).await?;
|
let upload_target = upload_dummy_file(&storage, upload_name, None, &cancel)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let start = 1_000_000_000;
|
storage
|
||||||
let end = start + 1;
|
.download(
|
||||||
match storage
|
|
||||||
.download_byte_range(
|
|
||||||
&upload_target,
|
&upload_target,
|
||||||
start,
|
&DownloadOpts {
|
||||||
Some(end), // exclusive end
|
byte_start: Bound::Included(10),
|
||||||
|
byte_end: Bound::Excluded(10),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
&cancel,
|
&cancel,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
.unwrap();
|
||||||
Ok(_) => panic!("Should not allow downloading wrong ranges"),
|
|
||||||
Err(e) => {
|
|
||||||
let error_string = e.to_string();
|
|
||||||
assert!(error_string.contains("zero bytes"));
|
|
||||||
assert!(error_string.contains(&start.to_string()));
|
|
||||||
assert!(error_string.contains(&end.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let start = 10000;
|
|
||||||
let end = 234;
|
|
||||||
assert!(start > end, "Should test an incorrect range");
|
|
||||||
match storage
|
|
||||||
.download_byte_range(&upload_target, start, Some(end), &cancel)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => panic!("Should not allow downloading wrong ranges"),
|
|
||||||
Err(e) => {
|
|
||||||
let error_string = e.to_string();
|
|
||||||
assert!(error_string.contains("Invalid range"));
|
|
||||||
assert!(error_string.contains(&start.to_string()));
|
|
||||||
assert!(error_string.contains(&end.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -945,10 +888,12 @@ mod fs_tests {
|
|||||||
let (first_part_local, _) = uploaded_bytes.split_at(3);
|
let (first_part_local, _) = uploaded_bytes.split_at(3);
|
||||||
|
|
||||||
let partial_download_with_metadata = storage
|
let partial_download_with_metadata = storage
|
||||||
.download_byte_range(
|
.download(
|
||||||
&upload_target,
|
&upload_target,
|
||||||
0,
|
&DownloadOpts {
|
||||||
Some(first_part_local.len() as u64),
|
byte_end: Bound::Excluded(first_part_local.len() as u64),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
&cancel,
|
&cancel,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -804,34 +804,7 @@ impl RemoteStorage for S3Bucket {
|
|||||||
bucket: self.bucket_name.clone(),
|
bucket: self.bucket_name.clone(),
|
||||||
key: self.relative_path_to_s3_object(from),
|
key: self.relative_path_to_s3_object(from),
|
||||||
etag: opts.etag.as_ref().map(|e| e.to_string()),
|
etag: opts.etag.as_ref().map(|e| e.to_string()),
|
||||||
range: None,
|
range: opts.byte_range_header(),
|
||||||
},
|
|
||||||
cancel,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn download_byte_range(
|
|
||||||
&self,
|
|
||||||
from: &RemotePath,
|
|
||||||
start_inclusive: u64,
|
|
||||||
end_exclusive: Option<u64>,
|
|
||||||
cancel: &CancellationToken,
|
|
||||||
) -> Result<Download, DownloadError> {
|
|
||||||
// S3 accepts ranges as https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
|
|
||||||
// and needs both ends to be exclusive
|
|
||||||
let end_inclusive = end_exclusive.map(|end| end.saturating_sub(1));
|
|
||||||
let range = Some(match end_inclusive {
|
|
||||||
Some(end_inclusive) => format!("bytes={start_inclusive}-{end_inclusive}"),
|
|
||||||
None => format!("bytes={start_inclusive}-"),
|
|
||||||
});
|
|
||||||
|
|
||||||
self.download_object(
|
|
||||||
GetObjectRequest {
|
|
||||||
bucket: self.bucket_name.clone(),
|
|
||||||
key: self.relative_path_to_s3_object(from),
|
|
||||||
etag: None,
|
|
||||||
range,
|
|
||||||
},
|
},
|
||||||
cancel,
|
cancel,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -170,28 +170,13 @@ impl RemoteStorage for UnreliableWrapper {
|
|||||||
opts: &DownloadOpts,
|
opts: &DownloadOpts,
|
||||||
cancel: &CancellationToken,
|
cancel: &CancellationToken,
|
||||||
) -> Result<Download, DownloadError> {
|
) -> Result<Download, DownloadError> {
|
||||||
|
// Note: We treat any byte range as an "attempt" of the same operation.
|
||||||
|
// We don't pay attention to the ranges. That's good enough for now.
|
||||||
self.attempt(RemoteOp::Download(from.clone()))
|
self.attempt(RemoteOp::Download(from.clone()))
|
||||||
.map_err(DownloadError::Other)?;
|
.map_err(DownloadError::Other)?;
|
||||||
self.inner.download(from, opts, cancel).await
|
self.inner.download(from, opts, cancel).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_byte_range(
|
|
||||||
&self,
|
|
||||||
from: &RemotePath,
|
|
||||||
start_inclusive: u64,
|
|
||||||
end_exclusive: Option<u64>,
|
|
||||||
cancel: &CancellationToken,
|
|
||||||
) -> Result<Download, DownloadError> {
|
|
||||||
// Note: We treat any download_byte_range as an "attempt" of the same
|
|
||||||
// operation. We don't pay attention to the ranges. That's good enough
|
|
||||||
// for now.
|
|
||||||
self.attempt(RemoteOp::Download(from.clone()))
|
|
||||||
.map_err(DownloadError::Other)?;
|
|
||||||
self.inner
|
|
||||||
.download_byte_range(from, start_inclusive, end_exclusive, cancel)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete(&self, path: &RemotePath, cancel: &CancellationToken) -> anyhow::Result<()> {
|
async fn delete(&self, path: &RemotePath, cancel: &CancellationToken) -> anyhow::Result<()> {
|
||||||
self.delete_inner(path, true, cancel).await
|
self.delete_inner(path, true, cancel).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use anyhow::Context;
|
|||||||
use camino::Utf8Path;
|
use camino::Utf8Path;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use remote_storage::{DownloadError, DownloadOpts, ListingMode, ListingObject, RemotePath};
|
use remote_storage::{DownloadError, DownloadOpts, ListingMode, ListingObject, RemotePath};
|
||||||
|
use std::ops::Bound;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{collections::HashSet, num::NonZeroU32};
|
use std::{collections::HashSet, num::NonZeroU32};
|
||||||
use test_context::test_context;
|
use test_context::test_context;
|
||||||
@@ -293,7 +294,15 @@ async fn upload_download_works(ctx: &mut MaybeEnabledStorage) -> anyhow::Result<
|
|||||||
// Full range (end specified)
|
// Full range (end specified)
|
||||||
let dl = ctx
|
let dl = ctx
|
||||||
.client
|
.client
|
||||||
.download_byte_range(&path, 0, Some(len as u64), &cancel)
|
.download(
|
||||||
|
&path,
|
||||||
|
&DownloadOpts {
|
||||||
|
byte_start: Bound::Included(0),
|
||||||
|
byte_end: Bound::Excluded(len as u64),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&cancel,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let buf = download_to_vec(dl).await?;
|
let buf = download_to_vec(dl).await?;
|
||||||
assert_eq!(&buf, &orig);
|
assert_eq!(&buf, &orig);
|
||||||
@@ -301,7 +310,15 @@ async fn upload_download_works(ctx: &mut MaybeEnabledStorage) -> anyhow::Result<
|
|||||||
// partial range (end specified)
|
// partial range (end specified)
|
||||||
let dl = ctx
|
let dl = ctx
|
||||||
.client
|
.client
|
||||||
.download_byte_range(&path, 4, Some(10), &cancel)
|
.download(
|
||||||
|
&path,
|
||||||
|
&DownloadOpts {
|
||||||
|
byte_start: Bound::Included(4),
|
||||||
|
byte_end: Bound::Excluded(10),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&cancel,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let buf = download_to_vec(dl).await?;
|
let buf = download_to_vec(dl).await?;
|
||||||
assert_eq!(&buf, &orig[4..10]);
|
assert_eq!(&buf, &orig[4..10]);
|
||||||
@@ -309,7 +326,15 @@ async fn upload_download_works(ctx: &mut MaybeEnabledStorage) -> anyhow::Result<
|
|||||||
// partial range (end beyond real end)
|
// partial range (end beyond real end)
|
||||||
let dl = ctx
|
let dl = ctx
|
||||||
.client
|
.client
|
||||||
.download_byte_range(&path, 8, Some(len as u64 * 100), &cancel)
|
.download(
|
||||||
|
&path,
|
||||||
|
&DownloadOpts {
|
||||||
|
byte_start: Bound::Included(8),
|
||||||
|
byte_end: Bound::Excluded(len as u64 * 100),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&cancel,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let buf = download_to_vec(dl).await?;
|
let buf = download_to_vec(dl).await?;
|
||||||
assert_eq!(&buf, &orig[8..]);
|
assert_eq!(&buf, &orig[8..]);
|
||||||
@@ -317,7 +342,14 @@ async fn upload_download_works(ctx: &mut MaybeEnabledStorage) -> anyhow::Result<
|
|||||||
// Partial range (end unspecified)
|
// Partial range (end unspecified)
|
||||||
let dl = ctx
|
let dl = ctx
|
||||||
.client
|
.client
|
||||||
.download_byte_range(&path, 4, None, &cancel)
|
.download(
|
||||||
|
&path,
|
||||||
|
&DownloadOpts {
|
||||||
|
byte_start: Bound::Included(4),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&cancel,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let buf = download_to_vec(dl).await?;
|
let buf = download_to_vec(dl).await?;
|
||||||
assert_eq!(&buf, &orig[4..]);
|
assert_eq!(&buf, &orig[4..]);
|
||||||
@@ -325,7 +357,14 @@ async fn upload_download_works(ctx: &mut MaybeEnabledStorage) -> anyhow::Result<
|
|||||||
// Full range (end unspecified)
|
// Full range (end unspecified)
|
||||||
let dl = ctx
|
let dl = ctx
|
||||||
.client
|
.client
|
||||||
.download_byte_range(&path, 0, None, &cancel)
|
.download(
|
||||||
|
&path,
|
||||||
|
&DownloadOpts {
|
||||||
|
byte_start: Bound::Included(0),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&cancel,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let buf = download_to_vec(dl).await?;
|
let buf = download_to_vec(dl).await?;
|
||||||
assert_eq!(&buf, &orig);
|
assert_eq!(&buf, &orig);
|
||||||
|
|||||||
@@ -31,9 +31,12 @@ pub enum Scope {
|
|||||||
/// The scope used by pageservers in upcalls to storage controller and cloud control plane
|
/// The scope used by pageservers in upcalls to storage controller and cloud control plane
|
||||||
#[serde(rename = "generations_api")]
|
#[serde(rename = "generations_api")]
|
||||||
GenerationsApi,
|
GenerationsApi,
|
||||||
/// Allows access to control plane managment API and some storage controller endpoints.
|
/// Allows access to control plane managment API and all storage controller endpoints.
|
||||||
Admin,
|
Admin,
|
||||||
|
|
||||||
|
/// Allows access to control plane & storage controller endpoints used in infrastructure automation (e.g. node registration)
|
||||||
|
Infra,
|
||||||
|
|
||||||
/// Allows access to storage controller APIs used by the scrubber, to interrogate the state
|
/// Allows access to storage controller APIs used by the scrubber, to interrogate the state
|
||||||
/// of a tenant & post scrub results.
|
/// of a tenant & post scrub results.
|
||||||
Scrubber,
|
Scrubber,
|
||||||
|
|||||||
@@ -79,8 +79,7 @@ pub struct Config {
|
|||||||
/// memory.
|
/// memory.
|
||||||
///
|
///
|
||||||
/// The default value of `0.15` means that we *guarantee* sending upscale requests if the
|
/// The default value of `0.15` means that we *guarantee* sending upscale requests if the
|
||||||
/// cgroup is using more than 85% of total memory (even if we're *not* separately reserving
|
/// cgroup is using more than 85% of total memory.
|
||||||
/// memory for the file cache).
|
|
||||||
cgroup_min_overhead_fraction: f64,
|
cgroup_min_overhead_fraction: f64,
|
||||||
|
|
||||||
cgroup_downscale_threshold_buffer_bytes: u64,
|
cgroup_downscale_threshold_buffer_bytes: u64,
|
||||||
@@ -97,24 +96,12 @@ impl Default for Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
fn cgroup_threshold(&self, total_mem: u64, file_cache_disk_size: u64) -> u64 {
|
fn cgroup_threshold(&self, total_mem: u64) -> u64 {
|
||||||
// If the file cache is in tmpfs, then it will count towards shmem usage of the cgroup,
|
// We want our threshold to be met gracefully instead of letting postgres get OOM-killed
|
||||||
// and thus be non-reclaimable, so we should allow for additional memory usage.
|
// (or if there's room, spilling to swap).
|
||||||
//
|
|
||||||
// If the file cache sits on disk, our desired stable system state is for it to be fully
|
|
||||||
// page cached (its contents should only be paged to/from disk in situations where we can't
|
|
||||||
// upscale fast enough). Page-cached memory is reclaimable, so we need to lower the
|
|
||||||
// threshold for non-reclaimable memory so we scale up *before* the kernel starts paging
|
|
||||||
// out the file cache.
|
|
||||||
let memory_remaining_for_cgroup = total_mem.saturating_sub(file_cache_disk_size);
|
|
||||||
|
|
||||||
// Even if we're not separately making room for the file cache (if it's in tmpfs), we still
|
|
||||||
// want our threshold to be met gracefully instead of letting postgres get OOM-killed.
|
|
||||||
// So we guarantee that there's at least `cgroup_min_overhead_fraction` of total memory
|
// So we guarantee that there's at least `cgroup_min_overhead_fraction` of total memory
|
||||||
// remaining above the threshold.
|
// remaining above the threshold.
|
||||||
let max_threshold = (total_mem as f64 * (1.0 - self.cgroup_min_overhead_fraction)) as u64;
|
(total_mem as f64 * (1.0 - self.cgroup_min_overhead_fraction)) as u64
|
||||||
|
|
||||||
memory_remaining_for_cgroup.min(max_threshold)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,11 +136,6 @@ impl Runner {
|
|||||||
|
|
||||||
let mem = get_total_system_memory();
|
let mem = get_total_system_memory();
|
||||||
|
|
||||||
let mut file_cache_disk_size = 0;
|
|
||||||
|
|
||||||
// We need to process file cache initialization before cgroup initialization, so that the memory
|
|
||||||
// allocated to the file cache is appropriately taken into account when we decide the cgroup's
|
|
||||||
// memory limits.
|
|
||||||
if let Some(connstr) = &args.pgconnstr {
|
if let Some(connstr) = &args.pgconnstr {
|
||||||
info!("initializing file cache");
|
info!("initializing file cache");
|
||||||
let config = FileCacheConfig::default();
|
let config = FileCacheConfig::default();
|
||||||
@@ -184,7 +166,6 @@ impl Runner {
|
|||||||
info!("file cache size actually got set to {actual_size}")
|
info!("file cache size actually got set to {actual_size}")
|
||||||
}
|
}
|
||||||
|
|
||||||
file_cache_disk_size = actual_size;
|
|
||||||
state.filecache = Some(file_cache);
|
state.filecache = Some(file_cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +188,7 @@ impl Runner {
|
|||||||
cgroup.watch(hist_tx).await
|
cgroup.watch(hist_tx).await
|
||||||
});
|
});
|
||||||
|
|
||||||
let threshold = state.config.cgroup_threshold(mem, file_cache_disk_size);
|
let threshold = state.config.cgroup_threshold(mem);
|
||||||
info!(threshold, "set initial cgroup threshold",);
|
info!(threshold, "set initial cgroup threshold",);
|
||||||
|
|
||||||
state.cgroup = Some(CgroupState {
|
state.cgroup = Some(CgroupState {
|
||||||
@@ -259,9 +240,7 @@ impl Runner {
|
|||||||
return Ok((false, status.to_owned()));
|
return Ok((false, status.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_threshold = self
|
let new_threshold = self.config.cgroup_threshold(usable_system_memory);
|
||||||
.config
|
|
||||||
.cgroup_threshold(usable_system_memory, expected_file_cache_size);
|
|
||||||
|
|
||||||
let current = last_history.avg_non_reclaimable;
|
let current = last_history.avg_non_reclaimable;
|
||||||
|
|
||||||
@@ -282,13 +261,11 @@ impl Runner {
|
|||||||
|
|
||||||
// The downscaling has been approved. Downscale the file cache, then the cgroup.
|
// The downscaling has been approved. Downscale the file cache, then the cgroup.
|
||||||
let mut status = vec![];
|
let mut status = vec![];
|
||||||
let mut file_cache_disk_size = 0;
|
|
||||||
if let Some(file_cache) = &mut self.filecache {
|
if let Some(file_cache) = &mut self.filecache {
|
||||||
let actual_usage = file_cache
|
let actual_usage = file_cache
|
||||||
.set_file_cache_size(expected_file_cache_size)
|
.set_file_cache_size(expected_file_cache_size)
|
||||||
.await
|
.await
|
||||||
.context("failed to set file cache size")?;
|
.context("failed to set file cache size")?;
|
||||||
file_cache_disk_size = actual_usage;
|
|
||||||
let message = format!(
|
let message = format!(
|
||||||
"set file cache size to {} MiB",
|
"set file cache size to {} MiB",
|
||||||
bytes_to_mebibytes(actual_usage),
|
bytes_to_mebibytes(actual_usage),
|
||||||
@@ -298,9 +275,7 @@ impl Runner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(cgroup) = &mut self.cgroup {
|
if let Some(cgroup) = &mut self.cgroup {
|
||||||
let new_threshold = self
|
let new_threshold = self.config.cgroup_threshold(usable_system_memory);
|
||||||
.config
|
|
||||||
.cgroup_threshold(usable_system_memory, file_cache_disk_size);
|
|
||||||
|
|
||||||
let message = format!(
|
let message = format!(
|
||||||
"set cgroup memory threshold from {} MiB to {} MiB, of new total {} MiB",
|
"set cgroup memory threshold from {} MiB to {} MiB, of new total {} MiB",
|
||||||
@@ -329,7 +304,6 @@ impl Runner {
|
|||||||
let new_mem = resources.mem;
|
let new_mem = resources.mem;
|
||||||
let usable_system_memory = new_mem.saturating_sub(self.config.sys_buffer_bytes);
|
let usable_system_memory = new_mem.saturating_sub(self.config.sys_buffer_bytes);
|
||||||
|
|
||||||
let mut file_cache_disk_size = 0;
|
|
||||||
if let Some(file_cache) = &mut self.filecache {
|
if let Some(file_cache) = &mut self.filecache {
|
||||||
let expected_usage = file_cache.config.calculate_cache_size(usable_system_memory);
|
let expected_usage = file_cache.config.calculate_cache_size(usable_system_memory);
|
||||||
info!(
|
info!(
|
||||||
@@ -342,7 +316,6 @@ impl Runner {
|
|||||||
.set_file_cache_size(expected_usage)
|
.set_file_cache_size(expected_usage)
|
||||||
.await
|
.await
|
||||||
.context("failed to set file cache size")?;
|
.context("failed to set file cache size")?;
|
||||||
file_cache_disk_size = actual_usage;
|
|
||||||
|
|
||||||
if actual_usage != expected_usage {
|
if actual_usage != expected_usage {
|
||||||
warn!(
|
warn!(
|
||||||
@@ -354,9 +327,7 @@ impl Runner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(cgroup) = &mut self.cgroup {
|
if let Some(cgroup) = &mut self.cgroup {
|
||||||
let new_threshold = self
|
let new_threshold = self.config.cgroup_threshold(usable_system_memory);
|
||||||
.config
|
|
||||||
.cgroup_threshold(usable_system_memory, file_cache_disk_size);
|
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"set cgroup memory threshold from {} MiB to {} MiB of new total {} MiB",
|
"set cgroup memory threshold from {} MiB to {} MiB of new total {} MiB",
|
||||||
|
|||||||
@@ -164,11 +164,7 @@ fn criterion_benchmark(c: &mut Criterion) {
|
|||||||
let conf: &'static PageServerConf = Box::leak(Box::new(
|
let conf: &'static PageServerConf = Box::leak(Box::new(
|
||||||
pageserver::config::PageServerConf::dummy_conf(temp_dir.path().to_path_buf()),
|
pageserver::config::PageServerConf::dummy_conf(temp_dir.path().to_path_buf()),
|
||||||
));
|
));
|
||||||
virtual_file::init(
|
virtual_file::init(16384, virtual_file::io_engine_for_bench());
|
||||||
16384,
|
|
||||||
virtual_file::io_engine_for_bench(),
|
|
||||||
pageserver_api::config::defaults::DEFAULT_IO_BUFFER_ALIGNMENT,
|
|
||||||
);
|
|
||||||
page_cache::init(conf.page_cache_size);
|
page_cache::init(conf.page_cache_size);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -540,10 +540,13 @@ impl Client {
|
|||||||
.map_err(Error::ReceiveBody)
|
.map_err(Error::ReceiveBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configs io buffer alignment at runtime.
|
/// Configs io mode at runtime.
|
||||||
pub async fn put_io_alignment(&self, align: usize) -> Result<()> {
|
pub async fn put_io_mode(
|
||||||
let uri = format!("{}/v1/io_alignment", self.mgmt_api_endpoint);
|
&self,
|
||||||
self.request(Method::PUT, uri, align)
|
mode: &pageserver_api::models::virtual_file::IoMode,
|
||||||
|
) -> Result<()> {
|
||||||
|
let uri = format!("{}/v1/io_mode", self.mgmt_api_endpoint);
|
||||||
|
self.request(Method::PUT, uri, mode)
|
||||||
.await?
|
.await?
|
||||||
.json()
|
.json()
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -152,11 +152,7 @@ pub(crate) async fn main(cmd: &AnalyzeLayerMapCmd) -> Result<()> {
|
|||||||
let ctx = RequestContext::new(TaskKind::DebugTool, DownloadBehavior::Error);
|
let ctx = RequestContext::new(TaskKind::DebugTool, DownloadBehavior::Error);
|
||||||
|
|
||||||
// Initialize virtual_file (file desriptor cache) and page cache which are needed to access layer persistent B-Tree.
|
// Initialize virtual_file (file desriptor cache) and page cache which are needed to access layer persistent B-Tree.
|
||||||
pageserver::virtual_file::init(
|
pageserver::virtual_file::init(10, virtual_file::api::IoEngineKind::StdFs);
|
||||||
10,
|
|
||||||
virtual_file::api::IoEngineKind::StdFs,
|
|
||||||
pageserver_api::config::defaults::DEFAULT_IO_BUFFER_ALIGNMENT,
|
|
||||||
);
|
|
||||||
pageserver::page_cache::init(100);
|
pageserver::page_cache::init(100);
|
||||||
|
|
||||||
let mut total_delta_layers = 0usize;
|
let mut total_delta_layers = 0usize;
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ pub(crate) enum LayerCmd {
|
|||||||
|
|
||||||
async fn read_delta_file(path: impl AsRef<Path>, ctx: &RequestContext) -> Result<()> {
|
async fn read_delta_file(path: impl AsRef<Path>, ctx: &RequestContext) -> Result<()> {
|
||||||
let path = Utf8Path::from_path(path.as_ref()).expect("non-Unicode path");
|
let path = Utf8Path::from_path(path.as_ref()).expect("non-Unicode path");
|
||||||
virtual_file::init(10, virtual_file::api::IoEngineKind::StdFs, 1);
|
virtual_file::init(10, virtual_file::api::IoEngineKind::StdFs);
|
||||||
page_cache::init(100);
|
page_cache::init(100);
|
||||||
let file = VirtualFile::open(path, ctx).await?;
|
let file = VirtualFile::open(path, ctx).await?;
|
||||||
let file_id = page_cache::next_file_id();
|
let file_id = page_cache::next_file_id();
|
||||||
@@ -190,11 +190,7 @@ pub(crate) async fn main(cmd: &LayerCmd) -> Result<()> {
|
|||||||
new_tenant_id,
|
new_tenant_id,
|
||||||
new_timeline_id,
|
new_timeline_id,
|
||||||
} => {
|
} => {
|
||||||
pageserver::virtual_file::init(
|
pageserver::virtual_file::init(10, virtual_file::api::IoEngineKind::StdFs);
|
||||||
10,
|
|
||||||
virtual_file::api::IoEngineKind::StdFs,
|
|
||||||
pageserver_api::config::defaults::DEFAULT_IO_BUFFER_ALIGNMENT,
|
|
||||||
);
|
|
||||||
pageserver::page_cache::init(100);
|
pageserver::page_cache::init(100);
|
||||||
|
|
||||||
let ctx = RequestContext::new(TaskKind::DebugTool, DownloadBehavior::Error);
|
let ctx = RequestContext::new(TaskKind::DebugTool, DownloadBehavior::Error);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ use pageserver::{
|
|||||||
tenant::{dump_layerfile_from_path, metadata::TimelineMetadata},
|
tenant::{dump_layerfile_from_path, metadata::TimelineMetadata},
|
||||||
virtual_file,
|
virtual_file,
|
||||||
};
|
};
|
||||||
use pageserver_api::{config::defaults::DEFAULT_IO_BUFFER_ALIGNMENT, shard::TenantShardId};
|
use pageserver_api::shard::TenantShardId;
|
||||||
use postgres_ffi::ControlFileData;
|
use postgres_ffi::ControlFileData;
|
||||||
use remote_storage::{RemotePath, RemoteStorageConfig};
|
use remote_storage::{RemotePath, RemoteStorageConfig};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
@@ -205,11 +205,7 @@ fn read_pg_control_file(control_file_path: &Utf8Path) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
async fn print_layerfile(path: &Utf8Path) -> anyhow::Result<()> {
|
async fn print_layerfile(path: &Utf8Path) -> anyhow::Result<()> {
|
||||||
// Basic initialization of things that don't change after startup
|
// Basic initialization of things that don't change after startup
|
||||||
virtual_file::init(
|
virtual_file::init(10, virtual_file::api::IoEngineKind::StdFs);
|
||||||
10,
|
|
||||||
virtual_file::api::IoEngineKind::StdFs,
|
|
||||||
DEFAULT_IO_BUFFER_ALIGNMENT,
|
|
||||||
);
|
|
||||||
page_cache::init(100);
|
page_cache::init(100);
|
||||||
let ctx = RequestContext::new(TaskKind::DebugTool, DownloadBehavior::Error);
|
let ctx = RequestContext::new(TaskKind::DebugTool, DownloadBehavior::Error);
|
||||||
dump_layerfile_from_path(path, true, &ctx).await
|
dump_layerfile_from_path(path, true, &ctx).await
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ pub(crate) struct Args {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
set_io_engine: Option<pageserver_api::models::virtual_file::IoEngineKind>,
|
set_io_engine: Option<pageserver_api::models::virtual_file::IoEngineKind>,
|
||||||
|
|
||||||
/// Before starting the benchmark, live-reconfigure the pageserver to use specified alignment for io buffers.
|
/// Before starting the benchmark, live-reconfigure the pageserver to use specified io mode (buffered vs. direct).
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
set_io_alignment: Option<usize>,
|
set_io_mode: Option<pageserver_api::models::virtual_file::IoMode>,
|
||||||
|
|
||||||
targets: Option<Vec<TenantTimelineId>>,
|
targets: Option<Vec<TenantTimelineId>>,
|
||||||
}
|
}
|
||||||
@@ -129,8 +129,8 @@ async fn main_impl(
|
|||||||
mgmt_api_client.put_io_engine(engine_str).await?;
|
mgmt_api_client.put_io_engine(engine_str).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(align) = args.set_io_alignment {
|
if let Some(mode) = &args.set_io_mode {
|
||||||
mgmt_api_client.put_io_alignment(align).await?;
|
mgmt_api_client.put_io_mode(mode).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// discover targets
|
// discover targets
|
||||||
|
|||||||
+14
-9
@@ -14,14 +14,19 @@ pub fn check_permission(claims: &Claims, tenant_id: Option<TenantId>) -> Result<
|
|||||||
}
|
}
|
||||||
(Scope::PageServerApi, None) => Ok(()), // access to management api for PageServerApi scope
|
(Scope::PageServerApi, None) => Ok(()), // access to management api for PageServerApi scope
|
||||||
(Scope::PageServerApi, Some(_)) => Ok(()), // access to tenant api using PageServerApi scope
|
(Scope::PageServerApi, Some(_)) => Ok(()), // access to tenant api using PageServerApi scope
|
||||||
(Scope::Admin | Scope::SafekeeperData | Scope::GenerationsApi | Scope::Scrubber, _) => {
|
(
|
||||||
Err(AuthError(
|
Scope::Admin
|
||||||
format!(
|
| Scope::SafekeeperData
|
||||||
"JWT scope '{:?}' is ineligible for Pageserver auth",
|
| Scope::GenerationsApi
|
||||||
claims.scope
|
| Scope::Infra
|
||||||
)
|
| Scope::Scrubber,
|
||||||
.into(),
|
_,
|
||||||
))
|
) => Err(AuthError(
|
||||||
}
|
format!(
|
||||||
|
"JWT scope '{:?}' is ineligible for Pageserver auth",
|
||||||
|
claims.scope
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,8 +125,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// after setting up logging, log the effective IO engine choice and read path implementations
|
// after setting up logging, log the effective IO engine choice and read path implementations
|
||||||
info!(?conf.virtual_file_io_engine, "starting with virtual_file IO engine");
|
info!(?conf.virtual_file_io_engine, "starting with virtual_file IO engine");
|
||||||
info!(?conf.virtual_file_direct_io, "starting with virtual_file Direct IO settings");
|
info!(?conf.virtual_file_io_mode, "starting with virtual_file IO mode");
|
||||||
info!(?conf.io_buffer_alignment, "starting with setting for IO buffer alignment");
|
|
||||||
|
|
||||||
// The tenants directory contains all the pageserver local disk state.
|
// The tenants directory contains all the pageserver local disk state.
|
||||||
// Create if not exists and make sure all the contents are durable before proceeding.
|
// Create if not exists and make sure all the contents are durable before proceeding.
|
||||||
@@ -168,11 +167,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
let scenario = failpoint_support::init();
|
let scenario = failpoint_support::init();
|
||||||
|
|
||||||
// Basic initialization of things that don't change after startup
|
// Basic initialization of things that don't change after startup
|
||||||
virtual_file::init(
|
virtual_file::init(conf.max_file_descriptors, conf.virtual_file_io_engine);
|
||||||
conf.max_file_descriptors,
|
|
||||||
conf.virtual_file_io_engine,
|
|
||||||
conf.io_buffer_alignment,
|
|
||||||
);
|
|
||||||
page_cache::init(conf.page_cache_size);
|
page_cache::init(conf.page_cache_size);
|
||||||
|
|
||||||
start_pageserver(launch_ts, conf).context("Failed to start pageserver")?;
|
start_pageserver(launch_ts, conf).context("Failed to start pageserver")?;
|
||||||
|
|||||||
@@ -174,9 +174,7 @@ pub struct PageServerConf {
|
|||||||
pub l0_flush: crate::l0_flush::L0FlushConfig,
|
pub l0_flush: crate::l0_flush::L0FlushConfig,
|
||||||
|
|
||||||
/// Direct IO settings
|
/// Direct IO settings
|
||||||
pub virtual_file_direct_io: virtual_file::DirectIoMode,
|
pub virtual_file_io_mode: virtual_file::IoMode,
|
||||||
|
|
||||||
pub io_buffer_alignment: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Token for authentication to safekeepers
|
/// Token for authentication to safekeepers
|
||||||
@@ -325,11 +323,10 @@ impl PageServerConf {
|
|||||||
image_compression,
|
image_compression,
|
||||||
ephemeral_bytes_per_memory_kb,
|
ephemeral_bytes_per_memory_kb,
|
||||||
l0_flush,
|
l0_flush,
|
||||||
virtual_file_direct_io,
|
virtual_file_io_mode,
|
||||||
concurrent_tenant_warmup,
|
concurrent_tenant_warmup,
|
||||||
concurrent_tenant_size_logical_size_queries,
|
concurrent_tenant_size_logical_size_queries,
|
||||||
virtual_file_io_engine,
|
virtual_file_io_engine,
|
||||||
io_buffer_alignment,
|
|
||||||
tenant_config,
|
tenant_config,
|
||||||
} = config_toml;
|
} = config_toml;
|
||||||
|
|
||||||
@@ -368,8 +365,6 @@ impl PageServerConf {
|
|||||||
max_vectored_read_bytes,
|
max_vectored_read_bytes,
|
||||||
image_compression,
|
image_compression,
|
||||||
ephemeral_bytes_per_memory_kb,
|
ephemeral_bytes_per_memory_kb,
|
||||||
virtual_file_direct_io,
|
|
||||||
io_buffer_alignment,
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
// fields that require additional validation or custom handling
|
// fields that require additional validation or custom handling
|
||||||
@@ -408,6 +403,7 @@ impl PageServerConf {
|
|||||||
l0_flush: l0_flush
|
l0_flush: l0_flush
|
||||||
.map(crate::l0_flush::L0FlushConfig::from)
|
.map(crate::l0_flush::L0FlushConfig::from)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
virtual_file_io_mode: virtual_file_io_mode.unwrap_or(virtual_file::IoMode::preferred()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use hyper::header;
|
|||||||
use hyper::StatusCode;
|
use hyper::StatusCode;
|
||||||
use hyper::{Body, Request, Response, Uri};
|
use hyper::{Body, Request, Response, Uri};
|
||||||
use metrics::launch_timestamp::LaunchTimestamp;
|
use metrics::launch_timestamp::LaunchTimestamp;
|
||||||
|
use pageserver_api::models::virtual_file::IoMode;
|
||||||
use pageserver_api::models::AuxFilePolicy;
|
use pageserver_api::models::AuxFilePolicy;
|
||||||
use pageserver_api::models::DownloadRemoteLayersTaskSpawnRequest;
|
use pageserver_api::models::DownloadRemoteLayersTaskSpawnRequest;
|
||||||
use pageserver_api::models::IngestAuxFilesRequest;
|
use pageserver_api::models::IngestAuxFilesRequest;
|
||||||
@@ -703,6 +704,8 @@ async fn timeline_archival_config_handler(
|
|||||||
let tenant_shard_id: TenantShardId = parse_request_param(&request, "tenant_shard_id")?;
|
let tenant_shard_id: TenantShardId = parse_request_param(&request, "tenant_shard_id")?;
|
||||||
let timeline_id: TimelineId = parse_request_param(&request, "timeline_id")?;
|
let timeline_id: TimelineId = parse_request_param(&request, "timeline_id")?;
|
||||||
|
|
||||||
|
let ctx = RequestContext::new(TaskKind::MgmtRequest, DownloadBehavior::Warn);
|
||||||
|
|
||||||
let request_data: TimelineArchivalConfigRequest = json_request(&mut request).await?;
|
let request_data: TimelineArchivalConfigRequest = json_request(&mut request).await?;
|
||||||
check_permission(&request, Some(tenant_shard_id.tenant_id))?;
|
check_permission(&request, Some(tenant_shard_id.tenant_id))?;
|
||||||
let state = get_state(&request);
|
let state = get_state(&request);
|
||||||
@@ -713,7 +716,7 @@ async fn timeline_archival_config_handler(
|
|||||||
.get_attached_tenant_shard(tenant_shard_id)?;
|
.get_attached_tenant_shard(tenant_shard_id)?;
|
||||||
|
|
||||||
tenant
|
tenant
|
||||||
.apply_timeline_archival_config(timeline_id, request_data.state)
|
.apply_timeline_archival_config(timeline_id, request_data.state, ctx)
|
||||||
.await?;
|
.await?;
|
||||||
Ok::<_, ApiError>(())
|
Ok::<_, ApiError>(())
|
||||||
}
|
}
|
||||||
@@ -2379,17 +2382,13 @@ async fn put_io_engine_handler(
|
|||||||
json_response(StatusCode::OK, ())
|
json_response(StatusCode::OK, ())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn put_io_alignment_handler(
|
async fn put_io_mode_handler(
|
||||||
mut r: Request<Body>,
|
mut r: Request<Body>,
|
||||||
_cancel: CancellationToken,
|
_cancel: CancellationToken,
|
||||||
) -> Result<Response<Body>, ApiError> {
|
) -> Result<Response<Body>, ApiError> {
|
||||||
check_permission(&r, None)?;
|
check_permission(&r, None)?;
|
||||||
let align: usize = json_request(&mut r).await?;
|
let mode: IoMode = json_request(&mut r).await?;
|
||||||
crate::virtual_file::set_io_buffer_alignment(align).map_err(|align| {
|
crate::virtual_file::set_io_mode(mode);
|
||||||
ApiError::PreconditionFailed(
|
|
||||||
format!("Requested io alignment ({align}) is not a power of two").into(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
json_response(StatusCode::OK, ())
|
json_response(StatusCode::OK, ())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3080,9 +3079,7 @@ pub fn make_router(
|
|||||||
|r| api_handler(r, timeline_collect_keyspace),
|
|r| api_handler(r, timeline_collect_keyspace),
|
||||||
)
|
)
|
||||||
.put("/v1/io_engine", |r| api_handler(r, put_io_engine_handler))
|
.put("/v1/io_engine", |r| api_handler(r, put_io_engine_handler))
|
||||||
.put("/v1/io_alignment", |r| {
|
.put("/v1/io_mode", |r| api_handler(r, put_io_mode_handler))
|
||||||
api_handler(r, put_io_alignment_handler)
|
|
||||||
})
|
|
||||||
.put(
|
.put(
|
||||||
"/v1/tenant/:tenant_shard_id/timeline/:timeline_id/force_aux_policy_switch",
|
"/v1/tenant/:tenant_shard_id/timeline/:timeline_id/force_aux_policy_switch",
|
||||||
|r| api_handler(r, force_aux_policy_switch_handler),
|
|r| api_handler(r, force_aux_policy_switch_handler),
|
||||||
|
|||||||
+281
-55
@@ -38,6 +38,7 @@ use std::future::Future;
|
|||||||
use std::sync::Weak;
|
use std::sync::Weak;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use storage_broker::BrokerClientChannel;
|
use storage_broker::BrokerClientChannel;
|
||||||
|
use timeline::offload::offload_timeline;
|
||||||
use tokio::io::BufReader;
|
use tokio::io::BufReader;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
@@ -287,9 +288,13 @@ pub struct Tenant {
|
|||||||
|
|
||||||
/// During timeline creation, we first insert the TimelineId to the
|
/// During timeline creation, we first insert the TimelineId to the
|
||||||
/// creating map, then `timelines`, then remove it from the creating map.
|
/// creating map, then `timelines`, then remove it from the creating map.
|
||||||
/// **Lock order**: if acquring both, acquire`timelines` before `timelines_creating`
|
/// **Lock order**: if acquiring both, acquire`timelines` before `timelines_creating`
|
||||||
timelines_creating: std::sync::Mutex<HashSet<TimelineId>>,
|
timelines_creating: std::sync::Mutex<HashSet<TimelineId>>,
|
||||||
|
|
||||||
|
/// Possibly offloaded and archived timelines
|
||||||
|
/// **Lock order**: if acquiring both, acquire`timelines` before `timelines_offloaded`
|
||||||
|
timelines_offloaded: Mutex<HashMap<TimelineId, Arc<OffloadedTimeline>>>,
|
||||||
|
|
||||||
// This mutex prevents creation of new timelines during GC.
|
// This mutex prevents creation of new timelines during GC.
|
||||||
// Adding yet another mutex (in addition to `timelines`) is needed because holding
|
// Adding yet another mutex (in addition to `timelines`) is needed because holding
|
||||||
// `timelines` mutex during all GC iteration
|
// `timelines` mutex during all GC iteration
|
||||||
@@ -484,6 +489,65 @@ impl WalRedoManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct OffloadedTimeline {
|
||||||
|
pub tenant_shard_id: TenantShardId,
|
||||||
|
pub timeline_id: TimelineId,
|
||||||
|
pub ancestor_timeline_id: Option<TimelineId>,
|
||||||
|
|
||||||
|
// TODO: once we persist offloaded state, make this lazily constructed
|
||||||
|
pub remote_client: Arc<RemoteTimelineClient>,
|
||||||
|
|
||||||
|
/// Prevent two tasks from deleting the timeline at the same time. If held, the
|
||||||
|
/// timeline is being deleted. If 'true', the timeline has already been deleted.
|
||||||
|
pub delete_progress: Arc<tokio::sync::Mutex<DeleteTimelineFlow>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OffloadedTimeline {
|
||||||
|
fn from_timeline(timeline: &Timeline) -> Self {
|
||||||
|
Self {
|
||||||
|
tenant_shard_id: timeline.tenant_shard_id,
|
||||||
|
timeline_id: timeline.timeline_id,
|
||||||
|
ancestor_timeline_id: timeline.get_ancestor_timeline_id(),
|
||||||
|
|
||||||
|
remote_client: timeline.remote_client.clone(),
|
||||||
|
delete_progress: timeline.delete_progress.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum TimelineOrOffloaded {
|
||||||
|
Timeline(Arc<Timeline>),
|
||||||
|
Offloaded(Arc<OffloadedTimeline>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimelineOrOffloaded {
|
||||||
|
pub fn tenant_shard_id(&self) -> TenantShardId {
|
||||||
|
match self {
|
||||||
|
TimelineOrOffloaded::Timeline(timeline) => timeline.tenant_shard_id,
|
||||||
|
TimelineOrOffloaded::Offloaded(offloaded) => offloaded.tenant_shard_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn timeline_id(&self) -> TimelineId {
|
||||||
|
match self {
|
||||||
|
TimelineOrOffloaded::Timeline(timeline) => timeline.timeline_id,
|
||||||
|
TimelineOrOffloaded::Offloaded(offloaded) => offloaded.timeline_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn delete_progress(&self) -> &Arc<tokio::sync::Mutex<DeleteTimelineFlow>> {
|
||||||
|
match self {
|
||||||
|
TimelineOrOffloaded::Timeline(timeline) => &timeline.delete_progress,
|
||||||
|
TimelineOrOffloaded::Offloaded(offloaded) => &offloaded.delete_progress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn remote_client(&self) -> &Arc<RemoteTimelineClient> {
|
||||||
|
match self {
|
||||||
|
TimelineOrOffloaded::Timeline(timeline) => &timeline.remote_client,
|
||||||
|
TimelineOrOffloaded::Offloaded(offloaded) => &offloaded.remote_client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||||
pub enum GetTimelineError {
|
pub enum GetTimelineError {
|
||||||
#[error("Timeline is shutting down")]
|
#[error("Timeline is shutting down")]
|
||||||
@@ -1406,52 +1470,192 @@ impl Tenant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn apply_timeline_archival_config(
|
fn check_to_be_archived_has_no_unarchived_children(
|
||||||
&self,
|
|
||||||
timeline_id: TimelineId,
|
timeline_id: TimelineId,
|
||||||
state: TimelineArchivalState,
|
timelines: &std::sync::MutexGuard<'_, HashMap<TimelineId, Arc<Timeline>>>,
|
||||||
|
) -> Result<(), TimelineArchivalError> {
|
||||||
|
let children: Vec<TimelineId> = timelines
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(id, entry)| {
|
||||||
|
if entry.get_ancestor_timeline_id() != Some(timeline_id) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if entry.is_archived() == Some(true) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(*id)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !children.is_empty() {
|
||||||
|
return Err(TimelineArchivalError::HasUnarchivedChildren(children));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_ancestor_of_to_be_unarchived_is_not_archived(
|
||||||
|
ancestor_timeline_id: TimelineId,
|
||||||
|
timelines: &std::sync::MutexGuard<'_, HashMap<TimelineId, Arc<Timeline>>>,
|
||||||
|
offloaded_timelines: &std::sync::MutexGuard<
|
||||||
|
'_,
|
||||||
|
HashMap<TimelineId, Arc<OffloadedTimeline>>,
|
||||||
|
>,
|
||||||
|
) -> Result<(), TimelineArchivalError> {
|
||||||
|
let has_archived_parent =
|
||||||
|
if let Some(ancestor_timeline) = timelines.get(&ancestor_timeline_id) {
|
||||||
|
ancestor_timeline.is_archived() == Some(true)
|
||||||
|
} else if offloaded_timelines.contains_key(&ancestor_timeline_id) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
error!("ancestor timeline {ancestor_timeline_id} not found");
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
panic!("ancestor timeline {ancestor_timeline_id} not found");
|
||||||
|
}
|
||||||
|
return Err(TimelineArchivalError::NotFound);
|
||||||
|
};
|
||||||
|
if has_archived_parent {
|
||||||
|
return Err(TimelineArchivalError::HasArchivedParent(
|
||||||
|
ancestor_timeline_id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_to_be_unarchived_timeline_has_no_archived_parent(
|
||||||
|
timeline: &Arc<Timeline>,
|
||||||
|
) -> Result<(), TimelineArchivalError> {
|
||||||
|
if let Some(ancestor_timeline) = timeline.ancestor_timeline() {
|
||||||
|
if ancestor_timeline.is_archived() == Some(true) {
|
||||||
|
return Err(TimelineArchivalError::HasArchivedParent(
|
||||||
|
ancestor_timeline.timeline_id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads the specified (offloaded) timeline from S3 and attaches it as a loaded timeline
|
||||||
|
async fn unoffload_timeline(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
timeline_id: TimelineId,
|
||||||
|
ctx: RequestContext,
|
||||||
|
) -> Result<Arc<Timeline>, TimelineArchivalError> {
|
||||||
|
let cancel = self.cancel.clone();
|
||||||
|
let timeline_preload = self
|
||||||
|
.load_timeline_metadata(timeline_id, self.remote_storage.clone(), cancel)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let index_part = match timeline_preload.index_part {
|
||||||
|
Ok(index_part) => {
|
||||||
|
debug!("remote index part exists for timeline {timeline_id}");
|
||||||
|
index_part
|
||||||
|
}
|
||||||
|
Err(DownloadError::NotFound) => {
|
||||||
|
error!(%timeline_id, "index_part not found on remote");
|
||||||
|
return Err(TimelineArchivalError::NotFound);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Some (possibly ephemeral) error happened during index_part download.
|
||||||
|
warn!(%timeline_id, "Failed to load index_part from remote storage, failed creation? ({e})");
|
||||||
|
return Err(TimelineArchivalError::Other(
|
||||||
|
anyhow::Error::new(e).context("downloading index_part from remote storage"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let index_part = match index_part {
|
||||||
|
MaybeDeletedIndexPart::IndexPart(index_part) => index_part,
|
||||||
|
MaybeDeletedIndexPart::Deleted(_index_part) => {
|
||||||
|
info!("timeline is deleted according to index_part.json");
|
||||||
|
return Err(TimelineArchivalError::NotFound);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let remote_metadata = index_part.metadata.clone();
|
||||||
|
let timeline_resources = self.build_timeline_resources(timeline_id);
|
||||||
|
self.load_remote_timeline(
|
||||||
|
timeline_id,
|
||||||
|
index_part,
|
||||||
|
remote_metadata,
|
||||||
|
timeline_resources,
|
||||||
|
&ctx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to load remote timeline {} for tenant {}",
|
||||||
|
timeline_id, self.tenant_shard_id
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let timelines = self.timelines.lock().unwrap();
|
||||||
|
if let Some(timeline) = timelines.get(&timeline_id) {
|
||||||
|
let mut offloaded_timelines = self.timelines_offloaded.lock().unwrap();
|
||||||
|
if offloaded_timelines.remove(&timeline_id).is_none() {
|
||||||
|
warn!("timeline already removed from offloaded timelines");
|
||||||
|
}
|
||||||
|
Ok(Arc::clone(timeline))
|
||||||
|
} else {
|
||||||
|
warn!("timeline not available directly after attach");
|
||||||
|
Err(TimelineArchivalError::Other(anyhow::anyhow!(
|
||||||
|
"timeline not available directly after attach"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn apply_timeline_archival_config(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
timeline_id: TimelineId,
|
||||||
|
new_state: TimelineArchivalState,
|
||||||
|
ctx: RequestContext,
|
||||||
) -> Result<(), TimelineArchivalError> {
|
) -> Result<(), TimelineArchivalError> {
|
||||||
info!("setting timeline archival config");
|
info!("setting timeline archival config");
|
||||||
let timeline = {
|
// First part: figure out what is needed to do, and do validation
|
||||||
|
let timeline_or_unarchive_offloaded = 'outer: {
|
||||||
let timelines = self.timelines.lock().unwrap();
|
let timelines = self.timelines.lock().unwrap();
|
||||||
|
|
||||||
let Some(timeline) = timelines.get(&timeline_id) else {
|
let Some(timeline) = timelines.get(&timeline_id) else {
|
||||||
return Err(TimelineArchivalError::NotFound);
|
let offloaded_timelines = self.timelines_offloaded.lock().unwrap();
|
||||||
|
let Some(offloaded) = offloaded_timelines.get(&timeline_id) else {
|
||||||
|
return Err(TimelineArchivalError::NotFound);
|
||||||
|
};
|
||||||
|
if new_state == TimelineArchivalState::Archived {
|
||||||
|
// It's offloaded already, so nothing to do
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if let Some(ancestor_timeline_id) = offloaded.ancestor_timeline_id {
|
||||||
|
Self::check_ancestor_of_to_be_unarchived_is_not_archived(
|
||||||
|
ancestor_timeline_id,
|
||||||
|
&timelines,
|
||||||
|
&offloaded_timelines,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
break 'outer None;
|
||||||
};
|
};
|
||||||
|
|
||||||
if state == TimelineArchivalState::Unarchived {
|
// Do some validation. We release the timelines lock below, so there is potential
|
||||||
if let Some(ancestor_timeline) = timeline.ancestor_timeline() {
|
// for race conditions: these checks are more present to prevent misunderstandings of
|
||||||
if ancestor_timeline.is_archived() == Some(true) {
|
// the API's capabilities, instead of serving as the sole way to defend their invariants.
|
||||||
return Err(TimelineArchivalError::HasArchivedParent(
|
match new_state {
|
||||||
ancestor_timeline.timeline_id,
|
TimelineArchivalState::Unarchived => {
|
||||||
));
|
Self::check_to_be_unarchived_timeline_has_no_archived_parent(timeline)?
|
||||||
}
|
}
|
||||||
|
TimelineArchivalState::Archived => {
|
||||||
|
Self::check_to_be_archived_has_no_unarchived_children(timeline_id, &timelines)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(Arc::clone(timeline))
|
||||||
// Ensure that there are no non-archived child timelines
|
|
||||||
let children: Vec<TimelineId> = timelines
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(id, entry)| {
|
|
||||||
if entry.get_ancestor_timeline_id() != Some(timeline_id) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if entry.is_archived() == Some(true) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(*id)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if !children.is_empty() && state == TimelineArchivalState::Archived {
|
|
||||||
return Err(TimelineArchivalError::HasUnarchivedChildren(children));
|
|
||||||
}
|
|
||||||
Arc::clone(timeline)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Second part: unarchive timeline (if needed)
|
||||||
|
let timeline = if let Some(timeline) = timeline_or_unarchive_offloaded {
|
||||||
|
timeline
|
||||||
|
} else {
|
||||||
|
// Turn offloaded timeline into a non-offloaded one
|
||||||
|
self.unoffload_timeline(timeline_id, ctx).await?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Third part: upload new timeline archival state and block until it is present in S3
|
||||||
let upload_needed = timeline
|
let upload_needed = timeline
|
||||||
.remote_client
|
.remote_client
|
||||||
.schedule_index_upload_for_timeline_archival_state(state)?;
|
.schedule_index_upload_for_timeline_archival_state(new_state)?;
|
||||||
|
|
||||||
if upload_needed {
|
if upload_needed {
|
||||||
info!("Uploading new state");
|
info!("Uploading new state");
|
||||||
@@ -1884,7 +2088,7 @@ impl Tenant {
|
|||||||
///
|
///
|
||||||
/// Returns whether we have pending compaction task.
|
/// Returns whether we have pending compaction task.
|
||||||
async fn compaction_iteration(
|
async fn compaction_iteration(
|
||||||
&self,
|
self: &Arc<Self>,
|
||||||
cancel: &CancellationToken,
|
cancel: &CancellationToken,
|
||||||
ctx: &RequestContext,
|
ctx: &RequestContext,
|
||||||
) -> Result<bool, timeline::CompactionError> {
|
) -> Result<bool, timeline::CompactionError> {
|
||||||
@@ -1905,21 +2109,28 @@ impl Tenant {
|
|||||||
// while holding the lock. Then drop the lock and actually perform the
|
// while holding the lock. Then drop the lock and actually perform the
|
||||||
// compactions. We don't want to block everything else while the
|
// compactions. We don't want to block everything else while the
|
||||||
// compaction runs.
|
// compaction runs.
|
||||||
let timelines_to_compact = {
|
let timelines_to_compact_or_offload;
|
||||||
|
{
|
||||||
let timelines = self.timelines.lock().unwrap();
|
let timelines = self.timelines.lock().unwrap();
|
||||||
let timelines_to_compact = timelines
|
timelines_to_compact_or_offload = timelines
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(timeline_id, timeline)| {
|
.filter_map(|(timeline_id, timeline)| {
|
||||||
if timeline.is_active() {
|
let (is_active, can_offload) = (timeline.is_active(), timeline.can_offload());
|
||||||
Some((*timeline_id, timeline.clone()))
|
let has_no_unoffloaded_children = {
|
||||||
} else {
|
!timelines
|
||||||
|
.iter()
|
||||||
|
.any(|(_id, tl)| tl.get_ancestor_timeline_id() == Some(*timeline_id))
|
||||||
|
};
|
||||||
|
let can_offload = can_offload && has_no_unoffloaded_children;
|
||||||
|
if (is_active, can_offload) == (false, false) {
|
||||||
None
|
None
|
||||||
|
} else {
|
||||||
|
Some((*timeline_id, timeline.clone(), (is_active, can_offload)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
drop(timelines);
|
drop(timelines);
|
||||||
timelines_to_compact
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Before doing any I/O work, check our circuit breaker
|
// Before doing any I/O work, check our circuit breaker
|
||||||
if self.compaction_circuit_breaker.lock().unwrap().is_broken() {
|
if self.compaction_circuit_breaker.lock().unwrap().is_broken() {
|
||||||
@@ -1929,20 +2140,34 @@ impl Tenant {
|
|||||||
|
|
||||||
let mut has_pending_task = false;
|
let mut has_pending_task = false;
|
||||||
|
|
||||||
for (timeline_id, timeline) in &timelines_to_compact {
|
for (timeline_id, timeline, (can_compact, can_offload)) in &timelines_to_compact_or_offload
|
||||||
has_pending_task |= timeline
|
{
|
||||||
.compact(cancel, EnumSet::empty(), ctx)
|
let pending_task_left = if *can_compact {
|
||||||
.instrument(info_span!("compact_timeline", %timeline_id))
|
Some(
|
||||||
.await
|
timeline
|
||||||
.inspect_err(|e| match e {
|
.compact(cancel, EnumSet::empty(), ctx)
|
||||||
timeline::CompactionError::ShuttingDown => (),
|
.instrument(info_span!("compact_timeline", %timeline_id))
|
||||||
timeline::CompactionError::Other(e) => {
|
.await
|
||||||
self.compaction_circuit_breaker
|
.inspect_err(|e| match e {
|
||||||
.lock()
|
timeline::CompactionError::ShuttingDown => (),
|
||||||
.unwrap()
|
timeline::CompactionError::Other(e) => {
|
||||||
.fail(&CIRCUIT_BREAKERS_BROKEN, e);
|
self.compaction_circuit_breaker
|
||||||
}
|
.lock()
|
||||||
})?;
|
.unwrap()
|
||||||
|
.fail(&CIRCUIT_BREAKERS_BROKEN, e);
|
||||||
|
}
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
has_pending_task |= pending_task_left.unwrap_or(false);
|
||||||
|
if pending_task_left == Some(false) && *can_offload {
|
||||||
|
offload_timeline(self, timeline)
|
||||||
|
.instrument(info_span!("offload_timeline", %timeline_id))
|
||||||
|
.await
|
||||||
|
.map_err(timeline::CompactionError::Other)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.compaction_circuit_breaker
|
self.compaction_circuit_breaker
|
||||||
@@ -2852,6 +3077,7 @@ impl Tenant {
|
|||||||
constructed_at: Instant::now(),
|
constructed_at: Instant::now(),
|
||||||
timelines: Mutex::new(HashMap::new()),
|
timelines: Mutex::new(HashMap::new()),
|
||||||
timelines_creating: Mutex::new(HashSet::new()),
|
timelines_creating: Mutex::new(HashSet::new()),
|
||||||
|
timelines_offloaded: Mutex::new(HashMap::new()),
|
||||||
gc_cs: tokio::sync::Mutex::new(()),
|
gc_cs: tokio::sync::Mutex::new(()),
|
||||||
walredo_mgr,
|
walredo_mgr,
|
||||||
remote_storage,
|
remote_storage,
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ impl Drop for EphemeralFile {
|
|||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// unlink the file
|
// unlink the file
|
||||||
// we are clear to do this, because we have entered a gate
|
// we are clear to do this, because we have entered a gate
|
||||||
let path = &self.buffered_writer.as_inner().as_inner().path;
|
let path = self.buffered_writer.as_inner().as_inner().path();
|
||||||
let res = std::fs::remove_file(path);
|
let res = std::fs::remove_file(path);
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
if e.kind() != std::io::ErrorKind::NotFound {
|
if e.kind() != std::io::ErrorKind::NotFound {
|
||||||
@@ -356,7 +356,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let file_contents =
|
let file_contents =
|
||||||
std::fs::read(&file.buffered_writer.as_inner().as_inner().path).unwrap();
|
std::fs::read(file.buffered_writer.as_inner().as_inner().path()).unwrap();
|
||||||
assert_eq!(file_contents, &content[0..cap]);
|
assert_eq!(file_contents, &content[0..cap]);
|
||||||
|
|
||||||
let buffer_contents = file.buffered_writer.inspect_buffer();
|
let buffer_contents = file.buffered_writer.inspect_buffer();
|
||||||
@@ -392,7 +392,7 @@ mod tests {
|
|||||||
.buffered_writer
|
.buffered_writer
|
||||||
.as_inner()
|
.as_inner()
|
||||||
.as_inner()
|
.as_inner()
|
||||||
.path
|
.path()
|
||||||
.metadata()
|
.metadata()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -141,14 +141,14 @@ impl GcBlock {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn before_delete(&self, timeline: &super::Timeline) {
|
pub(crate) fn before_delete(&self, timeline_id: &super::TimelineId) {
|
||||||
let unblocked = {
|
let unblocked = {
|
||||||
let mut g = self.reasons.lock().unwrap();
|
let mut g = self.reasons.lock().unwrap();
|
||||||
if g.is_empty() {
|
if g.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g.remove(&timeline.timeline_id);
|
g.remove(timeline_id);
|
||||||
|
|
||||||
BlockingReasons::clean_and_summarize(g).is_none()
|
BlockingReasons::clean_and_summarize(g).is_none()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -950,6 +950,7 @@ impl<'a> TenantDownloader<'a> {
|
|||||||
let cancel = &self.secondary_state.cancel;
|
let cancel = &self.secondary_state.cancel;
|
||||||
let opts = DownloadOpts {
|
let opts = DownloadOpts {
|
||||||
etag: prev_etag.cloned(),
|
etag: prev_etag.cloned(),
|
||||||
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
backoff::retry(
|
backoff::retry(
|
||||||
|
|||||||
@@ -573,7 +573,7 @@ impl DeltaLayerWriterInner {
|
|||||||
ensure!(
|
ensure!(
|
||||||
metadata.len() <= S3_UPLOAD_LIMIT,
|
metadata.len() <= S3_UPLOAD_LIMIT,
|
||||||
"Created delta layer file at {} of size {} above limit {S3_UPLOAD_LIMIT}!",
|
"Created delta layer file at {} of size {} above limit {S3_UPLOAD_LIMIT}!",
|
||||||
file.path,
|
file.path(),
|
||||||
metadata.len()
|
metadata.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -791,7 +791,7 @@ impl DeltaLayerInner {
|
|||||||
max_vectored_read_bytes: Option<MaxVectoredReadBytes>,
|
max_vectored_read_bytes: Option<MaxVectoredReadBytes>,
|
||||||
ctx: &RequestContext,
|
ctx: &RequestContext,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let file = VirtualFile::open(path, ctx)
|
let file = VirtualFile::open_v2(path, ctx)
|
||||||
.await
|
.await
|
||||||
.context("open layer file")?;
|
.context("open layer file")?;
|
||||||
|
|
||||||
@@ -1022,7 +1022,7 @@ impl DeltaLayerInner {
|
|||||||
blob_meta.key,
|
blob_meta.key,
|
||||||
PageReconstructError::Other(anyhow!(
|
PageReconstructError::Other(anyhow!(
|
||||||
"Failed to read blobs from virtual file {}: {}",
|
"Failed to read blobs from virtual file {}: {}",
|
||||||
self.file.path,
|
self.file.path(),
|
||||||
kind
|
kind
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
@@ -1048,7 +1048,7 @@ impl DeltaLayerInner {
|
|||||||
meta.meta.key,
|
meta.meta.key,
|
||||||
PageReconstructError::Other(anyhow!(e).context(format!(
|
PageReconstructError::Other(anyhow!(e).context(format!(
|
||||||
"Failed to decompress blob from virtual file {}",
|
"Failed to decompress blob from virtual file {}",
|
||||||
self.file.path,
|
self.file.path(),
|
||||||
))),
|
))),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1066,7 +1066,7 @@ impl DeltaLayerInner {
|
|||||||
meta.meta.key,
|
meta.meta.key,
|
||||||
PageReconstructError::Other(anyhow!(e).context(format!(
|
PageReconstructError::Other(anyhow!(e).context(format!(
|
||||||
"Failed to deserialize blob from virtual file {}",
|
"Failed to deserialize blob from virtual file {}",
|
||||||
self.file.path,
|
self.file.path(),
|
||||||
))),
|
))),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1198,7 +1198,6 @@ impl DeltaLayerInner {
|
|||||||
let mut prev: Option<(Key, Lsn, BlobRef)> = None;
|
let mut prev: Option<(Key, Lsn, BlobRef)> = None;
|
||||||
|
|
||||||
let mut read_builder: Option<ChunkedVectoredReadBuilder> = None;
|
let mut read_builder: Option<ChunkedVectoredReadBuilder> = None;
|
||||||
let align = virtual_file::get_io_buffer_alignment();
|
|
||||||
|
|
||||||
let max_read_size = self
|
let max_read_size = self
|
||||||
.max_vectored_read_bytes
|
.max_vectored_read_bytes
|
||||||
@@ -1247,7 +1246,6 @@ impl DeltaLayerInner {
|
|||||||
offsets.end.pos(),
|
offsets.end.pos(),
|
||||||
meta,
|
meta,
|
||||||
max_read_size,
|
max_read_size,
|
||||||
align,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -389,7 +389,7 @@ impl ImageLayerInner {
|
|||||||
max_vectored_read_bytes: Option<MaxVectoredReadBytes>,
|
max_vectored_read_bytes: Option<MaxVectoredReadBytes>,
|
||||||
ctx: &RequestContext,
|
ctx: &RequestContext,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let file = VirtualFile::open(path, ctx)
|
let file = VirtualFile::open_v2(path, ctx)
|
||||||
.await
|
.await
|
||||||
.context("open layer file")?;
|
.context("open layer file")?;
|
||||||
let file_id = page_cache::next_file_id();
|
let file_id = page_cache::next_file_id();
|
||||||
@@ -626,7 +626,7 @@ impl ImageLayerInner {
|
|||||||
meta.meta.key,
|
meta.meta.key,
|
||||||
PageReconstructError::Other(anyhow!(e).context(format!(
|
PageReconstructError::Other(anyhow!(e).context(format!(
|
||||||
"Failed to decompress blob from virtual file {}",
|
"Failed to decompress blob from virtual file {}",
|
||||||
self.file.path,
|
self.file.path(),
|
||||||
))),
|
))),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -647,7 +647,7 @@ impl ImageLayerInner {
|
|||||||
blob_meta.key,
|
blob_meta.key,
|
||||||
PageReconstructError::from(anyhow!(
|
PageReconstructError::from(anyhow!(
|
||||||
"Failed to read blobs from virtual file {}: {}",
|
"Failed to read blobs from virtual file {}: {}",
|
||||||
self.file.path,
|
self.file.path(),
|
||||||
kind
|
kind
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ pub(crate) mod handle;
|
|||||||
mod init;
|
mod init;
|
||||||
pub mod layer_manager;
|
pub mod layer_manager;
|
||||||
pub(crate) mod logical_size;
|
pub(crate) mod logical_size;
|
||||||
|
pub mod offload;
|
||||||
pub mod span;
|
pub mod span;
|
||||||
pub mod uninit;
|
pub mod uninit;
|
||||||
mod walreceiver;
|
mod walreceiver;
|
||||||
@@ -1556,6 +1557,17 @@ impl Timeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if the internal state of the timeline is consistent with it being able to be offloaded.
|
||||||
|
/// This is neccessary but not sufficient for offloading of the timeline as it might have
|
||||||
|
/// child timelines that are not offloaded yet.
|
||||||
|
pub(crate) fn can_offload(&self) -> bool {
|
||||||
|
if self.remote_client.is_archived() != Some(true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// Outermost timeline compaction operation; downloads needed layers. Returns whether we have pending
|
/// Outermost timeline compaction operation; downloads needed layers. Returns whether we have pending
|
||||||
/// compaction tasks.
|
/// compaction tasks.
|
||||||
pub(crate) async fn compact(
|
pub(crate) async fn compact(
|
||||||
@@ -1818,7 +1830,6 @@ impl Timeline {
|
|||||||
self.current_state() == TimelineState::Active
|
self.current_state() == TimelineState::Active
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub(crate) fn is_archived(&self) -> Option<bool> {
|
pub(crate) fn is_archived(&self) -> Option<bool> {
|
||||||
self.remote_client.is_archived()
|
self.remote_client.is_archived()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::{
|
|||||||
tenant::{
|
tenant::{
|
||||||
metadata::TimelineMetadata,
|
metadata::TimelineMetadata,
|
||||||
remote_timeline_client::{PersistIndexPartWithDeletedFlagError, RemoteTimelineClient},
|
remote_timeline_client::{PersistIndexPartWithDeletedFlagError, RemoteTimelineClient},
|
||||||
CreateTimelineCause, DeleteTimelineError, Tenant,
|
CreateTimelineCause, DeleteTimelineError, Tenant, TimelineOrOffloaded,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -24,12 +24,14 @@ use super::{Timeline, TimelineResources};
|
|||||||
/// Mark timeline as deleted in S3 so we won't pick it up next time
|
/// Mark timeline as deleted in S3 so we won't pick it up next time
|
||||||
/// during attach or pageserver restart.
|
/// during attach or pageserver restart.
|
||||||
/// See comment in persist_index_part_with_deleted_flag.
|
/// See comment in persist_index_part_with_deleted_flag.
|
||||||
async fn set_deleted_in_remote_index(timeline: &Timeline) -> Result<(), DeleteTimelineError> {
|
async fn set_deleted_in_remote_index(
|
||||||
match timeline
|
timeline: &TimelineOrOffloaded,
|
||||||
.remote_client
|
) -> Result<(), DeleteTimelineError> {
|
||||||
|
let res = timeline
|
||||||
|
.remote_client()
|
||||||
.persist_index_part_with_deleted_flag()
|
.persist_index_part_with_deleted_flag()
|
||||||
.await
|
.await;
|
||||||
{
|
match res {
|
||||||
// If we (now, or already) marked it successfully as deleted, we can proceed
|
// If we (now, or already) marked it successfully as deleted, we can proceed
|
||||||
Ok(()) | Err(PersistIndexPartWithDeletedFlagError::AlreadyDeleted(_)) => (),
|
Ok(()) | Err(PersistIndexPartWithDeletedFlagError::AlreadyDeleted(_)) => (),
|
||||||
// Bail out otherwise
|
// Bail out otherwise
|
||||||
@@ -127,9 +129,9 @@ pub(super) async fn delete_local_timeline_directory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Removes remote layers and an index file after them.
|
/// Removes remote layers and an index file after them.
|
||||||
async fn delete_remote_layers_and_index(timeline: &Timeline) -> anyhow::Result<()> {
|
async fn delete_remote_layers_and_index(timeline: &TimelineOrOffloaded) -> anyhow::Result<()> {
|
||||||
timeline
|
timeline
|
||||||
.remote_client
|
.remote_client()
|
||||||
.delete_all()
|
.delete_all()
|
||||||
.await
|
.await
|
||||||
.context("delete_all")
|
.context("delete_all")
|
||||||
@@ -137,27 +139,41 @@ async fn delete_remote_layers_and_index(timeline: &Timeline) -> anyhow::Result<(
|
|||||||
|
|
||||||
/// It is important that this gets called when DeletionGuard is being held.
|
/// It is important that this gets called when DeletionGuard is being held.
|
||||||
/// For more context see comments in [`DeleteTimelineFlow::prepare`]
|
/// For more context see comments in [`DeleteTimelineFlow::prepare`]
|
||||||
async fn remove_timeline_from_tenant(
|
async fn remove_maybe_offloaded_timeline_from_tenant(
|
||||||
tenant: &Tenant,
|
tenant: &Tenant,
|
||||||
timeline: &Timeline,
|
timeline: &TimelineOrOffloaded,
|
||||||
_: &DeletionGuard, // using it as a witness
|
_: &DeletionGuard, // using it as a witness
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
// Remove the timeline from the map.
|
// Remove the timeline from the map.
|
||||||
|
// This observes the locking order between timelines and timelines_offloaded
|
||||||
let mut timelines = tenant.timelines.lock().unwrap();
|
let mut timelines = tenant.timelines.lock().unwrap();
|
||||||
|
let mut timelines_offloaded = tenant.timelines_offloaded.lock().unwrap();
|
||||||
|
let offloaded_children_exist = timelines_offloaded
|
||||||
|
.iter()
|
||||||
|
.any(|(_, entry)| entry.ancestor_timeline_id == Some(timeline.timeline_id()));
|
||||||
let children_exist = timelines
|
let children_exist = timelines
|
||||||
.iter()
|
.iter()
|
||||||
.any(|(_, entry)| entry.get_ancestor_timeline_id() == Some(timeline.timeline_id));
|
.any(|(_, entry)| entry.get_ancestor_timeline_id() == Some(timeline.timeline_id()));
|
||||||
// XXX this can happen because `branch_timeline` doesn't check `TimelineState::Stopping`.
|
// XXX this can happen because of race conditions with branch creation.
|
||||||
// We already deleted the layer files, so it's probably best to panic.
|
// We already deleted the remote layer files, so it's probably best to panic.
|
||||||
// (Ideally, above remove_dir_all is atomic so we don't see this timeline after a restart)
|
if children_exist || offloaded_children_exist {
|
||||||
if children_exist {
|
|
||||||
panic!("Timeline grew children while we removed layer files");
|
panic!("Timeline grew children while we removed layer files");
|
||||||
}
|
}
|
||||||
|
|
||||||
timelines
|
match timeline {
|
||||||
.remove(&timeline.timeline_id)
|
TimelineOrOffloaded::Timeline(timeline) => {
|
||||||
.expect("timeline that we were deleting was concurrently removed from 'timelines' map");
|
timelines.remove(&timeline.timeline_id).expect(
|
||||||
|
"timeline that we were deleting was concurrently removed from 'timelines' map",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
TimelineOrOffloaded::Offloaded(timeline) => {
|
||||||
|
timelines_offloaded
|
||||||
|
.remove(&timeline.timeline_id)
|
||||||
|
.expect("timeline that we were deleting was concurrently removed from 'timelines_offloaded' map");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(timelines_offloaded);
|
||||||
drop(timelines);
|
drop(timelines);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -207,9 +223,11 @@ impl DeleteTimelineFlow {
|
|||||||
guard.mark_in_progress()?;
|
guard.mark_in_progress()?;
|
||||||
|
|
||||||
// Now that the Timeline is in Stopping state, request all the related tasks to shut down.
|
// Now that the Timeline is in Stopping state, request all the related tasks to shut down.
|
||||||
timeline.shutdown(super::ShutdownMode::Hard).await;
|
if let TimelineOrOffloaded::Timeline(timeline) = &timeline {
|
||||||
|
timeline.shutdown(super::ShutdownMode::Hard).await;
|
||||||
|
}
|
||||||
|
|
||||||
tenant.gc_block.before_delete(&timeline);
|
tenant.gc_block.before_delete(&timeline.timeline_id());
|
||||||
|
|
||||||
fail::fail_point!("timeline-delete-before-index-deleted-at", |_| {
|
fail::fail_point!("timeline-delete-before-index-deleted-at", |_| {
|
||||||
Err(anyhow::anyhow!(
|
Err(anyhow::anyhow!(
|
||||||
@@ -285,15 +303,16 @@ impl DeleteTimelineFlow {
|
|||||||
|
|
||||||
guard.mark_in_progress()?;
|
guard.mark_in_progress()?;
|
||||||
|
|
||||||
|
let timeline = TimelineOrOffloaded::Timeline(timeline);
|
||||||
Self::schedule_background(guard, tenant.conf, tenant, timeline);
|
Self::schedule_background(guard, tenant.conf, tenant, timeline);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare(
|
pub(super) fn prepare(
|
||||||
tenant: &Tenant,
|
tenant: &Tenant,
|
||||||
timeline_id: TimelineId,
|
timeline_id: TimelineId,
|
||||||
) -> Result<(Arc<Timeline>, DeletionGuard), DeleteTimelineError> {
|
) -> Result<(TimelineOrOffloaded, DeletionGuard), DeleteTimelineError> {
|
||||||
// Note the interaction between this guard and deletion guard.
|
// Note the interaction between this guard and deletion guard.
|
||||||
// Here we attempt to lock deletion guard when we're holding a lock on timelines.
|
// Here we attempt to lock deletion guard when we're holding a lock on timelines.
|
||||||
// This is important because when you take into account `remove_timeline_from_tenant`
|
// This is important because when you take into account `remove_timeline_from_tenant`
|
||||||
@@ -307,8 +326,14 @@ impl DeleteTimelineFlow {
|
|||||||
let timelines = tenant.timelines.lock().unwrap();
|
let timelines = tenant.timelines.lock().unwrap();
|
||||||
|
|
||||||
let timeline = match timelines.get(&timeline_id) {
|
let timeline = match timelines.get(&timeline_id) {
|
||||||
Some(t) => t,
|
Some(t) => TimelineOrOffloaded::Timeline(Arc::clone(t)),
|
||||||
None => return Err(DeleteTimelineError::NotFound),
|
None => {
|
||||||
|
let offloaded_timelines = tenant.timelines_offloaded.lock().unwrap();
|
||||||
|
match offloaded_timelines.get(&timeline_id) {
|
||||||
|
Some(t) => TimelineOrOffloaded::Offloaded(Arc::clone(t)),
|
||||||
|
None => return Err(DeleteTimelineError::NotFound),
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure that there are no child timelines **attached to that pageserver**,
|
// Ensure that there are no child timelines **attached to that pageserver**,
|
||||||
@@ -334,30 +359,32 @@ impl DeleteTimelineFlow {
|
|||||||
// to remove the timeline from it.
|
// to remove the timeline from it.
|
||||||
// Always if you have two locks that are taken in different order this can result in a deadlock.
|
// Always if you have two locks that are taken in different order this can result in a deadlock.
|
||||||
|
|
||||||
let delete_progress = Arc::clone(&timeline.delete_progress);
|
let delete_progress = Arc::clone(timeline.delete_progress());
|
||||||
let delete_lock_guard = match delete_progress.try_lock_owned() {
|
let delete_lock_guard = match delete_progress.try_lock_owned() {
|
||||||
Ok(guard) => DeletionGuard(guard),
|
Ok(guard) => DeletionGuard(guard),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// Unfortunately if lock fails arc is consumed.
|
// Unfortunately if lock fails arc is consumed.
|
||||||
return Err(DeleteTimelineError::AlreadyInProgress(Arc::clone(
|
return Err(DeleteTimelineError::AlreadyInProgress(Arc::clone(
|
||||||
&timeline.delete_progress,
|
timeline.delete_progress(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
timeline.set_state(TimelineState::Stopping);
|
if let TimelineOrOffloaded::Timeline(timeline) = &timeline {
|
||||||
|
timeline.set_state(TimelineState::Stopping);
|
||||||
|
}
|
||||||
|
|
||||||
Ok((Arc::clone(timeline), delete_lock_guard))
|
Ok((timeline, delete_lock_guard))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn schedule_background(
|
fn schedule_background(
|
||||||
guard: DeletionGuard,
|
guard: DeletionGuard,
|
||||||
conf: &'static PageServerConf,
|
conf: &'static PageServerConf,
|
||||||
tenant: Arc<Tenant>,
|
tenant: Arc<Tenant>,
|
||||||
timeline: Arc<Timeline>,
|
timeline: TimelineOrOffloaded,
|
||||||
) {
|
) {
|
||||||
let tenant_shard_id = timeline.tenant_shard_id;
|
let tenant_shard_id = timeline.tenant_shard_id();
|
||||||
let timeline_id = timeline.timeline_id;
|
let timeline_id = timeline.timeline_id();
|
||||||
|
|
||||||
task_mgr::spawn(
|
task_mgr::spawn(
|
||||||
task_mgr::BACKGROUND_RUNTIME.handle(),
|
task_mgr::BACKGROUND_RUNTIME.handle(),
|
||||||
@@ -368,7 +395,9 @@ impl DeleteTimelineFlow {
|
|||||||
async move {
|
async move {
|
||||||
if let Err(err) = Self::background(guard, conf, &tenant, &timeline).await {
|
if let Err(err) = Self::background(guard, conf, &tenant, &timeline).await {
|
||||||
error!("Error: {err:#}");
|
error!("Error: {err:#}");
|
||||||
timeline.set_broken(format!("{err:#}"))
|
if let TimelineOrOffloaded::Timeline(timeline) = timeline {
|
||||||
|
timeline.set_broken(format!("{err:#}"))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -380,15 +409,19 @@ impl DeleteTimelineFlow {
|
|||||||
mut guard: DeletionGuard,
|
mut guard: DeletionGuard,
|
||||||
conf: &PageServerConf,
|
conf: &PageServerConf,
|
||||||
tenant: &Tenant,
|
tenant: &Tenant,
|
||||||
timeline: &Timeline,
|
timeline: &TimelineOrOffloaded,
|
||||||
) -> Result<(), DeleteTimelineError> {
|
) -> Result<(), DeleteTimelineError> {
|
||||||
delete_local_timeline_directory(conf, tenant.tenant_shard_id, timeline).await?;
|
// Offloaded timelines have no local state
|
||||||
|
// TODO: once we persist offloaded information, delete the timeline from there, too
|
||||||
|
if let TimelineOrOffloaded::Timeline(timeline) = timeline {
|
||||||
|
delete_local_timeline_directory(conf, tenant.tenant_shard_id, timeline).await?;
|
||||||
|
}
|
||||||
|
|
||||||
delete_remote_layers_and_index(timeline).await?;
|
delete_remote_layers_and_index(timeline).await?;
|
||||||
|
|
||||||
pausable_failpoint!("in_progress_delete");
|
pausable_failpoint!("in_progress_delete");
|
||||||
|
|
||||||
remove_timeline_from_tenant(tenant, timeline, &guard).await?;
|
remove_maybe_offloaded_timeline_from_tenant(tenant, timeline, &guard).await?;
|
||||||
|
|
||||||
*guard = Self::Finished;
|
*guard = Self::Finished;
|
||||||
|
|
||||||
@@ -400,7 +433,7 @@ impl DeleteTimelineFlow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DeletionGuard(OwnedMutexGuard<DeleteTimelineFlow>);
|
pub(super) struct DeletionGuard(OwnedMutexGuard<DeleteTimelineFlow>);
|
||||||
|
|
||||||
impl Deref for DeletionGuard {
|
impl Deref for DeletionGuard {
|
||||||
type Target = DeleteTimelineFlow;
|
type Target = DeleteTimelineFlow;
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::tenant::{OffloadedTimeline, Tenant, TimelineOrOffloaded};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
delete::{delete_local_timeline_directory, DeleteTimelineFlow, DeletionGuard},
|
||||||
|
Timeline,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) async fn offload_timeline(
|
||||||
|
tenant: &Tenant,
|
||||||
|
timeline: &Arc<Timeline>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
tracing::info!("offloading archived timeline");
|
||||||
|
let (timeline, guard) = DeleteTimelineFlow::prepare(tenant, timeline.timeline_id)?;
|
||||||
|
|
||||||
|
let TimelineOrOffloaded::Timeline(timeline) = timeline else {
|
||||||
|
tracing::error!("timeline already offloaded, but given timeline object");
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO extend guard mechanism above with method
|
||||||
|
// to make deletions possible while offloading is in progress
|
||||||
|
|
||||||
|
// TODO mark timeline as offloaded in S3
|
||||||
|
|
||||||
|
let conf = &tenant.conf;
|
||||||
|
delete_local_timeline_directory(conf, tenant.tenant_shard_id, &timeline).await?;
|
||||||
|
|
||||||
|
remove_timeline_from_tenant(tenant, &timeline, &guard).await?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut offloaded_timelines = tenant.timelines_offloaded.lock().unwrap();
|
||||||
|
offloaded_timelines.insert(
|
||||||
|
timeline.timeline_id,
|
||||||
|
Arc::new(OffloadedTimeline::from_timeline(&timeline)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// It is important that this gets called when DeletionGuard is being held.
|
||||||
|
/// For more context see comments in [`DeleteTimelineFlow::prepare`]
|
||||||
|
async fn remove_timeline_from_tenant(
|
||||||
|
tenant: &Tenant,
|
||||||
|
timeline: &Timeline,
|
||||||
|
_: &DeletionGuard, // using it as a witness
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
// Remove the timeline from the map.
|
||||||
|
let mut timelines = tenant.timelines.lock().unwrap();
|
||||||
|
let children_exist = timelines
|
||||||
|
.iter()
|
||||||
|
.any(|(_, entry)| entry.get_ancestor_timeline_id() == Some(timeline.timeline_id));
|
||||||
|
// XXX this can happen because `branch_timeline` doesn't check `TimelineState::Stopping`.
|
||||||
|
// We already deleted the layer files, so it's probably best to panic.
|
||||||
|
// (Ideally, above remove_dir_all is atomic so we don't see this timeline after a restart)
|
||||||
|
if children_exist {
|
||||||
|
panic!("Timeline grew children while we removed layer files");
|
||||||
|
}
|
||||||
|
|
||||||
|
timelines
|
||||||
|
.remove(&timeline.timeline_id)
|
||||||
|
.expect("timeline that we were deleting was concurrently removed from 'timelines' map");
|
||||||
|
|
||||||
|
drop(timelines);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -194,8 +194,6 @@ pub(crate) struct ChunkedVectoredReadBuilder {
|
|||||||
/// Start offset and metadata for each blob in this read
|
/// Start offset and metadata for each blob in this read
|
||||||
blobs_at: VecMap<u64, BlobMeta>,
|
blobs_at: VecMap<u64, BlobMeta>,
|
||||||
max_read_size: Option<usize>,
|
max_read_size: Option<usize>,
|
||||||
/// Chunk size reads are coalesced into.
|
|
||||||
chunk_size: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes x / d rounded up.
|
/// Computes x / d rounded up.
|
||||||
@@ -204,6 +202,7 @@ fn div_round_up(x: usize, d: usize) -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ChunkedVectoredReadBuilder {
|
impl ChunkedVectoredReadBuilder {
|
||||||
|
const CHUNK_SIZE: usize = virtual_file::get_io_buffer_alignment();
|
||||||
/// Start building a new vectored read.
|
/// Start building a new vectored read.
|
||||||
///
|
///
|
||||||
/// Note that by design, this does not check against reading more than `max_read_size` to
|
/// Note that by design, this does not check against reading more than `max_read_size` to
|
||||||
@@ -214,21 +213,19 @@ impl ChunkedVectoredReadBuilder {
|
|||||||
end_offset: u64,
|
end_offset: u64,
|
||||||
meta: BlobMeta,
|
meta: BlobMeta,
|
||||||
max_read_size: Option<usize>,
|
max_read_size: Option<usize>,
|
||||||
chunk_size: usize,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut blobs_at = VecMap::default();
|
let mut blobs_at = VecMap::default();
|
||||||
blobs_at
|
blobs_at
|
||||||
.append(start_offset, meta)
|
.append(start_offset, meta)
|
||||||
.expect("First insertion always succeeds");
|
.expect("First insertion always succeeds");
|
||||||
|
|
||||||
let start_blk_no = start_offset as usize / chunk_size;
|
let start_blk_no = start_offset as usize / Self::CHUNK_SIZE;
|
||||||
let end_blk_no = div_round_up(end_offset as usize, chunk_size);
|
let end_blk_no = div_round_up(end_offset as usize, Self::CHUNK_SIZE);
|
||||||
Self {
|
Self {
|
||||||
start_blk_no,
|
start_blk_no,
|
||||||
end_blk_no,
|
end_blk_no,
|
||||||
blobs_at,
|
blobs_at,
|
||||||
max_read_size,
|
max_read_size,
|
||||||
chunk_size,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,18 +234,12 @@ impl ChunkedVectoredReadBuilder {
|
|||||||
end_offset: u64,
|
end_offset: u64,
|
||||||
meta: BlobMeta,
|
meta: BlobMeta,
|
||||||
max_read_size: usize,
|
max_read_size: usize,
|
||||||
align: usize,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new_impl(start_offset, end_offset, meta, Some(max_read_size), align)
|
Self::new_impl(start_offset, end_offset, meta, Some(max_read_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_streaming(
|
pub(crate) fn new_streaming(start_offset: u64, end_offset: u64, meta: BlobMeta) -> Self {
|
||||||
start_offset: u64,
|
Self::new_impl(start_offset, end_offset, meta, None)
|
||||||
end_offset: u64,
|
|
||||||
meta: BlobMeta,
|
|
||||||
align: usize,
|
|
||||||
) -> Self {
|
|
||||||
Self::new_impl(start_offset, end_offset, meta, None, align)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to extend the current read with a new blob if the new blob resides in the same or the immediate next chunk.
|
/// Attempts to extend the current read with a new blob if the new blob resides in the same or the immediate next chunk.
|
||||||
@@ -256,12 +247,12 @@ impl ChunkedVectoredReadBuilder {
|
|||||||
/// The resulting size also must be below the max read size.
|
/// The resulting size also must be below the max read size.
|
||||||
pub(crate) fn extend(&mut self, start: u64, end: u64, meta: BlobMeta) -> VectoredReadExtended {
|
pub(crate) fn extend(&mut self, start: u64, end: u64, meta: BlobMeta) -> VectoredReadExtended {
|
||||||
tracing::trace!(start, end, "trying to extend");
|
tracing::trace!(start, end, "trying to extend");
|
||||||
let start_blk_no = start as usize / self.chunk_size;
|
let start_blk_no = start as usize / Self::CHUNK_SIZE;
|
||||||
let end_blk_no = div_round_up(end as usize, self.chunk_size);
|
let end_blk_no = div_round_up(end as usize, Self::CHUNK_SIZE);
|
||||||
|
|
||||||
let not_limited_by_max_read_size = {
|
let not_limited_by_max_read_size = {
|
||||||
if let Some(max_read_size) = self.max_read_size {
|
if let Some(max_read_size) = self.max_read_size {
|
||||||
let coalesced_size = (end_blk_no - self.start_blk_no) * self.chunk_size;
|
let coalesced_size = (end_blk_no - self.start_blk_no) * Self::CHUNK_SIZE;
|
||||||
coalesced_size <= max_read_size
|
coalesced_size <= max_read_size
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
@@ -292,12 +283,12 @@ impl ChunkedVectoredReadBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn size(&self) -> usize {
|
pub(crate) fn size(&self) -> usize {
|
||||||
(self.end_blk_no - self.start_blk_no) * self.chunk_size
|
(self.end_blk_no - self.start_blk_no) * Self::CHUNK_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn build(self) -> VectoredRead {
|
pub(crate) fn build(self) -> VectoredRead {
|
||||||
let start = (self.start_blk_no * self.chunk_size) as u64;
|
let start = (self.start_blk_no * Self::CHUNK_SIZE) as u64;
|
||||||
let end = (self.end_blk_no * self.chunk_size) as u64;
|
let end = (self.end_blk_no * Self::CHUNK_SIZE) as u64;
|
||||||
VectoredRead {
|
VectoredRead {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
@@ -328,18 +319,14 @@ pub struct VectoredReadPlanner {
|
|||||||
prev: Option<(Key, Lsn, u64, BlobFlag)>,
|
prev: Option<(Key, Lsn, u64, BlobFlag)>,
|
||||||
|
|
||||||
max_read_size: usize,
|
max_read_size: usize,
|
||||||
|
|
||||||
align: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VectoredReadPlanner {
|
impl VectoredReadPlanner {
|
||||||
pub fn new(max_read_size: usize) -> Self {
|
pub fn new(max_read_size: usize) -> Self {
|
||||||
let align = virtual_file::get_io_buffer_alignment();
|
|
||||||
Self {
|
Self {
|
||||||
blobs: BTreeMap::new(),
|
blobs: BTreeMap::new(),
|
||||||
prev: None,
|
prev: None,
|
||||||
max_read_size,
|
max_read_size,
|
||||||
align,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,7 +405,6 @@ impl VectoredReadPlanner {
|
|||||||
end_offset,
|
end_offset,
|
||||||
BlobMeta { key, lsn },
|
BlobMeta { key, lsn },
|
||||||
self.max_read_size,
|
self.max_read_size,
|
||||||
self.align,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let prev_read_builder = current_read_builder.replace(next_read_builder);
|
let prev_read_builder = current_read_builder.replace(next_read_builder);
|
||||||
@@ -472,13 +458,13 @@ impl<'a> VectoredBlobReader<'a> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
let align = virtual_file::get_io_buffer_alignment() as u64;
|
const ALIGN: u64 = virtual_file::get_io_buffer_alignment() as u64;
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
read.start % align,
|
read.start % ALIGN,
|
||||||
0,
|
0,
|
||||||
"Read start at {} does not satisfy the required io buffer alignment ({} bytes)",
|
"Read start at {} does not satisfy the required io buffer alignment ({} bytes)",
|
||||||
read.start,
|
read.start,
|
||||||
align
|
ALIGN
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,22 +539,18 @@ pub struct StreamingVectoredReadPlanner {
|
|||||||
max_cnt: usize,
|
max_cnt: usize,
|
||||||
/// Size of the current batch
|
/// Size of the current batch
|
||||||
cnt: usize,
|
cnt: usize,
|
||||||
|
|
||||||
align: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StreamingVectoredReadPlanner {
|
impl StreamingVectoredReadPlanner {
|
||||||
pub fn new(max_read_size: u64, max_cnt: usize) -> Self {
|
pub fn new(max_read_size: u64, max_cnt: usize) -> Self {
|
||||||
assert!(max_cnt > 0);
|
assert!(max_cnt > 0);
|
||||||
assert!(max_read_size > 0);
|
assert!(max_read_size > 0);
|
||||||
let align = virtual_file::get_io_buffer_alignment();
|
|
||||||
Self {
|
Self {
|
||||||
read_builder: None,
|
read_builder: None,
|
||||||
prev: None,
|
prev: None,
|
||||||
max_cnt,
|
max_cnt,
|
||||||
max_read_size,
|
max_read_size,
|
||||||
cnt: 0,
|
cnt: 0,
|
||||||
align,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,7 +603,6 @@ impl StreamingVectoredReadPlanner {
|
|||||||
start_offset,
|
start_offset,
|
||||||
end_offset,
|
end_offset,
|
||||||
BlobMeta { key, lsn },
|
BlobMeta { key, lsn },
|
||||||
self.align,
|
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -656,9 +637,9 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn validate_read(read: &VectoredRead, offset_range: &[(Key, Lsn, u64, BlobFlag)]) {
|
fn validate_read(read: &VectoredRead, offset_range: &[(Key, Lsn, u64, BlobFlag)]) {
|
||||||
let align = virtual_file::get_io_buffer_alignment() as u64;
|
const ALIGN: u64 = virtual_file::get_io_buffer_alignment() as u64;
|
||||||
assert_eq!(read.start % align, 0);
|
assert_eq!(read.start % ALIGN, 0);
|
||||||
assert_eq!(read.start / align, offset_range.first().unwrap().2 / align);
|
assert_eq!(read.start / ALIGN, offset_range.first().unwrap().2 / ALIGN);
|
||||||
|
|
||||||
let expected_offsets_in_read: Vec<_> = offset_range.iter().map(|o| o.2).collect();
|
let expected_offsets_in_read: Vec<_> = offset_range.iter().map(|o| o.2).collect();
|
||||||
|
|
||||||
@@ -676,32 +657,27 @@ mod tests {
|
|||||||
fn planner_chunked_coalesce_all_test() {
|
fn planner_chunked_coalesce_all_test() {
|
||||||
use crate::virtual_file;
|
use crate::virtual_file;
|
||||||
|
|
||||||
let chunk_size = virtual_file::get_io_buffer_alignment() as u64;
|
const CHUNK_SIZE: u64 = virtual_file::get_io_buffer_alignment() as u64;
|
||||||
|
|
||||||
// The test explicitly does not check chunk size < 512
|
let max_read_size = CHUNK_SIZE as usize * 8;
|
||||||
if chunk_size < 512 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let max_read_size = chunk_size as usize * 8;
|
|
||||||
let key = Key::MIN;
|
let key = Key::MIN;
|
||||||
let lsn = Lsn(0);
|
let lsn = Lsn(0);
|
||||||
|
|
||||||
let blob_descriptions = [
|
let blob_descriptions = [
|
||||||
(key, lsn, chunk_size / 8, BlobFlag::None), // Read 1 BEGIN
|
(key, lsn, CHUNK_SIZE / 8, BlobFlag::None), // Read 1 BEGIN
|
||||||
(key, lsn, chunk_size / 4, BlobFlag::Ignore), // Gap
|
(key, lsn, CHUNK_SIZE / 4, BlobFlag::Ignore), // Gap
|
||||||
(key, lsn, chunk_size / 2, BlobFlag::None),
|
(key, lsn, CHUNK_SIZE / 2, BlobFlag::None),
|
||||||
(key, lsn, chunk_size - 2, BlobFlag::Ignore), // Gap
|
(key, lsn, CHUNK_SIZE - 2, BlobFlag::Ignore), // Gap
|
||||||
(key, lsn, chunk_size, BlobFlag::None),
|
(key, lsn, CHUNK_SIZE, BlobFlag::None),
|
||||||
(key, lsn, chunk_size * 2 - 1, BlobFlag::None),
|
(key, lsn, CHUNK_SIZE * 2 - 1, BlobFlag::None),
|
||||||
(key, lsn, chunk_size * 2 + 1, BlobFlag::Ignore), // Gap
|
(key, lsn, CHUNK_SIZE * 2 + 1, BlobFlag::Ignore), // Gap
|
||||||
(key, lsn, chunk_size * 3 + 1, BlobFlag::None),
|
(key, lsn, CHUNK_SIZE * 3 + 1, BlobFlag::None),
|
||||||
(key, lsn, chunk_size * 5 + 1, BlobFlag::None),
|
(key, lsn, CHUNK_SIZE * 5 + 1, BlobFlag::None),
|
||||||
(key, lsn, chunk_size * 6 + 1, BlobFlag::Ignore), // skipped chunk size, but not a chunk: should coalesce.
|
(key, lsn, CHUNK_SIZE * 6 + 1, BlobFlag::Ignore), // skipped chunk size, but not a chunk: should coalesce.
|
||||||
(key, lsn, chunk_size * 7 + 1, BlobFlag::None),
|
(key, lsn, CHUNK_SIZE * 7 + 1, BlobFlag::None),
|
||||||
(key, lsn, chunk_size * 8, BlobFlag::None), // Read 2 BEGIN (b/c max_read_size)
|
(key, lsn, CHUNK_SIZE * 8, BlobFlag::None), // Read 2 BEGIN (b/c max_read_size)
|
||||||
(key, lsn, chunk_size * 9, BlobFlag::Ignore), // ==== skipped a chunk
|
(key, lsn, CHUNK_SIZE * 9, BlobFlag::Ignore), // ==== skipped a chunk
|
||||||
(key, lsn, chunk_size * 10, BlobFlag::None), // Read 3 BEGIN (cannot coalesce)
|
(key, lsn, CHUNK_SIZE * 10, BlobFlag::None), // Read 3 BEGIN (cannot coalesce)
|
||||||
];
|
];
|
||||||
|
|
||||||
let ranges = [
|
let ranges = [
|
||||||
@@ -780,19 +756,19 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn planner_replacement_test() {
|
fn planner_replacement_test() {
|
||||||
let chunk_size = virtual_file::get_io_buffer_alignment() as u64;
|
const CHUNK_SIZE: u64 = virtual_file::get_io_buffer_alignment() as u64;
|
||||||
let max_read_size = 128 * chunk_size as usize;
|
let max_read_size = 128 * CHUNK_SIZE as usize;
|
||||||
let first_key = Key::MIN;
|
let first_key = Key::MIN;
|
||||||
let second_key = first_key.next();
|
let second_key = first_key.next();
|
||||||
let lsn = Lsn(0);
|
let lsn = Lsn(0);
|
||||||
|
|
||||||
let blob_descriptions = vec![
|
let blob_descriptions = vec![
|
||||||
(first_key, lsn, 0, BlobFlag::None), // First in read 1
|
(first_key, lsn, 0, BlobFlag::None), // First in read 1
|
||||||
(first_key, lsn, chunk_size, BlobFlag::None), // Last in read 1
|
(first_key, lsn, CHUNK_SIZE, BlobFlag::None), // Last in read 1
|
||||||
(second_key, lsn, 2 * chunk_size, BlobFlag::ReplaceAll),
|
(second_key, lsn, 2 * CHUNK_SIZE, BlobFlag::ReplaceAll),
|
||||||
(second_key, lsn, 3 * chunk_size, BlobFlag::None),
|
(second_key, lsn, 3 * CHUNK_SIZE, BlobFlag::None),
|
||||||
(second_key, lsn, 4 * chunk_size, BlobFlag::ReplaceAll), // First in read 2
|
(second_key, lsn, 4 * CHUNK_SIZE, BlobFlag::ReplaceAll), // First in read 2
|
||||||
(second_key, lsn, 5 * chunk_size, BlobFlag::None), // Last in read 2
|
(second_key, lsn, 5 * CHUNK_SIZE, BlobFlag::None), // Last in read 2
|
||||||
];
|
];
|
||||||
|
|
||||||
let ranges = [&blob_descriptions[0..2], &blob_descriptions[4..]];
|
let ranges = [&blob_descriptions[0..2], &blob_descriptions[4..]];
|
||||||
@@ -802,7 +778,7 @@ mod tests {
|
|||||||
planner.handle(key, lsn, offset, flag);
|
planner.handle(key, lsn, offset, flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
planner.handle_range_end(6 * chunk_size);
|
planner.handle_range_end(6 * CHUNK_SIZE);
|
||||||
|
|
||||||
let reads = planner.finish();
|
let reads = planner.finish();
|
||||||
assert_eq!(reads.len(), 2);
|
assert_eq!(reads.len(), 2);
|
||||||
@@ -947,7 +923,6 @@ mod tests {
|
|||||||
let reserved_bytes = blobs.iter().map(|bl| bl.len()).max().unwrap() * 2 + 16;
|
let reserved_bytes = blobs.iter().map(|bl| bl.len()).max().unwrap() * 2 + 16;
|
||||||
let mut buf = BytesMut::with_capacity(reserved_bytes);
|
let mut buf = BytesMut::with_capacity(reserved_bytes);
|
||||||
|
|
||||||
let align = virtual_file::get_io_buffer_alignment();
|
|
||||||
let vectored_blob_reader = VectoredBlobReader::new(&file);
|
let vectored_blob_reader = VectoredBlobReader::new(&file);
|
||||||
let meta = BlobMeta {
|
let meta = BlobMeta {
|
||||||
key: Key::MIN,
|
key: Key::MIN,
|
||||||
@@ -959,8 +934,7 @@ mod tests {
|
|||||||
if idx + 1 == offsets.len() {
|
if idx + 1 == offsets.len() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let read_builder =
|
let read_builder = ChunkedVectoredReadBuilder::new(*offset, *end, meta, 16 * 4096);
|
||||||
ChunkedVectoredReadBuilder::new(*offset, *end, meta, 16 * 4096, align);
|
|
||||||
let read = read_builder.build();
|
let read = read_builder.build();
|
||||||
let result = vectored_blob_reader.read_blobs(&read, buf, &ctx).await?;
|
let result = vectored_blob_reader.read_blobs(&read, buf, &ctx).await?;
|
||||||
assert_eq!(result.blobs.len(), 1);
|
assert_eq!(result.blobs.len(), 1);
|
||||||
|
|||||||
+207
-57
@@ -23,10 +23,12 @@ use pageserver_api::config::defaults::DEFAULT_IO_BUFFER_ALIGNMENT;
|
|||||||
use pageserver_api::shard::TenantShardId;
|
use pageserver_api::shard::TenantShardId;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Error, ErrorKind, Seek, SeekFrom};
|
use std::io::{Error, ErrorKind, Seek, SeekFrom};
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
use tokio_epoll_uring::{BoundedBuf, IoBuf, IoBufMut, Slice};
|
use tokio_epoll_uring::{BoundedBuf, IoBuf, IoBufMut, Slice};
|
||||||
|
|
||||||
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
|
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering};
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ pub use io_engine::FeatureTestResult as IoEngineFeatureTestResult;
|
|||||||
mod metadata;
|
mod metadata;
|
||||||
mod open_options;
|
mod open_options;
|
||||||
use self::owned_buffers_io::write::OwnedAsyncWriter;
|
use self::owned_buffers_io::write::OwnedAsyncWriter;
|
||||||
pub(crate) use api::DirectIoMode;
|
pub(crate) use api::IoMode;
|
||||||
pub(crate) use io_engine::IoEngineKind;
|
pub(crate) use io_engine::IoEngineKind;
|
||||||
pub(crate) use metadata::Metadata;
|
pub(crate) use metadata::Metadata;
|
||||||
pub(crate) use open_options::*;
|
pub(crate) use open_options::*;
|
||||||
@@ -61,6 +63,171 @@ pub(crate) mod owned_buffers_io {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VirtualFile {
|
||||||
|
inner: VirtualFileInner,
|
||||||
|
_mode: IoMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtualFile {
|
||||||
|
/// Open a file in read-only mode. Like File::open.
|
||||||
|
pub async fn open<P: AsRef<Utf8Path>>(
|
||||||
|
path: P,
|
||||||
|
ctx: &RequestContext,
|
||||||
|
) -> Result<Self, std::io::Error> {
|
||||||
|
let inner = VirtualFileInner::open(path, ctx).await?;
|
||||||
|
Ok(VirtualFile {
|
||||||
|
inner,
|
||||||
|
_mode: IoMode::Buffered,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open a file in read-only mode. Like File::open.
|
||||||
|
///
|
||||||
|
/// `O_DIRECT` will be enabled base on `virtual_file_io_mode`.
|
||||||
|
pub async fn open_v2<P: AsRef<Utf8Path>>(
|
||||||
|
path: P,
|
||||||
|
ctx: &RequestContext,
|
||||||
|
) -> Result<Self, std::io::Error> {
|
||||||
|
Self::open_with_options_v2(path.as_ref(), OpenOptions::new().read(true), ctx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create<P: AsRef<Utf8Path>>(
|
||||||
|
path: P,
|
||||||
|
ctx: &RequestContext,
|
||||||
|
) -> Result<Self, std::io::Error> {
|
||||||
|
let inner = VirtualFileInner::create(path, ctx).await?;
|
||||||
|
Ok(VirtualFile {
|
||||||
|
inner,
|
||||||
|
_mode: IoMode::Buffered,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_v2<P: AsRef<Utf8Path>>(
|
||||||
|
path: P,
|
||||||
|
ctx: &RequestContext,
|
||||||
|
) -> Result<Self, std::io::Error> {
|
||||||
|
VirtualFile::open_with_options_v2(
|
||||||
|
path.as_ref(),
|
||||||
|
OpenOptions::new().write(true).create(true).truncate(true),
|
||||||
|
ctx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn open_with_options<P: AsRef<Utf8Path>>(
|
||||||
|
path: P,
|
||||||
|
open_options: &OpenOptions,
|
||||||
|
ctx: &RequestContext, /* TODO: carry a pointer to the metrics in the RequestContext instead of the parsing https://github.com/neondatabase/neon/issues/6107 */
|
||||||
|
) -> Result<Self, std::io::Error> {
|
||||||
|
let inner = VirtualFileInner::open_with_options(path, open_options, ctx).await?;
|
||||||
|
Ok(VirtualFile {
|
||||||
|
inner,
|
||||||
|
_mode: IoMode::Buffered,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn open_with_options_v2<P: AsRef<Utf8Path>>(
|
||||||
|
path: P,
|
||||||
|
open_options: &OpenOptions,
|
||||||
|
ctx: &RequestContext, /* TODO: carry a pointer to the metrics in the RequestContext instead of the parsing https://github.com/neondatabase/neon/issues/6107 */
|
||||||
|
) -> Result<Self, std::io::Error> {
|
||||||
|
let file = match get_io_mode() {
|
||||||
|
IoMode::Buffered => {
|
||||||
|
let inner = VirtualFileInner::open_with_options(path, open_options, ctx).await?;
|
||||||
|
VirtualFile {
|
||||||
|
inner,
|
||||||
|
_mode: IoMode::Buffered,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
IoMode::Direct => {
|
||||||
|
let inner = VirtualFileInner::open_with_options(
|
||||||
|
path,
|
||||||
|
open_options.clone().custom_flags(nix::libc::O_DIRECT),
|
||||||
|
ctx,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
VirtualFile {
|
||||||
|
inner,
|
||||||
|
_mode: IoMode::Direct,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> &Utf8Path {
|
||||||
|
self.inner.path.as_path()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn crashsafe_overwrite<B: BoundedBuf<Buf = Buf> + Send, Buf: IoBuf + Send>(
|
||||||
|
final_path: Utf8PathBuf,
|
||||||
|
tmp_path: Utf8PathBuf,
|
||||||
|
content: B,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
VirtualFileInner::crashsafe_overwrite(final_path, tmp_path, content).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sync_all(&self) -> Result<(), Error> {
|
||||||
|
self.inner.sync_all().await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sync_data(&self) -> Result<(), Error> {
|
||||||
|
self.inner.sync_data().await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn metadata(&self) -> Result<Metadata, Error> {
|
||||||
|
self.inner.metadata().await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(self) {
|
||||||
|
self.inner.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn seek(&mut self, pos: SeekFrom) -> Result<u64, Error> {
|
||||||
|
self.inner.seek(pos).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read_exact_at<Buf>(
|
||||||
|
&self,
|
||||||
|
slice: Slice<Buf>,
|
||||||
|
offset: u64,
|
||||||
|
ctx: &RequestContext,
|
||||||
|
) -> Result<Slice<Buf>, Error>
|
||||||
|
where
|
||||||
|
Buf: IoBufMut + Send,
|
||||||
|
{
|
||||||
|
self.inner.read_exact_at(slice, offset, ctx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read_exact_at_page(
|
||||||
|
&self,
|
||||||
|
page: PageWriteGuard<'static>,
|
||||||
|
offset: u64,
|
||||||
|
ctx: &RequestContext,
|
||||||
|
) -> Result<PageWriteGuard<'static>, Error> {
|
||||||
|
self.inner.read_exact_at_page(page, offset, ctx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn write_all_at<Buf: IoBuf + Send>(
|
||||||
|
&self,
|
||||||
|
buf: FullSlice<Buf>,
|
||||||
|
offset: u64,
|
||||||
|
ctx: &RequestContext,
|
||||||
|
) -> (FullSlice<Buf>, Result<(), Error>) {
|
||||||
|
self.inner.write_all_at(buf, offset, ctx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn write_all<Buf: IoBuf + Send>(
|
||||||
|
&mut self,
|
||||||
|
buf: FullSlice<Buf>,
|
||||||
|
ctx: &RequestContext,
|
||||||
|
) -> (FullSlice<Buf>, Result<usize, Error>) {
|
||||||
|
self.inner.write_all(buf, ctx).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// A virtual file descriptor. You can use this just like std::fs::File, but internally
|
/// A virtual file descriptor. You can use this just like std::fs::File, but internally
|
||||||
/// the underlying file is closed if the system is low on file descriptors,
|
/// the underlying file is closed if the system is low on file descriptors,
|
||||||
@@ -77,7 +244,7 @@ pub(crate) mod owned_buffers_io {
|
|||||||
/// 'tag' field is used to detect whether the handle still is valid or not.
|
/// 'tag' field is used to detect whether the handle still is valid or not.
|
||||||
///
|
///
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct VirtualFile {
|
pub struct VirtualFileInner {
|
||||||
/// Lazy handle to the global file descriptor cache. The slot that this points to
|
/// Lazy handle to the global file descriptor cache. The slot that this points to
|
||||||
/// might contain our File, or it may be empty, or it may contain a File that
|
/// might contain our File, or it may be empty, or it may contain a File that
|
||||||
/// belongs to a different VirtualFile.
|
/// belongs to a different VirtualFile.
|
||||||
@@ -350,12 +517,12 @@ macro_rules! with_file {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtualFile {
|
impl VirtualFileInner {
|
||||||
/// Open a file in read-only mode. Like File::open.
|
/// Open a file in read-only mode. Like File::open.
|
||||||
pub async fn open<P: AsRef<Utf8Path>>(
|
pub async fn open<P: AsRef<Utf8Path>>(
|
||||||
path: P,
|
path: P,
|
||||||
ctx: &RequestContext,
|
ctx: &RequestContext,
|
||||||
) -> Result<VirtualFile, std::io::Error> {
|
) -> Result<VirtualFileInner, std::io::Error> {
|
||||||
Self::open_with_options(path.as_ref(), OpenOptions::new().read(true), ctx).await
|
Self::open_with_options(path.as_ref(), OpenOptions::new().read(true), ctx).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,7 +531,7 @@ impl VirtualFile {
|
|||||||
pub async fn create<P: AsRef<Utf8Path>>(
|
pub async fn create<P: AsRef<Utf8Path>>(
|
||||||
path: P,
|
path: P,
|
||||||
ctx: &RequestContext,
|
ctx: &RequestContext,
|
||||||
) -> Result<VirtualFile, std::io::Error> {
|
) -> Result<VirtualFileInner, std::io::Error> {
|
||||||
Self::open_with_options(
|
Self::open_with_options(
|
||||||
path.as_ref(),
|
path.as_ref(),
|
||||||
OpenOptions::new().write(true).create(true).truncate(true),
|
OpenOptions::new().write(true).create(true).truncate(true),
|
||||||
@@ -382,7 +549,7 @@ impl VirtualFile {
|
|||||||
path: P,
|
path: P,
|
||||||
open_options: &OpenOptions,
|
open_options: &OpenOptions,
|
||||||
_ctx: &RequestContext, /* TODO: carry a pointer to the metrics in the RequestContext instead of the parsing https://github.com/neondatabase/neon/issues/6107 */
|
_ctx: &RequestContext, /* TODO: carry a pointer to the metrics in the RequestContext instead of the parsing https://github.com/neondatabase/neon/issues/6107 */
|
||||||
) -> Result<VirtualFile, std::io::Error> {
|
) -> Result<VirtualFileInner, std::io::Error> {
|
||||||
let path_ref = path.as_ref();
|
let path_ref = path.as_ref();
|
||||||
let path_str = path_ref.to_string();
|
let path_str = path_ref.to_string();
|
||||||
let parts = path_str.split('/').collect::<Vec<&str>>();
|
let parts = path_str.split('/').collect::<Vec<&str>>();
|
||||||
@@ -423,7 +590,7 @@ impl VirtualFile {
|
|||||||
reopen_options.create_new(false);
|
reopen_options.create_new(false);
|
||||||
reopen_options.truncate(false);
|
reopen_options.truncate(false);
|
||||||
|
|
||||||
let vfile = VirtualFile {
|
let vfile = VirtualFileInner {
|
||||||
handle: RwLock::new(handle),
|
handle: RwLock::new(handle),
|
||||||
pos: 0,
|
pos: 0,
|
||||||
path: path_ref.to_path_buf(),
|
path: path_ref.to_path_buf(),
|
||||||
@@ -1034,6 +1201,21 @@ impl tokio_epoll_uring::IoFd for FileGuard {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl VirtualFile {
|
impl VirtualFile {
|
||||||
|
pub(crate) async fn read_blk(
|
||||||
|
&self,
|
||||||
|
blknum: u32,
|
||||||
|
ctx: &RequestContext,
|
||||||
|
) -> Result<crate::tenant::block_io::BlockLease<'_>, std::io::Error> {
|
||||||
|
self.inner.read_blk(blknum, ctx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_to_end(&mut self, buf: &mut Vec<u8>, ctx: &RequestContext) -> Result<(), Error> {
|
||||||
|
self.inner.read_to_end(buf, ctx).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl VirtualFileInner {
|
||||||
pub(crate) async fn read_blk(
|
pub(crate) async fn read_blk(
|
||||||
&self,
|
&self,
|
||||||
blknum: u32,
|
blknum: u32,
|
||||||
@@ -1067,7 +1249,7 @@ impl VirtualFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for VirtualFile {
|
impl Drop for VirtualFileInner {
|
||||||
/// If a VirtualFile is dropped, close the underlying file if it was open.
|
/// If a VirtualFile is dropped, close the underlying file if it was open.
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let handle = self.handle.get_mut();
|
let handle = self.handle.get_mut();
|
||||||
@@ -1143,15 +1325,10 @@ impl OpenFiles {
|
|||||||
/// server startup.
|
/// server startup.
|
||||||
///
|
///
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
pub fn init(num_slots: usize, engine: IoEngineKind, io_buffer_alignment: usize) {
|
pub fn init(num_slots: usize, engine: IoEngineKind) {
|
||||||
if OPEN_FILES.set(OpenFiles::new(num_slots)).is_err() {
|
if OPEN_FILES.set(OpenFiles::new(num_slots)).is_err() {
|
||||||
panic!("virtual_file::init called twice");
|
panic!("virtual_file::init called twice");
|
||||||
}
|
}
|
||||||
if set_io_buffer_alignment(io_buffer_alignment).is_err() {
|
|
||||||
panic!(
|
|
||||||
"IO buffer alignment needs to be a power of two and greater than 512, got {io_buffer_alignment}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
io_engine::init(engine);
|
io_engine::init(engine);
|
||||||
crate::metrics::virtual_file_descriptor_cache::SIZE_MAX.set(num_slots as u64);
|
crate::metrics::virtual_file_descriptor_cache::SIZE_MAX.set(num_slots as u64);
|
||||||
}
|
}
|
||||||
@@ -1175,47 +1352,20 @@ fn get_open_files() -> &'static OpenFiles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static IO_BUFFER_ALIGNMENT: AtomicUsize = AtomicUsize::new(DEFAULT_IO_BUFFER_ALIGNMENT);
|
|
||||||
|
|
||||||
/// Returns true if the alignment is a power of two and is greater or equal to 512.
|
|
||||||
fn is_valid_io_buffer_alignment(align: usize) -> bool {
|
|
||||||
align.is_power_of_two() && align >= 512
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets IO buffer alignment requirement. Returns error if the alignment requirement is
|
|
||||||
/// not a power of two or less than 512 bytes.
|
|
||||||
#[allow(unused)]
|
|
||||||
pub(crate) fn set_io_buffer_alignment(align: usize) -> Result<(), usize> {
|
|
||||||
if is_valid_io_buffer_alignment(align) {
|
|
||||||
IO_BUFFER_ALIGNMENT.store(align, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(align)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the io buffer alignment.
|
/// Gets the io buffer alignment.
|
||||||
///
|
pub(crate) const fn get_io_buffer_alignment() -> usize {
|
||||||
/// This function should be used for getting the actual alignment value to use.
|
DEFAULT_IO_BUFFER_ALIGNMENT
|
||||||
pub(crate) fn get_io_buffer_alignment() -> usize {
|
|
||||||
let align = IO_BUFFER_ALIGNMENT.load(std::sync::atomic::Ordering::Relaxed);
|
|
||||||
|
|
||||||
if cfg!(test) {
|
|
||||||
let env_var_name = "NEON_PAGESERVER_UNIT_TEST_IO_BUFFER_ALIGNMENT";
|
|
||||||
if let Some(test_align) = utils::env::var(env_var_name) {
|
|
||||||
if is_valid_io_buffer_alignment(test_align) {
|
|
||||||
test_align
|
|
||||||
} else {
|
|
||||||
panic!("IO buffer alignment needs to be a power of two and greater than 512, got {test_align}");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
align
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
align
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static IO_MODE: AtomicU8 = AtomicU8::new(IoMode::preferred() as u8);
|
||||||
|
|
||||||
|
pub(crate) fn set_io_mode(mode: IoMode) {
|
||||||
|
IO_MODE.store(mode as u8, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_io_mode() -> IoMode {
|
||||||
|
IoMode::try_from(IO_MODE.load(Ordering::Relaxed)).unwrap()
|
||||||
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::context::DownloadBehavior;
|
use crate::context::DownloadBehavior;
|
||||||
@@ -1524,7 +1674,7 @@ mod tests {
|
|||||||
// Open the file many times.
|
// Open the file many times.
|
||||||
let mut files = Vec::new();
|
let mut files = Vec::new();
|
||||||
for _ in 0..VIRTUAL_FILES {
|
for _ in 0..VIRTUAL_FILES {
|
||||||
let f = VirtualFile::open_with_options(
|
let f = VirtualFileInner::open_with_options(
|
||||||
&test_file_path,
|
&test_file_path,
|
||||||
OpenOptions::new().read(true),
|
OpenOptions::new().read(true),
|
||||||
&ctx,
|
&ctx,
|
||||||
@@ -1576,7 +1726,7 @@ mod tests {
|
|||||||
let path = testdir.join("myfile");
|
let path = testdir.join("myfile");
|
||||||
let tmp_path = testdir.join("myfile.tmp");
|
let tmp_path = testdir.join("myfile.tmp");
|
||||||
|
|
||||||
VirtualFile::crashsafe_overwrite(path.clone(), tmp_path.clone(), b"foo".to_vec())
|
VirtualFileInner::crashsafe_overwrite(path.clone(), tmp_path.clone(), b"foo".to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut file = MaybeVirtualFile::from(VirtualFile::open(&path, &ctx).await.unwrap());
|
let mut file = MaybeVirtualFile::from(VirtualFile::open(&path, &ctx).await.unwrap());
|
||||||
@@ -1585,7 +1735,7 @@ mod tests {
|
|||||||
assert!(!tmp_path.exists());
|
assert!(!tmp_path.exists());
|
||||||
drop(file);
|
drop(file);
|
||||||
|
|
||||||
VirtualFile::crashsafe_overwrite(path.clone(), tmp_path.clone(), b"bar".to_vec())
|
VirtualFileInner::crashsafe_overwrite(path.clone(), tmp_path.clone(), b"bar".to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut file = MaybeVirtualFile::from(VirtualFile::open(&path, &ctx).await.unwrap());
|
let mut file = MaybeVirtualFile::from(VirtualFile::open(&path, &ctx).await.unwrap());
|
||||||
@@ -1608,7 +1758,7 @@ mod tests {
|
|||||||
std::fs::write(&tmp_path, "some preexisting junk that should be removed").unwrap();
|
std::fs::write(&tmp_path, "some preexisting junk that should be removed").unwrap();
|
||||||
assert!(tmp_path.exists());
|
assert!(tmp_path.exists());
|
||||||
|
|
||||||
VirtualFile::crashsafe_overwrite(path.clone(), tmp_path.clone(), b"foo".to_vec())
|
VirtualFileInner::crashsafe_overwrite(path.clone(), tmp_path.clone(), b"foo".to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -146,6 +146,8 @@ ConstructDeltaMessage()
|
|||||||
if (RootTable.role_table)
|
if (RootTable.role_table)
|
||||||
{
|
{
|
||||||
JsonbValue roles;
|
JsonbValue roles;
|
||||||
|
HASH_SEQ_STATUS status;
|
||||||
|
RoleEntry *entry;
|
||||||
|
|
||||||
roles.type = jbvString;
|
roles.type = jbvString;
|
||||||
roles.val.string.val = "roles";
|
roles.val.string.val = "roles";
|
||||||
@@ -153,9 +155,6 @@ ConstructDeltaMessage()
|
|||||||
pushJsonbValue(&state, WJB_KEY, &roles);
|
pushJsonbValue(&state, WJB_KEY, &roles);
|
||||||
pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
|
pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
|
||||||
|
|
||||||
HASH_SEQ_STATUS status;
|
|
||||||
RoleEntry *entry;
|
|
||||||
|
|
||||||
hash_seq_init(&status, RootTable.role_table);
|
hash_seq_init(&status, RootTable.role_table);
|
||||||
while ((entry = hash_seq_search(&status)) != NULL)
|
while ((entry = hash_seq_search(&status)) != NULL)
|
||||||
{
|
{
|
||||||
@@ -190,10 +189,12 @@ ConstructDeltaMessage()
|
|||||||
}
|
}
|
||||||
pushJsonbValue(&state, WJB_END_ARRAY, NULL);
|
pushJsonbValue(&state, WJB_END_ARRAY, NULL);
|
||||||
}
|
}
|
||||||
JsonbValue *result = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
|
{
|
||||||
Jsonb *jsonb = JsonbValueToJsonb(result);
|
JsonbValue *result = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
|
||||||
|
Jsonb *jsonb = JsonbValueToJsonb(result);
|
||||||
|
|
||||||
return JsonbToCString(NULL, &jsonb->root, 0 /* estimated_len */ );
|
return JsonbToCString(NULL, &jsonb->root, 0 /* estimated_len */ );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define ERROR_SIZE 1024
|
#define ERROR_SIZE 1024
|
||||||
@@ -272,32 +273,28 @@ SendDeltasToControlPlane()
|
|||||||
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ErrorWriteCallback);
|
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ErrorWriteCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
char *message = ConstructDeltaMessage();
|
|
||||||
ErrorString str;
|
|
||||||
|
|
||||||
str.size = 0;
|
|
||||||
|
|
||||||
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, message);
|
|
||||||
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &str);
|
|
||||||
|
|
||||||
const int num_retries = 5;
|
|
||||||
CURLcode curl_status;
|
|
||||||
|
|
||||||
for (int i = 0; i < num_retries; i++)
|
|
||||||
{
|
|
||||||
if ((curl_status = curl_easy_perform(handle)) == 0)
|
|
||||||
break;
|
|
||||||
elog(LOG, "Curl request failed on attempt %d: %s", i, CurlErrorBuf);
|
|
||||||
pg_usleep(1000 * 1000);
|
|
||||||
}
|
|
||||||
if (curl_status != CURLE_OK)
|
|
||||||
{
|
|
||||||
elog(ERROR, "Failed to perform curl request: %s", CurlErrorBuf);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
|
char *message = ConstructDeltaMessage();
|
||||||
|
ErrorString str;
|
||||||
|
const int num_retries = 5;
|
||||||
|
CURLcode curl_status;
|
||||||
long response_code;
|
long response_code;
|
||||||
|
|
||||||
|
str.size = 0;
|
||||||
|
|
||||||
|
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, message);
|
||||||
|
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &str);
|
||||||
|
|
||||||
|
for (int i = 0; i < num_retries; i++)
|
||||||
|
{
|
||||||
|
if ((curl_status = curl_easy_perform(handle)) == 0)
|
||||||
|
break;
|
||||||
|
elog(LOG, "Curl request failed on attempt %d: %s", i, CurlErrorBuf);
|
||||||
|
pg_usleep(1000 * 1000);
|
||||||
|
}
|
||||||
|
if (curl_status != CURLE_OK)
|
||||||
|
elog(ERROR, "Failed to perform curl request: %s", CurlErrorBuf);
|
||||||
|
|
||||||
if (curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &response_code) != CURLE_UNKNOWN_OPTION)
|
if (curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &response_code) != CURLE_UNKNOWN_OPTION)
|
||||||
{
|
{
|
||||||
if (response_code != 200)
|
if (response_code != 200)
|
||||||
@@ -376,10 +373,11 @@ MergeTable()
|
|||||||
|
|
||||||
if (old_table->db_table)
|
if (old_table->db_table)
|
||||||
{
|
{
|
||||||
InitDbTableIfNeeded();
|
|
||||||
DbEntry *entry;
|
DbEntry *entry;
|
||||||
HASH_SEQ_STATUS status;
|
HASH_SEQ_STATUS status;
|
||||||
|
|
||||||
|
InitDbTableIfNeeded();
|
||||||
|
|
||||||
hash_seq_init(&status, old_table->db_table);
|
hash_seq_init(&status, old_table->db_table);
|
||||||
while ((entry = hash_seq_search(&status)) != NULL)
|
while ((entry = hash_seq_search(&status)) != NULL)
|
||||||
{
|
{
|
||||||
@@ -421,10 +419,11 @@ MergeTable()
|
|||||||
|
|
||||||
if (old_table->role_table)
|
if (old_table->role_table)
|
||||||
{
|
{
|
||||||
InitRoleTableIfNeeded();
|
|
||||||
RoleEntry *entry;
|
RoleEntry *entry;
|
||||||
HASH_SEQ_STATUS status;
|
HASH_SEQ_STATUS status;
|
||||||
|
|
||||||
|
InitRoleTableIfNeeded();
|
||||||
|
|
||||||
hash_seq_init(&status, old_table->role_table);
|
hash_seq_init(&status, old_table->role_table);
|
||||||
while ((entry = hash_seq_search(&status)) != NULL)
|
while ((entry = hash_seq_search(&status)) != NULL)
|
||||||
{
|
{
|
||||||
@@ -515,9 +514,12 @@ RoleIsNeonSuperuser(const char *role_name)
|
|||||||
static void
|
static void
|
||||||
HandleCreateDb(CreatedbStmt *stmt)
|
HandleCreateDb(CreatedbStmt *stmt)
|
||||||
{
|
{
|
||||||
InitDbTableIfNeeded();
|
|
||||||
DefElem *downer = NULL;
|
DefElem *downer = NULL;
|
||||||
ListCell *option;
|
ListCell *option;
|
||||||
|
bool found = false;
|
||||||
|
DbEntry *entry;
|
||||||
|
|
||||||
|
InitDbTableIfNeeded();
|
||||||
|
|
||||||
foreach(option, stmt->options)
|
foreach(option, stmt->options)
|
||||||
{
|
{
|
||||||
@@ -526,13 +528,11 @@ HandleCreateDb(CreatedbStmt *stmt)
|
|||||||
if (strcmp(defel->defname, "owner") == 0)
|
if (strcmp(defel->defname, "owner") == 0)
|
||||||
downer = defel;
|
downer = defel;
|
||||||
}
|
}
|
||||||
bool found = false;
|
|
||||||
DbEntry *entry = hash_search(
|
|
||||||
CurrentDdlTable->db_table,
|
|
||||||
stmt->dbname,
|
|
||||||
HASH_ENTER,
|
|
||||||
&found);
|
|
||||||
|
|
||||||
|
entry = hash_search(CurrentDdlTable->db_table,
|
||||||
|
stmt->dbname,
|
||||||
|
HASH_ENTER,
|
||||||
|
&found);
|
||||||
if (!found)
|
if (!found)
|
||||||
memset(entry->old_name, 0, sizeof(entry->old_name));
|
memset(entry->old_name, 0, sizeof(entry->old_name));
|
||||||
|
|
||||||
@@ -554,21 +554,24 @@ HandleCreateDb(CreatedbStmt *stmt)
|
|||||||
static void
|
static void
|
||||||
HandleAlterOwner(AlterOwnerStmt *stmt)
|
HandleAlterOwner(AlterOwnerStmt *stmt)
|
||||||
{
|
{
|
||||||
|
const char *name;
|
||||||
|
bool found = false;
|
||||||
|
DbEntry *entry;
|
||||||
|
const char *new_owner;
|
||||||
|
|
||||||
if (stmt->objectType != OBJECT_DATABASE)
|
if (stmt->objectType != OBJECT_DATABASE)
|
||||||
return;
|
return;
|
||||||
InitDbTableIfNeeded();
|
InitDbTableIfNeeded();
|
||||||
const char *name = strVal(stmt->object);
|
|
||||||
bool found = false;
|
|
||||||
DbEntry *entry = hash_search(
|
|
||||||
CurrentDdlTable->db_table,
|
|
||||||
name,
|
|
||||||
HASH_ENTER,
|
|
||||||
&found);
|
|
||||||
|
|
||||||
|
name = strVal(stmt->object);
|
||||||
|
entry = hash_search(CurrentDdlTable->db_table,
|
||||||
|
name,
|
||||||
|
HASH_ENTER,
|
||||||
|
&found);
|
||||||
if (!found)
|
if (!found)
|
||||||
memset(entry->old_name, 0, sizeof(entry->old_name));
|
memset(entry->old_name, 0, sizeof(entry->old_name));
|
||||||
const char *new_owner = get_rolespec_name(stmt->newowner);
|
|
||||||
|
|
||||||
|
new_owner = get_rolespec_name(stmt->newowner);
|
||||||
if (RoleIsNeonSuperuser(new_owner))
|
if (RoleIsNeonSuperuser(new_owner))
|
||||||
elog(ERROR, "can't alter owner to neon_superuser");
|
elog(ERROR, "can't alter owner to neon_superuser");
|
||||||
entry->owner = get_role_oid(new_owner, false);
|
entry->owner = get_role_oid(new_owner, false);
|
||||||
@@ -578,21 +581,23 @@ HandleAlterOwner(AlterOwnerStmt *stmt)
|
|||||||
static void
|
static void
|
||||||
HandleDbRename(RenameStmt *stmt)
|
HandleDbRename(RenameStmt *stmt)
|
||||||
{
|
{
|
||||||
|
bool found = false;
|
||||||
|
DbEntry *entry;
|
||||||
|
DbEntry *entry_for_new_name;
|
||||||
|
|
||||||
Assert(stmt->renameType == OBJECT_DATABASE);
|
Assert(stmt->renameType == OBJECT_DATABASE);
|
||||||
InitDbTableIfNeeded();
|
InitDbTableIfNeeded();
|
||||||
bool found = false;
|
entry = hash_search(CurrentDdlTable->db_table,
|
||||||
DbEntry *entry = hash_search(
|
stmt->subname,
|
||||||
CurrentDdlTable->db_table,
|
HASH_FIND,
|
||||||
stmt->subname,
|
&found);
|
||||||
HASH_FIND,
|
|
||||||
&found);
|
|
||||||
DbEntry *entry_for_new_name = hash_search(
|
|
||||||
CurrentDdlTable->db_table,
|
|
||||||
stmt->newname,
|
|
||||||
HASH_ENTER,
|
|
||||||
NULL);
|
|
||||||
|
|
||||||
|
entry_for_new_name = hash_search(CurrentDdlTable->db_table,
|
||||||
|
stmt->newname,
|
||||||
|
HASH_ENTER,
|
||||||
|
NULL);
|
||||||
entry_for_new_name->type = Op_Set;
|
entry_for_new_name->type = Op_Set;
|
||||||
|
|
||||||
if (found)
|
if (found)
|
||||||
{
|
{
|
||||||
if (entry->old_name[0] != '\0')
|
if (entry->old_name[0] != '\0')
|
||||||
@@ -600,8 +605,7 @@ HandleDbRename(RenameStmt *stmt)
|
|||||||
else
|
else
|
||||||
strlcpy(entry_for_new_name->old_name, entry->name, NAMEDATALEN);
|
strlcpy(entry_for_new_name->old_name, entry->name, NAMEDATALEN);
|
||||||
entry_for_new_name->owner = entry->owner;
|
entry_for_new_name->owner = entry->owner;
|
||||||
hash_search(
|
hash_search(CurrentDdlTable->db_table,
|
||||||
CurrentDdlTable->db_table,
|
|
||||||
stmt->subname,
|
stmt->subname,
|
||||||
HASH_REMOVE,
|
HASH_REMOVE,
|
||||||
NULL);
|
NULL);
|
||||||
@@ -616,14 +620,15 @@ HandleDbRename(RenameStmt *stmt)
|
|||||||
static void
|
static void
|
||||||
HandleDropDb(DropdbStmt *stmt)
|
HandleDropDb(DropdbStmt *stmt)
|
||||||
{
|
{
|
||||||
InitDbTableIfNeeded();
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
DbEntry *entry = hash_search(
|
DbEntry *entry;
|
||||||
CurrentDdlTable->db_table,
|
|
||||||
stmt->dbname,
|
|
||||||
HASH_ENTER,
|
|
||||||
&found);
|
|
||||||
|
|
||||||
|
InitDbTableIfNeeded();
|
||||||
|
|
||||||
|
entry = hash_search(CurrentDdlTable->db_table,
|
||||||
|
stmt->dbname,
|
||||||
|
HASH_ENTER,
|
||||||
|
&found);
|
||||||
entry->type = Op_Delete;
|
entry->type = Op_Delete;
|
||||||
entry->owner = InvalidOid;
|
entry->owner = InvalidOid;
|
||||||
if (!found)
|
if (!found)
|
||||||
@@ -633,16 +638,14 @@ HandleDropDb(DropdbStmt *stmt)
|
|||||||
static void
|
static void
|
||||||
HandleCreateRole(CreateRoleStmt *stmt)
|
HandleCreateRole(CreateRoleStmt *stmt)
|
||||||
{
|
{
|
||||||
InitRoleTableIfNeeded();
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
RoleEntry *entry = hash_search(
|
RoleEntry *entry;
|
||||||
CurrentDdlTable->role_table,
|
DefElem *dpass;
|
||||||
stmt->role,
|
|
||||||
HASH_ENTER,
|
|
||||||
&found);
|
|
||||||
DefElem *dpass = NULL;
|
|
||||||
ListCell *option;
|
ListCell *option;
|
||||||
|
|
||||||
|
InitRoleTableIfNeeded();
|
||||||
|
|
||||||
|
dpass = NULL;
|
||||||
foreach(option, stmt->options)
|
foreach(option, stmt->options)
|
||||||
{
|
{
|
||||||
DefElem *defel = lfirst(option);
|
DefElem *defel = lfirst(option);
|
||||||
@@ -650,6 +653,11 @@ HandleCreateRole(CreateRoleStmt *stmt)
|
|||||||
if (strcmp(defel->defname, "password") == 0)
|
if (strcmp(defel->defname, "password") == 0)
|
||||||
dpass = defel;
|
dpass = defel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entry = hash_search(CurrentDdlTable->role_table,
|
||||||
|
stmt->role,
|
||||||
|
HASH_ENTER,
|
||||||
|
&found);
|
||||||
if (!found)
|
if (!found)
|
||||||
memset(entry->old_name, 0, sizeof(entry->old_name));
|
memset(entry->old_name, 0, sizeof(entry->old_name));
|
||||||
if (dpass && dpass->arg)
|
if (dpass && dpass->arg)
|
||||||
@@ -662,14 +670,18 @@ HandleCreateRole(CreateRoleStmt *stmt)
|
|||||||
static void
|
static void
|
||||||
HandleAlterRole(AlterRoleStmt *stmt)
|
HandleAlterRole(AlterRoleStmt *stmt)
|
||||||
{
|
{
|
||||||
InitRoleTableIfNeeded();
|
|
||||||
DefElem *dpass = NULL;
|
|
||||||
ListCell *option;
|
|
||||||
const char *role_name = stmt->role->rolename;
|
const char *role_name = stmt->role->rolename;
|
||||||
|
DefElem *dpass;
|
||||||
|
ListCell *option;
|
||||||
|
bool found = false;
|
||||||
|
RoleEntry *entry;
|
||||||
|
|
||||||
|
InitRoleTableIfNeeded();
|
||||||
|
|
||||||
if (RoleIsNeonSuperuser(role_name) && !superuser())
|
if (RoleIsNeonSuperuser(role_name) && !superuser())
|
||||||
elog(ERROR, "can't ALTER neon_superuser");
|
elog(ERROR, "can't ALTER neon_superuser");
|
||||||
|
|
||||||
|
dpass = NULL;
|
||||||
foreach(option, stmt->options)
|
foreach(option, stmt->options)
|
||||||
{
|
{
|
||||||
DefElem *defel = lfirst(option);
|
DefElem *defel = lfirst(option);
|
||||||
@@ -680,13 +692,11 @@ HandleAlterRole(AlterRoleStmt *stmt)
|
|||||||
/* We only care about updates to the password */
|
/* We only care about updates to the password */
|
||||||
if (!dpass)
|
if (!dpass)
|
||||||
return;
|
return;
|
||||||
bool found = false;
|
|
||||||
RoleEntry *entry = hash_search(
|
|
||||||
CurrentDdlTable->role_table,
|
|
||||||
role_name,
|
|
||||||
HASH_ENTER,
|
|
||||||
&found);
|
|
||||||
|
|
||||||
|
entry = hash_search(CurrentDdlTable->role_table,
|
||||||
|
role_name,
|
||||||
|
HASH_ENTER,
|
||||||
|
&found);
|
||||||
if (!found)
|
if (!found)
|
||||||
memset(entry->old_name, 0, sizeof(entry->old_name));
|
memset(entry->old_name, 0, sizeof(entry->old_name));
|
||||||
if (dpass->arg)
|
if (dpass->arg)
|
||||||
@@ -699,20 +709,22 @@ HandleAlterRole(AlterRoleStmt *stmt)
|
|||||||
static void
|
static void
|
||||||
HandleRoleRename(RenameStmt *stmt)
|
HandleRoleRename(RenameStmt *stmt)
|
||||||
{
|
{
|
||||||
InitRoleTableIfNeeded();
|
|
||||||
Assert(stmt->renameType == OBJECT_ROLE);
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
RoleEntry *entry = hash_search(
|
RoleEntry *entry;
|
||||||
CurrentDdlTable->role_table,
|
RoleEntry *entry_for_new_name;
|
||||||
stmt->subname,
|
|
||||||
HASH_FIND,
|
|
||||||
&found);
|
|
||||||
|
|
||||||
RoleEntry *entry_for_new_name = hash_search(
|
Assert(stmt->renameType == OBJECT_ROLE);
|
||||||
CurrentDdlTable->role_table,
|
InitRoleTableIfNeeded();
|
||||||
stmt->newname,
|
|
||||||
HASH_ENTER,
|
entry = hash_search(CurrentDdlTable->role_table,
|
||||||
NULL);
|
stmt->subname,
|
||||||
|
HASH_FIND,
|
||||||
|
&found);
|
||||||
|
|
||||||
|
entry_for_new_name = hash_search(CurrentDdlTable->role_table,
|
||||||
|
stmt->newname,
|
||||||
|
HASH_ENTER,
|
||||||
|
NULL);
|
||||||
|
|
||||||
entry_for_new_name->type = Op_Set;
|
entry_for_new_name->type = Op_Set;
|
||||||
if (found)
|
if (found)
|
||||||
@@ -738,9 +750,10 @@ HandleRoleRename(RenameStmt *stmt)
|
|||||||
static void
|
static void
|
||||||
HandleDropRole(DropRoleStmt *stmt)
|
HandleDropRole(DropRoleStmt *stmt)
|
||||||
{
|
{
|
||||||
InitRoleTableIfNeeded();
|
|
||||||
ListCell *item;
|
ListCell *item;
|
||||||
|
|
||||||
|
InitRoleTableIfNeeded();
|
||||||
|
|
||||||
foreach(item, stmt->roles)
|
foreach(item, stmt->roles)
|
||||||
{
|
{
|
||||||
RoleSpec *spec = lfirst(item);
|
RoleSpec *spec = lfirst(item);
|
||||||
|
|||||||
@@ -170,12 +170,14 @@ lfc_disable(char const *op)
|
|||||||
|
|
||||||
if (lfc_desc > 0)
|
if (lfc_desc > 0)
|
||||||
{
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the reason of error is ENOSPC, then truncation of file may
|
* If the reason of error is ENOSPC, then truncation of file may
|
||||||
* help to reclaim some space
|
* help to reclaim some space
|
||||||
*/
|
*/
|
||||||
pgstat_report_wait_start(WAIT_EVENT_NEON_LFC_TRUNCATE);
|
pgstat_report_wait_start(WAIT_EVENT_NEON_LFC_TRUNCATE);
|
||||||
int rc = ftruncate(lfc_desc, 0);
|
rc = ftruncate(lfc_desc, 0);
|
||||||
pgstat_report_wait_end();
|
pgstat_report_wait_end();
|
||||||
|
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
@@ -616,7 +618,7 @@ lfc_evict(NRelFileInfo rinfo, ForkNumber forkNum, BlockNumber blkno)
|
|||||||
*/
|
*/
|
||||||
if (entry->bitmap[chunk_offs >> 5] == 0)
|
if (entry->bitmap[chunk_offs >> 5] == 0)
|
||||||
{
|
{
|
||||||
bool has_remaining_pages;
|
bool has_remaining_pages = false;
|
||||||
|
|
||||||
for (int i = 0; i < CHUNK_BITMAP_SIZE; i++)
|
for (int i = 0; i < CHUNK_BITMAP_SIZE; i++)
|
||||||
{
|
{
|
||||||
@@ -666,7 +668,6 @@ lfc_readv_select(NRelFileInfo rinfo, ForkNumber forkNum, BlockNumber blkno,
|
|||||||
BufferTag tag;
|
BufferTag tag;
|
||||||
FileCacheEntry *entry;
|
FileCacheEntry *entry;
|
||||||
ssize_t rc;
|
ssize_t rc;
|
||||||
bool result = true;
|
|
||||||
uint32 hash;
|
uint32 hash;
|
||||||
uint64 generation;
|
uint64 generation;
|
||||||
uint32 entry_offset;
|
uint32 entry_offset;
|
||||||
@@ -925,10 +926,10 @@ lfc_writev(NRelFileInfo rinfo, ForkNumber forkNum, BlockNumber blkno,
|
|||||||
/* We can reuse a hole that was left behind when the LFC was shrunk previously */
|
/* We can reuse a hole that was left behind when the LFC was shrunk previously */
|
||||||
FileCacheEntry *hole = dlist_container(FileCacheEntry, list_node, dlist_pop_head_node(&lfc_ctl->holes));
|
FileCacheEntry *hole = dlist_container(FileCacheEntry, list_node, dlist_pop_head_node(&lfc_ctl->holes));
|
||||||
uint32 offset = hole->offset;
|
uint32 offset = hole->offset;
|
||||||
bool found;
|
bool hole_found;
|
||||||
|
|
||||||
hash_search_with_hash_value(lfc_hash, &hole->key, hole->hash, HASH_REMOVE, &found);
|
hash_search_with_hash_value(lfc_hash, &hole->key, hole->hash, HASH_REMOVE, &hole_found);
|
||||||
CriticalAssert(found);
|
CriticalAssert(hole_found);
|
||||||
|
|
||||||
lfc_ctl->used += 1;
|
lfc_ctl->used += 1;
|
||||||
entry->offset = offset; /* reuse the hole */
|
entry->offset = offset; /* reuse the hole */
|
||||||
@@ -1004,7 +1005,7 @@ neon_get_lfc_stats(PG_FUNCTION_ARGS)
|
|||||||
Datum result;
|
Datum result;
|
||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
char const *key;
|
char const *key;
|
||||||
uint64 value;
|
uint64 value = 0;
|
||||||
Datum values[NUM_NEON_GET_STATS_COLS];
|
Datum values[NUM_NEON_GET_STATS_COLS];
|
||||||
bool nulls[NUM_NEON_GET_STATS_COLS];
|
bool nulls[NUM_NEON_GET_STATS_COLS];
|
||||||
|
|
||||||
|
|||||||
@@ -116,8 +116,6 @@ addSHLL(HyperLogLogState *cState, uint32 hash)
|
|||||||
{
|
{
|
||||||
uint8 count;
|
uint8 count;
|
||||||
uint32 index;
|
uint32 index;
|
||||||
size_t i;
|
|
||||||
size_t j;
|
|
||||||
|
|
||||||
TimestampTz now = GetCurrentTimestamp();
|
TimestampTz now = GetCurrentTimestamp();
|
||||||
/* Use the first "k" (registerWidth) bits as a zero based index */
|
/* Use the first "k" (registerWidth) bits as a zero based index */
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ typedef struct
|
|||||||
|
|
||||||
#if PG_VERSION_NUM >= 150000
|
#if PG_VERSION_NUM >= 150000
|
||||||
static shmem_request_hook_type prev_shmem_request_hook = NULL;
|
static shmem_request_hook_type prev_shmem_request_hook = NULL;
|
||||||
static void walproposer_shmem_request(void);
|
|
||||||
#endif
|
#endif
|
||||||
static shmem_startup_hook_type prev_shmem_startup_hook;
|
static shmem_startup_hook_type prev_shmem_startup_hook;
|
||||||
static PagestoreShmemState *pagestore_shared;
|
static PagestoreShmemState *pagestore_shared;
|
||||||
@@ -441,8 +440,8 @@ pageserver_connect(shardno_t shard_no, int elevel)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
shard->state = PS_Connecting_Startup;
|
shard->state = PS_Connecting_Startup;
|
||||||
/* fallthrough */
|
|
||||||
}
|
}
|
||||||
|
/* FALLTHROUGH */
|
||||||
case PS_Connecting_Startup:
|
case PS_Connecting_Startup:
|
||||||
{
|
{
|
||||||
char *pagestream_query;
|
char *pagestream_query;
|
||||||
@@ -453,8 +452,6 @@ pageserver_connect(shardno_t shard_no, int elevel)
|
|||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
WaitEvent event;
|
|
||||||
|
|
||||||
switch (poll_result)
|
switch (poll_result)
|
||||||
{
|
{
|
||||||
default: /* unknown/unused states are handled as a failed connection */
|
default: /* unknown/unused states are handled as a failed connection */
|
||||||
@@ -585,8 +582,8 @@ pageserver_connect(shardno_t shard_no, int elevel)
|
|||||||
}
|
}
|
||||||
|
|
||||||
shard->state = PS_Connecting_PageStream;
|
shard->state = PS_Connecting_PageStream;
|
||||||
/* fallthrough */
|
|
||||||
}
|
}
|
||||||
|
/* FALLTHROUGH */
|
||||||
case PS_Connecting_PageStream:
|
case PS_Connecting_PageStream:
|
||||||
{
|
{
|
||||||
neon_shard_log(shard_no, DEBUG5, "Connection state: Connecting_PageStream");
|
neon_shard_log(shard_no, DEBUG5, "Connection state: Connecting_PageStream");
|
||||||
@@ -631,8 +628,8 @@ pageserver_connect(shardno_t shard_no, int elevel)
|
|||||||
}
|
}
|
||||||
|
|
||||||
shard->state = PS_Connected;
|
shard->state = PS_Connected;
|
||||||
/* fallthrough */
|
|
||||||
}
|
}
|
||||||
|
/* FALLTHROUGH */
|
||||||
case PS_Connected:
|
case PS_Connected:
|
||||||
/*
|
/*
|
||||||
* We successfully connected. Future connections to this PageServer
|
* We successfully connected. Future connections to this PageServer
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ neon_perf_counters_to_metrics(neon_per_backend_counters *counters)
|
|||||||
metric_t *metrics = palloc((NUM_METRICS + 1) * sizeof(metric_t));
|
metric_t *metrics = palloc((NUM_METRICS + 1) * sizeof(metric_t));
|
||||||
uint64 bucket_accum;
|
uint64 bucket_accum;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
Datum getpage_wait_str;
|
|
||||||
|
|
||||||
metrics[i].name = "getpage_wait_seconds_count";
|
metrics[i].name = "getpage_wait_seconds_count";
|
||||||
metrics[i].is_bucket = false;
|
metrics[i].is_bucket = false;
|
||||||
@@ -224,7 +223,6 @@ neon_get_perf_counters(PG_FUNCTION_ARGS)
|
|||||||
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||||
Datum values[3];
|
Datum values[3];
|
||||||
bool nulls[3];
|
bool nulls[3];
|
||||||
Datum getpage_wait_str;
|
|
||||||
neon_per_backend_counters totals = {0};
|
neon_per_backend_counters totals = {0};
|
||||||
metric_t *metrics;
|
metric_t *metrics;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#define NEON_PGVERSIONCOMPAT_H
|
#define NEON_PGVERSIONCOMPAT_H
|
||||||
|
|
||||||
#include "fmgr.h"
|
#include "fmgr.h"
|
||||||
|
#include "storage/buf_internals.h"
|
||||||
|
|
||||||
#if PG_MAJORVERSION_NUM < 17
|
#if PG_MAJORVERSION_NUM < 17
|
||||||
#define NRelFileInfoBackendIsTemp(rinfo) (rinfo.backend != InvalidBackendId)
|
#define NRelFileInfoBackendIsTemp(rinfo) (rinfo.backend != InvalidBackendId)
|
||||||
@@ -20,11 +21,24 @@
|
|||||||
NInfoGetRelNumber(a) == NInfoGetRelNumber(b) \
|
NInfoGetRelNumber(a) == NInfoGetRelNumber(b) \
|
||||||
)
|
)
|
||||||
|
|
||||||
/* buftag population & RelFileNode/RelFileLocator rework */
|
/* These macros were turned into static inline functions in v16 */
|
||||||
#if PG_MAJORVERSION_NUM < 16
|
#if PG_MAJORVERSION_NUM < 16
|
||||||
|
static inline bool
|
||||||
|
BufferTagsEqual(const BufferTag *tag1, const BufferTag *tag2)
|
||||||
|
{
|
||||||
|
return BUFFERTAGS_EQUAL(*tag1, *tag2);
|
||||||
|
}
|
||||||
|
|
||||||
#define InitBufferTag(tag, rfn, fn, bn) INIT_BUFFERTAG(*tag, *rfn, fn, bn)
|
static inline void
|
||||||
|
InitBufferTag(BufferTag *tag, const RelFileNode *rnode,
|
||||||
|
ForkNumber forkNum, BlockNumber blockNum)
|
||||||
|
{
|
||||||
|
INIT_BUFFERTAG(*tag, *rnode, forkNum, blockNum);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* RelFileNode -> RelFileLocator rework */
|
||||||
|
#if PG_MAJORVERSION_NUM < 16
|
||||||
#define USE_RELFILENODE
|
#define USE_RELFILENODE
|
||||||
|
|
||||||
#define RELFILEINFO_HDR "storage/relfilenode.h"
|
#define RELFILEINFO_HDR "storage/relfilenode.h"
|
||||||
@@ -73,8 +87,6 @@
|
|||||||
|
|
||||||
#define USE_RELFILELOCATOR
|
#define USE_RELFILELOCATOR
|
||||||
|
|
||||||
#define BUFFERTAGS_EQUAL(a, b) BufferTagsEqual(&(a), &(b))
|
|
||||||
|
|
||||||
#define RELFILEINFO_HDR "storage/relfilelocator.h"
|
#define RELFILEINFO_HDR "storage/relfilelocator.h"
|
||||||
|
|
||||||
#define NRelFileInfo RelFileLocator
|
#define NRelFileInfo RelFileLocator
|
||||||
|
|||||||
@@ -213,32 +213,6 @@ extern const f_smgr *smgr_neon(ProcNumber backend, NRelFileInfo rinfo);
|
|||||||
extern void smgr_init_neon(void);
|
extern void smgr_init_neon(void);
|
||||||
extern void readahead_buffer_resize(int newsize, void *extra);
|
extern void readahead_buffer_resize(int newsize, void *extra);
|
||||||
|
|
||||||
/* Neon storage manager functionality */
|
|
||||||
|
|
||||||
extern void neon_init(void);
|
|
||||||
extern void neon_open(SMgrRelation reln);
|
|
||||||
extern void neon_close(SMgrRelation reln, ForkNumber forknum);
|
|
||||||
extern void neon_create(SMgrRelation reln, ForkNumber forknum, bool isRedo);
|
|
||||||
extern bool neon_exists(SMgrRelation reln, ForkNumber forknum);
|
|
||||||
extern void neon_unlink(NRelFileInfoBackend rnode, ForkNumber forknum, bool isRedo);
|
|
||||||
#if PG_MAJORVERSION_NUM < 16
|
|
||||||
extern void neon_extend(SMgrRelation reln, ForkNumber forknum,
|
|
||||||
BlockNumber blocknum, char *buffer, bool skipFsync);
|
|
||||||
#else
|
|
||||||
extern void neon_extend(SMgrRelation reln, ForkNumber forknum,
|
|
||||||
BlockNumber blocknum, const void *buffer, bool skipFsync);
|
|
||||||
extern void neon_zeroextend(SMgrRelation reln, ForkNumber forknum,
|
|
||||||
BlockNumber blocknum, int nbuffers, bool skipFsync);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if PG_MAJORVERSION_NUM >=17
|
|
||||||
extern bool neon_prefetch(SMgrRelation reln, ForkNumber forknum,
|
|
||||||
BlockNumber blocknum, int nblocks);
|
|
||||||
#else
|
|
||||||
extern bool neon_prefetch(SMgrRelation reln, ForkNumber forknum,
|
|
||||||
BlockNumber blocknum);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* LSN values associated with each request to the pageserver
|
* LSN values associated with each request to the pageserver
|
||||||
*/
|
*/
|
||||||
@@ -278,13 +252,7 @@ extern PGDLLEXPORT void neon_read_at_lsn(NRelFileInfo rnode, ForkNumber forkNum,
|
|||||||
extern PGDLLEXPORT void neon_read_at_lsn(NRelFileInfo rnode, ForkNumber forkNum, BlockNumber blkno,
|
extern PGDLLEXPORT void neon_read_at_lsn(NRelFileInfo rnode, ForkNumber forkNum, BlockNumber blkno,
|
||||||
neon_request_lsns request_lsns, void *buffer);
|
neon_request_lsns request_lsns, void *buffer);
|
||||||
#endif
|
#endif
|
||||||
extern void neon_writeback(SMgrRelation reln, ForkNumber forknum,
|
|
||||||
BlockNumber blocknum, BlockNumber nblocks);
|
|
||||||
extern BlockNumber neon_nblocks(SMgrRelation reln, ForkNumber forknum);
|
|
||||||
extern int64 neon_dbsize(Oid dbNode);
|
extern int64 neon_dbsize(Oid dbNode);
|
||||||
extern void neon_truncate(SMgrRelation reln, ForkNumber forknum,
|
|
||||||
BlockNumber nblocks);
|
|
||||||
extern void neon_immedsync(SMgrRelation reln, ForkNumber forknum);
|
|
||||||
|
|
||||||
/* utils for neon relsize cache */
|
/* utils for neon relsize cache */
|
||||||
extern void relsize_hash_init(void);
|
extern void relsize_hash_init(void);
|
||||||
|
|||||||
+69
-54
@@ -118,6 +118,8 @@ static UnloggedBuildPhase unlogged_build_phase = UNLOGGED_BUILD_NOT_IN_PROGRESS;
|
|||||||
static bool neon_redo_read_buffer_filter(XLogReaderState *record, uint8 block_id);
|
static bool neon_redo_read_buffer_filter(XLogReaderState *record, uint8 block_id);
|
||||||
static bool (*old_redo_read_buffer_filter) (XLogReaderState *record, uint8 block_id) = NULL;
|
static bool (*old_redo_read_buffer_filter) (XLogReaderState *record, uint8 block_id) = NULL;
|
||||||
|
|
||||||
|
static BlockNumber neon_nblocks(SMgrRelation reln, ForkNumber forknum);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prefetch implementation:
|
* Prefetch implementation:
|
||||||
*
|
*
|
||||||
@@ -215,7 +217,7 @@ typedef struct PrfHashEntry
|
|||||||
sizeof(BufferTag) \
|
sizeof(BufferTag) \
|
||||||
)
|
)
|
||||||
|
|
||||||
#define SH_EQUAL(tb, a, b) (BUFFERTAGS_EQUAL((a)->buftag, (b)->buftag))
|
#define SH_EQUAL(tb, a, b) (BufferTagsEqual(&(a)->buftag, &(b)->buftag))
|
||||||
#define SH_SCOPE static inline
|
#define SH_SCOPE static inline
|
||||||
#define SH_DEFINE
|
#define SH_DEFINE
|
||||||
#define SH_DECLARE
|
#define SH_DECLARE
|
||||||
@@ -736,7 +738,7 @@ static void
|
|||||||
prefetch_do_request(PrefetchRequest *slot, neon_request_lsns *force_request_lsns)
|
prefetch_do_request(PrefetchRequest *slot, neon_request_lsns *force_request_lsns)
|
||||||
{
|
{
|
||||||
bool found;
|
bool found;
|
||||||
uint64 mySlotNo = slot->my_ring_index;
|
uint64 mySlotNo PG_USED_FOR_ASSERTS_ONLY = slot->my_ring_index;
|
||||||
|
|
||||||
NeonGetPageRequest request = {
|
NeonGetPageRequest request = {
|
||||||
.req.tag = T_NeonGetPageRequest,
|
.req.tag = T_NeonGetPageRequest,
|
||||||
@@ -803,15 +805,19 @@ prefetch_register_bufferv(BufferTag tag, neon_request_lsns *frlsns,
|
|||||||
bool is_prefetch)
|
bool is_prefetch)
|
||||||
{
|
{
|
||||||
uint64 min_ring_index;
|
uint64 min_ring_index;
|
||||||
PrefetchRequest req;
|
PrefetchRequest hashkey;
|
||||||
#if USE_ASSERT_CHECKING
|
#if USE_ASSERT_CHECKING
|
||||||
bool any_hits = false;
|
bool any_hits = false;
|
||||||
#endif
|
#endif
|
||||||
/* We will never read further ahead than our buffer can store. */
|
/* We will never read further ahead than our buffer can store. */
|
||||||
nblocks = Max(1, Min(nblocks, readahead_buffer_size));
|
nblocks = Max(1, Min(nblocks, readahead_buffer_size));
|
||||||
|
|
||||||
/* use an intermediate PrefetchRequest struct to ensure correct alignment */
|
/*
|
||||||
req.buftag = tag;
|
* Use an intermediate PrefetchRequest struct as the hash key to ensure
|
||||||
|
* correct alignment and that the padding bytes are cleared.
|
||||||
|
*/
|
||||||
|
memset(&hashkey.buftag, 0, sizeof(BufferTag));
|
||||||
|
hashkey.buftag = tag;
|
||||||
|
|
||||||
Retry:
|
Retry:
|
||||||
min_ring_index = UINT64_MAX;
|
min_ring_index = UINT64_MAX;
|
||||||
@@ -837,8 +843,8 @@ Retry:
|
|||||||
slot = NULL;
|
slot = NULL;
|
||||||
entry = NULL;
|
entry = NULL;
|
||||||
|
|
||||||
req.buftag.blockNum = tag.blockNum + i;
|
hashkey.buftag.blockNum = tag.blockNum + i;
|
||||||
entry = prfh_lookup(MyPState->prf_hash, (PrefetchRequest *) &req);
|
entry = prfh_lookup(MyPState->prf_hash, &hashkey);
|
||||||
|
|
||||||
if (entry != NULL)
|
if (entry != NULL)
|
||||||
{
|
{
|
||||||
@@ -849,7 +855,7 @@ Retry:
|
|||||||
Assert(slot->status != PRFS_UNUSED);
|
Assert(slot->status != PRFS_UNUSED);
|
||||||
Assert(MyPState->ring_last <= ring_index &&
|
Assert(MyPState->ring_last <= ring_index &&
|
||||||
ring_index < MyPState->ring_unused);
|
ring_index < MyPState->ring_unused);
|
||||||
Assert(BUFFERTAGS_EQUAL(slot->buftag, req.buftag));
|
Assert(BufferTagsEqual(&slot->buftag, &hashkey.buftag));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the caller specified a request LSN to use, only accept
|
* If the caller specified a request LSN to use, only accept
|
||||||
@@ -886,12 +892,19 @@ Retry:
|
|||||||
{
|
{
|
||||||
min_ring_index = Min(min_ring_index, ring_index);
|
min_ring_index = Min(min_ring_index, ring_index);
|
||||||
/* The buffered request is good enough, return that index */
|
/* The buffered request is good enough, return that index */
|
||||||
pgBufferUsage.prefetch.duplicates++;
|
if (is_prefetch)
|
||||||
|
pgBufferUsage.prefetch.duplicates++;
|
||||||
|
else
|
||||||
|
pgBufferUsage.prefetch.hits++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (!is_prefetch)
|
||||||
|
{
|
||||||
|
pgBufferUsage.prefetch.misses += 1;
|
||||||
|
MyNeonCounters->getpage_prefetch_misses_total++;
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* We can only leave the block above by finding that there's
|
* We can only leave the block above by finding that there's
|
||||||
* no entry that can satisfy this request, either because there
|
* no entry that can satisfy this request, either because there
|
||||||
@@ -974,7 +987,7 @@ Retry:
|
|||||||
* We must update the slot data before insertion, because the hash
|
* We must update the slot data before insertion, because the hash
|
||||||
* function reads the buffer tag from the slot.
|
* function reads the buffer tag from the slot.
|
||||||
*/
|
*/
|
||||||
slot->buftag = req.buftag;
|
slot->buftag = hashkey.buftag;
|
||||||
slot->shard_no = get_shard_number(&tag);
|
slot->shard_no = get_shard_number(&tag);
|
||||||
slot->my_ring_index = ring_index;
|
slot->my_ring_index = ring_index;
|
||||||
|
|
||||||
@@ -1452,7 +1465,6 @@ log_newpages_copy(NRelFileInfo * rinfo, ForkNumber forkNum, BlockNumber blkno,
|
|||||||
BlockNumber blknos[XLR_MAX_BLOCK_ID];
|
BlockNumber blknos[XLR_MAX_BLOCK_ID];
|
||||||
Page pageptrs[XLR_MAX_BLOCK_ID];
|
Page pageptrs[XLR_MAX_BLOCK_ID];
|
||||||
int nregistered = 0;
|
int nregistered = 0;
|
||||||
XLogRecPtr result = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < nblocks; i++)
|
for (int i = 0; i < nblocks; i++)
|
||||||
{
|
{
|
||||||
@@ -1765,7 +1777,7 @@ neon_wallog_page(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, co
|
|||||||
/*
|
/*
|
||||||
* neon_init() -- Initialize private state
|
* neon_init() -- Initialize private state
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
neon_init(void)
|
neon_init(void)
|
||||||
{
|
{
|
||||||
Size prfs_size;
|
Size prfs_size;
|
||||||
@@ -2155,7 +2167,7 @@ neon_prefetch_response_usable(neon_request_lsns *request_lsns,
|
|||||||
/*
|
/*
|
||||||
* neon_exists() -- Does the physical file exist?
|
* neon_exists() -- Does the physical file exist?
|
||||||
*/
|
*/
|
||||||
bool
|
static bool
|
||||||
neon_exists(SMgrRelation reln, ForkNumber forkNum)
|
neon_exists(SMgrRelation reln, ForkNumber forkNum)
|
||||||
{
|
{
|
||||||
bool exists;
|
bool exists;
|
||||||
@@ -2261,7 +2273,7 @@ neon_exists(SMgrRelation reln, ForkNumber forkNum)
|
|||||||
*
|
*
|
||||||
* If isRedo is true, it's okay for the relation to exist already.
|
* If isRedo is true, it's okay for the relation to exist already.
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
neon_create(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
|
neon_create(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
|
||||||
{
|
{
|
||||||
switch (reln->smgr_relpersistence)
|
switch (reln->smgr_relpersistence)
|
||||||
@@ -2337,7 +2349,7 @@ neon_create(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
|
|||||||
* Note: any failure should be reported as WARNING not ERROR, because
|
* Note: any failure should be reported as WARNING not ERROR, because
|
||||||
* we are usually not in a transaction anymore when this is called.
|
* we are usually not in a transaction anymore when this is called.
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
neon_unlink(NRelFileInfoBackend rinfo, ForkNumber forkNum, bool isRedo)
|
neon_unlink(NRelFileInfoBackend rinfo, ForkNumber forkNum, bool isRedo)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -2361,7 +2373,7 @@ neon_unlink(NRelFileInfoBackend rinfo, ForkNumber forkNum, bool isRedo)
|
|||||||
* EOF). Note that we assume writing a block beyond current EOF
|
* EOF). Note that we assume writing a block beyond current EOF
|
||||||
* causes intervening file space to become filled with zeroes.
|
* causes intervening file space to become filled with zeroes.
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
#if PG_MAJORVERSION_NUM < 16
|
#if PG_MAJORVERSION_NUM < 16
|
||||||
neon_extend(SMgrRelation reln, ForkNumber forkNum, BlockNumber blkno,
|
neon_extend(SMgrRelation reln, ForkNumber forkNum, BlockNumber blkno,
|
||||||
char *buffer, bool skipFsync)
|
char *buffer, bool skipFsync)
|
||||||
@@ -2453,7 +2465,7 @@ neon_extend(SMgrRelation reln, ForkNumber forkNum, BlockNumber blkno,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if PG_MAJORVERSION_NUM >= 16
|
#if PG_MAJORVERSION_NUM >= 16
|
||||||
void
|
static void
|
||||||
neon_zeroextend(SMgrRelation reln, ForkNumber forkNum, BlockNumber blocknum,
|
neon_zeroextend(SMgrRelation reln, ForkNumber forkNum, BlockNumber blocknum,
|
||||||
int nblocks, bool skipFsync)
|
int nblocks, bool skipFsync)
|
||||||
{
|
{
|
||||||
@@ -2549,7 +2561,7 @@ neon_zeroextend(SMgrRelation reln, ForkNumber forkNum, BlockNumber blocknum,
|
|||||||
/*
|
/*
|
||||||
* neon_open() -- Initialize newly-opened relation.
|
* neon_open() -- Initialize newly-opened relation.
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
neon_open(SMgrRelation reln)
|
neon_open(SMgrRelation reln)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -2567,7 +2579,7 @@ neon_open(SMgrRelation reln)
|
|||||||
/*
|
/*
|
||||||
* neon_close() -- Close the specified relation, if it isn't closed already.
|
* neon_close() -- Close the specified relation, if it isn't closed already.
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
neon_close(SMgrRelation reln, ForkNumber forknum)
|
neon_close(SMgrRelation reln, ForkNumber forknum)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -2582,13 +2594,12 @@ neon_close(SMgrRelation reln, ForkNumber forknum)
|
|||||||
/*
|
/*
|
||||||
* neon_prefetch() -- Initiate asynchronous read of the specified block of a relation
|
* neon_prefetch() -- Initiate asynchronous read of the specified block of a relation
|
||||||
*/
|
*/
|
||||||
bool
|
static bool
|
||||||
neon_prefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
|
neon_prefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
|
||||||
int nblocks)
|
int nblocks)
|
||||||
{
|
{
|
||||||
uint64 ring_index PG_USED_FOR_ASSERTS_ONLY;
|
uint64 ring_index PG_USED_FOR_ASSERTS_ONLY;
|
||||||
BufferTag tag;
|
BufferTag tag;
|
||||||
bool io_initiated = false;
|
|
||||||
|
|
||||||
switch (reln->smgr_relpersistence)
|
switch (reln->smgr_relpersistence)
|
||||||
{
|
{
|
||||||
@@ -2612,7 +2623,6 @@ neon_prefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
|
|||||||
while (nblocks > 0)
|
while (nblocks > 0)
|
||||||
{
|
{
|
||||||
int iterblocks = Min(nblocks, PG_IOV_MAX);
|
int iterblocks = Min(nblocks, PG_IOV_MAX);
|
||||||
int seqlen = 0;
|
|
||||||
bits8 lfc_present[PG_IOV_MAX / 8];
|
bits8 lfc_present[PG_IOV_MAX / 8];
|
||||||
memset(lfc_present, 0, sizeof(lfc_present));
|
memset(lfc_present, 0, sizeof(lfc_present));
|
||||||
|
|
||||||
@@ -2624,8 +2634,6 @@ neon_prefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
io_initiated = true;
|
|
||||||
|
|
||||||
tag.blockNum = blocknum;
|
tag.blockNum = blocknum;
|
||||||
|
|
||||||
for (int i = 0; i < PG_IOV_MAX / 8; i++)
|
for (int i = 0; i < PG_IOV_MAX / 8; i++)
|
||||||
@@ -2648,7 +2656,7 @@ neon_prefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
|
|||||||
/*
|
/*
|
||||||
* neon_prefetch() -- Initiate asynchronous read of the specified block of a relation
|
* neon_prefetch() -- Initiate asynchronous read of the specified block of a relation
|
||||||
*/
|
*/
|
||||||
bool
|
static bool
|
||||||
neon_prefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum)
|
neon_prefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum)
|
||||||
{
|
{
|
||||||
uint64 ring_index PG_USED_FOR_ASSERTS_ONLY;
|
uint64 ring_index PG_USED_FOR_ASSERTS_ONLY;
|
||||||
@@ -2692,7 +2700,7 @@ neon_prefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum)
|
|||||||
* This accepts a range of blocks because flushing several pages at once is
|
* This accepts a range of blocks because flushing several pages at once is
|
||||||
* considerably more efficient than doing so individually.
|
* considerably more efficient than doing so individually.
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
neon_writeback(SMgrRelation reln, ForkNumber forknum,
|
neon_writeback(SMgrRelation reln, ForkNumber forknum,
|
||||||
BlockNumber blocknum, BlockNumber nblocks)
|
BlockNumber blocknum, BlockNumber nblocks)
|
||||||
{
|
{
|
||||||
@@ -2742,14 +2750,19 @@ neon_read_at_lsnv(NRelFileInfo rinfo, ForkNumber forkNum, BlockNumber base_block
|
|||||||
uint64 ring_index;
|
uint64 ring_index;
|
||||||
PrfHashEntry *entry;
|
PrfHashEntry *entry;
|
||||||
PrefetchRequest *slot;
|
PrefetchRequest *slot;
|
||||||
BufferTag buftag = {0};
|
PrefetchRequest hashkey;
|
||||||
|
|
||||||
Assert(PointerIsValid(request_lsns));
|
Assert(PointerIsValid(request_lsns));
|
||||||
Assert(nblocks >= 1);
|
Assert(nblocks >= 1);
|
||||||
|
|
||||||
CopyNRelFileInfoToBufTag(buftag, rinfo);
|
/*
|
||||||
buftag.forkNum = forkNum;
|
* Use an intermediate PrefetchRequest struct as the hash key to ensure
|
||||||
buftag.blockNum = base_blockno;
|
* correct alignment and that the padding bytes are cleared.
|
||||||
|
*/
|
||||||
|
memset(&hashkey.buftag, 0, sizeof(BufferTag));
|
||||||
|
CopyNRelFileInfoToBufTag(hashkey.buftag, rinfo);
|
||||||
|
hashkey.buftag.forkNum = forkNum;
|
||||||
|
hashkey.buftag.blockNum = base_blockno;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The redo process does not lock pages that it needs to replay but are
|
* The redo process does not lock pages that it needs to replay but are
|
||||||
@@ -2767,7 +2780,7 @@ neon_read_at_lsnv(NRelFileInfo rinfo, ForkNumber forkNum, BlockNumber base_block
|
|||||||
* weren't for the behaviour of the LwLsn cache that uses the highest
|
* weren't for the behaviour of the LwLsn cache that uses the highest
|
||||||
* value of the LwLsn cache when the entry is not found.
|
* value of the LwLsn cache when the entry is not found.
|
||||||
*/
|
*/
|
||||||
prefetch_register_bufferv(buftag, request_lsns, nblocks, mask, false);
|
prefetch_register_bufferv(hashkey.buftag, request_lsns, nblocks, mask, false);
|
||||||
|
|
||||||
for (int i = 0; i < nblocks; i++)
|
for (int i = 0; i < nblocks; i++)
|
||||||
{
|
{
|
||||||
@@ -2788,8 +2801,8 @@ neon_read_at_lsnv(NRelFileInfo rinfo, ForkNumber forkNum, BlockNumber base_block
|
|||||||
* Try to find prefetched page in the list of received pages.
|
* Try to find prefetched page in the list of received pages.
|
||||||
*/
|
*/
|
||||||
Retry:
|
Retry:
|
||||||
buftag.blockNum = blockno;
|
hashkey.buftag.blockNum = blockno;
|
||||||
entry = prfh_lookup(MyPState->prf_hash, (PrefetchRequest *) &buftag);
|
entry = prfh_lookup(MyPState->prf_hash, &hashkey);
|
||||||
|
|
||||||
if (entry != NULL)
|
if (entry != NULL)
|
||||||
{
|
{
|
||||||
@@ -2797,7 +2810,6 @@ Retry:
|
|||||||
if (neon_prefetch_response_usable(reqlsns, slot))
|
if (neon_prefetch_response_usable(reqlsns, slot))
|
||||||
{
|
{
|
||||||
ring_index = slot->my_ring_index;
|
ring_index = slot->my_ring_index;
|
||||||
pgBufferUsage.prefetch.hits += 1;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2827,10 +2839,7 @@ Retry:
|
|||||||
{
|
{
|
||||||
if (entry == NULL)
|
if (entry == NULL)
|
||||||
{
|
{
|
||||||
pgBufferUsage.prefetch.misses += 1;
|
ring_index = prefetch_register_bufferv(hashkey.buftag, reqlsns, 1, NULL, false);
|
||||||
MyNeonCounters->getpage_prefetch_misses_total++;
|
|
||||||
|
|
||||||
ring_index = prefetch_register_bufferv(buftag, reqlsns, 1, NULL, false);
|
|
||||||
Assert(ring_index != UINT64_MAX);
|
Assert(ring_index != UINT64_MAX);
|
||||||
slot = GetPrfSlot(ring_index);
|
slot = GetPrfSlot(ring_index);
|
||||||
}
|
}
|
||||||
@@ -2855,8 +2864,8 @@ Retry:
|
|||||||
} while (!prefetch_wait_for(ring_index));
|
} while (!prefetch_wait_for(ring_index));
|
||||||
|
|
||||||
Assert(slot->status == PRFS_RECEIVED);
|
Assert(slot->status == PRFS_RECEIVED);
|
||||||
Assert(memcmp(&buftag, &slot->buftag, sizeof(BufferTag)) == 0);
|
Assert(memcmp(&hashkey.buftag, &slot->buftag, sizeof(BufferTag)) == 0);
|
||||||
Assert(buftag.blockNum == base_blockno + i);
|
Assert(hashkey.buftag.blockNum == base_blockno + i);
|
||||||
|
|
||||||
resp = slot->response;
|
resp = slot->response;
|
||||||
|
|
||||||
@@ -2912,10 +2921,10 @@ neon_read_at_lsn(NRelFileInfo rinfo, ForkNumber forkNum, BlockNumber blkno,
|
|||||||
* neon_read() -- Read the specified block from a relation.
|
* neon_read() -- Read the specified block from a relation.
|
||||||
*/
|
*/
|
||||||
#if PG_MAJORVERSION_NUM < 16
|
#if PG_MAJORVERSION_NUM < 16
|
||||||
void
|
static void
|
||||||
neon_read(SMgrRelation reln, ForkNumber forkNum, BlockNumber blkno, char *buffer)
|
neon_read(SMgrRelation reln, ForkNumber forkNum, BlockNumber blkno, char *buffer)
|
||||||
#else
|
#else
|
||||||
void
|
static void
|
||||||
neon_read(SMgrRelation reln, ForkNumber forkNum, BlockNumber blkno, void *buffer)
|
neon_read(SMgrRelation reln, ForkNumber forkNum, BlockNumber blkno, void *buffer)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
@@ -3024,7 +3033,7 @@ neon_read(SMgrRelation reln, ForkNumber forkNum, BlockNumber blkno, void *buffer
|
|||||||
#endif /* PG_MAJORVERSION_NUM <= 16 */
|
#endif /* PG_MAJORVERSION_NUM <= 16 */
|
||||||
|
|
||||||
#if PG_MAJORVERSION_NUM >= 17
|
#if PG_MAJORVERSION_NUM >= 17
|
||||||
void
|
static void
|
||||||
neon_readv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
|
neon_readv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
|
||||||
void **buffers, BlockNumber nblocks)
|
void **buffers, BlockNumber nblocks)
|
||||||
{
|
{
|
||||||
@@ -3059,6 +3068,9 @@ neon_readv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
|
|||||||
lfc_result = lfc_readv_select(InfoFromSMgrRel(reln), forknum, blocknum, buffers,
|
lfc_result = lfc_readv_select(InfoFromSMgrRel(reln), forknum, blocknum, buffers,
|
||||||
nblocks, read);
|
nblocks, read);
|
||||||
|
|
||||||
|
if (lfc_result > 0)
|
||||||
|
MyNeonCounters->file_cache_hits_total += lfc_result;
|
||||||
|
|
||||||
/* Read all blocks from LFC, so we're done */
|
/* Read all blocks from LFC, so we're done */
|
||||||
if (lfc_result == nblocks)
|
if (lfc_result == nblocks)
|
||||||
return;
|
return;
|
||||||
@@ -3185,6 +3197,7 @@ hexdump_page(char *page)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if PG_MAJORVERSION_NUM < 17
|
||||||
/*
|
/*
|
||||||
* neon_write() -- Write the supplied block at the appropriate location.
|
* neon_write() -- Write the supplied block at the appropriate location.
|
||||||
*
|
*
|
||||||
@@ -3192,7 +3205,7 @@ hexdump_page(char *page)
|
|||||||
* relation (ie, those before the current EOF). To extend a relation,
|
* relation (ie, those before the current EOF). To extend a relation,
|
||||||
* use mdextend().
|
* use mdextend().
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
#if PG_MAJORVERSION_NUM < 16
|
#if PG_MAJORVERSION_NUM < 16
|
||||||
neon_write(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync)
|
neon_write(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync)
|
||||||
#else
|
#else
|
||||||
@@ -3258,11 +3271,12 @@ neon_write(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const vo
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if PG_MAJORVERSION_NUM >= 17
|
#if PG_MAJORVERSION_NUM >= 17
|
||||||
void
|
static void
|
||||||
neon_writev(SMgrRelation reln, ForkNumber forknum, BlockNumber blkno,
|
neon_writev(SMgrRelation reln, ForkNumber forknum, BlockNumber blkno,
|
||||||
const void **buffers, BlockNumber nblocks, bool skipFsync)
|
const void **buffers, BlockNumber nblocks, bool skipFsync)
|
||||||
{
|
{
|
||||||
@@ -3312,7 +3326,7 @@ neon_writev(SMgrRelation reln, ForkNumber forknum, BlockNumber blkno,
|
|||||||
/*
|
/*
|
||||||
* neon_nblocks() -- Get the number of blocks stored in a relation.
|
* neon_nblocks() -- Get the number of blocks stored in a relation.
|
||||||
*/
|
*/
|
||||||
BlockNumber
|
static BlockNumber
|
||||||
neon_nblocks(SMgrRelation reln, ForkNumber forknum)
|
neon_nblocks(SMgrRelation reln, ForkNumber forknum)
|
||||||
{
|
{
|
||||||
NeonResponse *resp;
|
NeonResponse *resp;
|
||||||
@@ -3449,7 +3463,7 @@ neon_dbsize(Oid dbNode)
|
|||||||
/*
|
/*
|
||||||
* neon_truncate() -- Truncate relation to specified number of blocks.
|
* neon_truncate() -- Truncate relation to specified number of blocks.
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
neon_truncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks)
|
neon_truncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks)
|
||||||
{
|
{
|
||||||
XLogRecPtr lsn;
|
XLogRecPtr lsn;
|
||||||
@@ -3518,7 +3532,7 @@ neon_truncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks)
|
|||||||
* crash before the next checkpoint syncs the newly-inactive segment, that
|
* crash before the next checkpoint syncs the newly-inactive segment, that
|
||||||
* segment may survive recovery, reintroducing unwanted data into the table.
|
* segment may survive recovery, reintroducing unwanted data into the table.
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
neon_immedsync(SMgrRelation reln, ForkNumber forknum)
|
neon_immedsync(SMgrRelation reln, ForkNumber forknum)
|
||||||
{
|
{
|
||||||
switch (reln->smgr_relpersistence)
|
switch (reln->smgr_relpersistence)
|
||||||
@@ -3548,8 +3562,8 @@ neon_immedsync(SMgrRelation reln, ForkNumber forknum)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if PG_MAJORVERSION_NUM >= 17
|
#if PG_MAJORVERSION_NUM >= 17
|
||||||
void
|
static void
|
||||||
neon_regisersync(SMgrRelation reln, ForkNumber forknum)
|
neon_registersync(SMgrRelation reln, ForkNumber forknum)
|
||||||
{
|
{
|
||||||
switch (reln->smgr_relpersistence)
|
switch (reln->smgr_relpersistence)
|
||||||
{
|
{
|
||||||
@@ -3733,6 +3747,8 @@ neon_read_slru_segment(SMgrRelation reln, const char* path, int segno, void* buf
|
|||||||
SlruKind kind;
|
SlruKind kind;
|
||||||
int n_blocks;
|
int n_blocks;
|
||||||
shardno_t shard_no = 0; /* All SLRUs are at shard 0 */
|
shardno_t shard_no = 0; /* All SLRUs are at shard 0 */
|
||||||
|
NeonResponse *resp;
|
||||||
|
NeonGetSlruSegmentRequest request;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Compute a request LSN to use, similar to neon_get_request_lsns() but the
|
* Compute a request LSN to use, similar to neon_get_request_lsns() but the
|
||||||
@@ -3771,8 +3787,7 @@ neon_read_slru_segment(SMgrRelation reln, const char* path, int segno, void* buf
|
|||||||
else
|
else
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
NeonResponse *resp;
|
request = (NeonGetSlruSegmentRequest) {
|
||||||
NeonGetSlruSegmentRequest request = {
|
|
||||||
.req.tag = T_NeonGetSlruSegmentRequest,
|
.req.tag = T_NeonGetSlruSegmentRequest,
|
||||||
.req.lsn = request_lsn,
|
.req.lsn = request_lsn,
|
||||||
.req.not_modified_since = not_modified_since,
|
.req.not_modified_since = not_modified_since,
|
||||||
@@ -3879,7 +3894,7 @@ static const struct f_smgr neon_smgr =
|
|||||||
.smgr_truncate = neon_truncate,
|
.smgr_truncate = neon_truncate,
|
||||||
.smgr_immedsync = neon_immedsync,
|
.smgr_immedsync = neon_immedsync,
|
||||||
#if PG_MAJORVERSION_NUM >= 17
|
#if PG_MAJORVERSION_NUM >= 17
|
||||||
.smgr_registersync = neon_regisersync,
|
.smgr_registersync = neon_registersync,
|
||||||
#endif
|
#endif
|
||||||
.smgr_start_unlogged_build = neon_start_unlogged_build,
|
.smgr_start_unlogged_build = neon_start_unlogged_build,
|
||||||
.smgr_finish_unlogged_build_phase_1 = neon_finish_unlogged_build_phase_1,
|
.smgr_finish_unlogged_build_phase_1 = neon_finish_unlogged_build_phase_1,
|
||||||
|
|||||||
+13
-16
@@ -252,8 +252,6 @@ WalProposerPoll(WalProposer *wp)
|
|||||||
/* timeout expired: poll state */
|
/* timeout expired: poll state */
|
||||||
if (rc == 0 || TimeToReconnect(wp, now) <= 0)
|
if (rc == 0 || TimeToReconnect(wp, now) <= 0)
|
||||||
{
|
{
|
||||||
TimestampTz now;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If no WAL was generated during timeout (and we have already
|
* If no WAL was generated during timeout (and we have already
|
||||||
* collected the quorum), then send empty keepalive message
|
* collected the quorum), then send empty keepalive message
|
||||||
@@ -269,8 +267,7 @@ WalProposerPoll(WalProposer *wp)
|
|||||||
now = wp->api.get_current_timestamp(wp);
|
now = wp->api.get_current_timestamp(wp);
|
||||||
for (int i = 0; i < wp->n_safekeepers; i++)
|
for (int i = 0; i < wp->n_safekeepers; i++)
|
||||||
{
|
{
|
||||||
Safekeeper *sk = &wp->safekeeper[i];
|
sk = &wp->safekeeper[i];
|
||||||
|
|
||||||
if (TimestampDifferenceExceeds(sk->latestMsgReceivedAt, now,
|
if (TimestampDifferenceExceeds(sk->latestMsgReceivedAt, now,
|
||||||
wp->config->safekeeper_connection_timeout))
|
wp->config->safekeeper_connection_timeout))
|
||||||
{
|
{
|
||||||
@@ -1080,7 +1077,7 @@ SendProposerElected(Safekeeper *sk)
|
|||||||
ProposerElected msg;
|
ProposerElected msg;
|
||||||
TermHistory *th;
|
TermHistory *th;
|
||||||
term_t lastCommonTerm;
|
term_t lastCommonTerm;
|
||||||
int i;
|
int idx;
|
||||||
|
|
||||||
/* Now that we are ready to send it's a good moment to create WAL reader */
|
/* Now that we are ready to send it's a good moment to create WAL reader */
|
||||||
wp->api.wal_reader_allocate(sk);
|
wp->api.wal_reader_allocate(sk);
|
||||||
@@ -1099,15 +1096,15 @@ SendProposerElected(Safekeeper *sk)
|
|||||||
/* We must start somewhere. */
|
/* We must start somewhere. */
|
||||||
Assert(wp->propTermHistory.n_entries >= 1);
|
Assert(wp->propTermHistory.n_entries >= 1);
|
||||||
|
|
||||||
for (i = 0; i < Min(wp->propTermHistory.n_entries, th->n_entries); i++)
|
for (idx = 0; idx < Min(wp->propTermHistory.n_entries, th->n_entries); idx++)
|
||||||
{
|
{
|
||||||
if (wp->propTermHistory.entries[i].term != th->entries[i].term)
|
if (wp->propTermHistory.entries[idx].term != th->entries[idx].term)
|
||||||
break;
|
break;
|
||||||
/* term must begin everywhere at the same point */
|
/* term must begin everywhere at the same point */
|
||||||
Assert(wp->propTermHistory.entries[i].lsn == th->entries[i].lsn);
|
Assert(wp->propTermHistory.entries[idx].lsn == th->entries[idx].lsn);
|
||||||
}
|
}
|
||||||
i--; /* step back to the last common term */
|
idx--; /* step back to the last common term */
|
||||||
if (i < 0)
|
if (idx < 0)
|
||||||
{
|
{
|
||||||
/* safekeeper is empty or no common point, start from the beginning */
|
/* safekeeper is empty or no common point, start from the beginning */
|
||||||
sk->startStreamingAt = wp->propTermHistory.entries[0].lsn;
|
sk->startStreamingAt = wp->propTermHistory.entries[0].lsn;
|
||||||
@@ -1128,14 +1125,14 @@ SendProposerElected(Safekeeper *sk)
|
|||||||
* proposer, LSN it is currently writing, but then we just pick
|
* proposer, LSN it is currently writing, but then we just pick
|
||||||
* safekeeper pos as it obviously can't be higher.
|
* safekeeper pos as it obviously can't be higher.
|
||||||
*/
|
*/
|
||||||
if (wp->propTermHistory.entries[i].term == wp->propTerm)
|
if (wp->propTermHistory.entries[idx].term == wp->propTerm)
|
||||||
{
|
{
|
||||||
sk->startStreamingAt = sk->voteResponse.flushLsn;
|
sk->startStreamingAt = sk->voteResponse.flushLsn;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
XLogRecPtr propEndLsn = wp->propTermHistory.entries[i + 1].lsn;
|
XLogRecPtr propEndLsn = wp->propTermHistory.entries[idx + 1].lsn;
|
||||||
XLogRecPtr skEndLsn = (i + 1 < th->n_entries ? th->entries[i + 1].lsn : sk->voteResponse.flushLsn);
|
XLogRecPtr skEndLsn = (idx + 1 < th->n_entries ? th->entries[idx + 1].lsn : sk->voteResponse.flushLsn);
|
||||||
|
|
||||||
sk->startStreamingAt = Min(propEndLsn, skEndLsn);
|
sk->startStreamingAt = Min(propEndLsn, skEndLsn);
|
||||||
}
|
}
|
||||||
@@ -1149,7 +1146,7 @@ SendProposerElected(Safekeeper *sk)
|
|||||||
msg.termHistory = &wp->propTermHistory;
|
msg.termHistory = &wp->propTermHistory;
|
||||||
msg.timelineStartLsn = wp->timelineStartLsn;
|
msg.timelineStartLsn = wp->timelineStartLsn;
|
||||||
|
|
||||||
lastCommonTerm = i >= 0 ? wp->propTermHistory.entries[i].term : 0;
|
lastCommonTerm = idx >= 0 ? wp->propTermHistory.entries[idx].term : 0;
|
||||||
wp_log(LOG,
|
wp_log(LOG,
|
||||||
"sending elected msg to node " UINT64_FORMAT " term=" UINT64_FORMAT ", startStreamingAt=%X/%X (lastCommonTerm=" UINT64_FORMAT "), termHistory.n_entries=%u to %s:%s, timelineStartLsn=%X/%X",
|
"sending elected msg to node " UINT64_FORMAT " term=" UINT64_FORMAT ", startStreamingAt=%X/%X (lastCommonTerm=" UINT64_FORMAT "), termHistory.n_entries=%u to %s:%s, timelineStartLsn=%X/%X",
|
||||||
sk->greetResponse.nodeId, msg.term, LSN_FORMAT_ARGS(msg.startStreamingAt), lastCommonTerm, msg.termHistory->n_entries, sk->host, sk->port, LSN_FORMAT_ARGS(msg.timelineStartLsn));
|
sk->greetResponse.nodeId, msg.term, LSN_FORMAT_ARGS(msg.startStreamingAt), lastCommonTerm, msg.termHistory->n_entries, sk->host, sk->port, LSN_FORMAT_ARGS(msg.timelineStartLsn));
|
||||||
@@ -1641,7 +1638,7 @@ UpdateDonorShmem(WalProposer *wp)
|
|||||||
* Process AppendResponse message from safekeeper.
|
* Process AppendResponse message from safekeeper.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
HandleSafekeeperResponse(WalProposer *wp, Safekeeper *sk)
|
HandleSafekeeperResponse(WalProposer *wp, Safekeeper *fromsk)
|
||||||
{
|
{
|
||||||
XLogRecPtr candidateTruncateLsn;
|
XLogRecPtr candidateTruncateLsn;
|
||||||
XLogRecPtr newCommitLsn;
|
XLogRecPtr newCommitLsn;
|
||||||
@@ -1660,7 +1657,7 @@ HandleSafekeeperResponse(WalProposer *wp, Safekeeper *sk)
|
|||||||
* and WAL is committed by the quorum. BroadcastAppendRequest() should be
|
* and WAL is committed by the quorum. BroadcastAppendRequest() should be
|
||||||
* called to notify safekeepers about the new commitLsn.
|
* called to notify safekeepers about the new commitLsn.
|
||||||
*/
|
*/
|
||||||
wp->api.process_safekeeper_feedback(wp, sk);
|
wp->api.process_safekeeper_feedback(wp, fromsk);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Try to advance truncateLsn -- the last record flushed to all
|
* Try to advance truncateLsn -- the last record flushed to all
|
||||||
|
|||||||
@@ -725,7 +725,7 @@ extern void WalProposerBroadcast(WalProposer *wp, XLogRecPtr startpos, XLogRecPt
|
|||||||
extern void WalProposerPoll(WalProposer *wp);
|
extern void WalProposerPoll(WalProposer *wp);
|
||||||
extern void WalProposerFree(WalProposer *wp);
|
extern void WalProposerFree(WalProposer *wp);
|
||||||
|
|
||||||
extern WalproposerShmemState *GetWalpropShmemState();
|
extern WalproposerShmemState *GetWalpropShmemState(void);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* WaitEventSet API doesn't allow to remove socket, so walproposer_pg uses it to
|
* WaitEventSet API doesn't allow to remove socket, so walproposer_pg uses it to
|
||||||
@@ -745,7 +745,7 @@ extern TimeLineID walprop_pg_get_timeline_id(void);
|
|||||||
* catch logging.
|
* catch logging.
|
||||||
*/
|
*/
|
||||||
#ifdef WALPROPOSER_LIB
|
#ifdef WALPROPOSER_LIB
|
||||||
extern void WalProposerLibLog(WalProposer *wp, int elevel, char *fmt,...);
|
extern void WalProposerLibLog(WalProposer *wp, int elevel, char *fmt,...) pg_attribute_printf(3, 4);
|
||||||
#define wp_log(elevel, fmt, ...) WalProposerLibLog(wp, elevel, fmt, ## __VA_ARGS__)
|
#define wp_log(elevel, fmt, ...) WalProposerLibLog(wp, elevel, fmt, ## __VA_ARGS__)
|
||||||
#else
|
#else
|
||||||
#define wp_log(elevel, fmt, ...) elog(elevel, WP_LOG_PREFIX fmt, ## __VA_ARGS__)
|
#define wp_log(elevel, fmt, ...) elog(elevel, WP_LOG_PREFIX fmt, ## __VA_ARGS__)
|
||||||
|
|||||||
@@ -286,6 +286,9 @@ safekeepers_cmp(char *old, char *new)
|
|||||||
static void
|
static void
|
||||||
assign_neon_safekeepers(const char *newval, void *extra)
|
assign_neon_safekeepers(const char *newval, void *extra)
|
||||||
{
|
{
|
||||||
|
char *newval_copy;
|
||||||
|
char *oldval;
|
||||||
|
|
||||||
if (!am_walproposer)
|
if (!am_walproposer)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -295,8 +298,8 @@ assign_neon_safekeepers(const char *newval, void *extra)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Copy values because we will modify them in split_safekeepers_list() */
|
/* Copy values because we will modify them in split_safekeepers_list() */
|
||||||
char *newval_copy = pstrdup(newval);
|
newval_copy = pstrdup(newval);
|
||||||
char *oldval = pstrdup(wal_acceptors_list);
|
oldval = pstrdup(wal_acceptors_list);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: restarting through FATAL is stupid and introduces 1s delay before
|
* TODO: restarting through FATAL is stupid and introduces 1s delay before
|
||||||
@@ -538,7 +541,7 @@ nwp_shmem_startup_hook(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
WalproposerShmemState *
|
WalproposerShmemState *
|
||||||
GetWalpropShmemState()
|
GetWalpropShmemState(void)
|
||||||
{
|
{
|
||||||
Assert(walprop_shared != NULL);
|
Assert(walprop_shared != NULL);
|
||||||
return walprop_shared;
|
return walprop_shared;
|
||||||
|
|||||||
@@ -44,27 +44,6 @@ infobits_desc(StringInfo buf, uint8 infobits, const char *keyname)
|
|||||||
appendStringInfoString(buf, "]");
|
appendStringInfoString(buf, "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
truncate_flags_desc(StringInfo buf, uint8 flags)
|
|
||||||
{
|
|
||||||
appendStringInfoString(buf, "flags: [");
|
|
||||||
|
|
||||||
if (flags & XLH_TRUNCATE_CASCADE)
|
|
||||||
appendStringInfoString(buf, "CASCADE, ");
|
|
||||||
if (flags & XLH_TRUNCATE_RESTART_SEQS)
|
|
||||||
appendStringInfoString(buf, "RESTART_SEQS, ");
|
|
||||||
|
|
||||||
if (buf->data[buf->len - 1] == ' ')
|
|
||||||
{
|
|
||||||
/* Truncate-away final unneeded ", " */
|
|
||||||
Assert(buf->data[buf->len - 2] == ',');
|
|
||||||
buf->len -= 2;
|
|
||||||
buf->data[buf->len] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
appendStringInfoString(buf, "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
neon_rm_desc(StringInfo buf, XLogReaderState *record)
|
neon_rm_desc(StringInfo buf, XLogReaderState *record)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ static bool redo_block_filter(XLogReaderState *record, uint8 block_id);
|
|||||||
static void GetPage(StringInfo input_message);
|
static void GetPage(StringInfo input_message);
|
||||||
static void Ping(StringInfo input_message);
|
static void Ping(StringInfo input_message);
|
||||||
static ssize_t buffered_read(void *buf, size_t count);
|
static ssize_t buffered_read(void *buf, size_t count);
|
||||||
static void CreateFakeSharedMemoryAndSemaphores();
|
static void CreateFakeSharedMemoryAndSemaphores(void);
|
||||||
|
|
||||||
static BufferTag target_redo_tag;
|
static BufferTag target_redo_tag;
|
||||||
|
|
||||||
@@ -170,6 +170,40 @@ close_range_syscall(unsigned int start_fd, unsigned int count, unsigned int flag
|
|||||||
return syscall(__NR_close_range, start_fd, count, flags);
|
return syscall(__NR_close_range, start_fd, count, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PgSeccompRule allowed_syscalls[] =
|
||||||
|
{
|
||||||
|
/* Hard requirements */
|
||||||
|
PG_SCMP_ALLOW(exit_group),
|
||||||
|
PG_SCMP_ALLOW(pselect6),
|
||||||
|
PG_SCMP_ALLOW(read),
|
||||||
|
PG_SCMP_ALLOW(select),
|
||||||
|
PG_SCMP_ALLOW(write),
|
||||||
|
|
||||||
|
/* Memory allocation */
|
||||||
|
PG_SCMP_ALLOW(brk),
|
||||||
|
#ifndef MALLOC_NO_MMAP
|
||||||
|
/* TODO: musl doesn't have mallopt */
|
||||||
|
PG_SCMP_ALLOW(mmap),
|
||||||
|
PG_SCMP_ALLOW(munmap),
|
||||||
|
#endif
|
||||||
|
/*
|
||||||
|
* getpid() is called on assertion failure, in ExceptionalCondition.
|
||||||
|
* It's not really needed, but seems pointless to hide it either. The
|
||||||
|
* system call unlikely to expose a kernel vulnerability, and the PID
|
||||||
|
* is stored in MyProcPid anyway.
|
||||||
|
*/
|
||||||
|
PG_SCMP_ALLOW(getpid),
|
||||||
|
|
||||||
|
/* Enable those for a proper shutdown. */
|
||||||
|
#if 0
|
||||||
|
PG_SCMP_ALLOW(munmap),
|
||||||
|
PG_SCMP_ALLOW(shmctl),
|
||||||
|
PG_SCMP_ALLOW(shmdt),
|
||||||
|
PG_SCMP_ALLOW(unlink), /* shm_unlink */
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
enter_seccomp_mode(void)
|
enter_seccomp_mode(void)
|
||||||
{
|
{
|
||||||
@@ -183,44 +217,12 @@ enter_seccomp_mode(void)
|
|||||||
(errcode(ERRCODE_SYSTEM_ERROR),
|
(errcode(ERRCODE_SYSTEM_ERROR),
|
||||||
errmsg("seccomp: could not close files >= fd 3")));
|
errmsg("seccomp: could not close files >= fd 3")));
|
||||||
|
|
||||||
PgSeccompRule syscalls[] =
|
|
||||||
{
|
|
||||||
/* Hard requirements */
|
|
||||||
PG_SCMP_ALLOW(exit_group),
|
|
||||||
PG_SCMP_ALLOW(pselect6),
|
|
||||||
PG_SCMP_ALLOW(read),
|
|
||||||
PG_SCMP_ALLOW(select),
|
|
||||||
PG_SCMP_ALLOW(write),
|
|
||||||
|
|
||||||
/* Memory allocation */
|
|
||||||
PG_SCMP_ALLOW(brk),
|
|
||||||
#ifndef MALLOC_NO_MMAP
|
|
||||||
/* TODO: musl doesn't have mallopt */
|
|
||||||
PG_SCMP_ALLOW(mmap),
|
|
||||||
PG_SCMP_ALLOW(munmap),
|
|
||||||
#endif
|
|
||||||
/*
|
|
||||||
* getpid() is called on assertion failure, in ExceptionalCondition.
|
|
||||||
* It's not really needed, but seems pointless to hide it either. The
|
|
||||||
* system call unlikely to expose a kernel vulnerability, and the PID
|
|
||||||
* is stored in MyProcPid anyway.
|
|
||||||
*/
|
|
||||||
PG_SCMP_ALLOW(getpid),
|
|
||||||
|
|
||||||
/* Enable those for a proper shutdown.
|
|
||||||
PG_SCMP_ALLOW(munmap),
|
|
||||||
PG_SCMP_ALLOW(shmctl),
|
|
||||||
PG_SCMP_ALLOW(shmdt),
|
|
||||||
PG_SCMP_ALLOW(unlink), // shm_unlink
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef MALLOC_NO_MMAP
|
#ifdef MALLOC_NO_MMAP
|
||||||
/* Ask glibc not to use mmap() */
|
/* Ask glibc not to use mmap() */
|
||||||
mallopt(M_MMAP_MAX, 0);
|
mallopt(M_MMAP_MAX, 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
seccomp_load_rules(syscalls, lengthof(syscalls));
|
seccomp_load_rules(allowed_syscalls, lengthof(allowed_syscalls));
|
||||||
}
|
}
|
||||||
#endif /* HAVE_LIBSECCOMP */
|
#endif /* HAVE_LIBSECCOMP */
|
||||||
|
|
||||||
@@ -449,7 +451,7 @@ WalRedoMain(int argc, char *argv[])
|
|||||||
* half-initialized postgres.
|
* half-initialized postgres.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
CreateFakeSharedMemoryAndSemaphores()
|
CreateFakeSharedMemoryAndSemaphores(void)
|
||||||
{
|
{
|
||||||
PGShmemHeader *shim = NULL;
|
PGShmemHeader *shim = NULL;
|
||||||
PGShmemHeader *hdr;
|
PGShmemHeader *hdr;
|
||||||
@@ -992,7 +994,7 @@ redo_block_filter(XLogReaderState *record, uint8 block_id)
|
|||||||
* If this block isn't one we are currently restoring, then return 'true'
|
* If this block isn't one we are currently restoring, then return 'true'
|
||||||
* so that this gets ignored
|
* so that this gets ignored
|
||||||
*/
|
*/
|
||||||
return !BUFFERTAGS_EQUAL(target_tag, target_redo_tag);
|
return !BufferTagsEqual(&target_tag, &target_redo_tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
Generated
+22
-7
@@ -2095,6 +2095,7 @@ files = [
|
|||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"},
|
{file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"},
|
||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"},
|
{file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"},
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"},
|
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"},
|
||||||
|
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"},
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"},
|
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"},
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"},
|
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"},
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"},
|
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"},
|
||||||
@@ -2103,6 +2104,8 @@ files = [
|
|||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"},
|
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"},
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"},
|
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"},
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"},
|
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"},
|
||||||
|
{file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"},
|
||||||
|
{file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"},
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"},
|
{file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"},
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"},
|
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"},
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"},
|
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"},
|
||||||
@@ -2584,6 +2587,7 @@ files = [
|
|||||||
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
||||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
||||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
||||||
|
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
|
||||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
|
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
|
||||||
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
|
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
|
||||||
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
|
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
|
||||||
@@ -2729,21 +2733,22 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "responses"
|
name = "responses"
|
||||||
version = "0.21.0"
|
version = "0.25.3"
|
||||||
description = "A utility library for mocking out the `requests` Python library."
|
description = "A utility library for mocking out the `requests` Python library."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "responses-0.21.0-py3-none-any.whl", hash = "sha256:2dcc863ba63963c0c3d9ee3fa9507cbe36b7d7b0fccb4f0bdfd9e96c539b1487"},
|
{file = "responses-0.25.3-py3-none-any.whl", hash = "sha256:521efcbc82081ab8daa588e08f7e8a64ce79b91c39f6e62199b19159bea7dbcb"},
|
||||||
{file = "responses-0.21.0.tar.gz", hash = "sha256:b82502eb5f09a0289d8e209e7bad71ef3978334f56d09b444253d5ad67bf5253"},
|
{file = "responses-0.25.3.tar.gz", hash = "sha256:617b9247abd9ae28313d57a75880422d55ec63c29d33d629697590a034358dba"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
requests = ">=2.0,<3.0"
|
pyyaml = "*"
|
||||||
urllib3 = ">=1.25.10"
|
requests = ">=2.30.0,<3.0"
|
||||||
|
urllib3 = ">=1.25.10,<3.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-localserver", "types-mock", "types-requests"]
|
tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rfc3339-validator"
|
name = "rfc3339-validator"
|
||||||
@@ -3137,6 +3142,16 @@ files = [
|
|||||||
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
|
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
|
||||||
{file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
|
{file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
|
||||||
{file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
|
{file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
|
||||||
|
{file = "wrapt-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecee4132c6cd2ce5308e21672015ddfed1ff975ad0ac8d27168ea82e71413f55"},
|
||||||
|
{file = "wrapt-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2020f391008ef874c6d9e208b24f28e31bcb85ccff4f335f15a3251d222b92d9"},
|
||||||
|
{file = "wrapt-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2feecf86e1f7a86517cab34ae6c2f081fd2d0dac860cb0c0ded96d799d20b335"},
|
||||||
|
{file = "wrapt-1.14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:240b1686f38ae665d1b15475966fe0472f78e71b1b4903c143a842659c8e4cb9"},
|
||||||
|
{file = "wrapt-1.14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9008dad07d71f68487c91e96579c8567c98ca4c3881b9b113bc7b33e9fd78b8"},
|
||||||
|
{file = "wrapt-1.14.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6447e9f3ba72f8e2b985a1da758767698efa72723d5b59accefd716e9e8272bf"},
|
||||||
|
{file = "wrapt-1.14.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:acae32e13a4153809db37405f5eba5bac5fbe2e2ba61ab227926a22901051c0a"},
|
||||||
|
{file = "wrapt-1.14.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49ef582b7a1152ae2766557f0550a9fcbf7bbd76f43fbdc94dd3bf07cc7168be"},
|
||||||
|
{file = "wrapt-1.14.1-cp311-cp311-win32.whl", hash = "sha256:358fe87cc899c6bb0ddc185bf3dbfa4ba646f05b1b0b9b5a27c2cb92c2cea204"},
|
||||||
|
{file = "wrapt-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:26046cd03936ae745a502abf44dac702a5e6880b2b01c29aea8ddf3353b68224"},
|
||||||
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
|
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
|
||||||
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
|
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
|
||||||
{file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
|
{file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
|
||||||
|
|||||||
+4
-3
@@ -1,11 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import enum
|
import enum
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
@enum.unique
|
@enum.unique
|
||||||
@@ -55,12 +56,12 @@ def mypy() -> str:
|
|||||||
return "poetry run mypy"
|
return "poetry run mypy"
|
||||||
|
|
||||||
|
|
||||||
def get_commit_files() -> List[str]:
|
def get_commit_files() -> list[str]:
|
||||||
files = subprocess.check_output("git diff --cached --name-only --diff-filter=ACM".split())
|
files = subprocess.check_output("git diff --cached --name-only --diff-filter=ACM".split())
|
||||||
return files.decode().splitlines()
|
return files.decode().splitlines()
|
||||||
|
|
||||||
|
|
||||||
def check(name: str, suffix: str, cmd: str, changed_files: List[str], no_color: bool = False):
|
def check(name: str, suffix: str, cmd: str, changed_files: list[str], no_color: bool = False):
|
||||||
print(f"Checking: {name} ", end="")
|
print(f"Checking: {name} ", end="")
|
||||||
applicable_files = list(filter(lambda fname: fname.strip().endswith(suffix), changed_files))
|
applicable_files = list(filter(lambda fname: fname.strip().endswith(suffix), changed_files))
|
||||||
if not applicable_files:
|
if not applicable_files:
|
||||||
|
|||||||
+3
-3
@@ -39,7 +39,7 @@ http.workspace = true
|
|||||||
humantime.workspace = true
|
humantime.workspace = true
|
||||||
humantime-serde.workspace = true
|
humantime-serde.workspace = true
|
||||||
hyper0.workspace = true
|
hyper0.workspace = true
|
||||||
hyper1 = { package = "hyper", version = "1.2", features = ["server"] }
|
hyper = { workspace = true, features = ["server", "http1", "http2"] }
|
||||||
hyper-util = { version = "0.1", features = ["server", "http1", "http2", "tokio"] }
|
hyper-util = { version = "0.1", features = ["server", "http1", "http2", "tokio"] }
|
||||||
http-body-util = { version = "0.1" }
|
http-body-util = { version = "0.1" }
|
||||||
indexmap.workspace = true
|
indexmap.workspace = true
|
||||||
@@ -77,7 +77,7 @@ subtle.workspace = true
|
|||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tikv-jemallocator.workspace = true
|
tikv-jemallocator.workspace = true
|
||||||
tikv-jemalloc-ctl = { workspace = true, features = ["use_std"] }
|
tikv-jemalloc-ctl = { workspace = true, features = ["use_std"] }
|
||||||
tokio-postgres.workspace = true
|
tokio-postgres = { workspace = true, features = ["with-serde_json-1"] }
|
||||||
tokio-postgres-rustls.workspace = true
|
tokio-postgres-rustls.workspace = true
|
||||||
tokio-rustls.workspace = true
|
tokio-rustls.workspace = true
|
||||||
tokio-util.workspace = true
|
tokio-util.workspace = true
|
||||||
@@ -101,7 +101,7 @@ jose-jwa = "0.1.2"
|
|||||||
jose-jwk = { version = "0.1.2", features = ["p256", "p384", "rsa"] }
|
jose-jwk = { version = "0.1.2", features = ["p256", "p384", "rsa"] }
|
||||||
signature = "2"
|
signature = "2"
|
||||||
ecdsa = "0.16"
|
ecdsa = "0.16"
|
||||||
p256 = "0.13"
|
p256 = { version = "0.13", features = ["jwk"] }
|
||||||
rsa = "0.9"
|
rsa = "0.9"
|
||||||
|
|
||||||
workspace_hack.workspace = true
|
workspace_hack.workspace = true
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use crate::{
|
|||||||
auth::{self, backend::ComputeCredentialKeys, AuthFlow},
|
auth::{self, backend::ComputeCredentialKeys, AuthFlow},
|
||||||
compute,
|
compute,
|
||||||
config::AuthenticationConfig,
|
config::AuthenticationConfig,
|
||||||
console::AuthSecret,
|
|
||||||
context::RequestMonitoring,
|
context::RequestMonitoring,
|
||||||
|
control_plane::AuthSecret,
|
||||||
sasl,
|
sasl,
|
||||||
stream::{PqStream, Stream},
|
stream::{PqStream, Stream},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
auth, compute,
|
auth, compute,
|
||||||
config::AuthenticationConfig,
|
config::AuthenticationConfig,
|
||||||
console::{self, provider::NodeInfo},
|
|
||||||
context::RequestMonitoring,
|
context::RequestMonitoring,
|
||||||
|
control_plane::{self, provider::NodeInfo},
|
||||||
error::{ReportableError, UserFacingError},
|
error::{ReportableError, UserFacingError},
|
||||||
stream::PqStream,
|
stream::PqStream,
|
||||||
waiters,
|
waiters,
|
||||||
@@ -25,6 +25,10 @@ pub(crate) enum WebAuthError {
|
|||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ConsoleRedirectBackend {
|
||||||
|
console_uri: reqwest::Url,
|
||||||
|
}
|
||||||
|
|
||||||
impl UserFacingError for WebAuthError {
|
impl UserFacingError for WebAuthError {
|
||||||
fn to_string_client(&self) -> String {
|
fn to_string_client(&self) -> String {
|
||||||
"Internal error".to_string()
|
"Internal error".to_string()
|
||||||
@@ -57,7 +61,26 @@ pub(crate) fn new_psql_session_id() -> String {
|
|||||||
hex::encode(rand::random::<[u8; 8]>())
|
hex::encode(rand::random::<[u8; 8]>())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn authenticate(
|
impl ConsoleRedirectBackend {
|
||||||
|
pub fn new(console_uri: reqwest::Url) -> Self {
|
||||||
|
Self { console_uri }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn url(&self) -> &reqwest::Url {
|
||||||
|
&self.console_uri
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn authenticate(
|
||||||
|
&self,
|
||||||
|
ctx: &RequestMonitoring,
|
||||||
|
auth_config: &'static AuthenticationConfig,
|
||||||
|
client: &mut PqStream<impl AsyncRead + AsyncWrite + Unpin>,
|
||||||
|
) -> auth::Result<NodeInfo> {
|
||||||
|
authenticate(ctx, auth_config, &self.console_uri, client).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn authenticate(
|
||||||
ctx: &RequestMonitoring,
|
ctx: &RequestMonitoring,
|
||||||
auth_config: &'static AuthenticationConfig,
|
auth_config: &'static AuthenticationConfig,
|
||||||
link_uri: &reqwest::Url,
|
link_uri: &reqwest::Url,
|
||||||
@@ -70,7 +93,7 @@ pub(super) async fn authenticate(
|
|||||||
let (psql_session_id, waiter) = loop {
|
let (psql_session_id, waiter) = loop {
|
||||||
let psql_session_id = new_psql_session_id();
|
let psql_session_id = new_psql_session_id();
|
||||||
|
|
||||||
match console::mgmt::get_waiter(&psql_session_id) {
|
match control_plane::mgmt::get_waiter(&psql_session_id) {
|
||||||
Ok(waiter) => break (psql_session_id, waiter),
|
Ok(waiter) => break (psql_session_id, waiter),
|
||||||
Err(_e) => continue,
|
Err(_e) => continue,
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,8 @@ use super::{ComputeCredentials, ComputeUserInfo, ComputeUserInfoNoEndpoint};
|
|||||||
use crate::{
|
use crate::{
|
||||||
auth::{self, AuthFlow},
|
auth::{self, AuthFlow},
|
||||||
config::AuthenticationConfig,
|
config::AuthenticationConfig,
|
||||||
console::AuthSecret,
|
|
||||||
context::RequestMonitoring,
|
context::RequestMonitoring,
|
||||||
|
control_plane::AuthSecret,
|
||||||
intern::EndpointIdInt,
|
intern::EndpointIdInt,
|
||||||
sasl,
|
sasl,
|
||||||
stream::{self, Stream},
|
stream::{self, Stream},
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ use crate::{
|
|||||||
RoleName,
|
RoleName,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::ComputeCredentialKeys;
|
||||||
|
|
||||||
// TODO(conrad): make these configurable.
|
// TODO(conrad): make these configurable.
|
||||||
const CLOCK_SKEW_LEEWAY: Duration = Duration::from_secs(30);
|
const CLOCK_SKEW_LEEWAY: Duration = Duration::from_secs(30);
|
||||||
const MIN_RENEW: Duration = Duration::from_secs(30);
|
const MIN_RENEW: Duration = Duration::from_secs(30);
|
||||||
@@ -241,7 +243,7 @@ impl JwkCacheEntryLock {
|
|||||||
endpoint: EndpointId,
|
endpoint: EndpointId,
|
||||||
role_name: &RoleName,
|
role_name: &RoleName,
|
||||||
fetch: &F,
|
fetch: &F,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<ComputeCredentialKeys, anyhow::Error> {
|
||||||
// JWT compact form is defined to be
|
// JWT compact form is defined to be
|
||||||
// <B64(Header)> || . || <B64(Payload)> || . || <B64(Signature)>
|
// <B64(Header)> || . || <B64(Payload)> || . || <B64(Signature)>
|
||||||
// where Signature = alg(<B64(Header)> || . || <B64(Payload)>);
|
// where Signature = alg(<B64(Header)> || . || <B64(Payload)>);
|
||||||
@@ -300,9 +302,9 @@ impl JwkCacheEntryLock {
|
|||||||
key => bail!("unsupported key type {key:?}"),
|
key => bail!("unsupported key type {key:?}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let payload = base64::decode_config(payload, base64::URL_SAFE_NO_PAD)
|
let payloadb = base64::decode_config(payload, base64::URL_SAFE_NO_PAD)
|
||||||
.context("Provided authentication token is not a valid JWT encoding")?;
|
.context("Provided authentication token is not a valid JWT encoding")?;
|
||||||
let payload = serde_json::from_slice::<JwtPayload<'_>>(&payload)
|
let payload = serde_json::from_slice::<JwtPayload<'_>>(&payloadb)
|
||||||
.context("Provided authentication token is not a valid JWT encoding")?;
|
.context("Provided authentication token is not a valid JWT encoding")?;
|
||||||
|
|
||||||
tracing::debug!(?payload, "JWT signature valid with claims");
|
tracing::debug!(?payload, "JWT signature valid with claims");
|
||||||
@@ -327,7 +329,7 @@ impl JwkCacheEntryLock {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(ComputeCredentialKeys::JwtPayload(payloadb))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +341,7 @@ impl JwkCache {
|
|||||||
role_name: &RoleName,
|
role_name: &RoleName,
|
||||||
fetch: &F,
|
fetch: &F,
|
||||||
jwt: &str,
|
jwt: &str,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<ComputeCredentialKeys, anyhow::Error> {
|
||||||
// try with just a read lock first
|
// try with just a read lock first
|
||||||
let key = (endpoint.clone(), role_name.clone());
|
let key = (endpoint.clone(), role_name.clone());
|
||||||
let entry = self.map.get(&key).as_deref().map(Arc::clone);
|
let entry = self.map.get(&key).as_deref().map(Arc::clone);
|
||||||
@@ -571,7 +573,7 @@ mod tests {
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::Response;
|
use http::Response;
|
||||||
use http_body_util::Full;
|
use http_body_util::Full;
|
||||||
use hyper1::service::service_fn;
|
use hyper::service::service_fn;
|
||||||
use hyper_util::rt::TokioIo;
|
use hyper_util::rt::TokioIo;
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use rsa::pkcs8::DecodePrivateKey;
|
use rsa::pkcs8::DecodePrivateKey;
|
||||||
@@ -736,7 +738,7 @@ X0n5X2/pBLJzxZc62ccvZYVnctBiFs6HbSnxpuMQCfkt/BcR/ttIepBQQIW86wHL
|
|||||||
});
|
});
|
||||||
|
|
||||||
let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
|
let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
|
||||||
let server = hyper1::server::conn::http1::Builder::new();
|
let server = hyper::server::conn::http1::Builder::new();
|
||||||
let addr = listener.local_addr().unwrap();
|
let addr = listener.local_addr().unwrap();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ use arc_swap::ArcSwapOption;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
compute::ConnCfg,
|
compute::ConnCfg,
|
||||||
console::{
|
context::RequestMonitoring,
|
||||||
|
control_plane::{
|
||||||
messages::{ColdStartInfo, EndpointJwksResponse, MetricsAuxInfo},
|
messages::{ColdStartInfo, EndpointJwksResponse, MetricsAuxInfo},
|
||||||
NodeInfo,
|
NodeInfo,
|
||||||
},
|
},
|
||||||
context::RequestMonitoring,
|
|
||||||
intern::{BranchIdTag, EndpointIdTag, InternId, ProjectIdTag},
|
intern::{BranchIdTag, EndpointIdTag, InternId, ProjectIdTag},
|
||||||
EndpointId,
|
EndpointId,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
mod classic;
|
mod classic;
|
||||||
|
mod console_redirect;
|
||||||
mod hacks;
|
mod hacks;
|
||||||
pub mod jwt;
|
pub mod jwt;
|
||||||
pub mod local;
|
pub mod local;
|
||||||
mod web;
|
|
||||||
|
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
pub use console_redirect::ConsoleRedirectBackend;
|
||||||
|
pub(crate) use console_redirect::WebAuthError;
|
||||||
use ipnet::{Ipv4Net, Ipv6Net};
|
use ipnet::{Ipv4Net, Ipv6Net};
|
||||||
use local::LocalBackend;
|
use local::LocalBackend;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tokio_postgres::config::AuthKeys;
|
use tokio_postgres::config::AuthKeys;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
pub(crate) use web::WebAuthError;
|
|
||||||
|
|
||||||
use crate::auth::credentials::check_peer_addr_is_in_list;
|
use crate::auth::credentials::check_peer_addr_is_in_list;
|
||||||
use crate::auth::{validate_password_and_exchange, AuthError};
|
use crate::auth::{validate_password_and_exchange, AuthError};
|
||||||
use crate::cache::Cached;
|
use crate::cache::Cached;
|
||||||
use crate::console::errors::GetAuthInfoError;
|
|
||||||
use crate::console::provider::{CachedRoleSecret, ConsoleBackend};
|
|
||||||
use crate::console::{AuthSecret, NodeInfo};
|
|
||||||
use crate::context::RequestMonitoring;
|
use crate::context::RequestMonitoring;
|
||||||
|
use crate::control_plane::errors::GetAuthInfoError;
|
||||||
|
use crate::control_plane::provider::{CachedRoleSecret, ControlPlaneBackend};
|
||||||
|
use crate::control_plane::{AuthSecret, NodeInfo};
|
||||||
use crate::intern::EndpointIdInt;
|
use crate::intern::EndpointIdInt;
|
||||||
use crate::metrics::Metrics;
|
use crate::metrics::Metrics;
|
||||||
use crate::proxy::connect_compute::ComputeConnectBackend;
|
use crate::proxy::connect_compute::ComputeConnectBackend;
|
||||||
@@ -31,12 +32,12 @@ use crate::stream::Stream;
|
|||||||
use crate::{
|
use crate::{
|
||||||
auth::{self, ComputeUserInfoMaybeEndpoint},
|
auth::{self, ComputeUserInfoMaybeEndpoint},
|
||||||
config::AuthenticationConfig,
|
config::AuthenticationConfig,
|
||||||
console::{
|
control_plane::{
|
||||||
self,
|
self,
|
||||||
provider::{CachedAllowedIps, CachedNodeInfo},
|
provider::{CachedAllowedIps, CachedNodeInfo},
|
||||||
Api,
|
Api,
|
||||||
},
|
},
|
||||||
stream, url,
|
stream,
|
||||||
};
|
};
|
||||||
use crate::{scram, EndpointCacheKey, EndpointId, RoleName};
|
use crate::{scram, EndpointCacheKey, EndpointId, RoleName};
|
||||||
|
|
||||||
@@ -67,19 +68,19 @@ impl<T> std::ops::Deref for MaybeOwned<'_, T> {
|
|||||||
/// backends which require them for the authentication process.
|
/// backends which require them for the authentication process.
|
||||||
pub enum Backend<'a, T, D> {
|
pub enum Backend<'a, T, D> {
|
||||||
/// Cloud API (V2).
|
/// Cloud API (V2).
|
||||||
Console(MaybeOwned<'a, ConsoleBackend>, T),
|
ControlPlane(MaybeOwned<'a, ControlPlaneBackend>, T),
|
||||||
/// Authentication via a web browser.
|
/// Authentication via a web browser.
|
||||||
Web(MaybeOwned<'a, url::ApiUrl>, D),
|
ConsoleRedirect(MaybeOwned<'a, ConsoleRedirectBackend>, D),
|
||||||
/// Local proxy uses configured auth credentials and does not wake compute
|
/// Local proxy uses configured auth credentials and does not wake compute
|
||||||
Local(MaybeOwned<'a, LocalBackend>),
|
Local(MaybeOwned<'a, LocalBackend>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) trait TestBackend: Send + Sync + 'static {
|
pub(crate) trait TestBackend: Send + Sync + 'static {
|
||||||
fn wake_compute(&self) -> Result<CachedNodeInfo, console::errors::WakeComputeError>;
|
fn wake_compute(&self) -> Result<CachedNodeInfo, control_plane::errors::WakeComputeError>;
|
||||||
fn get_allowed_ips_and_secret(
|
fn get_allowed_ips_and_secret(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<(CachedAllowedIps, Option<CachedRoleSecret>), console::errors::GetAuthInfoError>;
|
) -> Result<(CachedAllowedIps, Option<CachedRoleSecret>), control_plane::errors::GetAuthInfoError>;
|
||||||
fn dyn_clone(&self) -> Box<dyn TestBackend>;
|
fn dyn_clone(&self) -> Box<dyn TestBackend>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,18 +94,23 @@ impl Clone for Box<dyn TestBackend> {
|
|||||||
impl std::fmt::Display for Backend<'_, (), ()> {
|
impl std::fmt::Display for Backend<'_, (), ()> {
|
||||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(api, ()) => match &**api {
|
Self::ControlPlane(api, ()) => match &**api {
|
||||||
ConsoleBackend::Console(endpoint) => {
|
ControlPlaneBackend::Management(endpoint) => fmt
|
||||||
fmt.debug_tuple("Console").field(&endpoint.url()).finish()
|
.debug_tuple("ControlPlane::Management")
|
||||||
}
|
.field(&endpoint.url())
|
||||||
|
.finish(),
|
||||||
#[cfg(any(test, feature = "testing"))]
|
#[cfg(any(test, feature = "testing"))]
|
||||||
ConsoleBackend::Postgres(endpoint) => {
|
ControlPlaneBackend::PostgresMock(endpoint) => fmt
|
||||||
fmt.debug_tuple("Postgres").field(&endpoint.url()).finish()
|
.debug_tuple("ControlPlane::PostgresMock")
|
||||||
}
|
.field(&endpoint.url())
|
||||||
|
.finish(),
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
ConsoleBackend::Test(_) => fmt.debug_tuple("Test").finish(),
|
ControlPlaneBackend::Test(_) => fmt.debug_tuple("ControlPlane::Test").finish(),
|
||||||
},
|
},
|
||||||
Self::Web(url, ()) => fmt.debug_tuple("Web").field(&url.as_str()).finish(),
|
Self::ConsoleRedirect(backend, ()) => fmt
|
||||||
|
.debug_tuple("ConsoleRedirect")
|
||||||
|
.field(&backend.url().as_str())
|
||||||
|
.finish(),
|
||||||
Self::Local(_) => fmt.debug_tuple("Local").finish(),
|
Self::Local(_) => fmt.debug_tuple("Local").finish(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,8 +121,8 @@ impl<T, D> Backend<'_, T, D> {
|
|||||||
/// This helps us pass structured config to async tasks.
|
/// This helps us pass structured config to async tasks.
|
||||||
pub(crate) fn as_ref(&self) -> Backend<'_, &T, &D> {
|
pub(crate) fn as_ref(&self) -> Backend<'_, &T, &D> {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(c, x) => Backend::Console(MaybeOwned::Borrowed(c), x),
|
Self::ControlPlane(c, x) => Backend::ControlPlane(MaybeOwned::Borrowed(c), x),
|
||||||
Self::Web(c, x) => Backend::Web(MaybeOwned::Borrowed(c), x),
|
Self::ConsoleRedirect(c, x) => Backend::ConsoleRedirect(MaybeOwned::Borrowed(c), x),
|
||||||
Self::Local(l) => Backend::Local(MaybeOwned::Borrowed(l)),
|
Self::Local(l) => Backend::Local(MaybeOwned::Borrowed(l)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,8 +134,8 @@ impl<'a, T, D> Backend<'a, T, D> {
|
|||||||
/// a function to a contained value.
|
/// a function to a contained value.
|
||||||
pub(crate) fn map<R>(self, f: impl FnOnce(T) -> R) -> Backend<'a, R, D> {
|
pub(crate) fn map<R>(self, f: impl FnOnce(T) -> R) -> Backend<'a, R, D> {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(c, x) => Backend::Console(c, f(x)),
|
Self::ControlPlane(c, x) => Backend::ControlPlane(c, f(x)),
|
||||||
Self::Web(c, x) => Backend::Web(c, x),
|
Self::ConsoleRedirect(c, x) => Backend::ConsoleRedirect(c, x),
|
||||||
Self::Local(l) => Backend::Local(l),
|
Self::Local(l) => Backend::Local(l),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,8 +145,8 @@ impl<'a, T, D, E> Backend<'a, Result<T, E>, D> {
|
|||||||
/// This is most useful for error handling.
|
/// This is most useful for error handling.
|
||||||
pub(crate) fn transpose(self) -> Result<Backend<'a, T, D>, E> {
|
pub(crate) fn transpose(self) -> Result<Backend<'a, T, D>, E> {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(c, x) => x.map(|x| Backend::Console(c, x)),
|
Self::ControlPlane(c, x) => x.map(|x| Backend::ControlPlane(c, x)),
|
||||||
Self::Web(c, x) => Ok(Backend::Web(c, x)),
|
Self::ConsoleRedirect(c, x) => Ok(Backend::ConsoleRedirect(c, x)),
|
||||||
Self::Local(l) => Ok(Backend::Local(l)),
|
Self::Local(l) => Ok(Backend::Local(l)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,10 +176,12 @@ impl ComputeUserInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(test, derive(Debug))]
|
||||||
pub(crate) enum ComputeCredentialKeys {
|
pub(crate) enum ComputeCredentialKeys {
|
||||||
#[cfg(any(test, feature = "testing"))]
|
#[cfg(any(test, feature = "testing"))]
|
||||||
Password(Vec<u8>),
|
Password(Vec<u8>),
|
||||||
AuthKeys(AuthKeys),
|
AuthKeys(AuthKeys),
|
||||||
|
JwtPayload(Vec<u8>),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +242,6 @@ impl AuthenticationConfig {
|
|||||||
pub(crate) fn check_rate_limit(
|
pub(crate) fn check_rate_limit(
|
||||||
&self,
|
&self,
|
||||||
ctx: &RequestMonitoring,
|
ctx: &RequestMonitoring,
|
||||||
config: &AuthenticationConfig,
|
|
||||||
secret: AuthSecret,
|
secret: AuthSecret,
|
||||||
endpoint: &EndpointId,
|
endpoint: &EndpointId,
|
||||||
is_cleartext: bool,
|
is_cleartext: bool,
|
||||||
@@ -258,7 +265,7 @@ impl AuthenticationConfig {
|
|||||||
let limit_not_exceeded = self.rate_limiter.check(
|
let limit_not_exceeded = self.rate_limiter.check(
|
||||||
(
|
(
|
||||||
endpoint_int,
|
endpoint_int,
|
||||||
MaskedIp::new(ctx.peer_addr(), config.rate_limit_ip_subnet),
|
MaskedIp::new(ctx.peer_addr(), self.rate_limit_ip_subnet),
|
||||||
),
|
),
|
||||||
password_weight,
|
password_weight,
|
||||||
);
|
);
|
||||||
@@ -290,7 +297,7 @@ impl AuthenticationConfig {
|
|||||||
/// All authentication flows will emit an AuthenticationOk message if successful.
|
/// All authentication flows will emit an AuthenticationOk message if successful.
|
||||||
async fn auth_quirks(
|
async fn auth_quirks(
|
||||||
ctx: &RequestMonitoring,
|
ctx: &RequestMonitoring,
|
||||||
api: &impl console::Api,
|
api: &impl control_plane::Api,
|
||||||
user_info: ComputeUserInfoMaybeEndpoint,
|
user_info: ComputeUserInfoMaybeEndpoint,
|
||||||
client: &mut stream::PqStream<Stream<impl AsyncRead + AsyncWrite + Unpin>>,
|
client: &mut stream::PqStream<Stream<impl AsyncRead + AsyncWrite + Unpin>>,
|
||||||
allow_cleartext: bool,
|
allow_cleartext: bool,
|
||||||
@@ -332,7 +339,6 @@ async fn auth_quirks(
|
|||||||
let secret = if let Some(secret) = secret {
|
let secret = if let Some(secret) = secret {
|
||||||
config.check_rate_limit(
|
config.check_rate_limit(
|
||||||
ctx,
|
ctx,
|
||||||
config,
|
|
||||||
secret,
|
secret,
|
||||||
&info.endpoint,
|
&info.endpoint,
|
||||||
unauthenticated_password.is_some() || allow_cleartext,
|
unauthenticated_password.is_some() || allow_cleartext,
|
||||||
@@ -412,8 +418,8 @@ impl<'a> Backend<'a, ComputeUserInfoMaybeEndpoint, &()> {
|
|||||||
/// Get username from the credentials.
|
/// Get username from the credentials.
|
||||||
pub(crate) fn get_user(&self) -> &str {
|
pub(crate) fn get_user(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(_, user_info) => &user_info.user,
|
Self::ControlPlane(_, user_info) => &user_info.user,
|
||||||
Self::Web(_, ()) => "web",
|
Self::ConsoleRedirect(_, ()) => "web",
|
||||||
Self::Local(_) => "local",
|
Self::Local(_) => "local",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -429,7 +435,7 @@ impl<'a> Backend<'a, ComputeUserInfoMaybeEndpoint, &()> {
|
|||||||
endpoint_rate_limiter: Arc<EndpointRateLimiter>,
|
endpoint_rate_limiter: Arc<EndpointRateLimiter>,
|
||||||
) -> auth::Result<Backend<'a, ComputeCredentials, NodeInfo>> {
|
) -> auth::Result<Backend<'a, ComputeCredentials, NodeInfo>> {
|
||||||
let res = match self {
|
let res = match self {
|
||||||
Self::Console(api, user_info) => {
|
Self::ControlPlane(api, user_info) => {
|
||||||
info!(
|
info!(
|
||||||
user = &*user_info.user,
|
user = &*user_info.user,
|
||||||
project = user_info.endpoint(),
|
project = user_info.endpoint(),
|
||||||
@@ -446,15 +452,15 @@ impl<'a> Backend<'a, ComputeUserInfoMaybeEndpoint, &()> {
|
|||||||
endpoint_rate_limiter,
|
endpoint_rate_limiter,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Backend::Console(api, credentials)
|
Backend::ControlPlane(api, credentials)
|
||||||
}
|
}
|
||||||
// NOTE: this auth backend doesn't use client credentials.
|
// NOTE: this auth backend doesn't use client credentials.
|
||||||
Self::Web(url, ()) => {
|
Self::ConsoleRedirect(backend, ()) => {
|
||||||
info!("performing web authentication");
|
info!("performing web authentication");
|
||||||
|
|
||||||
let info = web::authenticate(ctx, config, &url, client).await?;
|
let info = backend.authenticate(ctx, config, client).await?;
|
||||||
|
|
||||||
Backend::Web(url, info)
|
Backend::ConsoleRedirect(backend, info)
|
||||||
}
|
}
|
||||||
Self::Local(_) => {
|
Self::Local(_) => {
|
||||||
return Err(auth::AuthError::bad_auth_method("invalid for local proxy"))
|
return Err(auth::AuthError::bad_auth_method("invalid for local proxy"))
|
||||||
@@ -472,8 +478,8 @@ impl Backend<'_, ComputeUserInfo, &()> {
|
|||||||
ctx: &RequestMonitoring,
|
ctx: &RequestMonitoring,
|
||||||
) -> Result<CachedRoleSecret, GetAuthInfoError> {
|
) -> Result<CachedRoleSecret, GetAuthInfoError> {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(api, user_info) => api.get_role_secret(ctx, user_info).await,
|
Self::ControlPlane(api, user_info) => api.get_role_secret(ctx, user_info).await,
|
||||||
Self::Web(_, ()) => Ok(Cached::new_uncached(None)),
|
Self::ConsoleRedirect(_, ()) => Ok(Cached::new_uncached(None)),
|
||||||
Self::Local(_) => Ok(Cached::new_uncached(None)),
|
Self::Local(_) => Ok(Cached::new_uncached(None)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -483,8 +489,10 @@ impl Backend<'_, ComputeUserInfo, &()> {
|
|||||||
ctx: &RequestMonitoring,
|
ctx: &RequestMonitoring,
|
||||||
) -> Result<(CachedAllowedIps, Option<CachedRoleSecret>), GetAuthInfoError> {
|
) -> Result<(CachedAllowedIps, Option<CachedRoleSecret>), GetAuthInfoError> {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(api, user_info) => api.get_allowed_ips_and_secret(ctx, user_info).await,
|
Self::ControlPlane(api, user_info) => {
|
||||||
Self::Web(_, ()) => Ok((Cached::new_uncached(Arc::new(vec![])), None)),
|
api.get_allowed_ips_and_secret(ctx, user_info).await
|
||||||
|
}
|
||||||
|
Self::ConsoleRedirect(_, ()) => Ok((Cached::new_uncached(Arc::new(vec![])), None)),
|
||||||
Self::Local(_) => Ok((Cached::new_uncached(Arc::new(vec![])), None)),
|
Self::Local(_) => Ok((Cached::new_uncached(Arc::new(vec![])), None)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -495,18 +503,18 @@ impl ComputeConnectBackend for Backend<'_, ComputeCredentials, NodeInfo> {
|
|||||||
async fn wake_compute(
|
async fn wake_compute(
|
||||||
&self,
|
&self,
|
||||||
ctx: &RequestMonitoring,
|
ctx: &RequestMonitoring,
|
||||||
) -> Result<CachedNodeInfo, console::errors::WakeComputeError> {
|
) -> Result<CachedNodeInfo, control_plane::errors::WakeComputeError> {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(api, creds) => api.wake_compute(ctx, &creds.info).await,
|
Self::ControlPlane(api, creds) => api.wake_compute(ctx, &creds.info).await,
|
||||||
Self::Web(_, info) => Ok(Cached::new_uncached(info.clone())),
|
Self::ConsoleRedirect(_, info) => Ok(Cached::new_uncached(info.clone())),
|
||||||
Self::Local(local) => Ok(Cached::new_uncached(local.node_info.clone())),
|
Self::Local(local) => Ok(Cached::new_uncached(local.node_info.clone())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_keys(&self) -> &ComputeCredentialKeys {
|
fn get_keys(&self) -> &ComputeCredentialKeys {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(_, creds) => &creds.keys,
|
Self::ControlPlane(_, creds) => &creds.keys,
|
||||||
Self::Web(_, _) => &ComputeCredentialKeys::None,
|
Self::ConsoleRedirect(_, _) => &ComputeCredentialKeys::None,
|
||||||
Self::Local(_) => &ComputeCredentialKeys::None,
|
Self::Local(_) => &ComputeCredentialKeys::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -517,10 +525,10 @@ impl ComputeConnectBackend for Backend<'_, ComputeCredentials, &()> {
|
|||||||
async fn wake_compute(
|
async fn wake_compute(
|
||||||
&self,
|
&self,
|
||||||
ctx: &RequestMonitoring,
|
ctx: &RequestMonitoring,
|
||||||
) -> Result<CachedNodeInfo, console::errors::WakeComputeError> {
|
) -> Result<CachedNodeInfo, control_plane::errors::WakeComputeError> {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(api, creds) => api.wake_compute(ctx, &creds.info).await,
|
Self::ControlPlane(api, creds) => api.wake_compute(ctx, &creds.info).await,
|
||||||
Self::Web(_, ()) => {
|
Self::ConsoleRedirect(_, ()) => {
|
||||||
unreachable!("web auth flow doesn't support waking the compute")
|
unreachable!("web auth flow doesn't support waking the compute")
|
||||||
}
|
}
|
||||||
Self::Local(local) => Ok(Cached::new_uncached(local.node_info.clone())),
|
Self::Local(local) => Ok(Cached::new_uncached(local.node_info.clone())),
|
||||||
@@ -529,8 +537,8 @@ impl ComputeConnectBackend for Backend<'_, ComputeCredentials, &()> {
|
|||||||
|
|
||||||
fn get_keys(&self) -> &ComputeCredentialKeys {
|
fn get_keys(&self) -> &ComputeCredentialKeys {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(_, creds) => &creds.keys,
|
Self::ControlPlane(_, creds) => &creds.keys,
|
||||||
Self::Web(_, ()) => &ComputeCredentialKeys::None,
|
Self::ConsoleRedirect(_, ()) => &ComputeCredentialKeys::None,
|
||||||
Self::Local(_) => &ComputeCredentialKeys::None,
|
Self::Local(_) => &ComputeCredentialKeys::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -553,12 +561,12 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
auth::{backend::MaskedIp, ComputeUserInfoMaybeEndpoint, IpPattern},
|
auth::{backend::MaskedIp, ComputeUserInfoMaybeEndpoint, IpPattern},
|
||||||
config::AuthenticationConfig,
|
config::AuthenticationConfig,
|
||||||
console::{
|
context::RequestMonitoring,
|
||||||
|
control_plane::{
|
||||||
self,
|
self,
|
||||||
provider::{self, CachedAllowedIps, CachedRoleSecret},
|
provider::{self, CachedAllowedIps, CachedRoleSecret},
|
||||||
CachedNodeInfo,
|
CachedNodeInfo,
|
||||||
},
|
},
|
||||||
context::RequestMonitoring,
|
|
||||||
proxy::NeonOptions,
|
proxy::NeonOptions,
|
||||||
rate_limiter::{EndpointRateLimiter, RateBucketInfo},
|
rate_limiter::{EndpointRateLimiter, RateBucketInfo},
|
||||||
scram::{threadpool::ThreadPool, ServerSecret},
|
scram::{threadpool::ThreadPool, ServerSecret},
|
||||||
@@ -572,12 +580,12 @@ mod tests {
|
|||||||
secret: AuthSecret,
|
secret: AuthSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl console::Api for Auth {
|
impl control_plane::Api for Auth {
|
||||||
async fn get_role_secret(
|
async fn get_role_secret(
|
||||||
&self,
|
&self,
|
||||||
_ctx: &RequestMonitoring,
|
_ctx: &RequestMonitoring,
|
||||||
_user_info: &super::ComputeUserInfo,
|
_user_info: &super::ComputeUserInfo,
|
||||||
) -> Result<CachedRoleSecret, console::errors::GetAuthInfoError> {
|
) -> Result<CachedRoleSecret, control_plane::errors::GetAuthInfoError> {
|
||||||
Ok(CachedRoleSecret::new_uncached(Some(self.secret.clone())))
|
Ok(CachedRoleSecret::new_uncached(Some(self.secret.clone())))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,8 +593,10 @@ mod tests {
|
|||||||
&self,
|
&self,
|
||||||
_ctx: &RequestMonitoring,
|
_ctx: &RequestMonitoring,
|
||||||
_user_info: &super::ComputeUserInfo,
|
_user_info: &super::ComputeUserInfo,
|
||||||
) -> Result<(CachedAllowedIps, Option<CachedRoleSecret>), console::errors::GetAuthInfoError>
|
) -> Result<
|
||||||
{
|
(CachedAllowedIps, Option<CachedRoleSecret>),
|
||||||
|
control_plane::errors::GetAuthInfoError,
|
||||||
|
> {
|
||||||
Ok((
|
Ok((
|
||||||
CachedAllowedIps::new_uncached(Arc::new(self.ips.clone())),
|
CachedAllowedIps::new_uncached(Arc::new(self.ips.clone())),
|
||||||
Some(CachedRoleSecret::new_uncached(Some(self.secret.clone()))),
|
Some(CachedRoleSecret::new_uncached(Some(self.secret.clone()))),
|
||||||
@@ -605,7 +615,7 @@ mod tests {
|
|||||||
&self,
|
&self,
|
||||||
_ctx: &RequestMonitoring,
|
_ctx: &RequestMonitoring,
|
||||||
_user_info: &super::ComputeUserInfo,
|
_user_info: &super::ComputeUserInfo,
|
||||||
) -> Result<CachedNodeInfo, console::errors::WakeComputeError> {
|
) -> Result<CachedNodeInfo, control_plane::errors::WakeComputeError> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
use super::{backend::ComputeCredentialKeys, AuthErrorImpl, PasswordHackPayload};
|
use super::{backend::ComputeCredentialKeys, AuthErrorImpl, PasswordHackPayload};
|
||||||
use crate::{
|
use crate::{
|
||||||
config::TlsServerEndPoint,
|
config::TlsServerEndPoint,
|
||||||
console::AuthSecret,
|
|
||||||
context::RequestMonitoring,
|
context::RequestMonitoring,
|
||||||
|
control_plane::AuthSecret,
|
||||||
intern::EndpointIdInt,
|
intern::EndpointIdInt,
|
||||||
sasl,
|
sasl,
|
||||||
scram::{self, threadpool::ThreadPool},
|
scram::{self, threadpool::ThreadPool},
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub(crate) use flow::*;
|
|||||||
use tokio::time::error::Elapsed;
|
use tokio::time::error::Elapsed;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
console,
|
control_plane,
|
||||||
error::{ReportableError, UserFacingError},
|
error::{ReportableError, UserFacingError},
|
||||||
};
|
};
|
||||||
use std::{io, net::IpAddr};
|
use std::{io, net::IpAddr};
|
||||||
@@ -34,7 +34,7 @@ pub(crate) enum AuthErrorImpl {
|
|||||||
Web(#[from] backend::WebAuthError),
|
Web(#[from] backend::WebAuthError),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
GetAuthInfo(#[from] console::errors::GetAuthInfoError),
|
GetAuthInfo(#[from] control_plane::errors::GetAuthInfoError),
|
||||||
|
|
||||||
/// SASL protocol errors (includes [SCRAM](crate::scram)).
|
/// SASL protocol errors (includes [SCRAM](crate::scram)).
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
@@ -6,13 +6,16 @@ use compute_api::spec::LocalProxySpec;
|
|||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use futures::future::Either;
|
use futures::future::Either;
|
||||||
use proxy::{
|
use proxy::{
|
||||||
auth::backend::{
|
auth::{
|
||||||
jwt::JwkCache,
|
self,
|
||||||
local::{LocalBackend, JWKS_ROLE_MAP},
|
backend::{
|
||||||
|
jwt::JwkCache,
|
||||||
|
local::{LocalBackend, JWKS_ROLE_MAP},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
cancellation::CancellationHandlerMain,
|
cancellation::CancellationHandlerMain,
|
||||||
config::{self, AuthenticationConfig, HttpConfig, ProxyConfig, RetryConfig},
|
config::{self, AuthenticationConfig, HttpConfig, ProxyConfig, RetryConfig},
|
||||||
console::{
|
control_plane::{
|
||||||
locks::ApiLocks,
|
locks::ApiLocks,
|
||||||
messages::{EndpointJwksResponse, JwksSettings},
|
messages::{EndpointJwksResponse, JwksSettings},
|
||||||
},
|
},
|
||||||
@@ -132,6 +135,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let args = LocalProxyCliArgs::parse();
|
let args = LocalProxyCliArgs::parse();
|
||||||
let config = build_config(&args)?;
|
let config = build_config(&args)?;
|
||||||
|
let auth_backend = build_auth_backend(&args)?;
|
||||||
|
|
||||||
// before we bind to any ports, write the process ID to a file
|
// before we bind to any ports, write the process ID to a file
|
||||||
// so that compute-ctl can find our process later
|
// so that compute-ctl can find our process later
|
||||||
@@ -193,6 +197,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let task = serverless::task_main(
|
let task = serverless::task_main(
|
||||||
config,
|
config,
|
||||||
|
auth_backend,
|
||||||
http_listener,
|
http_listener,
|
||||||
shutdown.clone(),
|
shutdown.clone(),
|
||||||
Arc::new(CancellationHandlerMain::new(
|
Arc::new(CancellationHandlerMain::new(
|
||||||
@@ -257,9 +262,6 @@ fn build_config(args: &LocalProxyCliArgs) -> anyhow::Result<&'static ProxyConfig
|
|||||||
|
|
||||||
Ok(Box::leak(Box::new(ProxyConfig {
|
Ok(Box::leak(Box::new(ProxyConfig {
|
||||||
tls_config: None,
|
tls_config: None,
|
||||||
auth_backend: proxy::auth::Backend::Local(proxy::auth::backend::MaybeOwned::Owned(
|
|
||||||
LocalBackend::new(args.compute),
|
|
||||||
)),
|
|
||||||
metric_collection: None,
|
metric_collection: None,
|
||||||
allow_self_signed_compute: false,
|
allow_self_signed_compute: false,
|
||||||
http_config,
|
http_config,
|
||||||
@@ -286,6 +288,17 @@ fn build_config(args: &LocalProxyCliArgs) -> anyhow::Result<&'static ProxyConfig
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// auth::Backend is created at proxy startup, and lives forever.
|
||||||
|
fn build_auth_backend(
|
||||||
|
args: &LocalProxyCliArgs,
|
||||||
|
) -> anyhow::Result<&'static auth::Backend<'static, (), ()>> {
|
||||||
|
let auth_backend = proxy::auth::Backend::Local(proxy::auth::backend::MaybeOwned::Owned(
|
||||||
|
LocalBackend::new(args.compute),
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(Box::leak(Box::new(auth_backend)))
|
||||||
|
}
|
||||||
|
|
||||||
async fn refresh_config_loop(path: Utf8PathBuf, rx: Arc<Notify>) {
|
async fn refresh_config_loop(path: Utf8PathBuf, rx: Arc<Notify>) {
|
||||||
loop {
|
loop {
|
||||||
rx.notified().await;
|
rx.notified().await;
|
||||||
|
|||||||
+84
-74
@@ -10,6 +10,7 @@ use futures::future::Either;
|
|||||||
use proxy::auth;
|
use proxy::auth;
|
||||||
use proxy::auth::backend::jwt::JwkCache;
|
use proxy::auth::backend::jwt::JwkCache;
|
||||||
use proxy::auth::backend::AuthRateLimiter;
|
use proxy::auth::backend::AuthRateLimiter;
|
||||||
|
use proxy::auth::backend::ConsoleRedirectBackend;
|
||||||
use proxy::auth::backend::MaybeOwned;
|
use proxy::auth::backend::MaybeOwned;
|
||||||
use proxy::cancellation::CancelMap;
|
use proxy::cancellation::CancelMap;
|
||||||
use proxy::cancellation::CancellationHandler;
|
use proxy::cancellation::CancellationHandler;
|
||||||
@@ -19,8 +20,8 @@ use proxy::config::CacheOptions;
|
|||||||
use proxy::config::HttpConfig;
|
use proxy::config::HttpConfig;
|
||||||
use proxy::config::ProjectInfoCacheOptions;
|
use proxy::config::ProjectInfoCacheOptions;
|
||||||
use proxy::config::ProxyProtocolV2;
|
use proxy::config::ProxyProtocolV2;
|
||||||
use proxy::console;
|
|
||||||
use proxy::context::parquet::ParquetUploadArgs;
|
use proxy::context::parquet::ParquetUploadArgs;
|
||||||
|
use proxy::control_plane;
|
||||||
use proxy::http;
|
use proxy::http;
|
||||||
use proxy::http::health_server::AppMetrics;
|
use proxy::http::health_server::AppMetrics;
|
||||||
use proxy::metrics::Metrics;
|
use proxy::metrics::Metrics;
|
||||||
@@ -311,8 +312,9 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let args = ProxyCliArgs::parse();
|
let args = ProxyCliArgs::parse();
|
||||||
let config = build_config(&args)?;
|
let config = build_config(&args)?;
|
||||||
|
let auth_backend = build_auth_backend(&args)?;
|
||||||
|
|
||||||
info!("Authentication backend: {}", config.auth_backend);
|
info!("Authentication backend: {}", auth_backend);
|
||||||
info!("Using region: {}", args.aws_region);
|
info!("Using region: {}", args.aws_region);
|
||||||
|
|
||||||
let region_provider =
|
let region_provider =
|
||||||
@@ -462,6 +464,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
if let Some(proxy_listener) = proxy_listener {
|
if let Some(proxy_listener) = proxy_listener {
|
||||||
client_tasks.spawn(proxy::proxy::task_main(
|
client_tasks.spawn(proxy::proxy::task_main(
|
||||||
config,
|
config,
|
||||||
|
auth_backend,
|
||||||
proxy_listener,
|
proxy_listener,
|
||||||
cancellation_token.clone(),
|
cancellation_token.clone(),
|
||||||
cancellation_handler.clone(),
|
cancellation_handler.clone(),
|
||||||
@@ -472,6 +475,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
if let Some(serverless_listener) = serverless_listener {
|
if let Some(serverless_listener) = serverless_listener {
|
||||||
client_tasks.spawn(serverless::task_main(
|
client_tasks.spawn(serverless::task_main(
|
||||||
config,
|
config,
|
||||||
|
auth_backend,
|
||||||
serverless_listener,
|
serverless_listener,
|
||||||
cancellation_token.clone(),
|
cancellation_token.clone(),
|
||||||
cancellation_handler.clone(),
|
cancellation_handler.clone(),
|
||||||
@@ -495,7 +499,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
proxy: proxy::metrics::Metrics::get(),
|
proxy: proxy::metrics::Metrics::get(),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
maintenance_tasks.spawn(console::mgmt::task_main(mgmt_listener));
|
maintenance_tasks.spawn(control_plane::mgmt::task_main(mgmt_listener));
|
||||||
|
|
||||||
if let Some(metrics_config) = &config.metric_collection {
|
if let Some(metrics_config) = &config.metric_collection {
|
||||||
// TODO: Add gc regardles of the metric collection being enabled.
|
// TODO: Add gc regardles of the metric collection being enabled.
|
||||||
@@ -506,8 +510,8 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let auth::Backend::Console(api, _) = &config.auth_backend {
|
if let auth::Backend::ControlPlane(api, _) = auth_backend {
|
||||||
if let proxy::console::provider::ConsoleBackend::Console(api) = &**api {
|
if let proxy::control_plane::provider::ControlPlaneBackend::Management(api) = &**api {
|
||||||
match (redis_notifications_client, regional_redis_client.clone()) {
|
match (redis_notifications_client, regional_redis_client.clone()) {
|
||||||
(None, None) => {}
|
(None, None) => {}
|
||||||
(client1, client2) => {
|
(client1, client2) => {
|
||||||
@@ -610,73 +614,6 @@ fn build_config(args: &ProxyCliArgs) -> anyhow::Result<&'static ProxyConfig> {
|
|||||||
bail!("dynamic rate limiter should be disabled");
|
bail!("dynamic rate limiter should be disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
let auth_backend = match &args.auth_backend {
|
|
||||||
AuthBackendType::Console => {
|
|
||||||
let wake_compute_cache_config: CacheOptions = args.wake_compute_cache.parse()?;
|
|
||||||
let project_info_cache_config: ProjectInfoCacheOptions =
|
|
||||||
args.project_info_cache.parse()?;
|
|
||||||
let endpoint_cache_config: config::EndpointCacheConfig =
|
|
||||||
args.endpoint_cache_config.parse()?;
|
|
||||||
|
|
||||||
info!("Using NodeInfoCache (wake_compute) with options={wake_compute_cache_config:?}");
|
|
||||||
info!(
|
|
||||||
"Using AllowedIpsCache (wake_compute) with options={project_info_cache_config:?}"
|
|
||||||
);
|
|
||||||
info!("Using EndpointCacheConfig with options={endpoint_cache_config:?}");
|
|
||||||
let caches = Box::leak(Box::new(console::caches::ApiCaches::new(
|
|
||||||
wake_compute_cache_config,
|
|
||||||
project_info_cache_config,
|
|
||||||
endpoint_cache_config,
|
|
||||||
)));
|
|
||||||
|
|
||||||
let config::ConcurrencyLockOptions {
|
|
||||||
shards,
|
|
||||||
limiter,
|
|
||||||
epoch,
|
|
||||||
timeout,
|
|
||||||
} = args.wake_compute_lock.parse()?;
|
|
||||||
info!(?limiter, shards, ?epoch, "Using NodeLocks (wake_compute)");
|
|
||||||
let locks = Box::leak(Box::new(console::locks::ApiLocks::new(
|
|
||||||
"wake_compute_lock",
|
|
||||||
limiter,
|
|
||||||
shards,
|
|
||||||
timeout,
|
|
||||||
epoch,
|
|
||||||
&Metrics::get().wake_compute_lock,
|
|
||||||
)?));
|
|
||||||
tokio::spawn(locks.garbage_collect_worker());
|
|
||||||
|
|
||||||
let url = args.auth_endpoint.parse()?;
|
|
||||||
let endpoint = http::Endpoint::new(url, http::new_client());
|
|
||||||
|
|
||||||
let mut wake_compute_rps_limit = args.wake_compute_limit.clone();
|
|
||||||
RateBucketInfo::validate(&mut wake_compute_rps_limit)?;
|
|
||||||
let wake_compute_endpoint_rate_limiter =
|
|
||||||
Arc::new(WakeComputeRateLimiter::new(wake_compute_rps_limit));
|
|
||||||
let api = console::provider::neon::Api::new(
|
|
||||||
endpoint,
|
|
||||||
caches,
|
|
||||||
locks,
|
|
||||||
wake_compute_endpoint_rate_limiter,
|
|
||||||
);
|
|
||||||
let api = console::provider::ConsoleBackend::Console(api);
|
|
||||||
auth::Backend::Console(MaybeOwned::Owned(api), ())
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthBackendType::Web => {
|
|
||||||
let url = args.uri.parse()?;
|
|
||||||
auth::Backend::Web(MaybeOwned::Owned(url), ())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
|
||||||
AuthBackendType::Postgres => {
|
|
||||||
let url = args.auth_endpoint.parse()?;
|
|
||||||
let api = console::provider::mock::Api::new(url, !args.is_private_access_proxy);
|
|
||||||
let api = console::provider::ConsoleBackend::Postgres(api);
|
|
||||||
auth::Backend::Console(MaybeOwned::Owned(api), ())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let config::ConcurrencyLockOptions {
|
let config::ConcurrencyLockOptions {
|
||||||
shards,
|
shards,
|
||||||
limiter,
|
limiter,
|
||||||
@@ -689,7 +626,7 @@ fn build_config(args: &ProxyCliArgs) -> anyhow::Result<&'static ProxyConfig> {
|
|||||||
?epoch,
|
?epoch,
|
||||||
"Using NodeLocks (connect_compute)"
|
"Using NodeLocks (connect_compute)"
|
||||||
);
|
);
|
||||||
let connect_compute_locks = console::locks::ApiLocks::new(
|
let connect_compute_locks = control_plane::locks::ApiLocks::new(
|
||||||
"connect_compute_lock",
|
"connect_compute_lock",
|
||||||
limiter,
|
limiter,
|
||||||
shards,
|
shards,
|
||||||
@@ -728,7 +665,6 @@ fn build_config(args: &ProxyCliArgs) -> anyhow::Result<&'static ProxyConfig> {
|
|||||||
|
|
||||||
let config = Box::leak(Box::new(ProxyConfig {
|
let config = Box::leak(Box::new(ProxyConfig {
|
||||||
tls_config,
|
tls_config,
|
||||||
auth_backend,
|
|
||||||
metric_collection,
|
metric_collection,
|
||||||
allow_self_signed_compute: args.allow_self_signed_compute,
|
allow_self_signed_compute: args.allow_self_signed_compute,
|
||||||
http_config,
|
http_config,
|
||||||
@@ -748,6 +684,80 @@ fn build_config(args: &ProxyCliArgs) -> anyhow::Result<&'static ProxyConfig> {
|
|||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// auth::Backend is created at proxy startup, and lives forever.
|
||||||
|
fn build_auth_backend(
|
||||||
|
args: &ProxyCliArgs,
|
||||||
|
) -> anyhow::Result<&'static auth::Backend<'static, (), ()>> {
|
||||||
|
let auth_backend = match &args.auth_backend {
|
||||||
|
AuthBackendType::Console => {
|
||||||
|
let wake_compute_cache_config: CacheOptions = args.wake_compute_cache.parse()?;
|
||||||
|
let project_info_cache_config: ProjectInfoCacheOptions =
|
||||||
|
args.project_info_cache.parse()?;
|
||||||
|
let endpoint_cache_config: config::EndpointCacheConfig =
|
||||||
|
args.endpoint_cache_config.parse()?;
|
||||||
|
|
||||||
|
info!("Using NodeInfoCache (wake_compute) with options={wake_compute_cache_config:?}");
|
||||||
|
info!(
|
||||||
|
"Using AllowedIpsCache (wake_compute) with options={project_info_cache_config:?}"
|
||||||
|
);
|
||||||
|
info!("Using EndpointCacheConfig with options={endpoint_cache_config:?}");
|
||||||
|
let caches = Box::leak(Box::new(control_plane::caches::ApiCaches::new(
|
||||||
|
wake_compute_cache_config,
|
||||||
|
project_info_cache_config,
|
||||||
|
endpoint_cache_config,
|
||||||
|
)));
|
||||||
|
|
||||||
|
let config::ConcurrencyLockOptions {
|
||||||
|
shards,
|
||||||
|
limiter,
|
||||||
|
epoch,
|
||||||
|
timeout,
|
||||||
|
} = args.wake_compute_lock.parse()?;
|
||||||
|
info!(?limiter, shards, ?epoch, "Using NodeLocks (wake_compute)");
|
||||||
|
let locks = Box::leak(Box::new(control_plane::locks::ApiLocks::new(
|
||||||
|
"wake_compute_lock",
|
||||||
|
limiter,
|
||||||
|
shards,
|
||||||
|
timeout,
|
||||||
|
epoch,
|
||||||
|
&Metrics::get().wake_compute_lock,
|
||||||
|
)?));
|
||||||
|
tokio::spawn(locks.garbage_collect_worker());
|
||||||
|
|
||||||
|
let url = args.auth_endpoint.parse()?;
|
||||||
|
let endpoint = http::Endpoint::new(url, http::new_client());
|
||||||
|
|
||||||
|
let mut wake_compute_rps_limit = args.wake_compute_limit.clone();
|
||||||
|
RateBucketInfo::validate(&mut wake_compute_rps_limit)?;
|
||||||
|
let wake_compute_endpoint_rate_limiter =
|
||||||
|
Arc::new(WakeComputeRateLimiter::new(wake_compute_rps_limit));
|
||||||
|
let api = control_plane::provider::neon::Api::new(
|
||||||
|
endpoint,
|
||||||
|
caches,
|
||||||
|
locks,
|
||||||
|
wake_compute_endpoint_rate_limiter,
|
||||||
|
);
|
||||||
|
let api = control_plane::provider::ControlPlaneBackend::Management(api);
|
||||||
|
auth::Backend::ControlPlane(MaybeOwned::Owned(api), ())
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthBackendType::Web => {
|
||||||
|
let url = args.uri.parse()?;
|
||||||
|
auth::Backend::ConsoleRedirect(MaybeOwned::Owned(ConsoleRedirectBackend::new(url)), ())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "testing")]
|
||||||
|
AuthBackendType::Postgres => {
|
||||||
|
let url = args.auth_endpoint.parse()?;
|
||||||
|
let api = control_plane::provider::mock::Api::new(url, !args.is_private_access_proxy);
|
||||||
|
let api = control_plane::provider::ControlPlaneBackend::PostgresMock(api);
|
||||||
|
auth::Backend::ControlPlane(MaybeOwned::Owned(api), ())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Box::leak(Box::new(auth_backend)))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|||||||
Vendored
+1
-1
@@ -16,7 +16,7 @@ use tracing::{debug, info};
|
|||||||
use crate::{
|
use crate::{
|
||||||
auth::IpPattern,
|
auth::IpPattern,
|
||||||
config::ProjectInfoCacheOptions,
|
config::ProjectInfoCacheOptions,
|
||||||
console::AuthSecret,
|
control_plane::AuthSecret,
|
||||||
intern::{EndpointIdInt, ProjectIdInt, RoleNameInt},
|
intern::{EndpointIdInt, ProjectIdInt, RoleNameInt},
|
||||||
EndpointId, RoleName,
|
EndpointId, RoleName,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
auth::parse_endpoint_param,
|
auth::parse_endpoint_param,
|
||||||
cancellation::CancelClosure,
|
cancellation::CancelClosure,
|
||||||
console::{errors::WakeComputeError, messages::MetricsAuxInfo, provider::ApiLockError},
|
|
||||||
context::RequestMonitoring,
|
context::RequestMonitoring,
|
||||||
|
control_plane::{errors::WakeComputeError, messages::MetricsAuxInfo, provider::ApiLockError},
|
||||||
error::{ReportableError, UserFacingError},
|
error::{ReportableError, UserFacingError},
|
||||||
metrics::{Metrics, NumDbConnectionsGuard},
|
metrics::{Metrics, NumDbConnectionsGuard},
|
||||||
proxy::neon_option,
|
proxy::neon_option,
|
||||||
|
|||||||
+7
-11
@@ -1,9 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
auth::{
|
auth::backend::{jwt::JwkCache, AuthRateLimiter},
|
||||||
self,
|
control_plane::locks::ApiLocks,
|
||||||
backend::{jwt::JwkCache, AuthRateLimiter},
|
|
||||||
},
|
|
||||||
console::locks::ApiLocks,
|
|
||||||
rate_limiter::{RateBucketInfo, RateLimitAlgorithm, RateLimiterConfig},
|
rate_limiter::{RateBucketInfo, RateLimitAlgorithm, RateLimiterConfig},
|
||||||
scram::threadpool::ThreadPool,
|
scram::threadpool::ThreadPool,
|
||||||
serverless::{cancel_set::CancelSet, GlobalConnPoolOptions},
|
serverless::{cancel_set::CancelSet, GlobalConnPoolOptions},
|
||||||
@@ -29,7 +26,6 @@ use x509_parser::oid_registry;
|
|||||||
|
|
||||||
pub struct ProxyConfig {
|
pub struct ProxyConfig {
|
||||||
pub tls_config: Option<TlsConfig>,
|
pub tls_config: Option<TlsConfig>,
|
||||||
pub auth_backend: auth::Backend<'static, (), ()>,
|
|
||||||
pub metric_collection: Option<MetricCollectionConfig>,
|
pub metric_collection: Option<MetricCollectionConfig>,
|
||||||
pub allow_self_signed_compute: bool,
|
pub allow_self_signed_compute: bool,
|
||||||
pub http_config: HttpConfig,
|
pub http_config: HttpConfig,
|
||||||
@@ -372,7 +368,7 @@ pub struct EndpointCacheConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EndpointCacheConfig {
|
impl EndpointCacheConfig {
|
||||||
/// Default options for [`crate::console::provider::NodeInfoCache`].
|
/// Default options for [`crate::control_plane::provider::NodeInfoCache`].
|
||||||
/// Notice that by default the limiter is empty, which means that cache is disabled.
|
/// Notice that by default the limiter is empty, which means that cache is disabled.
|
||||||
pub const CACHE_DEFAULT_OPTIONS: &'static str =
|
pub const CACHE_DEFAULT_OPTIONS: &'static str =
|
||||||
"initial_batch_size=1000,default_batch_size=10,xread_timeout=5m,stream_name=controlPlane,disable_cache=true,limiter_info=1000@1s,retry_interval=1s";
|
"initial_batch_size=1000,default_batch_size=10,xread_timeout=5m,stream_name=controlPlane,disable_cache=true,limiter_info=1000@1s,retry_interval=1s";
|
||||||
@@ -447,7 +443,7 @@ pub struct CacheOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CacheOptions {
|
impl CacheOptions {
|
||||||
/// Default options for [`crate::console::provider::NodeInfoCache`].
|
/// Default options for [`crate::control_plane::provider::NodeInfoCache`].
|
||||||
pub const CACHE_DEFAULT_OPTIONS: &'static str = "size=4000,ttl=4m";
|
pub const CACHE_DEFAULT_OPTIONS: &'static str = "size=4000,ttl=4m";
|
||||||
|
|
||||||
/// Parse cache options passed via cmdline.
|
/// Parse cache options passed via cmdline.
|
||||||
@@ -503,7 +499,7 @@ pub struct ProjectInfoCacheOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectInfoCacheOptions {
|
impl ProjectInfoCacheOptions {
|
||||||
/// Default options for [`crate::console::provider::NodeInfoCache`].
|
/// Default options for [`crate::control_plane::provider::NodeInfoCache`].
|
||||||
pub const CACHE_DEFAULT_OPTIONS: &'static str =
|
pub const CACHE_DEFAULT_OPTIONS: &'static str =
|
||||||
"size=10000,ttl=4m,max_roles=10,gc_interval=60m";
|
"size=10000,ttl=4m,max_roles=10,gc_interval=60m";
|
||||||
|
|
||||||
@@ -622,9 +618,9 @@ pub struct ConcurrencyLockOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ConcurrencyLockOptions {
|
impl ConcurrencyLockOptions {
|
||||||
/// Default options for [`crate::console::provider::ApiLocks`].
|
/// Default options for [`crate::control_plane::provider::ApiLocks`].
|
||||||
pub const DEFAULT_OPTIONS_WAKE_COMPUTE_LOCK: &'static str = "permits=0";
|
pub const DEFAULT_OPTIONS_WAKE_COMPUTE_LOCK: &'static str = "permits=0";
|
||||||
/// Default options for [`crate::console::provider::ApiLocks`].
|
/// Default options for [`crate::control_plane::provider::ApiLocks`].
|
||||||
pub const DEFAULT_OPTIONS_CONNECT_COMPUTE_LOCK: &'static str =
|
pub const DEFAULT_OPTIONS_CONNECT_COMPUTE_LOCK: &'static str =
|
||||||
"shards=64,permits=100,epoch=10m,timeout=10ms";
|
"shards=64,permits=100,epoch=10m,timeout=10ms";
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use try_lock::TryLock;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
console::messages::{ColdStartInfo, MetricsAuxInfo},
|
control_plane::messages::{ColdStartInfo, MetricsAuxInfo},
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
intern::{BranchIdInt, ProjectIdInt},
|
intern::{BranchIdInt, ProjectIdInt},
|
||||||
metrics::{ConnectOutcome, InvalidEndpointsGroup, LatencyTimer, Metrics, Protocol, Waiting},
|
metrics::{ConnectOutcome, InvalidEndpointsGroup, LatencyTimer, Metrics, Protocol, Waiting},
|
||||||
@@ -10,14 +10,14 @@ use crate::proxy::retry::CouldRetry;
|
|||||||
/// Generic error response with human-readable description.
|
/// Generic error response with human-readable description.
|
||||||
/// Note that we can't always present it to user as is.
|
/// Note that we can't always present it to user as is.
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub(crate) struct ConsoleError {
|
pub(crate) struct ControlPlaneError {
|
||||||
pub(crate) error: Box<str>,
|
pub(crate) error: Box<str>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub(crate) http_status_code: http::StatusCode,
|
pub(crate) http_status_code: http::StatusCode,
|
||||||
pub(crate) status: Option<Status>,
|
pub(crate) status: Option<Status>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConsoleError {
|
impl ControlPlaneError {
|
||||||
pub(crate) fn get_reason(&self) -> Reason {
|
pub(crate) fn get_reason(&self) -> Reason {
|
||||||
self.status
|
self.status
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -51,7 +51,7 @@ impl ConsoleError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ConsoleError {
|
impl Display for ControlPlaneError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let msg: &str = self
|
let msg: &str = self
|
||||||
.status
|
.status
|
||||||
@@ -62,7 +62,7 @@ impl Display for ConsoleError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CouldRetry for ConsoleError {
|
impl CouldRetry for ControlPlaneError {
|
||||||
fn could_retry(&self) -> bool {
|
fn could_retry(&self) -> bool {
|
||||||
// If the error message does not have a status,
|
// If the error message does not have a status,
|
||||||
// the error is unknown and probably should not retry automatically
|
// the error is unknown and probably should not retry automatically
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
console::messages::{DatabaseInfo, KickSession},
|
control_plane::messages::{DatabaseInfo, KickSession},
|
||||||
waiters::{self, Waiter, Waiters},
|
waiters::{self, Waiter, Waiters},
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
@@ -10,7 +10,7 @@ use crate::{
|
|||||||
use crate::{auth::backend::ComputeUserInfo, compute, error::io_error, scram, url::ApiUrl};
|
use crate::{auth::backend::ComputeUserInfo, compute, error::io_error, scram, url::ApiUrl};
|
||||||
use crate::{auth::IpPattern, cache::Cached};
|
use crate::{auth::IpPattern, cache::Cached};
|
||||||
use crate::{
|
use crate::{
|
||||||
console::{
|
control_plane::{
|
||||||
messages::MetricsAuxInfo,
|
messages::MetricsAuxInfo,
|
||||||
provider::{CachedAllowedIps, CachedRoleSecret},
|
provider::{CachedAllowedIps, CachedRoleSecret},
|
||||||
},
|
},
|
||||||
@@ -166,7 +166,7 @@ impl Api {
|
|||||||
endpoint_id: (&EndpointId::from("endpoint")).into(),
|
endpoint_id: (&EndpointId::from("endpoint")).into(),
|
||||||
project_id: (&ProjectId::from("project")).into(),
|
project_id: (&ProjectId::from("project")).into(),
|
||||||
branch_id: (&BranchId::from("branch")).into(),
|
branch_id: (&BranchId::from("branch")).into(),
|
||||||
cold_start_info: crate::console::messages::ColdStartInfo::Warm,
|
cold_start_info: crate::control_plane::messages::ColdStartInfo::Warm,
|
||||||
},
|
},
|
||||||
allow_self_signed_compute: false,
|
allow_self_signed_compute: false,
|
||||||
};
|
};
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
pub mod mock;
|
pub mod mock;
|
||||||
pub mod neon;
|
pub mod neon;
|
||||||
|
|
||||||
use super::messages::{ConsoleError, MetricsAuxInfo};
|
use super::messages::{ControlPlaneError, MetricsAuxInfo};
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::{
|
auth::{
|
||||||
backend::{
|
backend::{
|
||||||
@@ -28,7 +28,7 @@ use tracing::info;
|
|||||||
|
|
||||||
pub(crate) mod errors {
|
pub(crate) mod errors {
|
||||||
use crate::{
|
use crate::{
|
||||||
console::messages::{self, ConsoleError, Reason},
|
control_plane::messages::{self, ControlPlaneError, Reason},
|
||||||
error::{io_error, ErrorKind, ReportableError, UserFacingError},
|
error::{io_error, ErrorKind, ReportableError, UserFacingError},
|
||||||
proxy::retry::CouldRetry,
|
proxy::retry::CouldRetry,
|
||||||
};
|
};
|
||||||
@@ -44,7 +44,7 @@ pub(crate) mod errors {
|
|||||||
pub(crate) enum ApiError {
|
pub(crate) enum ApiError {
|
||||||
/// Error returned by the console itself.
|
/// Error returned by the console itself.
|
||||||
#[error("{REQUEST_FAILED} with {0}")]
|
#[error("{REQUEST_FAILED} with {0}")]
|
||||||
Console(ConsoleError),
|
ControlPlane(ControlPlaneError),
|
||||||
|
|
||||||
/// Various IO errors like broken pipe or malformed payload.
|
/// Various IO errors like broken pipe or malformed payload.
|
||||||
#[error("{REQUEST_FAILED}: {0}")]
|
#[error("{REQUEST_FAILED}: {0}")]
|
||||||
@@ -55,7 +55,7 @@ pub(crate) mod errors {
|
|||||||
/// Returns HTTP status code if it's the reason for failure.
|
/// Returns HTTP status code if it's the reason for failure.
|
||||||
pub(crate) fn get_reason(&self) -> messages::Reason {
|
pub(crate) fn get_reason(&self) -> messages::Reason {
|
||||||
match self {
|
match self {
|
||||||
ApiError::Console(e) => e.get_reason(),
|
ApiError::ControlPlane(e) => e.get_reason(),
|
||||||
ApiError::Transport(_) => messages::Reason::Unknown,
|
ApiError::Transport(_) => messages::Reason::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ pub(crate) mod errors {
|
|||||||
fn to_string_client(&self) -> String {
|
fn to_string_client(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
// To minimize risks, only select errors are forwarded to users.
|
// To minimize risks, only select errors are forwarded to users.
|
||||||
ApiError::Console(c) => c.get_user_facing_message(),
|
ApiError::ControlPlane(c) => c.get_user_facing_message(),
|
||||||
ApiError::Transport(_) => REQUEST_FAILED.to_owned(),
|
ApiError::Transport(_) => REQUEST_FAILED.to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,51 +74,51 @@ pub(crate) mod errors {
|
|||||||
impl ReportableError for ApiError {
|
impl ReportableError for ApiError {
|
||||||
fn get_error_kind(&self) -> crate::error::ErrorKind {
|
fn get_error_kind(&self) -> crate::error::ErrorKind {
|
||||||
match self {
|
match self {
|
||||||
ApiError::Console(e) => match e.get_reason() {
|
ApiError::ControlPlane(e) => match e.get_reason() {
|
||||||
Reason::RoleProtected => ErrorKind::User,
|
Reason::RoleProtected => ErrorKind::User,
|
||||||
Reason::ResourceNotFound => ErrorKind::User,
|
Reason::ResourceNotFound => ErrorKind::User,
|
||||||
Reason::ProjectNotFound => ErrorKind::User,
|
Reason::ProjectNotFound => ErrorKind::User,
|
||||||
Reason::EndpointNotFound => ErrorKind::User,
|
Reason::EndpointNotFound => ErrorKind::User,
|
||||||
Reason::BranchNotFound => ErrorKind::User,
|
Reason::BranchNotFound => ErrorKind::User,
|
||||||
Reason::RateLimitExceeded => ErrorKind::ServiceRateLimit,
|
Reason::RateLimitExceeded => ErrorKind::ServiceRateLimit,
|
||||||
Reason::NonDefaultBranchComputeTimeExceeded => ErrorKind::User,
|
Reason::NonDefaultBranchComputeTimeExceeded => ErrorKind::Quota,
|
||||||
Reason::ActiveTimeQuotaExceeded => ErrorKind::User,
|
Reason::ActiveTimeQuotaExceeded => ErrorKind::Quota,
|
||||||
Reason::ComputeTimeQuotaExceeded => ErrorKind::User,
|
Reason::ComputeTimeQuotaExceeded => ErrorKind::Quota,
|
||||||
Reason::WrittenDataQuotaExceeded => ErrorKind::User,
|
Reason::WrittenDataQuotaExceeded => ErrorKind::Quota,
|
||||||
Reason::DataTransferQuotaExceeded => ErrorKind::User,
|
Reason::DataTransferQuotaExceeded => ErrorKind::Quota,
|
||||||
Reason::LogicalSizeQuotaExceeded => ErrorKind::User,
|
Reason::LogicalSizeQuotaExceeded => ErrorKind::Quota,
|
||||||
Reason::ConcurrencyLimitReached => ErrorKind::ControlPlane,
|
Reason::ConcurrencyLimitReached => ErrorKind::ControlPlane,
|
||||||
Reason::LockAlreadyTaken => ErrorKind::ControlPlane,
|
Reason::LockAlreadyTaken => ErrorKind::ControlPlane,
|
||||||
Reason::RunningOperations => ErrorKind::ControlPlane,
|
Reason::RunningOperations => ErrorKind::ControlPlane,
|
||||||
Reason::Unknown => match &e {
|
Reason::Unknown => match &e {
|
||||||
ConsoleError {
|
ControlPlaneError {
|
||||||
http_status_code:
|
http_status_code:
|
||||||
http::StatusCode::NOT_FOUND | http::StatusCode::NOT_ACCEPTABLE,
|
http::StatusCode::NOT_FOUND | http::StatusCode::NOT_ACCEPTABLE,
|
||||||
..
|
..
|
||||||
} => crate::error::ErrorKind::User,
|
} => crate::error::ErrorKind::User,
|
||||||
ConsoleError {
|
ControlPlaneError {
|
||||||
http_status_code: http::StatusCode::UNPROCESSABLE_ENTITY,
|
http_status_code: http::StatusCode::UNPROCESSABLE_ENTITY,
|
||||||
error,
|
error,
|
||||||
..
|
..
|
||||||
} if error
|
} if error
|
||||||
.contains("compute time quota of non-primary branches is exceeded") =>
|
.contains("compute time quota of non-primary branches is exceeded") =>
|
||||||
{
|
{
|
||||||
crate::error::ErrorKind::User
|
crate::error::ErrorKind::Quota
|
||||||
}
|
}
|
||||||
ConsoleError {
|
ControlPlaneError {
|
||||||
http_status_code: http::StatusCode::LOCKED,
|
http_status_code: http::StatusCode::LOCKED,
|
||||||
error,
|
error,
|
||||||
..
|
..
|
||||||
} if error.contains("quota exceeded")
|
} if error.contains("quota exceeded")
|
||||||
|| error.contains("the limit for current plan reached") =>
|
|| error.contains("the limit for current plan reached") =>
|
||||||
{
|
{
|
||||||
crate::error::ErrorKind::User
|
crate::error::ErrorKind::Quota
|
||||||
}
|
}
|
||||||
ConsoleError {
|
ControlPlaneError {
|
||||||
http_status_code: http::StatusCode::TOO_MANY_REQUESTS,
|
http_status_code: http::StatusCode::TOO_MANY_REQUESTS,
|
||||||
..
|
..
|
||||||
} => crate::error::ErrorKind::ServiceRateLimit,
|
} => crate::error::ErrorKind::ServiceRateLimit,
|
||||||
ConsoleError { .. } => crate::error::ErrorKind::ControlPlane,
|
ControlPlaneError { .. } => crate::error::ErrorKind::ControlPlane,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ApiError::Transport(_) => crate::error::ErrorKind::ControlPlane,
|
ApiError::Transport(_) => crate::error::ErrorKind::ControlPlane,
|
||||||
@@ -131,7 +131,7 @@ pub(crate) mod errors {
|
|||||||
match self {
|
match self {
|
||||||
// retry some transport errors
|
// retry some transport errors
|
||||||
Self::Transport(io) => io.could_retry(),
|
Self::Transport(io) => io.could_retry(),
|
||||||
Self::Console(e) => e.could_retry(),
|
Self::ControlPlane(e) => e.could_retry(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,12 +309,13 @@ impl NodeInfo {
|
|||||||
#[cfg(any(test, feature = "testing"))]
|
#[cfg(any(test, feature = "testing"))]
|
||||||
ComputeCredentialKeys::Password(password) => self.config.password(password),
|
ComputeCredentialKeys::Password(password) => self.config.password(password),
|
||||||
ComputeCredentialKeys::AuthKeys(auth_keys) => self.config.auth_keys(*auth_keys),
|
ComputeCredentialKeys::AuthKeys(auth_keys) => self.config.auth_keys(*auth_keys),
|
||||||
ComputeCredentialKeys::None => &mut self.config,
|
ComputeCredentialKeys::JwtPayload(_) | ComputeCredentialKeys::None => &mut self.config,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type NodeInfoCache = TimedLru<EndpointCacheKey, Result<NodeInfo, Box<ConsoleError>>>;
|
pub(crate) type NodeInfoCache =
|
||||||
|
TimedLru<EndpointCacheKey, Result<NodeInfo, Box<ControlPlaneError>>>;
|
||||||
pub(crate) type CachedNodeInfo = Cached<&'static NodeInfoCache, NodeInfo>;
|
pub(crate) type CachedNodeInfo = Cached<&'static NodeInfoCache, NodeInfo>;
|
||||||
pub(crate) type CachedRoleSecret = Cached<&'static ProjectInfoCacheImpl, Option<AuthSecret>>;
|
pub(crate) type CachedRoleSecret = Cached<&'static ProjectInfoCacheImpl, Option<AuthSecret>>;
|
||||||
pub(crate) type CachedAllowedIps = Cached<&'static ProjectInfoCacheImpl, Arc<Vec<IpPattern>>>;
|
pub(crate) type CachedAllowedIps = Cached<&'static ProjectInfoCacheImpl, Arc<Vec<IpPattern>>>;
|
||||||
@@ -353,28 +354,28 @@ pub(crate) trait Api {
|
|||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum ConsoleBackend {
|
pub enum ControlPlaneBackend {
|
||||||
/// Current Cloud API (V2).
|
/// Current Management API (V2).
|
||||||
Console(neon::Api),
|
Management(neon::Api),
|
||||||
/// Local mock of Cloud API (V2).
|
/// Local mock control plane.
|
||||||
#[cfg(any(test, feature = "testing"))]
|
#[cfg(any(test, feature = "testing"))]
|
||||||
Postgres(mock::Api),
|
PostgresMock(mock::Api),
|
||||||
/// Internal testing
|
/// Internal testing
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(private_interfaces)]
|
#[allow(private_interfaces)]
|
||||||
Test(Box<dyn crate::auth::backend::TestBackend>),
|
Test(Box<dyn crate::auth::backend::TestBackend>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Api for ConsoleBackend {
|
impl Api for ControlPlaneBackend {
|
||||||
async fn get_role_secret(
|
async fn get_role_secret(
|
||||||
&self,
|
&self,
|
||||||
ctx: &RequestMonitoring,
|
ctx: &RequestMonitoring,
|
||||||
user_info: &ComputeUserInfo,
|
user_info: &ComputeUserInfo,
|
||||||
) -> Result<CachedRoleSecret, errors::GetAuthInfoError> {
|
) -> Result<CachedRoleSecret, errors::GetAuthInfoError> {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(api) => api.get_role_secret(ctx, user_info).await,
|
Self::Management(api) => api.get_role_secret(ctx, user_info).await,
|
||||||
#[cfg(any(test, feature = "testing"))]
|
#[cfg(any(test, feature = "testing"))]
|
||||||
Self::Postgres(api) => api.get_role_secret(ctx, user_info).await,
|
Self::PostgresMock(api) => api.get_role_secret(ctx, user_info).await,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
Self::Test(_) => {
|
Self::Test(_) => {
|
||||||
unreachable!("this function should never be called in the test backend")
|
unreachable!("this function should never be called in the test backend")
|
||||||
@@ -388,9 +389,9 @@ impl Api for ConsoleBackend {
|
|||||||
user_info: &ComputeUserInfo,
|
user_info: &ComputeUserInfo,
|
||||||
) -> Result<(CachedAllowedIps, Option<CachedRoleSecret>), errors::GetAuthInfoError> {
|
) -> Result<(CachedAllowedIps, Option<CachedRoleSecret>), errors::GetAuthInfoError> {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(api) => api.get_allowed_ips_and_secret(ctx, user_info).await,
|
Self::Management(api) => api.get_allowed_ips_and_secret(ctx, user_info).await,
|
||||||
#[cfg(any(test, feature = "testing"))]
|
#[cfg(any(test, feature = "testing"))]
|
||||||
Self::Postgres(api) => api.get_allowed_ips_and_secret(ctx, user_info).await,
|
Self::PostgresMock(api) => api.get_allowed_ips_and_secret(ctx, user_info).await,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
Self::Test(api) => api.get_allowed_ips_and_secret(),
|
Self::Test(api) => api.get_allowed_ips_and_secret(),
|
||||||
}
|
}
|
||||||
@@ -402,9 +403,9 @@ impl Api for ConsoleBackend {
|
|||||||
endpoint: EndpointId,
|
endpoint: EndpointId,
|
||||||
) -> anyhow::Result<Vec<AuthRule>> {
|
) -> anyhow::Result<Vec<AuthRule>> {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(api) => api.get_endpoint_jwks(ctx, endpoint).await,
|
Self::Management(api) => api.get_endpoint_jwks(ctx, endpoint).await,
|
||||||
#[cfg(any(test, feature = "testing"))]
|
#[cfg(any(test, feature = "testing"))]
|
||||||
Self::Postgres(api) => api.get_endpoint_jwks(ctx, endpoint).await,
|
Self::PostgresMock(api) => api.get_endpoint_jwks(ctx, endpoint).await,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
Self::Test(_api) => Ok(vec![]),
|
Self::Test(_api) => Ok(vec![]),
|
||||||
}
|
}
|
||||||
@@ -416,16 +417,16 @@ impl Api for ConsoleBackend {
|
|||||||
user_info: &ComputeUserInfo,
|
user_info: &ComputeUserInfo,
|
||||||
) -> Result<CachedNodeInfo, errors::WakeComputeError> {
|
) -> Result<CachedNodeInfo, errors::WakeComputeError> {
|
||||||
match self {
|
match self {
|
||||||
Self::Console(api) => api.wake_compute(ctx, user_info).await,
|
Self::Management(api) => api.wake_compute(ctx, user_info).await,
|
||||||
#[cfg(any(test, feature = "testing"))]
|
#[cfg(any(test, feature = "testing"))]
|
||||||
Self::Postgres(api) => api.wake_compute(ctx, user_info).await,
|
Self::PostgresMock(api) => api.wake_compute(ctx, user_info).await,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
Self::Test(api) => api.wake_compute(),
|
Self::Test(api) => api.wake_compute(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Various caches for [`console`](super).
|
/// Various caches for [`control_plane`](super).
|
||||||
pub struct ApiCaches {
|
pub struct ApiCaches {
|
||||||
/// Cache for the `wake_compute` API method.
|
/// Cache for the `wake_compute` API method.
|
||||||
pub(crate) node_info: NodeInfoCache,
|
pub(crate) node_info: NodeInfoCache,
|
||||||
@@ -454,7 +455,7 @@ impl ApiCaches {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Various caches for [`console`](super).
|
/// Various caches for [`control_plane`](super).
|
||||||
pub struct ApiLocks<K> {
|
pub struct ApiLocks<K> {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
node_locks: DashMap<K, Arc<DynamicLimiter>>,
|
node_locks: DashMap<K, Arc<DynamicLimiter>>,
|
||||||
@@ -577,7 +578,7 @@ impl WakeComputePermit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FetchAuthRules for ConsoleBackend {
|
impl FetchAuthRules for ControlPlaneBackend {
|
||||||
async fn fetch_auth_rules(
|
async fn fetch_auth_rules(
|
||||||
&self,
|
&self,
|
||||||
ctx: &RequestMonitoring,
|
ctx: &RequestMonitoring,
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Production console backend.
|
//! Production console backend.
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
super::messages::{ConsoleError, GetRoleSecret, WakeCompute},
|
super::messages::{ControlPlaneError, GetRoleSecret, WakeCompute},
|
||||||
errors::{ApiError, GetAuthInfoError, WakeComputeError},
|
errors::{ApiError, GetAuthInfoError, WakeComputeError},
|
||||||
ApiCaches, ApiLocks, AuthInfo, AuthSecret, CachedAllowedIps, CachedNodeInfo, CachedRoleSecret,
|
ApiCaches, ApiLocks, AuthInfo, AuthSecret, CachedAllowedIps, CachedNodeInfo, CachedRoleSecret,
|
||||||
NodeInfo,
|
NodeInfo,
|
||||||
@@ -9,7 +9,7 @@ use super::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
auth::backend::{jwt::AuthRule, ComputeUserInfo},
|
auth::backend::{jwt::AuthRule, ComputeUserInfo},
|
||||||
compute,
|
compute,
|
||||||
console::messages::{ColdStartInfo, EndpointJwksResponse, Reason},
|
control_plane::messages::{ColdStartInfo, EndpointJwksResponse, Reason},
|
||||||
http,
|
http,
|
||||||
metrics::{CacheOutcome, Metrics},
|
metrics::{CacheOutcome, Metrics},
|
||||||
rate_limiter::WakeComputeRateLimiter,
|
rate_limiter::WakeComputeRateLimiter,
|
||||||
@@ -22,7 +22,7 @@ use futures::TryFutureExt;
|
|||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
use tokio_postgres::config::SslMode;
|
use tokio_postgres::config::SslMode;
|
||||||
use tracing::{debug, error, info, info_span, warn, Instrument};
|
use tracing::{debug, info, info_span, warn, Instrument};
|
||||||
|
|
||||||
const X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id");
|
const X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id");
|
||||||
|
|
||||||
@@ -348,7 +348,7 @@ impl super::Api for Api {
|
|||||||
let (cached, info) = cached.take_value();
|
let (cached, info) = cached.take_value();
|
||||||
let info = info.map_err(|c| {
|
let info = info.map_err(|c| {
|
||||||
info!(key = &*key, "found cached wake_compute error");
|
info!(key = &*key, "found cached wake_compute error");
|
||||||
WakeComputeError::ApiError(ApiError::Console(*c))
|
WakeComputeError::ApiError(ApiError::ControlPlane(*c))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
debug!(key = &*key, "found cached compute node info");
|
debug!(key = &*key, "found cached compute node info");
|
||||||
@@ -395,9 +395,9 @@ impl super::Api for Api {
|
|||||||
Ok(cached.map(|()| node))
|
Ok(cached.map(|()| node))
|
||||||
}
|
}
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
WakeComputeError::ApiError(ApiError::Console(err)) => {
|
WakeComputeError::ApiError(ApiError::ControlPlane(err)) => {
|
||||||
let Some(status) = &err.status else {
|
let Some(status) = &err.status else {
|
||||||
return Err(WakeComputeError::ApiError(ApiError::Console(err)));
|
return Err(WakeComputeError::ApiError(ApiError::ControlPlane(err)));
|
||||||
};
|
};
|
||||||
|
|
||||||
let reason = status
|
let reason = status
|
||||||
@@ -407,7 +407,7 @@ impl super::Api for Api {
|
|||||||
|
|
||||||
// if we can retry this error, do not cache it.
|
// if we can retry this error, do not cache it.
|
||||||
if reason.can_retry() {
|
if reason.can_retry() {
|
||||||
return Err(WakeComputeError::ApiError(ApiError::Console(err)));
|
return Err(WakeComputeError::ApiError(ApiError::ControlPlane(err)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// at this point, we should only have quota errors.
|
// at this point, we should only have quota errors.
|
||||||
@@ -422,7 +422,7 @@ impl super::Api for Api {
|
|||||||
Duration::from_secs(30),
|
Duration::from_secs(30),
|
||||||
);
|
);
|
||||||
|
|
||||||
Err(WakeComputeError::ApiError(ApiError::Console(err)))
|
Err(WakeComputeError::ApiError(ApiError::ControlPlane(err)))
|
||||||
}
|
}
|
||||||
err => return Err(err),
|
err => return Err(err),
|
||||||
},
|
},
|
||||||
@@ -448,7 +448,7 @@ async fn parse_body<T: for<'a> serde::Deserialize<'a>>(
|
|||||||
// as the fact that the request itself has failed.
|
// as the fact that the request itself has failed.
|
||||||
let mut body = serde_json::from_slice(&s).unwrap_or_else(|e| {
|
let mut body = serde_json::from_slice(&s).unwrap_or_else(|e| {
|
||||||
warn!("failed to parse error body: {e}");
|
warn!("failed to parse error body: {e}");
|
||||||
ConsoleError {
|
ControlPlaneError {
|
||||||
error: "reason unclear (malformed error message)".into(),
|
error: "reason unclear (malformed error message)".into(),
|
||||||
http_status_code: status,
|
http_status_code: status,
|
||||||
status: None,
|
status: None,
|
||||||
@@ -456,8 +456,8 @@ async fn parse_body<T: for<'a> serde::Deserialize<'a>>(
|
|||||||
});
|
});
|
||||||
body.http_status_code = status;
|
body.http_status_code = status;
|
||||||
|
|
||||||
error!("console responded with an error ({status}): {body:?}");
|
warn!("console responded with an error ({status}): {body:?}");
|
||||||
Err(ApiError::Console(body))
|
Err(ApiError::ControlPlane(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_host_port(input: &str) -> Option<(&str, u16)> {
|
fn parse_host_port(input: &str) -> Option<(&str, u16)> {
|
||||||
@@ -49,6 +49,10 @@ pub enum ErrorKind {
|
|||||||
#[label(rename = "serviceratelimit")]
|
#[label(rename = "serviceratelimit")]
|
||||||
ServiceRateLimit,
|
ServiceRateLimit,
|
||||||
|
|
||||||
|
/// Proxy quota limit violation
|
||||||
|
#[label(rename = "quota")]
|
||||||
|
Quota,
|
||||||
|
|
||||||
/// internal errors
|
/// internal errors
|
||||||
Service,
|
Service,
|
||||||
|
|
||||||
@@ -70,6 +74,7 @@ impl ErrorKind {
|
|||||||
ErrorKind::ClientDisconnect => "clientdisconnect",
|
ErrorKind::ClientDisconnect => "clientdisconnect",
|
||||||
ErrorKind::RateLimit => "ratelimit",
|
ErrorKind::RateLimit => "ratelimit",
|
||||||
ErrorKind::ServiceRateLimit => "serviceratelimit",
|
ErrorKind::ServiceRateLimit => "serviceratelimit",
|
||||||
|
ErrorKind::Quota => "quota",
|
||||||
ErrorKind::Service => "service",
|
ErrorKind::Service => "service",
|
||||||
ErrorKind::ControlPlane => "controlplane",
|
ErrorKind::ControlPlane => "controlplane",
|
||||||
ErrorKind::Postgres => "postgres",
|
ErrorKind::Postgres => "postgres",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use anyhow::{anyhow, bail};
|
use anyhow::{anyhow, bail};
|
||||||
use hyper::{header::CONTENT_TYPE, Body, Request, Response, StatusCode};
|
use hyper0::{header::CONTENT_TYPE, Body, Request, Response, StatusCode};
|
||||||
use measured::{text::BufferedTextEncoder, MetricGroup};
|
use measured::{text::BufferedTextEncoder, MetricGroup};
|
||||||
use metrics::NeonMetrics;
|
use metrics::NeonMetrics;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -21,7 +21,7 @@ async fn status_handler(_: Request<Body>) -> Result<Response<Body>, ApiError> {
|
|||||||
json_response(StatusCode::OK, "")
|
json_response(StatusCode::OK, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_router(metrics: AppMetrics) -> RouterBuilder<hyper::Body, ApiError> {
|
fn make_router(metrics: AppMetrics) -> RouterBuilder<hyper0::Body, ApiError> {
|
||||||
let state = Arc::new(Mutex::new(PrometheusHandler {
|
let state = Arc::new(Mutex::new(PrometheusHandler {
|
||||||
encoder: BufferedTextEncoder::new(),
|
encoder: BufferedTextEncoder::new(),
|
||||||
metrics,
|
metrics,
|
||||||
@@ -45,7 +45,7 @@ pub async fn task_main(
|
|||||||
|
|
||||||
let service = || RouterService::new(make_router(metrics).build()?);
|
let service = || RouterService::new(make_router(metrics).build()?);
|
||||||
|
|
||||||
hyper::Server::from_tcp(http_listener)?
|
hyper0::Server::from_tcp(http_listener)?
|
||||||
.serve(service().map_err(|e| anyhow!(e))?)
|
.serve(service().map_err(|e| anyhow!(e))?)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use std::time::Duration;
|
|||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http_body_util::BodyExt;
|
use http_body_util::BodyExt;
|
||||||
use hyper1::body::Body;
|
use hyper::body::Body;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
pub(crate) use reqwest::{Request, Response};
|
pub(crate) use reqwest::{Request, Response};
|
||||||
+1
-3
@@ -90,15 +90,13 @@ use tokio::task::JoinError;
|
|||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
extern crate hyper0 as hyper;
|
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod cancellation;
|
pub mod cancellation;
|
||||||
pub mod compute;
|
pub mod compute;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod console;
|
|
||||||
pub mod context;
|
pub mod context;
|
||||||
|
pub mod control_plane;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
pub mod intern;
|
pub mod intern;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use metrics::{CounterPairAssoc, CounterPairVec, HyperLogLog, HyperLogLogVec};
|
|||||||
|
|
||||||
use tokio::time::{self, Instant};
|
use tokio::time::{self, Instant};
|
||||||
|
|
||||||
use crate::console::messages::ColdStartInfo;
|
use crate::control_plane::messages::ColdStartInfo;
|
||||||
|
|
||||||
#[derive(MetricGroup)]
|
#[derive(MetricGroup)]
|
||||||
#[metric(new(thread_pool: Arc<ThreadPoolMetrics>))]
|
#[metric(new(thread_pool: Arc<ThreadPoolMetrics>))]
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use crate::{
|
|||||||
compute::COULD_NOT_CONNECT,
|
compute::COULD_NOT_CONNECT,
|
||||||
compute::{self, PostgresConnection},
|
compute::{self, PostgresConnection},
|
||||||
config::RetryConfig,
|
config::RetryConfig,
|
||||||
console::{self, errors::WakeComputeError, locks::ApiLocks, CachedNodeInfo, NodeInfo},
|
|
||||||
context::RequestMonitoring,
|
context::RequestMonitoring,
|
||||||
|
control_plane::{self, errors::WakeComputeError, locks::ApiLocks, CachedNodeInfo, NodeInfo},
|
||||||
error::ReportableError,
|
error::ReportableError,
|
||||||
metrics::{ConnectOutcome, ConnectionFailureKind, Metrics, RetriesMetricGroup, RetryType},
|
metrics::{ConnectOutcome, ConnectionFailureKind, Metrics, RetriesMetricGroup, RetryType},
|
||||||
proxy::{
|
proxy::{
|
||||||
@@ -26,7 +26,7 @@ const CONNECT_TIMEOUT: time::Duration = time::Duration::from_secs(2);
|
|||||||
/// (e.g. the compute node's address might've changed at the wrong time).
|
/// (e.g. the compute node's address might've changed at the wrong time).
|
||||||
/// Invalidate the cache entry (if any) to prevent subsequent errors.
|
/// Invalidate the cache entry (if any) to prevent subsequent errors.
|
||||||
#[tracing::instrument(name = "invalidate_cache", skip_all)]
|
#[tracing::instrument(name = "invalidate_cache", skip_all)]
|
||||||
pub(crate) fn invalidate_cache(node_info: console::CachedNodeInfo) -> NodeInfo {
|
pub(crate) fn invalidate_cache(node_info: control_plane::CachedNodeInfo) -> NodeInfo {
|
||||||
let is_cached = node_info.cached();
|
let is_cached = node_info.cached();
|
||||||
if is_cached {
|
if is_cached {
|
||||||
warn!("invalidating stalled compute node info cache entry");
|
warn!("invalidating stalled compute node info cache entry");
|
||||||
@@ -49,7 +49,7 @@ pub(crate) trait ConnectMechanism {
|
|||||||
async fn connect_once(
|
async fn connect_once(
|
||||||
&self,
|
&self,
|
||||||
ctx: &RequestMonitoring,
|
ctx: &RequestMonitoring,
|
||||||
node_info: &console::CachedNodeInfo,
|
node_info: &control_plane::CachedNodeInfo,
|
||||||
timeout: time::Duration,
|
timeout: time::Duration,
|
||||||
) -> Result<Self::Connection, Self::ConnectError>;
|
) -> Result<Self::Connection, Self::ConnectError>;
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ pub(crate) trait ComputeConnectBackend {
|
|||||||
async fn wake_compute(
|
async fn wake_compute(
|
||||||
&self,
|
&self,
|
||||||
ctx: &RequestMonitoring,
|
ctx: &RequestMonitoring,
|
||||||
) -> Result<CachedNodeInfo, console::errors::WakeComputeError>;
|
) -> Result<CachedNodeInfo, control_plane::errors::WakeComputeError>;
|
||||||
|
|
||||||
fn get_keys(&self) -> &ComputeCredentialKeys;
|
fn get_keys(&self) -> &ComputeCredentialKeys;
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ impl ConnectMechanism for TcpMechanism<'_> {
|
|||||||
async fn connect_once(
|
async fn connect_once(
|
||||||
&self,
|
&self,
|
||||||
ctx: &RequestMonitoring,
|
ctx: &RequestMonitoring,
|
||||||
node_info: &console::CachedNodeInfo,
|
node_info: &control_plane::CachedNodeInfo,
|
||||||
timeout: time::Duration,
|
timeout: time::Duration,
|
||||||
) -> Result<PostgresConnection, Self::Error> {
|
) -> Result<PostgresConnection, Self::Error> {
|
||||||
let host = node_info.config.get_host()?;
|
let host = node_info.config.get_host()?;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ use std::sync::Arc;
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
|
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing::{error, info, Instrument};
|
use tracing::{error, info, warn, Instrument};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
connect_compute::{connect_to_compute, TcpMechanism},
|
connect_compute::{connect_to_compute, TcpMechanism},
|
||||||
@@ -61,6 +61,7 @@ pub async fn run_until_cancelled<F: std::future::Future>(
|
|||||||
|
|
||||||
pub async fn task_main(
|
pub async fn task_main(
|
||||||
config: &'static ProxyConfig,
|
config: &'static ProxyConfig,
|
||||||
|
auth_backend: &'static auth::Backend<'static, (), ()>,
|
||||||
listener: tokio::net::TcpListener,
|
listener: tokio::net::TcpListener,
|
||||||
cancellation_token: CancellationToken,
|
cancellation_token: CancellationToken,
|
||||||
cancellation_handler: Arc<CancellationHandlerMain>,
|
cancellation_handler: Arc<CancellationHandlerMain>,
|
||||||
@@ -95,15 +96,15 @@ pub async fn task_main(
|
|||||||
connections.spawn(async move {
|
connections.spawn(async move {
|
||||||
let (socket, peer_addr) = match read_proxy_protocol(socket).await {
|
let (socket, peer_addr) = match read_proxy_protocol(socket).await {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("per-client task finished with an error: {e:#}");
|
warn!("per-client task finished with an error: {e:#}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Ok((_socket, None)) if config.proxy_protocol_v2 == ProxyProtocolV2::Required => {
|
Ok((_socket, None)) if config.proxy_protocol_v2 == ProxyProtocolV2::Required => {
|
||||||
error!("missing required proxy protocol header");
|
warn!("missing required proxy protocol header");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Ok((_socket, Some(_))) if config.proxy_protocol_v2 == ProxyProtocolV2::Rejected => {
|
Ok((_socket, Some(_))) if config.proxy_protocol_v2 == ProxyProtocolV2::Rejected => {
|
||||||
error!("proxy protocol header not supported");
|
warn!("proxy protocol header not supported");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Ok((socket, Some(addr))) => (socket, addr.ip()),
|
Ok((socket, Some(addr))) => (socket, addr.ip()),
|
||||||
@@ -129,6 +130,7 @@ pub async fn task_main(
|
|||||||
let startup = Box::pin(
|
let startup = Box::pin(
|
||||||
handle_client(
|
handle_client(
|
||||||
config,
|
config,
|
||||||
|
auth_backend,
|
||||||
&ctx,
|
&ctx,
|
||||||
cancellation_handler,
|
cancellation_handler,
|
||||||
socket,
|
socket,
|
||||||
@@ -144,7 +146,7 @@ pub async fn task_main(
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
// todo: log and push to ctx the error kind
|
// todo: log and push to ctx the error kind
|
||||||
ctx.set_error_kind(e.get_error_kind());
|
ctx.set_error_kind(e.get_error_kind());
|
||||||
error!(parent: &span, "per-client task finished with an error: {e:#}");
|
warn!(parent: &span, "per-client task finished with an error: {e:#}");
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
ctx.set_success();
|
ctx.set_success();
|
||||||
@@ -155,7 +157,7 @@ pub async fn task_main(
|
|||||||
match p.proxy_pass().instrument(span.clone()).await {
|
match p.proxy_pass().instrument(span.clone()).await {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(ErrorSource::Client(e)) => {
|
Err(ErrorSource::Client(e)) => {
|
||||||
error!(parent: &span, "per-client task finished with an IO error from the client: {e:#}");
|
warn!(parent: &span, "per-client task finished with an IO error from the client: {e:#}");
|
||||||
}
|
}
|
||||||
Err(ErrorSource::Compute(e)) => {
|
Err(ErrorSource::Compute(e)) => {
|
||||||
error!(parent: &span, "per-client task finished with an IO error from the compute: {e:#}");
|
error!(parent: &span, "per-client task finished with an IO error from the compute: {e:#}");
|
||||||
@@ -243,8 +245,10 @@ impl ReportableError for ClientRequestError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) async fn handle_client<S: AsyncRead + AsyncWrite + Unpin>(
|
pub(crate) async fn handle_client<S: AsyncRead + AsyncWrite + Unpin>(
|
||||||
config: &'static ProxyConfig,
|
config: &'static ProxyConfig,
|
||||||
|
auth_backend: &'static auth::Backend<'static, (), ()>,
|
||||||
ctx: &RequestMonitoring,
|
ctx: &RequestMonitoring,
|
||||||
cancellation_handler: Arc<CancellationHandlerMain>,
|
cancellation_handler: Arc<CancellationHandlerMain>,
|
||||||
stream: S,
|
stream: S,
|
||||||
@@ -285,8 +289,7 @@ pub(crate) async fn handle_client<S: AsyncRead + AsyncWrite + Unpin>(
|
|||||||
let common_names = tls.map(|tls| &tls.common_names);
|
let common_names = tls.map(|tls| &tls.common_names);
|
||||||
|
|
||||||
// Extract credentials which we're going to use for auth.
|
// Extract credentials which we're going to use for auth.
|
||||||
let result = config
|
let result = auth_backend
|
||||||
.auth_backend
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|()| auth::ComputeUserInfoMaybeEndpoint::parse(ctx, ¶ms, hostname, common_names))
|
.map(|()| auth::ComputeUserInfoMaybeEndpoint::parse(ctx, ¶ms, hostname, common_names))
|
||||||
.transpose();
|
.transpose();
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
cancellation,
|
cancellation,
|
||||||
compute::PostgresConnection,
|
compute::PostgresConnection,
|
||||||
console::messages::MetricsAuxInfo,
|
control_plane::messages::MetricsAuxInfo,
|
||||||
metrics::{Direction, Metrics, NumClientConnectionsGuard, NumConnectionRequestsGuard},
|
metrics::{Direction, Metrics, NumClientConnectionsGuard, NumConnectionRequestsGuard},
|
||||||
stream::Stream,
|
stream::Stream,
|
||||||
usage_metrics::{Ids, MetricCounterRecorder, USAGE_METRICS},
|
usage_metrics::{Ids, MetricCounterRecorder, USAGE_METRICS},
|
||||||
@@ -71,7 +71,7 @@ impl<P, S: AsyncRead + AsyncWrite + Unpin> ProxyPassthrough<P, S> {
|
|||||||
pub(crate) async fn proxy_pass(self) -> Result<(), ErrorSource> {
|
pub(crate) async fn proxy_pass(self) -> Result<(), ErrorSource> {
|
||||||
let res = proxy_pass(self.client, self.compute.stream, self.aux).await;
|
let res = proxy_pass(self.client, self.compute.stream, self.aux).await;
|
||||||
if let Err(err) = self.compute.cancel_closure.try_cancel_query().await {
|
if let Err(err) = self.compute.cancel_closure.try_cancel_query().await {
|
||||||
tracing::error!(?err, "could not cancel the query in the database");
|
tracing::warn!(?err, "could not cancel the query in the database");
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ use crate::auth::backend::{
|
|||||||
ComputeCredentialKeys, ComputeCredentials, ComputeUserInfo, MaybeOwned, TestBackend,
|
ComputeCredentialKeys, ComputeCredentials, ComputeUserInfo, MaybeOwned, TestBackend,
|
||||||
};
|
};
|
||||||
use crate::config::{CertResolver, RetryConfig};
|
use crate::config::{CertResolver, RetryConfig};
|
||||||
use crate::console::messages::{ConsoleError, Details, MetricsAuxInfo, Status};
|
use crate::control_plane::messages::{ControlPlaneError, Details, MetricsAuxInfo, Status};
|
||||||
use crate::console::provider::{CachedAllowedIps, CachedRoleSecret, ConsoleBackend, NodeInfoCache};
|
use crate::control_plane::provider::{
|
||||||
use crate::console::{self, CachedNodeInfo, NodeInfo};
|
CachedAllowedIps, CachedRoleSecret, ControlPlaneBackend, NodeInfoCache,
|
||||||
|
};
|
||||||
|
use crate::control_plane::{self, CachedNodeInfo, NodeInfo};
|
||||||
use crate::error::ErrorKind;
|
use crate::error::ErrorKind;
|
||||||
use crate::{sasl, scram, BranchId, EndpointId, ProjectId};
|
use crate::{sasl, scram, BranchId, EndpointId, ProjectId};
|
||||||
use anyhow::{bail, Context};
|
use anyhow::{bail, Context};
|
||||||
@@ -459,7 +461,7 @@ impl ConnectMechanism for TestConnectMechanism {
|
|||||||
async fn connect_once(
|
async fn connect_once(
|
||||||
&self,
|
&self,
|
||||||
_ctx: &RequestMonitoring,
|
_ctx: &RequestMonitoring,
|
||||||
_node_info: &console::CachedNodeInfo,
|
_node_info: &control_plane::CachedNodeInfo,
|
||||||
_timeout: std::time::Duration,
|
_timeout: std::time::Duration,
|
||||||
) -> Result<Self::Connection, Self::ConnectError> {
|
) -> Result<Self::Connection, Self::ConnectError> {
|
||||||
let mut counter = self.counter.lock().unwrap();
|
let mut counter = self.counter.lock().unwrap();
|
||||||
@@ -483,23 +485,23 @@ impl ConnectMechanism for TestConnectMechanism {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TestBackend for TestConnectMechanism {
|
impl TestBackend for TestConnectMechanism {
|
||||||
fn wake_compute(&self) -> Result<CachedNodeInfo, console::errors::WakeComputeError> {
|
fn wake_compute(&self) -> Result<CachedNodeInfo, control_plane::errors::WakeComputeError> {
|
||||||
let mut counter = self.counter.lock().unwrap();
|
let mut counter = self.counter.lock().unwrap();
|
||||||
let action = self.sequence[*counter];
|
let action = self.sequence[*counter];
|
||||||
*counter += 1;
|
*counter += 1;
|
||||||
match action {
|
match action {
|
||||||
ConnectAction::Wake => Ok(helper_create_cached_node_info(self.cache)),
|
ConnectAction::Wake => Ok(helper_create_cached_node_info(self.cache)),
|
||||||
ConnectAction::WakeFail => {
|
ConnectAction::WakeFail => {
|
||||||
let err = console::errors::ApiError::Console(ConsoleError {
|
let err = control_plane::errors::ApiError::ControlPlane(ControlPlaneError {
|
||||||
http_status_code: StatusCode::BAD_REQUEST,
|
http_status_code: StatusCode::BAD_REQUEST,
|
||||||
error: "TEST".into(),
|
error: "TEST".into(),
|
||||||
status: None,
|
status: None,
|
||||||
});
|
});
|
||||||
assert!(!err.could_retry());
|
assert!(!err.could_retry());
|
||||||
Err(console::errors::WakeComputeError::ApiError(err))
|
Err(control_plane::errors::WakeComputeError::ApiError(err))
|
||||||
}
|
}
|
||||||
ConnectAction::WakeRetry => {
|
ConnectAction::WakeRetry => {
|
||||||
let err = console::errors::ApiError::Console(ConsoleError {
|
let err = control_plane::errors::ApiError::ControlPlane(ControlPlaneError {
|
||||||
http_status_code: StatusCode::BAD_REQUEST,
|
http_status_code: StatusCode::BAD_REQUEST,
|
||||||
error: "TEST".into(),
|
error: "TEST".into(),
|
||||||
status: Some(Status {
|
status: Some(Status {
|
||||||
@@ -507,13 +509,15 @@ impl TestBackend for TestConnectMechanism {
|
|||||||
message: "error".into(),
|
message: "error".into(),
|
||||||
details: Details {
|
details: Details {
|
||||||
error_info: None,
|
error_info: None,
|
||||||
retry_info: Some(console::messages::RetryInfo { retry_delay_ms: 1 }),
|
retry_info: Some(control_plane::messages::RetryInfo {
|
||||||
|
retry_delay_ms: 1,
|
||||||
|
}),
|
||||||
user_facing_message: None,
|
user_facing_message: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
assert!(err.could_retry());
|
assert!(err.could_retry());
|
||||||
Err(console::errors::WakeComputeError::ApiError(err))
|
Err(control_plane::errors::WakeComputeError::ApiError(err))
|
||||||
}
|
}
|
||||||
x => panic!("expecting action {x:?}, wake_compute is called instead"),
|
x => panic!("expecting action {x:?}, wake_compute is called instead"),
|
||||||
}
|
}
|
||||||
@@ -521,7 +525,7 @@ impl TestBackend for TestConnectMechanism {
|
|||||||
|
|
||||||
fn get_allowed_ips_and_secret(
|
fn get_allowed_ips_and_secret(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<(CachedAllowedIps, Option<CachedRoleSecret>), console::errors::GetAuthInfoError>
|
) -> Result<(CachedAllowedIps, Option<CachedRoleSecret>), control_plane::errors::GetAuthInfoError>
|
||||||
{
|
{
|
||||||
unimplemented!("not used in tests")
|
unimplemented!("not used in tests")
|
||||||
}
|
}
|
||||||
@@ -538,7 +542,7 @@ fn helper_create_cached_node_info(cache: &'static NodeInfoCache) -> CachedNodeIn
|
|||||||
endpoint_id: (&EndpointId::from("endpoint")).into(),
|
endpoint_id: (&EndpointId::from("endpoint")).into(),
|
||||||
project_id: (&ProjectId::from("project")).into(),
|
project_id: (&ProjectId::from("project")).into(),
|
||||||
branch_id: (&BranchId::from("branch")).into(),
|
branch_id: (&BranchId::from("branch")).into(),
|
||||||
cold_start_info: crate::console::messages::ColdStartInfo::Warm,
|
cold_start_info: crate::control_plane::messages::ColdStartInfo::Warm,
|
||||||
},
|
},
|
||||||
allow_self_signed_compute: false,
|
allow_self_signed_compute: false,
|
||||||
};
|
};
|
||||||
@@ -549,8 +553,8 @@ fn helper_create_cached_node_info(cache: &'static NodeInfoCache) -> CachedNodeIn
|
|||||||
fn helper_create_connect_info(
|
fn helper_create_connect_info(
|
||||||
mechanism: &TestConnectMechanism,
|
mechanism: &TestConnectMechanism,
|
||||||
) -> auth::Backend<'static, ComputeCredentials, &()> {
|
) -> auth::Backend<'static, ComputeCredentials, &()> {
|
||||||
let user_info = auth::Backend::Console(
|
let user_info = auth::Backend::ControlPlane(
|
||||||
MaybeOwned::Owned(ConsoleBackend::Test(Box::new(mechanism.clone()))),
|
MaybeOwned::Owned(ControlPlaneBackend::Test(Box::new(mechanism.clone()))),
|
||||||
ComputeCredentials {
|
ComputeCredentials {
|
||||||
info: ComputeUserInfo {
|
info: ComputeUserInfo {
|
||||||
endpoint: "endpoint".into(),
|
endpoint: "endpoint".into(),
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
use crate::config::RetryConfig;
|
use crate::config::RetryConfig;
|
||||||
use crate::console::messages::{ConsoleError, Reason};
|
|
||||||
use crate::console::{errors::WakeComputeError, provider::CachedNodeInfo};
|
|
||||||
use crate::context::RequestMonitoring;
|
use crate::context::RequestMonitoring;
|
||||||
|
use crate::control_plane::messages::{ControlPlaneError, Reason};
|
||||||
|
use crate::control_plane::{errors::WakeComputeError, provider::CachedNodeInfo};
|
||||||
use crate::metrics::{
|
use crate::metrics::{
|
||||||
ConnectOutcome, ConnectionFailuresBreakdownGroup, Metrics, RetriesMetricGroup, RetryType,
|
ConnectOutcome, ConnectionFailuresBreakdownGroup, Metrics, RetriesMetricGroup, RetryType,
|
||||||
WakeupFailureKind,
|
WakeupFailureKind,
|
||||||
};
|
};
|
||||||
use crate::proxy::retry::{retry_after, should_retry};
|
use crate::proxy::retry::{retry_after, should_retry};
|
||||||
use hyper1::StatusCode;
|
use hyper::StatusCode;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use super::connect_compute::ComputeConnectBackend;
|
use super::connect_compute::ComputeConnectBackend;
|
||||||
@@ -59,11 +59,11 @@ pub(crate) async fn wake_compute<B: ComputeConnectBackend>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn report_error(e: &WakeComputeError, retry: bool) {
|
fn report_error(e: &WakeComputeError, retry: bool) {
|
||||||
use crate::console::errors::ApiError;
|
use crate::control_plane::errors::ApiError;
|
||||||
let kind = match e {
|
let kind = match e {
|
||||||
WakeComputeError::BadComputeAddress(_) => WakeupFailureKind::BadComputeAddress,
|
WakeComputeError::BadComputeAddress(_) => WakeupFailureKind::BadComputeAddress,
|
||||||
WakeComputeError::ApiError(ApiError::Transport(_)) => WakeupFailureKind::ApiTransportError,
|
WakeComputeError::ApiError(ApiError::Transport(_)) => WakeupFailureKind::ApiTransportError,
|
||||||
WakeComputeError::ApiError(ApiError::Console(e)) => match e.get_reason() {
|
WakeComputeError::ApiError(ApiError::ControlPlane(e)) => match e.get_reason() {
|
||||||
Reason::RoleProtected => WakeupFailureKind::ApiConsoleBadRequest,
|
Reason::RoleProtected => WakeupFailureKind::ApiConsoleBadRequest,
|
||||||
Reason::ResourceNotFound => WakeupFailureKind::ApiConsoleBadRequest,
|
Reason::ResourceNotFound => WakeupFailureKind::ApiConsoleBadRequest,
|
||||||
Reason::ProjectNotFound => WakeupFailureKind::ApiConsoleBadRequest,
|
Reason::ProjectNotFound => WakeupFailureKind::ApiConsoleBadRequest,
|
||||||
@@ -80,7 +80,7 @@ fn report_error(e: &WakeComputeError, retry: bool) {
|
|||||||
Reason::LockAlreadyTaken => WakeupFailureKind::ApiConsoleLocked,
|
Reason::LockAlreadyTaken => WakeupFailureKind::ApiConsoleLocked,
|
||||||
Reason::RunningOperations => WakeupFailureKind::ApiConsoleLocked,
|
Reason::RunningOperations => WakeupFailureKind::ApiConsoleLocked,
|
||||||
Reason::Unknown => match e {
|
Reason::Unknown => match e {
|
||||||
ConsoleError {
|
ControlPlaneError {
|
||||||
http_status_code: StatusCode::LOCKED,
|
http_status_code: StatusCode::LOCKED,
|
||||||
ref error,
|
ref error,
|
||||||
..
|
..
|
||||||
@@ -89,27 +89,27 @@ fn report_error(e: &WakeComputeError, retry: bool) {
|
|||||||
{
|
{
|
||||||
WakeupFailureKind::QuotaExceeded
|
WakeupFailureKind::QuotaExceeded
|
||||||
}
|
}
|
||||||
ConsoleError {
|
ControlPlaneError {
|
||||||
http_status_code: StatusCode::UNPROCESSABLE_ENTITY,
|
http_status_code: StatusCode::UNPROCESSABLE_ENTITY,
|
||||||
ref error,
|
ref error,
|
||||||
..
|
..
|
||||||
} if error.contains("compute time quota of non-primary branches is exceeded") => {
|
} if error.contains("compute time quota of non-primary branches is exceeded") => {
|
||||||
WakeupFailureKind::QuotaExceeded
|
WakeupFailureKind::QuotaExceeded
|
||||||
}
|
}
|
||||||
ConsoleError {
|
ControlPlaneError {
|
||||||
http_status_code: StatusCode::LOCKED,
|
http_status_code: StatusCode::LOCKED,
|
||||||
..
|
..
|
||||||
} => WakeupFailureKind::ApiConsoleLocked,
|
} => WakeupFailureKind::ApiConsoleLocked,
|
||||||
ConsoleError {
|
ControlPlaneError {
|
||||||
http_status_code: StatusCode::BAD_REQUEST,
|
http_status_code: StatusCode::BAD_REQUEST,
|
||||||
..
|
..
|
||||||
} => WakeupFailureKind::ApiConsoleBadRequest,
|
} => WakeupFailureKind::ApiConsoleBadRequest,
|
||||||
ConsoleError {
|
ControlPlaneError {
|
||||||
http_status_code, ..
|
http_status_code, ..
|
||||||
} if http_status_code.is_server_error() => {
|
} if http_status_code.is_server_error() => {
|
||||||
WakeupFailureKind::ApiConsoleOtherServerError
|
WakeupFailureKind::ApiConsoleOtherServerError
|
||||||
}
|
}
|
||||||
ConsoleError { .. } => WakeupFailureKind::ApiConsoleOtherError,
|
ControlPlaneError { .. } => WakeupFailureKind::ApiConsoleOtherError,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
WakeComputeError::TooManyConnections => WakeupFailureKind::ApiConsoleLocked,
|
WakeComputeError::TooManyConnections => WakeupFailureKind::ApiConsoleLocked,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use redis::{
|
|||||||
ConnectionInfo, IntoConnectionInfo, RedisConnectionInfo, RedisResult,
|
ConnectionInfo, IntoConnectionInfo, RedisConnectionInfo, RedisResult,
|
||||||
};
|
};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
use super::elasticache::CredentialsProvider;
|
use super::elasticache::CredentialsProvider;
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ impl ConnectionWithCredentialsProvider {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error during PING: {e:?}");
|
warn!("Error during PING: {e:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -121,7 +121,7 @@ impl ConnectionWithCredentialsProvider {
|
|||||||
info!("Connection succesfully established");
|
info!("Connection succesfully established");
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Connection is broken. Error during PING: {e:?}");
|
warn!("Connection is broken. Error during PING: {e:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.con = Some(con);
|
self.con = Some(con);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user