feat(inputs.snmp): Add displayhint conversion (#15935)
This commit is contained in:
parent
52d30f9a3a
commit
ddd6023cee
|
|
@ -12,6 +12,9 @@ import (
|
|||
"unicode/utf8"
|
||||
|
||||
"github.com/gosnmp/gosnmp"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
)
|
||||
|
||||
// Field holds the configuration for a Field to look up.
|
||||
|
|
@ -36,6 +39,7 @@ type Field struct {
|
|||
// "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)
|
||||
// "displayhint" will format the value according to the textual convention. (Only supported with gosmi translator)
|
||||
Conversion string
|
||||
// Translate tells if the value of the field should be snmptranslated
|
||||
Translate bool
|
||||
|
|
@ -78,7 +82,6 @@ func (f *Field) Init(tr Translator) error {
|
|||
if f.Conversion == "" {
|
||||
f.Conversion = conversion
|
||||
}
|
||||
// TODO use textual convention conversion from the MIB
|
||||
}
|
||||
|
||||
if f.SecondaryIndexTable && f.SecondaryIndexUse {
|
||||
|
|
@ -89,38 +92,46 @@ func (f *Field) Init(tr Translator) error {
|
|||
return errors.New("SecondaryOuterJoin set to true, but field is not being used in join")
|
||||
}
|
||||
|
||||
switch f.Conversion {
|
||||
case "hwaddr", "enum(1)":
|
||||
config.PrintOptionValueDeprecationNotice("inputs.snmp", "field.conversion", f.Conversion, telegraf.DeprecationInfo{
|
||||
Since: "1.33.0",
|
||||
Notice: "Use 'displayhint' instead",
|
||||
})
|
||||
}
|
||||
|
||||
f.initialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// fieldConvert converts from any type according to the conv specification
|
||||
func (f *Field) Convert(ent gosnmp.SnmpPDU) (interface{}, error) {
|
||||
v := ent.Value
|
||||
|
||||
// snmptranslate table field value here
|
||||
if f.Translate {
|
||||
if entOid, ok := ent.Value.(string); ok {
|
||||
if entOid, ok := v.(string); ok {
|
||||
_, _, oidText, _, err := f.translator.SnmpTranslate(entOid)
|
||||
if err == nil {
|
||||
// If no error translating, the original value for ent.Value should be replaced
|
||||
ent.Value = oidText
|
||||
// If no error translating, the original value should be replaced
|
||||
v = oidText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if f.Conversion == "" {
|
||||
// OctetStrings may contain hex data that needs its own conversion
|
||||
if ent.Type == gosnmp.OctetString && !utf8.Valid(ent.Value.([]byte)[:]) {
|
||||
return hex.EncodeToString(ent.Value.([]byte)), nil
|
||||
if ent.Type == gosnmp.OctetString && !utf8.Valid(v.([]byte)[:]) {
|
||||
return hex.EncodeToString(v.([]byte)), nil
|
||||
}
|
||||
if bs, ok := ent.Value.([]byte); ok {
|
||||
if bs, ok := v.([]byte); ok {
|
||||
return string(bs), nil
|
||||
}
|
||||
return ent.Value, nil
|
||||
return v, nil
|
||||
}
|
||||
|
||||
var v interface{}
|
||||
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)
|
||||
|
|
@ -163,7 +174,6 @@ func (f *Field) Convert(ent gosnmp.SnmpPDU) (interface{}, error) {
|
|||
}
|
||||
|
||||
if f.Conversion == "int" {
|
||||
v = ent.Value
|
||||
var err error
|
||||
switch vt := v.(type) {
|
||||
case float32:
|
||||
|
|
@ -198,8 +208,9 @@ func (f *Field) Convert(ent gosnmp.SnmpPDU) (interface{}, error) {
|
|||
return v, err
|
||||
}
|
||||
|
||||
// Deprecated: Use displayhint instead
|
||||
if f.Conversion == "hwaddr" {
|
||||
switch vt := ent.Value.(type) {
|
||||
switch vt := v.(type) {
|
||||
case string:
|
||||
v = net.HardwareAddr(vt).String()
|
||||
case []byte:
|
||||
|
|
@ -211,7 +222,7 @@ func (f *Field) Convert(ent gosnmp.SnmpPDU) (interface{}, error) {
|
|||
}
|
||||
|
||||
if f.Conversion == "hex" {
|
||||
switch vt := ent.Value.(type) {
|
||||
switch vt := v.(type) {
|
||||
case string:
|
||||
switch ent.Type {
|
||||
case gosnmp.IPAddress:
|
||||
|
|
@ -237,9 +248,9 @@ func (f *Field) Convert(ent gosnmp.SnmpPDU) (interface{}, error) {
|
|||
endian := split[1]
|
||||
bit := split[2]
|
||||
|
||||
bv, ok := ent.Value.([]byte)
|
||||
bv, ok := v.([]byte)
|
||||
if !ok {
|
||||
return ent.Value, nil
|
||||
return v, nil
|
||||
}
|
||||
|
||||
switch endian {
|
||||
|
|
@ -275,7 +286,7 @@ func (f *Field) Convert(ent gosnmp.SnmpPDU) (interface{}, error) {
|
|||
if f.Conversion == "ipaddr" {
|
||||
var ipbs []byte
|
||||
|
||||
switch vt := ent.Value.(type) {
|
||||
switch vt := v.(type) {
|
||||
case string:
|
||||
ipbs = []byte(vt)
|
||||
case []byte:
|
||||
|
|
@ -298,9 +309,14 @@ func (f *Field) Convert(ent gosnmp.SnmpPDU) (interface{}, error) {
|
|||
return f.translator.SnmpFormatEnum(ent.Name, ent.Value, false)
|
||||
}
|
||||
|
||||
// Deprecated: Use displayhint instead
|
||||
if f.Conversion == "enum(1)" {
|
||||
return f.translator.SnmpFormatEnum(ent.Name, ent.Value, true)
|
||||
}
|
||||
|
||||
if f.Conversion == "displayhint" {
|
||||
return f.translator.SnmpFormatDisplayHint(ent.Name, ent.Value)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid conversion type %q", f.Conversion)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,39 @@ TestMIB MODULE-IDENTITY
|
|||
"
|
||||
::= { iso 1 }
|
||||
|
||||
DateAndTime ::= TEXTUAL-CONVENTION
|
||||
DISPLAY-HINT "2d-1d-1d,1d:1d:1d.1d,1a1d:1d"
|
||||
STATUS current
|
||||
DESCRIPTION
|
||||
"A date-time specification.
|
||||
|
||||
field octets contents range
|
||||
----- ------ -------- -----
|
||||
1 1-2 year* 0..65536
|
||||
2 3 month 1..12
|
||||
3 4 day 1..31
|
||||
4 5 hour 0..23
|
||||
5 6 minutes 0..59
|
||||
6 7 seconds 0..60
|
||||
(use 60 for leap-second)
|
||||
7 8 deci-seconds 0..9
|
||||
8 9 direction from UTC '+' / '-'
|
||||
9 10 hours from UTC* 0..13
|
||||
10 11 minutes from UTC 0..59
|
||||
|
||||
* Notes:
|
||||
- the value of year is in network-byte order
|
||||
- daylight saving time in New Zealand is +13
|
||||
|
||||
For example, Tuesday May 26, 1992 at 1:30:15 PM EDT would be
|
||||
displayed as:
|
||||
|
||||
1992-5-26,13:30:15.0,-4:0
|
||||
|
||||
Note that if only local time is known, then timezone
|
||||
information (fields 8-10) is not present."
|
||||
SYNTAX OCTET STRING (SIZE (8 | 11))
|
||||
|
||||
testingObjects OBJECT IDENTIFIER ::= { iso 0 }
|
||||
testObjects OBJECT IDENTIFIER ::= { testingObjects 0 }
|
||||
hostnameone OBJECT IDENTIFIER ::= {testObjects 1 }
|
||||
|
|
@ -54,4 +87,12 @@ description OBJECT-TYPE
|
|||
"server mib for testing"
|
||||
::= { testMIBObjects 4 }
|
||||
|
||||
dateAndTime OBJECT-TYPE
|
||||
SYNTAX DateAndTime
|
||||
ACCESS read-only
|
||||
STATUS current
|
||||
DESCRIPTION
|
||||
"A date-time specification."
|
||||
::= { testMIBObjects 5 }
|
||||
|
||||
END
|
||||
|
|
@ -25,12 +25,12 @@ DisplayString ::=
|
|||
--
|
||||
-- SIZE (0..255)
|
||||
|
||||
PhysAddress ::=
|
||||
OCTET STRING
|
||||
-- This data type is used to model media addresses. For many
|
||||
-- types of media, this will be in a binary representation.
|
||||
-- For example, an ethernet address would be represented as
|
||||
-- a string of 6 octets.
|
||||
PhysAddress ::= TEXTUAL-CONVENTION
|
||||
DISPLAY-HINT "1x:"
|
||||
STATUS current
|
||||
DESCRIPTION
|
||||
"Represents media- or physical-level addresses."
|
||||
SYNTAX OCTET STRING
|
||||
|
||||
-- groups in MIB-II
|
||||
|
||||
|
|
|
|||
|
|
@ -21,4 +21,9 @@ type Translator interface {
|
|||
formatted string,
|
||||
err error,
|
||||
)
|
||||
|
||||
SnmpFormatDisplayHint(oid string, value interface{}) (
|
||||
formatted string,
|
||||
err error,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,22 @@ func (g *gosmiTranslator) SnmpFormatEnum(oid string, value interface{}, full boo
|
|||
return v.Formatted, nil
|
||||
}
|
||||
|
||||
func (g *gosmiTranslator) SnmpFormatDisplayHint(oid string, value interface{}) (string, error) {
|
||||
if value == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
//nolint:dogsled // only need to get the node
|
||||
_, _, _, _, node, err := snmpTranslateCall(oid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
v := node.FormatValue(value)
|
||||
|
||||
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{}{}
|
||||
|
|
@ -154,20 +170,22 @@ func snmpTranslateCall(oid string) (mibName string, oidNum string, oidText strin
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if tc[i].Type.Format != "" {
|
||||
conversion = "displayhint"
|
||||
} else {
|
||||
switch tc[i].Type.Name {
|
||||
case "MacAddress", "PhysAddress":
|
||||
conversion = "hwaddr"
|
||||
case "InetAddressIPv4", "InetAddressIPv6", "InetAddress", "IPSIpAddress":
|
||||
case "InetAddress", "IPSIpAddress":
|
||||
conversion = "ipaddr"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oidText = out.RenderQualified()
|
||||
i := strings.Index(oidText, "::")
|
||||
|
|
|
|||
|
|
@ -42,9 +42,10 @@ func TestFieldInitGosmi(t *testing.T) {
|
|||
{".1.2.3", "foo", "", ".1.2.3", "foo", ""},
|
||||
{".iso.2.3", "foo", "", ".1.2.3", "foo", ""},
|
||||
{".1.0.0.0.1.1", "", "", ".1.0.0.0.1.1", "server", ""},
|
||||
{"IF-MIB::ifPhysAddress.1", "", "", ".1.3.6.1.2.1.2.2.1.6.1", "ifPhysAddress.1", "hwaddr"},
|
||||
{".1.0.0.0.1.5", "", "", ".1.0.0.0.1.5", "dateAndTime", "displayhint"},
|
||||
{"IF-MIB::ifPhysAddress.1", "", "", ".1.3.6.1.2.1.2.2.1.6.1", "ifPhysAddress.1", "displayhint"},
|
||||
{"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"},
|
||||
{"BRIDGE-MIB::dot1dTpFdbAddress.1", "", "", ".1.3.6.1.2.1.17.4.3.1.1.1", "dot1dTpFdbAddress.1", "displayhint"},
|
||||
{"TCP-MIB::tcpConnectionLocalAddress.1", "", "", ".1.3.6.1.2.1.6.19.1.2.1", "tcpConnectionLocalAddress.1", "ipaddr"},
|
||||
{".999", "", "", ".999", ".999", ""},
|
||||
}
|
||||
|
|
@ -89,7 +90,7 @@ func TestTableInitGosmi(t *testing.T) {
|
|||
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, "displayhint", tbl.Fields[2].Conversion)
|
||||
|
||||
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)
|
||||
|
|
@ -355,6 +356,48 @@ func TestFieldConvertGosmi(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSnmpFormatDisplayHint(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
oid string
|
||||
input interface{}
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "ifOperStatus",
|
||||
oid: ".1.3.6.1.2.1.2.2.1.8",
|
||||
input: 3,
|
||||
expected: "testing(3)",
|
||||
}, {
|
||||
name: "ifPhysAddress",
|
||||
oid: ".1.3.6.1.2.1.2.2.1.6",
|
||||
input: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
|
||||
expected: "01:23:45:67:89:ab:cd:ef",
|
||||
}, {
|
||||
name: "DateAndTime short",
|
||||
oid: ".1.0.0.0.1.5",
|
||||
input: []byte{0x07, 0xe8, 0x09, 0x18, 0x10, 0x24, 0x27, 0x05},
|
||||
expected: "2024-9-24,16:36:39.5",
|
||||
}, {
|
||||
name: "DateAndTime long",
|
||||
oid: ".1.0.0.0.1.5",
|
||||
input: []byte{0x07, 0xe8, 0x09, 0x18, 0x10, 0x24, 0x27, 0x05, 0x2b, 0x02, 0x00},
|
||||
expected: "2024-9-24,16:36:39.5,+2:0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tr := getGosmiTr(t)
|
||||
|
||||
actual, err := tr.SnmpFormatDisplayHint(tt.oid, tt.input)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableJoin_walkGosmi(t *testing.T) {
|
||||
tbl := Table{
|
||||
Name: "mytable",
|
||||
|
|
|
|||
|
|
@ -266,3 +266,7 @@ func (n *netsnmpTranslator) snmpTranslateCall(oid string) (mibName string, oidNu
|
|||
func (n *netsnmpTranslator) SnmpFormatEnum(_ string, _ interface{}, _ bool) (string, error) {
|
||||
return "", errors.New("not implemented in netsnmp translator")
|
||||
}
|
||||
|
||||
func (n *netsnmpTranslator) SnmpFormatDisplayHint(_ string, _ interface{}) (string, error) {
|
||||
return "", errors.New("not implemented in netsnmp translator")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,7 +169,6 @@ option operate similar to the `snmpget` utility.
|
|||
## float: Convert the value into a float with no adjustment. Same
|
||||
## as `float(0)`.
|
||||
## int: Convert the value into an integer.
|
||||
## hwaddr: Convert the value to a MAC address.
|
||||
## ipaddr: Convert the value to an IP address.
|
||||
## hex: Convert bytes to a hex string.
|
||||
## hextoint:X:Y Convert bytes to integer, where X is the endian and Y the
|
||||
|
|
@ -177,10 +176,10 @@ option operate similar to the `snmpget` utility.
|
|||
## hextoint:BigEndian:uint32. Valid options for the endian
|
||||
## are: BigEndian and LittleEndian. For the bit size:
|
||||
## uint16, uint32 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)
|
||||
## displayhint: Format the value according to the textual convention in the MIB.
|
||||
## (Only supported with gosmi translator)
|
||||
##
|
||||
# conversion = ""
|
||||
```
|
||||
|
|
|
|||
|
|
@ -647,7 +647,7 @@ func TestSnmpInitGosmi(t *testing.T) {
|
|||
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, "displayhint", 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)
|
||||
|
|
@ -657,7 +657,7 @@ func TestSnmpInitGosmi(t *testing.T) {
|
|||
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)
|
||||
require.Equal(t, "displayhint", s.Fields[0].Conversion)
|
||||
}
|
||||
|
||||
func TestSnmpInit_noTranslateGosmi(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -97,10 +97,9 @@ to use them.
|
|||
# name = ""
|
||||
|
||||
## Apply one of the following conversions to the variable value:
|
||||
## hwaddr: Convert the value to a MAC address.
|
||||
## ipaddr: Convert the value to an IP address.
|
||||
## enum(1): Convert the value according to its syntax in the MIB (full).
|
||||
## enum: Convert the value according to its syntax in the MIB.
|
||||
## displayhint: Format the value according to the textual convention in the MIB.
|
||||
##
|
||||
# conversion = ""
|
||||
```
|
||||
|
|
|
|||
|
|
@ -70,9 +70,8 @@
|
|||
# name = ""
|
||||
|
||||
## Apply one of the following conversions to the variable value:
|
||||
## hwaddr: Convert the value to a MAC address.
|
||||
## ipaddr: Convert the value to an IP address.
|
||||
## enum(1): Convert the value according to its syntax in the MIB (full).
|
||||
## enum: Convert the value according to its syntax in the MIB.
|
||||
## displayhint: Format the value according to the textual convention in the MIB.
|
||||
##
|
||||
# conversion = ""
|
||||
|
|
|
|||
Loading…
Reference in New Issue