github.com/hernad/nomad@v1.6.112/helper/pluginutils/loader/plugin_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package loader 5 6 import ( 7 "context" 8 "flag" 9 "fmt" 10 "os" 11 "testing" 12 "time" 13 14 log "github.com/hashicorp/go-hclog" 15 plugin "github.com/hashicorp/go-plugin" 16 "github.com/hernad/nomad/plugins/base" 17 "github.com/hernad/nomad/plugins/device" 18 "github.com/hernad/nomad/plugins/shared/hclspec" 19 ) 20 21 type stringSliceFlags []string 22 23 func (i *stringSliceFlags) String() string { 24 return "my string representation" 25 } 26 27 func (i *stringSliceFlags) Set(value string) error { 28 *i = append(*i, value) 29 return nil 30 } 31 32 // TestMain runs either the tests or runs a mock plugin based on the passed 33 // flags 34 func TestMain(m *testing.M) { 35 var plugin, configSchema bool 36 var name, pluginType, pluginVersion string 37 var pluginApiVersions stringSliceFlags 38 flag.BoolVar(&plugin, "plugin", false, "run binary as a plugin") 39 flag.BoolVar(&configSchema, "config-schema", true, "return a config schema") 40 flag.StringVar(&name, "name", "", "plugin name") 41 flag.StringVar(&pluginType, "type", "", "plugin type") 42 flag.StringVar(&pluginVersion, "version", "", "plugin version") 43 flag.Var(&pluginApiVersions, "api-version", "supported plugin API version") 44 flag.Parse() 45 46 if plugin { 47 if err := pluginMain(name, pluginType, pluginVersion, pluginApiVersions, configSchema); err != nil { 48 fmt.Println(err.Error()) 49 os.Exit(1) 50 } 51 } else { 52 os.Exit(m.Run()) 53 } 54 } 55 56 // pluginMain starts a mock plugin using the passed parameters 57 func pluginMain(name, pluginType, version string, apiVersions []string, config bool) error { 58 // Validate passed parameters 59 if name == "" || pluginType == "" { 60 return fmt.Errorf("name and plugin type must be specified") 61 } 62 63 switch pluginType { 64 case base.PluginTypeDevice: 65 default: 66 return fmt.Errorf("unsupported plugin type %q", pluginType) 67 } 68 69 // Create the mock plugin 70 m := &mockPlugin{ 71 name: name, 72 ptype: pluginType, 73 version: version, 74 apiVersions: apiVersions, 75 configSchema: config, 76 } 77 78 // Build the plugin map 79 pmap := map[string]plugin.Plugin{ 80 base.PluginTypeBase: &base.PluginBase{Impl: m}, 81 } 82 switch pluginType { 83 case base.PluginTypeDevice: 84 pmap[base.PluginTypeDevice] = &device.PluginDevice{Impl: m} 85 } 86 87 // Serve the plugin 88 plugin.Serve(&plugin.ServeConfig{ 89 HandshakeConfig: base.Handshake, 90 Plugins: pmap, 91 GRPCServer: plugin.DefaultGRPCServer, 92 }) 93 94 return nil 95 } 96 97 // mockFactory returns a PluginFactory method which creates the mock plugin with 98 // the passed parameters 99 func mockFactory(name, ptype, version string, apiVersions []string, configSchema bool) func(context.Context, log.Logger) interface{} { 100 return func(ctx context.Context, log log.Logger) interface{} { 101 return &mockPlugin{ 102 name: name, 103 ptype: ptype, 104 version: version, 105 apiVersions: apiVersions, 106 configSchema: configSchema, 107 } 108 } 109 } 110 111 // mockPlugin is a plugin that meets various plugin interfaces but is only 112 // useful for testing. 113 type mockPlugin struct { 114 name string 115 ptype string 116 version string 117 apiVersions []string 118 configSchema bool 119 120 // config is built on SetConfig 121 config *mockPluginConfig 122 123 // nomadconfig is set on SetConfig 124 nomadConfig *base.AgentConfig 125 126 // negotiatedApiVersion is the version of the api to use and is set on 127 // SetConfig 128 negotiatedApiVersion string 129 } 130 131 // mockPluginConfig is the configuration for the mock plugin 132 type mockPluginConfig struct { 133 Foo string `codec:"foo"` 134 Bar int `codec:"bar"` 135 136 // ResKey is a key that is populated in the Env map when a device is 137 // reserved. 138 ResKey string `codec:"res_key"` 139 } 140 141 // PluginInfo returns the plugin information based on the passed fields when 142 // building the mock plugin 143 func (m *mockPlugin) PluginInfo() (*base.PluginInfoResponse, error) { 144 return &base.PluginInfoResponse{ 145 Type: m.ptype, 146 PluginApiVersions: m.apiVersions, 147 PluginVersion: m.version, 148 Name: m.name, 149 }, nil 150 } 151 152 func (m *mockPlugin) ConfigSchema() (*hclspec.Spec, error) { 153 if !m.configSchema { 154 return nil, nil 155 } 156 157 // configSpec is the hclspec for parsing the mock's configuration 158 configSpec := hclspec.NewObject(map[string]*hclspec.Spec{ 159 "foo": hclspec.NewAttr("foo", "string", false), 160 "bar": hclspec.NewAttr("bar", "number", false), 161 "res_key": hclspec.NewAttr("res_key", "string", false), 162 }) 163 164 return configSpec, nil 165 } 166 167 // SetConfig decodes the configuration and stores it 168 func (m *mockPlugin) SetConfig(c *base.Config) error { 169 var config mockPluginConfig 170 if len(c.PluginConfig) != 0 { 171 if err := base.MsgPackDecode(c.PluginConfig, &config); err != nil { 172 return err 173 } 174 } 175 176 m.config = &config 177 m.nomadConfig = c.AgentConfig 178 m.negotiatedApiVersion = c.ApiVersion 179 return nil 180 } 181 182 func (m *mockPlugin) Fingerprint(ctx context.Context) (<-chan *device.FingerprintResponse, error) { 183 return make(chan *device.FingerprintResponse), nil 184 } 185 186 func (m *mockPlugin) Reserve(deviceIDs []string) (*device.ContainerReservation, error) { 187 if m.config == nil || m.config.ResKey == "" { 188 return nil, nil 189 } 190 191 return &device.ContainerReservation{ 192 Envs: map[string]string{m.config.ResKey: "config-set"}, 193 }, nil 194 } 195 196 func (m *mockPlugin) Stats(ctx context.Context, interval time.Duration) (<-chan *device.StatsResponse, error) { 197 return make(chan *device.StatsResponse), nil 198 }