Skip to content

HYPERFLEET-706 - fix: lastUpdateTime for Ready#81

Open
rh-amarin wants to merge 1 commit intoopenshift-hyperfleet:mainfrom
rh-amarin:HYPERFLEET-706
Open

HYPERFLEET-706 - fix: lastUpdateTime for Ready#81
rh-amarin wants to merge 1 commit intoopenshift-hyperfleet:mainfrom
rh-amarin:HYPERFLEET-706

Conversation

@rh-amarin
Copy link
Contributor

@rh-amarin rh-amarin commented Mar 11, 2026

What

  • Ready last_updated_time is now set so Sentinel (and other consumers) can tell when the condition was last re-evaluated and apply a freshness threshold.
  • When Ready=True, last_updated_time is the minimum of last_report_time across all required adapters that report Available=True at the current generation (instead of always “now” or an incorrect value).
  • When Ready=False, last_updated_time is the evaluation time so each re-evaluation updates the timestamp and stale conditions can be detected.

Why
Previously, Ready’s LastUpdatedTime did not reflect when adapters last reported or when the condition was re-evaluated. That made it hard for Sentinel to treat “no recent update” as “condition may be stale” and to reason about readiness correctly. This change aligns the API’s semantics with how consumers use the field.

How
pkg/services/status_aggregation.go

  • Added computeReadyLastUpdated() to compute Ready’s LastUpdatedTime: returns evaluation time when not ready; when ready, returns the minimum LastReportTime among required adapters that have Available=True at the current generation; falls back to evaluation time if any required adapter is missing or has no timestamp.
  • Wired BuildSyntheticConditions() to use this value for the Ready condition’s LastUpdatedTime.
  • Refactors: findAdapterStatus() to look up an adapter by name in the list; adapterConditionsHasAvailableTrue() to detect Available=True in conditions JSON.
  • Tests
    • status_aggregation_test.go: New unit tests for computeReadyLastUpdated (not ready, missing adapter, nil LastReportTime, wrong generation, Available=False, single qualifying adapter, multiple adapters minimum, mixed generations).
    • cluster_test.go and node_pool_test.go: Updated TestClusterSyntheticTimestampsStableWithoutAdapterStatus and TestNodePoolSyntheticTimestampsStableWithoutAdapterStatus so they expect LastUpdatedTime to be after the fixed “now” (i.e. refreshed at evaluation time), with brief comments explaining the behavior.

Docs

  • docs/api-resources.md: Clarified last_updated_time for ResourceCondition — Available always uses evaluation time; Ready uses minimum adapter last_report_time when Ready=True and evaluation time when Ready=False.
  • pkg/services/CLAUDE.md: Documented that Ready’s LastUpdatedTime comes from computeReadyLastUpdated and when it is evaluation time vs minimum adapter time.

Testing
make test (unit tests).
make test-integration (optional, for full confidence).

Summary by CodeRabbit

  • Documentation

    • Clarified semantics for LastUpdatedTime on resource conditions, specifying evaluation-time behavior for Available and how Ready’s timestamp is derived.
  • Tests

    • Expanded and adjusted tests to validate timestamp windows and many Ready/Available scenarios, including missing or mixed adapter reports.
  • Refactor

    • Reworked condition timestamp handling and propagation to yield more consistent, generation-aware LastUpdatedTime values and improved timestamp stability.
  • Bug Fixes

    • Improved handling and logging for malformed adapter status data.

@openshift-ci
Copy link

openshift-ci bot commented Mar 11, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci
Copy link

openshift-ci bot commented Mar 11, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign mbrudnoy for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found 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

@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

Walkthrough

