Skip to content

Commit

Permalink
add dynamic registration handling to device manager (#380)
Browse files Browse the repository at this point in the history
  • Loading branch information
edaniszewski committed Mar 30, 2020
1 parent ad838aa commit b21f235
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 33 deletions.
37 changes: 15 additions & 22 deletions examples/dynamic_registration/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import (
"math/rand"
"strconv"

"github.com/vapor-ware/synse-sdk/sdk/config"

"github.com/vapor-ware/synse-sdk/sdk"
"github.com/vapor-ware/synse-sdk/sdk/config"
"github.com/vapor-ware/synse-sdk/sdk/output"
)

Expand All @@ -23,7 +22,7 @@ var (
var temperatureHandler = sdk.DeviceHandler{
Name: "temperature",
Read: func(device *sdk.Device) ([]*output.Reading, error) {
value := strconv.Itoa(rand.Int()) // nolint: gas, gosec
value := strconv.Itoa(rand.Intn(100)) // nolint: gas, gosec
reading := output.Temperature.FromMetric(value)
return []*output.Reading{reading}, nil
},
Expand All @@ -45,36 +44,30 @@ func ProtocolIdentifier(data map[string]interface{}) string {
// "dynamic registration" by definition, but it is a valid usage. A more appropriate
// example could be taking an IP from the configuration, and using that to hit some
// endpoint which would give back all the information on the devices it manages.
func DynamicDeviceConfig(cfg map[string]interface{}) ([]*config.Devices, error) {
var res []*config.Devices

func DynamicDeviceConfig(cfg map[string]interface{}) ([]*config.DeviceProto, error) {
// create a new device - here, we are using the base address and appending
// index of the loop to create the id of the device. we are hardcoding in
// the name as temperature and temp2010, respectively, because we need the
// devices to match to their device handlers. in a real case, all of this info
// should be gathered from whatever the real source of dynamic registration is,
// e.g. for IPMI - the SDR records.
d := config.Devices{
Version: 3,
Devices: []*config.DeviceProto{
{
Type: "temperature",
Metadata: map[string]string{
"model": "temp2010",
},
Instances: []*config.DeviceInstance{
{
Info: "test device",
Data: map[string]interface{}{
"id": fmt.Sprint(cfg["base"]),
},
res := []*config.DeviceProto{
{
Type: "temperature",
Metadata: map[string]string{
"model": "temp2010",
},
Instances: []*config.DeviceInstance{
{
Handler: "temperature",
Info: "test device",
Data: map[string]interface{}{
"id": fmt.Sprint(cfg["base"]),
},
},
},
},
}

res = append(res, &d)
return res, nil
}

Expand Down
15 changes: 14 additions & 1 deletion sdk/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ type Loader struct {
// without a file extension.
FileName string

// The policy used for the most recent configuration Load.
policy policy.Policy

// The files which were found to match the loader parameters on search.
// This is populated by the `search()` function and used in the `read()`
// function.
Expand Down Expand Up @@ -142,6 +145,8 @@ func (loader *Loader) Load(pol policy.Policy) error {
"ext": loader.Ext,
}).Info("[config] loading configuration")

loader.policy = pol

if err := loader.checkOverrides(); err != nil {
return err
}
Expand Down Expand Up @@ -173,7 +178,15 @@ func (loader *Loader) Load(pol policy.Policy) error {
//
func (loader *Loader) Scan(out interface{}) error {
if loader.merged == nil || len(loader.merged) == 0 {
// fixme
if loader.policy == policy.Optional {
log.WithFields(log.Fields{
"loader": loader.Name,
"policy": loader.policy,
}).Debug("[config] no optional config found for Scan")
return nil
}

// fixme: better err message
return fmt.Errorf("unable to scan, no merged content, did you Load first")
}

Expand Down
75 changes: 69 additions & 6 deletions sdk/device_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,22 @@ type deviceManager struct {
id *pluginID
pluginHandlers *PluginHandlers
policies *policy.Policies

tagCache *TagCache
setupActions []*DeviceAction
devices map[string]*Device
handlers map[string]*DeviceHandler
dynamicConfig *config.DynamicRegistrationSettings
tagCache *TagCache
setupActions []*DeviceAction
devices map[string]*Device
handlers map[string]*DeviceHandler
}

// newDeviceManager creates a new DeviceManager.
// fixme (etd): this constructor will be cleaned up in the future. instead of passing everything in
// one at a time, we can pass in some sort of context which has everything it needs...
func newDeviceManager(id *pluginID, handlers *PluginHandlers, policies *policy.Policies) *deviceManager {
func newDeviceManager(id *pluginID, handlers *PluginHandlers, policies *policy.Policies, dynamicCfg *config.DynamicRegistrationSettings) *deviceManager {
return &deviceManager{
config: new(config.Devices),
id: id,
pluginHandlers: handlers,
dynamicConfig: dynamicCfg,
policies: policies,
tagCache: NewTagCache(),
devices: make(map[string]*Device),
Expand All @@ -89,14 +90,76 @@ func newDeviceManager(id *pluginID, handlers *PluginHandlers, policies *policy.P
func (manager *deviceManager) init() error {
log.Info("[device manager] initializing")

// Load device config from file.
if err := manager.loadConfig(); err != nil {
return err
}

// Load device configs dynamically.
if err := manager.loadDynamicConfig(); err != nil {
return err
}

// Create devices from config.
if err := manager.createDevices(); err != nil {
return err
}

// Create devices dynamically.
if err := manager.createDynamicDevices(); err != nil {

}

return nil
}

// loadDynamicConfig loads device configurations using the dynamic device config
// registrar plugin handler.
func (manager *deviceManager) loadDynamicConfig() error {
for _, cfg := range manager.dynamicConfig.Config {
devices, err := manager.pluginHandlers.DynamicConfigRegistrar(cfg)
if err != nil {
switch manager.policies.DynamicDeviceConfig {
case policy.Optional:
log.Info("[device manager] failed dynamic device config; skipping since its optional")
continue
case policy.Required:
log.Error("[device manager] failed dynamic device config; erroring since its required")
return err
default:
log.Error("[device manager] invalid policy when loading dynamic device config")
return err
}
}
manager.config.Devices = append(manager.config.Devices, devices...)
}
return nil
}

// createDynamicDevices creates devices using the dynamic device registrar plugin handler.
func (manager *deviceManager) createDynamicDevices() error {
for _, cfg := range manager.dynamicConfig.Config {
devices, err := manager.pluginHandlers.DynamicRegistrar(cfg)
if err != nil {
switch manager.policies.DynamicDeviceConfig {
case policy.Optional:
log.Info("[device manager] failed dynamic devices; skipping since its optional")
continue
case policy.Required:
log.Error("[device manager] failed dynamic devices; erroring since its required")
return err
default:
log.Error("[device manager] invalid policy when loading dynamic devices")
return err
}
}

for _, device := range devices {
if err := manager.AddDevice(device); err != nil {
return err
}
}
}
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion sdk/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func NewPlugin(options ...PluginOption) (*Plugin, error) {
pluginPolicies := policy.NewDefaultPolicies()

// Initialize plugin components.
dm := newDeviceManager(id, pluginHandlers, pluginPolicies)
dm := newDeviceManager(id, pluginHandlers, pluginPolicies, conf.DynamicRegistration)
sm := NewStateManager(conf.Settings)
sched := NewScheduler(conf.Settings, dm, sm)
hm := health.NewManager(conf.Health)
Expand Down
6 changes: 3 additions & 3 deletions sdk/plugin_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type DynamicDeviceRegistrar func(map[string]interface{}) ([]*Device, error)
// DynamicDeviceConfigRegistrar is a handler function that takes a Plugin config's "dynamic
// registration" data and generates Devices config instances from it. How this is done
// is specific to the plugin/protocol.
type DynamicDeviceConfigRegistrar func(map[string]interface{}) ([]*config.Devices, error)
type DynamicDeviceConfigRegistrar func(map[string]interface{}) ([]*config.DeviceProto, error)

// DeviceDataValidator is a handler function that takes the `Data` field of a device config
// and performs some validation on it. This allows users to provide validation on the
Expand Down Expand Up @@ -118,8 +118,8 @@ func defaultDynamicDeviceRegistration(_ map[string]interface{}) ([]*Device, erro
//
// This implementation simply returns an empty slice. A plugin will not do any dynamic
// registration by default.
func defaultDynamicDeviceConfigRegistration(_ map[string]interface{}) ([]*config.Devices, error) {
return []*config.Devices{}, nil
func defaultDynamicDeviceConfigRegistration(_ map[string]interface{}) ([]*config.DeviceProto, error) {
return []*config.DeviceProto{}, nil
}

// defaultDeviceDataValidator is the default implementation that fulfils the
Expand Down

0 comments on commit b21f235

Please sign in to comment.