Skip to content

feat(dataset-mount): authorize and perform a repository mount - #6896

Open
aicam wants to merge 1 commit into
apache:mainfrom
aicam:feat/mount-B1-platform
Open

feat(dataset-mount): authorize and perform a repository mount#6896
aicam wants to merge 1 commit into
apache:mainfrom
aicam:feat/mount-B1-platform

Conversation

@aicam

@aicam aicam commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

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 TokenReview against a token bound to the texera-mounter audience. 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:

  1. The request has a shape a mount path can be built from — each component a single safe segment, so a separator, a .. 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.
  2. The caller holds write access to the computing unit. Mounting puts data into someone's unit, so it takes the same privilege as any other change to one; a read-only sharee may use the unit, not alter what it can see.
  3. The caller may read the repository. It is matched by name across datasets and models — by name rather than by parsing an id out of it, because an earlier migration backfilled that column from the dataset's plain name — and anything other than exactly one match is refused rather than resolved arbitrarily.
  4. The commit belongs to that repository, so a well-formed but unrelated digest cannot be mounted.

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?

  • New unit tests cover each refusal — no access to the unit, read-only access to it, a repository the caller cannot read, one that does not exist, a commit belonging to another repository, a malformed component, a unit not yet scheduled, and mounting disabled or misconfigured — each asserting the mounter was never asked. Plus the node lookup and the mounter client's own request shape, service-account token and error handling. Compile and tests green across the touched modules.
  • Validated end to end on a single-node minikube with 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 mount 200, the same mount again 200, unauthenticated 401, a computing unit the caller may not use 403, a repository that does not exist 403, a commit from another repository 403, a malformed name 400, and calling the node mounter directly 401 while its health probe stays open at 200.
  • The mount itself: fuse.geesefs at 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.
  • Terminating the computing unit took GeeseFS mounts from one to zero and emptied the mount root, which is why there is no unmount endpoint.
  • With the feature off, the service account, the pod-reader role, the mounter objects, the projected token and every new environment variable render zero times.
  • The cluster run caught a real bug: file-service was being addressed through an endpoint whose path had to be stripped back off, when what GeeseFS needs is the root the S3 proxy is served at. Fixed.

Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Opus 5

@github-actions github-actions Bot added feature frontend Changes related to the frontend GUI infra common platform Non-amber Scala service paths labels Jul 25, 2026
@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • Contributors with relevant context: @tanishqgandhi1908, @Ma77Ball, @mengw15
    You can notify them by mentioning @tanishqgandhi1908, @Ma77Ball, @mengw15 in a comment.

@codecov-commenter

codecov-commenter commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 57.97101% with 58 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.57%. Comparing base (a268c1c) to head (962260e).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...texera/service/util/ComputingUnitNodeLocator.scala 5.55% 34 Missing ⚠️
.../service/resource/ComputingUnitMountResource.scala 70.37% 10 Missing and 6 partials ⚠️
...org/apache/texera/service/util/MounterClient.scala 82.97% 3 Missing and 5 partials ⚠️
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     
Flag Coverage Δ *Carryforward flag
access-control-service 71.59% <57.97%> (-9.41%) ⬇️
agent-service 99.32% <ø> (ø) Carriedforward from a268c1c
amber 89.20% <ø> (-0.72%) ⬇️
computing-unit-managing-service 75.05% <ø> (ø)
config-service 87.12% <ø> (ø)
file-service 81.70% <ø> (-1.96%) ⬇️
frontend 96.77% <ø> (ø) Carriedforward from a268c1c
notebook-migration-service 83.57% <ø> (ø)
pyamber 98.47% <ø> (ø) Carriedforward from a268c1c
workflow-compiling-service 77.19% <ø> (ø)

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

⚠️ Benchmark changes need a look

🟢 2 better · 🔴 7 worse · ⚪ 6 noise (<±5%) · 0 without baseline

Compared against main a268c1c benchmarked on this same runner, so the delta is largely free of cross-runner hardware noise. The "7d avg" column still reflects the gh-pages dashboard. Treat <±5% as noise unless repeated.

Dashboard · Run

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.16

@aicam
aicam marked this pull request as draft August 3, 2026 16:44
@aicam
aicam force-pushed the feat/mount-B1-platform branch from d4f0f3a to 972ca8f Compare August 13, 2026 20:20
@aicam
aicam force-pushed the feat/mount-B1-platform branch 2 times, most recently from c993afe to 05c243f Compare August 17, 2026 20:47
@aicam
aicam force-pushed the feat/mount-B1-platform branch from 05c243f to cadf7d0 Compare September 8, 2026 19:06
@aicam aicam changed the title feat(dataset-mount): mount datasets onto a computing unit feat(dataset-mount): mount datasets and models onto a computing unit Sep 8, 2026
@aicam
aicam force-pushed the feat/mount-B1-platform branch from cadf7d0 to 3797945 Compare September 8, 2026 20:22
@aicam aicam changed the title feat(dataset-mount): mount datasets and models onto a computing unit feat(dataset-mount): mount authority and version resolution Sep 8, 2026
@github-actions github-actions Bot removed the frontend Changes related to the frontend GUI label Sep 8, 2026
@aicam
aicam force-pushed the feat/mount-B1-platform branch 7 times, most recently from 021245a to a3fcb8d Compare September 8, 2026 21:47
@aicam
aicam force-pushed the feat/mount-B1-platform branch from a3fcb8d to 7422e25 Compare September 8, 2026 21:53
@aicam aicam changed the title feat(dataset-mount): mount authority and version resolution feat(dataset-mount): authorize and perform a repository mount Sep 8, 2026
@github-actions github-actions Bot removed the common label Sep 8, 2026
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
@aicam
aicam force-pushed the feat/mount-B1-platform branch from 45b49bd to 962260e Compare September 8, 2026 22:10
@github-actions github-actions Bot added dependencies Pull requests that update a dependency file common labels Sep 8, 2026
@aicam
aicam marked this pull request as ready for review September 8, 2026 22:25
@aicam
aicam requested a review from parshimers September 8, 2026 22:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

common dependencies Pull requests that update a dependency file feature infra platform Non-amber Scala service paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Authorize and perform a repository mount

2 participants