Add plugin API versioning to plugin loader and plugins

This commit is contained in:
Alex Dadgar
2018-12-17 16:40:58 -08:00
parent 0cdf6634a5
commit ed4f8eac6e
33 changed files with 929 additions and 315 deletions

View File

@@ -9,7 +9,6 @@ import (
"time"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/nomad/structs/config"
@@ -368,8 +367,8 @@ func (c *Config) ReadStringListToMapDefault(key, defaultValue string) map[string
}
// NomadPluginConfig produces the NomadConfig struct which is sent to Nomad plugins
func (c *Config) NomadPluginConfig() *base.ClientAgentConfig {
return &base.ClientAgentConfig{
func (c *Config) NomadPluginConfig() *base.AgentConfig {
return &base.AgentConfig{
Driver: &base.ClientDriverConfig{
ClientMinPort: c.ClientMinPort,
ClientMaxPort: c.ClientMaxPort,

View File

@@ -40,7 +40,7 @@ type instanceManagerConfig struct {
StoreReattach StorePluginReattachFn
// PluginConfig is the config passed to the launched plugins
PluginConfig *base.ClientAgentConfig
PluginConfig *base.AgentConfig
// Id is the ID of the plugin being managed
Id *loader.PluginID
@@ -70,7 +70,7 @@ type instanceManager struct {
storeReattach StorePluginReattachFn
// pluginConfig is the config passed to the launched plugins
pluginConfig *base.ClientAgentConfig
pluginConfig *base.AgentConfig
// id is the ID of the plugin being managed
id *loader.PluginID

View File

@@ -63,7 +63,7 @@ type Config struct {
Loader loader.PluginCatalog
// PluginConfig is the config passed to the launched plugins
PluginConfig *base.ClientAgentConfig
PluginConfig *base.AgentConfig
// Updater is used to update the node when device information changes
Updater UpdateNodeDevicesFn
@@ -91,7 +91,7 @@ type manager struct {
loader loader.PluginCatalog
// pluginConfig is the config passed to the launched plugins
pluginConfig *base.ClientAgentConfig
pluginConfig *base.AgentConfig
// updater is used to update the node when device information changes
updater UpdateNodeDevicesFn

View File

@@ -121,7 +121,7 @@ func baseTestConfig(t *testing.T) (
// Create the config
config = &Config{
Logger: testlog.HCLogger(t),
PluginConfig: &base.ClientAgentConfig{},
PluginConfig: &base.AgentConfig{},
StatsInterval: 100 * time.Millisecond,
State: state.NewMemDB(),
Updater: updateFn,
@@ -133,7 +133,7 @@ func baseTestConfig(t *testing.T) (
func configureCatalogWith(catalog *loader.MockCatalog, plugins map[*base.PluginInfoResponse]loader.PluginInstance) {
catalog.DispenseF = func(name, _ string, _ *base.ClientAgentConfig, _ log.Logger) (loader.PluginInstance, error) {
catalog.DispenseF = func(name, _ string, _ *base.AgentConfig, _ log.Logger) (loader.PluginInstance, error) {
for info, v := range plugins {
if info.Name == name {
return v, nil
@@ -167,10 +167,10 @@ func configureCatalogWith(catalog *loader.MockCatalog, plugins map[*base.PluginI
func pluginInfoResponse(name string) *base.PluginInfoResponse {
return &base.PluginInfoResponse{
Type: base.PluginTypeDevice,
PluginApiVersion: "v0.0.1",
PluginVersion: "v0.0.1",
Name: name,
Type: base.PluginTypeDevice,
PluginApiVersions: []string{"v0.0.1"},
PluginVersion: "v0.0.1",
Name: name,
}
}
@@ -209,7 +209,7 @@ func nvidiaAndIntelDefaultPlugins(catalog *loader.MockCatalog) {
ReserveF: deviceReserveFn,
StatsF: device.StaticStats([]*device.DeviceGroupStats{nvidiaDeviceGroupStats}),
}
pluginNvidia := loader.MockBasicExternalPlugin(deviceNvidia)
pluginNvidia := loader.MockBasicExternalPlugin(deviceNvidia, device.ApiVersion010)
pluginInfoIntel := pluginInfoResponse("intel")
deviceIntel := &device.MockDevicePlugin{
@@ -222,7 +222,7 @@ func nvidiaAndIntelDefaultPlugins(catalog *loader.MockCatalog) {
ReserveF: deviceReserveFn,
StatsF: device.StaticStats([]*device.DeviceGroupStats{intelDeviceGroupStats}),
}
pluginIntel := loader.MockBasicExternalPlugin(deviceIntel)
pluginIntel := loader.MockBasicExternalPlugin(deviceIntel, device.ApiVersion010)
// Configure the catalog with two plugins
configureCatalogWith(catalog, map[*base.PluginInfoResponse]loader.PluginInstance{
@@ -454,7 +454,7 @@ func TestManager_Shutdown(t *testing.T) {
m.Shutdown()
for _, resp := range catalog.Catalog()[base.PluginTypeDevice] {
pinst, _ := catalog.Dispense(resp.Name, resp.Type, &base.ClientAgentConfig{}, config.Logger)
pinst, _ := catalog.Dispense(resp.Name, resp.Type, &base.AgentConfig{}, config.Logger)
require.True(pinst.Exited())
}
}
@@ -486,7 +486,7 @@ func TestManager_Run_ShutdownOld(t *testing.T) {
testutil.WaitForResult(func() (bool, error) {
for _, resp := range catalog.Catalog()[base.PluginTypeDevice] {
pinst, _ := catalog.Dispense(resp.Name, resp.Type, &base.ClientAgentConfig{}, config.Logger)
pinst, _ := catalog.Dispense(resp.Name, resp.Type, &base.AgentConfig{}, config.Logger)
if !pinst.Exited() {
return false, fmt.Errorf("plugin %q not shutdown", resp.Name)
}

View File

@@ -18,10 +18,11 @@ func (a *Agent) setupPlugins() error {
// Build the plugin loader
config := &loader.PluginLoaderConfig{
Logger: a.logger,
PluginDir: a.config.PluginDir,
Configs: a.config.Plugins,
InternalPlugins: internal,
Logger: a.logger,
PluginDir: a.config.PluginDir,
Configs: a.config.Plugins,
InternalPlugins: internal,
SupportedVersions: loader.AgentSupportedApiVersions,
}
l, err := loader.NewPluginLoader(config)
if err != nil {

View File

@@ -37,10 +37,10 @@ const (
var (
// pluginInfo describes the plugin
pluginInfo = &base.PluginInfoResponse{
Type: base.PluginTypeDevice,
PluginApiVersion: "0.0.1", // XXX This should be an array and should be consts
PluginVersion: "0.1.0",
Name: pluginName,
Type: base.PluginTypeDevice,
PluginApiVersions: []string{"v0.2.0"},
PluginVersion: "0.1.0",
Name: pluginName,
}
// configSpec is the specification of the plugin's configuration
@@ -111,10 +111,12 @@ func (d *NvidiaDevice) ConfigSchema() (*hclspec.Spec, error) {
}
// SetConfig is used to set the configuration of the plugin.
func (d *NvidiaDevice) SetConfig(data []byte, cfg *base.ClientAgentConfig) error {
func (d *NvidiaDevice) SetConfig(cfg *base.Config) error {
var config Config
if err := base.MsgPackDecode(data, &config); err != nil {
return err
if len(cfg.PluginConfig) != 0 {
if err := base.MsgPackDecode(cfg.PluginConfig, &config); err != nil {
return err
}
}
for _, ignoredGPUId := range config.IgnoredGPUIDs {

View File

@@ -122,10 +122,10 @@ var (
// pluginInfo is the response returned for the PluginInfo RPC
pluginInfo = &base.PluginInfoResponse{
Type: base.PluginTypeDriver,
PluginApiVersion: "0.0.1",
PluginVersion: "0.1.0",
Name: pluginName,
Type: base.PluginTypeDriver,
PluginApiVersions: []string{drivers.ApiVersion010},
PluginVersion: "0.1.0",
Name: pluginName,
}
// configSpec is the hcl specification returned by the ConfigSchema RPC
@@ -502,10 +502,12 @@ func (d *Driver) ConfigSchema() (*hclspec.Spec, error) {
return configSpec, nil
}
func (d *Driver) SetConfig(data []byte, cfg *base.ClientAgentConfig) error {
func (d *Driver) SetConfig(c *base.Config) error {
var config DriverConfig
if err := base.MsgPackDecode(data, &config); err != nil {
return err
if len(c.PluginConfig) != 0 {
if err := base.MsgPackDecode(c.PluginConfig, &config); err != nil {
return err
}
}
d.config = &config
@@ -517,8 +519,8 @@ func (d *Driver) SetConfig(data []byte, cfg *base.ClientAgentConfig) error {
d.config.GC.imageDelayDuration = dur
}
if cfg != nil {
d.clientConfig = cfg.Driver
if c.AgentConfig != nil {
d.clientConfig = c.AgentConfig.Driver
}
dockerClient, _, err := d.dockerClients()

View File

@@ -49,10 +49,10 @@ var (
// pluginInfo is the response returned for the PluginInfo RPC
pluginInfo = &base.PluginInfoResponse{
Type: base.PluginTypeDriver,
PluginApiVersion: "0.0.1",
PluginVersion: "0.1.0",
Name: pluginName,
Type: base.PluginTypeDriver,
PluginApiVersions: []string{drivers.ApiVersion010},
PluginVersion: "0.1.0",
Name: pluginName,
}
// configSpec is the hcl specification returned by the ConfigSchema RPC
@@ -136,9 +136,9 @@ func (d *Driver) ConfigSchema() (*hclspec.Spec, error) {
return configSpec, nil
}
func (d *Driver) SetConfig(_ []byte, cfg *base.ClientAgentConfig) error {
if cfg != nil {
d.nomadConfig = cfg.Driver
func (d *Driver) SetConfig(cfg *base.Config) error {
if cfg != nil && cfg.AgentConfig != nil {
d.nomadConfig = cfg.AgentConfig.Driver
}
return nil
}

View File

@@ -54,10 +54,10 @@ var (
// pluginInfo is the response returned for the PluginInfo RPC
pluginInfo = &base.PluginInfoResponse{
Type: base.PluginTypeDriver,
PluginApiVersion: "0.0.1",
PluginVersion: "0.1.0",
Name: pluginName,
Type: base.PluginTypeDriver,
PluginApiVersions: []string{drivers.ApiVersion010},
PluginVersion: "0.1.0",
Name: pluginName,
}
// configSpec is the hcl specification returned by the ConfigSchema RPC
@@ -156,9 +156,9 @@ func (d *Driver) ConfigSchema() (*hclspec.Spec, error) {
return configSpec, nil
}
func (d *Driver) SetConfig(_ []byte, cfg *base.ClientAgentConfig) error {
if cfg != nil {
d.nomadConfig = cfg.Driver
func (d *Driver) SetConfig(cfg *base.Config) error {
if cfg != nil && cfg.AgentConfig != nil {
d.nomadConfig = cfg.AgentConfig.Driver
}
return nil
}

View File

@@ -62,10 +62,10 @@ func PluginLoader(opts map[string]string) (map[string]interface{}, error) {
var (
// pluginInfo is the response returned for the PluginInfo RPC
pluginInfo = &base.PluginInfoResponse{
Type: base.PluginTypeDriver,
PluginApiVersion: "0.0.1",
PluginVersion: "0.1.0",
Name: pluginName,
Type: base.PluginTypeDriver,
PluginApiVersions: []string{drivers.ApiVersion010},
PluginVersion: "0.1.0",
Name: pluginName,
}
// configSpec is the hcl specification returned by the ConfigSchema RPC
@@ -200,15 +200,17 @@ func (d *Driver) ConfigSchema() (*hclspec.Spec, error) {
return configSpec, nil
}
func (d *Driver) SetConfig(data []byte, cfg *base.ClientAgentConfig) error {
func (d *Driver) SetConfig(cfg *base.Config) error {
var config Config
if err := base.MsgPackDecode(data, &config); err != nil {
return err
if len(cfg.PluginConfig) != 0 {
if err := base.MsgPackDecode(cfg.PluginConfig, &config); err != nil {
return err
}
}
d.config = &config
if cfg != nil {
d.nomadConfig = cfg.Driver
if cfg.AgentConfig != nil {
d.nomadConfig = cfg.AgentConfig.Driver
}
return nil

View File

@@ -45,10 +45,10 @@ var (
// pluginInfo is the response returned for the PluginInfo RPC
pluginInfo = &base.PluginInfoResponse{
Type: base.PluginTypeDriver,
PluginApiVersion: "0.0.1",
PluginVersion: "0.1.0",
Name: pluginName,
Type: base.PluginTypeDriver,
PluginApiVersions: []string{drivers.ApiVersion010},
PluginVersion: "0.1.0",
Name: pluginName,
}
// configSpec is the hcl specification returned by the ConfigSchema RPC
@@ -225,10 +225,12 @@ func (d *Driver) ConfigSchema() (*hclspec.Spec, error) {
return configSpec, nil
}
func (d *Driver) SetConfig(data []byte, cfg *base.ClientAgentConfig) error {
func (d *Driver) SetConfig(cfg *base.Config) error {
var config Config
if err := base.MsgPackDecode(data, &config); err != nil {
return err
if len(cfg.PluginConfig) != 0 {
if err := base.MsgPackDecode(cfg.PluginConfig, &config); err != nil {
return err
}
}
d.config = &config

View File

@@ -74,10 +74,10 @@ var (
// pluginInfo is the response returned for the PluginInfo RPC
pluginInfo = &base.PluginInfoResponse{
Type: base.PluginTypeDriver,
PluginApiVersion: "0.0.1",
PluginVersion: "0.1.0",
Name: pluginName,
Type: base.PluginTypeDriver,
PluginApiVersions: []string{drivers.ApiVersion010},
PluginVersion: "0.1.0",
Name: pluginName,
}
// configSpec is the hcl specification returned by the ConfigSchema RPC
@@ -167,9 +167,9 @@ func (d *Driver) ConfigSchema() (*hclspec.Spec, error) {
return configSpec, nil
}
func (d *Driver) SetConfig(_ []byte, cfg *base.ClientAgentConfig) error {
if cfg != nil {
d.nomadConfig = cfg.Driver
func (d *Driver) SetConfig(cfg *base.Config) error {
if cfg.AgentConfig != nil {
d.nomadConfig = cfg.AgentConfig.Driver
}
return nil
}

View File

@@ -63,10 +63,10 @@ func PluginLoader(opts map[string]string) (map[string]interface{}, error) {
var (
// pluginInfo is the response returned for the PluginInfo RPC
pluginInfo = &base.PluginInfoResponse{
Type: base.PluginTypeDriver,
PluginApiVersion: "0.0.1",
PluginVersion: "0.1.0",
Name: pluginName,
Type: base.PluginTypeDriver,
PluginApiVersions: []string{drivers.ApiVersion010},
PluginVersion: "0.1.0",
Name: pluginName,
}
// configSpec is the hcl specification returned by the ConfigSchema RPC
@@ -174,15 +174,17 @@ func (d *Driver) ConfigSchema() (*hclspec.Spec, error) {
return configSpec, nil
}
func (d *Driver) SetConfig(data []byte, cfg *base.ClientAgentConfig) error {
func (d *Driver) SetConfig(cfg *base.Config) error {
var config Config
if err := base.MsgPackDecode(data, &config); err != nil {
return err
if len(cfg.PluginConfig) != 0 {
if err := base.MsgPackDecode(cfg.PluginConfig, &config); err != nil {
return err
}
}
d.config = &config
if cfg != nil {
d.nomadConfig = cfg.Driver
if cfg.AgentConfig != nil {
d.nomadConfig = cfg.AgentConfig.Driver
}
return nil
}

View File

@@ -42,25 +42,30 @@ func TestRawExecDriver_SetConfig(t *testing.T) {
harness := dtestutil.NewDriverHarness(t, d)
defer harness.Kill()
bconfig := &basePlug.Config{}
// Disable raw exec.
config := &Config{}
var data []byte
require.NoError(basePlug.MsgPackEncode(&data, config))
require.NoError(harness.SetConfig(data, nil))
bconfig.PluginConfig = data
require.NoError(harness.SetConfig(bconfig))
require.Exactly(config, d.(*Driver).config)
config.Enabled = true
config.NoCgroups = true
data = []byte{}
require.NoError(basePlug.MsgPackEncode(&data, config))
require.NoError(harness.SetConfig(data, nil))
bconfig.PluginConfig = data
require.NoError(harness.SetConfig(bconfig))
require.Exactly(config, d.(*Driver).config)
config.NoCgroups = false
data = []byte{}
require.NoError(basePlug.MsgPackEncode(&data, config))
require.NoError(harness.SetConfig(data, nil))
bconfig.PluginConfig = data
require.NoError(harness.SetConfig(bconfig))
require.Exactly(config, d.(*Driver).config)
}
@@ -76,7 +81,10 @@ func TestRawExecDriver_Fingerprint(t *testing.T) {
var data []byte
require.NoError(basePlug.MsgPackEncode(&data, config))
require.NoError(harness.SetConfig(data, nil))
bconfig := &basePlug.Config{
PluginConfig: data,
}
require.NoError(harness.SetConfig(bconfig))
fingerCh, err := harness.Fingerprint(context.Background())
require.NoError(err)
@@ -168,7 +176,8 @@ func TestRawExecDriver_StartWaitStop(t *testing.T) {
config := &Config{NoCgroups: true}
var data []byte
require.NoError(basePlug.MsgPackEncode(&data, config))
require.NoError(harness.SetConfig(data, nil))
bconfig := &basePlug.Config{PluginConfig: data}
require.NoError(harness.SetConfig(bconfig))
task := &drivers.TaskConfig{
ID: uuid.Generate(),
@@ -234,7 +243,8 @@ func TestRawExecDriver_StartWaitRecoverWaitStop(t *testing.T) {
config := &Config{NoCgroups: true}
var data []byte
require.NoError(basePlug.MsgPackEncode(&data, config))
require.NoError(harness.SetConfig(data, nil))
bconfig := &basePlug.Config{PluginConfig: data}
require.NoError(harness.SetConfig(bconfig))
task := &drivers.TaskConfig{
ID: uuid.Generate(),

View File

@@ -86,10 +86,10 @@ func PluginLoader(opts map[string]string) (map[string]interface{}, error) {
var (
// pluginInfo is the response returned for the PluginInfo RPC
pluginInfo = &base.PluginInfoResponse{
Type: base.PluginTypeDriver,
PluginApiVersion: "0.0.1",
PluginVersion: "0.1.0",
Name: pluginName,
Type: base.PluginTypeDriver,
PluginApiVersions: []string{drivers.ApiVersion010},
PluginVersion: "0.1.0",
Name: pluginName,
}
// configSpec is the hcl specification returned by the ConfigSchema RPC
@@ -216,15 +216,17 @@ func (d *Driver) ConfigSchema() (*hclspec.Spec, error) {
return configSpec, nil
}
func (d *Driver) SetConfig(data []byte, cfg *base.ClientAgentConfig) error {
func (d *Driver) SetConfig(cfg *base.Config) error {
var config Config
if err := base.MsgPackDecode(data, &config); err != nil {
return err
if len(cfg.PluginConfig) != 0 {
if err := base.MsgPackDecode(cfg.PluginConfig, &config); err != nil {
return err
}
}
d.config = &config
if cfg != nil {
d.nomadConfig = cfg.Driver
if cfg.AgentConfig != nil {
d.nomadConfig = cfg.AgentConfig.Driver
}
return nil
}

View File

@@ -9,9 +9,8 @@ import (
"sync"
"time"
"github.com/hashicorp/nomad/helper"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/plugins/base"
"github.com/hashicorp/nomad/plugins/device"
"github.com/hashicorp/nomad/plugins/shared/hclspec"
@@ -38,10 +37,10 @@ const (
var (
// pluginInfo describes the plugin
pluginInfo = &base.PluginInfoResponse{
Type: base.PluginTypeDevice,
PluginApiVersion: "0.0.1", // XXX This should be an array and should be consts
PluginVersion: "0.1.0",
Name: pluginName,
Type: base.PluginTypeDevice,
PluginApiVersions: []string{device.ApiVersion010},
PluginVersion: "v0.1.0",
Name: pluginName,
}
// configSpec is the specification of the plugin's configuration
@@ -109,9 +108,9 @@ func (d *FsDevice) ConfigSchema() (*hclspec.Spec, error) {
}
// SetConfig is used to set the configuration of the plugin.
func (d *FsDevice) SetConfig(data []byte, cfg *base.ClientAgentConfig) error {
func (d *FsDevice) SetConfig(c *base.Config) error {
var config Config
if err := base.MsgPackDecode(data, &config); err != nil {
if err := base.MsgPackDecode(c.PluginConfig, &config); err != nil {
return err
}

View File

@@ -24,27 +24,30 @@ func TestDevicePlugin_PluginInfo(t *testing.T) {
t.Parallel()
require := require.New(t)
var (
apiVersions = []string{"v0.1.0", "v0.2.0"}
)
const (
apiVersion = "v0.1.0"
pluginVersion = "v0.2.1"
pluginName = "mock_device"
)
knownType := func() (*base.PluginInfoResponse, error) {
info := &base.PluginInfoResponse{
Type: base.PluginTypeDevice,
PluginApiVersion: apiVersion,
PluginVersion: pluginVersion,
Name: pluginName,
Type: base.PluginTypeDevice,
PluginApiVersions: apiVersions,
PluginVersion: pluginVersion,
Name: pluginName,
}
return info, nil
}
unknownType := func() (*base.PluginInfoResponse, error) {
info := &base.PluginInfoResponse{
Type: "bad",
PluginApiVersion: apiVersion,
PluginVersion: pluginVersion,
Name: pluginName,
Type: "bad",
PluginApiVersions: apiVersions,
PluginVersion: pluginVersion,
Name: pluginName,
}
return info, nil
}
@@ -74,7 +77,7 @@ func TestDevicePlugin_PluginInfo(t *testing.T) {
resp, err := impl.PluginInfo()
require.NoError(err)
require.Equal(apiVersion, resp.PluginApiVersion)
require.Equal(apiVersions, resp.PluginApiVersions)
require.Equal(pluginVersion, resp.PluginVersion)
require.Equal(pluginName, resp.Name)
require.Equal(base.PluginTypeDevice, resp.Type)
@@ -129,17 +132,17 @@ func TestDevicePlugin_SetConfig(t *testing.T) {
MockPlugin: &base.MockPlugin{
PluginInfoF: func() (*base.PluginInfoResponse, error) {
return &base.PluginInfoResponse{
Type: base.PluginTypeDevice,
PluginApiVersion: "v0.0.1",
PluginVersion: "v0.0.1",
Name: "mock_device",
Type: base.PluginTypeDevice,
PluginApiVersions: []string{"v0.0.1"},
PluginVersion: "v0.0.1",
Name: "mock_device",
}, nil
},
ConfigSchemaF: func() (*hclspec.Spec, error) {
return base.TestSpec, nil
},
SetConfigF: func(data []byte, cfg *base.ClientAgentConfig) error {
receivedData = data
SetConfigF: func(cfg *base.Config) error {
receivedData = cfg.PluginConfig
return nil
},
},
@@ -169,7 +172,7 @@ func TestDevicePlugin_SetConfig(t *testing.T) {
})
cdata, err := msgpack.Marshal(config, config.Type())
require.NoError(err)
require.NoError(impl.SetConfig(cdata, nil))
require.NoError(impl.SetConfig(&base.Config{PluginConfig: cdata}))
require.Equal(cdata, receivedData)
// Decode the value back

View File

@@ -0,0 +1,6 @@
package device
const (
// ApiVersion010 is the initial API version for the device plugins
ApiVersion010 = "v0.1.0"
)

View File

@@ -0,0 +1,6 @@
package drivers
const (
// ApiVersion010 is the initial API version for the device plugins
ApiVersion010 = "v0.1.0"
)

View File

@@ -51,10 +51,11 @@ func TestPluginLoaderWithOptions(t testing.T,
// Build the plugin loader
config := &loader.PluginLoaderConfig{
Logger: logger,
PluginDir: "",
Configs: configs,
InternalPlugins: internal,
Logger: logger,
PluginDir: "",
Configs: configs,
InternalPlugins: internal,
SupportedVersions: loader.AgentSupportedApiVersions,
}
l, err := loader.NewPluginLoader(config)
if err != nil {

View File

@@ -120,7 +120,7 @@ func (c *Device) Run(args []string) int {
}
c.spec = spec
if err := c.setConfig(spec, config, nil); err != nil {
if err := c.setConfig(spec, device.ApiVersion010, config, nil); err != nil {
c.logger.Error("failed to set config", "error", err)
return 1
}
@@ -188,7 +188,7 @@ func (c *Device) getSpec() (hcldec.Spec, error) {
return schema, nil
}
func (c *Device) setConfig(spec hcldec.Spec, config []byte, nmdCfg *base.ClientAgentConfig) error {
func (c *Device) setConfig(spec hcldec.Spec, apiVersion string, config []byte, nmdCfg *base.AgentConfig) error {
// Parse the config into hcl
configVal, err := hclConfigToInterface(config)
if err != nil {
@@ -216,8 +216,14 @@ func (c *Device) setConfig(spec hcldec.Spec, config []byte, nmdCfg *base.ClientA
return err
}
req := &base.Config{
PluginConfig: config,
AgentConfig: nmdCfg,
ApiVersion: apiVersion,
}
c.logger.Trace("msgpack config", "config", string(cdata))
if err := c.dev.SetConfig(cdata, nmdCfg); err != nil {
if err := c.dev.SetConfig(req); err != nil {
return err
}

View File

@@ -0,0 +1,15 @@
package loader
import (
"github.com/hashicorp/nomad/plugins/base"
"github.com/hashicorp/nomad/plugins/device"
)
var (
// AgentSupportedApiVersions is the set of API versions supported by the
// Nomad agent by plugin type.
AgentSupportedApiVersions = map[string][]string{
base.PluginTypeDevice: []string{device.ApiVersion010},
base.PluginTypeDriver: []string{device.ApiVersion010},
}
)

View File

@@ -5,6 +5,7 @@ import (
"os"
"os/exec"
"path/filepath"
"sort"
multierror "github.com/hashicorp/go-multierror"
plugin "github.com/hashicorp/go-plugin"
@@ -130,6 +131,18 @@ func (l *PluginLoader) initInternal(plugins map[PluginID]*InternalPluginConfig,
}
info.version = v
// Detect the plugin API version to use
av, err := l.selectApiVersion(i)
if err != nil {
multierror.Append(&mErr, fmt.Errorf("failed to validate API versions %v for internal plugin %s: %v", i.PluginApiVersions, k, err))
continue
}
if av == "" {
l.logger.Warn("skipping plugin because supported API versions for plugin and Nomad do not overlap", "plugin", k)
continue
}
info.apiVersion = av
// Get the config schema
schema, err := base.ConfigSchema()
if err != nil {
@@ -142,9 +155,66 @@ func (l *PluginLoader) initInternal(plugins map[PluginID]*InternalPluginConfig,
fingerprinted[k] = info
}
if err := mErr.ErrorOrNil(); err != nil {
return nil, err
}
return fingerprinted, nil
}
// selectApiVersion takes in PluginInfo and returns the highest compatable
// version or an error if the plugins response is malformed. If there is no
// overlap, an empty string is returned.
func (l *PluginLoader) selectApiVersion(i *base.PluginInfoResponse) (string, error) {
if i == nil {
return "", fmt.Errorf("nil plugin info given")
}
if len(i.PluginApiVersions) == 0 {
return "", fmt.Errorf("plugin provided no compatible API versions")
}
pluginVersions, err := convertVersions(i.PluginApiVersions)
if err != nil {
return "", fmt.Errorf("plugin provided invalid versions: %v", err)
}
// Lookup the supported versions. These will be sorted highest to lowest
supportedVersions, ok := l.supportedVersions[i.Type]
if !ok {
return "", fmt.Errorf("unsupported plugin type %q", i.Type)
}
for _, sv := range supportedVersions {
for _, pv := range pluginVersions {
if sv.Equal(pv) {
return pv.Original(), nil
}
}
}
return "", nil
}
// convertVersions takes a list of string versions and returns a sorted list of
// versions from highest to lowest.
func convertVersions(in []string) ([]*version.Version, error) {
converted := make([]*version.Version, len(in))
for i, v := range in {
vv, err := version.NewVersion(v)
if err != nil {
return nil, fmt.Errorf("failed to convert version %q : %v", v, err)
}
converted[i] = vv
}
sort.Slice(converted, func(i, j int) bool {
return converted[i].GreaterThan(converted[j])
})
return converted, nil
}
// scan scans the plugin directory and retrieves potentially eligible binaries
func (l *PluginLoader) scan() ([]os.FileInfo, error) {
if l.pluginDir == "" {
@@ -200,10 +270,14 @@ func (l *PluginLoader) fingerprintPlugins(plugins []os.FileInfo, configs map[str
c := configs[name]
info, err := l.fingerprintPlugin(p, c)
if err != nil {
l.logger.Error("failed to fingerprint plugin", "plugin", name)
l.logger.Error("failed to fingerprint plugin", "plugin", name, "error", err)
multierror.Append(&mErr, err)
continue
}
if info == nil {
// Plugin was skipped for validation reasons
continue
}
id := PluginID{
Name: info.baseInfo.Name,
@@ -297,6 +371,17 @@ func (l *PluginLoader) fingerprintPlugin(pluginExe os.FileInfo, config *config.P
}
info.version = v
// Detect the plugin API version to use
av, err := l.selectApiVersion(i)
if err != nil {
return nil, fmt.Errorf("failed to validate API versions %v for plugin %s (%v): %v", i.PluginApiVersions, i.Name, info.exePath, err)
}
if av == "" {
l.logger.Warn("skipping plugin because supported API versions for plugin and Nomad do not overlap", "plugin", i.Name, "path", info.exePath)
return nil, nil
}
info.apiVersion = av
// Retrieve the schema
schema, err := bplugin.ConfigSchema()
if err != nil {
@@ -404,12 +489,18 @@ func (l *PluginLoader) validePluginConfig(id PluginID, info *pluginInfo) error {
}
defer instance.Kill()
base, ok := instance.Plugin().(base.BasePlugin)
b, ok := instance.Plugin().(base.BasePlugin)
if !ok {
return fmt.Errorf("dispensed plugin %s doesn't meet base plugin interface", id)
}
if err := base.SetConfig(cdata, nil); err != nil {
c := &base.Config{
PluginConfig: cdata,
AgentConfig: nil,
ApiVersion: info.apiVersion,
}
if err := b.SetConfig(c); err != nil {
return fmt.Errorf("setting config on plugin failed: %v", err)
}
return nil

View File

@@ -22,11 +22,15 @@ type PluginInstance interface {
// Exited returns whether the plugin has exited
Exited() bool
// ApiVersion returns the API version to be used with the plugin
ApiVersion() string
}
// internalPluginInstance wraps an internal plugin
type internalPluginInstance struct {
instance interface{}
instance interface{}
apiVersion string
}
func (p *internalPluginInstance) Internal() bool { return true }
@@ -34,16 +38,19 @@ func (p *internalPluginInstance) Kill()
func (p *internalPluginInstance) ReattachConfig() (*plugin.ReattachConfig, bool) { return nil, false }
func (p *internalPluginInstance) Plugin() interface{} { return p.instance }
func (p *internalPluginInstance) Exited() bool { return false }
func (p *internalPluginInstance) ApiVersion() string { return p.apiVersion }
// externalPluginInstance wraps an external plugin
type externalPluginInstance struct {
client *plugin.Client
instance interface{}
client *plugin.Client
instance interface{}
apiVersion string
}
func (p *externalPluginInstance) Internal() bool { return false }
func (p *externalPluginInstance) Plugin() interface{} { return p.instance }
func (p *externalPluginInstance) Exited() bool { return p.client.Exited() }
func (p *externalPluginInstance) ApiVersion() string { return p.apiVersion }
func (p *externalPluginInstance) ReattachConfig() (*plugin.ReattachConfig, bool) {
return p.client.ReattachConfig(), true

View File

@@ -19,7 +19,7 @@ import (
type PluginCatalog interface {
// Dispense returns the plugin given its name and type. This will also
// configure the plugin
Dispense(name, pluginType string, config *base.ClientAgentConfig, logger log.Logger) (PluginInstance, error)
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)
@@ -28,17 +28,10 @@ type PluginCatalog interface {
Catalog() map[string][]*base.PluginInfoResponse
}
// PluginLoader is used to retrieve plugins either externally or from internal
// factories.
type PluginLoader struct {
// logger is the plugin loaders logger
logger log.Logger
// pluginDir is the directory containing plugin binaries
pluginDir string
// plugins maps a plugin to information required to launch it
plugins map[PluginID]*pluginInfo
// 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
@@ -75,12 +68,25 @@ type PluginLoaderConfig struct {
// InternalPlugins allows registering internal plugins.
InternalPlugins map[PluginID]*InternalPluginConfig
// SupportedVersions is a mapping of plugin type to the supported versions
SupportedVersions map[string][]string
}
// InternalPluginConfig is used to configure launching an internal plugin.
type InternalPluginConfig struct {
Config map[string]interface{}
Factory plugins.PluginFactory
// 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
@@ -91,8 +97,9 @@ type pluginInfo struct {
exePath string
args []string
baseInfo *base.PluginInfoResponse
version *version.Version
baseInfo *base.PluginInfoResponse
version *version.Version
apiVersion string
configSchema *hclspec.Spec
config map[string]interface{}
@@ -106,11 +113,22 @@ func NewPluginLoader(config *PluginLoaderConfig) (*PluginLoader, error) {
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,
pluginDir: config.PluginDir,
plugins: make(map[PluginID]*pluginInfo),
logger: logger,
supportedVersions: supportedVersions,
pluginDir: config.PluginDir,
plugins: make(map[PluginID]*pluginInfo),
}
if err := l.init(config); err != nil {
@@ -122,7 +140,7 @@ func NewPluginLoader(config *PluginLoaderConfig) (*PluginLoader, error) {
// Dispense returns a plugin instance, loading it either internally or by
// launching an external plugin.
func (l *PluginLoader) Dispense(name, pluginType string, config *base.ClientAgentConfig, logger log.Logger) (PluginInstance, error) {
func (l *PluginLoader) Dispense(name, pluginType string, config *base.AgentConfig, logger log.Logger) (PluginInstance, error) {
id := PluginID{
Name: name,
PluginType: pluginType,
@@ -136,26 +154,31 @@ func (l *PluginLoader) Dispense(name, pluginType string, config *base.ClientAgen
var instance PluginInstance
if pinfo.factory != nil {
instance = &internalPluginInstance{
instance: pinfo.factory(logger),
instance: pinfo.factory(logger),
apiVersion: pinfo.apiVersion,
}
} else {
var err error
instance, err = l.dispensePlugin(pinfo.baseInfo.Type, pinfo.exePath, pinfo.args, nil, logger)
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
base, ok := instance.Plugin().(base.BasePlugin)
b, ok := instance.Plugin().(base.BasePlugin)
if !ok {
return nil, fmt.Errorf("plugin %s doesn't implement base plugin interface", id)
}
if len(pinfo.msgpackConfig) != 0 {
if err := base.SetConfig(pinfo.msgpackConfig, config); err != nil {
return nil, fmt.Errorf("setting config for plugin %s failed: %v", id, err)
}
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
@@ -163,12 +186,12 @@ func (l *PluginLoader) Dispense(name, pluginType string, config *base.ClientAgen
// 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)
return l.dispensePlugin(pluginType, "", "", nil, config, l.logger)
}
// dispensePlugin is used to launch or reattach to an external plugin.
func (l *PluginLoader) dispensePlugin(
pluginType, cmd string, args []string, reattach *plugin.ReattachConfig,
pluginType, apiVersion, cmd string, args []string, reattach *plugin.ReattachConfig,
logger log.Logger) (PluginInstance, error) {
var pluginCmd *exec.Cmd
@@ -207,6 +230,31 @@ func (l *PluginLoader) dispensePlugin(
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
}

View File

@@ -6,9 +6,11 @@ import (
"os"
"path/filepath"
"sort"
"strings"
"testing"
log "github.com/hashicorp/go-hclog"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/nomad/structs/config"
"github.com/hashicorp/nomad/plugins/base"
@@ -16,6 +18,14 @@ import (
"github.com/stretchr/testify/require"
)
var (
// supportedApiVersions is the set of api versions that the "client" can
// support
supportedApiVersions = map[string][]string{
base.PluginTypeDevice: []string{device.ApiVersion010},
}
)
// harness is used to build a temp directory and copy our own test executable
// into it, allowing the plugin loader to scan for plugins.
type harness struct {
@@ -104,18 +114,21 @@ func TestPluginLoader_External(t *testing.T) {
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
Configs: []*config.PluginConfig{
{
Name: plugins[0],
Args: []string{"-plugin", "-name", plugins[0],
"-type", base.PluginTypeDevice, "-version", pluginVersions[0]},
"-type", base.PluginTypeDevice, "-version", pluginVersions[0],
"-api-version", device.ApiVersion010},
},
{
Name: plugins[1],
Args: []string{"-plugin", "-name", plugins[1],
"-type", base.PluginTypeDevice, "-version", pluginVersions[1]},
"-type", base.PluginTypeDevice, "-version", pluginVersions[1],
"-api-version", device.ApiVersion010, "-api-version", "v0.2.0"},
},
},
}
@@ -133,21 +146,155 @@ func TestPluginLoader_External(t *testing.T) {
expected := []*base.PluginInfoResponse{
{
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[0],
PluginApiVersion: "v0.1.0",
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[0],
PluginApiVersions: []string{"v0.1.0"},
},
{
Name: plugins[1],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersion: "v0.1.0",
Name: plugins[1],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersions: []string{"v0.1.0", "v0.2.0"},
},
}
require.EqualValues(expected, detected)
}
func TestPluginLoader_External_ApiVersions(t *testing.T) {
t.Parallel()
require := require.New(t)
// Create two plugins
plugins := []string{"mock-device", "mock-device-2", "mock-device-3"}
pluginVersions := []string{"v0.0.1", "v0.0.2"}
h := newHarness(t, plugins)
defer h.cleanup()
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: map[string][]string{
base.PluginTypeDevice: []string{"0.2.0", "0.2.1", "0.3.0"},
},
Configs: []*config.PluginConfig{
{
// No supporting version
Name: plugins[0],
Args: []string{"-plugin", "-name", plugins[0],
"-type", base.PluginTypeDevice, "-version", pluginVersions[0],
"-api-version", "v0.1.0"},
},
{
// Pick highest matching
Name: plugins[1],
Args: []string{"-plugin", "-name", plugins[1],
"-type", base.PluginTypeDevice, "-version", pluginVersions[1],
"-api-version", "v0.1.0",
"-api-version", "v0.2.0",
"-api-version", "v0.2.1",
"-api-version", "v0.2.2",
},
},
{
// Pick highest matching
Name: plugins[2],
Args: []string{"-plugin", "-name", plugins[2],
"-type", base.PluginTypeDevice, "-version", pluginVersions[1],
"-api-version", "v0.1.0",
"-api-version", "v0.2.0",
"-api-version", "v0.2.1",
"-api-version", "v0.3.0",
},
},
},
}
l, err := NewPluginLoader(lconfig)
require.NoError(err)
// Get the catalog and assert we have the two plugins
c := l.Catalog()
require.Len(c, 1)
require.Contains(c, base.PluginTypeDevice)
detected := c[base.PluginTypeDevice]
require.Len(detected, 2)
sort.Slice(detected, func(i, j int) bool { return detected[i].Name < detected[j].Name })
expected := []*base.PluginInfoResponse{
{
Name: plugins[1],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersions: []string{"v0.1.0", "v0.2.0", "v0.2.1", "v0.2.2"},
},
{
Name: plugins[2],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersions: []string{"v0.1.0", "v0.2.0", "v0.2.1", "v0.3.0"},
},
}
require.EqualValues(expected, detected)
// Test we chose the correct versions by dispensing and checking and then
// reattaching and checking
p1, err := l.Dispense(plugins[1], base.PluginTypeDevice, nil, logger)
require.NoError(err)
defer p1.Kill()
require.Equal("v0.2.1", p1.ApiVersion())
p2, err := l.Dispense(plugins[2], base.PluginTypeDevice, nil, logger)
require.NoError(err)
defer p2.Kill()
require.Equal("v0.3.0", p2.ApiVersion())
// Test reattach api versions
rc1, ok := p1.ReattachConfig()
require.True(ok)
r1, err := l.Reattach(plugins[1], base.PluginTypeDriver, rc1)
require.NoError(err)
require.Equal("v0.2.1", r1.ApiVersion())
rc2, ok := p2.ReattachConfig()
require.True(ok)
r2, err := l.Reattach(plugins[2], base.PluginTypeDriver, rc2)
require.NoError(err)
require.Equal("v0.3.0", r2.ApiVersion())
}
func TestPluginLoader_External_NoApiVersion(t *testing.T) {
t.Parallel()
require := require.New(t)
// Create two plugins
plugins := []string{"mock-device"}
pluginVersions := []string{"v0.0.1", "v0.0.2"}
h := newHarness(t, plugins)
defer h.cleanup()
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
Configs: []*config.PluginConfig{
{
Name: plugins[0],
Args: []string{"-plugin", "-name", plugins[0],
"-type", base.PluginTypeDevice, "-version", pluginVersions[0]},
},
},
}
_, err := NewPluginLoader(lconfig)
require.Error(err)
require.Contains(err.Error(), "no compatible API versions")
}
func TestPluginLoader_External_Config(t *testing.T) {
t.Parallel()
require := require.New(t)
@@ -161,13 +308,14 @@ func TestPluginLoader_External_Config(t *testing.T) {
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
Configs: []*config.PluginConfig{
{
Name: plugins[0],
Args: []string{"-plugin", "-name", plugins[0],
"-type", base.PluginTypeDevice, "-version", pluginVersions[0]},
"-type", base.PluginTypeDevice, "-version", pluginVersions[0], "-api-version", device.ApiVersion010},
Config: map[string]interface{}{
"foo": "1",
"bar": "2",
@@ -176,7 +324,7 @@ func TestPluginLoader_External_Config(t *testing.T) {
{
Name: plugins[1],
Args: []string{"-plugin", "-name", plugins[1],
"-type", base.PluginTypeDevice, "-version", pluginVersions[1]},
"-type", base.PluginTypeDevice, "-version", pluginVersions[1], "-api-version", device.ApiVersion010},
Config: map[string]interface{}{
"foo": "3",
"bar": "4",
@@ -198,16 +346,16 @@ func TestPluginLoader_External_Config(t *testing.T) {
expected := []*base.PluginInfoResponse{
{
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[0],
PluginApiVersion: "v0.1.0",
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[0],
PluginApiVersions: []string{device.ApiVersion010},
},
{
Name: plugins[1],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersion: "v0.1.0",
Name: plugins[1],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersions: []string{device.ApiVersion010},
},
}
require.EqualValues(expected, detected)
@@ -227,13 +375,14 @@ func TestPluginLoader_External_Config_Bad(t *testing.T) {
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
Configs: []*config.PluginConfig{
{
Name: plugins[0],
Args: []string{"-plugin", "-name", plugins[0],
"-type", base.PluginTypeDevice, "-version", pluginVersions[0]},
"-type", base.PluginTypeDevice, "-version", pluginVersions[0], "-api-version", device.ApiVersion010},
Config: map[string]interface{}{
"foo": "1",
"bar": "2",
@@ -261,18 +410,19 @@ func TestPluginLoader_External_VersionOverlap(t *testing.T) {
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
Configs: []*config.PluginConfig{
{
Name: plugins[0],
Args: []string{"-plugin", "-name", plugins[0],
"-type", base.PluginTypeDevice, "-version", pluginVersions[0]},
"-type", base.PluginTypeDevice, "-version", pluginVersions[0], "-api-version", device.ApiVersion010},
},
{
Name: plugins[1],
Args: []string{"-plugin", "-name", plugins[0],
"-type", base.PluginTypeDevice, "-version", pluginVersions[1]},
"-type", base.PluginTypeDevice, "-version", pluginVersions[1], "-api-version", device.ApiVersion010},
},
},
}
@@ -290,10 +440,10 @@ func TestPluginLoader_External_VersionOverlap(t *testing.T) {
expected := []*base.PluginInfoResponse{
{
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersion: "v0.1.0",
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersions: []string{device.ApiVersion010},
},
}
require.EqualValues(expected, detected)
@@ -309,24 +459,26 @@ func TestPluginLoader_Internal(t *testing.T) {
plugins := []string{"mock-device", "mock-device-2"}
pluginVersions := []string{"v0.0.1", "v0.0.2"}
pluginApiVersions := []string{device.ApiVersion010}
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
InternalPlugins: map[PluginID]*InternalPluginConfig{
{
Name: plugins[0],
PluginType: base.PluginTypeDevice,
}: {
Factory: mockFactory(plugins[0], base.PluginTypeDevice, pluginVersions[0], true),
Factory: mockFactory(plugins[0], base.PluginTypeDevice, pluginVersions[0], pluginApiVersions, true),
},
{
Name: plugins[1],
PluginType: base.PluginTypeDevice,
}: {
Factory: mockFactory(plugins[1], base.PluginTypeDevice, pluginVersions[1], true),
Factory: mockFactory(plugins[1], base.PluginTypeDevice, pluginVersions[1], pluginApiVersions, true),
},
},
}
@@ -344,21 +496,134 @@ func TestPluginLoader_Internal(t *testing.T) {
expected := []*base.PluginInfoResponse{
{
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[0],
PluginApiVersion: "v0.1.0",
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[0],
PluginApiVersions: []string{device.ApiVersion010},
},
{
Name: plugins[1],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersion: "v0.1.0",
Name: plugins[1],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersions: []string{device.ApiVersion010},
},
}
require.EqualValues(expected, detected)
}
func TestPluginLoader_Internal_ApiVersions(t *testing.T) {
t.Parallel()
require := require.New(t)
// Create two plugins
plugins := []string{"mock-device", "mock-device-2", "mock-device-3"}
pluginVersions := []string{"v0.0.1", "v0.0.2"}
h := newHarness(t, nil)
defer h.cleanup()
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: map[string][]string{
base.PluginTypeDevice: []string{"0.2.0", "0.2.1", "0.3.0"},
},
InternalPlugins: map[PluginID]*InternalPluginConfig{
{
Name: plugins[0],
PluginType: base.PluginTypeDevice,
}: {
Factory: mockFactory(plugins[0], base.PluginTypeDevice, pluginVersions[0], []string{"v0.1.0"}, true),
},
{
Name: plugins[1],
PluginType: base.PluginTypeDevice,
}: {
Factory: mockFactory(plugins[1], base.PluginTypeDevice, pluginVersions[1],
[]string{"v0.1.0", "v0.2.0", "v0.2.1", "v0.2.2"}, true),
},
{
Name: plugins[2],
PluginType: base.PluginTypeDevice,
}: {
Factory: mockFactory(plugins[2], base.PluginTypeDevice, pluginVersions[1],
[]string{"v0.1.0", "v0.2.0", "v0.2.1", "v0.3.0"}, true),
},
},
}
l, err := NewPluginLoader(lconfig)
require.NoError(err)
// Get the catalog and assert we have the two plugins
c := l.Catalog()
require.Len(c, 1)
require.Contains(c, base.PluginTypeDevice)
detected := c[base.PluginTypeDevice]
require.Len(detected, 2)
sort.Slice(detected, func(i, j int) bool { return detected[i].Name < detected[j].Name })
expected := []*base.PluginInfoResponse{
{
Name: plugins[1],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersions: []string{"v0.1.0", "v0.2.0", "v0.2.1", "v0.2.2"},
},
{
Name: plugins[2],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersions: []string{"v0.1.0", "v0.2.0", "v0.2.1", "v0.3.0"},
},
}
require.EqualValues(expected, detected)
// Test we chose the correct versions by dispensing and checking and then
// reattaching and checking
p1, err := l.Dispense(plugins[1], base.PluginTypeDevice, nil, logger)
require.NoError(err)
defer p1.Kill()
require.Equal("v0.2.1", p1.ApiVersion())
p2, err := l.Dispense(plugins[2], base.PluginTypeDevice, nil, logger)
require.NoError(err)
defer p2.Kill()
require.Equal("v0.3.0", p2.ApiVersion())
}
func TestPluginLoader_Internal_NoApiVersion(t *testing.T) {
t.Parallel()
require := require.New(t)
// Create two plugins
plugins := []string{"mock-device"}
pluginVersions := []string{"v0.0.1", "v0.0.2"}
h := newHarness(t, nil)
defer h.cleanup()
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
InternalPlugins: map[PluginID]*InternalPluginConfig{
{
Name: plugins[0],
PluginType: base.PluginTypeDevice,
}: {
Factory: mockFactory(plugins[0], base.PluginTypeDevice, pluginVersions[0], nil, true),
},
},
}
_, err := NewPluginLoader(lconfig)
require.Error(err)
require.Contains(err.Error(), "no compatible API versions")
}
func TestPluginLoader_Internal_Config(t *testing.T) {
t.Parallel()
require := require.New(t)
@@ -369,18 +634,20 @@ func TestPluginLoader_Internal_Config(t *testing.T) {
plugins := []string{"mock-device", "mock-device-2"}
pluginVersions := []string{"v0.0.1", "v0.0.2"}
pluginApiVersions := []string{device.ApiVersion010}
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
InternalPlugins: map[PluginID]*InternalPluginConfig{
{
Name: plugins[0],
PluginType: base.PluginTypeDevice,
}: {
Factory: mockFactory(plugins[0], base.PluginTypeDevice, pluginVersions[0], true),
Factory: mockFactory(plugins[0], base.PluginTypeDevice, pluginVersions[0], pluginApiVersions, true),
Config: map[string]interface{}{
"foo": "1",
"bar": "2",
@@ -390,7 +657,7 @@ func TestPluginLoader_Internal_Config(t *testing.T) {
Name: plugins[1],
PluginType: base.PluginTypeDevice,
}: {
Factory: mockFactory(plugins[1], base.PluginTypeDevice, pluginVersions[1], true),
Factory: mockFactory(plugins[1], base.PluginTypeDevice, pluginVersions[1], pluginApiVersions, true),
Config: map[string]interface{}{
"foo": "3",
"bar": "4",
@@ -412,16 +679,16 @@ func TestPluginLoader_Internal_Config(t *testing.T) {
expected := []*base.PluginInfoResponse{
{
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[0],
PluginApiVersion: "v0.1.0",
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[0],
PluginApiVersions: []string{device.ApiVersion010},
},
{
Name: plugins[1],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersion: "v0.1.0",
Name: plugins[1],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersions: []string{device.ApiVersion010},
},
}
require.EqualValues(expected, detected)
@@ -438,6 +705,7 @@ func TestPluginLoader_Internal_ExternalConfig(t *testing.T) {
plugin := "mock-device"
pluginVersion := "v0.0.1"
pluginApiVersions := []string{device.ApiVersion010}
id := PluginID{
Name: plugin,
@@ -451,11 +719,12 @@ func TestPluginLoader_Internal_ExternalConfig(t *testing.T) {
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
InternalPlugins: map[PluginID]*InternalPluginConfig{
id: {
Factory: mockFactory(plugin, base.PluginTypeDevice, pluginVersion, true),
Factory: mockFactory(plugin, base.PluginTypeDevice, pluginVersion, pluginApiVersions, true),
Config: map[string]interface{}{
"foo": "1",
"bar": "2",
@@ -482,10 +751,10 @@ func TestPluginLoader_Internal_ExternalConfig(t *testing.T) {
expected := []*base.PluginInfoResponse{
{
Name: plugin,
Type: base.PluginTypeDevice,
PluginVersion: pluginVersion,
PluginApiVersion: "v0.1.0",
Name: plugin,
Type: base.PluginTypeDevice,
PluginVersion: pluginVersion,
PluginApiVersions: []string{device.ApiVersion010},
},
}
require.EqualValues(expected, detected)
@@ -507,18 +776,20 @@ func TestPluginLoader_Internal_Config_Bad(t *testing.T) {
plugins := []string{"mock-device"}
pluginVersions := []string{"v0.0.1"}
pluginApiVersions := []string{device.ApiVersion010}
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
InternalPlugins: map[PluginID]*InternalPluginConfig{
{
Name: plugins[0],
PluginType: base.PluginTypeDevice,
}: {
Factory: mockFactory(plugins[0], base.PluginTypeDevice, pluginVersions[0], true),
Factory: mockFactory(plugins[0], base.PluginTypeDevice, pluginVersions[0], pluginApiVersions, true),
Config: map[string]interface{}{
"foo": "1",
"bar": "2",
@@ -540,19 +811,22 @@ func TestPluginLoader_InternalOverrideExternal(t *testing.T) {
// Create two plugins
plugins := []string{"mock-device"}
pluginVersions := []string{"v0.0.1", "v0.0.2"}
pluginApiVersions := []string{device.ApiVersion010}
h := newHarness(t, plugins)
defer h.cleanup()
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
Configs: []*config.PluginConfig{
{
Name: plugins[0],
Args: []string{"-plugin", "-name", plugins[0],
"-type", base.PluginTypeDevice, "-version", pluginVersions[0]},
"-type", base.PluginTypeDevice, "-version", pluginVersions[0], "-api-version", pluginApiVersions[0]},
},
},
InternalPlugins: map[PluginID]*InternalPluginConfig{
@@ -560,7 +834,7 @@ func TestPluginLoader_InternalOverrideExternal(t *testing.T) {
Name: plugins[0],
PluginType: base.PluginTypeDevice,
}: {
Factory: mockFactory(plugins[0], base.PluginTypeDevice, pluginVersions[1], true),
Factory: mockFactory(plugins[0], base.PluginTypeDevice, pluginVersions[1], pluginApiVersions, true),
},
},
}
@@ -578,10 +852,10 @@ func TestPluginLoader_InternalOverrideExternal(t *testing.T) {
expected := []*base.PluginInfoResponse{
{
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersion: "v0.1.0",
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersions: []string{device.ApiVersion010},
},
}
require.EqualValues(expected, detected)
@@ -594,19 +868,22 @@ func TestPluginLoader_ExternalOverrideInternal(t *testing.T) {
// Create two plugins
plugins := []string{"mock-device"}
pluginVersions := []string{"v0.0.1", "v0.0.2"}
pluginApiVersions := []string{device.ApiVersion010}
h := newHarness(t, plugins)
defer h.cleanup()
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
Configs: []*config.PluginConfig{
{
Name: plugins[0],
Args: []string{"-plugin", "-name", plugins[0],
"-type", base.PluginTypeDevice, "-version", pluginVersions[1]},
"-type", base.PluginTypeDevice, "-version", pluginVersions[1], "-api-version", pluginApiVersions[0]},
},
},
InternalPlugins: map[PluginID]*InternalPluginConfig{
@@ -614,7 +891,7 @@ func TestPluginLoader_ExternalOverrideInternal(t *testing.T) {
Name: plugins[0],
PluginType: base.PluginTypeDevice,
}: {
Factory: mockFactory(plugins[0], base.PluginTypeDevice, pluginVersions[0], true),
Factory: mockFactory(plugins[0], base.PluginTypeDevice, pluginVersions[0], pluginApiVersions, true),
},
},
}
@@ -632,10 +909,10 @@ func TestPluginLoader_ExternalOverrideInternal(t *testing.T) {
expected := []*base.PluginInfoResponse{
{
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersion: "v0.1.0",
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[1],
PluginApiVersions: []string{device.ApiVersion010},
},
}
require.EqualValues(expected, detected)
@@ -656,13 +933,14 @@ func TestPluginLoader_Dispense_External(t *testing.T) {
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
Configs: []*config.PluginConfig{
{
Name: plugin,
Args: []string{"-plugin", "-name", plugin,
"-type", base.PluginTypeDevice, "-version", pluginVersion},
"-type", base.PluginTypeDevice, "-version", pluginVersion, "-api-version", device.ApiVersion010},
Config: map[string]interface{}{
"res_key": expKey,
},
@@ -694,11 +972,12 @@ func TestPluginLoader_Dispense_Internal(t *testing.T) {
// Create two plugins
plugin := "mock-device"
pluginVersion := "v0.0.1"
pluginApiVersions := []string{device.ApiVersion010}
h := newHarness(t, nil)
defer h.cleanup()
expKey := "set_config_worked"
expNomadConfig := &base.ClientAgentConfig{
expNomadConfig := &base.AgentConfig{
Driver: &base.ClientDriverConfig{
ClientMinPort: 100,
},
@@ -707,14 +986,15 @@ func TestPluginLoader_Dispense_Internal(t *testing.T) {
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
InternalPlugins: map[PluginID]*InternalPluginConfig{
{
Name: plugin,
PluginType: base.PluginTypeDevice,
}: {
Factory: mockFactory(plugin, base.PluginTypeDevice, pluginVersion, true),
Factory: mockFactory(plugin, base.PluginTypeDevice, pluginVersion, pluginApiVersions, true),
Config: map[string]interface{}{
"res_key": expKey,
},
@@ -741,6 +1021,7 @@ func TestPluginLoader_Dispense_Internal(t *testing.T) {
mock, ok := p.Plugin().(*mockPlugin)
require.True(ok)
require.Exactly(expNomadConfig, mock.nomadConfig)
require.Equal(device.ApiVersion010, mock.negotiatedApiVersion)
}
func TestPluginLoader_Dispense_NoConfigSchema_External(t *testing.T) {
@@ -758,13 +1039,14 @@ func TestPluginLoader_Dispense_NoConfigSchema_External(t *testing.T) {
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
Configs: []*config.PluginConfig{
{
Name: plugin,
Args: []string{"-plugin", "-config-schema=false", "-name", plugin,
"-type", base.PluginTypeDevice, "-version", pluginVersion},
"-type", base.PluginTypeDevice, "-version", pluginVersion, "-api-version", device.ApiVersion010},
Config: map[string]interface{}{
"res_key": expKey,
},
@@ -797,6 +1079,7 @@ func TestPluginLoader_Dispense_NoConfigSchema_Internal(t *testing.T) {
// Create two plugins
plugin := "mock-device"
pluginVersion := "v0.0.1"
pluginApiVersions := []string{device.ApiVersion010}
h := newHarness(t, nil)
defer h.cleanup()
@@ -809,11 +1092,12 @@ func TestPluginLoader_Dispense_NoConfigSchema_Internal(t *testing.T) {
PluginType: base.PluginTypeDevice,
}
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
InternalPlugins: map[PluginID]*InternalPluginConfig{
pid: {
Factory: mockFactory(plugin, base.PluginTypeDevice, pluginVersion, false),
Factory: mockFactory(plugin, base.PluginTypeDevice, pluginVersion, pluginApiVersions, false),
Config: map[string]interface{}{
"res_key": expKey,
},
@@ -826,7 +1110,7 @@ func TestPluginLoader_Dispense_NoConfigSchema_Internal(t *testing.T) {
require.Contains(err.Error(), "configuration not allowed")
// Remove the config and try again
lconfig.InternalPlugins[pid].Factory = mockFactory(plugin, base.PluginTypeDevice, pluginVersion, true)
lconfig.InternalPlugins[pid].Factory = mockFactory(plugin, base.PluginTypeDevice, pluginVersion, pluginApiVersions, true)
l, err := NewPluginLoader(lconfig)
require.NoError(err)
@@ -854,13 +1138,14 @@ func TestPluginLoader_Reattach_External(t *testing.T) {
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
Configs: []*config.PluginConfig{
{
Name: plugin,
Args: []string{"-plugin", "-name", plugin,
"-type", base.PluginTypeDevice, "-version", pluginVersion},
"-type", base.PluginTypeDevice, "-version", pluginVersion, "-api-version", device.ApiVersion010},
Config: map[string]interface{}{
"res_key": expKey,
},
@@ -914,8 +1199,9 @@ func TestPluginLoader_Bad_Executable(t *testing.T) {
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
Configs: []*config.PluginConfig{
{
Name: plugin,
@@ -956,13 +1242,14 @@ func TestPluginLoader_External_SkipBadFiles(t *testing.T) {
logger := testlog.HCLogger(t)
logger.SetLevel(log.Trace)
lconfig := &PluginLoaderConfig{
Logger: logger,
PluginDir: h.pluginDir(),
Logger: logger,
PluginDir: h.pluginDir(),
SupportedVersions: supportedApiVersions,
Configs: []*config.PluginConfig{
{
Name: plugins[0],
Args: []string{"-plugin", "-name", plugins[0],
"-type", base.PluginTypeDevice, "-version", pluginVersions[0]},
"-type", base.PluginTypeDevice, "-version", pluginVersions[0], "-api-version", device.ApiVersion010},
},
},
}
@@ -980,11 +1267,55 @@ func TestPluginLoader_External_SkipBadFiles(t *testing.T) {
expected := []*base.PluginInfoResponse{
{
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[0],
PluginApiVersion: "v0.1.0",
Name: plugins[0],
Type: base.PluginTypeDevice,
PluginVersion: pluginVersions[0],
PluginApiVersions: []string{device.ApiVersion010},
},
}
require.EqualValues(expected, detected)
}
func TestPluginLoader_ConvertVersions(t *testing.T) {
v010 := version.Must(version.NewVersion("v0.1.0"))
v020 := version.Must(version.NewVersion("v0.2.0"))
v021 := version.Must(version.NewVersion("v0.2.1"))
v030 := version.Must(version.NewVersion("v0.3.0"))
cases := []struct {
in []string
out []*version.Version
err bool
}{
{
in: []string{"v0.1.0", "0.2.0", "v0.2.1"},
out: []*version.Version{v021, v020, v010},
},
{
in: []string{"0.3.0", "v0.1.0", "0.2.0", "v0.2.1"},
out: []*version.Version{v030, v021, v020, v010},
},
{
in: []string{"foo", "v0.1.0", "0.2.0", "v0.2.1"},
err: true,
},
}
for _, c := range cases {
t.Run(strings.Join(c.in, ","), func(t *testing.T) {
act, err := convertVersions(c.in)
if err != nil {
if c.err {
return
}
t.Fatalf("unexpected err: %v", err)
}
require.Len(t, act, len(c.out))
for i, v := range act {
if !v.Equal(c.out[i]) {
t.Fatalf("parsed version[%d] not equal: %v != %v", i, v, c.out[i])
}
}
})
}
}

View File

@@ -15,20 +15,33 @@ import (
"github.com/hashicorp/nomad/plugins/shared/hclspec"
)
type stringSliceFlags []string
func (i *stringSliceFlags) String() string {
return "my string representation"
}
func (i *stringSliceFlags) Set(value string) error {
*i = append(*i, value)
return nil
}
// TestMain runs either the tests or runs a mock plugin based on the passed
// flags
func TestMain(m *testing.M) {
var plugin, configSchema bool
var name, pluginType, pluginVersion string
var pluginApiVersions stringSliceFlags
flag.BoolVar(&plugin, "plugin", false, "run binary as a plugin")
flag.BoolVar(&configSchema, "config-schema", true, "return a config schema")
flag.StringVar(&name, "name", "", "plugin name")
flag.StringVar(&pluginType, "type", "", "plugin type")
flag.StringVar(&pluginVersion, "version", "", "plugin version")
flag.Var(&pluginApiVersions, "api-version", "supported plugin API version")
flag.Parse()
if plugin {
if err := pluginMain(name, pluginType, pluginVersion, configSchema); err != nil {
if err := pluginMain(name, pluginType, pluginVersion, pluginApiVersions, configSchema); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
@@ -38,7 +51,7 @@ func TestMain(m *testing.M) {
}
// pluginMain starts a mock plugin using the passed parameters
func pluginMain(name, pluginType, version string, config bool) error {
func pluginMain(name, pluginType, version string, apiVersions []string, config bool) error {
// Validate passed parameters
if name == "" || pluginType == "" {
return fmt.Errorf("name and plugin type must be specified")
@@ -55,6 +68,7 @@ func pluginMain(name, pluginType, version string, config bool) error {
name: name,
ptype: pluginType,
version: version,
apiVersions: apiVersions,
configSchema: config,
}
@@ -79,12 +93,13 @@ func pluginMain(name, pluginType, version string, config bool) error {
// mockFactory returns a PluginFactory method which creates the mock plugin with
// the passed parameters
func mockFactory(name, ptype, version string, configSchema bool) func(log log.Logger) interface{} {
func mockFactory(name, ptype, version string, apiVersions []string, configSchema bool) func(log log.Logger) interface{} {
return func(log log.Logger) interface{} {
return &mockPlugin{
name: name,
ptype: ptype,
version: version,
apiVersions: apiVersions,
configSchema: configSchema,
}
}
@@ -96,12 +111,18 @@ type mockPlugin struct {
name string
ptype string
version string
apiVersions []string
configSchema bool
// config is built on SetConfig
config *mockPluginConfig
// nomadconfig is set on SetConfig
nomadConfig *base.ClientAgentConfig
nomadConfig *base.AgentConfig
// negotiatedApiVersion is the version of the api to use and is set on
// SetConfig
negotiatedApiVersion string
}
// mockPluginConfig is the configuration for the mock plugin
@@ -118,10 +139,10 @@ type mockPluginConfig struct {
// building the mock plugin
func (m *mockPlugin) PluginInfo() (*base.PluginInfoResponse, error) {
return &base.PluginInfoResponse{
Type: m.ptype,
PluginApiVersion: "v0.1.0",
PluginVersion: m.version,
Name: m.name,
Type: m.ptype,
PluginApiVersions: m.apiVersions,
PluginVersion: m.version,
Name: m.name,
}, nil
}
@@ -141,14 +162,17 @@ func (m *mockPlugin) ConfigSchema() (*hclspec.Spec, error) {
}
// SetConfig decodes the configuration and stores it
func (m *mockPlugin) SetConfig(data []byte, cfg *base.ClientAgentConfig) error {
func (m *mockPlugin) SetConfig(c *base.Config) error {
var config mockPluginConfig
if err := base.MsgPackDecode(data, &config); err != nil {
return err
if len(c.PluginConfig) != 0 {
if err := base.MsgPackDecode(c.PluginConfig, &config); err != nil {
return err
}
}
m.config = &config
m.nomadConfig = cfg
m.nomadConfig = c.AgentConfig
m.negotiatedApiVersion = c.ApiVersion
return nil
}

View File

@@ -12,12 +12,12 @@ import (
// MockCatalog provides a mock PluginCatalog to be used for testing
type MockCatalog struct {
DispenseF func(name, pluginType string, cfg *base.ClientAgentConfig, logger log.Logger) (PluginInstance, error)
DispenseF func(name, pluginType string, cfg *base.AgentConfig, logger log.Logger) (PluginInstance, error)
ReattachF func(name, pluginType string, config *plugin.ReattachConfig) (PluginInstance, error)
CatalogF func() map[string][]*base.PluginInfoResponse
}
func (m *MockCatalog) Dispense(name, pluginType string, cfg *base.ClientAgentConfig, logger log.Logger) (PluginInstance, error) {
func (m *MockCatalog) Dispense(name, pluginType string, cfg *base.AgentConfig, logger log.Logger) (PluginInstance, error) {
return m.DispenseF(name, pluginType, cfg, logger)
}
@@ -36,6 +36,7 @@ type MockInstance struct {
ReattachConfigF func() (*plugin.ReattachConfig, bool)
PluginF func() interface{}
ExitedF func() bool
ApiVersionF func() string
}
func (m *MockInstance) Internal() bool { return m.InternalPlugin }
@@ -43,11 +44,12 @@ func (m *MockInstance) Kill() { m.KillF
func (m *MockInstance) ReattachConfig() (*plugin.ReattachConfig, bool) { return m.ReattachConfigF() }
func (m *MockInstance) Plugin() interface{} { return m.PluginF() }
func (m *MockInstance) Exited() bool { return m.ExitedF() }
func (m *MockInstance) ApiVersion() string { return m.ApiVersionF() }
// MockBasicExternalPlugin returns a MockInstance that simulates an external
// plugin returning it has been exited after kill is called. It returns the
// passed inst as the plugin
func MockBasicExternalPlugin(inst interface{}) *MockInstance {
func MockBasicExternalPlugin(inst interface{}, apiVersion string) *MockInstance {
var killedLock sync.Mutex
killed := helper.BoolToPtr(false)
return &MockInstance{
@@ -79,5 +81,7 @@ func MockBasicExternalPlugin(inst interface{}) *MockInstance {
defer killedLock.Unlock()
return *killed
},
ApiVersionF: func() string { return apiVersion },
}
}

View File

@@ -50,7 +50,7 @@ func (s *SingletonLoader) Catalog() map[string][]*base.PluginInfoResponse {
// 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) {
func (s *SingletonLoader) Dispense(name, pluginType string, config *base.AgentConfig, logger log.Logger) (loader.PluginInstance, error) {
return s.getPlugin(false, name, pluginType, logger, config, nil)
}
@@ -62,7 +62,7 @@ func (s *SingletonLoader) Reattach(name, pluginType string, config *plugin.Reatt
// 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) {
nomadConfig *base.AgentConfig, config *plugin.ReattachConfig) (loader.PluginInstance, error) {
// Lock the instance map to prevent races
s.instanceLock.Lock()
@@ -102,7 +102,7 @@ func (s *SingletonLoader) getPlugin(reattach bool, name, pluginType string, logg
// 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) {
func (s *SingletonLoader) dispense(f *future, name, pluginType string, config *base.AgentConfig, logger log.Logger) {
i, err := s.loader.Dispense(name, pluginType, config, logger)
f.set(i, err)
}

View File

@@ -27,7 +27,7 @@ func TestSingleton_Dispense(t *testing.T) {
dispenseCalled := 0
s, c := harness(t)
c.DispenseF = func(_, _ string, _ *base.ClientAgentConfig, _ log.Logger) (loader.PluginInstance, error) {
c.DispenseF = func(_, _ string, _ *base.AgentConfig, _ log.Logger) (loader.PluginInstance, error) {
p := &base.MockPlugin{}
i := &loader.MockInstance{
ExitedF: func() bool { return false },
@@ -77,7 +77,7 @@ func TestSingleton_Dispense_Exit_Dispense(t *testing.T) {
exited := false
dispenseCalled := 0
s, c := harness(t)
c.DispenseF = func(_, _ string, _ *base.ClientAgentConfig, _ log.Logger) (loader.PluginInstance, error) {
c.DispenseF = func(_, _ string, _ *base.AgentConfig, _ log.Logger) (loader.PluginInstance, error) {
p := &base.MockPlugin{}
i := &loader.MockInstance{
ExitedF: func() bool { return exited },
@@ -125,7 +125,7 @@ func TestSingleton_DispenseError_Dispense(t *testing.T) {
require := require.New(t)
dispenseCalled := 0
good := func(_, _ string, _ *base.ClientAgentConfig, _ log.Logger) (loader.PluginInstance, error) {
good := func(_, _ string, _ *base.AgentConfig, _ log.Logger) (loader.PluginInstance, error) {
p := &base.MockPlugin{}
i := &loader.MockInstance{
ExitedF: func() bool { return false },
@@ -135,7 +135,7 @@ func TestSingleton_DispenseError_Dispense(t *testing.T) {
return i, nil
}
bad := func(_, _ string, _ *base.ClientAgentConfig, _ log.Logger) (loader.PluginInstance, error) {
bad := func(_, _ string, _ *base.AgentConfig, _ log.Logger) (loader.PluginInstance, error) {
dispenseCalled++
return nil, fmt.Errorf("bad")
}
@@ -169,7 +169,7 @@ func TestSingleton_ReattachError_Dispense(t *testing.T) {
dispenseCalled, reattachCalled := 0, 0
s, c := harness(t)
c.DispenseF = func(_, _ string, _ *base.ClientAgentConfig, _ log.Logger) (loader.PluginInstance, error) {
c.DispenseF = func(_, _ string, _ *base.AgentConfig, _ log.Logger) (loader.PluginInstance, error) {
p := &base.MockPlugin{}
i := &loader.MockInstance{
ExitedF: func() bool { return false },
@@ -209,7 +209,7 @@ func TestSingleton_Reattach_Dispense(t *testing.T) {
dispenseCalled, reattachCalled := 0, 0
s, c := harness(t)
c.DispenseF = func(_, _ string, _ *base.ClientAgentConfig, _ log.Logger) (loader.PluginInstance, error) {
c.DispenseF = func(_, _ string, _ *base.AgentConfig, _ log.Logger) (loader.PluginInstance, error) {
dispenseCalled++
return nil, fmt.Errorf("bad")
}

View File

@@ -2,6 +2,7 @@ package version
import (
"fmt"
"reflect"
"regexp"
"strings"
)
@@ -113,6 +114,26 @@ func parseSingle(v string) (*Constraint, error) {
}, nil
}
func prereleaseCheck(v, c *Version) bool {
switch vPre, cPre := v.Prerelease() != "", c.Prerelease() != ""; {
case cPre && vPre:
// A constraint with a pre-release can only match a pre-release version
// with the same base segments.
return reflect.DeepEqual(c.Segments64(), v.Segments64())
case !cPre && vPre:
// A constraint without a pre-release can only match a version without a
// pre-release.
return false
case cPre && !vPre:
// OK, except with the pessimistic operator
case !cPre && !vPre:
// OK
}
return true
}
//-------------------------------------------------------------------
// Constraint functions
//-------------------------------------------------------------------
@@ -126,22 +147,27 @@ func constraintNotEqual(v, c *Version) bool {
}
func constraintGreaterThan(v, c *Version) bool {
return v.Compare(c) == 1
return prereleaseCheck(v, c) && v.Compare(c) == 1
}
func constraintLessThan(v, c *Version) bool {
return v.Compare(c) == -1
return prereleaseCheck(v, c) && v.Compare(c) == -1
}
func constraintGreaterThanEqual(v, c *Version) bool {
return v.Compare(c) >= 0
return prereleaseCheck(v, c) && v.Compare(c) >= 0
}
func constraintLessThanEqual(v, c *Version) bool {
return v.Compare(c) <= 0
return prereleaseCheck(v, c) && v.Compare(c) <= 0
}
func constraintPessimistic(v, c *Version) bool {
// Using a pessimistic constraint with a pre-release, restricts versions to pre-releases
if !prereleaseCheck(v, c) || (c.Prerelease() != "" && v.Prerelease() == "") {
return false
}
// If the version being checked is naturally less than the constraint, then there
// is no way for the version to be valid against the constraint
if v.LessThan(c) {

View File

@@ -15,8 +15,8 @@ var versionRegexp *regexp.Regexp
// The raw regular expression string used for testing the validity
// of a version.
const VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` +
`(-?([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
`(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-?([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` +
`(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` +
`?`
// Version represents a single version.
@@ -25,6 +25,7 @@ type Version struct {
pre string
segments []int64
si int
original string
}
func init() {
@@ -59,11 +60,17 @@ func NewVersion(v string) (*Version, error) {
segments = append(segments, 0)
}
pre := matches[7]
if pre == "" {
pre = matches[4]
}
return &Version{
metadata: matches[7],
pre: matches[4],
metadata: matches[10],
pre: pre,
segments: segments,
si: si,
original: v,
}, nil
}
@@ -166,14 +173,16 @@ func comparePart(preSelf string, preOther string) int {
return 0
}
var selfInt int64
selfNumeric := true
_, err := strconv.ParseInt(preSelf, 10, 64)
selfInt, err := strconv.ParseInt(preSelf, 10, 64)
if err != nil {
selfNumeric = false
}
var otherInt int64
otherNumeric := true
_, err = strconv.ParseInt(preOther, 10, 64)
otherInt, err = strconv.ParseInt(preOther, 10, 64)
if err != nil {
otherNumeric = false
}
@@ -197,7 +206,9 @@ func comparePart(preSelf string, preOther string) int {
return -1
} else if !selfNumeric && otherNumeric {
return 1
} else if preSelf > preOther {
} else if !selfNumeric && !otherNumeric && preSelf > preOther {
return 1
} else if selfInt > otherInt {
return 1
}
@@ -297,11 +308,19 @@ func (v *Version) Segments() []int {
// for a version "1.2.3-beta", segments will return a slice of
// 1, 2, 3.
func (v *Version) Segments64() []int64 {
return v.segments
result := make([]int64, len(v.segments))
copy(result, v.segments)
return result
}
// String returns the full version string included pre-release
// and metadata information.
//
// This value is rebuilt according to the parsed segments and other
// information. Therefore, ambiguities in the version string such as
// prefixed zeroes (1.04.0 => 1.4.0), `v` prefix (v1.0.0 => 1.0.0), and
// missing parts (1.0 => 1.0.0) will be made into a canonicalized form
// as shown in the parenthesized examples.
func (v *Version) String() string {
var buf bytes.Buffer
fmtParts := make([]string, len(v.segments))
@@ -320,3 +339,9 @@ func (v *Version) String() string {
return buf.String()
}
// Original returns the original parsed version as-is, including any
// potential whitespace, `v` prefix, etc.
func (v *Version) Original() string {
return v.original
}

2
vendor/vendor.json vendored
View File

@@ -196,7 +196,7 @@
{"path":"github.com/hashicorp/go-sockaddr/template","checksumSHA1":"PDp9DVLvf3KWxhs4G4DpIwauMSU=","revision":"6d291a969b86c4b633730bfc6b8b9d64c3aafed9","revisionTime":"2018-03-20T11:50:54Z"},
{"path":"github.com/hashicorp/go-syslog","checksumSHA1":"xZ7Ban1x//6uUIU1xtrTbCYNHBc=","revision":"42a2b573b664dbf281bd48c3cc12c086b17a39ba"},
{"path":"github.com/hashicorp/go-uuid","checksumSHA1":"mAkPa/RLuIwN53GbwIEMATexams=","revision":"64130c7a86d732268a38cb04cfbaf0cc987fda98","revisionTime":"2016-07-17T02:21:40Z"},
{"path":"github.com/hashicorp/go-version","checksumSHA1":"tUGxc7rfX0cmhOOUDhMuAZ9rWsA=","revision":"03c5bf6be031b6dd45afec16b1cf94fc8938bc77","revisionTime":"2017-02-02T08:07:59Z"},
{"path":"github.com/hashicorp/go-version","checksumSHA1":"r0pj5dMHCghpaQZ3f1BRGoKiSWw=","revision":"b5a281d3160aa11950a6182bd9a9dc2cb1e02d50","revisionTime":"2018-08-24T00:43:55Z"},
{"path":"github.com/hashicorp/golang-lru","checksumSHA1":"d9PxF1XQGLMJZRct2R8qVM/eYlE=","revision":"a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4","revisionTime":"2016-02-07T21:47:19Z"},
{"path":"github.com/hashicorp/golang-lru/simplelru","checksumSHA1":"2nOpYjx8Sn57bqlZq17yM4YJuM4=","revision":"a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4"},
{"path":"github.com/hashicorp/hcl","checksumSHA1":"8OPDk+bKyRGJoKcS4QNw9F7dpE8=","revision":"6e968a3fcdcbab092f5307fd0d85479d5af1e4dc","revisionTime":"2016-11-01T18:00:25Z"},