Skip to content

OCPBUGS-54776: fix(ignition-server): log MCS output on HTTP request failures - #8936

Merged
openshift-merge-bot[bot] merged 1 commit into
openshift:mainfrom
hypershift-community:fix-OCPBUGS-54776
Jul 28, 2026
Merged

OCPBUGS-54776: fix(ignition-server): log MCS output on HTTP request failures#8936
openshift-merge-bot[bot] merged 1 commit into
openshift:mainfrom
hypershift-community:fix-OCPBUGS-54776

Conversation

@hypershift-jira-solve-ci

@hypershift-jira-solve-ci hypershift-jira-solve-ci Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

When the MCS HTTP polling loop encounters errors (connection failures, unexpected status codes, or body read failures), the log messages now include the MCS process stdout/stderr output to aid debugging.

Changes:

  • Add syncBuffer type for thread-safe capture of MCS process output
  • Use cmd.Start() + deferred cmd.Wait() instead of goroutine for process lifecycle management
  • Fix nil error passed to log.Error() on non-200 status codes
  • Fix response body leak on non-200 status codes
  • Truncate MCS output in log fields to last 8KB to bound log size
  • Add unit tests for syncBuffer including concurrent safety

Which issue(s) this PR fixes:

Fixes https://redhat.atlassian.net/browse/OCPBUGS-54776

Special notes for your reviewer:

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Always review AI generated responses prior to use.
Generated with Claude Code via openshift-developer plugin


Note: This PR was auto-generated by the jira-agent periodic CI job in response to OCPBUGS-54776. See the full report for token usage, cost breakdown, and detailed phase output.

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability while starting/stopping the background machine-config server during payload retrieval.
    • Enhanced error reporting by capturing recent stdout/stderr and including it in polling/final logs.
    • Updated polling behavior to continue on non-success HTTP responses and retry appropriately, returning fetched payload only on success.
  • Tests
    • Added unit tests for tail truncation and a thread-safe output buffer with concurrent read/write coverage.
    • Added HTTP handler tests for payload fetching, verifying success behavior, retry signaling on failures, inclusion of captured output, and context cancellation handling.

Note: This PR was auto-generated by the jira-agent periodic CI job in response to OCPBUGS-54776. See the full report for token usage, cost breakdown, and detailed phase output.

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci-robot openshift-ci-robot added jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Jul 6, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@hypershift-jira-solve-ci[bot]: This pull request references Jira Issue OCPBUGS-54776, which is invalid:

  • expected the bug to target the "5.0.0" version, but no target version was set

Comment /jira refresh to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

What this PR does / why we need it:

When the MCS HTTP polling loop encounters errors (connection failures, unexpected status codes, or body read failures), the log messages now include the MCS process stdout/stderr output to aid debugging.

Changes:

  • Add syncBuffer type for thread-safe capture of MCS process output
  • Use cmd.Start() + deferred cmd.Wait() instead of goroutine for process lifecycle management
  • Fix nil error passed to log.Error() on non-200 status codes
  • Fix response body leak on non-200 status codes
  • Truncate MCS output in log fields to last 8KB to bound log size
  • Add unit tests for syncBuffer including concurrent safety

Which issue(s) this PR fixes:

Fixes https://redhat.atlassian.net/browse/OCPBUGS-54776

Special notes for your reviewer:

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Always review AI generated responses prior to use.
Generated with Claude Code via openshift-developer plugin

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 Jul 6, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change adds a mutex-protected, tail-truncated buffer for machine-config-server output and retry-aware HTTP payload fetching. runMCSAndFetchPayload now starts the process with a cancelable context, captures stdout and stderr, waits for process exit after polling, and includes buffered output when returning polling errors. Tests cover truncation, concurrent access, HTTP responses, connection failures, headers, and output snapshots.

Sequence Diagram(s)

sequenceDiagram
  participant runMCSAndFetchPayload
  participant machine-config-server
  participant fetchMCSIgnitionPayload
  participant syncBuffer
  runMCSAndFetchPayload->>machine-config-server: start with syncBuffer stdout and stderr
  runMCSAndFetchPayload->>fetchMCSIgnitionPayload: poll for payload
  machine-config-server->>syncBuffer: write process output
  fetchMCSIgnitionPayload-->>runMCSAndFetchPayload: payload or retry signal
  runMCSAndFetchPayload->>machine-config-server: cancel context and wait for exit
  runMCSAndFetchPayload->>syncBuffer: read final output
  runMCSAndFetchPayload-->>runMCSAndFetchPayload: return payload or wrapped error
Loading

