k8s.io/client-go@v0.31.1/tools/events/event_broadcaster.go (about) 1 /* 2 Copyright 2019 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 events 18 19 import ( 20 "context" 21 "fmt" 22 "os" 23 "sync" 24 "time" 25 26 corev1 "k8s.io/api/core/v1" 27 eventsv1 "k8s.io/api/events/v1" 28 "k8s.io/apimachinery/pkg/api/errors" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/apimachinery/pkg/util/json" 33 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 34 "k8s.io/apimachinery/pkg/util/strategicpatch" 35 "k8s.io/apimachinery/pkg/util/wait" 36 "k8s.io/apimachinery/pkg/watch" 37 clientset "k8s.io/client-go/kubernetes" 38 "k8s.io/client-go/kubernetes/scheme" 39 typedv1core "k8s.io/client-go/kubernetes/typed/core/v1" 40 typedeventsv1 "k8s.io/client-go/kubernetes/typed/events/v1" 41 restclient "k8s.io/client-go/rest" 42 "k8s.io/client-go/tools/record" 43 "k8s.io/client-go/tools/record/util" 44 "k8s.io/klog/v2" 45 "k8s.io/utils/clock" 46 ) 47 48 const ( 49 maxTriesPerEvent = 12 50 finishTime = 6 * time.Minute 51 refreshTime = 30 * time.Minute 52 maxQueuedEvents = 1000 53 ) 54 55 var defaultSleepDuration = 10 * time.Second 56 57 // TODO: validate impact of copying and investigate hashing 58 type eventKey struct { 59 eventType string 60 action string 61 reason string 62 reportingController string 63 reportingInstance string 64 regarding corev1.ObjectReference 65 related corev1.ObjectReference 66 } 67 68 type eventBroadcasterImpl struct { 69 *watch.Broadcaster 70 mu sync.Mutex 71 eventCache map[eventKey]*eventsv1.Event 72 sleepDuration time.Duration 73 sink EventSink 74 } 75 76 // EventSinkImpl wraps EventsV1Interface to implement EventSink. 77 // TODO: this makes it easier for testing purpose and masks the logic of performing API calls. 78 // Note that rollbacking to raw clientset should also be transparent. 79 type EventSinkImpl struct { 80 Interface typedeventsv1.EventsV1Interface 81 } 82 83 // Create takes the representation of a event and creates it. Returns the server's representation of the event, and an error, if there is any. 84 func (e *EventSinkImpl) Create(ctx context.Context, event *eventsv1.Event) (*eventsv1.Event, error) { 85 if event.Namespace == "" { 86 return nil, fmt.Errorf("can't create an event with empty namespace") 87 } 88 return e.Interface.Events(event.Namespace).Create(ctx, event, metav1.CreateOptions{}) 89 } 90 91 // Update takes the representation of a event and updates it. Returns the server's representation of the event, and an error, if there is any. 92 func (e *EventSinkImpl) Update(ctx context.Context, event *eventsv1.Event) (*eventsv1.Event, error) { 93 if event.Namespace == "" { 94 return nil, fmt.Errorf("can't update an event with empty namespace") 95 } 96 return e.Interface.Events(event.Namespace).Update(ctx, event, metav1.UpdateOptions{}) 97 } 98 99 // Patch applies the patch and returns the patched event, and an error, if there is any. 100 func (e *EventSinkImpl) Patch(ctx context.Context, event *eventsv1.Event, data []byte) (*eventsv1.Event, error) { 101 if event.Namespace == "" { 102 return nil, fmt.Errorf("can't patch an event with empty namespace") 103 } 104 return e.Interface.Events(event.Namespace).Patch(ctx, event.Name, types.StrategicMergePatchType, data, metav1.PatchOptions{}) 105 } 106 107 // NewBroadcaster Creates a new event broadcaster. 108 func NewBroadcaster(sink EventSink) EventBroadcaster { 109 return newBroadcaster(sink, defaultSleepDuration, map[eventKey]*eventsv1.Event{}) 110 } 111 112 // NewBroadcasterForTest Creates a new event broadcaster for test purposes. 113 func newBroadcaster(sink EventSink, sleepDuration time.Duration, eventCache map[eventKey]*eventsv1.Event) EventBroadcaster { 114 return &eventBroadcasterImpl{ 115 Broadcaster: watch.NewBroadcaster(maxQueuedEvents, watch.DropIfChannelFull), 116 eventCache: eventCache, 117 sleepDuration: sleepDuration, 118 sink: sink, 119 } 120 } 121 122 func (e *eventBroadcasterImpl) Shutdown() { 123 e.Broadcaster.Shutdown() 124 } 125 126 // refreshExistingEventSeries refresh events TTL 127 func (e *eventBroadcasterImpl) refreshExistingEventSeries(ctx context.Context) { 128 // TODO: Investigate whether lock contention won't be a problem 129 e.mu.Lock() 130 defer e.mu.Unlock() 131 for isomorphicKey, event := range e.eventCache { 132 if event.Series != nil { 133 if recordedEvent, retry := recordEvent(ctx, e.sink, event); !retry { 134 if recordedEvent != nil { 135 e.eventCache[isomorphicKey] = recordedEvent 136 } 137 } 138 } 139 } 140 } 141 142 // finishSeries checks if a series has ended and either: 143 // - write final count to the apiserver 144 // - delete a singleton event (i.e. series field is nil) from the cache 145 func (e *eventBroadcasterImpl) finishSeries(ctx context.Context) { 146 // TODO: Investigate whether lock contention won't be a problem 147 e.mu.Lock() 148 defer e.mu.Unlock() 149 for isomorphicKey, event := range e.eventCache { 150 eventSerie := event.Series 151 if eventSerie != nil { 152 if eventSerie.LastObservedTime.Time.Before(time.Now().Add(-finishTime)) { 153 if _, retry := recordEvent(ctx, e.sink, event); !retry { 154 delete(e.eventCache, isomorphicKey) 155 } 156 } 157 } else if event.EventTime.Time.Before(time.Now().Add(-finishTime)) { 158 delete(e.eventCache, isomorphicKey) 159 } 160 } 161 } 162 163 // NewRecorder returns an EventRecorder that records events with the given event source. 164 func (e *eventBroadcasterImpl) NewRecorder(scheme *runtime.Scheme, reportingController string) EventRecorderLogger { 165 hostname, _ := os.Hostname() 166 reportingInstance := reportingController + "-" + hostname 167 return &recorderImplLogger{recorderImpl: &recorderImpl{scheme, reportingController, reportingInstance, e.Broadcaster, clock.RealClock{}}, logger: klog.Background()} 168 } 169 170 func (e *eventBroadcasterImpl) recordToSink(ctx context.Context, event *eventsv1.Event, clock clock.Clock) { 171 // Make a copy before modification, because there could be multiple listeners. 172 eventCopy := event.DeepCopy() 173 go func() { 174 evToRecord := func() *eventsv1.Event { 175 e.mu.Lock() 176 defer e.mu.Unlock() 177 eventKey := getKey(eventCopy) 178 isomorphicEvent, isIsomorphic := e.eventCache[eventKey] 179 if isIsomorphic { 180 if isomorphicEvent.Series != nil { 181 isomorphicEvent.Series.Count++ 182 isomorphicEvent.Series.LastObservedTime = metav1.MicroTime{Time: clock.Now()} 183 return nil 184 } 185 isomorphicEvent.Series = &eventsv1.EventSeries{ 186 Count: 2, 187 LastObservedTime: metav1.MicroTime{Time: clock.Now()}, 188 } 189 // Make a copy of the Event to make sure that recording it 190 // doesn't mess with the object stored in cache. 191 return isomorphicEvent.DeepCopy() 192 } 193 e.eventCache[eventKey] = eventCopy 194 // Make a copy of the Event to make sure that recording it doesn't 195 // mess with the object stored in cache. 196 return eventCopy.DeepCopy() 197 }() 198 if evToRecord != nil { 199 // TODO: Add a metric counting the number of recording attempts 200 e.attemptRecording(ctx, evToRecord) 201 // We don't want the new recorded Event to be reflected in the 202 // client's cache because server-side mutations could mess with the 203 // aggregation mechanism used by the client. 204 } 205 }() 206 } 207 208 func (e *eventBroadcasterImpl) attemptRecording(ctx context.Context, event *eventsv1.Event) { 209 tries := 0 210 for { 211 if _, retry := recordEvent(ctx, e.sink, event); !retry { 212 return 213 } 214 tries++ 215 if tries >= maxTriesPerEvent { 216 klog.FromContext(ctx).Error(nil, "Unable to write event (retry limit exceeded!)", "event", event) 217 return 218 } 219 // Randomize sleep so that various clients won't all be 220 // synced up if the master goes down. Give up when 221 // the context is canceled. 222 select { 223 case <-ctx.Done(): 224 return 225 case <-time.After(wait.Jitter(e.sleepDuration, 0.25)): 226 } 227 } 228 } 229 230 func recordEvent(ctx context.Context, sink EventSink, event *eventsv1.Event) (*eventsv1.Event, bool) { 231 var newEvent *eventsv1.Event 232 var err error 233 isEventSeries := event.Series != nil 234 if isEventSeries { 235 patch, patchBytesErr := createPatchBytesForSeries(event) 236 if patchBytesErr != nil { 237 klog.FromContext(ctx).Error(patchBytesErr, "Unable to calculate diff, no merge is possible") 238 return nil, false 239 } 240 newEvent, err = sink.Patch(ctx, event, patch) 241 } 242 // Update can fail because the event may have been removed and it no longer exists. 243 if !isEventSeries || (isEventSeries && util.IsKeyNotFoundError(err)) { 244 // Making sure that ResourceVersion is empty on creation 245 event.ResourceVersion = "" 246 newEvent, err = sink.Create(ctx, event) 247 } 248 if err == nil { 249 return newEvent, false 250 } 251 // If we can't contact the server, then hold everything while we keep trying. 252 // Otherwise, something about the event is malformed and we should abandon it. 253 switch err.(type) { 254 case *restclient.RequestConstructionError: 255 // We will construct the request the same next time, so don't keep trying. 256 klog.FromContext(ctx).Error(err, "Unable to construct event (will not retry!)", "event", event) 257 return nil, false 258 case *errors.StatusError: 259 if errors.IsAlreadyExists(err) { 260 // If we tried to create an Event from an EventSerie, it means that 261 // the original Patch request failed because the Event we were 262 // trying to patch didn't exist. If the creation failed because the 263 // Event now exists, it is safe to retry. This occurs when a new 264 // Event is emitted twice in a very short period of time. 265 if isEventSeries { 266 return nil, true 267 } 268 klog.FromContext(ctx).V(5).Info("Server rejected event (will not retry!)", "event", event, "err", err) 269 } else { 270 klog.FromContext(ctx).Error(err, "Server rejected event (will not retry!)", "event", event) 271 } 272 return nil, false 273 case *errors.UnexpectedObjectError: 274 // We don't expect this; it implies the server's response didn't match a 275 // known pattern. Go ahead and retry. 276 default: 277 // This case includes actual http transport errors. Go ahead and retry. 278 } 279 klog.FromContext(ctx).Error(err, "Unable to write event (may retry after sleeping)") 280 return nil, true 281 } 282 283 func createPatchBytesForSeries(event *eventsv1.Event) ([]byte, error) { 284 oldEvent := event.DeepCopy() 285 oldEvent.Series = nil 286 oldData, err := json.Marshal(oldEvent) 287 if err != nil { 288 return nil, err 289 } 290 newData, err := json.Marshal(event) 291 if err != nil { 292 return nil, err 293 } 294 return strategicpatch.CreateTwoWayMergePatch(oldData, newData, eventsv1.Event{}) 295 } 296 297 func getKey(event *eventsv1.Event) eventKey { 298 key := eventKey{ 299 eventType: event.Type, 300 action: event.Action, 301 reason: event.Reason, 302 reportingController: event.ReportingController, 303 reportingInstance: event.ReportingInstance, 304 regarding: event.Regarding, 305 } 306 if event.Related != nil { 307 key.related = *event.Related 308 } 309 return key 310 } 311 312 // StartStructuredLogging starts sending events received from this EventBroadcaster to the structured logging function. 313 // The return value can be ignored or used to stop recording, if desired. 314 // TODO: this function should also return an error. 315 // 316 // Deprecated: use StartLogging instead. 317 func (e *eventBroadcasterImpl) StartStructuredLogging(verbosity klog.Level) func() { 318 logger := klog.Background().V(int(verbosity)) 319 stopWatcher, err := e.StartLogging(logger) 320 if err != nil { 321 logger.Error(err, "Failed to start event watcher") 322 return func() {} 323 } 324 return stopWatcher 325 } 326 327 // StartLogging starts sending events received from this EventBroadcaster to the structured logger. 328 // To adjust verbosity, use the logger's V method (i.e. pass `logger.V(3)` instead of `logger`). 329 // The returned function can be ignored or used to stop recording, if desired. 330 func (e *eventBroadcasterImpl) StartLogging(logger klog.Logger) (func(), error) { 331 return e.StartEventWatcher( 332 func(obj runtime.Object) { 333 event, ok := obj.(*eventsv1.Event) 334 if !ok { 335 logger.Error(nil, "unexpected type, expected eventsv1.Event") 336 return 337 } 338 logger.Info("Event occurred", "object", klog.KRef(event.Regarding.Namespace, event.Regarding.Name), "kind", event.Regarding.Kind, "apiVersion", event.Regarding.APIVersion, "type", event.Type, "reason", event.Reason, "action", event.Action, "note", event.Note) 339 }) 340 } 341 342 // StartEventWatcher starts sending events received from this EventBroadcaster to the given event handler function. 343 // The return value is used to stop recording 344 func (e *eventBroadcasterImpl) StartEventWatcher(eventHandler func(event runtime.Object)) (func(), error) { 345 watcher, err := e.Watch() 346 if err != nil { 347 return nil, err 348 } 349 go func() { 350 defer utilruntime.HandleCrash() 351 for { 352 watchEvent, ok := <-watcher.ResultChan() 353 if !ok { 354 return 355 } 356 eventHandler(watchEvent.Object) 357 } 358 }() 359 return watcher.Stop, nil 360 } 361 362 func (e *eventBroadcasterImpl) startRecordingEvents(ctx context.Context) error { 363 eventHandler := func(obj runtime.Object) { 364 event, ok := obj.(*eventsv1.Event) 365 if !ok { 366 klog.FromContext(ctx).Error(nil, "unexpected type, expected eventsv1.Event") 367 return 368 } 369 e.recordToSink(ctx, event, clock.RealClock{}) 370 } 371 stopWatcher, err := e.StartEventWatcher(eventHandler) 372 if err != nil { 373 return err 374 } 375 go func() { 376 <-ctx.Done() 377 stopWatcher() 378 }() 379 return nil 380 } 381 382 // StartRecordingToSink starts sending events received from the specified eventBroadcaster to the given sink. 383 // Deprecated: use StartRecordingToSinkWithContext instead. 384 func (e *eventBroadcasterImpl) StartRecordingToSink(stopCh <-chan struct{}) { 385 err := e.StartRecordingToSinkWithContext(wait.ContextForChannel(stopCh)) 386 if err != nil { 387 klog.Background().Error(err, "Failed to start recording to sink") 388 } 389 } 390 391 // StartRecordingToSinkWithContext starts sending events received from the specified eventBroadcaster to the given sink. 392 func (e *eventBroadcasterImpl) StartRecordingToSinkWithContext(ctx context.Context) error { 393 go wait.UntilWithContext(ctx, e.refreshExistingEventSeries, refreshTime) 394 go wait.UntilWithContext(ctx, e.finishSeries, finishTime) 395 return e.startRecordingEvents(ctx) 396 } 397 398 type eventBroadcasterAdapterImpl struct { 399 coreClient typedv1core.EventsGetter 400 coreBroadcaster record.EventBroadcaster 401 eventsv1Client typedeventsv1.EventsV1Interface 402 eventsv1Broadcaster EventBroadcaster 403 } 404 405 // NewEventBroadcasterAdapter creates a wrapper around new and legacy broadcasters to simplify 406 // migration of individual components to the new Event API. 407 // 408 //logcheck:context // NewEventBroadcasterAdapterWithContext should be used instead because record.NewBroadcaster is called and works better when a context is supplied (contextual logging, cancellation). 409 func NewEventBroadcasterAdapter(client clientset.Interface) EventBroadcasterAdapter { 410 return NewEventBroadcasterAdapterWithContext(context.Background(), client) 411 } 412 413 // NewEventBroadcasterAdapterWithContext creates a wrapper around new and legacy broadcasters to simplify 414 // migration of individual components to the new Event API. 415 func NewEventBroadcasterAdapterWithContext(ctx context.Context, client clientset.Interface) EventBroadcasterAdapter { 416 eventClient := &eventBroadcasterAdapterImpl{} 417 if _, err := client.Discovery().ServerResourcesForGroupVersion(eventsv1.SchemeGroupVersion.String()); err == nil { 418 eventClient.eventsv1Client = client.EventsV1() 419 eventClient.eventsv1Broadcaster = NewBroadcaster(&EventSinkImpl{Interface: eventClient.eventsv1Client}) 420 } 421 // Even though there can soon exist cases when coreBroadcaster won't really be needed, 422 // we create it unconditionally because its overhead is minor and will simplify using usage 423 // patterns of this library in all components. 424 eventClient.coreClient = client.CoreV1() 425 eventClient.coreBroadcaster = record.NewBroadcaster(record.WithContext(ctx)) 426 return eventClient 427 } 428 429 // StartRecordingToSink starts sending events received from the specified eventBroadcaster to the given sink. 430 func (e *eventBroadcasterAdapterImpl) StartRecordingToSink(stopCh <-chan struct{}) { 431 if e.eventsv1Broadcaster != nil && e.eventsv1Client != nil { 432 e.eventsv1Broadcaster.StartRecordingToSink(stopCh) 433 } 434 if e.coreBroadcaster != nil && e.coreClient != nil { 435 e.coreBroadcaster.StartRecordingToSink(&typedv1core.EventSinkImpl{Interface: e.coreClient.Events("")}) 436 } 437 } 438 439 func (e *eventBroadcasterAdapterImpl) NewRecorder(name string) EventRecorderLogger { 440 if e.eventsv1Broadcaster != nil && e.eventsv1Client != nil { 441 return e.eventsv1Broadcaster.NewRecorder(scheme.Scheme, name) 442 } 443 return record.NewEventRecorderAdapter(e.DeprecatedNewLegacyRecorder(name)) 444 } 445 446 func (e *eventBroadcasterAdapterImpl) DeprecatedNewLegacyRecorder(name string) record.EventRecorderLogger { 447 return e.coreBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: name}) 448 } 449 450 func (e *eventBroadcasterAdapterImpl) Shutdown() { 451 if e.coreBroadcaster != nil { 452 e.coreBroadcaster.Shutdown() 453 } 454 if e.eventsv1Broadcaster != nil { 455 e.eventsv1Broadcaster.Shutdown() 456 } 457 }