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