master
go 283 lines 6.8 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package snmpsd
4
5 import (
6 "strconv"
7 "strings"
8 "testing"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12
13 "github.com/netdata/netdata/go/plugins/plugin/agent/discovery/sd/model"
14 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/snmputils"
15 )
16
17 func TestNewDiscoverer(t *testing.T) {
18 tests := map[string]struct {
19 cfg Config
20 wantFail bool
21 }{
22 "succeeds with valid SNMPv1 config": {
23 wantFail: false,
24 cfg: Config{
25 Credentials: []CredentialConfig{
26 {Name: "v1cred", Version: "1", Community: "public"},
27 },
28 Networks: []NetworkConfig{
29 {Subnet: "192.0.2.0/24", Credential: "v1cred"},
30 },
31 },
32 },
33 "succeeds with valid SNMPv2c config": {
34 wantFail: false,
35 cfg: Config{
36 Credentials: []CredentialConfig{
37 {Name: "v2cred", Version: "2c", Community: "public"},
38 },
39 Networks: []NetworkConfig{
40 {Subnet: "192.0.2.0/24", Credential: "v2cred"},
41 },
42 },
43 },
44 "succeeds with valid SNMPv3 config": {
45 wantFail: false,
46 cfg: Config{
47 Credentials: []CredentialConfig{
48 {
49 Name: "v3cred",
50 Version: "3",
51 UserName: "user",
52 SecurityLevel: "authPriv",
53 AuthProtocol: "sha",
54 AuthPassphrase: "authpass",
55 PrivacyProtocol: "aes",
56 PrivacyPassphrase: "privpass",
57 },
58 },
59 Networks: []NetworkConfig{
60 {Subnet: "192.0.2.0/24", Credential: "v3cred"},
61 },
62 },
63 },
64 "succeeds with multiple valid credentials and networks": {
65 cfg: Config{
66 Credentials: []CredentialConfig{
67 {Name: "v1cred", Version: "1", Community: "public"},
68 {Name: "v2cred", Version: "2c", Community: "private"},
69 {
70 Name: "v3cred",
71 Version: "3",
72 UserName: "user",
73 SecurityLevel: "authPriv",
74 AuthProtocol: "sha",
75 AuthPassphrase: "authpass",
76 PrivacyProtocol: "aes",
77 PrivacyPassphrase: "privpass",
78 },
79 },
80 Networks: []NetworkConfig{
81 {Subnet: "192.0.2.0/24", Credential: "v1cred"},
82 {Subnet: "10.0.0.0/24", Credential: "v2cred"},
83 {Subnet: "172.16.0.0/24", Credential: "v3cred"},
84 },
85 },
86 wantFail: false,
87 },
88 "fails on empty config": {
89 wantFail: true,
90 },
91 "fails with credentials but no networks": {
92 wantFail: true,
93 cfg: Config{
94 Credentials: []CredentialConfig{
95 {Name: "test", Version: "2c", Community: "public"},
96 },
97 },
98 },
99 "fails with networks but no credentials": {
100 wantFail: true,
101 cfg: Config{
102 Networks: []NetworkConfig{
103 {Subnet: "192.0.2.0/24", Credential: "test"},
104 },
105 },
106 },
107 "fails with credential without name": {
108 wantFail: true,
109 cfg: Config{
110 Credentials: []CredentialConfig{
111 {Version: "2c", Community: "public"},
112 },
113 Networks: []NetworkConfig{
114 {Subnet: "192.0.2.0/24", Credential: "test"},
115 },
116 },
117 },
118 "fails with duplicate credential names": {
119 wantFail: true,
120 cfg: Config{
121 Credentials: []CredentialConfig{
122 {Name: "test", Version: "2c", Community: "public"},
123 {Name: "test", Version: "2c", Community: "private"},
124 },
125 Networks: []NetworkConfig{
126 {Subnet: "192.0.2.0/24", Credential: "test"},
127 },
128 },
129 },
130 "fails with network without subnet": {
131 wantFail: true,
132 cfg: Config{
133 Credentials: []CredentialConfig{
134 {Name: "test", Version: "2c", Community: "public"},
135 },
136 Networks: []NetworkConfig{
137 {Credential: "test"},
138 },
139 },
140 },
141 "fails with network without credential": {
142 wantFail: true,
143 cfg: Config{
144 Credentials: []CredentialConfig{
145 {Name: "test", Version: "2c", Community: "public"},
146 },
147 Networks: []NetworkConfig{
148 {Subnet: "192.0.2.0/24"},
149 },
150 },
151 },
152 "fails with network with nonexistent credential": {
153 wantFail: true,
154 cfg: Config{
155 Credentials: []CredentialConfig{
156 {Name: "test", Version: "2c", Community: "public"},
157 },
158 Networks: []NetworkConfig{
159 {Subnet: "192.0.2.0/24", Credential: "nonexistent"},
160 },
161 },
162 },
163 "fails with invalid subnet format": {
164 wantFail: true,
165 cfg: Config{
166 Credentials: []CredentialConfig{
167 {Name: "test", Version: "2c", Community: "public"},
168 },
169 Networks: []NetworkConfig{
170 {Subnet: "invalid-subnet", Credential: "test"},
171 },
172 },
173 },
174 "fails with subnet too large (> 512 IPs)": {
175 wantFail: true,
176 cfg: Config{
177 Credentials: []CredentialConfig{
178 {Name: "test", Version: "2c", Community: "public"},
179 },
180 Networks: []NetworkConfig{
181 {Subnet: "192.0.2.0/22", Credential: "test"}, // 1024 IPs
182 },
183 },
184 },
185 "fails with duplicate subnet": {
186 wantFail: true,
187 cfg: Config{
188 Credentials: []CredentialConfig{
189 {Name: "test", Version: "2c", Community: "public"},
190 },
191 Networks: []NetworkConfig{
192 {Subnet: "192.0.2.0/24", Credential: "test"},
193 {Subnet: "192.0.2.0/24", Credential: "test"},
194 },
195 },
196 },
197 }
198
199 for name, test := range tests {
200 t.Run(name, func(t *testing.T) {
201 d, err := NewDiscoverer(test.cfg)
202
203 if test.wantFail {
204 assert.Error(t, err)
205 } else {
206 assert.NoError(t, err)
207 assert.NotNil(t, d)
208 }
209 })
210 }
211 }
212
213 func TestDiscoverer_Run(t *testing.T) {
214 tests := map[string]struct {
215 prepareSim func(t *testing.T) *discoverySim
216 }{
217 "simple discovery": {
218 prepareSim: func(t *testing.T) *discoverySim {
219 cfg := Config{
220 Credentials: []CredentialConfig{
221 {Name: "public-v2", Version: "2", Community: "public-v2"},
222 },
223 Networks: []NetworkConfig{
224 {Subnet: "192.0.2.0/29", Credential: "public-v2"},
225 },
226 }
227
228 subnets, err := cfg.validateAndParse()
229 sub := subnets[0]
230 require.NoError(t, err)
231
232 sim := discoverySim{
233 cfg: cfg,
234 updateSnmpHandler: func(m *mockSnmpHandler) {
235 m.skipOnConnect = func(ip string) bool {
236 // Skip if the last octet is odd
237 i := strings.LastIndexByte(ip, '.')
238 if i == -1 {
239 return false
240 }
241 lastOctet, err := strconv.Atoi(ip[i+1:])
242 return err == nil && lastOctet%2 != 0
243 }
244 },
245 wantGroups: []model.TargetGroup{
246 prepareNewTargetGroup(sub, "192.0.2.2", "192.0.2.4", "192.0.2.6"),
247 },
248 }
249
250 return &sim
251 },
252 },
253 }
254
255 for name, test := range tests {
256 t.Run(name, func(t *testing.T) {
257 sim := test.prepareSim(t)
258 sim.run(t)
259 })
260 }
261 }
262
263 func prepareNewTargetGroup(sub subnet, ips ...string) *targetGroup {
264 tgg := newTargetGroup(sub)
265 for _, ip := range ips {
266 tg := prepareNewTarget(sub, ip)
267 tgg.addTarget(tg)
268 }
269 return tgg
270 }
271
272 func prepareNewTarget(sub subnet, ip string) *target {
273 return newTarget(ip, sub.credential, snmputils.SysInfo{
274 Descr: mockSysDescr,
275 Contact: mockSysContact,
276 Name: mockSysName,
277 Location: mockSysLocation,
278 SysObjectID: mockSysObject[1:],
279 Organization: "net-snmp",
280 Category: "Server",
281 Model: "Linux",
282 })
283 }