From 1357525f0dd0ce8d0dc34e535e02c7903c73e02c Mon Sep 17 00:00:00 2001 From: Cameron Sparr Date: Thu, 28 Jul 2016 15:55:03 +0100 Subject: [PATCH] New snmp plugin and snmp_legacy rename closes #1371 closes #808 closes #1361 closes #1151 closes #997 closes #1163 closes #856 closes #1043 closes #961 closes #1389 --- CHANGELOG.md | 196 +-- README.md | 1 - etc/telegraf.conf | 50 +- plugins/inputs/all/all.go | 2 +- plugins/inputs/snmp/README.md | 609 ++------ plugins/inputs/snmp/snmp.go | 1348 +++++++---------- plugins/inputs/snmp/snmp_test.go | 942 ++++++------ plugins/inputs/snmp2/README.md | 130 -- plugins/inputs/snmp2/snmp.go | 624 -------- plugins/inputs/snmp2/snmp_test.go | 493 ------ plugins/inputs/snmp_legacy/README.md | 549 +++++++ plugins/inputs/snmp_legacy/snmp_legacy.go | 818 ++++++++++ .../inputs/snmp_legacy/snmp_legacy_test.go | 482 ++++++ .../{snmp => snmp_legacy}/testdata/oids.txt | 0 14 files changed, 3130 insertions(+), 3114 deletions(-) delete mode 100644 plugins/inputs/snmp2/README.md delete mode 100644 plugins/inputs/snmp2/snmp.go delete mode 100644 plugins/inputs/snmp2/snmp_test.go create mode 100644 plugins/inputs/snmp_legacy/README.md create mode 100644 plugins/inputs/snmp_legacy/snmp_legacy.go create mode 100644 plugins/inputs/snmp_legacy/snmp_legacy_test.go rename plugins/inputs/{snmp => snmp_legacy}/testdata/oids.txt (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b3624a9e..72b77f024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,137 +1,49 @@ ## v1.0 [unreleased] -### Features - -- [#1413](https://github.com/influxdata/telegraf/issues/1413): Separate container_version from container_image tag. -- [#1525](https://github.com/influxdata/telegraf/pull/1525): Support setting per-device and total metrics for Docker network and blockio. -- [#1466](https://github.com/influxdata/telegraf/pull/1466): MongoDB input plugin: adding per DB stats from db.stats() - -### Bugfixes - -- [#1519](https://github.com/influxdata/telegraf/pull/1519): Fix error race conditions and partial failures. -- [#1477](https://github.com/influxdata/telegraf/issues/1477): nstat: fix inaccurate config panic. -- [#1481](https://github.com/influxdata/telegraf/issues/1481): jolokia: fix handling multiple multi-dimensional attributes. -- [#1430](https://github.com/influxdata/telegraf/issues/1430): Fix prometheus character sanitizing. Sanitize more win_perf_counters characters. -- [#1534](https://github.com/influxdata/telegraf/pull/1534): Add diskio io_time to FreeBSD & report timing metrics as ms (as linux does). -- [#1379](https://github.com/influxdata/telegraf/issues/1379): Fix covering Amazon Linux for post remove flow. - -## v1.0 beta 3 [2016-07-18] - ### Release Notes -**Breaking Change**: Aerospike main server node measurements have been renamed +- **Breaking Change** The SNMP plugin is being deprecated in it's current form. +There is a [new SNMP plugin](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp) +which fixes many of the issues and confusions +of it's predecessor. For users wanting to continue to use the deprecated SNMP +plugin, you will need to change your config file from `[[inputs.snmp]]` to +`[[inputs.snmp_legacy]]`. The configuration of the new SNMP plugin is _not_ +backwards-compatible. + +- **Breaking Change**: Aerospike main server node measurements have been renamed aerospike_node. Aerospike namespace measurements have been renamed to aerospike_namespace. They will also now be tagged with the node_name that they correspond to. This has been done to differentiate measurements that pertain to node vs. namespace statistics. -**Breaking Change**: users of github_webhooks must change to the new +- **Breaking Change**: users of github_webhooks must change to the new `[[inputs.webhooks]]` plugin. -This means that the default github_webhooks config: + This means that the default github_webhooks config: -``` -# A Github Webhook Event collector -[[inputs.github_webhooks]] - ## Address and port to host Webhook listener on - service_address = ":1618" -``` + ``` + # A Github Webhook Event collector + [[inputs.github_webhooks]] + ## Address and port to host Webhook listener on + service_address = ":1618" + ``` -should now look like: + should now look like: -``` -# A Webhooks Event collector -[[inputs.webhooks]] - ## Address and port to host Webhook listener on - service_address = ":1618" + ``` + # A Webhooks Event collector + [[inputs.webhooks]] + ## Address and port to host Webhook listener on + service_address = ":1618" - [inputs.webhooks.github] - path = "/" -``` - -### Features - -- [#1503](https://github.com/influxdata/telegraf/pull/1503): Add tls support for certs to RabbitMQ input plugin -- [#1289](https://github.com/influxdata/telegraf/pull/1289): webhooks input plugin. Thanks @francois2metz and @cduez! -- [#1247](https://github.com/influxdata/telegraf/pull/1247): rollbar webhook plugin. -- [#1408](https://github.com/influxdata/telegraf/pull/1408): mandrill webhook plugin. -- [#1402](https://github.com/influxdata/telegraf/pull/1402): docker-machine/boot2docker no longer required for unit tests. -- [#1350](https://github.com/influxdata/telegraf/pull/1350): cgroup input plugin. -- [#1369](https://github.com/influxdata/telegraf/pull/1369): Add input plugin for consuming metrics from NSQD. -- [#1369](https://github.com/influxdata/telegraf/pull/1480): add ability to read redis from a socket. -- [#1387](https://github.com/influxdata/telegraf/pull/1387): **Breaking Change** - Redis `role` tag renamed to `replication_role` to avoid global_tags override -- [#1437](https://github.com/influxdata/telegraf/pull/1437): Fetching Galera status metrics in MySQL -- [#1500](https://github.com/influxdata/telegraf/pull/1500): Aerospike plugin refactored to use official client lib. -- [#1434](https://github.com/influxdata/telegraf/pull/1434): Add measurement name arg to logparser plugin. -- [#1479](https://github.com/influxdata/telegraf/pull/1479): logparser: change resp_code from a field to a tag. -- [#1411](https://github.com/influxdata/telegraf/pull/1411): Implement support for fetching hddtemp data -- [#1466](https://github.com/influxdata/telegraf/pull/1466): MongoDB input plugin: adding per DB stats from db.stats() -- [#1389](https://github.com/influxdata/telegraf/pull/1389): Add a new and improved SNMP plugin (snmp2). - -### Bugfixes - -- [#1472](https://github.com/influxdata/telegraf/pull/1472): diskio input plugin: set 'skip_serial_number = true' by default to avoid high cardinality. -- [#1426](https://github.com/influxdata/telegraf/pull/1426): nil metrics panic fix. -- [#1384](https://github.com/influxdata/telegraf/pull/1384): Fix datarace in apache input plugin. -- [#1399](https://github.com/influxdata/telegraf/issues/1399): Add `read_repairs` statistics to riak plugin. -- [#1405](https://github.com/influxdata/telegraf/issues/1405): Fix memory/connection leak in prometheus input plugin. -- [#1378](https://github.com/influxdata/telegraf/issues/1378): Trim BOM from config file for Windows support. -- [#1339](https://github.com/influxdata/telegraf/issues/1339): Prometheus client output panic on service reload. -- [#1461](https://github.com/influxdata/telegraf/pull/1461): Prometheus parser, protobuf format header fix. -- [#1334](https://github.com/influxdata/telegraf/issues/1334): Prometheus output, metric refresh and caching fixes. -- [#1432](https://github.com/influxdata/telegraf/issues/1432): Panic fix for multiple graphite outputs under very high load. -- [#1412](https://github.com/influxdata/telegraf/pull/1412): Instrumental output has better reconnect behavior -- [#1460](https://github.com/influxdata/telegraf/issues/1460): Remove PID from procstat plugin to fix cardinality issues. -- [#1427](https://github.com/influxdata/telegraf/issues/1427): Cassandra input: version 2.x "column family" fix. -- [#1463](https://github.com/influxdata/telegraf/issues/1463): Shared WaitGroup in Exec plugin -- [#1436](https://github.com/influxdata/telegraf/issues/1436): logparser: honor modifiers in "pattern" config. -- [#1418](https://github.com/influxdata/telegraf/issues/1418): logparser: error and exit on file permissions/missing errors. -- [#1499](https://github.com/influxdata/telegraf/pull/1499): Make the user able to specify full path for HAproxy stats -- [#1521](https://github.com/influxdata/telegraf/pull/1521): Fix Redis url, an extra "tcp://" was added. - -## v1.0 beta 2 [2016-06-21] - -### Features - -- [#1340](https://github.com/influxdata/telegraf/issues/1340): statsd: do not log every dropped metric. -- [#1368](https://github.com/influxdata/telegraf/pull/1368): Add precision rounding to all metrics on collection. -- [#1390](https://github.com/influxdata/telegraf/pull/1390): Add support for Tengine -- [#1320](https://github.com/influxdata/telegraf/pull/1320): Logparser input plugin for parsing grok-style log patterns. -- [#1397](https://github.com/influxdata/telegraf/issues/1397): ElasticSearch: now supports connecting to ElasticSearch via SSL - -### Bugfixes - -- [#1330](https://github.com/influxdata/telegraf/issues/1330): Fix exec plugin panic when using single binary. -- [#1336](https://github.com/influxdata/telegraf/issues/1336): Fixed incorrect prometheus metrics source selection. -- [#1112](https://github.com/influxdata/telegraf/issues/1112): Set default Zookeeper chroot to empty string. -- [#1335](https://github.com/influxdata/telegraf/issues/1335): Fix overall ping timeout to be calculated based on per-ping timeout. -- [#1374](https://github.com/influxdata/telegraf/pull/1374): Change "default" retention policy to "". -- [#1377](https://github.com/influxdata/telegraf/issues/1377): Graphite output mangling '%' character. -- [#1396](https://github.com/influxdata/telegraf/pull/1396): Prometheus input plugin now supports x509 certs authentication - -## v1.0 beta 1 [2016-06-07] - -### Release Notes + [inputs.webhooks.github] + path = "/" + ``` - `flush_jitter` behavior has been changed. The random jitter will now be evaluated at every flush interval, rather than once at startup. This makes it consistent with the behavior of `collection_jitter`. -- All AWS plugins now utilize a standard mechanism for evaluating credentials. -This allows all AWS plugins to support environment variables, shared credential -files & profiles, and role assumptions. See the specific plugin README for -details. - -- The AWS CloudWatch input plugin can now declare a wildcard value for a metric -dimension. This causes the plugin to read all metrics that contain the specified -dimension key regardless of value. This is used to export collections of metrics -without having to know the dimension values ahead of time. - -- The AWS CloudWatch input plugin can now be configured with the `cache_ttl` -attribute. This configures the TTL of the internal metric cache. This is useful -in conjunction with wildcard dimension values as it will control the amount of -time before a new metric is included by the plugin. - ### Features - [#1262](https://github.com/influxdata/telegraf/pull/1261): Add graylog input pluging. @@ -149,6 +61,30 @@ time before a new metric is included by the plugin. - [#1278](https://github.com/influxdata/telegraf/pull/1278) & [#1288](https://github.com/influxdata/telegraf/pull/1288) & [#1295](https://github.com/influxdata/telegraf/pull/1295): RabbitMQ/Apache/InfluxDB inputs: made url(s) parameter optional by using reasonable input defaults if not specified - [#1296](https://github.com/influxdata/telegraf/issues/1296): Refactor of flush_jitter argument. - [#1213](https://github.com/influxdata/telegraf/issues/1213): Add inactive & active memory to mem plugin. +- [#1340](https://github.com/influxdata/telegraf/issues/1340): statsd: do not log every dropped metric. +- [#1368](https://github.com/influxdata/telegraf/pull/1368): Add precision rounding to all metrics on collection. +- [#1390](https://github.com/influxdata/telegraf/pull/1390): Add support for Tengine +- [#1320](https://github.com/influxdata/telegraf/pull/1320): Logparser input plugin for parsing grok-style log patterns. +- [#1397](https://github.com/influxdata/telegraf/issues/1397): ElasticSearch: now supports connecting to ElasticSearch via SSL +- [#1503](https://github.com/influxdata/telegraf/pull/1503): Add tls support for certs to RabbitMQ input plugin +- [#1289](https://github.com/influxdata/telegraf/pull/1289): webhooks input plugin. Thanks @francois2metz and @cduez! +- [#1247](https://github.com/influxdata/telegraf/pull/1247): rollbar webhook plugin. +- [#1408](https://github.com/influxdata/telegraf/pull/1408): mandrill webhook plugin. +- [#1402](https://github.com/influxdata/telegraf/pull/1402): docker-machine/boot2docker no longer required for unit tests. +- [#1350](https://github.com/influxdata/telegraf/pull/1350): cgroup input plugin. +- [#1369](https://github.com/influxdata/telegraf/pull/1369): Add input plugin for consuming metrics from NSQD. +- [#1369](https://github.com/influxdata/telegraf/pull/1480): add ability to read redis from a socket. +- [#1387](https://github.com/influxdata/telegraf/pull/1387): **Breaking Change** - Redis `role` tag renamed to `replication_role` to avoid global_tags override +- [#1437](https://github.com/influxdata/telegraf/pull/1437): Fetching Galera status metrics in MySQL +- [#1500](https://github.com/influxdata/telegraf/pull/1500): Aerospike plugin refactored to use official client lib. +- [#1434](https://github.com/influxdata/telegraf/pull/1434): Add measurement name arg to logparser plugin. +- [#1479](https://github.com/influxdata/telegraf/pull/1479): logparser: change resp_code from a field to a tag. +- [#1411](https://github.com/influxdata/telegraf/pull/1411): Implement support for fetching hddtemp data +- [#1466](https://github.com/influxdata/telegraf/pull/1466): MongoDB input plugin: adding per DB stats from db.stats() +- [#1389](https://github.com/influxdata/telegraf/pull/1389): Add a new and improved SNMP plugin (snmp2). +- [#1413](https://github.com/influxdata/telegraf/issues/1413): Separate container_version from container_image tag. +- [#1525](https://github.com/influxdata/telegraf/pull/1525): Support setting per-device and total metrics for Docker network and blockio. +- [#1466](https://github.com/influxdata/telegraf/pull/1466): MongoDB input plugin: adding per DB stats from db.stats() ### Bugfixes @@ -161,6 +97,38 @@ time before a new metric is included by the plugin. - [#1316](https://github.com/influxdata/telegraf/pull/1316): Removed leaked "database" tag on redis metrics. Thanks @PierreF! - [#1323](https://github.com/influxdata/telegraf/issues/1323): Processes plugin: fix potential error with /proc/net/stat directory. - [#1322](https://github.com/influxdata/telegraf/issues/1322): Fix rare RHEL 5.2 panic in gopsutil diskio gathering function. +- [#1330](https://github.com/influxdata/telegraf/issues/1330): Fix exec plugin panic when using single binary. +- [#1336](https://github.com/influxdata/telegraf/issues/1336): Fixed incorrect prometheus metrics source selection. +- [#1112](https://github.com/influxdata/telegraf/issues/1112): Set default Zookeeper chroot to empty string. +- [#1335](https://github.com/influxdata/telegraf/issues/1335): Fix overall ping timeout to be calculated based on per-ping timeout. +- [#1374](https://github.com/influxdata/telegraf/pull/1374): Change "default" retention policy to "". +- [#1377](https://github.com/influxdata/telegraf/issues/1377): Graphite output mangling '%' character. +- [#1396](https://github.com/influxdata/telegraf/pull/1396): Prometheus input plugin now supports x509 certs authentication +- [#1472](https://github.com/influxdata/telegraf/pull/1472): diskio input plugin: set 'skip_serial_number = true' by default to avoid high cardinality. +- [#1426](https://github.com/influxdata/telegraf/pull/1426): nil metrics panic fix. +- [#1384](https://github.com/influxdata/telegraf/pull/1384): Fix datarace in apache input plugin. +- [#1399](https://github.com/influxdata/telegraf/issues/1399): Add `read_repairs` statistics to riak plugin. +- [#1405](https://github.com/influxdata/telegraf/issues/1405): Fix memory/connection leak in prometheus input plugin. +- [#1378](https://github.com/influxdata/telegraf/issues/1378): Trim BOM from config file for Windows support. +- [#1339](https://github.com/influxdata/telegraf/issues/1339): Prometheus client output panic on service reload. +- [#1461](https://github.com/influxdata/telegraf/pull/1461): Prometheus parser, protobuf format header fix. +- [#1334](https://github.com/influxdata/telegraf/issues/1334): Prometheus output, metric refresh and caching fixes. +- [#1432](https://github.com/influxdata/telegraf/issues/1432): Panic fix for multiple graphite outputs under very high load. +- [#1412](https://github.com/influxdata/telegraf/pull/1412): Instrumental output has better reconnect behavior +- [#1460](https://github.com/influxdata/telegraf/issues/1460): Remove PID from procstat plugin to fix cardinality issues. +- [#1427](https://github.com/influxdata/telegraf/issues/1427): Cassandra input: version 2.x "column family" fix. +- [#1463](https://github.com/influxdata/telegraf/issues/1463): Shared WaitGroup in Exec plugin +- [#1436](https://github.com/influxdata/telegraf/issues/1436): logparser: honor modifiers in "pattern" config. +- [#1418](https://github.com/influxdata/telegraf/issues/1418): logparser: error and exit on file permissions/missing errors. +- [#1499](https://github.com/influxdata/telegraf/pull/1499): Make the user able to specify full path for HAproxy stats +- [#1521](https://github.com/influxdata/telegraf/pull/1521): Fix Redis url, an extra "tcp://" was added. +- [#1519](https://github.com/influxdata/telegraf/pull/1519): Fix error race conditions and partial failures. +- [#1477](https://github.com/influxdata/telegraf/issues/1477): nstat: fix inaccurate config panic. +- [#1481](https://github.com/influxdata/telegraf/issues/1481): jolokia: fix handling multiple multi-dimensional attributes. +- [#1430](https://github.com/influxdata/telegraf/issues/1430): Fix prometheus character sanitizing. Sanitize more win_perf_counters characters. +- [#1534](https://github.com/influxdata/telegraf/pull/1534): Add diskio io_time to FreeBSD & report timing metrics as ms (as linux does). +- [#1379](https://github.com/influxdata/telegraf/issues/1379): Fix covering Amazon Linux for post remove flow. + ## v0.13.1 [2016-05-24] diff --git a/README.md b/README.md index b0a1cb8a7..9d2ee3ce1 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,6 @@ Currently implemented sources: * [riak](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/riak) * [sensors ](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sensors) (only available if built from source) * [snmp](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp) -* [snmp2](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp2) * [sql server](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sqlserver) (microsoft) * [twemproxy](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/twemproxy) * [varnish](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/varnish) diff --git a/etc/telegraf.conf b/etc/telegraf.conf index 5189d2e3f..b7d40894c 100644 --- a/etc/telegraf.conf +++ b/etc/telegraf.conf @@ -1377,8 +1377,56 @@ # servers = ["http://localhost:8098"] -# # Reads oids value from one or many snmp agents +# # Retrieves SNMP values from remote agents # [[inputs.snmp]] +# agents = [ "127.0.0.1:161" ] +# version = 2 # Values: 1, 2, or 3 +# +# ## SNMPv1 & SNMPv2 parameters +# community = "public" +# +# ## SNMPv2 & SNMPv3 parameters +# max_repetitions = 50 +# +# ## SNMPv3 parameters +# #sec_name = "myuser" +# #auth_protocol = "md5" # Values: "MD5", "SHA", "" +# #auth_password = "password123" +# #sec_level = "authNoPriv" # Values: "noAuthNoPriv", "authNoPriv", "authPriv" +# #context_name = "" +# #priv_protocol = "" # Values: "DES", "AES", "" +# #priv_password = "" +# +# ## measurement name +# name = "system" +# [[inputs.snmp.field]] +# name = "hostname" +# oid = ".1.2.3.0.1.1" +# [[inputs.snmp.field]] +# name = "uptime" +# oid = ".1.2.3.0.1.200" +# [[inputs.snmp.field]] +# name = "load" +# oid = ".1.2.3.0.1.201" +# +# [[inputs.snmp.table]] +# # measurement name +# name = "remote_servers" +# inherit_tags = [ "hostname" ] +# [[inputs.snmp.table.field]] +# name = "server" +# oid = ".1.2.3.0.0.0" +# is_tag = true +# [[inputs.snmp.table.field]] +# name = "connections" +# oid = ".1.2.3.0.0.1" +# [[inputs.snmp.table.field]] +# name = "latency" +# oid = ".1.2.3.0.0.2" + + +# # DEPRECATED: This will be removed in a future release. +# [[inputs.snmp_legacy]] # ## 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 diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index ad964555e..e9fd9873a 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -61,7 +61,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/riak" _ "github.com/influxdata/telegraf/plugins/inputs/sensors" _ "github.com/influxdata/telegraf/plugins/inputs/snmp" - _ "github.com/influxdata/telegraf/plugins/inputs/snmp2" + _ "github.com/influxdata/telegraf/plugins/inputs/snmp_legacy" _ "github.com/influxdata/telegraf/plugins/inputs/sqlserver" _ "github.com/influxdata/telegraf/plugins/inputs/statsd" _ "github.com/influxdata/telegraf/plugins/inputs/sysstat" diff --git a/plugins/inputs/snmp/README.md b/plugins/inputs/snmp/README.md index bee783228..763054cea 100644 --- a/plugins/inputs/snmp/README.md +++ b/plugins/inputs/snmp/README.md @@ -1,549 +1,130 @@ -# SNMP Input Plugin +# SNMP Plugin -The SNMP input plugin gathers metrics from SNMP agents +The SNMP input plugin gathers metrics from SNMP agents. +It is an alternative to the SNMP plugin with significantly different configuration & behavior. -### Configuration: +## Configuration: +### Example: -#### Very simple example - -In this example, the plugin will gather value of OIDS: - - - `.1.3.6.1.2.1.2.2.1.4.1` - -```toml -# Very Simple Example -[[inputs.snmp]] - - [[inputs.snmp.host]] - address = "127.0.0.1:161" - # SNMP community - community = "public" # default public - # SNMP version (1, 2 or 3) - # Version 3 not supported yet - version = 2 # default 2 - # Simple list of OIDs to get, in addition to "collect" - get_oids = [".1.3.6.1.2.1.2.2.1.4.1"] +SNMP data: +``` +.1.2.3.0.0.1.0 octet_str "foo" +.1.2.3.0.0.1.1 octet_str "bar" +.1.2.3.0.0.102 octet_str "bad" +.1.2.3.0.0.2.0 integer 1 +.1.2.3.0.0.2.1 integer 2 +.1.2.3.0.0.3.0 octet_str "0.123" +.1.2.3.0.0.3.1 octet_str "0.456" +.1.2.3.0.0.3.2 octet_str "9.999" +.1.2.3.0.1 octet_str "baz" +.1.2.3.0.2 uinteger 54321 +.1.2.3.0.3 uinteger 234 ``` - -#### Simple example - -In this example, Telegraf gathers value of OIDS: - - - named **ifnumber** - - named **interface_speed** - -With **inputs.snmp.get** section the plugin gets the oid number: - - - **ifnumber** => `.1.3.6.1.2.1.2.1.0` - - **interface_speed** => *ifSpeed* - -As you can see *ifSpeed* is not a valid OID. In order to get -the valid OID, the plugin uses `snmptranslate_file` to match the OID: - - - **ifnumber** => `.1.3.6.1.2.1.2.1.0` - - **interface_speed** => *ifSpeed* => `.1.3.6.1.2.1.2.2.1.5` - -Also as the plugin will append `instance` to the corresponding OID: - - - **ifnumber** => `.1.3.6.1.2.1.2.1.0` - - **interface_speed** => *ifSpeed* => `.1.3.6.1.2.1.2.2.1.5.1` - -In this example, the plugin will gather value of OIDS: - -- `.1.3.6.1.2.1.2.1.0` -- `.1.3.6.1.2.1.2.2.1.5.1` - - +Telegraf config: ```toml -# Simple example [[inputs.snmp]] - ## 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 - snmptranslate_file = "/tmp/oids.txt" - [[inputs.snmp.host]] - address = "127.0.0.1:161" - # SNMP community - community = "public" # default public - # SNMP version (1, 2 or 3) - # Version 3 not supported yet - version = 2 # default 2 - # Which get/bulk do you want to collect for this host - collect = ["ifnumber", "interface_speed"] + agents = [ "127.0.0.1:161" ] + version = 2 + community = "public" - [[inputs.snmp.get]] - name = "ifnumber" - oid = ".1.3.6.1.2.1.2.1.0" + name = "system" + [[inputs.snmp.field]] + name = "hostname" + oid = ".1.2.3.0.1" + is_tag = true + [[inputs.snmp.field]] + name = "uptime" + oid = ".1.2.3.0.2" + [[inputs.snmp.field]] + name = "loadavg" + oid = ".1.2.3.0.3" + conversion = "float(2)" - [[inputs.snmp.get]] - name = "interface_speed" - oid = "ifSpeed" - instance = "1" - -``` - - -#### Simple bulk example - -In this example, Telegraf gathers value of OIDS: - - - named **ifnumber** - - named **interface_speed** - - named **if_out_octets** - -With **inputs.snmp.get** section the plugin gets oid number: - - - **ifnumber** => `.1.3.6.1.2.1.2.1.0` - - **interface_speed** => *ifSpeed* - -With **inputs.snmp.bulk** section the plugin gets the oid number: - - - **if_out_octets** => *ifOutOctets* - -As you can see *ifSpeed* and *ifOutOctets* are not a valid OID. -In order to get the valid OID, the plugin uses `snmptranslate_file` -to match the OID: - - - **ifnumber** => `.1.3.6.1.2.1.2.1.0` - - **interface_speed** => *ifSpeed* => `.1.3.6.1.2.1.2.2.1.5` - - **if_out_octets** => *ifOutOctets* => `.1.3.6.1.2.1.2.2.1.16` - -Also, the plugin will append `instance` to the corresponding OID: - - - **ifnumber** => `.1.3.6.1.2.1.2.1.0` - - **interface_speed** => *ifSpeed* => `.1.3.6.1.2.1.2.2.1.5.1` - -And **if_out_octets** is a bulk request, the plugin will gathers all -OIDS in the table. - -- `.1.3.6.1.2.1.2.2.1.16.1` -- `.1.3.6.1.2.1.2.2.1.16.2` -- `.1.3.6.1.2.1.2.2.1.16.3` -- `.1.3.6.1.2.1.2.2.1.16.4` -- `.1.3.6.1.2.1.2.2.1.16.5` -- `...` - -In this example, the plugin will gather value of OIDS: - -- `.1.3.6.1.2.1.2.1.0` -- `.1.3.6.1.2.1.2.2.1.5.1` -- `.1.3.6.1.2.1.2.2.1.16.1` -- `.1.3.6.1.2.1.2.2.1.16.2` -- `.1.3.6.1.2.1.2.2.1.16.3` -- `.1.3.6.1.2.1.2.2.1.16.4` -- `.1.3.6.1.2.1.2.2.1.16.5` -- `...` - - -```toml -# Simple bulk example -[[inputs.snmp]] - ## 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 - snmptranslate_file = "/tmp/oids.txt" - [[inputs.snmp.host]] - address = "127.0.0.1:161" - # SNMP community - community = "public" # default public - # SNMP version (1, 2 or 3) - # Version 3 not supported yet - version = 2 # default 2 - # Which get/bulk do you want to collect for this host - collect = ["interface_speed", "if_number", "if_out_octets"] - - [[inputs.snmp.get]] - name = "interface_speed" - oid = "ifSpeed" - instance = "1" - - [[inputs.snmp.get]] - name = "if_number" - oid = "ifNumber" - - [[inputs.snmp.bulk]] - name = "if_out_octets" - oid = "ifOutOctets" -``` - - -#### Table example - -In this example, we remove collect attribute to the host section, -but you can still use it in combination of the following part. - -Note: This example is like a bulk request a but using an -other configuration - -Telegraf gathers value of OIDS of the table: - - - named **iftable1** - -With **inputs.snmp.table** section the plugin gets oid number: - - - **iftable1** => `.1.3.6.1.2.1.31.1.1.1` - -Also **iftable1** is a table, the plugin will gathers all -OIDS in the table and in the subtables - -- `.1.3.6.1.2.1.31.1.1.1.1` -- `.1.3.6.1.2.1.31.1.1.1.1.1` -- `.1.3.6.1.2.1.31.1.1.1.1.2` -- `.1.3.6.1.2.1.31.1.1.1.1.3` -- `.1.3.6.1.2.1.31.1.1.1.1.4` -- `.1.3.6.1.2.1.31.1.1.1.1....` -- `.1.3.6.1.2.1.31.1.1.1.2` -- `.1.3.6.1.2.1.31.1.1.1.2....` -- `.1.3.6.1.2.1.31.1.1.1.3` -- `.1.3.6.1.2.1.31.1.1.1.3....` -- `.1.3.6.1.2.1.31.1.1.1.4` -- `.1.3.6.1.2.1.31.1.1.1.4....` -- `.1.3.6.1.2.1.31.1.1.1.5` -- `.1.3.6.1.2.1.31.1.1.1.5....` -- `.1.3.6.1.2.1.31.1.1.1.6....` -- `...` - -```toml -# Table example -[[inputs.snmp]] - ## 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 - snmptranslate_file = "/tmp/oids.txt" - [[inputs.snmp.host]] - address = "127.0.0.1:161" - # SNMP community - community = "public" # default public - # SNMP version (1, 2 or 3) - # Version 3 not supported yet - version = 2 # default 2 - # Which get/bulk do you want to collect for this host - # Which table do you want to collect - [[inputs.snmp.host.table]] - name = "iftable1" - - # table without mapping neither subtables - # This is like bulk request [[inputs.snmp.table]] - name = "iftable1" - oid = ".1.3.6.1.2.1.31.1.1.1" + name = "remote_servers" + inherit_tags = [ "hostname" ] + [[inputs.snmp.table.field]] + name = "server" + oid = ".1.2.3.0.0.1" + is_tag = true + [[inputs.snmp.table.field]] + name = "connections" + oid = ".1.2.3.0.0.2" + [[inputs.snmp.table.field]] + name = "latency" + oid = ".1.2.3.0.0.3" + conversion = "float" ``` - -#### Table with subtable example - -In this example, we remove collect attribute to the host section, -but you can still use it in combination of the following part. - -Note: This example is like a bulk request a but using an -other configuration - -Telegraf gathers value of OIDS of the table: - - - named **iftable2** - -With **inputs.snmp.table** section *AND* **sub_tables** attribute, -the plugin will get OIDS from subtables: - - - **iftable2** => `.1.3.6.1.2.1.2.2.1.13` - -Also **iftable2** is a table, the plugin will gathers all -OIDS in subtables: - -- `.1.3.6.1.2.1.2.2.1.13.1` -- `.1.3.6.1.2.1.2.2.1.13.2` -- `.1.3.6.1.2.1.2.2.1.13.3` -- `.1.3.6.1.2.1.2.2.1.13.4` -- `.1.3.6.1.2.1.2.2.1.13....` - - -```toml -# Table with subtable example -[[inputs.snmp]] - ## 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 - snmptranslate_file = "/tmp/oids.txt" - [[inputs.snmp.host]] - address = "127.0.0.1:161" - # SNMP community - community = "public" # default public - # SNMP version (1, 2 or 3) - # Version 3 not supported yet - version = 2 # default 2 - # Which table do you want to collect - [[inputs.snmp.host.table]] - name = "iftable2" - - # table without mapping but with subtables - [[inputs.snmp.table]] - name = "iftable2" - sub_tables = [".1.3.6.1.2.1.2.2.1.13"] - # note - # oid attribute is useless +Resulting output: +``` +* Plugin: snmp, Collection 1 +> system,agent_host=127.0.0.1,host=mylocalhost,hostname=baz loadavg=2.34,uptime=54321i 1468953135000000000 +> remote_servers,agent_host=127.0.0.1,host=mylocalhost,hostname=baz,server=foo connections=1i,latency=0.123 1468953135000000000 +> remote_servers,agent_host=127.0.0.1,host=mylocalhost,hostname=baz,server=bar connections=2i,latency=0.456 1468953135000000000 ``` +### Config parameters -#### Table with mapping example +* `agents`: Default: `[]` +List of SNMP agents to connect to in the form of `IP[:PORT]`. If `:PORT` is unspecified, it defaults to `161`. -In this example, we remove collect attribute to the host section, -but you can still use it in combination of the following part. +* `version`: Default: `2` +SNMP protocol version to use. -Telegraf gathers value of OIDS of the table: +* `community`: Default: `"public"` +SNMP community to use. - - named **iftable3** +* `max_repetitions`: Default: `50` +Maximum number of iterations for repeating variables. -With **inputs.snmp.table** section the plugin gets oid number: +* `sec_name`: +Security name for authenticated SNMPv3 requests. - - **iftable3** => `.1.3.6.1.2.1.31.1.1.1` +* `auth_protocol`: Values: `"MD5"`,`"SHA"`,`""`. Default: `""` +Authentication protocol for authenticated SNMPv3 requests. -Also **iftable2** is a table, the plugin will gathers all -OIDS in the table and in the subtables +* `auth_password`: +Authentication password for authenticated SNMPv3 requests. -- `.1.3.6.1.2.1.31.1.1.1.1` -- `.1.3.6.1.2.1.31.1.1.1.1.1` -- `.1.3.6.1.2.1.31.1.1.1.1.2` -- `.1.3.6.1.2.1.31.1.1.1.1.3` -- `.1.3.6.1.2.1.31.1.1.1.1.4` -- `.1.3.6.1.2.1.31.1.1.1.1....` -- `.1.3.6.1.2.1.31.1.1.1.2` -- `.1.3.6.1.2.1.31.1.1.1.2....` -- `.1.3.6.1.2.1.31.1.1.1.3` -- `.1.3.6.1.2.1.31.1.1.1.3....` -- `.1.3.6.1.2.1.31.1.1.1.4` -- `.1.3.6.1.2.1.31.1.1.1.4....` -- `.1.3.6.1.2.1.31.1.1.1.5` -- `.1.3.6.1.2.1.31.1.1.1.5....` -- `.1.3.6.1.2.1.31.1.1.1.6....` -- `...` +* `sec_level`: Values: `"noAuthNoPriv"`,`"authNoPriv"`,`"authPriv"`. Default: `"noAuthNoPriv"` +Security level used for SNMPv3 messages. -But the **include_instances** attribute will filter which OIDS -will be gathered; As you see, there is an other attribute, `mapping_table`. -`include_instances` and `mapping_table` permit to build a hash table -to filter only OIDS you want. -Let's say, we have the following data on SNMP server: - - OID: `.1.3.6.1.2.1.31.1.1.1.1.1` has as value: `enp5s0` - - OID: `.1.3.6.1.2.1.31.1.1.1.1.2` has as value: `enp5s1` - - OID: `.1.3.6.1.2.1.31.1.1.1.1.3` has as value: `enp5s2` - - OID: `.1.3.6.1.2.1.31.1.1.1.1.4` has as value: `eth0` - - OID: `.1.3.6.1.2.1.31.1.1.1.1.5` has as value: `eth1` +* `context_name`: +Context name used for SNMPv3 requests. -The plugin will build the following hash table: +* `priv_protocol`: Values: `"DES"`,`"AES"`,`""`. Default: `""` +Privacy protocol used for encrypted SNMPv3 messages. -| instance name | instance id | -|---------------|-------------| -| `enp5s0` | `1` | -| `enp5s1` | `2` | -| `enp5s2` | `3` | -| `eth0` | `4` | -| `eth1` | `5` | - -With the **include_instances** attribute, the plugin will gather -the following OIDS: - -- `.1.3.6.1.2.1.31.1.1.1.1.1` -- `.1.3.6.1.2.1.31.1.1.1.1.5` -- `.1.3.6.1.2.1.31.1.1.1.2.1` -- `.1.3.6.1.2.1.31.1.1.1.2.5` -- `.1.3.6.1.2.1.31.1.1.1.3.1` -- `.1.3.6.1.2.1.31.1.1.1.3.5` -- `.1.3.6.1.2.1.31.1.1.1.4.1` -- `.1.3.6.1.2.1.31.1.1.1.4.5` -- `.1.3.6.1.2.1.31.1.1.1.5.1` -- `.1.3.6.1.2.1.31.1.1.1.5.5` -- `.1.3.6.1.2.1.31.1.1.1.6.1` -- `.1.3.6.1.2.1.31.1.1.1.6.5` -- `...` - -Note: the plugin will add instance name as tag *instance* - -```toml -# Simple table with mapping example -[[inputs.snmp]] - ## 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 - snmptranslate_file = "/tmp/oids.txt" - [[inputs.snmp.host]] - address = "127.0.0.1:161" - # SNMP community - community = "public" # default public - # SNMP version (1, 2 or 3) - # Version 3 not supported yet - version = 2 # default 2 - # Which table do you want to collect - [[inputs.snmp.host.table]] - name = "iftable3" - include_instances = ["enp5s0", "eth1"] - - # table with mapping but without subtables - [[inputs.snmp.table]] - name = "iftable3" - oid = ".1.3.6.1.2.1.31.1.1.1" - # if empty. get all instances - mapping_table = ".1.3.6.1.2.1.31.1.1.1.1" - # if empty, get all subtables -``` +* `priv_password`: +Privacy password used for encrypted SNMPv3 messages. -#### Table with both mapping and subtable example +* `name`: +Output measurement name. -In this example, we remove collect attribute to the host section, -but you can still use it in combination of the following part. +#### Field parameters: +* `name`: +Output field/tag name. -Telegraf gathers value of OIDS of the table: +* `oid`: +OID to get. Must be in dotted notation, not textual. - - named **iftable4** +* `is_tag`: +Output this field as a tag. -With **inputs.snmp.table** section *AND* **sub_tables** attribute, -the plugin will get OIDS from subtables: +* `conversion`: Values: `"float(X)"`,`"float"`,`"int"`,`""`. Default: `""` +Converts the value according to the given specification. - - **iftable4** => `.1.3.6.1.2.1.31.1.1.1` + - `float(X)`: Converts the input value into a float and divides by the Xth power of 10. Efficively just moves the decimal left X places. For example a value of `123` with `float(2)` will result in `1.23`. + - `float`: Converts the value into a float with no adjustment. Same as `float(0)`. + - `int`: Convertes the value into an integer. -Also **iftable2** is a table, the plugin will gathers all -OIDS in the table and in the subtables +#### Table parameters: +* `name`: +Output measurement name. -- `.1.3.6.1.2.1.31.1.1.1.6.1 -- `.1.3.6.1.2.1.31.1.1.1.6.2` -- `.1.3.6.1.2.1.31.1.1.1.6.3` -- `.1.3.6.1.2.1.31.1.1.1.6.4` -- `.1.3.6.1.2.1.31.1.1.1.6....` -- `.1.3.6.1.2.1.31.1.1.1.10.1` -- `.1.3.6.1.2.1.31.1.1.1.10.2` -- `.1.3.6.1.2.1.31.1.1.1.10.3` -- `.1.3.6.1.2.1.31.1.1.1.10.4` -- `.1.3.6.1.2.1.31.1.1.1.10....` - -But the **include_instances** attribute will filter which OIDS -will be gathered; As you see, there is an other attribute, `mapping_table`. -`include_instances` and `mapping_table` permit to build a hash table -to filter only OIDS you want. -Let's say, we have the following data on SNMP server: - - OID: `.1.3.6.1.2.1.31.1.1.1.1.1` has as value: `enp5s0` - - OID: `.1.3.6.1.2.1.31.1.1.1.1.2` has as value: `enp5s1` - - OID: `.1.3.6.1.2.1.31.1.1.1.1.3` has as value: `enp5s2` - - OID: `.1.3.6.1.2.1.31.1.1.1.1.4` has as value: `eth0` - - OID: `.1.3.6.1.2.1.31.1.1.1.1.5` has as value: `eth1` - -The plugin will build the following hash table: - -| instance name | instance id | -|---------------|-------------| -| `enp5s0` | `1` | -| `enp5s1` | `2` | -| `enp5s2` | `3` | -| `eth0` | `4` | -| `eth1` | `5` | - -With the **include_instances** attribute, the plugin will gather -the following OIDS: - -- `.1.3.6.1.2.1.31.1.1.1.6.1` -- `.1.3.6.1.2.1.31.1.1.1.6.5` -- `.1.3.6.1.2.1.31.1.1.1.10.1` -- `.1.3.6.1.2.1.31.1.1.1.10.5` - -Note: the plugin will add instance name as tag *instance* - - - -```toml -# Table with both mapping and subtable example -[[inputs.snmp]] - ## 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 - snmptranslate_file = "/tmp/oids.txt" - [[inputs.snmp.host]] - address = "127.0.0.1:161" - # SNMP community - community = "public" # default public - # SNMP version (1, 2 or 3) - # Version 3 not supported yet - version = 2 # default 2 - # Which table do you want to collect - [[inputs.snmp.host.table]] - name = "iftable4" - include_instances = ["enp5s0", "eth1"] - - # table with both mapping and subtables - [[inputs.snmp.table]] - name = "iftable4" - # if empty get all instances - mapping_table = ".1.3.6.1.2.1.31.1.1.1.1" - # if empty get all subtables - # sub_tables could be not "real subtables" - sub_tables=[".1.3.6.1.2.1.2.2.1.13", "bytes_recv", "bytes_send"] - # note - # oid attribute is useless - - # SNMP SUBTABLES - [[inputs.snmp.subtable]] - name = "bytes_recv" - oid = ".1.3.6.1.2.1.31.1.1.1.6" - unit = "octets" - - [[inputs.snmp.subtable]] - name = "bytes_send" - oid = ".1.3.6.1.2.1.31.1.1.1.10" - unit = "octets" -``` - -#### Configuration notes - -- In **inputs.snmp.table** section, the `oid` attribute is useless if - the `sub_tables` attributes is defined - -- In **inputs.snmp.subtable** section, you can put a name from `snmptranslate_file` - as `oid` attribute instead of a valid OID - -### Measurements & Fields: - -With the last example (Table with both mapping and subtable example): - -- ifHCOutOctets - - ifHCOutOctets -- ifInDiscards - - ifInDiscards -- ifHCInOctets - - ifHCInOctets - -### Tags: - -With the last example (Table with both mapping and subtable example): - -- ifHCOutOctets - - host - - instance - - unit -- ifInDiscards - - host - - instance -- ifHCInOctets - - host - - instance - - unit - -### Example Output: - -With the last example (Table with both mapping and subtable example): - -``` -ifHCOutOctets,host=127.0.0.1,instance=enp5s0,unit=octets ifHCOutOctets=10565628i 1456878706044462901 -ifInDiscards,host=127.0.0.1,instance=enp5s0 ifInDiscards=0i 1456878706044510264 -ifHCInOctets,host=127.0.0.1,instance=enp5s0,unit=octets ifHCInOctets=76351777i 1456878706044531312 -``` +* `inherit_tags`: +Which tags to inherit from the top-level config and to use in the output of this table's measurement. diff --git a/plugins/inputs/snmp/snmp.go b/plugins/inputs/snmp/snmp.go index 3cbfa0db1..72ececec5 100644 --- a/plugins/inputs/snmp/snmp.go +++ b/plugins/inputs/snmp/snmp.go @@ -1,8 +1,8 @@ package snmp import ( - "io/ioutil" - "log" + "fmt" + "math" "net" "strconv" "strings" @@ -14,805 +14,611 @@ import ( "github.com/soniah/gosnmp" ) -// Snmp is a snmp plugin -type Snmp struct { - Host []Host - Get []Data - Bulk []Data - Table []Table - Subtable []Subtable - SnmptranslateFile string +const description = `Retrieves SNMP values from remote agents` +const sampleConfig = ` + agents = [ "127.0.0.1:161" ] + version = 2 # Values: 1, 2, or 3 - nameToOid map[string]string - initNode Node - subTableMap map[string]Subtable -} + ## SNMPv1 & SNMPv2 parameters + community = "public" -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 - // easy get oids - GetOids []string - // Table - Table []HostTable - // Oids - getOids []Data - bulkOids []Data - tables []HostTable - // array of processed oids - // to skip oid duplication - processedOids []string + ## SNMPv2 & SNMPv3 parameters + max_repetitions = 50 - OidInstanceMapping map[string]map[string]string -} + ## SNMPv3 parameters + #sec_name = "myuser" + #auth_protocol = "md5" # Values: "MD5", "SHA", "" + #auth_password = "password123" + #sec_level = "authNoPriv" # Values: "noAuthNoPriv", "authNoPriv", "authPriv" + #context_name = "" + #priv_protocol = "" # Values: "DES", "AES", "" + #priv_password = "" -type Table struct { - // name = "iftable" - Name string - // oid = ".1.3.6.1.2.1.31.1.1.1" - Oid string - //if empty get all instances - //mapping_table = ".1.3.6.1.2.1.31.1.1.1.1" - MappingTable string - // if empty get all subtables - // sub_tables could be not "real subtables" - //sub_tables=[".1.3.6.1.2.1.2.2.1.13", "bytes_recv", "bytes_send"] - SubTables []string -} + ## measurement name + name = "system" + [[inputs.snmp.field]] + name = "hostname" + oid = ".1.2.3.0.1.1" + [[inputs.snmp.field]] + name = "uptime" + oid = ".1.2.3.0.1.200" + [[inputs.snmp.field]] + name = "load" + oid = ".1.2.3.0.1.201" -type HostTable struct { - // name = "iftable" - Name string - // Includes only these instances - // include_instances = ["eth0", "eth1"] - IncludeInstances []string - // Excludes only these instances - // exclude_instances = ["eth20", "eth21"] - ExcludeInstances []string - // From Table struct - oid string - mappingTable string - subTables []string -} - -// TODO find better names -type Subtable struct { - //name = "bytes_send" - Name string - //oid = ".1.3.6.1.2.1.31.1.1.1.10" - Oid string - //unit = "octets" - Unit string -} - -type Data struct { - Name string - // OID (could be numbers or name) - Oid string - // Unit - Unit string - // SNMP getbulk max repetition - MaxRepetition uint8 `toml:"max_repetition"` - // SNMP Instance (default 0) - // (only used with GET request and if - // OID is a name from snmptranslate file) - Instance string - // OID (only number) (used for computation) - rawOid string -} - -type Node struct { - id string - name string - subnodes 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 - snmptranslate_file = "/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"] - # Simple list of OIDs to get, in addition to "collect" - get_oids = [] - - [[inputs.snmp.host]] - address = "192.168.2.3:161" - community = "public" - version = 2 - timeout = 2.0 - retries = 2 - collect = ["mybulk"] - get_oids = [ - "ifNumber", - ".1.3.6.1.2.1.1.3.0", - ] - - [[inputs.snmp.get]] - name = "ifnumber" - oid = "ifNumber" - - [[inputs.snmp.get]] - name = "interface_speed" - oid = "ifSpeed" - instance = "0" - - [[inputs.snmp.get]] - name = "sysuptime" - oid = ".1.3.6.1.2.1.1.3.0" - unit = "second" - - [[inputs.snmp.bulk]] - name = "mybulk" - max_repetition = 127 - oid = ".1.3.6.1.2.1.1" - - [[inputs.snmp.bulk]] - name = "ifoutoctets" - max_repetition = 127 - oid = "ifOutOctets" - - [[inputs.snmp.host]] - address = "192.168.2.13:161" - #address = "127.0.0.1:161" - community = "public" - version = 2 - timeout = 2.0 - retries = 2 - #collect = ["mybulk", "sysservices", "sysdescr", "systype"] - collect = ["sysuptime" ] - [[inputs.snmp.host.table]] - name = "iftable3" - include_instances = ["enp5s0", "eth1"] - - # SNMP TABLEs - # table without mapping neither subtables [[inputs.snmp.table]] - name = "iftable1" - oid = ".1.3.6.1.2.1.31.1.1.1" - - # table without mapping but with subtables - [[inputs.snmp.table]] - name = "iftable2" - oid = ".1.3.6.1.2.1.31.1.1.1" - sub_tables = [".1.3.6.1.2.1.2.2.1.13"] - - # table with mapping but without subtables - [[inputs.snmp.table]] - name = "iftable3" - oid = ".1.3.6.1.2.1.31.1.1.1" - # if empty. get all instances - mapping_table = ".1.3.6.1.2.1.31.1.1.1.1" - # if empty, get all subtables - - # table with both mapping and subtables - [[inputs.snmp.table]] - name = "iftable4" - oid = ".1.3.6.1.2.1.31.1.1.1" - # if empty get all instances - mapping_table = ".1.3.6.1.2.1.31.1.1.1.1" - # if empty get all subtables - # sub_tables could be not "real subtables" - sub_tables=[".1.3.6.1.2.1.2.2.1.13", "bytes_recv", "bytes_send"] + # measurement name + name = "remote_servers" + inherit_tags = [ "hostname" ] + [[inputs.snmp.table.field]] + name = "server" + oid = ".1.2.3.0.0.0" + is_tag = true + [[inputs.snmp.table.field]] + name = "connections" + oid = ".1.2.3.0.0.1" + [[inputs.snmp.table.field]] + name = "latency" + oid = ".1.2.3.0.0.2" ` -// SampleConfig returns sample configuration message -func (s *Snmp) SampleConfig() string { - return sampleConfig +// Snmp holds the configuration for the plugin. +type Snmp struct { + // The SNMP agent to query. Format is ADDR[:PORT] (e.g. 1.2.3.4:161). + Agents []string + // Timeout to wait for a response. Value is anything accepted by time.ParseDuration(). + Timeout string + Retries int + // Values: 1, 2, 3 + Version uint8 + + // Parameters for Version 1 & 2 + Community string + + // Parameters for Version 2 & 3 + MaxRepetitions uint + + // Parameters for Version 3 + ContextName string + // Values: "noAuthNoPriv", "authNoPriv", "authPriv" + SecLevel string + SecName string + // Values: "MD5", "SHA", "". Default: "" + AuthProtocol string + AuthPassword string + // Values: "DES", "AES", "". Default: "" + PrivProtocol string + PrivPassword string + EngineID string + EngineBoots uint32 + EngineTime uint32 + + Tables []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 + Fields []Field `toml:"field"` + + connectionCache map[string]snmpConnection } -// Description returns description of Zookeeper plugin -func (s *Snmp) Description() string { - return `Reads oids value from one or many snmp agents` +// 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 + + // Fields is the tags and values to look up. + Fields []Field `toml:"field"` } -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) - } +// 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 + // 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 conver the value into an integer. + Conversion string } -func findnodename(node Node, ids []string) (string, string) { - // ids = ["1", "3", "6", ...] - if len(ids) == 1 { - return node.name, ids[0] - } - 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, "0" - } 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, "" +// 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 } -func (s *Snmp) Gather(acc telegraf.Accumulator) error { - // TODO put this in cache on first run - // Create subtables mapping - if len(s.subTableMap) == 0 { - s.subTableMap = make(map[string]Subtable) - for _, sb := range s.Subtable { - s.subTableMap[sb.Name] = sb - } - } - // TODO put this in cache on first run - // Create oid tree - if s.SnmptranslateFile != "" && len(s.initNode.subnodes) == 0 { - s.nameToOid = make(map[string]string) - s.initNode = Node{ - id: "1", - name: "", - subnodes: make(map[string]Node), - } +// 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{} +} - data, err := ioutil.ReadFile(s.SnmptranslateFile) - if err != nil { - log.Printf("Reading SNMPtranslate file error: %s", err) - return err +// Errors is a list of errors accumulated during an interval. +type Errors []error + +func (errs Errors) Error() string { + s := "" + for _, err := range errs { + if s == "" { + s = err.Error() } else { - for _, line := range strings.Split(string(data), "\n") { - oids := strings.Fields(string(line)) - if len(oids) == 2 && oids[1] != "" { - oid_name := oids[0] - oid := oids[1] - fillnode(s.initNode, oid_name, strings.Split(string(oid), ".")) - s.nameToOid[oid_name] = oid - } - } + s = s + ". " + err.Error() } } - // 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 - // Get Easy GET oids - for _, oidstring := range host.GetOids { - oid := Data{} - if val, ok := s.nameToOid[oidstring]; ok { - // TODO should we add the 0 instance ? - oid.Name = oidstring - oid.Oid = val - oid.rawOid = "." + val + ".0" - } else { - oid.Name = oidstring - oid.Oid = oidstring - if string(oidstring[:1]) != "." { - oid.rawOid = "." + oidstring - } else { - oid.rawOid = oidstring - } - } - host.getOids = append(host.getOids, oid) - } - - for _, oid_name := range host.Collect { - // Get GET oids - for _, oid := range s.Get { - if oid.Name == oid_name { - if val, ok := s.nameToOid[oid.Oid]; ok { - // TODO should we add the 0 instance ? - if oid.Instance != "" { - oid.rawOid = "." + val + "." + oid.Instance - } else { - oid.rawOid = "." + val + ".0" - } - } else { - oid.rawOid = oid.Oid - } - host.getOids = append(host.getOids, oid) - } - } - // Get GETBULK oids - for _, oid := range s.Bulk { - if oid.Name == oid_name { - if val, ok := s.nameToOid[oid.Oid]; ok { - oid.rawOid = "." + val - } else { - oid.rawOid = oid.Oid - } - host.bulkOids = append(host.bulkOids, oid) - } - } - } - // Table - for _, hostTable := range host.Table { - for _, snmpTable := range s.Table { - if hostTable.Name == snmpTable.Name { - table := hostTable - table.oid = snmpTable.Oid - table.mappingTable = snmpTable.MappingTable - table.subTables = snmpTable.SubTables - host.tables = append(host.tables, table) - } - } - } - // Launch Mapping - // TODO put this in cache on first run - // TODO save mapping and computed oids - // to do it only the first time - // only if len(s.OidInstanceMapping) == 0 - if len(host.OidInstanceMapping) >= 0 { - if err := host.SNMPMap(acc, s.nameToOid, s.subTableMap); err != nil { - log.Printf("SNMP Mapping error for host '%s': %s", host.Address, err) - continue - } - } - // Launch Get requests - if err := host.SNMPGet(acc, s.initNode); err != nil { - log.Printf("SNMP Error for host '%s': %s", host.Address, err) - } - if err := host.SNMPBulk(acc, s.initNode); err != nil { - log.Printf("SNMP Error for host '%s': %s", host.Address, err) - } - } - return nil + return s } -func (h *Host) SNMPMap( - acc telegraf.Accumulator, - nameToOid map[string]string, - subTableMap map[string]Subtable, -) error { - if h.OidInstanceMapping == nil { - h.OidInstanceMapping = make(map[string]map[string]string) - } - // Get snmp client - snmpClient, err := h.GetSNMPClient() - if err != nil { - return err - } - // Deconnection - defer snmpClient.Conn.Close() - // Prepare OIDs - for _, table := range h.tables { - // We don't have mapping - if table.mappingTable == "" { - if len(table.subTables) == 0 { - // If We don't have mapping table - // neither subtables list - // This is just a bulk request - oid := Data{} - oid.Oid = table.oid - if val, ok := nameToOid[oid.Oid]; ok { - oid.rawOid = "." + val - } else { - oid.rawOid = oid.Oid - } - h.bulkOids = append(h.bulkOids, oid) - } else { - // If We don't have mapping table - // but we have subtables - // This is a bunch of bulk requests - // For each subtable ... - for _, sb := range table.subTables { - // ... we create a new Data (oid) object - oid := Data{} - // Looking for more information about this subtable - ssb, exists := subTableMap[sb] - if exists { - // We found a subtable section in config files - oid.Oid = ssb.Oid - oid.rawOid = ssb.Oid - oid.Unit = ssb.Unit - } else { - // We did NOT find a subtable section in config files - oid.Oid = sb - oid.rawOid = sb - } - // TODO check oid validity - - // Add the new oid to getOids list - h.bulkOids = append(h.bulkOids, oid) - } - } - } else { - // We have a mapping table - // We need to query this table - // To get mapping between instance id - // and instance name - oid_asked := table.mappingTable - oid_next := oid_asked - need_more_requests := true - // Set max repetition - maxRepetition := uint8(32) - // Launch requests - for need_more_requests { - // Launch request - result, err3 := snmpClient.GetBulk([]string{oid_next}, 0, maxRepetition) - if err3 != nil { - return err3 - } - - lastOid := "" - for _, variable := range result.Variables { - lastOid = variable.Name - if strings.HasPrefix(variable.Name, oid_asked) { - switch variable.Type { - // handle instance names - case gosnmp.OctetString: - // Check if instance is in includes instances - getInstances := true - if len(table.IncludeInstances) > 0 { - getInstances = false - for _, instance := range table.IncludeInstances { - if instance == string(variable.Value.([]byte)) { - getInstances = true - } - } - } - // Check if instance is in excludes instances - if len(table.ExcludeInstances) > 0 { - getInstances = true - for _, instance := range table.ExcludeInstances { - if instance == string(variable.Value.([]byte)) { - getInstances = false - } - } - } - // We don't want this instance - if !getInstances { - continue - } - - // remove oid table from the complete oid - // in order to get the current instance id - key := strings.Replace(variable.Name, oid_asked, "", 1) - - if len(table.subTables) == 0 { - // We have a mapping table - // but no subtables - // This is just a bulk request - - // Building mapping table - mapping := map[string]string{strings.Trim(key, "."): string(variable.Value.([]byte))} - _, exists := h.OidInstanceMapping[table.oid] - if exists { - h.OidInstanceMapping[table.oid][strings.Trim(key, ".")] = string(variable.Value.([]byte)) - } else { - h.OidInstanceMapping[table.oid] = mapping - } - - // Add table oid in bulk oid list - oid := Data{} - oid.Oid = table.oid - if val, ok := nameToOid[oid.Oid]; ok { - oid.rawOid = "." + val - } else { - oid.rawOid = oid.Oid - } - h.bulkOids = append(h.bulkOids, oid) - } else { - // We have a mapping table - // and some subtables - // This is a bunch of get requests - // This is the best case :) - - // For each subtable ... - for _, sb := range table.subTables { - // ... we create a new Data (oid) object - oid := Data{} - // Looking for more information about this subtable - ssb, exists := subTableMap[sb] - if exists { - // We found a subtable section in config files - oid.Oid = ssb.Oid + key - oid.rawOid = ssb.Oid + key - oid.Unit = ssb.Unit - oid.Instance = string(variable.Value.([]byte)) - } else { - // We did NOT find a subtable section in config files - oid.Oid = sb + key - oid.rawOid = sb + key - oid.Instance = string(variable.Value.([]byte)) - } - // TODO check oid validity - - // Add the new oid to getOids list - h.getOids = append(h.getOids, oid) - } - } - default: - } - } else { - break - } - } - // Determine if we need more requests - if strings.HasPrefix(lastOid, oid_asked) { - need_more_requests = true - oid_next = lastOid - } else { - need_more_requests = false - } - } - } - } - // Mapping finished - - // Create newoids based on mapping - - return nil +// NestedError wraps an error returned from deeper in the code. +type NestedError struct { + // Err is the error from where the NestedError was constructed. + Err error + // NestedError is the error that was passed back from the called function. + NestedErr error } -func (h *Host) SNMPGet(acc telegraf.Accumulator, initNode Node) error { - // Get snmp client - snmpClient, err := h.GetSNMPClient() - if err != nil { - return err - } - // Deconnection - defer snmpClient.Conn.Close() - // Prepare OIDs - oidsList := make(map[string]Data) - for _, oid := range h.getOids { - oidsList[oid.rawOid] = oid - } - oidsNameList := make([]string, 0, len(oidsList)) - for _, oid := range oidsList { - oidsNameList = append(oidsNameList, oid.rawOid) - } - - // 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, initNode) - if err != nil { - return err - } - } - return nil +// Error returns a concatenated string of all the nested errors. +func (ne NestedError) Error() string { + return ne.Err.Error() + ": " + ne.NestedErr.Error() } -func (h *Host) SNMPBulk(acc telegraf.Accumulator, initNode Node) error { - // Get snmp client - snmpClient, err := h.GetSNMPClient() - if err != nil { - return err +// Errorf is a convenience function for constructing a NestedError. +func Errorf(err error, msg string, format ...interface{}) error { + return NestedError{ + NestedErr: err, + Err: fmt.Errorf(msg, format...), } - // Deconnection - defer snmpClient.Conn.Close() - // Prepare OIDs - oidsList := make(map[string]Data) - for _, oid := range h.bulkOids { - oidsList[oid.rawOid] = oid - } - oidsNameList := make([]string, 0, len(oidsList)) - for _, oid := range oidsList { - oidsNameList = append(oidsNameList, oid.rawOid) - } - // 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, initNode) - 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 - } - } - } - 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 telegraf.Accumulator, - initNode Node, -) (string, error) { - var lastOid string - for _, variable := range result.Variables { - lastOid = variable.Name - nextresult: - // Get only oid wanted - for oid_key, oid := range oids { - // Skip oids already processed - for _, processedOid := range h.processedOids { - if variable.Name == processedOid { - break nextresult - } - } - // If variable.Name is the same as oid_key - // OR - // the result is SNMP table which "." comes right after oid_key. - // ex: oid_key: .1.3.6.1.2.1.2.2.1.16, variable.Name: .1.3.6.1.2.1.2.2.1.16.1 - if variable.Name == oid_key || 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, gosnmp.OctetString: - // 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 - // Get oidname and instance from translate file - oid_name, instance = findnodename(initNode, - strings.Split(string(variable.Name[1:]), ".")) - // Set instance tag - // From mapping table - mapping, inMappingNoSubTable := h.OidInstanceMapping[oid_key] - if inMappingNoSubTable { - // filter if the instance in not in - // OidInstanceMapping mapping map - if instance_name, exists := mapping[instance]; exists { - tags["instance"] = instance_name - } else { - continue - } - } else if oid.Instance != "" { - // From config files - tags["instance"] = oid.Instance - } else if instance != "" { - // Using last id of the current oid, ie: - // with .1.3.6.1.2.1.31.1.1.1.10.3 - // instance is 3 - tags["instance"] = instance - } - - // Set name - var field_name string - if oid_name != "" { - // Set fieldname as oid name from translate file - field_name = oid_name - } else { - // Set fieldname as oid name from inputs.snmp.get section - // Because the result oid is equal to inputs.snmp.get section - field_name = oid.Name - } - tags["snmp_host"], _, _ = net.SplitHostPort(h.Address) - fields := make(map[string]interface{}) - fields[string(field_name)] = variable.Value - - h.processedOids = append(h.processedOids, variable.Name) - 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() telegraf.Input { - return &Snmp{} + return &Snmp{ + Retries: 5, + MaxRepetitions: 50, + } }) } + +// SampleConfig returns the default configuration of the input. +func (s *Snmp) SampleConfig() string { + return sampleConfig +} + +// Description returns a one-sentence description on the input. +func (s *Snmp) Description() string { + return description +} + +// 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. +func (s *Snmp) Gather(acc telegraf.Accumulator) error { + var errs Errors + for _, agent := range s.Agents { + gs, err := s.getConnection(agent) + if err != nil { + errs = append(errs, Errorf(err, "agent %s", agent)) + continue + } + + // First is the top-level fields. We treat the fields as table prefixes with an empty index. + t := Table{ + Name: s.Name, + Fields: s.Fields, + } + topTags := map[string]string{} + if err := s.gatherTable(acc, gs, t, topTags, false); err != nil { + errs = append(errs, Errorf(err, "agent %s", agent)) + } + + // Now is the real tables. + for _, t := range s.Tables { + if err := s.gatherTable(acc, gs, t, topTags, true); err != nil { + errs = append(errs, Errorf(err, "agent %s", agent)) + } + } + } + + if errs == nil { + return nil + } + return errs +} + +func (s *Snmp) gatherTable(acc telegraf.Accumulator, gs snmpConnection, t Table, topTags map[string]string, walk bool) error { + rt, err := t.Build(gs, walk) + if err != nil { + return err + } + + for _, tr := range rt.Rows { + if !walk { + // top-level table. Add tags to topTags. + for k, v := range tr.Tags { + topTags[k] = v + } + } else { + // real table. Inherit any specified tags. + for _, k := range t.InheritTags { + if v, ok := topTags[k]; ok { + tr.Tags[k] = v + } + } + } + if _, ok := tr.Tags["agent_host"]; !ok { + tr.Tags["agent_host"] = gs.Host() + } + acc.AddFields(rt.Name, tr.Fields, tr.Tags, rt.Time) + } + + return nil +} + +// Build retrieves all the fields specified in the table and constructs the RTable. +func (t Table) Build(gs snmpConnection, walk bool) (*RTable, error) { + rows := map[string]RTableRow{} + + tagCount := 0 + for _, f := range t.Fields { + if f.IsTag { + tagCount++ + } + + if len(f.Oid) == 0 { + return nil, fmt.Errorf("cannot have empty OID") + } + 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 { + return nil, Errorf(err, "performing get") + } else if pkt != nil && len(pkt.Variables) > 0 && pkt.Variables[0].Type != gosnmp.NoSuchObject { + ent := pkt.Variables[0] + ifv[ent.Name[len(oid):]] = fieldConvert(f.Conversion, ent.Value) + } + } else { + err := gs.Walk(oid, func(ent gosnmp.SnmpPDU) error { + if len(ent.Name) <= len(oid) || ent.Name[:len(oid)+1] != oid+"." { + return NestedError{} // break the walk + } + ifv[ent.Name[len(oid):]] = fieldConvert(f.Conversion, ent.Value) + return nil + }) + if err != nil { + if _, ok := err.(NestedError); !ok { + return nil, Errorf(err, "performing bulk walk") + } + } + } + + for i, v := range ifv { + rtr, ok := rows[i] + if !ok { + rtr = RTableRow{} + rtr.Tags = map[string]string{} + rtr.Fields = map[string]interface{}{} + rows[i] = rtr + } + if f.IsTag { + if vs, ok := v.(string); ok { + rtr.Tags[f.Name] = vs + } else { + rtr.Tags[f.Name] = fmt.Sprintf("%v", v) + } + } else { + rtr.Fields[f.Name] = v + } + } + } + + rt := RTable{ + Name: t.Name, + Time: time.Now(), //TODO record time at start + Rows: make([]RTableRow, 0, len(rows)), + } + for _, r := range rows { + if len(r.Tags) < tagCount { + // don't add rows which are missing tags, as without tags you can't filter + continue + } + 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) +} + +// gosnmpWrapper wraps a *gosnmp.GoSNMP object so we can use it as a snmpConnection. +type gosnmpWrapper struct { + *gosnmp.GoSNMP +} + +// Host returns the value of GoSNMP.Target. +func (gsw gosnmpWrapper) Host() string { + return gsw.Target +} + +// Walk wraps GoSNMP.Walk() or GoSNMP.BulkWalk(), depending on whether the +// connection is using SNMPv1 or newer. +// Also, if any error is encountered, it will just once reconnect and try again. +func (gsw gosnmpWrapper) Walk(oid string, fn gosnmp.WalkFunc) error { + var err error + // On error, retry once. + // Unfortunately we can't distinguish between an error returned by gosnmp, and one returned by the walk function. + for i := 0; i < 2; i++ { + if gsw.Version == gosnmp.Version1 { + err = gsw.GoSNMP.Walk(oid, fn) + } else { + err = gsw.GoSNMP.BulkWalk(oid, fn) + } + if err == nil { + return nil + } + if err := gsw.GoSNMP.Connect(); err != nil { + return Errorf(err, "reconnecting") + } + } + return err +} + +// Get wraps GoSNMP.GET(). +// If any error is encountered, it will just once reconnect and try again. +func (gsw gosnmpWrapper) Get(oids []string) (*gosnmp.SnmpPacket, error) { + var err error + var pkt *gosnmp.SnmpPacket + for i := 0; i < 2; i++ { + pkt, err = gsw.GoSNMP.Get(oids) + if err == nil { + return pkt, nil + } + if err := gsw.GoSNMP.Connect(); err != nil { + return nil, Errorf(err, "reconnecting") + } + } + return nil, err +} + +// getConnection creates a snmpConnection (*gosnmp.GoSNMP) object and caches the +// result using `agent` as the cache key. +func (s *Snmp) getConnection(agent string) (snmpConnection, error) { + if s.connectionCache == nil { + s.connectionCache = map[string]snmpConnection{} + } + if gs, ok := s.connectionCache[agent]; ok { + return gs, nil + } + + gs := gosnmpWrapper{&gosnmp.GoSNMP{}} + + host, portStr, err := net.SplitHostPort(agent) + if err != nil { + if err, ok := err.(*net.AddrError); !ok || err.Err != "missing port in address" { + return nil, Errorf(err, "parsing host") + } + host = agent + portStr = "161" + } + gs.Target = host + + port, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return nil, Errorf(err, "parsing port") + } + gs.Port = uint16(port) + + if s.Timeout != "" { + if gs.Timeout, err = time.ParseDuration(s.Timeout); err != nil { + return nil, Errorf(err, "parsing timeout") + } + } else { + gs.Timeout = time.Second * 1 + } + + gs.Retries = s.Retries + + switch s.Version { + case 3: + gs.Version = gosnmp.Version3 + case 2, 0: + gs.Version = gosnmp.Version2c + case 1: + gs.Version = gosnmp.Version1 + default: + return nil, fmt.Errorf("invalid version") + } + + if s.Version < 3 { + if s.Community == "" { + gs.Community = "public" + } else { + gs.Community = s.Community + } + } + + gs.MaxRepetitions = int(s.MaxRepetitions) + + if s.Version == 3 { + gs.ContextName = s.ContextName + + sp := &gosnmp.UsmSecurityParameters{} + gs.SecurityParameters = sp + gs.SecurityModel = gosnmp.UserSecurityModel + + switch strings.ToLower(s.SecLevel) { + case "noauthnopriv", "": + gs.MsgFlags = gosnmp.NoAuthNoPriv + case "authnopriv": + gs.MsgFlags = gosnmp.AuthNoPriv + case "authpriv": + gs.MsgFlags = gosnmp.AuthPriv + default: + return nil, fmt.Errorf("invalid secLevel") + } + + sp.UserName = s.SecName + + switch strings.ToLower(s.AuthProtocol) { + case "md5": + sp.AuthenticationProtocol = gosnmp.MD5 + case "sha": + sp.AuthenticationProtocol = gosnmp.SHA + case "": + sp.AuthenticationProtocol = gosnmp.NoAuth + default: + return nil, fmt.Errorf("invalid authProtocol") + } + + sp.AuthenticationPassphrase = s.AuthPassword + + switch strings.ToLower(s.PrivProtocol) { + case "des": + sp.PrivacyProtocol = gosnmp.DES + case "aes": + sp.PrivacyProtocol = gosnmp.AES + case "": + sp.PrivacyProtocol = gosnmp.NoPriv + default: + return nil, fmt.Errorf("invalid privProtocol") + } + + sp.PrivacyPassphrase = s.PrivPassword + + sp.AuthoritativeEngineID = s.EngineID + + sp.AuthoritativeEngineBoots = s.EngineBoots + + sp.AuthoritativeEngineTime = s.EngineTime + } + + if err := gs.Connect(); err != nil { + return nil, Errorf(err, "setting up connection") + } + + s.connectionCache[agent] = gs + return gs, nil +} + +// fieldConvert converts from any type according to the conv specification +// "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. +// "" will convert a byte slice into a string. +// Any other conv will return the input value unchanged. +func fieldConvert(conv string, v interface{}) interface{} { + if conv == "" { + if bs, ok := v.([]byte); ok { + return string(bs) + } + return v + } + + var d int + if _, err := fmt.Sscanf(conv, "float(%d)", &d); err == nil || conv == "float" { + switch vt := v.(type) { + case float32: + v = float64(vt) / math.Pow10(d) + case float64: + v = float64(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) + } + } + if conv == "int" { + 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 = int64(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.Atoi(string(vt)) + case string: + v, _ = strconv.Atoi(vt) + } + } + + return v +} diff --git a/plugins/inputs/snmp/snmp_test.go b/plugins/inputs/snmp/snmp_test.go index 2faaa1408..3a4f0b29a 100644 --- a/plugins/inputs/snmp/snmp_test.go +++ b/plugins/inputs/snmp/snmp_test.go @@ -1,482 +1,494 @@ package snmp import ( + "fmt" + "net" + "sync" "testing" + "time" "github.com/influxdata/telegraf/testutil" - + "github.com/influxdata/toml" + "github.com/soniah/gosnmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestSNMPErrorGet1(t *testing.T) { - get1 := Data{ - Name: "oid1", - Unit: "octets", - Oid: ".1.3.6.1.2.1.2.2.1.16.1", +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, + }) } - h := Host{ - Collect: []string{"oid1"}, + 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 +} + +var tsc = &testSNMPConnection{ + host: "tsc", + values: map[string]interface{}{ + ".1.2.3.0.0.1.0": "foo", + ".1.2.3.0.0.1.1": []byte("bar"), + ".1.2.3.0.0.102": "bad", + ".1.2.3.0.0.2.0": 1, + ".1.2.3.0.0.2.1": 2, + ".1.2.3.0.0.3.0": "0.123", + ".1.2.3.0.0.3.1": "0.456", + ".1.2.3.0.0.3.2": "9.999", + ".1.2.3.0.0.4.0": 123456, + ".1.2.3.0.1": "baz", + ".1.2.3.0.2": 234, + ".1.2.3.0.3": []byte("byte slice"), + }, +} + +func TestSampleConfig(t *testing.T) { + conf := struct { + Inputs struct { + Snmp []*Snmp + } + }{} + err := toml.Unmarshal([]byte("[[inputs.snmp]]\n"+(*Snmp)(nil).SampleConfig()), &conf) + assert.NoError(t, err) + s := Snmp{ - SnmptranslateFile: "bad_oid.txt", - Host: []Host{h}, - Get: []Data{get1}, + Agents: []string{"127.0.0.1:161"}, + Version: 2, + Community: "public", + MaxRepetitions: 50, + + Name: "system", + Fields: []Field{ + {Name: "hostname", Oid: ".1.2.3.0.1.1"}, + {Name: "uptime", Oid: ".1.2.3.0.1.200"}, + {Name: "load", Oid: ".1.2.3.0.1.201"}, + }, + Tables: []Table{ + { + Name: "remote_servers", + InheritTags: []string{"hostname"}, + Fields: []Field{ + {Name: "server", Oid: ".1.2.3.0.0.0", IsTag: true}, + {Name: "connections", Oid: ".1.2.3.0.0.1"}, + {Name: "latency", Oid: ".1.2.3.0.0.2"}, + }, + }, + }, + } + assert.Equal(t, s, *conf.Inputs.Snmp[0]) +} + +func TestGetSNMPConnection_v2(t *testing.T) { + s := &Snmp{ + Timeout: "3s", + Retries: 4, + Version: 2, + Community: "foo", } - var acc testutil.Accumulator - err := s.Gather(&acc) + gsc, err := s.getConnection("1.2.3.4:567") + require.NoError(t, err) + gs := gsc.(gosnmpWrapper) + assert.Equal(t, "1.2.3.4", gs.Target) + assert.EqualValues(t, 567, gs.Port) + assert.Equal(t, gosnmp.Version2c, gs.Version) + assert.Equal(t, "foo", gs.Community) + + gsc, err = s.getConnection("1.2.3.4") + require.NoError(t, err) + gs = gsc.(gosnmpWrapper) + assert.Equal(t, "1.2.3.4", gs.Target) + assert.EqualValues(t, 161, gs.Port) +} + +func TestGetSNMPConnection_v3(t *testing.T) { + s := &Snmp{ + Version: 3, + MaxRepetitions: 20, + ContextName: "mycontext", + SecLevel: "authPriv", + SecName: "myuser", + AuthProtocol: "md5", + AuthPassword: "password123", + PrivProtocol: "des", + PrivPassword: "321drowssap", + EngineID: "myengineid", + EngineBoots: 1, + EngineTime: 2, + } + + gsc, err := s.getConnection("1.2.3.4") + require.NoError(t, err) + gs := gsc.(gosnmpWrapper) + assert.Equal(t, gs.Version, gosnmp.Version3) + sp := gs.SecurityParameters.(*gosnmp.UsmSecurityParameters) + assert.Equal(t, "1.2.3.4", gsc.Host()) + assert.Equal(t, 20, gs.MaxRepetitions) + assert.Equal(t, "mycontext", gs.ContextName) + assert.Equal(t, gosnmp.AuthPriv, gs.MsgFlags&gosnmp.AuthPriv) + assert.Equal(t, "myuser", sp.UserName) + assert.Equal(t, gosnmp.MD5, sp.AuthenticationProtocol) + assert.Equal(t, "password123", sp.AuthenticationPassphrase) + assert.Equal(t, gosnmp.DES, sp.PrivacyProtocol) + assert.Equal(t, "321drowssap", sp.PrivacyPassphrase) + assert.Equal(t, "myengineid", sp.AuthoritativeEngineID) + assert.EqualValues(t, 1, sp.AuthoritativeEngineBoots) + assert.EqualValues(t, 2, sp.AuthoritativeEngineTime) +} + +func TestGetSNMPConnection_caching(t *testing.T) { + s := &Snmp{} + gs1, err := s.getConnection("1.2.3.4") + require.NoError(t, err) + gs2, err := s.getConnection("1.2.3.4") + require.NoError(t, err) + gs3, err := s.getConnection("1.2.3.5") + require.NoError(t, err) + assert.True(t, gs1 == gs2) + assert.False(t, gs2 == gs3) +} + +func TestGosnmpWrapper_walk_retry(t *testing.T) { + srvr, err := net.ListenUDP("udp4", &net.UDPAddr{}) + defer srvr.Close() + require.NoError(t, err) + reqCount := 0 + // Set up a WaitGroup to wait for the server goroutine to exit and protect + // reqCount. + // Even though simultaneous access is impossible because the server will be + // blocked on ReadFrom, without this the race detector gets unhappy. + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + buf := make([]byte, 256) + for { + _, addr, err := srvr.ReadFrom(buf) + if err != nil { + return + } + reqCount++ + + srvr.WriteTo([]byte{'X'}, addr) // will cause decoding error + } + }() + + gs := &gosnmp.GoSNMP{ + Target: srvr.LocalAddr().(*net.UDPAddr).IP.String(), + Port: uint16(srvr.LocalAddr().(*net.UDPAddr).Port), + Version: gosnmp.Version2c, + Community: "public", + Timeout: time.Millisecond * 10, + Retries: 1, + } + err = gs.Connect() + require.NoError(t, err) + conn := gs.Conn + + gsw := gosnmpWrapper{gs} + err = gsw.Walk(".1.2.3", func(_ gosnmp.SnmpPDU) error { return nil }) + srvr.Close() + wg.Wait() + assert.Error(t, err) + assert.False(t, gs.Conn == conn) + assert.Equal(t, (gs.Retries+1)*2, reqCount) +} + +func TestGosnmpWrapper_get_retry(t *testing.T) { + srvr, err := net.ListenUDP("udp4", &net.UDPAddr{}) + defer srvr.Close() + require.NoError(t, err) + reqCount := 0 + // Set up a WaitGroup to wait for the server goroutine to exit and protect + // reqCount. + // Even though simultaneous access is impossible because the server will be + // blocked on ReadFrom, without this the race detector gets unhappy. + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + buf := make([]byte, 256) + for { + _, addr, err := srvr.ReadFrom(buf) + if err != nil { + return + } + reqCount++ + + srvr.WriteTo([]byte{'X'}, addr) // will cause decoding error + } + }() + + gs := &gosnmp.GoSNMP{ + Target: srvr.LocalAddr().(*net.UDPAddr).IP.String(), + Port: uint16(srvr.LocalAddr().(*net.UDPAddr).Port), + Version: gosnmp.Version2c, + Community: "public", + Timeout: time.Millisecond * 10, + Retries: 1, + } + err = gs.Connect() + require.NoError(t, err) + conn := gs.Conn + + gsw := gosnmpWrapper{gs} + _, err = gsw.Get([]string{".1.2.3"}) + srvr.Close() + wg.Wait() + assert.Error(t, err) + assert.False(t, gs.Conn == conn) + assert.Equal(t, (gs.Retries+1)*2, reqCount) +} + +func TestTableBuild_walk(t *testing.T) { + tbl := Table{ + Name: "mytable", + Fields: []Field{ + { + Name: "myfield1", + Oid: ".1.2.3.0.0.1", + IsTag: true, + }, + { + Name: "myfield2", + Oid: ".1.2.3.0.0.2", + }, + { + Name: "myfield3", + Oid: ".1.2.3.0.0.3", + Conversion: "float", + }, + }, + } + + tb, err := tbl.Build(tsc, true) + require.NoError(t, err) + + assert.Equal(t, tb.Name, "mytable") + rtr1 := RTableRow{ + Tags: map[string]string{"myfield1": "foo"}, + Fields: map[string]interface{}{"myfield2": 1, "myfield3": float64(0.123)}, + } + rtr2 := RTableRow{ + Tags: map[string]string{"myfield1": "bar"}, + Fields: map[string]interface{}{"myfield2": 2, "myfield3": float64(0.456)}, + } + assert.Len(t, tb.Rows, 2) + assert.Contains(t, tb.Rows, rtr1) + assert.Contains(t, tb.Rows, rtr2) +} + +func TestTableBuild_noWalk(t *testing.T) { + tbl := Table{ + Name: "mytable", + Fields: []Field{ + { + Name: "myfield1", + Oid: ".1.2.3.0.1", + IsTag: true, + }, + { + Name: "myfield2", + Oid: ".1.2.3.0.2", + }, + { + Name: "myfield3", + Oid: ".1.2.3.0.2", + IsTag: true, + }, + }, + } + + 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}, + } + assert.Len(t, tb.Rows, 1) + assert.Contains(t, tb.Rows, rtr) +} + +func TestGather(t *testing.T) { + s := &Snmp{ + Agents: []string{"TestGather"}, + Name: "mytable", + Fields: []Field{ + { + Name: "myfield1", + Oid: ".1.2.3.0.1", + IsTag: true, + }, + { + Name: "myfield2", + Oid: ".1.2.3.0.2", + }, + { + Name: "myfield3", + Oid: "1.2.3.0.1", + }, + }, + Tables: []Table{ + { + Name: "myOtherTable", + InheritTags: []string{"myfield1"}, + Fields: []Field{ + { + Name: "myOtherField", + Oid: ".1.2.3.0.0.4", + }, + }, + }, + }, + + connectionCache: map[string]snmpConnection{ + "TestGather": tsc, + }, + } + + acc := &testutil.Accumulator{} + + tstart := time.Now() + s.Gather(acc) + tstop := time.Now() + + require.Len(t, acc.Metrics, 2) + + m := acc.Metrics[0] + assert.Equal(t, "mytable", m.Measurement) + assert.Equal(t, "tsc", m.Tags["agent_host"]) + assert.Equal(t, "baz", m.Tags["myfield1"]) + assert.Len(t, m.Fields, 2) + assert.Equal(t, 234, m.Fields["myfield2"]) + assert.Equal(t, "baz", m.Fields["myfield3"]) + assert.True(t, tstart.Before(m.Time)) + assert.True(t, tstop.After(m.Time)) + + m2 := acc.Metrics[1] + assert.Equal(t, "myOtherTable", m2.Measurement) + assert.Equal(t, "tsc", m2.Tags["agent_host"]) + assert.Equal(t, "baz", m2.Tags["myfield1"]) + assert.Len(t, m2.Fields, 1) + assert.Equal(t, 123456, m2.Fields["myOtherField"]) +} + +func TestGather_host(t *testing.T) { + s := &Snmp{ + Agents: []string{"TestGather"}, + Name: "mytable", + Fields: []Field{ + { + Name: "host", + Oid: ".1.2.3.0.1", + IsTag: true, + }, + { + Name: "myfield2", + Oid: ".1.2.3.0.2", + }, + }, + + connectionCache: map[string]snmpConnection{ + "TestGather": tsc, + }, + } + + acc := &testutil.Accumulator{} + + s.Gather(acc) + + require.Len(t, acc.Metrics, 1) + m := acc.Metrics[0] + assert.Equal(t, "baz", m.Tags["host"]) +} + +func TestFieldConvert(t *testing.T) { + testTable := []struct { + input interface{} + conv string + expected interface{} + }{ + {[]byte("foo"), "", string("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)}, + {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)}, + {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)}, + } + + for _, tc := range testTable { + act := fieldConvert(tc.conv, tc.input) + assert.EqualValues(t, tc.expected, act, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected) + } +} + +func TestError(t *testing.T) { + e := fmt.Errorf("nested error") + err := Errorf(e, "top error %d", 123) require.Error(t, err) -} - -func TestSNMPErrorGet2(t *testing.T) { - get1 := Data{ - Name: "oid1", - Unit: "octets", - 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.NoError(t, err) - assert.Equal(t, 0, len(acc.Metrics)) -} - -func TestSNMPErrorBulk(t *testing.T) { - bulk1 := Data{ - Name: "oid1", - Unit: "octets", - Oid: ".1.3.6.1.2.1.2.2.1.16", - } - h := Host{ - Address: testutil.GetLocalHost(), - Collect: []string{"oid1"}, - } - s := Snmp{ - Host: []Host{h}, - Bulk: []Data{bulk1}, - } - - var acc testutil.Accumulator - err := s.Gather(&acc) - require.NoError(t, err) - assert.Equal(t, 0, len(acc.Metrics)) -} - -func TestSNMPGet1(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - get1 := Data{ - Name: "oid1", - Unit: "octets", - Oid: ".1.3.6.1.2.1.2.2.1.16.1", - } - h := Host{ - Address: testutil.GetLocalHost() + ":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) - - acc.AssertContainsTaggedFields(t, - "oid1", - map[string]interface{}{ - "oid1": uint(543846), - }, - map[string]string{ - "unit": "octets", - "snmp_host": testutil.GetLocalHost(), - }, - ) -} - -func TestSNMPGet2(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - get1 := Data{ - Name: "oid1", - Oid: "ifNumber", - } - h := Host{ - Address: testutil.GetLocalHost() + ":31161", - Community: "telegraf", - Version: 2, - Timeout: 2.0, - Retries: 2, - Collect: []string{"oid1"}, - } - s := Snmp{ - SnmptranslateFile: "./testdata/oids.txt", - Host: []Host{h}, - Get: []Data{get1}, - } - - var acc testutil.Accumulator - err := s.Gather(&acc) - require.NoError(t, err) - - acc.AssertContainsTaggedFields(t, - "ifNumber", - map[string]interface{}{ - "ifNumber": int(4), - }, - map[string]string{ - "instance": "0", - "snmp_host": testutil.GetLocalHost(), - }, - ) -} - -func TestSNMPGet3(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - get1 := Data{ - Name: "oid1", - Unit: "octets", - Oid: "ifSpeed", - Instance: "1", - } - h := Host{ - Address: testutil.GetLocalHost() + ":31161", - Community: "telegraf", - Version: 2, - Timeout: 2.0, - Retries: 2, - Collect: []string{"oid1"}, - } - s := Snmp{ - SnmptranslateFile: "./testdata/oids.txt", - Host: []Host{h}, - Get: []Data{get1}, - } - - var acc testutil.Accumulator - err := s.Gather(&acc) - require.NoError(t, err) - - acc.AssertContainsTaggedFields(t, - "ifSpeed", - map[string]interface{}{ - "ifSpeed": uint(10000000), - }, - map[string]string{ - "unit": "octets", - "instance": "1", - "snmp_host": testutil.GetLocalHost(), - }, - ) -} - -func TestSNMPEasyGet4(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - get1 := Data{ - Name: "oid1", - Unit: "octets", - Oid: "ifSpeed", - Instance: "1", - } - h := Host{ - Address: testutil.GetLocalHost() + ":31161", - Community: "telegraf", - Version: 2, - Timeout: 2.0, - Retries: 2, - Collect: []string{"oid1"}, - GetOids: []string{"ifNumber"}, - } - s := Snmp{ - SnmptranslateFile: "./testdata/oids.txt", - Host: []Host{h}, - Get: []Data{get1}, - } - - var acc testutil.Accumulator - err := s.Gather(&acc) - require.NoError(t, err) - - acc.AssertContainsTaggedFields(t, - "ifSpeed", - map[string]interface{}{ - "ifSpeed": uint(10000000), - }, - map[string]string{ - "unit": "octets", - "instance": "1", - "snmp_host": testutil.GetLocalHost(), - }, - ) - - acc.AssertContainsTaggedFields(t, - "ifNumber", - map[string]interface{}{ - "ifNumber": int(4), - }, - map[string]string{ - "instance": "0", - "snmp_host": testutil.GetLocalHost(), - }, - ) -} - -func TestSNMPEasyGet5(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - get1 := Data{ - Name: "oid1", - Unit: "octets", - Oid: "ifSpeed", - Instance: "1", - } - h := Host{ - Address: testutil.GetLocalHost() + ":31161", - Community: "telegraf", - Version: 2, - Timeout: 2.0, - Retries: 2, - Collect: []string{"oid1"}, - GetOids: []string{".1.3.6.1.2.1.2.1.0"}, - } - s := Snmp{ - SnmptranslateFile: "./testdata/oids.txt", - Host: []Host{h}, - Get: []Data{get1}, - } - - var acc testutil.Accumulator - err := s.Gather(&acc) - require.NoError(t, err) - - acc.AssertContainsTaggedFields(t, - "ifSpeed", - map[string]interface{}{ - "ifSpeed": uint(10000000), - }, - map[string]string{ - "unit": "octets", - "instance": "1", - "snmp_host": testutil.GetLocalHost(), - }, - ) - - acc.AssertContainsTaggedFields(t, - "ifNumber", - map[string]interface{}{ - "ifNumber": int(4), - }, - map[string]string{ - "instance": "0", - "snmp_host": testutil.GetLocalHost(), - }, - ) -} - -func TestSNMPEasyGet6(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - h := Host{ - Address: testutil.GetLocalHost() + ":31161", - Community: "telegraf", - Version: 2, - Timeout: 2.0, - Retries: 2, - GetOids: []string{"1.3.6.1.2.1.2.1.0"}, - } - s := Snmp{ - SnmptranslateFile: "./testdata/oids.txt", - Host: []Host{h}, - } - - var acc testutil.Accumulator - err := s.Gather(&acc) - require.NoError(t, err) - - acc.AssertContainsTaggedFields(t, - "ifNumber", - map[string]interface{}{ - "ifNumber": int(4), - }, - map[string]string{ - "instance": "0", - "snmp_host": testutil.GetLocalHost(), - }, - ) -} - -func TestSNMPBulk1(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - bulk1 := Data{ - Name: "oid1", - Unit: "octets", - Oid: ".1.3.6.1.2.1.2.2.1.16", - MaxRepetition: 2, - } - h := Host{ - Address: testutil.GetLocalHost() + ":31161", - Community: "telegraf", - Version: 2, - Timeout: 2.0, - Retries: 2, - Collect: []string{"oid1"}, - } - s := Snmp{ - SnmptranslateFile: "./testdata/oids.txt", - Host: []Host{h}, - Bulk: []Data{bulk1}, - } - - var acc testutil.Accumulator - err := s.Gather(&acc) - require.NoError(t, err) - - acc.AssertContainsTaggedFields(t, - "ifOutOctets", - map[string]interface{}{ - "ifOutOctets": uint(543846), - }, - map[string]string{ - "unit": "octets", - "instance": "1", - "snmp_host": testutil.GetLocalHost(), - }, - ) - - acc.AssertContainsTaggedFields(t, - "ifOutOctets", - map[string]interface{}{ - "ifOutOctets": uint(26475179), - }, - map[string]string{ - "unit": "octets", - "instance": "2", - "snmp_host": testutil.GetLocalHost(), - }, - ) - - acc.AssertContainsTaggedFields(t, - "ifOutOctets", - map[string]interface{}{ - "ifOutOctets": uint(108963968), - }, - map[string]string{ - "unit": "octets", - "instance": "3", - "snmp_host": testutil.GetLocalHost(), - }, - ) - - acc.AssertContainsTaggedFields(t, - "ifOutOctets", - map[string]interface{}{ - "ifOutOctets": uint(12991453), - }, - map[string]string{ - "unit": "octets", - "instance": "36", - "snmp_host": testutil.GetLocalHost(), - }, - ) -} - -// TODO find why, if this test is active -// Circle CI stops with the following error... -// bash scripts/circle-test.sh died unexpectedly -// Maybe the test is too long ?? -func dTestSNMPBulk2(t *testing.T) { - bulk1 := Data{ - Name: "oid1", - Unit: "octets", - Oid: "ifOutOctets", - MaxRepetition: 2, - } - h := Host{ - Address: testutil.GetLocalHost() + ":31161", - Community: "telegraf", - Version: 2, - Timeout: 2.0, - Retries: 2, - Collect: []string{"oid1"}, - } - s := Snmp{ - SnmptranslateFile: "./testdata/oids.txt", - Host: []Host{h}, - Bulk: []Data{bulk1}, - } - - var acc testutil.Accumulator - err := s.Gather(&acc) - require.NoError(t, err) - - acc.AssertContainsTaggedFields(t, - "ifOutOctets", - map[string]interface{}{ - "ifOutOctets": uint(543846), - }, - map[string]string{ - "unit": "octets", - "instance": "1", - "snmp_host": testutil.GetLocalHost(), - }, - ) - - acc.AssertContainsTaggedFields(t, - "ifOutOctets", - map[string]interface{}{ - "ifOutOctets": uint(26475179), - }, - map[string]string{ - "unit": "octets", - "instance": "2", - "snmp_host": testutil.GetLocalHost(), - }, - ) - - acc.AssertContainsTaggedFields(t, - "ifOutOctets", - map[string]interface{}{ - "ifOutOctets": uint(108963968), - }, - map[string]string{ - "unit": "octets", - "instance": "3", - "snmp_host": testutil.GetLocalHost(), - }, - ) - - acc.AssertContainsTaggedFields(t, - "ifOutOctets", - map[string]interface{}{ - "ifOutOctets": uint(12991453), - }, - map[string]string{ - "unit": "octets", - "instance": "36", - "snmp_host": testutil.GetLocalHost(), - }, - ) + + ne, ok := err.(NestedError) + require.True(t, ok) + assert.Equal(t, e, ne.NestedErr) + + assert.Contains(t, err.Error(), "top error 123") + assert.Contains(t, err.Error(), "nested error") } diff --git a/plugins/inputs/snmp2/README.md b/plugins/inputs/snmp2/README.md deleted file mode 100644 index 901af9726..000000000 --- a/plugins/inputs/snmp2/README.md +++ /dev/null @@ -1,130 +0,0 @@ -# SNMP2 Plugin - -The SNMP2 input plugin gathers metrics from SNMP agents. -It is an alternative to the SNMP plugin with significantly different configuration & behavior. - -## Configuration: - -### Example: - -SNMP data: -``` -.1.2.3.0.0.1.0 octet_str "foo" -.1.2.3.0.0.1.1 octet_str "bar" -.1.2.3.0.0.102 octet_str "bad" -.1.2.3.0.0.2.0 integer 1 -.1.2.3.0.0.2.1 integer 2 -.1.2.3.0.0.3.0 octet_str "0.123" -.1.2.3.0.0.3.1 octet_str "0.456" -.1.2.3.0.0.3.2 octet_str "9.999" -.1.2.3.0.1 octet_str "baz" -.1.2.3.0.2 uinteger 54321 -.1.2.3.0.3 uinteger 234 -``` - -Telegraf config: -```toml -[[inputs.snmp2]] - agents = [ "127.0.0.1:161" ] - version = 2 - community = "public" - - name = "system" - [[inputs.snmp2.field]] - name = "hostname" - oid = ".1.2.3.0.1" - is_tag = true - [[inputs.snmp2.field]] - name = "uptime" - oid = ".1.2.3.0.2" - [[inputs.snmp2.field]] - name = "loadavg" - oid = ".1.2.3.0.3" - conversion = "float(2)" - - [[inputs.snmp2.table]] - name = "remote_servers" - inherit_tags = [ "hostname" ] - [[inputs.snmp2.table.field]] - name = "server" - oid = ".1.2.3.0.0.1" - is_tag = true - [[inputs.snmp2.table.field]] - name = "connections" - oid = ".1.2.3.0.0.2" - [[inputs.snmp2.table.field]] - name = "latency" - oid = ".1.2.3.0.0.3" - conversion = "float" -``` - -Resulting output: -``` -* Plugin: snmp2, Collection 1 -> system,agent_host=127.0.0.1,host=mylocalhost,hostname=baz loadavg=2.34,uptime=54321i 1468953135000000000 -> remote_servers,agent_host=127.0.0.1,host=mylocalhost,hostname=baz,server=foo connections=1i,latency=0.123 1468953135000000000 -> remote_servers,agent_host=127.0.0.1,host=mylocalhost,hostname=baz,server=bar connections=2i,latency=0.456 1468953135000000000 -``` - -### Config parameters - -* `agents`: Default: `[]` -List of SNMP agents to connect to in the form of `IP[:PORT]`. If `:PORT` is unspecified, it defaults to `161`. - -* `version`: Default: `2` -SNMP protocol version to use. - -* `community`: Default: `"public"` -SNMP community to use. - -* `max_repetitions`: Default: `50` -Maximum number of iterations for repeating variables. - -* `sec_name`: -Security name for authenticated SNMPv3 requests. - -* `auth_protocol`: Values: `"MD5"`,`"SHA"`,`""`. Default: `""` -Authentication protocol for authenticated SNMPv3 requests. - -* `auth_password`: -Authentication password for authenticated SNMPv3 requests. - -* `sec_level`: Values: `"noAuthNoPriv"`,`"authNoPriv"`,`"authPriv"`. Default: `"noAuthNoPriv"` -Security level used for SNMPv3 messages. - -* `context_name`: -Context name used for SNMPv3 requests. - -* `priv_protocol`: Values: `"DES"`,`"AES"`,`""`. Default: `""` -Privacy protocol used for encrypted SNMPv3 messages. - -* `priv_password`: -Privacy password used for encrypted SNMPv3 messages. - - -* `name`: -Output measurement name. - -#### Field parameters: -* `name`: -Output field/tag name. - -* `oid`: -OID to get. Must be in dotted notation, not textual. - -* `is_tag`: -Output this field as a tag. - -* `conversion`: Values: `"float(X)"`,`"float"`,`"int"`,`""`. Default: `""` -Converts the value according to the given specification. - - - `float(X)`: Converts the input value into a float and divides by the Xth power of 10. Efficively just moves the decimal left X places. For example a value of `123` with `float(2)` will result in `1.23`. - - `float`: Converts the value into a float with no adjustment. Same as `float(0)`. - - `int`: Convertes the value into an integer. - -#### Table parameters: -* `name`: -Output measurement name. - -* `inherit_tags`: -Which tags to inherit from the top-level config and to use in the output of this table's measurement. diff --git a/plugins/inputs/snmp2/snmp.go b/plugins/inputs/snmp2/snmp.go deleted file mode 100644 index 366c2d451..000000000 --- a/plugins/inputs/snmp2/snmp.go +++ /dev/null @@ -1,624 +0,0 @@ -package snmp2 - -import ( - "fmt" - "math" - "net" - "strconv" - "strings" - "time" - - "github.com/influxdata/telegraf" - "github.com/influxdata/telegraf/plugins/inputs" - - "github.com/soniah/gosnmp" -) - -const description = `Retrieves SNMP values from remote agents` -const sampleConfig = ` - agents = [ "127.0.0.1:161" ] - version = 2 - - # SNMPv1 & SNMPv2 parameters - community = "public" - - # SNMPv2 & SNMPv3 parameters - max_repetitions = 50 - - # SNMPv3 parameters - #sec_name = "myuser" - #auth_protocol = "md5" # Values: "MD5", "SHA", "" - #auth_password = "password123" - #sec_level = "authNoPriv" # Values: "noAuthNoPriv", "authNoPriv", "authPriv" - #context_name = "" - #priv_protocol = "" # Values: "DES", "AES", "" - #priv_password = "" - - # measurement name - name = "system" - [[inputs.snmp2.field]] - name = "hostname" - oid = ".1.2.3.0.1.1" - [[inputs.snmp2.field]] - name = "uptime" - oid = ".1.2.3.0.1.200" - [[inputs.snmp2.field]] - name = "load" - oid = ".1.2.3.0.1.201" - - [[inputs.snmp2.table]] - # measurement name - name = "remote_servers" - inherit_tags = [ "hostname" ] - [[inputs.snmp2.table.field]] - name = "server" - oid = ".1.2.3.0.0.0" - is_tag = true - [[inputs.snmp2.table.field]] - name = "connections" - oid = ".1.2.3.0.0.1" - [[inputs.snmp2.table.field]] - name = "latency" - oid = ".1.2.3.0.0.2" -` - -// Snmp2 holds the configuration for the plugin. -type Snmp2 struct { - // The SNMP agent to query. Format is ADDR[:PORT] (e.g. 1.2.3.4:161). - Agents []string - // Timeout to wait for a response. Value is anything accepted by time.ParseDuration(). - Timeout string - Retries int - // Values: 1, 2, 3 - Version uint8 - - // Parameters for Version 1 & 2 - Community string - - // Parameters for Version 2 & 3 - MaxRepetitions uint - - // Parameters for Version 3 - ContextName string - // Values: "noAuthNoPriv", "authNoPriv", "authPriv" - SecLevel string - SecName string - // Values: "MD5", "SHA", "". Default: "" - AuthProtocol string - AuthPassword string - // Values: "DES", "AES", "". Default: "" - PrivProtocol string - PrivPassword string - EngineID string - EngineBoots uint32 - EngineTime uint32 - - Tables []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 - Fields []Field `toml:"field"` - - connectionCache map[string]snmpConnection -} - -// 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 - - // Fields is the tags and values to look up. - Fields []Field `toml:"field"` -} - -// 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 - // 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 conver the value into an integer. - Conversion string -} - -// 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{} -} - -// Errors is a list of errors accumulated during an interval. -type Errors []error - -func (errs Errors) Error() string { - s := "" - for _, err := range errs { - if s == "" { - s = err.Error() - } else { - s = s + ". " + err.Error() - } - } - return s -} - -// NestedError wraps an error returned from deeper in the code. -type NestedError struct { - // Err is the error from where the NestedError was constructed. - Err error - // NestedError is the error that was passed back from the called function. - NestedErr error -} - -// Error returns a concatenated string of all the nested errors. -func (ne NestedError) Error() string { - return ne.Err.Error() + ": " + ne.NestedErr.Error() -} - -// Errorf is a convenience function for constructing a NestedError. -func Errorf(err error, msg string, format ...interface{}) error { - return NestedError{ - NestedErr: err, - Err: fmt.Errorf(msg, format...), - } -} - -func init() { - inputs.Add("snmp2", func() telegraf.Input { - return &Snmp2{ - Retries: 5, - MaxRepetitions: 50, - } - }) -} - -// SampleConfig returns the default configuration of the input. -func (s *Snmp2) SampleConfig() string { - return sampleConfig -} - -// Description returns a one-sentence description on the input. -func (s *Snmp2) Description() string { - return description -} - -// 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. -func (s *Snmp2) Gather(acc telegraf.Accumulator) error { - var errs Errors - for _, agent := range s.Agents { - gs, err := s.getConnection(agent) - if err != nil { - errs = append(errs, Errorf(err, "agent %s", agent)) - continue - } - - // First is the top-level fields. We treat the fields as table prefixes with an empty index. - t := Table{ - Name: s.Name, - Fields: s.Fields, - } - topTags := map[string]string{} - if err := s.gatherTable(acc, gs, t, topTags, false); err != nil { - errs = append(errs, Errorf(err, "agent %s", agent)) - } - - // Now is the real tables. - for _, t := range s.Tables { - if err := s.gatherTable(acc, gs, t, topTags, true); err != nil { - errs = append(errs, Errorf(err, "agent %s", agent)) - } - } - } - - if errs == nil { - return nil - } - return errs -} - -func (s *Snmp2) gatherTable(acc telegraf.Accumulator, gs snmpConnection, t Table, topTags map[string]string, walk bool) error { - rt, err := t.Build(gs, walk) - if err != nil { - return err - } - - for _, tr := range rt.Rows { - if !walk { - // top-level table. Add tags to topTags. - for k, v := range tr.Tags { - topTags[k] = v - } - } else { - // real table. Inherit any specified tags. - for _, k := range t.InheritTags { - if v, ok := topTags[k]; ok { - tr.Tags[k] = v - } - } - } - if _, ok := tr.Tags["agent_host"]; !ok { - tr.Tags["agent_host"] = gs.Host() - } - acc.AddFields(rt.Name, tr.Fields, tr.Tags, rt.Time) - } - - return nil -} - -// Build retrieves all the fields specified in the table and constructs the RTable. -func (t Table) Build(gs snmpConnection, walk bool) (*RTable, error) { - rows := map[string]RTableRow{} - - tagCount := 0 - for _, f := range t.Fields { - if f.IsTag { - tagCount++ - } - - if len(f.Oid) == 0 { - return nil, fmt.Errorf("cannot have empty OID") - } - 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 { - return nil, Errorf(err, "performing get") - } else if pkt != nil && len(pkt.Variables) > 0 && pkt.Variables[0].Type != gosnmp.NoSuchObject { - ent := pkt.Variables[0] - ifv[ent.Name[len(oid):]] = fieldConvert(f.Conversion, ent.Value) - } - } else { - err := gs.Walk(oid, func(ent gosnmp.SnmpPDU) error { - if len(ent.Name) <= len(oid) || ent.Name[:len(oid)+1] != oid+"." { - return NestedError{} // break the walk - } - ifv[ent.Name[len(oid):]] = fieldConvert(f.Conversion, ent.Value) - return nil - }) - if err != nil { - if _, ok := err.(NestedError); !ok { - return nil, Errorf(err, "performing bulk walk") - } - } - } - - for i, v := range ifv { - rtr, ok := rows[i] - if !ok { - rtr = RTableRow{} - rtr.Tags = map[string]string{} - rtr.Fields = map[string]interface{}{} - rows[i] = rtr - } - if f.IsTag { - if vs, ok := v.(string); ok { - rtr.Tags[f.Name] = vs - } else { - rtr.Tags[f.Name] = fmt.Sprintf("%v", v) - } - } else { - rtr.Fields[f.Name] = v - } - } - } - - rt := RTable{ - Name: t.Name, - Time: time.Now(), //TODO record time at start - Rows: make([]RTableRow, 0, len(rows)), - } - for _, r := range rows { - if len(r.Tags) < tagCount { - // don't add rows which are missing tags, as without tags you can't filter - continue - } - 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) -} - -// gosnmpWrapper wraps a *gosnmp.GoSNMP object so we can use it as a snmpConnection. -type gosnmpWrapper struct { - *gosnmp.GoSNMP -} - -// Host returns the value of GoSNMP.Target. -func (gsw gosnmpWrapper) Host() string { - return gsw.Target -} - -// Walk wraps GoSNMP.Walk() or GoSNMP.BulkWalk(), depending on whether the -// connection is using SNMPv1 or newer. -// Also, if any error is encountered, it will just once reconnect and try again. -func (gsw gosnmpWrapper) Walk(oid string, fn gosnmp.WalkFunc) error { - var err error - // On error, retry once. - // Unfortunately we can't distinguish between an error returned by gosnmp, and one returned by the walk function. - for i := 0; i < 2; i++ { - if gsw.Version == gosnmp.Version1 { - err = gsw.GoSNMP.Walk(oid, fn) - } else { - err = gsw.GoSNMP.BulkWalk(oid, fn) - } - if err == nil { - return nil - } - if err := gsw.GoSNMP.Connect(); err != nil { - return Errorf(err, "reconnecting") - } - } - return err -} - -// Get wraps GoSNMP.GET(). -// If any error is encountered, it will just once reconnect and try again. -func (gsw gosnmpWrapper) Get(oids []string) (*gosnmp.SnmpPacket, error) { - var err error - var pkt *gosnmp.SnmpPacket - for i := 0; i < 2; i++ { - pkt, err = gsw.GoSNMP.Get(oids) - if err == nil { - return pkt, nil - } - if err := gsw.GoSNMP.Connect(); err != nil { - return nil, Errorf(err, "reconnecting") - } - } - return nil, err -} - -// getConnection creates a snmpConnection (*gosnmp.GoSNMP) object and caches the -// result using `agent` as the cache key. -func (s *Snmp2) getConnection(agent string) (snmpConnection, error) { - if s.connectionCache == nil { - s.connectionCache = map[string]snmpConnection{} - } - if gs, ok := s.connectionCache[agent]; ok { - return gs, nil - } - - gs := gosnmpWrapper{&gosnmp.GoSNMP{}} - - host, portStr, err := net.SplitHostPort(agent) - if err != nil { - if err, ok := err.(*net.AddrError); !ok || err.Err != "missing port in address" { - return nil, Errorf(err, "parsing host") - } - host = agent - portStr = "161" - } - gs.Target = host - - port, err := strconv.ParseUint(portStr, 10, 16) - if err != nil { - return nil, Errorf(err, "parsing port") - } - gs.Port = uint16(port) - - if s.Timeout != "" { - if gs.Timeout, err = time.ParseDuration(s.Timeout); err != nil { - return nil, Errorf(err, "parsing timeout") - } - } else { - gs.Timeout = time.Second * 1 - } - - gs.Retries = s.Retries - - switch s.Version { - case 3: - gs.Version = gosnmp.Version3 - case 2, 0: - gs.Version = gosnmp.Version2c - case 1: - gs.Version = gosnmp.Version1 - default: - return nil, fmt.Errorf("invalid version") - } - - if s.Version < 3 { - if s.Community == "" { - gs.Community = "public" - } else { - gs.Community = s.Community - } - } - - gs.MaxRepetitions = int(s.MaxRepetitions) - - if s.Version == 3 { - gs.ContextName = s.ContextName - - sp := &gosnmp.UsmSecurityParameters{} - gs.SecurityParameters = sp - gs.SecurityModel = gosnmp.UserSecurityModel - - switch strings.ToLower(s.SecLevel) { - case "noauthnopriv", "": - gs.MsgFlags = gosnmp.NoAuthNoPriv - case "authnopriv": - gs.MsgFlags = gosnmp.AuthNoPriv - case "authpriv": - gs.MsgFlags = gosnmp.AuthPriv - default: - return nil, fmt.Errorf("invalid secLevel") - } - - sp.UserName = s.SecName - - switch strings.ToLower(s.AuthProtocol) { - case "md5": - sp.AuthenticationProtocol = gosnmp.MD5 - case "sha": - sp.AuthenticationProtocol = gosnmp.SHA - case "": - sp.AuthenticationProtocol = gosnmp.NoAuth - default: - return nil, fmt.Errorf("invalid authProtocol") - } - - sp.AuthenticationPassphrase = s.AuthPassword - - switch strings.ToLower(s.PrivProtocol) { - case "des": - sp.PrivacyProtocol = gosnmp.DES - case "aes": - sp.PrivacyProtocol = gosnmp.AES - case "": - sp.PrivacyProtocol = gosnmp.NoPriv - default: - return nil, fmt.Errorf("invalid privProtocol") - } - - sp.PrivacyPassphrase = s.PrivPassword - - sp.AuthoritativeEngineID = s.EngineID - - sp.AuthoritativeEngineBoots = s.EngineBoots - - sp.AuthoritativeEngineTime = s.EngineTime - } - - if err := gs.Connect(); err != nil { - return nil, Errorf(err, "setting up connection") - } - - s.connectionCache[agent] = gs - return gs, nil -} - -// fieldConvert converts from any type according to the conv specification -// "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. -// "" will convert a byte slice into a string. -// Any other conv will return the input value unchanged. -func fieldConvert(conv string, v interface{}) interface{} { - if conv == "" { - if bs, ok := v.([]byte); ok { - return string(bs) - } - return v - } - - var d int - if _, err := fmt.Sscanf(conv, "float(%d)", &d); err == nil || conv == "float" { - switch vt := v.(type) { - case float32: - v = float64(vt) / math.Pow10(d) - case float64: - v = float64(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) - } - } - if conv == "int" { - 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 = int64(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.Atoi(string(vt)) - case string: - v, _ = strconv.Atoi(vt) - } - } - - return v -} diff --git a/plugins/inputs/snmp2/snmp_test.go b/plugins/inputs/snmp2/snmp_test.go deleted file mode 100644 index 6bd8c37f1..000000000 --- a/plugins/inputs/snmp2/snmp_test.go +++ /dev/null @@ -1,493 +0,0 @@ -package snmp2 - -import ( - "fmt" - "net" - "sync" - "testing" - "time" - - "github.com/influxdata/telegraf/testutil" - "github.com/influxdata/toml" - "github.com/soniah/gosnmp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -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 -} - -var tsc = &testSNMPConnection{ - host: "tsc", - values: map[string]interface{}{ - ".1.2.3.0.0.1.0": "foo", - ".1.2.3.0.0.1.1": []byte("bar"), - ".1.2.3.0.0.102": "bad", - ".1.2.3.0.0.2.0": 1, - ".1.2.3.0.0.2.1": 2, - ".1.2.3.0.0.3.0": "0.123", - ".1.2.3.0.0.3.1": "0.456", - ".1.2.3.0.0.3.2": "9.999", - ".1.2.3.0.0.4.0": 123456, - ".1.2.3.0.1": "baz", - ".1.2.3.0.2": 234, - ".1.2.3.0.3": []byte("byte slice"), - }, -} - -func TestSampleConfig(t *testing.T) { - conf := struct { - Inputs struct { - Snmp2 []*Snmp2 - } - }{} - err := toml.Unmarshal([]byte("[[inputs.snmp2]]\n"+(*Snmp2)(nil).SampleConfig()), &conf) - assert.NoError(t, err) - - s := Snmp2{ - Agents: []string{"127.0.0.1:161"}, - Version: 2, - Community: "public", - MaxRepetitions: 50, - - Name: "system", - Fields: []Field{ - {Name: "hostname", Oid: ".1.2.3.0.1.1"}, - {Name: "uptime", Oid: ".1.2.3.0.1.200"}, - {Name: "load", Oid: ".1.2.3.0.1.201"}, - }, - Tables: []Table{ - { - Name: "remote_servers", - InheritTags: []string{"hostname"}, - Fields: []Field{ - {Name: "server", Oid: ".1.2.3.0.0.0", IsTag: true}, - {Name: "connections", Oid: ".1.2.3.0.0.1"}, - {Name: "latency", Oid: ".1.2.3.0.0.2"}, - }, - }, - }, - } - assert.Equal(t, s, *conf.Inputs.Snmp2[0]) -} - -func TestGetSNMPConnection_v2(t *testing.T) { - s := &Snmp2{ - Timeout: "3s", - Retries: 4, - Version: 2, - Community: "foo", - } - - gsc, err := s.getConnection("1.2.3.4:567") - require.NoError(t, err) - gs := gsc.(gosnmpWrapper) - assert.Equal(t, "1.2.3.4", gs.Target) - assert.EqualValues(t, 567, gs.Port) - assert.Equal(t, gosnmp.Version2c, gs.Version) - assert.Equal(t, "foo", gs.Community) - - gsc, err = s.getConnection("1.2.3.4") - require.NoError(t, err) - gs = gsc.(gosnmpWrapper) - assert.Equal(t, "1.2.3.4", gs.Target) - assert.EqualValues(t, 161, gs.Port) -} - -func TestGetSNMPConnection_v3(t *testing.T) { - s := &Snmp2{ - Version: 3, - MaxRepetitions: 20, - ContextName: "mycontext", - SecLevel: "authPriv", - SecName: "myuser", - AuthProtocol: "md5", - AuthPassword: "password123", - PrivProtocol: "des", - PrivPassword: "321drowssap", - EngineID: "myengineid", - EngineBoots: 1, - EngineTime: 2, - } - - gsc, err := s.getConnection("1.2.3.4") - require.NoError(t, err) - gs := gsc.(gosnmpWrapper) - assert.Equal(t, gs.Version, gosnmp.Version3) - sp := gs.SecurityParameters.(*gosnmp.UsmSecurityParameters) - assert.Equal(t, "1.2.3.4", gsc.Host()) - assert.Equal(t, 20, gs.MaxRepetitions) - assert.Equal(t, "mycontext", gs.ContextName) - assert.Equal(t, gosnmp.AuthPriv, gs.MsgFlags&gosnmp.AuthPriv) - assert.Equal(t, "myuser", sp.UserName) - assert.Equal(t, gosnmp.MD5, sp.AuthenticationProtocol) - assert.Equal(t, "password123", sp.AuthenticationPassphrase) - assert.Equal(t, gosnmp.DES, sp.PrivacyProtocol) - assert.Equal(t, "321drowssap", sp.PrivacyPassphrase) - assert.Equal(t, "myengineid", sp.AuthoritativeEngineID) - assert.EqualValues(t, 1, sp.AuthoritativeEngineBoots) - assert.EqualValues(t, 2, sp.AuthoritativeEngineTime) -} - -func TestGetSNMPConnection_caching(t *testing.T) { - s := &Snmp2{} - gs1, err := s.getConnection("1.2.3.4") - require.NoError(t, err) - gs2, err := s.getConnection("1.2.3.4") - require.NoError(t, err) - gs3, err := s.getConnection("1.2.3.5") - require.NoError(t, err) - assert.True(t, gs1 == gs2) - assert.False(t, gs2 == gs3) -} - -func TestGosnmpWrapper_walk_retry(t *testing.T) { - srvr, err := net.ListenUDP("udp4", &net.UDPAddr{}) - defer srvr.Close() - require.NoError(t, err) - reqCount := 0 - // Set up a WaitGroup to wait for the server goroutine to exit and protect - // reqCount. - // Even though simultaneous access is impossible because the server will be - // blocked on ReadFrom, without this the race detector gets unhappy. - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - buf := make([]byte, 256) - for { - _, addr, err := srvr.ReadFrom(buf) - if err != nil { - return - } - reqCount++ - - srvr.WriteTo([]byte{'X'}, addr) // will cause decoding error - } - }() - - gs := &gosnmp.GoSNMP{ - Target: srvr.LocalAddr().(*net.UDPAddr).IP.String(), - Port: uint16(srvr.LocalAddr().(*net.UDPAddr).Port), - Version: gosnmp.Version2c, - Community: "public", - Timeout: time.Millisecond * 10, - Retries: 1, - } - err = gs.Connect() - require.NoError(t, err) - conn := gs.Conn - - gsw := gosnmpWrapper{gs} - err = gsw.Walk(".1.2.3", func(_ gosnmp.SnmpPDU) error { return nil }) - srvr.Close() - wg.Wait() - assert.Error(t, err) - assert.False(t, gs.Conn == conn) - assert.Equal(t, (gs.Retries+1)*2, reqCount) -} - -func TestGosnmpWrapper_get_retry(t *testing.T) { - srvr, err := net.ListenUDP("udp4", &net.UDPAddr{}) - defer srvr.Close() - require.NoError(t, err) - reqCount := 0 - // Set up a WaitGroup to wait for the server goroutine to exit and protect - // reqCount. - // Even though simultaneous access is impossible because the server will be - // blocked on ReadFrom, without this the race detector gets unhappy. - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - buf := make([]byte, 256) - for { - _, addr, err := srvr.ReadFrom(buf) - if err != nil { - return - } - reqCount++ - - srvr.WriteTo([]byte{'X'}, addr) // will cause decoding error - } - }() - - gs := &gosnmp.GoSNMP{ - Target: srvr.LocalAddr().(*net.UDPAddr).IP.String(), - Port: uint16(srvr.LocalAddr().(*net.UDPAddr).Port), - Version: gosnmp.Version2c, - Community: "public", - Timeout: time.Millisecond * 10, - Retries: 1, - } - err = gs.Connect() - require.NoError(t, err) - conn := gs.Conn - - gsw := gosnmpWrapper{gs} - _, err = gsw.Get([]string{".1.2.3"}) - srvr.Close() - wg.Wait() - assert.Error(t, err) - assert.False(t, gs.Conn == conn) - assert.Equal(t, (gs.Retries+1)*2, reqCount) -} - -func TestTableBuild_walk(t *testing.T) { - tbl := Table{ - Name: "mytable", - Fields: []Field{ - { - Name: "myfield1", - Oid: ".1.2.3.0.0.1", - IsTag: true, - }, - { - Name: "myfield2", - Oid: ".1.2.3.0.0.2", - }, - { - Name: "myfield3", - Oid: ".1.2.3.0.0.3", - Conversion: "float", - }, - }, - } - - tb, err := tbl.Build(tsc, true) - require.NoError(t, err) - - assert.Equal(t, tb.Name, "mytable") - rtr1 := RTableRow{ - Tags: map[string]string{"myfield1": "foo"}, - Fields: map[string]interface{}{"myfield2": 1, "myfield3": float64(0.123)}, - } - rtr2 := RTableRow{ - Tags: map[string]string{"myfield1": "bar"}, - Fields: map[string]interface{}{"myfield2": 2, "myfield3": float64(0.456)}, - } - assert.Len(t, tb.Rows, 2) - assert.Contains(t, tb.Rows, rtr1) - assert.Contains(t, tb.Rows, rtr2) -} - -func TestTableBuild_noWalk(t *testing.T) { - tbl := Table{ - Name: "mytable", - Fields: []Field{ - { - Name: "myfield1", - Oid: ".1.2.3.0.1", - IsTag: true, - }, - { - Name: "myfield2", - Oid: ".1.2.3.0.2", - }, - { - Name: "myfield3", - Oid: ".1.2.3.0.2", - IsTag: true, - }, - }, - } - - 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}, - } - assert.Len(t, tb.Rows, 1) - assert.Contains(t, tb.Rows, rtr) -} - -func TestGather(t *testing.T) { - s := &Snmp2{ - Agents: []string{"TestGather"}, - Name: "mytable", - Fields: []Field{ - { - Name: "myfield1", - Oid: ".1.2.3.0.1", - IsTag: true, - }, - { - Name: "myfield2", - Oid: ".1.2.3.0.2", - }, - { - Name: "myfield3", - Oid: "1.2.3.0.1", - }, - }, - Tables: []Table{ - { - Name: "myOtherTable", - InheritTags: []string{"myfield1"}, - Fields: []Field{ - { - Name: "myOtherField", - Oid: ".1.2.3.0.0.4", - }, - }, - }, - }, - - connectionCache: map[string]snmpConnection{ - "TestGather": tsc, - }, - } - - acc := &testutil.Accumulator{} - - tstart := time.Now() - s.Gather(acc) - tstop := time.Now() - - require.Len(t, acc.Metrics, 2) - - m := acc.Metrics[0] - assert.Equal(t, "mytable", m.Measurement) - assert.Equal(t, "tsc", m.Tags["agent_host"]) - assert.Equal(t, "baz", m.Tags["myfield1"]) - assert.Len(t, m.Fields, 2) - assert.Equal(t, 234, m.Fields["myfield2"]) - assert.Equal(t, "baz", m.Fields["myfield3"]) - assert.True(t, tstart.Before(m.Time)) - assert.True(t, tstop.After(m.Time)) - - m2 := acc.Metrics[1] - assert.Equal(t, "myOtherTable", m2.Measurement) - assert.Equal(t, "tsc", m2.Tags["agent_host"]) - assert.Equal(t, "baz", m2.Tags["myfield1"]) - assert.Len(t, m2.Fields, 1) - assert.Equal(t, 123456, m2.Fields["myOtherField"]) -} - -func TestGather_host(t *testing.T) { - s := &Snmp2{ - Agents: []string{"TestGather"}, - Name: "mytable", - Fields: []Field{ - { - Name: "host", - Oid: ".1.2.3.0.1", - IsTag: true, - }, - { - Name: "myfield2", - Oid: ".1.2.3.0.2", - }, - }, - - connectionCache: map[string]snmpConnection{ - "TestGather": tsc, - }, - } - - acc := &testutil.Accumulator{} - - s.Gather(acc) - - require.Len(t, acc.Metrics, 1) - m := acc.Metrics[0] - assert.Equal(t, "baz", m.Tags["host"]) -} - -func TestFieldConvert(t *testing.T) { - testTable := []struct { - input interface{} - conv string - expected interface{} - }{ - {[]byte("foo"), "", string("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)}, - {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)}, - {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)}, - } - - for _, tc := range testTable { - act := fieldConvert(tc.conv, tc.input) - assert.EqualValues(t, tc.expected, act, "input=%T(%v) conv=%s expected=%T(%v)", tc.input, tc.input, tc.conv, tc.expected, tc.expected) - } -} - -func TestError(t *testing.T) { - e := fmt.Errorf("nested error") - err := Errorf(e, "top error %d", 123) - require.Error(t, err) - - ne, ok := err.(NestedError) - require.True(t, ok) - assert.Equal(t, e, ne.NestedErr) - - assert.Contains(t, err.Error(), "top error 123") - assert.Contains(t, err.Error(), "nested error") -} diff --git a/plugins/inputs/snmp_legacy/README.md b/plugins/inputs/snmp_legacy/README.md new file mode 100644 index 000000000..bee783228 --- /dev/null +++ b/plugins/inputs/snmp_legacy/README.md @@ -0,0 +1,549 @@ +# SNMP Input Plugin + +The SNMP input plugin gathers metrics from SNMP agents + +### Configuration: + + +#### Very simple example + +In this example, the plugin will gather value of OIDS: + + - `.1.3.6.1.2.1.2.2.1.4.1` + +```toml +# Very Simple Example +[[inputs.snmp]] + + [[inputs.snmp.host]] + address = "127.0.0.1:161" + # SNMP community + community = "public" # default public + # SNMP version (1, 2 or 3) + # Version 3 not supported yet + version = 2 # default 2 + # Simple list of OIDs to get, in addition to "collect" + get_oids = [".1.3.6.1.2.1.2.2.1.4.1"] +``` + + +#### Simple example + +In this example, Telegraf gathers value of OIDS: + + - named **ifnumber** + - named **interface_speed** + +With **inputs.snmp.get** section the plugin gets the oid number: + + - **ifnumber** => `.1.3.6.1.2.1.2.1.0` + - **interface_speed** => *ifSpeed* + +As you can see *ifSpeed* is not a valid OID. In order to get +the valid OID, the plugin uses `snmptranslate_file` to match the OID: + + - **ifnumber** => `.1.3.6.1.2.1.2.1.0` + - **interface_speed** => *ifSpeed* => `.1.3.6.1.2.1.2.2.1.5` + +Also as the plugin will append `instance` to the corresponding OID: + + - **ifnumber** => `.1.3.6.1.2.1.2.1.0` + - **interface_speed** => *ifSpeed* => `.1.3.6.1.2.1.2.2.1.5.1` + +In this example, the plugin will gather value of OIDS: + +- `.1.3.6.1.2.1.2.1.0` +- `.1.3.6.1.2.1.2.2.1.5.1` + + +```toml +# Simple example +[[inputs.snmp]] + ## 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 + snmptranslate_file = "/tmp/oids.txt" + [[inputs.snmp.host]] + address = "127.0.0.1:161" + # SNMP community + community = "public" # default public + # SNMP version (1, 2 or 3) + # Version 3 not supported yet + version = 2 # default 2 + # Which get/bulk do you want to collect for this host + collect = ["ifnumber", "interface_speed"] + + [[inputs.snmp.get]] + name = "ifnumber" + oid = ".1.3.6.1.2.1.2.1.0" + + [[inputs.snmp.get]] + name = "interface_speed" + oid = "ifSpeed" + instance = "1" + +``` + + +#### Simple bulk example + +In this example, Telegraf gathers value of OIDS: + + - named **ifnumber** + - named **interface_speed** + - named **if_out_octets** + +With **inputs.snmp.get** section the plugin gets oid number: + + - **ifnumber** => `.1.3.6.1.2.1.2.1.0` + - **interface_speed** => *ifSpeed* + +With **inputs.snmp.bulk** section the plugin gets the oid number: + + - **if_out_octets** => *ifOutOctets* + +As you can see *ifSpeed* and *ifOutOctets* are not a valid OID. +In order to get the valid OID, the plugin uses `snmptranslate_file` +to match the OID: + + - **ifnumber** => `.1.3.6.1.2.1.2.1.0` + - **interface_speed** => *ifSpeed* => `.1.3.6.1.2.1.2.2.1.5` + - **if_out_octets** => *ifOutOctets* => `.1.3.6.1.2.1.2.2.1.16` + +Also, the plugin will append `instance` to the corresponding OID: + + - **ifnumber** => `.1.3.6.1.2.1.2.1.0` + - **interface_speed** => *ifSpeed* => `.1.3.6.1.2.1.2.2.1.5.1` + +And **if_out_octets** is a bulk request, the plugin will gathers all +OIDS in the table. + +- `.1.3.6.1.2.1.2.2.1.16.1` +- `.1.3.6.1.2.1.2.2.1.16.2` +- `.1.3.6.1.2.1.2.2.1.16.3` +- `.1.3.6.1.2.1.2.2.1.16.4` +- `.1.3.6.1.2.1.2.2.1.16.5` +- `...` + +In this example, the plugin will gather value of OIDS: + +- `.1.3.6.1.2.1.2.1.0` +- `.1.3.6.1.2.1.2.2.1.5.1` +- `.1.3.6.1.2.1.2.2.1.16.1` +- `.1.3.6.1.2.1.2.2.1.16.2` +- `.1.3.6.1.2.1.2.2.1.16.3` +- `.1.3.6.1.2.1.2.2.1.16.4` +- `.1.3.6.1.2.1.2.2.1.16.5` +- `...` + + +```toml +# Simple bulk example +[[inputs.snmp]] + ## 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 + snmptranslate_file = "/tmp/oids.txt" + [[inputs.snmp.host]] + address = "127.0.0.1:161" + # SNMP community + community = "public" # default public + # SNMP version (1, 2 or 3) + # Version 3 not supported yet + version = 2 # default 2 + # Which get/bulk do you want to collect for this host + collect = ["interface_speed", "if_number", "if_out_octets"] + + [[inputs.snmp.get]] + name = "interface_speed" + oid = "ifSpeed" + instance = "1" + + [[inputs.snmp.get]] + name = "if_number" + oid = "ifNumber" + + [[inputs.snmp.bulk]] + name = "if_out_octets" + oid = "ifOutOctets" +``` + + +#### Table example + +In this example, we remove collect attribute to the host section, +but you can still use it in combination of the following part. + +Note: This example is like a bulk request a but using an +other configuration + +Telegraf gathers value of OIDS of the table: + + - named **iftable1** + +With **inputs.snmp.table** section the plugin gets oid number: + + - **iftable1** => `.1.3.6.1.2.1.31.1.1.1` + +Also **iftable1** is a table, the plugin will gathers all +OIDS in the table and in the subtables + +- `.1.3.6.1.2.1.31.1.1.1.1` +- `.1.3.6.1.2.1.31.1.1.1.1.1` +- `.1.3.6.1.2.1.31.1.1.1.1.2` +- `.1.3.6.1.2.1.31.1.1.1.1.3` +- `.1.3.6.1.2.1.31.1.1.1.1.4` +- `.1.3.6.1.2.1.31.1.1.1.1....` +- `.1.3.6.1.2.1.31.1.1.1.2` +- `.1.3.6.1.2.1.31.1.1.1.2....` +- `.1.3.6.1.2.1.31.1.1.1.3` +- `.1.3.6.1.2.1.31.1.1.1.3....` +- `.1.3.6.1.2.1.31.1.1.1.4` +- `.1.3.6.1.2.1.31.1.1.1.4....` +- `.1.3.6.1.2.1.31.1.1.1.5` +- `.1.3.6.1.2.1.31.1.1.1.5....` +- `.1.3.6.1.2.1.31.1.1.1.6....` +- `...` + +```toml +# Table example +[[inputs.snmp]] + ## 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 + snmptranslate_file = "/tmp/oids.txt" + [[inputs.snmp.host]] + address = "127.0.0.1:161" + # SNMP community + community = "public" # default public + # SNMP version (1, 2 or 3) + # Version 3 not supported yet + version = 2 # default 2 + # Which get/bulk do you want to collect for this host + # Which table do you want to collect + [[inputs.snmp.host.table]] + name = "iftable1" + + # table without mapping neither subtables + # This is like bulk request + [[inputs.snmp.table]] + name = "iftable1" + oid = ".1.3.6.1.2.1.31.1.1.1" +``` + + +#### Table with subtable example + +In this example, we remove collect attribute to the host section, +but you can still use it in combination of the following part. + +Note: This example is like a bulk request a but using an +other configuration + +Telegraf gathers value of OIDS of the table: + + - named **iftable2** + +With **inputs.snmp.table** section *AND* **sub_tables** attribute, +the plugin will get OIDS from subtables: + + - **iftable2** => `.1.3.6.1.2.1.2.2.1.13` + +Also **iftable2** is a table, the plugin will gathers all +OIDS in subtables: + +- `.1.3.6.1.2.1.2.2.1.13.1` +- `.1.3.6.1.2.1.2.2.1.13.2` +- `.1.3.6.1.2.1.2.2.1.13.3` +- `.1.3.6.1.2.1.2.2.1.13.4` +- `.1.3.6.1.2.1.2.2.1.13....` + + +```toml +# Table with subtable example +[[inputs.snmp]] + ## 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 + snmptranslate_file = "/tmp/oids.txt" + [[inputs.snmp.host]] + address = "127.0.0.1:161" + # SNMP community + community = "public" # default public + # SNMP version (1, 2 or 3) + # Version 3 not supported yet + version = 2 # default 2 + # Which table do you want to collect + [[inputs.snmp.host.table]] + name = "iftable2" + + # table without mapping but with subtables + [[inputs.snmp.table]] + name = "iftable2" + sub_tables = [".1.3.6.1.2.1.2.2.1.13"] + # note + # oid attribute is useless +``` + + +#### Table with mapping example + +In this example, we remove collect attribute to the host section, +but you can still use it in combination of the following part. + +Telegraf gathers value of OIDS of the table: + + - named **iftable3** + +With **inputs.snmp.table** section the plugin gets oid number: + + - **iftable3** => `.1.3.6.1.2.1.31.1.1.1` + +Also **iftable2** is a table, the plugin will gathers all +OIDS in the table and in the subtables + +- `.1.3.6.1.2.1.31.1.1.1.1` +- `.1.3.6.1.2.1.31.1.1.1.1.1` +- `.1.3.6.1.2.1.31.1.1.1.1.2` +- `.1.3.6.1.2.1.31.1.1.1.1.3` +- `.1.3.6.1.2.1.31.1.1.1.1.4` +- `.1.3.6.1.2.1.31.1.1.1.1....` +- `.1.3.6.1.2.1.31.1.1.1.2` +- `.1.3.6.1.2.1.31.1.1.1.2....` +- `.1.3.6.1.2.1.31.1.1.1.3` +- `.1.3.6.1.2.1.31.1.1.1.3....` +- `.1.3.6.1.2.1.31.1.1.1.4` +- `.1.3.6.1.2.1.31.1.1.1.4....` +- `.1.3.6.1.2.1.31.1.1.1.5` +- `.1.3.6.1.2.1.31.1.1.1.5....` +- `.1.3.6.1.2.1.31.1.1.1.6....` +- `...` + +But the **include_instances** attribute will filter which OIDS +will be gathered; As you see, there is an other attribute, `mapping_table`. +`include_instances` and `mapping_table` permit to build a hash table +to filter only OIDS you want. +Let's say, we have the following data on SNMP server: + - OID: `.1.3.6.1.2.1.31.1.1.1.1.1` has as value: `enp5s0` + - OID: `.1.3.6.1.2.1.31.1.1.1.1.2` has as value: `enp5s1` + - OID: `.1.3.6.1.2.1.31.1.1.1.1.3` has as value: `enp5s2` + - OID: `.1.3.6.1.2.1.31.1.1.1.1.4` has as value: `eth0` + - OID: `.1.3.6.1.2.1.31.1.1.1.1.5` has as value: `eth1` + +The plugin will build the following hash table: + +| instance name | instance id | +|---------------|-------------| +| `enp5s0` | `1` | +| `enp5s1` | `2` | +| `enp5s2` | `3` | +| `eth0` | `4` | +| `eth1` | `5` | + +With the **include_instances** attribute, the plugin will gather +the following OIDS: + +- `.1.3.6.1.2.1.31.1.1.1.1.1` +- `.1.3.6.1.2.1.31.1.1.1.1.5` +- `.1.3.6.1.2.1.31.1.1.1.2.1` +- `.1.3.6.1.2.1.31.1.1.1.2.5` +- `.1.3.6.1.2.1.31.1.1.1.3.1` +- `.1.3.6.1.2.1.31.1.1.1.3.5` +- `.1.3.6.1.2.1.31.1.1.1.4.1` +- `.1.3.6.1.2.1.31.1.1.1.4.5` +- `.1.3.6.1.2.1.31.1.1.1.5.1` +- `.1.3.6.1.2.1.31.1.1.1.5.5` +- `.1.3.6.1.2.1.31.1.1.1.6.1` +- `.1.3.6.1.2.1.31.1.1.1.6.5` +- `...` + +Note: the plugin will add instance name as tag *instance* + +```toml +# Simple table with mapping example +[[inputs.snmp]] + ## 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 + snmptranslate_file = "/tmp/oids.txt" + [[inputs.snmp.host]] + address = "127.0.0.1:161" + # SNMP community + community = "public" # default public + # SNMP version (1, 2 or 3) + # Version 3 not supported yet + version = 2 # default 2 + # Which table do you want to collect + [[inputs.snmp.host.table]] + name = "iftable3" + include_instances = ["enp5s0", "eth1"] + + # table with mapping but without subtables + [[inputs.snmp.table]] + name = "iftable3" + oid = ".1.3.6.1.2.1.31.1.1.1" + # if empty. get all instances + mapping_table = ".1.3.6.1.2.1.31.1.1.1.1" + # if empty, get all subtables +``` + + +#### Table with both mapping and subtable example + +In this example, we remove collect attribute to the host section, +but you can still use it in combination of the following part. + +Telegraf gathers value of OIDS of the table: + + - named **iftable4** + +With **inputs.snmp.table** section *AND* **sub_tables** attribute, +the plugin will get OIDS from subtables: + + - **iftable4** => `.1.3.6.1.2.1.31.1.1.1` + +Also **iftable2** is a table, the plugin will gathers all +OIDS in the table and in the subtables + +- `.1.3.6.1.2.1.31.1.1.1.6.1 +- `.1.3.6.1.2.1.31.1.1.1.6.2` +- `.1.3.6.1.2.1.31.1.1.1.6.3` +- `.1.3.6.1.2.1.31.1.1.1.6.4` +- `.1.3.6.1.2.1.31.1.1.1.6....` +- `.1.3.6.1.2.1.31.1.1.1.10.1` +- `.1.3.6.1.2.1.31.1.1.1.10.2` +- `.1.3.6.1.2.1.31.1.1.1.10.3` +- `.1.3.6.1.2.1.31.1.1.1.10.4` +- `.1.3.6.1.2.1.31.1.1.1.10....` + +But the **include_instances** attribute will filter which OIDS +will be gathered; As you see, there is an other attribute, `mapping_table`. +`include_instances` and `mapping_table` permit to build a hash table +to filter only OIDS you want. +Let's say, we have the following data on SNMP server: + - OID: `.1.3.6.1.2.1.31.1.1.1.1.1` has as value: `enp5s0` + - OID: `.1.3.6.1.2.1.31.1.1.1.1.2` has as value: `enp5s1` + - OID: `.1.3.6.1.2.1.31.1.1.1.1.3` has as value: `enp5s2` + - OID: `.1.3.6.1.2.1.31.1.1.1.1.4` has as value: `eth0` + - OID: `.1.3.6.1.2.1.31.1.1.1.1.5` has as value: `eth1` + +The plugin will build the following hash table: + +| instance name | instance id | +|---------------|-------------| +| `enp5s0` | `1` | +| `enp5s1` | `2` | +| `enp5s2` | `3` | +| `eth0` | `4` | +| `eth1` | `5` | + +With the **include_instances** attribute, the plugin will gather +the following OIDS: + +- `.1.3.6.1.2.1.31.1.1.1.6.1` +- `.1.3.6.1.2.1.31.1.1.1.6.5` +- `.1.3.6.1.2.1.31.1.1.1.10.1` +- `.1.3.6.1.2.1.31.1.1.1.10.5` + +Note: the plugin will add instance name as tag *instance* + + + +```toml +# Table with both mapping and subtable example +[[inputs.snmp]] + ## 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 + snmptranslate_file = "/tmp/oids.txt" + [[inputs.snmp.host]] + address = "127.0.0.1:161" + # SNMP community + community = "public" # default public + # SNMP version (1, 2 or 3) + # Version 3 not supported yet + version = 2 # default 2 + # Which table do you want to collect + [[inputs.snmp.host.table]] + name = "iftable4" + include_instances = ["enp5s0", "eth1"] + + # table with both mapping and subtables + [[inputs.snmp.table]] + name = "iftable4" + # if empty get all instances + mapping_table = ".1.3.6.1.2.1.31.1.1.1.1" + # if empty get all subtables + # sub_tables could be not "real subtables" + sub_tables=[".1.3.6.1.2.1.2.2.1.13", "bytes_recv", "bytes_send"] + # note + # oid attribute is useless + + # SNMP SUBTABLES + [[inputs.snmp.subtable]] + name = "bytes_recv" + oid = ".1.3.6.1.2.1.31.1.1.1.6" + unit = "octets" + + [[inputs.snmp.subtable]] + name = "bytes_send" + oid = ".1.3.6.1.2.1.31.1.1.1.10" + unit = "octets" +``` + +#### Configuration notes + +- In **inputs.snmp.table** section, the `oid` attribute is useless if + the `sub_tables` attributes is defined + +- In **inputs.snmp.subtable** section, you can put a name from `snmptranslate_file` + as `oid` attribute instead of a valid OID + +### Measurements & Fields: + +With the last example (Table with both mapping and subtable example): + +- ifHCOutOctets + - ifHCOutOctets +- ifInDiscards + - ifInDiscards +- ifHCInOctets + - ifHCInOctets + +### Tags: + +With the last example (Table with both mapping and subtable example): + +- ifHCOutOctets + - host + - instance + - unit +- ifInDiscards + - host + - instance +- ifHCInOctets + - host + - instance + - unit + +### Example Output: + +With the last example (Table with both mapping and subtable example): + +``` +ifHCOutOctets,host=127.0.0.1,instance=enp5s0,unit=octets ifHCOutOctets=10565628i 1456878706044462901 +ifInDiscards,host=127.0.0.1,instance=enp5s0 ifInDiscards=0i 1456878706044510264 +ifHCInOctets,host=127.0.0.1,instance=enp5s0,unit=octets ifHCInOctets=76351777i 1456878706044531312 +``` diff --git a/plugins/inputs/snmp_legacy/snmp_legacy.go b/plugins/inputs/snmp_legacy/snmp_legacy.go new file mode 100644 index 000000000..975893bfd --- /dev/null +++ b/plugins/inputs/snmp_legacy/snmp_legacy.go @@ -0,0 +1,818 @@ +package snmp_legacy + +import ( + "io/ioutil" + "log" + "net" + "strconv" + "strings" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" + + "github.com/soniah/gosnmp" +) + +// Snmp is a snmp plugin +type Snmp struct { + Host []Host + Get []Data + Bulk []Data + Table []Table + Subtable []Subtable + SnmptranslateFile string + + nameToOid map[string]string + initNode Node + subTableMap map[string]Subtable +} + +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 + // easy get oids + GetOids []string + // Table + Table []HostTable + // Oids + getOids []Data + bulkOids []Data + tables []HostTable + // array of processed oids + // to skip oid duplication + processedOids []string + + OidInstanceMapping map[string]map[string]string +} + +type Table struct { + // name = "iftable" + Name string + // oid = ".1.3.6.1.2.1.31.1.1.1" + Oid string + //if empty get all instances + //mapping_table = ".1.3.6.1.2.1.31.1.1.1.1" + MappingTable string + // if empty get all subtables + // sub_tables could be not "real subtables" + //sub_tables=[".1.3.6.1.2.1.2.2.1.13", "bytes_recv", "bytes_send"] + SubTables []string +} + +type HostTable struct { + // name = "iftable" + Name string + // Includes only these instances + // include_instances = ["eth0", "eth1"] + IncludeInstances []string + // Excludes only these instances + // exclude_instances = ["eth20", "eth21"] + ExcludeInstances []string + // From Table struct + oid string + mappingTable string + subTables []string +} + +// TODO find better names +type Subtable struct { + //name = "bytes_send" + Name string + //oid = ".1.3.6.1.2.1.31.1.1.1.10" + Oid string + //unit = "octets" + Unit string +} + +type Data struct { + Name string + // OID (could be numbers or name) + Oid string + // Unit + Unit string + // SNMP getbulk max repetition + MaxRepetition uint8 `toml:"max_repetition"` + // SNMP Instance (default 0) + // (only used with GET request and if + // OID is a name from snmptranslate file) + Instance string + // OID (only number) (used for computation) + rawOid string +} + +type Node struct { + id string + name string + subnodes 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 + snmptranslate_file = "/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"] + # Simple list of OIDs to get, in addition to "collect" + get_oids = [] + + [[inputs.snmp.host]] + address = "192.168.2.3:161" + community = "public" + version = 2 + timeout = 2.0 + retries = 2 + collect = ["mybulk"] + get_oids = [ + "ifNumber", + ".1.3.6.1.2.1.1.3.0", + ] + + [[inputs.snmp.get]] + name = "ifnumber" + oid = "ifNumber" + + [[inputs.snmp.get]] + name = "interface_speed" + oid = "ifSpeed" + instance = "0" + + [[inputs.snmp.get]] + name = "sysuptime" + oid = ".1.3.6.1.2.1.1.3.0" + unit = "second" + + [[inputs.snmp.bulk]] + name = "mybulk" + max_repetition = 127 + oid = ".1.3.6.1.2.1.1" + + [[inputs.snmp.bulk]] + name = "ifoutoctets" + max_repetition = 127 + oid = "ifOutOctets" + + [[inputs.snmp.host]] + address = "192.168.2.13:161" + #address = "127.0.0.1:161" + community = "public" + version = 2 + timeout = 2.0 + retries = 2 + #collect = ["mybulk", "sysservices", "sysdescr", "systype"] + collect = ["sysuptime" ] + [[inputs.snmp.host.table]] + name = "iftable3" + include_instances = ["enp5s0", "eth1"] + + # SNMP TABLEs + # table without mapping neither subtables + [[inputs.snmp.table]] + name = "iftable1" + oid = ".1.3.6.1.2.1.31.1.1.1" + + # table without mapping but with subtables + [[inputs.snmp.table]] + name = "iftable2" + oid = ".1.3.6.1.2.1.31.1.1.1" + sub_tables = [".1.3.6.1.2.1.2.2.1.13"] + + # table with mapping but without subtables + [[inputs.snmp.table]] + name = "iftable3" + oid = ".1.3.6.1.2.1.31.1.1.1" + # if empty. get all instances + mapping_table = ".1.3.6.1.2.1.31.1.1.1.1" + # if empty, get all subtables + + # table with both mapping and subtables + [[inputs.snmp.table]] + name = "iftable4" + oid = ".1.3.6.1.2.1.31.1.1.1" + # if empty get all instances + mapping_table = ".1.3.6.1.2.1.31.1.1.1.1" + # if empty get all subtables + # sub_tables could be not "real subtables" + sub_tables=[".1.3.6.1.2.1.2.2.1.13", "bytes_recv", "bytes_send"] +` + +// SampleConfig returns sample configuration message +func (s *Snmp) SampleConfig() string { + return sampleConfig +} + +// Description returns description of Zookeeper plugin +func (s *Snmp) Description() string { + return `DEPRECATED: This will be removed in a future release.` +} + +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, string) { + // ids = ["1", "3", "6", ...] + if len(ids) == 1 { + return node.name, ids[0] + } + 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, "0" + } 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 telegraf.Accumulator) error { + // TODO put this in cache on first run + // Create subtables mapping + if len(s.subTableMap) == 0 { + s.subTableMap = make(map[string]Subtable) + for _, sb := range s.Subtable { + s.subTableMap[sb.Name] = sb + } + } + // TODO put this in cache on first run + // Create oid tree + if s.SnmptranslateFile != "" && len(s.initNode.subnodes) == 0 { + s.nameToOid = make(map[string]string) + s.initNode = Node{ + id: "1", + name: "", + subnodes: make(map[string]Node), + } + + data, err := ioutil.ReadFile(s.SnmptranslateFile) + if err != nil { + log.Printf("Reading SNMPtranslate file error: %s", err) + return err + } else { + for _, line := range strings.Split(string(data), "\n") { + oids := strings.Fields(string(line)) + if len(oids) == 2 && oids[1] != "" { + oid_name := oids[0] + oid := oids[1] + fillnode(s.initNode, oid_name, strings.Split(string(oid), ".")) + s.nameToOid[oid_name] = 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 + // Get Easy GET oids + for _, oidstring := range host.GetOids { + oid := Data{} + if val, ok := s.nameToOid[oidstring]; ok { + // TODO should we add the 0 instance ? + oid.Name = oidstring + oid.Oid = val + oid.rawOid = "." + val + ".0" + } else { + oid.Name = oidstring + oid.Oid = oidstring + if string(oidstring[:1]) != "." { + oid.rawOid = "." + oidstring + } else { + oid.rawOid = oidstring + } + } + host.getOids = append(host.getOids, oid) + } + + for _, oid_name := range host.Collect { + // Get GET oids + for _, oid := range s.Get { + if oid.Name == oid_name { + if val, ok := s.nameToOid[oid.Oid]; ok { + // TODO should we add the 0 instance ? + if oid.Instance != "" { + oid.rawOid = "." + val + "." + oid.Instance + } else { + oid.rawOid = "." + val + ".0" + } + } else { + oid.rawOid = oid.Oid + } + host.getOids = append(host.getOids, oid) + } + } + // Get GETBULK oids + for _, oid := range s.Bulk { + if oid.Name == oid_name { + if val, ok := s.nameToOid[oid.Oid]; ok { + oid.rawOid = "." + val + } else { + oid.rawOid = oid.Oid + } + host.bulkOids = append(host.bulkOids, oid) + } + } + } + // Table + for _, hostTable := range host.Table { + for _, snmpTable := range s.Table { + if hostTable.Name == snmpTable.Name { + table := hostTable + table.oid = snmpTable.Oid + table.mappingTable = snmpTable.MappingTable + table.subTables = snmpTable.SubTables + host.tables = append(host.tables, table) + } + } + } + // Launch Mapping + // TODO put this in cache on first run + // TODO save mapping and computed oids + // to do it only the first time + // only if len(s.OidInstanceMapping) == 0 + if len(host.OidInstanceMapping) >= 0 { + if err := host.SNMPMap(acc, s.nameToOid, s.subTableMap); err != nil { + log.Printf("SNMP Mapping error for host '%s': %s", host.Address, err) + continue + } + } + // Launch Get requests + if err := host.SNMPGet(acc, s.initNode); err != nil { + log.Printf("SNMP Error for host '%s': %s", host.Address, err) + } + if err := host.SNMPBulk(acc, s.initNode); err != nil { + log.Printf("SNMP Error for host '%s': %s", host.Address, err) + } + } + return nil +} + +func (h *Host) SNMPMap( + acc telegraf.Accumulator, + nameToOid map[string]string, + subTableMap map[string]Subtable, +) error { + if h.OidInstanceMapping == nil { + h.OidInstanceMapping = make(map[string]map[string]string) + } + // Get snmp client + snmpClient, err := h.GetSNMPClient() + if err != nil { + return err + } + // Deconnection + defer snmpClient.Conn.Close() + // Prepare OIDs + for _, table := range h.tables { + // We don't have mapping + if table.mappingTable == "" { + if len(table.subTables) == 0 { + // If We don't have mapping table + // neither subtables list + // This is just a bulk request + oid := Data{} + oid.Oid = table.oid + if val, ok := nameToOid[oid.Oid]; ok { + oid.rawOid = "." + val + } else { + oid.rawOid = oid.Oid + } + h.bulkOids = append(h.bulkOids, oid) + } else { + // If We don't have mapping table + // but we have subtables + // This is a bunch of bulk requests + // For each subtable ... + for _, sb := range table.subTables { + // ... we create a new Data (oid) object + oid := Data{} + // Looking for more information about this subtable + ssb, exists := subTableMap[sb] + if exists { + // We found a subtable section in config files + oid.Oid = ssb.Oid + oid.rawOid = ssb.Oid + oid.Unit = ssb.Unit + } else { + // We did NOT find a subtable section in config files + oid.Oid = sb + oid.rawOid = sb + } + // TODO check oid validity + + // Add the new oid to getOids list + h.bulkOids = append(h.bulkOids, oid) + } + } + } else { + // We have a mapping table + // We need to query this table + // To get mapping between instance id + // and instance name + oid_asked := table.mappingTable + oid_next := oid_asked + need_more_requests := true + // Set max repetition + maxRepetition := uint8(32) + // Launch requests + for need_more_requests { + // Launch request + result, err3 := snmpClient.GetBulk([]string{oid_next}, 0, maxRepetition) + if err3 != nil { + return err3 + } + + lastOid := "" + for _, variable := range result.Variables { + lastOid = variable.Name + if strings.HasPrefix(variable.Name, oid_asked) { + switch variable.Type { + // handle instance names + case gosnmp.OctetString: + // Check if instance is in includes instances + getInstances := true + if len(table.IncludeInstances) > 0 { + getInstances = false + for _, instance := range table.IncludeInstances { + if instance == string(variable.Value.([]byte)) { + getInstances = true + } + } + } + // Check if instance is in excludes instances + if len(table.ExcludeInstances) > 0 { + getInstances = true + for _, instance := range table.ExcludeInstances { + if instance == string(variable.Value.([]byte)) { + getInstances = false + } + } + } + // We don't want this instance + if !getInstances { + continue + } + + // remove oid table from the complete oid + // in order to get the current instance id + key := strings.Replace(variable.Name, oid_asked, "", 1) + + if len(table.subTables) == 0 { + // We have a mapping table + // but no subtables + // This is just a bulk request + + // Building mapping table + mapping := map[string]string{strings.Trim(key, "."): string(variable.Value.([]byte))} + _, exists := h.OidInstanceMapping[table.oid] + if exists { + h.OidInstanceMapping[table.oid][strings.Trim(key, ".")] = string(variable.Value.([]byte)) + } else { + h.OidInstanceMapping[table.oid] = mapping + } + + // Add table oid in bulk oid list + oid := Data{} + oid.Oid = table.oid + if val, ok := nameToOid[oid.Oid]; ok { + oid.rawOid = "." + val + } else { + oid.rawOid = oid.Oid + } + h.bulkOids = append(h.bulkOids, oid) + } else { + // We have a mapping table + // and some subtables + // This is a bunch of get requests + // This is the best case :) + + // For each subtable ... + for _, sb := range table.subTables { + // ... we create a new Data (oid) object + oid := Data{} + // Looking for more information about this subtable + ssb, exists := subTableMap[sb] + if exists { + // We found a subtable section in config files + oid.Oid = ssb.Oid + key + oid.rawOid = ssb.Oid + key + oid.Unit = ssb.Unit + oid.Instance = string(variable.Value.([]byte)) + } else { + // We did NOT find a subtable section in config files + oid.Oid = sb + key + oid.rawOid = sb + key + oid.Instance = string(variable.Value.([]byte)) + } + // TODO check oid validity + + // Add the new oid to getOids list + h.getOids = append(h.getOids, oid) + } + } + default: + } + } else { + break + } + } + // Determine if we need more requests + if strings.HasPrefix(lastOid, oid_asked) { + need_more_requests = true + oid_next = lastOid + } else { + need_more_requests = false + } + } + } + } + // Mapping finished + + // Create newoids based on mapping + + return nil +} + +func (h *Host) SNMPGet(acc telegraf.Accumulator, initNode Node) error { + // Get snmp client + snmpClient, err := h.GetSNMPClient() + if err != nil { + return err + } + // Deconnection + defer snmpClient.Conn.Close() + // Prepare OIDs + oidsList := make(map[string]Data) + for _, oid := range h.getOids { + oidsList[oid.rawOid] = oid + } + oidsNameList := make([]string, 0, len(oidsList)) + for _, oid := range oidsList { + oidsNameList = append(oidsNameList, oid.rawOid) + } + + // 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, initNode) + if err != nil { + return err + } + } + return nil +} + +func (h *Host) SNMPBulk(acc telegraf.Accumulator, initNode Node) error { + // Get snmp client + snmpClient, err := h.GetSNMPClient() + if err != nil { + return err + } + // Deconnection + defer snmpClient.Conn.Close() + // Prepare OIDs + oidsList := make(map[string]Data) + for _, oid := range h.bulkOids { + oidsList[oid.rawOid] = oid + } + oidsNameList := make([]string, 0, len(oidsList)) + for _, oid := range oidsList { + oidsNameList = append(oidsNameList, oid.rawOid) + } + // 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, initNode) + 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 + } + } + } + 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 telegraf.Accumulator, + initNode Node, +) (string, error) { + var lastOid string + for _, variable := range result.Variables { + lastOid = variable.Name + nextresult: + // Get only oid wanted + for oid_key, oid := range oids { + // Skip oids already processed + for _, processedOid := range h.processedOids { + if variable.Name == processedOid { + break nextresult + } + } + // If variable.Name is the same as oid_key + // OR + // the result is SNMP table which "." comes right after oid_key. + // ex: oid_key: .1.3.6.1.2.1.2.2.1.16, variable.Name: .1.3.6.1.2.1.2.2.1.16.1 + if variable.Name == oid_key || 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, gosnmp.OctetString: + // 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 + // Get oidname and instance from translate file + oid_name, instance = findnodename(initNode, + strings.Split(string(variable.Name[1:]), ".")) + // Set instance tag + // From mapping table + mapping, inMappingNoSubTable := h.OidInstanceMapping[oid_key] + if inMappingNoSubTable { + // filter if the instance in not in + // OidInstanceMapping mapping map + if instance_name, exists := mapping[instance]; exists { + tags["instance"] = instance_name + } else { + continue + } + } else if oid.Instance != "" { + // From config files + tags["instance"] = oid.Instance + } else if instance != "" { + // Using last id of the current oid, ie: + // with .1.3.6.1.2.1.31.1.1.1.10.3 + // instance is 3 + tags["instance"] = instance + } + + // Set name + var field_name string + if oid_name != "" { + // Set fieldname as oid name from translate file + field_name = oid_name + } else { + // Set fieldname as oid name from inputs.snmp.get section + // Because the result oid is equal to inputs.snmp.get section + field_name = oid.Name + } + tags["snmp_host"], _, _ = net.SplitHostPort(h.Address) + fields := make(map[string]interface{}) + fields[string(field_name)] = variable.Value + + h.processedOids = append(h.processedOids, variable.Name) + 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_legacy", func() telegraf.Input { + return &Snmp{} + }) +} diff --git a/plugins/inputs/snmp_legacy/snmp_legacy_test.go b/plugins/inputs/snmp_legacy/snmp_legacy_test.go new file mode 100644 index 000000000..a6bf2922b --- /dev/null +++ b/plugins/inputs/snmp_legacy/snmp_legacy_test.go @@ -0,0 +1,482 @@ +package snmp_legacy + +import ( + "testing" + + "github.com/influxdata/telegraf/testutil" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSNMPErrorGet1(t *testing.T) { + get1 := Data{ + Name: "oid1", + Unit: "octets", + Oid: ".1.3.6.1.2.1.2.2.1.16.1", + } + h := Host{ + Collect: []string{"oid1"}, + } + s := Snmp{ + SnmptranslateFile: "bad_oid.txt", + Host: []Host{h}, + Get: []Data{get1}, + } + + var acc testutil.Accumulator + err := s.Gather(&acc) + require.Error(t, err) +} + +func TestSNMPErrorGet2(t *testing.T) { + get1 := Data{ + Name: "oid1", + Unit: "octets", + 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.NoError(t, err) + assert.Equal(t, 0, len(acc.Metrics)) +} + +func TestSNMPErrorBulk(t *testing.T) { + bulk1 := Data{ + Name: "oid1", + Unit: "octets", + Oid: ".1.3.6.1.2.1.2.2.1.16", + } + h := Host{ + Address: testutil.GetLocalHost(), + Collect: []string{"oid1"}, + } + s := Snmp{ + Host: []Host{h}, + Bulk: []Data{bulk1}, + } + + var acc testutil.Accumulator + err := s.Gather(&acc) + require.NoError(t, err) + assert.Equal(t, 0, len(acc.Metrics)) +} + +func TestSNMPGet1(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + get1 := Data{ + Name: "oid1", + Unit: "octets", + Oid: ".1.3.6.1.2.1.2.2.1.16.1", + } + h := Host{ + Address: testutil.GetLocalHost() + ":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) + + acc.AssertContainsTaggedFields(t, + "oid1", + map[string]interface{}{ + "oid1": uint(543846), + }, + map[string]string{ + "unit": "octets", + "snmp_host": testutil.GetLocalHost(), + }, + ) +} + +func TestSNMPGet2(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + get1 := Data{ + Name: "oid1", + Oid: "ifNumber", + } + h := Host{ + Address: testutil.GetLocalHost() + ":31161", + Community: "telegraf", + Version: 2, + Timeout: 2.0, + Retries: 2, + Collect: []string{"oid1"}, + } + s := Snmp{ + SnmptranslateFile: "./testdata/oids.txt", + Host: []Host{h}, + Get: []Data{get1}, + } + + var acc testutil.Accumulator + err := s.Gather(&acc) + require.NoError(t, err) + + acc.AssertContainsTaggedFields(t, + "ifNumber", + map[string]interface{}{ + "ifNumber": int(4), + }, + map[string]string{ + "instance": "0", + "snmp_host": testutil.GetLocalHost(), + }, + ) +} + +func TestSNMPGet3(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + get1 := Data{ + Name: "oid1", + Unit: "octets", + Oid: "ifSpeed", + Instance: "1", + } + h := Host{ + Address: testutil.GetLocalHost() + ":31161", + Community: "telegraf", + Version: 2, + Timeout: 2.0, + Retries: 2, + Collect: []string{"oid1"}, + } + s := Snmp{ + SnmptranslateFile: "./testdata/oids.txt", + Host: []Host{h}, + Get: []Data{get1}, + } + + var acc testutil.Accumulator + err := s.Gather(&acc) + require.NoError(t, err) + + acc.AssertContainsTaggedFields(t, + "ifSpeed", + map[string]interface{}{ + "ifSpeed": uint(10000000), + }, + map[string]string{ + "unit": "octets", + "instance": "1", + "snmp_host": testutil.GetLocalHost(), + }, + ) +} + +func TestSNMPEasyGet4(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + get1 := Data{ + Name: "oid1", + Unit: "octets", + Oid: "ifSpeed", + Instance: "1", + } + h := Host{ + Address: testutil.GetLocalHost() + ":31161", + Community: "telegraf", + Version: 2, + Timeout: 2.0, + Retries: 2, + Collect: []string{"oid1"}, + GetOids: []string{"ifNumber"}, + } + s := Snmp{ + SnmptranslateFile: "./testdata/oids.txt", + Host: []Host{h}, + Get: []Data{get1}, + } + + var acc testutil.Accumulator + err := s.Gather(&acc) + require.NoError(t, err) + + acc.AssertContainsTaggedFields(t, + "ifSpeed", + map[string]interface{}{ + "ifSpeed": uint(10000000), + }, + map[string]string{ + "unit": "octets", + "instance": "1", + "snmp_host": testutil.GetLocalHost(), + }, + ) + + acc.AssertContainsTaggedFields(t, + "ifNumber", + map[string]interface{}{ + "ifNumber": int(4), + }, + map[string]string{ + "instance": "0", + "snmp_host": testutil.GetLocalHost(), + }, + ) +} + +func TestSNMPEasyGet5(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + get1 := Data{ + Name: "oid1", + Unit: "octets", + Oid: "ifSpeed", + Instance: "1", + } + h := Host{ + Address: testutil.GetLocalHost() + ":31161", + Community: "telegraf", + Version: 2, + Timeout: 2.0, + Retries: 2, + Collect: []string{"oid1"}, + GetOids: []string{".1.3.6.1.2.1.2.1.0"}, + } + s := Snmp{ + SnmptranslateFile: "./testdata/oids.txt", + Host: []Host{h}, + Get: []Data{get1}, + } + + var acc testutil.Accumulator + err := s.Gather(&acc) + require.NoError(t, err) + + acc.AssertContainsTaggedFields(t, + "ifSpeed", + map[string]interface{}{ + "ifSpeed": uint(10000000), + }, + map[string]string{ + "unit": "octets", + "instance": "1", + "snmp_host": testutil.GetLocalHost(), + }, + ) + + acc.AssertContainsTaggedFields(t, + "ifNumber", + map[string]interface{}{ + "ifNumber": int(4), + }, + map[string]string{ + "instance": "0", + "snmp_host": testutil.GetLocalHost(), + }, + ) +} + +func TestSNMPEasyGet6(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + h := Host{ + Address: testutil.GetLocalHost() + ":31161", + Community: "telegraf", + Version: 2, + Timeout: 2.0, + Retries: 2, + GetOids: []string{"1.3.6.1.2.1.2.1.0"}, + } + s := Snmp{ + SnmptranslateFile: "./testdata/oids.txt", + Host: []Host{h}, + } + + var acc testutil.Accumulator + err := s.Gather(&acc) + require.NoError(t, err) + + acc.AssertContainsTaggedFields(t, + "ifNumber", + map[string]interface{}{ + "ifNumber": int(4), + }, + map[string]string{ + "instance": "0", + "snmp_host": testutil.GetLocalHost(), + }, + ) +} + +func TestSNMPBulk1(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + bulk1 := Data{ + Name: "oid1", + Unit: "octets", + Oid: ".1.3.6.1.2.1.2.2.1.16", + MaxRepetition: 2, + } + h := Host{ + Address: testutil.GetLocalHost() + ":31161", + Community: "telegraf", + Version: 2, + Timeout: 2.0, + Retries: 2, + Collect: []string{"oid1"}, + } + s := Snmp{ + SnmptranslateFile: "./testdata/oids.txt", + Host: []Host{h}, + Bulk: []Data{bulk1}, + } + + var acc testutil.Accumulator + err := s.Gather(&acc) + require.NoError(t, err) + + acc.AssertContainsTaggedFields(t, + "ifOutOctets", + map[string]interface{}{ + "ifOutOctets": uint(543846), + }, + map[string]string{ + "unit": "octets", + "instance": "1", + "snmp_host": testutil.GetLocalHost(), + }, + ) + + acc.AssertContainsTaggedFields(t, + "ifOutOctets", + map[string]interface{}{ + "ifOutOctets": uint(26475179), + }, + map[string]string{ + "unit": "octets", + "instance": "2", + "snmp_host": testutil.GetLocalHost(), + }, + ) + + acc.AssertContainsTaggedFields(t, + "ifOutOctets", + map[string]interface{}{ + "ifOutOctets": uint(108963968), + }, + map[string]string{ + "unit": "octets", + "instance": "3", + "snmp_host": testutil.GetLocalHost(), + }, + ) + + acc.AssertContainsTaggedFields(t, + "ifOutOctets", + map[string]interface{}{ + "ifOutOctets": uint(12991453), + }, + map[string]string{ + "unit": "octets", + "instance": "36", + "snmp_host": testutil.GetLocalHost(), + }, + ) +} + +// TODO find why, if this test is active +// Circle CI stops with the following error... +// bash scripts/circle-test.sh died unexpectedly +// Maybe the test is too long ?? +func dTestSNMPBulk2(t *testing.T) { + bulk1 := Data{ + Name: "oid1", + Unit: "octets", + Oid: "ifOutOctets", + MaxRepetition: 2, + } + h := Host{ + Address: testutil.GetLocalHost() + ":31161", + Community: "telegraf", + Version: 2, + Timeout: 2.0, + Retries: 2, + Collect: []string{"oid1"}, + } + s := Snmp{ + SnmptranslateFile: "./testdata/oids.txt", + Host: []Host{h}, + Bulk: []Data{bulk1}, + } + + var acc testutil.Accumulator + err := s.Gather(&acc) + require.NoError(t, err) + + acc.AssertContainsTaggedFields(t, + "ifOutOctets", + map[string]interface{}{ + "ifOutOctets": uint(543846), + }, + map[string]string{ + "unit": "octets", + "instance": "1", + "snmp_host": testutil.GetLocalHost(), + }, + ) + + acc.AssertContainsTaggedFields(t, + "ifOutOctets", + map[string]interface{}{ + "ifOutOctets": uint(26475179), + }, + map[string]string{ + "unit": "octets", + "instance": "2", + "snmp_host": testutil.GetLocalHost(), + }, + ) + + acc.AssertContainsTaggedFields(t, + "ifOutOctets", + map[string]interface{}{ + "ifOutOctets": uint(108963968), + }, + map[string]string{ + "unit": "octets", + "instance": "3", + "snmp_host": testutil.GetLocalHost(), + }, + ) + + acc.AssertContainsTaggedFields(t, + "ifOutOctets", + map[string]interface{}{ + "ifOutOctets": uint(12991453), + }, + map[string]string{ + "unit": "octets", + "instance": "36", + "snmp_host": testutil.GetLocalHost(), + }, + ) +} diff --git a/plugins/inputs/snmp/testdata/oids.txt b/plugins/inputs/snmp_legacy/testdata/oids.txt similarity index 100% rename from plugins/inputs/snmp/testdata/oids.txt rename to plugins/inputs/snmp_legacy/testdata/oids.txt