diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 12:08:03 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 12:08:18 +0000 |
commit | 5da14042f70711ea5cf66e034699730335462f66 (patch) | |
tree | 0f6354ccac934ed87a2d555f45be4c831cf92f4a /src/go/collectors/go.d.plugin/modules/snmp/snmp_test.go | |
parent | Releasing debian version 1.44.3-2. (diff) | |
download | netdata-5da14042f70711ea5cf66e034699730335462f66.tar.xz netdata-5da14042f70711ea5cf66e034699730335462f66.zip |
Merging upstream version 1.45.3+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/go/collectors/go.d.plugin/modules/snmp/snmp_test.go')
-rw-r--r-- | src/go/collectors/go.d.plugin/modules/snmp/snmp_test.go | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/snmp/snmp_test.go b/src/go/collectors/go.d.plugin/modules/snmp/snmp_test.go new file mode 100644 index 000000000..04d9db3f9 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/snmp/snmp_test.go @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package snmp + +import ( + "errors" + "fmt" + "os" + "strings" + "testing" + + "github.com/golang/mock/gomock" + "github.com/gosnmp/gosnmp" + snmpmock "github.com/gosnmp/gosnmp/mocks" + "github.com/netdata/netdata/go/go.d.plugin/agent/module" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + dataConfigJSON, _ = os.ReadFile("testdata/config.json") + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml") +) + +func Test_testDataIsValid(t *testing.T) { + for name, data := range map[string][]byte{ + "dataConfigJSON": dataConfigJSON, + "dataConfigYAML": dataConfigYAML, + } { + require.NotNil(t, data, name) + } +} + +func TestSNMP_ConfigurationSerialize(t *testing.T) { + module.TestConfigurationSerialize(t, &SNMP{}, dataConfigJSON, dataConfigYAML) +} + +func TestSNMP_Init(t *testing.T) { + tests := map[string]struct { + prepareSNMP func() *SNMP + wantFail bool + }{ + "fail with default config": { + wantFail: true, + prepareSNMP: func() *SNMP { + return New() + }, + }, + "fail when 'charts' not set": { + wantFail: true, + prepareSNMP: func() *SNMP { + snmp := New() + snmp.Config = prepareV2Config() + snmp.ChartsInput = nil + return snmp + }, + }, + "fail when using SNMPv3 but 'user.name' not set": { + wantFail: true, + prepareSNMP: func() *SNMP { + snmp := New() + snmp.Config = prepareV3Config() + snmp.User.Name = "" + return snmp + }, + }, + "fail when using SNMPv3 but 'user.level' is invalid": { + wantFail: true, + prepareSNMP: func() *SNMP { + snmp := New() + snmp.Config = prepareV3Config() + snmp.User.SecurityLevel = "invalid" + return snmp + }, + }, + "fail when using SNMPv3 but 'user.auth_proto' is invalid": { + wantFail: true, + prepareSNMP: func() *SNMP { + snmp := New() + snmp.Config = prepareV3Config() + snmp.User.AuthProto = "invalid" + return snmp + }, + }, + "fail when using SNMPv3 but 'user.priv_proto' is invalid": { + wantFail: true, + prepareSNMP: func() *SNMP { + snmp := New() + snmp.Config = prepareV3Config() + snmp.User.PrivProto = "invalid" + return snmp + }, + }, + "success when using SNMPv1 with valid config": { + wantFail: false, + prepareSNMP: func() *SNMP { + snmp := New() + snmp.Config = prepareV1Config() + return snmp + }, + }, + "success when using SNMPv2 with valid config": { + wantFail: false, + prepareSNMP: func() *SNMP { + snmp := New() + snmp.Config = prepareV2Config() + return snmp + }, + }, + "success when using SNMPv3 with valid config": { + wantFail: false, + prepareSNMP: func() *SNMP { + snmp := New() + snmp.Config = prepareV3Config() + return snmp + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + snmp := test.prepareSNMP() + + if test.wantFail { + assert.Error(t, snmp.Init()) + } else { + assert.NoError(t, snmp.Init()) + } + }) + } +} + +func TestSNMP_Check(t *testing.T) { + tests := map[string]struct { + prepareSNMP func(m *snmpmock.MockHandler) *SNMP + wantFail bool + }{ + "success when 'max_request_size' > returned OIDs": { + wantFail: false, + prepareSNMP: func(m *snmpmock.MockHandler) *SNMP { + snmp := New() + snmp.Config = prepareV2Config() + + m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{ + Variables: []gosnmp.SnmpPDU{ + {Value: 10, Type: gosnmp.Gauge32}, + {Value: 20, Type: gosnmp.Gauge32}, + }, + }, nil).Times(1) + + return snmp + }, + }, + "success when 'max_request_size' < returned OIDs": { + wantFail: false, + prepareSNMP: func(m *snmpmock.MockHandler) *SNMP { + snmp := New() + snmp.Config = prepareV2Config() + snmp.Config.Options.MaxOIDs = 1 + + m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{ + Variables: []gosnmp.SnmpPDU{ + {Value: 10, Type: gosnmp.Gauge32}, + {Value: 20, Type: gosnmp.Gauge32}, + }, + }, nil).Times(2) + + return snmp + }, + }, + "success when using 'multiply_range'": { + wantFail: false, + prepareSNMP: func(m *snmpmock.MockHandler) *SNMP { + snmp := New() + snmp.Config = prepareConfigWithIndexRange(prepareV2Config, 0, 1) + + m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{ + Variables: []gosnmp.SnmpPDU{ + {Value: 10, Type: gosnmp.Gauge32}, + {Value: 20, Type: gosnmp.Gauge32}, + {Value: 30, Type: gosnmp.Gauge32}, + {Value: 40, Type: gosnmp.Gauge32}, + }, + }, nil).Times(1) + + return snmp + }, + }, + "fail when snmp client Get fails": { + wantFail: true, + prepareSNMP: func(m *snmpmock.MockHandler) *SNMP { + snmp := New() + snmp.Config = prepareV2Config() + + m.EXPECT().Get(gomock.Any()).Return(nil, errors.New("mock Get() error")).Times(1) + + return snmp + }, + }, + "fail when all OIDs type is unsupported": { + wantFail: true, + prepareSNMP: func(m *snmpmock.MockHandler) *SNMP { + snmp := New() + snmp.Config = prepareV2Config() + + m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{ + Variables: []gosnmp.SnmpPDU{ + {Value: nil, Type: gosnmp.NoSuchInstance}, + {Value: nil, Type: gosnmp.NoSuchInstance}, + }, + }, nil).Times(1) + + return snmp + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + mockSNMP, cleanup := mockInit(t) + defer cleanup() + + newSNMPClient = func() gosnmp.Handler { return mockSNMP } + defaultMockExpects(mockSNMP) + + snmp := test.prepareSNMP(mockSNMP) + require.NoError(t, snmp.Init()) + + if test.wantFail { + assert.Error(t, snmp.Check()) + } else { + assert.NoError(t, snmp.Check()) + } + }) + } +} + +func TestSNMP_Collect(t *testing.T) { + tests := map[string]struct { + prepareSNMP func(m *snmpmock.MockHandler) *SNMP + wantCollected map[string]int64 + }{ + "success when collecting supported type": { + prepareSNMP: func(m *snmpmock.MockHandler) *SNMP { + snmp := New() + snmp.Config = prepareConfigWithIndexRange(prepareV2Config, 0, 3) + + m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{ + Variables: []gosnmp.SnmpPDU{ + {Value: 10, Type: gosnmp.Counter32}, + {Value: 20, Type: gosnmp.Counter64}, + {Value: 30, Type: gosnmp.Gauge32}, + {Value: 1, Type: gosnmp.Boolean}, + {Value: 40, Type: gosnmp.Gauge32}, + {Value: 50, Type: gosnmp.TimeTicks}, + {Value: 60, Type: gosnmp.Uinteger32}, + {Value: 70, Type: gosnmp.Integer}, + }, + }, nil).Times(1) + + return snmp + }, + wantCollected: map[string]int64{ + "1.3.6.1.2.1.2.2.1.10.0": 10, + "1.3.6.1.2.1.2.2.1.16.0": 20, + "1.3.6.1.2.1.2.2.1.10.1": 30, + "1.3.6.1.2.1.2.2.1.16.1": 1, + "1.3.6.1.2.1.2.2.1.10.2": 40, + "1.3.6.1.2.1.2.2.1.16.2": 50, + "1.3.6.1.2.1.2.2.1.10.3": 60, + "1.3.6.1.2.1.2.2.1.16.3": 70, + }, + }, + "success when collecting supported and unsupported type": { + prepareSNMP: func(m *snmpmock.MockHandler) *SNMP { + snmp := New() + snmp.Config = prepareConfigWithIndexRange(prepareV2Config, 0, 2) + + m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{ + Variables: []gosnmp.SnmpPDU{ + {Value: 10, Type: gosnmp.Counter32}, + {Value: 20, Type: gosnmp.Counter64}, + {Value: 30, Type: gosnmp.Gauge32}, + {Value: nil, Type: gosnmp.NoSuchInstance}, + {Value: nil, Type: gosnmp.NoSuchInstance}, + {Value: nil, Type: gosnmp.NoSuchInstance}, + }, + }, nil).Times(1) + + return snmp + }, + wantCollected: map[string]int64{ + "1.3.6.1.2.1.2.2.1.10.0": 10, + "1.3.6.1.2.1.2.2.1.16.0": 20, + "1.3.6.1.2.1.2.2.1.10.1": 30, + }, + }, + "fails when collecting unsupported type": { + prepareSNMP: func(m *snmpmock.MockHandler) *SNMP { + snmp := New() + snmp.Config = prepareConfigWithIndexRange(prepareV2Config, 0, 2) + + m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{ + Variables: []gosnmp.SnmpPDU{ + {Value: nil, Type: gosnmp.NoSuchInstance}, + {Value: nil, Type: gosnmp.NoSuchInstance}, + {Value: nil, Type: gosnmp.NoSuchObject}, + {Value: "192.0.2.0", Type: gosnmp.NsapAddress}, + {Value: []uint8{118, 101, 116}, Type: gosnmp.OctetString}, + {Value: ".1.3.6.1.2.1.4.32.1.5.2.1.4.10.19.0.0.16", Type: gosnmp.ObjectIdentifier}, + }, + }, nil).Times(1) + + return snmp + }, + wantCollected: nil, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + mockSNMP, cleanup := mockInit(t) + defer cleanup() + + newSNMPClient = func() gosnmp.Handler { return mockSNMP } + defaultMockExpects(mockSNMP) + + snmp := test.prepareSNMP(mockSNMP) + require.NoError(t, snmp.Init()) + + collected := snmp.Collect() + + assert.Equal(t, test.wantCollected, collected) + }) + } +} + +func TestSNMP_Cleanup(t *testing.T) { + tests := map[string]struct { + prepareSNMP func(t *testing.T, m *snmpmock.MockHandler) *SNMP + }{ + "cleanup call if snmpClient initialized": { + prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *SNMP { + snmp := New() + snmp.Config = prepareV2Config() + require.NoError(t, snmp.Init()) + + m.EXPECT().Close().Times(1) + + return snmp + }, + }, + "cleanup call does not panic if snmpClient not initialized": { + prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *SNMP { + snmp := New() + snmp.Config = prepareV2Config() + require.NoError(t, snmp.Init()) + snmp.snmpClient = nil + + return snmp + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + mockSNMP, cleanup := mockInit(t) + defer cleanup() + + newSNMPClient = func() gosnmp.Handler { return mockSNMP } + defaultMockExpects(mockSNMP) + + snmp := test.prepareSNMP(t, mockSNMP) + assert.NotPanics(t, snmp.Cleanup) + }) + } +} + +func TestSNMP_Charts(t *testing.T) { + tests := map[string]struct { + prepareSNMP func(t *testing.T, m *snmpmock.MockHandler) *SNMP + wantNumCharts int + }{ + "without 'multiply_range': got expected number of charts": { + wantNumCharts: 1, + prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *SNMP { + snmp := New() + snmp.Config = prepareV2Config() + require.NoError(t, snmp.Init()) + + return snmp + }, + }, + "with 'multiply_range': got expected number of charts": { + wantNumCharts: 10, + prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *SNMP { + snmp := New() + snmp.Config = prepareConfigWithIndexRange(prepareV2Config, 0, 9) + require.NoError(t, snmp.Init()) + + return snmp + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + mockSNMP, cleanup := mockInit(t) + defer cleanup() + + newSNMPClient = func() gosnmp.Handler { return mockSNMP } + defaultMockExpects(mockSNMP) + + snmp := test.prepareSNMP(t, mockSNMP) + assert.Equal(t, test.wantNumCharts, len(*snmp.Charts())) + }) + } +} + +func mockInit(t *testing.T) (*snmpmock.MockHandler, func()) { + mockCtl := gomock.NewController(t) + cleanup := func() { mockCtl.Finish() } + mockSNMP := snmpmock.NewMockHandler(mockCtl) + + return mockSNMP, cleanup +} + +func defaultMockExpects(m *snmpmock.MockHandler) { + m.EXPECT().Target().AnyTimes() + m.EXPECT().Port().AnyTimes() + m.EXPECT().Retries().AnyTimes() + m.EXPECT().Timeout().AnyTimes() + m.EXPECT().MaxOids().AnyTimes() + m.EXPECT().Version().AnyTimes() + m.EXPECT().Community().AnyTimes() + m.EXPECT().SetTarget(gomock.Any()).AnyTimes() + m.EXPECT().SetPort(gomock.Any()).AnyTimes() + m.EXPECT().SetRetries(gomock.Any()).AnyTimes() + m.EXPECT().SetMaxOids(gomock.Any()).AnyTimes() + m.EXPECT().SetLogger(gomock.Any()).AnyTimes() + m.EXPECT().SetTimeout(gomock.Any()).AnyTimes() + m.EXPECT().SetCommunity(gomock.Any()).AnyTimes() + m.EXPECT().SetVersion(gomock.Any()).AnyTimes() + m.EXPECT().SetSecurityModel(gomock.Any()).AnyTimes() + m.EXPECT().SetMsgFlags(gomock.Any()).AnyTimes() + m.EXPECT().SetSecurityParameters(gomock.Any()).AnyTimes() + m.EXPECT().Connect().Return(nil).AnyTimes() +} + +func prepareConfigWithIndexRange(p func() Config, start, end int) Config { + if start > end || start < 0 || end < 1 { + panic(fmt.Sprintf("invalid index range ('%d'-'%d')", start, end)) + } + cfg := p() + for i := range cfg.ChartsInput { + cfg.ChartsInput[i].IndexRange = []int{start, end} + } + return cfg +} + +func prepareV3Config() Config { + cfg := prepareV2Config() + cfg.Options.Version = gosnmp.Version3.String() + cfg.User = User{ + Name: "name", + SecurityLevel: "authPriv", + AuthProto: strings.ToLower(gosnmp.MD5.String()), + AuthKey: "auth_key", + PrivProto: strings.ToLower(gosnmp.AES.String()), + PrivKey: "priv_key", + } + return cfg +} + +func prepareV2Config() Config { + cfg := prepareV1Config() + cfg.Options.Version = gosnmp.Version2c.String() + return cfg +} + +func prepareV1Config() Config { + return Config{ + UpdateEvery: defaultUpdateEvery, + Hostname: defaultHostname, + Community: defaultCommunity, + Options: Options{ + Port: defaultPort, + Retries: defaultRetries, + Timeout: defaultTimeout, + Version: gosnmp.Version1.String(), + MaxOIDs: defaultMaxOIDs, + }, + ChartsInput: []ChartConfig{ + { + ID: "test_chart1", + Title: "This is Test Chart1", + Units: "kilobits/s", + Family: "family", + Type: module.Area.String(), + Priority: module.Priority, + Dimensions: []DimensionConfig{ + { + OID: "1.3.6.1.2.1.2.2.1.10", + Name: "in", + Algorithm: module.Incremental.String(), + Multiplier: 8, + Divisor: 1000, + }, + { + OID: "1.3.6.1.2.1.2.2.1.16", + Name: "out", + Algorithm: module.Incremental.String(), + Multiplier: 8, + Divisor: 1000, + }, + }, + }, + }, + } +} |