Skip to content

WIP: OCPBUGS-80958: Kube apiserver node replace#2178

Draft
bhperry wants to merge 2 commits into
openshift:masterfrom
bhperry:kube-apiserver-node-replace
Draft

WIP: OCPBUGS-80958: Kube apiserver node replace#2178
bhperry wants to merge 2 commits into
openshift:masterfrom
bhperry:kube-apiserver-node-replace

Conversation

@bhperry

@bhperry bhperry commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

When master node is deleted and recreated with the same name, ensure the node status is reset by comparing UID.

Related PRs:

openshift/api#2821
openshift/client-go#371
openshift/cluster-kube-apiserver-operator#2199

TODO:

  • Remove go.mod replace once the api and client-go PRs merge

Summary by CodeRabbit

  • Bug Fixes
    • Improved Static Pod Operator node status pruning and replacement logic to be UID-aware, preserving existing entries only when the stored UID matches the current node’s UID and handling unknown/mismatched UIDs correctly.
  • New Features
    • Newly observed node statuses now record the node UID for more reliable tracking.
  • Tests
    • Added UID-focused test coverage, including deterministic fake node UID assignment and assertions for preserve/replace/remove behavior.
  • Chores
    • Updated module resolution for OpenShift components.

@openshift-ci

openshift-ci Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

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 added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 24, 2026
@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 Apr 24, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@bhperry: This pull request references Jira Issue OCPBUGS-80958, 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:

When master node is deleted and recreated with the same name, ensure the node status is reset by comparing UID.

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 Apr 24, 2026

Copy link
Copy Markdown

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

Adds Node UID tracking to static pod node reconciliation, updates helpers and tests for NodeUID propagation, and redirects two OpenShift modules in go.mod.

Changes

Static Pod Node UID Tracking

Layer / File(s) Summary
Module redirects
go.mod
Adds replace directives for github.com/openshift/api and github.com/openshift/client-go to point at bhperry pseudo-version commits.
UID-aware sync logic
pkg/operator/staticpod/controller/node/node_controller.go
Reconciles NodeStatuses by node name and UID, preserves or updates stored UIDs, and emits different events for unknown, replaced, removed, and observed nodes.
UID test support and validation
pkg/operator/staticpod/controller/node/node_controller_test.go, pkg/operator/v1helpers/test_helpers.go
Sets deterministic test node UIDs, preserves NodeUID through apply-configuration merging, and adds coverage for UID creation, preservation, replacement, and removal cases.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 14 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (14 passed)
Check name Status Explanation
Title check ✅ Passed The title is relevant and specific to the main change: handling kube apiserver node replacement by comparing node UIDs.
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 The added and touched test titles are static strings; no dynamic names, timestamps, UUIDs, or generated identifiers appear in titles.
Test Structure And Quality ✅ Passed PASS: The new unit test is table-driven, isolates one behavior per subtest, uses fake clients only, has no waits/cleanup needs, and includes explicit failure messages.
Microshift Test Compatibility ✅ Passed The PR adds only a plain Go unit test and controller/helper changes; no new Ginkgo e2e tests or MicroShift-unsupported APIs/features were introduced.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The PR adds only Go unit tests and controller logic; no new Ginkgo/e2e tests or SNO-assumption code was added.
Topology-Aware Scheduling Compatibility ✅ Passed No scheduling constraints were added; the changes only track master node UIDs in status and tests, with no affinity, topology spread, nodeSelector, or replica logic.
Ote Binary Stdout Contract ✅ Passed No process-level init/main/TestMain/setup code in touched files writes to stdout; new output uses event recorders, not stdout.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo/e2e tests were added; the new unit test and helper changes use fake nodes only and have no IPv4 or external-network assumptions.
No-Weak-Crypto ✅ Passed Touched files only add node UID tracking/test helpers; no MD5/SHA1/DES/RC4/3DES/Blowfish/ECB, custom crypto, or secret/token compares found.
Container-Privileges ✅ Passed PASS: The diff only changes Go code and a generated CRD manifest; none of the touched files contain privileged/hostPID/hostNetwork/hostIPC/allowPrivilegeEscalation or runAsUser: 0 settings.
No-Sensitive-Data-In-Logs ✅ Passed Only node names/UIDs appear in new recorder messages; no passwords, tokens, PII, or customer data were added.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@openshift-ci

