Skip to content

feat(e2e): performance harness with incremental checkpoints - #212

Merged
squizzi merged 5 commits into
mainfrom
squizzi/fix-e2e-perf-path-rename
Sep 3, 2026
Merged

feat(e2e): performance harness with incremental checkpoints#212
squizzi merged 5 commits into
mainfrom
squizzi/fix-e2e-perf-path-rename

Conversation

@squizzi

@squizzi squizzi commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

What

Implements the HYPERSHELL-18 performance-test spec: an infra-agnostic e2e performance harness with batched, checkpointed gateway scale-up.

Highlights

  • Adds E2E_MODE=short|long step-depth tagging to the e2e suite (tests/e2e/lib.sh, tests/e2e/e2e-openshell.sh); long remains the default so existing CI is unchanged.
  • New tests/e2e/e2e-performance.sh harness: batched gateway scale-up with bounded concurrency, per-batch short-mode checkpoints, a final long-mode functional gate, optional SLO gating, and EXIT-trap teardown.
  • New tests/e2e/perf/lib.sh utilities (timing, latency percentiles, bounded concurrency, schema_version=1 JSON results I/O) plus tests/e2e/perf/lib_test.sh unit tests (no cluster required).
  • New scripts/perf-report.sh and make e2e-performance / make e2e-performance-report targets to run the harness and tabulate local run history.
  • Spec correction: the report depends only on bash, not python3/jq as previously stated.

Scope

Local/manual performance testing only — not wired into PR CI (too slow/heavy for the 20-minute gate). The OpenShift driver itself remains out of scope (tracked under HYPERSHELL-44).

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Team

Run ID: b066fe90-1f8c-4c34-825f-6ce3a0d45afa

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@squizzi
squizzi force-pushed the squizzi/fix-e2e-perf-path-rename branch 2 times, most recently from e174d67 to 65f52c7 Compare August 26, 2026 20:41
@jsell-rh

jsell-rh commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Amber review

Status: In progress

Amber started a review of commit 65f52c7 at 2026-08-27T18:46:49Z.

@jsell-rh

jsell-rh commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Amber review

Status: Complete

Verdict

COMMENT (approve-with-nits). This is a well-structured, well-tested local/manual performance harness plus a partial OpenShift e2e driver and an E2E_MODE=short|long split; there is no production Go code, long remains the default so existing CI is untouched, and both cluster-free unit suites (tests/e2e/perf/lib_test.sh, tests/e2e/openshift_driver_test.sh) pass locally. My findings are all Minor (worker credential handling, a small JSON-schema default inconsistency, and nondeterministic seed selection); none block merge, but a couple of cross-PR coordination points below need a maintainer decision.

I reviewed against CLAUDE.md, security.spec.md, control-plane/conventions.spec.md, and the review checklists. I paid particular attention to Test Diff Scrutiny: the pre-existing assertions in tests/e2e/e2e-openshell.sh are gated behind if e2e_step long, and because E2E_MODE defaults to long, every previously-unconditional assertion still runs in the default/CI path — no guarantee is silently removed. The CA-extraction assertion that changed for the openshift branch is a new-driver code path, not a weakened contract for the existing kind path.

