Add snmp plugin
This commit is contained in:
parent
c313af1b24
commit
03846baba7
6
Makefile
6
Makefile
|
@ -53,6 +53,7 @@ endif
|
|||
docker run --name nsq -p "4150:4150" -d nsqio/nsq /nsqd
|
||||
docker run --name mqtt -p "1883:1883" -d ncarlier/mqtt
|
||||
docker run --name riemann -p "5555:5555" -d blalor/riemann
|
||||
docker run --name snmp -p "31161:31161/udp" -d titilambert/snmpsim
|
||||
|
||||
# Run docker containers necessary for CircleCI unit tests
|
||||
docker-run-circle:
|
||||
|
@ -66,11 +67,12 @@ docker-run-circle:
|
|||
docker run --name nsq -p "4150:4150" -d nsqio/nsq /nsqd
|
||||
docker run --name mqtt -p "1883:1883" -d ncarlier/mqtt
|
||||
docker run --name riemann -p "5555:5555" -d blalor/riemann
|
||||
docker run --name snmp -p "31161:31161/udp" -d titilambert/snmpsim
|
||||
|
||||
# Kill all docker containers, ignore errors
|
||||
docker-kill:
|
||||
-docker kill nsq aerospike redis opentsdb rabbitmq postgres memcached mysql kafka mqtt riemann
|
||||
-docker rm nsq aerospike redis opentsdb rabbitmq postgres memcached mysql kafka mqtt riemann
|
||||
-docker kill nsq aerospike redis opentsdb rabbitmq postgres memcached mysql kafka mqtt riemann snmp
|
||||
-docker rm nsq aerospike redis opentsdb rabbitmq postgres memcached mysql kafka mqtt riemann snmp
|
||||
|
||||
# Run full unit tests using docker containers (includes setup and teardown)
|
||||
test: docker-kill docker-run
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
_ "github.com/influxdb/telegraf/plugins/inputs/redis"
|
||||
_ "github.com/influxdb/telegraf/plugins/inputs/rethinkdb"
|
||||
_ "github.com/influxdb/telegraf/plugins/inputs/sensors"
|
||||
_ "github.com/influxdb/telegraf/plugins/inputs/snmp"
|
||||
_ "github.com/influxdb/telegraf/plugins/inputs/statsd"
|
||||
_ "github.com/influxdb/telegraf/plugins/inputs/system"
|
||||
_ "github.com/influxdb/telegraf/plugins/inputs/trig"
|
||||
|
|
|
@ -0,0 +1,425 @@
|
|||
package snmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/influxdb/telegraf/plugins/inputs"
|
||||
|
||||
"github.com/soniah/gosnmp"
|
||||
)
|
||||
|
||||
// Snmp is a snmp plugin
|
||||
type Snmp struct {
|
||||
Host []Host
|
||||
Get []Data
|
||||
Bulk []Data
|
||||
Snmptranslatefile string
|
||||
}
|
||||
|
||||
type Host struct {
|
||||
Address string
|
||||
Community string
|
||||
// SNMP version. Default 2
|
||||
Version int
|
||||
// SNMP timeout, in seconds. 0 means no timeout
|
||||
Timeout float64
|
||||
// SNMP retries
|
||||
Retries int
|
||||
// Data to collect (list of Data names)
|
||||
Collect []string
|
||||
// Oids
|
||||
getOids []Data
|
||||
bulkOids []Data
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
Name string
|
||||
// OIds
|
||||
Oid string
|
||||
// Unit
|
||||
Unit string
|
||||
// SNMP getbulk max repetition
|
||||
MaxRepetition uint8 `toml:"max_repetition"`
|
||||
// We want to resolve oid to names oids text file
|
||||
Snmptranslate bool
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
id string
|
||||
name string
|
||||
subnodes map[string]Node
|
||||
}
|
||||
|
||||
var initNode = Node{
|
||||
id: "1",
|
||||
name: "",
|
||||
subnodes: make(map[string]Node),
|
||||
}
|
||||
|
||||
var sampleConfig = `
|
||||
# Use 'oids.txt' file to translate oids to names
|
||||
# To generate 'oids.txt' you need to run:
|
||||
# snmptranslate -m all -Tz -On | sed -e 's/"//g' > /tmp/oids.txt
|
||||
# Or if you have an other MIB folder with custom MIBs
|
||||
# snmptranslate -M /mycustommibfolder -Tz -On -m all | sed -e 's/"//g' > oids.txt
|
||||
snmptranslatefile = "/tmp/oids.txt"
|
||||
[[inputs.snmp.host]]
|
||||
address = "192.168.2.2:161"
|
||||
# SNMP community
|
||||
community = "public" # default public
|
||||
# SNMP version (1, 2 or 3)
|
||||
# Version 3 not supported yet
|
||||
version = 2 # default 2
|
||||
# SNMP response timeout
|
||||
timeout = 2.0 # default 2.0
|
||||
# SNMP request retries
|
||||
retries = 2 # default 2
|
||||
# Which get/bulk do you want to collect for this host
|
||||
collect = ["mybulk", "sysservices", "sysdescr"]
|
||||
|
||||
[[inputs.snmp.host]]
|
||||
address = "192.168.2.3:161"
|
||||
community = "public"
|
||||
version = 2
|
||||
timeout = 2.0
|
||||
retries = 2
|
||||
collect = ["mybulk"]
|
||||
|
||||
[[inputs.snmp.get]]
|
||||
name = "sysservices"
|
||||
oid = ".1.3.6.1.2.1.1.7.0"
|
||||
|
||||
[[inputs.snmp.get]]
|
||||
name = "sysdescr"
|
||||
oid = ".1.3.6.1.2.1.1.1.0"
|
||||
snmptranslate = true
|
||||
|
||||
[[inputs.snmp.get]]
|
||||
name = "sysuptime"
|
||||
oid = ".1.3.6.1.2.1.1.3.0"
|
||||
unit = "seconds"
|
||||
snmptranslate = true
|
||||
|
||||
[[inputs.snmp.bulk]]
|
||||
name = "mybulk"
|
||||
max_repetition = 127
|
||||
oid = ".1.3.6.1.2.1.1"
|
||||
snmptranslate = true
|
||||
`
|
||||
|
||||
// SampleConfig returns sample configuration message
|
||||
func (s *Snmp) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
// Description returns description of Zookeeper plugin
|
||||
func (s *Snmp) Description() string {
|
||||
return `Reads oids value from one or many snmp agents`
|
||||
}
|
||||
|
||||
func fillnode(parentNode Node, oid_name string, ids []string) {
|
||||
// ids = ["1", "3", "6", ...]
|
||||
id, ids := ids[0], ids[1:]
|
||||
node, ok := parentNode.subnodes[id]
|
||||
if ok == false {
|
||||
node = Node{
|
||||
id: id,
|
||||
name: "",
|
||||
subnodes: make(map[string]Node),
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
node.name = oid_name
|
||||
}
|
||||
parentNode.subnodes[id] = node
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
fillnode(node, oid_name, ids)
|
||||
}
|
||||
}
|
||||
|
||||
func findnodename(node Node, ids []string) string {
|
||||
// ids = ["1", "3", "6", ...]
|
||||
if len(ids) == 1 {
|
||||
return node.name
|
||||
}
|
||||
id, ids := ids[0], ids[1:]
|
||||
// Get node
|
||||
subnode, ok := node.subnodes[id]
|
||||
if ok {
|
||||
return findnodename(subnode, ids)
|
||||
}
|
||||
// We got a node
|
||||
// Get node name
|
||||
if node.name != "" && len(ids) == 0 && id == "0" {
|
||||
// node with instance 0
|
||||
return node.name
|
||||
} else if node.name != "" && len(ids) == 0 && id != "0" {
|
||||
// node with an instance
|
||||
return node.name + "." + string(id)
|
||||
} else if node.name != "" && len(ids) > 0 {
|
||||
// node with subinstances
|
||||
return node.name + "." + strings.Join(ids, ".")
|
||||
}
|
||||
// return an empty node name
|
||||
return node.name
|
||||
}
|
||||
|
||||
func (s *Snmp) Gather(acc inputs.Accumulator) error {
|
||||
// Create oid tree
|
||||
if s.Snmptranslatefile != "" {
|
||||
data, err := ioutil.ReadFile(s.Snmptranslatefile)
|
||||
if err != nil {
|
||||
fmt.Print(err)
|
||||
} else {
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
oidsRegEx := regexp.MustCompile(`([^\t]*)\t*([^\t]*)`)
|
||||
oids := oidsRegEx.FindStringSubmatch(string(line))
|
||||
if oids[2] != "" {
|
||||
oid_name := oids[1]
|
||||
oid := oids[2]
|
||||
fillnode(initNode, oid_name, strings.Split(string(oid), "."))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fetching data
|
||||
for _, host := range s.Host {
|
||||
// Set default args
|
||||
if len(host.Address) == 0 {
|
||||
host.Address = "127.0.0.1:161"
|
||||
}
|
||||
if host.Community == "" {
|
||||
host.Community = "public"
|
||||
}
|
||||
if host.Timeout <= 0 {
|
||||
host.Timeout = 2.0
|
||||
}
|
||||
if host.Retries <= 0 {
|
||||
host.Retries = 2
|
||||
}
|
||||
// Prepare host
|
||||
for _, oid_name := range host.Collect {
|
||||
// Get GET oids
|
||||
for _, oid := range s.Get {
|
||||
if oid.Name == oid_name {
|
||||
host.getOids = append(host.getOids, oid)
|
||||
}
|
||||
}
|
||||
// Get GETBULK oids
|
||||
for _, oid := range s.Bulk {
|
||||
if oid.Name == oid_name {
|
||||
host.bulkOids = append(host.bulkOids, oid)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Launch Get requests
|
||||
if err := host.SNMPGet(acc); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := host.SNMPBulk(acc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Host) SNMPGet(acc inputs.Accumulator) error {
|
||||
// Get snmp client
|
||||
snmpClient, err := h.GetSNMPClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Prepare OIDs
|
||||
oidsList := make(map[string]Data)
|
||||
for _, oid := range h.getOids {
|
||||
oidsList[oid.Oid] = oid
|
||||
}
|
||||
oidsNameList := make([]string, 0, len(oidsList))
|
||||
for _, oid := range oidsList {
|
||||
oidsNameList = append(oidsNameList, oid.Oid)
|
||||
}
|
||||
|
||||
// gosnmp.MAX_OIDS == 60
|
||||
// TODO use gosnmp.MAX_OIDS instead of hard coded value
|
||||
max_oids := 60
|
||||
// limit 60 (MAX_OIDS) oids by requests
|
||||
for i := 0; i < len(oidsList); i = i + max_oids {
|
||||
// Launch request
|
||||
max_index := i + max_oids
|
||||
if i+max_oids > len(oidsList) {
|
||||
max_index = len(oidsList)
|
||||
}
|
||||
result, err3 := snmpClient.Get(oidsNameList[i:max_index]) // Get() accepts up to g.MAX_OIDS
|
||||
if err3 != nil {
|
||||
return err3
|
||||
}
|
||||
// Handle response
|
||||
_, err = h.HandleResponse(oidsList, result, acc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Deconnection
|
||||
defer snmpClient.Conn.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Host) SNMPBulk(acc inputs.Accumulator) error {
|
||||
// Get snmp client
|
||||
snmpClient, err := h.GetSNMPClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Prepare OIDs
|
||||
oidsList := make(map[string]Data)
|
||||
for _, oid := range h.bulkOids {
|
||||
oidsList[oid.Oid] = oid
|
||||
}
|
||||
oidsNameList := make([]string, 0, len(oidsList))
|
||||
for _, oid := range oidsList {
|
||||
oidsNameList = append(oidsNameList, oid.Oid)
|
||||
}
|
||||
// TODO Trying to make requests with more than one OID
|
||||
// to reduce the number of requests
|
||||
for _, oid := range oidsNameList {
|
||||
oid_asked := oid
|
||||
need_more_requests := true
|
||||
// Set max repetition
|
||||
maxRepetition := oidsList[oid].MaxRepetition
|
||||
if maxRepetition <= 0 {
|
||||
maxRepetition = 32
|
||||
}
|
||||
// Launch requests
|
||||
for need_more_requests {
|
||||
// Launch request
|
||||
result, err3 := snmpClient.GetBulk([]string{oid}, 0, maxRepetition)
|
||||
if err3 != nil {
|
||||
return err3
|
||||
}
|
||||
// Handle response
|
||||
last_oid, err := h.HandleResponse(oidsList, result, acc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Determine if we need more requests
|
||||
if strings.HasPrefix(last_oid, oid_asked) {
|
||||
need_more_requests = true
|
||||
oid = last_oid
|
||||
} else {
|
||||
need_more_requests = false
|
||||
}
|
||||
}
|
||||
}
|
||||
// Deconnection
|
||||
defer snmpClient.Conn.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Host) GetSNMPClient() (*gosnmp.GoSNMP, error) {
|
||||
// Prepare Version
|
||||
var version gosnmp.SnmpVersion
|
||||
if h.Version == 1 {
|
||||
version = gosnmp.Version1
|
||||
} else if h.Version == 3 {
|
||||
version = gosnmp.Version3
|
||||
} else {
|
||||
version = gosnmp.Version2c
|
||||
}
|
||||
// Prepare host and port
|
||||
host, port_str, err := net.SplitHostPort(h.Address)
|
||||
if err != nil {
|
||||
port_str = string("161")
|
||||
}
|
||||
// convert port_str to port in uint16
|
||||
port_64, err := strconv.ParseUint(port_str, 10, 16)
|
||||
port := uint16(port_64)
|
||||
// Get SNMP client
|
||||
snmpClient := &gosnmp.GoSNMP{
|
||||
Target: host,
|
||||
Port: port,
|
||||
Community: h.Community,
|
||||
Version: version,
|
||||
Timeout: time.Duration(h.Timeout) * time.Second,
|
||||
Retries: h.Retries,
|
||||
}
|
||||
// Connection
|
||||
err2 := snmpClient.Connect()
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
// Return snmpClient
|
||||
return snmpClient, nil
|
||||
}
|
||||
|
||||
func (h *Host) HandleResponse(oids map[string]Data, result *gosnmp.SnmpPacket, acc inputs.Accumulator) (string, error) {
|
||||
var lastOid string
|
||||
for _, variable := range result.Variables {
|
||||
lastOid = variable.Name
|
||||
// Remove unwanted oid
|
||||
for oid_key, oid := range oids {
|
||||
if strings.HasPrefix(variable.Name, oid_key) {
|
||||
switch variable.Type {
|
||||
// handle Metrics
|
||||
case gosnmp.Boolean, gosnmp.Integer, gosnmp.Counter32, gosnmp.Gauge32,
|
||||
gosnmp.TimeTicks, gosnmp.Counter64, gosnmp.Uinteger32:
|
||||
// Prepare tags
|
||||
tags := make(map[string]string)
|
||||
if oid.Unit != "" {
|
||||
tags["unit"] = oid.Unit
|
||||
}
|
||||
// Get name and instance
|
||||
var oid_name string
|
||||
var instance string
|
||||
if oid.Snmptranslate {
|
||||
// Get oidname and instannce from translate file
|
||||
oidfullname := findnodename(initNode,
|
||||
strings.Split(string(variable.Name[1:]), "."))
|
||||
res := strings.SplitN(string(oidfullname), ".", 2)
|
||||
oid_name = res[0]
|
||||
if len(res) > 1 {
|
||||
instance = res[1]
|
||||
}
|
||||
}
|
||||
if instance != "" {
|
||||
tags["instance"] = instance
|
||||
}
|
||||
// Set name
|
||||
var field_name string
|
||||
if oid_name != "" {
|
||||
field_name = oid_name
|
||||
} else if oid.Oid == variable.Name {
|
||||
field_name = oid.Name
|
||||
} else {
|
||||
field_name = variable.Name
|
||||
}
|
||||
tags["host"], _, _ = net.SplitHostPort(h.Address)
|
||||
fields := make(map[string]interface{})
|
||||
fields[string(field_name)] = variable.Value
|
||||
acc.AddFields(field_name, fields, tags)
|
||||
case gosnmp.NoSuchObject, gosnmp.NoSuchInstance:
|
||||
// Oid not found
|
||||
log.Printf("[snmp input] Oid not found: %s", oid_key)
|
||||
default:
|
||||
// delete other data
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return lastOid, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("snmp", func() inputs.Input {
|
||||
return &Snmp{}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package snmp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/influxdb/telegraf/testutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSNMPErrorGet(t *testing.T) {
|
||||
get1 := Data{
|
||||
Name: "oid1",
|
||||
Unit: "second",
|
||||
Oid: ".1.3.6.1.2.1.2.2.1.16.1",
|
||||
}
|
||||
h := Host{
|
||||
Collect: []string{"oid1"},
|
||||
}
|
||||
s := Snmp{
|
||||
Host: []Host{h},
|
||||
Get: []Data{get1},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := s.Gather(&acc)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSNMPErrorBulk(t *testing.T) {
|
||||
bulk1 := Data{
|
||||
Name: "oid1",
|
||||
Unit: "second",
|
||||
Oid: ".1.3.6.1.2.1.2.2.1.16",
|
||||
Snmptranslate: true,
|
||||
}
|
||||
h := Host{
|
||||
Collect: []string{"oid1"},
|
||||
}
|
||||
s := Snmp{
|
||||
Host: []Host{h},
|
||||
Bulk: []Data{bulk1},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := s.Gather(&acc)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSNMPBulk(t *testing.T) {
|
||||
bulk1 := Data{
|
||||
Name: "oid1",
|
||||
Unit: "second",
|
||||
Oid: ".1.3.6.1.2.1.2.2.1.16",
|
||||
MaxRepetition: 2,
|
||||
Snmptranslate: true,
|
||||
}
|
||||
h := Host{
|
||||
Address: "127.0.0.1:31161",
|
||||
Community: "telegraf",
|
||||
Version: 2,
|
||||
Timeout: 2.0,
|
||||
Retries: 2,
|
||||
Collect: []string{"oid1"},
|
||||
}
|
||||
s := Snmp{
|
||||
Host: []Host{h},
|
||||
Bulk: []Data{bulk1},
|
||||
}
|
||||
|
||||
expected := map[string]uint{".1.3.6.1.2.1.2.2.1.16.1": 543846,
|
||||
".1.3.6.1.2.1.2.2.1.16.2": 26475179,
|
||||
".1.3.6.1.2.1.2.2.1.16.3": 108963968,
|
||||
".1.3.6.1.2.1.2.2.1.16.36": 12991453,
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := s.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, p := range acc.Points {
|
||||
assert.Equal(t, expected[p.Measurement], p.Fields[p.Measurement])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSNMPGet(t *testing.T) {
|
||||
get1 := Data{
|
||||
Name: "oid1",
|
||||
Unit: "second",
|
||||
Oid: ".1.3.6.1.2.1.2.2.1.16.1",
|
||||
}
|
||||
h := Host{
|
||||
Address: "127.0.0.1:31161",
|
||||
Community: "telegraf",
|
||||
Version: 2,
|
||||
Timeout: 2.0,
|
||||
Retries: 2,
|
||||
Collect: []string{"oid1"},
|
||||
}
|
||||
s := Snmp{
|
||||
Host: []Host{h},
|
||||
Get: []Data{get1},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := s.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, p := range acc.Points {
|
||||
value := p.Fields["oid1"].(uint)
|
||||
assert.Equal(t, get1.Name, p.Measurement)
|
||||
assert.Equal(t, uint(543846), value)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue