test: add proxy burn-in tooling - #455
Conversation
|
Proxy already has benchmark setup that might be worth extending if it doesn't do what you need
|
Review: Standards & Spec (vs
|
179b9dc to
043b273
Compare
Ah, I just looked for a |
|
Addressed in
Verified with formatting, the burn-in package tests, and Clippy with warnings denied. |
45768c0 to
ff0243b
Compare
tobyhede
left a comment
There was a problem hiding this comment.
Review: Correctness at ff0243ba
The two follow-up commits fix the original aggregate type mismatch, add an automated encrypted soak, and verify ciphertext at rest. I rechecked the remaining findings against the new head; these still need attention.
Blocking
1. Release artifact discovery still ignores Cargo configuration — src/soak.rs:223-253 [repro]
cargo build respects CARGO_TARGET_DIR, build.target-dir, and configured build targets, but spawn_release_proxy() always opens workspace/target/release/cipherstash-proxy. On a shared-target setup, the run either fails after building or launches a stale workspace binary.
Consume Cargo’s JSON compiler-artifact.executable instead of reconstructing the path. That identifies the artifact produced by this exact build and handles target triples as well as custom target directories.
2. An existing listener on 6432 still makes soak measure a dead process — src/soak.rs:63-116, src/database.rs:183-193 [repro]
The child is reduced to a PID before readiness and is never checked again. With another Proxy already listening, the newly built child exits with Address already in use, readiness and fixture setup use the existing Proxy, and RSS sampling targets the dead child. I reproduced a successful run with 1,701 CRUD cycles, zero final RSS, and zero reported growth.
Preflight the configured bind address, but do not treat that as proof of identity—it remains racy. Pass the Child into the run loop, check try_wait() throughout startup, migration, sampling, and worker completion, and reject zero RSS samples.
3. A timed soak can run indefinitely and runtime failures can erase its report — src/soak.rs:89-136, src/database.rs:183-193
Connection, startup-handshake, readiness-query, and CRUD futures have no deadlines. Once the sampling deadline passes, join_next() can wait forever for a wedged worker.
Sampling and worker errors also propagate before the report is written. Add bounded readiness attempts, per-operation timeouts, and a bounded worker-shutdown period. Preserve partial samples and record the terminal error before returning failure.
4. Database credentials are exposed in help and errors — src/main.rs:31-50, src/database.rs:17-20,183-190 [repro]
Clap prints the complete BURN_IN_*_DATABASE_URL environment value in --help, and connection/readiness errors interpolate the same URL.
Use hide_env_values = true and a parsed connection type with a redacted display form. Never include raw database URLs in diagnostics or reports.
Should fix
5. The aggregate assertion still panics on its intended failure case — src/conformance.rs:86-97
The new ::bigint cast fixes the unconditional numeric decoding panic. However, if the join loses every row, sum(...)::bigint is NULL and get::<_, i64>() still panics before "joined CRUD result was corrupted" can fire. Decode with try_get::<_, Option<i64>>() and assert Some(4_998).
6. Concurrent runs can deadlock or invalidate one another — migrations/0001_schema.sql, migrations/0002_seed.sql:3-5, src/soak.rs:156-219
Every run drops and recreates the public fixture tables. The remaining TRUNCATE order is also the reverse of the CRUD insert order. Reordering prevents that specific lock inversion, but concurrent runs would still destroy or contaminate each other’s fixtures and measurements.
Acquire a run-level advisory lock and retain its connection for the entire conformance or soak run; a migration-only lock is insufficient.
7. Report and gate semantics remain inconsistent — src/soak.rs:113-153
A worker error already short-circuits at join_next(), so ensure!(report.errors == 0) cannot observe one. Zero completed cycles can pass, output-path errors are discovered only after the workload, and soak passed prints before the RSS gate—I reproduced it printing success immediately before exiting 1.
Require at least one completed cycle, include terminal status in partial reports, preflight output with a sibling temporary file and atomic rename, and print success only after all gates pass.
8. The migrated database may not be the spawned Proxy’s upstream — src/soak.rs:61-74, src/soak.rs:247-253
--direct-database-url selects the database where EQL is installed and ciphertext is inspected, but the child Proxy is spawned without database arguments and reads ambient CS_DATABASE__*. An override can therefore migrate one database while the child serves another.
Pass or validate the child’s upstream configuration. Record sanitized provenance in the report: artifact hash or commit, concurrency, timestamp, actual elapsed duration, and a redacted database identity.
Additional notes
- The new CI job closes the encryption-path and execution gaps. It runs only
soakand omits--max-rss-growth-mib, so deterministic conformance and retained-growth gating remain local-only. Adding them would strengthen coverage once a stable threshold is established. - First-to-last RSS delta matches the README’s “retained growth” wording. The immediate first tick is still a cold baseline; add a warm-up or delayed first sample. Trend fitting is optional rather than a correctness requirement.
- Add
kill_on_drop(true)and signal handling so panics and interrupts do not leave an owned child or discard all samples. - Make the wide-text assertion exact, use checked multiplication for
--max-rss-growth-mib, and establish a fixture migration/version strategy before the schema evolves again. - The updated package’s three unit tests pass. The live subcommands remain the only tests of lifecycle, encryption, and report behavior.
Separately, commits 5c72f021, 31836df6, d64fcba4, and ff0243ba lack the repository-required DCO sign-off, and src/main.rs:61 should say “connections,” not “sessions.”
Signed-off-by: James Sadler <james@cipherstash.com>
Signed-off-by: James Sadler <james@cipherstash.com>
Signed-off-by: James Sadler <james@cipherstash.com>
The burn-in fixtures previously lived in custom schemas, used only native PostgreSQL types, and referenced every table with schema-qualified names. Proxy therefore could not load or resolve the tables and silently treated the workload as unmappable passthrough traffic, so the soak could not detect encryption-path leaks. Install EQL when its domains are absent, move uniquely named fixtures into public, declare representative integer, text, and JSON columns with EQL v3 domains, and use unqualified table names throughout conformance and soak queries. Apply DDL through one Proxy connection and seed through a fresh connection so the new connection snapshots the reloaded schema and column encryption config. Seed encrypted values through Proxy rather than directly into PostgreSQL. Conformance now reads the underlying JSON through the direct connection and fails unless representative values have the EQL ciphertext shape, then verifies they decrypt to the original typed values through Proxy. Static regression tests lock down the public-schema, EQL-domain, and unqualified-query requirements. Signed-off-by: James Sadler <james@cipherstash.com>
Add a dedicated PostgreSQL 17 CI job that decrypts the standard test credentials, starts PostgreSQL, installs EQL, and runs a bounded release-Proxy soak. Keeping this outside the four-version test matrix exercises the leak-sensitive encryption path without multiplying the expensive release build across every supported PostgreSQL version. Expose the CI command as `mise run test:burn-in`, with configurable duration and concurrency, and upload the RSS report for diagnosis. Move the direct ciphertext-at-rest assertion into shared fixture migration so both conformance and the CI soak fail if workload writes ever fall back to plaintext. Document each burn-in module’s role and the public-table, unqualified-SQL, fresh-connection, and direct-ciphertext invariants that prevent the workload from silently becoming passthrough traffic. Signed-off-by: James Sadler <james@cipherstash.com>
Build the release proxy with Cargo JSON output and execute the exact compiler artifact, then configure its upstream from the parsed direct database target. Preflight the listener and continuously verify the owned child so an unrelated proxy can no longer make a dead child look healthy. Bound readiness, database operations, and worker shutdown; retain partial RSS evidence and terminal errors in an atomic report; require real work and live non-zero RSS before reporting success. Delay the first measurement until after warm-up and terminate the child on interruption or drop. Parse connection settings into a redacting type, hide environment defaults from CLI help, and acquire a run-wide advisory lock so concurrent burn-ins cannot corrupt shared fixtures. Also make aggregate NULL handling explicit, compare wide values exactly, use checked RSS-limit conversion, and truncate fixtures in dependency order. Signed-off-by: James Sadler <james@cipherstash.com>
ff0243b to
c9dbd6f
Compare
|
Addressed the latest review in
Regression coverage includes credential redaction, custom Cargo artifact discovery, report validity, occupied-listener rejection, and an end-to-end custom-target soak. The burn-in package tests and Clippy pass. I also rewrote the stack: GitHub reports every commit as validly signed by |
Summary
cipherstash-proxy-burn-inworkspace package with deterministic conformance coveragepublictables with representative EQL v3 integer, text, and JSON domainsSoak reliability
Verification
cargo fmt --all -- --checkRUSTC_WRAPPER= cargo test -p cipherstash-proxy-burn-inRUSTC_WRAPPER= cargo clippy -p cipherstash-proxy-burn-in --all-targets -- -D warningsCARGO_TARGET_DIR: 15 encrypted CRUD cycles, zero errors, live RSS report generatedjames@cipherstash.comand one DCO sign-off each