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  }