@cryptotaxi247 / netdata-1 / commits / a13fa5086

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 +}