k8s.io/apiserver@v0.31.1/plugin/pkg/audit/buffered/buffered.go (about)

     1  /*
     2  Copyright 2018 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 buffered
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  	"time"
    23  
    24  	"k8s.io/apimachinery/pkg/util/runtime"
    25  	"k8s.io/apimachinery/pkg/util/wait"
    26  	auditinternal "k8s.io/apiserver/pkg/apis/audit"
    27  	"k8s.io/apiserver/pkg/audit"
    28  	"k8s.io/client-go/util/flowcontrol"
    29  )
    30  
    31  // PluginName is the name reported in error metrics.
    32  const PluginName = "buffered"
    33  
    34  // BatchConfig represents batching delegate audit backend configuration.
    35  type BatchConfig struct {
    36  	// BufferSize defines a size of the buffering queue.
    37  	BufferSize int
    38  	// MaxBatchSize defines maximum size of a batch.
    39  	MaxBatchSize int
    40  	// MaxBatchWait indicates the maximum interval between two batches.
    41  	MaxBatchWait time.Duration
    42  
    43  	// ThrottleEnable defines whether throttling will be applied to the batching process.
    44  	ThrottleEnable bool
    45  	// ThrottleQPS defines the allowed rate of batches per second sent to the delegate backend.
    46  	ThrottleQPS float32
    47  	// ThrottleBurst defines the maximum number of requests sent to the delegate backend at the same moment in case
    48  	// the capacity defined by ThrottleQPS was not utilized.
    49  	ThrottleBurst int
    50  
    51  	// Whether the delegate backend should be called asynchronously.
    52  	AsyncDelegate bool
    53  }
    54  
    55  type bufferedBackend struct {
    56  	// The delegate backend that actually exports events.
    57  	delegateBackend audit.Backend
    58  
    59  	// Channel to buffer events before sending to the delegate backend.
    60  	buffer chan *auditinternal.Event
    61  	// Maximum number of events in a batch sent to the delegate backend.
    62  	maxBatchSize int
    63  	// Amount of time to wait after sending a batch to the delegate backend before sending another one.
    64  	//
    65  	// Receiving maxBatchSize events will always trigger sending a batch, regardless of the amount of time passed.
    66  	maxBatchWait time.Duration
    67  
    68  	// Whether the delegate backend should be called asynchronously.
    69  	asyncDelegate bool
    70  
    71  	// Channel to signal that the batching routine has processed all remaining events and exited.
    72  	// Once `shutdownCh` is closed no new events will be sent to the delegate backend.
    73  	shutdownCh chan struct{}
    74  
    75  	// WaitGroup to control the concurrency of sending batches to the delegate backend.
    76  	// Worker routine calls Add before sending a batch and
    77  	// then spawns a routine that calls Done after batch was processed by the delegate backend.
    78  	// This WaitGroup is used to wait for all sending routines to finish before shutting down audit backend.
    79  	wg sync.WaitGroup
    80  
    81  	// Limits the number of batches sent to the delegate backend per second.
    82  	throttle flowcontrol.RateLimiter
    83  }
    84  
    85  var _ audit.Backend = &bufferedBackend{}
    86  
    87  // NewBackend returns a buffered audit backend that wraps delegate backend.
    88  // Buffered backend automatically runs and shuts down the delegate backend.
    89  func NewBackend(delegate audit.Backend, config BatchConfig) audit.Backend {
    90  	var throttle flowcontrol.RateLimiter
    91  	if config.ThrottleEnable {
    92  		throttle = flowcontrol.NewTokenBucketRateLimiter(config.ThrottleQPS, config.ThrottleBurst)
    93  	}
    94  	return &bufferedBackend{
    95  		delegateBackend: delegate,
    96  		buffer:          make(chan *auditinternal.Event, config.BufferSize),
    97  		maxBatchSize:    config.MaxBatchSize,
    98  		maxBatchWait:    config.MaxBatchWait,
    99  		asyncDelegate:   config.AsyncDelegate,
   100  		shutdownCh:      make(chan struct{}),
   101  		wg:              sync.WaitGroup{},
   102  		throttle:        throttle,
   103  	}
   104  }
   105  
   106  func (b *bufferedBackend) Run(stopCh <-chan struct{}) error {
   107  	go func() {
   108  		// Signal that the working routine has exited.
   109  		defer close(b.shutdownCh)
   110  
   111  		b.processIncomingEvents(stopCh)
   112  
   113  		// Handle the events that were received after the last buffer
   114  		// scraping and before this line. Since the buffer is closed, no new
   115  		// events will come through.
   116  		allEventsProcessed := false
   117  		timer := make(chan time.Time)
   118  		for !allEventsProcessed {
   119  			allEventsProcessed = func() bool {
   120  				// Recover from any panic in order to try to process all remaining events.
   121  				// Note, that in case of a panic, the return value will be false and
   122  				// the loop execution will continue.
   123  				defer runtime.HandleCrash()
   124  
   125  				events := b.collectEvents(timer, wait.NeverStop)
   126  				b.processEvents(events)
   127  				return len(events) == 0
   128  			}()
   129  		}
   130  	}()
   131  	return b.delegateBackend.Run(stopCh)
   132  }
   133  
   134  // Shutdown blocks until stopCh passed to the Run method is closed and all
   135  // events added prior to that moment are batched and sent to the delegate backend.
   136  func (b *bufferedBackend) Shutdown() {
   137  	// Wait until the routine spawned in Run method exits.
   138  	<-b.shutdownCh
   139  
   140  	// Wait until all sending routines exit.
   141  	//
   142  	// - When b.shutdownCh is closed, we know that the goroutine in Run has terminated.
   143  	// - This means that processIncomingEvents has terminated.
   144  	// - Which means that b.buffer is closed and cannot accept any new events anymore.
   145  	// - Because processEvents is called synchronously from the Run goroutine, the waitgroup has its final value.
   146  	// Hence wg.Wait will not miss any more outgoing batches.
   147  	b.wg.Wait()
   148  
   149  	b.delegateBackend.Shutdown()
   150  }
   151  
   152  // processIncomingEvents runs a loop that collects events from the buffer. When
   153  // b.stopCh is closed, processIncomingEvents stops and closes the buffer.
   154  func (b *bufferedBackend) processIncomingEvents(stopCh <-chan struct{}) {
   155  	defer close(b.buffer)
   156  
   157  	var (
   158  		maxWaitChan  <-chan time.Time
   159  		maxWaitTimer *time.Timer
   160  	)
   161  	// Only use max wait batching if batching is enabled.
   162  	if b.maxBatchSize > 1 {
   163  		maxWaitTimer = time.NewTimer(b.maxBatchWait)
   164  		maxWaitChan = maxWaitTimer.C
   165  		defer maxWaitTimer.Stop()
   166  	}
   167  
   168  	for {
   169  		func() {
   170  			// Recover from any panics caused by this function so a panic in the
   171  			// goroutine can't bring down the main routine.
   172  			defer runtime.HandleCrash()
   173  
   174  			if b.maxBatchSize > 1 {
   175  				maxWaitTimer.Reset(b.maxBatchWait)
   176  			}
   177  			b.processEvents(b.collectEvents(maxWaitChan, stopCh))
   178  		}()
   179  
   180  		select {
   181  		case <-stopCh:
   182  			return
   183  		default:
   184  		}
   185  	}
   186  }
   187  
   188  // collectEvents attempts to collect some number of events in a batch.
   189  //
   190  // The following things can cause collectEvents to stop and return the list
   191  // of events:
   192  //
   193  //   - Maximum number of events for a batch.
   194  //   - Timer has passed.
   195  //   - Buffer channel is closed and empty.
   196  //   - stopCh is closed.
   197  func (b *bufferedBackend) collectEvents(timer <-chan time.Time, stopCh <-chan struct{}) []*auditinternal.Event {
   198  	var events []*auditinternal.Event
   199  
   200  L:
   201  	for i := 0; i < b.maxBatchSize; i++ {
   202  		select {
   203  		case ev, ok := <-b.buffer:
   204  			// Buffer channel was closed and no new events will follow.
   205  			if !ok {
   206  				break L
   207  			}
   208  			events = append(events, ev)
   209  		case <-timer:
   210  			// Timer has expired. Send currently accumulated batch.
   211  			break L
   212  		case <-stopCh:
   213  			// Backend has been stopped. Send currently accumulated batch.
   214  			break L
   215  		}
   216  	}
   217  
   218  	return events
   219  }
   220  
   221  // processEvents process the batch events in a goroutine using delegateBackend's ProcessEvents.
   222  func (b *bufferedBackend) processEvents(events []*auditinternal.Event) {
   223  	if len(events) == 0 {
   224  		return
   225  	}
   226  
   227  	// TODO(audit): Should control the number of active goroutines
   228  	// if one goroutine takes 5 seconds to finish, the number of goroutines can be 5 * defaultBatchThrottleQPS
   229  	if b.throttle != nil {
   230  		b.throttle.Accept()
   231  	}
   232  
   233  	if b.asyncDelegate {
   234  		b.wg.Add(1)
   235  		go func() {
   236  			defer b.wg.Done()
   237  			defer runtime.HandleCrash()
   238  
   239  			// Execute the real processing in a goroutine to keep it from blocking.
   240  			// This lets the batching routine continue draining the queue immediately.
   241  			b.delegateBackend.ProcessEvents(events...)
   242  		}()
   243  	} else {
   244  		func() {
   245  			defer runtime.HandleCrash()
   246  
   247  			// Execute the real processing in a goroutine to keep it from blocking.
   248  			// This lets the batching routine continue draining the queue immediately.
   249  			b.delegateBackend.ProcessEvents(events...)
   250  		}()
   251  	}
   252  }
   253  
   254  func (b *bufferedBackend) ProcessEvents(ev ...*auditinternal.Event) bool {
   255  	// The following mechanism is in place to support the situation when audit
   256  	// events are still coming after the backend was stopped.
   257  	var sendErr error
   258  	var evIndex int
   259  
   260  	// If the delegateBackend was shutdown and the buffer channel was closed, an
   261  	// attempt to add an event to it will result in panic that we should
   262  	// recover from.
   263  	defer func() {
   264  		if err := recover(); err != nil {
   265  			sendErr = fmt.Errorf("audit backend shut down")
   266  		}
   267  		if sendErr != nil {
   268  			audit.HandlePluginError(PluginName, sendErr, ev[evIndex:]...)
   269  		}
   270  	}()
   271  
   272  	for i, e := range ev {
   273  		evIndex = i
   274  		// Per the audit.Backend interface these events are reused after being
   275  		// sent to the Sink. Deep copy and send the copy to the queue.
   276  		event := e.DeepCopy()
   277  
   278  		select {
   279  		case b.buffer <- event:
   280  		default:
   281  			sendErr = fmt.Errorf("audit buffer queue blocked")
   282  			return true
   283  		}
   284  	}
   285  	return true
   286  }
   287  
   288  func (b *bufferedBackend) String() string {
   289  	return fmt.Sprintf("%s<%s>", PluginName, b.delegateBackend)
   290  }