chore(snmp): Refactor SNMP translation and data-structures (#14830)
This commit is contained in:
parent
4111cee421
commit
d018363261
|
|
@ -0,0 +1,259 @@
|
||||||
|
package snmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gosnmp/gosnmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Field holds the configuration for a Field to look up.
|
||||||
|
type Field struct {
|
||||||
|
// Name will be the name of the field.
|
||||||
|
Name string
|
||||||
|
// OID is prefix for this field. The plugin will perform a walk through all
|
||||||
|
// OIDs with this as their parent. For each value found, the plugin will strip
|
||||||
|
// off the OID prefix, and use the remainder as the index. For multiple fields
|
||||||
|
// to show up in the same row, they must share the same index.
|
||||||
|
Oid string
|
||||||
|
// OidIndexSuffix is the trailing sub-identifier on a table record OID that will be stripped off to get the record's index.
|
||||||
|
OidIndexSuffix string
|
||||||
|
// OidIndexLength specifies the length of the index in OID path segments. It can be used to remove sub-identifiers that vary in content or length.
|
||||||
|
OidIndexLength int
|
||||||
|
// IsTag controls whether this OID is output as a tag or a value.
|
||||||
|
IsTag bool
|
||||||
|
// Conversion controls any type conversion that is done on the value.
|
||||||
|
// "float"/"float(0)" will convert the value into a float.
|
||||||
|
// "float(X)" will convert the value into a float, and then move the decimal before Xth right-most digit.
|
||||||
|
// "int" will convert the value into an integer.
|
||||||
|
// "hwaddr" will convert a 6-byte string to a MAC address.
|
||||||
|
// "ipaddr" will convert the value to an IPv4 or IPv6 address.
|
||||||
|
// "enum"/"enum(1)" will convert the value according to its syntax. (Only supported with gosmi translator)
|
||||||
|
Conversion string
|
||||||
|
// Translate tells if the value of the field should be snmptranslated
|
||||||
|
Translate bool
|
||||||
|
// Secondary index table allows to merge data from two tables with different index
|
||||||
|
// that this filed will be used to join them. There can be only one secondary index table.
|
||||||
|
SecondaryIndexTable bool
|
||||||
|
// This field is using secondary index, and will be later merged with primary index
|
||||||
|
// using SecondaryIndexTable. SecondaryIndexTable and SecondaryIndexUse are exclusive.
|
||||||
|
SecondaryIndexUse bool
|
||||||
|
// Controls if entries from secondary table should be added or not if joining
|
||||||
|
// index is present or not. I set to true, means that join is outer, and
|
||||||
|
// index is prepended with "Secondary." for missing values to avoid overlapping
|
||||||
|
// indexes from both tables.
|
||||||
|
// Can be set per field or globally with SecondaryIndexTable, global true overrides
|
||||||
|
// per field false.
|
||||||
|
SecondaryOuterJoin bool
|
||||||
|
|
||||||
|
initialized bool
|
||||||
|
translator Translator
|
||||||
|
}
|
||||||
|
|
||||||
|
// init() converts OID names to numbers, and sets the .Name attribute if unset.
|
||||||
|
func (f *Field) Init(tr Translator) error {
|
||||||
|
if f.initialized {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f.translator = tr
|
||||||
|
|
||||||
|
// check if oid needs translation or name is not set
|
||||||
|
if strings.ContainsAny(f.Oid, ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") || f.Name == "" {
|
||||||
|
_, oidNum, oidText, conversion, err := f.translator.SnmpTranslate(f.Oid)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("translating: %w", err)
|
||||||
|
}
|
||||||
|
f.Oid = oidNum
|
||||||
|
if f.Name == "" {
|
||||||
|
f.Name = oidText
|
||||||
|
}
|
||||||
|
if f.Conversion == "" {
|
||||||
|
f.Conversion = conversion
|
||||||
|
}
|
||||||
|
//TODO use textual convention conversion from the MIB
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.SecondaryIndexTable && f.SecondaryIndexUse {
|
||||||
|
return errors.New("SecondaryIndexTable and UseSecondaryIndex are exclusive")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.SecondaryIndexTable && !f.SecondaryIndexUse && f.SecondaryOuterJoin {
|
||||||
|
return errors.New("SecondaryOuterJoin set to true, but field is not being used in join")
|
||||||
|
}
|
||||||
|
|
||||||
|
f.initialized = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldConvert converts from any type according to the conv specification
|
||||||
|
func (f *Field) Convert(ent gosnmp.SnmpPDU) (v interface{}, err error) {
|
||||||
|
if f.Conversion == "" {
|
||||||
|
if bs, ok := ent.Value.([]byte); ok {
|
||||||
|
return string(bs), nil
|
||||||
|
}
|
||||||
|
return ent.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var d int
|
||||||
|
if _, err := fmt.Sscanf(f.Conversion, "float(%d)", &d); err == nil || f.Conversion == "float" {
|
||||||
|
v = ent.Value
|
||||||
|
switch vt := v.(type) {
|
||||||
|
case float32:
|
||||||
|
v = float64(vt) / math.Pow10(d)
|
||||||
|
case float64:
|
||||||
|
v = vt / math.Pow10(d)
|
||||||
|
case int:
|
||||||
|
v = float64(vt) / math.Pow10(d)
|
||||||
|
case int8:
|
||||||
|
v = float64(vt) / math.Pow10(d)
|
||||||
|
case int16:
|
||||||
|
v = float64(vt) / math.Pow10(d)
|
||||||
|
case int32:
|
||||||
|
v = float64(vt) / math.Pow10(d)
|
||||||
|
case int64:
|
||||||
|
v = float64(vt) / math.Pow10(d)
|
||||||
|
case uint:
|
||||||
|
v = float64(vt) / math.Pow10(d)
|
||||||
|
case uint8:
|
||||||
|
v = float64(vt) / math.Pow10(d)
|
||||||
|
case uint16:
|
||||||
|
v = float64(vt) / math.Pow10(d)
|
||||||
|
case uint32:
|
||||||
|
v = float64(vt) / math.Pow10(d)
|
||||||
|
case uint64:
|
||||||
|
v = float64(vt) / math.Pow10(d)
|
||||||
|
case []byte:
|
||||||
|
vf, _ := strconv.ParseFloat(string(vt), 64)
|
||||||
|
v = vf / math.Pow10(d)
|
||||||
|
case string:
|
||||||
|
vf, _ := strconv.ParseFloat(vt, 64)
|
||||||
|
v = vf / math.Pow10(d)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Conversion == "int" {
|
||||||
|
v = ent.Value
|
||||||
|
switch vt := v.(type) {
|
||||||
|
case float32:
|
||||||
|
v = int64(vt)
|
||||||
|
case float64:
|
||||||
|
v = int64(vt)
|
||||||
|
case int:
|
||||||
|
v = int64(vt)
|
||||||
|
case int8:
|
||||||
|
v = int64(vt)
|
||||||
|
case int16:
|
||||||
|
v = int64(vt)
|
||||||
|
case int32:
|
||||||
|
v = int64(vt)
|
||||||
|
case int64:
|
||||||
|
v = vt
|
||||||
|
case uint:
|
||||||
|
v = int64(vt)
|
||||||
|
case uint8:
|
||||||
|
v = int64(vt)
|
||||||
|
case uint16:
|
||||||
|
v = int64(vt)
|
||||||
|
case uint32:
|
||||||
|
v = int64(vt)
|
||||||
|
case uint64:
|
||||||
|
v = int64(vt)
|
||||||
|
case []byte:
|
||||||
|
v, _ = strconv.ParseInt(string(vt), 10, 64)
|
||||||
|
case string:
|
||||||
|
v, _ = strconv.ParseInt(vt, 10, 64)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Conversion == "hwaddr" {
|
||||||
|
switch vt := ent.Value.(type) {
|
||||||
|
case string:
|
||||||
|
v = net.HardwareAddr(vt).String()
|
||||||
|
case []byte:
|
||||||
|
v = net.HardwareAddr(vt).String()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid type (%T) for hwaddr conversion", v)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.Split(f.Conversion, ":")
|
||||||
|
if split[0] == "hextoint" && len(split) == 3 {
|
||||||
|
endian := split[1]
|
||||||
|
bit := split[2]
|
||||||
|
|
||||||
|
bv, ok := ent.Value.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return ent.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch endian {
|
||||||
|
case "LittleEndian":
|
||||||
|
switch bit {
|
||||||
|
case "uint64":
|
||||||
|
v = binary.LittleEndian.Uint64(bv)
|
||||||
|
case "uint32":
|
||||||
|
v = binary.LittleEndian.Uint32(bv)
|
||||||
|
case "uint16":
|
||||||
|
v = binary.LittleEndian.Uint16(bv)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid bit value (%s) for hex to int conversion", bit)
|
||||||
|
}
|
||||||
|
case "BigEndian":
|
||||||
|
switch bit {
|
||||||
|
case "uint64":
|
||||||
|
v = binary.BigEndian.Uint64(bv)
|
||||||
|
case "uint32":
|
||||||
|
v = binary.BigEndian.Uint32(bv)
|
||||||
|
case "uint16":
|
||||||
|
v = binary.BigEndian.Uint16(bv)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid bit value (%s) for hex to int conversion", bit)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid Endian value (%s) for hex to int conversion", endian)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Conversion == "ipaddr" {
|
||||||
|
var ipbs []byte
|
||||||
|
|
||||||
|
switch vt := ent.Value.(type) {
|
||||||
|
case string:
|
||||||
|
ipbs = []byte(vt)
|
||||||
|
case []byte:
|
||||||
|
ipbs = vt
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid type (%T) for ipaddr conversion", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(ipbs) {
|
||||||
|
case 4, 16:
|
||||||
|
v = net.IP(ipbs).String()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid length (%d) for ipaddr conversion", len(ipbs))
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Conversion == "enum" {
|
||||||
|
return f.translator.SnmpFormatEnum(ent.Name, ent.Value, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Conversion == "enum(1)" {
|
||||||
|
return f.translator.SnmpFormatEnum(ent.Name, ent.Value, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("invalid conversion type %q", f.Conversion)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
package snmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sleepinggenius2/gosmi"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// must init, append path for each directory, load module for every file
|
||||||
|
// or gosmi will fail without saying why
|
||||||
|
var m sync.Mutex
|
||||||
|
var once sync.Once
|
||||||
|
var cache = make(map[string]bool)
|
||||||
|
|
||||||
|
type MibLoader interface {
|
||||||
|
// appendPath takes the path of a directory
|
||||||
|
appendPath(path string)
|
||||||
|
|
||||||
|
// loadModule takes the name of a file in one of the
|
||||||
|
// directories. Basename only, no relative or absolute path
|
||||||
|
loadModule(path string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type GosmiMibLoader struct{}
|
||||||
|
|
||||||
|
func (*GosmiMibLoader) appendPath(path string) {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
gosmi.AppendPath(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GosmiMibLoader) loadModule(path string) error {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
_, err := gosmi.LoadModule(path)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// will give all found folders to gosmi and load in all modules found in the folders
|
||||||
|
func LoadMibsFromPath(paths []string, log telegraf.Logger, loader MibLoader) error {
|
||||||
|
folders, err := walkPaths(paths, log)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, path := range folders {
|
||||||
|
loader.appendPath(path)
|
||||||
|
modules, err := os.ReadDir(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Can't read directory %v", modules)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range modules {
|
||||||
|
info, err := entry.Info()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Couldn't get info for %v: %v", entry.Name(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if info.Mode()&os.ModeSymlink != 0 {
|
||||||
|
symlink := filepath.Join(path, info.Name())
|
||||||
|
target, err := filepath.EvalSymlinks(symlink)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Couldn't evaluate symbolic links for %v: %v", symlink, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//replace symlink's info with the target's info
|
||||||
|
info, err = os.Lstat(target)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Couldn't stat target %v: %v", target, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if info.Mode().IsRegular() {
|
||||||
|
err := loader.loadModule(info.Name())
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Couldn't load module %v: %v", info.Name(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// should walk the paths given and find all folders
|
||||||
|
func walkPaths(paths []string, log telegraf.Logger) ([]string, error) {
|
||||||
|
once.Do(gosmi.Init)
|
||||||
|
folders := []string{}
|
||||||
|
|
||||||
|
for _, mibPath := range paths {
|
||||||
|
// Check if we loaded that path already and skip it if so
|
||||||
|
m.Lock()
|
||||||
|
cached := cache[mibPath]
|
||||||
|
cache[mibPath] = true
|
||||||
|
m.Unlock()
|
||||||
|
if cached {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := filepath.Walk(mibPath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if info == nil {
|
||||||
|
log.Warnf("No mibs found")
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
log.Warnf("MIB path doesn't exist: %q", mibPath)
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Mode()&os.ModeSymlink != 0 {
|
||||||
|
target, err := filepath.EvalSymlinks(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Couldn't evaluate symbolic links for %v: %v", path, err)
|
||||||
|
}
|
||||||
|
info, err = os.Lstat(target)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Couldn't stat target %v: %v", target, err)
|
||||||
|
}
|
||||||
|
path = target
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
folders = append(folders, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return folders, fmt.Errorf("couldn't walk path %q: %w", mibPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return folders, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
package snmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestingMibLoader struct {
|
||||||
|
folders []string
|
||||||
|
files []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestingMibLoader) appendPath(path string) {
|
||||||
|
t.folders = append(t.folders, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestingMibLoader) loadModule(path string) error {
|
||||||
|
t.files = append(t.files, path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func TestFolderLookup(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("Skipping on windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mibPath [][]string
|
||||||
|
paths [][]string
|
||||||
|
files []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "loading folders",
|
||||||
|
mibPath: [][]string{{"testdata", "loadMibsFromPath", "root"}},
|
||||||
|
paths: [][]string{
|
||||||
|
{"testdata", "loadMibsFromPath", "root"},
|
||||||
|
{"testdata", "loadMibsFromPath", "root", "dirOne"},
|
||||||
|
{"testdata", "loadMibsFromPath", "root", "dirOne", "dirTwo"},
|
||||||
|
{"testdata", "loadMibsFromPath", "linkTarget"},
|
||||||
|
},
|
||||||
|
files: []string{"empty", "emptyFile"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
loader := TestingMibLoader{}
|
||||||
|
|
||||||
|
var givenPath []string
|
||||||
|
for _, paths := range tt.mibPath {
|
||||||
|
rootPath := filepath.Join(paths...)
|
||||||
|
givenPath = append(givenPath, rootPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := LoadMibsFromPath(givenPath, testutil.Logger{}, &loader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var folders []string
|
||||||
|
for _, pathSlice := range tt.paths {
|
||||||
|
path := filepath.Join(pathSlice...)
|
||||||
|
folders = append(folders, path)
|
||||||
|
}
|
||||||
|
require.Equal(t, folders, loader.folders)
|
||||||
|
|
||||||
|
require.Equal(t, tt.files, loader.files)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMissingMibPath(t *testing.T) {
|
||||||
|
log := testutil.Logger{}
|
||||||
|
path := []string{"non-existing-directory"}
|
||||||
|
require.NoError(t, LoadMibsFromPath(path, log, &GosmiMibLoader{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMibLoading(b *testing.B) {
|
||||||
|
log := testutil.Logger{}
|
||||||
|
path := []string{"testdata/gosmi"}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
require.NoError(b, LoadMibsFromPath(path, log, &GosmiMibLoader{}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,315 @@
|
||||||
|
package snmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gosnmp/gosnmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Table holds the configuration for a SNMP table.
|
||||||
|
type Table struct {
|
||||||
|
// Name will be the name of the measurement.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Which tags to inherit from the top-level config.
|
||||||
|
InheritTags []string
|
||||||
|
|
||||||
|
// Adds each row's table index as a tag.
|
||||||
|
IndexAsTag bool
|
||||||
|
|
||||||
|
// Fields is the tags and values to look up.
|
||||||
|
Fields []Field `toml:"field"`
|
||||||
|
|
||||||
|
// OID for automatic field population.
|
||||||
|
// If provided, init() will populate Fields with all the table columns of the
|
||||||
|
// given OID.
|
||||||
|
Oid string
|
||||||
|
|
||||||
|
initialized bool
|
||||||
|
translator Translator
|
||||||
|
}
|
||||||
|
|
||||||
|
// RTable is the resulting table built from a Table.
|
||||||
|
type RTable struct {
|
||||||
|
// Name is the name of the field, copied from Table.Name.
|
||||||
|
Name string
|
||||||
|
// Time is the time the table was built.
|
||||||
|
Time time.Time
|
||||||
|
// Rows are the rows that were found, one row for each table OID index found.
|
||||||
|
Rows []RTableRow
|
||||||
|
}
|
||||||
|
|
||||||
|
// RTableRow is the resulting row containing all the OID values which shared
|
||||||
|
// the same index.
|
||||||
|
type RTableRow struct {
|
||||||
|
// Tags are all the Field values which had IsTag=true.
|
||||||
|
Tags map[string]string
|
||||||
|
// Fields are all the Field values which had IsTag=false.
|
||||||
|
Fields map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init() builds & initializes the nested fields.
|
||||||
|
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 == "" {
|
||||||
|
return errors.New("SNMP table in config file is not named. One or both of the oid and name settings must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.initialized {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.translator = tr
|
||||||
|
if err := t.initBuild(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
secondaryIndexTablePresent := false
|
||||||
|
// initialize all the nested fields
|
||||||
|
for i := range t.Fields {
|
||||||
|
if err := t.Fields[i].Init(t.translator); err != nil {
|
||||||
|
return fmt.Errorf("initializing field %s: %w", t.Fields[i].Name, err)
|
||||||
|
}
|
||||||
|
if t.Fields[i].SecondaryIndexTable {
|
||||||
|
if secondaryIndexTablePresent {
|
||||||
|
return errors.New("only one field can be SecondaryIndexTable")
|
||||||
|
}
|
||||||
|
secondaryIndexTablePresent = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.initialized = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
if t.Oid == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, oidText, fields, err := t.translator.SnmpTable(t.Oid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Name == "" {
|
||||||
|
t.Name = oidText
|
||||||
|
}
|
||||||
|
|
||||||
|
knownOIDs := map[string]bool{}
|
||||||
|
for _, f := range t.Fields {
|
||||||
|
knownOIDs[f.Oid] = true
|
||||||
|
}
|
||||||
|
for _, f := range fields {
|
||||||
|
if !knownOIDs[f.Oid] {
|
||||||
|
t.Fields = append(t.Fields, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build retrieves all the fields specified in the table and constructs the RTable.
|
||||||
|
func (t Table) Build(gs Connection, walk bool) (*RTable, error) {
|
||||||
|
rows := map[string]RTableRow{}
|
||||||
|
|
||||||
|
//translation table for secondary index (when preforming join on two tables)
|
||||||
|
secIdxTab := make(map[string]string)
|
||||||
|
secGlobalOuterJoin := false
|
||||||
|
for i, f := range t.Fields {
|
||||||
|
if f.SecondaryIndexTable {
|
||||||
|
secGlobalOuterJoin = f.SecondaryOuterJoin
|
||||||
|
if i != 0 {
|
||||||
|
t.Fields[0], t.Fields[i] = t.Fields[i], t.Fields[0]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tagCount := 0
|
||||||
|
for _, f := range t.Fields {
|
||||||
|
if f.IsTag {
|
||||||
|
tagCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.Oid) == 0 {
|
||||||
|
return nil, fmt.Errorf("cannot have empty OID on field %s", f.Name)
|
||||||
|
}
|
||||||
|
var oid string
|
||||||
|
if f.Oid[0] == '.' {
|
||||||
|
oid = f.Oid
|
||||||
|
} else {
|
||||||
|
// make sure OID has "." because the BulkWalkAll results do, and the prefix needs to match
|
||||||
|
oid = "." + f.Oid
|
||||||
|
}
|
||||||
|
|
||||||
|
// ifv contains a mapping of table OID index to field value
|
||||||
|
ifv := map[string]interface{}{}
|
||||||
|
|
||||||
|
if !walk {
|
||||||
|
// This is used when fetching non-table fields. Fields configured a the top
|
||||||
|
// scope of the plugin.
|
||||||
|
// We fetch the fields directly, and add them to ifv as if the index were an
|
||||||
|
// empty string. This results in all the non-table fields sharing the same
|
||||||
|
// index, and being added on the same row.
|
||||||
|
if pkt, err := gs.Get([]string{oid}); err != nil {
|
||||||
|
if errors.Is(err, gosnmp.ErrUnknownSecurityLevel) {
|
||||||
|
return nil, errors.New("unknown security level (sec_level)")
|
||||||
|
} else if errors.Is(err, gosnmp.ErrUnknownUsername) {
|
||||||
|
return nil, errors.New("unknown username (sec_name)")
|
||||||
|
} else if errors.Is(err, gosnmp.ErrWrongDigest) {
|
||||||
|
return nil, errors.New("wrong digest (auth_protocol, auth_password)")
|
||||||
|
} else if errors.Is(err, gosnmp.ErrDecryption) {
|
||||||
|
return nil, errors.New("decryption error (priv_protocol, priv_password)")
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("performing get on field %s: %w", f.Name, err)
|
||||||
|
} else if pkt != nil && len(pkt.Variables) > 0 && pkt.Variables[0].Type != gosnmp.NoSuchObject && pkt.Variables[0].Type != gosnmp.NoSuchInstance {
|
||||||
|
ent := pkt.Variables[0]
|
||||||
|
fv, err := f.Convert(ent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting %q (OID %s) for field %s: %w", ent.Value, ent.Name, f.Name, err)
|
||||||
|
}
|
||||||
|
ifv[""] = fv
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := gs.Walk(oid, func(ent gosnmp.SnmpPDU) error {
|
||||||
|
if len(ent.Name) <= len(oid) || ent.Name[:len(oid)+1] != oid+"." {
|
||||||
|
return &walkError{} // break the walk
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := ent.Name[len(oid):]
|
||||||
|
if f.OidIndexSuffix != "" {
|
||||||
|
if !strings.HasSuffix(idx, f.OidIndexSuffix) {
|
||||||
|
// this entry doesn't match our OidIndexSuffix. skip it
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
idx = idx[:len(idx)-len(f.OidIndexSuffix)]
|
||||||
|
}
|
||||||
|
if f.OidIndexLength != 0 {
|
||||||
|
i := f.OidIndexLength + 1 // leading separator
|
||||||
|
idx = strings.Map(func(r rune) rune {
|
||||||
|
if r == '.' {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
if i < 1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// snmptranslate table field value here
|
||||||
|
if f.Translate {
|
||||||
|
if entOid, ok := ent.Value.(string); ok {
|
||||||
|
_, _, oidText, _, err := t.translator.SnmpTranslate(entOid)
|
||||||
|
if err == nil {
|
||||||
|
// If no error translating, the original value for ent.Value should be replaced
|
||||||
|
ent.Value = oidText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fv, err := f.Convert(ent)
|
||||||
|
if err != nil {
|
||||||
|
return &walkError{
|
||||||
|
msg: fmt.Sprintf("converting %q (OID %s) for field %s", ent.Value, ent.Name, f.Name),
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ifv[idx] = fv
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// Our callback always wraps errors in a walkError.
|
||||||
|
// If this error isn't a walkError, we know it's not
|
||||||
|
// from the callback
|
||||||
|
var walkErr *walkError
|
||||||
|
if !errors.As(err, &walkErr) {
|
||||||
|
return nil, fmt.Errorf("performing bulk walk for field %s: %w", f.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, v := range ifv {
|
||||||
|
if f.SecondaryIndexUse {
|
||||||
|
if newidx, ok := secIdxTab[idx]; ok {
|
||||||
|
idx = newidx
|
||||||
|
} else {
|
||||||
|
if !secGlobalOuterJoin && !f.SecondaryOuterJoin {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
idx = ".Secondary" + idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rtr, ok := rows[idx]
|
||||||
|
if !ok {
|
||||||
|
rtr = RTableRow{}
|
||||||
|
rtr.Tags = map[string]string{}
|
||||||
|
rtr.Fields = map[string]interface{}{}
|
||||||
|
rows[idx] = rtr
|
||||||
|
}
|
||||||
|
if t.IndexAsTag && idx != "" {
|
||||||
|
if idx[0] == '.' {
|
||||||
|
idx = idx[1:]
|
||||||
|
}
|
||||||
|
rtr.Tags["index"] = idx
|
||||||
|
}
|
||||||
|
// don't add an empty string
|
||||||
|
if vs, ok := v.(string); !ok || vs != "" {
|
||||||
|
if f.IsTag {
|
||||||
|
if ok {
|
||||||
|
rtr.Tags[f.Name] = vs
|
||||||
|
} else {
|
||||||
|
rtr.Tags[f.Name] = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rtr.Fields[f.Name] = v
|
||||||
|
}
|
||||||
|
if f.SecondaryIndexTable {
|
||||||
|
//indexes are stored here with prepending "." so we need to add them if needed
|
||||||
|
var vss string
|
||||||
|
if ok {
|
||||||
|
vss = "." + vs
|
||||||
|
} else {
|
||||||
|
vss = fmt.Sprintf(".%v", v)
|
||||||
|
}
|
||||||
|
if idx[0] == '.' {
|
||||||
|
secIdxTab[vss] = idx
|
||||||
|
} else {
|
||||||
|
secIdxTab[vss] = "." + idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rt := RTable{
|
||||||
|
Name: t.Name,
|
||||||
|
Time: time.Now(), //TODO record time at start
|
||||||
|
Rows: make([]RTableRow, 0, len(rows)),
|
||||||
|
}
|
||||||
|
for _, r := range rows {
|
||||||
|
rt.Rows = append(rt.Rows, r)
|
||||||
|
}
|
||||||
|
return &rt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type walkError struct {
|
||||||
|
msg string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *walkError) Error() string {
|
||||||
|
return e.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *walkError) Unwrap() error {
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,246 @@
|
||||||
|
package snmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTableJoin_walk(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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tb, err := tbl.Build(tsc, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "mytable", tb.Name)
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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 TestTableOuterJoin_walk(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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tb, err := tbl.Build(tsc, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "mytable", tb.Name)
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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 TestTableJoinNoIndexAsTag_walk(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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tb, err := tbl.Build(tsc, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "mytable", tb.Name)
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.Len(t, tb.Rows, 3)
|
||||||
|
require.Contains(t, tb.Rows, rtr1)
|
||||||
|
require.Contains(t, tb.Rows, rtr2)
|
||||||
|
require.Contains(t, tb.Rows, rtr3)
|
||||||
|
}
|
||||||
|
|
@ -1,281 +0,0 @@
|
||||||
package snmp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sleepinggenius2/gosmi"
|
|
||||||
"github.com/sleepinggenius2/gosmi/types"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
)
|
|
||||||
|
|
||||||
// must init, append path for each directory, load module for every file
|
|
||||||
// or gosmi will fail without saying why
|
|
||||||
var m sync.Mutex
|
|
||||||
var once sync.Once
|
|
||||||
var cache = make(map[string]bool)
|
|
||||||
|
|
||||||
type MibLoader interface {
|
|
||||||
// appendPath takes the path of a directory
|
|
||||||
appendPath(path string)
|
|
||||||
|
|
||||||
// loadModule takes the name of a file in one of the
|
|
||||||
// directories. Basename only, no relative or absolute path
|
|
||||||
loadModule(path string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type GosmiMibLoader struct{}
|
|
||||||
|
|
||||||
func (*GosmiMibLoader) appendPath(path string) {
|
|
||||||
m.Lock()
|
|
||||||
defer m.Unlock()
|
|
||||||
|
|
||||||
gosmi.AppendPath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*GosmiMibLoader) loadModule(path string) error {
|
|
||||||
m.Lock()
|
|
||||||
defer m.Unlock()
|
|
||||||
|
|
||||||
_, err := gosmi.LoadModule(path)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// will give all found folders to gosmi and load in all modules found in the folders
|
|
||||||
func LoadMibsFromPath(paths []string, log telegraf.Logger, loader MibLoader) error {
|
|
||||||
folders, err := walkPaths(paths, log)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, path := range folders {
|
|
||||||
loader.appendPath(path)
|
|
||||||
modules, err := os.ReadDir(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Can't read directory %v", modules)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range modules {
|
|
||||||
info, err := entry.Info()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Couldn't get info for %v: %v", entry.Name(), err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if info.Mode()&os.ModeSymlink != 0 {
|
|
||||||
symlink := filepath.Join(path, info.Name())
|
|
||||||
target, err := filepath.EvalSymlinks(symlink)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Couldn't evaluate symbolic links for %v: %v", symlink, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
//replace symlink's info with the target's info
|
|
||||||
info, err = os.Lstat(target)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Couldn't stat target %v: %v", target, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if info.Mode().IsRegular() {
|
|
||||||
err := loader.loadModule(info.Name())
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Couldn't load module %v: %v", info.Name(), err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// should walk the paths given and find all folders
|
|
||||||
func walkPaths(paths []string, log telegraf.Logger) ([]string, error) {
|
|
||||||
once.Do(gosmi.Init)
|
|
||||||
folders := []string{}
|
|
||||||
|
|
||||||
for _, mibPath := range paths {
|
|
||||||
// Check if we loaded that path already and skip it if so
|
|
||||||
m.Lock()
|
|
||||||
cached := cache[mibPath]
|
|
||||||
cache[mibPath] = true
|
|
||||||
m.Unlock()
|
|
||||||
if cached {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err := filepath.Walk(mibPath, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if info == nil {
|
|
||||||
log.Warnf("No mibs found")
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
log.Warnf("MIB path doesn't exist: %q", mibPath)
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.Mode()&os.ModeSymlink != 0 {
|
|
||||||
target, err := filepath.EvalSymlinks(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Couldn't evaluate symbolic links for %v: %v", path, err)
|
|
||||||
}
|
|
||||||
info, err = os.Lstat(target)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Couldn't stat target %v: %v", target, err)
|
|
||||||
}
|
|
||||||
path = target
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
folders = append(folders, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return folders, fmt.Errorf("couldn't walk path %q: %w", mibPath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return folders, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following is for snmp_trap
|
|
||||||
type MibEntry struct {
|
|
||||||
MibName string
|
|
||||||
OidText string
|
|
||||||
}
|
|
||||||
|
|
||||||
func TrapLookup(oid string) (e MibEntry, err error) {
|
|
||||||
var givenOid types.Oid
|
|
||||||
if givenOid, err = types.OidFromString(oid); err != nil {
|
|
||||||
return e, fmt.Errorf("could not convert OID %s: %w", oid, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get node name
|
|
||||||
var node gosmi.SmiNode
|
|
||||||
if node, err = gosmi.GetNodeByOID(givenOid); err != nil {
|
|
||||||
return e, err
|
|
||||||
}
|
|
||||||
e.OidText = node.Name
|
|
||||||
|
|
||||||
// Add not found OID part
|
|
||||||
if !givenOid.Equals(node.Oid) {
|
|
||||||
e.OidText += "." + givenOid[len(node.Oid):].String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get module name
|
|
||||||
module := node.GetModule()
|
|
||||||
if module.Name != "<well-known>" {
|
|
||||||
e.MibName = module.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
return e, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following is for snmp
|
|
||||||
|
|
||||||
func GetIndex(mibPrefix string, node gosmi.SmiNode) (col []string, tagOids map[string]struct{}) {
|
|
||||||
// first attempt to get the table's tags
|
|
||||||
tagOids = map[string]struct{}{}
|
|
||||||
|
|
||||||
// mimcks grabbing INDEX {} that is returned from snmptranslate -Td MibName
|
|
||||||
for _, index := range node.GetIndex() {
|
|
||||||
tagOids[mibPrefix+index.Name] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// grabs all columns from the table
|
|
||||||
// mimmicks grabbing everything returned from snmptable -Ch -Cl -c public 127.0.0.1 oidFullName
|
|
||||||
_, col = node.GetColumns()
|
|
||||||
|
|
||||||
return col, tagOids
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:revive //Too many return variable but necessary
|
|
||||||
func SnmpTranslateCall(oid string) (mibName string, oidNum string, oidText string, conversion string, node gosmi.SmiNode, err error) {
|
|
||||||
var out gosmi.SmiNode
|
|
||||||
var end string
|
|
||||||
if strings.ContainsAny(oid, "::") {
|
|
||||||
// split given oid
|
|
||||||
// for example RFC1213-MIB::sysUpTime.0
|
|
||||||
s := strings.SplitN(oid, "::", 2)
|
|
||||||
// moduleName becomes RFC1213
|
|
||||||
moduleName := s[0]
|
|
||||||
module, err := gosmi.GetModule(moduleName)
|
|
||||||
if err != nil {
|
|
||||||
return oid, oid, oid, oid, gosmi.SmiNode{}, err
|
|
||||||
}
|
|
||||||
if s[1] == "" {
|
|
||||||
return "", oid, oid, oid, gosmi.SmiNode{}, fmt.Errorf("cannot parse %v", oid)
|
|
||||||
}
|
|
||||||
// node becomes sysUpTime.0
|
|
||||||
node := s[1]
|
|
||||||
if strings.ContainsAny(node, ".") {
|
|
||||||
s = strings.SplitN(node, ".", 2)
|
|
||||||
// node becomes sysUpTime
|
|
||||||
node = s[0]
|
|
||||||
end = "." + s[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err = module.GetNode(node)
|
|
||||||
if err != nil {
|
|
||||||
return oid, oid, oid, oid, out, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if oidNum = out.RenderNumeric(); oidNum == "" {
|
|
||||||
return oid, oid, oid, oid, out, fmt.Errorf("cannot translate %v into a numeric OID, please ensure all imported MIBs are in the path", oid)
|
|
||||||
}
|
|
||||||
|
|
||||||
oidNum = "." + oidNum + end
|
|
||||||
} else if strings.ContainsAny(oid, "abcdefghijklnmopqrstuvwxyz") {
|
|
||||||
//handle mixed oid ex. .iso.2.3
|
|
||||||
s := strings.Split(oid, ".")
|
|
||||||
for i := range s {
|
|
||||||
if strings.ContainsAny(s[i], "abcdefghijklmnopqrstuvwxyz") {
|
|
||||||
out, err = gosmi.GetNode(s[i])
|
|
||||||
if err != nil {
|
|
||||||
return oid, oid, oid, oid, out, err
|
|
||||||
}
|
|
||||||
s[i] = out.RenderNumeric()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
oidNum = strings.Join(s, ".")
|
|
||||||
out, _ = gosmi.GetNodeByOID(types.OidMustFromString(oidNum))
|
|
||||||
} else {
|
|
||||||
out, err = gosmi.GetNodeByOID(types.OidMustFromString(oid))
|
|
||||||
oidNum = oid
|
|
||||||
// ensure modules are loaded or node will be empty (might not error)
|
|
||||||
//nolint:nilerr // do not return the err as the oid is numeric and telegraf can continue
|
|
||||||
if err != nil || out.Name == "iso" {
|
|
||||||
return oid, oid, oid, oid, out, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tc := out.GetSubtree()
|
|
||||||
|
|
||||||
for i := range tc {
|
|
||||||
// case where the mib doesn't have a conversion so Type struct will be nil
|
|
||||||
// prevents seg fault
|
|
||||||
if tc[i].Type == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch tc[i].Type.Name {
|
|
||||||
case "MacAddress", "PhysAddress":
|
|
||||||
conversion = "hwaddr"
|
|
||||||
case "InetAddressIPv4", "InetAddressIPv6", "InetAddress", "IPSIpAddress":
|
|
||||||
conversion = "ipaddr"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
oidText = out.RenderQualified()
|
|
||||||
i := strings.Index(oidText, "::")
|
|
||||||
if i == -1 {
|
|
||||||
return "", oid, oid, oid, out, errors.New("not found")
|
|
||||||
}
|
|
||||||
mibName = oidText[:i]
|
|
||||||
oidText = oidText[i+2:] + end
|
|
||||||
|
|
||||||
return mibName, oidNum, oidText, conversion, out, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
package snmp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf/testutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTrapLookup(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
oid string
|
|
||||||
expected MibEntry
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Known trap OID",
|
|
||||||
oid: ".1.3.6.1.6.3.1.1.5.1",
|
|
||||||
expected: MibEntry{
|
|
||||||
MibName: "TGTEST-MIB",
|
|
||||||
OidText: "coldStart",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Known trap value OID",
|
|
||||||
oid: ".1.3.6.1.2.1.1.3.0",
|
|
||||||
expected: MibEntry{
|
|
||||||
MibName: "TGTEST-MIB",
|
|
||||||
OidText: "sysUpTimeInstance",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Unknown enterprise sub-OID",
|
|
||||||
oid: ".1.3.6.1.4.1.0.1.2.3",
|
|
||||||
expected: MibEntry{
|
|
||||||
MibName: "TGTEST-MIB",
|
|
||||||
OidText: "enterprises.0.1.2.3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Unknown MIB",
|
|
||||||
oid: ".1.2.3",
|
|
||||||
expected: MibEntry{OidText: "iso.2.3"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the MIBs
|
|
||||||
require.NoError(t, LoadMibsFromPath([]string{"testdata/mibs"}, testutil.Logger{}, &GosmiMibLoader{}))
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// Run the actual test
|
|
||||||
actual, err := TrapLookup(tt.oid)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, tt.expected, actual)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTrapLookupFail(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
oid string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "New top level OID",
|
|
||||||
oid: ".3.6.1.3.0",
|
|
||||||
expected: "Could not find node for OID 3.6.1.3.0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Malformed OID",
|
|
||||||
oid: ".1.3.dod.1.3.0",
|
|
||||||
expected: "could not convert OID .1.3.dod.1.3.0: strconv.ParseUint: parsing \"dod\": invalid syntax",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the MIBs
|
|
||||||
require.NoError(t, LoadMibsFromPath([]string{"testdata/mibs"}, testutil.Logger{}, &GosmiMibLoader{}))
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// Run the actual test
|
|
||||||
_, err := TrapLookup(tt.oid)
|
|
||||||
require.EqualError(t, err, tt.expected)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestingMibLoader struct {
|
|
||||||
folders []string
|
|
||||||
files []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestingMibLoader) appendPath(path string) {
|
|
||||||
t.folders = append(t.folders, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestingMibLoader) loadModule(path string) error {
|
|
||||||
t.files = append(t.files, path)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func TestFolderLookup(t *testing.T) {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
t.Skip("Skipping on windows")
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
mibPath [][]string
|
|
||||||
paths [][]string
|
|
||||||
files []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "loading folders",
|
|
||||||
mibPath: [][]string{{"testdata", "loadMibsFromPath", "root"}},
|
|
||||||
paths: [][]string{
|
|
||||||
{"testdata", "loadMibsFromPath", "root"},
|
|
||||||
{"testdata", "loadMibsFromPath", "root", "dirOne"},
|
|
||||||
{"testdata", "loadMibsFromPath", "root", "dirOne", "dirTwo"},
|
|
||||||
{"testdata", "loadMibsFromPath", "linkTarget"},
|
|
||||||
},
|
|
||||||
files: []string{"empty", "emptyFile"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
loader := TestingMibLoader{}
|
|
||||||
|
|
||||||
var givenPath []string
|
|
||||||
for _, paths := range tt.mibPath {
|
|
||||||
rootPath := filepath.Join(paths...)
|
|
||||||
givenPath = append(givenPath, rootPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := LoadMibsFromPath(givenPath, testutil.Logger{}, &loader)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var folders []string
|
|
||||||
for _, pathSlice := range tt.paths {
|
|
||||||
path := filepath.Join(pathSlice...)
|
|
||||||
folders = append(folders, path)
|
|
||||||
}
|
|
||||||
require.Equal(t, folders, loader.folders)
|
|
||||||
|
|
||||||
require.Equal(t, tt.files, loader.files)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,3 +3,22 @@ package snmp
|
||||||
type TranslatorPlugin interface {
|
type TranslatorPlugin interface {
|
||||||
SetTranslator(name string) // Agent calls this on inputs before Init
|
SetTranslator(name string) // Agent calls this on inputs before Init
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
SnmpFormatEnum(oid string, value interface{}, full bool) (
|
||||||
|
formatted string,
|
||||||
|
err error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
package snmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sleepinggenius2/gosmi"
|
||||||
|
"github.com/sleepinggenius2/gosmi/models"
|
||||||
|
"github.com/sleepinggenius2/gosmi/types"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gosmiTranslator struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGosmiTranslator(paths []string, log telegraf.Logger) (*gosmiTranslator, error) {
|
||||||
|
err := LoadMibsFromPath(paths, log, &GosmiMibLoader{})
|
||||||
|
if err == nil {
|
||||||
|
return &gosmiTranslator{}, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:revive //function-result-limit conditionally 5 return results allowed
|
||||||
|
func (g *gosmiTranslator) SnmpTranslate(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) {
|
||||||
|
mibName, oidNum, oidText, conversion, _, err = snmpTranslateCall(oid)
|
||||||
|
return mibName, oidNum, oidText, conversion, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
mibName, oidNum, oidText, _, node, err := snmpTranslateCall(oid)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", nil, fmt.Errorf("translating: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mibPrefix := mibName + "::"
|
||||||
|
|
||||||
|
col, tagOids := getIndex(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, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gosmiTranslator) SnmpFormatEnum(oid string, value interface{}, full bool) (string, error) {
|
||||||
|
//nolint:dogsled // only need to get the node
|
||||||
|
_, _, _, _, node, err := snmpTranslateCall(oid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var v models.Value
|
||||||
|
if full {
|
||||||
|
v = node.FormatValue(value, models.FormatEnumName, models.FormatEnumValue)
|
||||||
|
} else {
|
||||||
|
v = node.FormatValue(value, models.FormatEnumName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.Formatted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIndex(mibPrefix string, node gosmi.SmiNode) (col []string, tagOids map[string]struct{}) {
|
||||||
|
// first attempt to get the table's tags
|
||||||
|
tagOids = map[string]struct{}{}
|
||||||
|
|
||||||
|
// mimcks grabbing INDEX {} that is returned from snmptranslate -Td MibName
|
||||||
|
for _, index := range node.GetIndex() {
|
||||||
|
tagOids[mibPrefix+index.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// grabs all columns from the table
|
||||||
|
// mimmicks grabbing everything returned from snmptable -Ch -Cl -c public 127.0.0.1 oidFullName
|
||||||
|
_, col = node.GetColumns()
|
||||||
|
|
||||||
|
return col, tagOids
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:revive //Too many return variable but necessary
|
||||||
|
func snmpTranslateCall(oid string) (mibName string, oidNum string, oidText string, conversion string, node gosmi.SmiNode, err error) {
|
||||||
|
var out gosmi.SmiNode
|
||||||
|
var end string
|
||||||
|
if strings.ContainsAny(oid, "::") {
|
||||||
|
// split given oid
|
||||||
|
// for example RFC1213-MIB::sysUpTime.0
|
||||||
|
s := strings.SplitN(oid, "::", 2)
|
||||||
|
// moduleName becomes RFC1213
|
||||||
|
moduleName := s[0]
|
||||||
|
module, err := gosmi.GetModule(moduleName)
|
||||||
|
if err != nil {
|
||||||
|
return oid, oid, oid, "", gosmi.SmiNode{}, err
|
||||||
|
}
|
||||||
|
if s[1] == "" {
|
||||||
|
return "", oid, oid, "", gosmi.SmiNode{}, fmt.Errorf("cannot parse %v", oid)
|
||||||
|
}
|
||||||
|
// node becomes sysUpTime.0
|
||||||
|
node := s[1]
|
||||||
|
if strings.ContainsAny(node, ".") {
|
||||||
|
s = strings.SplitN(node, ".", 2)
|
||||||
|
// node becomes sysUpTime
|
||||||
|
node = s[0]
|
||||||
|
end = "." + s[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err = module.GetNode(node)
|
||||||
|
if err != nil {
|
||||||
|
return oid, oid, oid, "", out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if oidNum = out.RenderNumeric(); oidNum == "" {
|
||||||
|
return oid, oid, oid, "", out, fmt.Errorf("cannot translate %v into a numeric OID, please ensure all imported MIBs are in the path", oid)
|
||||||
|
}
|
||||||
|
|
||||||
|
oidNum = "." + oidNum + end
|
||||||
|
} else if strings.ContainsAny(oid, "abcdefghijklnmopqrstuvwxyz") {
|
||||||
|
//handle mixed oid ex. .iso.2.3
|
||||||
|
s := strings.Split(oid, ".")
|
||||||
|
for i := range s {
|
||||||
|
if strings.ContainsAny(s[i], "abcdefghijklmnopqrstuvwxyz") {
|
||||||
|
out, err = gosmi.GetNode(s[i])
|
||||||
|
if err != nil {
|
||||||
|
return oid, oid, oid, "", out, err
|
||||||
|
}
|
||||||
|
s[i] = out.RenderNumeric()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oidNum = strings.Join(s, ".")
|
||||||
|
out, _ = gosmi.GetNodeByOID(types.OidMustFromString(oidNum))
|
||||||
|
} else {
|
||||||
|
out, err = gosmi.GetNodeByOID(types.OidMustFromString(oid))
|
||||||
|
oidNum = oid
|
||||||
|
// ensure modules are loaded or node will be empty (might not error)
|
||||||
|
//nolint:nilerr // do not return the err as the oid is numeric and telegraf can continue
|
||||||
|
if err != nil || out.Name == "iso" {
|
||||||
|
return oid, oid, oid, "", out, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tc := out.GetSubtree()
|
||||||
|
|
||||||
|
for i := range tc {
|
||||||
|
// case where the mib doesn't have a conversion so Type struct will be nil
|
||||||
|
// prevents seg fault
|
||||||
|
if tc[i].Type == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch tc[i].Type.Name {
|
||||||
|
case "MacAddress", "PhysAddress":
|
||||||
|
conversion = "hwaddr"
|
||||||
|
case "InetAddressIPv4", "InetAddressIPv6", "InetAddress", "IPSIpAddress":
|
||||||
|
conversion = "ipaddr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oidText = out.RenderQualified()
|
||||||
|
i := strings.Index(oidText, "::")
|
||||||
|
if i == -1 {
|
||||||
|
return "", oid, oid, "", out, errors.New("not found")
|
||||||
|
}
|
||||||
|
mibName = oidText[:i]
|
||||||
|
oidText = oidText[i+2:] + end
|
||||||
|
|
||||||
|
return mibName, oidNum, oidText, conversion, out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following is for snmp_trap
|
||||||
|
type MibEntry struct {
|
||||||
|
MibName string
|
||||||
|
OidText string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrapLookup(oid string) (e MibEntry, err error) {
|
||||||
|
var givenOid types.Oid
|
||||||
|
if givenOid, err = types.OidFromString(oid); err != nil {
|
||||||
|
return e, fmt.Errorf("could not convert OID %s: %w", oid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get node name
|
||||||
|
var node gosmi.SmiNode
|
||||||
|
if node, err = gosmi.GetNodeByOID(givenOid); err != nil {
|
||||||
|
return e, err
|
||||||
|
}
|
||||||
|
e.OidText = node.Name
|
||||||
|
|
||||||
|
// Add not found OID part
|
||||||
|
if !givenOid.Equals(node.Oid) {
|
||||||
|
e.OidText += "." + givenOid[len(node.Oid):].String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get module name
|
||||||
|
module := node.GetModule()
|
||||||
|
if module.Name != "<well-known>" {
|
||||||
|
e.MibName = module.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,17 @@
|
||||||
package snmp
|
package snmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gosnmp/gosnmp"
|
"github.com/gosnmp/gosnmp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf/internal/snmp"
|
|
||||||
"github.com/influxdata/telegraf/testutil"
|
"github.com/influxdata/telegraf/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getGosmiTr(t *testing.T) Translator {
|
func getGosmiTr(t *testing.T) Translator {
|
||||||
testDataPath, err := filepath.Abs("./testdata")
|
testDataPath, err := filepath.Abs("./testdata/gosmi")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
|
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
|
||||||
|
|
@ -31,56 +28,8 @@ func TestGosmiTranslator(t *testing.T) {
|
||||||
require.NotNil(t, tr)
|
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) {
|
func TestFieldInitGosmi(t *testing.T) {
|
||||||
testDataPath, err := filepath.Abs("./testdata")
|
tr := getGosmiTr(t)
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
translations := []struct {
|
translations := []struct {
|
||||||
inputOid string
|
inputOid string
|
||||||
|
|
@ -102,124 +51,50 @@ func TestFieldInitGosmi(t *testing.T) {
|
||||||
|
|
||||||
for _, txl := range translations {
|
for _, txl := range translations {
|
||||||
f := Field{Oid: txl.inputOid, Name: txl.inputName, Conversion: txl.inputConversion}
|
f := Field{Oid: txl.inputOid, Name: txl.inputName, Conversion: txl.inputConversion}
|
||||||
err := f.init(tr)
|
require.NoError(t, f.Init(tr), "inputOid=%q inputName=%q", txl.inputOid, txl.inputName)
|
||||||
require.NoError(t, err, "inputOid=%q inputName=%q", txl.inputOid, txl.inputName)
|
|
||||||
|
|
||||||
require.Equal(t, txl.expectedOid, f.Oid, "inputOid=%q inputName=%q inputConversion=%q", txl.inputOid, txl.inputName, txl.inputConversion)
|
require.Equal(t, txl.expectedOid, f.Oid, "inputOid=%q inputName=%q inputConversion=%q", txl.inputOid, txl.inputName, txl.inputConversion)
|
||||||
require.Equal(t, txl.expectedName, f.Name, "inputOid=%q inputName=%q inputConversion=%q", txl.inputOid, txl.inputName, txl.inputConversion)
|
require.Equal(t, txl.expectedName, f.Name, "inputOid=%q inputName=%q inputConversion=%q", txl.inputOid, txl.inputName, txl.inputConversion)
|
||||||
|
require.Equal(t, txl.expectedConversion, f.Conversion, "inputOid=%q inputName=%q inputConversion=%q", txl.inputOid, txl.inputName, txl.inputConversion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTableInitGosmi(t *testing.T) {
|
func TestTableInitGosmi(t *testing.T) {
|
||||||
testDataPath, err := filepath.Abs("./testdata")
|
tbl := Table{
|
||||||
require.NoError(t, err)
|
Oid: ".1.3.6.1.2.1.3.1",
|
||||||
|
|
||||||
s := &Snmp{
|
|
||||||
ClientConfig: snmp.ClientConfig{
|
|
||||||
Path: []string{testDataPath},
|
|
||||||
Translator: "gosmi",
|
|
||||||
},
|
|
||||||
Tables: []Table{
|
|
||||||
{Oid: ".1.3.6.1.2.1.3.1",
|
|
||||||
Fields: []Field{
|
Fields: []Field{
|
||||||
{Oid: ".999", Name: "foo"},
|
{Oid: ".999", Name: "foo"},
|
||||||
{Oid: ".1.3.6.1.2.1.3.1.1.1", Name: "atIfIndex", IsTag: true},
|
{Oid: ".1.3.6.1.2.1.3.1.1.1", Name: "atIfIndex", IsTag: true},
|
||||||
{Oid: "RFC1213-MIB::atPhysAddress", Name: "atPhysAddress"},
|
{Oid: "RFC1213-MIB::atPhysAddress", Name: "atPhysAddress"},
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err = s.Init()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "atTable", s.Tables[0].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})
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
tr := getGosmiTr(t)
|
||||||
require.NoError(t, err)
|
require.NoError(t, tbl.Init(tr))
|
||||||
|
|
||||||
require.Len(t, s.Tables[0].Fields, 3)
|
require.Equal(t, "atTable", tbl.Name)
|
||||||
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{
|
require.Len(t, tbl.Fields, 5)
|
||||||
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) {
|
require.Equal(t, ".999", tbl.Fields[0].Oid)
|
||||||
s := &Snmp{
|
require.Equal(t, "foo", tbl.Fields[0].Name)
|
||||||
Fields: []Field{
|
require.False(t, tbl.Fields[0].IsTag)
|
||||||
{Oid: ".9.1.1.1.1", Name: "one", IsTag: true},
|
require.Empty(t, tbl.Fields[0].Conversion)
|
||||||
{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.Equal(t, ".1.3.6.1.2.1.3.1.1.1", tbl.Fields[1].Oid)
|
||||||
require.NoError(t, err)
|
require.Equal(t, "atIfIndex", tbl.Fields[1].Name)
|
||||||
|
require.True(t, tbl.Fields[1].IsTag)
|
||||||
|
require.Empty(t, tbl.Fields[1].Conversion)
|
||||||
|
|
||||||
require.Equal(t, ".9.1.1.1.1", s.Fields[0].Oid)
|
require.Equal(t, ".1.3.6.1.2.1.3.1.1.2", tbl.Fields[2].Oid)
|
||||||
require.Equal(t, "one", s.Fields[0].Name)
|
require.Equal(t, "atPhysAddress", tbl.Fields[2].Name)
|
||||||
require.True(t, s.Fields[0].IsTag)
|
require.False(t, tbl.Fields[2].IsTag)
|
||||||
|
require.Equal(t, "hwaddr", tbl.Fields[2].Conversion)
|
||||||
|
|
||||||
require.Equal(t, ".9.1.1.1.2", s.Fields[1].Oid)
|
require.Equal(t, ".1.3.6.1.2.1.3.1.1.3", tbl.Fields[4].Oid)
|
||||||
require.Equal(t, "two", s.Fields[1].Name)
|
require.Equal(t, "atNetAddress", tbl.Fields[4].Name)
|
||||||
require.False(t, s.Fields[1].IsTag)
|
require.True(t, tbl.Fields[4].IsTag)
|
||||||
|
require.Empty(t, tbl.Fields[4].Conversion)
|
||||||
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.False(t, s.Fields[2].IsTag)
|
|
||||||
|
|
||||||
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.True(t, s.Tables[0].Fields[0].IsTag)
|
|
||||||
|
|
||||||
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.False(t, s.Tables[0].Fields[1].IsTag)
|
|
||||||
|
|
||||||
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.False(t, s.Tables[0].Fields[2].IsTag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestTableBuild_walk in snmp_test.go is split into two tests here,
|
// TestTableBuild_walk in snmp_test.go is split into two tests here,
|
||||||
|
|
@ -259,13 +134,7 @@ func TestTableBuild_walk_noTranslate(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testDataPath, err := filepath.Abs("./testdata")
|
tb, err := tbl.Build(tsc, true)
|
||||||
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)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "mytable", tb.Name)
|
require.Equal(t, "mytable", tb.Name)
|
||||||
rtr1 := RTableRow{
|
rtr1 := RTableRow{
|
||||||
|
|
@ -317,12 +186,6 @@ func TestTableBuild_walk_noTranslate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTableBuild_walk_Translate(t *testing.T) {
|
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{
|
tbl := Table{
|
||||||
Name: "atTable",
|
Name: "atTable",
|
||||||
IndexAsTag: true,
|
IndexAsTag: true,
|
||||||
|
|
@ -345,9 +208,8 @@ func TestTableBuild_walk_Translate(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tbl.Init(tr)
|
require.NoError(t, tbl.Init(getGosmiTr(t)))
|
||||||
require.NoError(t, err)
|
tb, err := tbl.Build(tsc, true)
|
||||||
tb, err := tbl.Build(gosmiTsc, true, tr)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, "atTable", tb.Name)
|
require.Equal(t, "atTable", tb.Name)
|
||||||
|
|
@ -387,12 +249,6 @@ func TestTableBuild_walk_Translate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTableBuild_noWalkGosmi(t *testing.T) {
|
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{
|
tbl := Table{
|
||||||
Name: "mytable",
|
Name: "mytable",
|
||||||
Fields: []Field{
|
Fields: []Field{
|
||||||
|
|
@ -421,7 +277,7 @@ func TestTableBuild_noWalkGosmi(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tb, err := tbl.Build(gosmiTsc, false, tr)
|
tb, err := tbl.Build(tsc, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rtr := RTableRow{
|
rtr := RTableRow{
|
||||||
|
|
@ -432,103 +288,6 @@ func TestTableBuild_noWalkGosmi(t *testing.T) {
|
||||||
require.Contains(t, tb.Rows, rtr)
|
require.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]
|
|
||||||
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))
|
|
||||||
|
|
||||||
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"])
|
|
||||||
}
|
|
||||||
|
|
||||||
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]
|
|
||||||
require.Equal(t, "baz", m.Tags["host"])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFieldConvertGosmi(t *testing.T) {
|
func TestFieldConvertGosmi(t *testing.T) {
|
||||||
testTable := []struct {
|
testTable := []struct {
|
||||||
input interface{}
|
input interface{}
|
||||||
|
|
@ -585,77 +344,18 @@ func TestFieldConvertGosmi(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testTable {
|
for _, tc := range testTable {
|
||||||
act, err := fieldConvert(getGosmiTr(t), tc.conv, gosnmp.SnmpPDU{Name: ".1.3.6.1.2.1.2.2.1.8", Value: tc.input})
|
f := Field{
|
||||||
|
Name: "test",
|
||||||
|
Conversion: tc.conv,
|
||||||
|
}
|
||||||
|
require.NoError(t, f.Init(getGosmiTr(t)))
|
||||||
|
|
||||||
|
act, err := f.Convert(gosnmp.SnmpPDU{Name: ".1.3.6.1.2.1.2.2.1.8", Value: 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.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)
|
require.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)
|
|
||||||
require.Len(t, gosmiSnmpTranslateCaches, 1)
|
|
||||||
stc := gosmiSnmpTranslateCaches[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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSnmpTranslateCache_hitGosmi(t *testing.T) {
|
|
||||||
gosmiSnmpTranslateCaches = map[string]gosmiSnmpTranslateCache{
|
|
||||||
"foo": {
|
|
||||||
mibName: "a",
|
|
||||||
oidNum: "b",
|
|
||||||
oidText: "c",
|
|
||||||
conversion: "d",
|
|
||||||
err: errors.New("e"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
mibName, oidNum, oidText, conversion, err := getGosmiTr(t).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, errors.New("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)
|
|
||||||
require.Len(t, gosmiSnmpTableCaches, 1)
|
|
||||||
stc := gosmiSnmpTableCaches[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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSnmpTableCache_hitGosmi(t *testing.T) {
|
|
||||||
gosmiSnmpTableCaches = map[string]gosmiSnmpTableCache{
|
|
||||||
"foo": {
|
|
||||||
mibName: "a",
|
|
||||||
oidNum: "b",
|
|
||||||
oidText: "c",
|
|
||||||
fields: []Field{{Name: "d"}},
|
|
||||||
err: errors.New("e"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
mibName, oidNum, oidText, fields, err := getGosmiTr(t).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, errors.New("e"), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTableJoin_walkGosmi(t *testing.T) {
|
func TestTableJoin_walkGosmi(t *testing.T) {
|
||||||
tbl := Table{
|
tbl := Table{
|
||||||
Name: "mytable",
|
Name: "mytable",
|
||||||
|
|
@ -689,13 +389,8 @@ func TestTableJoin_walkGosmi(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testDataPath, err := filepath.Abs("./testdata")
|
require.NoError(t, tbl.Init(getGosmiTr(t)))
|
||||||
require.NoError(t, err)
|
tb, err := tbl.Build(tsc, true)
|
||||||
|
|
||||||
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tb, err := tbl.Build(gosmiTsc, true, tr)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, "mytable", tb.Name)
|
require.Equal(t, "mytable", tb.Name)
|
||||||
|
|
@ -772,13 +467,7 @@ func TestTableOuterJoin_walkGosmi(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testDataPath, err := filepath.Abs("./testdata")
|
tb, err := tbl.Build(tsc, true)
|
||||||
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, "mytable", tb.Name)
|
require.Equal(t, "mytable", tb.Name)
|
||||||
|
|
@ -864,13 +553,7 @@ func TestTableJoinNoIndexAsTag_walkGosmi(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testDataPath, err := filepath.Abs("./testdata")
|
tb, err := tbl.Build(tsc, true)
|
||||||
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, "mytable", tb.Name)
|
require.Equal(t, "mytable", tb.Name)
|
||||||
|
|
@ -913,33 +596,91 @@ func TestTableJoinNoIndexAsTag_walkGosmi(t *testing.T) {
|
||||||
require.Contains(t, tb.Rows, rtr3)
|
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) {
|
func TestCanNotParse(t *testing.T) {
|
||||||
s := &Snmp{
|
tr := getGosmiTr(t)
|
||||||
Fields: []Field{
|
f := Field{
|
||||||
{Oid: "RFC1213-MIB::"},
|
Oid: "RFC1213-MIB::",
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Error(t, f.Init(tr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrapLookup(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
oid string
|
||||||
|
expected MibEntry
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Known trap OID",
|
||||||
|
oid: ".1.3.6.1.6.3.1.1.5.1",
|
||||||
|
expected: MibEntry{
|
||||||
|
MibName: "TGTEST-MIB",
|
||||||
|
OidText: "coldStart",
|
||||||
},
|
},
|
||||||
ClientConfig: snmp.ClientConfig{
|
},
|
||||||
Path: []string{"testdata"},
|
{
|
||||||
Translator: "gosmi",
|
name: "Known trap value OID",
|
||||||
|
oid: ".1.3.6.1.2.1.1.3.0",
|
||||||
|
expected: MibEntry{
|
||||||
|
MibName: "TGTEST-MIB",
|
||||||
|
OidText: "sysUpTimeInstance",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unknown enterprise sub-OID",
|
||||||
|
oid: ".1.3.6.1.4.1.0.1.2.3",
|
||||||
|
expected: MibEntry{
|
||||||
|
MibName: "SNMPv2-SMI",
|
||||||
|
OidText: "enterprises.0.1.2.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unknown MIB",
|
||||||
|
oid: ".1.999",
|
||||||
|
expected: MibEntry{OidText: "iso.999"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.Init()
|
// Load the MIBs
|
||||||
require.Error(t, err)
|
getGosmiTr(t)
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Run the actual test
|
||||||
|
actual, err := TrapLookup(tt.oid)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tt.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMissingMibPath(t *testing.T) {
|
func TestTrapLookupFail(t *testing.T) {
|
||||||
log := testutil.Logger{}
|
tests := []struct {
|
||||||
path := []string{"non-existing-directory"}
|
name string
|
||||||
err := snmp.LoadMibsFromPath(path, log, &snmp.GosmiMibLoader{})
|
oid string
|
||||||
require.NoError(t, err)
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "New top level OID",
|
||||||
|
oid: ".3.6.1.3.0",
|
||||||
|
expected: "Could not find node for OID 3.6.1.3.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Malformed OID",
|
||||||
|
oid: ".1.3.dod.1.3.0",
|
||||||
|
expected: "could not convert OID .1.3.dod.1.3.0: strconv.ParseUint: parsing \"dod\": invalid syntax",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the MIBs
|
||||||
|
getGosmiTr(t)
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Run the actual test
|
||||||
|
_, err := TrapLookup(tt.oid)
|
||||||
|
require.EqualError(t, err, tt.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,21 +5,21 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log" //nolint:depguard // Allow exceptional but valid use of log here.
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/influxdata/wlog"
|
"github.com/influxdata/telegraf"
|
||||||
)
|
)
|
||||||
|
|
||||||
// struct that implements the translator interface. This calls existing
|
// struct that implements the translator interface. This calls existing
|
||||||
// code to exec netsnmp's snmptranslate program
|
// code to exec netsnmp's snmptranslate program
|
||||||
type netsnmpTranslator struct {
|
type netsnmpTranslator struct {
|
||||||
|
log telegraf.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNetsnmpTranslator() *netsnmpTranslator {
|
func NewNetsnmpTranslator(log telegraf.Logger) *netsnmpTranslator {
|
||||||
return &netsnmpTranslator{}
|
return &netsnmpTranslator{log: log}
|
||||||
}
|
}
|
||||||
|
|
||||||
type snmpTableCache struct {
|
type snmpTableCache struct {
|
||||||
|
|
@ -35,14 +35,12 @@ var execCommand = exec.Command
|
||||||
|
|
||||||
// execCmd executes the specified command, returning the STDOUT content.
|
// execCmd executes the specified command, returning the STDOUT content.
|
||||||
// If command exits with error status, the output is captured into the returned error.
|
// If command exits with error status, the output is captured into the returned error.
|
||||||
func execCmd(arg0 string, args ...string) ([]byte, error) {
|
func (n *netsnmpTranslator) execCmd(arg0 string, args ...string) ([]byte, error) {
|
||||||
if wlog.LogLevel() == wlog.DEBUG {
|
|
||||||
quoted := make([]string, 0, len(args))
|
quoted := make([]string, 0, len(args))
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
quoted = append(quoted, fmt.Sprintf("%q", arg))
|
quoted = append(quoted, fmt.Sprintf("%q", arg))
|
||||||
}
|
}
|
||||||
log.Printf("D! [inputs.snmp] executing %q %s", arg0, strings.Join(quoted, " "))
|
n.log.Debugf("executing %q %s", arg0, strings.Join(quoted, " "))
|
||||||
}
|
|
||||||
|
|
||||||
out, err := execCommand(arg0, args...).Output()
|
out, err := execCommand(arg0, args...).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -98,7 +96,7 @@ func (n *netsnmpTranslator) snmpTableCall(oid string) (
|
||||||
// first attempt to get the table's tags
|
// first attempt to get the table's tags
|
||||||
tagOids := map[string]struct{}{}
|
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.
|
// 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 {
|
if out, err := n.execCmd("snmptranslate", "-Td", oidFullName+".1"); err == nil {
|
||||||
scanner := bufio.NewScanner(bytes.NewBuffer(out))
|
scanner := bufio.NewScanner(bytes.NewBuffer(out))
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
|
|
@ -124,7 +122,7 @@ func (n *netsnmpTranslator) snmpTableCall(oid string) (
|
||||||
}
|
}
|
||||||
|
|
||||||
// this won't actually try to run a query. The `-Ch` will just cause it to dump headers.
|
// 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)
|
out, err := n.execCmd("snmptable", "-Ch", "-Cl", "-c", "public", "127.0.0.1", oidFullName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", nil, fmt.Errorf("getting table columns: %w", err)
|
return "", "", "", nil, fmt.Errorf("getting table columns: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +177,7 @@ func (n *netsnmpTranslator) SnmpTranslate(oid string) (
|
||||||
// is worth it. Especially when it would slam the system pretty hard if lots
|
// is worth it. Especially when it would slam the system pretty hard if lots
|
||||||
// of lookups are being performed.
|
// of lookups are being performed.
|
||||||
|
|
||||||
stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.err = snmpTranslateCall(oid)
|
stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.err = n.snmpTranslateCall(oid)
|
||||||
snmpTranslateCaches[oid] = stc
|
snmpTranslateCaches[oid] = stc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,12 +187,12 @@ func (n *netsnmpTranslator) SnmpTranslate(oid string) (
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:revive //function-result-limit conditionally 5 return results allowed
|
//nolint:revive //function-result-limit conditionally 5 return results allowed
|
||||||
func snmpTranslateCall(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) {
|
func (n *netsnmpTranslator) snmpTranslateCall(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) {
|
||||||
var out []byte
|
var out []byte
|
||||||
if strings.ContainsAny(oid, ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
|
if strings.ContainsAny(oid, ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
|
||||||
out, err = execCmd("snmptranslate", "-Td", "-Ob", oid)
|
out, err = n.execCmd("snmptranslate", "-Td", "-Ob", oid)
|
||||||
} else {
|
} else {
|
||||||
out, err = execCmd("snmptranslate", "-Td", "-Ob", "-m", "all", oid)
|
out, err = n.execCmd("snmptranslate", "-Td", "-Ob", "-m", "all", oid)
|
||||||
var execErr *exec.Error
|
var execErr *exec.Error
|
||||||
if errors.As(err, &execErr) && errors.Is(execErr, exec.ErrNotFound) {
|
if errors.As(err, &execErr) && errors.Is(execErr, exec.ErrNotFound) {
|
||||||
// Silently discard error if snmptranslate not found and we have a numeric OID.
|
// Silently discard error if snmptranslate not found and we have a numeric OID.
|
||||||
|
|
@ -98,5 +98,5 @@ func generate() error {
|
||||||
f.Write([]byte("}\n"))
|
f.Write([]byte("}\n"))
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
return exec.Command("gofmt", "-w", "snmp_mocks_test.go").Run()
|
return exec.Command("gofmt", "-w", "translator_netsnmp_mocks_test.go").Run()
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,352 @@
|
||||||
|
//go:generate go run -tags generate translator_netsnmp_mocks_generate.go
|
||||||
|
package snmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gosnmp/gosnmp"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFieldInit(t *testing.T) {
|
||||||
|
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", ""},
|
||||||
|
{".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"},
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := NewNetsnmpTranslator(testutil.Logger{})
|
||||||
|
for _, txl := range translations {
|
||||||
|
f := Field{Oid: txl.inputOid, Name: txl.inputName, Conversion: txl.inputConversion}
|
||||||
|
err := f.Init(tr)
|
||||||
|
require.NoError(t, err, "inputOid=%q inputName=%q", txl.inputOid, txl.inputName)
|
||||||
|
require.Equal(t, txl.expectedOid, f.Oid, "inputOid=%q inputName=%q inputConversion=%q", txl.inputOid, txl.inputName, txl.inputConversion)
|
||||||
|
require.Equal(t, txl.expectedName, f.Name, "inputOid=%q inputName=%q inputConversion=%q", txl.inputOid, txl.inputName, txl.inputConversion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTableInit(t *testing.T) {
|
||||||
|
tbl := Table{
|
||||||
|
Oid: ".1.0.0.0",
|
||||||
|
Fields: []Field{
|
||||||
|
{Oid: ".999", Name: "foo"},
|
||||||
|
{Oid: "TEST::description", Name: "description", IsTag: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := tbl.Init(NewNetsnmpTranslator(testutil.Logger{}))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "testTable", tbl.Name)
|
||||||
|
|
||||||
|
require.Len(t, tbl.Fields, 5)
|
||||||
|
|
||||||
|
require.Equal(t, ".999", tbl.Fields[0].Oid)
|
||||||
|
require.Equal(t, "foo", tbl.Fields[0].Name)
|
||||||
|
require.False(t, tbl.Fields[0].IsTag)
|
||||||
|
require.Empty(t, tbl.Fields[0].Conversion)
|
||||||
|
|
||||||
|
require.Equal(t, ".1.0.0.0.1.1", tbl.Fields[2].Oid)
|
||||||
|
require.Equal(t, "server", tbl.Fields[2].Name)
|
||||||
|
require.True(t, tbl.Fields[1].IsTag)
|
||||||
|
require.Empty(t, tbl.Fields[1].Conversion)
|
||||||
|
|
||||||
|
require.Equal(t, ".1.0.0.0.1.2", tbl.Fields[3].Oid)
|
||||||
|
require.Equal(t, "connections", tbl.Fields[3].Name)
|
||||||
|
require.False(t, tbl.Fields[3].IsTag)
|
||||||
|
require.Empty(t, tbl.Fields[3].Conversion)
|
||||||
|
|
||||||
|
require.Equal(t, ".1.0.0.0.1.3", tbl.Fields[4].Oid)
|
||||||
|
require.Equal(t, "latency", tbl.Fields[4].Name)
|
||||||
|
require.False(t, tbl.Fields[4].IsTag)
|
||||||
|
require.Empty(t, tbl.Fields[4].Conversion)
|
||||||
|
|
||||||
|
require.Equal(t, ".1.0.0.0.1.4", tbl.Fields[1].Oid)
|
||||||
|
require.Equal(t, "description", tbl.Fields[1].Name)
|
||||||
|
require.True(t, tbl.Fields[1].IsTag)
|
||||||
|
require.Empty(t, tbl.Fields[1].Conversion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTableBuild_walk(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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "myfield6",
|
||||||
|
Oid: ".1.0.0.0.1.6",
|
||||||
|
Translate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "myfield7",
|
||||||
|
Oid: ".1.0.0.0.1.6",
|
||||||
|
Translate: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, tbl.Init(NewNetsnmpTranslator(testutil.Logger{})))
|
||||||
|
|
||||||
|
tb, err := tbl.Build(tsc, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "mytable", tb.Name)
|
||||||
|
rtr1 := RTableRow{
|
||||||
|
Tags: map[string]string{
|
||||||
|
"myfield1": "foo",
|
||||||
|
"index": "0",
|
||||||
|
},
|
||||||
|
Fields: map[string]interface{}{
|
||||||
|
"myfield2": 1,
|
||||||
|
"myfield3": float64(0.123),
|
||||||
|
"myfield4": 11,
|
||||||
|
"myfield5": 11,
|
||||||
|
"myfield6": "testTableEntry.7",
|
||||||
|
"myfield7": ".1.0.0.0.1.7",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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_noWalk(t *testing.T) {
|
||||||
|
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(tsc, false)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFieldConvert(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)},
|
||||||
|
{int(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 {
|
||||||
|
f := Field{
|
||||||
|
Name: "test",
|
||||||
|
Conversion: tc.conv,
|
||||||
|
}
|
||||||
|
require.NoError(t, f.Init(NewNetsnmpTranslator(testutil.Logger{})))
|
||||||
|
|
||||||
|
act, err := f.Convert(gosnmp.SnmpPDU{Value: 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSnmpTranslateCache_miss(t *testing.T) {
|
||||||
|
snmpTranslateCaches = nil
|
||||||
|
oid := "IF-MIB::ifPhysAddress.1"
|
||||||
|
mibName, oidNum, oidText, conversion, err := NewNetsnmpTranslator(testutil.Logger{}).SnmpTranslate(oid)
|
||||||
|
require.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSnmpTranslateCache_hit(t *testing.T) {
|
||||||
|
snmpTranslateCaches = map[string]snmpTranslateCache{
|
||||||
|
"foo": {
|
||||||
|
mibName: "a",
|
||||||
|
oidNum: "b",
|
||||||
|
oidText: "c",
|
||||||
|
conversion: "d",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mibName, oidNum, oidText, conversion, err := NewNetsnmpTranslator(testutil.Logger{}).SnmpTranslate("foo")
|
||||||
|
require.Equal(t, "a", mibName)
|
||||||
|
require.Equal(t, "b", oidNum)
|
||||||
|
require.Equal(t, "c", oidText)
|
||||||
|
require.Equal(t, "d", conversion)
|
||||||
|
require.NoError(t, err)
|
||||||
|
snmpTranslateCaches = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSnmpTableCache_miss(t *testing.T) {
|
||||||
|
snmpTableCaches = nil
|
||||||
|
oid := ".1.0.0.0"
|
||||||
|
mibName, oidNum, oidText, fields, err := NewNetsnmpTranslator(testutil.Logger{}).SnmpTable(oid)
|
||||||
|
require.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSnmpTableCache_hit(t *testing.T) {
|
||||||
|
snmpTableCaches = map[string]snmpTableCache{
|
||||||
|
"foo": {
|
||||||
|
mibName: "a",
|
||||||
|
oidNum: "b",
|
||||||
|
oidText: "c",
|
||||||
|
fields: []Field{{Name: "d"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mibName, oidNum, oidText, fields, err := NewNetsnmpTranslator(testutil.Logger{}).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.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,16 @@ import (
|
||||||
"github.com/gosnmp/gosnmp"
|
"github.com/gosnmp/gosnmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Connection is an interface which wraps a *gosnmp.GoSNMP object.
|
||||||
|
// We interact through an interface so we can mock it out in tests.
|
||||||
|
type Connection interface {
|
||||||
|
Host() string
|
||||||
|
//BulkWalkAll(string) ([]gosnmp.SnmpPDU, error)
|
||||||
|
Walk(string, gosnmp.WalkFunc) error
|
||||||
|
Get(oids []string) (*gosnmp.SnmpPacket, error)
|
||||||
|
Reconnect() error
|
||||||
|
}
|
||||||
|
|
||||||
// GosnmpWrapper wraps a *gosnmp.GoSNMP object so we can use it as a snmpConnection.
|
// GosnmpWrapper wraps a *gosnmp.GoSNMP object so we can use it as a snmpConnection.
|
||||||
type GosnmpWrapper struct {
|
type GosnmpWrapper struct {
|
||||||
*gosnmp.GoSNMP
|
*gosnmp.GoSNMP
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
package snmp
|
||||||
|
|
||||||
|
import "github.com/gosnmp/gosnmp"
|
||||||
|
|
||||||
|
type testSNMPConnection struct {
|
||||||
|
host string
|
||||||
|
values map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tsc *testSNMPConnection) Host() string {
|
||||||
|
return tsc.host
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tsc *testSNMPConnection) Get(oids []string) (*gosnmp.SnmpPacket, error) {
|
||||||
|
sp := &gosnmp.SnmpPacket{}
|
||||||
|
for _, oid := range oids {
|
||||||
|
v, ok := tsc.values[oid]
|
||||||
|
if !ok {
|
||||||
|
sp.Variables = append(sp.Variables, gosnmp.SnmpPDU{
|
||||||
|
Name: oid,
|
||||||
|
Type: gosnmp.NoSuchObject,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sp.Variables = append(sp.Variables, gosnmp.SnmpPDU{
|
||||||
|
Name: oid,
|
||||||
|
Value: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return sp, nil
|
||||||
|
}
|
||||||
|
func (tsc *testSNMPConnection) Walk(oid string, wf gosnmp.WalkFunc) error {
|
||||||
|
for void, v := range tsc.values {
|
||||||
|
if void == oid || (len(void) > len(oid) && void[:len(oid)+1] == oid+".") {
|
||||||
|
if err := wf(gosnmp.SnmpPDU{
|
||||||
|
Name: void,
|
||||||
|
Value: v,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (tsc *testSNMPConnection) Reconnect() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var tsc = &testSNMPConnection{
|
||||||
|
host: "tsc",
|
||||||
|
values: map[string]interface{}{
|
||||||
|
".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.0.1.6.0": ".1.0.0.0.1.7",
|
||||||
|
".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.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.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,143 +0,0 @@
|
||||||
package snmp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sleepinggenius2/gosmi"
|
|
||||||
"github.com/sleepinggenius2/gosmi/models"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/internal/snmp"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 //function-result-limit conditionally 5 return results allowed
|
|
||||||
func (g *gosmiTranslator) SnmpTranslate(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) {
|
|
||||||
mibName, oidNum, oidText, conversion, _, err = g.SnmpTranslateFull(oid)
|
|
||||||
return mibName, oidNum, oidText, conversion, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:revive //function-result-limit conditionally 6 return results allowed
|
|
||||||
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 := snmp.GetIndex(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, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *gosmiTranslator) SnmpFormatEnum(oid string, value interface{}, full bool) (string, error) {
|
|
||||||
//nolint:dogsled // only need to get the node
|
|
||||||
_, _, _, _, node, err := g.SnmpTranslateFull(oid)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var v models.Value
|
|
||||||
if full {
|
|
||||||
v = node.FormatValue(value, models.FormatEnumName, models.FormatEnumValue)
|
|
||||||
} else {
|
|
||||||
v = node.FormatValue(value, models.FormatEnumName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v.Formatted, nil
|
|
||||||
}
|
|
||||||
|
|
@ -3,18 +3,11 @@ package snmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gosnmp/gosnmp"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/config"
|
"github.com/influxdata/telegraf/config"
|
||||||
"github.com/influxdata/telegraf/internal/snmp"
|
"github.com/influxdata/telegraf/internal/snmp"
|
||||||
|
|
@ -25,25 +18,6 @@ import (
|
||||||
//go:embed sample.conf
|
//go:embed sample.conf
|
||||||
var sampleConfig string
|
var sampleConfig string
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
SnmpFormatEnum(oid string, value interface{}, full bool) (
|
|
||||||
formatted string,
|
|
||||||
err error,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snmp holds the configuration for the plugin.
|
// Snmp holds the configuration for the plugin.
|
||||||
type Snmp struct {
|
type Snmp struct {
|
||||||
// The SNMP agent to query. Format is [SCHEME://]ADDR[:PORT] (e.g.
|
// The SNMP agent to query. Format is [SCHEME://]ADDR[:PORT] (e.g.
|
||||||
|
|
@ -55,19 +29,19 @@ type Snmp struct {
|
||||||
|
|
||||||
snmp.ClientConfig
|
snmp.ClientConfig
|
||||||
|
|
||||||
Tables []Table `toml:"table"`
|
Tables []snmp.Table `toml:"table"`
|
||||||
|
|
||||||
// Name & Fields are the elements of a Table.
|
// Name & Fields are the elements of a Table.
|
||||||
// Telegraf chokes if we try to embed a Table. So instead we have to embed the
|
// Telegraf chokes if we try to embed a Table. So instead we have to embed the
|
||||||
// fields of a Table, and construct a Table during runtime.
|
// fields of a Table, and construct a Table during runtime.
|
||||||
Name string `toml:"name"`
|
Name string `toml:"name"`
|
||||||
Fields []Field `toml:"field"`
|
Fields []snmp.Field `toml:"field"`
|
||||||
|
|
||||||
connectionCache []snmpConnection
|
connectionCache []snmp.Connection
|
||||||
|
|
||||||
Log telegraf.Logger `toml:"-"`
|
Log telegraf.Logger `toml:"-"`
|
||||||
|
|
||||||
translator Translator
|
translator snmp.Translator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Snmp) SetTranslator(name string) {
|
func (s *Snmp) SetTranslator(name string) {
|
||||||
|
|
@ -82,17 +56,17 @@ func (s *Snmp) Init() error {
|
||||||
var err error
|
var err error
|
||||||
switch s.Translator {
|
switch s.Translator {
|
||||||
case "gosmi":
|
case "gosmi":
|
||||||
s.translator, err = NewGosmiTranslator(s.Path, s.Log)
|
s.translator, err = snmp.NewGosmiTranslator(s.Path, s.Log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "netsnmp":
|
case "netsnmp":
|
||||||
s.translator = NewNetsnmpTranslator()
|
s.translator = snmp.NewNetsnmpTranslator(s.Log)
|
||||||
default:
|
default:
|
||||||
return errors.New("invalid translator value")
|
return errors.New("invalid translator value")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.connectionCache = make([]snmpConnection, len(s.Agents))
|
s.connectionCache = make([]snmp.Connection, len(s.Agents))
|
||||||
|
|
||||||
for i := range s.Tables {
|
for i := range s.Tables {
|
||||||
if err := s.Tables[i].Init(s.translator); err != nil {
|
if err := s.Tables[i].Init(s.translator); err != nil {
|
||||||
|
|
@ -101,7 +75,7 @@ func (s *Snmp) Init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range s.Fields {
|
for i := range s.Fields {
|
||||||
if err := s.Fields[i].init(s.translator); err != nil {
|
if err := s.Fields[i].Init(s.translator); err != nil {
|
||||||
return fmt.Errorf("initializing field %s: %w", s.Fields[i].Name, err)
|
return fmt.Errorf("initializing field %s: %w", s.Fields[i].Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -119,200 +93,6 @@ func (s *Snmp) Init() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Table holds the configuration for a SNMP table.
|
|
||||||
type Table struct {
|
|
||||||
// Name will be the name of the measurement.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Which tags to inherit from the top-level config.
|
|
||||||
InheritTags []string
|
|
||||||
|
|
||||||
// Adds each row's table index as a tag.
|
|
||||||
IndexAsTag bool
|
|
||||||
|
|
||||||
// Fields is the tags and values to look up.
|
|
||||||
Fields []Field `toml:"field"`
|
|
||||||
|
|
||||||
// OID for automatic field population.
|
|
||||||
// If provided, init() will populate Fields with all the table columns of the
|
|
||||||
// given OID.
|
|
||||||
Oid string
|
|
||||||
|
|
||||||
initialized bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init() builds & initializes the nested fields.
|
|
||||||
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 == "" {
|
|
||||||
return errors.New("SNMP table in config file is not named. One or both of the oid and name settings must be set")
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.initialized {
|
|
||||||
return 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(tr); err != nil {
|
|
||||||
return fmt.Errorf("initializing field %s: %w", t.Fields[i].Name, err)
|
|
||||||
}
|
|
||||||
if t.Fields[i].SecondaryIndexTable {
|
|
||||||
if secondaryIndexTablePresent {
|
|
||||||
return errors.New("only one field can be SecondaryIndexTable")
|
|
||||||
}
|
|
||||||
secondaryIndexTablePresent = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.initialized = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(tr Translator) error {
|
|
||||||
if t.Oid == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, oidText, fields, err := tr.SnmpTable(t.Oid)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Name == "" {
|
|
||||||
t.Name = oidText
|
|
||||||
}
|
|
||||||
|
|
||||||
knownOIDs := map[string]bool{}
|
|
||||||
for _, f := range t.Fields {
|
|
||||||
knownOIDs[f.Oid] = true
|
|
||||||
}
|
|
||||||
for _, f := range fields {
|
|
||||||
if !knownOIDs[f.Oid] {
|
|
||||||
t.Fields = append(t.Fields, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Field holds the configuration for a Field to look up.
|
|
||||||
type Field struct {
|
|
||||||
// Name will be the name of the field.
|
|
||||||
Name string
|
|
||||||
// OID is prefix for this field. The plugin will perform a walk through all
|
|
||||||
// OIDs with this as their parent. For each value found, the plugin will strip
|
|
||||||
// off the OID prefix, and use the remainder as the index. For multiple fields
|
|
||||||
// to show up in the same row, they must share the same index.
|
|
||||||
Oid string
|
|
||||||
// OidIndexSuffix is the trailing sub-identifier on a table record OID that will be stripped off to get the record's index.
|
|
||||||
OidIndexSuffix string
|
|
||||||
// OidIndexLength specifies the length of the index in OID path segments. It can be used to remove sub-identifiers that vary in content or length.
|
|
||||||
OidIndexLength int
|
|
||||||
// IsTag controls whether this OID is output as a tag or a value.
|
|
||||||
IsTag bool
|
|
||||||
// Conversion controls any type conversion that is done on the value.
|
|
||||||
// "float"/"float(0)" will convert the value into a float.
|
|
||||||
// "float(X)" will convert the value into a float, and then move the decimal before Xth right-most digit.
|
|
||||||
// "int" will convert the value into an integer.
|
|
||||||
// "hwaddr" will convert a 6-byte string to a MAC address.
|
|
||||||
// "ipaddr" will convert the value to an IPv4 or IPv6 address.
|
|
||||||
// "enum"/"enum(1)" will convert the value according to its syntax. (Only supported with gosmi translator)
|
|
||||||
Conversion string
|
|
||||||
// Translate tells if the value of the field should be snmptranslated
|
|
||||||
Translate bool
|
|
||||||
// Secondary index table allows to merge data from two tables with different index
|
|
||||||
// that this filed will be used to join them. There can be only one secondary index table.
|
|
||||||
SecondaryIndexTable bool
|
|
||||||
// This field is using secondary index, and will be later merged with primary index
|
|
||||||
// using SecondaryIndexTable. SecondaryIndexTable and SecondaryIndexUse are exclusive.
|
|
||||||
SecondaryIndexUse bool
|
|
||||||
// Controls if entries from secondary table should be added or not if joining
|
|
||||||
// index is present or not. I set to true, means that join is outer, and
|
|
||||||
// index is prepended with "Secondary." for missing values to avoid overlapping
|
|
||||||
// indexes from both tables.
|
|
||||||
// Can be set per field or globally with SecondaryIndexTable, global true overrides
|
|
||||||
// per field false.
|
|
||||||
SecondaryOuterJoin bool
|
|
||||||
|
|
||||||
initialized bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// init() converts OID names to numbers, and sets the .Name attribute if unset.
|
|
||||||
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 := tr.SnmpTranslate(f.Oid)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("translating: %w", err)
|
|
||||||
}
|
|
||||||
f.Oid = oidNum
|
|
||||||
if f.Name == "" {
|
|
||||||
f.Name = oidText
|
|
||||||
}
|
|
||||||
if f.Conversion == "" {
|
|
||||||
f.Conversion = conversion
|
|
||||||
}
|
|
||||||
//TODO use textual convention conversion from the MIB
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.SecondaryIndexTable && f.SecondaryIndexUse {
|
|
||||||
return errors.New("SecondaryIndexTable and UseSecondaryIndex are exclusive")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.SecondaryIndexTable && !f.SecondaryIndexUse && f.SecondaryOuterJoin {
|
|
||||||
return errors.New("SecondaryOuterJoin set to true, but field is not being used in join")
|
|
||||||
}
|
|
||||||
|
|
||||||
f.initialized = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RTable is the resulting table built from a Table.
|
|
||||||
type RTable struct {
|
|
||||||
// Name is the name of the field, copied from Table.Name.
|
|
||||||
Name string
|
|
||||||
// Time is the time the table was built.
|
|
||||||
Time time.Time
|
|
||||||
// Rows are the rows that were found, one row for each table OID index found.
|
|
||||||
Rows []RTableRow
|
|
||||||
}
|
|
||||||
|
|
||||||
// RTableRow is the resulting row containing all the OID values which shared
|
|
||||||
// the same index.
|
|
||||||
type RTableRow struct {
|
|
||||||
// Tags are all the Field values which had IsTag=true.
|
|
||||||
Tags map[string]string
|
|
||||||
// Fields are all the Field values which had IsTag=false.
|
|
||||||
Fields map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type walkError struct {
|
|
||||||
msg string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *walkError) Error() string {
|
|
||||||
return e.msg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *walkError) Unwrap() error {
|
|
||||||
return e.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gather retrieves all the configured fields and tables.
|
// Gather retrieves all the configured fields and tables.
|
||||||
// Any error encountered does not halt the process. The errors are accumulated
|
// Any error encountered does not halt the process. The errors are accumulated
|
||||||
// and returned at the end.
|
// and returned at the end.
|
||||||
|
|
@ -329,7 +109,7 @@ func (s *Snmp) Gather(acc telegraf.Accumulator) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// First is the top-level fields. We treat the fields as table prefixes with an empty index.
|
// First is the top-level fields. We treat the fields as table prefixes with an empty index.
|
||||||
t := Table{
|
t := snmp.Table{
|
||||||
Name: s.Name,
|
Name: s.Name,
|
||||||
Fields: s.Fields,
|
Fields: s.Fields,
|
||||||
}
|
}
|
||||||
|
|
@ -351,8 +131,8 @@ func (s *Snmp) Gather(acc telegraf.Accumulator) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Snmp) gatherTable(acc telegraf.Accumulator, gs snmpConnection, t Table, topTags map[string]string, walk bool) error {
|
func (s *Snmp) gatherTable(acc telegraf.Accumulator, gs snmp.Connection, t snmp.Table, topTags map[string]string, walk bool) error {
|
||||||
rt, err := t.Build(gs, walk, s.translator)
|
rt, err := t.Build(gs, walk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -380,206 +160,11 @@ func (s *Snmp) gatherTable(acc telegraf.Accumulator, gs snmpConnection, t Table,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build retrieves all the fields specified in the table and constructs the RTable.
|
|
||||||
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)
|
|
||||||
secIdxTab := make(map[string]string)
|
|
||||||
secGlobalOuterJoin := false
|
|
||||||
for i, f := range t.Fields {
|
|
||||||
if f.SecondaryIndexTable {
|
|
||||||
secGlobalOuterJoin = f.SecondaryOuterJoin
|
|
||||||
if i != 0 {
|
|
||||||
t.Fields[0], t.Fields[i] = t.Fields[i], t.Fields[0]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tagCount := 0
|
|
||||||
for _, f := range t.Fields {
|
|
||||||
if f.IsTag {
|
|
||||||
tagCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(f.Oid) == 0 {
|
|
||||||
return nil, fmt.Errorf("cannot have empty OID on field %s", f.Name)
|
|
||||||
}
|
|
||||||
var oid string
|
|
||||||
if f.Oid[0] == '.' {
|
|
||||||
oid = f.Oid
|
|
||||||
} else {
|
|
||||||
// make sure OID has "." because the BulkWalkAll results do, and the prefix needs to match
|
|
||||||
oid = "." + f.Oid
|
|
||||||
}
|
|
||||||
|
|
||||||
// ifv contains a mapping of table OID index to field value
|
|
||||||
ifv := map[string]interface{}{}
|
|
||||||
|
|
||||||
if !walk {
|
|
||||||
// This is used when fetching non-table fields. Fields configured a the top
|
|
||||||
// scope of the plugin.
|
|
||||||
// We fetch the fields directly, and add them to ifv as if the index were an
|
|
||||||
// empty string. This results in all the non-table fields sharing the same
|
|
||||||
// index, and being added on the same row.
|
|
||||||
if pkt, err := gs.Get([]string{oid}); err != nil {
|
|
||||||
if errors.Is(err, gosnmp.ErrUnknownSecurityLevel) {
|
|
||||||
return nil, errors.New("unknown security level (sec_level)")
|
|
||||||
} else if errors.Is(err, gosnmp.ErrUnknownUsername) {
|
|
||||||
return nil, errors.New("unknown username (sec_name)")
|
|
||||||
} else if errors.Is(err, gosnmp.ErrWrongDigest) {
|
|
||||||
return nil, errors.New("wrong digest (auth_protocol, auth_password)")
|
|
||||||
} else if errors.Is(err, gosnmp.ErrDecryption) {
|
|
||||||
return nil, errors.New("decryption error (priv_protocol, priv_password)")
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("performing get on field %s: %w", f.Name, err)
|
|
||||||
} else if pkt != nil && len(pkt.Variables) > 0 && pkt.Variables[0].Type != gosnmp.NoSuchObject && pkt.Variables[0].Type != gosnmp.NoSuchInstance {
|
|
||||||
ent := pkt.Variables[0]
|
|
||||||
fv, err := fieldConvert(tr, f.Conversion, ent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("converting %q (OID %s) for field %s: %w", ent.Value, ent.Name, f.Name, err)
|
|
||||||
}
|
|
||||||
ifv[""] = fv
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := gs.Walk(oid, func(ent gosnmp.SnmpPDU) error {
|
|
||||||
if len(ent.Name) <= len(oid) || ent.Name[:len(oid)+1] != oid+"." {
|
|
||||||
return &walkError{} // break the walk
|
|
||||||
}
|
|
||||||
|
|
||||||
idx := ent.Name[len(oid):]
|
|
||||||
if f.OidIndexSuffix != "" {
|
|
||||||
if !strings.HasSuffix(idx, f.OidIndexSuffix) {
|
|
||||||
// this entry doesn't match our OidIndexSuffix. skip it
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
idx = idx[:len(idx)-len(f.OidIndexSuffix)]
|
|
||||||
}
|
|
||||||
if f.OidIndexLength != 0 {
|
|
||||||
i := f.OidIndexLength + 1 // leading separator
|
|
||||||
idx = strings.Map(func(r rune) rune {
|
|
||||||
if r == '.' {
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
if i < 1 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}, idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// snmptranslate table field value here
|
|
||||||
if f.Translate {
|
|
||||||
if entOid, ok := ent.Value.(string); ok {
|
|
||||||
_, _, oidText, _, err := tr.SnmpTranslate(entOid)
|
|
||||||
if err == nil {
|
|
||||||
// If no error translating, the original value for ent.Value should be replaced
|
|
||||||
ent.Value = oidText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fv, err := fieldConvert(tr, f.Conversion, ent)
|
|
||||||
if err != nil {
|
|
||||||
return &walkError{
|
|
||||||
msg: fmt.Sprintf("converting %q (OID %s) for field %s", ent.Value, ent.Name, f.Name),
|
|
||||||
err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ifv[idx] = fv
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// Our callback always wraps errors in a walkError.
|
|
||||||
// If this error isn't a walkError, we know it's not
|
|
||||||
// from the callback
|
|
||||||
var walkErr *walkError
|
|
||||||
if !errors.As(err, &walkErr) {
|
|
||||||
return nil, fmt.Errorf("performing bulk walk for field %s: %w", f.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, v := range ifv {
|
|
||||||
if f.SecondaryIndexUse {
|
|
||||||
if newidx, ok := secIdxTab[idx]; ok {
|
|
||||||
idx = newidx
|
|
||||||
} else {
|
|
||||||
if !secGlobalOuterJoin && !f.SecondaryOuterJoin {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
idx = ".Secondary" + idx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rtr, ok := rows[idx]
|
|
||||||
if !ok {
|
|
||||||
rtr = RTableRow{}
|
|
||||||
rtr.Tags = map[string]string{}
|
|
||||||
rtr.Fields = map[string]interface{}{}
|
|
||||||
rows[idx] = rtr
|
|
||||||
}
|
|
||||||
if t.IndexAsTag && idx != "" {
|
|
||||||
if idx[0] == '.' {
|
|
||||||
idx = idx[1:]
|
|
||||||
}
|
|
||||||
rtr.Tags["index"] = idx
|
|
||||||
}
|
|
||||||
// don't add an empty string
|
|
||||||
if vs, ok := v.(string); !ok || vs != "" {
|
|
||||||
if f.IsTag {
|
|
||||||
if ok {
|
|
||||||
rtr.Tags[f.Name] = vs
|
|
||||||
} else {
|
|
||||||
rtr.Tags[f.Name] = fmt.Sprintf("%v", v)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rtr.Fields[f.Name] = v
|
|
||||||
}
|
|
||||||
if f.SecondaryIndexTable {
|
|
||||||
//indexes are stored here with prepending "." so we need to add them if needed
|
|
||||||
var vss string
|
|
||||||
if ok {
|
|
||||||
vss = "." + vs
|
|
||||||
} else {
|
|
||||||
vss = fmt.Sprintf(".%v", v)
|
|
||||||
}
|
|
||||||
if idx[0] == '.' {
|
|
||||||
secIdxTab[vss] = idx
|
|
||||||
} else {
|
|
||||||
secIdxTab[vss] = "." + idx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rt := RTable{
|
|
||||||
Name: t.Name,
|
|
||||||
Time: time.Now(), //TODO record time at start
|
|
||||||
Rows: make([]RTableRow, 0, len(rows)),
|
|
||||||
}
|
|
||||||
for _, r := range rows {
|
|
||||||
rt.Rows = append(rt.Rows, r)
|
|
||||||
}
|
|
||||||
return &rt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// snmpConnection is an interface which wraps a *gosnmp.GoSNMP object.
|
|
||||||
// We interact through an interface so we can mock it out in tests.
|
|
||||||
type snmpConnection interface {
|
|
||||||
Host() string
|
|
||||||
//BulkWalkAll(string) ([]gosnmp.SnmpPDU, error)
|
|
||||||
Walk(string, gosnmp.WalkFunc) error
|
|
||||||
Get(oids []string) (*gosnmp.SnmpPacket, error)
|
|
||||||
Reconnect() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// getConnection creates a snmpConnection (*gosnmp.GoSNMP) object and caches the
|
// getConnection creates a snmpConnection (*gosnmp.GoSNMP) object and caches the
|
||||||
// result using `agentIndex` as the cache key. This is done to allow multiple
|
// result using `agentIndex` as the cache key. This is done to allow multiple
|
||||||
// connections to a single address. It is an error to use a connection in
|
// connections to a single address. It is an error to use a connection in
|
||||||
// more than one goroutine.
|
// more than one goroutine.
|
||||||
func (s *Snmp) getConnection(idx int) (snmpConnection, error) {
|
func (s *Snmp) getConnection(idx int) (snmp.Connection, error) {
|
||||||
if gs := s.connectionCache[idx]; gs != nil {
|
if gs := s.connectionCache[idx]; gs != nil {
|
||||||
if err := gs.Reconnect(); err != nil {
|
if err := gs.Reconnect(); err != nil {
|
||||||
return gs, fmt.Errorf("reconnecting: %w", err)
|
return gs, fmt.Errorf("reconnecting: %w", err)
|
||||||
|
|
@ -590,9 +175,7 @@ func (s *Snmp) getConnection(idx int) (snmpConnection, error) {
|
||||||
|
|
||||||
agent := s.Agents[idx]
|
agent := s.Agents[idx]
|
||||||
|
|
||||||
var err error
|
gs, err := snmp.NewWrapper(s.ClientConfig)
|
||||||
var gs snmp.GosnmpWrapper
|
|
||||||
gs, err = snmp.NewWrapper(s.ClientConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -611,173 +194,6 @@ func (s *Snmp) getConnection(idx int) (snmpConnection, error) {
|
||||||
return gs, nil
|
return gs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fieldConvert converts from any type according to the conv specification
|
|
||||||
func fieldConvert(tr Translator, conv string, ent gosnmp.SnmpPDU) (v interface{}, err error) {
|
|
||||||
if conv == "" {
|
|
||||||
if bs, ok := ent.Value.([]byte); ok {
|
|
||||||
return string(bs), nil
|
|
||||||
}
|
|
||||||
return ent.Value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var d int
|
|
||||||
if _, err := fmt.Sscanf(conv, "float(%d)", &d); err == nil || conv == "float" {
|
|
||||||
v = ent.Value
|
|
||||||
switch vt := v.(type) {
|
|
||||||
case float32:
|
|
||||||
v = float64(vt) / math.Pow10(d)
|
|
||||||
case float64:
|
|
||||||
v = vt / math.Pow10(d)
|
|
||||||
case int:
|
|
||||||
v = float64(vt) / math.Pow10(d)
|
|
||||||
case int8:
|
|
||||||
v = float64(vt) / math.Pow10(d)
|
|
||||||
case int16:
|
|
||||||
v = float64(vt) / math.Pow10(d)
|
|
||||||
case int32:
|
|
||||||
v = float64(vt) / math.Pow10(d)
|
|
||||||
case int64:
|
|
||||||
v = float64(vt) / math.Pow10(d)
|
|
||||||
case uint:
|
|
||||||
v = float64(vt) / math.Pow10(d)
|
|
||||||
case uint8:
|
|
||||||
v = float64(vt) / math.Pow10(d)
|
|
||||||
case uint16:
|
|
||||||
v = float64(vt) / math.Pow10(d)
|
|
||||||
case uint32:
|
|
||||||
v = float64(vt) / math.Pow10(d)
|
|
||||||
case uint64:
|
|
||||||
v = float64(vt) / math.Pow10(d)
|
|
||||||
case []byte:
|
|
||||||
vf, _ := strconv.ParseFloat(string(vt), 64)
|
|
||||||
v = vf / math.Pow10(d)
|
|
||||||
case string:
|
|
||||||
vf, _ := strconv.ParseFloat(vt, 64)
|
|
||||||
v = vf / math.Pow10(d)
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if conv == "int" {
|
|
||||||
v = ent.Value
|
|
||||||
switch vt := v.(type) {
|
|
||||||
case float32:
|
|
||||||
v = int64(vt)
|
|
||||||
case float64:
|
|
||||||
v = int64(vt)
|
|
||||||
case int:
|
|
||||||
v = int64(vt)
|
|
||||||
case int8:
|
|
||||||
v = int64(vt)
|
|
||||||
case int16:
|
|
||||||
v = int64(vt)
|
|
||||||
case int32:
|
|
||||||
v = int64(vt)
|
|
||||||
case int64:
|
|
||||||
v = vt
|
|
||||||
case uint:
|
|
||||||
v = int64(vt)
|
|
||||||
case uint8:
|
|
||||||
v = int64(vt)
|
|
||||||
case uint16:
|
|
||||||
v = int64(vt)
|
|
||||||
case uint32:
|
|
||||||
v = int64(vt)
|
|
||||||
case uint64:
|
|
||||||
v = int64(vt)
|
|
||||||
case []byte:
|
|
||||||
v, _ = strconv.ParseInt(string(vt), 10, 64)
|
|
||||||
case string:
|
|
||||||
v, _ = strconv.ParseInt(vt, 10, 64)
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if conv == "hwaddr" {
|
|
||||||
switch vt := ent.Value.(type) {
|
|
||||||
case string:
|
|
||||||
v = net.HardwareAddr(vt).String()
|
|
||||||
case []byte:
|
|
||||||
v = net.HardwareAddr(vt).String()
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid type (%T) for hwaddr conversion", v)
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
split := strings.Split(conv, ":")
|
|
||||||
if split[0] == "hextoint" && len(split) == 3 {
|
|
||||||
endian := split[1]
|
|
||||||
bit := split[2]
|
|
||||||
|
|
||||||
bv, ok := ent.Value.([]byte)
|
|
||||||
if !ok {
|
|
||||||
return ent.Value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch endian {
|
|
||||||
case "LittleEndian":
|
|
||||||
switch bit {
|
|
||||||
case "uint64":
|
|
||||||
v = binary.LittleEndian.Uint64(bv)
|
|
||||||
case "uint32":
|
|
||||||
v = binary.LittleEndian.Uint32(bv)
|
|
||||||
case "uint16":
|
|
||||||
v = binary.LittleEndian.Uint16(bv)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid bit value (%s) for hex to int conversion", bit)
|
|
||||||
}
|
|
||||||
case "BigEndian":
|
|
||||||
switch bit {
|
|
||||||
case "uint64":
|
|
||||||
v = binary.BigEndian.Uint64(bv)
|
|
||||||
case "uint32":
|
|
||||||
v = binary.BigEndian.Uint32(bv)
|
|
||||||
case "uint16":
|
|
||||||
v = binary.BigEndian.Uint16(bv)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid bit value (%s) for hex to int conversion", bit)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid Endian value (%s) for hex to int conversion", endian)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if conv == "ipaddr" {
|
|
||||||
var ipbs []byte
|
|
||||||
|
|
||||||
switch vt := ent.Value.(type) {
|
|
||||||
case string:
|
|
||||||
ipbs = []byte(vt)
|
|
||||||
case []byte:
|
|
||||||
ipbs = vt
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid type (%T) for ipaddr conversion", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch len(ipbs) {
|
|
||||||
case 4, 16:
|
|
||||||
v = net.IP(ipbs).String()
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid length (%d) for ipaddr conversion", len(ipbs))
|
|
||||||
}
|
|
||||||
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if conv == "enum" {
|
|
||||||
return tr.SnmpFormatEnum(ent.Name, ent.Value, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if conv == "enum(1)" {
|
|
||||||
return tr.SnmpFormatEnum(ent.Name, ent.Value, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("invalid conversion type %q", conv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
inputs.Add("snmp", func() telegraf.Input {
|
inputs.Add("snmp", func() telegraf.Input {
|
||||||
return &Snmp{
|
return &Snmp{
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
//go:generate go run -tags generate snmp_mocks_generate.go
|
|
||||||
package snmp
|
package snmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os/exec"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -77,12 +75,12 @@ var tsc = &testSNMPConnection{
|
||||||
".1.0.0.0.1.3.2": "0.000",
|
".1.0.0.0.1.3.2": "0.000",
|
||||||
".1.0.0.0.1.3.3": "9.999",
|
".1.0.0.0.1.3.3": "9.999",
|
||||||
".1.0.0.0.1.5.0": 123456,
|
".1.0.0.0.1.5.0": 123456,
|
||||||
|
".1.0.0.0.1.6.0": ".1.0.0.0.1.7",
|
||||||
".1.0.0.1.1": "baz",
|
".1.0.0.1.1": "baz",
|
||||||
".1.0.0.1.2": 234,
|
".1.0.0.1.2": 234,
|
||||||
".1.0.0.1.3": []byte("byte slice"),
|
".1.0.0.1.3": []byte("byte slice"),
|
||||||
".1.0.0.2.1.5.0.9.9": 11,
|
".1.0.0.2.1.5.0.9.9": 11,
|
||||||
".1.0.0.2.1.5.1.9.9": 22,
|
".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.10": "instance",
|
||||||
".1.0.0.3.1.1.11": "instance2",
|
".1.0.0.3.1.1.11": "instance2",
|
||||||
".1.0.0.3.1.1.12": "instance3",
|
".1.0.0.3.1.1.12": "instance3",
|
||||||
|
|
@ -92,108 +90,38 @@ var tsc = &testSNMPConnection{
|
||||||
".1.0.0.3.1.3.10": 1,
|
".1.0.0.3.1.3.10": 1,
|
||||||
".1.0.0.3.1.3.11": 2,
|
".1.0.0.3.1.3.11": 2,
|
||||||
".1.0.0.3.1.3.12": 3,
|
".1.0.0.3.1.3.12": 3,
|
||||||
|
".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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFieldInit(t *testing.T) {
|
|
||||||
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", ""},
|
|
||||||
{".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"},
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := NewNetsnmpTranslator()
|
|
||||||
for _, txl := range translations {
|
|
||||||
f := Field{Oid: txl.inputOid, Name: txl.inputName, Conversion: txl.inputConversion}
|
|
||||||
err := f.init(tr)
|
|
||||||
require.NoError(t, err, "inputOid=%q inputName=%q", txl.inputOid, txl.inputName)
|
|
||||||
require.Equal(t, txl.expectedOid, f.Oid, "inputOid=%q inputName=%q inputConversion=%q", txl.inputOid, txl.inputName, txl.inputConversion)
|
|
||||||
require.Equal(t, txl.expectedName, f.Name, "inputOid=%q inputName=%q inputConversion=%q", txl.inputOid, txl.inputName, txl.inputConversion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTableInit(t *testing.T) {
|
|
||||||
tbl := Table{
|
|
||||||
Oid: ".1.0.0.0",
|
|
||||||
Fields: []Field{
|
|
||||||
{Oid: ".999", Name: "foo"},
|
|
||||||
{Oid: "TEST::description", Name: "description", IsTag: true},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err := tbl.Init(NewNetsnmpTranslator())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "testTable", tbl.Name)
|
|
||||||
|
|
||||||
require.Len(t, tbl.Fields, 5)
|
|
||||||
require.Contains(t, tbl.Fields, Field{Oid: ".999", Name: "foo", initialized: true})
|
|
||||||
require.Contains(t, tbl.Fields, Field{Oid: ".1.0.0.0.1.1", Name: "server", IsTag: true, initialized: true})
|
|
||||||
require.Contains(t, tbl.Fields, Field{Oid: ".1.0.0.0.1.2", Name: "connections", initialized: true})
|
|
||||||
require.Contains(t, tbl.Fields, Field{Oid: ".1.0.0.0.1.3", Name: "latency", initialized: true})
|
|
||||||
require.Contains(t, tbl.Fields, Field{Oid: ".1.0.0.0.1.4", Name: "description", IsTag: true, initialized: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSnmpInit(t *testing.T) {
|
func TestSnmpInit(t *testing.T) {
|
||||||
s := &Snmp{
|
s := &Snmp{
|
||||||
Tables: []Table{
|
|
||||||
{Oid: "TEST::testTable"},
|
|
||||||
},
|
|
||||||
Fields: []Field{
|
|
||||||
{Oid: "TEST::hostname"},
|
|
||||||
},
|
|
||||||
ClientConfig: snmp.ClientConfig{
|
ClientConfig: snmp.ClientConfig{
|
||||||
Translator: "netsnmp",
|
Translator: "netsnmp",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.Init()
|
require.NoError(t, s.Init())
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Len(t, s.Tables[0].Fields, 4)
|
|
||||||
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.0.0.0.1.1", Name: "server", IsTag: true, initialized: true})
|
|
||||||
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.0.0.0.1.2", Name: "connections", initialized: true})
|
|
||||||
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.0.0.0.1.3", Name: "latency", initialized: true})
|
|
||||||
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.0.0.0.1.4", Name: "description", initialized: true})
|
|
||||||
|
|
||||||
require.Equal(t, Field{
|
|
||||||
Oid: ".1.0.0.1.1",
|
|
||||||
Name: "hostname",
|
|
||||||
initialized: true,
|
|
||||||
}, s.Fields[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSnmpInit_noTranslate(t *testing.T) {
|
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &Snmp{
|
s := &Snmp{
|
||||||
Fields: []Field{
|
Fields: []snmp.Field{
|
||||||
{Oid: ".1.1.1.1", Name: "one", IsTag: true},
|
{Oid: ".1.1.1.1", Name: "one", IsTag: true},
|
||||||
{Oid: ".1.1.1.2", Name: "two"},
|
{Oid: ".1.1.1.2", Name: "two"},
|
||||||
{Oid: ".1.1.1.3"},
|
{Oid: ".1.1.1.3"},
|
||||||
},
|
},
|
||||||
Tables: []Table{
|
Tables: []snmp.Table{
|
||||||
{Name: "testing",
|
{Name: "testing",
|
||||||
Fields: []Field{
|
Fields: []snmp.Field{
|
||||||
{Oid: ".1.1.1.4", Name: "four", IsTag: true},
|
{Oid: ".1.1.1.4", Name: "four", IsTag: true},
|
||||||
{Oid: ".1.1.1.5", Name: "five"},
|
{Oid: ".1.1.1.5", Name: "five"},
|
||||||
{Oid: ".1.1.1.6"},
|
{Oid: ".1.1.1.6"},
|
||||||
|
|
@ -202,6 +130,7 @@ func TestSnmpInit_noTranslate(t *testing.T) {
|
||||||
ClientConfig: snmp.ClientConfig{
|
ClientConfig: snmp.ClientConfig{
|
||||||
Translator: "netsnmp",
|
Translator: "netsnmp",
|
||||||
},
|
},
|
||||||
|
Log: testutil.Logger{Name: "inputs.snmp"},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.Init()
|
err := s.Init()
|
||||||
|
|
@ -234,8 +163,8 @@ func TestSnmpInit_noTranslate(t *testing.T) {
|
||||||
|
|
||||||
func TestSnmpInit_noName_noOid(t *testing.T) {
|
func TestSnmpInit_noName_noOid(t *testing.T) {
|
||||||
s := &Snmp{
|
s := &Snmp{
|
||||||
Tables: []Table{
|
Tables: []snmp.Table{
|
||||||
{Fields: []Field{
|
{Fields: []snmp.Field{
|
||||||
{Oid: ".1.1.1.4", Name: "four", IsTag: true},
|
{Oid: ".1.1.1.4", Name: "four", IsTag: true},
|
||||||
{Oid: ".1.1.1.5", Name: "five"},
|
{Oid: ".1.1.1.5", Name: "five"},
|
||||||
{Oid: ".1.1.1.6"},
|
{Oid: ".1.1.1.6"},
|
||||||
|
|
@ -243,8 +172,7 @@ func TestSnmpInit_noName_noOid(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.Init()
|
require.Error(t, s.Init())
|
||||||
require.Error(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetSNMPConnection_v2(t *testing.T) {
|
func TestGetSNMPConnection_v2(t *testing.T) {
|
||||||
|
|
@ -258,8 +186,7 @@ func TestGetSNMPConnection_v2(t *testing.T) {
|
||||||
Translator: "netsnmp",
|
Translator: "netsnmp",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err := s.Init()
|
require.NoError(t, s.Init())
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gsc, err := s.getConnection(0)
|
gsc, err := s.getConnection(0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -600,147 +527,11 @@ func TestGosnmpWrapper_get_retry(t *testing.T) {
|
||||||
require.Equal(t, (gs.Retries+1)*2, reqCount)
|
require.Equal(t, (gs.Retries+1)*2, reqCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTableBuild_walk(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,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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, NewNetsnmpTranslator())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "mytable", tb.Name)
|
|
||||||
rtr1 := RTableRow{
|
|
||||||
Tags: map[string]string{
|
|
||||||
"myfield1": "foo",
|
|
||||||
"index": "0",
|
|
||||||
},
|
|
||||||
Fields: map[string]interface{}{
|
|
||||||
"myfield2": 1,
|
|
||||||
"myfield3": float64(0.123),
|
|
||||||
"myfield4": 11,
|
|
||||||
"myfield5": 11,
|
|
||||||
"myfield6": "testTableEntry.7",
|
|
||||||
"myfield7": ".1.0.0.0.1.7",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
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),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
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_noWalk(t *testing.T) {
|
|
||||||
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(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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGather(t *testing.T) {
|
func TestGather(t *testing.T) {
|
||||||
s := &Snmp{
|
s := &Snmp{
|
||||||
Agents: []string{"TestGather"},
|
Agents: []string{"TestGather"},
|
||||||
Name: "mytable",
|
Name: "mytable",
|
||||||
Fields: []Field{
|
Fields: []snmp.Field{
|
||||||
{
|
{
|
||||||
Name: "myfield1",
|
Name: "myfield1",
|
||||||
Oid: ".1.0.0.1.1",
|
Oid: ".1.0.0.1.1",
|
||||||
|
|
@ -755,11 +546,11 @@ func TestGather(t *testing.T) {
|
||||||
Oid: "1.0.0.1.1",
|
Oid: "1.0.0.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Tables: []Table{
|
Tables: []snmp.Table{
|
||||||
{
|
{
|
||||||
Name: "myOtherTable",
|
Name: "myOtherTable",
|
||||||
InheritTags: []string{"myfield1"},
|
InheritTags: []string{"myfield1"},
|
||||||
Fields: []Field{
|
Fields: []snmp.Field{
|
||||||
{
|
{
|
||||||
Name: "myOtherField",
|
Name: "myOtherField",
|
||||||
Oid: ".1.0.0.0.1.5",
|
Oid: ".1.0.0.0.1.5",
|
||||||
|
|
@ -768,7 +559,7 @@ func TestGather(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
connectionCache: []snmpConnection{
|
connectionCache: []snmp.Connection{
|
||||||
tsc,
|
tsc,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -787,8 +578,7 @@ func TestGather(t *testing.T) {
|
||||||
require.Len(t, m.Fields, 2)
|
require.Len(t, m.Fields, 2)
|
||||||
require.Equal(t, 234, m.Fields["myfield2"])
|
require.Equal(t, 234, m.Fields["myfield2"])
|
||||||
require.Equal(t, "baz", m.Fields["myfield3"])
|
require.Equal(t, "baz", m.Fields["myfield3"])
|
||||||
require.False(t, tstart.After(m.Time))
|
require.WithinRange(t, m.Time, tstart, tstop)
|
||||||
require.False(t, tstop.Before(m.Time))
|
|
||||||
|
|
||||||
m2 := acc.Metrics[1]
|
m2 := acc.Metrics[1]
|
||||||
require.Equal(t, "myOtherTable", m2.Measurement)
|
require.Equal(t, "myOtherTable", m2.Measurement)
|
||||||
|
|
@ -802,7 +592,7 @@ func TestGather_host(t *testing.T) {
|
||||||
s := &Snmp{
|
s := &Snmp{
|
||||||
Agents: []string{"TestGather"},
|
Agents: []string{"TestGather"},
|
||||||
Name: "mytable",
|
Name: "mytable",
|
||||||
Fields: []Field{
|
Fields: []snmp.Field{
|
||||||
{
|
{
|
||||||
Name: "host",
|
Name: "host",
|
||||||
Oid: ".1.0.0.1.1",
|
Oid: ".1.0.0.1.1",
|
||||||
|
|
@ -814,7 +604,7 @@ func TestGather_host(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
connectionCache: []snmpConnection{
|
connectionCache: []snmp.Connection{
|
||||||
tsc,
|
tsc,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -828,366 +618,183 @@ func TestGather_host(t *testing.T) {
|
||||||
require.Equal(t, "baz", m.Tags["host"])
|
require.Equal(t, "baz", m.Tags["host"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFieldConvert(t *testing.T) {
|
func TestSnmpInitGosmi(t *testing.T) {
|
||||||
testTable := []struct {
|
testDataPath, err := filepath.Abs("../../../internal/snmp/testdata/gosmi")
|
||||||
input interface{}
|
require.NoError(t, err)
|
||||||
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)},
|
|
||||||
{int(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 {
|
s := &Snmp{
|
||||||
act, err := fieldConvert(NewNetsnmpTranslator(), tc.conv, gosnmp.SnmpPDU{Value: tc.input})
|
Tables: []snmp.Table{
|
||||||
require.NoError(t, err, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected)
|
{Oid: "RFC1213-MIB::atTable"},
|
||||||
require.EqualValues(t, tc.expected, act, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected)
|
},
|
||||||
}
|
Fields: []snmp.Field{
|
||||||
}
|
{Oid: "RFC1213-MIB::atPhysAddress"},
|
||||||
|
},
|
||||||
func TestSnmpTranslateCache_miss(t *testing.T) {
|
ClientConfig: snmp.ClientConfig{
|
||||||
snmpTranslateCaches = nil
|
Path: []string{testDataPath},
|
||||||
oid := "IF-MIB::ifPhysAddress.1"
|
Translator: "gosmi",
|
||||||
mibName, oidNum, oidText, conversion, err := NewNetsnmpTranslator().SnmpTranslate(oid)
|
|
||||||
require.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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSnmpTranslateCache_hit(t *testing.T) {
|
|
||||||
snmpTranslateCaches = map[string]snmpTranslateCache{
|
|
||||||
"foo": {
|
|
||||||
mibName: "a",
|
|
||||||
oidNum: "b",
|
|
||||||
oidText: "c",
|
|
||||||
conversion: "d",
|
|
||||||
err: errors.New("e"),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
mibName, oidNum, oidText, conversion, err := NewNetsnmpTranslator().SnmpTranslate("foo")
|
|
||||||
require.Equal(t, "a", mibName)
|
require.NoError(t, s.Init())
|
||||||
require.Equal(t, "b", oidNum)
|
|
||||||
require.Equal(t, "c", oidText)
|
require.Len(t, s.Tables[0].Fields, 3)
|
||||||
require.Equal(t, "d", conversion)
|
|
||||||
require.Equal(t, errors.New("e"), err)
|
require.Equal(t, ".1.3.6.1.2.1.3.1.1.1", s.Tables[0].Fields[0].Oid)
|
||||||
snmpTranslateCaches = nil
|
require.Equal(t, "atIfIndex", s.Tables[0].Fields[0].Name)
|
||||||
|
require.True(t, s.Tables[0].Fields[0].IsTag)
|
||||||
|
require.Empty(t, s.Tables[0].Fields[0].Conversion)
|
||||||
|
|
||||||
|
require.Equal(t, ".1.3.6.1.2.1.3.1.1.2", s.Tables[0].Fields[1].Oid)
|
||||||
|
require.Equal(t, "atPhysAddress", s.Tables[0].Fields[1].Name)
|
||||||
|
require.False(t, s.Tables[0].Fields[1].IsTag)
|
||||||
|
require.Equal(t, "hwaddr", s.Tables[0].Fields[1].Conversion)
|
||||||
|
|
||||||
|
require.Equal(t, ".1.3.6.1.2.1.3.1.1.3", s.Tables[0].Fields[2].Oid)
|
||||||
|
require.Equal(t, "atNetAddress", s.Tables[0].Fields[2].Name)
|
||||||
|
require.True(t, s.Tables[0].Fields[2].IsTag)
|
||||||
|
require.Empty(t, s.Tables[0].Fields[2].Conversion)
|
||||||
|
|
||||||
|
require.Equal(t, ".1.3.6.1.2.1.3.1.1.2", s.Fields[0].Oid)
|
||||||
|
require.Equal(t, "atPhysAddress", s.Fields[0].Name)
|
||||||
|
require.False(t, s.Fields[0].IsTag)
|
||||||
|
require.Equal(t, "hwaddr", s.Fields[0].Conversion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSnmpTableCache_miss(t *testing.T) {
|
func TestSnmpInit_noTranslateGosmi(t *testing.T) {
|
||||||
snmpTableCaches = nil
|
s := &Snmp{
|
||||||
oid := ".1.0.0.0"
|
Fields: []snmp.Field{
|
||||||
mibName, oidNum, oidText, fields, err := NewNetsnmpTranslator().SnmpTable(oid)
|
{Oid: ".9.1.1.1.1", Name: "one", IsTag: true},
|
||||||
require.Len(t, snmpTableCaches, 1)
|
{Oid: ".9.1.1.1.2", Name: "two"},
|
||||||
stc := snmpTableCaches[oid]
|
{Oid: ".9.1.1.1.3"},
|
||||||
require.NotNil(t, stc)
|
},
|
||||||
require.Equal(t, mibName, stc.mibName)
|
Tables: []snmp.Table{
|
||||||
require.Equal(t, oidNum, stc.oidNum)
|
{Name: "testing",
|
||||||
require.Equal(t, oidText, stc.oidText)
|
Fields: []snmp.Field{
|
||||||
require.Equal(t, fields, stc.fields)
|
{Oid: ".9.1.1.1.4", Name: "four", IsTag: true},
|
||||||
require.Equal(t, err, stc.err)
|
{Oid: ".9.1.1.1.5", Name: "five"},
|
||||||
}
|
{Oid: ".9.1.1.1.6"},
|
||||||
|
}},
|
||||||
func TestSnmpTableCache_hit(t *testing.T) {
|
},
|
||||||
snmpTableCaches = map[string]snmpTableCache{
|
ClientConfig: snmp.ClientConfig{
|
||||||
"foo": {
|
Path: []string{},
|
||||||
mibName: "a",
|
Translator: "gosmi",
|
||||||
oidNum: "b",
|
|
||||||
oidText: "c",
|
|
||||||
fields: []Field{{Name: "d"}},
|
|
||||||
err: errors.New("e"),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
mibName, oidNum, oidText, fields, err := NewNetsnmpTranslator().SnmpTable("foo")
|
|
||||||
require.Equal(t, "a", mibName)
|
require.NoError(t, s.Init())
|
||||||
require.Equal(t, "b", oidNum)
|
|
||||||
require.Equal(t, "c", oidText)
|
require.Equal(t, ".9.1.1.1.1", s.Fields[0].Oid)
|
||||||
require.Equal(t, []Field{{Name: "d"}}, fields)
|
require.Equal(t, "one", s.Fields[0].Name)
|
||||||
require.Equal(t, errors.New("e"), err)
|
require.True(t, s.Fields[0].IsTag)
|
||||||
|
|
||||||
|
require.Equal(t, ".9.1.1.1.2", s.Fields[1].Oid)
|
||||||
|
require.Equal(t, "two", s.Fields[1].Name)
|
||||||
|
require.False(t, s.Fields[1].IsTag)
|
||||||
|
|
||||||
|
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.False(t, s.Fields[2].IsTag)
|
||||||
|
|
||||||
|
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.True(t, s.Tables[0].Fields[0].IsTag)
|
||||||
|
|
||||||
|
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.False(t, s.Tables[0].Fields[1].IsTag)
|
||||||
|
|
||||||
|
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.False(t, s.Tables[0].Fields[2].IsTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTableJoin_walk(t *testing.T) {
|
func TestGatherGosmi(t *testing.T) {
|
||||||
tbl := Table{
|
s := &Snmp{
|
||||||
|
Agents: []string{"TestGather"},
|
||||||
Name: "mytable",
|
Name: "mytable",
|
||||||
IndexAsTag: true,
|
Fields: []snmp.Field{
|
||||||
Fields: []Field{
|
|
||||||
{
|
{
|
||||||
Name: "myfield1",
|
Name: "myfield1",
|
||||||
Oid: ".1.0.0.3.1.1",
|
Oid: ".1.0.0.1.1",
|
||||||
IsTag: true,
|
IsTag: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "myfield2",
|
Name: "myfield2",
|
||||||
Oid: ".1.0.0.3.1.2",
|
Oid: ".1.0.0.1.2",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "myfield3",
|
Name: "myfield3",
|
||||||
Oid: ".1.0.0.3.1.3",
|
Oid: "1.0.0.1.1",
|
||||||
SecondaryIndexTable: true,
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
Tables: []snmp.Table{
|
||||||
{
|
{
|
||||||
Name: "myfield4",
|
Name: "myOtherTable",
|
||||||
Oid: ".1.0.0.0.1.1",
|
InheritTags: []string{"myfield1"},
|
||||||
SecondaryIndexUse: true,
|
Fields: []snmp.Field{
|
||||||
IsTag: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "myfield5",
|
Name: "myOtherField",
|
||||||
Oid: ".1.0.0.0.1.2",
|
Oid: ".1.0.0.0.1.5",
|
||||||
SecondaryIndexUse: true,
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
|
|
||||||
tb, err := tbl.Build(tsc, true, NewNetsnmpTranslator())
|
connectionCache: []snmp.Connection{tsc},
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "mytable", tb.Name)
|
ClientConfig: snmp.ClientConfig{
|
||||||
rtr1 := RTableRow{
|
Translator: "gosmi",
|
||||||
Tags: map[string]string{
|
|
||||||
"myfield1": "instance",
|
|
||||||
"myfield4": "bar",
|
|
||||||
"index": "10",
|
|
||||||
},
|
|
||||||
Fields: map[string]interface{}{
|
|
||||||
"myfield2": 10,
|
|
||||||
"myfield3": 1,
|
|
||||||
"myfield5": 2,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
rtr2 := RTableRow{
|
acc := &testutil.Accumulator{}
|
||||||
Tags: map[string]string{
|
|
||||||
"myfield1": "instance2",
|
tstart := time.Now()
|
||||||
"index": "11",
|
require.NoError(t, s.Gather(acc))
|
||||||
},
|
tstop := time.Now()
|
||||||
Fields: map[string]interface{}{
|
|
||||||
"myfield2": 20,
|
require.Len(t, acc.Metrics, 2)
|
||||||
"myfield3": 2,
|
|
||||||
"myfield5": 0,
|
m := acc.Metrics[0]
|
||||||
},
|
require.Equal(t, "mytable", m.Measurement)
|
||||||
}
|
require.Equal(t, "tsc", m.Tags[s.AgentHostTag])
|
||||||
rtr3 := RTableRow{
|
require.Equal(t, "baz", m.Tags["myfield1"])
|
||||||
Tags: map[string]string{
|
require.Len(t, m.Fields, 2)
|
||||||
"myfield1": "instance3",
|
require.Equal(t, 234, m.Fields["myfield2"])
|
||||||
"index": "12",
|
require.Equal(t, "baz", m.Fields["myfield3"])
|
||||||
},
|
require.WithinRange(t, m.Time, tstart, tstop)
|
||||||
Fields: map[string]interface{}{
|
|
||||||
"myfield2": 20,
|
m2 := acc.Metrics[1]
|
||||||
"myfield3": 3,
|
require.Equal(t, "myOtherTable", m2.Measurement)
|
||||||
},
|
require.Equal(t, "tsc", m2.Tags[s.AgentHostTag])
|
||||||
}
|
require.Equal(t, "baz", m2.Tags["myfield1"])
|
||||||
require.Len(t, tb.Rows, 3)
|
require.Len(t, m2.Fields, 1)
|
||||||
require.Contains(t, tb.Rows, rtr1)
|
require.Equal(t, 123456, m2.Fields["myOtherField"])
|
||||||
require.Contains(t, tb.Rows, rtr2)
|
|
||||||
require.Contains(t, tb.Rows, rtr3)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTableOuterJoin_walk(t *testing.T) {
|
func TestGather_hostGosmi(t *testing.T) {
|
||||||
tbl := Table{
|
s := &Snmp{
|
||||||
|
Agents: []string{"TestGather"},
|
||||||
Name: "mytable",
|
Name: "mytable",
|
||||||
IndexAsTag: true,
|
Fields: []snmp.Field{
|
||||||
Fields: []Field{
|
|
||||||
{
|
{
|
||||||
Name: "myfield1",
|
Name: "host",
|
||||||
Oid: ".1.0.0.3.1.1",
|
Oid: ".1.0.0.1.1",
|
||||||
IsTag: true,
|
IsTag: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "myfield2",
|
Name: "myfield2",
|
||||||
Oid: ".1.0.0.3.1.2",
|
Oid: ".1.0.0.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,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
connectionCache: []snmp.Connection{tsc},
|
||||||
}
|
}
|
||||||
|
|
||||||
tb, err := tbl.Build(tsc, true, NewNetsnmpTranslator())
|
acc := &testutil.Accumulator{}
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "mytable", tb.Name)
|
require.NoError(t, s.Gather(acc))
|
||||||
rtr1 := RTableRow{
|
|
||||||
Tags: map[string]string{
|
require.Len(t, acc.Metrics, 1)
|
||||||
"myfield1": "instance",
|
m := acc.Metrics[0]
|
||||||
"myfield4": "bar",
|
require.Equal(t, "baz", m.Tags["host"])
|
||||||
"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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
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 TestTableJoinNoIndexAsTag_walk(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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
tb, err := tbl.Build(tsc, true, NewNetsnmpTranslator())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "mytable", tb.Name)
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
require.Len(t, tb.Rows, 3)
|
|
||||||
require.Contains(t, tb.Rows, rtr1)
|
|
||||||
require.Contains(t, tb.Rows, rtr2)
|
|
||||||
require.Contains(t, tb.Rows, rtr3)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/influxdata/telegraf/config"
|
"github.com/influxdata/telegraf/config"
|
||||||
"github.com/influxdata/telegraf/internal/snmp"
|
"github.com/influxdata/telegraf/internal/snmp"
|
||||||
"github.com/influxdata/telegraf/plugins/common/parallel"
|
"github.com/influxdata/telegraf/plugins/common/parallel"
|
||||||
si "github.com/influxdata/telegraf/plugins/inputs/snmp"
|
|
||||||
"github.com/influxdata/telegraf/plugins/processors"
|
"github.com/influxdata/telegraf/plugins/processors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -25,7 +24,6 @@ type keyType = string
|
||||||
type valType = nameMap
|
type valType = nameMap
|
||||||
|
|
||||||
type mapFunc func(agent string) (nameMap, error)
|
type mapFunc func(agent string) (nameMap, error)
|
||||||
type makeTableFunc func(string) (*si.Table, error)
|
|
||||||
|
|
||||||
type sigMap map[string]chan struct{}
|
type sigMap map[string]chan struct{}
|
||||||
|
|
||||||
|
|
@ -43,8 +41,8 @@ type IfName struct {
|
||||||
|
|
||||||
Log telegraf.Logger `toml:"-"`
|
Log telegraf.Logger `toml:"-"`
|
||||||
|
|
||||||
ifTable *si.Table
|
ifTable *snmp.Table
|
||||||
ifXTable *si.Table
|
ifXTable *snmp.Table
|
||||||
|
|
||||||
cache *TTLCache
|
cache *TTLCache
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
|
|
@ -52,9 +50,6 @@ type IfName struct {
|
||||||
sigs sigMap
|
sigs sigMap
|
||||||
|
|
||||||
getMapRemote mapFunc
|
getMapRemote mapFunc
|
||||||
makeTable makeTableFunc
|
|
||||||
|
|
||||||
translator si.Translator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const minRetry = 5 * time.Minute
|
const minRetry = 5 * time.Minute
|
||||||
|
|
@ -65,7 +60,6 @@ func (*IfName) SampleConfig() string {
|
||||||
|
|
||||||
func (d *IfName) Init() error {
|
func (d *IfName) Init() error {
|
||||||
d.getMapRemote = d.getMapRemoteNoMock
|
d.getMapRemote = d.getMapRemoteNoMock
|
||||||
d.makeTable = d.makeTableNoMock
|
|
||||||
|
|
||||||
c := NewTTLCache(time.Duration(d.CacheTTL), d.CacheSize)
|
c := NewTTLCache(time.Duration(d.CacheTTL), d.CacheSize)
|
||||||
d.cache = &c
|
d.cache = &c
|
||||||
|
|
@ -76,10 +70,6 @@ func (d *IfName) Init() error {
|
||||||
return fmt.Errorf("parsing SNMP client config: %w", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,17 +277,17 @@ func init() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IfName) makeTableNoMock(oid string) (*si.Table, error) {
|
func (d *IfName) makeTable(oid string) (*snmp.Table, error) {
|
||||||
var err error
|
var err error
|
||||||
tab := si.Table{
|
tab := snmp.Table{
|
||||||
Name: "ifTable",
|
Name: "ifTable",
|
||||||
IndexAsTag: true,
|
IndexAsTag: true,
|
||||||
Fields: []si.Field{
|
Fields: []snmp.Field{
|
||||||
{Oid: oid, Name: "ifName"},
|
{Oid: oid, Name: "ifName"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tab.Init(d.translator)
|
err = tab.Init(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Init already wraps
|
//Init already wraps
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -306,10 +296,10 @@ func (d *IfName) makeTableNoMock(oid string) (*si.Table, error) {
|
||||||
return &tab, nil
|
return &tab, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IfName) buildMap(gs snmp.GosnmpWrapper, tab *si.Table) (nameMap, error) {
|
func (d *IfName) buildMap(gs snmp.GosnmpWrapper, tab *snmp.Table) (nameMap, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
rtab, err := tab.Build(gs, true, d.translator)
|
rtab, err := tab.Build(gs, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Build already wraps
|
//Build already wraps
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/influxdata/telegraf/config"
|
"github.com/influxdata/telegraf/config"
|
||||||
"github.com/influxdata/telegraf/internal/snmp"
|
"github.com/influxdata/telegraf/internal/snmp"
|
||||||
"github.com/influxdata/telegraf/metric"
|
"github.com/influxdata/telegraf/metric"
|
||||||
si "github.com/influxdata/telegraf/plugins/inputs/snmp"
|
|
||||||
"github.com/influxdata/telegraf/testutil"
|
"github.com/influxdata/telegraf/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -105,12 +104,7 @@ func TestGetMap(t *testing.T) {
|
||||||
CacheTTL: config.Duration(10 * time.Second),
|
CacheTTL: config.Duration(10 * time.Second),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't run net-snmp commands to look up table names.
|
require.NoError(t, d.Init())
|
||||||
d.makeTable = func(string) (*si.Table, error) {
|
|
||||||
return &si.Table{}, nil
|
|
||||||
}
|
|
||||||
err := d.Init()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected := nameMap{
|
expected := nameMap{
|
||||||
1: "ifname1",
|
1: "ifname1",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue