Demo bootstrap (LOAD_TPC)
LOAD_TPC=N is the single flag that turns a fresh install into a self-contained, fully populated multi-tenant demo. One command, two tenants, two real datasets, a complete RBAC graph, and a cross-tenant federated catalog, all reproducible and all live against a real Postgres + DuckDB stack.
# native
NUKE=1 LOAD_TPC=1 ./scripts/run-jar.sh
# docker compose
BUILD=1 NUKE=1 LOAD_TPC=1 ./scripts/run-docker-compose.sh
# kubernetes (local stack)
NUKE=1 LOAD_TPC=1 ./charts/quack-on-demand/local-stack-k8s/run-local-stack-k8s.sh
The N is the TPC scale factor; LOAD_TPC=1 is the fast path (~10 seconds of TPC-H + ~30 seconds of TPC-DS). Use LOAD_TPC=10 for a bigger workload.
What gets created
| Component | Where it comes from |
|---|---|
Bundled manifest at src/main/resources/bootstrap-demo.yaml | Loaded into the JVM classpath; imported on boot via DemoBootstrapHook when QOD_BOOTSTRAP_YAML=classpath:bootstrap-demo.yaml is set (the launcher script sets this automatically when LOAD_TPC is non-empty). |
acme_tpch Postgres database, seeded with TPC-H | scripts/load-tpch-dbgen.sh forked by the launcher; runs DuckDB's dbgen(sf=N) and copies the 8 TPC-H tables into the DuckLake catalog. |
globex_tpcds Postgres database, seeded with TPC-DS | scripts/load-tpcds-dbgen.sh forked by the launcher; runs DuckDB's dsdgen(sf=N) and copies the 24 TPC-DS tables. |
| Two tenants, three pools, six quack nodes, an RBAC graph, a federated catalog | The bootstrap manifest is applied to the control plane, then PoolSupervisor.reconcile spawns nodes from each pool's role distribution. |
Tenants and tenant-dbs
| Tenant | Tenant-db | Default schema | Pools |
|---|---|---|---|
acme | acme_tpch (DuckLake on Postgres) | tpch1 | bi (1 read-only + 1 dual), etl (1 write-only + 1 dual) |
globex | globex_tpcds (DuckLake on Postgres) | tpcds1 | bi (1 read-only + 1 dual) |
acme carries the TPC-H tables (customer, lineitem, nation, orders, part, partsupp, region, supplier). globex carries the TPC-DS tables (store_sales, web_sales, catalog_sales, customer, customer_demographics, and 19 others).
Roles and verb-matrix
The four verb classes are exercised explicitly. See Access control model for what RO / RW / DDL / ALL cover.
acme
| Role | Grants | Demonstrates |
|---|---|---|
analyst | RO on customer, orders, nation, region | Narrow per-table read |
etl | RW on lineitem, RO on orders | RW includes read so a writer can also SELECT |
dba | DDL on tpch1.* | DDL alone, no data verbs |
tenant_admin | ALL on *.*.* | Wildcard, tenant-scoped (does NOT reach globex) |
globex
| Role | Grants | Demonstrates |
|---|---|---|
analyst | RO on store_sales, customer, customer_demographics | Same pattern, different schema |
etl | RW on web_sales, RO on store_sales | |
cross_tenant_analyst | RO on acme_pg.tpch1.customer, RO on acme_pg.tpch1.orders | Cross-tenant read via federation, explicit grants required |
tenant_admin | ALL on *.*.* | Wildcard, tenant-scoped |
Groups
| Tenant | Group | Roles |
|---|---|---|
acme | analysts | analyst |
acme | data-eng | etl, dba (multi-role group) |
globex | analysts | analyst |
Users
All passwords are plaintext in the bundled manifest and are bcrypted by ManifestImporter at import time. Anyone reading the file in the jar sees the credentials, which is the point of a demo. The file is opt-in: nothing imports it unless LOAD_TPC (or QOD_BOOTSTRAP_YAML) is set.
| Username | Tenant | Password | Role-grant path | Pool grant |
|---|---|---|---|---|
root | none (superuser) | demo-root | bypass | n/a |
alice | acme | demo-alice | direct: analyst | bi |
bob | acme | demo-bob | group data-eng -> etl, dba | etl |
dave | acme | demo-dave | direct: dba | etl |
acme-admin | acme | demo-acme-admin | direct: tenant_admin | bi, etl |
carol | globex | demo-carol | group analysts -> analyst AND direct: cross_tenant_analyst | bi |
globex-admin | globex | demo-globex-admin | direct: tenant_admin | bi |
Federation
globex_tpcds carries a federated source named acme_pg that ATTACHes the acme_tpch Postgres database read-only. The connection secrets pull from the manager's existing QOD_PG_* environment variables via externalRef: env:QOD_PG_HOST (etc.), so the demo works unchanged in both native (localhost) and docker (postgres service) modes without a YAML edit.
When a globex user runs SELECT * FROM acme_pg.tpch1.customer, the query is routed to a globex node which transparently forwards it to acme's Postgres via DuckDB's postgres extension. The result returns through the same FlightSQL session.
ACL is still enforced over federated tables: cross_tenant_analyst is the role that opens the door, and it grants RO only on customer and orders under acme_pg.tpch1. Other acme tables (lineitem, supplier, ...) remain unreachable from globex.
Walkthrough: see the ACL in action
The matrix below is also pinned as BootstrapDemoEffectiveSpec in the test suite, so the documented decisions and the bundled YAML cannot drift apart silently.
alice (acme analyst, RO on a few TPC-H tables)
| Statement | Decision | Why |
|---|---|---|
SELECT * FROM customer | Allowed | analyst RO covers acme_tpch.tpch1.customer |
INSERT INTO customer VALUES (...) | Denied | RO does not cover Write |
SELECT * FROM globex_tpcds.tpcds1.store_sales | Denied | analyst has no globex grant; * wildcard is tenant-scoped |
bob (acme etl + dba via data-eng group)
| Statement | Decision | Why |
|---|---|---|
INSERT INTO lineitem VALUES (...) | Allowed | etl role grants RW on lineitem (via data-eng group) |
CREATE TABLE x (id INT) | Allowed | dba role grants DDL on tpch1.* (via data-eng group) |
dave (acme dba only)
| Statement | Decision | Why |
|---|---|---|
SELECT * FROM customer | Denied | dba role grants DDL only, no Read |
CREATE TABLE foo (id INT) | Allowed | DDL covers Ddl access |
acme-admin (acme tenant admin)
| Statement | Decision | Why |
|---|---|---|
SELECT * FROM customer | Allowed | tenant_admin *.*.* ALL covers everything in acme |
SELECT * FROM globex_tpcds.tpcds1.store_sales | Denied | catalog * is tenant-scoped; does not reach a sibling tenant's catalog |
carol (globex analyst + cross-tenant federation reader)
| Statement | Decision | Why |
|---|---|---|
SELECT * FROM store_sales | Allowed | analyst via analysts group |
SELECT * FROM acme_pg.tpch1.customer | Allowed | cross_tenant_analyst direct grant on the federated table |
SELECT * FROM acme_pg.tpch1.lineitem | Denied | no grant on lineitem under acme_pg |
globex-admin (globex tenant admin)
| Statement | Decision | Why |
|---|---|---|
SELECT * FROM acme_pg.tpch1.customer | Denied | tenantCatalogs enumerates tenant-dbs only, not federated aliases. *.*.* ALL does NOT reach acme_pg; an explicit named grant would be required. |
root (superuser, no tenant)
| Statement | Decision | Why |
|---|---|---|
SELECT * FROM globex_tpcds.tpcds1.store_sales | Allowed | superuser bypass; no per-statement check |
Logging in
Admin UI (http://localhost:20900/ui/)
- Tenant ID blank, username
root, passworddemo-rootfor the superuser console. - Tenant ID
acmeand any acme username/password for a tenant-scoped view of acme's roles, groups, users, and pool permissions. - Tenant ID
globexfor the globex view (which also surfaces theacme_pgfederated source underglobex_tpcds).
FlightSQL (grpc+tls://localhost:31338)
The standard FlightSQL JDBC URL form:
jdbc:arrow-flight-sql://localhost:31338
?useEncryption=true
&disableCertificateVerification=true
&tenant=acme
&pool=bi
&username=alice
&password=demo-alice
Replace tenant, pool, username, password with the personas from the table above. For the superuser, swap the tenant=... parameter for superuser=true.
What to try next
- Walk through the Access control model with concrete tenants in front of you.
- Add a new role to
globex.cross_tenant_analystvia the Administering access page and watch carol's effective set widen on the next handshake. - Read Federation to see how
acme_pg'ssetupSqlis composed and how secrets are resolved. - Strip the demo out: run without
LOAD_TPC, get an empty manager, build your own tenants via REST/UI.
Cleanup
NUKE=1 (without LOAD_TPC) tears down state and boots empty:
NUKE=1 ./scripts/run-jar.sh
It drops the control-plane database, the acme_tpch and globex_tpcds tenant-db databases, the certs, and the ducklake data directories. Run-as-you-like operator setups built via the REST API or UI on top of the demo will be wiped along with everything else.