Skip to content
This repository has been archived by the owner on Nov 8, 2022. It is now read-only.

Commit

Permalink
Enhancement #386: Implement Global Config per Spec with support for YAML
Browse files Browse the repository at this point in the history
config files
  • Loading branch information
Tom McSweeney committed Mar 16, 2016
1 parent 27fa657 commit fcab2fc
Show file tree
Hide file tree
Showing 35 changed files with 1,616 additions and 517 deletions.
7 changes: 5 additions & 2 deletions Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ If you would like to write your own, read through [Author a Plugin](#author-a-pl
Documentation for snap will be kept in this repository for now in the `docs/` directory. We would also like to link to external how-to blog posts as people write them. See our [CONTRIBUTING.md](#contributing) for more details.

* [snapd (snap agent)](docs/SNAPD.md)
* [configuring snapd](docs/SNAPD_CONFIGURATION.md)
* [snapctl (snap CLI)](docs/SNAPCTL.md)
* [build and test](docs/BUILD_AND_TEST.md)
* [REST API](docs/REST_API.md)
Expand Down
60 changes: 34 additions & 26 deletions control/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,25 @@ import (
"fmt"
"reflect"
"strconv"
"time"

log "github.com/Sirupsen/logrus"
"github.com/vrischmann/jsonutil"

"github.com/intelsdi-x/snap/core"
"github.com/intelsdi-x/snap/core/cdata"
"github.com/intelsdi-x/snap/core/ctypes"
)

// default configuration values
const (
defaultMaxRunningPlugins int = 3
defaultPluginTrust int = 1
defaultAutoDiscoverPath string = ""
defaultKeyringPaths string = ""
defaultCacheExpiration time.Duration = 500 * time.Millisecond
)

type pluginConfig struct {
All *cdata.ConfigDataNode `json:"all"`
Collector *pluginTypeConfigItem `json:"collector"`
Expand All @@ -51,15 +62,25 @@ type pluginConfigItem struct {
Versions map[int]*cdata.ConfigDataNode `json:"versions"`
}

type config struct {
Plugins *pluginConfig `json:"plugins"`
// holds the configuration passed in through the SNAP config file
type Config struct {
MaxRunningPlugins int `json:"max_running_plugins,omitempty"yaml:"max_running_plugins,omitempty"`
PluginTrust int `json:"plugin_trust_level,omitempty"yaml:"plugin_trust_level,omitempty"`
AutoDiscoverPath string `json:"auto_discover_path,omitempty"yaml:"auto_discover_path,omitempty"`
KeyringPaths string `json:"keyring_paths,omitempty"yaml:"keyring_paths,omitempty"`
CacheExpiration jsonutil.Duration `json:"cache_expiration,omitempty"yaml:"cache_expiration,omitempty"`
Plugins *pluginConfig `json:"plugins,omitempty"yaml:"plugins,omitempty"`
}

// NewConfig returns a reference to a global config type for the snap daemon
// by using a newly created empty plugin config.
func NewConfig() *config {
return &config{
Plugins: newPluginConfig(),
// get the default snapd configuration
func GetDefaultConfig() *Config {
return &Config{
MaxRunningPlugins: defaultMaxRunningPlugins,
PluginTrust: defaultPluginTrust,
AutoDiscoverPath: defaultAutoDiscoverPath,
KeyringPaths: defaultKeyringPaths,
CacheExpiration: jsonutil.Duration{defaultCacheExpiration},
Plugins: newPluginConfig(),
}
}

Expand All @@ -80,48 +101,35 @@ func newPluginConfig() *pluginConfig {
}
}

func (p *config) LoadConfig(b []byte, cfg map[string]interface{}) {
if _, ok := cfg["plugins"]; ok {
err := json.Unmarshal(b, p)
if err != nil {
log.WithFields(log.Fields{
"block": "LoadConfig",
"_module": "control-config",
"error": err.Error(),
}).Fatal("invalid config")
}
}
}

func (p *config) GetPluginConfigDataNode(pluginType core.PluginType, name string, ver int) cdata.ConfigDataNode {
func (p *Config) GetPluginConfigDataNode(pluginType core.PluginType, name string, ver int) cdata.ConfigDataNode {
return *p.Plugins.getPluginConfigDataNode(pluginType, name, ver)
}

func (p *config) MergePluginConfigDataNode(pluginType core.PluginType, name string, ver int, cdn *cdata.ConfigDataNode) cdata.ConfigDataNode {
func (p *Config) MergePluginConfigDataNode(pluginType core.PluginType, name string, ver int, cdn *cdata.ConfigDataNode) cdata.ConfigDataNode {
p.Plugins.mergePluginConfigDataNode(pluginType, name, ver, cdn)
return *p.Plugins.getPluginConfigDataNode(pluginType, name, ver)
}

func (p *config) MergePluginConfigDataNodeAll(cdn *cdata.ConfigDataNode) cdata.ConfigDataNode {
func (p *Config) MergePluginConfigDataNodeAll(cdn *cdata.ConfigDataNode) cdata.ConfigDataNode {
p.Plugins.mergePluginConfigDataNodeAll(cdn)
return *p.Plugins.All
}

func (p *config) DeletePluginConfigDataNodeField(pluginType core.PluginType, name string, ver int, fields ...string) cdata.ConfigDataNode {
func (p *Config) DeletePluginConfigDataNodeField(pluginType core.PluginType, name string, ver int, fields ...string) cdata.ConfigDataNode {
for _, field := range fields {
p.Plugins.deletePluginConfigDataNodeField(pluginType, name, ver, field)
}
return *p.Plugins.getPluginConfigDataNode(pluginType, name, ver)
}

func (p *config) DeletePluginConfigDataNodeFieldAll(fields ...string) cdata.ConfigDataNode {
func (p *Config) DeletePluginConfigDataNodeFieldAll(fields ...string) cdata.ConfigDataNode {
for _, field := range fields {
p.Plugins.deletePluginConfigDataNodeFieldAll(field)
}
return *p.Plugins.All
}

func (p *config) GetPluginConfigDataNodeAll() cdata.ConfigDataNode {
func (p *Config) GetPluginConfigDataNodeAll() cdata.ConfigDataNode {
return *p.Plugins.All
}

Expand Down
165 changes: 160 additions & 5 deletions control/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,22 @@ package control

import (
"testing"
"time"

"github.com/intelsdi-x/snap/core"
"github.com/intelsdi-x/snap/core/cdata"
"github.com/intelsdi-x/snap/core/ctypes"
"github.com/intelsdi-x/snap/pkg/globalconfig"
"github.com/intelsdi-x/snap/pkg/cfgfile"
. "github.com/smartystreets/goconvey/convey"
)

type mockConfig struct {
Control *Config
}

func TestPluginConfig(t *testing.T) {
Convey("Given a plugin config", t, func() {
cfg := NewConfig()
cfg := GetDefaultConfig()
So(cfg, ShouldNotBeNil)
Convey("with an entry for ALL plugins", func() {
cfg.Plugins.All.AddItem("gvar", ctypes.ConfigValueBool{Value: true})
Expand Down Expand Up @@ -59,10 +64,13 @@ func TestPluginConfig(t *testing.T) {
})

Convey("Provided a config in JSON we are able to unmarshal it into a valid config", t, func() {
cfg := NewConfig()
config := &mockConfig{
Control: GetDefaultConfig(),
}
path := "../examples/configs/snap-config-sample.json"
b, config := globalconfig.Read(path)
cfg.LoadConfig(b, config)
err := cfgfile.Read(path, &config)
So(err, ShouldBeNil)
cfg := config.Control
So(cfg.Plugins, ShouldNotBeNil)
So(cfg.Plugins.All, ShouldNotBeNil)
So(cfg.Plugins.All.Table()["password"], ShouldResemble, ctypes.ConfigValueStr{Value: "p@ssw0rd"})
Expand Down Expand Up @@ -110,3 +118,150 @@ func TestPluginConfig(t *testing.T) {
})
})
}

func TestControlConfigJSON(t *testing.T) {
config := &mockConfig{
Control: GetDefaultConfig(),
}
path := "../examples/configs/snap-config-sample.json"
err := cfgfile.Read(path, &config)
var cfg *Config
if err == nil {
cfg = config.Control
}
Convey("Provided a valid config in JSON", t, func() {
Convey("An error should not be returned when unmarshalling the config", func() {
So(err, ShouldBeNil)
})
Convey("AutoDiscoverPath should be set to /some/directory/with/plugins", func() {
So(cfg.AutoDiscoverPath, ShouldEqual, "/some/directory/with/plugins")
})
Convey("CacheExpiration should be set to 750ms", func() {
So(cfg.CacheExpiration.Duration, ShouldResemble, 750*time.Millisecond)
})
Convey("MaxRunningPlugins should be set to 1", func() {
So(cfg.MaxRunningPlugins, ShouldEqual, 1)
})
Convey("KeyringPaths should be set to /some/path/with/keyring/files", func() {
So(cfg.KeyringPaths, ShouldEqual, "/some/path/with/keyring/files")
})
Convey("PluginTrust should be set to 0", func() {
So(cfg.PluginTrust, ShouldEqual, 0)
})
Convey("Plugins section of control configuration should not be nil", func() {
So(cfg.Plugins, ShouldNotBeNil)
})
Convey("Plugins.All section should not be nil", func() {
So(cfg.Plugins.All, ShouldNotBeNil)
})
Convey("A password should be configured for all plugins", func() {
So(cfg.Plugins.All.Table()["password"], ShouldResemble, ctypes.ConfigValueStr{Value: "p@ssw0rd"})
})
Convey("Plugins.Collector section should not be nil", func() {
So(cfg.Plugins.Collector, ShouldNotBeNil)
})
Convey("Plugins.Collector should have config for pcm collector plugin", func() {
So(cfg.Plugins.Collector.Plugins["pcm"], ShouldNotBeNil)
})
Convey("Config for pcm should set path to pcm binary to /usr/local/pcm/bin", func() {
So(cfg.Plugins.Collector.Plugins["pcm"].Table()["path"], ShouldResemble, ctypes.ConfigValueStr{Value: "/usr/local/pcm/bin"})
})
Convey("Config for pcm plugin at version 1 should set user to john", func() {
So(cfg.Plugins.Collector.Plugins["pcm"].Versions[1].Table()["user"], ShouldResemble, ctypes.ConfigValueStr{Value: "john"})
})
Convey("Plugins.Processor section should not be nil", func() {
So(cfg.Plugins.Processor, ShouldNotBeNil)
})
Convey("Movingaverage processor plugin should have user set to jane", func() {
So(cfg.Plugins.Processor.Plugins["movingaverage"].Table()["user"], ShouldResemble, ctypes.ConfigValueStr{Value: "jane"})
})
Convey("Plugins.Publisher should not be nil", func() {
So(cfg.Plugins.Publisher, ShouldNotBeNil)
})
})

}

func TestControlConfigYaml(t *testing.T) {
config := &mockConfig{
Control: GetDefaultConfig(),
}
path := "../examples/configs/snap-config-sample.yaml"
err := cfgfile.Read(path, &config)
var cfg *Config
if err == nil {
cfg = config.Control
}
Convey("Provided a valid config in YAML", t, func() {
Convey("An error should not be returned when unmarshalling the config", func() {
So(err, ShouldBeNil)
})
Convey("AutoDiscoverPath should be set to /some/directory/with/plugins", func() {
So(cfg.AutoDiscoverPath, ShouldEqual, "/some/directory/with/plugins")
})
Convey("CacheExpiration should be set to 750ms", func() {
So(cfg.CacheExpiration.Duration, ShouldResemble, 750*time.Millisecond)
})
Convey("MaxRunningPlugins should be set to 1", func() {
So(cfg.MaxRunningPlugins, ShouldEqual, 1)
})
Convey("KeyringPaths should be set to /some/path/with/keyring/files", func() {
So(cfg.KeyringPaths, ShouldEqual, "/some/path/with/keyring/files")
})
Convey("PluginTrust should be set to 0", func() {
So(cfg.PluginTrust, ShouldEqual, 0)
})
Convey("Plugins section of control configuration should not be nil", func() {
So(cfg.Plugins, ShouldNotBeNil)
})
Convey("Plugins.All section should not be nil", func() {
So(cfg.Plugins.All, ShouldNotBeNil)
})
Convey("A password should be configured for all plugins", func() {
So(cfg.Plugins.All.Table()["password"], ShouldResemble, ctypes.ConfigValueStr{Value: "p@ssw0rd"})
})
Convey("Plugins.Collector section should not be nil", func() {
So(cfg.Plugins.Collector, ShouldNotBeNil)
})
Convey("Plugins.Collector should have config for pcm collector plugin", func() {
So(cfg.Plugins.Collector.Plugins["pcm"], ShouldNotBeNil)
})
Convey("Config for pcm should set path to pcm binary to /usr/local/pcm/bin", func() {
So(cfg.Plugins.Collector.Plugins["pcm"].Table()["path"], ShouldResemble, ctypes.ConfigValueStr{Value: "/usr/local/pcm/bin"})
})
Convey("Config for pcm plugin at version 1 should set user to john", func() {
So(cfg.Plugins.Collector.Plugins["pcm"].Versions[1].Table()["user"], ShouldResemble, ctypes.ConfigValueStr{Value: "john"})
})
Convey("Plugins.Processor section should not be nil", func() {
So(cfg.Plugins.Processor, ShouldNotBeNil)
})
Convey("Movingaverage processor plugin should have user set to jane", func() {
So(cfg.Plugins.Processor.Plugins["movingaverage"].Table()["user"], ShouldResemble, ctypes.ConfigValueStr{Value: "jane"})
})
Convey("Plugins.Publisher should not be nil", func() {
So(cfg.Plugins.Publisher, ShouldNotBeNil)
})
})

}

func TestControlDefaultConfig(t *testing.T) {
cfg := GetDefaultConfig()
Convey("Provided a default config", t, func() {
Convey("AutoDiscoverPath should be empty", func() {
So(cfg.AutoDiscoverPath, ShouldEqual, "")
})
Convey("CacheExpiration should equal 500ms", func() {
So(cfg.CacheExpiration.Duration, ShouldEqual, 500*time.Millisecond)
})
Convey("MaxRunningPlugins should equal 3", func() {
So(cfg.MaxRunningPlugins, ShouldEqual, 3)
})
Convey("KeyringPaths should be empty", func() {
So(cfg.KeyringPaths, ShouldEqual, "")
})
Convey("PluginTrust should equal 1", func() {
So(cfg.PluginTrust, ShouldEqual, 1)
})
})
}
15 changes: 10 additions & 5 deletions control/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ type pluginControl struct {
// TODO, going to need coordination on changing of these
RunningPlugins executablePlugins
Started bool
Config *config
Config *Config

autodiscoverPaths []string
eventManager *gomit.EventController
Expand Down Expand Up @@ -143,18 +143,23 @@ func CacheExpiration(t time.Duration) PluginControlOpt {
}

// OptSetConfig sets the plugin control configuration.
func OptSetConfig(cfg *config) PluginControlOpt {
func OptSetConfig(cfg *Config) PluginControlOpt {
return func(c *pluginControl) {
c.Config = cfg
c.pluginManager.SetPluginConfig(cfg.Plugins)
}
}

// New returns a new pluginControl instance
func New(opts ...PluginControlOpt) *pluginControl {

func New(cfg *Config) *pluginControl {
// construct a slice of options from the input configuration
opts := []PluginControlOpt{
MaxRunningPlugins(cfg.MaxRunningPlugins),
CacheExpiration(cfg.CacheExpiration.Duration),
OptSetConfig(cfg),
}
c := &pluginControl{}
c.Config = NewConfig()
c.Config = cfg
// Initialize components
//
// Event Manager
Expand Down
Loading

0 comments on commit fcab2fc

Please sign in to comment.