diff options
author | Ilya Mashchenko <ilya@netdata.cloud> | 2024-04-10 20:18:05 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-10 20:18:05 +0300 |
commit | 7b4b258879ba4a83817981033dd6342a70cf80af (patch) | |
tree | 9c028b0aac49b5e440c8e7ec66de91d162dd5ebb | |
parent | 30d5acdaef786228112ac1b9d9673b1cec6b887b (diff) |
add collector to monitor ZFS pools space usage (#17367)
18 files changed, 985 insertions, 1 deletions
diff --git a/src/collectors/proc.plugin/plugin_proc.c b/src/collectors/proc.plugin/plugin_proc.c index 7742b344f4..cacc70523e 100644 --- a/src/collectors/proc.plugin/plugin_proc.c +++ b/src/collectors/proc.plugin/plugin_proc.c @@ -160,6 +160,7 @@ void *proc_main(void *ptr) netdata_thread_cleanup_push(proc_main_cleanup, ptr) { config_get_boolean("plugin:proc", "/proc/pagetypeinfo", CONFIG_BOOLEAN_NO); + config_get_boolean("plugin:proc", "/proc/spl/kstat/zfs/pool/state", CONFIG_BOOLEAN_NO); // check the enabled status for each module int i; diff --git a/src/go/collectors/go.d.plugin/README.md b/src/go/collectors/go.d.plugin/README.md index 3423c69686..41129166d7 100644 --- a/src/go/collectors/go.d.plugin/README.md +++ b/src/go/collectors/go.d.plugin/README.md @@ -128,6 +128,7 @@ see the appropriate collector readme. | [whoisquery](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/whoisquery) | Domain Expiry | | [windows](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/windows) | Windows | | [x509check](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/x509check) | Digital Certificates | +| [zfspool](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/zfspool) | ZFS Pools | | [zookeeper](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/zookeeper) | ZooKeeper | ## Configuration diff --git a/src/go/collectors/go.d.plugin/config/go.d.conf b/src/go/collectors/go.d.plugin/config/go.d.conf index bcd5455bc0..a201c1ff22 100644 --- a/src/go/collectors/go.d.plugin/config/go.d.conf +++ b/src/go/collectors/go.d.plugin/config/go.d.conf @@ -88,4 +88,5 @@ modules: # whoisquery: yes # windows: yes # x509check: yes +# zfspool: yes # zookeeper: yes diff --git a/src/go/collectors/go.d.plugin/config/go.d/zfspool.conf b/src/go/collectors/go.d.plugin/config/go.d/zfspool.conf new file mode 100644 index 0000000000..587c9b772a --- /dev/null +++ b/src/go/collectors/go.d.plugin/config/go.d/zfspool.conf @@ -0,0 +1,9 @@ +## All available configuration options, their descriptions and default values: +## https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/zpool#readme + +jobs: + - name: zfspool + binary_path: /usr/bin/zpool + + - name: zfspool + binary_path: /sbin/zpool # FreeBSD diff --git a/src/go/collectors/go.d.plugin/modules/init.go b/src/go/collectors/go.d.plugin/modules/init.go index bcfd39f3e2..66ec43c165 100644 --- a/src/go/collectors/go.d.plugin/modules/init.go +++ b/src/go/collectors/go.d.plugin/modules/init.go @@ -80,5 +80,6 @@ import ( _ "github.com/netdata/netdata/go/go.d.plugin/modules/windows" _ "github.com/netdata/netdata/go/go.d.plugin/modules/wireguard" _ "github.com/netdata/netdata/go/go.d.plugin/modules/x509check" + _ "github.com/netdata/netdata/go/go.d.plugin/modules/zfspool" _ "github.com/netdata/netdata/go/go.d.plugin/modules/zookeeper" ) diff --git a/src/go/collectors/go.d.plugin/modules/nvme/config_schema.json b/src/go/collectors/go.d.plugin/modules/nvme/config_schema.json index ee3f19d8d7..001d2a3e7d 100644 --- a/src/go/collectors/go.d.plugin/modules/nvme/config_schema.json +++ b/src/go/collectors/go.d.plugin/modules/nvme/config_schema.json @@ -1,7 +1,7 @@ { "jsonSchema": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NVMe Collector Configuration", + "title": "NVMe collector configuration", "type": "object", "properties": { "update_every": { diff --git a/src/go/collectors/go.d.plugin/modules/zfspool/charts.go b/src/go/collectors/go.d.plugin/modules/zfspool/charts.go new file mode 100644 index 0000000000..45943c656e --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/zfspool/charts.go @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package zfspool + +import ( + "fmt" + "strings" + + "github.com/netdata/netdata/go/go.d.plugin/agent/module" +) + +const ( + prioZpoolSpaceUtilization = 2820 + iota + prioZpoolSpaceUsage + prioZpoolFragmentation + prioZpoolHealthState +) + +var zpoolChartsTmpl = module.Charts{ + zpoolSpaceUtilizationChartTmpl.Copy(), + zpoolSpaceUsageChartTmpl.Copy(), + + zpoolFragmentationChartTmpl.Copy(), + + zpoolHealthStateChartTmpl.Copy(), +} + +var ( + zpoolSpaceUtilizationChartTmpl = module.Chart{ + ID: "zfspool_%s_space_utilization", + Title: "Zpool space utilization", + Units: "percentage", + Fam: "space usage", + Ctx: "zfspool.pool_space_utilization", + Type: module.Area, + Priority: prioZpoolSpaceUtilization, + Dims: module.Dims{ + {ID: "zpool_%s_cap", Name: "utilization"}, + }, + } + zpoolSpaceUsageChartTmpl = module.Chart{ + ID: "zfspool_%s_space_usage", + Title: "Zpool space usage", + Units: "bytes", + Fam: "space usage", + Ctx: "zfspool.pool_space_usage", + Type: module.Stacked, + Priority: prioZpoolSpaceUsage, + Dims: module.Dims{ + {ID: "zpool_%s_free", Name: "free"}, + {ID: "zpool_%s_alloc", Name: "used"}, + }, + } + + zpoolFragmentationChartTmpl = module.Chart{ + ID: "zfspool_%s_fragmentation", + Title: "Zpool fragmentation", + Units: "percentage", + Fam: "fragmentation", + Ctx: "zfspool.pool_fragmentation", + Type: module.Line, + Priority: prioZpoolFragmentation, + Dims: module.Dims{ + {ID: "zpool_%s_frag", Name: "fragmentation"}, + }, + } + + zpoolHealthStateChartTmpl = module.Chart{ + ID: "zfspool_%s_health_state", + Title: "Zpool health state", + Units: "state", + Fam: "health", + Ctx: "zfspool.pool_health_state", + Type: module.Line, + Priority: prioZpoolHealthState, + Dims: module.Dims{ + {ID: "zpool_%s_health_state_online", Name: "online"}, + {ID: "zpool_%s_health_state_degraded", Name: "degraded"}, + {ID: "zpool_%s_health_state_faulted", Name: "faulted"}, + {ID: "zpool_%s_health_state_offline", Name: "offline"}, + {ID: "zpool_%s_health_state_unavail", Name: "unavail"}, + {ID: "zpool_%s_health_state_removed", Name: "removed"}, + {ID: "zpool_%s_health_state_suspended", Name: "suspended"}, + }, + } +) + +func (z *ZFSPool) addZpoolCharts(name string) { + charts := zpoolChartsTmpl.Copy() + + for _, chart := range *charts { + chart.ID = fmt.Sprintf(chart.ID, name) + chart.Labels = []module.Label{ + {Key: "pool", Value: name}, + } + for _, dim := range chart.Dims { + dim.ID = fmt.Sprintf(dim.ID, name) + } + } + + if err := z.Charts().Add(*charts...); err != nil { + z.Warning(err) + } +} + +func (z *ZFSPool) removeZpoolCharts(name string) { + px := fmt.Sprintf("zpool_%s_", name) + + for _, chart := range *z.Charts() { + if strings.HasPrefix(chart.ID, px) { + chart.MarkRemove() + chart.MarkNotCreated() + } + } +} diff --git a/src/go/collectors/go.d.plugin/modules/zfspool/collect.go b/src/go/collectors/go.d.plugin/modules/zfspool/collect.go new file mode 100644 index 0000000000..43994bfc1b --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/zfspool/collect.go @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package zfspool + +import ( + "bufio" + "bytes" + "fmt" + "strconv" + "strings" +) + +var zpoolHealthStates = []string{ + "online", + "degraded", + "faulted", + "offline", + "removed", + "unavail", + "suspended", +} + +type zpoolStats struct { + name string + sizeBytes string + allocBytes string + freeBytes string + fragPerc string + capPerc string + dedupRatio string + health string +} + +func (z *ZFSPool) collect() (map[string]int64, error) { + bs, err := z.exec.list() + if err != nil { + return nil, err + } + + zpools, err := parseZpoolListOutput(bs) + if err != nil { + return nil, err + } + + mx := make(map[string]int64) + + z.collectZpoolListStats(mx, zpools) + + return mx, nil +} + +func (z *ZFSPool) collectZpoolListStats(mx map[string]int64, zpools []zpoolStats) { + seen := make(map[string]bool) + + for _, zpool := range zpools { + seen[zpool.name] = true + + if !z.zpools[zpool.name] { + z.addZpoolCharts(zpool.name) + z.zpools[zpool.name] = true + } + + px := "zpool_" + zpool.name + "_" + + if v, ok := parseInt(zpool.sizeBytes); ok { + mx[px+"size"] = v + } + if v, ok := parseInt(zpool.freeBytes); ok { + mx[px+"free"] = v + } + if v, ok := parseInt(zpool.allocBytes); ok { + mx[px+"alloc"] = v + } + if v, ok := parseFloat(zpool.capPerc); ok { + mx[px+"cap"] = int64(v) + } + if v, ok := parseFloat(zpool.fragPerc); ok { + mx[px+"frag"] = int64(v) + } + for _, s := range zpoolHealthStates { + mx[px+"health_state_"+s] = 0 + } + mx[px+"health_state_"+zpool.health] = 1 + } + + for name := range z.zpools { + if !seen[name] { + z.removeZpoolCharts(name) + delete(z.zpools, name) + } + } +} + +func parseZpoolListOutput(bs []byte) ([]zpoolStats, error) { + var lines []string + sc := bufio.NewScanner(bytes.NewReader(bs)) + for sc.Scan() { + if text := strings.TrimSpace(sc.Text()); text != "" { + lines = append(lines, text) + } + + } + if len(lines) < 2 { + return nil, fmt.Errorf("unexpected data: wanted >= 2 lines, got %d", len(lines)) + } + + headers := strings.Fields(lines[0]) + if len(headers) == 0 { + return nil, fmt.Errorf("unexpected data: missing headers") + } + + var zpools []zpoolStats + + /* + # zpool list -p + NAME SIZE ALLOC FREE EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT + rpool 21367462298 9051643576 12240656794 - 33 42 1.00 ONLINE - + zion - - - - - - - FAULTED - + */ + + for _, line := range lines[1:] { + values := strings.Fields(line) + if len(values) != len(headers) { + return nil, fmt.Errorf("unequal columns: headers(%d) != values(%d)", len(headers), len(values)) + } + + var zpool zpoolStats + + for i, v := range values { + v = strings.TrimSpace(v) + switch strings.ToLower(headers[i]) { + case "name": + zpool.name = v + case "size": + zpool.sizeBytes = v + case "alloc": + zpool.allocBytes = v + case "free": + zpool.freeBytes = v + case "frag": + zpool.fragPerc = v + case "cap": + zpool.capPerc = v + case "dedup": + zpool.dedupRatio = v + case "health": + zpool.health = strings.ToLower(v) + } + + if last := i+1 == len(headers); last && zpool.name != "" && zpool.health != "" { + zpools = append(zpools, zpool) + } + } + } + + if len(zpools) == 0 { + return nil, fmt.Errorf("unexpected data: missing pools") + } + + return zpools, nil +} + +func parseInt(s string) (int64, bool) { + if s == "-" { + return 0, false + } + v, err := strconv.ParseInt(s, 10, 64) + return v, err == nil +} + +func parseFloat(s string) (float64, bool) { + if s == "-" { + return 0, false + } + v, err := strconv.ParseFloat(s, 64) + return v, err == nil +} diff --git a/src/go/collectors/go.d.plugin/modules/zfspool/config_schema.json b/src/go/collectors/go.d.plugin/modules/zfspool/config_schema.json new file mode 100644 index 0000000000..dc11055f00 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/zfspool/config_schema.json @@ -0,0 +1,47 @@ +{ + "jsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ZFS Pools collector configuration", + "type": "object", + "properties": { + "update_every": { + "title": "Update every", + "description": "Data collection interval, measured in seconds.", + "type": "integer", + "minimum": 1, + "default": 10 + }, + "binary_path": { + "title": "Binary path", + "description": "Path to the `zpool` binary.", + "type": "string", + "default": "nvme" + }, + "timeout": { + "title": "Timeout", + "description": "Timeout for executing the binary, specified in seconds.", + "type": "number", + "minimum": 0.5, + "default": 10 + } + }, + "required": [ + "binary_path" + ], + "additionalProperties": false, + "patternProperties": { + "^name$": {} + } + }, + "uiSchema": { + "uiOptions": { + "fullPage": true + }, + "binary_path": { + "ui:help": "If an absolute path is provided, the collector will use it directly; otherwise, it will search for the binary in directories specified in the PATH environment variable." + }, + "timeout": { + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)." + } + } +} diff --git a/src/go/collectors/go.d.plugin/modules/zfspool/exec.go b/src/go/collectors/go.d.plugin/modules/zfspool/exec.go new file mode 100644 index 0000000000..0c155872e4 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/zfspool/exec.go @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package zfspool + +import ( + "context" + "fmt" + "os/exec" + "time" + + "github.com/netdata/netdata/go/go.d.plugin/logger" +) + +func newZpoolCLIExec(binPath string, timeout time.Duration) *zpoolCLIExec { + return &zpoolCLIExec{ + binPath: binPath, + timeout: timeout, + } +} + +type zpoolCLIExec struct { + *logger.Logger + + binPath string + timeout time.Duration +} + +func (e *zpoolCLIExec) list() ([]byte, error) { + ctx, cancel := context.WithTimeout(context.Background(), e.timeout) + defer cancel() + + cmd := exec.CommandContext(ctx, e.binPath, "list", "-p") + e.Debugf("executing '%s'", cmd) + + bs, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("error on '%s': %v", cmd, err) + } + + return bs, nil +} diff --git a/src/go/collectors/go.d.plugin/modules/zfspool/init.go b/src/go/collectors/go.d.plugin/modules/zfspool/init.go new file mode 100644 index 0000000000..d4debe3fa0 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/zfspool/init.go @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package zfspool + +import ( + "errors" + "os" + "os/exec" + "strings" +) + +func (z *ZFSPool) validateConfig() error { + if z.BinaryPath == "" { + return errors.New("no zpool binary path specified") + } + return nil +} + +func (z *ZFSPool) initZPoolCLIExec() (zpoolCLI, error) { + binPath := z.BinaryPath + + if !strings.HasPrefix(binPath, "/") { + path, err := exec.LookPath(binPath) + if err != nil { + return nil, err + } + binPath = path + } + + if _, err := os.Stat(binPath); err != nil { + return nil, err + } + + zpoolExec := newZpoolCLIExec(binPath, z.Timeout.Duration()) + + return zpoolExec, nil +} diff --git a/src/go/collectors/go.d.plugin/modules/zfspool/metadata.yaml b/src/go/collectors/go.d.plugin/modules/zfspool/metadata.yaml new file mode 100644 index 0000000000..e82611be4f --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/zfspool/metadata.yaml @@ -0,0 +1,138 @@ +plugin_name: go.d.plugin +modules: + - meta: + id: collector-go.d.plugin-zfspool + plugin_name: go.d.plugin + module_name: zfspool + monitored_instance: + name: ZFS Pools + link: "" + icon_filename: filesystem.svg + categories: + - data-collection.storage-mount-points-and-filesystems + keywords: + - zfs pools + - pools + - zfs + - filesystem + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + most_popular: false + overview: + data_collection: + metrics_description: > + This collector monitors the health and space usage of ZFS pools using the command line + tool [zpool](https://openzfs.github.io/openzfs-docs/man/master/8/zpool-list.8.html). + method_description: "" + supported_platforms: + include: [] + exclude: [] + multi_instance: false + additional_permissions: + description: "" + default_behavior: + auto_detection: + description: "" + limits: + description: "" + performance_impact: + description: "" + setup: + prerequisites: + list: [] + configuration: + file: + name: go.d/zfspool.conf + options: + description: | + The following options can be defined globally: update_every. + folding: + title: Config options + enabled: true + list: + - name: update_every + description: Data collection frequency. + default_value: 10 + required: false + - name: binary_path + description: Path to the `zpool` binary. If an absolute path is provided, the collector will use it directly; otherwise, it will search for the binary in directories specified in the PATH environment variable. + default_value: /usr/bin/zpool + required: true + - name: timeout + description: Timeout for executing the binary, specified in seconds. + default_value: 2 + required: false + examples: + folding: + title: Config + enabled: true + list: + - name: Custom binary path + description: The executable is not in the directories specified in the PATH environment variable. + config: | + jobs: + - name: zfspool + binary_path: /usr/local/sbin/zpool + troubleshooting: + problems: + list: [] + alerts: + - name: zfs_pool_space_utilization + metric: zfspool.pool_space_utilization + info: "ZFS pool **${label:pool}** is nearing capacity. Current space usage is above the threshold." + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/zfs.conf + - name: zfs_pool_health_state_warn + metric: zfspool.pool_health_state + info: "ZFS pool ${label:pool} state is degraded" + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/zfs.conf + - name: zfs_pool_health_state_crit + metric: zfspool.pool_health_state + info: "ZFS pool ${label:pool} state is faulted or unavail" + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/zfs.conf + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: zfs pool + description: These metrics refer to the ZFS pool. + labels: + - name: pool + description: Zpool name + metrics: + - name: zfspool.pool_space_utilization + description: Zpool space utilization + unit: '%' + chart_type: area + dimensions: + - name: utilization + - name: zfspool.pool_space_usage + description: Zpool space usage + unit: 'bytes' + chart_type: stacked + dimensions: + - name: free + - name: used + - name: zfspool.pool_fragmentation + description: Zpool fragmentation + unit: '%' + chart_type: line + dimensions: + - name: fragmentation + - name: zfspool.pool_health_state + description: Zpool health state + unit: 'state' + chart_type: line + dimensions: + - name: online + - name: degraded + - name: faulted + - name: offline + - name: unavail + - name: removed + - name: suspended diff --git a/src/go/collectors/go.d.plugin/modules/zfspool/testdata/config.json b/src/go/collectors/go.d.plugin/modules/zfspool/testdata/config.json new file mode 100644 index 0000000000..0957131934 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/zfspool/testdata/config.json @@ -0,0 +1,5 @@ +{ + "update_every": 123, + "timeout": 123.123, + "binary_path": "ok" +} diff --git a/src/go/collectors/go.d.plugin/modules/zfspool/testdata/config.yaml b/src/go/collectors/go.d.plugin/modules/zfspool/testdata/config.yaml new file mode 100644 index 0000000000..baf3bcd0b0 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/zfspool/testdata/config.yaml @@ -0,0 +1,3 @@ +update_every: 123 +timeout: 123.123 +binary_path: "ok" diff --git a/src/go/collectors/go.d.plugin/modules/zfspool/testdata/zpool-list.txt b/src/go/collectors/go.d.plugin/modules/zfspool/testdata/zpool-list.txt new file mode 100644 index 0000000000..06d9915c22 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/zfspool/testdata/zpool-list.txt @@ -0,0 +1,3 @@ +NAME SIZE ALLOC FREE EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT +rpool 21367462298 9051643576 12240656794 - 33 42 1.00 ONLINE - +zion - - - - - - - FAULTED - diff --git a/src/go/collectors/go.d.plugin/modules/zfspool/zfspool.go b/src/go/collectors/go.d.plugin/modules/zfspool/zfspool.go new file mode 100644 index 0000000000..eab83ef3bf --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/zfspool/zfspool.go @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package zfspool + +import ( + _ "embed" + "errors" + "time" + + "github.com/netdata/netdata/go/go.d.plugin/agent/module" + "github.com/netdata/netdata/go/go.d.plugin/pkg/web" +) + +//go:embed "config_schema.json" +var configSchema string + +func init() { + module.Register("zfspool", module.Creator{ + JobConfigSchema: configSchema, + Defaults: module.Defaults{ + UpdateEvery: 10, + }, + Create: func() module.Module { return New() }, + }) +} + +func New() *ZFSPool { + return &ZFSPool{ + Config: Config{ + BinaryPath: "/usr/bin/zpool", + Timeout: web.Duration(time.Second * 2), + }, + charts: &module.Charts{}, + zpools: make(map[string]bool), + } +} + +type Config struct { + UpdateEvery int `yaml:"update_every" json:"update_every"` + Timeout web.Duration `yaml:"timeout" json:"timeout"` + BinaryPath string `yaml:"binary_path" json:"binary_path"` +} + +type ( + ZFSPool struct { + module.Base + Config `yaml:",inline" json:""` + + charts *module.Charts + + exec zpoolCLI + + zpools map[string]bool + } + zpoolCLI interface { + list() ([]byte, error) + } +) + +func (z *ZFSPool) Configuration() any { + return z.Config +} + +func (z *ZFSPool) Init() error { + if err := z.validateConfig(); err != nil { + z.Errorf("config validation: %s", err) + return err + } + + zpoolExec, err := z.initZPoolCLIExec() + if err != nil { + z.Errorf("zpool exec initialization: %v", err) + return err + } + z.exec = zpoolExec + + return nil +} + +func (z *ZFSPool) Check() error { + mx, err := z.collect() + if err != nil { + z.Error(err) + return err + } + + if len(mx) == 0 { + return errors.New("no metrics collected") + } + + return nil +} + +func (z *ZFSPool) Charts() *module.Charts { + return z.charts +} + +func (z *ZFSPool) Collect() map[string]int64 { + mx, err := z.collect() + if err != nil { + z.Error(err) + } + + if len(mx) == 0 { + return nil + } + + return mx +} + +func (z *ZFSPool) Cleanup() {} diff --git a/src/go/collectors/go.d.plugin/modules/zfspool/zfspool_test.go b/src/go/collectors/go.d.plugin/modules/zfspool/zfspool_test.go new file mode 100644 index 0000000000..7a9392a034 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/zfspool/zfspool_test.go @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package zfspool + +import ( + "errors" + "os" + "testing" + + "github.com/netdata/netdata/go/go.d.plugin/agent/module" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + dataConfigJSON, _ = os.ReadFile("testdata/config.json") + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml") + + dataZpoolList, _ = os.ReadFile("testdata/zpool-list.txt") +) + +func Test_testDataIsValid(t *testing.T) { + for name, data := range map[string][]byte{ + "dataConfigJSON": dataConfigJSON, + "dataConfigYAML": dataConfigYAML, + + "dataZpoolList": dataZpoolList, + } { + require.NotNil(t, data, name) + + } +} + +func TestZFSPool_Configuration(t *testing.T) { + module.TestConfigurationSerialize(t, &ZFSPool{}, dataConfigJSON, dataConfigYAML) +} + +func TestZFSPool_Init(t *testing.T) { + tests := map[string]struct { + config Config + wantFail bool + }{ + "fails if 'binary_path' is not set": { + wantFail: true, + config: Config{ + BinaryPath: "", + }, + }, + "fails if failed to find binary": { + wantFail: true, + config: Config{ + BinaryPath: "zpool!!!", + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + zp := New() + zp.Config = test.config + + if test.wantFail { + assert.Error(t, zp.Init()) + } else { + assert.NoError(t, zp.Init()) + } + }) + } + +} + +func TestZFSPool_Cleanup(t *testing.T) { + tests := map[string]struct { + prepare func() *ZFSPool + }{ + "not initialized exec": { + prepare: func() *ZFSPool { + return New() + }, + }, + "after check": { + prepare: func() *ZFSPool { + zp := New() + zp.exec = prepareMockOK() + _ = zp.Check() + return zp + }, + }, + "after collect": { + prepare: func() *ZFSPool { + zp := New() + zp.exec = prepareMockOK() + _ = zp.Collect() + return zp + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + zp := test.prepare() + + assert.NotPanics(t, zp.Cleanup) + }) + } +} + +func TestZFSPool_Charts(t *testing.T) { + assert.NotNil(t, New().Charts()) +} + +func TestZFSPool_Check(t *testing.T) { + tests := map[string]struct { + prepareMock func() *mockZpoolCLIExec + wantFail bool + }{ + "success case": { + prepareMock: prepareMockOK, + wantFail: false, + }, + "error on list call": { + prepareMock: prepareMockErrOnList, + wantFail: true, + }, + "empty response": { + prepareMock: prepareMockEmptyResponse, + wantFail: true, + }, + "unexpected response": { + prepareMock: prepareMockUnexpectedResponse, + wantFail: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + zp := New() + mock := test.prepareMock() + zp.exec = mock + + if test.wantFail { + assert.Error(t, zp.Check()) |