github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/dynamicplugins/registry.go (about)

     1  // dynamicplugins is a package that manages dynamic plugins in Nomad.
     2  // It exposes a registry that allows for plugins to be registered/deregistered
     3  // and also allows subscribers to receive real time updates of these events.
     4  package dynamicplugins
     5  
     6  import (
     7  	"container/list"
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"sync"
    12  )
    13  
    14  const (
    15  	PluginTypeCSIController = "csi-controller"
    16  	PluginTypeCSINode       = "csi-node"
    17  )
    18  
    19  // Registry is an interface that allows for the dynamic registration of plugins
    20  // that are running as Nomad Tasks.
    21  type Registry interface {
    22  	RegisterPlugin(info *PluginInfo) error
    23  	DeregisterPlugin(ptype, name, allocID string) error
    24  
    25  	ListPlugins(ptype string) []*PluginInfo
    26  	DispensePlugin(ptype, name string) (interface{}, error)
    27  	PluginForAlloc(ptype, name, allocID string) (*PluginInfo, error)
    28  
    29  	PluginsUpdatedCh(ctx context.Context, ptype string) <-chan *PluginUpdateEvent
    30  
    31  	Shutdown()
    32  
    33  	StubDispenserForType(ptype string, dispenser PluginDispenser)
    34  }
    35  
    36  // RegistryState is what we persist in the client state
    37  // store. It contains a map of plugin types to maps of plugin name ->
    38  // list of *PluginInfo, sorted by recency of registration
    39  type RegistryState struct {
    40  	Plugins map[string]map[string]*list.List
    41  }
    42  
    43  type PluginDispenser func(info *PluginInfo) (interface{}, error)
    44  
    45  // NewRegistry takes a map of `plugintype` to PluginDispenser functions
    46  // that should be used to vend clients for plugins to be used.
    47  func NewRegistry(state StateStorage, dispensers map[string]PluginDispenser) Registry {
    48  
    49  	registry := &dynamicRegistry{
    50  		plugins:      make(map[string]map[string]*list.List),
    51  		broadcasters: make(map[string]*pluginEventBroadcaster),
    52  		dispensers:   dispensers,
    53  		state:        state,
    54  	}
    55  
    56  	// populate the state and initial broadcasters if we have an
    57  	// existing state DB to restore
    58  	if state != nil {
    59  		storedState, err := state.GetDynamicPluginRegistryState()
    60  		if err == nil && storedState != nil {
    61  			registry.plugins = storedState.Plugins
    62  			for ptype := range registry.plugins {
    63  				registry.broadcasterForPluginType(ptype)
    64  			}
    65  		}
    66  	}
    67  
    68  	return registry
    69  }
    70  
    71  // StateStorage is used to persist the dynamic plugin registry's state
    72  // across agent restarts.
    73  type StateStorage interface {
    74  	// GetDynamicPluginRegistryState is used to restore the registry state
    75  	GetDynamicPluginRegistryState() (*RegistryState, error)
    76  
    77  	// PutDynamicPluginRegistryState is used to store the registry state
    78  	PutDynamicPluginRegistryState(state *RegistryState) error
    79  }
    80  
    81  // PluginInfo is the metadata that is stored by the registry for a given plugin.
    82  type PluginInfo struct {
    83  	Name    string
    84  	Type    string
    85  	Version string
    86  
    87  	// ConnectionInfo should only be used externally during `RegisterPlugin` and
    88  	// may not be exposed in the future.
    89  	ConnectionInfo *PluginConnectionInfo
    90  
    91  	// AllocID tracks the allocation running the plugin
    92  	AllocID string
    93  
    94  	// Options is used for plugin registrations to pass further metadata along to
    95  	// other subsystems
    96  	Options map[string]string
    97  }
    98  
    99  // PluginConnectionInfo is the data required to connect to the plugin.
   100  // note: We currently only support Unix Domain Sockets, but this may be expanded
   101  //
   102  //	to support other connection modes in the future.
   103  type PluginConnectionInfo struct {
   104  	// SocketPath is the path to the plugins api socket.
   105  	SocketPath string
   106  }
   107  
   108  // EventType is the enum of events that will be emitted by a Registry's
   109  // PluginsUpdatedCh.
   110  type EventType string
   111  
   112  const (
   113  	// EventTypeRegistered is emitted by the Registry when a new plugin has been
   114  	// registered.
   115  	EventTypeRegistered EventType = "registered"
   116  	// EventTypeDeregistered is emitted by the Registry when a plugin has been
   117  	// removed.
   118  	EventTypeDeregistered EventType = "deregistered"
   119  )
   120  
   121  // PluginUpdateEvent is a struct that is sent over a PluginsUpdatedCh when
   122  // plugins are added or removed from the registry.
   123  type PluginUpdateEvent struct {
   124  	EventType EventType
   125  	Info      *PluginInfo
   126  }
   127  
   128  type dynamicRegistry struct {
   129  	plugins     map[string]map[string]*list.List
   130  	pluginsLock sync.RWMutex
   131  
   132  	broadcasters     map[string]*pluginEventBroadcaster
   133  	broadcastersLock sync.Mutex
   134  
   135  	dispensers     map[string]PluginDispenser
   136  	stubDispensers map[string]PluginDispenser
   137  
   138  	state StateStorage
   139  }
   140  
   141  // StubDispenserForType allows test functions to provide alternative plugin
   142  // dispensers to simplify writing tests for higher level Nomad features.
   143  // This function should not be called from production code.
   144  func (d *dynamicRegistry) StubDispenserForType(ptype string, dispenser PluginDispenser) {
   145  	// delete from stubs
   146  	if dispenser == nil && d.stubDispensers != nil {
   147  		delete(d.stubDispensers, ptype)
   148  		if len(d.stubDispensers) == 0 {
   149  			d.stubDispensers = nil
   150  		}
   151  
   152  		return
   153  	}
   154  
   155  	// setup stubs
   156  	if d.stubDispensers == nil {
   157  		d.stubDispensers = make(map[string]PluginDispenser, 1)
   158  	}
   159  
   160  	d.stubDispensers[ptype] = dispenser
   161  }
   162  
   163  func (d *dynamicRegistry) RegisterPlugin(info *PluginInfo) error {
   164  	if info.Type == "" {
   165  		// This error shouldn't make it to a production cluster and is to aid
   166  		// developers during the development of new plugin types.
   167  		return errors.New("Plugin.Type must not be empty")
   168  	}
   169  
   170  	if info.ConnectionInfo == nil {
   171  		// This error shouldn't make it to a production cluster and is to aid
   172  		// developers during the development of new plugin types.
   173  		return errors.New("Plugin.ConnectionInfo must not be nil")
   174  	}
   175  
   176  	if info.Name == "" {
   177  		// This error shouldn't make it to a production cluster and is to aid
   178  		// developers during the development of new plugin types.
   179  		return errors.New("Plugin.Name must not be empty")
   180  	}
   181  
   182  	d.pluginsLock.Lock()
   183  	defer d.pluginsLock.Unlock()
   184  
   185  	pmap, ok := d.plugins[info.Type]
   186  	if !ok {
   187  		pmap = make(map[string]*list.List)
   188  		d.plugins[info.Type] = pmap
   189  	}
   190  	infos, ok := pmap[info.Name]
   191  	if !ok {
   192  		infos = list.New()
   193  		pmap[info.Name] = infos
   194  	}
   195  
   196  	// TODO(tgross): https://github.com/hashicorp/nomad/issues/11786
   197  	// If we're already registered, we should update the definition
   198  	// and send a broadcast of any update so the instanceManager can
   199  	// be restarted if there's been a change
   200  	var alreadyRegistered bool
   201  	for e := infos.Front(); e != nil; e = e.Next() {
   202  		if e.Value.(*PluginInfo).AllocID == info.AllocID {
   203  			alreadyRegistered = true
   204  			break
   205  		}
   206  	}
   207  	if !alreadyRegistered {
   208  		infos.PushFront(info)
   209  		broadcaster := d.broadcasterForPluginType(info.Type)
   210  		event := &PluginUpdateEvent{
   211  			EventType: EventTypeRegistered,
   212  			Info:      info,
   213  		}
   214  		broadcaster.broadcast(event)
   215  	}
   216  
   217  	return d.sync()
   218  }
   219  
   220  func (d *dynamicRegistry) broadcasterForPluginType(ptype string) *pluginEventBroadcaster {
   221  	d.broadcastersLock.Lock()
   222  	defer d.broadcastersLock.Unlock()
   223  
   224  	broadcaster, ok := d.broadcasters[ptype]
   225  	if !ok {
   226  		broadcaster = newPluginEventBroadcaster()
   227  		d.broadcasters[ptype] = broadcaster
   228  	}
   229  
   230  	return broadcaster
   231  }
   232  
   233  func (d *dynamicRegistry) DeregisterPlugin(ptype, name, allocID string) error {
   234  	d.pluginsLock.Lock()
   235  	defer d.pluginsLock.Unlock()
   236  
   237  	if ptype == "" {
   238  		// This error shouldn't make it to a production cluster and is to aid
   239  		// developers during the development of new plugin types.
   240  		return errors.New("must specify plugin type to deregister")
   241  	}
   242  	if name == "" {
   243  		// This error shouldn't make it to a production cluster and is to aid
   244  		// developers during the development of new plugin types.
   245  		return errors.New("must specify plugin name to deregister")
   246  	}
   247  	if allocID == "" {
   248  		return errors.New("must specify plugin allocation ID to deregister")
   249  	}
   250  
   251  	pmap, ok := d.plugins[ptype]
   252  	if !ok {
   253  		// If this occurs there's a bug in the registration handler.
   254  		return fmt.Errorf("no plugins registered for type: %s", ptype)
   255  	}
   256  
   257  	infos, ok := pmap[name]
   258  	if !ok {
   259  		// plugin already deregistered, don't send events or try re-deleting.
   260  		return nil
   261  	}
   262  
   263  	var info *PluginInfo
   264  	for e := infos.Front(); e != nil; e = e.Next() {
   265  		info = e.Value.(*PluginInfo)
   266  		if info.AllocID == allocID {
   267  			infos.Remove(e)
   268  			break
   269  		}
   270  	}
   271  
   272  	if info != nil {
   273  		broadcaster := d.broadcasterForPluginType(ptype)
   274  		event := &PluginUpdateEvent{
   275  			EventType: EventTypeDeregistered,
   276  			Info:      info,
   277  		}
   278  		broadcaster.broadcast(event)
   279  	}
   280  
   281  	return d.sync()
   282  }
   283  
   284  func (d *dynamicRegistry) ListPlugins(ptype string) []*PluginInfo {
   285  	d.pluginsLock.RLock()
   286  	defer d.pluginsLock.RUnlock()
   287  
   288  	pmap, ok := d.plugins[ptype]
   289  	if !ok {
   290  		return nil
   291  	}
   292  
   293  	plugins := make([]*PluginInfo, 0, len(pmap))
   294  
   295  	for _, info := range pmap {
   296  		if info.Front() != nil {
   297  			plugins = append(plugins, info.Front().Value.(*PluginInfo))
   298  		}
   299  	}
   300  
   301  	return plugins
   302  }
   303  
   304  func (d *dynamicRegistry) DispensePlugin(ptype string, name string) (interface{}, error) {
   305  	d.pluginsLock.Lock()
   306  	defer d.pluginsLock.Unlock()
   307  
   308  	if ptype == "" {
   309  		// This error shouldn't make it to a production cluster and is to aid
   310  		// developers during the development of new plugin types.
   311  		return nil, errors.New("must specify plugin type to dispense")
   312  	}
   313  	if name == "" {
   314  		// This error shouldn't make it to a production cluster and is to aid
   315  		// developers during the development of new plugin types.
   316  		return nil, errors.New("must specify plugin name to dispense")
   317  	}
   318  
   319  	dispenseFunc, ok := d.dispensers[ptype]
   320  	if !ok {
   321  		// This error shouldn't make it to a production cluster and is to aid
   322  		// developers during the development of new plugin types.
   323  		return nil, fmt.Errorf("no plugin dispenser found for type: %s", ptype)
   324  	}
   325  
   326  	// After initially loading the dispenser (to avoid masking missing setup in
   327  	// client/client.go), we then check to see if we have any stub dispensers for
   328  	// this plugin type. If we do, then replace the dispenser fn with the stub.
   329  	if d.stubDispensers != nil {
   330  		if stub, ok := d.stubDispensers[ptype]; ok {
   331  			dispenseFunc = stub
   332  		}
   333  	}
   334  
   335  	pmap, ok := d.plugins[ptype]
   336  	if !ok {
   337  		return nil, fmt.Errorf("no plugins registered for type: %s", ptype)
   338  	}
   339  
   340  	info, ok := pmap[name]
   341  	if !ok || info.Front() == nil {
   342  		return nil, fmt.Errorf("plugin %s for type %s not found", name, ptype)
   343  	}
   344  
   345  	return dispenseFunc(info.Front().Value.(*PluginInfo))
   346  }
   347  
   348  func (d *dynamicRegistry) PluginForAlloc(ptype, name, allocID string) (*PluginInfo, error) {
   349  	d.pluginsLock.Lock()
   350  	defer d.pluginsLock.Unlock()
   351  
   352  	pmap, ok := d.plugins[ptype]
   353  	if !ok {
   354  		return nil, fmt.Errorf("no plugins registered for type: %s", ptype)
   355  	}
   356  
   357  	infos, ok := pmap[name]
   358  	if ok {
   359  		for e := infos.Front(); e != nil; e = e.Next() {
   360  			plugin := e.Value.(*PluginInfo)
   361  			if plugin.AllocID == allocID {
   362  				return plugin, nil
   363  			}
   364  		}
   365  	}
   366  	return nil, fmt.Errorf("no plugin for that allocation")
   367  }
   368  
   369  // PluginsUpdatedCh returns a channel over which plugin events for the requested
   370  // plugin type will be emitted. These events are strongly ordered and will never
   371  // be dropped.
   372  //
   373  // The receiving channel _must not_ be closed before the provided context is
   374  // cancelled.
   375  func (d *dynamicRegistry) PluginsUpdatedCh(ctx context.Context, ptype string) <-chan *PluginUpdateEvent {
   376  	b := d.broadcasterForPluginType(ptype)
   377  	ch := b.subscribe()
   378  	go func() {
   379  		select {
   380  		case <-b.shutdownCh:
   381  			return
   382  		case <-ctx.Done():
   383  			b.unsubscribe(ch)
   384  		}
   385  	}()
   386  
   387  	return ch
   388  }
   389  
   390  func (d *dynamicRegistry) sync() error {
   391  	if d.state != nil {
   392  		storedState := &RegistryState{Plugins: d.plugins}
   393  		return d.state.PutDynamicPluginRegistryState(storedState)
   394  	}
   395  	return nil
   396  }
   397  
   398  func (d *dynamicRegistry) Shutdown() {
   399  	for _, b := range d.broadcasters {
   400  		b.shutdown()
   401  	}
   402  }
   403  
   404  type pluginEventBroadcaster struct {
   405  	stopCh     chan struct{}
   406  	shutdownCh chan struct{}
   407  	publishCh  chan *PluginUpdateEvent
   408  
   409  	subscriptions     map[chan *PluginUpdateEvent]struct{}
   410  	subscriptionsLock sync.RWMutex
   411  }
   412  
   413  func newPluginEventBroadcaster() *pluginEventBroadcaster {
   414  	b := &pluginEventBroadcaster{
   415  		stopCh:        make(chan struct{}),
   416  		shutdownCh:    make(chan struct{}),
   417  		publishCh:     make(chan *PluginUpdateEvent, 1),
   418  		subscriptions: make(map[chan *PluginUpdateEvent]struct{}),
   419  	}
   420  	go b.run()
   421  	return b
   422  }
   423  
   424  func (p *pluginEventBroadcaster) run() {
   425  	for {
   426  		select {
   427  		case <-p.stopCh:
   428  			close(p.shutdownCh)
   429  			return
   430  		case msg := <-p.publishCh:
   431  			p.subscriptionsLock.RLock()
   432  			for msgCh := range p.subscriptions {
   433  				msgCh <- msg
   434  			}
   435  			p.subscriptionsLock.RUnlock()
   436  		}
   437  	}
   438  }
   439  
   440  func (p *pluginEventBroadcaster) shutdown() {
   441  	close(p.stopCh)
   442  
   443  	// Wait for loop to exit before closing subscriptions
   444  	<-p.shutdownCh
   445  
   446  	p.subscriptionsLock.Lock()
   447  	for sub := range p.subscriptions {
   448  		delete(p.subscriptions, sub)
   449  		close(sub)
   450  	}
   451  	p.subscriptionsLock.Unlock()
   452  }
   453  
   454  func (p *pluginEventBroadcaster) broadcast(e *PluginUpdateEvent) {
   455  	p.publishCh <- e
   456  }
   457  
   458  func (p *pluginEventBroadcaster) subscribe() chan *PluginUpdateEvent {
   459  	p.subscriptionsLock.Lock()
   460  	defer p.subscriptionsLock.Unlock()
   461  
   462  	ch := make(chan *PluginUpdateEvent, 1)
   463  	p.subscriptions[ch] = struct{}{}
   464  	return ch
   465  }
   466  
   467  func (p *pluginEventBroadcaster) unsubscribe(ch chan *PluginUpdateEvent) {
   468  	p.subscriptionsLock.Lock()
   469  	defer p.subscriptionsLock.Unlock()
   470  
   471  	_, ok := p.subscriptions[ch]
   472  	if ok {
   473  		delete(p.subscriptions, ch)
   474  		close(ch)
   475  	}
   476  }