Suggested reviewers: sdminonne, sjenning


Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 1 inconclusive)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error runMCSAndFetchPayload logs and returns mcsOutput.String(), so arbitrary MCS stdout/stderr can leak secrets into info/error logs. Redact or sanitize MCS stdout/stderr before logging; keep full output out of structured logs/errors, or log only safe summaries/IDs.
Test Structure And Quality ❓ Inconclusive placeholder need evidence
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main change: logging MCS output when HTTP requests fail in ignition-server.
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 added test titles are static, descriptive strings; no generated names, timestamps, UUIDs, or runtime-derived values found.
Topology-Aware Scheduling Compatibility ✅ Passed The PR only changes ignition-server polling/logging and tests; no manifests, replicas, affinity, selectors, PDBs, or topology logic were added.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed Only unit tests were added; they use httptest/local URLs and no IPv4-only or external connectivity assumptions were introduced.
No-Weak-Crypto ✅ Passed No banned weak-crypto primitives appear in the PR changes; the only hash comparison is util.HashSimple (FNV-1a) for config matching, not secret/token crypto.
Container-Privileges ✅ Passed Only Go controller/test files changed; no manifest or security-context settings for privileged/root/host* or SYS_ADMIN were added.
✨ 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 requested review from sdminonne and sjenning July 6, 2026 16:06
@openshift-ci openshift-ci Bot added area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release and removed do-not-merge/needs-area labels Jul 6, 2026
@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 52.94118% with 32 lines in your changes missing coverage. Please review.
✅ Project coverage is 44.53%. Comparing base (144cca9) to head (877c3fb).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
...ition-server/controllers/local_ignitionprovider.go 52.94% 31 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8936      +/-   ##
==========================================
+ Coverage   44.51%   44.53%   +0.01%     
==========================================
  Files         774      774              
  Lines       96997    97036      +39     
==========================================
+ Hits        43179    43215      +36     
- Misses      50830    50832       +2     
- Partials     2988     2989       +1     
Files with missing lines Coverage Δ
...ition-server/controllers/local_ignitionprovider.go 42.22% <52.94%> (+2.69%) ⬆️
Flag Coverage Δ
cmd-support 38.39% <ø> (ø)
cpo-hostedcontrolplane 47.22% <ø> (ø)
cpo-other 45.25% <ø> (ø)
hypershift-operator 54.45% <ø> (ø)
other 32.87% <52.94%> (+0.23%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
ignition-server/controllers/local_ignitionprovider.go (1)

618-628: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider bounding the captured MCS output in the log field and error message.

mcsOutput.String() is emitted in full at both Line 622 and Line 625. MCS bootstrap output is unbounded, so a verbose/looping process can bloat structured logs and, worse, produce very large error strings that propagate up the call chain. The PR intent notes truncating to the last 8KB — that safeguard isn't present here. Truncating to the tail keeps the most recent (usually most relevant) output while capping size.

Correctness of the buffer read itself is fine: both reads occur after <-mcsDone (i.e., after cmd.Wait() and its stdout/stderr copy goroutines have completed), so there's no concurrent access to the bytes.Buffer.

♻️ Example: truncate to the last 8KB
+	const maxMCSLog = 8 << 10 // 8KB
+	mcsLog := mcsOutput.String()
+	if len(mcsLog) > maxMCSLog {
+		mcsLog = mcsLog[len(mcsLog)-maxMCSLog:]
+	}
+
 	// Stop MCS and wait for process exit so all output is flushed to the buffer.
 	cancel()
 	mcsErr := <-mcsDone
-	log.Info("machine-config-server process exited", "output", mcsOutput.String(), "error", mcsErr)
+	log.Info("machine-config-server process exited", "output", mcsLog, "error", mcsErr)

 	if err != nil {
-		return nil, fmt.Errorf("mcs logs: %s: %w", mcsOutput.String(), err)
+		return nil, fmt.Errorf("mcs logs: %s: %w", mcsLog, err)
 	}

Note: move the truncation computation after <-mcsDone so the buffer is fully populated before reading.

🤖 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 `@ignition-server/controllers/local_ignitionprovider.go` around lines 618 -
628, The MCS exit logging and returned error in local_ignitionprovider.go are
using the full mcsOutput.String(), which can be unbounded and bloat logs/error
chains. Update the post-<mcsDone> handling around the machine-config-server
shutdown to cap the captured output to the last 8KB before using it in log.Info
and the fmt.Errorf return path. Keep the truncation logic near the existing
mcsOutput.String() reads so the tail of the output is preserved while limiting
size.
🤖 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.

Nitpick comments:
In `@ignition-server/controllers/local_ignitionprovider.go`:
- Around line 618-628: The MCS exit logging and returned error in
local_ignitionprovider.go are using the full mcsOutput.String(), which can be
unbounded and bloat logs/error chains. Update the post-<mcsDone> handling around
the machine-config-server shutdown to cap the captured output to the last 8KB
before using it in log.Info and the fmt.Errorf return path. Keep the truncation
logic near the existing mcsOutput.String() reads so the tail of the output is
preserved while limiting size.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 90494ab4-8c09-47a1-9cb6-ad201dc3979b

📥 Commits

Reviewing files that changed from the base of the PR and between da61727 and cebd35e.

📒 Files selected for processing (1)
  • ignition-server/controllers/local_ignitionprovider.go

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is the use case we want to cover, something like

		if res.StatusCode != http.StatusOK {
			if closeErr := res.Body.Close(); closeErr != nil {
				log.Error(closeErr, "failed to close mcs response body")
			}
			log.Error(fmt.Errorf("unexpected status code %d", res.StatusCode), "mcs returned unexpected response code", "code", res.StatusCode, "mcsOutput", mcsOutput.String())
			return false, nil
		}
``

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.

Done. Moved defer res.Body.Close() before the status check, replaced the nil err with a real fmt.Errorf, and added mcsOutput to the log line — matching the snippet you suggested. Also replaced bytes.Buffer with a mutex-protected syncBuffer capped at 8 KiB (per the truncation request in the general comment) and added unit tests for truncateTail and syncBuffer.


AI-assisted response via Claude Code

@enxebre

enxebre commented Jul 7, 2026

Copy link
Copy Markdown
Member

Implement truncation as suggested by code rabbit.
Maybe something like


const maxMCSLogBytes = 8 << 10 // 8KB

// syncBuffer is a thread-safe buffer for capturing MCS process stdout/stderr while
// the polling loop reads snapshots for error logging.
type syncBuffer struct {
	mu  sync.Mutex
	buf bytes.Buffer
}

func (b *syncBuffer) Write(p []byte) (int, error) {
	b.mu.Lock()
	defer b.mu.Unlock()
	return b.buf.Write(p)
}

func (b *syncBuffer) String() string {
	b.mu.Lock()
	defer b.mu.Unlock()
	return truncateTail(b.buf.String(), maxMCSLogBytes)
}

func truncateTail(s string, max int) string {
	if len(s) <= max {
		return s
	}
	return s[len(s)-max:]
}

cover these with units

@enxebre

enxebre commented Jul 7, 2026

Copy link
Copy Markdown
Member

/test address-review-comments

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Review agent triggered. View job

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (2)
ignition-server/controllers/local_ignitionprovider.go (2)

121-126: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Parameter max shadows the built-in max.

Since Go 1.21, max is a predeclared built-in. Shadowing it here is a variable-shadowing smell (and predeclared linters flag it). Rename to limit or similar.

♻️ Proposed rename
-func truncateTail(s string, max int) string {
-	if len(s) <= max {
-		return s
-	}
-	return s[len(s)-max:]
+func truncateTail(s string, limit int) string {
+	if len(s) <= limit {
+		return s
+	}
+	return s[len(s)-limit:]
 }

Update the TestTruncateTail table field name accordingly if you also rename there.

As per coding guidelines: "Avoid variable shadowing."

🤖 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 `@ignition-server/controllers/local_ignitionprovider.go` around lines 121 -
126, Rename the `max` parameter in `truncateTail` to a non-shadowing name such
as `limit` to avoid clashing with Go’s predeclared built-in, and update the
length comparison and slicing logic accordingly. If `TestTruncateTail` or any
related table-driven tests use a matching field name, rename that field too so
the test data and call sites stay consistent.

Source: Coding guidelines


104-119: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

syncBuffer truncates only on read, not in memory.

String() caps output at 8 KiB, but the underlying buf is never reset, so it grows unbounded for the lifetime of the MCS process. Log size is bounded as intended, but memory is not — a verbose MCS that keeps emitting output during a long polling window can retain everything. If unbounded MCS output is plausible, consider trimming buf in Write once it exceeds a threshold (e.g., a few multiples of maxMCSLogBytes) so retained memory stays bounded too.

🤖 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 `@ignition-server/controllers/local_ignitionprovider.go` around lines 104 -
119, The syncBuffer implementation only truncates in String(), so the underlying
bytes.Buffer in syncBuffer keeps growing unbounded over time. Update
syncBuffer.Write to trim or reset buf when it exceeds a reasonable cap based on
maxMCSLogBytes, and keep String() as the read-side limit; use the syncBuffer
methods and maxMCSLogBytes constant to locate and adjust this behavior.
🤖 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.

Nitpick comments:
In `@ignition-server/controllers/local_ignitionprovider.go`:
- Around line 121-126: Rename the `max` parameter in `truncateTail` to a
non-shadowing name such as `limit` to avoid clashing with Go’s predeclared
built-in, and update the length comparison and slicing logic accordingly. If
`TestTruncateTail` or any related table-driven tests use a matching field name,
rename that field too so the test data and call sites stay consistent.
- Around line 104-119: The syncBuffer implementation only truncates in String(),
so the underlying bytes.Buffer in syncBuffer keeps growing unbounded over time.
Update syncBuffer.Write to trim or reset buf when it exceeds a reasonable cap
based on maxMCSLogBytes, and keep String() as the read-side limit; use the
syncBuffer methods and maxMCSLogBytes constant to locate and adjust this
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 678824ff-5799-4304-9d9b-d62460b6cbbb

📥 Commits

Reviewing files that changed from the base of the PR and between cebd35e and 0c55153.

📒 Files selected for processing (2)
  • ignition-server/controllers/local_ignitionprovider.go
  • ignition-server/controllers/local_ignitionprovider_test.go

@enxebre

enxebre commented Jul 7, 2026

Copy link
Copy Markdown
Member

/approve

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 7, 2026
@enxebre

enxebre commented Jul 8, 2026

Copy link
Copy Markdown
Member

/jira refresh

@openshift-ci-robot openshift-ci-robot added jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. and removed jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Jul 8, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@enxebre: This pull request references Jira Issue OCPBUGS-54776, which is valid. The bug has been moved to the POST state.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state ASSIGNED, which is one of the valid states (NEW, ASSIGNED, POST)
Details

In response to this:

/jira refresh

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.

@bryan-cox

Copy link
Copy Markdown
Member

codecov/patch is failing because the changes do not have adequate unit test coverage. Use https://github.com/bryan-cox/personal-claude-skills/blob/main/plugins/behavior-driven-testing/skills/behavior-driven-testing/SKILL.md to create unit tests for the changes in the PR.

@bryan-cox

Copy link
Copy Markdown
Member

/test address-review-comments

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Review agent triggered (Gangway execution ID: b9601d70-8ad3-42a0-97c6-e965b4b46df3). The Prow job has not started yet — check the job history for the run once it begins.

@openshift-ci-robot

Copy link
Copy Markdown

@hypershift-jira-solve-ci[bot]: This pull request references Jira Issue OCPBUGS-54776, which is valid.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, POST)
Details

In response to this:

What this PR does / why we need it:

When the MCS HTTP polling loop encounters errors (connection failures, unexpected status codes, or body read failures), the log messages now include the MCS process stdout/stderr output to aid debugging.

Changes:

  • Add syncBuffer type for thread-safe capture of MCS process output
  • Use cmd.Start() + deferred cmd.Wait() instead of goroutine for process lifecycle management
  • Fix nil error passed to log.Error() on non-200 status codes
  • Fix response body leak on non-200 status codes
  • Truncate MCS output in log fields to last 8KB to bound log size
  • Add unit tests for syncBuffer including concurrent safety

Which issue(s) this PR fixes:

Fixes https://redhat.atlassian.net/browse/OCPBUGS-54776

Special notes for your reviewer:

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Always review AI generated responses prior to use.
Generated with Claude Code via openshift-developer plugin


Note: This PR was auto-generated by the jira-agent periodic CI job in response to OCPBUGS-54776. See the full report for token usage, cost breakdown, and detailed phase output.

Summary by CodeRabbit

  • Bug Fixes
  • Improved reliability when fetching the initial payload by safely managing the background server process lifecycle.
  • Improved error reporting by including captured server output logs when startup or polling fails.
  • Adjusted handling of non-success HTTP responses to log details and avoid treating intermediate statuses as fatal errors.
  • Tests
  • Added coverage for tail truncation, a thread-safe output buffer (including concurrent write/read scenarios), and HTTP payload fetch behavior (success, retry signaling, and log content on non-success responses).

Note: This PR was auto-generated by the jira-agent periodic CI job in response to OCPBUGS-54776. See the full report for token usage, cost breakdown, and detailed phase output.

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 left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ignition-server/controllers/local_ignitionprovider_test.go (1)

1390-1447: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Bound the buffer during Write, not only when formatting String.

The test proves only that String() returns at most 8 KiB. However, syncBuffer.Write in local_ignitionprovider.go still appends all data to bytes.Buffer; truncation happens only when reading it. Long-running or noisy MCS output can therefore grow memory without limit. Make Write retain only the tail, and assert the retained buffer itself stays bounded.

🤖 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 `@ignition-server/controllers/local_ignitionprovider_test.go` around lines 1390
- 1447, Update syncBuffer.Write in local_ignitionprovider.go to truncate stored
data immediately, retaining only the newest maxMCSLogBytes bytes after every
write instead of allowing bytes.Buffer to grow unbounded. Preserve the existing
tail semantics and synchronization, then extend TestSyncBuffer to verify the
underlying retained buffer length remains at most maxMCSLogBytes, not just the
length returned by String.
🧹 Nitpick comments (2)
ignition-server/controllers/local_ignitionprovider_test.go (2)

1449-1528: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a response-body closure assertion.

These tests verify payloads and status handling, but they cannot detect a regression that omits resp.Body.Close(). Add a custom RoundTripper returning a tracking ReadCloser, and assert closure for both successful and non-200 responses.

🤖 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 `@ignition-server/controllers/local_ignitionprovider_test.go` around lines 1449
- 1528, Add response-body closure coverage to TestFetchMCSIgnitionPayload by
using a custom http.RoundTripper that returns a tracking ReadCloser, then assert
its Close method is called for both successful and non-200 responses. Preserve
the existing payload/status assertions while routing the client through this
transport so regressions omitting resp.Body.Close() are detected.

Source: Coding guidelines


1551-1574: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the log output, not only buffer readability.

This test does not verify that MCS output is actually emitted in the failure log; it only verifies that the buffer remains readable. Capture and assert the relevant logger output, or rename the test to reflect the narrower buffer-preservation contract.

🤖 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 `@ignition-server/controllers/local_ignitionprovider_test.go` around lines 1551
- 1574, Update TestFetchMCSIgnitionPayloadMCSOutputIncludedInLog to capture the
logger output used by fetchMCSIgnitionPayload and assert it contains the MCS
buffer text when the request returns non-200; alternatively, rename the test to
describe only buffer preservation if logging cannot be captured.
🤖 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 `@ignition-server/controllers/local_ignitionprovider_test.go`:
- Line 1464: The test fixtures currently discard errors returned by w.Write in
the affected handlers. Update each Write call, including those in the fixtures
around the referenced locations, to capture and check the returned error,
failing the test or returning from the handler when writing fails; do not use
blank assignments to ignore errors.

---

Outside diff comments:
In `@ignition-server/controllers/local_ignitionprovider_test.go`:
- Around line 1390-1447: Update syncBuffer.Write in local_ignitionprovider.go to
truncate stored data immediately, retaining only the newest maxMCSLogBytes bytes
after every write instead of allowing bytes.Buffer to grow unbounded. Preserve
the existing tail semantics and synchronization, then extend TestSyncBuffer to
verify the underlying retained buffer length remains at most maxMCSLogBytes, not
just the length returned by String.

---

Nitpick comments:
In `@ignition-server/controllers/local_ignitionprovider_test.go`:
- Around line 1449-1528: Add response-body closure coverage to
TestFetchMCSIgnitionPayload by using a custom http.RoundTripper that returns a
tracking ReadCloser, then assert its Close method is called for both successful
and non-200 responses. Preserve the existing payload/status assertions while
routing the client through this transport so regressions omitting
resp.Body.Close() are detected.
- Around line 1551-1574: Update
TestFetchMCSIgnitionPayloadMCSOutputIncludedInLog to capture the logger output
used by fetchMCSIgnitionPayload and assert it contains the MCS buffer text when
the request returns non-200; alternatively, rename the test to describe only
buffer preservation if logging cannot be captured.
🪄 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 YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 66ccfe58-3f29-4755-9ddd-b69437d75325

📥 Commits

Reviewing files that changed from the base of the PR and between 0c55153 and 2310167.

📒 Files selected for processing (2)
  • ignition-server/controllers/local_ignitionprovider.go
  • ignition-server/controllers/local_ignitionprovider_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • ignition-server/controllers/local_ignitionprovider.go

name: "When MCS returns 200 with payload, it should return the payload",
handler: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"ignition":{"version":"3.2.0"}}`))

@coderabbitai coderabbitai Bot Jul 10, 2026

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Do not discard test-fixture write errors.

The _, _ = ...Write(...) assignments hide failures in HTTP handlers and buffer setup. Check the returned errors and fail or return from the test fixture when a write fails.

As per coding guidelines: “Always check errors — don’t ignore them.” As per path instructions: “Never ignore error returns.”

Also applies to: 1481-1481, 1493-1493, 1511-1511, 1565-1565

🤖 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 `@ignition-server/controllers/local_ignitionprovider_test.go` at line 1464, The
test fixtures currently discard errors returned by w.Write in the affected
handlers. Update each Write call, including those in the fixtures around the
referenced locations, to capture and check the returned error, failing the test
or returning from the handler when writing fails; do not use blank assignments
to ignore errors.

Sources: Coding guidelines, Path instructions

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.

Done. Checked and fixed all w.Write error handling in test fixtures: HTTP handler closures now check the error and return, and mcsOutput.Write calls use gomega assertions where available. The goroutine concurrency test (syncBuffer thread-safety test) is left as-is since t.Fatal is unsafe from goroutines and bytes.Buffer.Write cannot error with small writes.


AI-assisted response via Claude Code

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.

Skipped: comment is from another GitHub bot.

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Test Failure Analysis Complete

Job Information

  • Prow Job: codecov/patch (Codecov GitHub App check)
  • Build ID: Check run 86462794072
  • PR: #8936OCPBUGS-54776: fix(ignition-server): log MCS output on HTTP request failures
  • Head SHA: 2310167043b938e3da8652e39146ee9498619ddc

Test Failure Analysis

Error

codecov/patch: 42.64% of diff hit (target 43.37%) — 39 lines in changes missing coverage
File: ignition-server/controllers/local_ignitionprovider.go — Patch 42.64%, 36 missing + 3 partials

Summary

The codecov/patch check failed because only 42.64% of the new/modified executable lines in local_ignitionprovider.go are covered by unit tests, falling below Codecov's auto-target threshold of 43.37%. The PR adds well-tested helper types (syncBuffer, truncateTail, fetchMCSIgnitionPayload) but the process-lifecycle and polling-loop code inside the runMCSAndFetchPayload method — which spawns a real machine-config-server subprocess — remains uncovered. The gap is only 0.73 percentage points (roughly 1 more covered line would pass). Note that codecov/patch is an advisory check, not a merge-blocking required status check in openshift/hypershift.

Root Cause

The 39 uncovered lines (36 missing + 3 partial) all reside within runMCSAndFetchPayload() in local_ignitionprovider.go and fall into three categories:

1. Process setup and lifecycle (~10 lines)

  • cmd.WaitDelay = 10 * time.Second
  • var mcsOutput syncBuffer / cmd.Stdout = &mcsOutput / cmd.Stderr = &mcsOutput
  • cmd.Start() + error branch (return nil, fmt.Errorf("failed to start machine-config-server: %w", err))
  • mcsDone := make(chan error, 1) + goroutine with mcsDone <- cmd.Wait()

These lines spawn a real machine-config-server binary via exec.CommandContext. They are untestable without either a real binary on disk or refactoring to inject a process-runner interface.

2. HTTP poll callback body (~15 lines)

  • The wait.PollUntilContextCancel callback that calls fetchMCSIgnitionPayload
  • Payload assignment payload = body
  • log.Info("got mcs payload", ...)
  • The return done, pollErr path

While the extracted fetchMCSIgnitionPayload function itself IS tested (via TestFetchMCSIgnitionPayload), the inline callback that calls it inside runMCSAndFetchPayload is not, because exercising the callback requires the full method to execute — meaning a running MCS subprocess.

3. Post-poll cleanup and error wrapping (~7 lines)

  • cancel() — cancels the MCS subprocess context
  • mcsErr := <-mcsDone — waits for process exit
  • log.Info("machine-config-server process exited", ...)
  • if err != nil { return nil, fmt.Errorf("mcs logs: %s: %w", mcsOutput.String(), err) }
  • return payload, nil

These are pure logic operating on channels and strings but are only reachable through the full runMCSAndFetchPayload integration flow.

Why existing tests don't cover these: The PR adds comprehensive tests for the independently testable parts:

  • TestTruncateTail: 5 table-driven cases covering all boundary conditions
  • TestSyncBuffer: 3 sub-tests including 10-goroutine concurrent write/read race verification
  • TestFetchMCSIgnitionPayload: 4 table-driven cases with httptest servers covering HTTP 200, non-200, 500, and Accept header validation
  • TestFetchMCSIgnitionPayloadConnectionError: connection failure retry behavior
  • TestFetchMCSIgnitionPayloadMCSOutputIncludedInLog: buffer readability after non-200 response

The remaining 39 uncovered lines are embedded inside a method that requires an actual filesystem binary (machine-config-server) to start, making unit testing impractical without further architectural refactoring.

Coverage arithmetic: The target 43.37% is Codecov's auto-computed threshold from the base branch. The PR achieves 42.64% — a gap of just 0.73 percentage points.

Recommendations
  1. This check is NOT a merge blocker. In openshift/hypershift, the required merge gates are the Prow CI jobs (e2e tests, unit tests, linting). codecov/patch is an advisory/informational check — it does not prevent merging. A maintainer can approve and /lgtm despite this failure.

  2. The PR already has high-quality test coverage for all testable code paths. The extracted helpers (syncBuffer, truncateTail, fetchMCSIgnitionPayload) have thorough tests including table-driven cases, boundary conditions, concurrency safety, and HTTP error scenarios.

  3. To close the 0.73% gap (optional): Extract the post-poll cleanup logic into a standalone testable function:

    func shutdownMCSAndWrapError(cancel context.CancelFunc, mcsDone <-chan error,
        mcsOutput *syncBuffer, payload []byte, pollErr error, log logr.Logger) ([]byte, error) {
        cancel()
        mcsErr := <-mcsDone
        log.Info("machine-config-server process exited", "output", mcsOutput.String(), "error", mcsErr)
        if pollErr != nil {
            return nil, fmt.Errorf("mcs logs: %s: %w", mcsOutput.String(), pollErr)
        }
        return payload, nil
    }

    This function is pure logic (cancel, channel receive, format error) and is trivially testable with a pre-loaded syncBuffer and a pre-filled channel. Covering 5–7 lines here would push patch coverage well above the 43.37% target.

  4. For the cmd.Start() error path (optional): Test by constructing an exec.Command with a nonexistent binary path and verifying the error wrapping — though this would require extracting the start logic first.

Evidence
Evidence Detail
Check conclusion failure — patch coverage 42.64% < target 43.37%
Coverage gap 0.73 percentage points (~1 additional covered line would pass)
Lines missing coverage 39 total: 36 missing + 3 partial, all in local_ignitionprovider.go
Uncovered code location runMCSAndFetchPayload() method — subprocess lifecycle, poll callback, post-poll cleanup
Tested code truncateTail (5 cases), syncBuffer (3 sub-tests incl. concurrency), fetchMCSIgnitionPayload (4 HTTP cases + connection error + buffer readability)
Files changed local_ignitionprovider.go (+95/−36), local_ignitionprovider_test.go (+239/−0)
Project coverage impact 43.37% → 43.78% (+0.40%) — project coverage actually improved
Check is advisory codecov/patch is not a required status check for merge in openshift/hypershift
Codecov report PR #8936 on Codecov

@bryan-cox

Copy link
Copy Markdown
Member

/rebase

@bryan-cox

Copy link
Copy Markdown
Member

/test address-review-comments

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Review agent triggered. View job

@bryan-cox

Copy link
Copy Markdown
Member

/restructure-commits

@github-actions

Copy link
Copy Markdown

🤖 Restructuring commits: workflow run

…ors and add unit tests

When the MCS HTTP polling fails, the error returned to callers did not
include the MCS binary's own logs, making troubleshooting ignition
payload failures significantly harder.

- Restructure MCS process management to capture stdout/stderr in a
  thread-safe syncBuffer with 8 KiB truncation, propagating output in
  both logs and returned error messages
- Extract HTTP polling into a standalone fetchMCSIgnitionPayload
  function for testability
- Add cmd.WaitDelay to prevent unbounded blocking from orphaned child
  processes holding pipe file descriptors
- Fix non-OK status code path to log errors with MCS output and close
  the response body
- Add comprehensive unit tests for fetch logic using httptest

Signed-off-by: OpenShift CI Bot <ci-bot@redhat.com>
Commit-Message-Assisted-by: Claude (via Claude Code)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Jul 25, 2026
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling tests matching the pipeline_run_if_changed or not excluded by pipeline_skip_if_only_changed parameters:
/test e2e-aks
/test e2e-aws
/test e2e-aws-upgrade-hypershift-operator
/test e2e-azure-v2-self-managed
/test e2e-kubevirt-aws-ovn-reduced
/test e2e-v2-aws
/test e2e-v2-gke
/test unit
/test verify

@openshift-ci

openshift-ci Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: bryan-cox, enxebre, hypershift-jira-solve-ci[bot]

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

@bryan-cox

Copy link
Copy Markdown
Member

/test verify

@bryan-cox

Copy link
Copy Markdown
Member

/test e2e-aro-hcp

1 similar comment
@bryan-cox

Copy link
Copy Markdown
Member

/test e2e-aro-hcp

@bryan-cox

Copy link
Copy Markdown
Member

Empirical Verification of Code Changes via e2e-aws CI Run

Analysis of the e2e-aws job artifacts confirms the PR's modified code paths in local_ignitionprovider.go were exercised and worked correctly during the test run.

1. PR image confirmed running in ignition-server pods

The ignition-server pod YAML from the TestCreateCluster HCP namespace shows:

  • Image: registry.build01.ci.openshift.org/ci-op-ix2mtqhx/stable@sha256:428f90eef7b6... (PR's CI build namespace)
  • restartCount: 0, ready: true, started: true

2. GetPayload events prove the full code chain executed

The call chain modified by this PR is:

TokenSecretReconciler.Reconcile()
  → IgnitionProvider.GetPayload()            [tokensecret_controller.go:294]
    → runMCSAndFetchPayload()                [cmd.Start() + goroutine cmd.Wait(), syncBuffer]
      → fetchMCSIgnitionPayload()            [new extracted function]

GetPayload events (recorded at start.go:273 after successful payload delivery) were found across multiple test HCP namespaces:

Test Token Secret Events
TestCreateCluster token-create-cluster-ks5x9-us-east-1a-* 1 (16:10:22Z)
TestCreateCluster token-create-cluster-ks5x9-us-east-1b-* 1 (16:10:24Z)
TestCreateCluster token-create-cluster-ks5x9-us-east-1c-* 1 (16:10:33Z)
TestCreateClusterProxy token-proxy-nxchs-us-east-1b-* 2 (16:10:19Z–16:10:48Z)
TestKarpenter token-karpenter-5mkxp-us-east-1c-* + 8 sub-test tokens Multiple each

Each event proves: the syncBuffer captured MCS output, cmd.Start()/cmd.Wait() lifecycle completed, fetchMCSIgnitionPayload() returned a valid payload, and nodes bootstrapped successfully.

3. Per-change verification

PR Change Exercised in e2e? How verified
syncBuffer for thread-safe MCS output stdout/stderr target for cmd.Start() in every runMCSAndFetchPayload() call
cmd.Start() + goroutine cmd.Wait() lifecycle Every GetPayload event proves MCS started, payload fetched, process cleaned up
Extracted fetchMCSIgnitionPayload() Only code path performing HTTP GET to MCS; every GetPayload proves it returned (payload, true, nil)
cmd.WaitDelay = 10 * time.Second MCS shutdown used this delay; no hangs observed
Response body leak fix (non-200 path) Error path only — covered by TestFetchMCSIgnitionPayload unit tests
Nil error fix on non-200 log Error path only — covered by unit tests
MCS output in error messages Error path only — covered by unit tests
truncateTail() 8KB log cap Called on every MCS exit, but log output not visible (mgmt cluster dump scrubbed)

4. Summary

All 16 test suites bootstrapped nodes through ignition-server pods running this PR's image. The GetPayload events in the per-test namespace dumps provide direct empirical evidence that the refactored runMCSAndFetchPayload() and new fetchMCSIgnitionPayload() worked correctly on the happy path. The error-handling improvements (body leak fix, nil-error fix, MCS output in errors) are error-path-only and verified by unit tests (ci/prow/unit passed).

@bryan-cox

Copy link
Copy Markdown
Member

/verified by e2e

See #8936 (comment). I think its acceptable to not test those 2 error paths since they are covered by a unit test.

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Jul 27, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@bryan-cox: This PR has been marked as verified by e2e.

Details

In response to this:

/verified by e2e

See #8936 (comment). I think its acceptable to not test those 2 error paths since they are covered by a unit test.

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.

@bryan-cox

Copy link
Copy Markdown
Member

/retest-required

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

/retest-required

Remaining retests: 0 against base HEAD a5b7926 and 2 for PR HEAD 877c3fb in total

@bryan-cox

Copy link
Copy Markdown
Member

/test e2e-aws

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

/retest-required

Remaining retests: 0 against base HEAD 5e4dd58 and 1 for PR HEAD 877c3fb in total

@bryan-cox

Copy link
Copy Markdown
Member

/test e2e-aws

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

/retest-required

Remaining retests: 0 against base HEAD fe62283 and 0 for PR HEAD 877c3fb in total

@openshift-ci

openshift-ci Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

@hypershift-jira-solve-ci[bot]: The following test 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/e2e-aro-hcp 877c3fb link false /test e2e-aro-hcp

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.

@bryan-cox

Copy link
Copy Markdown
Member

/test e2e-v2-gke

@openshift-merge-bot
openshift-merge-bot Bot merged commit 8761071 into openshift:main Jul 28, 2026
34 of 35 checks passed
@openshift-ci-robot

Copy link
Copy Markdown

@hypershift-jira-solve-ci[bot]: Jira Issue Verification Checks: Jira Issue OCPBUGS-54776
✔️ This pull request was pre-merge verified.
✔️ All associated pull requests have merged.
✔️ All associated, merged pull requests were pre-merge verified.

Jira Issue OCPBUGS-54776 has been moved to the MODIFIED state and will move to the VERIFIED state when the change is available in an accepted nightly payload. 🕓

Details

In response to this:

What this PR does / why we need it:

When the MCS HTTP polling loop encounters errors (connection failures, unexpected status codes, or body read failures), the log messages now include the MCS process stdout/stderr output to aid debugging.

Changes:

  • Add syncBuffer type for thread-safe capture of MCS process output
  • Use cmd.Start() + deferred cmd.Wait() instead of goroutine for process lifecycle management
  • Fix nil error passed to log.Error() on non-200 status codes
  • Fix response body leak on non-200 status codes
  • Truncate MCS output in log fields to last 8KB to bound log size
  • Add unit tests for syncBuffer including concurrent safety

Which issue(s) this PR fixes:

Fixes https://redhat.atlassian.net/browse/OCPBUGS-54776

Special notes for your reviewer:

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Always review AI generated responses prior to use.
Generated with Claude Code via openshift-developer plugin


Note: This PR was auto-generated by the jira-agent periodic CI job in response to OCPBUGS-54776. See the full report for token usage, cost breakdown, and detailed phase output.

Summary by CodeRabbit

  • Bug Fixes
  • Improved reliability while starting/stopping the background machine-config server during payload retrieval.
  • Enhanced error reporting by capturing recent stdout/stderr and including it in polling/final logs.
  • Updated polling behavior to continue on non-success HTTP responses and retry appropriately, returning fetched payload only on success.
  • Tests
  • Added unit tests for tail truncation and a thread-safe output buffer with concurrent read/write coverage.
  • Added HTTP handler tests for payload fetching, verifying success behavior, retry signaling on failures, inclusion of captured output, and context cancellation handling.

Note: This PR was auto-generated by the jira-agent periodic CI job in response to OCPBUGS-54776. See the full report for token usage, cost breakdown, and detailed phase output.

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.

@bryan-cox
bryan-cox deleted the fix-OCPBUGS-54776 branch July 28, 2026 12:21
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. area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged. verified Signifies that the PR passed pre-merge verification criteria

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants