Skip to main content

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

ComponentWhere it comes from
Bundled manifest at src/main/resources/bootstrap-demo.yamlLoaded 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-Hscripts/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-DSscripts/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 catalogThe bootstrap manifest is applied to the control plane, then PoolSupervisor.reconcile spawns nodes from each pool's role distribution.

Tenants and tenant-dbs

TenantTenant-dbDefault schemaPools
acmeacme_tpch (DuckLake on Postgres)tpch1bi (1 read-only + 1 dual), etl (1 write-only + 1 dual)
globexglobex_tpcds (DuckLake on Postgres)tpcds1bi (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

RoleGrantsDemonstrates
analystRO on customer, orders, nation, regionNarrow per-table read
etlRW on lineitem, RO on ordersRW includes read so a writer can also SELECT
dbaDDL on tpch1.*DDL alone, no data verbs
tenant_adminALL on *.*.*Wildcard, tenant-scoped (does NOT reach globex)

globex

RoleGrantsDemonstrates
analystRO on store_sales, customer, customer_demographicsSame pattern, different schema
etlRW on web_sales, RO on store_sales
cross_tenant_analystRO on acme_pg.tpch1.customer, RO on acme_pg.tpch1.ordersCross-tenant read via federation, explicit grants required
tenant_adminALL on *.*.*Wildcard, tenant-scoped

Groups

TenantGroupRoles
acmeanalystsanalyst
acmedata-engetl, dba (multi-role group)
globexanalystsanalyst

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.

UsernameTenantPasswordRole-grant pathPool grant
rootnone (superuser)demo-rootbypassn/a
aliceacmedemo-alicedirect: analystbi
bobacmedemo-bobgroup data-eng -> etl, dbaetl
daveacmedemo-davedirect: dbaetl
acme-adminacmedemo-acme-admindirect: tenant_adminbi, etl
carolglobexdemo-carolgroup analysts -> analyst AND direct: cross_tenant_analystbi
globex-adminglobexdemo-globex-admindirect: tenant_adminbi

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)

StatementDecisionWhy
SELECT * FROM customerAllowedanalyst RO covers acme_tpch.tpch1.customer
INSERT INTO customer VALUES (...)DeniedRO does not cover Write
SELECT * FROM globex_tpcds.tpcds1.store_salesDeniedanalyst has no globex grant; * wildcard is tenant-scoped

bob (acme etl + dba via data-eng group)

StatementDecisionWhy
INSERT INTO lineitem VALUES (...)Allowedetl role grants RW on lineitem (via data-eng group)
CREATE TABLE x (id INT)Alloweddba role grants DDL on tpch1.* (via data-eng group)

dave (acme dba only)

StatementDecisionWhy
SELECT * FROM customerDenieddba role grants DDL only, no Read
CREATE TABLE foo (id INT)AllowedDDL covers Ddl access

acme-admin (acme tenant admin)

StatementDecisionWhy
SELECT * FROM customerAllowedtenant_admin *.*.* ALL covers everything in acme
SELECT * FROM globex_tpcds.tpcds1.store_salesDeniedcatalog * is tenant-scoped; does not reach a sibling tenant's catalog

carol (globex analyst + cross-tenant federation reader)

StatementDecisionWhy
SELECT * FROM store_salesAllowedanalyst via analysts group
SELECT * FROM acme_pg.tpch1.customerAllowedcross_tenant_analyst direct grant on the federated table
SELECT * FROM acme_pg.tpch1.lineitemDeniedno grant on lineitem under acme_pg

globex-admin (globex tenant admin)

StatementDecisionWhy
SELECT * FROM acme_pg.tpch1.customerDeniedtenantCatalogs enumerates tenant-dbs only, not federated aliases. *.*.* ALL does NOT reach acme_pg; an explicit named grant would be required.

root (superuser, no tenant)

StatementDecisionWhy
SELECT * FROM globex_tpcds.tpcds1.store_salesAllowedsuperuser bypass; no per-statement check

Logging in

Admin UI (http://localhost:20900/ui/)

  • Tenant ID blank, username root, password demo-root for the superuser console.
  • Tenant ID acme and any acme username/password for a tenant-scoped view of acme's roles, groups, users, and pool permissions.
  • Tenant ID globex for the globex view (which also surfaces the acme_pg federated source under globex_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_analyst via the Administering access page and watch carol's effective set widen on the next handshake.
  • Read Federation to see how acme_pg's setupSql is 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.