This PR changes how ResourceCondition.LastUpdatedTime is computed and propagated. It adds helpers (findAdapterStatus, adapterConditionsHasAvailableTrue) and computeReadyLastUpdated, which returns the evaluation time when Ready is false or the minimum LastReportTime among required adapters that report Available=True at the current generation when Ready is true. applyConditionHistory centralizes copying CreatedTime/LastTransitionTime and accepts an explicit lastUpdatedTime to set LastUpdatedTime. BuildSyntheticConditions now supplies computed lastUpdatedTime for Available (evaluation time) and Ready (computed). Tests and documentation updated accordingly.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the issue (HYPERFLEET-706) and the main change (fixing lastUpdateTime for Ready condition), which aligns with the PR's core objective of computing Ready.LastUpdatedTime properly.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@rh-amarin rh-amarin force-pushed the HYPERFLEET-706 branch 2 times, most recently from 0f274fc to 0465f91 Compare March 11, 2026 17:11
@rh-amarin rh-amarin marked this pull request as ready for review March 11, 2026 17:21
@openshift-ci openshift-ci bot requested review from crizzo71 and xueli181114 March 11, 2026 17:21
Copy link

@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 (3)
pkg/services/cluster_test.go (1)

790-795: Assert against the actual evaluation window.

These matchers only prove LastUpdatedTime moved forward from fixedNow; they do not prove it was refreshed during Create() or UpdateClusterStatusFromAdapters(). Capture before/after timestamps around each call and assert the synthesized value falls inside that window so this test locks down the new freshness contract.

Also applies to: 817-823

🤖 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 790 - 795, The test currently only
asserts LastUpdatedTime > fixedNow; change it to capture timestamps immediately
before and after the call that should refresh the field (e.g., record
beforeCreate := time.Now() then call Create() or
UpdateClusterStatusFromAdapters(), then afterCreate := time.Now()) and assert
createdAvailable.LastUpdatedTime and createdReady.LastUpdatedTime lie between
those before/after bounds (using BeTemporally(">=", beforeCreate) and
BeTemporally("<=", afterCreate)); apply the same pattern for the other affected
assertions (the block around lines 817-823) so the test verifies the synthesized
timestamps fall inside the actual evaluation window.
pkg/services/node_pool_test.go (1)

650-655: Assert against the actual evaluation window.

As written, these checks only show that LastUpdatedTime is later than fixedNow. Capturing before/after timestamps around Create() and UpdateNodePoolStatusFromAdapters() would make the test verify the intended evaluation-time refresh behavior directly.

Also applies to: 677-683

🤖 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 650 - 655, The assertions for
LastUpdatedTime should verify the evaluation timestamp window rather than just
being greater than fixedNow: capture timestamps immediately before and after
calling Create() and UpdateNodePoolStatusFromAdapters() (e.g., store beforeEval
:= time.Now() before calling the evaluated function and afterEval := time.Now()
after it returns) and replace the BeTemporally(">", fixedNow) checks with
assertions that LastUpdatedTime is within [beforeEval, afterEval] (use
BeTemporally(">=", beforeEval) and BeTemporally("<=", afterEval) or equivalent);
apply this pattern to the checks for createdAvailable.LastUpdatedTime and
createdReady.LastUpdatedTime (and repeat the same change for the similar
assertions at lines 677-683).
pkg/services/status_aggregation_test.go (1)

35-155: Add one synthesized-condition test for the Ready=True path.

These cases validate computeReadyLastUpdated() directly, but they will not fail if BuildSyntheticConditions() stops threading that timestamp through applyConditionHistory(). One end-to-end assertion on the final api.ResourceCondition would cover the actual wiring.

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

