package notify import ( "context" "errors" "fmt" "strings" "sync" "time" "github.com/RafayLabs/rcloud-base/pkg/log" "github.com/RafayLabs/rcloud-base/pkg/match" "github.com/RafayLabs/rcloud-base/pkg/query" "github.com/RafayLabs/rcloud-base/pkg/service" commonv3 "github.com/RafayLabs/rcloud-base/proto/types/commonpb/v3" infrav3 "github.com/RafayLabs/rcloud-base/proto/types/infrapb/v3" ) var ( _log = log.GetLogger() _notifier Notifier // ErrNotInitialized is returned when notifier is not initialized ErrNotInitialized = errors.New("notifier not initialized") once = sync.Once{} ) const ( maxNotifyWorkers = 6 ) // Notifier is the interface for notifying cluster changes type Notifier interface { Start(stop <-chan struct{}) AddListener(c chan<- infrav3.Cluster, opts ...query.Option) error RemoveListener(c chan<- infrav3.Cluster) } // New returns new notifier func New(cs service.ClusterService) Notifier { return ¬ifier{ ClusterService: cs, listeners: make(map[chan<- infrav3.Cluster]match.Matcher), } } func KeyFromMeta(meta commonv3.Metadata) string { return fmt.Sprintf("%d/%d/%d/%s", meta.Partner, meta.Organization, meta.Project, meta.Name) } func MetaFromKey(key string) (meta commonv3.Metadata) { items := strings.Split(key, "/") if len(items) != 4 { return } meta.Name = items[3] meta.Partner = items[0] meta.Organization = items[1] meta.Project = items[2] return } type notifier struct { sync.RWMutex service.ClusterService listeners map[chan<- infrav3.Cluster]match.Matcher } var _ Notifier = (*notifier)(nil) func (n *notifier) Start(stop <-chan struct{}) { mChan := make(chan commonv3.Metadata, maxNotifyWorkers) ctx, cancel := context.WithCancel(context.Background()) go func() { <-stop defer cancel() //defer wq.ShutDown() }() // start cluster service listener go n.ListenClusters(ctx, mChan) for i := 0; i < maxNotifyWorkers; i++ { go func() { notifyLoop: for { select { case <-stop: break notifyLoop case m := <-mChan: notify := func(meta commonv3.Metadata) { nctx, cancel := context.WithTimeout(ctx, time.Second*1) defer cancel() c, err := n.Get(nctx, query.WithMeta(&m)) if err != nil { _log.Infow("invalid cluster meta for notify", "meta", m) return } n.notifyListeners(*c) } notify(m) } } }() } <-stop } func (n *notifier) AddListener(c chan<- infrav3.Cluster, opts ...query.Option) error { matcher, err := match.New(opts...) if err != nil { return err } n.Lock() defer n.Unlock() n.listeners[c] = matcher _log.Debugw("notify listerners", "number", len(n.listeners)) return nil } func (n *notifier) RemoveListener(c chan<- infrav3.Cluster) { n.Lock() delete(n.listeners, c) n.Unlock() _log.Debugw("notify listerners", "number", len(n.listeners)) } func (n *notifier) notifyListeners(c infrav3.Cluster) { n.RLock() for lChan, matcher := range n.listeners { if matcher.Match(*c.Metadata) { // only send if channel is ready to accept select { case lChan <- c: default: } // if len(lChan) < 1 { // lChan <- c // } } } n.RUnlock() } // Init initializes the notifier at package level func Init(cs service.ClusterService) { _notifier = New(cs) } // Start starts the notifier at package lvel func Start(stop <-chan struct{}) error { if _notifier == nil { return ErrNotInitialized } once.Do(func() { go _notifier.Start(stop) }) return nil } // AddListener adds listerner to the notifier func AddListener(c chan<- infrav3.Cluster, opts ...query.Option) error { if _notifier == nil { return ErrNotInitialized } return _notifier.AddListener(c, opts...) } // RemoveListener removes listener from notifier func RemoveListener(c chan<- infrav3.Cluster) error { if _notifier == nil { return ErrNotInitialized } _notifier.RemoveListener(c) return nil }