Skip to content

HYPERFLEET-853 - feat: add Reconciled condition alongside Ready for backward-compatible transition#111

Merged
openshift-merge-bot[bot] merged 8 commits into
openshift-hyperfleet:mainfrom
tirthct:hyperfleet-853
Apr 29, 2026
Merged

HYPERFLEET-853 - feat: add Reconciled condition alongside Ready for backward-compatible transition#111
openshift-merge-bot[bot] merged 8 commits into
openshift-hyperfleet:mainfrom
tirthct:hyperfleet-853

Conversation

@tirthct
Copy link
Copy Markdown
Contributor

@tirthct tirthct commented Apr 22, 2026

Summary

  • Replace Ready computation with Reconciled as the primary aggregated condition (HYPERFLEET-853)
  • Reconciled is a dual-mode condition:
    • Normal lifecycle (deleted_time not set): all adapters Available=True at current generation
    • Deletion lifecycle (deleted_time set): all adapters Finalized=True at current generation
  • Ready is deprecated — derived from Reconciled at the service layer for backward compatibility
  • Add Finalized condition type constant (not enforced as mandatory — keeps current adapters compatible)
  • Add BTREE index on status_conditions for efficient Reconciled queries
  • Update OpenAPI spec from hyperfleet-api-spec v1.0.8 (PR HYPERFLEET-853 - feat: Add Reconciled to OpenAPI spec hyperfleet-api-spec#34)

How it works

Internally, only Reconciled is computed. Ready is a copy of Reconciled with the type changed:

reconciled, available, adapterConditions := AggregateResourceStatus(...)
ready := reconciled                                                                                                                                                                                     
ready.Type = api.ConditionTypeReady

Both are stored in the DB so existing status.conditions.Ready='True' search queries continue to work. Customers can migrate to status.conditions.Reconciled='True' at their own pace.

Backward compatibility for existing DB data

parsePrevConditions uses stored Ready as a fallback for prevReconciled when no Reconciled condition exists yet — ensures correct transition time tracking for records written before this change.

Context
The deletion design (https://github.com/openshift-hyperfleet/architecture/blob/main/hyperfleet/components/adapter/framework/adapter-deletion-flow-design.md) requires a condition that adapts based on resource lifecycle. Ready doesn't work for deletion ("Ready=True" for a deleted resource is semantically wrong). Reconciled means "the desired state has been achieved" — works for both creation and
deletion.

Since no adapter sends Finalized in production today, Reconciled will always be False for deleted resources — which is correct (no cleanup has happened). The deletion-aware logic is ready for when adapters implement Finalized and the hard-delete mechanism (HYPERFLEET-854) lands.

Test plan

  • computeReconciled: normal lifecycle uses Available, deletion lifecycle uses Finalized
  • computeReconciled: correct status, reason, message, timestamps for both paths
  • parsePrevConditions: Ready used as prevReconciled fallback when no Reconciled stored
  • parsePrevConditions: Reconciled takes precedence over Ready when both present
  • normalizeAdapterReportsForAggregation: extracts finalizedTrue correctly
  • AggregateResourceStatus: returns Reconciled and Available (no Ready)
  • Deletion lifecycle integration: all Finalized=True → Reconciled=True
  • Deletion lifecycle integration: no Finalized → Reconciled=False
  • Normal lifecycle ignores Finalized
  • make test passes
  • make lint passes
  • make generate-all produces no additional diff
  • Local verification: created cluster via API, confirmed Reconciled and Ready in response

@openshift-ci openshift-ci Bot requested review from crizzo71 and ma-hill April 22, 2026 20:09
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature: introducing a Reconciled condition alongside Ready for backward compatibility during migration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
pkg/services/node_pool_test.go (1)

536-551: Add parity checks (Reconciled vs Ready) in transition test

This currently verifies only existence of Reconciled. Asserting status/observed-generation parity with Ready at each step would make regressions much easier to catch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/services/node_pool_test.go` around lines 536 - 551, The test currently
collects conditions into available, ready and reconciled (loop over conds and
variables available, ready, reconciled) and only asserts they are non-nil; add
parity assertions to compare the Reconciled and Ready conditions—specifically
assert that reconciled.Status equals ready.Status and
reconciled.ObservedGeneration equals ready.ObservedGeneration (or vice versa)
after the g.Expect(reconciled/available/ready).ToNot(BeNil()) checks so each
transition verifies status and observed-generation parity between Reconciled and
Ready.
pkg/services/cluster_test.go (1)

675-690: Strengthen transition assertions for Reconciled behavior

You validate presence of Reconciled, but not that it mirrors Ready through each transition in this test. Returning/asserting reconciled alongside ready would better protect the Phase 1 contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/services/cluster_test.go` around lines 675 - 690, The test currently
finds available, ready, and reconciled but only returns available and ready;
update the assertions to verify reconciled is present and that its
Status/Reason/Message (the same fields used for Ready) equals the Ready
condition on every transition, and modify the function to return reconciled as
well so callers can assert parity; locate the variables available, ready,
reconciled and the final g.Expect checks and add equality assertions between
reconciled and ready (and add reconciled to the return tuple).
pkg/services/aggregation.go (1)

311-361: Consider adding a comment noting the intentional duplication.

computeReconciled mirrors computeReady exactly (except for Type). Per the PR objectives, this is intentional for Phase 1, with Phase 2 (HYPERFLEET-854) planned to diverge the logic for deletion lifecycle handling.

A brief comment would help future maintainers understand this is not accidental duplication:

📝 Optional: Add clarifying comment
+// computeReconciled mirrors computeReady in Phase 1 (HYPERFLEET-853).
+// Phase 2 (HYPERFLEET-854) will diverge: Reconciled will aggregate Available
+// during normal lifecycle and Finalized during deletion lifecycle.
 func computeReconciled(
 	resourceGen int32,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/services/aggregation.go` around lines 311 - 361, Add a short in-code
comment at the top of the computeReconciled function stating that its
implementation intentionally mirrors computeReady for Phase 1 and will diverge
in a later change (reference HYPERFLEET-854) to handle deletion lifecycle;
mention that the duplication is deliberate to avoid confusion for future
maintainers and cite computeReady so readers can see the counterpart.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openapi/openapi.yaml`:
- Around line 1180-1187: The OpenAPI schema for the cluster's status conditions
is too strict: change the array constraint on status_conditions by lowering
minItems from 3 to 2 (or make it absent) so GET responses that currently only
include "Ready" and "Available" remain valid during phased rollout, and plan a
migration/backfill to populate the "Reconciled" condition before later
tightening to minItems: 3; update the schema element referencing minItems and
the description that lists mandatory conditions (mentioning "Ready",
"Reconciled", "Available") so they remain accurate during the phased rollout.

In `@pkg/db/migrations/202604211859_add_reconciled_index.go`:
- Around line 15-29: The current expression indexes
idx_clusters_reconciled_status and idx_node_pools_reconciled_status index the
whole JSON object returned by jsonb_path_query_first(status_conditions, ...)
which doesn’t help when queries filter on the scalar "status" field; change the
index expressions to extract and cast the 'status' scalar (e.g., using
jsonb_path_query_first(...) ->> 'status' and CAST to VARCHAR) so the index can
be used for comparisons like ->> 'status' = 'True'; update both CREATE INDEX
statements that reference jsonb_path_query_first(status_conditions, '$[*] ?
(@.type == "Reconciled")') to use the extracted/cast scalar instead.

---

Nitpick comments:
In `@pkg/services/aggregation.go`:
- Around line 311-361: Add a short in-code comment at the top of the
computeReconciled function stating that its implementation intentionally mirrors
computeReady for Phase 1 and will diverge in a later change (reference
HYPERFLEET-854) to handle deletion lifecycle; mention that the duplication is
deliberate to avoid confusion for future maintainers and cite computeReady so
readers can see the counterpart.

In `@pkg/services/cluster_test.go`:
- Around line 675-690: The test currently finds available, ready, and reconciled
but only returns available and ready; update the assertions to verify reconciled
is present and that its Status/Reason/Message (the same fields used for Ready)
equals the Ready condition on every transition, and modify the function to
return reconciled as well so callers can assert parity; locate the variables
available, ready, reconciled and the final g.Expect checks and add equality
assertions between reconciled and ready (and add reconciled to the return
tuple).

In `@pkg/services/node_pool_test.go`:
- Around line 536-551: The test currently collects conditions into available,
ready and reconciled (loop over conds and variables available, ready,
reconciled) and only asserts they are non-nil; add parity assertions to compare
the Reconciled and Ready conditions—specifically assert that reconciled.Status
equals ready.Status and reconciled.ObservedGeneration equals
ready.ObservedGeneration (or vice versa) after the
g.Expect(reconciled/available/ready).ToNot(BeNil()) checks so each transition
verifies status and observed-generation parity between Reconciled and Ready.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5e88d4a7-ca45-40d6-9090-11e3cedd1b75

📥 Commits

Reviewing files that changed from the base of the PR and between 4c6179f and 4e92480.

📒 Files selected for processing (10)
  • openapi/openapi.yaml
  • pkg/api/status_types.go
  • pkg/db/migrations/202604211859_add_reconciled_index.go
  • pkg/db/migrations/migration_structs.go
  • pkg/services/aggregation.go
  • pkg/services/aggregation_test.go
  • pkg/services/cluster.go
  • pkg/services/cluster_test.go
  • pkg/services/node_pool.go
  • pkg/services/node_pool_test.go

Comment thread openapi/openapi.yaml Outdated
Comment thread pkg/db/migrations/202604211859_add_reconciled_index.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
pkg/services/aggregation_test.go (1)

1294-1349: Strengthen the mirror-contract assertions for Reconciled vs Ready.

Line 1343 currently checks LastUpdatedTime, but LastTransitionTime is also part of the mirrored timestamp behavior and is not asserted. Add it here so mirror regressions are caught in one place.

Suggested test addition
 				if !ready.LastUpdatedTime.Equal(reconciled.LastUpdatedTime) {
 					t.Errorf("LastUpdatedTime mismatch: Ready=%v, Reconciled=%v",
 						ready.LastUpdatedTime, reconciled.LastUpdatedTime)
 				}
+				if !ready.LastTransitionTime.Equal(reconciled.LastTransitionTime) {
+					t.Errorf("LastTransitionTime mismatch: Ready=%v, Reconciled=%v",
+						ready.LastTransitionTime, reconciled.LastTransitionTime)
+				}

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/services/aggregation_test.go` around lines 1294 - 1349, Add an assertion
to mirror LastTransitionTime between the Ready and Reconciled results in the
test that calls AggregateResourceStatus (the subtest block using variables ready
and reconciled); specifically, after the LastUpdatedTime equality check, compare
ready.LastTransitionTime and reconciled.LastTransitionTime and fail the test if
they differ so the test verifies both timestamp fields are mirrored.
pkg/services/aggregation.go (1)

312-362: computeReconciled duplicates computeReady logic and can drift.

Line 312 onward repeats the same algorithm/timestamp/reason flow as computeReady, which makes future updates error-prone when Phase 1 requires mirror semantics. Prefer a shared helper parameterized by condition type + previous condition.

Refactor direction
-func computeReady(...) api.ResourceCondition { ... }
-func computeReconciled(...) api.ResourceCondition { ... }
+func computeGenerationGatedCondition(
+	conditionType string,
+	resourceGen int32,
+	refTime time.Time,
+	prev *api.ResourceCondition,
+	required []string,
+	byAdapter map[string]adapterAvailableSnapshot,
+) api.ResourceCondition { ... }
+
+func computeReady(...) api.ResourceCondition {
+	return computeGenerationGatedCondition(api.ConditionTypeReady, resourceGen, refTime, prev, required, byAdapter)
+}
+
+func computeReconciled(...) api.ResourceCondition {
+	return computeGenerationGatedCondition(api.ConditionTypeReconciled, resourceGen, refTime, prev, required, byAdapter)
+}

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/services/aggregation.go` around lines 312 - 362, computeReconciled
duplicates the same algorithm/timestamp/reason flow as computeReady, which risks
drift; refactor by extracting the shared logic into a single helper (e.g.,
computeConditionCommon or computeAvailabilityCondition) that is parameterized by
the condition Type (api.ConditionTypeReconciled vs api.ConditionTypeReady), the
previous condition pointer, and any condition-specific defaults (reason/message
builders like reasonAllAdaptersAvailable vs reasonMissingRequiredAdapters and
functions like buildReadyFalseMessage), then update both computeReconciled and
computeReady to call that helper with the appropriate parameters so timestamps,
status computation, reason selection, and message construction are centralized
and maintained in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/services/aggregation_test.go`:
- Around line 1294-1349: Add an assertion to mirror LastTransitionTime between
the Ready and Reconciled results in the test that calls AggregateResourceStatus
(the subtest block using variables ready and reconciled); specifically, after
the LastUpdatedTime equality check, compare ready.LastTransitionTime and
reconciled.LastTransitionTime and fail the test if they differ so the test
verifies both timestamp fields are mirrored.

In `@pkg/services/aggregation.go`:
- Around line 312-362: computeReconciled duplicates the same
algorithm/timestamp/reason flow as computeReady, which risks drift; refactor by
extracting the shared logic into a single helper (e.g., computeConditionCommon
or computeAvailabilityCondition) that is parameterized by the condition Type
(api.ConditionTypeReconciled vs api.ConditionTypeReady), the previous condition
pointer, and any condition-specific defaults (reason/message builders like
reasonAllAdaptersAvailable vs reasonMissingRequiredAdapters and functions like
buildReadyFalseMessage), then update both computeReconciled and computeReady to
call that helper with the appropriate parameters so timestamps, status
computation, reason selection, and message construction are centralized and
maintained in one place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e4a6f332-e5f9-441b-952a-6b73ae03ee9d

📥 Commits

Reviewing files that changed from the base of the PR and between 4e92480 and 4a33a29.

📒 Files selected for processing (2)
  • pkg/services/aggregation.go
  • pkg/services/aggregation_test.go

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/services/cluster_test.go (1)

667-689: Strengthen transition test by asserting Reconciled mirrors Ready at each step

The helper now checks Reconciled exists, but the scenario assertions still validate only Ready/Available. Consider returning Reconciled from getSynth() and asserting Reconciled.Status + ObservedGeneration match Ready on each transition to catch divergence regressions early.

Also applies to: 718-782

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/services/cluster_test.go` around lines 667 - 689, getSynth currently
returns only Available and Ready even though it verifies Reconciled exists;
modify getSynth to return the reconciled condition as a third return value
(change signature to return (api.ResourceCondition, api.ResourceCondition,
api.ResourceCondition)), and update callers to capture that third value. In each
transition/assertion block where Ready/Available are compared, add assertions
that reconciled.Status equals ready.Status and reconciled.ObservedGeneration
equals ready.ObservedGeneration to ensure Reconciled mirrors Ready; use the same
stored/status parsing logic in getSynth (clusterDao.Get, json.Unmarshal of
stored.StatusConditions) to locate the api.ConditionTypeReconciled entry. Ensure
all call sites (including the other similar helper usage) are updated to handle
the extra return value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/services/cluster_test.go`:
- Around line 667-689: getSynth currently returns only Available and Ready even
though it verifies Reconciled exists; modify getSynth to return the reconciled
condition as a third return value (change signature to return
(api.ResourceCondition, api.ResourceCondition, api.ResourceCondition)), and
update callers to capture that third value. In each transition/assertion block
where Ready/Available are compared, add assertions that reconciled.Status equals
ready.Status and reconciled.ObservedGeneration equals ready.ObservedGeneration
to ensure Reconciled mirrors Ready; use the same stored/status parsing logic in
getSynth (clusterDao.Get, json.Unmarshal of stored.StatusConditions) to locate
the api.ConditionTypeReconciled entry. Ensure all call sites (including the
other similar helper usage) are updated to handle the extra return value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d5e32d3c-edca-411e-8d43-9949a744f0fc

📥 Commits

Reviewing files that changed from the base of the PR and between 4a33a29 and a6ee035.

📒 Files selected for processing (3)
  • openapi/openapi.yaml
  • pkg/services/cluster_test.go
  • pkg/services/node_pool_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/services/node_pool_test.go

Comment on lines +121 to +127
reconciled = computeReconciled(
in.ResourceGeneration,
in.RefTime,
prevReconciled,
in.RequiredAdapters,
reports,
)
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.

I get from the PR comment that this duplication is intentional to mirror today's Available aggregated condition, and that in the future it will diverge when we include the Finalized condition from adapters when computing the Reconciled.

Let me challenge a bit this decision.

IMO the computation has to be exactly the same for Reconciled and Available, even after we add the new formula to take into account Finalized conditions.

This means, that it will be simpler/safer to just do:

reconciled = available
  • And avoid the code duplication.
  • You will not need to introduce the prevReconciled in the code signature

We could even go a bit further and rename code to work with Reconciled instead of Available and do the opposite:

  • rename computeAvailable -> computeReconciled
  • available=computeReconciled

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.

As an additional bonus points for not duplicating the functions, is that IMO we don't need to introduce more tests, since we are not adding the code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since we've added the deletion lifecycle branching in this PR, i.e., when deleted_time is set, computeReconciled checks Finalized instead of Available, I think they're now fundamentally different computations and can't be collapsed into reconciled = available. Wdyt?

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.

Before these changes we didn't have the concept of deleted resources, now he have
This changes the meaning of Ready

  • When a resource exists, being Ready means that all adapters report Available=True
  • When a resource doesn't exist being ready means that all adapters report Finalized=True

And then... we are just renaming Ready to Reconciled

These are not two separate things
We are just keeping them both for now to have some compatibility with e2e and infra repos

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Computed only Reconciled and Ready is now replicating the same

Comment thread openapi/openapi.yaml
created_time: '2021-01-01T10:00:00Z'
last_updated_time: '2021-01-01T10:00:00Z'
last_transition_time: '2021-01-01T10:00:00Z'
- type: Reconciled
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.

openapi.yaml changes need to update the openapi.info.version of the contract (at the top)

Moreover, the changes to the OpenAPI contract should be performed first in the https://github.com/openshift-hyperfleet/hyperfleet-api-spec repository and then the generated openapi.yaml file is copied to this repository.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Created PR openshift-hyperfleet/hyperfleet-api-spec#34 in openapi-spec

Comment thread openapi/openapi.yaml Outdated
last_transition_time: '2021-01-01T10:00:00Z'
- type: Reconciled
status: 'True'
reason: All adapters reconciled at the current generation
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.

Tip

nit — non-blocking suggestion

Category: Inconsistency

The OpenAPI examples for Reconciled show reason: All adapters reconciled at the current generation, but computeReconciled uses reasonAllAdaptersAvailable which produces "All required adapters reported Available=True at the current generation".

Either update the examples to match the actual code output, or introduce a dedicated reason constant for Reconciled (e.g. reasonAllAdaptersReconciled) — the latter would also help when Phase 2 diverges the logic.

Same mismatch on lines 184, 387, 1057 (clusters) and the nodepool schema example.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed the message here and openshift-hyperfleet/hyperfleet-api-spec#34

Comment thread pkg/services/aggregation_test.go Outdated
if r != nil || a != nil || len(m) != 0 {
r, a, rc, m := parsePrevConditions(context.Background(), nil)
if r != nil || a != nil || rc != nil || len(m) != 0 {
t.Fatalf("expected (nil,nil,empty), got (%v,%v,%v)", r, a, m)
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.

Tip

nit — non-blocking suggestion

Category: Improvement

The error messages in the "nil input", "empty input", and "invalid JSON" subtests still format 3 values (r, a, m) but the function now returns 4 (r, a, rc, m). If rc triggers the failure, its value won't appear in the output.

// Before
t.Fatalf("expected (nil,nil,empty), got (%v,%v,%v)", r, a, m)

// After
t.Fatalf("expected (nil,nil,nil,empty), got (%v,%v,%v,%v)", r, a, rc, m)

Same fix needed on lines 115, 123, and 131.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ack, fixed

Comment thread pkg/services/cluster.go Outdated
allConditions := make([]api.ResourceCondition, 0, 2+len(adapterConditions))
allConditions = append(allConditions, ready, available)
allConditions := make([]api.ResourceCondition, 0, 3+len(adapterConditions))
allConditions = append(allConditions, ready, available, reconciled)
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.

Tip

nit — non-blocking suggestion

Category: Inconsistency

OpenAPI examples list conditions as Ready → Reconciled → Available, but the code appends them as Ready → Available → Reconciled. Not a functional issue since consumers filter by type, but aligning the order avoids confusion when comparing spec vs real responses.

// Current
allConditions = append(allConditions, ready, available, reconciled)

// Matching OpenAPI example order
allConditions = append(allConditions, ready, reconciled, available)

Same in node_pool.go:205.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. Fixed the order in append

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/services/aggregation.go (1)

332-345: Consider adding early break for consistency and efficiency.

When deletedTime == nil, the loop continues even after setting allAtCurrent = false, unlike the deletion path which breaks early. This is functionally correct but inconsistent with computeReady and wastes iterations.

♻️ Suggested refactor for consistency
 	allAtCurrent := true
 	for _, name := range required {
 		s, ok := byAdapter[name]
 		if !ok || !s.availableTrue || s.observedGeneration != resourceGen {
 			allAtCurrent = false
+			if deletedTime == nil {
+				break
+			}
 		}
 		if deletedTime != nil {
 			// Deletion lifecycle: check Finalized
 			if !s.finalizedTrue {
 				allAtCurrent = false
 				break
 			}
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/services/aggregation.go` around lines 332 - 345, The loop over required
adapters should exit early when we already know not all are current to match
computeReady's behavior: inside the for loop in aggregation.go (iterating
required and using byAdapter, s.availableTrue, s.observedGeneration,
resourceGen, deletedTime, allAtCurrent, s.finalizedTrue) add a break after you
set allAtCurrent = false for the non-deletion path (i.e., when deletedTime ==
nil) so the loop stops immediately rather than continuing extra iterations; keep
the existing deletion-path check (which already breaks when finalized is false).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/services/aggregation.go`:
- Around line 332-345: The loop over required adapters should exit early when we
already know not all are current to match computeReady's behavior: inside the
for loop in aggregation.go (iterating required and using byAdapter,
s.availableTrue, s.observedGeneration, resourceGen, deletedTime, allAtCurrent,
s.finalizedTrue) add a break after you set allAtCurrent = false for the
non-deletion path (i.e., when deletedTime == nil) so the loop stops immediately
rather than continuing extra iterations; keep the existing deletion-path check
(which already breaks when finalized is false).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: e9a02c3a-e640-48d5-aea3-8c1b0b13f9cd

📥 Commits

Reviewing files that changed from the base of the PR and between 25773d8 and 06822df.

📒 Files selected for processing (5)
  • pkg/api/status_types.go
  • pkg/services/aggregation.go
  • pkg/services/aggregation_test.go
  • pkg/services/cluster.go
  • pkg/services/status_helpers.go
✅ Files skipped from review due to trivial changes (2)
  • pkg/api/status_types.go
  • pkg/services/aggregation_test.go

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
pkg/services/aggregation_test.go (1)

123-144: ⚠️ Potential issue | 🟡 Minor

Failure output still omits the new rc return value.

These subtests now validate four values, but the t.Fatalf messages still print only three. If rc is wrong, the failure output hides it.

Suggested fix
-			t.Fatalf("expected (nil,nil,empty), got (%v,%v,%v)", r, a, m)
+			t.Fatalf("expected (nil,nil,nil,empty), got (%v,%v,%v,%v)", r, a, rc, m)
...
-			t.Fatalf("expected (nil,nil,empty), got (%v,%v,%v)", r, a, m)
+			t.Fatalf("expected (nil,nil,nil,empty), got (%v,%v,%v,%v)", r, a, rc, m)
...
-			t.Fatalf("expected (nil,nil,empty) on bad JSON, got (%v,%v,%v)", r, a, m)
+			t.Fatalf("expected (nil,nil,nil,empty) on bad JSON, got (%v,%v,%v,%v)", r, a, rc, m)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/services/aggregation_test.go` around lines 123 - 144, The tests calling
parsePrevConditions now return four values (r, a, rc, m) but the t.Fatalf
messages only print three; update each failing t.Fatalf in the "nil input",
"empty input", and "invalid JSON" subtests to include rc in the formatted output
so failures show all four values (e.g., include %v for rc and add rc to the
argument list) and ensure the expectation string mentions the four-tuple to
match the returned values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/services/aggregation_test.go`:
- Around line 1468-1488: The test's deletion fixture incorrectly sets the
adapters to Available=True via finalizedConds, causing Ready to be true; update
the fixture for this case so the adapters reflect Available=False at the current
generation (so AggregateResourceStatus returns Ready=False while
Reconciled=True). Concretely, modify the adapter status entries created by
makeAdapterStatus/finalizedConds in this test (the t.Run block) to use a
finalized condition set where Available is False (or use a helper that sets
Available=False) so ready.Status becomes api.ConditionFalse while
reconciled.Status remains api.ConditionTrue.

In `@pkg/services/aggregation.go`:
- Around line 364-369: The current branch sets reason/message using "Available"
wording even for deleted resources; change the logic to detect deletion (e.g.,
resourceGen.DeletionTimestamp != nil or an IsBeingDeleted helper) and when
deleted emit Finalized-specific reasons/messages instead of Available ones: use
a Finalized true reason (e.g., reasonAllAdaptersFinalized) when status ==
api.ConditionTrue and a Finalized false reason/message (you may need a new
reasonMissingRequiredAdaptersFinalized and a
buildFinalizedFalseMessage(required, byAdapter, resourceGen) or reuse
buildReadyFalseMessage but with Finalized wording) otherwise; keep the existing
variables (reason, message, required, byAdapter, resourceGen, status,
api.ConditionTrue, reasonAllAdaptersAvailable) as anchors when updating the
branches.
- Around line 332-345: The loop that computes allAtCurrent improperly requires
s.availableTrue before it checks deletion semantics when deletedTime != nil;
adjust the iteration in aggregation.go so that when deletedTime is non-nil you
skip the Available=True check and only validate Finalized=True and
observedGeneration == resourceGen for each adapter (i.e., for each name in
required check byAdapter[name], ensure s.observedGeneration == resourceGen and
if deletedTime != nil require s.finalizedTrue, else require s.availableTrue),
using the existing variables and fields (allAtCurrent, required, byAdapter,
deletedTime, s.availableTrue, s.finalizedTrue, s.observedGeneration,
resourceGen) to short-circuit or set allAtCurrent accordingly.

---

Duplicate comments:
In `@pkg/services/aggregation_test.go`:
- Around line 123-144: The tests calling parsePrevConditions now return four
values (r, a, rc, m) but the t.Fatalf messages only print three; update each
failing t.Fatalf in the "nil input", "empty input", and "invalid JSON" subtests
to include rc in the formatted output so failures show all four values (e.g.,
include %v for rc and add rc to the argument list) and ensure the expectation
string mentions the four-tuple to match the returned values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 0a1ae4c1-ed00-47e6-9091-e649595abd99

📥 Commits

Reviewing files that changed from the base of the PR and between 25773d8 and 06822df.

📒 Files selected for processing (5)
  • pkg/api/status_types.go
  • pkg/services/aggregation.go
  • pkg/services/aggregation_test.go
  • pkg/services/cluster.go
  • pkg/services/status_helpers.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/api/status_types.go
  • pkg/services/cluster.go

Comment thread pkg/services/aggregation_test.go Outdated
Comment thread pkg/services/aggregation.go
Comment thread pkg/services/aggregation.go Outdated
Comment thread pkg/services/aggregation.go Outdated
// required adapters have not yet reported Available=True at the current resource generation.
const reasonMissingRequiredAdapters = "MissingRequiredAdapters"
const reasonAllAdaptersAvailable = "All required adapters reported Available=True at the current generation"
const reasonAllAdaptersReconciled = "All required adapters reconciled at the current generation"
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.

Same comment as in the api-spec, we need to be more specific for what it means that a required adapter "is reconciled"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changed to All required adapters reported Available=True or Finalized=True at the current generation (similar to openapi)

Comment on lines +123 to +130
reconciled = computeReconciled(
in.ResourceGeneration,
in.RefTime,
in.DeletedTime,
prevReconciled,
in.RequiredAdapters,
reports,
)
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.

Doesn't ready and reconciled have the same value?
Shouldn't they use the same function for computation?

You could think of the changes as "ready" being just a mirror of "reconciled"
In this case, the mirror may not even happen at the service layer but in the presentation layer.
Think about like this

  • Internally, the service works only with "reconciled"
  • Which is a renaming of the previous "ready" condition
  • Then, when presenting the JSON, you present both ready and reconciled

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As discussed, Ready now replicated Reconciled. However, we have kept the ready = reconciled assignment one layer below the presentation layer since we still need to support search implementation for Ready for backward compatibility (search queries (status.conditions.Ready='True') hit the DB JSONB column directly)

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.

good catch

…d remove Ready implementation with test fixes
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
pkg/services/aggregation.go (1)

343-358: ⚠️ Potential issue | 🟠 Major

Use a Finalized-specific false reason during deletion.

On Lines 344-346, deleted resources still emit MissingRequiredAdapters even though the predicate and message are now Finalized=True. That leaves status.conditions[].reason misleading for clients that inspect reasons during teardown.

Suggested direction
+const reasonMissingRequiredFinalizedAdapters = "MissingRequiredFinalizedAdapters"
+
  var reason, message string
  if deletedTime != nil {
-		reason = reasonMissingRequiredAdapters
+		reason = reasonMissingRequiredFinalizedAdapters
 		message = buildFinalizedFalseMessage(required, byAdapter, resourceGen)
 		if status == api.ConditionTrue {
 			reason = reasonAllAdaptersReconciled
 			message = reason
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/services/aggregation.go` around lines 343 - 358, When handling deleted
resources (deletedTime != nil) use a Finalized-specific false reason instead of
reasonMissingRequiredAdapters: set reason =
reasonMissingRequiredAdaptersFinalized (create this new constant) and message =
buildFinalizedFalseMessage(required, byAdapter, resourceGen) when status !=
api.ConditionTrue; keep the status == api.ConditionTrue branch assigning
reasonAllAdaptersReconciled as-is. Leave the non-deleted (else) branch unchanged
so it still uses reasonMissingRequiredAdapters with buildReadyFalseMessage.
🧹 Nitpick comments (1)
pkg/services/status_helpers.go (1)

22-37: Extract the shared Ready/Reconciled assembly helper.

This block now duplicates the same reconciled → deprecated ready mirror → ordered condition slice flow as pkg/services/cluster.go:218-233. Pulling that into one helper would reduce drift in the compatibility layer.

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/services/status_helpers.go` around lines 22 - 37, Extract the duplicated
"reconciled → ready mirror → ordered condition slice" into a single helper
(e.g., BuildReadyReconciledConditions or AssembleResourceConditions) that
accepts the reconciled and available api.ResourceCondition values plus
adapterConditions ([]api.ResourceCondition) and returns the final
[]api.ResourceCondition; replace the inline logic in the current function (where
reconciled, available, adapterConditions are produced by
AggregateResourceStatus) and the similar block in Cluster code with calls to
this helper so the ready mirror is created (ready := reconciled; ready.Type =
api.ConditionTypeReady) and the ordering (ready, reconciled, available,
adapterConditions...) is centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@pkg/services/aggregation.go`:
- Around line 343-358: When handling deleted resources (deletedTime != nil) use
a Finalized-specific false reason instead of reasonMissingRequiredAdapters: set
reason = reasonMissingRequiredAdaptersFinalized (create this new constant) and
message = buildFinalizedFalseMessage(required, byAdapter, resourceGen) when
status != api.ConditionTrue; keep the status == api.ConditionTrue branch
assigning reasonAllAdaptersReconciled as-is. Leave the non-deleted (else) branch
unchanged so it still uses reasonMissingRequiredAdapters with
buildReadyFalseMessage.

---

Nitpick comments:
In `@pkg/services/status_helpers.go`:
- Around line 22-37: Extract the duplicated "reconciled → ready mirror → ordered
condition slice" into a single helper (e.g., BuildReadyReconciledConditions or
AssembleResourceConditions) that accepts the reconciled and available
api.ResourceCondition values plus adapterConditions ([]api.ResourceCondition)
and returns the final []api.ResourceCondition; replace the inline logic in the
current function (where reconciled, available, adapterConditions are produced by
AggregateResourceStatus) and the similar block in Cluster code with calls to
this helper so the ready mirror is created (ready := reconciled; ready.Type =
api.ConditionTypeReady) and the ordering (ready, reconciled, available,
adapterConditions...) is centralized.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 9eca8c7d-8836-4573-aa4b-09c941dcefeb

📥 Commits

Reviewing files that changed from the base of the PR and between ff82e4e and 77b9081.

📒 Files selected for processing (5)
  • openapi/openapi.yaml
  • pkg/services/aggregation.go
  • pkg/services/aggregation_test.go
  • pkg/services/cluster.go
  • pkg/services/status_helpers.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • openapi/openapi.yaml

@mliptak0
Copy link
Copy Markdown
Contributor

mliptak0 commented Apr 29, 2026

I see a missing changes to CHANGELOG.md, consider adding that there as well for visibility (even though I see it's unreleased...)

@rh-amarin
Copy link
Copy Markdown
Contributor

/lgtm

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Apr 29, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: rh-amarin

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-merge-bot openshift-merge-bot Bot merged commit f31a6c9 into openshift-hyperfleet:main Apr 29, 2026
8 checks passed
@tirthct tirthct deleted the hyperfleet-853 branch April 29, 2026 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants