Skip to content

Make live Cloud integration CI selective, on-demand, and resilient #403

Description

@sdairs

Context

The live Cloud integration workflow is expensive, mutates a shared test organization, and currently runs on every matching PR update. This becomes particularly noisy and unreliable when refreshing a stack of PRs.

Example failure: https://github.com/ClickHouse/clickhousectl/actions/runs/31696362617/job/94434938976?pr=393

That job failed on a one-shot query-endpoint upsert with 503 SERVICE_UNAVAILABLE. It was not related to the PR changes. During the same stacked-PR refresh, many Cloud Integration jobs entered the shared organization together and produced:

  • 429 TOO_MANY_REQUESTS while creating API keys, updating services, and cleaning up.
  • Repeated 503 SERVICE_UNAVAILABLE responses from query-endpoint upserts.
  • A transient Query API 404 saying the ClickHouse service was currently unavailable.
  • Postgres 500 Internal error responses.

The linked job spent roughly four minutes compiling, failed about one minute into the live test, and then spent another 2.5 minutes cleaning up before reporting failure. Rerunning the job repeats all of that work.

Current behavior

.github/workflows/cloud-integration.yml runs five suites sequentially in one job:

  1. ClickHouse service lifecycle.
  2. Postgres lifecycle.
  3. Organization lifecycle.
  4. Postgres CDC ClickPipe.
  5. Optional ClickPipe smoke tests.

The workflow currently triggers for every change under crates/clickhousectl/src/cloud/**, but it runs only cargo test -p clickhouse-cloud-api. CLI-only changes therefore launch live API tests that neither compile nor execute the changed CLI code.

The API source is now split into domain modules, but every API-domain change still launches every live suite. ClickStack, UDF, and backup changes also launch the workflow even though it has no live suite for those domains.

Existing resilience is limited:

  • poll_until retries polling checks at a fixed interval, without jitter or error classification.
  • retry_api_call exists but is used only for the known Postgres read-replica "no backups yet" race.
  • Direct management operations normally get one attempt.
  • Cleanup mutations normally get one attempt and can themselves fail with 429 or 5xx.
  • The API test client has no explicit request timeout.
  • Workflow concurrency is scoped to one PR ref, so it does not limit concurrent jobs from different PRs in a stack.

Goals

  • Keep ordinary build, unit-test, clippy, and formatting CI automatic.
  • Make live Cloud integration opt-in rather than running on every PR update.
  • Allow one full run on the top PR of a stack to validate the complete inherited stack.
  • Avoid running unrelated live suites when a targeted domain run is sufficient.
  • Absorb temporary API throttling and service failures without rerunning a complete job.
  • Keep cleanup reliable when the original test operation fails.

Proposed work

1. Harden live tests

  • Add bounded exponential backoff with jitter for 429, retryable 5xx, and transport timeouts.
  • Back off more aggressively after transient polling errors instead of keeping every job on the same fixed cadence.
  • Fail immediately on permanent API errors rather than waiting for a polling timeout.
  • Retry only safe reads and idempotent updates, upserts, state changes, and deletes.
  • Apply the same retry policy to cleanup operations.
  • Add an explicit management-request timeout.
  • Do not blindly retry resource-creation POSTs after ambiguous transport or server failures. Reconcile by deterministic run tags/name first so retries cannot create duplicate resources.
  • Add mock coverage for transient-success, terminal-error, timeout, and cleanup cases.

2. Select suites by affected domain

Keep one workflow with a small changed-path classifier rather than duplicating the complete secret-bearing job across several workflows.

Changed domain Live suites
Services Service, ClickPipe CDC
Postgres Postgres, ClickPipe CDC
ClickPipes ClickPipe CDC, smoke
Organizations, activity, members, RBAC Organization
API keys Service, Organization
ClickStack, UDFs, backups None until a corresponding live suite exists
CLI Cloud modules None; regular CLI CI owns these
Shared client, error, models, test support, manifests All

Scheduled and explicitly requested full runs should select every suite. Unknown new API source paths should fail closed by selecting all suites.

Selected suites can remain sequential in one job to reuse compilation and avoid creating another traffic burst. Each selected step should still run if an earlier selected suite fails, followed by one final result step that reports the aggregate failure.

3. Make live CI on-demand

The leading option is a maintainer-applied label such as can be tested or run-cloud-integration.

Recommended semantics:

  • The expensive live workflow responds to the pull_request labeled event, not every synchronize event.
  • Applying the full-suite label is a one-shot trigger for the PR head SHA at that moment.
  • A maintainer can apply it only to the top PR of a stack. Because that branch contains every lower stack commit, a full run validates the combined stack.
  • New commits do not automatically launch another live run. Remove and reapply the label when another run is wanted.
  • The workflow may remove the trigger label after enqueueing so it behaves like a button, although that requires narrowly scoped pull-requests: write permission.
  • The full-suite label must override changed-path selection. The top PR diff is relative to its immediate stacked base and does not list changes made by lower PRs, even though those commits are present in the tested checkout.
  • Keep the weekday scheduled run as a full sweep against main.

GitHub label permissions provide the intended approval boundary: users need triage/write access to apply repository labels. Fork PRs still do not receive repository secrets under pull_request; do not switch to pull_request_target and execute untrusted PR code. A maintainer-owned mirror branch is the safe route when a fork needs live validation.

Other trigger options

Manual workflow dispatch

Add workflow_dispatch inputs for PR/ref and scope (all, affected, service, postgres, org, or clickpipes). This is secure and explicit for maintainers, but less discoverable from the PR page and requires care to ensure the exact intended SHA is tested.

PR comment command

A command such as /run-cloud-ci all is convenient and can support scopes, but authorization and secret handling are more complex. An issue_comment/pull_request_target workflow must only validate the commenter and dispatch a separate trusted workflow; it must never directly check out and execute PR code with secrets.

Persistent approval label

Treat the label as approval and continue running on every later push while it remains attached. This resembles the main ClickHouse repository model, but it is less suitable here because repeated pushes to a labeled PR can recreate the same load. One-shot label semantics are preferable for this repository.

Environment approval

Trigger workflows automatically but hold the live job behind a protected GitHub environment. This prevents unapproved API access but leaves many pending workflow runs and becomes awkward during stack refreshes. It is not preferred.

Ready-for-review or merge-ready trigger

Run once when a PR leaves draft or receives approval. This is lightweight but less explicit, and review changes do not always correspond to the exact moment a live Cloud run is useful. It could complement, but should not replace, a manual trigger.

Suggested rollout

  1. Add retry/backoff, request timeouts, and cleanup hardening.
  2. Remove CLI-only live triggers and add domain-aware suite selection.
  3. Add a one-shot maintainer label for a full run, retaining manual and scheduled full runs.
  4. Optionally add scoped labels or workflow_dispatch inputs for targeted reruns.
  5. Observe retry logs, API status distribution, run frequency, and leaked-resource counts before changing concurrency further.

Acceptance criteria

  • A CLI-only, ClickStack-only, UDF-only, or backup-only PR update does not launch live Cloud tests.
  • A domain-targeted invocation runs only the mapped suites.
  • Applying the full-suite label to the top PR of a stack tests that exact head SHA and runs all suites once.
  • Subsequent pushes do not automatically rerun the one-shot labeled workflow.
  • Transient 429 and retryable 5xx responses show bounded backoff and can recover within the same test run.
  • Cleanup uses the same transient retry policy and reports any final failure without losing the original test error.
  • Scheduled runs against main continue to exercise the full suite.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requesttest-coverageLive API integration test coverage work

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions