Skip to content
This repository has been archived by the owner on Jul 10, 2023. It is now read-only.

Commit

Permalink
RSDK-2003 Move & update AttrConfig (#159)
Browse files Browse the repository at this point in the history
  • Loading branch information
zaporter-work authored Mar 6, 2023
1 parent 1346090 commit 8aac0bc
Show file tree
Hide file tree
Showing 4 changed files with 721 additions and 31 deletions.
93 changes: 92 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Package config implements functions to assist with attribute evaluation in the slam service
// Package config implements functions to assist with attribute evaluation in the SLAM service
package config

import (
"github.com/edaniels/golog"
"github.com/pkg/errors"
"go.viam.com/rdk/config"
"go.viam.com/utils"
)

// newError returns an error specific to a failure in the SLAM config.
Expand Down Expand Up @@ -39,3 +41,92 @@ func DetermineUseLiveData(logger golog.Logger, liveData *bool, sensors []string)
}
return useLiveData, nil
}

// AttrConfig describes how to configure the SLAM service.
type AttrConfig struct {
Sensors []string `json:"sensors"`
ConfigParams map[string]string `json:"config_params"`
DataDirectory string `json:"data_dir"`
UseLiveData *bool `json:"use_live_data"`
DataRateMsec int `json:"data_rate_msec"`
MapRateSec *int `json:"map_rate_sec"`
Port string `json:"port"`
DeleteProcessedData *bool `json:"delete_processed_data"`
Dev bool `json:"dev"`
}

// NewAttrConfig creates a SLAM config from a service config.
func NewAttrConfig(cfg config.Service) (*AttrConfig, error) {
attrCfg := &AttrConfig{}

_, err := config.TransformAttributeMapToStruct(attrCfg, cfg.Attributes)
if err != nil {
return &AttrConfig{}, newError(err.Error())
}

// This temporary value will be replaced once we are using rdk's validation
_, err = attrCfg.Validate("services.slam.attributes.fake")
if err != nil {
return &AttrConfig{}, newError(err.Error())
}

return attrCfg, nil
}

// Validate creates the list of implicit dependencies.
func (config *AttrConfig) Validate(path string) ([]string, error) {
if config.ConfigParams["mode"] == "" {
return nil, utils.NewConfigValidationFieldRequiredError(path, "config_params[mode]")
}

if config.DataDirectory == "" {
return nil, utils.NewConfigValidationFieldRequiredError(path, "data_dir")
}

if config.UseLiveData == nil {
return nil, utils.NewConfigValidationFieldRequiredError(path, "use_live_data")
}

if config.DataRateMsec < 0 {
return nil, errors.New("cannot specify data_rate_msec less than zero")
}

if config.MapRateSec != nil && *config.MapRateSec < 0 {
return nil, errors.New("cannot specify map_rate_sec less than zero")
}

deps := config.Sensors

return deps, nil
}

// SetOptionalParameters updates any unset optional config parameters to the values passed to this function.
func (config *AttrConfig) SetOptionalParameters(defaultPort string, defaultDataRateMsec, defaultMapRateSec int, logger golog.Logger) error {
if config.Port == "" {
config.Port = defaultPort
}

if config.DataRateMsec == 0 {
config.DataRateMsec = defaultDataRateMsec
logger.Debugf("no data_rate_msec given, setting to default value of %d", defaultDataRateMsec)
}

if config.MapRateSec == nil {
logger.Debugf("no map_rate_sec given, setting to default value of %d", defaultMapRateSec)
config.MapRateSec = &defaultMapRateSec
}
if *config.MapRateSec == 0 {
logger.Info("setting slam system to localization mode")
}

useLiveData, err := DetermineUseLiveData(logger, config.UseLiveData, config.Sensors)
if err != nil {
return err
}
config.UseLiveData = &useLiveData

deleteProcessedData := DetermineDeleteProcessedData(logger, config.DeleteProcessedData, useLiveData)
config.DeleteProcessedData = &deleteProcessedData

return nil
}
127 changes: 127 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import (
"testing"

"github.com/edaniels/golog"
"go.viam.com/rdk/config"
"go.viam.com/rdk/resource"
"go.viam.com/test"
"go.viam.com/utils"
)

var (
Expand Down Expand Up @@ -70,3 +73,127 @@ func TestDetermineUseLiveData(t *testing.T) {
test.That(t, useLiveData, test.ShouldBeTrue)
})
}

// makeCfgService creates the simplest possible config that can pass validation.
func makeCfgService() config.Service {
model := resource.NewDefaultModel(resource.ModelName("test"))
cfgService := config.Service{Name: "test", Type: "slam", Model: model}
cfgService.Attributes = make(map[string]interface{})
cfgService.Attributes["config_params"] = map[string]string{
"mode": "test mode",
}
cfgService.Attributes["data_dir"] = "path"
cfgService.Attributes["use_live_data"] = true
return cfgService
}

