github.com/cilium/cilium@v1.16.2/pkg/kvstore/store/watchstoremgr.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package store
     5  
     6  import (
     7  	"context"
     8  	"path"
     9  	"sync"
    10  	"sync/atomic"
    11  
    12  	"github.com/sirupsen/logrus"
    13  
    14  	"github.com/cilium/cilium/pkg/kvstore"
    15  	"github.com/cilium/cilium/pkg/logging/logfields"
    16  )
    17  
    18  // WSMFunc if a function which can be registered in the WatchStoreManager.
    19  type WSMFunc func(context.Context)
    20  
    21  // WatchStoreManager enables to register a set of functions to be asynchronously
    22  // executed when the corresponding kvstore prefixes are synchronized (based on
    23  // the implementation).
    24  type WatchStoreManager interface {
    25  	// Register registers a function associated with a given kvstore prefix.
    26  	// It cannot be called once Run() has started.
    27  	Register(prefix string, function WSMFunc)
    28  	// Run starts the manager, blocking until the context is closed and all
    29  	// started functions terminated.
    30  	Run(ctx context.Context)
    31  }
    32  
    33  // wsmCommon implements the common logic shared by WatchStoreManager implementations.
    34  type wsmCommon struct {
    35  	wg        sync.WaitGroup
    36  	functions map[string]WSMFunc
    37  
    38  	running atomic.Bool
    39  	log     *logrus.Entry
    40  }
    41  
    42  func newWSMCommon(clusterName string) wsmCommon {
    43  	return wsmCommon{
    44  		functions: make(map[string]WSMFunc),
    45  		log:       log.WithField(logfields.ClusterName, clusterName),
    46  	}
    47  }
    48  
    49  // Register registers a function associated with a given kvstore prefix.
    50  // It cannot be called once Run() has started.
    51  func (mgr *wsmCommon) Register(prefix string, function WSMFunc) {
    52  	if mgr.running.Load() {
    53  		mgr.log.Panic("Cannot call Register while the watch store manager is running")
    54  	}
    55  
    56  	mgr.functions[prefix] = function
    57  }
    58  
    59  func (mgr *wsmCommon) ready(ctx context.Context, prefix string) {
    60  	if fn := mgr.functions[prefix]; fn != nil {
    61  		mgr.log.WithField(logfields.Prefix, prefix).Debug("Starting function for kvstore prefix")
    62  		delete(mgr.functions, prefix)
    63  
    64  		mgr.wg.Add(1)
    65  		go func() {
    66  			defer mgr.wg.Done()
    67  			fn(ctx)
    68  			mgr.log.WithField(logfields.Prefix, prefix).Debug("Function terminated for kvstore prefix")
    69  		}()
    70  	} else {
    71  		mgr.log.WithField(logfields.Prefix, prefix).Debug("Received sync event for unregistered prefix")
    72  	}
    73  }
    74  
    75  func (mgr *wsmCommon) run() {
    76  	mgr.log.Info("Starting watch store manager")
    77  	if mgr.running.Swap(true) {
    78  		mgr.log.Panic("Cannot start the watch store manager twice")
    79  	}
    80  }
    81  
    82  func (mgr *wsmCommon) wait() {
    83  	mgr.wg.Wait()
    84  	mgr.log.Info("Stopped watch store manager")
    85  }
    86  
    87  type wsmSync struct {
    88  	wsmCommon
    89  
    90  	clusterName string
    91  	backend     WatchStoreBackend
    92  	store       WatchStore
    93  	onUpdate    func(prefix string)
    94  }
    95  
    96  // NewWatchStoreManagerSync implements the WatchStoreManager interface, starting the
    97  // registered functions only once the corresponding prefix sync canary has been received.
    98  // This ensures that the synchronization of the keys hosted under the given prefix
    99  // have been successfully synchronized from the external source, even in case an
   100  // ephemeral kvstore is used.
   101  func newWatchStoreManagerSync(backend WatchStoreBackend, clusterName string, factory Factory) WatchStoreManager {
   102  	mgr := wsmSync{
   103  		wsmCommon:   newWSMCommon(clusterName),
   104  		clusterName: clusterName,
   105  		backend:     backend,
   106  	}
   107  
   108  	mgr.store = factory.NewWatchStore(clusterName, KVPairCreator, &mgr)
   109  	return &mgr
   110  }
   111  
   112  // Run starts the manager, blocking until the context is closed and all
   113  // started functions terminated.
   114  func (mgr *wsmSync) Run(ctx context.Context) {
   115  	mgr.run()
   116  	mgr.onUpdate = func(prefix string) { mgr.ready(ctx, prefix) }
   117  	mgr.store.Watch(ctx, mgr.backend, path.Join(kvstore.SyncedPrefix, mgr.clusterName))
   118  	mgr.wait()
   119  }
   120  
   121  func (mgr *wsmSync) OnUpdate(k Key)      { mgr.onUpdate(k.GetKeyName()) }
   122  func (mgr *wsmSync) OnDelete(k NamedKey) {}
   123  
   124  type wsmImmediate struct {
   125  	wsmCommon
   126  }
   127  
   128  // NewWatchStoreManagerImmediate implements the WatchStoreManager interface,
   129  // immediately starting the registered functions once Run() is executed.
   130  func NewWatchStoreManagerImmediate(clusterName string) WatchStoreManager {
   131  	return &wsmImmediate{
   132  		wsmCommon: newWSMCommon(clusterName),
   133  	}
   134  }
   135  
   136  // Run starts the manager, blocking until the context is closed and all
   137  // started functions terminated.
   138  func (mgr *wsmImmediate) Run(ctx context.Context) {
   139  	mgr.run()
   140  	for prefix := range mgr.functions {
   141  		mgr.ready(ctx, prefix)
   142  	}
   143  	mgr.wait()
   144  }