Commit 7015dd7
authored
SEP-1941: Add a diagnostics-delivery target to the connectivity check (#1441)
Adds a `delivery` target to `POST /api/sep/admin/connectivity-check/`,
so an
operator can test the configured diagnostics-delivery receiver without
sending a
bundle. It fans out alongside the existing four targets and is isolated
the same
way — a delivery failure never fails the response or the other targets.
**The probe request is declared on the delivery plan, not derived from
its send
steps.** Replaying the send steps is not possible three ways over: a
resolution
step may be a mutating `POST` (`sidecar/settings.yaml:111` declares
one), its
values may cite the `source_ref` / `case_ref` / `manifest` send inputs
that only
exist during a real send, and the upload step's values may cite an
earlier step's
outputs. So `DeliveryPlan` gains an optional `probe` field, sibling to
`upload`:
- `ProbeStep` carries `path`, `headers` and `query` only — no `name` (so
it joins
no step namespace and cannot collide with a configured step), no
`method` (GET
by construction), no `body`, no `outputs`.
- Its value maps are typed with `ProbeValue`, a discriminated union
narrowed to
`literal` and `secret`. The other three sources are refused by pydantic
at
parse time with `union_tag_invalid`, rather than by a validator —
`_check_value` (`app/sep/bundle_upload/plan.py`) branches only on
`SecretValue`
and `StepOutputValue`, so cross-reference validation alone would let
`input`
and `manifest_key` through. The two rules that validator does own — a
secret
must be declared, and no secret in `query` — are reused unchanged.
- `ProbeStep.path` is validated as a relative reference.
`URL(origin).join()`
resolves a `//host/p` or `https://host/p` path *instead of* the endpoint
rather
than under it, which would send the probe's credentials to a host the
plan
never named. This is a misconfiguration guard, not an attacker guard:
`DIAGNOSTICS_DELIVERY` is settable through the settings file and
environment
variables only, never the settings API, so anyone who can write a probe
path
can already repoint `endpoint`.
`DeliveryPlanExecutor.probe()` issues that one GET through
`get_delivery_executor`, reusing the existing per-call, unpooled
transport and
`redact_headers(_secret_valued_keys(...))`. It runs no resolution step
and no
upload, and records no step trail.
**It reads no response body either.** `RemoteAPI.request()` parses every
body
but a `204` before it checks the status, so a receiver whose health
route
answers `200 text/plain` reaches the caller as a `2xx` `HTTPException`
and
would have been reported as an upstream error. `upload()` already
documents
and tolerates that case inline; this promotes its condition to
`is_non_json_success()` in `app/core/requests/remote_api.py` and applies
it in
both callers. A non-JSON *error* status still fails (a `401 text/html`
still
reports `auth_failed`), and an unfollowed redirect still fails — that
branch
raises before the body is parsed, so it carries no non-JSON stamp.
`_rebased_plan` gains a `probe` branch. It enumerates step kinds by
hand, so
without it a plan endpoint carrying a path prefix or query would have
probed the
wrong URL — and only a prefixed-endpoint test can detect that, since the
unrebased path is already correct under a root endpoint.
`_rebase_query`'s first
parameter widens from `dict` to `Mapping` so the probe's narrower value
map goes
through the same helper; no behaviour changes.
`DeliveryPlanResolution` gains a `DeliveryUnavailableCode`, so the route
tells an
unconfigured deployment from one whose stored inputs have drifted
without
matching on the resolver's prose; that prose still travels verbatim as
the result
`detail`. The dataclass invariant now requires the code and reason to be
set
together. `unavailable()` takes the code as a required argument, and a
new
`unconfigured()` classmethod names the outcome its three call sites
share. Both
existing consumers (`app/sep/apps/atw/deps.py`,
`app/sep/apps/atw/send.py`) read
only `.unavailable_reason` and are unchanged.
`ConnectivityStatusEnum` widens by three members — `not_configured`,
`inputs_drifted`, `probe_undeclared` — each with a `_DEFAULT_DETAILS`
entry,
which is required rather than optional: `build_connectivity_result`
indexes that
dict unguarded. `classify_connectivity_error` is unchanged; the three
are only
ever passed explicitly.
The delivery probe takes its own `EXTERNAL_PROBE_TIMEOUT_SECONDS = 15`,
declared
beside `PROBE_TIMEOUT_SECONDS`, which stays 5s for the four
intra-cluster
targets. Because targets are gathered concurrently, this caps the
worst-case
whole-response wait at 15s rather than lengthening it per target.
The Settings → Test connection panel requests the new target and labels
all three
new statuses. `ALL_TARGETS` and `STATUS_CHIP` are hardcoded in
`TestConnectionButton.tsx`, so a backend-only change would have shipped
a target
no UI ever asks for; a test now asserts the requested target list. The
settings
e2e spec gains the fifth row and two of the three new statuses — its
comment
claiming every status is rendered end to end was true for four targets
and is
now stated accurately, with `inputs_drifted` covered by the component
test
instead.
**Behaviour change on an existing target:** an unconfigured PMM reports
`not_configured` instead of `unreachable`. `reachable` stays `false` and
the
`detail` text is unchanged, so only a consumer branching on the status
value is
affected. It ships with its own changelog fragment.
## Known limitations
- **The side-car's baked probe block is not included.**
`sidecar/settings.yaml`
points at production ServiceNow, and this branch has no production
credential
to confirm the connector key may read the probe target. The path is
confirmed
working against the development instance (`perconadev.service-now.com`
returns 200 with a real key, 401 with a bogus one or none), but
production
authenticates before resolving a table, so a credential-free check there
establishes nothing about the read ACL. Shipping an unverified block
would let
the side-car report `auth_failed` against a receiver that would accept a
real
send — worse than no probe, because it tells an operator to fix a
credential
that is fine. The side-car answers `probe_undeclared` until a follow-up
supplies a verified path; the ticket's In Scope explicitly provides for
this.
The owner to ask is the ServiceNow connector owner named on
https://perconadev.atlassian.net/browse/PMM-15390
<!-- followup -->
- **`ResolutionStep.path` and `UploadStep.path` carry the same
off-origin hazard
the new `ProbeStep.path` validator closes.** They are not fixed here:
adding
the validator to them would change validation for every already-deployed
plan,
and a plan that fails validation resolves to "delivery unavailable",
turning
delivery off on upgrade.
<!-- followup -->
- **A connect-phase failure reports `unreachable`, not `timeout`.**
`BaseRemoteAPI.__aenter__` sets a client timeout of `total=300,
connect=5,
sock_connect=5, sock_read=120` in app/core/requests/remote_api.py:357,
so a
receiver slow to *connect* fails at aiohttp's 5s connector bound before
the
probe's own 15s bound is reached. This is deliberate and left alone: the
probe's timeout profile then matches a real send's, which is what a
"test this
connection" button should report. Widening `RemoteAPI` would change
every
caller.
- **A misspelled `probe:` key is silently ignored.** `DeliveryPlan`
declares no
`model_config`, so pydantic's default `extra='ignore'` applies. This is
the
same property that makes a settings file carrying `probe:` safe to
deploy
against an older image — it is ignored, not rejected — so it is kept
rather
than closed.1 parent 6fc5955 commit 7015dd7
20 files changed
Lines changed: 1191 additions & 47 deletions
File tree
- app
- core/requests
- sep
- api/routes
- bundle_upload
- changelog.d
- frontend/packages
- api
- specs
- src/generated
- e2e/tests
- shell/src/components/settings
- __tests__
- tests/app
- core/requests
- sep
- api/routes
- bundle_upload
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
34 | 41 | | |
35 | 42 | | |
36 | 43 | | |
| |||
46 | 53 | | |
47 | 54 | | |
48 | 55 | | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
49 | 59 | | |
50 | 60 | | |
51 | 61 | | |
52 | 62 | | |
53 | | - | |
| 63 | + | |
| 64 | + | |
54 | 65 | | |
55 | 66 | | |
56 | 67 | | |
57 | 68 | | |
58 | 69 | | |
59 | 70 | | |
60 | 71 | | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
61 | 77 | | |
62 | 78 | | |
63 | 79 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
| |||
159 | 160 | | |
160 | 161 | | |
161 | 162 | | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
162 | 181 | | |
163 | 182 | | |
164 | 183 | | |
| |||
1104 | 1123 | | |
1105 | 1124 | | |
1106 | 1125 | | |
1107 | | - | |
1108 | | - | |
1109 | | - | |
| 1126 | + | |
1110 | 1127 | | |
1111 | 1128 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
52 | 58 | | |
53 | 59 | | |
54 | 60 | | |
| |||
61 | 67 | | |
62 | 68 | | |
63 | 69 | | |
| 70 | + | |
64 | 71 | | |
65 | 72 | | |
66 | 73 | | |
| |||
92 | 99 | | |
93 | 100 | | |
94 | 101 | | |
95 | | - | |
| 102 | + | |
96 | 103 | | |
97 | 104 | | |
98 | 105 | | |
| |||
150 | 157 | | |
151 | 158 | | |
152 | 159 | | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
153 | 213 | | |
154 | 214 | | |
155 | 215 | | |
| |||
188 | 248 | | |
189 | 249 | | |
190 | 250 | | |
| 251 | + | |
| 252 | + | |
191 | 253 | | |
192 | 254 | | |
193 | 255 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
| 27 | + | |
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
72 | | - | |
| 72 | + | |
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
76 | 80 | | |
77 | 81 | | |
78 | 82 | | |
| |||
94 | 98 | | |
95 | 99 | | |
96 | 100 | | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
97 | 105 | | |
98 | 106 | | |
99 | 107 | | |
| |||
114 | 122 | | |
115 | 123 | | |
116 | 124 | | |
117 | | - | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
118 | 138 | | |
119 | 139 | | |
120 | 140 | | |
| |||
0 commit comments