@cryptotaxi247 / netdata-1 / commits / b9ea4b4cb

Port postfix collector from python to go (#18026)

Fotis Voutsas committed Jun 27, 2024 at 18:44 UTC b9ea4b4cb22a10e5b8e4ef53ea10b47b1d5f3a2b
17 files changed +686 -252
CMakeLists.txt
-2
@@ -2844,7 +2844,6 @@ install(FILES
2844 src/collectors/python.d.plugin/openldap/openldap.conf
2845 src/collectors/python.d.plugin/oracledb/oracledb.conf
2846 src/collectors/python.d.plugin/pandas/pandas.conf
2847 - src/collectors/python.d.plugin/postfix/postfix.conf
2847 src/collectors/python.d.plugin/puppet/puppet.conf
2848 src/collectors/python.d.plugin/rethinkdbs/rethinkdbs.conf
2849 src/collectors/python.d.plugin/retroshare/retroshare.conf
@@ -2885,7 +2884,6 @@ install(FILES
2884 src/collectors/python.d.plugin/openldap/openldap.chart.py
2885 src/collectors/python.d.plugin/oracledb/oracledb.chart.py
2886 src/collectors/python.d.plugin/pandas/pandas.chart.py
2888 - src/collectors/python.d.plugin/postfix/postfix.chart.py
2887 src/collectors/python.d.plugin/puppet/puppet.chart.py
2888 src/collectors/python.d.plugin/rethinkdbs/rethinkdbs.chart.py
2889 src/collectors/python.d.plugin/retroshare/retroshare.chart.py
src/collectors/python.d.plugin/postfix/README.md deleted
-1
@@ -1 +0,0 @@
1 -integrations/postfix.md
\ No newline at end of file
src/collectors/python.d.plugin/postfix/integrations/postfix.md deleted
-184
@@ -1,184 +0,0 @@
1 -<!--startmeta
2 -custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/python.d.plugin/postfix/README.md"
3 -meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/python.d.plugin/postfix/metadata.yaml"
4 -sidebar_label: "Postfix"
5 -learn_status: "Published"
6 -learn_rel_path: "Collecting Metrics/Mail Servers"
7 -most_popular: False
8 -message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE"
9 -endmeta-->
10 -
11 -# Postfix
12 -
13 -
14 -<img src="https://netdata.cloud/img/postfix.svg" width="150"/>
15 -
16 -
17 -Plugin: python.d.plugin
18 -Module: postfix
19 -
20 -<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
21 -
22 -## Overview
23 -
24 -Keep an eye on Postfix metrics for efficient mail server operations.
25 -Improve your mail server performance with Netdata's real-time metrics and built-in alerts.
26 -
27 -
28 -Monitors MTA email queue statistics using [postqueue](http://www.postfix.org/postqueue.1.html) tool.
29 -
30 -
31 -This collector is supported on all platforms.
32 -
33 -This collector supports collecting metrics from multiple instances of this integration, including remote instances.
34 -
35 -Postfix has internal access controls that limit activities on the mail queue. By default, all users are allowed to view the queue. If your system is configured with stricter access controls, you need to grant the `netdata` user access to view the mail queue. In order to do it, add `netdata` to `authorized_mailq_users` in the `/etc/postfix/main.cf` file.
36 -See the `authorized_mailq_users` setting in the [Postfix documentation](https://www.postfix.org/postconf.5.html) for more details.
37 -
38 -
39 -### Default Behavior
40 -
41 -#### Auto-Detection
42 -
43 -The collector executes `postqueue -p` to get Postfix queue statistics.
44 -
45 -#### Limits
46 -
47 -The default configuration for this integration does not impose any limits on data collection.
48 -
49 -#### Performance Impact
50 -
51 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
52 -
53 -
54 -## Metrics
55 -
56 -Metrics grouped by *scope*.
57 -
58 -The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels.
59 -
60 -
61 -
62 -### Per Postfix instance
63 -
64 -These metrics refer to the entire monitored application.
65 -
66 -
67 -This scope has no labels.
68 -
69 -Metrics:
70 -
71 -| Metric | Dimensions | Unit |
72 -|:------|:----------|:----|
73 -| postfix.qemails | emails | emails |
74 -| postfix.qsize | size | KiB |
75 -
76 -
77 -
78 -## Alerts
79 -
80 -There are no alerts configured by default for this integration.
81 -
82 -
83 -## Setup
84 -
85 -### Prerequisites
86 -
87 -No action required.
88 -
89 -### Configuration
90 -
91 -#### File
92 -
93 -There is no configuration file.
94 -#### Options
95 -
96 -There are 2 sections:
97 -
98 -* Global variables
99 -* One or more JOBS that can define multiple different instances to monitor.
100 -
101 -The following options can be defined globally: priority, penalty, autodetection_retry, update_every, but can also be defined per JOB to override the global values.
102 -
103 -Additionally, the following collapsed table contains all the options that can be configured inside a JOB definition.
104 -
105 -Every configuration JOB starts with a `job_name` value which will appear in the dashboard, unless a `name` parameter is specified.
106 -
107 -
108 -<details open><summary>Config options</summary>
109 -
110 -| Name | Description | Default | Required |
111 -|:----|:-----------|:-------|:--------:|
112 -| update_every | Sets the default data collection frequency. | 1 | no |
113 -| priority | Controls the order of charts at the netdata dashboard. | 60000 | no |
114 -| autodetection_retry | Sets the job re-check interval in seconds. | 0 | no |
115 -| penalty | Indicates whether to apply penalty to update_every in case of failures. | yes | no |
116 -| name | Job name. This value will overwrite the `job_name` value. JOBS with the same name are mutually exclusive. Only one of them will be allowed running at any time. This allows autodetection to try several alternatives and pick the one that works. | | no |
117 -
118 -</details>
119 -
120 -#### Examples
121 -There are no configuration examples.
122 -
123 -
124 -
125 -## Troubleshooting
126 -
127 -### Debug Mode
128 -
129 -To troubleshoot issues with the `postfix` collector, run the `python.d.plugin` with the debug option enabled. The output
130 -should give you clues as to why the collector isn't working.
131 -
132 -- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on
133 - your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`.
134 -
135 - ```bash
136 - cd /usr/libexec/netdata/plugins.d/
137 - ```
138 -
139 -- Switch to the `netdata` user.
140 -
141 - ```bash
142 - sudo -u netdata -s
143 - ```
144 -
145 -- Run the `python.d.plugin` to debug the collector:
146 -
147 - ```bash
148 - ./python.d.plugin postfix debug trace
149 - ```
150 -
151 -### Getting Logs
152 -
153 -If you're encountering problems with the `postfix` collector, follow these steps to retrieve logs and identify potential issues:
154 -
155 -- **Run the command** specific to your system (systemd, non-systemd, or Docker container).
156 -- **Examine the output** for any warnings or error messages that might indicate issues. These messages should provide clues about the root cause of the problem.
157 -
158 -#### System with systemd
159 -
160 -Use the following command to view logs generated since the last Netdata service restart:
161 -
162 -```bash
163 -journalctl _SYSTEMD_INVOCATION_ID="$(systemctl show --value --property=InvocationID netdata)" --namespace=netdata --grep postfix
164 -```
165 -
166 -#### System without systemd
167 -
168 -Locate the collector log file, typically at `/var/log/netdata/collector.log`, and use `grep` to filter for collector's name:
169 -
170 -```bash
171 -grep postfix /var/log/netdata/collector.log
172 -```
173 -
174 -**Note**: This method shows logs from all restarts. Focus on the **latest entries** for troubleshooting current issues.
175 -
176 -#### Docker Container
177 -
178 -If your Netdata runs in a Docker container named "netdata" (replace if different), use this command:
179 -
180 -```bash
181 -docker logs netdata 2>&1 | grep postfix
182 -```
183 -
184 -
src/collectors/python.d.plugin/postfix/postfix.chart.py deleted
-52
@@ -1,52 +0,0 @@
1 -# -*- coding: utf-8 -*-
2 -# Description: postfix netdata python.d module
3 -# Author: Pawel Krupa (paulfantom)
4 -# SPDX-License-Identifier: GPL-3.0-or-later
5 -
6 -from bases.FrameworkServices.ExecutableService import ExecutableService
7 -
8 -POSTQUEUE_COMMAND = 'postqueue -p'
9 -
10 -ORDER = [
11 - 'qemails',
12 - 'qsize',
13 -]
14 -
15 -CHARTS = {
16 - 'qemails': {
17 - 'options': [None, 'Postfix Queue Emails', 'emails', 'queue', 'postfix.qemails', 'line'],
18 - 'lines': [
19 - ['emails', None, 'absolute']
20 - ]
21 - },
22 - 'qsize': {
23 - 'options': [None, 'Postfix Queue Emails Size', 'KiB', 'queue', 'postfix.qsize', 'area'],
24 - 'lines': [
25 - ['size', None, 'absolute']
26 - ]
27 - }
28 -}
29 -
30 -
31 -class Service(ExecutableService):
32 - def __init__(self, configuration=None, name=None):
33 - ExecutableService.__init__(self, configuration=configuration, name=name)
34 - self.order = ORDER
35 - self.definitions = CHARTS
36 - self.command = POSTQUEUE_COMMAND
37 -
38 - def _get_data(self):
39 - """
40 - Format data received from shell command
41 - :return: dict
42 - """
43 - try:
44 - raw = self._get_raw_data()[-1].split(' ')
45 - if raw[0] == 'Mail' and raw[1] == 'queue':
46 - return {'emails': 0,
47 - 'size': 0}
48 -
49 - return {'emails': raw[4],
50 - 'size': raw[1]}
51 - except (ValueError, AttributeError):
52 - return None
src/go/collectors/go.d.plugin/modules/init.go
+1
@@ -64,6 +64,7 @@ import (
64 _ "github.com/netdata/netdata/go/go.d.plugin/modules/pika"
65 _ "github.com/netdata/netdata/go/go.d.plugin/modules/ping"
66 _ "github.com/netdata/netdata/go/go.d.plugin/modules/portcheck"
67 + _ "github.com/netdata/netdata/go/go.d.plugin/modules/postfix"
68 _ "github.com/netdata/netdata/go/go.d.plugin/modules/postgres"
69 _ "github.com/netdata/netdata/go/go.d.plugin/modules/powerdns"
70 _ "github.com/netdata/netdata/go/go.d.plugin/modules/powerdns_recursor"
src/go/collectors/go.d.plugin/modules/postfix/charts.go new
+61
@@ -0,0 +1,61 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package postfix
4 +
5 +import (
6 + "github.com/netdata/netdata/go/go.d.plugin/agent/module"
7 +)
8 +
9 +const (
10 + prioPostfixQEmailsChartTmpl = 1000 + iota
11 + prioPostfixQSizeChartTmpl
12 +)
13 +
14 +var postfixChartsTmpl = module.Charts{
15 + postfixQEmailsChartTmpl.Copy(),
16 + postfixQSizeChartTmpl.Copy(),
17 +}
18 +
19 +var (
20 + postfixQEmailsChartTmpl = module.Chart{
21 + ID: "postfix_queue_emails",
22 + Title: "Postfix Queue Emails",
23 + Units: "emails",
24 + Fam: "queue",
25 + Ctx: "postfix.qemails",
26 + Type: module.Area,
27 + Priority: prioPostfixQEmailsChartTmpl,
28 + Dims: module.Dims{
29 + {ID: "emails", Name: "emails"},
30 + },
31 + }
32 + postfixQSizeChartTmpl = module.Chart{
33 + ID: "postfix_queue_size",
34 + Title: "Postfix Queue Size",
35 + Units: "KiB",
36 + Fam: "queue",
37 + Ctx: "postfix.qsize",
38 + Type: module.Area,
39 + Priority: prioPostfixQSizeChartTmpl,
40 + Dims: module.Dims{
41 + {ID: "size", Name: "size"},
42 + },
43 + }
44 +)
45 +
46 +func (p *Postfix) addPostfixCharts() {
47 + charts := postfixChartsTmpl.Copy()
48 +
49 + if err := p.Charts().Add(*charts...); err != nil {
50 + p.Warning(err)
51 + }
52 +}
53 +
54 +// func (p *Postfix) removePostfixCharts(name string) {
55 +// for _, chart := range *p.Charts() {
56 +// if strings.HasPrefix(chart.ID, name) {
57 +// chart.MarkRemove()
58 +// chart.MarkNotCreated()
59 +// }
60 +// }
61 +// }
src/go/collectors/go.d.plugin/modules/postfix/collect.go new
+97
@@ -0,0 +1,97 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package postfix
4 +
5 +import (
6 + "bufio"
7 + "bytes"
8 + "fmt"
9 + "log"
10 + "regexp"
11 + "strconv"
12 +)
13 +
14 +type postqueueStats struct {
15 + kbytes int64
16 + requests int64
17 +}
18 +
19 +func (p *Postfix) collect() (map[string]int64, error) {
20 + bs, err := p.exec.list()
21 + if err != nil {
22 + return nil, err
23 + }
24 +
25 + stats, err := parsePostfixOutput(bs)
26 + if err != nil {
27 + return nil, err
28 + }
29 +
30 + mx := make(map[string]int64)
31 +
32 + p.collectPostqueueStats(mx, *stats)
33 +
34 + return mx, nil
35 +}
36 +
37 +func (p *Postfix) collectPostqueueStats(mx map[string]int64, stats postqueueStats) {
38 +
39 + if !p.seen_metrics {
40 + p.addPostfixCharts()
41 + p.seen_metrics = true
42 + }
43 +
44 + mx["emails"] = stats.requests
45 + mx["size"] = stats.kbytes
46 +
47 +}
48 +
49 +func parsePostfixOutput(bs []byte) (*postqueueStats, error) {
50 + if len(bs) == 0 {
51 + return nil, fmt.Errorf("error: No bytes to read")
52 + }
53 +
54 + /*
55 + $ postqueue -p
56 + 752741009D2A* 10438 Wed Jun 26 13:39:26 root@localhost.test
57 + fotis@localhost.test
58 + 6B5FA10033D4* 10438 Wed Jun 26 13:39:23 root@localhost.test
59 + fotis@localhost.test
60 + -- 132422 Kbytes in 12991 Requests.
61 + */
62 +
63 + sc := bufio.NewScanner(bytes.NewReader(bs))
64 +
65 + kbytes := int64(-1)
66 + requests := int64(-1)
67 + re := regexp.MustCompile(`-- (\d+) Kbytes in (\d+) Requests\.`)
68 +
69 + for sc.Scan() {
70 + line := sc.Text()
71 + if line == "Mail queue is empty" {
72 + kbytes = 0
73 + requests = 0
74 + break
75 + }
76 +
77 + matches := re.FindStringSubmatch(line)
78 + if matches != nil {
79 + kbytes, _ = strconv.ParseInt(matches[1], 10, 64)
80 + requests, _ = strconv.ParseInt(matches[2], 10, 64)
81 + break
82 + }
83 + }
84 +
85 + if err := sc.Err(); err != nil {
86 + log.Fatalf("Error reading output: %v", err)
87 + }
88 +
89 + if kbytes == -1 && requests == -1 {
90 + return nil, fmt.Errorf("unexpected response")
91 + }
92 +
93 + return &postqueueStats{
94 + kbytes: kbytes,
95 + requests: requests,
96 + }, nil
97 +}
src/go/collectors/go.d.plugin/modules/postfix/config_schema.json new
+47
@@ -0,0 +1,47 @@
1 +{
2 + "jsonSchema": {
3 + "$schema": "http://json-schema.org/draft-07/schema#",
4 + "title": "Postfix collector configuration",
5 + "type": "object",
6 + "properties": {
7 + "update_every": {
8 + "title": "Update every",
9 + "description": "Data collection interval, measured in seconds.",
10 + "type": "integer",
11 + "minimum": 1,
12 + "default": 10
13 + },
14 + "binary_path": {
15 + "title": "Binary path",
16 + "description": "Path to the `postqueue` binary.",
17 + "type": "string",
18 + "default": "/usr/sbin/postqueue"
19 + },
20 + "timeout": {
21 + "title": "Timeout",
22 + "description": "Timeout for executing the binary, specified in seconds.",
23 + "type": "number",
24 + "minimum": 0.5,
25 + "default": 2
26 + }
27 + },
28 + "required": [
29 + "binary_path"
30 + ],
31 + "additionalProperties": false,
32 + "patternProperties": {
33 + "^name$": {}
34 + }
35 + },
36 + "uiSchema": {
37 + "uiOptions": {
38 + "fullPage": true
39 + },
40 + "binary_path": {
41 + "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."
42 + },
43 + "timeout": {
44 + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
45 + }
46 + }
47 +}
\ No newline at end of file
src/go/collectors/go.d.plugin/modules/postfix/exec.go new
+41
@@ -0,0 +1,41 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package postfix
4 +
5 +import (
6 + "context"
7 + "fmt"
8 + "os/exec"
9 + "time"
10 +
11 + "github.com/netdata/netdata/go/go.d.plugin/logger"
12 +)
13 +
14 +func newPostqueueExec(binPath string, timeout time.Duration) *postqueueExec {
15 + return &postqueueExec{
16 + binPath: binPath,
17 + timeout: timeout,
18 + }
19 +}
20 +
21 +type postqueueExec struct {
22 + *logger.Logger
23 +
24 + binPath string
25 + timeout time.Duration
26 +}
27 +
28 +func (p *postqueueExec) list() ([]byte, error) {
29 + ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
30 + defer cancel()
31 +
32 + cmd := exec.CommandContext(ctx, p.binPath, "-p")
33 + p.Debugf("executing '%s'", cmd)
34 +
35 + bs, err := cmd.Output()
36 + if err != nil {
37 + return nil, fmt.Errorf("error on '%s': %v", cmd, err)
38 + }
39 +
40 + return bs, nil
41 +}
src/go/collectors/go.d.plugin/modules/postfix/init.go new
+38
@@ -0,0 +1,38 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package postfix
4 +
5 +import (
6 + "errors"
7 + "os"
8 + "os/exec"
9 + "strings"
10 +)
11 +
12 +func (p *Postfix) validateConfig() error {
13 + if p.BinaryPath == "" {
14 + return errors.New("no postqueue binary path specified")
15 + }
16 + return nil
17 +}
18 +
19 +func (p *Postfix) initPostfixExec() (postqueue, error) {
20 + binPath := p.BinaryPath
21 +
22 + if !strings.HasPrefix(binPath, "/") {
23 + path, err := exec.LookPath(binPath)
24 + if err != nil {
25 + return nil, err
26 + }
27 + binPath = path
28 + }
29 +
30 + if _, err := os.Stat(binPath); err != nil {
31 + return nil, err
32 + }
33 +
34 + postqueueExec := newPostqueueExec(binPath, p.Timeout.Duration())
35 + postqueueExec.Logger = p.Logger
36 +
37 + return postqueueExec, nil
38 +}
src/go/collectors/go.d.plugin/modules/postfix/metadata.yaml renamed
+12 -13
@@ -1,7 +1,7 @@
1 -plugin_name: python.d.plugin
1 +plugin_name: go.d.plugin
2 modules:
3 - meta:
4 - plugin_name: python.d.plugin
4 + plugin_name: go.d.plugin
5 module_name: postfix
6 monitored_instance:
7 name: Postfix
@@ -23,22 +23,21 @@ modules:
23 data_collection:
24 metrics_description: >
25 Keep an eye on Postfix metrics for efficient mail server operations.
26 -
26 +
27 Improve your mail server performance with Netdata's real-time metrics and built-in alerts.
28 method_description: >
29 Monitors MTA email queue statistics using [postqueue](http://www.postfix.org/postqueue.1.html) tool.
30 +
31 + Runs `postqueue -p` every 10 seconds.
32 supported_platforms:
33 include: []
34 exclude: []
35 multi_instance: true
36 additional_permissions:
37 description: >
36 - Postfix has internal access controls that limit activities on the mail queue. By default, all users are allowed to view
37 - the queue. If your system is configured with stricter access controls, you need to grant the `netdata` user access to
38 - view the mail queue. In order to do it, add `netdata` to `authorized_mailq_users` in the `/etc/postfix/main.cf` file.
39 -
40 - See the `authorized_mailq_users` setting in
41 - the [Postfix documentation](https://www.postfix.org/postconf.5.html) for more details.
38 + Postfix has internal access controls that limit activities on the mail queue. By default, all users are allowed to view the queue. If your system is configured with more strict access controls, you need to grant the `netdata` user access to view the mail queue. In order to do it, add `netdata` to `authorized_mailq_users` in the `/etc/postfix/main.cf` file.
39 +
40 + See the `authorized_mailq_users` setting in the [Postfix documentation](https://www.postfix.org/postconf.5.html) for more details.
41 default_behavior:
42 auto_detection:
43 description: "The collector executes `postqueue -p` to get Postfix queue statistics."
@@ -56,14 +55,14 @@ modules:
55 options:
56 description: |
57 There are 2 sections:
59 -
58 +
59 * Global variables
60 * One or more JOBS that can define multiple different instances to monitor.
62 -
61 +
62 The following options can be defined globally: priority, penalty, autodetection_retry, update_every, but can also be defined per JOB to override the global values.
64 -
63 +
64 Additionally, the following collapsed table contains all the options that can be configured inside a JOB definition.
66 -
65 +
66 Every configuration JOB starts with a `job_name` value which will appear in the dashboard, unless a `name` parameter is specified.
67 folding:
68 title: Config options
src/go/collectors/go.d.plugin/modules/postfix/postfix.conf renamed
src/go/collectors/go.d.plugin/modules/postfix/postfix.go new
+112
@@ -0,0 +1,112 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package postfix
4 +
5 +import (
6 + _ "embed"
7 + "errors"
8 + "time"
9 +
10 + "github.com/netdata/netdata/go/go.d.plugin/agent/module"
11 + "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
12 +)
13 +
14 +//go:embed "config_schema.json"
15 +var configSchema string
16 +
17 +func init() {
18 + module.Register("postfix", module.Creator{
19 + JobConfigSchema: configSchema,
20 + Defaults: module.Defaults{
21 + UpdateEvery: 10,
22 + },
23 + Create: func() module.Module { return New() },
24 + Config: func() any { return &Config{} },
25 + })
26 +}
27 +
28 +func New() *Postfix {
29 + return &Postfix{
30 + Config: Config{
31 + BinaryPath: "/usr/sbin/postqueue",
32 + Timeout: web.Duration(time.Second * 2),
33 + },
34 + charts: &module.Charts{},
35 + // zpools: make(map[string]bool),
36 + }
37 +}
38 +
39 +type Config struct {
40 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
41 + Timeout web.Duration `yaml:"timeout,omitempty" json:"timeout"`
42 + BinaryPath string `yaml:"binary_path,omitempty" json:"binary_path"`
43 +}
44 +
45 +type (
46 + Postfix struct {
47 + module.Base
48 + Config `yaml:",inline" json:""`
49 +
50 + charts *module.Charts
51 +
52 + exec postqueue
53 +
54 + seen_metrics bool
55 + }
56 + postqueue interface {
57 + list() ([]byte, error)
58 + }
59 +)
60 +
61 +func (p *Postfix) Configuration() any {
62 + return p.Config
63 +}
64 +
65 +func (p *Postfix) Init() error {
66 + if err := p.validateConfig(); err != nil {
67 + p.Errorf("config validation: %s", err)
68 + return err
69 + }
70 +
71 + postfixExec, err := p.initPostfixExec()
72 + if err != nil {
73 + p.Errorf("postfix exec initialization: %v", err)
74 + return err
75 + }
76 + p.exec = postfixExec
77 +
78 + return nil
79 +}
80 +
81 +func (p *Postfix) Check() error {
82 + mx, err := p.collect()
83 + if err != nil {
84 + p.Error(err)
85 + return err
86 + }
87 +
88 + if len(mx) == 0 {
89 + return errors.New("no metrics collected")
90 + }
91 +
92 + return nil
93 +}
94 +
95 +func (p *Postfix) Charts() *module.Charts {
96 + return p.charts
97 +}
98 +
99 +func (p *Postfix) Collect() map[string]int64 {
100 + mx, err := p.collect()
101 + if err != nil {
102 + p.Error(err)
103 + }
104 +
105 + if len(mx) == 0 {
106 + return nil
107 + }
108 +
109 + return mx
110 +}
111 +
112 +func (p *Postfix) Cleanup() {}
src/go/collectors/go.d.plugin/modules/postfix/postfix_test.go new
+235
@@ -0,0 +1,235 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package postfix
4 +
5 +import (
6 + "errors"
7 + "os"
8 + "testing"
9 +
10 + "github.com/netdata/netdata/go/go.d.plugin/agent/module"
11 +
12 + "fmt"
13 +
14 + "github.com/stretchr/testify/assert"
15 + "github.com/stretchr/testify/require"
16 +)
17 +
18 +var (
19 + dataConfigJSON, _ = os.ReadFile("testdata/config.json")
20 + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
21 +
22 + dataPostqueue, _ = os.ReadFile("testdata/postqueue.txt")
23 +)
24 +
25 +func Test_testDataIsValid(t *testing.T) {
26 + for name, data := range map[string][]byte{
27 + "dataConfigJSON": dataConfigJSON,
28 + "dataConfigYAML": dataConfigYAML,
29 +
30 + "dataPostqueue": dataPostqueue,
31 + } {
32 + require.NotNil(t, data, name)
33 +
34 + }
35 + // fmt.Println(string(dataPostqueue))
36 +}
37 +
38 +func TestPostfix_Configuration(t *testing.T) {
39 + module.TestConfigurationSerialize(t, &Postfix{}, dataConfigJSON, dataConfigYAML)
40 +}
41 +
42 +func TestPostfix_Init(t *testing.T) {
43 + tests := map[string]struct {
44 + config Config
45 + wantFail bool
46 + }{
47 + "fails if 'binary_path' is not set": {
48 + wantFail: true,
49 + config: Config{
50 + BinaryPath: "",
51 + },
52 + },
53 + "fails if failed to find binary": {
54 + wantFail: true,
55 + config: Config{
56 + BinaryPath: "postqueue!!!",
57 + },
58 + },
59 + }
60 +
61 + for name, test := range tests {
62 + t.Run(name, func(t *testing.T) {
63 + pf := New()
64 + pf.Config = test.config
65 +
66 + if test.wantFail {
67 + assert.Error(t, pf.Init())
68 + } else {
69 + assert.NoError(t, pf.Init())
70 + }
71 + })
72 + }
73 +}
74 +
75 +func TestPostfix_Cleanup(t *testing.T) {
76 + tests := map[string]struct {
77 + prepare func() *Postfix
78 + }{
79 + "not initialized exec": {
80 + prepare: func() *Postfix {
81 + return New()
82 + },
83 + },
84 + "after check": {
85 + prepare: func() *Postfix {
86 + pf := New()
87 + pf.exec = prepareMockOK()
88 + _ = pf.Check()
89 + return pf
90 + },
91 + },
92 + "after collect": {
93 + prepare: func() *Postfix {
94 + pf := New()
95 + pf.exec = prepareMockOK()
96 + _ = pf.Collect()
97 + return pf
98 + },
99 + },
100 + }
101 +
102 + for name, test := range tests {
103 + t.Run(name, func(t *testing.T) {
104 + pf := test.prepare()
105 +
106 + assert.NotPanics(t, pf.Cleanup)
107 + })
108 + }
109 +}
110 +
111 +func TestPostfix_Charts(t *testing.T) {
112 + assert.NotNil(t, New().Charts())
113 +}
114 +
115 +func TestPostfix_Check(t *testing.T) {
116 + tests := map[string]struct {
117 + prepareMock func() *mockPostqueueExec
118 + wantFail bool
119 + }{
120 + "success case": {
121 + prepareMock: prepareMockOK,
122 + wantFail: false,
123 + },
124 + "error on list call": {
125 + prepareMock: prepareMockErrOnList,
126 + wantFail: true,
127 + },
128 + "empty response": {
129 + prepareMock: prepareMockEmptyResponse,
130 + wantFail: true,
131 + },
132 + "unexpected response": {
133 + prepareMock: prepareMockUnexpectedResponse,
134 + wantFail: true,
135 + },
136 + }
137 +
138 + for name, test := range tests {
139 + t.Run(name, func(t *testing.T) {
140 + pf := New()
141 + mock := test.prepareMock()
142 + pf.exec = mock
143 +
144 + if test.wantFail {
145 + assert.Error(t, pf.Check())
146 + } else {
147 + assert.NoError(t, pf.Check())
148 + }
149 + })
150 + }
151 +}
152 +
153 +func TestPostfix_Collect(t *testing.T) {
154 + tests := map[string]struct {
155 + prepareMock func() *mockPostqueueExec
156 + wantMetrics map[string]int64
157 + }{
158 + "success case": {
159 + prepareMock: prepareMockOK,
160 + wantMetrics: map[string]int64{
161 + "qemails": 12991,
162 + "qsize": 132422,
163 + },
164 + },
165 + "error on list call": {
166 + prepareMock: prepareMockErrOnList,
167 + wantMetrics: nil,
168 + },
169 + "empty response": {
170 + prepareMock: prepareMockEmptyResponse,
171 + wantMetrics: nil,
172 + },
173 + "unexpected response": {
174 + prepareMock: prepareMockUnexpectedResponse,
175 + wantMetrics: nil,
176 + },
177 + }
178 +
179 + for name, test := range tests {
180 + t.Run(name, func(t *testing.T) {
181 + pf := New()
182 + mock := test.prepareMock()
183 + pf.exec = mock
184 +
185 + mx := pf.Collect()
186 +
187 + assert.Equal(t, test.wantMetrics, mx)
188 +
189 + fmt.Println(assert.Equal(t, test.wantMetrics, mx))
190 +
191 + if len(test.wantMetrics) > 0 {
192 + assert.Len(t, *pf.Charts(), len(postfixChartsTmpl))
193 + }
194 + })
195 + }
196 +}
197 +
198 +func prepareMockOK() *mockPostqueueExec {
199 + return &mockPostqueueExec{
200 + listData: dataPostqueue,
201 + }
202 +}
203 +
204 +func prepareMockErrOnList() *mockPostqueueExec {
205 + return &mockPostqueueExec{
206 + errOnList: true,
207 + }
208 +}
209 +
210 +func prepareMockEmptyResponse() *mockPostqueueExec {
211 + return &mockPostqueueExec{}
212 +}
213 +
214 +func prepareMockUnexpectedResponse() *mockPostqueueExec {
215 + return &mockPostqueueExec{
216 + listData: []byte(`
217 +Lorem ipsum dolor sit amet, consectetur adipiscing elit.
218 +Nulla malesuada erat id magna mattis, eu viverra tellus rhoncus.
219 +Fusce et felis pulvinar, posuere sem non, porttitor eros.
220 +`),
221 + }
222 +}
223 +
224 +type mockPostqueueExec struct {
225 + errOnList bool
226 + listData []byte
227 +}
228 +
229 +func (m *mockPostqueueExec) list() ([]byte, error) {
230 + if m.errOnList {
231 + return nil, errors.New("mock.list() error")
232 + }
233 +
234 + return m.listData, nil
235 +}
src/go/collectors/go.d.plugin/modules/postfix/testdata/config.json new
+5
@@ -0,0 +1,5 @@
1 +{
2 + "update_every": 123,
3 + "timeout": 123.123,
4 + "binary_path": "/usr/sbin/postqueue"
5 +}
\ No newline at end of file
src/go/collectors/go.d.plugin/modules/postfix/testdata/config.yaml new
+3
@@ -0,0 +1,3 @@
1 +update_every: 123
2 +timeout: 123.123
3 +binary_path: "/usr/sbin/postqueue"
src/go/collectors/go.d.plugin/modules/postfix/testdata/postqueue.txt new
+34
@@ -0,0 +1,34 @@
1 +1FC3A100A1FF* 10438 Wed Jun 26 13:39:27 root@localhost.test
2 + fotis@localhost.test
3 +
4 +D4BBA10097DF* 10438 Wed Jun 26 13:39:25 root@localhost.test
5 + fotis@localhost.test
6 +
7 +078D8100A90D* 10438 Wed Jun 26 13:39:28 root@localhost.test
8 + fotis@localhost.test
9 +
10 +A23BB100961F* 10438 Wed Jun 26 13:39:25 root@localhost.test
11 + fotis@localhost.test
12 +
13 +CCF1D1009798* 10438 Wed Jun 26 13:39:25 root@localhost.test
14 + fotis@localhost.test
15 +
16 +58897100885C* 10438 Wed Jun 26 13:39:24 root@localhost.test
17 + fotis@localhost.test
18 +
19 +F1A951003C07* 10438 Wed Jun 26 13:39:23 root@localhost.test
20 + fotis@localhost.test
21 +
22 +3A24A1003239* 10438 Wed Jun 26 13:39:23 root@localhost.test
23 + fotis@localhost.test
24 +
25 +CAF5E1009FCC* 10438 Wed Jun 26 13:39:26 root@localhost.test
26 + fotis@localhost.test
27 +
28 +752741009D2A* 10438 Wed Jun 26 13:39:26 root@localhost.test
29 + fotis@localhost.test
30 +
31 +6B5FA10033D4* 10438 Wed Jun 26 13:39:23 root@localhost.test
32 + fotis@localhost.test
33 +
34 +-- 132422 Kbytes in 12991 Requests.