github.com/kubeshop/testkube@v1.17.23/pkg/event/emitter.go (about)

     1  package event
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	"go.uber.org/zap"
     9  
    10  	"github.com/kubeshop/testkube/pkg/api/v1/testkube"
    11  	"github.com/kubeshop/testkube/pkg/event/bus"
    12  	"github.com/kubeshop/testkube/pkg/event/kind/common"
    13  	"github.com/kubeshop/testkube/pkg/log"
    14  )
    15  
    16  const (
    17  	eventsBuffer      = 10000
    18  	workersCount      = 20
    19  	reconcileInterval = time.Second
    20  )
    21  
    22  // NewEmitter returns new emitter instance
    23  func NewEmitter(eventBus bus.Bus, clusterName string, envs map[string]string) *Emitter {
    24  	return &Emitter{
    25  		Results:     make(chan testkube.EventResult, eventsBuffer),
    26  		Log:         log.DefaultLogger,
    27  		Loader:      NewLoader(),
    28  		Bus:         eventBus,
    29  		Listeners:   make(common.Listeners, 0),
    30  		ClusterName: clusterName,
    31  		Envs:        envs,
    32  	}
    33  }
    34  
    35  // Emitter handles events emitting for webhooks
    36  type Emitter struct {
    37  	Results     chan testkube.EventResult
    38  	Listeners   common.Listeners
    39  	Loader      *Loader
    40  	Log         *zap.SugaredLogger
    41  	mutex       sync.RWMutex
    42  	Bus         bus.Bus
    43  	ClusterName string
    44  	Envs        map[string]string
    45  }
    46  
    47  // Register adds new listener
    48  func (e *Emitter) Register(listener common.Listener) {
    49  	e.mutex.Lock()
    50  	defer e.mutex.Unlock()
    51  
    52  	e.Listeners = append(e.Listeners, listener)
    53  }
    54  
    55  // UpdateListeners updates listeners list
    56  func (e *Emitter) UpdateListeners(listeners common.Listeners) {
    57  	e.mutex.Lock()
    58  	defer e.mutex.Unlock()
    59  
    60  	oldMap := make(map[string]map[string]common.Listener, 0)
    61  	newMap := make(map[string]map[string]common.Listener, 0)
    62  	result := make([]common.Listener, 0)
    63  
    64  	for _, l := range e.Listeners {
    65  		if _, ok := oldMap[l.Kind()]; !ok {
    66  			oldMap[l.Kind()] = make(map[string]common.Listener, 0)
    67  		}
    68  
    69  		oldMap[l.Kind()][l.Name()] = l
    70  	}
    71  
    72  	for _, l := range listeners {
    73  		if _, ok := newMap[l.Kind()]; !ok {
    74  			newMap[l.Kind()] = make(map[string]common.Listener, 0)
    75  		}
    76  
    77  		newMap[l.Kind()][l.Name()] = l
    78  	}
    79  
    80  	// check for missing listeners
    81  	for kind, lMap := range oldMap {
    82  		// clean missing kinds
    83  		if _, ok := newMap[kind]; !ok {
    84  			for _, l := range lMap {
    85  				e.stopListener(l.Name())
    86  			}
    87  
    88  			continue
    89  		}
    90  
    91  		// stop missing listeners
    92  		for name, l := range lMap {
    93  			if _, ok := newMap[kind][name]; !ok {
    94  				e.stopListener(l.Name())
    95  			}
    96  		}
    97  	}
    98  
    99  	// check for new listeners
   100  	for kind, lMap := range newMap {
   101  		// start all listeners for new kind
   102  		if _, ok := oldMap[kind]; !ok {
   103  			for _, l := range lMap {
   104  				e.startListener(l)
   105  				result = append(result, l)
   106  			}
   107  
   108  			continue
   109  		}
   110  
   111  		// start new listeners and restart updated ones
   112  		for name, l := range lMap {
   113  			if current, ok := oldMap[kind][name]; !ok {
   114  				e.startListener(l)
   115  			} else {
   116  				if !common.CompareListeners(current, l) {
   117  					e.stopListener(current.Name())
   118  					e.startListener(l)
   119  				}
   120  			}
   121  
   122  			result = append(result, l)
   123  		}
   124  	}
   125  
   126  	e.Listeners = result
   127  }
   128  
   129  // Notify notifies emitter with webhook
   130  func (e *Emitter) Notify(event testkube.Event) {
   131  	event.ClusterName = e.ClusterName
   132  	event.Envs = e.Envs
   133  	err := e.Bus.PublishTopic(event.Topic(), event)
   134  	e.Log.Infow("event published", append(event.Log(), "error", err)...)
   135  }
   136  
   137  // Listen runs emitter workers responsible for sending HTTP requests
   138  func (e *Emitter) Listen(ctx context.Context) {
   139  	// clean after closing Emitter
   140  	go func() {
   141  		<-ctx.Done()
   142  		e.Log.Warn("closing event bus")
   143  
   144  		for _, l := range e.Listeners {
   145  			go e.Bus.Unsubscribe(l.Name())
   146  		}
   147  
   148  		e.Bus.Close()
   149  	}()
   150  
   151  	e.mutex.Lock()
   152  	defer e.mutex.Unlock()
   153  
   154  	for _, l := range e.Listeners {
   155  		go e.startListener(l)
   156  	}
   157  }
   158  
   159  func (e *Emitter) startListener(l common.Listener) {
   160  	e.Log.Infow("starting listener", l.Name(), l.Metadata())
   161  	err := e.Bus.SubscribeTopic("events.>", l.Name(), e.notifyHandler(l))
   162  	if err != nil {
   163  		e.Log.Errorw("error subscribing to event", "error", err)
   164  	}
   165  }
   166  
   167  func (e *Emitter) stopListener(name string) {
   168  	e.Log.Infow("stoping listener", name)
   169  	err := e.Bus.Unsubscribe(name)
   170  	if err != nil {
   171  		e.Log.Errorw("error unsubscribing from event", "error", err)
   172  	}
   173  }
   174  
   175  func (e *Emitter) notifyHandler(l common.Listener) bus.Handler {
   176  	log := e.Log.With("listen-on", l.Events(), "queue-group", l.Name(), "selector", l.Selector(), "metadata", l.Metadata())
   177  	return func(event testkube.Event) error {
   178  		if event.Valid(l.Selector(), l.Events()) {
   179  			log.Infow("notification result", l.Notify(event))
   180  			log.Infow("listener notified", event.Log()...)
   181  		} else {
   182  			log.Infow("dropping event not matching selector or type", event.Log()...)
   183  		}
   184  		return nil
   185  	}
   186  }
   187  
   188  // Reconcile reloads listeners from all registered reconcilers
   189  func (e *Emitter) Reconcile(ctx context.Context) {
   190  	for {
   191  		select {
   192  		case <-ctx.Done():
   193  			e.Log.Infow("stopping reconciler")
   194  			return
   195  		default:
   196  			listeners := e.Loader.Reconcile()
   197  			e.UpdateListeners(listeners)
   198  			e.Log.Debugw("reconciled listeners", e.Logs()...)
   199  			time.Sleep(reconcileInterval)
   200  		}
   201  	}
   202  }
   203  
   204  func (e *Emitter) Logs() []any {
   205  	e.mutex.Lock()
   206  	defer e.mutex.Unlock()
   207  	return e.Listeners.Log()
   208  }
   209  
   210  func (e *Emitter) GetListeners() common.Listeners {
   211  	e.mutex.RLock()
   212  	defer e.mutex.RUnlock()
   213  	return e.Listeners
   214  }