feat(dataset-mount): authorize and perform a repository mount - #6896
feat(dataset-mount): authorize and perform a repository mount#6896aicam wants to merge 1 commit into
Conversation
Automated Reviewer SuggestionsBased on the
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #6896 +/- ##
============================================
- Coverage 93.94% 93.57% -0.38%
- Complexity 4824 4843 +19
============================================
Files 1209 1212 +3
Lines 49618 49756 +138
Branches 6056 6069 +13
============================================
- Hits 46615 46558 -57
- Misses 1522 1710 +188
- Partials 1481 1488 +7
*This pull request uses carry forward flags. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
| config | throughput | MB/s | latency | max Δ latest / 7d | |
|---|---|---|---|---|---|
| 🔴 | bs=10 sw=10 sl=64 | 366 | 0.223 | 27,438/30,533/30,533 us | 🔴 +18.2% / 🔴 +112.8% |
| 🔴 | bs=100 sw=10 sl=64 | 778 | 0.475 | 124,321/158,798/158,798 us | 🔴 +6.0% / 🔴 +46.5% |
| ⚪ | bs=1000 sw=10 sl=64 | 916 | 0.559 | 1,088,191/1,201,143/1,201,143 us | ⚪ within ±5% / 🔴 +15.5% |
Baseline details
Latest main a268c1c from same runner
| config | metric | PR | latest main | 7d avg | Δ latest | Δ 7d |
|---|---|---|---|---|---|---|
| bs=10 sw=10 sl=64 | throughput | 366 tuples/sec | 423 tuples/sec | 758.07 tuples/sec | -13.5% | -51.7% |
| bs=10 sw=10 sl=64 | MB/s | 0.223 MB/s | 0.258 MB/s | 0.463 MB/s | -13.6% | -51.8% |
| bs=10 sw=10 sl=64 | p50 | 27,438 us | 23,206 us | 12,892 us | +18.2% | +112.8% |
| bs=10 sw=10 sl=64 | p95 | 30,533 us | 34,029 us | 15,953 us | -10.3% | +91.4% |
| bs=10 sw=10 sl=64 | p99 | 30,533 us | 34,029 us | 19,452 us | -10.3% | +57.0% |
| bs=100 sw=10 sl=64 | throughput | 778 tuples/sec | 825 tuples/sec | 985.38 tuples/sec | -5.7% | -21.0% |
| bs=100 sw=10 sl=64 | MB/s | 0.475 MB/s | 0.504 MB/s | 0.601 MB/s | -5.8% | -21.0% |
| bs=100 sw=10 sl=64 | p50 | 124,321 us | 120,006 us | 101,292 us | +3.6% | +22.7% |
| bs=100 sw=10 sl=64 | p95 | 158,798 us | 149,748 us | 108,395 us | +6.0% | +46.5% |
| bs=100 sw=10 sl=64 | p99 | 158,798 us | 149,748 us | 119,793 us | +6.0% | +32.6% |
| bs=1000 sw=10 sl=64 | throughput | 916 tuples/sec | 911 tuples/sec | 1,010 tuples/sec | +0.5% | -9.3% |
| bs=1000 sw=10 sl=64 | MB/s | 0.559 MB/s | 0.556 MB/s | 0.616 MB/s | +0.5% | -9.3% |
| bs=1000 sw=10 sl=64 | p50 | 1,088,191 us | 1,091,703 us | 997,720 us | -0.3% | +9.1% |
| bs=1000 sw=10 sl=64 | p95 | 1,201,143 us | 1,168,802 us | 1,040,349 us | +2.8% | +15.5% |
| bs=1000 sw=10 sl=64 | p99 | 1,201,143 us | 1,168,802 us | 1,070,423 us | +2.8% | +12.2% |
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,547.09,200,128000,366,0.223,27437.98,30532.78,30532.78
1,100,10,64,20,2569.40,2000,1280000,778,0.475,124320.77,158797.51,158797.51
2,1000,10,64,20,21841.71,20000,12800000,916,0.559,1088191.06,1201143.16,1201143.16d4f0f3a to
972ca8f
Compare
c993afe to
05c243f
Compare
05c243f to
cadf7d0
Compare
cadf7d0 to
3797945
Compare
021245a to
a3fcb8d
Compare
a3fcb8d to
7422e25
Compare
Let a computing unit have a versioned LakeFS repository mounted into it, on the infrastructure merged in apache#6866. The per-node mounter authorizes nothing — it performs what it is told, which is why it admits exactly one caller, verified with TokenReview against an audience-bound service-account token that only access-control-service holds. Every decision therefore has to be made here, and this endpoint makes four before anything reaches the mounter: the request has the shape a mount path can be built from; the caller holds write access to the computing unit, mounting being a change to it; the caller may read the repository, matched by name across datasets and models and refused unless exactly one matches; and the commit belongs to that repository. It then resolves which node the unit's pod is on — itself, rather than taking one from the caller, or anything reaching it could aim requests at any node's privileged mounter — and forwards. file-service still re-checks read access on every byte it serves, but as the last line rather than the only one: without the check here a caller could have a mount created for a repository they cannot read, learning it exists and spending a node's resources on it. The rules themselves move to common/resource so both services decide from one definition. Mounts are released when the pod is deleted, so there is no unmount path. No new configuration: the mounter's port and file-service's root come from the environment the chart already sets. Everything is behind mounter.enabled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fy9tJ1AB4trv6ZYGwfm9pR
45b49bd to
962260e
Compare
What changes were proposed in this PR?
Let a computing unit have a versioned LakeFS repository mounted into it, on the infrastructure merged in #6866. This is the authorization step of #6606: the endpoint that decides whether a mount may happen, and performs it.
The per-node mounter is the one privileged component — root on every node, listening on a hostPort — so it admits exactly one caller, verified with
TokenReviewagainst a token bound to thetexera-mounteraudience. Only access-control-service holds one, which is also why it is the right place: it is already where the deployment decides whether a user may act on a computing unit.The mounter authorizes nothing. It performs what it is told, so every decision has to be made before it is asked. The endpoint makes four:
..or a leading-is refused by name rather than by whatever it fails next. The mounter checks this too, being privileged; here it is checked first, before any database work.Only then does it resolve which node the unit's pod is on — itself, rather than accepting one from the caller, or anything that can reach it could aim requests at any node's privileged mounter — and forward.
file-service still re-checks read access on every byte it serves, but as the last line rather than the only one: without the check here a caller could have a mount created for a repository they cannot read, learning that it exists and spending a node's resources on it. The read and ownership rules move to a module both services share, so they decide from one definition instead of two that can drift.
Mounts are released when the computing unit's pod is deleted, by the mounter's pod watcher, so there is no unmount path. No new configuration: the mounter's port and file-service's root are read from the environment the chart already sets, so each value is written once, in the chart. The whole feature stays behind
mounter.enabled; with it off the chart renders no new object and no new environment variable.Nothing here is used only by the follow-up. Resolving a logical dataset or model path to a repository and commit, and the engine client that calls this endpoint, land in #6895 where they have callers.
Any related issues, documentation, discussions?
Closes #6863 · part of #6606 · builds on #6866.
How was this PR tested?
mounter.enabled=true, deployed from this chart rather than by hand so the wiring itself is what ran. Every call was made from inside an unprivileged computing-unit pod: authorized mount200, the same mount again200, unauthenticated401, a computing unit the caller may not use403, a repository that does not exist403, a commit from another repository403, a malformed name400, and calling the node mounter directly401while its health probe stays open at200.fuse.geesefsat the expected path inside the pod, file byte-exact, read-only, in a pod whose effective capabilities are empty. The mounter's log confirms GeeseFS was pointed at file-service's root.Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Opus 5