openshift-ci Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: bhperry
Once this PR has been reviewed and has the lgtm label, please assign bertinatto 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 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: 1

🧹 Nitpick comments (3)
pkg/operator/staticpod/controller/node/node_controller_test.go (3)

740-740: Remove unused expectedNodes field.

expectedNodes [][2]string is declared in the table struct but never populated or read by any of the four scenarios. Drop it to avoid confusion about what the test actually asserts.

♻️ Proposed change
 	tests := []struct {
 		name               string
 		startNodes         []runtime.Object
 		startNodeStatus    []operatorv1.NodeStatus
-		expectedNodes      [][2]string
 		evaluateNodeStatus func([]operatorv1.NodeStatus) error
 	}{
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/operator/staticpod/controller/node/node_controller_test.go` at line 740,
The test table struct contains an unused field expectedNodes [][2]string; remove
this field from the table type declaration in node_controller_test.go and from
any table literal entries so the scenarios only include fields actually used by
the tests (ensure you update the table struct definition that declares
expectedNodes and all test case literals that reference it, e.g., in the
testCases slice for the Node controller tests).

786-807: Consider asserting the replacement event and JSONPatch, not just the final NodeStatuses.

The "node replaced mismatched uid in status" scenario is the most important new behavior this PR introduces, but the test only checks the final NodeStatuses. Nothing verifies that:

  1. The controller emits MasterNodeReplaced (with old+new UIDs) via the recorder — see node_controller.go line 106.
  2. A jsonPatch is created to remove the stale entry at the expected index before the apply — see node_controller.go line 117.

Both are observable via kubeClient.Fake.Actions() (event create) and fakeStaticPodOperatorClient.GetPatchedOperatorStatus(), as already done in TestNodeControllerDegradedConditionType scenarios 5 and 12. Adding these assertions would lock in the behavior the PR description calls out ("ensure the node status is reset by comparing the node UID"). Non-blocking.

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

In `@pkg/operator/staticpod/controller/node/node_controller_test.go` around lines
786 - 807, The test currently only verifies final NodeStatuses; update the "node
replaced mismatched uid in status" case to also assert that the controller
emitted a MasterNodeReplaced event and that a JSONPatch removing the stale
status entry was created: after running the controller, inspect
kubeClient.Fake.Actions() for an event create matching MasterNodeReplaced
(including old and new UIDs) and call
fakeStaticPodOperatorClient.GetPatchedOperatorStatus() to retrieve the patch and
assert it contains the expected JSONPatch operation that removes the stale index
(as produced by the jsonPatch logic in node_controller.go). Ensure you reference
the test case by name and check the exact event reason/message and the remove op
in the patch, failing the test if either is missing.

808-829: Scenario name is misleading.

The "node removed" scenario actually exercises the case where the previously tracked node name is gone and a different node has appeared (test-node-1test-node-2), i.e. a combined remove-and-add, not a pure removal. Consider renaming to something like "node removed and replaced by different name" to match the existing "single-node-removed" case in TestNewNodeController which tests the pure-removal behavior. Optional.

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

In `@pkg/operator/staticpod/controller/node/node_controller_test.go` around lines
808 - 829, The test case in the table uses a misleading name "node removed" but
actually exercises a remove-and-add scenario (previous NodeStatus test-node-1
replaced by startNodes test-node-2); update the test case's name field in
node_controller_test.go to something clearer (for example "node removed and
replaced by different name") so it accurately reflects the scenario and aligns
with the existing "single-node-removed" naming in TestNewNodeController; locate
the table entry by its name field in the test case slice and change only that
string (no behavior changes).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@go.mod`:
- Around line 5-7: The go.mod contains forked-replace directives (replace
github.com/openshift/api => github.com/bhperry/openshift-api
v0.0.0-20260422220337-85d77b44a5a5 and replace github.com/openshift/client-go =>
github.com/bhperry/openshift-client-go v0.0.0-20260424144941-de7d2d9bafad) that
must be removed from the shared module manifest; delete these two replace lines
and either move the local overrides into a developer-only go.work (or similar
local config) or update the require lines to point to the approved upstream
module revisions before committing. Ensure no other project-wide go.mod contains
the same forked replace entries and run go mod tidy to verify dependency graph
is clean.

---

Nitpick comments:
In `@pkg/operator/staticpod/controller/node/node_controller_test.go`:
- Line 740: The test table struct contains an unused field expectedNodes
[][2]string; remove this field from the table type declaration in
node_controller_test.go and from any table literal entries so the scenarios only
include fields actually used by the tests (ensure you update the table struct
definition that declares expectedNodes and all test case literals that reference
it, e.g., in the testCases slice for the Node controller tests).
- Around line 786-807: The test currently only verifies final NodeStatuses;
update the "node replaced mismatched uid in status" case to also assert that the
controller emitted a MasterNodeReplaced event and that a JSONPatch removing the
stale status entry was created: after running the controller, inspect
kubeClient.Fake.Actions() for an event create matching MasterNodeReplaced
(including old and new UIDs) and call
fakeStaticPodOperatorClient.GetPatchedOperatorStatus() to retrieve the patch and
assert it contains the expected JSONPatch operation that removes the stale index
(as produced by the jsonPatch logic in node_controller.go). Ensure you reference
the test case by name and check the exact event reason/message and the remove op
in the patch, failing the test if either is missing.
- Around line 808-829: The test case in the table uses a misleading name "node
removed" but actually exercises a remove-and-add scenario (previous NodeStatus
test-node-1 replaced by startNodes test-node-2); update the test case's name
field in node_controller_test.go to something clearer (for example "node removed
and replaced by different name") so it accurately reflects the scenario and
aligns with the existing "single-node-removed" naming in TestNewNodeController;
locate the table entry by its name field in the test case slice and change only
that string (no behavior changes).
🪄 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: 0131c169-b1c2-4ef4-ad19-3d6cd3ae6310

📥 Commits

Reviewing files that changed from the base of the PR and between 7acd240 and 69d77b8.

⛔ Files ignored due to path filters (6)
  • go.sum is excluded by !**/*.sum
  • vendor/github.com/openshift/api/operator/v1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operator/v1/zz_generated.swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/client-go/operator/applyconfigurations/internal/internal.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/client-go/operator/applyconfigurations/operator/v1/nodestatus.go is excluded by !**/vendor/**, !vendor/**
  • vendor/modules.txt is excluded by !**/vendor/**, !vendor/**
📒 Files selected for processing (4)
  • go.mod
  • pkg/operator/staticpod/controller/node/node_controller.go
  • pkg/operator/staticpod/controller/node/node_controller_test.go
  • pkg/operator/v1helpers/test_helpers.go

Comment thread go.mod Outdated
@bhperry

bhperry commented Apr 27, 2026

Copy link
Copy Markdown
Contributor Author

/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 Apr 27, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@bhperry: This pull request references Jira Issue OCPBUGS-80958, 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:

/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.

@bhperry

bhperry commented Apr 29, 2026

Copy link
Copy Markdown
Contributor Author

/retest

@bhperry bhperry force-pushed the kube-apiserver-node-replace branch from 69d77b8 to 9f5d47f Compare April 29, 2026 17:31

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

Caution

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

⚠️ Outside diff range comments (1)
pkg/operator/staticpod/controller/node/node_controller.go (1)

110-118: ⚠️ Potential issue | 🟠 Major

Tighten the remove patch precondition to the UID-aware match.

The JSONPatch delete still only guards on nodeName. If the status list has already been rewritten for the same name, this can delete the wrong entry. The removal path should validate the UID as well, or re-fetch before patching.

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

In `@pkg/operator/staticpod/controller/node/node_controller.go` around lines 110 -
118, The remove operation currently only tests /status/nodeStatuses/%d/nodeName
via jsonPatch.WithRemove and jsonpatch.NewTestCondition using
nodeState.NodeName, which can remove the wrong entry if names were reused;
update the precondition to also validate the node UID (e.g., add a test for
/status/nodeStatuses/%d/uid using nodeState.UID or otherwise combine name+uid
checks) before calling jsonPatch.WithRemove (referencing jsonPatch.WithRemove,
jsonpatch.NewTestCondition, nodeState.NodeName, nodeState.UID, and
removedNodeStatusesCounter), or alternatively re-fetch the status list and
re-compute removeAtIndex immediately prior to patching to ensure the exact entry
is removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/operator/staticpod/controller/node/node_controller.go`:
- Around line 98-103: The current condition treats an empty nodeState.NodeUID as
a match which preserves legacy statuses and can keep stale state after a node is
deleted/recreated; update the matching logic so you only consider it a match
when nodeState.NodeUID exactly equals the currentUID (i.e., remove the "||
nodeState.NodeUID == \"\"" branch) in the block building newTargetNodeState (the
code that calls
applyoperatorv1.NodeStatus().WithNodeName(...).WithNodeUID(currentUID) and
appends to newTargetNodeStates), and apply the same strict-equality change to
the analogous logic around lines 126-136 so legacy empty UIDs are not treated as
matches and will be reset instead of preserved.

---

Outside diff comments:
In `@pkg/operator/staticpod/controller/node/node_controller.go`:
- Around line 110-118: The remove operation currently only tests
/status/nodeStatuses/%d/nodeName via jsonPatch.WithRemove and
jsonpatch.NewTestCondition using nodeState.NodeName, which can remove the wrong
entry if names were reused; update the precondition to also validate the node
UID (e.g., add a test for /status/nodeStatuses/%d/uid using nodeState.UID or
otherwise combine name+uid checks) before calling jsonPatch.WithRemove
(referencing jsonPatch.WithRemove, jsonpatch.NewTestCondition,
nodeState.NodeName, nodeState.UID, and removedNodeStatusesCounter), or
alternatively re-fetch the status list and re-compute removeAtIndex immediately
prior to patching to ensure the exact entry is removed.
🪄 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: e0840c87-5aa4-4d00-bb60-525c3ca309aa

📥 Commits

Reviewing files that changed from the base of the PR and between 69d77b8 and 9f5d47f.

⛔ Files ignored due to path filters (6)
  • go.sum is excluded by !**/*.sum
  • vendor/github.com/openshift/api/operator/v1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operator/v1/zz_generated.swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/client-go/operator/applyconfigurations/internal/internal.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/client-go/operator/applyconfigurations/operator/v1/nodestatus.go is excluded by !**/vendor/**, !vendor/**
  • vendor/modules.txt is excluded by !**/vendor/**, !vendor/**
📒 Files selected for processing (4)
  • go.mod
  • pkg/operator/staticpod/controller/node/node_controller.go
  • pkg/operator/staticpod/controller/node/node_controller_test.go
  • pkg/operator/v1helpers/test_helpers.go
✅ Files skipped from review due to trivial changes (1)
  • go.mod
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/operator/v1helpers/test_helpers.go
  • pkg/operator/staticpod/controller/node/node_controller_test.go

Comment thread pkg/operator/staticpod/controller/node/node_controller.go
@bhperry bhperry force-pushed the kube-apiserver-node-replace branch from 9f5d47f to 849d9a8 Compare April 30, 2026 17:20
@bhperry bhperry force-pushed the kube-apiserver-node-replace branch from 849d9a8 to 21ab80d Compare June 16, 2026 15:20
@bhperry bhperry force-pushed the kube-apiserver-node-replace branch 8 times, most recently from e522e0d to a3d906c Compare July 2, 2026 14:18
Comment thread pkg/operator/staticpod/controller/node/node_controller.go Outdated
Comment thread pkg/operator/staticpod/controller/node/node_controller.go
Comment thread pkg/operator/staticpod/controller/node/node_controller.go Outdated
Comment thread pkg/operator/staticpod/controllers.go Outdated
Comment thread pkg/operator/staticpod/controller/installer/installer_controller.go Outdated
@bhperry bhperry force-pushed the kube-apiserver-node-replace branch 3 times, most recently from aaca0d3 to e96b42c Compare July 6, 2026 17:13
@openshift-ci

openshift-ci Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@bhperry: 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/unit e96b42c link true /test unit
ci/prow/verify-deps e96b42c link true /test verify-deps

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.

@bhperry bhperry force-pushed the kube-apiserver-node-replace branch from e96b42c to 82ef33f Compare July 6, 2026 18:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. 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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants