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 {
|
||||
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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gosnmp/gosnmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/internal/snmp"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
func getGosmiTr(t *testing.T) Translator {
|
||||
testDataPath, err := filepath.Abs("./testdata")
|
||||
testDataPath, err := filepath.Abs("./testdata/gosmi")
|
||||
require.NoError(t, err)
|
||||
|
||||
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
|
||||
|
|
@ -31,56 +28,8 @@ func TestGosmiTranslator(t *testing.T) {
|
|||
require.NotNil(t, tr)
|
||||
}
|
||||
|
||||
// gosmi uses the same connection struct as netsnmp but has a few
|
||||
// different test cases, so it has its own copy
|
||||
var gosmiTsc = &testSNMPConnection{
|
||||
host: "tsc",
|
||||
values: map[string]interface{}{
|
||||
".1.3.6.1.2.1.3.1.1.1.0": "foo",
|
||||
".1.3.6.1.2.1.3.1.1.1.1": []byte("bar"),
|
||||
".1.3.6.1.2.1.3.1.1.1.2": []byte(""),
|
||||
".1.3.6.1.2.1.3.1.1.102": "bad",
|
||||
".1.3.6.1.2.1.3.1.1.2.0": 1,
|
||||
".1.3.6.1.2.1.3.1.1.2.1": 2,
|
||||
".1.3.6.1.2.1.3.1.1.2.2": 0,
|
||||
".1.3.6.1.2.1.3.1.1.3.0": "1.3.6.1.2.1.3.1.1.3",
|
||||
".1.3.6.1.2.1.3.1.1.5.0": 123456,
|
||||
".1.0.0.0.1.1.0": "foo",
|
||||
".1.0.0.0.1.1.1": []byte("bar"),
|
||||
".1.0.0.0.1.1.2": []byte(""),
|
||||
".1.0.0.0.1.102": "bad",
|
||||
".1.0.0.0.1.2.0": 1,
|
||||
".1.0.0.0.1.2.1": 2,
|
||||
".1.0.0.0.1.2.2": 0,
|
||||
".1.0.0.0.1.3.0": "0.123",
|
||||
".1.0.0.0.1.3.1": "0.456",
|
||||
".1.0.0.0.1.3.2": "0.000",
|
||||
".1.0.0.0.1.3.3": "9.999",
|
||||
".1.0.0.0.1.5.0": 123456,
|
||||
".1.0.0.1.1": "baz",
|
||||
".1.0.0.1.2": 234,
|
||||
".1.0.0.1.3": []byte("byte slice"),
|
||||
".1.0.0.2.1.5.0.9.9": 11,
|
||||
".1.0.0.2.1.5.1.9.9": 22,
|
||||
".1.0.0.0.1.6.0": ".1.0.0.0.1.7",
|
||||
".1.0.0.3.1.1.10": "instance",
|
||||
".1.0.0.3.1.1.11": "instance2",
|
||||
".1.0.0.3.1.1.12": "instance3",
|
||||
".1.0.0.3.1.2.10": 10,
|
||||
".1.0.0.3.1.2.11": 20,
|
||||
".1.0.0.3.1.2.12": 20,
|
||||
".1.0.0.3.1.3.10": 1,
|
||||
".1.0.0.3.1.3.11": 2,
|
||||
".1.0.0.3.1.3.12": 3,
|
||||
},
|
||||
}
|
||||
|
||||
func TestFieldInitGosmi(t *testing.T) {
|
||||
testDataPath, err := filepath.Abs("./testdata")
|
||||
require.NoError(t, err)
|
||||
|
||||
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
|
||||
require.NoError(t, err)
|
||||
tr := getGosmiTr(t)
|
||||
|
||||
translations := []struct {
|
||||
inputOid string
|
||||
|
|
@ -102,124 +51,50 @@ func TestFieldInitGosmi(t *testing.T) {
|
|||
|
||||
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.NoError(t, f.Init(tr), "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)
|
||||
require.Equal(t, txl.expectedConversion, f.Conversion, "inputOid=%q inputName=%q inputConversion=%q", txl.inputOid, txl.inputName, txl.inputConversion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableInitGosmi(t *testing.T) {
|
||||
testDataPath, err := filepath.Abs("./testdata")
|
||||
require.NoError(t, err)
|
||||
|
||||
s := &Snmp{
|
||||
ClientConfig: snmp.ClientConfig{
|
||||
Path: []string{testDataPath},
|
||||
Translator: "gosmi",
|
||||
},
|
||||
Tables: []Table{
|
||||
{Oid: ".1.3.6.1.2.1.3.1",
|
||||
tbl := Table{
|
||||
Oid: ".1.3.6.1.2.1.3.1",
|
||||
Fields: []Field{
|
||||
{Oid: ".999", Name: "foo"},
|
||||
{Oid: ".1.3.6.1.2.1.3.1.1.1", Name: "atIfIndex", IsTag: true},
|
||||
{Oid: "RFC1213-MIB::atPhysAddress", Name: "atPhysAddress"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
err = s.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
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()
|
||||
require.NoError(t, err)
|
||||
tr := getGosmiTr(t)
|
||||
require.NoError(t, tbl.Init(tr))
|
||||
|
||||
require.Len(t, s.Tables[0].Fields, 3)
|
||||
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.1", Name: "atIfIndex", IsTag: true, initialized: true})
|
||||
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.2", Name: "atPhysAddress", initialized: true, Conversion: "hwaddr"})
|
||||
require.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.3.6.1.2.1.3.1.1.3", Name: "atNetAddress", IsTag: true, initialized: true})
|
||||
require.Equal(t, "atTable", tbl.Name)
|
||||
|
||||
require.Equal(t, Field{
|
||||
Oid: ".1.3.6.1.2.1.3.1.1.2",
|
||||
Name: "atPhysAddress",
|
||||
Conversion: "hwaddr",
|
||||
initialized: true,
|
||||
}, s.Fields[0])
|
||||
}
|
||||
require.Len(t, tbl.Fields, 5)
|
||||
|
||||
func TestSnmpInit_noTranslateGosmi(t *testing.T) {
|
||||
s := &Snmp{
|
||||
Fields: []Field{
|
||||
{Oid: ".9.1.1.1.1", Name: "one", IsTag: true},
|
||||
{Oid: ".9.1.1.1.2", Name: "two"},
|
||||
{Oid: ".9.1.1.1.3"},
|
||||
},
|
||||
Tables: []Table{
|
||||
{Name: "testing",
|
||||
Fields: []Field{
|
||||
{Oid: ".9.1.1.1.4", Name: "four", IsTag: true},
|
||||
{Oid: ".9.1.1.1.5", Name: "five"},
|
||||
{Oid: ".9.1.1.1.6"},
|
||||
}},
|
||||
},
|
||||
ClientConfig: snmp.ClientConfig{
|
||||
Path: []string{},
|
||||
Translator: "gosmi",
|
||||
},
|
||||
}
|
||||
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)
|
||||
|
||||
err := s.Init()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ".1.3.6.1.2.1.3.1.1.1", tbl.Fields[1].Oid)
|
||||
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, "one", s.Fields[0].Name)
|
||||
require.True(t, s.Fields[0].IsTag)
|
||||
require.Equal(t, ".1.3.6.1.2.1.3.1.1.2", tbl.Fields[2].Oid)
|
||||
require.Equal(t, "atPhysAddress", tbl.Fields[2].Name)
|
||||
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, "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)
|
||||
require.Equal(t, ".1.3.6.1.2.1.3.1.1.3", tbl.Fields[4].Oid)
|
||||
require.Equal(t, "atNetAddress", tbl.Fields[4].Name)
|
||||
require.True(t, tbl.Fields[4].IsTag)
|
||||
require.Empty(t, tbl.Fields[4].Conversion)
|
||||
}
|
||||
|
||||
// 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")
|
||||
require.NoError(t, err)
|
||||
|
||||
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
|
||||
require.NoError(t, err)
|
||||
|
||||
tb, err := tbl.Build(gosmiTsc, true, tr)
|
||||
tb, err := tbl.Build(tsc, true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "mytable", tb.Name)
|
||||
rtr1 := RTableRow{
|
||||
|
|
@ -317,12 +186,6 @@ func TestTableBuild_walk_noTranslate(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{
|
||||
Name: "atTable",
|
||||
IndexAsTag: true,
|
||||
|
|
@ -345,9 +208,8 @@ func TestTableBuild_walk_Translate(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
err = tbl.Init(tr)
|
||||
require.NoError(t, err)
|
||||
tb, err := tbl.Build(gosmiTsc, true, tr)
|
||||
require.NoError(t, tbl.Init(getGosmiTr(t)))
|
||||
tb, err := tbl.Build(tsc, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "atTable", tb.Name)
|
||||
|
|
@ -387,12 +249,6 @@ func TestTableBuild_walk_Translate(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{
|
||||
Name: "mytable",
|
||||
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)
|
||||
|
||||
rtr := RTableRow{
|
||||
|
|
@ -432,103 +288,6 @@ func TestTableBuild_noWalkGosmi(t *testing.T) {
|
|||
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) {
|
||||
testTable := []struct {
|
||||
input interface{}
|
||||
|
|
@ -585,77 +344,18 @@ func TestFieldConvertGosmi(t *testing.T) {
|
|||
}
|
||||
|
||||
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.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) {
|
||||
tbl := Table{
|
||||
Name: "mytable",
|
||||
|
|
@ -689,13 +389,8 @@ func TestTableJoin_walkGosmi(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
testDataPath, err := filepath.Abs("./testdata")
|
||||
require.NoError(t, err)
|
||||
|
||||
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
|
||||
require.NoError(t, err)
|
||||
|
||||
tb, err := tbl.Build(gosmiTsc, true, tr)
|
||||
require.NoError(t, tbl.Init(getGosmiTr(t)))
|
||||
tb, err := tbl.Build(tsc, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "mytable", tb.Name)
|
||||
|
|
@ -772,13 +467,7 @@ func TestTableOuterJoin_walkGosmi(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
testDataPath, err := filepath.Abs("./testdata")
|
||||
require.NoError(t, err)
|
||||
|
||||
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
|
||||
require.NoError(t, err)
|
||||
|
||||
tb, err := tbl.Build(gosmiTsc, true, tr)
|
||||
tb, err := tbl.Build(tsc, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "mytable", tb.Name)
|
||||
|
|
@ -864,13 +553,7 @@ func TestTableJoinNoIndexAsTag_walkGosmi(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
testDataPath, err := filepath.Abs("./testdata")
|
||||
require.NoError(t, err)
|
||||
|
||||
tr, err := NewGosmiTranslator([]string{testDataPath}, testutil.Logger{})
|
||||
require.NoError(t, err)
|
||||
|
||||
tb, err := tbl.Build(gosmiTsc, true, tr)
|
||||
tb, err := tbl.Build(tsc, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "mytable", tb.Name)
|
||||
|
|
@ -913,33 +596,91 @@ func TestTableJoinNoIndexAsTag_walkGosmi(t *testing.T) {
|
|||
require.Contains(t, tb.Rows, rtr3)
|
||||
}
|
||||
|
||||
func BenchmarkMibLoading(b *testing.B) {
|
||||
log := testutil.Logger{}
|
||||
path := []string{"testdata"}
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := snmp.LoadMibsFromPath(path, log, &snmp.GosmiMibLoader{})
|
||||
require.NoError(b, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanNotParse(t *testing.T) {
|
||||
s := &Snmp{
|
||||
Fields: []Field{
|
||||
{Oid: "RFC1213-MIB::"},
|
||||
tr := getGosmiTr(t)
|
||||
f := Field{
|
||||
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()
|
||||
require.Error(t, err)
|
||||
// Load the MIBs
|
||||
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) {
|
||||
log := testutil.Logger{}
|
||||
path := []string{"non-existing-directory"}
|
||||
err := snmp.LoadMibsFromPath(path, log, &snmp.GosmiMibLoader{})
|
||||
require.NoError(t, err)
|
||||
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
|
||||
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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log" //nolint:depguard // Allow exceptional but valid use of log here.
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/influxdata/wlog"
|
||||
"github.com/influxdata/telegraf"
|
||||
)
|
||||
|
||||
// struct that implements the translator interface. This calls existing
|
||||
// code to exec netsnmp's snmptranslate program
|
||||
type netsnmpTranslator struct {
|
||||
log telegraf.Logger
|
||||
}
|
||||
|
||||
func NewNetsnmpTranslator() *netsnmpTranslator {
|
||||
return &netsnmpTranslator{}
|
||||
func NewNetsnmpTranslator(log telegraf.Logger) *netsnmpTranslator {
|
||||
return &netsnmpTranslator{log: log}
|
||||
}
|
||||
|
||||
type snmpTableCache struct {
|
||||
|
|
@ -35,14 +35,12 @@ var execCommand = exec.Command
|
|||
|
||||
// execCmd executes the specified command, returning the STDOUT content.
|
||||
// If command exits with error status, the output is captured into the returned error.
|
||||
func execCmd(arg0 string, args ...string) ([]byte, error) {
|
||||
if wlog.LogLevel() == wlog.DEBUG {
|
||||
func (n *netsnmpTranslator) execCmd(arg0 string, args ...string) ([]byte, error) {
|
||||
quoted := make([]string, 0, len(args))
|
||||
for _, arg := range args {
|
||||
quoted = append(quoted, fmt.Sprintf("%q", arg))
|
||||
}
|
||||
log.Printf("D! [inputs.snmp] executing %q %s", arg0, strings.Join(quoted, " "))
|
||||
}
|
||||
n.log.Debugf("executing %q %s", arg0, strings.Join(quoted, " "))
|
||||
|
||||
out, err := execCommand(arg0, args...).Output()
|
||||
if err != nil {
|
||||
|
|
@ -98,7 +96,7 @@ func (n *netsnmpTranslator) snmpTableCall(oid string) (
|
|||
// first attempt to get the table's tags
|
||||
tagOids := map[string]struct{}{}
|
||||
// We have to guess that the "entry" oid is `oid+".1"`. snmptable and snmptranslate don't seem to have a way to provide the info.
|
||||
if out, err := execCmd("snmptranslate", "-Td", oidFullName+".1"); err == nil {
|
||||
if out, err := n.execCmd("snmptranslate", "-Td", oidFullName+".1"); err == nil {
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(out))
|
||||
for scanner.Scan() {
|
||||
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.
|
||||
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 {
|
||||
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
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
@ -189,12 +187,12 @@ func (n *netsnmpTranslator) SnmpTranslate(oid string) (
|
|||
}
|
||||
|
||||
//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
|
||||
if strings.ContainsAny(oid, ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
|
||||
out, err = execCmd("snmptranslate", "-Td", "-Ob", oid)
|
||||
out, err = n.execCmd("snmptranslate", "-Td", "-Ob", oid)
|
||||
} else {
|
||||
out, err = execCmd("snmptranslate", "-Td", "-Ob", "-m", "all", oid)
|
||||
out, err = n.execCmd("snmptranslate", "-Td", "-Ob", "-m", "all", oid)
|
||||
var execErr *exec.Error
|
||||
if errors.As(err, &execErr) && errors.Is(execErr, exec.ErrNotFound) {
|
||||
// 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.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"
|
||||
)
|
||||
|
||||
// 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.
|
||||
type GosnmpWrapper struct {
|
||||
*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 (
|
||||
_ "embed"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gosnmp/gosnmp"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/internal/snmp"
|
||||
|
|
@ -25,25 +18,6 @@ import (
|
|||
//go:embed sample.conf
|
||||
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.
|
||||
type Snmp struct {
|
||||
// The SNMP agent to query. Format is [SCHEME://]ADDR[:PORT] (e.g.
|
||||
|
|
@ -55,19 +29,19 @@ type Snmp struct {
|
|||
|
||||
snmp.ClientConfig
|
||||
|
||||
Tables []Table `toml:"table"`
|
||||
Tables []snmp.Table `toml:"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
|
||||
// fields of a Table, and construct a Table during runtime.
|
||||
Name string `toml:"name"`
|
||||
Fields []Field `toml:"field"`
|
||||
Fields []snmp.Field `toml:"field"`
|
||||
|
||||
connectionCache []snmpConnection
|
||||
connectionCache []snmp.Connection
|
||||
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
|
||||
translator Translator
|
||||
translator snmp.Translator
|
||||
}
|
||||
|
||||
func (s *Snmp) SetTranslator(name string) {
|
||||
|
|
@ -82,17 +56,17 @@ func (s *Snmp) Init() error {
|
|||
var err error
|
||||
switch s.Translator {
|
||||
case "gosmi":
|
||||
s.translator, err = NewGosmiTranslator(s.Path, s.Log)
|
||||
s.translator, err = snmp.NewGosmiTranslator(s.Path, s.Log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "netsnmp":
|
||||
s.translator = NewNetsnmpTranslator()
|
||||
s.translator = snmp.NewNetsnmpTranslator(s.Log)
|
||||
default:
|
||||
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 {
|
||||
if err := s.Tables[i].Init(s.translator); err != nil {
|
||||
|
|
@ -101,7 +75,7 @@ func (s *Snmp) Init() error {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -119,200 +93,6 @@ func (s *Snmp) Init() error {
|
|||
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.
|
||||
// Any error encountered does not halt the process. The errors are accumulated
|
||||
// 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.
|
||||
t := Table{
|
||||
t := snmp.Table{
|
||||
Name: s.Name,
|
||||
Fields: s.Fields,
|
||||
}
|
||||
|
|
@ -351,8 +131,8 @@ func (s *Snmp) Gather(acc telegraf.Accumulator) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Snmp) gatherTable(acc telegraf.Accumulator, gs snmpConnection, t Table, topTags map[string]string, walk bool) error {
|
||||
rt, err := t.Build(gs, walk, s.translator)
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -380,206 +160,11 @@ func (s *Snmp) gatherTable(acc telegraf.Accumulator, gs snmpConnection, t Table,
|
|||
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
|
||||
// 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
|
||||
// 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 err := gs.Reconnect(); err != nil {
|
||||
return gs, fmt.Errorf("reconnecting: %w", err)
|
||||
|
|
@ -590,9 +175,7 @@ func (s *Snmp) getConnection(idx int) (snmpConnection, error) {
|
|||
|
||||
agent := s.Agents[idx]
|
||||
|
||||
var err error
|
||||
var gs snmp.GosnmpWrapper
|
||||
gs, err = snmp.NewWrapper(s.ClientConfig)
|
||||
gs, err := snmp.NewWrapper(s.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -611,173 +194,6 @@ func (s *Snmp) getConnection(idx int) (snmpConnection, error) {
|
|||
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() {
|
||||
inputs.Add("snmp", func() telegraf.Input {
|
||||
return &Snmp{
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
//go:generate go run -tags generate snmp_mocks_generate.go
|
||||
package snmp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -77,12 +75,12 @@ var tsc = &testSNMPConnection{
|
|||
".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.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",
|
||||
|
|
@ -92,108 +90,38 @@ var tsc = &testSNMPConnection{
|
|||
".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,
|
||||
},
|
||||
}
|
||||
|
||||
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) {
|
||||
s := &Snmp{
|
||||
Tables: []Table{
|
||||
{Oid: "TEST::testTable"},
|
||||
},
|
||||
Fields: []Field{
|
||||
{Oid: "TEST::hostname"},
|
||||
},
|
||||
ClientConfig: snmp.ClientConfig{
|
||||
Translator: "netsnmp",
|
||||
},
|
||||
}
|
||||
|
||||
err := 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])
|
||||
require.NoError(t, s.Init())
|
||||
}
|
||||
|
||||
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{
|
||||
Fields: []Field{
|
||||
Fields: []snmp.Field{
|
||||
{Oid: ".1.1.1.1", Name: "one", IsTag: true},
|
||||
{Oid: ".1.1.1.2", Name: "two"},
|
||||
{Oid: ".1.1.1.3"},
|
||||
},
|
||||
Tables: []Table{
|
||||
Tables: []snmp.Table{
|
||||
{Name: "testing",
|
||||
Fields: []Field{
|
||||
Fields: []snmp.Field{
|
||||
{Oid: ".1.1.1.4", Name: "four", IsTag: true},
|
||||
{Oid: ".1.1.1.5", Name: "five"},
|
||||
{Oid: ".1.1.1.6"},
|
||||
|
|
@ -202,6 +130,7 @@ func TestSnmpInit_noTranslate(t *testing.T) {
|
|||
ClientConfig: snmp.ClientConfig{
|
||||
Translator: "netsnmp",
|
||||
},
|
||||
Log: testutil.Logger{Name: "inputs.snmp"},
|
||||
}
|
||||
|
||||
err := s.Init()
|
||||
|
|
@ -234,8 +163,8 @@ func TestSnmpInit_noTranslate(t *testing.T) {
|
|||
|
||||
func TestSnmpInit_noName_noOid(t *testing.T) {
|
||||
s := &Snmp{
|
||||
Tables: []Table{
|
||||
{Fields: []Field{
|
||||
Tables: []snmp.Table{
|
||||
{Fields: []snmp.Field{
|
||||
{Oid: ".1.1.1.4", Name: "four", IsTag: true},
|
||||
{Oid: ".1.1.1.5", Name: "five"},
|
||||
{Oid: ".1.1.1.6"},
|
||||
|
|
@ -243,8 +172,7 @@ func TestSnmpInit_noName_noOid(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
err := s.Init()
|
||||
require.Error(t, err)
|
||||
require.Error(t, s.Init())
|
||||
}
|
||||
|
||||
func TestGetSNMPConnection_v2(t *testing.T) {
|
||||
|
|
@ -258,8 +186,7 @@ func TestGetSNMPConnection_v2(t *testing.T) {
|
|||
Translator: "netsnmp",
|
||||
},
|
||||
}
|
||||
err := s.Init()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.Init())
|
||||
|
||||
gsc, err := s.getConnection(0)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -600,147 +527,11 @@ func TestGosnmpWrapper_get_retry(t *testing.T) {
|
|||
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) {
|
||||
s := &Snmp{
|
||||
Agents: []string{"TestGather"},
|
||||
Name: "mytable",
|
||||
Fields: []Field{
|
||||
Fields: []snmp.Field{
|
||||
{
|
||||
Name: "myfield1",
|
||||
Oid: ".1.0.0.1.1",
|
||||
|
|
@ -755,11 +546,11 @@ func TestGather(t *testing.T) {
|
|||
Oid: "1.0.0.1.1",
|
||||
},
|
||||
},
|
||||
Tables: []Table{
|
||||
Tables: []snmp.Table{
|
||||
{
|
||||
Name: "myOtherTable",
|
||||
InheritTags: []string{"myfield1"},
|
||||
Fields: []Field{
|
||||
Fields: []snmp.Field{
|
||||
{
|
||||
Name: "myOtherField",
|
||||
Oid: ".1.0.0.0.1.5",
|
||||
|
|
@ -768,7 +559,7 @@ func TestGather(t *testing.T) {
|
|||
},
|
||||
},
|
||||
|
||||
connectionCache: []snmpConnection{
|
||||
connectionCache: []snmp.Connection{
|
||||
tsc,
|
||||
},
|
||||
}
|
||||
|
|
@ -787,8 +578,7 @@ func TestGather(t *testing.T) {
|
|||
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))
|
||||
require.WithinRange(t, m.Time, tstart, tstop)
|
||||
|
||||
m2 := acc.Metrics[1]
|
||||
require.Equal(t, "myOtherTable", m2.Measurement)
|
||||
|
|
@ -802,7 +592,7 @@ func TestGather_host(t *testing.T) {
|
|||
s := &Snmp{
|
||||
Agents: []string{"TestGather"},
|
||||
Name: "mytable",
|
||||
Fields: []Field{
|
||||
Fields: []snmp.Field{
|
||||
{
|
||||
Name: "host",
|
||||
Oid: ".1.0.0.1.1",
|
||||
|
|
@ -814,7 +604,7 @@ func TestGather_host(t *testing.T) {
|
|||
},
|
||||
},
|
||||
|
||||
connectionCache: []snmpConnection{
|
||||
connectionCache: []snmp.Connection{
|
||||
tsc,
|
||||
},
|
||||
}
|
||||
|
|
@ -828,366 +618,183 @@ func TestGather_host(t *testing.T) {
|
|||
require.Equal(t, "baz", m.Tags["host"])
|
||||
}
|
||||
|
||||
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)},
|
||||
}
|
||||
func TestSnmpInitGosmi(t *testing.T) {
|
||||
testDataPath, err := filepath.Abs("../../../internal/snmp/testdata/gosmi")
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range testTable {
|
||||
act, err := fieldConvert(NewNetsnmpTranslator(), tc.conv, 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().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"),
|
||||
s := &Snmp{
|
||||
Tables: []snmp.Table{
|
||||
{Oid: "RFC1213-MIB::atTable"},
|
||||
},
|
||||
Fields: []snmp.Field{
|
||||
{Oid: "RFC1213-MIB::atPhysAddress"},
|
||||
},
|
||||
ClientConfig: snmp.ClientConfig{
|
||||
Path: []string{testDataPath},
|
||||
Translator: "gosmi",
|
||||
},
|
||||
}
|
||||
mibName, oidNum, oidText, conversion, err := NewNetsnmpTranslator().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)
|
||||
snmpTranslateCaches = nil
|
||||
|
||||
require.NoError(t, s.Init())
|
||||
|
||||
require.Len(t, s.Tables[0].Fields, 3)
|
||||
|
||||
require.Equal(t, ".1.3.6.1.2.1.3.1.1.1", s.Tables[0].Fields[0].Oid)
|
||||
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) {
|
||||
snmpTableCaches = nil
|
||||
oid := ".1.0.0.0"
|
||||
mibName, oidNum, oidText, fields, err := NewNetsnmpTranslator().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"}},
|
||||
err: errors.New("e"),
|
||||
func TestSnmpInit_noTranslateGosmi(t *testing.T) {
|
||||
s := &Snmp{
|
||||
Fields: []snmp.Field{
|
||||
{Oid: ".9.1.1.1.1", Name: "one", IsTag: true},
|
||||
{Oid: ".9.1.1.1.2", Name: "two"},
|
||||
{Oid: ".9.1.1.1.3"},
|
||||
},
|
||||
Tables: []snmp.Table{
|
||||
{Name: "testing",
|
||||
Fields: []snmp.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",
|
||||
},
|
||||
}
|
||||
mibName, oidNum, oidText, fields, err := NewNetsnmpTranslator().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)
|
||||
|
||||
require.NoError(t, s.Init())
|
||||
|
||||
require.Equal(t, ".9.1.1.1.1", s.Fields[0].Oid)
|
||||
require.Equal(t, "one", s.Fields[0].Name)
|
||||
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) {
|
||||
tbl := Table{
|
||||
func TestGatherGosmi(t *testing.T) {
|
||||
s := &Snmp{
|
||||
Agents: []string{"TestGather"},
|
||||
Name: "mytable",
|
||||
IndexAsTag: true,
|
||||
Fields: []Field{
|
||||
Fields: []snmp.Field{
|
||||
{
|
||||
Name: "myfield1",
|
||||
Oid: ".1.0.0.3.1.1",
|
||||
Oid: ".1.0.0.1.1",
|
||||
IsTag: true,
|
||||
},
|
||||
{
|
||||
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,
|
||||
Oid: "1.0.0.1.1",
|
||||
},
|
||||
},
|
||||
Tables: []snmp.Table{
|
||||
{
|
||||
Name: "myfield4",
|
||||
Oid: ".1.0.0.0.1.1",
|
||||
SecondaryIndexUse: true,
|
||||
IsTag: true,
|
||||
},
|
||||
Name: "myOtherTable",
|
||||
InheritTags: []string{"myfield1"},
|
||||
Fields: []snmp.Field{
|
||||
{
|
||||
Name: "myfield5",
|
||||
Oid: ".1.0.0.0.1.2",
|
||||
SecondaryIndexUse: true,
|
||||
Name: "myOtherField",
|
||||
Oid: ".1.0.0.0.1.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tb, err := tbl.Build(tsc, true, NewNetsnmpTranslator())
|
||||
require.NoError(t, err)
|
||||
connectionCache: []snmp.Connection{tsc},
|
||||
|
||||
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,
|
||||
ClientConfig: snmp.ClientConfig{
|
||||
Translator: "gosmi",
|
||||
},
|
||||
}
|
||||
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)
|
||||
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.WithinRange(t, m.Time, tstart, tstop)
|
||||
|
||||
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 TestTableOuterJoin_walk(t *testing.T) {
|
||||
tbl := Table{
|
||||
func TestGather_hostGosmi(t *testing.T) {
|
||||
s := &Snmp{
|
||||
Agents: []string{"TestGather"},
|
||||
Name: "mytable",
|
||||
IndexAsTag: true,
|
||||
Fields: []Field{
|
||||
Fields: []snmp.Field{
|
||||
{
|
||||
Name: "myfield1",
|
||||
Oid: ".1.0.0.3.1.1",
|
||||
Name: "host",
|
||||
Oid: ".1.0.0.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,
|
||||
Oid: ".1.0.0.1.2",
|
||||
},
|
||||
},
|
||||
|
||||
connectionCache: []snmp.Connection{tsc},
|
||||
}
|
||||
|
||||
tb, err := tbl.Build(tsc, true, NewNetsnmpTranslator())
|
||||
require.NoError(t, err)
|
||||
acc := &testutil.Accumulator{}
|
||||
|
||||
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, 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)
|
||||
require.NoError(t, s.Gather(acc))
|
||||
|
||||
require.Len(t, acc.Metrics, 1)
|
||||
m := acc.Metrics[0]
|
||||
require.Equal(t, "baz", m.Tags["host"])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import (
|
|||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/internal/snmp"
|
||||
"github.com/influxdata/telegraf/plugins/common/parallel"
|
||||
si "github.com/influxdata/telegraf/plugins/inputs/snmp"
|
||||
"github.com/influxdata/telegraf/plugins/processors"
|
||||
)
|
||||
|
||||
|
|
@ -25,7 +24,6 @@ type keyType = string
|
|||
type valType = nameMap
|
||||
|
||||
type mapFunc func(agent string) (nameMap, error)
|
||||
type makeTableFunc func(string) (*si.Table, error)
|
||||
|
||||
type sigMap map[string]chan struct{}
|
||||
|
||||
|
|
@ -43,8 +41,8 @@ type IfName struct {
|
|||
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
|
||||
ifTable *si.Table
|
||||
ifXTable *si.Table
|
||||
ifTable *snmp.Table
|
||||
ifXTable *snmp.Table
|
||||
|
||||
cache *TTLCache
|
||||
lock sync.Mutex
|
||||
|
|
@ -52,9 +50,6 @@ type IfName struct {
|
|||
sigs sigMap
|
||||
|
||||
getMapRemote mapFunc
|
||||
makeTable makeTableFunc
|
||||
|
||||
translator si.Translator
|
||||
}
|
||||
|
||||
const minRetry = 5 * time.Minute
|
||||
|
|
@ -65,7 +60,6 @@ func (*IfName) SampleConfig() string {
|
|||
|
||||
func (d *IfName) Init() error {
|
||||
d.getMapRemote = d.getMapRemoteNoMock
|
||||
d.makeTable = d.makeTableNoMock
|
||||
|
||||
c := NewTTLCache(time.Duration(d.CacheTTL), d.CacheSize)
|
||||
d.cache = &c
|
||||
|
|
@ -76,10 +70,6 @@ func (d *IfName) Init() error {
|
|||
return fmt.Errorf("parsing SNMP client config: %w", err)
|
||||
}
|
||||
|
||||
// Since OIDs in this plugin are always numeric there is no need
|
||||
// to translate.
|
||||
d.translator = si.NewNetsnmpTranslator()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
tab := si.Table{
|
||||
tab := snmp.Table{
|
||||
Name: "ifTable",
|
||||
IndexAsTag: true,
|
||||
Fields: []si.Field{
|
||||
Fields: []snmp.Field{
|
||||
{Oid: oid, Name: "ifName"},
|
||||
},
|
||||
}
|
||||
|
||||
err = tab.Init(d.translator)
|
||||
err = tab.Init(nil)
|
||||
if err != nil {
|
||||
//Init already wraps
|
||||
return nil, err
|
||||
|
|
@ -306,10 +296,10 @@ func (d *IfName) makeTableNoMock(oid string) (*si.Table, error) {
|
|||
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
|
||||
|
||||
rtab, err := tab.Build(gs, true, d.translator)
|
||||
rtab, err := tab.Build(gs, true)
|
||||
if err != nil {
|
||||
//Build already wraps
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/internal/snmp"
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
si "github.com/influxdata/telegraf/plugins/inputs/snmp"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
|
|
@ -105,12 +104,7 @@ func TestGetMap(t *testing.T) {
|
|||
CacheTTL: config.Duration(10 * time.Second),
|
||||
}
|
||||
|
||||
// Don't run net-snmp commands to look up table names.
|
||||
d.makeTable = func(string) (*si.Table, error) {
|
||||
return &si.Table{}, nil
|
||||
}
|
||||
err := d.Init()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, d.Init())
|
||||
|
||||
expected := nameMap{
|
||||
1: "ifname1",
|
||||
|
|
|
|||
Loading…
Reference in New Issue