fix: include node events with null involvedObject.uid in describe output

kubectl describe node omits events where involvedObject.uid is
unset/null. This happens with kubelet-emitted node status events
on some clusters (e.g. EKS).

NodeDescriber.Describe() searched for events using two UIDs:
1. The node's real UID (for controller events)
2. The node name as UID (for kubelet events using name workaround)

Neither matched events where involvedObject.uid is null/empty.

Add a third search with empty UID (matching by kind+name only)
and deduplicate the merged results to avoid showing duplicates.

Ref: kubernetes/kubectl/issues/1838

Signed-off-by: vigneshakaviki <kumarvignesh295@gmail.com>
This commit is contained in:
vigneshakaviki 2026-04-13 09:59:10 -07:00
parent 8144b746a4
commit e976d59fe7
2 changed files with 96 additions and 2 deletions

View file

@ -3467,14 +3467,35 @@ func (d *NodeDescriber) Describe(namespace, name string, describerSettings Descr
// there are two UIDs for host events:
// controller use node.uid
// kubelet use node.name
// some kubelet events have involvedObject.uid unset (null)
// TODO: Uniform use of UID
events, _ = searchEvents(d.CoreV1(), ref, describerSettings.ChunkSize)
ref.UID = types.UID(ref.Name)
eventsInvName, _ := searchEvents(d.CoreV1(), ref, describerSettings.ChunkSize)
// Merge the results of two queries
events.Items = append(events.Items, eventsInvName.Items...)
// Search for events with no UID set (involvedObject.uid is null),
// which can happen with kubelet-emitted node events.
ref.UID = ""
eventsNoUID, _ := searchEvents(d.CoreV1(), ref, describerSettings.ChunkSize)
// Merge and deduplicate the results of the three queries
seen := make(map[types.UID]bool, len(events.Items))
for _, e := range events.Items {
seen[e.UID] = true
}
for _, e := range eventsInvName.Items {
if !seen[e.UID] {
events.Items = append(events.Items, e)
seen[e.UID] = true
}
}
for _, e := range eventsNoUID.Items {
if !seen[e.UID] {
events.Items = append(events.Items, e)
seen[e.UID] = true
}
}
}
}

View file

@ -6447,6 +6447,79 @@ func TestDescribeNode(t *testing.T) {
}
}
func TestDescribeNodeEventsWithNullUID(t *testing.T) {
node := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "test-node",
UID: "real-node-uid",
},
}
fake := fake.NewClientset(
node,
&coordinationv1.Lease{
ObjectMeta: metav1.ObjectMeta{
Name: "test-node",
Namespace: corev1.NamespaceNodeLease,
},
},
&corev1.EventList{
Items: []corev1.Event{
{
ObjectMeta: metav1.ObjectMeta{
Name: "event-with-uid",
Namespace: "default",
UID: "evt-1",
},
InvolvedObject: corev1.ObjectReference{
Kind: "Node",
Name: "test-node",
UID: "test-node",
},
Message: "Node test-node status is now: NodeHasSufficientMemory",
FirstTimestamp: metav1.NewTime(time.Date(2024, time.January, 15, 0, 0, 0, 0, time.UTC)),
LastTimestamp: metav1.NewTime(time.Date(2024, time.January, 15, 0, 0, 0, 0, time.UTC)),
Count: 1,
Type: corev1.EventTypeNormal,
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "event-null-uid",
Namespace: "default",
UID: "evt-2",
},
InvolvedObject: corev1.ObjectReference{
Kind: "Node",
Name: "test-node",
// UID intentionally omitted (empty/null) —
// kubelet-emitted events on some clusters
},
Message: "Node test-node status is now: NodeHasInsufficientMemory",
FirstTimestamp: metav1.NewTime(time.Date(2024, time.January, 15, 0, 0, 0, 0, time.UTC)),
LastTimestamp: metav1.NewTime(time.Date(2024, time.January, 15, 0, 0, 0, 0, time.UTC)),
Count: 1,
Type: corev1.EventTypeWarning,
},
},
},
)
c := &describeClient{T: t, Namespace: "", Interface: fake}
d := NodeDescriber{c}
out, err := d.Describe("", "test-node", DescriberSettings{ShowEvents: true})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Event with UID should always appear
if !strings.Contains(out, "NodeHasSufficientMemory") {
t.Errorf("expected event with UID to appear in output, got:\n%s", out)
}
// Event with null/empty UID should also appear
if !strings.Contains(out, "NodeHasInsufficientMemory") {
t.Errorf("expected event with null UID to appear in output, got:\n%s", out)
}
}
func TestDescribeNodeWithSidecar(t *testing.T) {
holderIdentity := "holder"
nodeCapacity := mergeResourceLists(