mirror of
https://github.com/kemko/nomad.git
synced 2026-01-02 00:15:43 +03:00
Introduce a device manager that manages the lifecycle of device plugins on the client. It fingerprints, collects stats, and forwards Reserve requests to the correct plugin. The manager, also handles device plugins failing and validates their output.
126 lines
4.0 KiB
Go
126 lines
4.0 KiB
Go
package singleton
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
log "github.com/hashicorp/go-hclog"
|
|
plugin "github.com/hashicorp/go-plugin"
|
|
"github.com/hashicorp/nomad/plugins/base"
|
|
"github.com/hashicorp/nomad/plugins/shared/loader"
|
|
)
|
|
|
|
var (
|
|
// SingletonPluginExited is returned when the dispense is called and the
|
|
// existing plugin has exited. The caller should retry, and this will issue
|
|
// a new plugin instance.
|
|
SingletonPluginExited = fmt.Errorf("singleton plugin exited")
|
|
)
|
|
|
|
// SingletonLoader is used to only load a single external plugin at a time.
|
|
type SingletonLoader struct {
|
|
// Loader is the underlying plugin loader that we wrap to give a singleton
|
|
// behavior.
|
|
loader loader.PluginCatalog
|
|
|
|
// instances is a mapping of the plugin to a future which holds a plugin
|
|
// instance
|
|
instances map[loader.PluginID]*future
|
|
instanceLock sync.Mutex
|
|
|
|
// logger is the logger used by the singleton
|
|
logger log.Logger
|
|
}
|
|
|
|
// NewSingletonLoader wraps a plugin catalog and provides singleton behavior on
|
|
// top by caching running instances.
|
|
func NewSingletonLoader(logger log.Logger, catalog loader.PluginCatalog) *SingletonLoader {
|
|
return &SingletonLoader{
|
|
loader: catalog,
|
|
logger: logger.Named("singleton_plugin_loader"),
|
|
instances: make(map[loader.PluginID]*future, 4),
|
|
}
|
|
}
|
|
|
|
// Catalog returns the catalog of all plugins keyed by plugin type
|
|
func (s *SingletonLoader) Catalog() map[string][]*base.PluginInfoResponse {
|
|
return s.loader.Catalog()
|
|
}
|
|
|
|
// Dispense returns the plugin given its name and type. This will also
|
|
// configure the plugin. If there is an instance of an already running plugin,
|
|
// this is used.
|
|
func (s *SingletonLoader) Dispense(name, pluginType string, config *base.ClientAgentConfig, logger log.Logger) (loader.PluginInstance, error) {
|
|
return s.getPlugin(false, name, pluginType, logger, config, nil)
|
|
}
|
|
|
|
// Reattach is used to reattach to a previously launched external plugin.
|
|
func (s *SingletonLoader) Reattach(name, pluginType string, config *plugin.ReattachConfig) (loader.PluginInstance, error) {
|
|
return s.getPlugin(true, name, pluginType, nil, nil, config)
|
|
}
|
|
|
|
// getPlugin is a helper that either dispenses or reattaches to a plugin using
|
|
// futures to ensure only a single instance is retrieved
|
|
func (s *SingletonLoader) getPlugin(reattach bool, name, pluginType string, logger log.Logger,
|
|
nomadConfig *base.ClientAgentConfig, config *plugin.ReattachConfig) (loader.PluginInstance, error) {
|
|
|
|
// Lock the instance map to prevent races
|
|
s.instanceLock.Lock()
|
|
|
|
// Check if there is a future already
|
|
id := loader.PluginID{Name: name, PluginType: pluginType}
|
|
f, ok := s.instances[id]
|
|
|
|
// Create the future and go get a plugin
|
|
if !ok {
|
|
f = newFuture()
|
|
s.instances[id] = f
|
|
|
|
if reattach {
|
|
go s.reattach(f, name, pluginType, config)
|
|
} else {
|
|
go s.dispense(f, name, pluginType, nomadConfig, logger)
|
|
}
|
|
}
|
|
|
|
// Unlock so that the created future can be shared
|
|
s.instanceLock.Unlock()
|
|
|
|
i, err := f.wait().result()
|
|
if err != nil {
|
|
s.clearFuture(id, f)
|
|
return nil, err
|
|
}
|
|
|
|
if i.Exited() {
|
|
s.clearFuture(id, f)
|
|
return nil, SingletonPluginExited
|
|
}
|
|
|
|
return i, nil
|
|
}
|
|
|
|
// dispense should be called in a go routine to not block and creates the
|
|
// desired plugin, setting the results in the future.
|
|
func (s *SingletonLoader) dispense(f *future, name, pluginType string, config *base.ClientAgentConfig, logger log.Logger) {
|
|
i, err := s.loader.Dispense(name, pluginType, config, logger)
|
|
f.set(i, err)
|
|
}
|
|
|
|
// reattach should be called in a go routine to not block and reattaches to the
|
|
// desired plugin, setting the results in the future.
|
|
func (s *SingletonLoader) reattach(f *future, name, pluginType string, config *plugin.ReattachConfig) {
|
|
i, err := s.loader.Reattach(name, pluginType, config)
|
|
f.set(i, err)
|
|
}
|
|
|
|
// clearFuture clears the future from the instances map only if the futures
|
|
// match. This prevents clearing the unintented instance.
|
|
func (s *SingletonLoader) clearFuture(id loader.PluginID, f *future) {
|
|
s.instanceLock.Lock()
|
|
defer s.instanceLock.Unlock()
|
|
if f.equal(s.instances[id]) {
|
|
delete(s.instances, id)
|
|
}
|
|
}
|