master
go 179 lines 4.65 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package snmpsd
4
5 import (
6 "context"
7 "errors"
8 "sort"
9 "sync"
10 "testing"
11 "time"
12
13 "github.com/golang/mock/gomock"
14 "github.com/gosnmp/gosnmp"
15 snmpmock "github.com/gosnmp/gosnmp/mocks"
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
18
19 "github.com/netdata/netdata/go/plugins/plugin/agent/discovery/sd/model"
20 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/snmputils"
21 )
22
23 type discoverySim struct {
24 cfg Config
25 updateSnmpHandler func(m *mockSnmpHandler)
26 wantGroups []model.TargetGroup
27 }
28
29 func (sim *discoverySim) run(t *testing.T) {
30 d, err := NewDiscoverer(sim.cfg)
31 require.NoError(t, err)
32
33 d.newSnmpClient = func() (gosnmp.Handler, func()) {
34 h, cleanup := prepareMockSnmpHandler(t)
35 h.setExpectInit()
36 h.setExpectSysInfo()
37 if sim.updateSnmpHandler != nil {
38 sim.updateSnmpHandler(h)
39 }
40 return h, cleanup
41 }
42
43 seen := make(map[string]model.TargetGroup)
44 ctx, cancel := context.WithCancel(context.Background())
45 in := make(chan []model.TargetGroup)
46 var wg sync.WaitGroup
47
48 wg.Go(func() {
49 d.Discover(ctx, in)
50 })
51
52 wg.Go(func() {
53 for {
54 select {
55 case <-ctx.Done():
56 return
57 case tggs := <-in:
58 for _, tgg := range tggs {
59 seen[tgg.Source()] = tgg
60 }
61 }
62 }
63 })
64
65 done := make(chan struct{})
66 go func() {
67 defer close(done)
68 wg.Wait()
69 }()
70
71 select {
72 case <-d.started:
73 case <-time.After(time.Second * 5):
74 require.Fail(t, "discovery failed to start")
75 }
76
77 time.Sleep(time.Second * 2)
78
79 cancel()
80
81 select {
82 case <-done:
83 case <-time.After(time.Second * 5):
84 require.Fail(t, "discovery hasn't finished after cancel")
85 }
86
87 var tggs []model.TargetGroup
88 for _, tgg := range seen {
89 tggs = append(tggs, tgg)
90 }
91
92 sortTargetGroups(tggs)
93 sortTargetGroups(sim.wantGroups)
94
95 wantLen, gotLen := calcTargets(sim.wantGroups), calcTargets(tggs)
96 assert.Equalf(t, wantLen, gotLen, "different len (want %d got %d)", wantLen, gotLen)
97 assert.Equal(t, sim.wantGroups, tggs)
98 }
99
100 func calcTargets(tggs []model.TargetGroup) int {
101 var n int
102 for _, tgg := range tggs {
103 n += len(tgg.Targets())
104 }
105 return n
106 }
107
108 func sortTargetGroups(tggs []model.TargetGroup) {
109 if len(tggs) == 0 {
110 return
111 }
112 sort.Slice(tggs, func(i, j int) bool { return tggs[i].Source() < tggs[j].Source() })
113
114 for idx := range tggs {
115 tgts := tggs[idx].Targets()
116 sort.Slice(tgts, func(i, j int) bool { return tgts[i].Hash() < tgts[j].Hash() })
117 }
118 }
119
120 type mockSnmpHandler struct {
121 *snmpmock.MockHandler
122 skipOnConnect func(ip string) bool
123 }
124
125 func (m *mockSnmpHandler) Connect() error {
126 if m.skipOnConnect != nil && m.skipOnConnect(m.MockHandler.Target()) {
127 return errors.New("mock handler skip connect")
128 }
129 return m.MockHandler.Connect()
130 }
131
132 func prepareMockSnmpHandler(t *testing.T) (*mockSnmpHandler, func()) {
133 mockCtl := gomock.NewController(t)
134 cleanup := func() { mockCtl.Finish() }
135 mockSNMP := snmpmock.NewMockHandler(mockCtl)
136 m := &mockSnmpHandler{MockHandler: mockSNMP}
137
138 return m, cleanup
139 }
140
141 func (m *mockSnmpHandler) setExpectInit() {
142 var ip string
143 m.EXPECT().Target().DoAndReturn(func() string { return ip }).AnyTimes()
144 m.EXPECT().SetTarget(gomock.Any()).Do(func(target string) { ip = target }).AnyTimes()
145 m.EXPECT().Port().AnyTimes()
146 m.EXPECT().Version().AnyTimes()
147 m.EXPECT().Community().AnyTimes()
148 m.EXPECT().SetPort(gomock.Any()).AnyTimes()
149 m.EXPECT().SetRetries(gomock.Any()).AnyTimes()
150 m.EXPECT().SetMaxRepetitions(gomock.Any()).AnyTimes()
151 m.EXPECT().SetMaxOids(gomock.Any()).AnyTimes()
152 m.EXPECT().SetLogger(gomock.Any()).AnyTimes()
153 m.EXPECT().SetTimeout(gomock.Any()).AnyTimes()
154 m.EXPECT().SetCommunity(gomock.Any()).AnyTimes()
155 m.EXPECT().SetVersion(gomock.Any()).AnyTimes()
156 m.EXPECT().SetSecurityModel(gomock.Any()).AnyTimes()
157 m.EXPECT().SetMsgFlags(gomock.Any()).AnyTimes()
158 m.EXPECT().SetSecurityParameters(gomock.Any()).AnyTimes()
159 m.EXPECT().Connect().Return(nil).AnyTimes()
160 m.EXPECT().Close().Return(nil).AnyTimes()
161 }
162
163 const (
164 mockSysDescr = "mock sysDescr"
165 mockSysObject = ".1.3.6.1.4.1.8072.3.2.10"
166 mockSysContact = "mock sysContact"
167 mockSysName = "mock sysName"
168 mockSysLocation = "mock sysLocation"
169 )
170
171 func (m *mockSnmpHandler) setExpectSysInfo() {
172 m.EXPECT().WalkAll(snmputils.RootOidMibSystem).Return([]gosnmp.SnmpPDU{
173 {Name: snmputils.OidSysDescr, Value: []uint8(mockSysDescr), Type: gosnmp.OctetString},
174 {Name: snmputils.OidSysObject, Value: mockSysObject, Type: gosnmp.ObjectIdentifier},
175 {Name: snmputils.OidSysContact, Value: []uint8(mockSysContact), Type: gosnmp.OctetString},
176 {Name: snmputils.OidSysName, Value: []uint8(mockSysName), Type: gosnmp.OctetString},
177 {Name: snmputils.OidSysLocation, Value: []uint8(mockSysLocation), Type: gosnmp.OctetString},
178 }, nil).AnyTimes()
179 }