func TestNewAttrConf(t *testing.T) {
testCfgPath := "services.slam.attributes.fake"
logger := golog.NewTestLogger(t)

t.Run("Empty config", func(t *testing.T) {
model := resource.NewDefaultModel(resource.ModelName("test"))
cfgService := config.Service{Name: "test", Type: "slam", Model: model}
_, err := NewAttrConfig(cfgService)
test.That(t, err, test.ShouldBeError)
})

t.Run("Simplest valid config", func(t *testing.T) {
cfgService := makeCfgService()
_, err := NewAttrConfig(cfgService)
test.That(t, err, test.ShouldBeNil)
})

t.Run("Config without required fields", func(t *testing.T) {
// Test for missing main attribute fields
requiredFields := []string{"data_dir", "use_live_data"}
for _, requiredField := range requiredFields {
logger.Debugf("Testing SLAM config without %s", requiredField)
cfgService := makeCfgService()
delete(cfgService.Attributes, requiredField)
_, err := NewAttrConfig(cfgService)
test.That(t, err, test.ShouldBeError, newError(utils.NewConfigValidationFieldRequiredError(testCfgPath, requiredField).Error()))
}
// Test for missing config_params attributes
logger.Debug("Testing SLAM config without config_params[mode]")
cfgService := makeCfgService()
delete(cfgService.Attributes["config_params"].(map[string]string), "mode")
_, err := NewAttrConfig(cfgService)
test.That(t, err, test.ShouldBeError, newError(utils.NewConfigValidationFieldRequiredError(testCfgPath, "config_params[mode]").Error()))
})

t.Run("Config with invalid parameter type", func(t *testing.T) {
cfgService := makeCfgService()
cfgService.Attributes["use_live_data"] = "true"
_, err := NewAttrConfig(cfgService)
test.That(t, err, test.ShouldBeError)
cfgService.Attributes["use_live_data"] = true
_, err = NewAttrConfig(cfgService)
test.That(t, err, test.ShouldBeNil)
})

t.Run("Config with out of range values", func(t *testing.T) {
cfgService := makeCfgService()
cfgService.Attributes["data_rate_msec"] = -1
_, err := NewAttrConfig(cfgService)
test.That(t, err, test.ShouldBeError)
cfgService.Attributes["data_rate_msec"] = 1
cfgService.Attributes["map_rate_sec"] = -1
_, err = NewAttrConfig(cfgService)
test.That(t, err, test.ShouldBeError)
})

t.Run("All parameters e2e", func(t *testing.T) {
cfgService := makeCfgService()
cfgService.Attributes["sensors"] = []string{"a", "b"}
cfgService.Attributes["data_rate_msec"] = 1001
cfgService.Attributes["map_rate_sec"] = 1002
cfgService.Attributes["port"] = "47"
cfgService.Attributes["delete_processed_data"] = true
cfgService.Attributes["dev"] = false

cfgService.Attributes["config_params"] = map[string]string{
"mode": "test mode",
"value": "0",
"value_2": "test",
}
cfg, err := NewAttrConfig(cfgService)
test.That(t, err, test.ShouldBeNil)
test.That(t, cfg.DataDirectory, test.ShouldEqual, cfgService.Attributes["data_dir"])
test.That(t, *cfg.UseLiveData, test.ShouldEqual, cfgService.Attributes["use_live_data"])
test.That(t, cfg.Sensors, test.ShouldResemble, cfgService.Attributes["sensors"])
test.That(t, cfg.DataRateMsec, test.ShouldEqual, cfgService.Attributes["data_rate_msec"])
test.That(t, *cfg.MapRateSec, test.ShouldEqual, cfgService.Attributes["map_rate_sec"])
test.That(t, cfg.Port, test.ShouldEqual, cfgService.Attributes["port"])
test.That(t, cfg.Dev, test.ShouldEqual, cfgService.Attributes["dev"])
test.That(t, cfg.ConfigParams, test.ShouldResemble, cfgService.Attributes["config_params"])
})
}

func TestSetOptionalParameters(t *testing.T) {
logger := golog.NewTestLogger(t)

t.Run("Pass default parameters", func(t *testing.T) {
cfgService := makeCfgService()
cfgService.Attributes["sensors"] = []string{"a"}
cfgService.Attributes["use_live_data"] = true
cfg, err := NewAttrConfig(cfgService)
test.That(t, err, test.ShouldBeNil)
err = cfg.SetOptionalParameters("localhost", 1001, 1002, logger)
test.That(t, err, test.ShouldBeNil)
test.That(t, cfg.Port, test.ShouldResemble, "localhost")
test.That(t, cfg.DataRateMsec, test.ShouldEqual, 1001)
test.That(t, *cfg.MapRateSec, test.ShouldEqual, 1002)
test.That(t, *cfg.UseLiveData, test.ShouldEqual, true)
test.That(t, *cfg.DeleteProcessedData, test.ShouldEqual, true)
})

t.Run("Live data without sensors", func(t *testing.T) {
cfgService := makeCfgService()
cfgService.Attributes["use_live_data"] = true
cfg, err := NewAttrConfig(cfgService)
test.That(t, err, test.ShouldBeNil)
err = cfg.SetOptionalParameters("localhost", 1001, 1002, logger)
test.That(t, err, test.ShouldBeError, newError("sensors field cannot be empty when use_live_data is set to true"))
})
}
Loading

0 comments on commit 8aac0bc

Please sign in to comment.