fix: Add option to select translator (#10802)

This commit is contained in:
reimda 2022-03-17 21:43:46 -06:00 committed by GitHub
parent 7e652fdd00
commit 77040ef4e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 2046 additions and 520 deletions

View File

@ -13,6 +13,7 @@ import (
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/internal/snmp"
"github.com/influxdata/telegraf/models"
"github.com/influxdata/telegraf/plugins/serializers/influx"
)
@ -186,6 +187,10 @@ func (a *Agent) Run(ctx context.Context) error {
// initPlugins runs the Init function on plugins.
func (a *Agent) initPlugins() error {
for _, input := range a.Config.Inputs {
// Share the snmp translator setting with plugins that need it.
if tp, ok := input.Input.(snmp.TranslatorPlugin); ok {
tp.SetTranslator(a.Config.Agent.Translator)
}
err := input.Init()
if err != nil {
return fmt.Errorf("could not initialize input %s: %v",

View File

@ -225,6 +225,8 @@ type AgentConfig struct {
Hostname string
OmitHostname bool
Translator string `toml:"translator"`
}
// InputNames returns a list of strings of the configured inputs.
@ -418,6 +420,11 @@ var agentConfig = `
hostname = ""
## If set to true, do no set the "host" tag in the telegraf agent.
omit_hostname = false
## Translator for SNMP OIDs
## Valid values are "netsnmp" which runs snmptranslate and snmptable,
## and "gosmi" which uses the gosmi library.
# translator = "netsnmp"
`
var outputHeader = `
@ -855,6 +862,11 @@ func (c *Config) LoadConfigData(data []byte) error {
c.Tags["host"] = c.Agent.Hostname
}
// Set snmp agent translator default
if c.Agent.Translator == "" {
c.Agent.Translator = "netsnmp"
}
if len(c.UnusedFields) > 0 {
return fmt.Errorf("line %d: configuration specified the fields %q, but they weren't used", tbl.Line, keys(c.UnusedFields))
}

View File

@ -12,6 +12,8 @@ type ClientConfig struct {
Version uint8 `toml:"version"`
// Path to mib files
Path []string `toml:"path"`
// Translator implementation
Translator string `toml:"-"`
// Parameters for Version 1 & 2
Community string `toml:"community"`

View File

@ -0,0 +1,5 @@
package snmp
type TranslatorPlugin interface {
SetTranslator(name string) // Agent calls this on inputs before Init
}

View File

@ -30,6 +30,8 @@ path onto the global path variable
# version = 2
## Path to mib files
## Used by the gosmi translator.
## To add paths when translating with netsnmp, use the MIBDIRS environment variable
# path = ["/usr/share/snmp/mibs"]
## SNMP community string.

View File

@ -0,0 +1,123 @@
package snmp
import (
"fmt"
"sync"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/snmp"
"github.com/sleepinggenius2/gosmi"
)
type gosmiTranslator struct {
}
func NewGosmiTranslator(paths []string, log telegraf.Logger) (*gosmiTranslator, error) {
err := snmp.LoadMibsFromPath(paths, log, &snmp.GosmiMibLoader{})
if err == nil {
return &gosmiTranslator{}, nil
}
return nil, err
}
type gosmiSnmpTranslateCache struct {
mibName string
oidNum string
oidText string
conversion string
node gosmi.SmiNode
err error
}
var gosmiSnmpTranslateCachesLock sync.Mutex
var gosmiSnmpTranslateCaches map[string]gosmiSnmpTranslateCache
//nolint:revive
func (g *gosmiTranslator) SnmpTranslate(oid string) (string, string, string, string, error) {
a, b, c, d, _, e := g.SnmpTranslateFull(oid)
return a, b, c, d, e
}
//nolint:revive
func (g *gosmiTranslator) SnmpTranslateFull(oid string) (
mibName string, oidNum string, oidText string,
conversion string,
node gosmi.SmiNode,
err error) {
gosmiSnmpTranslateCachesLock.Lock()
if gosmiSnmpTranslateCaches == nil {
gosmiSnmpTranslateCaches = map[string]gosmiSnmpTranslateCache{}
}
var stc gosmiSnmpTranslateCache
var ok bool
if stc, ok = gosmiSnmpTranslateCaches[oid]; !ok {
// This will result in only one call to snmptranslate running at a time.
// We could speed it up by putting a lock in snmpTranslateCache and then
// returning it immediately, and multiple callers would then release the
// snmpTranslateCachesLock and instead wait on the individual
// snmpTranslation.Lock to release. But I don't know that the extra complexity
// is worth it. Especially when it would slam the system pretty hard if lots
// of lookups are being performed.
stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.node, stc.err = snmp.SnmpTranslateCall(oid)
gosmiSnmpTranslateCaches[oid] = stc
}
gosmiSnmpTranslateCachesLock.Unlock()
return stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.node, stc.err
}
type gosmiSnmpTableCache struct {
mibName string
oidNum string
oidText string
fields []Field
err error
}
var gosmiSnmpTableCaches map[string]gosmiSnmpTableCache
var gosmiSnmpTableCachesLock sync.Mutex
// snmpTable resolves the given OID as a table, providing information about the
// table and fields within.
//nolint:revive //Too many return variable but necessary
func (g *gosmiTranslator) SnmpTable(oid string) (
mibName string, oidNum string, oidText string,
fields []Field,
err error) {
gosmiSnmpTableCachesLock.Lock()
if gosmiSnmpTableCaches == nil {
gosmiSnmpTableCaches = map[string]gosmiSnmpTableCache{}
}
var stc gosmiSnmpTableCache
var ok bool
if stc, ok = gosmiSnmpTableCaches[oid]; !ok {
stc.mibName, stc.oidNum, stc.oidText, stc.fields, stc.err = g.SnmpTableCall(oid)
gosmiSnmpTableCaches[oid] = stc
}
gosmiSnmpTableCachesLock.Unlock()
return stc.mibName, stc.oidNum, stc.oidText, stc.fields, stc.err
}
//nolint:revive //Too many return variable but necessary
func (g *gosmiTranslator) SnmpTableCall(oid string) (mibName string, oidNum string, oidText string, fields []Field, err error) {
mibName, oidNum, oidText, _, node, err := g.SnmpTranslateFull(oid)
if err != nil {
return "", "", "", nil, fmt.Errorf("translating: %w", err)
}
mibPrefix := mibName + "::"
col, tagOids, err := snmp.GetIndex(oidNum, mibPrefix, node)
for _, c := range col {
_, isTag := tagOids[mibPrefix+c]
fields = append(fields, Field{Name: c, Oid: mibPrefix + c, IsTag: isTag})
}
return mibName, oidNum, oidText, fields, err
}

View File

@ -0,0 +1,943 @@
package snmp
import (
"fmt"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/internal/snmp"
"github.com/influxdata/telegraf/testutil"
)
func getGosmiTr(t *testing.T) Translator {
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
require.NoError(t, err)
return tr
}
func TestGosmiTranslator(t *testing.T) {
var tr Translator
var err error
tr, err = NewGosmiTranslator([]string{"testdata"}, testutil.Logger{})
require.NoError(t, err)
require.NotNil(t, tr)
}
//gosmi uses the same connection struct as netsnmp but has a few
//different test cases, so it has its own copy
var gosmiTsc = &testSNMPConnection{
host: "tsc",
values: map[string]interface{}{
".1.3.6.1.2.1.3.1.1.1.0": "foo",
".1.3.6.1.2.1.3.1.1.1.1": []byte("bar"),
".1.3.6.1.2.1.3.1.1.1.2": []byte(""),
".1.3.6.1.2.1.3.1.1.102": "bad",
".1.3.6.1.2.1.3.1.1.2.0": 1,
".1.3.6.1.2.1.3.1.1.2.1": 2,
".1.3.6.1.2.1.3.1.1.2.2": 0,
".1.3.6.1.2.1.3.1.1.3.0": "1.3.6.1.2.1.3.1.1.3",
".1.3.6.1.2.1.3.1.1.5.0": 123456,
".1.0.0.0.1.1.0": "foo",
".1.0.0.0.1.1.1": []byte("bar"),
".1.0.0.0.1.1.2": []byte(""),
".1.0.0.0.1.102": "bad",
".1.0.0.0.1.2.0": 1,
".1.0.0.0.1.2.1": 2,
".1.0.0.0.1.2.2": 0,
".1.0.0.0.1.3.0": "0.123",
".1.0.0.0.1.3.1": "0.456",
".1.0.0.0.1.3.2": "0.000",
".1.0.0.0.1.3.3": "9.999",
".1.0.0.0.1.5.0": 123456,
".1.0.0.1.1": "baz",
".1.0.0.1.2": 234,
".1.0.0.1.3": []byte("byte slice"),
".1.0.0.2.1.5.0.9.9": 11,
".1.0.0.2.1.5.1.9.9": 22,
".1.0.0.0.1.6.0": ".1.0.0.0.1.7",
".1.0.0.3.1.1.10": "instance",
".1.0.0.3.1.1.11": "instance2",
".1.0.0.3.1.1.12": "instance3",
".1.0.0.3.1.2.10": 10,
".1.0.0.3.1.2.11": 20,
".1.0.0.3.1.2.12": 20,
".1.0.0.3.1.3.10": 1,
".1.0.0.3.1.3.11": 2,
".1.0.0.3.1.3.12": 3,
},
}
func TestFieldInitGosmi(t *testing.T) {
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
require.NoError(t, err)
translations := []struct {
inputOid string
inputName string
inputConversion string
expectedOid string
expectedName string
expectedConversion string
}{
{".1.2.3", "foo", "", ".1.2.3", "foo", ""},
{".iso.2.3", "foo", "", ".1.2.3", "foo", ""},
{".1.0.0.0.1.1", "", "", ".1.0.0.0.1.1", "server", ""},
{"IF-MIB::ifPhysAddress.1", "", "", ".1.3.6.1.2.1.2.2.1.6.1", "ifPhysAddress.1", "hwaddr"},
{"IF-MIB::ifPhysAddress.1", "", "none", ".1.3.6.1.2.1.2.2.1.6.1", "ifPhysAddress.1", "none"},
{"BRIDGE-MIB::dot1dTpFdbAddress.1", "", "", ".1.3.6.1.2.1.17.4.3.1.1.1", "dot1dTpFdbAddress.1", "hwaddr"},
{"TCP-MIB::tcpConnectionLocalAddress.1", "", "", ".1.3.6.1.2.1.6.19.1.2.1", "tcpConnectionLocalAddress.1", "ipaddr"},
{".999", "", "", ".999", ".999", ""},
}
for _, txl := range translations {
f := Field{Oid: txl.inputOid, Name: txl.inputName, Conversion: txl.inputConversion}
err := f.init(tr)
require.NoError(t, err, "inputOid='%s' inputName='%s'", txl.inputOid, txl.inputName)
assert.Equal(t, txl.expectedOid, f.Oid, "inputOid='%s' inputName='%s' inputConversion='%s'", txl.inputOid, txl.inputName, txl.inputConversion)
assert.Equal(t, txl.expectedName, f.Name, "inputOid='%s' inputName='%s' inputConversion='%s'", txl.inputOid, txl.inputName, txl.inputConversion)
}
}
func TestTableInitGosmi(t *testing.T) {
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
s := &Snmp{
ClientConfig: snmp.ClientConfig{
Path: []string{testDataPath},
Translator: "gosmi",
},
Tables: []Table{
{Oid: ".1.3.6.1.2.1.3.1",
Fields: []Field{
{Oid: ".999", Name: "foo"},
{Oid: ".1.3.6.1.2.1.3.1.1.1", Name: "atIfIndex", IsTag: true},
{Oid: "RFC1213-MIB::atPhysAddress", Name: "atPhysAddress"},
}},
},
}
err = s.Init()
require.NoError(t, err)
assert.Equal(t, "atTable", s.Tables[0].Name)
assert.Len(t, s.Tables[0].Fields, 5)
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".999", Name: "foo", initialized: true})
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.1", Name: "atIfIndex", initialized: true, IsTag: true})
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.2", Name: "atPhysAddress", initialized: true, Conversion: "hwaddr"})
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.3", Name: "atNetAddress", initialized: true, IsTag: true})
}
func TestSnmpInitGosmi(t *testing.T) {
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
s := &Snmp{
Tables: []Table{
{Oid: "RFC1213-MIB::atTable"},
},
Fields: []Field{
{Oid: "RFC1213-MIB::atPhysAddress"},
},
ClientConfig: snmp.ClientConfig{
Path: []string{testDataPath},
Translator: "gosmi",
},
}
err = s.Init()
require.NoError(t, err)
assert.Len(t, s.Tables[0].Fields, 3)
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.1", Name: "atIfIndex", IsTag: true, initialized: true})
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.2", Name: "atPhysAddress", initialized: true, Conversion: "hwaddr"})
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.3", Name: "atNetAddress", IsTag: true, initialized: true})
assert.Equal(t, Field{
Oid: ".1.3.6.1.2.1.3.1.1.2",
Name: "atPhysAddress",
Conversion: "hwaddr",
initialized: true,
}, s.Fields[0])
}
func TestSnmpInit_noTranslateGosmi(t *testing.T) {
s := &Snmp{
Fields: []Field{
{Oid: ".9.1.1.1.1", Name: "one", IsTag: true},
{Oid: ".9.1.1.1.2", Name: "two"},
{Oid: ".9.1.1.1.3"},
},
Tables: []Table{
{Name: "testing",
Fields: []Field{
{Oid: ".9.1.1.1.4", Name: "four", IsTag: true},
{Oid: ".9.1.1.1.5", Name: "five"},
{Oid: ".9.1.1.1.6"},
}},
},
ClientConfig: snmp.ClientConfig{
Path: []string{},
Translator: "gosmi",
},
}
err := s.Init()
require.NoError(t, err)
assert.Equal(t, ".9.1.1.1.1", s.Fields[0].Oid)
assert.Equal(t, "one", s.Fields[0].Name)
assert.Equal(t, true, s.Fields[0].IsTag)
assert.Equal(t, ".9.1.1.1.2", s.Fields[1].Oid)
assert.Equal(t, "two", s.Fields[1].Name)
assert.Equal(t, false, s.Fields[1].IsTag)
assert.Equal(t, ".9.1.1.1.3", s.Fields[2].Oid)
assert.Equal(t, ".9.1.1.1.3", s.Fields[2].Name)
assert.Equal(t, false, s.Fields[2].IsTag)
assert.Equal(t, ".9.1.1.1.4", s.Tables[0].Fields[0].Oid)
assert.Equal(t, "four", s.Tables[0].Fields[0].Name)
assert.Equal(t, true, s.Tables[0].Fields[0].IsTag)
assert.Equal(t, ".9.1.1.1.5", s.Tables[0].Fields[1].Oid)
assert.Equal(t, "five", s.Tables[0].Fields[1].Name)
assert.Equal(t, false, s.Tables[0].Fields[1].IsTag)
assert.Equal(t, ".9.1.1.1.6", s.Tables[0].Fields[2].Oid)
assert.Equal(t, ".9.1.1.1.6", s.Tables[0].Fields[2].Name)
assert.Equal(t, false, s.Tables[0].Fields[2].IsTag)
}
//TestTableBuild_walk in snmp_test.go is split into two tests here,
//noTranslate and Translate.
//
//This is only running with gosmi translator but should be valid with
//netsnmp too.
func TestTableBuild_walk_noTranslate(t *testing.T) {
tbl := Table{
Name: "mytable",
IndexAsTag: true,
Fields: []Field{
{
Name: "myfield1",
Oid: ".1.0.0.0.1.1",
IsTag: true,
},
{
Name: "myfield2",
Oid: ".1.0.0.0.1.2",
},
{
Name: "myfield3",
Oid: ".1.0.0.0.1.3",
Conversion: "float",
},
{
Name: "myfield4",
Oid: ".1.0.0.2.1.5",
OidIndexSuffix: ".9.9",
},
{
Name: "myfield5",
Oid: ".1.0.0.2.1.5",
OidIndexLength: 1,
},
},
}
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
require.NoError(t, err)
tb, err := tbl.Build(gosmiTsc, true, tr)
require.NoError(t, err)
assert.Equal(t, tb.Name, "mytable")
rtr1 := RTableRow{
Tags: map[string]string{
"myfield1": "foo",
"index": "0",
},
Fields: map[string]interface{}{
"myfield2": 1,
"myfield3": float64(0.123),
"myfield4": 11,
"myfield5": 11,
},
}
rtr2 := RTableRow{
Tags: map[string]string{
"myfield1": "bar",
"index": "1",
},
Fields: map[string]interface{}{
"myfield2": 2,
"myfield3": float64(0.456),
"myfield4": 22,
"myfield5": 22,
},
}
rtr3 := RTableRow{
Tags: map[string]string{
"index": "2",
},
Fields: map[string]interface{}{
"myfield2": 0,
"myfield3": float64(0.0),
},
}
rtr4 := RTableRow{
Tags: map[string]string{
"index": "3",
},
Fields: map[string]interface{}{
"myfield3": float64(9.999),
},
}
assert.Len(t, tb.Rows, 4)
assert.Contains(t, tb.Rows, rtr1)
assert.Contains(t, tb.Rows, rtr2)
assert.Contains(t, tb.Rows, rtr3)
assert.Contains(t, tb.Rows, rtr4)
}
func TestTableBuild_walk_Translate(t *testing.T) {
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
require.NoError(t, err)
tbl := Table{
Name: "atTable",
IndexAsTag: true,
Fields: []Field{
{
Name: "ifIndex",
Oid: "1.3.6.1.2.1.3.1.1.1",
IsTag: true,
},
{
Name: "atPhysAddress",
Oid: "1.3.6.1.2.1.3.1.1.2",
Translate: false,
},
{
Name: "atNetAddress",
Oid: "1.3.6.1.2.1.3.1.1.3",
Translate: true,
},
},
}
err = tbl.Init(tr)
require.NoError(t, err)
tb, err := tbl.Build(gosmiTsc, true, tr)
require.NoError(t, err)
require.Equal(t, tb.Name, "atTable")
rtr1 := RTableRow{
Tags: map[string]string{
"ifIndex": "foo",
"index": "0",
},
Fields: map[string]interface{}{
"atPhysAddress": 1,
"atNetAddress": "atNetAddress",
},
}
rtr2 := RTableRow{
Tags: map[string]string{
"ifIndex": "bar",
"index": "1",
},
Fields: map[string]interface{}{
"atPhysAddress": 2,
},
}
rtr3 := RTableRow{
Tags: map[string]string{
"index": "2",
},
Fields: map[string]interface{}{
"atPhysAddress": 0,
},
}
require.Len(t, tb.Rows, 3)
require.Contains(t, tb.Rows, rtr1)
require.Contains(t, tb.Rows, rtr2)
require.Contains(t, tb.Rows, rtr3)
}
func TestTableBuild_noWalkGosmi(t *testing.T) {
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
require.NoError(t, err)
tbl := Table{
Name: "mytable",
Fields: []Field{
{
Name: "myfield1",
Oid: ".1.0.0.1.1",
IsTag: true,
},
{
Name: "myfield2",
Oid: ".1.0.0.1.2",
},
{
Name: "myfield3",
Oid: ".1.0.0.1.2",
IsTag: true,
},
{
Name: "empty",
Oid: ".1.0.0.0.1.1.2",
},
{
Name: "noexist",
Oid: ".1.2.3.4.5",
},
},
}
tb, err := tbl.Build(gosmiTsc, false, tr)
require.NoError(t, err)
rtr := RTableRow{
Tags: map[string]string{"myfield1": "baz", "myfield3": "234"},
Fields: map[string]interface{}{"myfield2": 234},
}
assert.Len(t, tb.Rows, 1)
assert.Contains(t, tb.Rows, rtr)
}
func TestGatherGosmi(t *testing.T) {
s := &Snmp{
Agents: []string{"TestGather"},
Name: "mytable",
Fields: []Field{
{
Name: "myfield1",
Oid: ".1.0.0.1.1",
IsTag: true,
},
{
Name: "myfield2",
Oid: ".1.0.0.1.2",
},
{
Name: "myfield3",
Oid: "1.0.0.1.1",
},
},
Tables: []Table{
{
Name: "myOtherTable",
InheritTags: []string{"myfield1"},
Fields: []Field{
{
Name: "myOtherField",
Oid: ".1.0.0.0.1.5",
},
},
},
},
connectionCache: []snmpConnection{
gosmiTsc,
},
ClientConfig: snmp.ClientConfig{
Path: []string{"testdata"},
Translator: "gosmi",
},
}
acc := &testutil.Accumulator{}
tstart := time.Now()
require.NoError(t, s.Gather(acc))
tstop := time.Now()
require.Len(t, acc.Metrics, 2)
m := acc.Metrics[0]
assert.Equal(t, "mytable", m.Measurement)
assert.Equal(t, "tsc", m.Tags[s.AgentHostTag])
assert.Equal(t, "baz", m.Tags["myfield1"])
assert.Len(t, m.Fields, 2)
assert.Equal(t, 234, m.Fields["myfield2"])
assert.Equal(t, "baz", m.Fields["myfield3"])
assert.False(t, tstart.After(m.Time))
assert.False(t, tstop.Before(m.Time))
m2 := acc.Metrics[1]
assert.Equal(t, "myOtherTable", m2.Measurement)
assert.Equal(t, "tsc", m2.Tags[s.AgentHostTag])
assert.Equal(t, "baz", m2.Tags["myfield1"])
assert.Len(t, m2.Fields, 1)
assert.Equal(t, 123456, m2.Fields["myOtherField"])
}
func TestGather_hostGosmi(t *testing.T) {
s := &Snmp{
Agents: []string{"TestGather"},
Name: "mytable",
Fields: []Field{
{
Name: "host",
Oid: ".1.0.0.1.1",
IsTag: true,
},
{
Name: "myfield2",
Oid: ".1.0.0.1.2",
},
},
connectionCache: []snmpConnection{
gosmiTsc,
},
}
acc := &testutil.Accumulator{}
require.NoError(t, s.Gather(acc))
require.Len(t, acc.Metrics, 1)
m := acc.Metrics[0]
assert.Equal(t, "baz", m.Tags["host"])
}
func TestFieldConvertGosmi(t *testing.T) {
testTable := []struct {
input interface{}
conv string
expected interface{}
}{
{[]byte("foo"), "", "foo"},
{"0.123", "float", float64(0.123)},
{[]byte("0.123"), "float", float64(0.123)},
{float32(0.123), "float", float64(float32(0.123))},
{float64(0.123), "float", float64(0.123)},
{float64(0.123123123123), "float", float64(0.123123123123)},
{123, "float", float64(123)},
{123, "float(0)", float64(123)},
{123, "float(4)", float64(0.0123)},
{int8(123), "float(3)", float64(0.123)},
{int16(123), "float(3)", float64(0.123)},
{int32(123), "float(3)", float64(0.123)},
{int64(123), "float(3)", float64(0.123)},
{uint(123), "float(3)", float64(0.123)},
{uint8(123), "float(3)", float64(0.123)},
{uint16(123), "float(3)", float64(0.123)},
{uint32(123), "float(3)", float64(0.123)},
{uint64(123), "float(3)", float64(0.123)},
{"123", "int", int64(123)},
{[]byte("123"), "int", int64(123)},
{"123123123123", "int", int64(123123123123)},
{[]byte("123123123123"), "int", int64(123123123123)},
{float32(12.3), "int", int64(12)},
{float64(12.3), "int", int64(12)},
{123, "int", int64(123)},
{int8(123), "int", int64(123)},
{int16(123), "int", int64(123)},
{int32(123), "int", int64(123)},
{int64(123), "int", int64(123)},
{uint(123), "int", int64(123)},
{uint8(123), "int", int64(123)},
{uint16(123), "int", int64(123)},
{uint32(123), "int", int64(123)},
{uint64(123), "int", int64(123)},
{[]byte("abcdef"), "hwaddr", "61:62:63:64:65:66"},
{"abcdef", "hwaddr", "61:62:63:64:65:66"},
{[]byte("abcd"), "ipaddr", "97.98.99.100"},
{"abcd", "ipaddr", "97.98.99.100"},
{[]byte("abcdefghijklmnop"), "ipaddr", "6162:6364:6566:6768:696a:6b6c:6d6e:6f70"},
{[]byte{0x00, 0x09, 0x3E, 0xE3, 0xF6, 0xD5, 0x3B, 0x60}, "hextoint:BigEndian:uint64", uint64(2602423610063712)},
{[]byte{0x00, 0x09, 0x3E, 0xE3}, "hextoint:BigEndian:uint32", uint32(605923)},
{[]byte{0x00, 0x09}, "hextoint:BigEndian:uint16", uint16(9)},
{[]byte{0x00, 0x09, 0x3E, 0xE3, 0xF6, 0xD5, 0x3B, 0x60}, "hextoint:LittleEndian:uint64", uint64(6934371307618175232)},
{[]byte{0x00, 0x09, 0x3E, 0xE3}, "hextoint:LittleEndian:uint32", uint32(3812493568)},
{[]byte{0x00, 0x09}, "hextoint:LittleEndian:uint16", uint16(2304)},
}
for _, tc := range testTable {
act, err := fieldConvert(tc.conv, tc.input)
assert.NoError(t, err, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected)
assert.EqualValues(t, tc.expected, act, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected)
}
}
func TestSnmpTranslateCache_missGosmi(t *testing.T) {
gosmiSnmpTranslateCaches = nil
oid := "IF-MIB::ifPhysAddress.1"
mibName, oidNum, oidText, conversion, err := getGosmiTr(t).SnmpTranslate(oid)
assert.Len(t, gosmiSnmpTranslateCaches, 1)
stc := gosmiSnmpTranslateCaches[oid]
assert.NotNil(t, stc)
assert.Equal(t, mibName, stc.mibName)
assert.Equal(t, oidNum, stc.oidNum)
assert.Equal(t, oidText, stc.oidText)
assert.Equal(t, conversion, stc.conversion)
assert.Equal(t, err, stc.err)
}
func TestSnmpTranslateCache_hitGosmi(t *testing.T) {
gosmiSnmpTranslateCaches = map[string]gosmiSnmpTranslateCache{
"foo": {
mibName: "a",
oidNum: "b",
oidText: "c",
conversion: "d",
err: fmt.Errorf("e"),
},
}
mibName, oidNum, oidText, conversion, err := getGosmiTr(t).SnmpTranslate("foo")
assert.Equal(t, "a", mibName)
assert.Equal(t, "b", oidNum)
assert.Equal(t, "c", oidText)
assert.Equal(t, "d", conversion)
assert.Equal(t, fmt.Errorf("e"), err)
gosmiSnmpTranslateCaches = nil
}
func TestSnmpTableCache_missGosmi(t *testing.T) {
gosmiSnmpTableCaches = nil
oid := ".1.0.0.0"
mibName, oidNum, oidText, fields, err := getGosmiTr(t).SnmpTable(oid)
assert.Len(t, gosmiSnmpTableCaches, 1)
stc := gosmiSnmpTableCaches[oid]
assert.NotNil(t, stc)
assert.Equal(t, mibName, stc.mibName)
assert.Equal(t, oidNum, stc.oidNum)
assert.Equal(t, oidText, stc.oidText)
assert.Equal(t, fields, stc.fields)
assert.Equal(t, err, stc.err)
}
func TestSnmpTableCache_hitGosmi(t *testing.T) {
gosmiSnmpTableCaches = map[string]gosmiSnmpTableCache{
"foo": {
mibName: "a",
oidNum: "b",
oidText: "c",
fields: []Field{{Name: "d"}},
err: fmt.Errorf("e"),
},
}
mibName, oidNum, oidText, fields, err := getGosmiTr(t).SnmpTable("foo")
assert.Equal(t, "a", mibName)
assert.Equal(t, "b", oidNum)
assert.Equal(t, "c", oidText)
assert.Equal(t, []Field{{Name: "d"}}, fields)
assert.Equal(t, fmt.Errorf("e"), err)
}
func TestTableJoin_walkGosmi(t *testing.T) {
tbl := Table{
Name: "mytable",
IndexAsTag: true,
Fields: []Field{
{
Name: "myfield1",
Oid: ".1.0.0.3.1.1",
IsTag: true,
},
{
Name: "myfield2",
Oid: ".1.0.0.3.1.2",
},
{
Name: "myfield3",
Oid: ".1.0.0.3.1.3",
SecondaryIndexTable: true,
},
{
Name: "myfield4",
Oid: ".1.0.0.0.1.1",
SecondaryIndexUse: true,
IsTag: true,
},
{
Name: "myfield5",
Oid: ".1.0.0.0.1.2",
SecondaryIndexUse: true,
},
},
}
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
require.NoError(t, err)
tb, err := tbl.Build(gosmiTsc, true, tr)
require.NoError(t, err)
assert.Equal(t, tb.Name, "mytable")
rtr1 := RTableRow{
Tags: map[string]string{
"myfield1": "instance",
"myfield4": "bar",
"index": "10",
},
Fields: map[string]interface{}{
"myfield2": 10,
"myfield3": 1,
"myfield5": 2,
},
}
rtr2 := RTableRow{
Tags: map[string]string{
"myfield1": "instance2",
"index": "11",
},
Fields: map[string]interface{}{
"myfield2": 20,
"myfield3": 2,
"myfield5": 0,
},
}
rtr3 := RTableRow{
Tags: map[string]string{
"myfield1": "instance3",
"index": "12",
},
Fields: map[string]interface{}{
"myfield2": 20,
"myfield3": 3,
},
}
assert.Len(t, tb.Rows, 3)
assert.Contains(t, tb.Rows, rtr1)
assert.Contains(t, tb.Rows, rtr2)
assert.Contains(t, tb.Rows, rtr3)
}
func TestTableOuterJoin_walkGosmi(t *testing.T) {
tbl := Table{
Name: "mytable",
IndexAsTag: true,
Fields: []Field{
{
Name: "myfield1",
Oid: ".1.0.0.3.1.1",
IsTag: true,
},
{
Name: "myfield2",
Oid: ".1.0.0.3.1.2",
},
{
Name: "myfield3",
Oid: ".1.0.0.3.1.3",
SecondaryIndexTable: true,
SecondaryOuterJoin: true,
},
{
Name: "myfield4",
Oid: ".1.0.0.0.1.1",
SecondaryIndexUse: true,
IsTag: true,
},
{
Name: "myfield5",
Oid: ".1.0.0.0.1.2",
SecondaryIndexUse: true,
},
},
}
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
require.NoError(t, err)
tb, err := tbl.Build(gosmiTsc, true, tr)
require.NoError(t, err)
assert.Equal(t, tb.Name, "mytable")
rtr1 := RTableRow{
Tags: map[string]string{
"myfield1": "instance",
"myfield4": "bar",
"index": "10",
},
Fields: map[string]interface{}{
"myfield2": 10,
"myfield3": 1,
"myfield5": 2,
},
}
rtr2 := RTableRow{
Tags: map[string]string{
"myfield1": "instance2",
"index": "11",
},
Fields: map[string]interface{}{
"myfield2": 20,
"myfield3": 2,
"myfield5": 0,
},
}
rtr3 := RTableRow{
Tags: map[string]string{
"myfield1": "instance3",
"index": "12",
},
Fields: map[string]interface{}{
"myfield2": 20,
"myfield3": 3,
},
}
rtr4 := RTableRow{
Tags: map[string]string{
"index": "Secondary.0",
"myfield4": "foo",
},
Fields: map[string]interface{}{
"myfield5": 1,
},
}
assert.Len(t, tb.Rows, 4)
assert.Contains(t, tb.Rows, rtr1)
assert.Contains(t, tb.Rows, rtr2)
assert.Contains(t, tb.Rows, rtr3)
assert.Contains(t, tb.Rows, rtr4)
}
func TestTableJoinNoIndexAsTag_walkGosmi(t *testing.T) {
tbl := Table{
Name: "mytable",
IndexAsTag: false,
Fields: []Field{
{
Name: "myfield1",
Oid: ".1.0.0.3.1.1",
IsTag: true,
},
{
Name: "myfield2",
Oid: ".1.0.0.3.1.2",
},
{
Name: "myfield3",
Oid: ".1.0.0.3.1.3",
SecondaryIndexTable: true,
},
{
Name: "myfield4",
Oid: ".1.0.0.0.1.1",
SecondaryIndexUse: true,
IsTag: true,
},
{
Name: "myfield5",
Oid: ".1.0.0.0.1.2",
SecondaryIndexUse: true,
},
},
}
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
require.NoError(t, err)
tb, err := tbl.Build(gosmiTsc, true, tr)
require.NoError(t, err)
assert.Equal(t, tb.Name, "mytable")
rtr1 := RTableRow{
Tags: map[string]string{
"myfield1": "instance",
"myfield4": "bar",
//"index": "10",
},
Fields: map[string]interface{}{
"myfield2": 10,
"myfield3": 1,
"myfield5": 2,
},
}
rtr2 := RTableRow{
Tags: map[string]string{
"myfield1": "instance2",
//"index": "11",
},
Fields: map[string]interface{}{
"myfield2": 20,
"myfield3": 2,
"myfield5": 0,
},
}
rtr3 := RTableRow{
Tags: map[string]string{
"myfield1": "instance3",
//"index": "12",
},
Fields: map[string]interface{}{
"myfield2": 20,
"myfield3": 3,
},
}
assert.Len(t, tb.Rows, 3)
assert.Contains(t, tb.Rows, rtr1)
assert.Contains(t, tb.Rows, rtr2)
assert.Contains(t, tb.Rows, rtr3)
}
func BenchmarkMibLoading(b *testing.B) {
log := testutil.Logger{}
path := []string{"testdata"}
for i := 0; i < b.N; i++ {
err := snmp.LoadMibsFromPath(path, log, &snmp.GosmiMibLoader{})
require.NoError(b, err)
}
}
func TestCanNotParse(t *testing.T) {
s := &Snmp{
Fields: []Field{
{Oid: "RFC1213-MIB::"},
},
ClientConfig: snmp.ClientConfig{
Path: []string{"testdata"},
Translator: "gosmi",
},
}
err := s.Init()
require.Error(t, err)
}
func TestMissingMibPath(t *testing.T) {
log := testutil.Logger{}
path := []string{"non-existing-directory"}
err := snmp.LoadMibsFromPath(path, log, &snmp.GosmiMibLoader{})
require.NoError(t, err)
}

View File

@ -0,0 +1,256 @@
package snmp
import (
"bufio"
"bytes"
"fmt"
"log" //nolint:revive
"os/exec"
"strings"
"sync"
"github.com/influxdata/wlog"
)
//struct that implements the translator interface. This calls existing
//code to exec netsnmp's snmptranslate program
type netsnmpTranslator struct {
}
func NewNetsnmpTranslator() *netsnmpTranslator {
return &netsnmpTranslator{}
}
type snmpTableCache struct {
mibName string
oidNum string
oidText string
fields []Field
err error
}
// execCommand is so tests can mock out exec.Command usage.
var execCommand = exec.Command
// execCmd executes the specified command, returning the STDOUT content.
// If command exits with error status, the output is captured into the returned error.
func execCmd(arg0 string, args ...string) ([]byte, error) {
if wlog.LogLevel() == wlog.DEBUG {
quoted := make([]string, 0, len(args))
for _, arg := range args {
quoted = append(quoted, fmt.Sprintf("%q", arg))
}
log.Printf("D! [inputs.snmp] executing %q %s", arg0, strings.Join(quoted, " "))
}
out, err := execCommand(arg0, args...).Output()
if err != nil {
if err, ok := err.(*exec.ExitError); ok {
return nil, fmt.Errorf("%s: %w", bytes.TrimRight(err.Stderr, "\r\n"), err)
}
return nil, err
}
return out, nil
}
var snmpTableCaches map[string]snmpTableCache
var snmpTableCachesLock sync.Mutex
// snmpTable resolves the given OID as a table, providing information about the
// table and fields within.
//nolint:revive
func (n *netsnmpTranslator) SnmpTable(oid string) (
mibName string, oidNum string, oidText string,
fields []Field,
err error) {
snmpTableCachesLock.Lock()
if snmpTableCaches == nil {
snmpTableCaches = map[string]snmpTableCache{}
}
var stc snmpTableCache
var ok bool
if stc, ok = snmpTableCaches[oid]; !ok {
stc.mibName, stc.oidNum, stc.oidText, stc.fields, stc.err = n.snmpTableCall(oid)
snmpTableCaches[oid] = stc
}
snmpTableCachesLock.Unlock()
return stc.mibName, stc.oidNum, stc.oidText, stc.fields, stc.err
}
//nolint:revive
func (n *netsnmpTranslator) snmpTableCall(oid string) (
mibName string, oidNum string, oidText string,
fields []Field,
err error) {
mibName, oidNum, oidText, _, err = n.SnmpTranslate(oid)
if err != nil {
return "", "", "", nil, fmt.Errorf("translating: %w", err)
}
mibPrefix := mibName + "::"
oidFullName := mibPrefix + oidText
// first attempt to get the table's tags
tagOids := map[string]struct{}{}
// We have to guess that the "entry" oid is `oid+".1"`. snmptable and snmptranslate don't seem to have a way to provide the info.
if out, err := execCmd("snmptranslate", "-Td", oidFullName+".1"); err == nil {
scanner := bufio.NewScanner(bytes.NewBuffer(out))
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, " INDEX") {
continue
}
i := strings.Index(line, "{ ")
if i == -1 { // parse error
continue
}
line = line[i+2:]
i = strings.Index(line, " }")
if i == -1 { // parse error
continue
}
line = line[:i]
for _, col := range strings.Split(line, ", ") {
tagOids[mibPrefix+col] = struct{}{}
}
}
}
// this won't actually try to run a query. The `-Ch` will just cause it to dump headers.
out, err := execCmd("snmptable", "-Ch", "-Cl", "-c", "public", "127.0.0.1", oidFullName)
if err != nil {
return "", "", "", nil, fmt.Errorf("getting table columns: %w", err)
}
scanner := bufio.NewScanner(bytes.NewBuffer(out))
scanner.Scan()
cols := scanner.Text()
if len(cols) == 0 {
return "", "", "", nil, fmt.Errorf("could not find any columns in table")
}
for _, col := range strings.Split(cols, " ") {
if len(col) == 0 {
continue
}
_, isTag := tagOids[mibPrefix+col]
fields = append(fields, Field{Name: col, Oid: mibPrefix + col, IsTag: isTag})
}
return mibName, oidNum, oidText, fields, err
}
type snmpTranslateCache struct {
mibName string
oidNum string
oidText string
conversion string
err error
}
var snmpTranslateCachesLock sync.Mutex
var snmpTranslateCaches map[string]snmpTranslateCache
// snmpTranslate resolves the given OID.
//nolint:revive
func (n *netsnmpTranslator) SnmpTranslate(oid string) (
mibName string, oidNum string, oidText string,
conversion string,
err error) {
snmpTranslateCachesLock.Lock()
if snmpTranslateCaches == nil {
snmpTranslateCaches = map[string]snmpTranslateCache{}
}
var stc snmpTranslateCache
var ok bool
if stc, ok = snmpTranslateCaches[oid]; !ok {
// This will result in only one call to snmptranslate running at a time.
// We could speed it up by putting a lock in snmpTranslateCache and then
// returning it immediately, and multiple callers would then release the
// snmpTranslateCachesLock and instead wait on the individual
// snmpTranslation.Lock to release. But I don't know that the extra complexity
// is worth it. Especially when it would slam the system pretty hard if lots
// of lookups are being performed.
stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.err = snmpTranslateCall(oid)
snmpTranslateCaches[oid] = stc
}
snmpTranslateCachesLock.Unlock()
return stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.err
}
//nolint:revive
func snmpTranslateCall(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) {
var out []byte
if strings.ContainsAny(oid, ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
out, err = execCmd("snmptranslate", "-Td", "-Ob", oid)
} else {
out, err = execCmd("snmptranslate", "-Td", "-Ob", "-m", "all", oid)
if err, ok := err.(*exec.Error); ok && err.Err == exec.ErrNotFound {
// Silently discard error if snmptranslate not found and we have a numeric OID.
// Meaning we can get by without the lookup.
return "", oid, oid, "", nil
}
}
if err != nil {
return "", "", "", "", err
}
scanner := bufio.NewScanner(bytes.NewBuffer(out))
ok := scanner.Scan()
if !ok && scanner.Err() != nil {
return "", "", "", "", fmt.Errorf("getting OID text: %w", scanner.Err())
}
oidText = scanner.Text()
i := strings.Index(oidText, "::")
if i == -1 {
// was not found in MIB.
if bytes.Contains(out, []byte("[TRUNCATED]")) {
return "", oid, oid, "", nil
}
// not truncated, but not fully found. We still need to parse out numeric OID, so keep going
oidText = oid
} else {
mibName = oidText[:i]
oidText = oidText[i+2:]
}
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, " -- TEXTUAL CONVENTION ") {
tc := strings.TrimPrefix(line, " -- TEXTUAL CONVENTION ")
switch tc {
case "MacAddress", "PhysAddress":
conversion = "hwaddr"
case "InetAddressIPv4", "InetAddressIPv6", "InetAddress", "IPSIpAddress":
conversion = "ipaddr"
}
} else if strings.HasPrefix(line, "::= { ") {
objs := strings.TrimPrefix(line, "::= { ")
objs = strings.TrimSuffix(objs, " }")
for _, obj := range strings.Split(objs, " ") {
if len(obj) == 0 {
continue
}
if i := strings.Index(obj, "("); i != -1 {
obj = obj[i+1:]
oidNum += "." + obj[:strings.Index(obj, ")")]
} else {
oidNum += "." + obj
}
}
break
}
}
return mibName, oidNum, oidText, conversion, nil
}

View File

@ -12,7 +12,6 @@ import (
"time"
"github.com/gosnmp/gosnmp"
"github.com/sleepinggenius2/gosmi"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
@ -39,6 +38,8 @@ const sampleConfig = `
# version = 2
## Path to mib files
## Used by the gosmi translator.
## To add paths when translating with netsnmp, use the MIBDIRS environment variable
# path = ["/usr/share/snmp/mibs"]
## Agent host tag; the tag used to reference the source host
@ -69,12 +70,26 @@ const sampleConfig = `
# priv_protocol = ""
## Privacy password used for encrypted messages.
# priv_password = ""
## Add fields and tables defining the variables you wish to collect. This
## example collects the system uptime and interface variables. Reference the
## full plugin documentation for configuration details.
`
type Translator interface {
SnmpTranslate(oid string) (
mibName string, oidNum string, oidText string,
conversion string,
err error,
)
SnmpTable(oid string) (
mibName string, oidNum string, oidText string,
fields []Field,
err error,
)
}
// Snmp holds the configuration for the plugin.
type Snmp struct {
// The SNMP agent to query. Format is [SCHEME://]ADDR[:PORT] (e.g.
@ -97,24 +112,38 @@ type Snmp struct {
connectionCache []snmpConnection
Log telegraf.Logger `toml:"-"`
translator Translator
}
func (s *Snmp) SetTranslator(name string) {
s.Translator = name
}
func (s *Snmp) Init() error {
err := snmp.LoadMibsFromPath(s.Path, s.Log, &snmp.GosmiMibLoader{})
if err != nil {
return err
var err error
switch s.Translator {
case "gosmi":
s.translator, err = NewGosmiTranslator(s.Path, s.Log)
if err != nil {
return err
}
case "netsnmp":
s.translator = NewNetsnmpTranslator()
default:
return fmt.Errorf("invalid translator value")
}
s.connectionCache = make([]snmpConnection, len(s.Agents))
for i := range s.Tables {
if err := s.Tables[i].Init(); err != nil {
if err := s.Tables[i].Init(s.translator); err != nil {
return fmt.Errorf("initializing table %s: %w", s.Tables[i].Name, err)
}
}
for i := range s.Fields {
if err := s.Fields[i].init(); err != nil {
if err := s.Fields[i].init(s.translator); err != nil {
return fmt.Errorf("initializing field %s: %w", s.Fields[i].Name, err)
}
}
@ -149,7 +178,7 @@ type Table struct {
}
// Init() builds & initializes the nested fields.
func (t *Table) Init() error {
func (t *Table) Init(tr Translator) error {
//makes sure oid or name is set in config file
//otherwise snmp will produce metrics with an empty name
if t.Oid == "" && t.Name == "" {
@ -160,14 +189,14 @@ func (t *Table) Init() error {
return nil
}
if err := t.initBuild(); err != nil {
if err := t.initBuild(tr); err != nil {
return err
}
secondaryIndexTablePresent := false
// initialize all the nested fields
for i := range t.Fields {
if err := t.Fields[i].init(); err != nil {
if err := t.Fields[i].init(tr); err != nil {
return fmt.Errorf("initializing field %s: %w", t.Fields[i].Name, err)
}
if t.Fields[i].SecondaryIndexTable {
@ -185,12 +214,12 @@ func (t *Table) Init() error {
// initBuild initializes the table if it has an OID configured. If so, the
// net-snmp tools will be used to look up the OID and auto-populate the table's
// fields.
func (t *Table) initBuild() error {
func (t *Table) initBuild(tr Translator) error {
if t.Oid == "" {
return nil
}
_, _, oidText, fields, err := snmpTable(t.Oid)
_, _, oidText, fields, err := tr.SnmpTable(t.Oid)
if err != nil {
return err
}
@ -254,14 +283,14 @@ type Field struct {
}
// init() converts OID names to numbers, and sets the .Name attribute if unset.
func (f *Field) init() error {
func (f *Field) init(tr Translator) error {
if f.initialized {
return nil
}
// check if oid needs translation or name is not set
if strings.ContainsAny(f.Oid, ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") || f.Name == "" {
_, oidNum, oidText, conversion, _, err := SnmpTranslate(f.Oid)
_, oidNum, oidText, conversion, err := tr.SnmpTranslate(f.Oid)
if err != nil {
return fmt.Errorf("translating: %w", err)
}
@ -384,7 +413,7 @@ func (s *Snmp) Gather(acc telegraf.Accumulator) error {
}
func (s *Snmp) gatherTable(acc telegraf.Accumulator, gs snmpConnection, t Table, topTags map[string]string, walk bool) error {
rt, err := t.Build(gs, walk)
rt, err := t.Build(gs, walk, s.translator)
if err != nil {
return err
}
@ -413,7 +442,7 @@ func (s *Snmp) gatherTable(acc telegraf.Accumulator, gs snmpConnection, t Table,
}
// Build retrieves all the fields specified in the table and constructs the RTable.
func (t Table) Build(gs snmpConnection, walk bool) (*RTable, error) {
func (t Table) Build(gs snmpConnection, walk bool, tr Translator) (*RTable, error) {
rows := map[string]RTableRow{}
//translation table for secondary index (when preforming join on two tables)
@ -505,7 +534,7 @@ func (t Table) Build(gs snmpConnection, walk bool) (*RTable, error) {
// snmptranslate table field value here
if f.Translate {
if entOid, ok := ent.Value.(string); ok {
_, _, oidText, _, _, err := SnmpTranslate(entOid)
_, _, oidText, _, err := tr.SnmpTranslate(entOid)
if err == nil {
// If no error translating, the original value for ent.Value should be replaced
ent.Value = oidText
@ -794,93 +823,3 @@ func fieldConvert(conv string, v interface{}) (interface{}, error) {
return nil, fmt.Errorf("invalid conversion type '%s'", conv)
}
type snmpTableCache struct {
mibName string
oidNum string
oidText string
fields []Field
err error
}
var snmpTableCaches map[string]snmpTableCache
var snmpTableCachesLock sync.Mutex
// snmpTable resolves the given OID as a table, providing information about the
// table and fields within.
//nolint:revive //Too many return variable but necessary
func snmpTable(oid string) (mibName string, oidNum string, oidText string, fields []Field, err error) {
snmpTableCachesLock.Lock()
if snmpTableCaches == nil {
snmpTableCaches = map[string]snmpTableCache{}
}
var stc snmpTableCache
var ok bool
if stc, ok = snmpTableCaches[oid]; !ok {
stc.mibName, stc.oidNum, stc.oidText, stc.fields, stc.err = snmpTableCall(oid)
snmpTableCaches[oid] = stc
}
snmpTableCachesLock.Unlock()
return stc.mibName, stc.oidNum, stc.oidText, stc.fields, stc.err
}
//nolint:revive //Too many return variable but necessary
func snmpTableCall(oid string) (mibName string, oidNum string, oidText string, fields []Field, err error) {
mibName, oidNum, oidText, _, node, err := SnmpTranslate(oid)
if err != nil {
return "", "", "", nil, fmt.Errorf("translating: %w", err)
}
mibPrefix := mibName + "::"
col, tagOids, err := snmp.GetIndex(oidNum, mibPrefix, node)
for _, c := range col {
_, isTag := tagOids[mibPrefix+c]
fields = append(fields, Field{Name: c, Oid: mibPrefix + c, IsTag: isTag})
}
return mibName, oidNum, oidText, fields, err
}
type snmpTranslateCache struct {
mibName string
oidNum string
oidText string
conversion string
node gosmi.SmiNode
err error
}
var snmpTranslateCachesLock sync.Mutex
var snmpTranslateCaches map[string]snmpTranslateCache
// snmpTranslate resolves the given OID.
//nolint:revive //Too many return variable but necessary
func SnmpTranslate(oid string) (mibName string, oidNum string, oidText string, conversion string, node gosmi.SmiNode, err error) {
snmpTranslateCachesLock.Lock()
if snmpTranslateCaches == nil {
snmpTranslateCaches = map[string]snmpTranslateCache{}
}
var stc snmpTranslateCache
var ok bool
if stc, ok = snmpTranslateCaches[oid]; !ok {
// This will result in only one call to snmptranslate running at a time.
// We could speed it up by putting a lock in snmpTranslateCache and then
// returning it immediately, and multiple callers would then release the
// snmpTranslateCachesLock and instead wait on the individual
// snmpTranslation.Lock to release. But I don't know that the extra complexity
// is worth it. Especially when it would slam the system pretty hard if lots
// of lookups are being performed.
stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.node, stc.err = snmp.SnmpTranslateCall(oid)
snmpTranslateCaches[oid] = stc
}
snmpTranslateCachesLock.Unlock()
return stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.node, stc.err
}

View File

@ -0,0 +1,103 @@
//go:build generate
// +build generate
package main
import (
"bufio"
"bytes"
"fmt"
"os"
"os/exec"
"strings"
)
// This file is a generator used to generate the mocks for the commands used by the tests.
// These are the commands to be mocked.
var mockedCommands = [][]string{
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.1.1"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.1.2"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", "1.0.0.1.1"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0.1.1"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0.1.1.0"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0.1.5"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.2.3"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0.1.7"},
{"snmptranslate", "-Td", "-Ob", ".iso.2.3"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".999"},
{"snmptranslate", "-Td", "-Ob", "TEST::server"},
{"snmptranslate", "-Td", "-Ob", "TEST::server.0"},
{"snmptranslate", "-Td", "-Ob", "TEST::testTable"},
{"snmptranslate", "-Td", "-Ob", "TEST::connections"},
{"snmptranslate", "-Td", "-Ob", "TEST::latency"},
{"snmptranslate", "-Td", "-Ob", "TEST::description"},
{"snmptranslate", "-Td", "-Ob", "TEST::hostname"},
{"snmptranslate", "-Td", "-Ob", "IF-MIB::ifPhysAddress.1"},
{"snmptranslate", "-Td", "-Ob", "BRIDGE-MIB::dot1dTpFdbAddress.1"},
{"snmptranslate", "-Td", "-Ob", "TCP-MIB::tcpConnectionLocalAddress.1"},
{"snmptranslate", "-Td", "TEST::testTable.1"},
{"snmptable", "-Ch", "-Cl", "-c", "public", "127.0.0.1", "TEST::testTable"},
}
type mockedCommandResult struct {
stdout string
stderr string
exitError bool
}
func main() {
if err := generate(); err != nil {
fmt.Fprintf(os.Stderr, "error: %s\n", err)
os.Exit(1)
}
}
func generate() error {
f, err := os.OpenFile("snmp_mocks_test.go", os.O_RDWR, 0644)
if err != nil {
return err
}
br := bufio.NewReader(f)
var i int64
for l, err := br.ReadString('\n'); err == nil; l, err = br.ReadString('\n') {
i += int64(len(l))
if l == "// BEGIN GO GENERATE CONTENT\n" {
break
}
}
f.Truncate(i)
f.Seek(i, 0)
fmt.Fprintf(f, "var mockedCommandResults = map[string]mockedCommandResult{\n")
for _, cmd := range mockedCommands {
ec := exec.Command(cmd[0], cmd[1:]...)
out := bytes.NewBuffer(nil)
err := bytes.NewBuffer(nil)
ec.Stdout = out
ec.Stderr = err
ec.Env = []string{
"MIBDIRS=+./testdata",
}
var mcr mockedCommandResult
if err := ec.Run(); err != nil {
if err, ok := err.(*exec.ExitError); !ok {
mcr.exitError = true
} else {
return fmt.Errorf("executing %v: %s", cmd, err)
}
}
mcr.stdout = string(out.Bytes())
mcr.stderr = string(err.Bytes())
cmd0 := strings.Join(cmd, "\000")
mcrv := fmt.Sprintf("%#v", mcr)[5:] // trim `main.` prefix
fmt.Fprintf(f, "%#v: %s,\n", cmd0, mcrv)
}
f.Write([]byte("}\n"))
f.Close()
return exec.Command("gofmt", "-w", "snmp_mocks_test.go").Run()
}

View File

@ -0,0 +1,93 @@
package snmp
import (
"fmt"
"os"
"os/exec"
"strings"
"testing"
)
type mockedCommandResult struct {
stdout string
stderr string
exitError bool
}
func mockExecCommand(arg0 string, args ...string) *exec.Cmd {
args = append([]string{"-test.run=TestMockExecCommand", "--", arg0}, args...)
cmd := exec.Command(os.Args[0], args...)
cmd.Stderr = os.Stderr // so the test output shows errors
return cmd
}
// This is not a real test. This is just a way of mocking out commands.
//
// Idea based on https://github.com/golang/go/blob/7c31043/src/os/exec/exec_test.go#L568
func TestMockExecCommand(_ *testing.T) {
var cmd []string
for _, arg := range os.Args {
if arg == "--" {
cmd = []string{}
continue
}
if cmd == nil {
continue
}
cmd = append(cmd, arg)
}
if cmd == nil {
return
}
cmd0 := strings.Join(cmd, "\000")
mcr, ok := mockedCommandResults[cmd0]
if !ok {
cv := fmt.Sprintf("%#v", cmd)[8:] // trim `[]string` prefix
//nolint:errcheck,revive
fmt.Fprintf(os.Stderr, "Unmocked command. Please add the following to `mockedCommands` in snmp_mocks_generate.go, and then run `go generate`:\n\t%s,\n", cv)
//nolint:revive // error code is important for this "test"
os.Exit(1)
}
//nolint:errcheck,revive
fmt.Printf("%s", mcr.stdout)
//nolint:errcheck,revive
fmt.Fprintf(os.Stderr, "%s", mcr.stderr)
if mcr.exitError {
//nolint:revive // error code is important for this "test"
os.Exit(1)
}
//nolint:revive // error code is important for this "test"
os.Exit(0)
}
func init() {
execCommand = mockExecCommand
}
// BEGIN GO GENERATE CONTENT
var mockedCommandResults = map[string]mockedCommandResult{
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0": {stdout: "TEST::testTable\ntestTable OBJECT-TYPE\n -- FROM\tTEST\n MAX-ACCESS\tnot-accessible\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) 0 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.1.1": {stdout: "TEST::hostname\nhostname OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) 1 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.1.2": {stdout: "TEST::1.2\nanonymous#1 OBJECT-TYPE\n -- FROM\tTEST\n::= { iso(1) 0 testOID(0) 1 2 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x001.0.0.1.1": {stdout: "TEST::hostname\nhostname OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) 1 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0.1.1": {stdout: "TEST::server\nserver OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0.1.1.0": {stdout: "TEST::server.0\nserver OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) server(1) 0 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0.1.5": {stdout: "TEST::testTableEntry.5\ntestTableEntry OBJECT-TYPE\n -- FROM\tTEST\n MAX-ACCESS\tnot-accessible\n STATUS\tcurrent\n INDEX\t\t{ server }\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 5 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.2.3": {stdout: "iso.2.3\niso OBJECT-TYPE\n -- FROM\t#-1\n::= { iso(1) 2 3 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0.1.7": {stdout: "TEST::testTableEntry.7\ntestTableEntry OBJECT-TYPE\n -- FROM\tTEST\n MAX-ACCESS\tnot-accessible\n STATUS\tcurrent\n INDEX\t\t{ server }\n::= { iso(1) std(0) testOID(0) testTable(0) testTableEntry(1) 7 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00.iso.2.3": {stdout: "iso.2.3\niso OBJECT-TYPE\n -- FROM\t#-1\n::= { iso(1) 2 3 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.999": {stdout: ".999\n [TRUNCATED]\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00TEST::server": {stdout: "TEST::server\nserver OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00TEST::server.0": {stdout: "TEST::server.0\nserver OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) server(1) 0 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00TEST::testTable": {stdout: "TEST::testTable\ntestTable OBJECT-TYPE\n -- FROM\tTEST\n MAX-ACCESS\tnot-accessible\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) 0 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00TEST::connections": {stdout: "TEST::connections\nconnections OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tINTEGER\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 2 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00TEST::latency": {stdout: "TEST::latency\nlatency OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 3 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00TEST::description": {stdout: "TEST::description\ndescription OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 4 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00TEST::hostname": {stdout: "TEST::hostname\nhostname OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) 1 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00IF-MIB::ifPhysAddress.1": {stdout: "IF-MIB::ifPhysAddress.1\nifPhysAddress OBJECT-TYPE\n -- FROM\tIF-MIB\n -- TEXTUAL CONVENTION PhysAddress\n SYNTAX\tOCTET STRING\n DISPLAY-HINT\t\"1x:\"\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n DESCRIPTION\t\"The interface's address at its protocol sub-layer. For\n example, for an 802.x interface, this object normally\n contains a MAC address. The interface's media-specific MIB\n must define the bit and byte ordering and the format of the\n value of this object. For interfaces which do not have such\n an address (e.g., a serial line), this object should contain\n an octet string of zero length.\"\n::= { iso(1) org(3) dod(6) internet(1) mgmt(2) mib-2(1) interfaces(2) ifTable(2) ifEntry(1) ifPhysAddress(6) 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00BRIDGE-MIB::dot1dTpFdbAddress.1": {stdout: "BRIDGE-MIB::dot1dTpFdbAddress.1\ndot1dTpFdbAddress OBJECT-TYPE\n -- FROM\tBRIDGE-MIB\n -- TEXTUAL CONVENTION MacAddress\n SYNTAX\tOCTET STRING (6) \n DISPLAY-HINT\t\"1x:\"\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n DESCRIPTION\t\"A unicast MAC address for which the bridge has\n forwarding and/or filtering information.\"\n::= { iso(1) org(3) dod(6) internet(1) mgmt(2) mib-2(1) dot1dBridge(17) dot1dTp(4) dot1dTpFdbTable(3) dot1dTpFdbEntry(1) dot1dTpFdbAddress(1) 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00TCP-MIB::tcpConnectionLocalAddress.1": {stdout: "TCP-MIB::tcpConnectionLocalAddress.1\ntcpConnectionLocalAddress OBJECT-TYPE\n -- FROM\tTCP-MIB\n -- TEXTUAL CONVENTION InetAddress\n SYNTAX\tOCTET STRING (0..255) \n MAX-ACCESS\tnot-accessible\n STATUS\tcurrent\n DESCRIPTION\t\"The local IP address for this TCP connection. The type\n of this address is determined by the value of\n tcpConnectionLocalAddressType.\n\n As this object is used in the index for the\n tcpConnectionTable, implementors should be\n careful not to create entries that would result in OIDs\n with more than 128 subidentifiers; otherwise the information\n cannot be accessed by using SNMPv1, SNMPv2c, or SNMPv3.\"\n::= { iso(1) org(3) dod(6) internet(1) mgmt(2) mib-2(1) tcp(6) tcpConnectionTable(19) tcpConnectionEntry(1) tcpConnectionLocalAddress(2) 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00TEST::testTable.1": {stdout: "TEST::testTableEntry\ntestTableEntry OBJECT-TYPE\n -- FROM\tTEST\n MAX-ACCESS\tnot-accessible\n STATUS\tcurrent\n INDEX\t\t{ server }\n::= { iso(1) 0 testOID(0) testTable(0) 1 }\n", stderr: "", exitError: false},
"snmptable\x00-Ch\x00-Cl\x00-c\x00public\x00127.0.0.1\x00TEST::testTable": {stdout: "server connections latency description \nTEST::testTable: No entries\n", stderr: "", exitError: false},
}

View File

@ -1,21 +1,22 @@
//go:generate go run -tags generate snmp_mocks_generate.go
package snmp
import (
"fmt"
"net"
"path/filepath"
"os/exec"
"sync"
"testing"
"time"
"github.com/gosnmp/gosnmp"
"github.com/influxdata/toml"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/internal/snmp"
"github.com/influxdata/telegraf/plugins/inputs"
"github.com/influxdata/telegraf/testutil"
"github.com/influxdata/toml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testSNMPConnection struct {
@ -62,42 +63,33 @@ func (tsc *testSNMPConnection) Walk(oid string, wf gosnmp.WalkFunc) error {
var tsc = &testSNMPConnection{
host: "tsc",
values: map[string]interface{}{
".1.3.6.1.2.1.3.1.1.1.0": "foo",
".1.3.6.1.2.1.3.1.1.1.1": []byte("bar"),
".1.3.6.1.2.1.3.1.1.1.2": []byte(""),
".1.3.6.1.2.1.3.1.1.102": "bad",
".1.3.6.1.2.1.3.1.1.2.0": 1,
".1.3.6.1.2.1.3.1.1.2.1": 2,
".1.3.6.1.2.1.3.1.1.2.2": 0,
".1.3.6.1.2.1.3.1.1.3.0": "1.3.6.1.2.1.3.1.1.3",
".1.3.6.1.2.1.3.1.1.5.0": 123456,
".1.0.0.0.1.1.0": "foo",
".1.0.0.0.1.1.1": []byte("bar"),
".1.0.0.0.1.1.2": []byte(""),
".1.0.0.0.1.102": "bad",
".1.0.0.0.1.2.0": 1,
".1.0.0.0.1.2.1": 2,
".1.0.0.0.1.2.2": 0,
".1.0.0.0.1.3.0": "0.123",
".1.0.0.0.1.3.1": "0.456",
".1.0.0.0.1.3.2": "0.000",
".1.0.0.0.1.3.3": "9.999",
".1.0.0.0.1.5.0": 123456,
".1.0.0.1.1": "baz",
".1.0.0.1.2": 234,
".1.0.0.1.3": []byte("byte slice"),
".1.0.0.2.1.5.0.9.9": 11,
".1.0.0.2.1.5.1.9.9": 22,
".1.0.0.0.1.6.0": ".1.0.0.0.1.7",
".1.0.0.3.1.1.10": "instance",
".1.0.0.3.1.1.11": "instance2",
".1.0.0.3.1.1.12": "instance3",
".1.0.0.3.1.2.10": 10,
".1.0.0.3.1.2.11": 20,
".1.0.0.3.1.2.12": 20,
".1.0.0.3.1.3.10": 1,
".1.0.0.3.1.3.11": 2,
".1.0.0.3.1.3.12": 3,
".1.0.0.0.1.1.0": "foo",
".1.0.0.0.1.1.1": []byte("bar"),
".1.0.0.0.1.1.2": []byte(""),
".1.0.0.0.1.102": "bad",
".1.0.0.0.1.2.0": 1,
".1.0.0.0.1.2.1": 2,
".1.0.0.0.1.2.2": 0,
".1.0.0.0.1.3.0": "0.123",
".1.0.0.0.1.3.1": "0.456",
".1.0.0.0.1.3.2": "0.000",
".1.0.0.0.1.3.3": "9.999",
".1.0.0.0.1.5.0": 123456,
".1.0.0.1.1": "baz",
".1.0.0.1.2": 234,
".1.0.0.1.3": []byte("byte slice"),
".1.0.0.2.1.5.0.9.9": 11,
".1.0.0.2.1.5.1.9.9": 22,
".1.0.0.0.1.6.0": ".1.0.0.0.1.7",
".1.0.0.3.1.1.10": "instance",
".1.0.0.3.1.1.11": "instance2",
".1.0.0.3.1.1.12": "instance3",
".1.0.0.3.1.2.10": 10,
".1.0.0.3.1.2.11": 20,
".1.0.0.3.1.2.12": 20,
".1.0.0.3.1.3.10": 1,
".1.0.0.3.1.3.11": 2,
".1.0.0.3.1.3.12": 3,
},
}
@ -123,18 +115,6 @@ func TestSampleConfig(t *testing.T) {
}
func TestFieldInit(t *testing.T) {
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
s := &Snmp{
ClientConfig: snmp.ClientConfig{
Path: []string{testDataPath},
},
Log: &testutil.Logger{},
}
err = s.Init()
require.NoError(t, err)
translations := []struct {
inputOid string
inputName string
@ -146,130 +126,131 @@ func TestFieldInit(t *testing.T) {
{".1.2.3", "foo", "", ".1.2.3", "foo", ""},
{".iso.2.3", "foo", "", ".1.2.3", "foo", ""},
{".1.0.0.0.1.1", "", "", ".1.0.0.0.1.1", "server", ""},
{".1.0.0.0.1.1.0", "", "", ".1.0.0.0.1.1.0", "server.0", ""},
{".999", "", "", ".999", ".999", ""},
{"TEST::server", "", "", ".1.0.0.0.1.1", "server", ""},
{"TEST::server.0", "", "", ".1.0.0.0.1.1.0", "server.0", ""},
{"TEST::server", "foo", "", ".1.0.0.0.1.1", "foo", ""},
{"IF-MIB::ifPhysAddress.1", "", "", ".1.3.6.1.2.1.2.2.1.6.1", "ifPhysAddress.1", "hwaddr"},
{"IF-MIB::ifPhysAddress.1", "", "none", ".1.3.6.1.2.1.2.2.1.6.1", "ifPhysAddress.1", "none"},
{"BRIDGE-MIB::dot1dTpFdbAddress.1", "", "", ".1.3.6.1.2.1.17.4.3.1.1.1", "dot1dTpFdbAddress.1", "hwaddr"},
{"TCP-MIB::tcpConnectionLocalAddress.1", "", "", ".1.3.6.1.2.1.6.19.1.2.1", "tcpConnectionLocalAddress.1", "ipaddr"},
{".999", "", "", ".999", ".999", ""},
}
tr := NewNetsnmpTranslator()
for _, txl := range translations {
f := Field{Oid: txl.inputOid, Name: txl.inputName, Conversion: txl.inputConversion}
err := f.init()
require.NoError(t, err, "inputOid='%s' inputName='%s'", txl.inputOid, txl.inputName)
require.Equal(t, txl.expectedOid, f.Oid, "inputOid='%s' inputName='%s' inputConversion='%s'", txl.inputOid, txl.inputName, txl.inputConversion)
require.Equal(t, txl.expectedName, f.Name, "inputOid='%s' inputName='%s' inputConversion='%s'", txl.inputOid, txl.inputName, txl.inputConversion)
err := f.init(tr)
if !assert.NoError(t, err, "inputOid='%s' inputName='%s'", txl.inputOid, txl.inputName) {
continue
}
assert.Equal(t, txl.expectedOid, f.Oid, "inputOid='%s' inputName='%s' inputConversion='%s'", txl.inputOid, txl.inputName, txl.inputConversion)
assert.Equal(t, txl.expectedName, f.Name, "inputOid='%s' inputName='%s' inputConversion='%s'", txl.inputOid, txl.inputName, txl.inputConversion)
}
}
func TestTableInit(t *testing.T) {
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
s := &Snmp{
ClientConfig: snmp.ClientConfig{
Path: []string{testDataPath},
},
Tables: []Table{
{Oid: ".1.3.6.1.2.1.3.1",
Fields: []Field{
{Oid: ".999", Name: "foo"},
{Oid: ".1.3.6.1.2.1.3.1.1.1", Name: "atIfIndex", IsTag: true},
{Oid: "RFC1213-MIB::atPhysAddress", Name: "atPhysAddress"},
}},
tbl := Table{
Oid: ".1.0.0.0",
Fields: []Field{
{Oid: ".999", Name: "foo"},
{Oid: "TEST::description", Name: "description", IsTag: true},
},
}
err = s.Init()
err := tbl.Init(NewNetsnmpTranslator())
require.NoError(t, err)
require.Equal(t, "atTable", s.Tables[0].Name)
assert.Equal(t, "testTable", tbl.Name)
require.Len(t, s.Tables[0].Fields, 5)
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".999", Name: "foo", initialized: true})
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.1", Name: "atIfIndex", initialized: true, IsTag: true})
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.2", Name: "atPhysAddress", initialized: true, Conversion: "hwaddr"})
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.3", Name: "atNetAddress", initialized: true, IsTag: true})
assert.Len(t, tbl.Fields, 5)
assert.Contains(t, tbl.Fields, Field{Oid: ".999", Name: "foo", initialized: true})
assert.Contains(t, tbl.Fields, Field{Oid: ".1.0.0.0.1.1", Name: "server", IsTag: true, initialized: true})
assert.Contains(t, tbl.Fields, Field{Oid: ".1.0.0.0.1.2", Name: "connections", initialized: true})
assert.Contains(t, tbl.Fields, Field{Oid: ".1.0.0.0.1.3", Name: "latency", initialized: true})
assert.Contains(t, tbl.Fields, Field{Oid: ".1.0.0.0.1.4", Name: "description", IsTag: true, initialized: true})
}
func TestSnmpInit(t *testing.T) {
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
s := &Snmp{
Tables: []Table{
{Oid: "RFC1213-MIB::atTable"},
{Oid: "TEST::testTable"},
},
Fields: []Field{
{Oid: "RFC1213-MIB::atPhysAddress"},
{Oid: "TEST::hostname"},
},
ClientConfig: snmp.ClientConfig{
Path: []string{testDataPath},
},
}
err = s.Init()
require.NoError(t, err)
require.Len(t, s.Tables[0].Fields, 3)
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.1", Name: "atIfIndex", IsTag: true, initialized: true})
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.2", Name: "atPhysAddress", initialized: true, Conversion: "hwaddr"})
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.3", Name: "atNetAddress", IsTag: true, initialized: true})
require.Equal(t, Field{
Oid: ".1.3.6.1.2.1.3.1.1.2",
Name: "atPhysAddress",
Conversion: "hwaddr",
initialized: true,
}, s.Fields[0])
}
func TestSnmpInit_noTranslate(t *testing.T) {
s := &Snmp{
Fields: []Field{
{Oid: ".9.1.1.1.1", Name: "one", IsTag: true},
{Oid: ".9.1.1.1.2", Name: "two"},
{Oid: ".9.1.1.1.3"},
},
Tables: []Table{
{Name: "testing",
Fields: []Field{
{Oid: ".9.1.1.1.4", Name: "four", IsTag: true},
{Oid: ".9.1.1.1.5", Name: "five"},
{Oid: ".9.1.1.1.6"},
}},
},
ClientConfig: snmp.ClientConfig{
Path: []string{},
Translator: "netsnmp",
},
}
err := s.Init()
require.NoError(t, err)
require.Equal(t, ".9.1.1.1.1", s.Fields[0].Oid)
require.Equal(t, "one", s.Fields[0].Name)
require.Equal(t, true, s.Fields[0].IsTag)
assert.Len(t, s.Tables[0].Fields, 4)
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.0.0.0.1.1", Name: "server", IsTag: true, initialized: true})
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.0.0.0.1.2", Name: "connections", initialized: true})
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.0.0.0.1.3", Name: "latency", initialized: true})
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.0.0.0.1.4", Name: "description", initialized: true})
require.Equal(t, ".9.1.1.1.2", s.Fields[1].Oid)
require.Equal(t, "two", s.Fields[1].Name)
require.Equal(t, false, s.Fields[1].IsTag)
assert.Equal(t, Field{
Oid: ".1.0.0.1.1",
Name: "hostname",
initialized: true,
}, s.Fields[0])
}
require.Equal(t, ".9.1.1.1.3", s.Fields[2].Oid)
require.Equal(t, ".9.1.1.1.3", s.Fields[2].Name)
require.Equal(t, false, s.Fields[2].IsTag)
func TestSnmpInit_noTranslate(t *testing.T) {
// override execCommand so it returns exec.ErrNotFound
defer func(ec func(string, ...string) *exec.Cmd) { execCommand = ec }(execCommand)
execCommand = func(_ string, _ ...string) *exec.Cmd {
return exec.Command("snmptranslateExecErrNotFound")
}
require.Equal(t, ".9.1.1.1.4", s.Tables[0].Fields[0].Oid)
require.Equal(t, "four", s.Tables[0].Fields[0].Name)
require.Equal(t, true, s.Tables[0].Fields[0].IsTag)
s := &Snmp{
Fields: []Field{
{Oid: ".1.1.1.1", Name: "one", IsTag: true},
{Oid: ".1.1.1.2", Name: "two"},
{Oid: ".1.1.1.3"},
},
Tables: []Table{
{Name: "testing",
Fields: []Field{
{Oid: ".1.1.1.4", Name: "four", IsTag: true},
{Oid: ".1.1.1.5", Name: "five"},
{Oid: ".1.1.1.6"},
}},
},
ClientConfig: snmp.ClientConfig{
Translator: "netsnmp",
},
}
require.Equal(t, ".9.1.1.1.5", s.Tables[0].Fields[1].Oid)
require.Equal(t, "five", s.Tables[0].Fields[1].Name)
require.Equal(t, false, s.Tables[0].Fields[1].IsTag)
err := s.Init()
require.NoError(t, err)
require.Equal(t, ".9.1.1.1.6", s.Tables[0].Fields[2].Oid)
require.Equal(t, ".9.1.1.1.6", s.Tables[0].Fields[2].Name)
require.Equal(t, false, s.Tables[0].Fields[2].IsTag)
assert.Equal(t, ".1.1.1.1", s.Fields[0].Oid)
assert.Equal(t, "one", s.Fields[0].Name)
assert.Equal(t, true, s.Fields[0].IsTag)
assert.Equal(t, ".1.1.1.2", s.Fields[1].Oid)
assert.Equal(t, "two", s.Fields[1].Name)
assert.Equal(t, false, s.Fields[1].IsTag)
assert.Equal(t, ".1.1.1.3", s.Fields[2].Oid)
assert.Equal(t, ".1.1.1.3", s.Fields[2].Name)
assert.Equal(t, false, s.Fields[2].IsTag)
assert.Equal(t, ".1.1.1.4", s.Tables[0].Fields[0].Oid)
assert.Equal(t, "four", s.Tables[0].Fields[0].Name)
assert.Equal(t, true, s.Tables[0].Fields[0].IsTag)
assert.Equal(t, ".1.1.1.5", s.Tables[0].Fields[1].Oid)
assert.Equal(t, "five", s.Tables[0].Fields[1].Name)
assert.Equal(t, false, s.Tables[0].Fields[1].IsTag)
assert.Equal(t, ".1.1.1.6", s.Tables[0].Fields[2].Oid)
assert.Equal(t, ".1.1.1.6", s.Tables[0].Fields[2].Name)
assert.Equal(t, false, s.Tables[0].Fields[2].IsTag)
}
func TestSnmpInit_noName_noOid(t *testing.T) {
@ -288,17 +269,14 @@ func TestSnmpInit_noName_noOid(t *testing.T) {
}
func TestGetSNMPConnection_v2(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
s := &Snmp{
Agents: []string{"1.2.3.4:567", "1.2.3.4", "udp://127.0.0.1"},
ClientConfig: snmp.ClientConfig{
Timeout: config.Duration(3 * time.Second),
Retries: 4,
Version: 2,
Community: "foo",
Timeout: config.Duration(3 * time.Second),
Retries: 4,
Version: 2,
Community: "foo",
Translator: "netsnmp",
},
}
err := s.Init()
@ -307,25 +285,25 @@ func TestGetSNMPConnection_v2(t *testing.T) {
gsc, err := s.getConnection(0)
require.NoError(t, err)
gs := gsc.(snmp.GosnmpWrapper)
require.Equal(t, "1.2.3.4", gs.Target)
require.EqualValues(t, 567, gs.Port)
require.Equal(t, gosnmp.Version2c, gs.Version)
require.Equal(t, "foo", gs.Community)
require.Equal(t, "udp", gs.Transport)
assert.Equal(t, "1.2.3.4", gs.Target)
assert.EqualValues(t, 567, gs.Port)
assert.Equal(t, gosnmp.Version2c, gs.Version)
assert.Equal(t, "foo", gs.Community)
assert.Equal(t, "udp", gs.Transport)
gsc, err = s.getConnection(1)
require.NoError(t, err)
gs = gsc.(snmp.GosnmpWrapper)
require.Equal(t, "1.2.3.4", gs.Target)
require.EqualValues(t, 161, gs.Port)
require.Equal(t, "udp", gs.Transport)
assert.Equal(t, "1.2.3.4", gs.Target)
assert.EqualValues(t, 161, gs.Port)
assert.Equal(t, "udp", gs.Transport)
gsc, err = s.getConnection(2)
require.NoError(t, err)
gs = gsc.(snmp.GosnmpWrapper)
require.Equal(t, "127.0.0.1", gs.Target)
require.EqualValues(t, 161, gs.Port)
require.Equal(t, "udp", gs.Transport)
assert.Equal(t, "127.0.0.1", gs.Target)
assert.EqualValues(t, 161, gs.Port)
assert.Equal(t, "udp", gs.Transport)
}
func TestGetSNMPConnectionTCP(t *testing.T) {
@ -336,6 +314,9 @@ func TestGetSNMPConnectionTCP(t *testing.T) {
s := &Snmp{
Agents: []string{"tcp://127.0.0.1:56789"},
ClientConfig: snmp.ClientConfig{
Translator: "netsnmp",
},
}
err := s.Init()
require.NoError(t, err)
@ -344,9 +325,9 @@ func TestGetSNMPConnectionTCP(t *testing.T) {
gsc, err := s.getConnection(0)
require.NoError(t, err)
gs := gsc.(snmp.GosnmpWrapper)
require.Equal(t, "127.0.0.1", gs.Target)
require.EqualValues(t, 56789, gs.Port)
require.Equal(t, "tcp", gs.Transport)
assert.Equal(t, "127.0.0.1", gs.Target)
assert.EqualValues(t, 56789, gs.Port)
assert.Equal(t, "tcp", gs.Transport)
wg.Wait()
}
@ -361,10 +342,6 @@ func stubTCPServer(wg *sync.WaitGroup) {
}
func TestGetSNMPConnection_v3(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
s := &Snmp{
Agents: []string{"1.2.3.4"},
ClientConfig: snmp.ClientConfig{
@ -380,6 +357,7 @@ func TestGetSNMPConnection_v3(t *testing.T) {
EngineID: "myengineid",
EngineBoots: 1,
EngineTime: 2,
Translator: "netsnmp",
},
}
err := s.Init()
@ -388,27 +366,23 @@ func TestGetSNMPConnection_v3(t *testing.T) {
gsc, err := s.getConnection(0)
require.NoError(t, err)
gs := gsc.(snmp.GosnmpWrapper)
require.Equal(t, gs.Version, gosnmp.Version3)
assert.Equal(t, gs.Version, gosnmp.Version3)
sp := gs.SecurityParameters.(*gosnmp.UsmSecurityParameters)
require.Equal(t, "1.2.3.4", gsc.Host())
require.EqualValues(t, 20, gs.MaxRepetitions)
require.Equal(t, "mycontext", gs.ContextName)
require.Equal(t, gosnmp.AuthPriv, gs.MsgFlags&gosnmp.AuthPriv)
require.Equal(t, "myuser", sp.UserName)
require.Equal(t, gosnmp.MD5, sp.AuthenticationProtocol)
require.Equal(t, "password123", sp.AuthenticationPassphrase)
require.Equal(t, gosnmp.DES, sp.PrivacyProtocol)
require.Equal(t, "321drowssap", sp.PrivacyPassphrase)
require.Equal(t, "myengineid", sp.AuthoritativeEngineID)
require.EqualValues(t, 1, sp.AuthoritativeEngineBoots)
require.EqualValues(t, 2, sp.AuthoritativeEngineTime)
assert.Equal(t, "1.2.3.4", gsc.Host())
assert.EqualValues(t, 20, gs.MaxRepetitions)
assert.Equal(t, "mycontext", gs.ContextName)
assert.Equal(t, gosnmp.AuthPriv, gs.MsgFlags&gosnmp.AuthPriv)
assert.Equal(t, "myuser", sp.UserName)
assert.Equal(t, gosnmp.MD5, sp.AuthenticationProtocol)
assert.Equal(t, "password123", sp.AuthenticationPassphrase)
assert.Equal(t, gosnmp.DES, sp.PrivacyProtocol)
assert.Equal(t, "321drowssap", sp.PrivacyPassphrase)
assert.Equal(t, "myengineid", sp.AuthoritativeEngineID)
assert.EqualValues(t, 1, sp.AuthoritativeEngineBoots)
assert.EqualValues(t, 2, sp.AuthoritativeEngineTime)
}
func TestGetSNMPConnection_v3_blumenthal(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
testCases := []struct {
Name string
Algorithm gosnmp.SnmpV3PrivProtocol
@ -432,6 +406,7 @@ func TestGetSNMPConnection_v3_blumenthal(t *testing.T) {
EngineID: "myengineid",
EngineBoots: 1,
EngineTime: 2,
Translator: "netsnmp",
},
},
},
@ -453,6 +428,7 @@ func TestGetSNMPConnection_v3_blumenthal(t *testing.T) {
EngineID: "myengineid",
EngineBoots: 1,
EngineTime: 2,
Translator: "netsnmp",
},
},
},
@ -474,6 +450,7 @@ func TestGetSNMPConnection_v3_blumenthal(t *testing.T) {
EngineID: "myengineid",
EngineBoots: 1,
EngineTime: 2,
Translator: "netsnmp",
},
},
},
@ -495,6 +472,7 @@ func TestGetSNMPConnection_v3_blumenthal(t *testing.T) {
EngineID: "myengineid",
EngineBoots: 1,
EngineTime: 2,
Translator: "netsnmp",
},
},
},
@ -509,31 +487,30 @@ func TestGetSNMPConnection_v3_blumenthal(t *testing.T) {
gsc, err := s.getConnection(0)
require.NoError(t, err)
gs := gsc.(snmp.GosnmpWrapper)
require.Equal(t, gs.Version, gosnmp.Version3)
assert.Equal(t, gs.Version, gosnmp.Version3)
sp := gs.SecurityParameters.(*gosnmp.UsmSecurityParameters)
require.Equal(t, "1.2.3.4", gsc.Host())
require.EqualValues(t, 20, gs.MaxRepetitions)
require.Equal(t, "mycontext", gs.ContextName)
require.Equal(t, gosnmp.AuthPriv, gs.MsgFlags&gosnmp.AuthPriv)
require.Equal(t, "myuser", sp.UserName)
require.Equal(t, gosnmp.MD5, sp.AuthenticationProtocol)
require.Equal(t, "password123", sp.AuthenticationPassphrase)
require.Equal(t, tc.Algorithm, sp.PrivacyProtocol)
require.Equal(t, "password123", sp.PrivacyPassphrase)
require.Equal(t, "myengineid", sp.AuthoritativeEngineID)
require.EqualValues(t, 1, sp.AuthoritativeEngineBoots)
require.EqualValues(t, 2, sp.AuthoritativeEngineTime)
assert.Equal(t, "1.2.3.4", gsc.Host())
assert.EqualValues(t, 20, gs.MaxRepetitions)
assert.Equal(t, "mycontext", gs.ContextName)
assert.Equal(t, gosnmp.AuthPriv, gs.MsgFlags&gosnmp.AuthPriv)
assert.Equal(t, "myuser", sp.UserName)
assert.Equal(t, gosnmp.MD5, sp.AuthenticationProtocol)
assert.Equal(t, "password123", sp.AuthenticationPassphrase)
assert.Equal(t, tc.Algorithm, sp.PrivacyProtocol)
assert.Equal(t, "password123", sp.PrivacyPassphrase)
assert.Equal(t, "myengineid", sp.AuthoritativeEngineID)
assert.EqualValues(t, 1, sp.AuthoritativeEngineBoots)
assert.EqualValues(t, 2, sp.AuthoritativeEngineTime)
})
}
}
func TestGetSNMPConnection_caching(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
s := &Snmp{
Agents: []string{"1.2.3.4", "1.2.3.5", "1.2.3.5"},
ClientConfig: snmp.ClientConfig{
Translator: "netsnmp",
},
}
err := s.Init()
require.NoError(t, err)
@ -545,9 +522,9 @@ func TestGetSNMPConnection_caching(t *testing.T) {
require.NoError(t, err)
gs4, err := s.getConnection(2)
require.NoError(t, err)
require.Equal(t, gs1, gs2)
require.NotEqual(t, gs2, gs3)
require.NotEqual(t, gs3, gs4)
assert.Equal(t, gs1, gs2)
assert.NotEqual(t, gs2, gs3)
assert.NotEqual(t, gs3, gs4)
}
func TestGosnmpWrapper_walk_retry(t *testing.T) {
@ -599,9 +576,9 @@ func TestGosnmpWrapper_walk_retry(t *testing.T) {
err = gsw.Walk(".1.0.0", func(_ gosnmp.SnmpPDU) error { return nil })
require.NoError(t, srvr.Close())
wg.Wait()
require.Error(t, err)
require.NotEqual(t, gs.Conn, conn)
require.Equal(t, (gs.Retries+1)*2, reqCount)
assert.Error(t, err)
assert.NotEqual(t, gs.Conn, conn)
assert.Equal(t, (gs.Retries+1)*2, reqCount)
}
func TestGosnmpWrapper_get_retry(t *testing.T) {
@ -652,12 +629,12 @@ func TestGosnmpWrapper_get_retry(t *testing.T) {
_, err = gsw.Get([]string{".1.0.0"})
require.NoError(t, srvr.Close())
wg.Wait()
require.Error(t, err)
require.NotEqual(t, gs.Conn, conn)
require.Equal(t, (gs.Retries+1)*2, reqCount)
assert.Error(t, err)
assert.NotEqual(t, gs.Conn, conn)
assert.Equal(t, (gs.Retries+1)*2, reqCount)
}
func TestTableBuild_walk_noTranslate(t *testing.T) {
func TestTableBuild_walk(t *testing.T) {
tbl := Table{
Name: "mytable",
IndexAsTag: true,
@ -686,12 +663,23 @@ func TestTableBuild_walk_noTranslate(t *testing.T) {
Oid: ".1.0.0.2.1.5",
OidIndexLength: 1,
},
{
Name: "myfield6",
Oid: ".1.0.0.0.1.6",
Translate: true,
},
{
Name: "myfield7",
Oid: ".1.0.0.0.1.6",
Translate: false,
},
},
}
tb, err := tbl.Build(tsc, true)
tb, err := tbl.Build(tsc, true, NewNetsnmpTranslator())
require.NoError(t, err)
require.Equal(t, tb.Name, "mytable")
assert.Equal(t, tb.Name, "mytable")
rtr1 := RTableRow{
Tags: map[string]string{
"myfield1": "foo",
@ -702,6 +690,8 @@ func TestTableBuild_walk_noTranslate(t *testing.T) {
"myfield3": float64(0.123),
"myfield4": 11,
"myfield5": 11,
"myfield6": "testTableEntry.7",
"myfield7": ".1.0.0.0.1.7",
},
}
rtr2 := RTableRow{
@ -733,85 +723,11 @@ func TestTableBuild_walk_noTranslate(t *testing.T) {
"myfield3": float64(9.999),
},
}
require.Len(t, tb.Rows, 4)
require.Contains(t, tb.Rows, rtr1)
require.Contains(t, tb.Rows, rtr2)
require.Contains(t, tb.Rows, rtr3)
require.Contains(t, tb.Rows, rtr4)
}
func TestTableBuild_walk_Translate(t *testing.T) {
testDataPath, err := filepath.Abs("./testdata")
require.NoError(t, err)
s := &Snmp{
ClientConfig: snmp.ClientConfig{
Path: []string{testDataPath},
},
}
err = s.Init()
require.NoError(t, err)
tbl := Table{
Name: "atTable",
IndexAsTag: true,
Fields: []Field{
{
Name: "ifIndex",
Oid: "1.3.6.1.2.1.3.1.1.1",
IsTag: true,
},
{
Name: "atPhysAddress",
Oid: "1.3.6.1.2.1.3.1.1.2",
Translate: false,
},
{
Name: "atNetAddress",
Oid: "1.3.6.1.2.1.3.1.1.3",
Translate: true,
},
},
}
err = tbl.Init()
require.NoError(t, err)
tb, err := tbl.Build(tsc, true)
require.NoError(t, err)
require.Equal(t, tb.Name, "atTable")
rtr1 := RTableRow{
Tags: map[string]string{
"ifIndex": "foo",
"index": "0",
},
Fields: map[string]interface{}{
"atPhysAddress": 1,
"atNetAddress": "atNetAddress",
},
}
rtr2 := RTableRow{
Tags: map[string]string{
"ifIndex": "bar",
"index": "1",
},
Fields: map[string]interface{}{
"atPhysAddress": 2,
},
}
rtr3 := RTableRow{
Tags: map[string]string{
"index": "2",
},
Fields: map[string]interface{}{
"atPhysAddress": 0,
},
}
require.Len(t, tb.Rows, 3)
require.Contains(t, tb.Rows, rtr1)
require.Contains(t, tb.Rows, rtr2)
require.Contains(t, tb.Rows, rtr3)
assert.Len(t, tb.Rows, 4)
assert.Contains(t, tb.Rows, rtr1)
assert.Contains(t, tb.Rows, rtr2)
assert.Contains(t, tb.Rows, rtr3)
assert.Contains(t, tb.Rows, rtr4)
}
func TestTableBuild_noWalk(t *testing.T) {
@ -843,15 +759,15 @@ func TestTableBuild_noWalk(t *testing.T) {
},
}
tb, err := tbl.Build(tsc, false)
tb, err := tbl.Build(tsc, false, NewNetsnmpTranslator())
require.NoError(t, err)
rtr := RTableRow{
Tags: map[string]string{"myfield1": "baz", "myfield3": "234"},
Fields: map[string]interface{}{"myfield2": 234},
}
require.Len(t, tb.Rows, 1)
require.Contains(t, tb.Rows, rtr)
assert.Len(t, tb.Rows, 1)
assert.Contains(t, tb.Rows, rtr)
}
func TestGather(t *testing.T) {
@ -899,21 +815,21 @@ func TestGather(t *testing.T) {
require.Len(t, acc.Metrics, 2)
m := acc.Metrics[0]
require.Equal(t, "mytable", m.Measurement)
require.Equal(t, "tsc", m.Tags[s.AgentHostTag])
require.Equal(t, "baz", m.Tags["myfield1"])
require.Len(t, m.Fields, 2)
require.Equal(t, 234, m.Fields["myfield2"])
require.Equal(t, "baz", m.Fields["myfield3"])
require.False(t, tstart.After(m.Time))
require.False(t, tstop.Before(m.Time))
assert.Equal(t, "mytable", m.Measurement)
assert.Equal(t, "tsc", m.Tags[s.AgentHostTag])
assert.Equal(t, "baz", m.Tags["myfield1"])
assert.Len(t, m.Fields, 2)
assert.Equal(t, 234, m.Fields["myfield2"])
assert.Equal(t, "baz", m.Fields["myfield3"])
assert.True(t, !tstart.After(m.Time))
assert.True(t, !tstop.Before(m.Time))
m2 := acc.Metrics[1]
require.Equal(t, "myOtherTable", m2.Measurement)
require.Equal(t, "tsc", m2.Tags[s.AgentHostTag])
require.Equal(t, "baz", m2.Tags["myfield1"])
require.Len(t, m2.Fields, 1)
require.Equal(t, 123456, m2.Fields["myOtherField"])
assert.Equal(t, "myOtherTable", m2.Measurement)
assert.Equal(t, "tsc", m2.Tags[s.AgentHostTag])
assert.Equal(t, "baz", m2.Tags["myfield1"])
assert.Len(t, m2.Fields, 1)
assert.Equal(t, 123456, m2.Fields["myOtherField"])
}
func TestGather_host(t *testing.T) {
@ -943,7 +859,7 @@ func TestGather_host(t *testing.T) {
require.Len(t, acc.Metrics, 1)
m := acc.Metrics[0]
require.Equal(t, "baz", m.Tags["host"])
assert.Equal(t, "baz", m.Tags["host"])
}
func TestFieldConvert(t *testing.T) {
@ -976,7 +892,7 @@ func TestFieldConvert(t *testing.T) {
{[]byte("123123123123"), "int", int64(123123123123)},
{float32(12.3), "int", int64(12)},
{float64(12.3), "int", int64(12)},
{123, "int", int64(123)},
{int(123), "int", int64(123)},
{int8(123), "int", int64(123)},
{int16(123), "int", int64(123)},
{int32(123), "int", int64(123)},
@ -1001,23 +917,25 @@ func TestFieldConvert(t *testing.T) {
for _, tc := range testTable {
act, err := fieldConvert(tc.conv, tc.input)
require.NoError(t, err, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected)
require.EqualValues(t, tc.expected, act, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected)
if !assert.NoError(t, err, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected) {
continue
}
assert.EqualValues(t, tc.expected, act, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected)
}
}
func TestSnmpTranslateCache_miss(t *testing.T) {
snmpTranslateCaches = nil
oid := "IF-MIB::ifPhysAddress.1"
mibName, oidNum, oidText, conversion, _, err := SnmpTranslate(oid)
require.Len(t, snmpTranslateCaches, 1)
mibName, oidNum, oidText, conversion, err := NewNetsnmpTranslator().SnmpTranslate(oid)
assert.Len(t, snmpTranslateCaches, 1)
stc := snmpTranslateCaches[oid]
require.NotNil(t, stc)
require.Equal(t, mibName, stc.mibName)
require.Equal(t, oidNum, stc.oidNum)
require.Equal(t, oidText, stc.oidText)
require.Equal(t, conversion, stc.conversion)
require.Equal(t, err, stc.err)
assert.Equal(t, mibName, stc.mibName)
assert.Equal(t, oidNum, stc.oidNum)
assert.Equal(t, oidText, stc.oidText)
assert.Equal(t, conversion, stc.conversion)
assert.Equal(t, err, stc.err)
}
func TestSnmpTranslateCache_hit(t *testing.T) {
@ -1030,27 +948,27 @@ func TestSnmpTranslateCache_hit(t *testing.T) {
err: fmt.Errorf("e"),
},
}
mibName, oidNum, oidText, conversion, _, err := SnmpTranslate("foo")
require.Equal(t, "a", mibName)
require.Equal(t, "b", oidNum)
require.Equal(t, "c", oidText)
require.Equal(t, "d", conversion)
require.Equal(t, fmt.Errorf("e"), err)
mibName, oidNum, oidText, conversion, err := NewNetsnmpTranslator().SnmpTranslate("foo")
assert.Equal(t, "a", mibName)
assert.Equal(t, "b", oidNum)
assert.Equal(t, "c", oidText)
assert.Equal(t, "d", conversion)
assert.Equal(t, fmt.Errorf("e"), err)
snmpTranslateCaches = nil
}
func TestSnmpTableCache_miss(t *testing.T) {
snmpTableCaches = nil
oid := ".1.0.0.0"
mibName, oidNum, oidText, fields, err := snmpTable(oid)
require.Len(t, snmpTableCaches, 1)
mibName, oidNum, oidText, fields, err := NewNetsnmpTranslator().SnmpTable(oid)
assert.Len(t, snmpTableCaches, 1)
stc := snmpTableCaches[oid]
require.NotNil(t, stc)
require.Equal(t, mibName, stc.mibName)
require.Equal(t, oidNum, stc.oidNum)
require.Equal(t, oidText, stc.oidText)
require.Equal(t, fields, stc.fields)
require.Equal(t, err, stc.err)
assert.Equal(t, mibName, stc.mibName)
assert.Equal(t, oidNum, stc.oidNum)
assert.Equal(t, oidText, stc.oidText)
assert.Equal(t, fields, stc.fields)
assert.Equal(t, err, stc.err)
}
func TestSnmpTableCache_hit(t *testing.T) {
@ -1063,12 +981,12 @@ func TestSnmpTableCache_hit(t *testing.T) {
err: fmt.Errorf("e"),
},
}
mibName, oidNum, oidText, fields, err := snmpTable("foo")
require.Equal(t, "a", mibName)
require.Equal(t, "b", oidNum)
require.Equal(t, "c", oidText)
require.Equal(t, []Field{{Name: "d"}}, fields)
require.Equal(t, fmt.Errorf("e"), err)
mibName, oidNum, oidText, fields, err := NewNetsnmpTranslator().SnmpTable("foo")
assert.Equal(t, "a", mibName)
assert.Equal(t, "b", oidNum)
assert.Equal(t, "c", oidText)
assert.Equal(t, []Field{{Name: "d"}}, fields)
assert.Equal(t, fmt.Errorf("e"), err)
}
func TestTableJoin_walk(t *testing.T) {
@ -1104,10 +1022,10 @@ func TestTableJoin_walk(t *testing.T) {
},
}
tb, err := tbl.Build(tsc, true)
tb, err := tbl.Build(tsc, true, NewNetsnmpTranslator())
require.NoError(t, err)
require.Equal(t, tb.Name, "mytable")
assert.Equal(t, tb.Name, "mytable")
rtr1 := RTableRow{
Tags: map[string]string{
"myfield1": "instance",
@ -1141,10 +1059,10 @@ func TestTableJoin_walk(t *testing.T) {
"myfield3": 3,
},
}
require.Len(t, tb.Rows, 3)
require.Contains(t, tb.Rows, rtr1)
require.Contains(t, tb.Rows, rtr2)
require.Contains(t, tb.Rows, rtr3)
assert.Len(t, tb.Rows, 3)
assert.Contains(t, tb.Rows, rtr1)
assert.Contains(t, tb.Rows, rtr2)
assert.Contains(t, tb.Rows, rtr3)
}
func TestTableOuterJoin_walk(t *testing.T) {
@ -1181,10 +1099,10 @@ func TestTableOuterJoin_walk(t *testing.T) {
},
}
tb, err := tbl.Build(tsc, true)
tb, err := tbl.Build(tsc, true, NewNetsnmpTranslator())
require.NoError(t, err)
require.Equal(t, tb.Name, "mytable")
assert.Equal(t, tb.Name, "mytable")
rtr1 := RTableRow{
Tags: map[string]string{
"myfield1": "instance",
@ -1227,11 +1145,11 @@ func TestTableOuterJoin_walk(t *testing.T) {
"myfield5": 1,
},
}
require.Len(t, tb.Rows, 4)
require.Contains(t, tb.Rows, rtr1)
require.Contains(t, tb.Rows, rtr2)
require.Contains(t, tb.Rows, rtr3)
require.Contains(t, tb.Rows, rtr4)
assert.Len(t, tb.Rows, 4)
assert.Contains(t, tb.Rows, rtr1)
assert.Contains(t, tb.Rows, rtr2)
assert.Contains(t, tb.Rows, rtr3)
assert.Contains(t, tb.Rows, rtr4)
}
func TestTableJoinNoIndexAsTag_walk(t *testing.T) {
@ -1267,10 +1185,10 @@ func TestTableJoinNoIndexAsTag_walk(t *testing.T) {
},
}
tb, err := tbl.Build(tsc, true)
tb, err := tbl.Build(tsc, true, NewNetsnmpTranslator())
require.NoError(t, err)
require.Equal(t, tb.Name, "mytable")
assert.Equal(t, tb.Name, "mytable")
rtr1 := RTableRow{
Tags: map[string]string{
"myfield1": "instance",
@ -1304,35 +1222,8 @@ func TestTableJoinNoIndexAsTag_walk(t *testing.T) {
"myfield3": 3,
},
}
require.Len(t, tb.Rows, 3)
require.Contains(t, tb.Rows, rtr1)
require.Contains(t, tb.Rows, rtr2)
require.Contains(t, tb.Rows, rtr3)
}
func BenchmarkMibLoading(b *testing.B) {
log := testutil.Logger{}
path := []string{"testdata"}
for i := 0; i < b.N; i++ {
err := snmp.LoadMibsFromPath(path, log, &snmp.GosmiMibLoader{})
require.NoError(b, err)
}
}
func TestCanNotParse(t *testing.T) {
s := &Snmp{
Fields: []Field{
{Oid: "RFC1213-MIB::"},
},
}
err := s.Init()
require.Error(t, err)
}
func TestMissingMibPath(t *testing.T) {
log := testutil.Logger{}
path := []string{"non-existing-directory"}
err := snmp.LoadMibsFromPath(path, log, &snmp.GosmiMibLoader{})
require.NoError(t, err)
assert.Len(t, tb.Rows, 3)
assert.Contains(t, tb.Rows, rtr1)
assert.Contains(t, tb.Rows, rtr2)
assert.Contains(t, tb.Rows, rtr3)
}

View File

@ -25,6 +25,8 @@ path onto the global path variable
# service_address = "udp://:162"
##
## Path to mib files
## Used by the gosmi translator.
## To add paths when translating with netsnmp, use the MIBDIRS environment variable
# path = ["/usr/share/snmp/mibs"]
##
## Deprecated in 1.20.0; no longer running snmptranslate

View File

@ -0,0 +1,21 @@
package snmp_trap
import (
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/snmp"
)
type gosmiTranslator struct {
}
func (t *gosmiTranslator) lookup(oid string) (snmp.MibEntry, error) {
return snmp.TrapLookup(oid)
}
func newGosmiTranslator(paths []string, log telegraf.Logger) (*gosmiTranslator, error) {
err := snmp.LoadMibsFromPath(paths, log, &snmp.GosmiMibLoader{})
if err == nil {
return &gosmiTranslator{}, nil
}
return nil, err
}

View File

@ -0,0 +1,89 @@
package snmp_trap
import (
"bufio"
"bytes"
"fmt"
"os/exec"
"strings"
"sync"
"time"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/internal/snmp"
)
type execer func(config.Duration, string, ...string) ([]byte, error)
func realExecCmd(timeout config.Duration, arg0 string, args ...string) ([]byte, error) {
cmd := exec.Command(arg0, args...)
var out bytes.Buffer
cmd.Stdout = &out
err := internal.RunTimeout(cmd, time.Duration(timeout))
if err != nil {
return nil, err
}
return out.Bytes(), nil
}
type netsnmpTranslator struct {
// Each translator has its own cache and each plugin instance has
// its own translator. This is different than the snmp plugin
// which has one global cache.
//
// We may want to change snmp_trap to
// have a global cache although it's not as important for
// snmp_trap to be global because there is usually only one
// instance, while it's common to configure many snmp instances.
cacheLock sync.Mutex
cache map[string]snmp.MibEntry
execCmd execer
Timeout config.Duration
}
func (s *netsnmpTranslator) lookup(oid string) (e snmp.MibEntry, err error) {
s.cacheLock.Lock()
defer s.cacheLock.Unlock()
var ok bool
if e, ok = s.cache[oid]; !ok {
// cache miss. exec snmptranslate
e, err = s.snmptranslate(oid)
if err == nil {
s.cache[oid] = e
}
return e, err
}
return e, nil
}
func (s *netsnmpTranslator) snmptranslate(oid string) (e snmp.MibEntry, err error) {
var out []byte
out, err = s.execCmd(s.Timeout, "snmptranslate", "-Td", "-Ob", "-m", "all", oid)
if err != nil {
return e, err
}
scanner := bufio.NewScanner(bytes.NewBuffer(out))
ok := scanner.Scan()
if err = scanner.Err(); !ok && err != nil {
return e, err
}
e.OidText = scanner.Text()
i := strings.Index(e.OidText, "::")
if i == -1 {
return e, fmt.Errorf("not found")
}
e.MibName = e.OidText[:i]
e.OidText = e.OidText[i+2:]
return e, nil
}
func newNetsnmpTranslator() *netsnmpTranslator {
return &netsnmpTranslator{
execCmd: realExecCmd,
}
}

View File

@ -15,10 +15,15 @@ import (
"github.com/gosnmp/gosnmp"
)
type translator interface {
lookup(oid string) (snmp.MibEntry, error)
}
type SnmpTrap struct {
ServiceAddress string `toml:"service_address"`
Timeout config.Duration `toml:"timeout" deprecated:"1.20.0;unused option"`
Version string `toml:"version"`
Translator string `toml:"-"`
Path []string `toml:"path"`
// Settings for version 3
@ -32,15 +37,16 @@ type SnmpTrap struct {
PrivProtocol string `toml:"priv_protocol"`
PrivPassword string `toml:"priv_password"`
acc telegraf.Accumulator
listener *gosnmp.TrapListener
timeFunc func() time.Time
lookupFunc func(string) (snmp.MibEntry, error)
errCh chan error
acc telegraf.Accumulator
listener *gosnmp.TrapListener
timeFunc func() time.Time
errCh chan error
makeHandlerWrapper func(gosnmp.TrapHandlerFunc) gosnmp.TrapHandlerFunc
Log telegraf.Logger `toml:"-"`
translator translator //nolint:revive
}
var sampleConfig = `
@ -90,7 +96,6 @@ func init() {
inputs.Add("snmp_trap", func() telegraf.Input {
return &SnmpTrap{
timeFunc: time.Now,
lookupFunc: snmp.TrapLookup,
ServiceAddress: "udp://:162",
Path: []string{"/usr/share/snmp/mibs"},
Version: "2c",
@ -98,8 +103,24 @@ func init() {
})
}
func (s *SnmpTrap) SetTranslator(name string) {
s.Translator = name
}
func (s *SnmpTrap) Init() error {
err := snmp.LoadMibsFromPath(s.Path, s.Log, &snmp.GosmiMibLoader{})
var err error
switch s.Translator {
case "gosmi":
s.translator, err = newGosmiTranslator(s.Path, s.Log)
if err != nil {
return err
}
case "netsnmp":
s.translator = newNetsnmpTranslator()
default:
return fmt.Errorf("invalid translator value")
}
if err != nil {
s.Log.Errorf("Could not get path %v", err)
}
@ -259,7 +280,7 @@ func makeTrapHandler(s *SnmpTrap) gosnmp.TrapHandlerFunc {
}
if trapOid != "" {
e, err := s.lookupFunc(trapOid)
e, err := s.translator.lookup(trapOid)
if err != nil {
s.Log.Errorf("Error resolving V1 OID, oid=%s, source=%s: %v", trapOid, tags["source"], err)
return
@ -297,7 +318,7 @@ func makeTrapHandler(s *SnmpTrap) gosnmp.TrapHandlerFunc {
var e snmp.MibEntry
var err error
e, err = s.lookupFunc(val)
e, err = s.translator.lookup(val)
if nil != err {
s.Log.Errorf("Error resolving value OID, oid=%s, source=%s: %v", val, tags["source"], err)
return
@ -315,7 +336,7 @@ func makeTrapHandler(s *SnmpTrap) gosnmp.TrapHandlerFunc {
value = v.Value
}
e, err := s.lookupFunc(v.Name)
e, err := s.translator.lookup(v.Name)
if nil != err {
s.Log.Errorf("Error resolving OID oid=%s, source=%s: %v", v.Name, tags["source"], err)
return

View File

@ -16,6 +16,28 @@ import (
"github.com/influxdata/telegraf/testutil"
)
type entry struct {
oid string
e snmp.MibEntry
}
type testTranslator struct {
entries []entry
}
func (t *testTranslator) lookup(input string) (snmp.MibEntry, error) {
for _, entry := range t.entries {
if input == entry.oid {
return snmp.MibEntry{MibName: entry.e.MibName, OidText: entry.e.OidText}, nil
}
}
return snmp.MibEntry{}, fmt.Errorf("unexpected oid")
}
func newTestTranslator(entries []entry) *testTranslator {
return &testTranslator{entries: entries}
}
func newMsgFlagsV3(secLevel string) gosnmp.SnmpV3MsgFlags {
var msgFlags gosnmp.SnmpV3MsgFlags
switch strings.ToLower(secLevel) {
@ -130,11 +152,6 @@ func TestReceiveTrap(t *testing.T) {
now := uint32(123123123)
fakeTime := time.Unix(456456456, 456)
type entry struct {
oid string
e snmp.MibEntry
}
// If the first pdu isn't type TimeTicks, gosnmp.SendTrap() will
// prepend one with time.Now()
var tests = []struct {
@ -1260,14 +1277,6 @@ func TestReceiveTrap(t *testing.T) {
timeFunc: func() time.Time {
return fakeTime
},
lookupFunc: func(input string) (snmp.MibEntry, error) {
for _, entry := range tt.entries {
if input == entry.oid {
return snmp.MibEntry{MibName: entry.e.MibName, OidText: entry.e.OidText}, nil
}
}
return snmp.MibEntry{}, fmt.Errorf("unexpected oid")
},
//if cold start be answer otherwise err
Log: testutil.Logger{},
Version: tt.version.String(),
@ -1277,10 +1286,14 @@ func TestReceiveTrap(t *testing.T) {
AuthPassword: tt.authPass,
PrivProtocol: tt.privProto,
PrivPassword: tt.privPass,
Translator: "netsnmp",
}
require.NoError(t, s.Init())
//inject test translator
s.translator = newTestTranslator(tt.entries)
var acc testutil.Accumulator
require.Nil(t, s.Start(&acc))
defer s.Stop()

View File

@ -107,6 +107,8 @@ type IfName struct {
getMapRemote mapFunc
makeTable makeTableFunc
translator si.Translator
}
const minRetry = 5 * time.Minute
@ -121,7 +123,7 @@ func (d *IfName) Description() string {
func (d *IfName) Init() error {
d.getMapRemote = d.getMapRemoteNoMock
d.makeTable = makeTableNoMock
d.makeTable = d.makeTableNoMock
c := NewTTLCache(time.Duration(d.CacheTTL), d.CacheSize)
d.cache = &c
@ -132,6 +134,10 @@ func (d *IfName) Init() error {
return fmt.Errorf("parsing SNMP client config: %w", err)
}
// Since OIDs in this plugin are always numeric there is no need
// to translate.
d.translator = si.NewNetsnmpTranslator()
return nil
}
@ -309,11 +315,11 @@ func (d *IfName) getMapRemoteNoMock(agent string) (nameMap, error) {
//try ifXtable and ifName first. if that fails, fall back to
//ifTable and ifDescr
var m nameMap
if m, err = buildMap(gs, d.ifXTable); err == nil {
if m, err = d.buildMap(gs, d.ifXTable); err == nil {
return m, nil
}
if m, err = buildMap(gs, d.ifTable); err == nil {
if m, err = d.buildMap(gs, d.ifTable); err == nil {
return m, nil
}
@ -340,7 +346,7 @@ func init() {
})
}
func makeTableNoMock(oid string) (*si.Table, error) {
func (d *IfName) makeTableNoMock(oid string) (*si.Table, error) {
var err error
tab := si.Table{
Name: "ifTable",
@ -350,7 +356,7 @@ func makeTableNoMock(oid string) (*si.Table, error) {
},
}
err = tab.Init()
err = tab.Init(d.translator)
if err != nil {
//Init already wraps
return nil, err
@ -359,10 +365,10 @@ func makeTableNoMock(oid string) (*si.Table, error) {
return &tab, nil
}
func buildMap(gs snmp.GosnmpWrapper, tab *si.Table) (nameMap, error) {
func (d *IfName) buildMap(gs snmp.GosnmpWrapper, tab *si.Table) (nameMap, error) {
var err error
rtab, err := tab.Build(gs, true)
rtab, err := tab.Build(gs, true, d.translator)
if err != nil {
//Build already wraps
return nil, err

View File

@ -36,7 +36,7 @@ func TestTable(t *testing.T) {
require.NoError(t, err)
// Could use ifIndex but oid index is always the same
m, err := buildMap(gs, tab)
m, err := d.buildMap(gs, tab)
require.NoError(t, err)
require.NotEmpty(t, m)
}