k8s.io/client-go@v0.31.1/tools/record/event_test.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package record
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	stderrors "errors"
    23  	"fmt"
    24  	"net/http"
    25  	"strconv"
    26  	"sync"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/stretchr/testify/assert"
    31  	"go.uber.org/goleak"
    32  
    33  	v1 "k8s.io/api/core/v1"
    34  	"k8s.io/apimachinery/pkg/api/errors"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	k8sruntime "k8s.io/apimachinery/pkg/runtime"
    37  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    38  	"k8s.io/client-go/kubernetes/scheme"
    39  	restclient "k8s.io/client-go/rest"
    40  	ref "k8s.io/client-go/tools/reference"
    41  	"k8s.io/klog/v2"
    42  	"k8s.io/klog/v2/ktesting"
    43  	"k8s.io/utils/clock"
    44  	testclocks "k8s.io/utils/clock/testing"
    45  )
    46  
    47  type testEventSink struct {
    48  	OnCreate func(e *v1.Event) (*v1.Event, error)
    49  	OnUpdate func(e *v1.Event) (*v1.Event, error)
    50  	OnPatch  func(e *v1.Event, p []byte) (*v1.Event, error)
    51  }
    52  
    53  // CreateEvent records the event for testing.
    54  func (t *testEventSink) Create(e *v1.Event) (*v1.Event, error) {
    55  	if t.OnCreate != nil {
    56  		return t.OnCreate(e)
    57  	}
    58  	return e, nil
    59  }
    60  
    61  // UpdateEvent records the event for testing.
    62  func (t *testEventSink) Update(e *v1.Event) (*v1.Event, error) {
    63  	if t.OnUpdate != nil {
    64  		return t.OnUpdate(e)
    65  	}
    66  	return e, nil
    67  }
    68  
    69  // PatchEvent records the event for testing.
    70  func (t *testEventSink) Patch(e *v1.Event, p []byte) (*v1.Event, error) {
    71  	if t.OnPatch != nil {
    72  		return t.OnPatch(e, p)
    73  	}
    74  	return e, nil
    75  }
    76  
    77  type OnCreateFunc func(*v1.Event) (*v1.Event, error)
    78  
    79  func OnCreateFactory(testCache map[string]*v1.Event, createEvent chan<- *v1.Event) OnCreateFunc {
    80  	return func(event *v1.Event) (*v1.Event, error) {
    81  		testCache[getEventKey(event)] = event
    82  		createEvent <- event
    83  		return event, nil
    84  	}
    85  }
    86  
    87  type OnPatchFunc func(*v1.Event, []byte) (*v1.Event, error)
    88  
    89  func OnPatchFactory(testCache map[string]*v1.Event, patchEvent chan<- *v1.Event) OnPatchFunc {
    90  	return func(event *v1.Event, patch []byte) (*v1.Event, error) {
    91  		cachedEvent, found := testCache[getEventKey(event)]
    92  		if !found {
    93  			return nil, fmt.Errorf("unexpected error: couldn't find Event in testCache.")
    94  		}
    95  		originalData, err := json.Marshal(cachedEvent)
    96  		if err != nil {
    97  			return nil, fmt.Errorf("unexpected error: %v", err)
    98  		}
    99  		patched, err := strategicpatch.StrategicMergePatch(originalData, patch, event)
   100  		if err != nil {
   101  			return nil, fmt.Errorf("unexpected error: %v", err)
   102  		}
   103  		patchedObj := &v1.Event{}
   104  		err = json.Unmarshal(patched, patchedObj)
   105  		if err != nil {
   106  			return nil, fmt.Errorf("unexpected error: %v", err)
   107  		}
   108  		patchEvent <- patchedObj
   109  		return patchedObj, nil
   110  	}
   111  }
   112  
   113  // newBroadcasterForTests creates a new broadcaster which produces per-test log
   114  // output if StartStructuredLogging is used. Will be shut down automatically
   115  // after the test.
   116  func newBroadcasterForTests(tb testing.TB) EventBroadcaster {
   117  	_, ctx := ktesting.NewTestContext(tb)
   118  	caster := NewBroadcaster(WithSleepDuration(0), WithContext(ctx))
   119  	tb.Cleanup(caster.Shutdown)
   120  	return caster
   121  }
   122  
   123  func TestBroadcasterShutdown(t *testing.T) {
   124  	_, ctx := ktesting.NewTestContext(t)
   125  	ctx, cancel := context.WithCancelCause(ctx)
   126  
   127  	// Start a broadcaster with background activity.
   128  	caster := NewBroadcaster(WithContext(ctx))
   129  	caster.StartStructuredLogging(0)
   130  
   131  	// Stop it.
   132  	cancel(stderrors.New("time to stop"))
   133  
   134  	// Ensure that the broadcaster goroutine is not left running.
   135  	goleak.VerifyNone(t)
   136  }
   137  
   138  func TestNonRacyShutdown(t *testing.T) {
   139  	// Attempt to simulate previously racy conditions, and ensure that no race
   140  	// occurs: Nominally, calling "Eventf" *followed by* shutdown from the same
   141  	// thread should be a safe operation, but it's not if we launch recorder.Action
   142  	// in a goroutine.
   143  
   144  	caster := newBroadcasterForTests(t)
   145  	clock := testclocks.NewFakeClock(time.Now())
   146  	recorder := recorderWithFakeClock(t, v1.EventSource{Component: "eventTest"}, caster, clock)
   147  
   148  	var wg sync.WaitGroup
   149  	wg.Add(100)
   150  	for i := 0; i < 100; i++ {
   151  		go func() {
   152  			defer wg.Done()
   153  			recorder.Eventf(&v1.ObjectReference{}, v1.EventTypeNormal, "Started", "blah")
   154  		}()
   155  	}
   156  
   157  	wg.Wait()
   158  	caster.Shutdown()
   159  }
   160  
   161  func TestEventf(t *testing.T) {
   162  	testPod := &v1.Pod{
   163  		ObjectMeta: metav1.ObjectMeta{
   164  			Name:      "foo",
   165  			Namespace: "baz",
   166  			UID:       "bar",
   167  		},
   168  	}
   169  	testPod2 := &v1.Pod{
   170  		ObjectMeta: metav1.ObjectMeta{
   171  			Name:      "foo",
   172  			Namespace: "baz",
   173  			UID:       "differentUid",
   174  		},
   175  	}
   176  	testRef, err := ref.GetPartialReference(scheme.Scheme, testPod, "spec.containers[2]")
   177  	if err != nil {
   178  		t.Fatal(err)
   179  	}
   180  	testRef2, err := ref.GetPartialReference(scheme.Scheme, testPod2, "spec.containers[3]")
   181  	if err != nil {
   182  		t.Fatal(err)
   183  	}
   184  	table := []struct {
   185  		obj                 k8sruntime.Object
   186  		eventtype           string
   187  		reason              string
   188  		messageFmt          string
   189  		elements            []interface{}
   190  		expect              *v1.Event
   191  		expectLog           string
   192  		expectStructuredLog string
   193  		expectUpdate        bool
   194  	}{
   195  		{
   196  			obj:        testRef,
   197  			eventtype:  v1.EventTypeNormal,
   198  			reason:     "Started",
   199  			messageFmt: "some verbose message: %v",
   200  			elements:   []interface{}{1},
   201  			expect: &v1.Event{
   202  				ObjectMeta: metav1.ObjectMeta{
   203  					Name:      "foo",
   204  					Namespace: "baz",
   205  				},
   206  				InvolvedObject: v1.ObjectReference{
   207  					Kind:       "Pod",
   208  					Name:       "foo",
   209  					Namespace:  "baz",
   210  					UID:        "bar",
   211  					APIVersion: "v1",
   212  					FieldPath:  "spec.containers[2]",
   213  				},
   214  				Reason:              "Started",
   215  				Message:             "some verbose message: 1",
   216  				Source:              v1.EventSource{Component: "eventTest"},
   217  				ReportingController: "eventTest",
   218  				Count:               1,
   219  				Type:                v1.EventTypeNormal,
   220  			},
   221  			expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
   222  			expectStructuredLog: `INFO Event occurred object="baz/foo" fieldPath="spec.containers[2]" kind="Pod" apiVersion="v1" type="Normal" reason="Started" message="some verbose message: 1"
   223  `,
   224  			expectUpdate: false,
   225  		},
   226  		{
   227  			obj:        testPod,
   228  			eventtype:  v1.EventTypeNormal,
   229  			reason:     "Killed",
   230  			messageFmt: "some other verbose message: %v",
   231  			elements:   []interface{}{1},
   232  			expect: &v1.Event{
   233  				ObjectMeta: metav1.ObjectMeta{
   234  					Name:      "foo",
   235  					Namespace: "baz",
   236  				},
   237  				InvolvedObject: v1.ObjectReference{
   238  					Kind:       "Pod",
   239  					Name:       "foo",
   240  					Namespace:  "baz",
   241  					UID:        "bar",
   242  					APIVersion: "v1",
   243  				},
   244  				Reason:              "Killed",
   245  				Message:             "some other verbose message: 1",
   246  				Source:              v1.EventSource{Component: "eventTest"},
   247  				ReportingController: "eventTest",
   248  				Count:               1,
   249  				Type:                v1.EventTypeNormal,
   250  			},
   251  			expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:""}): type: 'Normal' reason: 'Killed' some other verbose message: 1`,
   252  			expectStructuredLog: `INFO Event occurred object="baz/foo" fieldPath="" kind="Pod" apiVersion="v1" type="Normal" reason="Killed" message="some other verbose message: 1"
   253  `,
   254  			expectUpdate: false,
   255  		},
   256  		{
   257  			obj:        testRef,
   258  			eventtype:  v1.EventTypeNormal,
   259  			reason:     "Started",
   260  			messageFmt: "some verbose message: %v",
   261  			elements:   []interface{}{1},
   262  			expect: &v1.Event{
   263  				ObjectMeta: metav1.ObjectMeta{
   264  					Name:      "foo",
   265  					Namespace: "baz",
   266  				},
   267  				InvolvedObject: v1.ObjectReference{
   268  					Kind:       "Pod",
   269  					Name:       "foo",
   270  					Namespace:  "baz",
   271  					UID:        "bar",
   272  					APIVersion: "v1",
   273  					FieldPath:  "spec.containers[2]",
   274  				},
   275  				Reason:              "Started",
   276  				Message:             "some verbose message: 1",
   277  				Source:              v1.EventSource{Component: "eventTest"},
   278  				ReportingController: "eventTest",
   279  				Count:               2,
   280  				Type:                v1.EventTypeNormal,
   281  			},
   282  			expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
   283  			expectStructuredLog: `INFO Event occurred object="baz/foo" fieldPath="spec.containers[2]" kind="Pod" apiVersion="v1" type="Normal" reason="Started" message="some verbose message: 1"
   284  `,
   285  			expectUpdate: true,
   286  		},
   287  		{
   288  			obj:        testRef2,
   289  			eventtype:  v1.EventTypeNormal,
   290  			reason:     "Started",
   291  			messageFmt: "some verbose message: %v",
   292  			elements:   []interface{}{1},
   293  			expect: &v1.Event{
   294  				ObjectMeta: metav1.ObjectMeta{
   295  					Name:      "foo",
   296  					Namespace: "baz",
   297  				},
   298  				InvolvedObject: v1.ObjectReference{
   299  					Kind:       "Pod",
   300  					Name:       "foo",
   301  					Namespace:  "baz",
   302  					UID:        "differentUid",
   303  					APIVersion: "v1",
   304  					FieldPath:  "spec.containers[3]",
   305  				},
   306  				Reason:              "Started",
   307  				Message:             "some verbose message: 1",
   308  				Source:              v1.EventSource{Component: "eventTest"},
   309  				ReportingController: "eventTest",
   310  				Count:               1,
   311  				Type:                v1.EventTypeNormal,
   312  			},
   313  			expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
   314  			expectStructuredLog: `INFO Event occurred object="baz/foo" fieldPath="spec.containers[3]" kind="Pod" apiVersion="v1" type="Normal" reason="Started" message="some verbose message: 1"
   315  `,
   316  			expectUpdate: false,
   317  		},
   318  		{
   319  			obj:        testRef,
   320  			eventtype:  v1.EventTypeNormal,
   321  			reason:     "Started",
   322  			messageFmt: "some verbose message: %v",
   323  			elements:   []interface{}{1},
   324  			expect: &v1.Event{
   325  				ObjectMeta: metav1.ObjectMeta{
   326  					Name:      "foo",
   327  					Namespace: "baz",
   328  				},
   329  				InvolvedObject: v1.ObjectReference{
   330  					Kind:       "Pod",
   331  					Name:       "foo",
   332  					Namespace:  "baz",
   333  					UID:        "bar",
   334  					APIVersion: "v1",
   335  					FieldPath:  "spec.containers[2]",
   336  				},
   337  				Reason:              "Started",
   338  				Message:             "some verbose message: 1",
   339  				Source:              v1.EventSource{Component: "eventTest"},
   340  				ReportingController: "eventTest",
   341  				Count:               3,
   342  				Type:                v1.EventTypeNormal,
   343  			},
   344  			expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
   345  			expectStructuredLog: `INFO Event occurred object="baz/foo" fieldPath="spec.containers[2]" kind="Pod" apiVersion="v1" type="Normal" reason="Started" message="some verbose message: 1"
   346  `,
   347  			expectUpdate: true,
   348  		},
   349  		{
   350  			obj:        testRef2,
   351  			eventtype:  v1.EventTypeNormal,
   352  			reason:     "Stopped",
   353  			messageFmt: "some verbose message: %v",
   354  			elements:   []interface{}{1},
   355  			expect: &v1.Event{
   356  				ObjectMeta: metav1.ObjectMeta{
   357  					Name:      "foo",
   358  					Namespace: "baz",
   359  				},
   360  				InvolvedObject: v1.ObjectReference{
   361  					Kind:       "Pod",
   362  					Name:       "foo",
   363  					Namespace:  "baz",
   364  					UID:        "differentUid",
   365  					APIVersion: "v1",
   366  					FieldPath:  "spec.containers[3]",
   367  				},
   368  				Reason:              "Stopped",
   369  				Message:             "some verbose message: 1",
   370  				Source:              v1.EventSource{Component: "eventTest"},
   371  				ReportingController: "eventTest",
   372  				Count:               1,
   373  				Type:                v1.EventTypeNormal,
   374  			},
   375  			expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`,
   376  			expectStructuredLog: `INFO Event occurred object="baz/foo" fieldPath="spec.containers[3]" kind="Pod" apiVersion="v1" type="Normal" reason="Stopped" message="some verbose message: 1"
   377  `,
   378  			expectUpdate: false,
   379  		},
   380  		{
   381  			obj:        testRef2,
   382  			eventtype:  v1.EventTypeNormal,
   383  			reason:     "Stopped",
   384  			messageFmt: "some verbose message: %v",
   385  			elements:   []interface{}{1},
   386  			expect: &v1.Event{
   387  				ObjectMeta: metav1.ObjectMeta{
   388  					Name:      "foo",
   389  					Namespace: "baz",
   390  				},
   391  				InvolvedObject: v1.ObjectReference{
   392  					Kind:       "Pod",
   393  					Name:       "foo",
   394  					Namespace:  "baz",
   395  					UID:        "differentUid",
   396  					APIVersion: "v1",
   397  					FieldPath:  "spec.containers[3]",
   398  				},
   399  				Reason:              "Stopped",
   400  				Message:             "some verbose message: 1",
   401  				Source:              v1.EventSource{Component: "eventTest"},
   402  				ReportingController: "eventTest",
   403  				Count:               2,
   404  				Type:                v1.EventTypeNormal,
   405  			},
   406  			expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`,
   407  			expectStructuredLog: `INFO Event occurred object="baz/foo" fieldPath="spec.containers[3]" kind="Pod" apiVersion="v1" type="Normal" reason="Stopped" message="some verbose message: 1"
   408  `,
   409  			expectUpdate: true,
   410  		},
   411  	}
   412  
   413  	testCache := map[string]*v1.Event{}
   414  	logCalled := make(chan struct{})
   415  	createEvent := make(chan *v1.Event)
   416  	updateEvent := make(chan *v1.Event)
   417  	patchEvent := make(chan *v1.Event)
   418  	testEvents := testEventSink{
   419  		OnCreate: OnCreateFactory(testCache, createEvent),
   420  		OnUpdate: func(event *v1.Event) (*v1.Event, error) {
   421  			updateEvent <- event
   422  			return event, nil
   423  		},
   424  		OnPatch: OnPatchFactory(testCache, patchEvent),
   425  	}
   426  	logger := ktesting.NewLogger(t, ktesting.NewConfig(ktesting.BufferLogs(true)))
   427  	logSink := logger.GetSink().(ktesting.Underlier)
   428  	ctx := klog.NewContext(context.Background(), logger)
   429  	eventBroadcaster := NewBroadcaster(WithSleepDuration(0), WithContext(ctx))
   430  	defer eventBroadcaster.Shutdown()
   431  	sinkWatcher := eventBroadcaster.StartRecordingToSink(&testEvents)
   432  
   433  	clock := testclocks.NewFakeClock(time.Now())
   434  	recorder := recorderWithFakeClock(t, v1.EventSource{Component: "eventTest"}, eventBroadcaster, clock)
   435  	for index, item := range table {
   436  		clock.Step(1 * time.Second)
   437  		//nolint:logcheck // Intentionally testing StartLogging here.
   438  		logWatcher := eventBroadcaster.StartLogging(func(formatter string, args ...interface{}) {
   439  			if e, a := item.expectLog, fmt.Sprintf(formatter, args...); e != a {
   440  				t.Errorf("Expected '%v', got '%v'", e, a)
   441  			}
   442  			logCalled <- struct{}{}
   443  		})
   444  		oldEnd := len(logSink.GetBuffer().String())
   445  		structuredLogWatcher := eventBroadcaster.StartStructuredLogging(0)
   446  		recorder.Eventf(item.obj, item.eventtype, item.reason, item.messageFmt, item.elements...)
   447  
   448  		<-logCalled
   449  
   450  		// We don't get notified by the structured test logger directly.
   451  		// Instead, we periodically check what new output it has produced.
   452  		assert.EventuallyWithT(t, func(t *assert.CollectT) {
   453  			assert.Equal(t, item.expectStructuredLog, logSink.GetBuffer().String()[oldEnd:], "new structured log output")
   454  		}, time.Minute, time.Millisecond)
   455  
   456  		// validate event
   457  		if item.expectUpdate {
   458  			actualEvent := <-patchEvent
   459  			validateEvent(strconv.Itoa(index), actualEvent, item.expect, t)
   460  		} else {
   461  			actualEvent := <-createEvent
   462  			validateEvent(strconv.Itoa(index), actualEvent, item.expect, t)
   463  		}
   464  		logWatcher.Stop()
   465  		structuredLogWatcher.Stop()
   466  	}
   467  	sinkWatcher.Stop()
   468  }
   469  
   470  func recorderWithFakeClock(t *testing.T, eventSource v1.EventSource, eventBroadcaster EventBroadcaster, clock clock.Clock) EventRecorder {
   471  	return &recorderImpl{scheme.Scheme, eventSource, eventBroadcaster.(*eventBroadcasterImpl).Broadcaster, clock}
   472  }
   473  
   474  func TestWriteEventError(t *testing.T) {
   475  	type entry struct {
   476  		timesToSendError int
   477  		attemptsWanted   int
   478  		err              error
   479  	}
   480  	table := map[string]*entry{
   481  		"giveUp1": {
   482  			timesToSendError: 1000,
   483  			attemptsWanted:   1,
   484  			err:              &restclient.RequestConstructionError{},
   485  		},
   486  		"giveUp2": {
   487  			timesToSendError: 1000,
   488  			attemptsWanted:   1,
   489  			err:              &errors.StatusError{},
   490  		},
   491  		"retry1": {
   492  			timesToSendError: 1000,
   493  			attemptsWanted:   12,
   494  			err:              &errors.UnexpectedObjectError{},
   495  		},
   496  		"retry2": {
   497  			timesToSendError: 1000,
   498  			attemptsWanted:   12,
   499  			err:              fmt.Errorf("A weird error"),
   500  		},
   501  		"succeedEventually": {
   502  			timesToSendError: 2,
   503  			attemptsWanted:   2,
   504  			err:              fmt.Errorf("A weird error"),
   505  		},
   506  	}
   507  
   508  	clock := testclocks.SimpleIntervalClock{Time: time.Now(), Duration: time.Second}
   509  	eventCorrelator := NewEventCorrelator(&clock)
   510  
   511  	for caseName, ent := range table {
   512  		attempts := 0
   513  		sink := &testEventSink{
   514  			OnCreate: func(event *v1.Event) (*v1.Event, error) {
   515  				attempts++
   516  				if attempts < ent.timesToSendError {
   517  					return nil, ent.err
   518  				}
   519  				return event, nil
   520  			},
   521  		}
   522  		ev := &v1.Event{}
   523  		ctx, cancel := context.WithCancel(context.Background())
   524  		defer cancel()
   525  		e := eventBroadcasterImpl{
   526  			cancelationCtx: ctx,
   527  		}
   528  		e.recordToSink(sink, ev, eventCorrelator)
   529  		if attempts != ent.attemptsWanted {
   530  			t.Errorf("case %v: wanted %d, got %d attempts", caseName, ent.attemptsWanted, attempts)
   531  		}
   532  	}
   533  }
   534  
   535  func TestUpdateExpiredEvent(t *testing.T) {
   536  	clock := testclocks.SimpleIntervalClock{Time: time.Now(), Duration: time.Second}
   537  	eventCorrelator := NewEventCorrelator(&clock)
   538  
   539  	var createdEvent *v1.Event
   540  
   541  	sink := &testEventSink{
   542  		OnPatch: func(*v1.Event, []byte) (*v1.Event, error) {
   543  			return nil, &errors.StatusError{
   544  				ErrStatus: metav1.Status{
   545  					Code:   http.StatusNotFound,
   546  					Reason: metav1.StatusReasonNotFound,
   547  				}}
   548  		},
   549  		OnCreate: func(event *v1.Event) (*v1.Event, error) {
   550  			createdEvent = event
   551  			return event, nil
   552  		},
   553  	}
   554  
   555  	ev := &v1.Event{}
   556  	ev.ResourceVersion = "updated-resource-version"
   557  	ev.Count = 2
   558  	ctx, cancel := context.WithCancel(context.Background())
   559  	defer cancel()
   560  	e := eventBroadcasterImpl{
   561  		cancelationCtx: ctx,
   562  	}
   563  	e.recordToSink(sink, ev, eventCorrelator)
   564  
   565  	if createdEvent == nil {
   566  		t.Error("Event did not get created after patch failed")
   567  		return
   568  	}
   569  
   570  	if createdEvent.ResourceVersion != "" {
   571  		t.Errorf("Event did not have its resource version cleared, was %s", createdEvent.ResourceVersion)
   572  	}
   573  }
   574  
   575  func TestCancelEvent(t *testing.T) {
   576  	clock := testclocks.SimpleIntervalClock{Time: time.Now(), Duration: time.Second}
   577  	eventCorrelator := NewEventCorrelator(&clock)
   578  
   579  	attempts := 0
   580  	sink := &testEventSink{
   581  		OnCreate: func(event *v1.Event) (*v1.Event, error) {
   582  			attempts++
   583  			return nil, &errors.UnexpectedObjectError{}
   584  		},
   585  	}
   586  
   587  	ev := &v1.Event{}
   588  
   589  	// Cancel before even calling recordToSink.
   590  	ctx, cancel := context.WithCancel(context.Background())
   591  	cancel()
   592  	e := eventBroadcasterImpl{
   593  		cancelationCtx: ctx,
   594  		sleepDuration:  time.Second,
   595  	}
   596  	e.recordToSink(sink, ev, eventCorrelator)
   597  	if attempts != 1 {
   598  		t.Errorf("recordToSink should have tried once, then given up immediately. Instead it tried %d times.", attempts)
   599  	}
   600  }
   601  
   602  func TestLotsOfEvents(t *testing.T) {
   603  	recorderCalled := make(chan struct{})
   604  	loggerCalled := make(chan struct{})
   605  
   606  	// Fail each event a few times to ensure there's some load on the tested code.
   607  	var counts [1000]int
   608  	testEvents := testEventSink{
   609  		OnCreate: func(event *v1.Event) (*v1.Event, error) {
   610  			num, err := strconv.Atoi(event.Message)
   611  			if err != nil {
   612  				t.Error(err)
   613  				return event, nil
   614  			}
   615  			counts[num]++
   616  			if counts[num] < 5 {
   617  				return nil, fmt.Errorf("fake error")
   618  			}
   619  			recorderCalled <- struct{}{}
   620  			return event, nil
   621  		},
   622  	}
   623  
   624  	eventBroadcaster := newBroadcasterForTests(t)
   625  	sinkWatcher := eventBroadcaster.StartRecordingToSink(&testEvents)
   626  	//nolint:logcheck // Intentionally using StartLogging here to get notified.
   627  	logWatcher := eventBroadcaster.StartLogging(func(formatter string, args ...interface{}) {
   628  		loggerCalled <- struct{}{}
   629  	})
   630  	recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "eventTest"})
   631  	for i := 0; i < maxQueuedEvents; i++ {
   632  		// we want a unique object to stop spam filtering
   633  		ref := &v1.ObjectReference{
   634  			Kind:       "Pod",
   635  			Name:       fmt.Sprintf("foo-%v", i),
   636  			Namespace:  "baz",
   637  			UID:        "bar",
   638  			APIVersion: "version",
   639  		}
   640  		// we need to vary the reason to prevent aggregation
   641  		go recorder.Eventf(ref, v1.EventTypeNormal, "Reason-"+strconv.Itoa(i), strconv.Itoa(i))
   642  	}
   643  	// Make sure no events were dropped by either of the listeners.
   644  	for i := 0; i < maxQueuedEvents; i++ {
   645  		<-recorderCalled
   646  		<-loggerCalled
   647  	}
   648  	// Make sure that every event was attempted 5 times
   649  	for i := 0; i < maxQueuedEvents; i++ {
   650  		if counts[i] < 5 {
   651  			t.Errorf("Only attempted to record event '%d' %d times.", i, counts[i])
   652  		}
   653  	}
   654  	sinkWatcher.Stop()
   655  	logWatcher.Stop()
   656  }
   657  
   658  func TestEventfNoNamespace(t *testing.T) {
   659  	testPod := &v1.Pod{
   660  		ObjectMeta: metav1.ObjectMeta{
   661  			Name: "foo",
   662  			UID:  "bar",
   663  		},
   664  	}
   665  	testRef, err := ref.GetPartialReference(scheme.Scheme, testPod, "spec.containers[2]")
   666  	if err != nil {
   667  		t.Fatal(err)
   668  	}
   669  	table := []struct {
   670  		obj          k8sruntime.Object
   671  		eventtype    string
   672  		reason       string
   673  		messageFmt   string
   674  		elements     []interface{}
   675  		expect       *v1.Event
   676  		expectLog    string
   677  		expectUpdate bool
   678  	}{
   679  		{
   680  			obj:        testRef,
   681  			eventtype:  v1.EventTypeNormal,
   682  			reason:     "Started",
   683  			messageFmt: "some verbose message: %v",
   684  			elements:   []interface{}{1},
   685  			expect: &v1.Event{
   686  				ObjectMeta: metav1.ObjectMeta{
   687  					Name:      "foo",
   688  					Namespace: "default",
   689  				},
   690  				InvolvedObject: v1.ObjectReference{
   691  					Kind:       "Pod",
   692  					Name:       "foo",
   693  					Namespace:  "",
   694  					UID:        "bar",
   695  					APIVersion: "v1",
   696  					FieldPath:  "spec.containers[2]",
   697  				},
   698  				Reason:  "Started",
   699  				Message: "some verbose message: 1",
   700  				Source:  v1.EventSource{Component: "eventTest"},
   701  				Count:   1,
   702  				Type:    v1.EventTypeNormal,
   703  			},
   704  			expectLog:    `Event(v1.ObjectReference{Kind:"Pod", Namespace:"", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
   705  			expectUpdate: false,
   706  		},
   707  	}
   708  
   709  	testCache := map[string]*v1.Event{}
   710  	logCalled := make(chan struct{})
   711  	createEvent := make(chan *v1.Event)
   712  	updateEvent := make(chan *v1.Event)
   713  	patchEvent := make(chan *v1.Event)
   714  	testEvents := testEventSink{
   715  		OnCreate: OnCreateFactory(testCache, createEvent),
   716  		OnUpdate: func(event *v1.Event) (*v1.Event, error) {
   717  			updateEvent <- event
   718  			return event, nil
   719  		},
   720  		OnPatch: OnPatchFactory(testCache, patchEvent),
   721  	}
   722  	eventBroadcaster := newBroadcasterForTests(t)
   723  	sinkWatcher := eventBroadcaster.StartRecordingToSink(&testEvents)
   724  
   725  	clock := testclocks.NewFakeClock(time.Now())
   726  	recorder := recorderWithFakeClock(t, v1.EventSource{Component: "eventTest"}, eventBroadcaster, clock)
   727  
   728  	for index, item := range table {
   729  		clock.Step(1 * time.Second)
   730  		logWatcher := eventBroadcaster.StartLogging(func(formatter string, args ...interface{}) {
   731  			if e, a := item.expectLog, fmt.Sprintf(formatter, args...); e != a {
   732  				t.Errorf("Expected '%v', got '%v'", e, a)
   733  			}
   734  			logCalled <- struct{}{}
   735  		})
   736  		recorder.Eventf(item.obj, item.eventtype, item.reason, item.messageFmt, item.elements...)
   737  
   738  		<-logCalled
   739  
   740  		// validate event
   741  		if item.expectUpdate {
   742  			actualEvent := <-patchEvent
   743  			validateEvent(strconv.Itoa(index), actualEvent, item.expect, t)
   744  		} else {
   745  			actualEvent := <-createEvent
   746  			validateEvent(strconv.Itoa(index), actualEvent, item.expect, t)
   747  		}
   748  
   749  		logWatcher.Stop()
   750  	}
   751  	sinkWatcher.Stop()
   752  }
   753  
   754  func TestMultiSinkCache(t *testing.T) {
   755  	testPod := &v1.Pod{
   756  		ObjectMeta: metav1.ObjectMeta{
   757  			Name:      "foo",
   758  			Namespace: "baz",
   759  			UID:       "bar",
   760  		},
   761  	}
   762  	testPod2 := &v1.Pod{
   763  		ObjectMeta: metav1.ObjectMeta{
   764  			Name:      "foo",
   765  			Namespace: "baz",
   766  			UID:       "differentUid",
   767  		},
   768  	}
   769  	testRef, err := ref.GetPartialReference(scheme.Scheme, testPod, "spec.containers[2]")
   770  	if err != nil {
   771  		t.Fatal(err)
   772  	}
   773  	testRef2, err := ref.GetPartialReference(scheme.Scheme, testPod2, "spec.containers[3]")
   774  	if err != nil {
   775  		t.Fatal(err)
   776  	}
   777  	table := []struct {
   778  		obj          k8sruntime.Object
   779  		eventtype    string
   780  		reason       string
   781  		messageFmt   string
   782  		elements     []interface{}
   783  		expect       *v1.Event
   784  		expectLog    string
   785  		expectUpdate bool
   786  	}{
   787  		{
   788  			obj:        testRef,
   789  			eventtype:  v1.EventTypeNormal,
   790  			reason:     "Started",
   791  			messageFmt: "some verbose message: %v",
   792  			elements:   []interface{}{1},
   793  			expect: &v1.Event{
   794  				ObjectMeta: metav1.ObjectMeta{
   795  					Name:      "foo",
   796  					Namespace: "baz",
   797  				},
   798  				InvolvedObject: v1.ObjectReference{
   799  					Kind:       "Pod",
   800  					Name:       "foo",
   801  					Namespace:  "baz",
   802  					UID:        "bar",
   803  					APIVersion: "v1",
   804  					FieldPath:  "spec.containers[2]",
   805  				},
   806  				Reason:              "Started",
   807  				Message:             "some verbose message: 1",
   808  				Source:              v1.EventSource{Component: "eventTest"},
   809  				ReportingController: "eventTest",
   810  				Count:               1,
   811  				Type:                v1.EventTypeNormal,
   812  			},
   813  			expectLog:    `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
   814  			expectUpdate: false,
   815  		},
   816  		{
   817  			obj:        testPod,
   818  			eventtype:  v1.EventTypeNormal,
   819  			reason:     "Killed",
   820  			messageFmt: "some other verbose message: %v",
   821  			elements:   []interface{}{1},
   822  			expect: &v1.Event{
   823  				ObjectMeta: metav1.ObjectMeta{
   824  					Name:      "foo",
   825  					Namespace: "baz",
   826  				},
   827  				InvolvedObject: v1.ObjectReference{
   828  					Kind:       "Pod",
   829  					Name:       "foo",
   830  					Namespace:  "baz",
   831  					UID:        "bar",
   832  					APIVersion: "v1",
   833  				},
   834  				Reason:              "Killed",
   835  				Message:             "some other verbose message: 1",
   836  				Source:              v1.EventSource{Component: "eventTest"},
   837  				ReportingController: "eventTest",
   838  				Count:               1,
   839  				Type:                v1.EventTypeNormal,
   840  			},
   841  			expectLog:    `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:""}): type: 'Normal' reason: 'Killed' some other verbose message: 1`,
   842  			expectUpdate: false,
   843  		},
   844  		{
   845  			obj:        testRef,
   846  			eventtype:  v1.EventTypeNormal,
   847  			reason:     "Started",
   848  			messageFmt: "some verbose message: %v",
   849  			elements:   []interface{}{1},
   850  			expect: &v1.Event{
   851  				ObjectMeta: metav1.ObjectMeta{
   852  					Name:      "foo",
   853  					Namespace: "baz",
   854  				},
   855  				InvolvedObject: v1.ObjectReference{
   856  					Kind:       "Pod",
   857  					Name:       "foo",
   858  					Namespace:  "baz",
   859  					UID:        "bar",
   860  					APIVersion: "v1",
   861  					FieldPath:  "spec.containers[2]",
   862  				},
   863  				Reason:              "Started",
   864  				Message:             "some verbose message: 1",
   865  				Source:              v1.EventSource{Component: "eventTest"},
   866  				ReportingController: "eventTest",
   867  				Count:               2,
   868  				Type:                v1.EventTypeNormal,
   869  			},
   870  			expectLog:    `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
   871  			expectUpdate: true,
   872  		},
   873  		{
   874  			obj:        testRef2,
   875  			eventtype:  v1.EventTypeNormal,
   876  			reason:     "Started",
   877  			messageFmt: "some verbose message: %v",
   878  			elements:   []interface{}{1},
   879  			expect: &v1.Event{
   880  				ObjectMeta: metav1.ObjectMeta{
   881  					Name:      "foo",
   882  					Namespace: "baz",
   883  				},
   884  				InvolvedObject: v1.ObjectReference{
   885  					Kind:       "Pod",
   886  					Name:       "foo",
   887  					Namespace:  "baz",
   888  					UID:        "differentUid",
   889  					APIVersion: "v1",
   890  					FieldPath:  "spec.containers[3]",
   891  				},
   892  				Reason:              "Started",
   893  				Message:             "some verbose message: 1",
   894  				Source:              v1.EventSource{Component: "eventTest"},
   895  				ReportingController: "eventTest",
   896  				Count:               1,
   897  				Type:                v1.EventTypeNormal,
   898  			},
   899  			expectLog:    `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
   900  			expectUpdate: false,
   901  		},
   902  		{
   903  			obj:        testRef,
   904  			eventtype:  v1.EventTypeNormal,
   905  			reason:     "Started",
   906  			messageFmt: "some verbose message: %v",
   907  			elements:   []interface{}{1},
   908  			expect: &v1.Event{
   909  				ObjectMeta: metav1.ObjectMeta{
   910  					Name:      "foo",
   911  					Namespace: "baz",
   912  				},
   913  				InvolvedObject: v1.ObjectReference{
   914  					Kind:       "Pod",
   915  					Name:       "foo",
   916  					Namespace:  "baz",
   917  					UID:        "bar",
   918  					APIVersion: "v1",
   919  					FieldPath:  "spec.containers[2]",
   920  				},
   921  				Reason:              "Started",
   922  				Message:             "some verbose message: 1",
   923  				Source:              v1.EventSource{Component: "eventTest"},
   924  				ReportingController: "eventTest",
   925  				Count:               3,
   926  				Type:                v1.EventTypeNormal,
   927  			},
   928  			expectLog:    `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
   929  			expectUpdate: true,
   930  		},
   931  		{
   932  			obj:        testRef2,
   933  			eventtype:  v1.EventTypeNormal,
   934  			reason:     "Stopped",
   935  			messageFmt: "some verbose message: %v",
   936  			elements:   []interface{}{1},
   937  			expect: &v1.Event{
   938  				ObjectMeta: metav1.ObjectMeta{
   939  					Name:      "foo",
   940  					Namespace: "baz",
   941  				},
   942  				InvolvedObject: v1.ObjectReference{
   943  					Kind:       "Pod",
   944  					Name:       "foo",
   945  					Namespace:  "baz",
   946  					UID:        "differentUid",
   947  					APIVersion: "v1",
   948  					FieldPath:  "spec.containers[3]",
   949  				},
   950  				Reason:              "Stopped",
   951  				Message:             "some verbose message: 1",
   952  				Source:              v1.EventSource{Component: "eventTest"},
   953  				ReportingController: "eventTest",
   954  				Count:               1,
   955  				Type:                v1.EventTypeNormal,
   956  			},
   957  			expectLog:    `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`,
   958  			expectUpdate: false,
   959  		},
   960  		{
   961  			obj:        testRef2,
   962  			eventtype:  v1.EventTypeNormal,
   963  			reason:     "Stopped",
   964  			messageFmt: "some verbose message: %v",
   965  			elements:   []interface{}{1},
   966  			expect: &v1.Event{
   967  				ObjectMeta: metav1.ObjectMeta{
   968  					Name:      "foo",
   969  					Namespace: "baz",
   970  				},
   971  				InvolvedObject: v1.ObjectReference{
   972  					Kind:       "Pod",
   973  					Name:       "foo",
   974  					Namespace:  "baz",
   975  					UID:        "differentUid",
   976  					APIVersion: "v1",
   977  					FieldPath:  "spec.containers[3]",
   978  				},
   979  				Reason:              "Stopped",
   980  				Message:             "some verbose message: 1",
   981  				Source:              v1.EventSource{Component: "eventTest"},
   982  				ReportingController: "eventTest",
   983  				Count:               2,
   984  				Type:                v1.EventTypeNormal,
   985  			},
   986  			expectLog:    `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`,
   987  			expectUpdate: true,
   988  		},
   989  	}
   990  
   991  	testCache := map[string]*v1.Event{}
   992  	createEvent := make(chan *v1.Event)
   993  	updateEvent := make(chan *v1.Event)
   994  	patchEvent := make(chan *v1.Event)
   995  	testEvents := testEventSink{
   996  		OnCreate: OnCreateFactory(testCache, createEvent),
   997  		OnUpdate: func(event *v1.Event) (*v1.Event, error) {
   998  			updateEvent <- event
   999  			return event, nil
  1000  		},
  1001  		OnPatch: OnPatchFactory(testCache, patchEvent),
  1002  	}
  1003  
  1004  	testCache2 := map[string]*v1.Event{}
  1005  	createEvent2 := make(chan *v1.Event)
  1006  	updateEvent2 := make(chan *v1.Event)
  1007  	patchEvent2 := make(chan *v1.Event)
  1008  	testEvents2 := testEventSink{
  1009  		OnCreate: OnCreateFactory(testCache2, createEvent2),
  1010  		OnUpdate: func(event *v1.Event) (*v1.Event, error) {
  1011  			updateEvent2 <- event
  1012  			return event, nil
  1013  		},
  1014  		OnPatch: OnPatchFactory(testCache2, patchEvent2),
  1015  	}
  1016  
  1017  	eventBroadcaster := newBroadcasterForTests(t)
  1018  	clock := testclocks.NewFakeClock(time.Now())
  1019  	recorder := recorderWithFakeClock(t, v1.EventSource{Component: "eventTest"}, eventBroadcaster, clock)
  1020  
  1021  	sinkWatcher := eventBroadcaster.StartRecordingToSink(&testEvents)
  1022  	for index, item := range table {
  1023  		clock.Step(1 * time.Second)
  1024  		recorder.Eventf(item.obj, item.eventtype, item.reason, item.messageFmt, item.elements...)
  1025  
  1026  		// validate event
  1027  		if item.expectUpdate {
  1028  			actualEvent := <-patchEvent
  1029  			validateEvent(strconv.Itoa(index), actualEvent, item.expect, t)
  1030  		} else {
  1031  			actualEvent := <-createEvent
  1032  			validateEvent(strconv.Itoa(index), actualEvent, item.expect, t)
  1033  		}
  1034  	}
  1035  	// Stop before creating more events, otherwise the On* callbacks above
  1036  	// get stuck writing to the channel that we don't read from anymore.
  1037  	sinkWatcher.Stop()
  1038  
  1039  	// Another StartRecordingToSink call should start to record events with new clean cache.
  1040  	sinkWatcher2 := eventBroadcaster.StartRecordingToSink(&testEvents2)
  1041  	for index, item := range table {
  1042  		clock.Step(1 * time.Second)
  1043  		recorder.Eventf(item.obj, item.eventtype, item.reason, item.messageFmt, item.elements...)
  1044  
  1045  		// validate event
  1046  		if item.expectUpdate {
  1047  			actualEvent := <-patchEvent2
  1048  			validateEvent(strconv.Itoa(index), actualEvent, item.expect, t)
  1049  		} else {
  1050  			actualEvent := <-createEvent2
  1051  			validateEvent(strconv.Itoa(index), actualEvent, item.expect, t)
  1052  		}
  1053  	}
  1054  
  1055  	sinkWatcher2.Stop()
  1056  }