add go.d/nginxunit (#18554)
Ilya Mashchenko committed
Sep 15, 2024 at 01:13 UTC
a13fa5086c35ba633090943d8110a034bb392eda
15 files changed
+945
src/go/plugin/go.d/README.md
+1
@@ -107,6 +107,7 @@ see the appropriate collector readme.
107
| [mysql](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/mysql) | MySQL |
108
| [nginx](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/nginx) | NGINX |
109
| [nginxplus](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/nginxplus) | NGINX Plus |
110
+| [nginxunit](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/nginxunit) | NGINX Unit |
111
| [nginxvts](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/nginxvts) | NGINX VTS |
112
| [nsd](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/nsd) | NSD (NLnet Labs) |
113
| [ntpd](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/ntpd) | NTP daemon |
src/go/plugin/go.d/config/go.d.conf
+1
@@ -71,6 +71,7 @@ modules:
71
# mysql: yes
72
# nginx: yes
73
# nginxplus: yes
74
+# nginxunit: yes
75
# nginxvts: yes
76
# nsd: yes
77
# ntpd: yes
src/go/plugin/go.d/config/go.d/nginxunit.conf
new
+6
@@ -0,0 +1,6 @@
1
+## All available configuration options, their descriptions and default values:
2
+## https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/nginxunit#readme
3
+
4
+#jobs:
5
+# - name: local
6
+# url: http://127.0.0.1:8000
src/go/plugin/go.d/config/go.d/sd/docker.conf
+7
@@ -58,6 +58,8 @@ classify:
58
expr: '{{ or (eq .PrivatePort "3306") (match "sp" .Image "mysql mysql:* */mysql */mysql:* mariadb mariadb:* */mariadb */mariadb:* percona percona:* */percona-mysql */percona-mysql:*") }}'
59
- tags: "nginx"
60
expr: '{{ match "sp" .Image "nginx nginx:*" }}'
61
+ - tags: "nginxunit"
62
+ expr: '{{ match "sp" .Image "nginx/unit nginx/unit:*" }}'
63
- tags: "pgbouncer"
64
expr: '{{ or (eq .PrivatePort "6432") (match "sp" .Image "*/pgbouncer */pgbouncer:*") }}'
65
- tags: "pika"
@@ -200,6 +202,11 @@ compose:
202
- module: nginx
203
name: docker_{{.Name}}
204
url: http://{{.Address}}/status
205
+ - selector: "nginxunit"
206
+ template: |
207
+ - module: nginxunit
208
+ name: docker_{{.Name}}
209
+ url: http://{{.Address}}
210
- selector: "pgbouncer"
211
template: |
212
module: pgbouncer
src/go/plugin/go.d/config/go.d/sd/net_listeners.conf
+7
@@ -90,6 +90,8 @@ classify:
90
expr: '{{ or (eq .Port "3306") (eq .Comm "mysqld" "mariadbd") }}'
91
- tags: "nginx"
92
expr: '{{ and (eq .Port "80" "8080") (eq .Comm "nginx") }}'
93
+ - tags: "nginxunit"
94
+ expr: '{{ and (eq .Port "8000") (eq .Comm "unit") }}'
95
- tags: "ntpd"
96
expr: '{{ or (eq .Port "123") (eq .Comm "ntpd") }}'
97
- tags: "openvpn"
@@ -385,6 +387,11 @@ compose:
387
- module: nginx
388
name: local
389
url: http://{{.Address}}/status
390
+ - selector: "nginxunit"
391
+ template: |
392
+ - module: nginxunit
393
+ name: local
394
+ url: http://{{.Address}}
395
- selector: "ntpd"
396
template: |
397
module: ntpd
src/go/plugin/go.d/modules/init.go
+1
@@ -62,6 +62,7 @@ import (
62
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/mysql"
63
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/nginx"
64
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/nginxplus"
65
+ _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/nginxunit"
66
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/nginxvts"
67
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/nsd"
68
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/ntpd"
src/go/plugin/go.d/modules/nginxunit/charts.go
new
+59
@@ -0,0 +1,59 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package nginxunit
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
7
+)
8
+
9
+const (
10
+ prioRequestsRate = module.Priority + iota
11
+ prioConnectionsRate
12
+ prioConnectionsCurrent
13
+)
14
+
15
+var charts = module.Charts{
16
+ requestsRateChart.Copy(),
17
+ connectionsRateChart.Copy(),
18
+ connectionsCurrentChart.Copy(),
19
+}
20
+
21
+var requestsRateChart = module.Chart{
22
+ ID: "requests",
23
+ Title: "Requests",
24
+ Units: "requests/s",
25
+ Fam: "requests",
26
+ Ctx: "nginxunit.requests_rate",
27
+ Priority: prioRequestsRate,
28
+ Dims: module.Dims{
29
+ {ID: "requests_total", Name: "requests", Algo: module.Incremental},
30
+ },
31
+}
32
+
33
+var connectionsRateChart = module.Chart{
34
+ ID: "connections_rate",
35
+ Title: "Connections",
36
+ Units: "connections/s",
37
+ Fam: "connections",
38
+ Ctx: "nginxunit.connections_rate",
39
+ Priority: prioConnectionsRate,
40
+ Type: module.Stacked,
41
+ Dims: module.Dims{
42
+ {ID: "connections_accepted", Name: "accepted", Algo: module.Incremental},
43
+ {ID: "connections_closed", Name: "closed", Algo: module.Incremental},
44
+ },
45
+}
46
+
47
+var connectionsCurrentChart = module.Chart{
48
+ ID: "connections_current",
49
+ Title: "Current Connections",
50
+ Units: "connections",
51
+ Fam: "connections",
52
+ Ctx: "nginxunit.connections_current",
53
+ Priority: prioConnectionsCurrent,
54
+ Type: module.Stacked,
55
+ Dims: module.Dims{
56
+ {ID: "connections_active", Name: "active"},
57
+ {ID: "connections_idle", Name: "idle"},
58
+ },
59
+}
src/go/plugin/go.d/modules/nginxunit/collect.go
new
+59
@@ -0,0 +1,59 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package nginxunit
4
+
5
+import (
6
+ "encoding/json"
7
+ "errors"
8
+ "fmt"
9
+ "net/http"
10
+
11
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
12
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
13
+)
14
+
15
+const (
16
+ urlPathStatus = "/status"
17
+)
18
+
19
+// https://unit.nginx.org/statusapi/
20
+type nuStatus struct {
21
+ Connections *struct {
22
+ Accepted int64 `json:"accepted" stm:"accepted"`
23
+ Active int64 `json:"active" stm:"active"`
24
+ Idle int64 `json:"idle" stm:"idle"`
25
+ Closed int64 `json:"closed" stm:"closed"`
26
+ } `json:"connections" stm:"connections"`
27
+ Requests struct {
28
+ Total int64 `json:"total" stm:"total"`
29
+ } `json:"requests" stm:"requests"`
30
+}
31
+
32
+func (n *NginxUnit) collect() (map[string]int64, error) {
33
+ req, err := web.NewHTTPRequestWithPath(n.RequestConfig, urlPathStatus)
34
+ if err != nil {
35
+ return nil, fmt.Errorf("failed to create HTTP request to '%s': %v", n.URL, err)
36
+ }
37
+
38
+ var status nuStatus
39
+
40
+ wc := web.DoHTTP(n.httpClient).OnNokCode(func(resp *http.Response) (bool, error) {
41
+ var msg struct {
42
+ Error string `json:"error"`
43
+ }
44
+ if json.NewDecoder(resp.Body).Decode(&msg) == nil && msg.Error != "" {
45
+ return false, errors.New(msg.Error)
46
+ }
47
+ return false, nil
48
+ })
49
+
50
+ if err := wc.RequestJSON(req, &status); err != nil {
51
+ return nil, err
52
+ }
53
+
54
+ if status.Connections == nil {
55
+ return nil, errors.New("unexpected response: no connections available")
56
+ }
57
+
58
+ return stm.ToMap(status), nil
59
+}
src/go/plugin/go.d/modules/nginxunit/config_schema.json
new
+183
@@ -0,0 +1,183 @@
1
+{
2
+ "jsonSchema": {
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "NGINX Unit 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": 1
13
+ },
14
+ "url": {
15
+ "title": "URL",
16
+ "description": "The URL of the NGINX Unit.",
17
+ "type": "string",
18
+ "default": "http://127.0.0.1:8000",
19
+ "format": "uri"
20
+ },
21
+ "timeout": {
22
+ "title": "Timeout",
23
+ "description": "The timeout in seconds for the HTTP request.",
24
+ "type": "number",
25
+ "minimum": 0.5,
26
+ "default": 1
27
+ },
28
+ "not_follow_redirects": {
29
+ "title": "Not follow redirects",
30
+ "description": "If set, the client will not follow HTTP redirects automatically.",
31
+ "type": "boolean"
32
+ },
33
+ "username": {
34
+ "title": "Username",
35
+ "description": "The username for basic authentication.",
36
+ "type": "string",
37
+ "sensitive": true
38
+ },
39
+ "password": {
40
+ "title": "Password",
41
+ "description": "The password for basic authentication.",
42
+ "type": "string",
43
+ "sensitive": true
44
+ },
45
+ "proxy_url": {
46
+ "title": "Proxy URL",
47
+ "description": "The URL of the proxy server.",
48
+ "type": "string"
49
+ },
50
+ "proxy_username": {
51
+ "title": "Proxy username",
52
+ "description": "The username for proxy authentication.",
53
+ "type": "string",
54
+ "sensitive": true
55
+ },
56
+ "proxy_password": {
57
+ "title": "Proxy password",
58
+ "description": "The password for proxy authentication.",
59
+ "type": "string",
60
+ "sensitive": true
61
+ },
62
+ "headers": {
63
+ "title": "Headers",
64
+ "description": "Additional HTTP headers to include in the request.",
65
+ "type": [
66
+ "object",
67
+ "null"
68
+ ],
69
+ "additionalProperties": {
70
+ "type": "string"
71
+ }
72
+ },
73
+ "tls_skip_verify": {
74
+ "title": "Skip TLS verification",
75
+ "description": "If set, TLS certificate verification will be skipped.",
76
+ "type": "boolean"
77
+ },
78
+ "tls_ca": {
79
+ "title": "TLS CA",
80
+ "description": "The path to the CA certificate file for TLS verification.",
81
+ "type": "string",
82
+ "pattern": "^$|^/"
83
+ },
84
+ "tls_cert": {
85
+ "title": "TLS certificate",
86
+ "description": "The path to the client certificate file for TLS authentication.",
87
+ "type": "string",
88
+ "pattern": "^$|^/"
89
+ },
90
+ "tls_key": {
91
+ "title": "TLS key",
92
+ "description": "The path to the client key file for TLS authentication.",
93
+ "type": "string",
94
+ "pattern": "^$|^/"
95
+ },
96
+ "body": {
97
+ "title": "Body",
98
+ "type": "string"
99
+ },
100
+ "method": {
101
+ "title": "Method",
102
+ "type": "string"
103
+ }
104
+ },
105
+ "required": [
106
+ "url"
107
+ ],
108
+ "additionalProperties": false,
109
+ "patternProperties": {
110
+ "^name$": {}
111
+ }
112
+ },
113
+ "uiSchema": {
114
+ "ui:flavour": "tabs",
115
+ "ui:options": {
116
+ "tabs": [
117
+ {
118
+ "title": "Base",
119
+ "fields": [
120
+ "update_every",
121
+ "url",
122
+ "timeout",
123
+ "not_follow_redirects"
124
+ ]
125
+ },
126
+ {
127
+ "title": "Auth",
128
+ "fields": [
129
+ "username",
130
+ "password"
131
+ ]
132
+ },
133
+ {
134
+ "title": "TLS",
135
+ "fields": [
136
+ "tls_skip_verify",
137
+ "tls_ca",
138
+ "tls_cert",
139
+ "tls_key"
140
+ ]
141
+ },
142
+ {
143
+ "title": "Proxy",
144
+ "fields": [
145
+ "proxy_url",
146
+ "proxy_username",
147
+ "proxy_password"
148
+ ]
149
+ },
150
+ {
151
+ "title": "Headers",
152
+ "fields": [
153
+ "headers"
154
+ ]
155
+ }
156
+ ]
157
+ },
158
+ "uiOptions": {
159
+ "fullPage": true
160
+ },
161
+ "body": {
162
+ "ui:widget": "hidden"
163
+ },
164
+ "method": {
165
+ "ui:widget": "hidden"
166
+ },
167
+ "timeout": {
168
+ "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
169
+ },
170
+ "username": {
171
+ "ui:widget": "password"
172
+ },
173
+ "proxy_username": {
174
+ "ui:widget": "password"
175
+ },
176
+ "password": {
177
+ "ui:widget": "password"
178
+ },
179
+ "proxy_password": {
180
+ "ui:widget": "password"
181
+ }
182
+ }
183
+}
src/go/plugin/go.d/modules/nginxunit/metadata.yaml
new
+207
@@ -0,0 +1,207 @@
1
+plugin_name: go.d.plugin
2
+modules:
3
+ - meta:
4
+ id: collector-go.d.plugin-nginxunit
5
+ plugin_name: go.d.plugin
6
+ module_name: nginxunit
7
+ monitored_instance:
8
+ name: NGINX Unit
9
+ link: https://unit.nginx.org/
10
+ categories:
11
+ - data-collection.web-servers-and-web-proxies
12
+ icon_filename: nginx.svg
13
+ related_resources:
14
+ integrations:
15
+ list: []
16
+ alternative_monitored_instances: []
17
+ info_provided_to_referring_integrations:
18
+ description: ""
19
+ keywords:
20
+ - nginx
21
+ - unit
22
+ - web
23
+ - appserver
24
+ - http
25
+ most_popular: true
26
+ overview:
27
+ data_collection:
28
+ metrics_description: |
29
+ This collector monitors the activity and performance of NGINX Unit servers, and collects metrics such as the number of connections, their status, and client requests.
30
+ method_description: |
31
+ It sends HTTP requests to the NGINX Unit [Status API](https://unit.nginx.org/statusapi/).
32
+ default_behavior:
33
+ auto_detection:
34
+ description: |
35
+ The collector can automatically detect NGINX Unit instances running on:
36
+
37
+ - localhost that are listening on port 8000
38
+ - within Docker containers
39
+ limits:
40
+ description: ""
41
+ performance_impact:
42
+ description: ""
43
+ additional_permissions:
44
+ description: ""
45
+ multi_instance: true
46
+ supported_platforms:
47
+ include: []
48
+ exclude: []
49
+ setup:
50
+ prerequisites:
51
+ list:
52
+ - title: Enable HTTP Control API
53
+ description: |
54
+ See [Control API]([HTTP Control API](https://unit.nginx.org/controlapi/#configuration-api)) documentation.
55
+ configuration:
56
+ file:
57
+ name: go.d/nginxunit.conf
58
+ options:
59
+ description: |
60
+ The following options can be defined globally: update_every, autodetection_retry.
61
+ folding:
62
+ title: Config options
63
+ enabled: true
64
+ list:
65
+ - name: update_every
66
+ description: Data collection frequency.
67
+ default_value: 1
68
+ required: false
69
+ - name: autodetection_retry
70
+ description: Recheck interval in seconds. Zero means no recheck will be scheduled.
71
+ default_value: 0
72
+ required: false
73
+ - name: url
74
+ description: NGINX Unit URL.
75
+ default_value: http://127.0.0.1:8000
76
+ required: true
77
+ - name: timeout
78
+ description: HTTP request timeout.
79
+ default_value: 1
80
+ required: false
81
+ - name: username
82
+ description: Username for basic HTTP authentication.
83
+ default_value: ""
84
+ required: false
85
+ - name: password
86
+ description: Password for basic HTTP authentication.
87
+ default_value: ""
88
+ required: false
89
+ - name: proxy_url
90
+ description: Proxy URL.
91
+ default_value: ""
92
+ required: false
93
+ - name: proxy_username
94
+ description: Username for proxy basic HTTP authentication.
95
+ default_value: ""
96
+ required: false
97
+ - name: proxy_password
98
+ description: Password for proxy basic HTTP authentication.
99
+ default_value: ""
100
+ required: false
101
+ - name: method
102
+ description: HTTP request method.
103
+ default_value: GET
104
+ required: false
105
+ - name: body
106
+ description: HTTP request body.
107
+ default_value: ""
108
+ required: false
109
+ - name: headers
110
+ description: HTTP request headers.
111
+ default_value: ""
112
+ required: false
113
+ - name: not_follow_redirects
114
+ description: Redirect handling policy. Controls whether the client follows redirects.
115
+ default_value: false
116
+ required: false
117
+ - name: tls_skip_verify
118
+ description: Server certificate chain and hostname validation policy. Controls whether the client performs this check.
119
+ default_value: false
120
+ required: false
121
+ - name: tls_ca
122
+ description: Certification authority that the client uses when verifying the server's certificates.
123
+ default_value: ""
124
+ required: false
125
+ - name: tls_cert
126
+ description: Client TLS certificate.
127
+ default_value: ""
128
+ required: false
129
+ - name: tls_key
130
+ description: Client TLS key.
131
+ default_value: ""
132
+ required: false
133
+ examples:
134
+ folding:
135
+ title: Config
136
+ enabled: true
137
+ list:
138
+ - name: Basic
139
+ description: A basic example configuration.
140
+ folding:
141
+ enabled: false
142
+ config: |
143
+ jobs:
144
+ - name: local
145
+ url: http://127.0.0.1:8000
146
+ - name: HTTP authentication
147
+ description: Basic HTTP authentication.
148
+ config: |
149
+ jobs:
150
+ - name: local
151
+ url: http://127.0.0.1::8000
152
+ username: username
153
+ password: password
154
+ - name: HTTPS with self-signed certificate
155
+ description: NGINX Unit with enabled HTTPS and self-signed certificate.
156
+ config: |
157
+ jobs:
158
+ - name: local
159
+ url: http://127.0.0.1:8000
160
+ tls_skip_verify: yes
161
+ - name: Multi-instance
162
+ description: |
163
+ > **Note**: When you define multiple jobs, their names must be unique.
164
+
165
+ Collecting metrics from local and remote instances.
166
+ config: |
167
+ jobs:
168
+ - name: local
169
+ url: http://127.0.0.1:8000
170
+
171
+ - name: remote
172
+ url: http://192.0.2.1:8000
173
+ troubleshooting:
174
+ problems:
175
+ list: []
176
+ alerts: []
177
+ metrics:
178
+ folding:
179
+ title: Metrics
180
+ enabled: false
181
+ description: ""
182
+ availability: []
183
+ scopes:
184
+ - name: global
185
+ description: These metrics refer to the entire monitored application.
186
+ labels: []
187
+ metrics:
188
+ - name: nginxunit.requests_rate
189
+ description: Requests
190
+ unit: requests/s
191
+ chart_type: line
192
+ dimensions:
193
+ - name: requests
194
+ - name: nginxunit.connections_rate
195
+ description: Connections
196
+ unit: connections/s
197
+ chart_type: stacked
198
+ dimensions:
199
+ - name: accepted
200
+ - name: closed
201
+ - name: nginxunit.connections_current
202
+ description: Current Connections
203
+ unit: connections
204
+ chart_type: stacked
205
+ dimensions:
206
+ - name: active
207
+ - name: idle
src/go/plugin/go.d/modules/nginxunit/nginxunit.go
new
+111
@@ -0,0 +1,111 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package nginxunit
4
+
5
+import (
6
+ _ "embed"
7
+ "errors"
8
+ "fmt"
9
+ "net/http"
10
+ "time"
11
+
12
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/confopt"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
15
+)
16
+
17
+//go:embed "config_schema.json"
18
+var configSchema string
19
+
20
+func init() {
21
+ module.Register("nginxunit", module.Creator{
22
+ JobConfigSchema: configSchema,
23
+ Create: func() module.Module { return New() },
24
+ Config: func() any { return &Config{} },
25
+ })
26
+}
27
+
28
+func New() *NginxUnit {
29
+ return &NginxUnit{
30
+ Config: Config{
31
+ HTTPConfig: web.HTTPConfig{
32
+ RequestConfig: web.RequestConfig{
33
+ URL: "http://127.0.0.1:8000",
34
+ },
35
+ ClientConfig: web.ClientConfig{
36
+ Timeout: confopt.Duration(time.Second * 1),
37
+ },
38
+ },
39
+ },
40
+ charts: charts.Copy(),
41
+ }
42
+}
43
+
44
+type Config struct {
45
+ UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
46
+ web.HTTPConfig `yaml:",inline" json:""`
47
+}
48
+
49
+type NginxUnit struct {
50
+ module.Base
51
+ Config `yaml:",inline" json:""`
52
+
53
+ charts *module.Charts
54
+
55
+ httpClient *http.Client
56
+}
57
+
58
+func (n *NginxUnit) Configuration() any {
59
+ return n.Config
60
+}
61
+
62
+func (n *NginxUnit) Init() error {
63
+ if n.URL == "" {
64
+ return errors.New("URL required but not set")
65
+ }
66
+
67
+ httpClient, err := web.NewHTTPClient(n.ClientConfig)
68
+ if err != nil {
69
+ return fmt.Errorf("failed initializing http client: %w", err)
70
+ }
71
+ n.httpClient = httpClient
72
+
73
+ n.Debugf("using URL %s", n.URL)
74
+ n.Debugf("using timeout: %s", n.Timeout)
75
+
76
+ return nil
77
+}
78
+
79
+func (n *NginxUnit) Check() error {
80
+ mx, err := n.collect()
81
+ if err != nil {
82
+ n.Error(err)
83
+ return err
84
+ }
85
+
86
+ if len(mx) == 0 {
87
+ return errors.New("no metrics collected")
88
+ }
89
+
90
+ return nil
91
+}
92
+
93
+func (n *NginxUnit) Charts() *module.Charts {
94
+ return n.charts
95
+}
96
+
97
+func (n *NginxUnit) Collect() map[string]int64 {
98
+ mx, err := n.collect()
99
+ if err != nil {
100
+ n.Error(err)
101
+ return nil
102
+ }
103
+
104
+ return mx
105
+}
106
+
107
+func (n *NginxUnit) Cleanup() {
108
+ if n.httpClient != nil {
109
+ n.httpClient.CloseIdleConnections()
110
+ }
111
+}
src/go/plugin/go.d/modules/nginxunit/nginxunit_test.go
new
+254
@@ -0,0 +1,254 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package nginxunit
4
+
5
+import (
6
+ "net/http"
7
+ "net/http/httptest"
8
+ "os"
9
+ "testing"
10
+
11
+ "github.com/stretchr/testify/assert"
12
+ "github.com/stretchr/testify/require"
13
+
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
15
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
16
+)
17
+
18
+var (
19
+ dataConfigJSON, _ = os.ReadFile("testdata/config.json")
20
+ dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
21
+
22
+ dataVer1291Status, _ = os.ReadFile("testdata/v1.29.1/status.json")
23
+)
24
+
25
+func Test_testDataIsValid(t *testing.T) {
26
+ for name, data := range map[string][]byte{
27
+ "dataConfigJSON": dataConfigJSON,
28
+ "dataConfigYAML": dataConfigYAML,
29
+ "dataVer1291Status": dataVer1291Status,
30
+ } {
31
+ require.NotNil(t, data, name)
32
+
33
+ }
34
+}
35
+
36
+func TestNginxUnit_ConfigurationSerialize(t *testing.T) {
37
+ module.TestConfigurationSerialize(t, &NginxUnit{}, dataConfigJSON, dataConfigYAML)
38
+}
39
+
40
+func TestNginxUnit_Init(t *testing.T) {
41
+ tests := map[string]struct {
42
+ wantFail bool
43
+ config Config
44
+ }{
45
+ "success with default": {
46
+ wantFail: false,
47
+ config: New().Config,
48
+ },
49
+ "fail when URL not set": {
50
+ wantFail: true,
51
+ config: Config{
52
+ HTTPConfig: web.HTTPConfig{
53
+ RequestConfig: web.RequestConfig{URL: ""},
54
+ },
55
+ },
56
+ },
57
+ }
58
+
59
+ for name, test := range tests {
60
+ t.Run(name, func(t *testing.T) {
61
+ nu := New()
62
+ nu.Config = test.config
63
+
64
+ if test.wantFail {
65
+ assert.Error(t, nu.Init())
66
+ } else {
67
+ assert.NoError(t, nu.Init())
68
+ }
69
+ })
70
+ }
71
+}
72
+
73
+func TestNginxUnit_Check(t *testing.T) {
74
+ tests := map[string]struct {
75
+ wantFail bool
76
+ prepare func(t *testing.T) (nu *NginxUnit, cleanup func())
77
+ }{
78
+ "success on valid response": {
79
+ wantFail: false,
80
+ prepare: caseOk,
81
+ },
82
+ "fail on unexpected JSON response": {
83
+ wantFail: true,
84
+ prepare: caseUnexpectedJsonResponse,
85
+ },
86
+ "fail on invalid data response": {
87
+ wantFail: true,
88
+ prepare: caseInvalidDataResponse,
89
+ },
90
+ "fail on connection refused": {
91
+ wantFail: true,
92
+ prepare: caseConnectionRefused,
93
+ },
94
+ "fail on 404 response": {
95
+ wantFail: true,
96
+ prepare: case404,
97
+ },
98
+ }
99
+
100
+ for name, test := range tests {
101
+ t.Run(name, func(t *testing.T) {
102
+ nu, cleanup := test.prepare(t)
103
+ defer cleanup()
104
+
105
+ if test.wantFail {
106
+ assert.Error(t, nu.Check())
107
+ } else {
108
+ assert.NoError(t, nu.Check())
109
+ }
110
+ })
111
+ }
112
+}
113
+
114
+func TestNginxUnit_Charts(t *testing.T) {
115
+ assert.NotNil(t, New().Charts())
116
+}
117
+
118
+func TestNginxUnit_Collect(t *testing.T) {
119
+ tests := map[string]struct {
120
+ prepare func(t *testing.T) (nu *NginxUnit, cleanup func())
121
+ wantNumOfCharts int
122
+ wantMetrics map[string]int64
123
+ }{
124
+ "success on valid response": {
125
+ prepare: caseOk,
126
+ wantNumOfCharts: len(charts),
127
+ wantMetrics: map[string]int64{
128
+ "connections_accepted": 1,
129
+ "connections_active": 1,
130
+ "connections_closed": 1,
131
+ "connections_idle": 1,
132
+ "requests_total": 1,
133
+ },
134
+ },
135
+ "fail on unexpected JSON response": {
136
+ prepare: caseUnexpectedJsonResponse,
137
+ wantMetrics: nil,
138
+ },
139
+ "fail on invalid data response": {
140
+ prepare: caseInvalidDataResponse,
141
+ wantMetrics: nil,
142
+ },
143
+ "fail on connection refused": {
144
+ prepare: caseConnectionRefused,
145
+ wantMetrics: nil,
146
+ },
147
+ "fail on 404 response": {
148
+ prepare: case404,
149
+ wantMetrics: nil,
150
+ },
151
+ }
152
+
153
+ for name, test := range tests {
154
+ t.Run(name, func(t *testing.T) {
155
+ nu, cleanup := test.prepare(t)
156
+ defer cleanup()
157
+
158
+ _ = nu.Check()
159
+
160
+ mx := nu.Collect()
161
+
162
+ require.Equal(t, test.wantMetrics, mx)
163
+
164
+ if len(test.wantMetrics) > 0 {
165
+ assert.Equal(t, test.wantNumOfCharts, len(*nu.Charts()), "want charnu")
166
+
167
+ module.TestMetricsHasAllChartsDims(t, nu.Charts(), mx)
168
+ }
169
+ })
170
+ }
171
+}
172
+
173
+func caseOk(t *testing.T) (*NginxUnit, func()) {
174
+ t.Helper()
175
+ srv := httptest.NewServer(http.HandlerFunc(
176
+ func(w http.ResponseWriter, r *http.Request) {
177
+ switch r.URL.Path {
178
+ case urlPathStatus:
179
+ _, _ = w.Write(dataVer1291Status)
180
+ default:
181
+ w.WriteHeader(http.StatusNotFound)
182
+ }
183
+ }))
184
+ nu := New()
185
+ nu.URL = srv.URL
186
+ require.NoError(t, nu.Init())
187
+
188
+ return nu, srv.Close
189
+}
190
+
191
+func caseUnexpectedJsonResponse(t *testing.T) (*NginxUnit, func()) {
192
+ t.Helper()
193
+ resp := `
194
+{
195
+ "elephant": {
196
+ "burn": false,
197
+ "mountain": true,
198
+ "fog": false,
199
+ "skin": -1561907625,
200
+ "burst": "anyway",
201
+ "shadow": 1558616893
202
+ },
203
+ "start": "ever",
204
+ "base": 2093056027,
205
+ "mission": -2007590351,
206
+ "victory": 999053756,
207
+ "die": false
208
+}
209
+`
210
+ srv := httptest.NewServer(http.HandlerFunc(
211
+ func(w http.ResponseWriter, r *http.Request) {
212
+ _, _ = w.Write([]byte(resp))
213
+ }))
214
+ nu := New()
215
+ nu.URL = srv.URL
216
+ require.NoError(t, nu.Init())
217
+
218
+ return nu, srv.Close
219
+}
220
+
221
+func caseInvalidDataResponse(t *testing.T) (*NginxUnit, func()) {
222
+ t.Helper()
223
+ srv := httptest.NewServer(http.HandlerFunc(
224
+ func(w http.ResponseWriter, r *http.Request) {
225
+ _, _ = w.Write([]byte("hello and\n goodbye"))
226
+ }))
227
+ nu := New()
228
+ nu.URL = srv.URL
229
+ require.NoError(t, nu.Init())
230
+
231
+ return nu, srv.Close
232
+}
233
+
234
+func caseConnectionRefused(t *testing.T) (*NginxUnit, func()) {
235
+ t.Helper()
236
+ nu := New()
237
+ nu.URL = "http://127.0.0.1:65001"
238
+ require.NoError(t, nu.Init())
239
+
240
+ return nu, func() {}
241
+}
242
+
243
+func case404(t *testing.T) (*NginxUnit, func()) {
244
+ t.Helper()
245
+ srv := httptest.NewServer(http.HandlerFunc(
246
+ func(w http.ResponseWriter, r *http.Request) {
247
+ w.WriteHeader(http.StatusNotFound)
248
+ }))
249
+ nu := New()
250
+ nu.URL = srv.URL
251
+ require.NoError(t, nu.Init())
252
+
253
+ return nu, srv.Close
254
+}
src/go/plugin/go.d/modules/nginxunit/testdata/config.json
new
+20
@@ -0,0 +1,20 @@
1
+{
2
+ "update_every": 123,
3
+ "url": "ok",
4
+ "body": "ok",
5
+ "method": "ok",
6
+ "headers": {
7
+ "ok": "ok"
8
+ },
9
+ "username": "ok",
10
+ "password": "ok",
11
+ "proxy_url": "ok",
12
+ "proxy_username": "ok",
13
+ "proxy_password": "ok",
14
+ "timeout": 123.123,
15
+ "not_follow_redirects": true,
16
+ "tls_ca": "ok",
17
+ "tls_cert": "ok",
18
+ "tls_key": "ok",
19
+ "tls_skip_verify": true
20
+}
src/go/plugin/go.d/modules/nginxunit/testdata/config.yaml
new
+17
@@ -0,0 +1,17 @@
1
+update_every: 123
2
+url: "ok"
3
+body: "ok"
4
+method: "ok"
5
+headers:
6
+ ok: "ok"
7
+username: "ok"
8
+password: "ok"
9
+proxy_url: "ok"
10
+proxy_username: "ok"
11
+proxy_password: "ok"
12
+timeout: 123.123
13
+not_follow_redirects: yes
14
+tls_ca: "ok"
15
+tls_cert: "ok"
16
+tls_key: "ok"
17
+tls_skip_verify: yes
src/go/plugin/go.d/modules/nginxunit/testdata/v1.29.1/status.json
new
+12
@@ -0,0 +1,12 @@
1
+{
2
+ "connections": {
3
+ "accepted": 1,
4
+ "active": 1,
5
+ "idle": 1,
6
+ "closed": 1
7
+ },
8
+ "requests": {
9
+ "total": 1
10
+ },
11
+ "applications": {}
12
+}