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 }