In `@pkg/services/status_aggregation_test.go` around lines 35 - 155, Add an
end-to-end unit test that synthesizes conditions and verifies the ready
timestamp is threaded to the final api.ResourceCondition: call
BuildSyntheticConditions(...) then pass its result through
applyConditionHistory(...) (or the code path that produces
api.ResourceCondition) and assert that the Ready condition's timestamp field
(e.g. LastTransitionTime/LastUpdateTime on the resulting api.ResourceCondition)
equals the value returned by computeReadyLastUpdated(...) for the same inputs;
place this alongside the existing TestComputeReadyLastUpdated_* tests and
reference the functions BuildSyntheticConditions, applyConditionHistory, and
computeReadyLastUpdated so the test fails if the timestamp is not propagated to
the final api.ResourceCondition.
🤖 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 790-795: The test currently only asserts LastUpdatedTime >
fixedNow; change it to capture timestamps immediately before and after the call
that should refresh the field (e.g., record beforeCreate := time.Now() then call
Create() or UpdateClusterStatusFromAdapters(), then afterCreate := time.Now())
and assert createdAvailable.LastUpdatedTime and createdReady.LastUpdatedTime lie
between those before/after bounds (using BeTemporally(">=", beforeCreate) and
BeTemporally("<=", afterCreate)); apply the same pattern for the other affected
assertions (the block around lines 817-823) so the test verifies the synthesized
timestamps fall inside the actual evaluation window.

In `@pkg/services/node_pool_test.go`:
- Around line 650-655: The assertions for LastUpdatedTime should verify the
evaluation timestamp window rather than just being greater than fixedNow:
capture timestamps immediately before and after calling Create() and
UpdateNodePoolStatusFromAdapters() (e.g., store beforeEval := time.Now() before
calling the evaluated function and afterEval := time.Now() after it returns) and
replace the BeTemporally(">", fixedNow) checks with assertions that
LastUpdatedTime is within [beforeEval, afterEval] (use BeTemporally(">=",
beforeEval) and BeTemporally("<=", afterEval) or equivalent); apply this pattern
to the checks for createdAvailable.LastUpdatedTime and
createdReady.LastUpdatedTime (and repeat the same change for the similar
assertions at lines 677-683).

In `@pkg/services/status_aggregation_test.go`:
- Around line 35-155: Add an end-to-end unit test that synthesizes conditions
and verifies the ready timestamp is threaded to the final api.ResourceCondition:
call BuildSyntheticConditions(...) then pass its result through
applyConditionHistory(...) (or the code path that produces
api.ResourceCondition) and assert that the Ready condition's timestamp field
(e.g. LastTransitionTime/LastUpdateTime on the resulting api.ResourceCondition)
equals the value returned by computeReadyLastUpdated(...) for the same inputs;
place this alongside the existing TestComputeReadyLastUpdated_* tests and
reference the functions BuildSyntheticConditions, applyConditionHistory, and
computeReadyLastUpdated so the test fails if the timestamp is not propagated to
the final api.ResourceCondition.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c5b7aae1-530a-4b11-847e-5ae5f4e614d4

📥 Commits

Reviewing files that changed from the base of the PR and between d55febc and 0465f91.

📒 Files selected for processing (6)
  • docs/api-resources.md
  • pkg/services/CLAUDE.md
  • pkg/services/cluster_test.go
  • pkg/services/node_pool_test.go
  • pkg/services/status_aggregation.go
  • pkg/services/status_aggregation_test.go

Copy link
Collaborator

@xueli181114 xueli181114 left a comment

Choose a reason for hiding this comment

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

Review: HYPERFLEET-706 — lastUpdateTime for Ready

The core logic in computeReadyLastUpdated is correct and well-tested. Six observations below — #1 is the main concern, the rest are quality improvements.

# Issue Severity
1 Available's LastUpdatedTime semantics changed (not in ticket scope) Major
2 Double JSON unmarshal per adapter Minor
3 Redundant guards when isReady=true Minor
4 MixedGenerations test is an impossible scenario Minor
5 Integration test uses same function for expected value Minor
6 findAdapterStatus vs map-overwrite on duplicates Nitpick

LastUpdatedTime: now,
}
preserveSyntheticCondition(&availableCondition, existingAvailable, now)
applyConditionHistory(&availableCondition, existingAvailable, now, now)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Major: Unintended behavioral change to Available's LastUpdatedTime

The ticket only asks to change Ready's LastUpdatedTime. But the refactor from preserveSyntheticConditionapplyConditionHistory also changes Available's behavior.

Before (main): When Available status is unchanged, preserveSyntheticCondition preserves the existing LastUpdatedTime:

if !existing.LastUpdatedTime.IsZero() {
    target.LastUpdatedTime = existing.LastUpdatedTime
}

After (PR): applyConditionHistory always overwrites LastUpdatedTime with the caller-provided value (now for Available):

target.LastUpdatedTime = lastUpdatedTime  // always overwritten

This means Available's LastUpdatedTime now refreshes on every evaluation cycle (~10s), even when nothing changed. Previously it was stable. Any consumer relying on "when did Available actually change?" will now see constant updates.

The updated tests (BeTemporally windows instead of exact match) confirm this is a behavioral change, not just a refactor.

Is this intentional? If not, consider preserving the old behavior for Available by passing existing.LastUpdatedTime when status is unchanged.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Even though I don't think lastUpdatedTime has very useful purpose... if preserving the old value can completely avoid an UPDATE to the database, then it would be a good thing.

I will change this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Avoiding the DB action will require a separate ticket

 The fix does not save a database write.

  Looking at cluster.go:232-233, the caller unconditionally calls s.clusterDao.Replace(ctx, cluster) after building conditions
   — there's no comparison between the old and new StatusConditions JSON before deciding whether to persist. Every adapter
  status report triggers a DB write regardless of whether the computed conditions changed.

  The fix only corrects what gets written: instead of writing a new LastUpdatedTime = now every ~10s, it writes back the
  preserved original timestamp. The DB update still happens either way.

  To actually skip the write when nothing changed, you'd need an equality check before the Replace call — but that's a separate optimization, not part of this ticket.

Status string `json:"status"`
}
if err := json.Unmarshal(conditions, &conds); err != nil {
return false
Copy link
Collaborator

Choose a reason for hiding this comment

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

Minor: Double JSON unmarshal per adapter

BuildSyntheticConditions calls ComputeReadyCondition() which unmarshals each adapter's conditions JSON to check Available=True. Then computeReadyLastUpdated() unmarshals the same JSON again via adapterConditionsHasAvailableTrue().

Each adapter's conditions are parsed twice per evaluation cycle. Not a problem at current scale, but ComputeReadyCondition already builds an adapterMap with parsed results — computeReadyLastUpdated could accept that map instead of re-parsing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a micro-optimization, building a map of 2 entries
I explicitly changed it to make the code more readable

Copy link
Contributor

@rafabene rafabene Mar 12, 2026

Choose a reason for hiding this comment

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

Priority: Inconsistency

This also silently returns false on
json.Unmarshal error, but this same PR adds explicit
logger.WithError(...).Warn(...) for the identical unmarshal failure in
ComputeAvailableCondition (line 128) and ComputeReadyCondition (line 202).
Corrupt adapter conditions reaching computeReadyLastUpdated would produce no
log entry, making the silent skip invisible to operators.

Consider adding logging here too, or restructuring so computeReadyLastUpdated
reuses the already-parsed results from ComputeReadyCondition (which would also
address @xueli181114 's double-unmarshal comment).

}
if status.LastReportTime == nil {
return now // safety: no timestamp
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Minor: Guards are unreachable when isReady=true

When isReady=true, ComputeReadyCondition has already confirmed ALL required adapters have Available=True at current generation. So:

  • The ObservedGeneration != resourceGenerationcontinue branch can't fire
  • The !adapterConditionsHasAvailableTrue()continue branch can't fire
  • minTime can never be nil after the loop

These aren't harmful (defense-in-depth), but a comment like // Redundant when isReady=true; provides safety if called independently would clarify intent and prevent someone from thinking these are load-bearing checks.

result := computeReadyLastUpdated(statuses, []string{"validator", "dns"}, 3, now, true)
if !result.Equal(newGenTime) {
t.Errorf("expected %v (only current-gen adapter), got %v", newGenTime, result)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Minor: This test exercises an impossible scenario

This passes isReady=true with dns at gen 2 and resource at gen 3. But ComputeReadyCondition would return false in this case (dns is at wrong gen), so isReady would never be true in production. The test provides false confidence.

Consider either:

  • Documenting it as a defense-in-depth test: // Tests function in isolation; this combination can't occur via BuildSyntheticConditions
  • Or removing it and relying on the integration test for the realistic path

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will remove the test

adapterStatuses, requiredAdapters, resourceGeneration, now, isReady,
)

if !readyCondition.LastUpdatedTime.Equal(expectedLastUpdated) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Minor: Tautological assertion

This test computes the expected value by calling computeReadyLastUpdated() — the same function under test. It only proves BuildSyntheticConditions passes the result through, not that the result is correct.

A stronger assertion would hardcode the expected value:

if !readyCondition.LastUpdatedTime.Equal(reportTime) {
    t.Errorf("expected reportTime %v, got %v", reportTime, readyCondition.LastUpdatedTime)
}

This independently verifies the full chain: BuildSyntheticConditionscomputeReadyLastUpdated → correct timestamp.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, implemented

Copy link

@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: 1

🤖 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/status_aggregation.go`:
- Around line 304-305: The generation check that currently skips adapters when
status.ObservedGeneration != resourceGeneration should be relaxed to match
ComputeReadyCondition's behavior: only enforce generation equality when
resourceGeneration > 0; i.e., if resourceGeneration == 0 allow adapters
regardless of ObservedGeneration. Update the condition inside the function
containing that check (look for references to status.ObservedGeneration,
resourceGeneration, and computeReadyLastUpdated) accordingly, and add a
regression unit test that calls BuildSyntheticConditions (or the public entry
that uses it) with resourceGeneration == 0 to assert Ready remains computed and
the staleness signal is produced rather than being skipped.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 48eb9e9d-2f4a-4da0-b1b9-4a9db369492b

📥 Commits

Reviewing files that changed from the base of the PR and between b7f6f99 and b8e4bd4.

📒 Files selected for processing (6)
  • docs/api-resources.md
  • pkg/services/CLAUDE.md
  • pkg/services/cluster_test.go
  • pkg/services/node_pool_test.go
  • pkg/services/status_aggregation.go
  • pkg/services/status_aggregation_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • docs/api-resources.md
  • pkg/services/node_pool_test.go
  • pkg/services/CLAUDE.md
  • pkg/services/cluster_test.go

Comment on lines +304 to +305
if status.ObservedGeneration != resourceGeneration {
continue // not at current gen, skip
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Keep the generation filter aligned with ComputeReadyCondition.

Line 304 is stricter than ComputeReadyCondition, which only enforces generation equality when resourceGeneration > 0. If a caller ever passes 0 here, BuildSyntheticConditions can return Ready=True while computeReadyLastUpdated() skips every adapter and falls back to now, so the new staleness signal disappears on that path. Please mirror the same guard here and add a regression test for the zero-generation case.

Suggested fix
-		if status.ObservedGeneration != resourceGeneration {
+		if resourceGeneration > 0 && status.ObservedGeneration != resourceGeneration {
 			continue // not at current gen, skip
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if status.ObservedGeneration != resourceGeneration {
continue // not at current gen, skip
if resourceGeneration > 0 && status.ObservedGeneration != resourceGeneration {
continue // not at current gen, skip
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/services/status_aggregation.go` around lines 304 - 305, The generation
check that currently skips adapters when status.ObservedGeneration !=
resourceGeneration should be relaxed to match ComputeReadyCondition's behavior:
only enforce generation equality when resourceGeneration > 0; i.e., if
resourceGeneration == 0 allow adapters regardless of ObservedGeneration. Update
the condition inside the function containing that check (look for references to
status.ObservedGeneration, resourceGeneration, and computeReadyLastUpdated)
accordingly, and add a regression unit test that calls BuildSyntheticConditions
(or the public entry that uses it) with resourceGeneration == 0 to assert Ready
remains computed and the staleness signal is produced rather than being skipped.

- `observed_generation` - Generation this condition reflects
- `created_time` - When condition was first created (API-managed)
- `last_updated_time` - When adapter last reported (API-managed, from AdapterStatus.last_report_time)
- `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).
Copy link
Contributor

Choose a reason for hiding this comment

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

Priority: Inconsistency

The doc on line 459 says Available's last_updated_time is "always the
evaluation time", but the code in BuildSyntheticConditions preserves the
existing value when status and ObservedGeneration are unchanged (lines 359-365
of status_aggregation.go). The test
TestBuildSyntheticConditions_AvailableLastUpdatedTime_Stable confirms the
preservation behavior.

Whichever direction the fix goes for the Available behavior (per Xue Li's
comment), please update the doc to match. If preservation is kept, something
like: "For Available, the evaluation time when the status or
observed_generation changes; otherwise preserved from the previous
evaluation."

)

// adapterConditionStatusTrue is the string value for a True adapter condition status.
const adapterConditionStatusTrue = "True"
Copy link
Contributor

Choose a reason for hiding this comment

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

Priority: Pattern

adapterConditionStatusTrue = "True" (line 26) duplicates
api.AdapterConditionTrue (AdapterConditionStatus("True") in
status_types.go:19). This creates a second source of truth.

Consider either:

  • Using string(api.AdapterConditionTrue) at the comparison sites (lines 161,
    239, 273), or
  • Deriving the constant explicitly: const adapterConditionStatusTrue =
    string(api.AdapterConditionTrue)

Both options keep a single source of truth while satisfying the string comparison.

}
break
if err := json.Unmarshal(adapterStatus.Conditions, &conditions); err != nil {
logger.WithError(context.Background(), err).Warn(
Copy link
Contributor

Choose a reason for hiding this comment

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

Priority: Pattern

context.Background() at lines 128 and 202 means these warn logs won't carry
request-scoped metadata (cluster_id, trace_id, request_id) that callers like
UpdateClusterStatusFromAdapters have available. If a corrupt conditions blob
causes unexpected Available/Ready results in production, correlating these
logs to a specific cluster would require timestamp matching.

Not blocking, but consider threading context.Context through
ComputeAvailableCondition and ComputeReadyCondition so the logs inherit the
caller's context. Alternatively, a // TODO noting the limitation would help
future maintainers.

now := time.Now()
statuses := api.AdapterStatusList{
makeAdapterStatus("validator", 1, ptr(now.Add(-5*time.Second)), makeConditionsJSON(t, []struct{ Type, Status string }{
{api.ConditionTypeAvailable, "True"},
Copy link
Contributor

@rafabene rafabene Mar 12, 2026

Choose a reason for hiding this comment

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

Priority: Pattern

The PR extracts adapterConditionStatusTrue to eliminate the "True" magic
string in production code, but the new tests (lines

  • 50,
  • 64,
  • 79,
  • 94,
  • 109,
  • 125,
  • 128,
  • 145,
  • 170,

reintroduce bare "True" and "False" literals across ~10 call sites.

Since the test helper uses struct{ Type, Status string } (untyped), consider
using string(api.AdapterConditionTrue) and string(api.AdapterConditionFalse)
to stay consistent with the PR's own motivation:
{api.ConditionTypeAvailable, string(api.AdapterConditionTrue)}

@rafabene
Copy link
Contributor

rafabene commented Mar 12, 2026

Priority: Improvement

The availableLastUpdated logic has an untested code path: generation changed
but status unchanged (existing Available=True@gen1 → new Available=True@gen2).
The ObservedGeneration guard on line 362 would cause availableLastUpdated to
remain now, which is the correct behavior but isn't verified by any test.

Consider adding a test like:

  func TestBuildSyntheticConditions_AvailableLastUpdatedTime_UpdatesOnGeneration
  Change(t *testing.T) {
      // existing Available=True at gen 1, adapters now at gen 2 still True
      // assert Available.LastUpdatedTime == now (refreshed due to gen change)
  }

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.

3 participants