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 }