Skip to content

MGMT-20398: Don't start main agent until ironic reports it's ready#1518

Open
pastequo wants to merge 1 commit into
openshift:masterfrom
pastequo:feat/wait-for-ironic-agent-signal
Open

MGMT-20398: Don't start main agent until ironic reports it's ready#1518
pastequo wants to merge 1 commit into
openshift:masterfrom
pastequo:feat/wait-for-ironic-agent-signal

Conversation

@pastequo

@pastequo pastequo commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

The check is based on a json file.
If the file is missing, the feature is considered as not implemented or that we aren't in a ABI installation ; then the check is skipped.

Assisted-by: Opencode

Following this slack discussion

Will require another PR in the ironic agent repo to actually write the file

cc @CrystalChun @dtantsur

Summary by CodeRabbit

  • New Features

    • Added support for configuring the ironic status file via a new command-line flag or environment variable, with a default path if neither is set.
    • The agent now checks the status file before starting and waits for readiness when needed.
  • Bug Fixes

    • Improved error handling for invalid, unreadable, missing, or unexpected status file contents.
    • The agent now exits with a clear fatal error if status file validation fails.

The check is based on a json file.
If the file is missing, the feature is considered as not implemented
or that we aren't in a ABI installation ; then the check is skipped.

Assisted-by: Opencode
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jun 24, 2026
@openshift-ci-robot

openshift-ci-robot commented Jun 24, 2026

Copy link
Copy Markdown

@pastequo: This pull request references MGMT-20398 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "5.0.0" version, but no target version was set.

Details

In response to this:

The check is based on a json file.
If the file is missing, the feature is considered as not implemented or that we aren't in a ABI installation ; then the check is skipped.

Assisted-by: Opencode

Following this slack discussion

Will require another PR in the ironic agent repo top actually write the file

cc @CrystalChun @dtantsur

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Walkthrough

Adds ironic status file readiness validation to the assisted-installer-agent. A new IronicStatusFilePath field is added to AgentConfig with CLI flag, environment variable, and default path resolution. A new ValidateStatusFile utility polls the status file in a loop, transitioning on JSON-parsed status values, and Main() calls it before starting the agent.

Changes

Ironic Status File Validation

