feat(inputs.snmp): convert enum values (#11872)

This commit is contained in:
Thomas Casteleyn 2022-11-28 16:01:18 +01:00 committed by GitHub
parent 8ec12c06b8
commit 25154e50fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 58 additions and 11 deletions

View File

@ -147,6 +147,10 @@ option operate similar to the `snmpget` utility.
## or hextoint:BigEndian:uint32. Valid options for the Endian are: ## or hextoint:BigEndian:uint32. Valid options for the Endian are:
## BigEndian and LittleEndian. For the bit size: uint16, uint32 ## BigEndian and LittleEndian. For the bit size: uint16, uint32
## and uint64. ## and uint64.
## enum(1): Convert the value according to its syntax in the MIB (full).
## (Only supported with gosmi translator)
## enum: Convert the value according to its syntax in the MIB.
## (Only supported with gosmi translator)
## ##
# conversion = "" # conversion = ""
``` ```

View File

@ -5,6 +5,7 @@ import (
"sync" "sync"
"github.com/sleepinggenius2/gosmi" "github.com/sleepinggenius2/gosmi"
"github.com/sleepinggenius2/gosmi/models"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/snmp" "github.com/influxdata/telegraf/internal/snmp"
@ -122,3 +123,21 @@ func (g *gosmiTranslator) SnmpTableCall(oid string) (mibName string, oidNum stri
return mibName, oidNum, oidText, fields, nil 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
}

View File

@ -6,6 +6,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/gosnmp/gosnmp"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -580,10 +581,12 @@ func TestFieldConvertGosmi(t *testing.T) {
{[]byte{0x00, 0x09, 0x3E, 0xE3, 0xF6, 0xD5, 0x3B, 0x60}, "hextoint:LittleEndian:uint64", uint64(6934371307618175232)}, {[]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, 0x3E, 0xE3}, "hextoint:LittleEndian:uint32", uint32(3812493568)},
{[]byte{0x00, 0x09}, "hextoint:LittleEndian:uint16", uint16(2304)}, {[]byte{0x00, 0x09}, "hextoint:LittleEndian:uint16", uint16(2304)},
{3, "enum", "testing"},
{3, "enum(1)", "testing(3)"},
} }
for _, tc := range testTable { for _, tc := range testTable {
act, err := fieldConvert(tc.conv, tc.input) act, err := fieldConvert(getGosmiTr(t), tc.conv, gosnmp.SnmpPDU{Name: ".1.3.6.1.2.1.2.2.1.8", Value: tc.input})
assert.NoError(t, err, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected) assert.NoError(t, err, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected)
assert.EqualValues(t, tc.expected, act, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected) assert.EqualValues(t, tc.expected, act, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected)
} }

View File

@ -3,6 +3,7 @@ package snmp
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"errors"
"fmt" "fmt"
"log" //nolint:depguard // Allow exceptional but valid use of log here. "log" //nolint:depguard // Allow exceptional but valid use of log here.
"os/exec" "os/exec"
@ -256,3 +257,7 @@ func snmpTranslateCall(oid string) (mibName string, oidNum string, oidText strin
return mibName, oidNum, oidText, conversion, nil return mibName, oidNum, oidText, conversion, nil
} }
func (n *netsnmpTranslator) SnmpFormatEnum(_ string, _ interface{}, _ bool) (string, error) {
return "", errors.New("not implemented in netsnmp translator")
}

View File

