client-go/tools/events/event_recorder_test.go
Antonio Ojea 023460f5a8 events: ensure the name is valid
The event Object is created from the referenced Object name, however,
there is no guarantee that the name from the referenced Object will be a
valid Event Name.

For those Objects with name not valid for an Event, generate a new valid
name using an UUID.

Kubernetes-commit: ee36b817df06f84ce1a48ef4d65ed559c3775404
2025-02-20 09:13:56 +00:00

241 lines
6.7 KiB
Go

/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package events
import (
"fmt"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
v1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
eventsv1 "k8s.io/api/events/v1"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/watch"
testclocks "k8s.io/utils/clock/testing"
)
func TestEventf(t *testing.T) {
// use a fixed time for generated names that depend on the unix timestamp
fakeClock := testclocks.NewFakeClock(time.Date(2023, time.January, 1, 12, 0, 0, 0, time.UTC))
testCases := []struct {
desc string
regarding runtime.Object
related runtime.Object
eventtype string
reason string
action string
note string
args []interface{}
expectedEvent *eventsv1.Event
}{
{
desc: "normal event",
regarding: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
Namespace: "ns1",
UID: "12345",
},
},
eventtype: "Normal",
reason: "Started",
action: "starting",
note: "Pod started",
expectedEvent: &eventsv1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("pod1.%x", fakeClock.Now().UnixNano()),
Namespace: "ns1",
},
Regarding: v1.ObjectReference{
Kind: "Pod",
Name: "pod1",
Namespace: "ns1",
UID: "12345",
APIVersion: "v1",
},
Type: "Normal",
Reason: "Started",
Action: "starting",
Note: "Pod started",
ReportingController: "c1",
ReportingInstance: "i1",
},
},
{
desc: "event with related object and format args",
regarding: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
Namespace: "ns1",
UID: "12345",
},
},
related: &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
UID: "67890",
},
},
eventtype: "Warning",
reason: "FailedScheduling",
action: "scheduling",
note: "Pod failed to schedule on %s: %s",
args: []interface{}{"node1", "not enough resources"},
expectedEvent: &eventsv1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("pod1.%x", fakeClock.Now().UnixNano()),
Namespace: "ns1",
},
Regarding: v1.ObjectReference{
Kind: "Pod",
Name: "pod1",
Namespace: "ns1",
UID: "12345",
APIVersion: "v1",
},
Related: &v1.ObjectReference{
Kind: "Node",
Name: "node1",
UID: "67890",
APIVersion: "v1",
},
Type: "Warning",
Reason: "FailedScheduling",
Action: "scheduling",
Note: "Pod failed to schedule on node1: not enough resources",
ReportingController: "c1",
ReportingInstance: "i1",
},
}, {
desc: "event with invalid Event name",
regarding: &networkingv1.IPAddress{
ObjectMeta: metav1.ObjectMeta{
Name: "2001:db8::123",
UID: "12345",
},
},
eventtype: "Warning",
reason: "IPAddressNotAllocated",
action: "IPAddressAllocation",
note: "Service default/test appears to have leaked",
expectedEvent: &eventsv1.Event{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
},
Regarding: v1.ObjectReference{
Kind: "IPAddress",
Name: "2001:db8::123",
UID: "12345",
APIVersion: "networking.k8s.io/v1",
},
Type: "Warning",
Reason: "IPAddressNotAllocated",
Action: "IPAddressAllocation",
Note: "Service default/test appears to have leaked",
ReportingController: "c1",
ReportingInstance: "i1",
},
}, {
desc: "large event name",
regarding: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: strings.Repeat("x", utilvalidation.DNS1123SubdomainMaxLength*4),
Namespace: "ns1",
UID: "12345",
},
},
eventtype: "Normal",
reason: "Started",
action: "starting",
note: "Pod started",
expectedEvent: &eventsv1.Event{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns1",
},
Regarding: v1.ObjectReference{
Kind: "Pod",
Name: strings.Repeat("x", utilvalidation.DNS1123SubdomainMaxLength*4),
Namespace: "ns1",
UID: "12345",
APIVersion: "v1",
},
Type: "Normal",
Reason: "Started",
Action: "starting",
Note: "Pod started",
ReportingController: "c1",
ReportingInstance: "i1",
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
broadcaster := watch.NewBroadcaster(1, watch.WaitIfChannelFull)
recorder := &recorderImpl{
scheme: runtime.NewScheme(),
reportingController: "c1",
reportingInstance: "i1",
Broadcaster: broadcaster,
clock: fakeClock,
}
if err := v1.AddToScheme(recorder.scheme); err != nil {
t.Fatal(err)
}
if err := networkingv1.AddToScheme(recorder.scheme); err != nil {
t.Fatal(err)
}
ch, err := broadcaster.Watch()
if err != nil {
t.Fatal(err)
}
recorder.Eventf(tc.regarding, tc.related, tc.eventtype, tc.reason, tc.action, tc.note, tc.args...)
select {
case event := <-ch.ResultChan():
actualEvent := event.Object.(*eventsv1.Event)
if errs := apimachineryvalidation.NameIsDNSSubdomain(actualEvent.Name, false); len(errs) > 0 {
t.Errorf("Event Name = %s; not a valid name: %v", actualEvent.Name, errs)
} // Overwrite fields that are not relevant for comparison
tc.expectedEvent.EventTime = actualEvent.EventTime
// invalid event names generate random names
if tc.expectedEvent.Name == "" {
actualEvent.Name = ""
}
if diff := cmp.Diff(tc.expectedEvent, actualEvent); diff != "" {
t.Errorf("Unexpected event diff (-want, +got):\n%s", diff)
}
case <-time.After(time.Second):
t.Errorf("Timeout waiting for event")
}
})
}
}