Layer / File(s) Summary
AgentConfig field, status constants, and CLI/env wiring
src/config/agent_config.go, src/util/status_file.go
AgentConfig.IronicStatusFilePath is added; ProcessArgs registers --ironic-status-file with CLI→env→default fallback; status_file.go defines StatusFileContent, StatusInitializing, StatusWaitingForAgent, StatusFinalizing, and polling interval.
ValidateStatusFile and readiness state machine
src/util/status_file.go
ValidateStatusFile returns early if path is unset or file is absent; waitForIronicReadiness loops, parsing JSON status and returning success on waiting for assisted agent, continuing on initializing/empty, or returning errors on finalizing or unexpected values.
Main() integration
src/agent/main/main.go
Main() extracts a logger variable and calls ValidateStatusFile(agentConfig.IronicStatusFilePath) before agent.RunAgent; validation failure triggers a fatal log.
Ginkgo tests
src/util/status_file_test.go
Covers empty path, missing file, blocking waits for content transitions, immediate success, finalizing error, unexpected status error, invalid JSON error, and unreadable file error.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
Container-Privileges ❌ Error subsystem/docker-compose.yml sets privileged: true, which matches the disallowed container privilege setting. Remove privileged: true or add a clear justification/exception; no other touched manifests showed privilege settings.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Structure And Quality ⚠️ Warning FAIL: the new Ginkgo suite uses many bare Expectations with no failure messages and calls the polling validator directly, so tests can hang without a timeout. Add meaningful messages to error checks, wrap polling paths in Eventually/explicit deadlines, and assert all goroutine/chmod/write errors instead of ignoring them.
✅ Passed checks (12 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: delaying agent startup until ironic signals readiness.
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.
Stable And Deterministic Test Names ✅ Passed All Ginkgo titles in the new test file are static, descriptive strings; no timestamps, IDs, or generated values appear in test names.
Microshift Test Compatibility ✅ Passed PASS: The added Ginkgo test is a unit test using only os/time/json; it references no OpenShift APIs/resources and needs no MicroShift guard.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The new Ginkgo tests only validate local status-file behavior and contain no node/topology or multi-node assumptions.
Topology-Aware Scheduling Compatibility ✅ Passed The PR only adds ironic status-file readiness checks in agent/config/util code; no manifests, controllers, replicas, affinity, nodeSelector, or topology logic were introduced.
Ote Binary Stdout Contract ✅ Passed No new process-level stdout writes were added; the new logs use logrus standard logger (stderr by default), and there are no fmt.Print/os.Stdout writes in the touched entrypoints.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed PASS: The new Ginkgo tests are unit tests under src/util; no hardcoded IPv4 addresses or external network access were found in changed files.
No-Weak-Crypto ✅ Passed The PR only adds status-file readiness checks and tests; no weak crypto, custom crypto, or secret/tokens comparisons appear in the changed files.
No-Sensitive-Data-In-Logs ✅ Passed New logs are static readiness/status messages; no passwords, tokens, PII, or API keys are logged, and dynamic values are limited to binary names or wrapped file-path/status errors.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@openshift-ci openshift-ci Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Jun 24, 2026
@openshift-ci

openshift-ci Bot commented Jun 24, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: pastequo

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-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jun 24, 2026

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

🧹 Nitpick comments (2)
src/util/status_file.go (1)

72-123: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Unbounded polling loop with no cancellation/timeout.

waitForIronicReadiness blocks forever on transitional states (empty file, initializing, empty status), and it gates the whole agent startup in Main(). There's no context.Context, deadline, or max-wait, so if ironic never reaches waiting for assisted agent the agent hangs indefinitely with no way to cancel. Consider threading a context.Context with a timeout (or an upper bound on retries) so the wait is cancellable and observable.

As per path instructions: "context.Context for cancellation and timeouts".

Please confirm whether an indefinite, uncancellable wait is the intended product behavior here, or whether an upper bound is expected (the PR objective only states "don't start until ironic reports ready").

🤖 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 `@src/util/status_file.go` around lines 72 - 123, The wait loop in
waitForIronicReadiness is unbounded and cannot be cancelled, so it can block
startup forever on empty or initializing statuses. Update waitForIronicReadiness
(and its caller in Main) to accept a context.Context and enforce a timeout or
max retries, then check ctx.Done() inside the polling loop and return a clear
error when the wait is cancelled or exceeds the limit.

Source: Path instructions

src/util/status_file_test.go (1)

35-184: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add context messages to assertions.

Most Expect(...) calls (e.g. Lines 37, 45, 65, 121, 135, 150, 164, 183) lack failure messages, so a failure reports only the matcher output without identifying which case/condition failed. Adding short messages aids triage when a case regresses.

As per coding guidelines: "Assertion messages - include meaningful failure messages, flag assertions without context messages".

🤖 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 `@src/util/status_file_test.go` around lines 35 - 184, Most Expect assertions
in this status file test are missing context messages, which makes failures hard
to trace to a specific case. Update the assertions in the ValidateStatusFile
test contexts to include short failure messages for each expectation, especially
around the scenarios for empty input, nonexistent file, initializing, waiting,
finalizing, unexpected status, invalid JSON, and unreadable file. Use the
existing test case descriptions and the ValidateStatusFile symbol to keep the
messages specific and consistent.

Source: Coding guidelines

🤖 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 `@src/config/agent_config.go`:
- Around line 13-15: The struct field list in AgentConfig is not gofmt-aligned
after adding IronicStatusFilePath, so realign the type column for the fields in
this block to match gofmt output. Update the field declarations in the
AgentConfig struct so IntervalSecs, HostID, and IronicStatusFilePath use
consistent spacing and the file passes gofmt/golangci-lint.

In `@src/util/status_file_test.go`:
- Around line 168-185: The unreadable-file test in ValidateStatusFile is flaky
because chmod 0000 does not reliably block reads under root, so update the test
to simulate an actual read failure in a root-safe way or skip this case when
running as root. Use the existing ValidateStatusFile and statusFilePath setup to
point at an unreadable path or a directory-as-file scenario, and make sure any
os.Chmod calls in the test handle and assert their returned errors instead of
ignoring them.
- Around line 57-62: The background status-file write in the test helper
goroutines ignores the `os.WriteFile` error, which can leave
`ValidateStatusFile` polling until timeout instead of failing at the real cause.
Update the goroutines in `status_file_test.go` that write `StatusFileContent` to
check and handle the `os.WriteFile` return value, and apply the same fix to the
similar write blocks later in the file so any write failure is surfaced
immediately.

In `@src/util/status_file.go`:
- Around line 113-117: The empty-status branch in the status polling loop is
logging at Info on every iteration, which is inconsistent with the other
transitional paths and can flood logs during long waits. Update the `case ""`
branch in the status handling logic to use `logger.Debug` instead of
`logger.Info`, matching the existing `StatusInitializing` and empty-file
behavior in the same polling loop.

---

Nitpick comments:
In `@src/util/status_file_test.go`:
- Around line 35-184: Most Expect assertions in this status file test are
missing context messages, which makes failures hard to trace to a specific case.
Update the assertions in the ValidateStatusFile test contexts to include short
failure messages for each expectation, especially around the scenarios for empty
input, nonexistent file, initializing, waiting, finalizing, unexpected status,
invalid JSON, and unreadable file. Use the existing test case descriptions and
the ValidateStatusFile symbol to keep the messages specific and consistent.

In `@src/util/status_file.go`:
- Around line 72-123: The wait loop in waitForIronicReadiness is unbounded and
cannot be cancelled, so it can block startup forever on empty or initializing
statuses. Update waitForIronicReadiness (and its caller in Main) to accept a
context.Context and enforce a timeout or max retries, then check ctx.Done()
inside the polling loop and return a clear error when the wait is cancelled or
exceeds the limit.
🪄 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: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 66da9279-5f91-4566-aae5-99d15f3c9329

📥 Commits

Reviewing files that changed from the base of the PR and between f077670 and 235bbb9.

📒 Files selected for processing (4)
  • src/agent/main/main.go
  • src/config/agent_config.go
  • src/util/status_file.go
  • src/util/status_file_test.go

Comment on lines +13 to +15
IntervalSecs int
HostID string
IronicStatusFilePath string

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

gofmt: fix struct field alignment.

golangci-lint reports the file is not gofmt-formatted at this block. The added field changes the longest field-name width, so the type column needs realignment; otherwise CI fails.

🔧 Proposed fix
-	IntervalSecs          int
-	HostID                string
-	IronicStatusFilePath  string
+	IntervalSecs         int
+	HostID               string
+	IronicStatusFilePath string
📝 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
IntervalSecs int
HostID string
IronicStatusFilePath string
IntervalSecs int
HostID string
IronicStatusFilePath string
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 13-13: File is not properly formatted

(gofmt)

🤖 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 `@src/config/agent_config.go` around lines 13 - 15, The struct field list in
AgentConfig is not gofmt-aligned after adding IronicStatusFilePath, so realign
the type column for the fields in this block to match gofmt output. Update the
field declarations in the AgentConfig struct so IntervalSecs, HostID, and
IronicStatusFilePath use consistent spacing and the file passes
gofmt/golangci-lint.

Source: Linters/SAST tools

Comment on lines +57 to +62
go func() {
time.Sleep(20 * time.Millisecond)
content := StatusFileContent{Status: StatusWaitingForAgent}
data, _ := json.Marshal(content)
os.WriteFile(statusFilePath, data, 0644)
}()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Check the goroutine's os.WriteFile error.

If this background write fails, the status file never transitions, ValidateStatusFile keeps polling, and the test hangs until the Ginkgo suite timeout — failing opaquely rather than surfacing the real cause. Same pattern applies to Lines 78-83 and 99-104.

🛡️ Proposed fix
 		go func() {
 			time.Sleep(20 * time.Millisecond)
 			content := StatusFileContent{Status: StatusWaitingForAgent}
-			data, _ := json.Marshal(content)
-			os.WriteFile(statusFilePath, data, 0644)
+			data, err := json.Marshal(content)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(os.WriteFile(statusFilePath, data, 0644)).To(Succeed())
 		}()

As per path instructions: "Never ignore error returns".

📝 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
go func() {
time.Sleep(20 * time.Millisecond)
content := StatusFileContent{Status: StatusWaitingForAgent}
data, _ := json.Marshal(content)
os.WriteFile(statusFilePath, data, 0644)
}()
go func() {
time.Sleep(20 * time.Millisecond)
content := StatusFileContent{Status: StatusWaitingForAgent}
data, err := json.Marshal(content)
Expect(err).ToNot(HaveOccurred())
Expect(os.WriteFile(statusFilePath, data, 0644)).To(Succeed())
}()
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 61-61: Error return value of os.WriteFile is not checked

(errcheck)

🤖 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 `@src/util/status_file_test.go` around lines 57 - 62, The background
status-file write in the test helper goroutines ignores the `os.WriteFile`
error, which can leave `ValidateStatusFile` polling until timeout instead of
failing at the real cause. Update the goroutines in `status_file_test.go` that
write `StatusFileContent` to check and handle the `os.WriteFile` return value,
and apply the same fix to the similar write blocks later in the file so any
write failure is surfaced immediately.

Sources: Path instructions, Linters/SAST tools

Comment on lines +168 to +185
Context("when file becomes unreadable after creation", func() {
It("should return error", func() {
statusFilePath := filepath.Join(tempDir, "status.json")

// Create a valid file first
content := StatusFileContent{Status: StatusInitializing}
data, _ := json.Marshal(content)
err := os.WriteFile(statusFilePath, data, 0644)
Expect(err).ToNot(HaveOccurred())

// Make it unreadable
os.Chmod(statusFilePath, 0000)
defer os.Chmod(statusFilePath, 0644) // Restore for cleanup

err = ValidateStatusFile(statusFilePath)
Expect(err).ToNot(BeNil())
})
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unreadable-file test is flaky when the suite runs as root.

os.Chmod(statusFilePath, 0000) does not deny read access to the root user, so when the test runs as root (common in CI containers) the file remains readable, the status parses as StatusInitializing, and ValidateStatusFile blocks/polls instead of erroring — failing this expectation. Consider gating on non-root, or simulating the read failure differently (e.g. point at a path whose parent directory is non-traversable, or use a directory in place of the file).

Additionally, the os.Chmod return values on Lines 179-180 are ignored.

As per path instructions: "Never ignore error returns".

🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 179-179: Error return value of os.Chmod is not checked

(errcheck)


[error] 180-180: Error return value of os.Chmod is not checked

(errcheck)

🤖 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 `@src/util/status_file_test.go` around lines 168 - 185, The unreadable-file
test in ValidateStatusFile is flaky because chmod 0000 does not reliably block
reads under root, so update the test to simulate an actual read failure in a
root-safe way or skip this case when running as root. Use the existing
ValidateStatusFile and statusFilePath setup to point at an unreadable path or a
directory-as-file scenario, and make sure any os.Chmod calls in the test handle
and assert their returned errors instead of ignoring them.

Sources: Path instructions, Linters/SAST tools

Comment thread src/util/status_file.go
Comment on lines +113 to +117
case "":
logger.Info("Ironic agent defined an empty state, waiting for readiness")
time.Sleep(waitingLoop)

continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Log flooding: empty-status branch logs at Info every iteration.

The case "" branch logs at Info on every 2s poll while transitional StatusInitializing and the empty-file path use Debug. Since this loop can run indefinitely, this floods logs at Info level. Lower to Debug for consistency and to avoid noise.

🔧 Proposed fix
 	case "":
-		logger.Info("Ironic agent defined an empty state, waiting for readiness")
+		logger.Debug("Ironic agent defined an empty state, waiting for readiness")
 		time.Sleep(waitingLoop)
📝 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
case "":
logger.Info("Ironic agent defined an empty state, waiting for readiness")
time.Sleep(waitingLoop)
continue
case "":
logger.Debug("Ironic agent defined an empty state, waiting for readiness")
time.Sleep(waitingLoop)
continue
🤖 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 `@src/util/status_file.go` around lines 113 - 117, The empty-status branch in
the status polling loop is logging at Info on every iteration, which is
inconsistent with the other transitional paths and can flood logs during long
waits. Update the `case ""` branch in the status handling logic to use
`logger.Debug` instead of `logger.Info`, matching the existing
`StatusInitializing` and empty-file behavior in the same polling loop.

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.70175% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.84%. Comparing base (23e48fc) to head (235bbb9).
⚠️ Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
src/config/agent_config.go 0.00% 5 Missing ⚠️
src/agent/main/main.go 0.00% 4 Missing ⚠️
src/util/status_file.go 95.83% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1518      +/-   ##
==========================================
+ Coverage   56.49%   56.84%   +0.35%     
==========================================
  Files          90       91       +1     
  Lines        4652     4709      +57     
==========================================
+ Hits         2628     2677      +49     
- Misses       1817     1826       +9     
+ Partials      207      206       -1     
Files with missing lines Coverage Δ
src/util/status_file.go 95.83% <95.83%> (ø)
src/agent/main/main.go 0.00% <0.00%> (ø)
src/config/agent_config.go 0.00% <0.00%> (ø)

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@openshift-ci

openshift-ci Bot commented Jun 24, 2026

Copy link
Copy Markdown

@pastequo: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/images 235bbb9 link true /test images
ci/prow/e2e-agent-compact-ipv4 235bbb9 link true /test e2e-agent-compact-ipv4
ci/prow/edge-lint 235bbb9 link true /test edge-lint
ci/prow/edge-e2e-ai-operator-ztp 235bbb9 link true /test edge-e2e-ai-operator-ztp

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants