mirror of
https://github.com/rancher/k3k.git
synced 2026-08-23 22:36:17 +00:00
Load the pod explicitly rather than the unstructured from the InvolvedObject reference.
367 lines
11 KiB
Go
367 lines
11 KiB
Go
package syncer
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"github.com/rancher/k3k/k3k-kubelet/translate"
|
|
)
|
|
|
|
func TestEventSyncerReconcile(t *testing.T) {
|
|
scheme := runtime.NewScheme()
|
|
require.NoError(t, corev1.AddToScheme(scheme))
|
|
|
|
tests := map[string]struct {
|
|
// the event being reconciled
|
|
receivedEvent *corev1.Event
|
|
// virtualObj is stored in the virtual fake client, simulating the resource
|
|
// in the virtual cluster that the event's InvolvedObject maps to.
|
|
virtualObj *unstructured.Unstructured
|
|
wantEvent *capturedEvent
|
|
}{
|
|
"normal event is re-emitted via virtEventRecorder": {
|
|
receivedEvent: &corev1.Event{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "nginx-test-mycluster-6e67696e782b746573742b6d79636c7573746572.18b194827e6b49da",
|
|
Namespace: "k3k-mycluster",
|
|
},
|
|
InvolvedObject: corev1.ObjectReference{
|
|
APIVersion: "v1",
|
|
Kind: "Pod",
|
|
Name: "nginx-test-mycluster-6e67696e782b746573742b6d79636c7573746572",
|
|
Namespace: "k3k-mycluster",
|
|
},
|
|
Type: corev1.EventTypeNormal,
|
|
Reason: "Started",
|
|
Message: "Container started successfully",
|
|
},
|
|
virtualObj: &unstructured.Unstructured{Object: map[string]any{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": map[string]any{
|
|
"name": "nginx",
|
|
"namespace": "test",
|
|
"uid": "pod-virtual-uid",
|
|
"resourceVersion": "1",
|
|
},
|
|
}},
|
|
wantEvent: &capturedEvent{
|
|
Object: &unstructured.Unstructured{Object: map[string]any{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": map[string]any{
|
|
"name": "nginx",
|
|
"namespace": "test",
|
|
"uid": "pod-virtual-uid",
|
|
},
|
|
}},
|
|
EventType: corev1.EventTypeNormal,
|
|
Reason: "Started",
|
|
Message: "Container started successfully",
|
|
},
|
|
},
|
|
"warning event is re-emitted via virtEventRecorder": {
|
|
receivedEvent: &corev1.Event{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-event-warning",
|
|
Namespace: "host-ns",
|
|
},
|
|
InvolvedObject: corev1.ObjectReference{
|
|
APIVersion: "v1",
|
|
Kind: "Pod",
|
|
Name: "nginx-test-mycluster-6e67696e782b746573742b6d79636c7573746572",
|
|
Namespace: "k3k-mycluster",
|
|
},
|
|
Type: corev1.EventTypeWarning,
|
|
Reason: "BackOff",
|
|
Message: "Back-off restarting failed container",
|
|
},
|
|
virtualObj: &unstructured.Unstructured{Object: map[string]any{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": map[string]any{
|
|
"name": "nginx",
|
|
"namespace": "test",
|
|
"uid": "pod-virtual-uid",
|
|
"resourceVersion": "1",
|
|
},
|
|
}},
|
|
wantEvent: &capturedEvent{
|
|
Object: &unstructured.Unstructured{Object: map[string]any{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": map[string]any{
|
|
"name": "nginx",
|
|
"namespace": "test",
|
|
"uid": "pod-virtual-uid",
|
|
},
|
|
}},
|
|
EventType: corev1.EventTypeWarning,
|
|
Reason: "BackOff",
|
|
Message: "Back-off restarting failed container",
|
|
},
|
|
},
|
|
"event with empty type and reason is still forwarded": {
|
|
receivedEvent: &corev1.Event{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-event-empty",
|
|
Namespace: "host-ns",
|
|
},
|
|
InvolvedObject: corev1.ObjectReference{
|
|
APIVersion: "v1",
|
|
Kind: "Pod",
|
|
Name: "nginx-test-mycluster-6e67696e782b746573742b6d79636c7573746572",
|
|
Namespace: "k3k-mycluster",
|
|
},
|
|
Type: "",
|
|
Reason: "",
|
|
Message: "some message",
|
|
},
|
|
virtualObj: &unstructured.Unstructured{Object: map[string]any{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": map[string]any{
|
|
"name": "nginx",
|
|
"namespace": "test",
|
|
"uid": "pod-virtual-uid",
|
|
"resourceVersion": "1",
|
|
},
|
|
}},
|
|
wantEvent: &capturedEvent{
|
|
Object: &unstructured.Unstructured{Object: map[string]any{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": map[string]any{
|
|
"name": "nginx",
|
|
"namespace": "test",
|
|
"uid": "pod-virtual-uid",
|
|
},
|
|
}},
|
|
EventType: "",
|
|
Reason: "",
|
|
Message: "some message",
|
|
},
|
|
},
|
|
"message containing host namespace/name is translated to virtual name/namespace": {
|
|
receivedEvent: &corev1.Event{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "nginx-test-mycluster-6e67696e782b746573742b6d79636c7573746572.18b194827e6b49da",
|
|
Namespace: "k3k-mycluster",
|
|
},
|
|
InvolvedObject: corev1.ObjectReference{
|
|
APIVersion: "v1",
|
|
Kind: "Pod",
|
|
Name: "nginx-test-mycluster-6e67696e782b746573742b6d79636c7573746572",
|
|
Namespace: "k3k-mycluster",
|
|
},
|
|
Type: corev1.EventTypeNormal,
|
|
Reason: "Scheduled",
|
|
Message: "Successfully assigned k3k-mycluster/nginx-test-mycluster-6e67696e782b746573742b6d79636c7573746572 to localhost.localdomain",
|
|
},
|
|
virtualObj: &unstructured.Unstructured{Object: map[string]any{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": map[string]any{
|
|
"name": "nginx",
|
|
"namespace": "test",
|
|
"uid": "pod-virtual-uid",
|
|
"resourceVersion": "1",
|
|
},
|
|
}},
|
|
wantEvent: &capturedEvent{
|
|
Object: &unstructured.Unstructured{Object: map[string]any{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": map[string]any{
|
|
"name": "nginx",
|
|
"namespace": "test",
|
|
"uid": "pod-virtual-uid",
|
|
},
|
|
}},
|
|
EventType: corev1.EventTypeNormal,
|
|
Reason: "Scheduled",
|
|
Message: "Successfully assigned test/nginx to localhost.localdomain",
|
|
},
|
|
},
|
|
"non-Pod InvolvedObject kind skips reconciliation": {
|
|
receivedEvent: &corev1.Event{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-event-non-pod",
|
|
Namespace: "k3k-mycluster",
|
|
},
|
|
InvolvedObject: corev1.ObjectReference{
|
|
APIVersion: "v1",
|
|
Kind: "ConfigMap",
|
|
Name: "nginx-test-mycluster-6e67696e782b746573742b6d79636c7573746572",
|
|
Namespace: "k3k-mycluster",
|
|
},
|
|
Type: corev1.EventTypeNormal,
|
|
Reason: "Updated",
|
|
Message: "ConfigMap updated",
|
|
},
|
|
// wantEvent is nil: non-Pod InvolvedObject is skipped
|
|
wantEvent: nil,
|
|
},
|
|
"non-reversible translated name skips event emission": {
|
|
receivedEvent: &corev1.Event{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-event-non-reversible",
|
|
Namespace: "k3k-mycluster",
|
|
},
|
|
InvolvedObject: corev1.ObjectReference{
|
|
APIVersion: "v1",
|
|
Kind: "Pod",
|
|
Name: "some-non-translated-name",
|
|
Namespace: "k3k-mycluster",
|
|
},
|
|
Type: corev1.EventTypeNormal,
|
|
Reason: "Started",
|
|
Message: "Container started",
|
|
},
|
|
// virtualObj is nil: object not found in virtual cluster → event is skipped
|
|
wantEvent: nil,
|
|
},
|
|
}
|
|
|
|
for name, tt := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
hostFakeClient := fake.NewClientBuilder().
|
|
WithScheme(scheme).
|
|
WithObjects(tt.receivedEvent).
|
|
Build()
|
|
|
|
virtFakeObjs := []client.Object{}
|
|
if tt.virtualObj != nil {
|
|
virtFakeObjs = append(virtFakeObjs, tt.virtualObj)
|
|
}
|
|
|
|
virtualFakeClient := fake.NewClientBuilder().
|
|
WithScheme(scheme).
|
|
WithObjects(virtFakeObjs...).
|
|
Build()
|
|
|
|
recorder := &fakeEventRecorder{}
|
|
|
|
syncer := &EventSyncer{
|
|
virtEventRecorder: recorder,
|
|
SyncerContext: &SyncerContext{
|
|
HostClient: hostFakeClient,
|
|
VirtualClient: virtualFakeClient,
|
|
Translator: translate.ToHostTranslator{
|
|
ClusterName: "mycluster",
|
|
ClusterNamespace: "k3k-mycluster",
|
|
},
|
|
ClusterName: "mycluster",
|
|
ClusterNamespace: "k3k-mycluster",
|
|
},
|
|
}
|
|
|
|
result, err := syncer.Reconcile(context.Background(), reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Name: tt.receivedEvent.Name,
|
|
Namespace: tt.receivedEvent.Namespace,
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, reconcile.Result{}, result)
|
|
|
|
if tt.wantEvent == nil {
|
|
assert.Empty(t, recorder.events)
|
|
} else {
|
|
require.Len(t, recorder.events, 1)
|
|
got := recorder.events[0]
|
|
assert.Equal(t, tt.wantEvent.EventType, got.EventType)
|
|
assert.Equal(t, tt.wantEvent.Reason, got.Reason)
|
|
assert.Equal(t, tt.wantEvent.Message, got.Message)
|
|
wantObj := tt.wantEvent.Object.(*unstructured.Unstructured)
|
|
gotObj := got.Object.(*corev1.Pod)
|
|
assert.Equal(t, wantObj.GetName(), gotObj.GetName())
|
|
assert.Equal(t, wantObj.GetNamespace(), gotObj.GetNamespace())
|
|
assert.Equal(t, wantObj.GetUID(), gotObj.GetUID())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEventSyncerReconcileNotFound(t *testing.T) {
|
|
scheme := runtime.NewScheme()
|
|
require.NoError(t, corev1.AddToScheme(scheme))
|
|
|
|
fakeClient := fake.NewClientBuilder().
|
|
WithScheme(scheme).
|
|
Build()
|
|
|
|
recorder := &fakeEventRecorder{}
|
|
|
|
syncer := &EventSyncer{
|
|
virtEventRecorder: recorder,
|
|
SyncerContext: &SyncerContext{
|
|
HostClient: fakeClient,
|
|
VirtualClient: fakeClient,
|
|
Translator: translate.ToHostTranslator{
|
|
ClusterName: "mycluster",
|
|
ClusterNamespace: "host-ns",
|
|
},
|
|
ClusterName: "mycluster",
|
|
ClusterNamespace: "host-ns",
|
|
},
|
|
}
|
|
|
|
result, err := syncer.Reconcile(context.Background(), reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Name: "non-existent-event",
|
|
Namespace: "host-ns",
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, reconcile.Result{}, result)
|
|
|
|
assert.Empty(t, recorder.events)
|
|
}
|
|
|
|
// This fake is used instead of the one in the client-go package because we need to check that the events are sent to the correct namespace.
|
|
//
|
|
// The client-go fake doesn't support namespaces, so we implement our own that captures the events in memory for verification.
|
|
type capturedEvent struct {
|
|
Object runtime.Object
|
|
EventType string
|
|
Reason string
|
|
Message string
|
|
}
|
|
|
|
type fakeEventRecorder struct {
|
|
events []capturedEvent
|
|
}
|
|
|
|
func (f *fakeEventRecorder) Event(object runtime.Object, eventtype, reason, message string) {
|
|
f.events = append(f.events, capturedEvent{
|
|
Object: object,
|
|
EventType: eventtype,
|
|
Reason: reason,
|
|
Message: message,
|
|
})
|
|
}
|
|
|
|
func (f *fakeEventRecorder) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...any) {
|
|
f.Event(object, eventtype, reason, fmt.Sprintf(messageFmt, args...))
|
|
}
|
|
|
|
func (f *fakeEventRecorder) AnnotatedEventf(object runtime.Object, _ map[string]string, eventtype, reason, messageFmt string, args ...any) {
|
|
f.Event(object, eventtype, reason, fmt.Sprintf(messageFmt, args...))
|
|
}
|