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  }