Commit 521ae79b authored by Olivier Bourdon's avatar Olivier Bourdon

Add config option for excluded filesystems types list

Fixed test execution
Added more tests for better coverage
parent b385d131
......@@ -45,7 +45,7 @@ const (
// PluginName df collector plugin name
PluginName = "df"
// Version of plugin
Version = 3
Version = 4
nsVendor = "intel"
nsClass = "procfs"
......@@ -73,7 +73,7 @@ var (
"device_name",
"device_type",
}
invalidFSTypes = []string{
dfltInvalidFSTypes = []string{
"proc",
"binfmt_misc",
"fuse.gvfsd-fuse",
......@@ -113,6 +113,16 @@ func (p *dfCollector) setProcPath(cfg interface{}) error {
}
p.proc_path = procPath.(string)
}
invalidFSTypes, err := config.GetConfigItem(cfg, "invalid_fs_types")
if err == nil {
if len(invalidFSTypes.(string)) > 0 {
p.invalid_fs_types = strings.Split(invalidFSTypes.(string), ",")
} else {
p.invalid_fs_types = []string{}
}
} else {
p.invalid_fs_types = dfltInvalidFSTypes
}
p.initialized = true
return nil
}
......@@ -124,7 +134,7 @@ func (p *dfCollector) GetMetricTypes(cfg plugin.ConfigType) ([]plugin.MetricType
for _, kind := range metricsKind {
mts = append(mts, plugin.MetricType{
Namespace_: core.NewNamespace(namespacePrefix...).
AddDynamicElement("filesystem", "name of filesystem").
AddDynamicElement(nsType, "name of filesystem").
AddStaticElement(kind),
Description_: "dynamic filesystem metric: " + kind,
})
......@@ -139,14 +149,12 @@ func (p *dfCollector) CollectMetrics(mts []plugin.MetricType) ([]plugin.MetricTy
if err != nil {
return nil, err
}
metrics := []plugin.MetricType{}
curTime := time.Now()
dfms, err := p.stats.collect(p.proc_path)
dfms, err := p.stats.collect(p.proc_path, p.invalid_fs_types)
if err != nil {
return metrics, fmt.Errorf(fmt.Sprintf("Unable to collect metrics from df: %s", err))
}
for _, m := range mts {
ns := m.Namespace()
lns := len(ns)
......@@ -230,6 +238,8 @@ func (p *dfCollector) GetConfigPolicy() (*cpolicy.ConfigPolicy, error) {
node := cpolicy.NewPolicyNode()
node.Add(rule)
cp.Add([]string{nsVendor, nsClass, PluginName}, node)
rule, _ = cpolicy.NewStringRule("invalid_fs_types", false, strings.Join(dfltInvalidFSTypes, ","))
node.Add(rule)
return cp, nil
}
......@@ -242,6 +252,7 @@ func NewDfCollector() *dfCollector {
logger: logger,
initializedMutex: imutex,
proc_path: procPath,
invalid_fs_types: dfltInvalidFSTypes,
}
}
......@@ -263,6 +274,7 @@ type dfCollector struct {
stats collector
logger *log.Logger
proc_path string
invalid_fs_types []string
}
type dfMetric struct {
......@@ -277,14 +289,13 @@ type dfMetric struct {
}
type collector interface {
collect(string) ([]dfMetric, error)
collect(string, []string) ([]dfMetric, error)
}
type dfStats struct{}
func (dfs *dfStats) collect(procPath string) ([]dfMetric, error) {
func (dfs *dfStats) collect(procPath string, invalid_fs_types []string) ([]dfMetric, error) {
dfms := []dfMetric{}
cpath := path.Join(procPath, "1", "mountinfo")
fh, err := os.Open(cpath)
if err != nil {
......@@ -310,7 +321,10 @@ func (dfs *dfStats) collect(procPath string) ([]dfMetric, error) {
return nil, fmt.Errorf("Wrong format %d fields found on the right side instead of 7 min", len(rightFields))
}
// Keep only meaningfull filesystems
if !invalidFS(rightFields[0]) {
if invalidFSType(rightFields[0], invalid_fs_types) {
log.Debug(fmt.Sprintf("Ignoring mount point %s with FS type %s",
leftFields[4], rightFields[0]))
} else {
var dfm dfMetric
dfm.Filesystem = rightFields[1]
dfm.FsType = rightFields[0]
......@@ -350,7 +364,7 @@ func (dfs *dfStats) collect(procPath string) ([]dfMetric, error) {
}
// Return true if filesystem should not be taken into account
func invalidFS(fs string) bool {
func invalidFSType(fs string, invalidFSTypes []string) bool {
for _, v := range invalidFSTypes {
if fs == v {
return true
......@@ -383,10 +397,8 @@ func ceilPercent(v uint64, t uint64) float64 {
func makeNamespace(dfm dfMetric, kind string) []string {
ns := []string{}
ns = append(ns, namespacePrefix...)
ns = append(ns, dfm.MountPoint, kind)
return ns
}
......@@ -396,6 +408,5 @@ func validateMetric(namespace []string, dfm dfMetric) bool {
if mountPoint == dfm.MountPoint {
return true
}
return false
}
......@@ -20,18 +20,18 @@ limitations under the License.
package df
import (
"errors"
"strings"
"sync"
"testing"
log "github.com/Sirupsen/logrus"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"github.com/intelsdi-x/snap/control/plugin"
"github.com/intelsdi-x/snap/core"
"github.com/intelsdi-x/snap/core/cdata"
"github.com/intelsdi-x/snap/core/ctypes"
)
type DfPluginSuite struct {
......@@ -71,21 +71,93 @@ func (dfp *DfPluginSuite) SetupSuite() {
IUse: 0.5,
},
}
mc.On("collect", "/proc").Return(dfms, nil)
mc.On("collect", "/proc", dfltInvalidFSTypes).Return(dfms, nil)
mc.On("collect", "/dummy", dfltInvalidFSTypes).Return(dfms, errors.New("Fake error"))
dfp.mockCollector = mc
dfp.cfg = plugin.ConfigType{}
}
func (dfp *DfPluginSuite) TestGetMetricTypes() {
Convey("Given df plugin is initialized", dfp.T(), func() {
dfPlg := dfCollector{
stats: dfp.mockCollector,
logger: log.New(),
initializedMutex: new(sync.Mutex),
proc_path: "/proc",
}
Convey("Given df plugin is badly initialized", dfp.T(), func() {
dfPlg := NewDfCollector()
dfPlg.stats = dfp.mockCollector
node := cdata.NewNode()
node.AddItem("proc_path", ctypes.ConfigValueStr{Value: "/dummy"})
Convey("When list of available metrics is requested", func() {
mts := []plugin.MetricType{
plugin.MetricType{
Namespace_: core.NewNamespace("intel", "procfs", "filesystem", "rootfs", "space_free"),
Config_: node,
},
}
metrics, err := dfPlg.CollectMetrics(mts)
Convey("Then error should be reported", func() {
So(err, ShouldNotBeNil)
So(err.Error(), ShouldContainSubstring, "no such file or directory")
So(metrics, ShouldBeNil)
})
})
})
Convey("Given df plugin is initialized", dfp.T(), func() {
dfPlg := NewDfCollector()
dfPlg.stats = dfp.mockCollector
Convey("When values for given metrics are requested", func() {
mts, err := dfPlg.GetMetricTypes(dfp.cfg)
Convey("Then no error should be reported", func() {
So(err, ShouldBeNil)
})
Convey("Then proper metrics are returned", func() {
ns := []string{}
for _, m := range mts {
ns = append(ns, m.Namespace().String())
}
So(len(mts), ShouldEqual, 14)
So(ns, ShouldContain, "/intel/procfs/filesystem/*/space_free")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/space_reserved")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/space_used")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/space_percent_free")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/space_percent_reserved")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/space_percent_used")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/inodes_free")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/inodes_reserved")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/inodes_used")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/inodes_percent_free")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/inodes_percent_reserved")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/inodes_percent_used")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/device_name")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/device_type")
})
})
})
}
func (dfp *DfPluginSuite) TestCollectMetrics() {
Convey("Given df plugin is initialized", dfp.T(), func() {
dfPlg := NewDfCollector()
dfPlg.stats = dfp.mockCollector
Convey("When list of metrics is requested with too short namespace", func() {
mts := []plugin.MetricType{
plugin.MetricType{
Namespace_: core.NewNamespace("filesystem", "rootfs", "space_free"),
},
}
metrics, err := dfPlg.CollectMetrics(mts)
Convey("Then error should be reported", func() {
So(err, ShouldNotBeNil)
So(err.Error(), ShouldContainSubstring, "Wrong namespace length")
So(metrics, ShouldBeNil)
})
})
Convey("When list of specific metrics is requested", func() {
mts := []plugin.MetricType{
plugin.MetricType{
Namespace_: core.NewNamespace("intel", "procfs", "filesystem", "rootfs", "space_free"),
......@@ -98,6 +170,7 @@ func (dfp *DfPluginSuite) TestGetMetricTypes() {
Convey("Then no error should be reported", func() {
So(err, ShouldBeNil)
So(metrics, ShouldNotBeNil)
})
Convey("Then proper metrics are returned", func() {
......@@ -117,46 +190,234 @@ func (dfp *DfPluginSuite) TestGetMetricTypes() {
So(val, ShouldNotBeNil)
})
})
Convey("When one single available dynamic metrics is requested", func() {
mts := []plugin.MetricType{
plugin.MetricType{
Namespace_: core.NewNamespace("intel", "procfs", "filesystem", "*", "space_free"),
},
}
metrics, err := dfPlg.CollectMetrics(mts)
Convey("Then no error should be reported", func() {
So(err, ShouldBeNil)
So(metrics, ShouldNotBeNil)
})
Convey("Then proper metrics are returned", func() {
metvals := map[string]interface{}{}
for _, m := range metrics {
stat := strings.Join(m.Namespace().Strings()[3:], "/")
metvals[stat] = m.Data()
}
So(len(metrics), ShouldEqual, 2)
val, ok := metvals["rootfs/space_free"]
So(ok, ShouldBeTrue)
So(val, ShouldNotBeNil)
val, ok = metvals["big/space_free"]
So(ok, ShouldBeTrue)
So(val, ShouldNotBeNil)
})
})
Convey("When calling twice", func() {
mts := []plugin.MetricType{
plugin.MetricType{
Namespace_: core.NewNamespace("intel", "procfs", "filesystem", "rootfs", "space_free"),
},
}
metrics, err := dfPlg.CollectMetrics(mts)
Convey("Then no error should be reported", func() {
So(err, ShouldBeNil)
So(metrics, ShouldNotBeNil)
})
dfPlg.proc_path = "/dummy"
_, err = dfPlg.CollectMetrics(mts)
Convey("Then error should be reported", func() {
So(err, ShouldNotBeNil)
So(err.Error(), ShouldContainSubstring, "Fake error")
})
})
})
}
func (dfp *DfPluginSuite) TestCollectMetrics() {
func (dfp *DfPluginSuite) TestCollect() {
Convey("Given df plugin is initialized", dfp.T(), func() {
dfPlg := dfCollector{
stats: dfp.mockCollector,
logger: log.New(),
initializedMutex: new(sync.Mutex),
proc_path: "/proc",
}
dfPlg := NewDfCollector()
Convey("When values for given metrics are requested", func() {
Convey("When called with non existing path", func() {
metrics, err := dfPlg.stats.collect("/dummy", []string{})
mts, err := dfPlg.GetMetricTypes(dfp.cfg)
Convey("Then error should be reported", func() {
So(err, ShouldNotBeNil)
So(err.Error(), ShouldContainSubstring, "no such file or directory")
So(metrics, ShouldBeNil)
})
})
Convey("When called with existing path", func() {
metrics, err := dfPlg.stats.collect("/proc", []string{})
Convey("Then error should be reported", func() {
So(err, ShouldBeNil)
So(metrics, ShouldNotBeNil)
})
})
})
}
func (dfp *DfPluginSuite) TestHelperRoutines() {
Convey("Basically initialized", dfp.T(), func() {
Convey("Namespace", func() {
ns := createNamespace("element", "name")
Convey("Then no error should be reported", func() {
So(ns, ShouldNotBeNil)
So(strings.Join(ns, ","), ShouldStartWith,
strings.Join(namespacePrefix, ","))
So(strings.Join(ns, ","), ShouldEndWith,
"element,name")
})
dfm := dfMetric{
MountPoint: "/mount",
}
ns = makeNamespace(dfm, "mykind")
Convey("Then namespace shoud be reported", func() {
So(ns, ShouldNotBeNil)
So(len(ns), ShouldNotEqual, 0)
So(ns[len(ns)-1], ShouldEqual, "mykind")
So(ns[len(ns)-2], ShouldEqual, "/mount")
})
})
Convey("Set config variables", func() {
node := cdata.NewNode()
node.AddItem("proc_path", ctypes.ConfigValueStr{Value: "/etc/hosts"})
cfg := plugin.ConfigType{ConfigDataNode: node}
dfPlg := NewDfCollector()
dfPlg.stats = dfp.mockCollector
err := dfPlg.setProcPath(cfg)
Convey("Then error should be reported (not a directory)", func() {
So(err, ShouldNotBeNil)
So(err.Error(), ShouldContainSubstring, "is not a directory")
})
node = cdata.NewNode()
node.AddItem("proc_path", ctypes.ConfigValueStr{Value: "/proc"})
cfg = plugin.ConfigType{ConfigDataNode: node}
dfPlg = NewDfCollector()
dfPlg.stats = dfp.mockCollector
err = dfPlg.setProcPath(cfg)
Convey("Then no error should be reported (proc_path)", func() {
So(err, ShouldBeNil)
})
Convey("Then proper metrics are returned", func() {
ns := []string{}
for _, m := range mts {
ns = append(ns, m.Namespace().String())
}
So(len(mts), ShouldEqual, 14)
So(ns, ShouldContain, "/intel/procfs/filesystem/*/space_free")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/space_reserved")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/space_used")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/space_percent_free")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/space_percent_reserved")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/space_percent_used")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/inodes_free")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/inodes_reserved")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/inodes_used")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/inodes_percent_free")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/inodes_percent_reserved")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/inodes_percent_used")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/device_name")
So(ns, ShouldContain, "/intel/procfs/filesystem/*/device_type")
err = dfPlg.setProcPath(cfg)
Convey("Then no error should be reported (already called)", func() {
So(err, ShouldBeNil)
So(len(dfPlg.invalid_fs_types), ShouldEqual, 17)
})
node = cdata.NewNode()
node.AddItem("invalid_fs_types", ctypes.ConfigValueStr{Value: ""})
dfPlg = NewDfCollector()
dfPlg.stats = dfp.mockCollector
cfg = plugin.ConfigType{ConfigDataNode: node}
err = dfPlg.setProcPath(cfg)
Convey("Then no error should be reported (invalid_fs_types with proper value for empty string)", func() {
So(err, ShouldBeNil)
So(len(dfPlg.invalid_fs_types), ShouldEqual, 0)
})
node = cdata.NewNode()
node.AddItem("invalid_fs_types", ctypes.ConfigValueStr{Value: "fs1,fs2"})
dfPlg = NewDfCollector()
dfPlg.stats = dfp.mockCollector
cfg = plugin.ConfigType{ConfigDataNode: node}
err = dfPlg.setProcPath(cfg)
Convey("Then no error should be reported (invalid_fs_types with proper value)", func() {
So(err, ShouldBeNil)
So(len(dfPlg.invalid_fs_types), ShouldEqual, 2)
})
})
Convey("Set get config policy", func() {
dfPlg := NewDfCollector()
dfPlg.stats = dfp.mockCollector
cp, err := dfPlg.GetConfigPolicy()
Convey("Then no error should be reported", func() {
So(err, ShouldBeNil)
So(cp, ShouldNotBeNil)
})
})
Convey("Miscellaneous", func() {
dfm := dfMetric{
MountPoint: "/mount",
}
v := validateMetric([]string{"/mount"}, dfm)
Convey("Then true value should be reported", func() {
So(v, ShouldEqual, true)
})
v = validateMetric([]string{"/test"}, dfm)
Convey("Then false value should be reported", func() {
So(v, ShouldEqual, false)
})
l := []string{"1", "2", "3", "4"}
v = invalidFSType("3", l)
Convey("Then value should be reported as found", func() {
So(v, ShouldEqual, true)
})
v = invalidFSType("error", l)
Convey("Then value should be reported as not found", func() {
So(v, ShouldEqual, false)
})
m := Meta()
Convey("Then meta value should be reported as not nil", func() {
So(m, ShouldNotBeNil)
})
})
Convey("ceilPercent", func() {
v := ceilPercent(1, 0)
Convey("Then 0.0 value should be reported", func() {
So(v, ShouldEqual, 0.0)
})
v = ceilPercent(80, 111)
Convey("Then x value should be reported", func() {
So(v, ShouldEqual, 73.0)
})
})
})
......@@ -170,7 +431,7 @@ type MockCollector struct {
mock.Mock
}
func (mc *MockCollector) collect(p string) ([]dfMetric, error) {
ret := mc.Mock.Called("/proc")
func (mc *MockCollector) collect(p string, i []string) ([]dfMetric, error) {
ret := mc.Mock.Called(p, i)
return ret.Get(0).([]dfMetric), ret.Error(1)
}
......@@ -16,7 +16,8 @@
},
"config": {
"/intel/procfs/filesystem": {
"proc_path": "/proc"
"proc_path": "/proc",
"invalid_fs_types": "proc,binfmt_misc,fuse.gvfsd-fuse,sysfs,cgroup,fusectl,pstore,debugfs,securityfs,devpts,mqueue,hugetlbfs,nsfs,rpc_pipefs,devtmpfs,none,tmpfs"
}
},
"process": null,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment