mirror of
https://github.com/kemko/nomad.git
synced 2026-01-05 09:55:44 +03:00
loader and singleton
This commit is contained in:
committed by
Michael Schurter
parent
b9f36134dc
commit
c19cd2e5cf
284
pluginutils/loader/loader.go
Normal file
284
pluginutils/loader/loader.go
Normal file
@@ -0,0 +1,284 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/nomad/nomad/structs/config"
|
||||
"github.com/hashicorp/nomad/plugins"
|
||||
"github.com/hashicorp/nomad/plugins/base"
|
||||
"github.com/hashicorp/nomad/plugins/device"
|
||||
"github.com/hashicorp/nomad/plugins/drivers"
|
||||
"github.com/hashicorp/nomad/plugins/shared/hclspec"
|
||||
)
|
||||
|
||||
// PluginCatalog is used to retrieve plugins, either external or internal
|
||||
type PluginCatalog interface {
|
||||
// Dispense returns the plugin given its name and type. This will also
|
||||
// configure the plugin
|
||||
Dispense(name, pluginType string, config *base.AgentConfig, logger log.Logger) (PluginInstance, error)
|
||||
|
||||
// Reattach is used to reattach to a previously launched external plugin.
|
||||
Reattach(name, pluginType string, config *plugin.ReattachConfig) (PluginInstance, error)
|
||||
|
||||
// Catalog returns the catalog of all plugins keyed by plugin type
|
||||
Catalog() map[string][]*base.PluginInfoResponse
|
||||
}
|
||||
|
||||
// InternalPluginConfig is used to configure launching an internal plugin.
|
||||
type InternalPluginConfig struct {
|
||||
Config map[string]interface{}
|
||||
Factory plugins.PluginFactory
|
||||
}
|
||||
|
||||
// PluginID is a tuple identifying a plugin
|
||||
type PluginID struct {
|
||||
// Name is the name of the plugin
|
||||
Name string
|
||||
|
||||
// PluginType is the plugin's type
|
||||
PluginType string
|
||||
}
|
||||
|
||||
// String returns a friendly representation of the plugin.
|
||||
func (id PluginID) String() string {
|
||||
return fmt.Sprintf("%q (%v)", id.Name, id.PluginType)
|
||||
}
|
||||
|
||||
func PluginInfoID(resp *base.PluginInfoResponse) PluginID {
|
||||
return PluginID{
|
||||
Name: resp.Name,
|
||||
PluginType: resp.Type,
|
||||
}
|
||||
}
|
||||
|
||||
// PluginLoaderConfig configures a plugin loader.
|
||||
type PluginLoaderConfig struct {
|
||||
// Logger is the logger used by the plugin loader
|
||||
Logger log.Logger
|
||||
|
||||
// PluginDir is the directory scanned for loading plugins
|
||||
PluginDir string
|
||||
|
||||
// Configs is an optional set of configs for plugins
|
||||
Configs []*config.PluginConfig
|
||||
|
||||
// InternalPlugins allows registering internal plugins.
|
||||
InternalPlugins map[PluginID]*InternalPluginConfig
|
||||
|
||||
// SupportedVersions is a mapping of plugin type to the supported versions
|
||||
SupportedVersions map[string][]string
|
||||
}
|
||||
|
||||
// PluginLoader is used to retrieve plugins either externally or from internal
|
||||
// factories.
|
||||
type PluginLoader struct {
|
||||
// logger is the plugin loaders logger
|
||||
logger log.Logger
|
||||
|
||||
// supportedVersions is a mapping of plugin type to the supported versions
|
||||
supportedVersions map[string][]*version.Version
|
||||
|
||||
// pluginDir is the directory containing plugin binaries
|
||||
pluginDir string
|
||||
|
||||
// plugins maps a plugin to information required to launch it
|
||||
plugins map[PluginID]*pluginInfo
|
||||
}
|
||||
|
||||
// pluginInfo captures the necessary information to launch and configure a
|
||||
// plugin.
|
||||
type pluginInfo struct {
|
||||
factory plugins.PluginFactory
|
||||
|
||||
exePath string
|
||||
args []string
|
||||
|
||||
baseInfo *base.PluginInfoResponse
|
||||
version *version.Version
|
||||
apiVersion string
|
||||
|
||||
configSchema *hclspec.Spec
|
||||
config map[string]interface{}
|
||||
msgpackConfig []byte
|
||||
}
|
||||
|
||||
// NewPluginLoader returns an instance of a plugin loader or an error if the
|
||||
// plugins could not be loaded
|
||||
func NewPluginLoader(config *PluginLoaderConfig) (*PluginLoader, error) {
|
||||
if err := validateConfig(config); err != nil {
|
||||
return nil, fmt.Errorf("invalid plugin loader configuration passed: %v", err)
|
||||
}
|
||||
|
||||
// Convert the versions
|
||||
supportedVersions := make(map[string][]*version.Version, len(config.SupportedVersions))
|
||||
for pType, versions := range config.SupportedVersions {
|
||||
converted, err := convertVersions(versions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
supportedVersions[pType] = converted
|
||||
}
|
||||
|
||||
logger := config.Logger.Named("plugin_loader").With("plugin_dir", config.PluginDir)
|
||||
l := &PluginLoader{
|
||||
logger: logger,
|
||||
supportedVersions: supportedVersions,
|
||||
pluginDir: config.PluginDir,
|
||||
plugins: make(map[PluginID]*pluginInfo),
|
||||
}
|
||||
|
||||
if err := l.init(config); err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize plugin loader: %v", err)
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// Dispense returns a plugin instance, loading it either internally or by
|
||||
// launching an external plugin.
|
||||
func (l *PluginLoader) Dispense(name, pluginType string, config *base.AgentConfig, logger log.Logger) (PluginInstance, error) {
|
||||
id := PluginID{
|
||||
Name: name,
|
||||
PluginType: pluginType,
|
||||
}
|
||||
pinfo, ok := l.plugins[id]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown plugin with name %q and type %q", name, pluginType)
|
||||
}
|
||||
|
||||
// If the plugin is internal, launch via the factory
|
||||
var instance PluginInstance
|
||||
if pinfo.factory != nil {
|
||||
instance = &internalPluginInstance{
|
||||
instance: pinfo.factory(logger),
|
||||
apiVersion: pinfo.apiVersion,
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
instance, err = l.dispensePlugin(pinfo.baseInfo.Type, pinfo.apiVersion, pinfo.exePath, pinfo.args, nil, logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to launch plugin: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Cast to the base type and set the config
|
||||
b, ok := instance.Plugin().(base.BasePlugin)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("plugin %s doesn't implement base plugin interface", id)
|
||||
}
|
||||
|
||||
c := &base.Config{
|
||||
PluginConfig: pinfo.msgpackConfig,
|
||||
AgentConfig: config,
|
||||
ApiVersion: pinfo.apiVersion,
|
||||
}
|
||||
|
||||
if err := b.SetConfig(c); err != nil {
|
||||
return nil, fmt.Errorf("setting config for plugin %s failed: %v", id, err)
|
||||
}
|
||||
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// Reattach reattaches to a previously launched external plugin.
|
||||
func (l *PluginLoader) Reattach(name, pluginType string, config *plugin.ReattachConfig) (PluginInstance, error) {
|
||||
return l.dispensePlugin(pluginType, "", "", nil, config, l.logger)
|
||||
}
|
||||
|
||||
// dispensePlugin is used to launch or reattach to an external plugin.
|
||||
func (l *PluginLoader) dispensePlugin(
|
||||
pluginType, apiVersion, cmd string, args []string, reattach *plugin.ReattachConfig,
|
||||
logger log.Logger) (PluginInstance, error) {
|
||||
|
||||
var pluginCmd *exec.Cmd
|
||||
if cmd != "" && reattach != nil {
|
||||
return nil, fmt.Errorf("both launch command and reattach config specified")
|
||||
} else if cmd == "" && reattach == nil {
|
||||
return nil, fmt.Errorf("one of launch command or reattach config must be specified")
|
||||
} else if cmd != "" {
|
||||
pluginCmd = exec.Command(cmd, args...)
|
||||
}
|
||||
|
||||
client := plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: base.Handshake,
|
||||
Plugins: getPluginMap(pluginType),
|
||||
Cmd: pluginCmd,
|
||||
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
|
||||
Logger: logger,
|
||||
Reattach: reattach,
|
||||
})
|
||||
|
||||
// Connect via RPC
|
||||
rpcClient, err := client.Client()
|
||||
if err != nil {
|
||||
client.Kill()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Request the plugin
|
||||
raw, err := rpcClient.Dispense(pluginType)
|
||||
if err != nil {
|
||||
client.Kill()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instance := &externalPluginInstance{
|
||||
client: client,
|
||||
instance: raw,
|
||||
}
|
||||
|
||||
if apiVersion != "" {
|
||||
instance.apiVersion = apiVersion
|
||||
} else {
|
||||
// We do not know the API version since we are reattaching, so discover
|
||||
// it
|
||||
bplugin := raw.(base.BasePlugin)
|
||||
|
||||
// Retrieve base plugin information
|
||||
i, err := bplugin.PluginInfo()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get plugin info for plugin: %v", err)
|
||||
}
|
||||
|
||||
apiVersion, err := l.selectApiVersion(i)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate API versions %v for plugin %s: %v", i.PluginApiVersions, i.Name, err)
|
||||
}
|
||||
if apiVersion == "" {
|
||||
return nil, fmt.Errorf("failed to reattach to plugin because supported API versions for the plugin and Nomad do not overlap")
|
||||
}
|
||||
|
||||
instance.apiVersion = apiVersion
|
||||
}
|
||||
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// getPluginMap returns a plugin map based on the type of plugin being launched.
|
||||
func getPluginMap(pluginType string) map[string]plugin.Plugin {
|
||||
pmap := map[string]plugin.Plugin{
|
||||
base.PluginTypeBase: &base.PluginBase{},
|
||||
}
|
||||
|
||||
switch pluginType {
|
||||
case base.PluginTypeDevice:
|
||||
pmap[base.PluginTypeDevice] = &device.PluginDevice{}
|
||||
case base.PluginTypeDriver:
|
||||
pmap[base.PluginTypeDriver] = &drivers.PluginDriver{}
|
||||
}
|
||||
|
||||
return pmap
|
||||
}
|
||||
|
||||
// Catalog returns the catalog of all plugins
|
||||
func (l *PluginLoader) Catalog() map[string][]*base.PluginInfoResponse {
|
||||
c := make(map[string][]*base.PluginInfoResponse, 3)
|
||||
for id, info := range l.plugins {
|
||||
c[id.PluginType] = append(c[id.PluginType], info.baseInfo)
|
||||
}
|
||||
return c
|
||||
}
|
||||
Reference in New Issue
Block a user