Skip to content

Commit 9f5d47f

Browse files
committed
Reset node status when UID changes
1 parent d45f1f2 commit 9f5d47f

10 files changed

Lines changed: 199 additions & 18 deletions

File tree

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ module github.com/openshift/library-go
22

33
go 1.25.0
44

5+
replace github.com/openshift/api => github.com/bhperry/openshift-api v0.0.0-20260422220337-85d77b44a5a5
6+
7+
replace github.com/openshift/client-go => github.com/bhperry/openshift-client-go v0.0.0-20260424144941-de7d2d9bafad
8+
59
require (
610
github.com/RangelReale/osincli v0.0.0-20160924135400-fababb0555f2
711
github.com/blang/semver/v4 v4.0.0

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
2020
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
2121
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
2222
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
23+
github.com/bhperry/openshift-api v0.0.0-20260422220337-85d77b44a5a5 h1:TVkomgoh0I2HMeVZLgW8XmLrR8BFp6QqDZ0j+HOCPsU=
24+
github.com/bhperry/openshift-api v0.0.0-20260422220337-85d77b44a5a5/go.mod h1:pyVjK0nZ4sRs4fuQVQ4rubsJdahI1PB94LnQ8sGdvxo=
25+
github.com/bhperry/openshift-client-go v0.0.0-20260424144941-de7d2d9bafad h1:k+ApEbVw3Dib+EYgl9A2ayfNdudSIVs/mKHUs9hVKVw=
26+
github.com/bhperry/openshift-client-go v0.0.0-20260424144941-de7d2d9bafad/go.mod h1:KUQZnn0WQjSuJs28f4nlluctKcyPAYneC1PwWCr6wRE=
2327
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
2428
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
2529
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
@@ -225,12 +229,8 @@ github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrB
225229
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
226230
github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84=
227231
github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s=
228-
github.com/openshift/api v0.0.0-20260429122012-1180c0f5c3e9 h1:lZw6pYY7El1giNk1lYvkp6hLungiqwIOqLlH+Hm7w9g=
229-
github.com/openshift/api v0.0.0-20260429122012-1180c0f5c3e9/go.mod h1:pyVjK0nZ4sRs4fuQVQ4rubsJdahI1PB94LnQ8sGdvxo=
230232
github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee h1:+Sp5GGnjHDhT/a/nQ1xdp43UscBMr7G5wxsYotyhzJ4=
231233
github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE=
232-
github.com/openshift/client-go v0.0.0-20260429123927-c81f86abfa6a h1:4GR6seHvlfv0rADe+LCQx63FqSExx6gaSo8uNiyWq+c=
233-
github.com/openshift/client-go v0.0.0-20260429123927-c81f86abfa6a/go.mod h1:Lm7X7aYbAaKhGsNhgYaowP7hiLKwfN/w0r+Q6VlQoI8=
234234
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
235235
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
236236
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=

pkg/operator/staticpod/controller/node/node_controller.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
coreapiv1 "k8s.io/api/core/v1"
99
"k8s.io/apimachinery/pkg/labels"
10+
"k8s.io/apimachinery/pkg/types"
1011
"k8s.io/client-go/informers"
1112
corelisterv1 "k8s.io/client-go/listers/core/v1"
1213

@@ -84,22 +85,28 @@ func (c *NodeController) sync(ctx context.Context, syncCtx factory.SyncContext)
8485
nodes = append(nodes, extraNodes...)
8586
}
8687

88+
nodeUIDs := map[string]types.UID{}
89+
for _, node := range nodes {
90+
nodeUIDs[node.Name] = node.UID
91+
}
92+
8793
jsonPatch := jsonpatch.New()
8894
var removedNodeStatusesCounter int
8995
newTargetNodeStates := []*applyoperatorv1.NodeStatusApplyConfiguration{}
9096
// remove entries for missing nodes
9197
for i, nodeState := range originalOperatorStatus.NodeStatuses {
92-
found := false
93-
for _, node := range nodes {
94-
if nodeState.NodeName == node.Name {
95-
found = true
96-
}
97-
}
98-
if found {
99-
newTargetNodeState := applyoperatorv1.NodeStatus().WithNodeName(originalOperatorStatus.NodeStatuses[i].NodeName)
98+
currentUID, found := nodeUIDs[nodeState.NodeName]
99+
if found && (nodeState.NodeUID == currentUID || nodeState.NodeUID == "") {
100+
newTargetNodeState := applyoperatorv1.NodeStatus().
101+
WithNodeName(nodeState.NodeName).
102+
WithNodeUID(currentUID)
100103
newTargetNodeStates = append(newTargetNodeStates, newTargetNodeState)
101104
} else {
102-
syncCtx.Recorder().Warningf("MasterNodeRemoved", "Observed removal of master node %s", nodeState.NodeName)
105+
if found {
106+
syncCtx.Recorder().Warningf("MasterNodeReplaced", "Observed replacement of master node %s (old UID: %s, new UID: %s)", nodeState.NodeName, nodeState.NodeUID, currentUID)
107+
} else {
108+
syncCtx.Recorder().Warningf("MasterNodeRemoved", "Observed removal of master node %s", nodeState.NodeName)
109+
}
103110
// each delete operation is applied to the object,
104111
// which modifies the array. Thus, we need to
105112
// adjust the indices to find the correct node to remove.
@@ -116,16 +123,17 @@ func (c *NodeController) sync(ctx context.Context, syncCtx factory.SyncContext)
116123
for _, node := range nodes {
117124
found := false
118125
for _, nodeState := range originalOperatorStatus.NodeStatuses {
119-
if nodeState.NodeName == node.Name {
126+
if nodeState.NodeName == node.Name && (nodeState.NodeUID == node.UID || nodeState.NodeUID == "") {
120127
found = true
128+
break
121129
}
122130
}
123131
if found {
124132
continue
125133
}
126134

127-
syncCtx.Recorder().Eventf("MasterNodeObserved", "Observed new master node %s", node.Name)
128-
newTargetNodeState := applyoperatorv1.NodeStatus().WithNodeName(node.Name)
135+
syncCtx.Recorder().Eventf("MasterNodeObserved", "Observed new master node %s (UID: %s)", node.Name, node.UID)
136+
newTargetNodeState := applyoperatorv1.NodeStatus().WithNodeName(node.Name).WithNodeUID(node.UID)
129137
newTargetNodeStates = append(newTargetNodeStates, newTargetNodeState)
130138
}
131139

pkg/operator/staticpod/controller/node/node_controller_test.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"k8s.io/apimachinery/pkg/labels"
1414
"k8s.io/apimachinery/pkg/runtime"
1515
"k8s.io/apimachinery/pkg/selection"
16+
"k8s.io/apimachinery/pkg/types"
1617
"k8s.io/client-go/kubernetes/fake"
1718
clientgotesting "k8s.io/client-go/testing"
1819
clocktesting "k8s.io/utils/clock/testing"
@@ -28,6 +29,7 @@ import (
2829
func fakeMasterNode(name string) *corev1.Node {
2930
n := &corev1.Node{}
3031
n.Name = name
32+
n.UID = types.UID(name + "-uid")
3133
n.Labels = map[string]string{
3234
"node-role.kubernetes.io/master": "",
3335
}
@@ -729,3 +731,143 @@ func TestNewNodeController(t *testing.T) {
729731

730732
}
731733
}
734+
735+
func TestNodeControllerTrackNodeUIDs(t *testing.T) {
736+
tests := []struct {
737+
name string
738+
startNodes []runtime.Object
739+
startNodeStatus []operatorv1.NodeStatus
740+
expectedNodes [][2]string
741+
evaluateNodeStatus func([]operatorv1.NodeStatus) error
742+
}{
743+
{
744+
name: "node exists missing uid in status",
745+
startNodes: []runtime.Object{fakeMasterNode("test-node-1")},
746+
startNodeStatus: []operatorv1.NodeStatus{
747+
{
748+
NodeName: "test-node-1",
749+
},
750+
},
751+
evaluateNodeStatus: func(s []operatorv1.NodeStatus) error {
752+
if len(s) != 1 {
753+
return fmt.Errorf("expected 1 node status, got %d", len(s))
754+
}
755+
if s[0].NodeName != "test-node-1" {
756+
return fmt.Errorf("expected 'test-node-1' as node name, got %q", s[0].NodeName)
757+
}
758+
if s[0].NodeUID != "test-node-1-uid" {
759+
return fmt.Errorf("expected 'test-node-1-uid' as node UID, got %q", s[0].NodeUID)
760+
}
761+
return nil
762+
},
763+
},
764+
{
765+
name: "node exists matching uid in status",
766+
startNodes: []runtime.Object{fakeMasterNode("test-node-1")},
767+
startNodeStatus: []operatorv1.NodeStatus{
768+
{
769+
NodeName: "test-node-1",
770+
NodeUID: "test-node-1-uid",
771+
},
772+
},
773+
evaluateNodeStatus: func(s []operatorv1.NodeStatus) error {
774+
if len(s) != 1 {
775+
return fmt.Errorf("expected 1 node status, got %d", len(s))
776+
}
777+
if s[0].NodeName != "test-node-1" {
778+
return fmt.Errorf("expected 'test-node-1' as node name, got %q", s[0].NodeName)
779+
}
780+
if s[0].NodeUID != "test-node-1-uid" {
781+
return fmt.Errorf("expected 'test-node-1-uid' as node UID, got %q", s[0].NodeUID)
782+
}
783+
return nil
784+
},
785+
},
786+
{
787+
name: "node replaced mismatched uid in status",
788+
startNodes: []runtime.Object{fakeMasterNode("test-node-1")},
789+
startNodeStatus: []operatorv1.NodeStatus{
790+
{
791+
NodeName: "test-node-1",
792+
NodeUID: "test-node-1-uid-init",
793+
},
794+
},
795+
evaluateNodeStatus: func(s []operatorv1.NodeStatus) error {
796+
if len(s) != 1 {
797+
return fmt.Errorf("expected 1 node status, got %d", len(s))
798+
}
799+
if s[0].NodeName != "test-node-1" {
800+
return fmt.Errorf("expected 'test-node-1' as node name, got %q", s[0].NodeName)
801+
}
802+
if s[0].NodeUID != "test-node-1-uid" {
803+
return fmt.Errorf("expected 'test-node-1-uid' as node UID, got %q", s[0].NodeUID)
804+
}
805+
return nil
806+
},
807+
},
808+
{
809+
name: "node removed",
810+
startNodes: []runtime.Object{fakeMasterNode("test-node-2")},
811+
startNodeStatus: []operatorv1.NodeStatus{
812+
{
813+
NodeName: "test-node-1",
814+
NodeUID: "test-node-1-uid",
815+
},
816+
},
817+
evaluateNodeStatus: func(s []operatorv1.NodeStatus) error {
818+
if len(s) != 1 {
819+
return fmt.Errorf("expected 1 node status, got %d", len(s))
820+
}
821+
if s[0].NodeName != "test-node-2" {
822+
return fmt.Errorf("expected 'test-node-2' as node name, got %q", s[0].NodeName)
823+
}
824+
if s[0].NodeUID != "test-node-2-uid" {
825+
return fmt.Errorf("expected 'test-node-2-uid' as node UID, got %q", s[0].NodeUID)
826+
}
827+
return nil
828+
},
829+
},
830+
}
831+
832+
for _, test := range tests {
833+
t.Run(test.name, func(t *testing.T) {
834+
kubeClient := fake.NewSimpleClientset(test.startNodes...)
835+
fakeLister := v1helpers.NewFakeNodeLister(kubeClient)
836+
fakeStaticPodOperatorClient := v1helpers.NewFakeStaticPodOperatorClient(
837+
&operatorv1.StaticPodOperatorSpec{
838+
OperatorSpec: operatorv1.OperatorSpec{
839+
ManagementState: operatorv1.Managed,
840+
},
841+
},
842+
&operatorv1.StaticPodOperatorStatus{
843+
OperatorStatus: operatorv1.OperatorStatus{
844+
LatestAvailableRevision: 1,
845+
},
846+
NodeStatuses: test.startNodeStatus,
847+
},
848+
nil,
849+
nil,
850+
)
851+
852+
eventRecorder := events.NewRecorder(kubeClient.CoreV1().Events("test"), "test-operator", &corev1.ObjectReference{}, clocktesting.NewFakePassiveClock(time.Now()))
853+
854+
c := &NodeController{
855+
operatorClient: fakeStaticPodOperatorClient,
856+
nodeLister: fakeLister,
857+
masterNodesSelector: masterNodesSelector(t),
858+
}
859+
860+
// override the lister so we don't have to run the informer to list nodes
861+
c.nodeLister = fakeLister
862+
if err := c.sync(context.TODO(), factory.NewSyncContext("NodeController", eventRecorder)); err != nil {
863+
t.Fatal(err)
864+
}
865+
866+
_, status, _, _ := fakeStaticPodOperatorClient.GetStaticPodOperatorState()
867+
if err := test.evaluateNodeStatus(status.NodeStatuses); err != nil {
868+
t.Errorf("%s: failed to evaluate node status: %v", test.name, err)
869+
}
870+
})
871+
872+
}
873+
}

pkg/operator/v1helpers/test_helpers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@ func mergeStaticPodOperatorStatusApplyConfiguration(currentOperatorStatus *v1.Op
470470
for _, nodeStatus := range applyConfiguration.NodeStatuses {
471471
newNodeStatus := operatorv1.NodeStatus{
472472
NodeName: ptr.Deref(nodeStatus.NodeName, ""),
473+
NodeUID: ptr.Deref(nodeStatus.NodeUID, ""),
473474
CurrentRevision: ptr.Deref(nodeStatus.CurrentRevision, 0),
474475
TargetRevision: ptr.Deref(nodeStatus.TargetRevision, 0),
475476
LastFailedRevision: ptr.Deref(nodeStatus.LastFailedRevision, 0),

vendor/github.com/openshift/api/operator/v1/types.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/openshift/api/operator/v1/zz_generated.swagger_doc_generated.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/openshift/client-go/operator/applyconfigurations/internal/internal.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/openshift/client-go/operator/applyconfigurations/operator/v1/nodestatus.go

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/modules.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ github.com/opencontainers/image-spec/specs-go/v1
286286
## explicit; go 1.19
287287
github.com/opencontainers/selinux/go-selinux
288288
github.com/opencontainers/selinux/pkg/pwalkdir
289-
# github.com/openshift/api v0.0.0-20260429122012-1180c0f5c3e9
289+
# github.com/openshift/api v0.0.0-20260420151639-34e60874783e => github.com/bhperry/openshift-api v0.0.0-20260422220337-85d77b44a5a5
290290
## explicit; go 1.25.0
291291
github.com/openshift/api
292292
github.com/openshift/api/annotations
@@ -376,7 +376,7 @@ github.com/openshift/build-machinery-go/make/targets/golang
376376
github.com/openshift/build-machinery-go/make/targets/openshift
377377
github.com/openshift/build-machinery-go/make/targets/openshift/operator
378378
github.com/openshift/build-machinery-go/scripts
379-
# github.com/openshift/client-go v0.0.0-20260429123927-c81f86abfa6a
379+
# github.com/openshift/client-go v0.0.0-20260424153654-c280f7942f94 => github.com/bhperry/openshift-client-go v0.0.0-20260424144941-de7d2d9bafad
380380
## explicit; go 1.25.0
381381
github.com/openshift/client-go/apps/applyconfigurations/apps/v1
382382
github.com/openshift/client-go/apps/applyconfigurations/internal
@@ -1675,3 +1675,5 @@ sigs.k8s.io/structured-merge-diff/v6/value
16751675
# sigs.k8s.io/yaml v1.6.0
16761676
## explicit; go 1.22
16771677
sigs.k8s.io/yaml
1678+
# github.com/openshift/api => github.com/bhperry/openshift-api v0.0.0-20260422220337-85d77b44a5a5
1679+
# github.com/openshift/client-go => github.com/bhperry/openshift-client-go v0.0.0-20260424144941-de7d2d9bafad

0 commit comments

Comments
 (0)