Skip to content

fix(jetstream): report CF's exact status for a 5xx - #6049

Merged
norman-abramovitz merged 3 commits into
cloudfoundry:developfrom
nabramovitz:norm/fix/capi-5xx-status
Oct 23, 2026
Merged

norman-abramovitz merged 3 commits into
cloudfoundry:developfrom
nabramovitz:norm/fix/capi-5xx-status

Conversation

@nabramovitz

@nabramovitz nabramovitz commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

When a Cloud Foundry endpoint keeps answering with a server error (5xx), Stratos showed "Cloud Foundry endpoint '' / Request failed." with no status code. The capi client retried the 5xx and then returned "giving up after N attempt(s)", which carried no status, sentinel or error envelope, so Jetstream could not tell an endpoint fault from any other failure. With this change the banner reads "HTTP 503: The endpoint is unreachable or down." with Cloud Foundry's own code.

This follows #6047, which passed Cloud Foundry's 4xx statuses through. Its "Not in this PR" section named this case.

What changes

capi pin (chore(deps): pin capi to the fork's integration branch)

  • The replace for github.com/fivetwenty-io/capi/v3 moves from the fork's fix/si-delete-204 branch (a3625ed, fix: treat a 204 service instance delete as done fivetwenty-io/capi#16) to its stratos/integration branch (a4e9d75). That branch merges Adding app.model.modelManager #16 with two capi changes:
    • A 5xx that outlasts the retries maps through MapHTTPError like any other error response, so it keeps capi.ErrServerError and Cloud Foundry's v3 error envelope.
    • Every error MapHTTPError returns is a *capi.StatusError that carries the exact HTTP status. The change is additive: messages, errors.Is on the sentinels and errors.As on *capi.ResponseError behave as before.
  • With the first change, the native handlers already classify a persistent CF 5xx as unreachable and pass Cloud Foundry's envelope through. The associate-user test that pinned the old plain 502 with no reason now expects that.
  • All seven Go modules are tidy.

Exact status (fix(jetstream): report CF's exact status for a 5xx)

  • upstreamStatusOf reads *capi.StatusError first, so X-Stratos-Upstream-Status and the error body's upstreamStatus carry Cloud Foundry's code for an envelope 5xx and for a 4xx outside the single-status sentinels, such as a 400.
  • The "(status NNN)" message parse and the single-status sentinel mapping stay as fallbacks for errors whose wrap chain no longer reaches the StatusError. One example is the "no web process" error, which wraps a bare capi.ErrNotFound.
  • statusFromCapiError is unchanged. It sets the response status, which still answers a CF 5xx with 502.

Tests

  • Go: TestUpstreamStatusOf gains a 400 and a 503 envelope case built by MapHTTPError. Both failed before the status commit (they reported 0).
  • Go: in TestAssociateUser_CFRejectionKeepsStatus, the "create server error" case runs the real capi client against a stub that answers 503 to every create attempt. It now expects a 502 with reason unreachable, upstream status 503 and Cloud Foundry's CF-ServerError envelope. Before the bump it got a plain 502 with no reason and no status.
  • make check gate: passed (4581 tests passed, 1 skipped). Go lint: 0 issues in all seven modules.

Live checks

Against a Cloud Foundry lab. One registered endpoint pointed at a local proxy that forwards to the CF API and, while armed, answers every GET /v3/spaces with a real 503 CF-ServerError envelope, so the failure goes through Jetstream and capi's retries. Same DB and the same clicks, first with develop's backend, then with this branch's:

Step develop this branch
Org Spaces tab, proxy disarmed Spaces load, no banner Spaces load, no banner
Same tab, proxy armed 502; banner "Cloud Foundry endpoint '' / Request failed." with no code 502; banner "Cloud Foundry endpoint '' / HTTP 503: The endpoint is unreachable or down."
Same org through an endpoint that bypasses the proxy, still armed — Spaces load, no banner
Proxy disarmed again — Spaces load, no banner

The response status stays 502 in both, as intended; only the reason and the upstream status change.

Dependency note

The pin is a pseudo-version of a personal fork. The upstream fivetwenty-io/capi PR for the two new capi changes opens after this release, together with #16. The replace is dropped once a capi release carries all three.

Release note: changelog.d/0025-capi-5xx-status.md.

AI disclosure (RFC-0047): prepared with AI coding assistance; reviewed by the author.

The capi replace moves from fix/si-delete-204 (a3625ed) to the
fork's stratos/integration branch (a4e9d75), which merges that fix
with two more:

- A CF 5xx that outlasts capi's retries used to come back as "giving
  up after N attempt(s)", with no sentinel and no envelope. It now
  maps through MapHTTPError like any other error response, so it keeps
  capi.ErrServerError and CF's v3 error envelope.
- Every error MapHTTPError returns is a *capi.StatusError carrying the
  exact HTTP status. This change does not read it yet.

With the first, the native handlers classify a persistent CF 5xx as
unreachable and pass CF's envelope through. The associate-user test
that pinned the old plain 502 with no reason now expects that.

All seven Go modules are tidy.
upstreamStatusOf read the CF status from the "(status NNN)" text in
capi's message, or from a sentinel that stands for one status. A CF v3
error envelope carries no "(status NNN)", and capi.ErrServerError and
capi.ErrBadRequest each cover a range, so an envelope 5xx or other 4xx
reported no status. The endpoint banner for a CF 5xx read "Request
failed." with no code.

It now reads *capi.StatusError first, which capi returns for every
mapped error response. X-Stratos-Upstream-Status and the error body's
upstreamStatus carry CF's code, and the banner reads "HTTP 503: The
endpoint is unreachable or down.". The message and sentinel fallbacks
stay for errors whose wrap chain no longer reaches the StatusError,
such as the "no web process" error that wraps a bare capi.ErrNotFound.

statusFromCapiError is unchanged: it picks the response status, which
still answers a CF 5xx with 502.

@norman-abramovitz norman-abramovitz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@nabramovitz
nabramovitz marked this pull request as ready for review October 23, 2026 00:04
@norman-abramovitz
norman-abramovitz merged commit ed9a164 into cloudfoundry:develop Oct 23, 2026
24 checks passed
@nabramovitz
nabramovitz deleted the norm/fix/capi-5xx-status branch October 23, 2026 01:09
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