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 }