Compare commits

...

65 Commits

Author SHA1 Message Date
Conrad Ludgate fe8b93ab9d fix Payload deser 2024-10-14 14:02:46 +01:00
Conrad Ludgate 7e3e7f1cca turns out we don't actually need to deser everything 2024-10-14 11:58:53 +01:00
Conrad Ludgate 0b0ed662d9 proxy: use RawValue to lazily process inputs 2024-10-14 11:48:58 +01:00
Conrad Ludgate 50bd65769f a seemingly random change... 2024-10-14 11:44:18 +01:00
Conrad Ludgate 90534b1745 remove iterator join 2024-10-14 11:42:20 +01:00
Conrad Ludgate 99d52df475 proxy: slight refactor to json parsing 2024-10-14 11:38:18 +01:00
Conrad Ludgate ab5bbb445b proxy: refactor auth backends (#9271)
preliminary for #9270 

The auth::Backend didn't need to be in the mega ProxyConfig object, so I
split it off and passed it manually in the few places it was necessary.

I've also refined some of the uses of config I saw while doing this
small refactor.

I've also followed the trend and make the console redirect backend it's
own struct, same as LocalBackend and ControlPlaneBackend.
2024-10-11 20:14:52 +01:00
Alexander Bayandin 5ef805e12c CI(run-python-test-set): allow to skip missing compatibility snapshot (#9365)
## Problem
Action `run-python-test-set` fails if it is not used for `regress_tests`
on release PR, because it expects
`test_compatibility.py::test_create_snapshot` to generate a snapshot,
and the test exists only in `regress_tests` suite.
For example, in https://github.com/neondatabase/neon/pull/9291
[`test-postgres-client-libs`](https://github.com/neondatabase/neon/actions/runs/11209615321/job/31155111544)
job failed.

## Summary of changes
- Add `skip-if-does-not-exist` input to `.github/actions/upload` action
(the same way we do for `.github/actions/download`)
- Set `skip-if-does-not-exist=true` for "Upload compatibility snapshot"
step in `run-python-test-set` action
2024-10-11 16:58:41 +01:00
a-masterov 091a175a3e Test versions mismatch (#9167)
## Problem
We faced the problem of incompatibility of the different components of
different versions.
This should be detected automatically to prevent production bugs.
## Summary of changes
The test for this situation was implemented

Co-authored-by: Alexander Bayandin <alexander@neon.tech>
2024-10-11 15:29:54 +02:00
Fedor Dikarev 326cd80f0d ci: gh-workflow-stats-action v0.1.4: remove debug output and proper pagination (#9356)
## Problem
In previous version pagination didn't work so we collect information
only for first 30 jobs in WorkflowRun
2024-10-11 14:46:45 +02:00
Folke Behrens 6baf1aae33 proxy: Demote some errors to warnings in logs (#9354) 2024-10-11 11:29:08 +02:00
John Spray 184935619e tests: stabilize test_storage_controller_heartbeats (#9347)
## Problem

This could fail with `reconciliation in progress` if running on a slow
test node such that background reconciliation happens at the same time
as we call consistency_check.

Example:
https://neon-github-public-dev.s3.amazonaws.com/reports/main/11258171952/index.html#/testresult/54889c9469afb232

## Summary of changes

- Call reconcile_until_idle before calling consistency check once,
rather than calling consistency check until it passes
2024-10-11 09:41:08 +01:00
Ivan Efremov b2ecbf3e80 Introduce "quota" ErrorKind (#9300)
## Problem
Fixes #8340
## Summary of changes
Introduced ErrorKind::quota to handle quota-related errors
## Checklist before requesting a review

- [x] I have performed a self-review of my code.
- [ ] If it is a core feature, I have added thorough tests.
- [ ] Do we need to implement analytics? if so did you add the relevant
metrics to the dashboard?
- [ ] If this PR requires public announcement, mark it with
/release-notes label and add several sentences in this section.

## Checklist before merging

- [ ] Do not forget to reformat commit message to not include the above
checklist
2024-10-11 10:45:55 +03:00
Tristan Partin 53147b51f9 Use valid type hints for Python 3.9
I have no idea how this made it past the linters.

Signed-off-by: Tristan Partin <tristan@neon.tech>
2024-10-10 13:00:25 -05:00
Tristan Partin 006d9dfb6b Add compute_config_dir fixture
Allows easy access to various compute config files.

Signed-off-by: Tristan Partin <tristan@neon.tech>
2024-10-10 12:43:40 -05:00
Tristan Partin 1f7904c917 Enable cargo caching in check-codestyle-rust
This job takes an extraordinary amount of time for what I understand it
to do. The obvious win is caching dependencies.

Rory disabled caching in cd5732d9d8.
I assume this was to get gen3 runners up and running.

Signed-off-by: Tristan Partin <tristan@neon.tech>
2024-10-10 12:40:30 -05:00
John Spray 07c714343f tests: allow a log warning in test_cli_start_stop_multi (#9320)
## Problem

This test restarts services in an undefined order (whatever neon_local
does), which means we should be tolerant of warnings that come from
restarting the storage controller while a pageserver is running.

We can see failures with warnings from dropped requests, e.g.
https://neon-github-public-dev.s3.amazonaws.com/reports/pr-9307/11229000712/index.html#/testresult/d33d5cb206331e28
```
 WARN request{method=GET path=/v1/location_config request_id=b7dbda15-6efb-4610-8b19-a3772b65455f}: request was dropped before completing\n')
```

## Summary of changes

- allow-list the `request was dropped before completing` message on
pageservers before restarting services
2024-10-10 17:06:42 +01:00
Tristan Partin 264c34dfb7 Move path-related fixtures into their own module (#9304)
neon_fixtures.py has grown into quite a beast.

Signed-off-by: Tristan Partin <tristan@neon.tech>
2024-10-10 10:26:23 -05:00
Erik Grinaker 9dd80b9b4c storage_scrubber: fix faulty assertion when no timelines (#9345)
When there are no timelines in remote storage, the storage scrubber
would incorrectly trip an assertion with "Must be set if results are
present", referring to the last processed tenant ID. When there are no
timelines we don't expect there to be a tenant ID either.

The assertion was introduced in 37aa6fd.

Only apply the assertion when any timelines are present.
2024-10-10 09:09:53 -04:00
Erik Grinaker c2623ffef4 CODEOWNERS: assign storage_scrubber to storage (#9346) 2024-10-10 12:40:35 +01:00
John Spray 426b1c5f08 storage controller: use 'infra' JWT scope for node registration (#9343)
## Problem

Storage controller `/control` API mostly requires admin tokens, for
interactive use by engineers. But for endpoints used by scripts, we
should not require admin tokens.

Discussion at
https://neondb.slack.com/archives/C033RQ5SPDH/p1728550081788989?thread_ts=1728548232.265019&cid=C033RQ5SPDH

## Summary of changes

- Introduce the 'infra' JWT scope, which was not previously used in the
neon repo
- For pageserver & safekeeper node registrations, require infra scope
instead of admin

Note that admin will still work, as the controller auth checks permit
admin tokens for all endpoints irrespective of what scope they require.
2024-10-10 12:26:43 +01:00
Conrad Ludgate 306094a87d add local-proxy suffix to wake-compute requests, respect the returned port (#9298)
https://github.com/neondatabase/cloud/issues/18349

Use the `-local-proxy` suffix to make sure we get the 10432 local_proxy
port back from cplane.
2024-10-09 22:43:35 +01:00
Tristan Partin d3464584a6 Improve some typing in test_runner
Fixes some types, adds some types, and adds some override annotations.

Signed-off-by: Tristan Partin <tristan@neon.tech>
2024-10-09 15:42:22 -05:00
Tristan Partin 878135fe9c Move PgBenchInitResult.EXTRACTORS to a private module constant
This seems to paper over a behavioral difference in Python 3.9 and
Python 3.12 with how dataclasses work with mutable variables. On Python
3.12, I get the following error:

ValueError: mutable default <class 'dict'> for field EXTRACTORS is not allowed: use default_factory

This obviously doesn't occur in our testing environment. When I do what
the error tells me, EXTRACTORS doesn't seem to exist as an attribute on
the class in at least Python 3.9.

The solution provided in this commit seems like the least amount of
friction to keep the wheels turning.

Signed-off-by: Tristan Partin <tristan@neon.tech>
2024-10-09 14:02:09 -05:00
Conrad Ludgate 75434060a5 local_proxy: integrate with pg_session_jwt extension (#9086) 2024-10-09 18:24:10 +01:00
Anastasia Lubennikova 721803a0e7 Add partial support of extensions for v17: (#9322)
- PostGIS 3.5.0
- pgrouting 3.6.2
- h3 4.1.3
- unit 7.9
- pgjwt version (f3d82fd)
- pg_hashids 1.2.1
- ip4r 2.4.2
- prefix 1.2.10
- postgresql-hll 2.18
- pg_roaringbitmap 0.5.4
- pg-semver 0.40.0

update support of extensions for v14-v16:
- unit 7.7 -> 7.9
- pgjwt 9742dab -> f3d82fd

---------

Co-authored-by: Heikki Linnakangas <heikki@neon.tech>
2024-10-09 17:07:59 +01:00
Fedor Dikarev 108a211917 added workflow Report Workflow Stats (#9330)
## Summary of changes
CI: Collect stats for Github Workflows Runs
2024-10-09 17:27:41 +02:00
Heikki Linnakangas 72ef0e0fa1 tests: Remove redundant log lines when stopping storage nodes (#9317)
The neon_cli functions print the command that gets executed, which
contains the same information.

Before:

    2024-10-07 22:32:28.884 INFO [neon_fixtures.py:3927] Stopping safekeeper 1
    2024-10-07 22:32:28.884 INFO [neon_cli.py:73] Running command "/tmp/neon/bin/neon_local safekeeper stop 1"
    2024-10-07 22:32:28.989 INFO [neon_fixtures.py:3927] Stopping safekeeper 2
    2024-10-07 22:32:28.989 INFO [neon_cli.py:73] Running command "/tmp/neon/bin/neon_local safekeeper stop 2"
    2024-10-07 22:32:29.93 INFO [neon_fixtures.py:3927] Stopping safekeeper 3
    2024-10-07 22:32:29.94 INFO [neon_cli.py:73] Running command "/tmp/neon/bin/neon_local safekeeper stop 3"
    2024-10-07 22:32:29.251 INFO [neon_cli.py:450] Stopping pageserver with ['pageserver', 'stop', '--id=1']
    2024-10-07 22:32:29.251 INFO [neon_cli.py:73] Running command "/tmp/neon/bin/neon_local pageserver stop --id=1"

After:

    2024-10-07 22:32:28.884 INFO [neon_cli.py:73] Running command "/tmp/neon/bin/neon_local safekeeper stop 1"
    2024-10-07 22:32:28.989 INFO [neon_cli.py:73] Running command "/tmp/neon/bin/neon_local safekeeper stop 2"
    2024-10-07 22:32:29.94 INFO [neon_cli.py:73] Running command "/tmp/neon/bin/neon_local safekeeper stop 3"
    2024-10-07 22:32:29.251 INFO [neon_cli.py:73] Running command "/tmp/neon/bin/neon_local pageserver stop --id=1"
2024-10-09 15:51:34 +03:00
Heikki Linnakangas eb23d355a9 tests: Use ThreadedMotoServer python class to launch mock S3 server (#9313)
This is simpler than using subprocess.

One difference is in how moto's log output is now collected. Previously,
moto's logs went to stderr, and were collected and printed at the end of
the test by pytest, like this:

    2024-10-07T22:45:12.3705222Z ----------------------------- Captured stderr call -----------------------------
    2024-10-07T22:45:12.3705577Z 127.0.0.1 - - [07/Oct/2024 22:35:14] "PUT /pageserver-test-deletion-queue-2e6efa8245ec92a37a07004569c29eb7 HTTP/1.1" 200 -
    2024-10-07T22:45:12.3706181Z 127.0.0.1 - - [07/Oct/2024 22:35:15] "GET /pageserver-test-deletion-queue-2e6efa8245ec92a37a07004569c29eb7/?list-type=2&delimiter=/&prefix=/tenants/43da25eac0f41412696dd31b94dbb83c/timelines/ HTTP/1.1" 200 -
    2024-10-07T22:45:12.3706894Z 127.0.0.1 - - [07/Oct/2024 22:35:16] "PUT /pageserver-test-deletion-queue-2e6efa8245ec92a37a07004569c29eb7//tenants/43da25eac0f41412696dd31b94dbb83c/timelines/eabba5f0c1c72c8656d3ef1d85b98c1d/initdb.tar.zst?x-id=PutObject HTTP/1.1" 200 -

Note the timestamps: the timestamp at the beginning of the line is the
time that the stderr was dumped, i.e. the end of the test, which makes
those timestamps rather useless. The timestamp in the middle of the line
is when the operation actually happened, but it has only 1 s
granularity.

With this change, moto's log lines are printed in the "live log call"
section, as they happen, which makes the timestamps more useful:

    2024-10-08 12:12:31.129 INFO [_internal.py:97] 127.0.0.1 - - [08/Oct/2024 12:12:31] "GET /pageserver-test-deletion-queue-e24e7525d437e1874d8a52030dcabb4f/?list-type=2&delimiter=/&prefix=/tenants/7b6a16b1460eda5204083fba78bc360f/timelines/ HTTP/1.1" 200 -
    2024-10-08 12:12:32.612 INFO [_internal.py:97] 127.0.0.1 - - [08/Oct/2024 12:12:32] "PUT /pageserver-test-deletion-queue-e24e7525d437e1874d8a52030dcabb4f//tenants/7b6a16b1460eda5204083fba78bc360f/timelines/7ab4c2b67fa8c712cada207675139877/initdb.tar.zst?x-id=PutObject HTTP/1.1" 200 -
2024-10-09 15:34:51 +03:00
Yuchen Liang bee04b8a69 pageserver: add direct io config to virtual file (#9214)
## Problem
We need a way to incrementally switch to direct IO. During the rollout
we might want to switch to O_DIRECT on image and delta layer read path
first before others.

## Summary of changes
- Revisited and simplified direct io config in `PageserverConf`. 
- We could add a fallback mode for open, but for read there isn't a
reasonable alternative (without creating another buffered virtual file).
- Added a wrapper around `VirtualFile`, current implementation become
`VirtualFileInner`
- Use `open_v2`, `create_v2`, `open_with_options_v2` when we want to use
the IO mode specified in PS config.
- Once we onboard all IO through VirtualFile using this new API, we will
delete the old code path.
- Make io mode live configurable for benchmarking.
- Only guaranteed for files opened after the config change, so do it
before the experiment.

As an example, we are using `open_v2` with
`virtual_file::IoMode::Direct` in
https://github.com/neondatabase/neon/pull/9169

We also remove `io_buffer_alignment` config in
a04cfd754b and use it as a compile time
constant. This way we don't have to carry the alignment around or make
frequent call to retrieve this information from the static variable.

Signed-off-by: Yuchen Liang <yuchen@neon.tech>
2024-10-09 08:33:07 -04:00
Anastasia Lubennikova 63e7fab990 Add /installed_extensions endpoint to collect statistics about extension usage. (#8917)
Add /installed_extensions endpoint to collect
statistics about extension usage.
It returns a list of installed extensions in the format:

```json
{
  "extensions": [
    {
      "extname": "extension_name",
      "versions": ["1.0", "1.1"],
      "n_databases": 5,
    }
  ]
}
```

---------

Co-authored-by: Heikki Linnakangas <heikki@neon.tech>
2024-10-09 13:32:13 +01:00
Arseny Sher a181392738 safekeeper: add evicted_timelines gauge. (#9318)
showing total number of evicted timelines.
2024-10-09 14:40:30 +03:00
Alexander Bayandin fc7397122c test_runner: fix path to tpc-h queries (#9327)
## Problem

The path to TPC-H queries was incorrectly changed in #9306.
This path is used for `test_tpch` parameterization, so all perf tests
started to fail:

```
==================================== ERRORS ====================================
__________ ERROR collecting test_runner/performance/test_perf_olap.py __________
test_runner/performance/test_perf_olap.py:205: in <module>
    @pytest.mark.parametrize("query", tpch_queuies())
test_runner/performance/test_perf_olap.py:196: in tpch_queuies
    assert queries_dir.exists(), f"TPC-H queries dir not found: {queries_dir}"
E   AssertionError: TPC-H queries dir not found: /__w/neon/neon/test_runner/performance/performance/tpc-h/queries
E   assert False
E    +  where False = <bound method Path.exists of PosixPath('/__w/neon/neon/test_runner/performance/performance/tpc-h/queries')>()
E    +    where <bound method Path.exists of PosixPath('/__w/neon/neon/test_runner/performance/performance/tpc-h/queries')> = PosixPath('/__w/neon/neon/test_runner/performance/performance/tpc-h/queries').exists
```

## Summary of changes
- Fix the path to tpc-h queries
2024-10-09 12:11:06 +01:00
Vlad Lazar cc599e23c1 storcon: make observed state updates more granular (#9276)
## Problem

Previously, observed state updates from the reconciler may have
clobbered inline changes made to the observed state by other code paths.

## Summary of changes

Model observed state changes from reconcilers as deltas. This means that
we only update what has changed. Handling for node going off-line concurrently
during the reconcile is also added: set observed state to None in such cases to
respect the convention.

Closes https://github.com/neondatabase/neon/issues/9124
2024-10-09 11:53:29 +01:00
Folke Behrens 54d1185789 proxy: Unalias hyper1 and replace one use of hyper0 in test (#9324)
Leaves one final use of hyper0 in proxy for the health service,
which requires some coordinated effort with other services.
2024-10-09 12:44:17 +02:00
Heikki Linnakangas 8a138db8b7 tests: Reduce noise from logging renamed files (#9315)
Instead of printing the full absolute path for every file, print just
the filenames.

Before:

    2024-10-08 13:19:39.98 INFO [test_pageserver_generations.py:669] Found file /home/heikki/git-sandbox/neon/test_output/test_upgrade_generationless_local_file_paths[debug-pg16]/repo/pageserver_1/tenants/0c04a8df7691a367ad0bb1cc1373ba4d/timelines/f41022551e5f96ce8dbefb9b5d35ab45/000000067F0000000100000A8D0100000000-000000067F0000000100000AC10000000002__00000000014F16F0-v1-00000001
    2024-10-08 13:19:39.99 INFO [test_pageserver_generations.py:673] Renamed /home/heikki/git-sandbox/neon/test_output/test_upgrade_generationless_local_file_paths[debug-pg16]/repo/pageserver_1/tenants/0c04a8df7691a367ad0bb1cc1373ba4d/timelines/f41022551e5f96ce8dbefb9b5d35ab45/000000067F0000000100000A8D0100000000-000000067F0000000100000AC10000000002__00000000014F16F0-v1-00000001 -> /home/heikki/git-sandbox/neon/test_output/test_upgrade_generationless_local_file_paths[debug-pg16]/repo/pageserver_1/tenants/0c04a8df7691a367ad0bb1cc1373ba4d/timelines/f41022551e5f96ce8dbefb9b5d35ab45/000000067F0000000100000A8D0100000000-000000067F0000000100000AC10000000002__00000000014F16F0

After:

    2024-10-08 13:24:39.726 INFO [test_pageserver_generations.py:667] Renaming files in /home/heikki/git-sandbox/neon/test_output/test_upgrade_generationless_local_file_paths[debug-pg16]/repo/pageserver_1/tenants/3439538816c520adecc541cc8b1de21c/timelines/6a7be8ee707b355de48dd91b326d6ae1
    2024-10-08 13:24:39.728 INFO [test_pageserver_generations.py:673] Renamed
000000067F0000000100000A8D0100000000-000000067F0000000100000AC10000000002__00000000014F16F0-v1-00000001 -> 000000067F0000000100000A8D0100000000-000000067F0000000100000AC10000000002__00000000014F16F0
2024-10-09 10:55:56 +01:00
Erik Grinaker 211970f0e0 remote_storage: add DownloadOpts::byte_(start|end) (#9293)
`download_byte_range()` is basically a copy of `download()` with an
additional option passed to the backend SDKs. This can cause these code
paths to diverge, and prevents combining various options.

This patch adds `DownloadOpts::byte_(start|end)` and move byte range
handling into `download()`.
2024-10-09 10:29:06 +01:00
Heikki Linnakangas f87f5a383e tests: Remove redundant log lines when starting an endpoint (#9316)
The "Starting postgres endpoint <name>" message is not needed, because
the neon_cli.py prints the neon_local command line used to start the
endpoint. That contains the same information. The "Postgres startup took
XX seconds" message is not very useful because no one pays attention to
those in the python test logs when things are going smoothly, and if you
do wonder about the startup speed, the same information and more can be
found in the compute log.

Before:

    2024-10-07 22:32:27.794 INFO [neon_fixtures.py:3492] Starting postgres endpoint ep-1
    2024-10-07 22:32:27.794 INFO [neon_cli.py:73] Running command "/tmp/neon/bin/neon_local endpoint start --safekeepers 1 ep-1"
    2024-10-07 22:32:27.901 INFO [neon_fixtures.py:3690] Postgres startup took 0.11398935317993164 seconds

After:

    2024-10-07 22:32:27.794 INFO [neon_cli.py:73] Running command "/tmp/neon/bin/neon_local endpoint start --safekeepers 1 ep-1"
2024-10-09 09:58:50 +01:00
Arpad Müller e8ae37652b Add timeline offload mechanism (#8907)
Implements an initial mechanism for offloading of archived timelines.

Offloading is implemented as specified in the RFC.

For now, there is no persistence, so a restart of the pageserver will
retrigger downloads until the timeline is offloaded again.

We trigger offloading in the compaction loop because we need the signal
for whether compaction is done and everything has been uploaded or not.

Part of #8088
2024-10-09 01:33:39 +02:00
Tristan Partin 5bd8e2363a Enable all pyupgrade checks in ruff
This will help to keep us from using deprecated Python features going
forward.

Signed-off-by: Tristan Partin <tristan@neon.tech>
2024-10-08 14:32:26 -05:00
Vlad Lazar 618680c299 storcon: apply all node status changes before handling transitions (#9281)
## Problem

When a node goes offline, we trigger reconciles to migrate shards away
from it. If multiple nodes go offline at the same time, we handled them in
sequence. Hence, we might migrate shards from the first offline node to the second
offline node and increase the unavailability period.

## Summary of changes

Refactor heartbeat delta handling to:
1. Update in memory state for all nodes first
2. Handle availability transitions one by one (we have full picture for each node after (1))

Closes https://github.com/neondatabase/neon/issues/9126
2024-10-08 17:55:25 +01:00
Alexander Bayandin baf27ba6a3 Fix compiler warnings on macOS (#9319)
## Problem

On macOS:
```
/Users/runner/work/neon/neon//pgxn/neon/file_cache.c:623:19: error: variable 'has_remaining_pages' is used uninitialized whenever 'for' loop exits because its condition is false [-Werror,-Wsometimes-uninitialized]
```

## Summary of changes
- Initialise `has_remaining_pages` with `false`
2024-10-08 17:34:35 +01:00
Tristan Partin 16417d919d Remove get_self_dir()
It didn't serve much value, and was only used twice.
Path(__file__).parent is a pretty easy invocation to use.

Signed-off-by: Tristan Partin <tristan@neon.tech>
2024-10-08 08:57:11 -05:00
Heikki Linnakangas 18b97150b2 Remove non-existent entries from .dockerignore (#9209) 2024-10-08 14:55:24 +03:00
Heikki Linnakangas 17c59ed786 Don't override CFLAGS when building neon extension
If you override CFLAGS, you also override any flags that PostgreSQL
configure script had picked. That includes many options that enable
extra compiler warnings, like '-Wall', '-Wmissing-prototypes', and so
forth. The override was added in commit 171385ac14, but the intention
of that was to be *more* strict, by enabling '-Werror', not less
strict. The proper way of setting '-Werror', as documented in the docs
and mentioned in PR #2405, is to set COPT='-Werror', but leave CFLAGS
alone.

All the compiler warnings with the standard PostgreSQL flags have now
been fixed, so we can do this without adding noise.

Part of the cleanup issue #9217.
2024-10-07 23:49:33 +03:00
Heikki Linnakangas d7b960c9b5 Silence compiler warning about using variable uninitialized
It's not a bug, the variable is initialized when it's used, but the
compiler isn't smart enough to see that through all the conditions.

Part of the cleanup issue #9217.
2024-10-07 23:49:31 +03:00
Heikki Linnakangas 2ff6d2b6b5 Silence compiler warning about variable only used in assertions
Part of the cleanup issue #9217.
2024-10-07 23:49:29 +03:00
Heikki Linnakangas 30f7fbc88d Add pg_attribute_printf to WalProposerLibLog, per gcc's suggestion
/pgxn/neon/walproposer_compat.c:192:9: warning: function ‘WalProposerLibLog’ might be a candidate for ‘gnu_printf’ format attribute [-Wsuggest-attribute=format]
      192 |         vsnprintf(buf, sizeof(buf), fmt, args);
          |         ^~~~~~~~~
2024-10-07 23:49:27 +03:00
Heikki Linnakangas 09f2000f91 Silence warnings about shadowed local variables
Part of the cleanup issue #9217.
2024-10-07 23:49:24 +03:00
Heikki Linnakangas e553ca9e4f Silence warnings about mixed declarations and code
The warning:

    warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]

It's PostgreSQL project style to stick to the old C90 style.
(Alternatively, we could disable it for our extension.)

Part of the cleanup issue #9217.
2024-10-07 23:49:22 +03:00
Heikki Linnakangas 0a80dbce83 neon_write() function is not used on v17
ifdef it out on v17, to silence compiler warning.

Part of the cleanup issue #9217.
2024-10-07 23:49:20 +03:00
Heikki Linnakangas e763256448 Fix warnings about missing function prototypes
Prototypes for neon_writev(), neon_readv(), and neon_regisersync()
were missing. But instead of adding the missing prototypes, mark all
the smgr functions 'static'.

Part of the cleanup issue #9217.
2024-10-07 23:49:18 +03:00
Heikki Linnakangas 129d4480bb Move "/* fallthrough */" comments so that GCC recognizes them
This silences warnings about implicit fallthroughs.

Part of the cleanup issue #9217.
2024-10-07 23:49:16 +03:00
Heikki Linnakangas 776df963ba Fix function prototypes
Silences these compiler warnings:

    /pgxn/neon_walredo/walredoproc.c:452:1: warning: ‘CreateFakeSharedMemoryAndSemaphores’ was used with no prototype before its definition [-Wmissing-prototypes]
      452 | CreateFakeSharedMemoryAndSemaphores()
          | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    /pgxn/neon/walproposer_pg.c:541:1: warning: no previous prototype for ‘GetWalpropShmemState’ [-Wmissing-prototypes]
      541 | GetWalpropShmemState()
          | ^~~~~~~~~~~~~~~~~~~~

Part of the cleanup issue #9217.
2024-10-07 23:49:13 +03:00
Heikki Linnakangas 11dc5feb36 Remove unused static function
In v16 merge, we copied much of heap RMGR, to distinguish vanilla
Postgres heap records from records generated with neon patches, with
the additional CID fields. This function is only used by the
HEAP_TRUNCATE records, however, which we didn't need to copy.

Part of the cleanup issue #9217.
2024-10-07 23:49:11 +03:00
Heikki Linnakangas dbbe57a837 Remove unused local vars and a prototype for non-existent function
Per compiler warnings. Part of the cleanup issue #9217.
2024-10-07 23:49:09 +03:00
Em Sharnoff cc29def544 vm-monitor: Ignore LFC in postgres cgroup memory threshold (#8668)
In short: Currently we reserve 75% of memory to the LFC, meaning that if
we scale up to keep postgres using less than 25% of the compute's
memory.

This means that for certain memory-heavy workloads, we end up scaling
much higher than is actually needed — in the worst case, up to 4x,
although in practice it tends not to be quite so bad.

Part of neondatabase/autoscaling#1030.
2024-10-07 21:25:34 +01:00
Arpad Müller 912d47ec02 storage_broker: update hyper and tonic again (#9299)
Update hyper and tonic again in the storage broker, this time with a fix
for the issue that made us revert the update last time.

The first commit is a revert of #9268, the second a fix for the issue.

fixes #9231.
2024-10-07 21:12:13 +02:00
Tristan Partin 6eba29c732 Improve logging on changes in a compute's status
I'm trying to debug a situation with the LR benchmark publisher not
being in the correct state. This should aid in debugging, while just
being generally useful.

PR: https://github.com/neondatabase/neon/pull/9265
Signed-off-by: Tristan Partin <tristan@neon.tech>
2024-10-07 13:19:48 -04:00
Heikki Linnakangas 99d4c1877b Replace BUFFERTAGS_EQUAL compatibility macro with new-style function (#9294)
In PostgreSQL v16, BUFFERTAGS_EQUAL was replaced with a static inline
macro, BufferTagsEqual. Let's use the new name going forward, and have
backwards-compatibility glue to allow using the new name on v14 and v15,
rather than the other way round. This also makes BufferTagsEquals
consistent with InitBufferTag, for which we were already using the new
name.
2024-10-07 19:49:27 +03:00
Jere Vaara 2272dc8a48 feat(compute_tools): Create JWKS Postgres roles without attributes (#9031)
Requires https://github.com/neondatabase/neon/pull/9086 first to have
`local_proxy_config`. This logic can still be reviewed implementation
wise.

Create JWT Auth functionality related roles without attributes and
`neon_superuser` group.

Read the JWT related roles from `local_proxy_config` `JWKS` settings and
handle them differently than other console created roles.
2024-10-07 19:37:32 +03:00
Heikki Linnakangas 323bd018cd Make sure BufferTag padding bytes are cleared in hash keys (#9292)
The prefetch-queue hash table uses a BufferTag struct as the hash key,
and it's hashed using hash_bytes(). It's important that all the padding
bytes in the key are cleared, because hash_bytes() will include them.

I was getting compiler warnings like this on v14 and v15, when compiling
with -Warray-bounds:

    In function ‘prfh_lookup_hash_internal’,
inlined from ‘prfh_lookup’ at
pg_install/v14/include/postgresql/server/lib/simplehash.h:821:9,
inlined from ‘neon_read_at_lsnv’ at pgxn/neon/pagestore_smgr.c:2789:11,
inlined from ‘neon_read_at_lsn’ at pgxn/neon/pagestore_smgr.c:2904:2:
pg_install/v14/include/postgresql/server/storage/relfilenode.h:90:43:
warning: array subscript ‘PrefetchRequest[0]’ is partly outside array
bounds of ‘BufferTag[1]’ {aka ‘struct buftag[1]’} [-Warray-bounds]
       89 |         ((node1).relNode == (node2).relNode && \
          |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       90 |          (node1).dbNode == (node2).dbNode && \
          |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
       91 |          (node1).spcNode == (node2).spcNode)
          |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pg_install/v14/include/postgresql/server/storage/buf_internals.h:116:9:
note: in expansion of macro ‘RelFileNodeEquals’
      116 |         RelFileNodeEquals((a).rnode, (b).rnode) && \
          |         ^~~~~~~~~~~~~~~~~
pgxn/neon/neon_pgversioncompat.h:25:31: note: in expansion of macro
‘BUFFERTAGS_EQUAL’
       25 | #define BufferTagsEqual(a, b) BUFFERTAGS_EQUAL(*(a), *(b))
          |                               ^~~~~~~~~~~~~~~~
pgxn/neon/pagestore_smgr.c:220:34: note: in expansion of macro
‘BufferTagsEqual’
220 | #define SH_EQUAL(tb, a, b) (BufferTagsEqual(&(a)->buftag,
&(b)->buftag))
          |                                  ^~~~~~~~~~~~~~~
pg_install/v14/include/postgresql/server/lib/simplehash.h:280:77: note:
in expansion of macro ‘SH_EQUAL’
280 | #define SH_COMPARE_KEYS(tb, ahash, akey, b) (ahash ==
SH_GET_HASH(tb, b) && SH_EQUAL(tb, b->SH_KEY, akey))
| ^~~~~~~~
pg_install/v14/include/postgresql/server/lib/simplehash.h:799:21: note:
in expansion of macro ‘SH_COMPARE_KEYS’
      799 |                 if (SH_COMPARE_KEYS(tb, hash, key, entry))
          |                     ^~~~~~~~~~~~~~~
    pgxn/neon/pagestore_smgr.c: In function ‘neon_read_at_lsn’:
    pgxn/neon/pagestore_smgr.c:2742:25: note: object ‘buftag’ of size 20
     2742 |         BufferTag       buftag = {0};
          |                         ^~~~~~

This commit silences those warnings, although it's not clear to me why
the compiler complained like that in the first place. I found the issue
with padding bytes while looking into those warnings, but that was
coincidental, I don't think the padding bytes explain the warnings as
such.

In v16, the BUFFERTAGS_EQUAL macro was replaced with a static inline
function, and that also silences the compiler warning. Not clear to me
why.
2024-10-07 18:04:04 +03:00
Folke Behrens ad267d849f proxy: Move module base files into module directory (#9297) 2024-10-07 16:25:34 +02:00
Conrad Ludgate 8cd7b5bf54 proxy: rename console -> control_plane, rename web -> console_redirect (#9266)
rename console -> control_plane
rename web -> console_redirect

I think these names are a little more representative.
2024-10-07 14:09:54 +01:00
Konstantin Knizhnik 47c3c9a413 Fix update of statistic for LFC/prefetch (#9272)
## Problem

See #9199

## Summary of changes

Fix update of hits/misses for LFC and prefetch introduced in
https://github.com/neondatabase/neon/commit/78938d1b591b33d23495a0edb8b123cc5cac6a27

## Checklist before requesting a review

- [ ] I have performed a self-review of my code.
- [ ] If it is a core feature, I have added thorough tests.
- [ ] Do we need to implement analytics? if so did you add the relevant
metrics to the dashboard?
- [ ] If this PR requires public announcement, mark it with
/release-notes label and add several sentences in this section.

## Checklist before merging

- [ ] Do not forget to reformat commit message to not include the above
checklist

Co-authored-by: Konstantin Knizhnik <knizhnik@neon.tech>
2024-10-07 12:21:16 +03:00
337 changed files with 5720 additions and 2849 deletions
-5
View File
@@ -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() }}
+16 -2
View File
@@ -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 }}
+9 -10
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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]
+7 -7
View File
@@ -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
View File
@@ -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 && \
+1 -2
View File
@@ -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: {:?}",
+36 -5
View File
@@ -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() {
+1 -2
View File
@@ -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;
+33 -7
View File
@@ -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
); );
} }
+32
View File
@@ -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
# #
+80
View File
@@ -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?
}
+1
View File
@@ -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;
+13
View File
@@ -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(), &[])?;
+30
View File
@@ -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>,
}
+2 -6
View File
@@ -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(),
} }
} }
+34 -41
View File
@@ -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, })
}
} }
} }
+6 -20
View File
@@ -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
View File
@@ -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();
+56 -111
View File
@@ -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?;
+1 -28
View File
@@ -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,
) )
+2 -17
View File
@@ -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
} }
+44 -5
View File
@@ -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);
+4 -1
View File
@@ -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,
+9 -38
View File
@@ -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",
+1 -5
View File
@@ -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);
{ {
+7 -4
View File
@@ -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
+1 -5
View File
@@ -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;
+2 -6
View File
@@ -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);
+2 -6
View File
@@ -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
View File
@@ -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(),
)),
} }
} }
+2 -7
View File
@@ -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")?;
+3 -7
View File
@@ -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()),
}; };
// ------------------------------------------------------------ // ------------------------------------------------------------
+8 -11
View File
@@ -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
View File
@@ -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,
+3 -3
View File
@@ -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!(
+2 -2
View File
@@ -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
)), )),
); );
+12 -1
View File
@@ -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()
} }
+69 -36
View File
@@ -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;
+69
View File
@@ -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(())
}
+43 -69
View File
@@ -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
View File
@@ -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();
+107 -94
View File
@@ -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);
+8 -7
View File
@@ -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];
-2
View File
@@ -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 */
+3 -6
View File
@@ -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
-2
View File
@@ -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;
+16 -4
View File
@@ -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
-32
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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__)
+6 -3
View File
@@ -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;
-21
View File
@@ -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)
{ {
+38 -36
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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,
} }
+1 -1
View File
@@ -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},
+9 -7
View File
@@ -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 {
+2 -2
View File
@@ -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!()
} }
} }
+1 -1
View File
@@ -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},
+2 -2
View File
@@ -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)]
+20 -7
View File
@@ -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
View File
@@ -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;
View File
+1 -1
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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)> {
+5
View File
@@ -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",
+3 -3
View File
@@ -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?;
+1 -1
View File
@@ -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
View File
@@ -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;
+1 -1
View File
@@ -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>))]
+5 -5
View File
@@ -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()?;
+11 -8
View File
@@ -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, &params, hostname, common_names)) .map(|()| auth::ComputeUserInfoMaybeEndpoint::parse(ctx, &params, hostname, common_names))
.transpose(); .transpose();
+2 -2
View File
@@ -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(),
+11 -11
View File
@@ -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