k8s.io/client-go@v0.22.2/tools/record/events_cache_test.go (about)

     1  /*
     2  Copyright 2015 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  	"reflect"
    21  	"strconv"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/util/clock"
    29  	"k8s.io/apimachinery/pkg/util/diff"
    30  )
    31  
    32  func makeObjectReference(kind, name, namespace string) v1.ObjectReference {
    33  	return v1.ObjectReference{
    34  		Kind:       kind,
    35  		Name:       name,
    36  		Namespace:  namespace,
    37  		UID:        "C934D34AFB20242",
    38  		APIVersion: "version",
    39  		FieldPath:  "spec.containers{mycontainer}",
    40  	}
    41  }
    42  
    43  func makeEvent(reason, message string, involvedObject v1.ObjectReference) v1.Event {
    44  	eventTime := metav1.Now()
    45  	event := v1.Event{
    46  		Reason:         reason,
    47  		Message:        message,
    48  		InvolvedObject: involvedObject,
    49  		Source: v1.EventSource{
    50  			Component: "kubelet",
    51  			Host:      "kublet.node1",
    52  		},
    53  		Count:          1,
    54  		FirstTimestamp: eventTime,
    55  		LastTimestamp:  eventTime,
    56  		Type:           v1.EventTypeNormal,
    57  	}
    58  	return event
    59  }
    60  
    61  func makeEvents(num int, template v1.Event) []v1.Event {
    62  	events := []v1.Event{}
    63  	for i := 0; i < num; i++ {
    64  		events = append(events, template)
    65  	}
    66  	return events
    67  }
    68  
    69  func makeUniqueEvents(num int) []v1.Event {
    70  	events := []v1.Event{}
    71  	kind := "Pod"
    72  	for i := 0; i < num; i++ {
    73  		reason := strings.Join([]string{"reason", strconv.Itoa(i)}, "-")
    74  		message := strings.Join([]string{"message", strconv.Itoa(i)}, "-")
    75  		name := strings.Join([]string{"pod", strconv.Itoa(i)}, "-")
    76  		namespace := strings.Join([]string{"ns", strconv.Itoa(i)}, "-")
    77  		involvedObject := makeObjectReference(kind, name, namespace)
    78  		events = append(events, makeEvent(reason, message, involvedObject))
    79  	}
    80  	return events
    81  }
    82  
    83  func makeSimilarEvents(num int, template v1.Event, messagePrefix string) []v1.Event {
    84  	events := makeEvents(num, template)
    85  	for i := range events {
    86  		events[i].Message = strings.Join([]string{messagePrefix, strconv.Itoa(i), events[i].Message}, "-")
    87  	}
    88  	return events
    89  }
    90  
    91  func setCount(event v1.Event, count int) v1.Event {
    92  	event.Count = int32(count)
    93  	return event
    94  }
    95  
    96  func validateEvent(messagePrefix string, actualEvent *v1.Event, expectedEvent *v1.Event, t *testing.T) (*v1.Event, error) {
    97  	recvEvent := *actualEvent
    98  	expectCompression := expectedEvent.Count > 1
    99  	t.Logf("%v - expectedEvent.Count is %d\n", messagePrefix, expectedEvent.Count)
   100  	// Just check that the timestamp was set.
   101  	if recvEvent.FirstTimestamp.IsZero() || recvEvent.LastTimestamp.IsZero() {
   102  		t.Errorf("%v - timestamp wasn't set: %#v", messagePrefix, recvEvent)
   103  	}
   104  	actualFirstTimestamp := recvEvent.FirstTimestamp
   105  	actualLastTimestamp := recvEvent.LastTimestamp
   106  	if actualFirstTimestamp.Equal(&actualLastTimestamp) {
   107  		if expectCompression {
   108  			t.Errorf("%v - FirstTimestamp (%q) and LastTimestamp (%q) must be different to indicate event compression happened, but were the same. Actual Event: %#v", messagePrefix, actualFirstTimestamp, actualLastTimestamp, recvEvent)
   109  		}
   110  	} else {
   111  		if expectedEvent.Count == 1 {
   112  			t.Errorf("%v - FirstTimestamp (%q) and LastTimestamp (%q) must be equal to indicate only one occurrence of the event, but were different. Actual Event: %#v", messagePrefix, actualFirstTimestamp, actualLastTimestamp, recvEvent)
   113  		}
   114  	}
   115  	// Temp clear time stamps for comparison because actual values don't matter for comparison
   116  	recvEvent.FirstTimestamp = expectedEvent.FirstTimestamp
   117  	recvEvent.LastTimestamp = expectedEvent.LastTimestamp
   118  	// Check that name has the right prefix.
   119  	if n, en := recvEvent.Name, expectedEvent.Name; !strings.HasPrefix(n, en) {
   120  		t.Errorf("%v - Name '%v' does not contain prefix '%v'", messagePrefix, n, en)
   121  	}
   122  	recvEvent.Name = expectedEvent.Name
   123  	if e, a := expectedEvent, &recvEvent; !reflect.DeepEqual(e, a) {
   124  		t.Errorf("%v - diff: %s", messagePrefix, diff.ObjectGoPrintDiff(e, a))
   125  	}
   126  	recvEvent.FirstTimestamp = actualFirstTimestamp
   127  	recvEvent.LastTimestamp = actualLastTimestamp
   128  	return actualEvent, nil
   129  }
   130  
   131  // TestEventAggregatorByReasonFunc ensures that two events are aggregated if they vary only by event.message
   132  func TestEventAggregatorByReasonFunc(t *testing.T) {
   133  	event1 := makeEvent("end-of-world", "it was fun", makeObjectReference("Pod", "pod1", "other"))
   134  	event2 := makeEvent("end-of-world", "it was awful", makeObjectReference("Pod", "pod1", "other"))
   135  	event3 := makeEvent("nevermind", "it was a bug", makeObjectReference("Pod", "pod1", "other"))
   136  
   137  	aggKey1, localKey1 := EventAggregatorByReasonFunc(&event1)
   138  	aggKey2, localKey2 := EventAggregatorByReasonFunc(&event2)
   139  	aggKey3, _ := EventAggregatorByReasonFunc(&event3)
   140  
   141  	if aggKey1 != aggKey2 {
   142  		t.Errorf("Expected %v equal %v", aggKey1, aggKey2)
   143  	}
   144  	if localKey1 == localKey2 {
   145  		t.Errorf("Expected %v to not equal %v", aggKey1, aggKey3)
   146  	}
   147  	if aggKey1 == aggKey3 {
   148  		t.Errorf("Expected %v to not equal %v", aggKey1, aggKey3)
   149  	}
   150  }
   151  
   152  // TestEventAggregatorByReasonMessageFunc validates the proper output for an aggregate message
   153  func TestEventAggregatorByReasonMessageFunc(t *testing.T) {
   154  	expectedPrefix := "(combined from similar events): "
   155  	event1 := makeEvent("end-of-world", "it was fun", makeObjectReference("Pod", "pod1", "other"))
   156  	actual := EventAggregatorByReasonMessageFunc(&event1)
   157  	if !strings.HasPrefix(actual, expectedPrefix) {
   158  		t.Errorf("Expected %v to begin with prefix %v", actual, expectedPrefix)
   159  	}
   160  }
   161  
   162  // TestEventCorrelator validates proper counting, aggregation of events
   163  func TestEventCorrelator(t *testing.T) {
   164  	firstEvent := makeEvent("first", "i am first", makeObjectReference("Pod", "my-pod", "my-ns"))
   165  	duplicateEvent := makeEvent("duplicate", "me again", makeObjectReference("Pod", "my-pod", "my-ns"))
   166  	uniqueEvent := makeEvent("unique", "snowflake", makeObjectReference("Pod", "my-pod", "my-ns"))
   167  	similarEvent := makeEvent("similar", "similar message", makeObjectReference("Pod", "my-pod", "my-ns"))
   168  	similarEvent.InvolvedObject.FieldPath = "spec.containers{container1}"
   169  	aggregateEvent := makeEvent(similarEvent.Reason, EventAggregatorByReasonMessageFunc(&similarEvent), similarEvent.InvolvedObject)
   170  	similarButDifferentContainerEvent := similarEvent
   171  	similarButDifferentContainerEvent.InvolvedObject.FieldPath = "spec.containers{container2}"
   172  	scenario := map[string]struct {
   173  		previousEvents  []v1.Event
   174  		newEvent        v1.Event
   175  		expectedEvent   v1.Event
   176  		intervalSeconds int
   177  		expectedSkip    bool
   178  	}{
   179  		"create-a-single-event": {
   180  			previousEvents:  []v1.Event{},
   181  			newEvent:        firstEvent,
   182  			expectedEvent:   setCount(firstEvent, 1),
   183  			intervalSeconds: 5,
   184  		},
   185  		"the-same-event-should-just-count": {
   186  			previousEvents:  makeEvents(1, duplicateEvent),
   187  			newEvent:        duplicateEvent,
   188  			expectedEvent:   setCount(duplicateEvent, 2),
   189  			intervalSeconds: 5,
   190  		},
   191  		"the-same-event-should-just-count-even-if-more-than-aggregate": {
   192  			previousEvents:  makeEvents(defaultAggregateMaxEvents, duplicateEvent),
   193  			newEvent:        duplicateEvent,
   194  			expectedEvent:   setCount(duplicateEvent, defaultAggregateMaxEvents+1),
   195  			intervalSeconds: 30, // larger interval induces aggregation but not spam.
   196  		},
   197  		"the-same-event-is-spam-if-happens-too-frequently": {
   198  			previousEvents:  makeEvents(defaultSpamBurst+1, duplicateEvent),
   199  			newEvent:        duplicateEvent,
   200  			expectedSkip:    true,
   201  			intervalSeconds: 1,
   202  		},
   203  		"create-many-unique-events": {
   204  			previousEvents:  makeUniqueEvents(30),
   205  			newEvent:        uniqueEvent,
   206  			expectedEvent:   setCount(uniqueEvent, 1),
   207  			intervalSeconds: 5,
   208  		},
   209  		"similar-events-should-aggregate-event": {
   210  			previousEvents:  makeSimilarEvents(defaultAggregateMaxEvents-1, similarEvent, similarEvent.Message),
   211  			newEvent:        similarEvent,
   212  			expectedEvent:   setCount(aggregateEvent, 1),
   213  			intervalSeconds: 5,
   214  		},
   215  		"similar-events-many-times-should-count-the-aggregate": {
   216  			previousEvents:  makeSimilarEvents(defaultAggregateMaxEvents, similarEvent, similarEvent.Message),
   217  			newEvent:        similarEvent,
   218  			expectedEvent:   setCount(aggregateEvent, 2),
   219  			intervalSeconds: 5,
   220  		},
   221  		"events-from-different-containers-do-not-aggregate": {
   222  			previousEvents:  makeEvents(1, similarButDifferentContainerEvent),
   223  			newEvent:        similarEvent,
   224  			expectedEvent:   setCount(similarEvent, 1),
   225  			intervalSeconds: 5,
   226  		},
   227  		"similar-events-whose-interval-is-greater-than-aggregate-interval-do-not-aggregate": {
   228  			previousEvents:  makeSimilarEvents(defaultAggregateMaxEvents-1, similarEvent, similarEvent.Message),
   229  			newEvent:        similarEvent,
   230  			expectedEvent:   setCount(similarEvent, 1),
   231  			intervalSeconds: defaultAggregateIntervalInSeconds,
   232  		},
   233  	}
   234  
   235  	for testScenario, testInput := range scenario {
   236  		eventInterval := time.Duration(testInput.intervalSeconds) * time.Second
   237  		clock := clock.IntervalClock{Time: time.Now(), Duration: eventInterval}
   238  		correlator := NewEventCorrelator(&clock)
   239  		for i := range testInput.previousEvents {
   240  			event := testInput.previousEvents[i]
   241  			now := metav1.NewTime(clock.Now())
   242  			event.FirstTimestamp = now
   243  			event.LastTimestamp = now
   244  			result, err := correlator.EventCorrelate(&event)
   245  			if err != nil {
   246  				t.Errorf("scenario %v: unexpected error playing back prevEvents %v", testScenario, err)
   247  			}
   248  			// if we are skipping the event, we can avoid updating state
   249  			if !result.Skip {
   250  				correlator.UpdateState(result.Event)
   251  			}
   252  		}
   253  
   254  		// update the input to current clock value
   255  		now := metav1.NewTime(clock.Now())
   256  		testInput.newEvent.FirstTimestamp = now
   257  		testInput.newEvent.LastTimestamp = now
   258  		result, err := correlator.EventCorrelate(&testInput.newEvent)
   259  		if err != nil {
   260  			t.Errorf("scenario %v: unexpected error correlating input event %v", testScenario, err)
   261  		}
   262  
   263  		// verify we did not get skip from filter function unexpectedly...
   264  		if result.Skip != testInput.expectedSkip {
   265  			t.Errorf("scenario %v: expected skip %v, but got %v", testScenario, testInput.expectedSkip, result.Skip)
   266  			continue
   267  		}
   268  
   269  		// we wanted to actually skip, so no event is needed to validate
   270  		if testInput.expectedSkip {
   271  			continue
   272  		}
   273  
   274  		// validate event
   275  		_, err = validateEvent(testScenario, result.Event, &testInput.expectedEvent, t)
   276  		if err != nil {
   277  			t.Errorf("scenario %v: unexpected error validating result %v", testScenario, err)
   278  		}
   279  	}
   280  }