k8s.io/client-go@v0.31.1/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 "github.com/google/go-cmp/cmp" 27 v1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 testclocks "k8s.io/utils/clock/testing" 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 119 recvEvent.ReportingController = expectedEvent.ReportingController 120 121 // Check that name has the right prefix. 122 if n, en := recvEvent.Name, expectedEvent.Name; !strings.HasPrefix(n, en) { 123 t.Errorf("%v - Name '%v' does not contain prefix '%v'", messagePrefix, n, en) 124 } 125 recvEvent.Name = expectedEvent.Name 126 if e, a := expectedEvent, &recvEvent; !reflect.DeepEqual(e, a) { 127 t.Errorf("%v - diff: %s", messagePrefix, cmp.Diff(e, a)) 128 } 129 recvEvent.FirstTimestamp = actualFirstTimestamp 130 recvEvent.LastTimestamp = actualLastTimestamp 131 return actualEvent, nil 132 } 133 134 // TestEventAggregatorByReasonFunc ensures that two events are aggregated if they vary only by event.message 135 func TestEventAggregatorByReasonFunc(t *testing.T) { 136 event1 := makeEvent("end-of-world", "it was fun", makeObjectReference("Pod", "pod1", "other")) 137 event2 := makeEvent("end-of-world", "it was awful", makeObjectReference("Pod", "pod1", "other")) 138 event3 := makeEvent("nevermind", "it was a bug", makeObjectReference("Pod", "pod1", "other")) 139 140 aggKey1, localKey1 := EventAggregatorByReasonFunc(&event1) 141 aggKey2, localKey2 := EventAggregatorByReasonFunc(&event2) 142 aggKey3, _ := EventAggregatorByReasonFunc(&event3) 143 144 if aggKey1 != aggKey2 { 145 t.Errorf("Expected %v equal %v", aggKey1, aggKey2) 146 } 147 if localKey1 == localKey2 { 148 t.Errorf("Expected %v to not equal %v", aggKey1, aggKey3) 149 } 150 if aggKey1 == aggKey3 { 151 t.Errorf("Expected %v to not equal %v", aggKey1, aggKey3) 152 } 153 } 154 155 // TestEventAggregatorByReasonMessageFunc validates the proper output for an aggregate message 156 func TestEventAggregatorByReasonMessageFunc(t *testing.T) { 157 expectedPrefix := "(combined from similar events): " 158 event1 := makeEvent("end-of-world", "it was fun", makeObjectReference("Pod", "pod1", "other")) 159 actual := EventAggregatorByReasonMessageFunc(&event1) 160 if !strings.HasPrefix(actual, expectedPrefix) { 161 t.Errorf("Expected %v to begin with prefix %v", actual, expectedPrefix) 162 } 163 } 164 165 // TestEventCorrelator validates proper counting, aggregation of events 166 func TestEventCorrelator(t *testing.T) { 167 firstEvent := makeEvent("first", "i am first", makeObjectReference("Pod", "my-pod", "my-ns")) 168 duplicateEvent := makeEvent("duplicate", "me again", makeObjectReference("Pod", "my-pod", "my-ns")) 169 uniqueEvent := makeEvent("unique", "snowflake", makeObjectReference("Pod", "my-pod", "my-ns")) 170 similarEvent := makeEvent("similar", "similar message", makeObjectReference("Pod", "my-pod", "my-ns")) 171 similarEvent.InvolvedObject.FieldPath = "spec.containers{container1}" 172 aggregateEvent := makeEvent(similarEvent.Reason, EventAggregatorByReasonMessageFunc(&similarEvent), similarEvent.InvolvedObject) 173 similarButDifferentContainerEvent := similarEvent 174 similarButDifferentContainerEvent.InvolvedObject.FieldPath = "spec.containers{container2}" 175 scenario := map[string]struct { 176 previousEvents []v1.Event 177 newEvent v1.Event 178 expectedEvent v1.Event 179 intervalSeconds int 180 expectedSkip bool 181 }{ 182 "create-a-single-event": { 183 previousEvents: []v1.Event{}, 184 newEvent: firstEvent, 185 expectedEvent: setCount(firstEvent, 1), 186 intervalSeconds: 5, 187 }, 188 "the-same-event-should-just-count": { 189 previousEvents: makeEvents(1, duplicateEvent), 190 newEvent: duplicateEvent, 191 expectedEvent: setCount(duplicateEvent, 2), 192 intervalSeconds: 5, 193 }, 194 "the-same-event-should-just-count-even-if-more-than-aggregate": { 195 previousEvents: makeEvents(defaultAggregateMaxEvents, duplicateEvent), 196 newEvent: duplicateEvent, 197 expectedEvent: setCount(duplicateEvent, defaultAggregateMaxEvents+1), 198 intervalSeconds: 30, // larger interval induces aggregation but not spam. 199 }, 200 "the-same-event-is-spam-if-happens-too-frequently": { 201 previousEvents: makeEvents(defaultSpamBurst+1, duplicateEvent), 202 newEvent: duplicateEvent, 203 expectedSkip: true, 204 intervalSeconds: 1, 205 }, 206 "create-many-unique-events": { 207 previousEvents: makeUniqueEvents(30), 208 newEvent: uniqueEvent, 209 expectedEvent: setCount(uniqueEvent, 1), 210 intervalSeconds: 5, 211 }, 212 "similar-events-should-aggregate-event": { 213 previousEvents: makeSimilarEvents(defaultAggregateMaxEvents-1, similarEvent, similarEvent.Message), 214 newEvent: similarEvent, 215 expectedEvent: setCount(aggregateEvent, 1), 216 intervalSeconds: 5, 217 }, 218 "similar-events-many-times-should-count-the-aggregate": { 219 previousEvents: makeSimilarEvents(defaultAggregateMaxEvents, similarEvent, similarEvent.Message), 220 newEvent: similarEvent, 221 expectedEvent: setCount(aggregateEvent, 2), 222 intervalSeconds: 5, 223 }, 224 "events-from-different-containers-do-not-aggregate": { 225 previousEvents: makeEvents(1, similarButDifferentContainerEvent), 226 newEvent: similarEvent, 227 expectedEvent: setCount(similarEvent, 1), 228 intervalSeconds: 5, 229 }, 230 "similar-events-whose-interval-is-greater-than-aggregate-interval-do-not-aggregate": { 231 previousEvents: makeSimilarEvents(defaultAggregateMaxEvents-1, similarEvent, similarEvent.Message), 232 newEvent: similarEvent, 233 expectedEvent: setCount(similarEvent, 1), 234 intervalSeconds: defaultAggregateIntervalInSeconds, 235 }, 236 } 237 238 for testScenario, testInput := range scenario { 239 eventInterval := time.Duration(testInput.intervalSeconds) * time.Second 240 clock := testclocks.SimpleIntervalClock{Time: time.Now(), Duration: eventInterval} 241 correlator := NewEventCorrelator(&clock) 242 for i := range testInput.previousEvents { 243 event := testInput.previousEvents[i] 244 now := metav1.NewTime(clock.Now()) 245 event.FirstTimestamp = now 246 event.LastTimestamp = now 247 result, err := correlator.EventCorrelate(&event) 248 if err != nil { 249 t.Errorf("scenario %v: unexpected error playing back prevEvents %v", testScenario, err) 250 } 251 // if we are skipping the event, we can avoid updating state 252 if !result.Skip { 253 correlator.UpdateState(result.Event) 254 } 255 } 256 257 // update the input to current clock value 258 now := metav1.NewTime(clock.Now()) 259 testInput.newEvent.FirstTimestamp = now 260 testInput.newEvent.LastTimestamp = now 261 result, err := correlator.EventCorrelate(&testInput.newEvent) 262 if err != nil { 263 t.Errorf("scenario %v: unexpected error correlating input event %v", testScenario, err) 264 } 265 266 // verify we did not get skip from filter function unexpectedly... 267 if result.Skip != testInput.expectedSkip { 268 t.Errorf("scenario %v: expected skip %v, but got %v", testScenario, testInput.expectedSkip, result.Skip) 269 continue 270 } 271 272 // we wanted to actually skip, so no event is needed to validate 273 if testInput.expectedSkip { 274 continue 275 } 276 277 // validate event 278 _, err = validateEvent(testScenario, result.Event, &testInput.expectedEvent, t) 279 if err != nil { 280 t.Errorf("scenario %v: unexpected error validating result %v", testScenario, err) 281 } 282 } 283 } 284 285 func TestEventSpamFilter(t *testing.T) { 286 spamKeyFuncBasedOnObjectsAndReason := func(e *v1.Event) string { 287 return strings.Join([]string{ 288 e.Source.Component, 289 e.Source.Host, 290 e.InvolvedObject.Kind, 291 e.InvolvedObject.Namespace, 292 e.InvolvedObject.Name, 293 string(e.InvolvedObject.UID), 294 e.InvolvedObject.APIVersion, 295 e.Reason, 296 }, 297 "") 298 } 299 burstSize := 1 300 eventInterval := time.Duration(1) * time.Second 301 originalEvent := makeEvent("original", "i am first", makeObjectReference("Pod", "my-pod", "my-ns")) 302 differentReasonEvent := makeEvent("duplicate", "me again", makeObjectReference("Pod", "my-pod", "my-ns")) 303 spamEvent := makeEvent("original", "me again", makeObjectReference("Pod", "my-pod", "my-ns")) 304 testCases := map[string]struct { 305 newEvent v1.Event 306 expectedEvent v1.Event 307 expectedSkip bool 308 spamKeyFunc EventSpamKeyFunc 309 }{ 310 "event should be reported as spam if object reference is the same for default spam filter": { 311 newEvent: differentReasonEvent, 312 expectedSkip: true, 313 }, 314 "event should not be reported as spam if object reference is the same, but reason is different for custom spam filter": { 315 newEvent: differentReasonEvent, 316 expectedEvent: differentReasonEvent, 317 expectedSkip: false, 318 spamKeyFunc: spamKeyFuncBasedOnObjectsAndReason, 319 }, 320 "event should be reported as spam if object reference and reason is the same, but message is different for custom spam filter": { 321 newEvent: spamEvent, 322 expectedSkip: true, 323 spamKeyFunc: spamKeyFuncBasedOnObjectsAndReason, 324 }, 325 } 326 327 for testDescription, testInput := range testCases { 328 c := testclocks.SimpleIntervalClock{Time: time.Now(), Duration: eventInterval} 329 correlator := NewEventCorrelatorWithOptions(CorrelatorOptions{ 330 Clock: &c, 331 SpamKeyFunc: testInput.spamKeyFunc, 332 BurstSize: burstSize, 333 }) 334 // emitting original event 335 result, err := correlator.EventCorrelate(&originalEvent) 336 if err != nil { 337 t.Errorf("scenario %v: unexpected error correlating originalEvent %v", testDescription, err) 338 } 339 // if we are skipping the event, we can avoid updating state 340 if !result.Skip { 341 correlator.UpdateState(result.Event) 342 } 343 344 result, err = correlator.EventCorrelate(&testInput.newEvent) 345 if err != nil { 346 t.Errorf("scenario %v: unexpected error correlating input event %v", testDescription, err) 347 } 348 349 // verify we did not get skip from filter function unexpectedly... 350 if result.Skip != testInput.expectedSkip { 351 t.Errorf("scenario %v: expected skip %v, but got %v", testDescription, testInput.expectedSkip, result.Skip) 352 continue 353 } 354 355 // we wanted to actually skip, so no event is needed to validate 356 if testInput.expectedSkip { 357 continue 358 } 359 360 // validate event 361 _, err = validateEvent(testDescription, result.Event, &testInput.expectedEvent, t) 362 if err != nil { 363 t.Errorf("scenario %v: unexpected error validating result %v", testDescription, err) 364 } 365 } 366 }