k8s.io/client-go@v0.22.2/tools/record/event.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 "fmt" 21 "math/rand" 22 "time" 23 24 v1 "k8s.io/api/core/v1" 25 "k8s.io/apimachinery/pkg/api/errors" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/apimachinery/pkg/util/clock" 29 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 30 "k8s.io/apimachinery/pkg/watch" 31 restclient "k8s.io/client-go/rest" 32 "k8s.io/client-go/tools/record/util" 33 ref "k8s.io/client-go/tools/reference" 34 "k8s.io/klog/v2" 35 ) 36 37 const maxTriesPerEvent = 12 38 39 var defaultSleepDuration = 10 * time.Second 40 41 const maxQueuedEvents = 1000 42 43 // EventSink knows how to store events (client.Client implements it.) 44 // EventSink must respect the namespace that will be embedded in 'event'. 45 // It is assumed that EventSink will return the same sorts of errors as 46 // pkg/client's REST client. 47 type EventSink interface { 48 Create(event *v1.Event) (*v1.Event, error) 49 Update(event *v1.Event) (*v1.Event, error) 50 Patch(oldEvent *v1.Event, data []byte) (*v1.Event, error) 51 } 52 53 // CorrelatorOptions allows you to change the default of the EventSourceObjectSpamFilter 54 // and EventAggregator in EventCorrelator 55 type CorrelatorOptions struct { 56 // The lru cache size used for both EventSourceObjectSpamFilter and the EventAggregator 57 // If not specified (zero value), the default specified in events_cache.go will be picked 58 // This means that the LRUCacheSize has to be greater than 0. 59 LRUCacheSize int 60 // The burst size used by the token bucket rate filtering in EventSourceObjectSpamFilter 61 // If not specified (zero value), the default specified in events_cache.go will be picked 62 // This means that the BurstSize has to be greater than 0. 63 BurstSize int 64 // The fill rate of the token bucket in queries per second in EventSourceObjectSpamFilter 65 // If not specified (zero value), the default specified in events_cache.go will be picked 66 // This means that the QPS has to be greater than 0. 67 QPS float32 68 // The func used by the EventAggregator to group event keys for aggregation 69 // If not specified (zero value), EventAggregatorByReasonFunc will be used 70 KeyFunc EventAggregatorKeyFunc 71 // The func used by the EventAggregator to produced aggregated message 72 // If not specified (zero value), EventAggregatorByReasonMessageFunc will be used 73 MessageFunc EventAggregatorMessageFunc 74 // The number of events in an interval before aggregation happens by the EventAggregator 75 // If not specified (zero value), the default specified in events_cache.go will be picked 76 // This means that the MaxEvents has to be greater than 0 77 MaxEvents int 78 // The amount of time in seconds that must transpire since the last occurrence of a similar event before it is considered new by the EventAggregator 79 // If not specified (zero value), the default specified in events_cache.go will be picked 80 // This means that the MaxIntervalInSeconds has to be greater than 0 81 MaxIntervalInSeconds int 82 // The clock used by the EventAggregator to allow for testing 83 // If not specified (zero value), clock.RealClock{} will be used 84 Clock clock.Clock 85 } 86 87 // EventRecorder knows how to record events on behalf of an EventSource. 88 type EventRecorder interface { 89 // Event constructs an event from the given information and puts it in the queue for sending. 90 // 'object' is the object this event is about. Event will make a reference-- or you may also 91 // pass a reference to the object directly. 92 // 'type' of this event, and can be one of Normal, Warning. New types could be added in future 93 // 'reason' is the reason this event is generated. 'reason' should be short and unique; it 94 // should be in UpperCamelCase format (starting with a capital letter). "reason" will be used 95 // to automate handling of events, so imagine people writing switch statements to handle them. 96 // You want to make that easy. 97 // 'message' is intended to be human readable. 98 // 99 // The resulting event will be created in the same namespace as the reference object. 100 Event(object runtime.Object, eventtype, reason, message string) 101 102 // Eventf is just like Event, but with Sprintf for the message field. 103 Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) 104 105 // AnnotatedEventf is just like eventf, but with annotations attached 106 AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) 107 } 108 109 // EventBroadcaster knows how to receive events and send them to any EventSink, watcher, or log. 110 type EventBroadcaster interface { 111 // StartEventWatcher starts sending events received from this EventBroadcaster to the given 112 // event handler function. The return value can be ignored or used to stop recording, if 113 // desired. 114 StartEventWatcher(eventHandler func(*v1.Event)) watch.Interface 115 116 // StartRecordingToSink starts sending events received from this EventBroadcaster to the given 117 // sink. The return value can be ignored or used to stop recording, if desired. 118 StartRecordingToSink(sink EventSink) watch.Interface 119 120 // StartLogging starts sending events received from this EventBroadcaster to the given logging 121 // function. The return value can be ignored or used to stop recording, if desired. 122 StartLogging(logf func(format string, args ...interface{})) watch.Interface 123 124 // StartStructuredLogging starts sending events received from this EventBroadcaster to the structured 125 // logging function. The return value can be ignored or used to stop recording, if desired. 126 StartStructuredLogging(verbosity klog.Level) watch.Interface 127 128 // NewRecorder returns an EventRecorder that can be used to send events to this EventBroadcaster 129 // with the event source set to the given event source. 130 NewRecorder(scheme *runtime.Scheme, source v1.EventSource) EventRecorder 131 132 // Shutdown shuts down the broadcaster 133 Shutdown() 134 } 135 136 // EventRecorderAdapter is a wrapper around a "k8s.io/client-go/tools/record".EventRecorder 137 // implementing the new "k8s.io/client-go/tools/events".EventRecorder interface. 138 type EventRecorderAdapter struct { 139 recorder EventRecorder 140 } 141 142 // NewEventRecorderAdapter returns an adapter implementing the new 143 // "k8s.io/client-go/tools/events".EventRecorder interface. 144 func NewEventRecorderAdapter(recorder EventRecorder) *EventRecorderAdapter { 145 return &EventRecorderAdapter{ 146 recorder: recorder, 147 } 148 } 149 150 // Eventf is a wrapper around v1 Eventf 151 func (a *EventRecorderAdapter) Eventf(regarding, _ runtime.Object, eventtype, reason, action, note string, args ...interface{}) { 152 a.recorder.Eventf(regarding, eventtype, reason, note, args...) 153 } 154 155 // Creates a new event broadcaster. 156 func NewBroadcaster() EventBroadcaster { 157 return &eventBroadcasterImpl{ 158 Broadcaster: watch.NewLongQueueBroadcaster(maxQueuedEvents, watch.DropIfChannelFull), 159 sleepDuration: defaultSleepDuration, 160 } 161 } 162 163 func NewBroadcasterForTests(sleepDuration time.Duration) EventBroadcaster { 164 return &eventBroadcasterImpl{ 165 Broadcaster: watch.NewLongQueueBroadcaster(maxQueuedEvents, watch.DropIfChannelFull), 166 sleepDuration: sleepDuration, 167 } 168 } 169 170 func NewBroadcasterWithCorrelatorOptions(options CorrelatorOptions) EventBroadcaster { 171 return &eventBroadcasterImpl{ 172 Broadcaster: watch.NewLongQueueBroadcaster(maxQueuedEvents, watch.DropIfChannelFull), 173 sleepDuration: defaultSleepDuration, 174 options: options, 175 } 176 } 177 178 type eventBroadcasterImpl struct { 179 *watch.Broadcaster 180 sleepDuration time.Duration 181 options CorrelatorOptions 182 } 183 184 // StartRecordingToSink starts sending events received from the specified eventBroadcaster to the given sink. 185 // The return value can be ignored or used to stop recording, if desired. 186 // TODO: make me an object with parameterizable queue length and retry interval 187 func (e *eventBroadcasterImpl) StartRecordingToSink(sink EventSink) watch.Interface { 188 eventCorrelator := NewEventCorrelatorWithOptions(e.options) 189 return e.StartEventWatcher( 190 func(event *v1.Event) { 191 recordToSink(sink, event, eventCorrelator, e.sleepDuration) 192 }) 193 } 194 195 func (e *eventBroadcasterImpl) Shutdown() { 196 e.Broadcaster.Shutdown() 197 } 198 199 func recordToSink(sink EventSink, event *v1.Event, eventCorrelator *EventCorrelator, sleepDuration time.Duration) { 200 // Make a copy before modification, because there could be multiple listeners. 201 // Events are safe to copy like this. 202 eventCopy := *event 203 event = &eventCopy 204 result, err := eventCorrelator.EventCorrelate(event) 205 if err != nil { 206 utilruntime.HandleError(err) 207 } 208 if result.Skip { 209 return 210 } 211 tries := 0 212 for { 213 if recordEvent(sink, result.Event, result.Patch, result.Event.Count > 1, eventCorrelator) { 214 break 215 } 216 tries++ 217 if tries >= maxTriesPerEvent { 218 klog.Errorf("Unable to write event '%#v' (retry limit exceeded!)", event) 219 break 220 } 221 // Randomize the first sleep so that various clients won't all be 222 // synced up if the master goes down. 223 if tries == 1 { 224 time.Sleep(time.Duration(float64(sleepDuration) * rand.Float64())) 225 } else { 226 time.Sleep(sleepDuration) 227 } 228 } 229 } 230 231 // recordEvent attempts to write event to a sink. It returns true if the event 232 // was successfully recorded or discarded, false if it should be retried. 233 // If updateExistingEvent is false, it creates a new event, otherwise it updates 234 // existing event. 235 func recordEvent(sink EventSink, event *v1.Event, patch []byte, updateExistingEvent bool, eventCorrelator *EventCorrelator) bool { 236 var newEvent *v1.Event 237 var err error 238 if updateExistingEvent { 239 newEvent, err = sink.Patch(event, patch) 240 } 241 // Update can fail because the event may have been removed and it no longer exists. 242 if !updateExistingEvent || (updateExistingEvent && util.IsKeyNotFoundError(err)) { 243 // Making sure that ResourceVersion is empty on creation 244 event.ResourceVersion = "" 245 newEvent, err = sink.Create(event) 246 } 247 if err == nil { 248 // we need to update our event correlator with the server returned state to handle name/resourceversion 249 eventCorrelator.UpdateState(newEvent) 250 return true 251 } 252 253 // If we can't contact the server, then hold everything while we keep trying. 254 // Otherwise, something about the event is malformed and we should abandon it. 255 switch err.(type) { 256 case *restclient.RequestConstructionError: 257 // We will construct the request the same next time, so don't keep trying. 258 klog.Errorf("Unable to construct event '%#v': '%v' (will not retry!)", event, err) 259 return true 260 case *errors.StatusError: 261 if errors.IsAlreadyExists(err) { 262 klog.V(5).Infof("Server rejected event '%#v': '%v' (will not retry!)", event, err) 263 } else { 264 klog.Errorf("Server rejected event '%#v': '%v' (will not retry!)", event, err) 265 } 266 return true 267 case *errors.UnexpectedObjectError: 268 // We don't expect this; it implies the server's response didn't match a 269 // known pattern. Go ahead and retry. 270 default: 271 // This case includes actual http transport errors. Go ahead and retry. 272 } 273 klog.Errorf("Unable to write event: '%#v': '%v'(may retry after sleeping)", event, err) 274 return false 275 } 276 277 // StartLogging starts sending events received from this EventBroadcaster to the given logging function. 278 // The return value can be ignored or used to stop recording, if desired. 279 func (e *eventBroadcasterImpl) StartLogging(logf func(format string, args ...interface{})) watch.Interface { 280 return e.StartEventWatcher( 281 func(e *v1.Event) { 282 logf("Event(%#v): type: '%v' reason: '%v' %v", e.InvolvedObject, e.Type, e.Reason, e.Message) 283 }) 284 } 285 286 // StartStructuredLogging starts sending events received from this EventBroadcaster to the structured logging function. 287 // The return value can be ignored or used to stop recording, if desired. 288 func (e *eventBroadcasterImpl) StartStructuredLogging(verbosity klog.Level) watch.Interface { 289 return e.StartEventWatcher( 290 func(e *v1.Event) { 291 klog.V(verbosity).InfoS("Event occurred", "object", klog.KRef(e.InvolvedObject.Namespace, e.InvolvedObject.Name), "kind", e.InvolvedObject.Kind, "apiVersion", e.InvolvedObject.APIVersion, "type", e.Type, "reason", e.Reason, "message", e.Message) 292 }) 293 } 294 295 // StartEventWatcher starts sending events received from this EventBroadcaster to the given event handler function. 296 // The return value can be ignored or used to stop recording, if desired. 297 func (e *eventBroadcasterImpl) StartEventWatcher(eventHandler func(*v1.Event)) watch.Interface { 298 watcher := e.Watch() 299 go func() { 300 defer utilruntime.HandleCrash() 301 for watchEvent := range watcher.ResultChan() { 302 event, ok := watchEvent.Object.(*v1.Event) 303 if !ok { 304 // This is all local, so there's no reason this should 305 // ever happen. 306 continue 307 } 308 eventHandler(event) 309 } 310 }() 311 return watcher 312 } 313 314 // NewRecorder returns an EventRecorder that records events with the given event source. 315 func (e *eventBroadcasterImpl) NewRecorder(scheme *runtime.Scheme, source v1.EventSource) EventRecorder { 316 return &recorderImpl{scheme, source, e.Broadcaster, clock.RealClock{}} 317 } 318 319 type recorderImpl struct { 320 scheme *runtime.Scheme 321 source v1.EventSource 322 *watch.Broadcaster 323 clock clock.Clock 324 } 325 326 func (recorder *recorderImpl) generateEvent(object runtime.Object, annotations map[string]string, eventtype, reason, message string) { 327 ref, err := ref.GetReference(recorder.scheme, object) 328 if err != nil { 329 klog.Errorf("Could not construct reference to: '%#v' due to: '%v'. Will not report event: '%v' '%v' '%v'", object, err, eventtype, reason, message) 330 return 331 } 332 333 if !util.ValidateEventType(eventtype) { 334 klog.Errorf("Unsupported event type: '%v'", eventtype) 335 return 336 } 337 338 event := recorder.makeEvent(ref, annotations, eventtype, reason, message) 339 event.Source = recorder.source 340 341 // NOTE: events should be a non-blocking operation, but we also need to not 342 // put this in a goroutine, otherwise we'll race to write to a closed channel 343 // when we go to shut down this broadcaster. Just drop events if we get overloaded, 344 // and log an error if that happens (we've configured the broadcaster to drop 345 // outgoing events anyway). 346 if sent := recorder.ActionOrDrop(watch.Added, event); !sent { 347 klog.Errorf("unable to record event: too many queued events, dropped event %#v", event) 348 } 349 } 350 351 func (recorder *recorderImpl) Event(object runtime.Object, eventtype, reason, message string) { 352 recorder.generateEvent(object, nil, eventtype, reason, message) 353 } 354 355 func (recorder *recorderImpl) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) { 356 recorder.Event(object, eventtype, reason, fmt.Sprintf(messageFmt, args...)) 357 } 358 359 func (recorder *recorderImpl) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) { 360 recorder.generateEvent(object, annotations, eventtype, reason, fmt.Sprintf(messageFmt, args...)) 361 } 362 363 func (recorder *recorderImpl) makeEvent(ref *v1.ObjectReference, annotations map[string]string, eventtype, reason, message string) *v1.Event { 364 t := metav1.Time{Time: recorder.clock.Now()} 365 namespace := ref.Namespace 366 if namespace == "" { 367 namespace = metav1.NamespaceDefault 368 } 369 return &v1.Event{ 370 ObjectMeta: metav1.ObjectMeta{ 371 Name: fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()), 372 Namespace: namespace, 373 Annotations: annotations, 374 }, 375 InvolvedObject: *ref, 376 Reason: reason, 377 Message: message, 378 FirstTimestamp: t, 379 LastTimestamp: t, 380 Count: 1, 381 Type: eventtype, 382 } 383 }