@ -36,6 +36,11 @@ type Translator interface {
fields []Field, fields []Field,
err error, err error,
) )
SnmpFormatEnum(oid string, value interface{}, full bool) (
formatted string,
err error,
)
} }
// Snmp holds the configuration for the plugin. // Snmp holds the configuration for the plugin.
@ -214,6 +219,7 @@ type Field struct {
// "int" will conver the value into an integer. // "int" will conver the value into an integer.
// "hwaddr" will convert a 6-byte string to a MAC address. // "hwaddr" will convert a 6-byte string to a MAC address.
// "ipaddr" will convert the value to an IPv4 or IPv6 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 Conversion string
// Translate tells if the value of the field should be snmptranslated // Translate tells if the value of the field should be snmptranslated
Translate bool Translate bool
@ -424,7 +430,7 @@ func (t Table) Build(gs snmpConnection, walk bool, tr Translator) (*RTable, erro
} }
} else if pkt != nil && len(pkt.Variables) > 0 && pkt.Variables[0].Type != gosnmp.NoSuchObject && pkt.Variables[0].Type != gosnmp.NoSuchInstance { } else if pkt != nil && len(pkt.Variables) > 0 && pkt.Variables[0].Type != gosnmp.NoSuchObject && pkt.Variables[0].Type != gosnmp.NoSuchInstance {
ent := pkt.Variables[0] ent := pkt.Variables[0]
fv, err := fieldConvert(f.Conversion, ent.Value) fv, err := fieldConvert(tr, f.Conversion, ent)
if err != nil { if err != nil {
return nil, fmt.Errorf("converting %q (OID %s) for field %s: %w", ent.Value, ent.Name, f.Name, err) return nil, fmt.Errorf("converting %q (OID %s) for field %s: %w", ent.Value, ent.Name, f.Name, err)
} }
@ -468,7 +474,7 @@ func (t Table) Build(gs snmpConnection, walk bool, tr Translator) (*RTable, erro
} }
} }
fv, err := fieldConvert(f.Conversion, ent.Value) fv, err := fieldConvert(tr, f.Conversion, ent)
if err != nil { if err != nil {
return &walkError{ return &walkError{
msg: fmt.Sprintf("converting %q (OID %s) for field %s", ent.Value, ent.Name, f.Name), msg: fmt.Sprintf("converting %q (OID %s) for field %s", ent.Value, ent.Name, f.Name),
@ -599,16 +605,17 @@ func (s *Snmp) getConnection(idx int) (snmpConnection, error) {
} }
// fieldConvert converts from any type according to the conv specification // fieldConvert converts from any type according to the conv specification
func fieldConvert(conv string, v interface{}) (interface{}, error) { func fieldConvert(tr Translator, conv string, ent gosnmp.SnmpPDU) (v interface{}, err error) {
if conv == "" { if conv == "" {
if bs, ok := v.([]byte); ok { if bs, ok := ent.Value.([]byte); ok {
return string(bs), nil return string(bs), nil
} }
return v, nil return ent.Value, nil
} }
var d int var d int
if _, err := fmt.Sscanf(conv, "float(%d)", &d); err == nil || conv == "float" { if _, err := fmt.Sscanf(conv, "float(%d)", &d); err == nil || conv == "float" {
v = ent.Value
switch vt := v.(type) { switch vt := v.(type) {
case float32: case float32:
v = float64(vt) / math.Pow10(d) v = float64(vt) / math.Pow10(d)
@ -645,6 +652,7 @@ func fieldConvert(conv string, v interface{}) (interface{}, error) {
} }
if conv == "int" { if conv == "int" {
v = ent.Value
switch vt := v.(type) { switch vt := v.(type) {
case float32: case float32:
v = int64(vt) v = int64(vt)
@ -679,7 +687,7 @@ func fieldConvert(conv string, v interface{}) (interface{}, error) {
} }
if conv == "hwaddr" { if conv == "hwaddr" {
switch vt := v.(type) { switch vt := ent.Value.(type) {
case string: case string:
v = net.HardwareAddr(vt).String() v = net.HardwareAddr(vt).String()
case []byte: case []byte:
@ -695,9 +703,9 @@ func fieldConvert(conv string, v interface{}) (interface{}, error) {
endian := split[1] endian := split[1]
bit := split[2] bit := split[2]
bv, ok := v.([]byte) bv, ok := ent.Value.([]byte)
if !ok { if !ok {
return v, nil return ent.Value, nil
} }
switch endian { switch endian {
@ -733,7 +741,7 @@ func fieldConvert(conv string, v interface{}) (interface{}, error) {
if conv == "ipaddr" { if conv == "ipaddr" {
var ipbs []byte var ipbs []byte
switch vt := v.(type) { switch vt := ent.Value.(type) {
case string: case string:
ipbs = []byte(vt) ipbs = []byte(vt)
case []byte: case []byte:
@ -752,6 +760,14 @@ func fieldConvert(conv string, v interface{}) (interface{}, error) {
return v, nil 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 '%s'", conv) return nil, fmt.Errorf("invalid conversion type '%s'", conv)
} }

View File

@ -901,7 +901,7 @@ func TestFieldConvert(t *testing.T) {
} }
for _, tc := range testTable { for _, tc := range testTable {
act, err := fieldConvert(tc.conv, tc.input) act, err := fieldConvert(NewNetsnmpTranslator(), tc.conv, gosnmp.SnmpPDU{Value: tc.input})
if !assert.NoError(t, err, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected) { if !assert.NoError(t, err, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected) {
continue continue
} }