@@ -249,6 +249,100 @@ The status uses Kubernetes-style conditions instead of a single phase field:
249249 - One adapter reports ` Available=False ` for ` observed_generation=1 ` ` Available ` transitions to ` False `
250250 - One adapter reports ` Available=False ` for ` observed_generation=2 ` ` Available ` keeps its ` True ` status
251251
252+ ### Aggregation logic
253+
254+ Description of the aggregation logic for the resource status conditions
255+
256+ - An API that stores resources entities (clusters, nodepools)
257+ - A sentinel that polls the API for changes and triggers messages
258+ - Instances of "adapters":
259+ - Read the messages
260+ - Reconcile the state with the world
261+ - Report back to the API, using statuses "conditions"
262+
263+ Resources keep track of its status, which is affected by the reports from adapters
264+
265+ - Each resource keeps a ` generation ` property that gets increased on every change
266+ - Adapters associated with a resource, report their state as an array of adapter conditions
267+ - Three of these conditions are always mandatory : ` Available ` , ` Applied ` , ` Health `
268+ - If one of the mandatory conditions is missing, the report is discarded
269+ - A ` observed_generation ` field indicating the generation associated with the report
270+ - ` observed_time ` for when the adapter work was done
271+ - If the reported ` observed_generation ` is lower than the already stored ` observed_generation ` for that adapter, the report is discarded
272+ - Each resource has a list of associated "adapters" used to compute the aggregated status.conditions
273+ - Each resource "status.conditions" is array property composed of:
274+ - The ` Available ` condition of each adapter, named as ` <adapter-name>Successful `
275+ - 2 aggregated conditions: ` Ready ` and ` Available ` computed from the array of ` Available ` resource statuses conditions
276+ - Only ` Available ` condition from adapters is used to compute aggregated conditions
277+
278+ The whole API spec is at: < https://raw.githubusercontent.com/openshift-hyperfleet/hyperfleet-api/refs/heads/main/openapi/openapi.yaml >
279+
280+ The aggregation logic for a resource (cluster/nodepool) works as follows.
281+
282+ ** Notation:**
283+
284+ - ` X ` = report's ` observed_generation `
285+ - ` G ` = resource's current ` generation `
286+ - ` statuses[] ` = all stored adapter condition reports
287+ - ` lut ` = ` last_update_time `
288+ - ` ltt ` = ` last_transition_time `
289+ - ` obs_gen ` = ` observed_generation `
290+ - ` obs_time ` = report's ` observed_time `
291+ - ` — ` = no change
292+
293+ ---
294+
295+ #### Discard / Reject Rules
296+
297+ Checked before any aggregation. A discarded or rejected report causes no state change.
298+
299+ | Rule | Condition | Outcome |
300+ | ---| ---| ---|
301+ | ` obs_gen ` too high | report ` observed_generation ` > resource ` generation ` | Discarded |
302+ | Stale adapter report | report ` observed_generation ` < adapter's stored ` observed_generation ` | Discarded |
303+ | Missing mandatory conditions | Missing any of ` Available ` , ` Applied ` , ` Health ` , or value not in ` {True, False, Unknown} ` | Discarded |
304+ | Available=Unknown | Report is valid but ` Available=Unknown ` | Discarded |
305+
306+ ---
307+
308+ #### Lifecycle Events
309+
310+ | Event | Condition | Target | → status | → obs_gen | → lut | → ltt |
311+ | ---| ---| ---| ---| ---| ---| ---|
312+ | Creation | — | ` Ready ` | ` False ` | ` 1 ` | ` now ` | ` now ` |
313+ | Creation | — | ` Available ` | ` False ` | ` 1 ` | ` now ` | ` now ` |
314+ | Change (→G) | Was ` Ready=True ` | ` Ready ` | ` False ` | ` G ` | ` now ` | ` now ` |
315+ | Change (→G) | Was ` Ready=False ` | ` Ready ` | ` False ` | ` G ` | ` now ` | ` — ` |
316+ | Change (→G) | — | ` Available ` | unchanged | unchanged | ` — ` | ` — ` |
317+
318+ ---
319+
320+ #### Adapter Report Aggregation Matrix
321+
322+ The ** Ready** check and ** Available** check are independent — both can apply to the same incoming report.
323+
324+ ##### Report ` Available=True ` (obs_gen = X)
325+
326+ | Target | Current State | Required Condition | → status | → lut | → ltt | → obs_gen |
327+ | ---| ---| ---| ---| ---| ---| ---|
328+ | ` Ready ` | ` Ready=True ` | ` X==G ` AND all ` statuses[].obs_gen==G ` AND all ` statuses[].status==True ` | unchanged | ` min(statuses[].lut) ` | ` — ` | ` — ` |
329+ | ` Ready ` | ` Ready=False ` | ` X==G ` AND all ` statuses[].obs_gen==G ` AND all ` statuses[].status==True ` | ** ` True ` ** | ` min(statuses[].lut) ` | ` obs_time ` | ` — ` |
330+ | ` Ready ` | any | Conditions above not met | ` — ` | ` — ` | ` — ` | ` — ` |
331+ | ` Available ` | ` Available=False ` | all ` statuses[].obs_gen==X ` | ** ` True ` ** | ` min(statuses[].lut) ` | ` obs_time ` | ` X ` |
332+ | ` Available ` | ` Available=True ` | all ` statuses[].obs_gen==X ` | unchanged | ` min(statuses[].lut) ` | ` — ` | ` X ` |
333+ | ` Available ` | any | Conditions above not met | ` — ` | ` — ` | ` — ` | ` — ` |
334+
335+ ##### Report ` Available=False ` (obs_gen = X)
336+
337+ | Target | Current State | Required Condition | → status | → lut | → ltt | → obs_gen |
338+ | ---| ---| ---| ---| ---| ---| ---|
339+ | ` Ready ` | ` Ready=False ` | ` X==G ` | unchanged | ` min(statuses[].lut) ` | ` — ` | ` — ` |
340+ | ` Ready ` | ` Ready=True ` | ` X==G ` | ** ` False ` ** | ` obs_time ` | ` obs_time ` | ` — ` |
341+ | ` Ready ` | any | Conditions above not met | ` — ` | ` — ` | ` — ` | ` — ` |
342+ | ` Available ` | ` Available=False ` | all ` statuses[].obs_gen==X ` | unchanged | ` min(statuses[].lut) ` | ` — ` | ` X ` |
343+ | ` Available ` | ` Available=True ` | all ` statuses[].obs_gen==X ` | ** ` False ` ** | ` obs_time ` | ` obs_time ` | ` X ` |
344+ | ` Available ` | any | Conditions above not met | ` — ` | ` — ` | ` — ` | ` — ` |
345+
252346## NodePool Management
253347
254348### Endpoints
@@ -456,7 +550,7 @@ The status object contains synthesized conditions computed from adapter reports:
456550- All above fields plus:
457551- ` observed_generation ` - Generation this condition reflects
458552- ` created_time ` - When condition was first created (API-managed)
459- - ` last_updated_time ` - When adapter last reported (API-managed, from AdapterStatus. last_report_time)
553+ - ` last_updated_time ` - When this condition was last refreshed (API-managed). For ** Available ** , always the evaluation time. For ** Ready ** : when Ready=True, the minimum of ` last_report_time ` across all required adapters that report Available=True at the current generation; when Ready=False, the evaluation time (so consumers can detect staleness).
460554- ` last_transition_time ` - When status last changed (API-managed)
461555
462556## Parameter Restrictions
0 commit comments