Findings

  1. [Minor] Credentials spliced into bash -c worker command strings (argv exposure).
    In tests/e2e/e2e-performance.sh, both the scale-up workers (export E2E_OIDC_PASSWORD="'"${E2E_OIDC_PASSWORD}"'" and friends) and the teardown workers build a bash -c '...' string with the secret values interpolated as literals. Those values then appear in the child process argv (visible via ps//proc/<pid>/cmdline) and the single-quote splice breaks on any value containing a quote or shell metacharacter. These are dev/test credentials so severity is Minor, but the safer pattern is to export the variables in the parent shell and let the backgrounded subshell inherit them — which the code already relies on for OPENSHIFT_NAMESPACE/E2E_HS_NAMESPACE. That would also remove the current asymmetry where OIDC vars are forwarded explicitly but OPENSHIFT_NAMESPACE (needed by the openshift driver's acquire_oidc_token) is only inherited. Confidence: High.

  2. [Minor] perf_results_init default latency objects omit the avg key.
    tests/e2e/perf/lib.sh:404-405 initialize PERF_RES_CREATE_JSON/PERF_RES_TTR_JSON to {"p50": null, ...} without avg, whereas the module-level defaults (lines 283-284), the documented schema, and completed-run writes all include avg. A run that is interrupted before scale-up metrics are computed therefore writes a history file whose time_to_running_seconds object has no avg. This happens to render as - (matching the spec's "Partial Run Shows Dashes" scenario, so it is harmless in practice), but the two init strings should include "avg": null for schema consistency. Confidence: High.

  3. [Minor] Nondeterministic seed selection.
    e2e_discover_seed_ids / e2e_json_first_id (tests/e2e/lib.sh) bind to the first fleet/cluster/release returned by the API. That is correct for the single-seed dev/CI environment the harness targets, but on any environment with more than one fleet/cluster/release the harness silently binds to an arbitrary one determined by API ordering. Consider selecting by a known name or documenting the single-seed assumption at the discovery call site. Confidence: Medium.

Cross-PR coordination

Two open pull requests require a maintainer decision or a defined merge order relative to this one:

  • feat(control-plane): adopt upstream OpenShell Helm chart for gateway deployments #194 (adopt upstream OpenShell Helm chart for gateway deployments) changes the gateway deployment mechanism and renames in-namespace gateway resources (e.g. openshell-caopenshell-gateway-ca, openshell-serveropenshell-gateway-server) while editing the same gateway-verification region of tests/e2e/e2e-openshell.sh that this PR reworks into short/long steps. This PR's new OpenShift driver (discover_gateway_endpoint, wait_for_gateway_route) and the performance harness readiness checks assume the current provisioning path and resource names. The two make incompatible assumptions about how a gateway is deployed and what its in-namespace objects are called; whichever merges second must reconcile the resource names and re-validate the harness/driver readiness probes against the Helm-based deployment. Both PRs also rewrite the shared skills/RECONCILE.md checkpoint state, so the coverage/gap tables will need to be reconciled rather than blindly merged. A merge-order decision is needed.

  • docs: define control plane reconciliation contract #200 (define control plane reconciliation contract) revises the gateway-deletion and managed-namespace-GC contract in the same specs/platform/e2e-testing.spec.md, restating deletion as finalizer-driven cleanup. This PR adds new normative scenarios and a short-mode "throwaway gateway" GC test premised on the delete-driven GC semantics (accepts 204/404 and polls the namespace to disappearance). The two edits describe the same behavior from different angles; maintainers should confirm which deletion/GC wording is authoritative and align this PR's new short-mode GC assertions with docs: define control plane reconciliation contract #200's finalization model, deciding the merge order accordingly.

Findings Summary (ordered by severity, highest first)

  1. [Minor] Credentials interpolated into bash -c worker argv - Security (tests/e2e/e2e-performance.sh)
  2. [Minor] perf_results_init default latency objects omit avg - Spec/Schema Consistency (tests/e2e/perf/lib.sh:404-405)
  3. [Minor] Nondeterministic seed fleet/cluster/release selection - Robustness (tests/e2e/lib.sh)

Convention Checklist

Convention Result
Conventional commit messages Pass
Input validated (E2E_MODE validated, fails fast) Pass
Reconcile / reuse-or-create pattern (not create-or-skip) Pass
Test Diff Scrutiny (default long preserves existing assertions) Pass
No secrets in logs or command arguments Fail (Minor - argv splice)
Config separate from code (env-driven knobs) Pass

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

COMMENT (approve-with-nits). This is a well-structured, well-tested local/manual performance harness plus a partial OpenShift e2e driver and an E2E_MODE=short|long split; there is no production Go code, long remains the default so existing CI is untouched, and both cluster-free unit suites (tests/e2e/perf/lib_test.sh, tests/e2e/openshift_driver_test.sh) pass locally. My findings are all Minor (worker credential handling, a small JSON-schema default inconsistency, and nondeterministic seed selection); none block merge, but a couple of cross-PR coordination points below need a maintainer decision.

I reviewed against CLAUDE.md, security.spec.md, control-plane/conventions.spec.md, and the review checklists. I paid particular attention to Test Diff Scrutiny: the pre-existing assertions in tests/e2e/e2e-openshell.sh are gated behind if e2e_step long, and because E2E_MODE defaults to long, every previously-unconditional assertion still runs in the default/CI path — no guarantee is silently removed. The CA-extraction assertion that changed for the openshift branch is a new-driver code path, not a weakened contract for the existing kind path.

Findings

  1. [Minor] Credentials spliced into bash -c worker command strings (argv exposure).
    In tests/e2e/e2e-performance.sh, both the scale-up workers (export E2E_OIDC_PASSWORD="'"${E2E_OIDC_PASSWORD}"'" and friends) and the teardown workers build a bash -c '...' string with the secret values interpolated as literals. Those values then appear in the child process argv (visible via ps//proc/<pid>/cmdline) and the single-quote splice breaks on any value containing a quote or shell metacharacter. These are dev/test credentials so severity is Minor, but the safer pattern is to export the variables in the parent shell and let the backgrounded subshell inherit them — which the code already relies on for OPENSHIFT_NAMESPACE/E2E_HS_NAMESPACE. That would also remove the current asymmetry where OIDC vars are forwarded explicitly but OPENSHIFT_NAMESPACE (needed by the openshift driver's acquire_oidc_token) is only inherited. Confidence: High.

  2. [Minor] perf_results_init default latency objects omit the avg key.
    tests/e2e/perf/lib.sh:404-405 initialize PERF_RES_CREATE_JSON/PERF_RES_TTR_JSON to {"p50": null, ...} without avg, whereas the module-level defaults (lines 283-284), the documented schema, and completed-run writes all include avg. A run that is interrupted before scale-up metrics are computed therefore writes a history file whose time_to_running_seconds object has no avg. This happens to render as - (matching the spec's "Partial Run Shows Dashes" scenario, so it is harmless in practice), but the two init strings should include "avg": null for schema consistency. Confidence: High.

  3. [Minor] Nondeterministic seed selection.
    e2e_discover_seed_ids / e2e_json_first_id (tests/e2e/lib.sh) bind to the first fleet/cluster/release returned by the API. That is correct for the single-seed dev/CI environment the harness targets, but on any environment with more than one fleet/cluster/release the harness silently binds to an arbitrary one determined by API ordering. Consider selecting by a known name or documenting the single-seed assumption at the discovery call site. Confidence: Medium.

Cross-PR coordination

Two open pull requests require a maintainer decision or a defined merge order relative to this one:

  • #194 (adopt upstream OpenShell Helm chart for gateway deployments) changes the gateway deployment mechanism and renames in-namespace gateway resources (e.g. openshell-caopenshell-gateway-ca, openshell-serveropenshell-gateway-server) while editing the same gateway-verification region of tests/e2e/e2e-openshell.sh that this PR reworks into short/long steps. This PR's new OpenShift driver (discover_gateway_endpoint, wait_for_gateway_route) and the performance harness readiness checks assume the current provisioning path and resource names. The two make incompatible assumptions about how a gateway is deployed and what its in-namespace objects are called; whichever merges second must reconcile the resource names and re-validate the harness/driver readiness probes against the Helm-based deployment. Both PRs also rewrite the shared skills/RECONCILE.md checkpoint state, so the coverage/gap tables will need to be reconciled rather than blindly merged. A merge-order decision is needed.

  • #200 (define control plane reconciliation contract) revises the gateway-deletion and managed-namespace-GC contract in the same specs/platform/e2e-testing.spec.md, restating deletion as finalizer-driven cleanup. This PR adds new normative scenarios and a short-mode "throwaway gateway" GC test premised on the delete-driven GC semantics (accepts 204/404 and polls the namespace to disappearance). The two edits describe the same behavior from different angles; maintainers should confirm which deletion/GC wording is authoritative and align this PR's new short-mode GC assertions with #200's finalization model, deciding the merge order accordingly.

Findings Summary (ordered by severity, highest first)

  1. [Minor] Credentials interpolated into bash -c worker argv - Security (tests/e2e/e2e-performance.sh)
  2. [Minor] perf_results_init default latency objects omit avg - Spec/Schema Consistency (tests/e2e/perf/lib.sh:404-405)
  3. [Minor] Nondeterministic seed fleet/cluster/release selection - Robustness (tests/e2e/lib.sh)

Convention Checklist

Convention Result
Conventional commit messages Pass
Input validated (E2E_MODE validated, fails fast) Pass
Reconcile / reuse-or-create pattern (not create-or-skip) Pass
Test Diff Scrutiny (default long preserves existing assertions) Pass
No secrets in logs or command arguments Fail (Minor - argv splice)
Config separate from code (env-driven knobs) Pass

Comment thread tests/e2e/e2e-performance.sh Outdated
export E2E_OIDC_ISSUER="'"${E2E_OIDC_ISSUER}"'"
export E2E_OIDC_CLIENT_ID="'"${E2E_OIDC_CLIENT_ID}"'"
export E2E_OIDC_USERNAME="'"${E2E_OIDC_USERNAME}"'"
export E2E_OIDC_PASSWORD="'"${E2E_OIDC_PASSWORD}"'"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor - Security] The scale-up worker interpolates E2E_OIDC_PASSWORD (and the other OIDC vars) as a literal inside the bash -c '...' string, so the secret ends up in the child process argv (ps//proc/<pid>/cmdline) and the single-quote splice breaks on any value containing a quote/metacharacter. The teardown worker (~line 182) does the same. Prefer export-ing these in the parent shell and letting the backgrounded subshell inherit them — as this code already does for OPENSHIFT_NAMESPACE. That also removes the asymmetry where OPENSHIFT_NAMESPACE (needed by the openshift driver's acquire_oidc_token) is only inherited, not forwarded.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 85c7a8c. Scale-up and teardown workers now inherit API_HOST, OIDC, seed ids, and OPENSHIFT_NAMESPACE from the parent via perf_export_child_env. The bash -c strings no longer interpolate secrets into argv.

Comment thread tests/e2e/perf/lib.sh Outdated
PERF_RES_WALL="null"
PERF_RES_THROUGHPUT="null"
PERF_RES_CREATE_JSON='{"p50": null, "p90": null, "p99": null, "max": null}'
PERF_RES_TTR_JSON='{"p50": null, "p90": null, "p99": null, "max": null}'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor - Schema consistency] These init defaults omit the avg key, unlike the module-level defaults (lines 283-284), the documented schema, and completed-run writes, which all include avg. An interrupted run therefore writes time_to_running_seconds with no avg. It renders as - (matching the spec's "Partial Run Shows Dashes" scenario, so harmless), but include "avg": null here for schema consistency.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 85c7a8c. perf_results_init now writes the same {"avg": null, "p50": null, "p90": null, "p99": null, "max": null} objects as the module-level defaults and the schema.

Comment thread tests/e2e/lib.sh
@squizzi
squizzi force-pushed the squizzi/fix-e2e-perf-path-rename branch 2 times, most recently from 1ca6671 to 8e0a159 Compare August 27, 2026 23:10
Implements the HYPERSHELL-18 performance-test spec: adds E2E_MODE
(short/long) step depth to the e2e suite, an infra-agnostic
tests/e2e/e2e-performance.sh harness that scales up a gateway fleet in
batches with per-batch short-mode checkpoints, tests/e2e/perf/lib.sh
utilities (timing, percentiles, bounded concurrency, JSON results I/O),
scripts/perf-report.sh for local reporting, and the make
e2e-performance / e2e-performance-report targets.

Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Kyle Squizzato <kysquizz@redhat.com>
Assisted-by: Cursor Grok 4.6
Signed-off-by: Kyle Squizzato <kysquizz@redhat.com>
Short-mode throwaway gateways failed every performance run because
cluster and release ids were not forwarded and discovery skipped when
fleet was already set. Report output now documents the table, prints
wall clock as HH:MM:SS, and aligns checkpoint rows.

Assisted-by: Cursor Grok 4.6
Export OIDC and API env in the parent so bash -c workers inherit
credentials instead of taking them on argv. Include avg in init
latency JSON. Kind seed discovery selects default/local-kind/
dev-release by name unless the caller overrides.

Assisted-by: Cursor Grok 4.6
Assisted-by: Cursor Grok 4.6
Signed-off-by: Kyle Squizzato <kysquizz@redhat.com>
@squizzi
squizzi force-pushed the squizzi/fix-e2e-perf-path-rename branch from 8e0a159 to c298b9e Compare September 3, 2026 14:03
@squizzi
squizzi marked this pull request as ready for review September 3, 2026 14:21
@squizzi
squizzi added this pull request to the merge queue Sep 3, 2026
@jsell-rh

jsell-rh commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Amber review: comment

Amber review

Status: Complete

View the submitted review.

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

This PR adds a well-structured, infra-agnostic bash performance harness (batched scale-up, canary checkpoints, functional gate, optional SLOs, schema_version=1 JSON) plus a manual OpenShift e2e driver and E2E_MODE=short|long step tagging; it is backward-compatible (long remains the default) and touches only test/build/spec files, no Go production code. The changes are carefully written around security (secrets go through the environment, never argv), are covered by runnable unit tests that pass locally, and the spec/RECONCILE updates track the behavior — I have only minor comments and one cross-PR coordination item.

Amber Assessment

Confidence: High. I read the full diff, ran bash -n on every changed script, and executed both tests/e2e/perf/lib_test.sh (28 tests) and tests/e2e/openshift_driver_test.sh (9 tests) — all pass with no cluster required.

What's good

  • No secrets in argv/logs. perf_export_child_env deliberately passes OIDC credentials through the child environment rather than splicing them into the bash -c command string, and there is a unit test asserting the password never reaches worker argv. Good instinct.
  • Backward compatible. E2E_MODE defaults to long, and for the kind driver _driver_curl remains curl -sk, so existing CI behavior is unchanged. The OpenShift driver correctly verifies TLS (curl -sS).
  • Signal-safe teardown. The EXIT trap masks INT/TERM during cleanup, cancels workers before removing temp state, deletes via the API first and then reaps tracked namespaces under one bounded timeout. This is thoughtful.
  • Removes hardcoded ids. Gateway create bodies now discover seeded cluster/release ids via the API instead of the previous hardcoded e2e-cluster/e2e-release, and the head commit correctly drops Fleet seeding to match the post-Fleet data model on main.

Findings (minor)

See inline comments. Neither blocks merge.

  1. [Minor] e2e_lookup_gateway_by_name interpolates the gateway name into the search=name%3D${name} query string without URL-encoding the value. Safe today (names are DNS labels and the Python filter re-checks exact name), but a value containing &/space would corrupt the query. Robustness (tests/e2e/lib.sh).
  2. [Minor] perf_provision_one runs acquire_oidc_token 2>/dev/null || true, so a systemic auth failure surfaces only as generic per-gateway fail records with no reason recorded. Consider a dedicated auth stage/record. Observability (tests/e2e/perf/lib.sh).

Cross-PR coordination

Another open pull request (#232, [HYPERSHELL-44] OpenShift development) implements OpenShift support for the same HYPERSHELL-44 / openshift-development.spec.md scope as this PR's OpenShift slice. The maintainers/owner should coordinate the following before both land:

  • Overlapping ownership of the OpenShift effort and its specs. This PR adds a manual OpenShift e2e/perf driver (tests/e2e/drivers/openshift.sh) framed as a partial implementation of openshift-development.spec.md, while #232 adds the OpenShift cluster lifecycle (scripts/cluster/drivers/openshift.sh, make openshift-up/down/swap). The two introduce distinct openshift.sh "driver" abstractions and both rewrite the same coverage tables in skills/RECONCILE.md and edit specs/platform/e2e-testing.spec.md, specs/platform/local-development.spec.md, and tests/e2e/e2e-openshell.sh. A merge order and an agreed ownership boundary are needed so the coverage tables and spec sections converge rather than overwrite each other.
  • Conflicting data-model assumption about Fleet. This PR's head commit removes Fleet seeding from e2e/local dev to match main (Fleet was deleted from the stack). #232 still seeds a Fleet resource as part of openshift-up. That assumption contradicts the current data model and must be reconciled — a decision is required on which direction is authoritative before #232 merges.

Findings Summary (ordered by severity, highest first):

  1. [Minor] Gateway name not URL-encoded in search= query - Robustness (tests/e2e/lib.sh L225)
  2. [Minor] Silent auth-failure swallowing in provisioning worker - Observability (tests/e2e/perf/lib.sh L641)

Convention Checklist (only conventions applicable to this diff):

Convention Result
No secrets in logs/argv Pass
Input validated (URL/query construction) Minor gap
Errors propagated, not silently swallowed Minor gap
Config separate from code (env-driven, no hardcoded ids) Pass
Conventional commit messages Pass
Tests accompany new behavior Pass
Spec/RECONCILE updated to match behavior Pass

Comment thread tests/e2e/lib.sh
_GW_NAMESPACE=""
_GW_PHASE=""
local resp
resp=$(api_curl "${API_HOST}/api/hypershell/v1/gateways?search=name%3D${name}" 2>/dev/null || true)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gateway name is interpolated straight into the query string (search=name%3D${name}) without URL-encoding the value. It's safe for the current callers (names are K8s DNS labels and the Python block below re-filters on an exact name match), so this is not a blocker — but a value containing &, space, or # would silently corrupt the query. Consider URL-encoding the value (e.g. via the existing python3 helper) if this lookup is ever reused for arbitrary names.

Comment thread tests/e2e/perf/lib.sh
running_s="null"
printf '%s\t%s\n' "$name" "authenticating" > "${record}.state"

acquire_oidc_token 2>/dev/null || true

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

acquire_oidc_token 2>/dev/null || true swallows all auth output and errors here. Under a systemic auth failure (expired admin creds, Keycloak down) every worker will still proceed and record a generic fail with no reason, making triage harder during a large scale-up. Consider recording an explicit auth-failure stage/record (as perf_delete_gateway_by_name already does with its auth marker) so the diagnostics distinguish auth failures from provisioning timeouts.

Merged via the queue into main with commit bcfb896 Sep 3, 2026
15 checks passed
@squizzi
squizzi deleted the squizzi/fix-e2e-perf-path-rename branch September 3, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants