Skip to content

HYPERFLEET-1152 - feat: separate resource_conditions table for generic resources#263

Open
kuudori wants to merge 6 commits into
openshift-hyperfleet:mainfrom
kuudori:HYPERFLEET-1152-feat-resource-conditions
Open

HYPERFLEET-1152 - feat: separate resource_conditions table for generic resources#263
kuudori wants to merge 6 commits into
openshift-hyperfleet:mainfrom
kuudori:HYPERFLEET-1152-feat-resource-conditions

Conversation

@kuudori

@kuudori kuudori commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add resource_conditions table with composite PK (resource_id, type) and FK to resources(id) ON DELETE CASCADE
  • New ResourceConditionDao interface with UpdateConditions that atomically replaces all condition rows, preserving LastTransitionTime when status is unchanged and CreatedTime across updates
  • ResourceDao.Get and GetByOwner now preload conditions via GORM HasMany association
  • Resource presenter surfaces conditions in the status.conditions response field
  • Save operations do not touch condition rows (Omit(clause.Associations) already in place)

Architecture

  • ADR-0007: Status values True/False only (not Unknown)
  • ADR-0008: Compute-on-write — UpdateConditions designed to be called within adapter status upsert transaction
  • Design doc §4.1, §4.2, §11.5: Table schema, composite PK, atomic-replace semantics

Design choices

  • Separate ResourceConditionDao (not added to ResourceDao) — follows AdapterStatusDao precedent for derived state
  • Dual-use ResourceCondition struct — GORM table model + JSONB-deserializable type; ResourceID field uses json:"-" for backward compat
  • ON DELETE CASCADE (vs RESTRICT used by node_pools) — conditions are derived metadata with no independent lifecycle
  • Explicit timestamp management (no GORM autoCreateTime) — codebase convention; delete+re-insert pattern requires pre-set value preservation

Test coverage

AC Test
Migration creates table with composite PK TestResourceConditions_UpdateAndPreload (implicit via migration run)
UpdateConditions replaces atomically TestResourceConditions_AtomicReplace
Get preloads Conditions TestResourceConditions_UpdateAndPreload
GetByOwner preloads Conditions TestResourceConditions_GetByOwnerPreload
LastTransitionTime preserved TestResourceConditions_LastTransitionTimePreserved
CreatedTime preserved TestResourceConditions_CreatedTimePreserved
Presenter surfaces conditions TestPresentResource_WithConditions, TestPresentResource_WithEmptyConditions
Save doesn't touch conditions TestResourceConditions_SaveDoesNotTouchConditions
Empty slice clears conditions TestResourceConditions_ClearWithEmptySlice

Test plan

  • make verify-all (build + lint + 1272 unit tests)
  • make test-integration (requires Docker — CI will run)
  • CI green

@openshift-ci

openshift-ci Bot commented Jun 29, 2026

Copy link
Copy Markdown

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

openshift-ci Bot commented Jun 29, 2026

Copy link
Copy Markdown

[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

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Resources can now include and return status conditions, so condition details are visible when viewing a resource.
    • Condition history is preserved more consistently when status updates occur.
  • Bug Fixes

    • Empty conditions are now returned as an empty list instead of missing data.
    • Resource lookups now include condition information in both direct and owner-based fetches.
  • Tests

    • Added coverage for condition persistence, replacement behavior, and timestamp handling.

Walkthrough

A new resource_conditions table is added via migration 202606290001, with a composite primary key on (resource_id, type) and a foreign key to resources(id) ON DELETE CASCADE. ResourceCondition gains GORM tags, constraints, and a TableName() method. Resource gains a Conditions []ResourceCondition GORM relationship field. A new ResourceConditionDao with UpdateConditions implements an atomic delete-then-insert with CreatedTime and conditional LastTransitionTime preservation. ResourceDao.Get and GetByOwner now preload Conditions. PresentResource maps conditions to the OpenAPI shape via a new presentResourceConditions helper. Integration and unit tests cover the full behavior surface.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 10 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (10 passed)
Check name Status Explanation
Title check ✅ Passed Matches the main change: adds a separate resource_conditions table for generic resources.
Description check ✅ Passed Describes the schema, DAO, presenter, and test changes present in the patch.
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.
Sec-02: Secrets In Log Output ✅ Passed No non-test log call interpolates token/password/credential/secret; the only adjacent case logs a redacted connection_string (CWE-532 not observed).
No Hardcoded Secrets ✅ Passed No hardcoded secrets, credentialed URLs, private keys, or long base64 literals were found in touched files; only test strings/identifiers. CWE-798 not present.
No Weak Cryptography ✅ Passed Touched files only add resource-condition persistence/presentation; no md5/des/rc4, SHA1-for-security, ECB, custom crypto, or secret compares. CWE-327/CWE-328 absent.
No Injection Vectors ✅ Passed No CWE-89/CWE-78/CWE-79/CWE-502 sink uses in modified non-test files; SQL uses placeholders or constant strings only.
No Privileged Containers ✅ Passed PR only changes Go source/tests/migrations; no Kubernetes/OpenShift manifests, Helm templates, or Dockerfiles were modified, so no privileged-container config was introduced.
No Pii Or Sensitive Data In Logs ✅ Passed CWE-532: no slog/logr/zap/fmt.Print additions in the modified files; no raw bodies, session IDs, or hostnames are logged.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

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

@kuudori kuudori marked this pull request as ready for review June 29, 2026 22:48
@openshift-ci openshift-ci Bot requested review from jsell-rh and tirthct June 29, 2026 22:48
@hyperfleet-ci-bot

Copy link
Copy Markdown

Risk Score: 3 — risk/medium

Signal Detail Points
PR size 545 lines (>500) +2
Sensitive paths none +0
Test coverage Missing tests for: pkg/api pkg/dao pkg/db/migrations +1

Computed by hyperfleet-risk-scorer

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (1)
pkg/db/migrations/202606290001_add_resource_conditions.go (1)

28-31: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Drop the standalone resource_id index unless you have a non-PK lookup to justify it.

The primary key on (resource_id, type) already covers the current WHERE resource_id = ? fetch/delete path, so this adds another index to maintain on every full condition replace. That is extra write amplification on the hot path for UpdateConditions.

Suggested change
-			if err := tx.Exec(
-				"CREATE INDEX IF NOT EXISTS idx_resource_conditions_resource_id " +
-					"ON resource_conditions (resource_id);",
-			).Error; err != nil {
-				return err
-			}
-
 			return nil

As per path instructions, apply the HyperFleet performance checks to new persistence paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/db/migrations/202606290001_add_resource_conditions.go` around lines 28 -
31, The migration in add_resource_conditions.go is creating a standalone
idx_resource_conditions_resource_id index that appears redundant with the
primary key on (resource_id, type) for the current resource_id lookup path.
Remove that index creation from the migration unless a separate non-PK query in
UpdateConditions or related resource_conditions access truly needs it, and keep
the migration focused on the table’s primary-key-backed access pattern.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/dao/resource_condition.go`:
- Around line 33-35: The initial `Find(&existing)` error path in the
`resource_condition` DAO currently returns without marking the request
transaction for rollback, which can leave earlier writes committed. Update the
write flow that loads existing conditions to call `db.MarkForRollback(ctx, err)`
before returning the `Find` error, following the same pattern used for other
write failures in `pkg/dao`. Keep the fix localized to the resource condition
replacement path so any failure in this method prevents a partial commit.

In `@pkg/db/migrations/202606290001_add_resource_conditions.go`:
- Around line 12-24: The `UpdateConditions` migration currently uses
`resource_conditions` with `CREATE TABLE IF NOT EXISTS`, which can silently
accept an existing table with the wrong PK/FK/defaults and mask schema drift.
Replace this in the migration with a schema validation or fail-fast check in the
migration logic so `gormigrate` does not mark it applied unless
`resource_conditions` matches the expected definition; use the
`UpdateConditions` migration block and the `resource_conditions` table
definition to enforce exact structure.

---

Nitpick comments:
In `@pkg/db/migrations/202606290001_add_resource_conditions.go`:
- Around line 28-31: The migration in add_resource_conditions.go is creating a
standalone idx_resource_conditions_resource_id index that appears redundant with
the primary key on (resource_id, type) for the current resource_id lookup path.
Remove that index creation from the migration unless a separate non-PK query in
UpdateConditions or related resource_conditions access truly needs it, and keep
the migration focused on the table’s primary-key-backed access pattern.
🪄 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: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 801f017f-ae5c-46c0-9d31-3a086fc0e835

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe21ec and 9620c0f.

📒 Files selected for processing (9)
  • pkg/api/presenters/resource.go
  • pkg/api/presenters/resource_test.go
  • pkg/api/resource.go
  • pkg/api/status_types.go
  • pkg/dao/resource.go
  • pkg/dao/resource_condition.go
  • pkg/db/migrations/202606290001_add_resource_conditions.go
  • pkg/db/migrations/migration_structs.go
  • test/integration/resource_conditions_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)

Comment on lines +33 to +35
var existing []api.ResourceCondition
if err := g2.Where("resource_id = ?", resourceID).Find(&existing).Error; err != nil {
return err

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Mark the initial query failure for rollback.

Line 34 returns the Find(&existing) error without db.MarkForRollback(ctx, err). In this write DAO, that can commit earlier mutations in the same request transaction while condition replacement fails, leaving the resource row and resource_conditions out of sync.

As per path instructions, pkg/dao/**: "On any write error, mark the transaction for rollback" and pkg/db/**: "Without this, the middleware will commit a partially-failed transaction."

Proposed fix
 	var existing []api.ResourceCondition
 	if err := g2.Where("resource_id = ?", resourceID).Find(&existing).Error; err != nil {
+		db.MarkForRollback(ctx, err)
 		return err
 	}
📝 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
var existing []api.ResourceCondition
if err := g2.Where("resource_id = ?", resourceID).Find(&existing).Error; err != nil {
return err
var existing []api.ResourceCondition
if err := g2.Where("resource_id = ?", resourceID).Find(&existing).Error; err != nil {
db.MarkForRollback(ctx, err)
return err
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/dao/resource_condition.go` around lines 33 - 35, The initial
`Find(&existing)` error path in the `resource_condition` DAO currently returns
without marking the request transaction for rollback, which can leave earlier
writes committed. Update the write flow that loads existing conditions to call
`db.MarkForRollback(ctx, err)` before returning the `Find` error, following the
same pattern used for other write failures in `pkg/dao`. Keep the fix localized
to the resource condition replacement path so any failure in this method
prevents a partial commit.

Source: Path instructions

Comment on lines +12 to +24
if err := tx.Exec(`CREATE TABLE IF NOT EXISTS resource_conditions (
resource_id VARCHAR(255) NOT NULL,
type VARCHAR(100) NOT NULL,
status VARCHAR(10) NOT NULL,
reason TEXT,
message TEXT,
observed_generation INTEGER NOT NULL DEFAULT 0,
created_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_updated_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_transition_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (resource_id, type),
FOREIGN KEY (resource_id) REFERENCES resources(id) ON DELETE CASCADE
);`).Error; err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail on schema drift instead of masking it.

UpdateConditions depends on this table having the exact PK/FK/defaults described in the PR. CREATE TABLE IF NOT EXISTS will still return success if a partial/manual resource_conditions table already exists, and gormigrate will then record this migration as applied against the wrong schema.

Suggested change
-			if err := tx.Exec(`CREATE TABLE IF NOT EXISTS resource_conditions (
+			if err := tx.Exec(`CREATE TABLE resource_conditions (
 				resource_id			VARCHAR(255) NOT NULL,
 				type				VARCHAR(100) NOT NULL,
 				status				VARCHAR(10) NOT NULL,
 				reason				TEXT,
 				message				TEXT,
 				observed_generation	INTEGER NOT NULL DEFAULT 0,
 				created_time		TIMESTAMPTZ NOT NULL DEFAULT NOW(),
 				last_updated_time	TIMESTAMPTZ NOT NULL DEFAULT NOW(),
 				last_transition_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
 				PRIMARY KEY (resource_id, type),
 				FOREIGN KEY (resource_id) REFERENCES resources(id) ON DELETE CASCADE
 			);`).Error; err != nil {
 				return err
 			}

As per path instructions, migrations must be reviewed for backward compatibility and incompatible-schema risks.

📝 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 err := tx.Exec(`CREATE TABLE IF NOT EXISTS resource_conditions (
resource_id VARCHAR(255) NOT NULL,
type VARCHAR(100) NOT NULL,
status VARCHAR(10) NOT NULL,
reason TEXT,
message TEXT,
observed_generation INTEGER NOT NULL DEFAULT 0,
created_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_updated_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_transition_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (resource_id, type),
FOREIGN KEY (resource_id) REFERENCES resources(id) ON DELETE CASCADE
);`).Error; err != nil {
if err := tx.Exec(`CREATE TABLE resource_conditions (
resource_id VARCHAR(255) NOT NULL,
type VARCHAR(100) NOT NULL,
status VARCHAR(10) NOT NULL,
reason TEXT,
message TEXT,
observed_generation INTEGER NOT NULL DEFAULT 0,
created_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_updated_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_transition_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (resource_id, type),
FOREIGN KEY (resource_id) REFERENCES resources(id) ON DELETE CASCADE
);`).Error; err != nil {
return err
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/db/migrations/202606290001_add_resource_conditions.go` around lines 12 -
24, The `UpdateConditions` migration currently uses `resource_conditions` with
`CREATE TABLE IF NOT EXISTS`, which can silently accept an existing table with
the wrong PK/FK/defaults and mask schema drift. Replace this in the migration
with a schema validation or fail-fast check in the migration logic so
`gormigrate` does not mark it applied unless `resource_conditions` matches the
expected definition; use the `UpdateConditions` migration block and the
`resource_conditions` table definition to enforce exact structure.

Source: Path instructions

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.

1 participant