summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/snmp
diff options
context:
space:
mode:
Diffstat (limited to '')
l---------src/go/collectors/go.d.plugin/modules/snmp/README.md1
-rw-r--r--src/go/collectors/go.d.plugin/modules/snmp/charts.go116
-rw-r--r--src/go/collectors/go.d.plugin/modules/snmp/collect.go55
-rw-r--r--src/go/collectors/go.d.plugin/modules/snmp/config_schema.json379
-rw-r--r--src/go/collectors/go.d.plugin/modules/snmp/init.go189
-rw-r--r--src/go/collectors/go.d.plugin/modules/snmp/integrations/snmp_devices.md404
-rw-r--r--src/go/collectors/go.d.plugin/modules/snmp/metadata.yaml398
-rw-r--r--src/go/collectors/go.d.plugin/modules/snmp/snmp.go201
-rw-r--r--src/go/collectors/go.d.plugin/modules/snmp/snmp_test.go520
-rw-r--r--src/go/collectors/go.d.plugin/modules/snmp/testdata/config.json42
-rw-r--r--src/go/collectors/go.d.plugin/modules/snmp/testdata/config.yaml31
11 files changed, 2336 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/snmp/README.md b/src/go/collectors/go.d.plugin/modules/snmp/README.md
new file mode 120000
index 000000000..edf223bf9
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/snmp/README.md
@@ -0,0 +1 @@
+integrations/snmp_devices.md \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/snmp/charts.go b/src/go/collectors/go.d.plugin/modules/snmp/charts.go
new file mode 100644
index 000000000..9899ec7aa
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/snmp/charts.go
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package snmp
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+)
+
+func newCharts(configs []ChartConfig) (*module.Charts, error) {
+ charts := &module.Charts{}
+ for _, cfg := range configs {
+ if len(cfg.IndexRange) == 2 {
+ cs, err := newChartsFromIndexRange(cfg)
+ if err != nil {
+ return nil, err
+ }
+ if err := charts.Add(*cs...); err != nil {
+ return nil, err
+ }
+ } else {
+ chart, err := newChart(cfg)
+ if err != nil {
+ return nil, err
+ }
+ if err = charts.Add(chart); err != nil {
+ return nil, err
+ }
+ }
+ }
+ return charts, nil
+}
+
+func newChartsFromIndexRange(cfg ChartConfig) (*module.Charts, error) {
+ var addPrio int
+ charts := &module.Charts{}
+ for i := cfg.IndexRange[0]; i <= cfg.IndexRange[1]; i++ {
+ chart, err := newChartWithOIDIndex(i, cfg)
+ if err != nil {
+ return nil, err
+ }
+ chart.Priority += addPrio
+ addPrio += 1
+ if err = charts.Add(chart); err != nil {
+ return nil, err
+ }
+ }
+ return charts, nil
+}
+
+func newChartWithOIDIndex(oidIndex int, cfg ChartConfig) (*module.Chart, error) {
+ chart, err := newChart(cfg)
+ if err != nil {
+ return nil, err
+ }
+
+ chart.ID = fmt.Sprintf("%s_%d", chart.ID, oidIndex)
+ chart.Title = fmt.Sprintf("%s %d", chart.Title, oidIndex)
+ for _, dim := range chart.Dims {
+ dim.ID = fmt.Sprintf("%s.%d", dim.ID, oidIndex)
+ }
+
+ return chart, nil
+}
+
+func newChart(cfg ChartConfig) (*module.Chart, error) {
+ chart := &module.Chart{
+ ID: cfg.ID,
+ Title: cfg.Title,
+ Units: cfg.Units,
+ Fam: cfg.Family,
+ Ctx: fmt.Sprintf("snmp.%s", cfg.ID),
+ Type: module.ChartType(cfg.Type),
+ Priority: cfg.Priority,
+ }
+
+ if chart.Title == "" {
+ chart.Title = "Untitled chart"
+ }
+ if chart.Units == "" {
+ chart.Units = "num"
+ }
+ if chart.Priority < module.Priority {
+ chart.Priority += module.Priority
+ }
+
+ seen := make(map[string]struct{})
+ var a string
+ for _, cfg := range cfg.Dimensions {
+ if cfg.Algorithm != "" {
+ seen[cfg.Algorithm] = struct{}{}
+ a = cfg.Algorithm
+ }
+ dim := &module.Dim{
+ ID: strings.TrimPrefix(cfg.OID, "."),
+ Name: cfg.Name,
+ Algo: module.DimAlgo(cfg.Algorithm),
+ Mul: cfg.Multiplier,
+ Div: cfg.Divisor,
+ }
+ if err := chart.AddDim(dim); err != nil {
+ return nil, err
+ }
+ }
+ if len(seen) == 1 && a != "" && len(chart.Dims) > 1 {
+ for _, d := range chart.Dims {
+ if d.Algo == "" {
+ d.Algo = module.DimAlgo(a)
+ }
+ }
+ }
+
+ return chart, nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/snmp/collect.go b/src/go/collectors/go.d.plugin/modules/snmp/collect.go
new file mode 100644
index 000000000..9f0e78d7e
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/snmp/collect.go
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package snmp
+
+import (
+ "github.com/gosnmp/gosnmp"
+)
+
+func (s *SNMP) collect() (map[string]int64, error) {
+ collected := make(map[string]int64)
+
+ if err := s.collectOIDs(collected); err != nil {
+ return nil, err
+ }
+
+ return collected, nil
+}
+
+func (s *SNMP) collectOIDs(collected map[string]int64) error {
+ for i, end := 0, 0; i < len(s.oids); i += s.Options.MaxOIDs {
+ if end = i + s.Options.MaxOIDs; end > len(s.oids) {
+ end = len(s.oids)
+ }
+
+ oids := s.oids[i:end]
+ resp, err := s.snmpClient.Get(oids)
+ if err != nil {
+ s.Errorf("cannot get SNMP data: %v", err)
+ return err
+ }
+
+ for i, oid := range oids {
+ if i >= len(resp.Variables) {
+ continue
+ }
+
+ switch v := resp.Variables[i]; v.Type {
+ case gosnmp.Boolean,
+ gosnmp.Counter32,
+ gosnmp.Counter64,
+ gosnmp.Gauge32,
+ gosnmp.TimeTicks,
+ gosnmp.Uinteger32,
+ gosnmp.OpaqueFloat,
+ gosnmp.OpaqueDouble,
+ gosnmp.Integer:
+ collected[oid] = gosnmp.ToBigInt(v.Value).Int64()
+ default:
+ s.Debugf("skipping OID '%s' (unsupported type '%s')", oid, v.Type)
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/snmp/config_schema.json b/src/go/collectors/go.d.plugin/modules/snmp/config_schema.json
new file mode 100644
index 000000000..a83a2da36
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/snmp/config_schema.json
@@ -0,0 +1,379 @@
+{
+ "jsonSchema": {
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "update_every": {
+ "title": "Update every",
+ "description": "Data collection interval, measured in seconds.",
+ "type": "integer",
+ "minimum": 1,
+ "default": 1
+ },
+ "hostname": {
+ "title": "Hostname",
+ "description": "The hostname or IP address of the SNMP-enabled device.",
+ "type": "string"
+ },
+ "community": {
+ "title": "SNMPv1/2 community",
+ "description": "The SNMP community string for SNMPv1/v2c authentication.",
+ "type": "string",
+ "default": "public"
+ },
+ "options": {
+ "title": "Options",
+ "description": "Configuration options for SNMP monitoring.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "properties": {
+ "version": {
+ "title": "SNMP version",
+ "type": "string",
+ "enum": [
+ "1",
+ "2c",
+ "3"
+ ],
+ "default": "2c"
+ },
+ "port": {
+ "title": "Port",
+ "description": "The port number on which the SNMP service is running.",
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "default": 161
+ },
+ "retries": {
+ "title": "Retries",
+ "description": "The number of retries to attempt for SNMP requests.",
+ "type": "integer",
+ "minimum": 0,
+ "default": 161
+ },
+ "timeout": {
+ "title": "Timeout",
+ "description": "The timeout duration in seconds for SNMP requests.",
+ "type": "integer",
+ "minimum": 1,
+ "default": 1
+ },
+ "max_request_size": {
+ "title": "Max OIDs in request",
+ "description": "The maximum number of OIDs allowed in a single SNMP request.",
+ "type": "integer",
+ "minimum": 1,
+ "default": 60
+ }
+ },
+ "required": [
+ "version",
+ "port",
+ "retries",
+ "timeout",
+ "max_request_size"
+ ]
+ },
+ "user": {
+ "title": "SNMPv3 configuration",
+ "description": "Configuration options for SNMPv3 authentication and encryption.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "properties": {
+ "name": {
+ "title": "Username",
+ "description": "The username for SNMPv3 authentication.",
+ "type": "string"
+ },
+ "level": {
+ "title": "Security level",
+ "description": "Controls the security aspects of SNMPv3 communication, including authentication and encryption.",
+ "type": "string",
+ "enum": [
+ "none",
+ "authNoPriv",
+ "authPriv"
+ ],
+ "default": "authPriv"
+ },
+ "auth_proto": {
+ "title": "Authentication protocol",
+ "type": "string",
+ "enum": [
+ "none",
+ "md5",
+ "sha",
+ "sha224",
+ "sha256",
+ "sha384",
+ "sha512"
+ ],
+ "default": "sha512"
+ },
+ "auth_key": {
+ "title": "Authentication passphrase",
+ "type": "string"
+ },
+ "priv_proto": {
+ "title": "Privacy protocol",
+ "type": "string",
+ "enum": [
+ "none",
+ "des",
+ "aes",
+ "aes192",
+ "aes256",
+ "aes192c"
+ ],
+ "default": "aes192c"
+ },
+ "priv_key": {
+ "title": "Privacy passphrase",
+ "type": "string"
+ }
+ }
+ },
+ "charts": {
+ "title": "Charts configuration",
+ "type": [
+ "array",
+ "null"
+ ],
+ "uniqueItems": true,
+ "minItems": 1,
+ "items": {
+ "title": "Chart",
+ "type": [
+ "object",
+ "null"
+ ],
+ "properties": {
+ "id": {
+ "title": "ID",
+ "description": "Unique identifier for the chart.",
+ "type": "string"
+ },
+ "title": {
+ "title": "Title",
+ "description": "Title of the chart.",
+ "type": "string"
+ },
+ "units": {
+ "title": "Units",
+ "description": "Unit label for the vertical axis on charts.",
+ "type": "string"
+ },
+ "family": {
+ "title": "Family",
+ "description": "Subsection on the dashboard where the chart will be displayed.",
+ "type": "string"
+ },
+ "type": {
+ "title": "Type",
+ "type": "string",
+ "enum": [
+ "line",
+ "area",
+ "stacked"
+ ],
+ "default": "line"
+ },
+ "priority": {
+ "title": "Priority",
+ "description": "Rendering priority of the chart on the dashboard. Lower priority values will cause the chart to appear before those with higher priority values.",
+ "type": "integer",
+ "minimum": 1,
+ "default": 90000
+ },
+ "multiply_range": {
+ "title": "OID index range",
+ "description": "Specifies the range of indexes used to create multiple charts. If set, a chart will be created for each index in the specified range. Each chart will have the index appended to the OID dimension.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "title": "Index",
+ "type": "integer",
+ "minimum": 0
+ },
+ "uniqueItems": true,
+ "maxItems": 2
+ },
+ "dimensions": {
+ "title": "Dimensions",
+ "description": "Configuration for dimensions of the chart.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "uniqueItems": true,
+ "minItems": 1,
+ "items": {
+ "title": "Dimension configuration",
+ "type": [
+ "object",
+ "null"
+ ],
+ "properties": {
+ "oid": {
+ "title": "OID",
+ "description": "SNMP OID.",
+ "type": "string"
+ },
+ "name": {
+ "title": "Dimension",
+ "description": "Name of the dimension.",
+ "type": "string"
+ },
+ "algorithm": {
+ "title": "Algorithm",
+ "description": "Algorithm of the dimension.",
+ "type": "string",
+ "enum": [
+ "absolute",
+ "incremental"
+ ],
+ "default": "absolute"
+ },
+ "multiplier": {
+ "title": "Multiplier",
+ "description": "Value to multiply the collected value.",
+ "type": "integer",
+ "not": {
+ "const": 0
+ },
+ "default": 1
+ },
+ "divisor": {
+ "title": "Divisor",
+ "description": "Value to divide the collected value.",
+ "type": "integer",
+ "not": {
+ "const": 0
+ },
+ "default": 1
+ }
+ },
+ "required": [
+ "oid",
+ "name",
+ "algorithm",
+ "multiplier",
+ "divisor"
+ ]
+ }
+ }
+ },
+ "required": [
+ "id",
+ "title",
+ "units",
+ "family",
+ "type",
+ "priority",
+ "dimensions"
+ ]
+ }
+ }
+ },
+ "required": [
+ "hostname",
+ "community",
+ "options",
+ "charts"
+ ],
+ "additionalProperties": false,
+ "patternProperties": {
+ "^name$": {}
+ }
+ },
+ "uiSchema": {
+ "uiOptions": {
+ "fullPage": true
+ },
+ "options": {
+ "version": {
+ "ui:widget": "radio",
+ "ui:options": {
+ "inline": true
+ }
+ }
+ },
+ "user": {
+ "level": {
+ "ui:widget": "radio",
+ "ui:options": {
+ "inline": true
+ }
+ },
+ "auth_proto": {
+ "ui:widget": "radio",
+ "ui:options": {
+ "inline": true
+ }
+ },
+ "priv_proto": {
+ "ui:widget": "radio",
+ "ui:options": {
+ "inline": true
+ }
+ }
+ },
+ "charts": {
+ "items": {
+ "ui:collapsible": true,
+ "type": {
+ "ui:widget": "radio",
+ "ui:options": {
+ "inline": true
+ }
+ },
+ "multiply_range": {
+ "ui:listFlavour": "list"
+ },
+ "dimensions": {
+ "items": {
+ "ui:collapsible": true,
+ "algorithm": {
+ "ui:widget": "radio",
+ "ui:options": {
+ "inline": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "ui:flavour": "tabs",
+ "ui:options": {
+ "tabs": [
+ {
+ "title": "Base",
+ "fields": [
+ "update_every",
+ "hostname",
+ "community",
+ "options"
+ ]
+ },
+ {
+ "title": "SNMPv3",
+ "fields": [
+ "user"
+ ]
+ },
+ {
+ "title": "Charts",
+ "fields": [
+ "charts"
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/snmp/init.go b/src/go/collectors/go.d.plugin/modules/snmp/init.go
new file mode 100644
index 000000000..5802d6682
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/snmp/init.go
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package snmp
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/gosnmp/gosnmp"
+)
+
+var newSNMPClient = gosnmp.NewHandler
+
+func (s *SNMP) validateConfig() error {
+ if len(s.ChartsInput) == 0 {
+ return errors.New("'charts' are required but not set")
+ }
+
+ if s.Options.Version == gosnmp.Version3.String() {
+ if s.User.Name == "" {
+ return errors.New("'user.name' is required when using SNMPv3 but not set")
+ }
+ if _, err := parseSNMPv3SecurityLevel(s.User.SecurityLevel); err != nil {
+ return err
+ }
+ if _, err := parseSNMPv3AuthProtocol(s.User.AuthProto); err != nil {
+ return err
+ }
+ if _, err := parseSNMPv3PrivProtocol(s.User.PrivProto); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *SNMP) initSNMPClient() (gosnmp.Handler, error) {
+ client := newSNMPClient()
+
+ if client.SetTarget(s.Hostname); client.Target() == "" {
+ s.Warningf("'hostname' not set, using the default value: '%s'", defaultHostname)
+ client.SetTarget(defaultHostname)
+ }
+ if client.SetPort(uint16(s.Options.Port)); client.Port() <= 0 || client.Port() > 65535 {
+ s.Warningf("'options.port' is invalid, changing to the default value: '%d' => '%d'", s.Options.Port, defaultPort)
+ client.SetPort(defaultPort)
+ }
+ if client.SetRetries(s.Options.Retries); client.Retries() < 1 || client.Retries() > 10 {
+ s.Warningf("'options.retries' is invalid, changing to the default value: '%d' => '%d'", s.Options.Retries, defaultRetries)
+ client.SetRetries(defaultRetries)
+ }
+ if client.SetTimeout(time.Duration(s.Options.Timeout) * time.Second); client.Timeout().Seconds() < 1 {
+ s.Warningf("'options.timeout' is invalid, changing to the default value: '%d' => '%d'", s.Options.Timeout, defaultTimeout)
+ client.SetTimeout(defaultTimeout * time.Second)
+ }
+ if client.SetMaxOids(s.Options.MaxOIDs); client.MaxOids() < 1 {
+ s.Warningf("'options.max_request_size' is invalid, changing to the default value: '%d' => '%d'", s.Options.MaxOIDs, defaultMaxOIDs)
+ client.SetMaxOids(defaultMaxOIDs)
+ }
+
+ ver, err := parseSNMPVersion(s.Options.Version)
+ if err != nil {
+ s.Warningf("'options.version' is invalid, changing to the default value: '%s' => '%s'",
+ s.Options.Version, defaultVersion)
+ ver = defaultVersion
+ }
+ comm := s.Community
+ if comm == "" && (ver <= gosnmp.Version2c) {
+ s.Warningf("'community' not set, using the default value: '%s'", defaultCommunity)
+ comm = defaultCommunity
+ }
+
+ switch ver {
+ case gosnmp.Version1:
+ client.SetCommunity(comm)
+ client.SetVersion(gosnmp.Version1)
+ case gosnmp.Version2c:
+ client.SetCommunity(comm)
+ client.SetVersion(gosnmp.Version2c)
+ case gosnmp.Version3:
+ client.SetVersion(gosnmp.Version3)
+ client.SetSecurityModel(gosnmp.UserSecurityModel)
+ client.SetMsgFlags(safeParseSNMPv3SecurityLevel(s.User.SecurityLevel))
+ client.SetSecurityParameters(&gosnmp.UsmSecurityParameters{
+ UserName: s.User.Name,
+ AuthenticationProtocol: safeParseSNMPv3AuthProtocol(s.User.AuthProto),
+ AuthenticationPassphrase: s.User.AuthKey,
+ PrivacyProtocol: safeParseSNMPv3PrivProtocol(s.User.PrivProto),
+ PrivacyPassphrase: s.User.PrivKey,
+ })
+ default:
+ return nil, fmt.Errorf("invalid SNMP version: %s", s.Options.Version)
+ }
+
+ return client, nil
+}
+
+func (s *SNMP) initOIDs() (oids []string) {
+ for _, c := range *s.charts {
+ for _, d := range c.Dims {
+ oids = append(oids, d.ID)
+ }
+ }
+ return oids
+}
+
+func parseSNMPVersion(version string) (gosnmp.SnmpVersion, error) {
+ switch version {
+ case "0", "1":
+ return gosnmp.Version1, nil
+ case "2", "2c", "":
+ return gosnmp.Version2c, nil
+ case "3":
+ return gosnmp.Version3, nil
+ default:
+ return gosnmp.Version2c, fmt.Errorf("invalid snmp version value (%s)", version)
+ }
+}
+
+func safeParseSNMPv3SecurityLevel(level string) gosnmp.SnmpV3MsgFlags {
+ v, _ := parseSNMPv3SecurityLevel(level)
+ return v
+}
+
+func parseSNMPv3SecurityLevel(level string) (gosnmp.SnmpV3MsgFlags, error) {
+ switch level {
+ case "1", "none", "noAuthNoPriv", "":
+ return gosnmp.NoAuthNoPriv, nil
+ case "2", "authNoPriv":
+ return gosnmp.AuthNoPriv, nil
+ case "3", "authPriv":
+ return gosnmp.AuthPriv, nil
+ default:
+ return gosnmp.NoAuthNoPriv, fmt.Errorf("invalid snmpv3 user security level value (%s)", level)
+ }
+}
+
+func safeParseSNMPv3AuthProtocol(protocol string) gosnmp.SnmpV3AuthProtocol {
+ v, _ := parseSNMPv3AuthProtocol(protocol)
+ return v
+}
+
+func parseSNMPv3AuthProtocol(protocol string) (gosnmp.SnmpV3AuthProtocol, error) {
+ switch protocol {
+ case "1", "none", "noAuth", "":
+ return gosnmp.NoAuth, nil
+ case "2", "md5":
+ return gosnmp.MD5, nil
+ case "3", "sha":
+ return gosnmp.SHA, nil
+ case "4", "sha224":
+ return gosnmp.SHA224, nil
+ case "5", "sha256":
+ return gosnmp.SHA256, nil
+ case "6", "sha384":
+ return gosnmp.SHA384, nil
+ case "7", "sha512":
+ return gosnmp.SHA512, nil
+ default:
+ return gosnmp.NoAuth, fmt.Errorf("invalid snmpv3 user auth protocol value (%s)", protocol)
+ }
+}
+
+func safeParseSNMPv3PrivProtocol(protocol string) gosnmp.SnmpV3PrivProtocol {
+ v, _ := parseSNMPv3PrivProtocol(protocol)
+ return v
+}
+
+func parseSNMPv3PrivProtocol(protocol string) (gosnmp.SnmpV3PrivProtocol, error) {
+ switch protocol {
+ case "1", "none", "noPriv", "":
+ return gosnmp.NoPriv, nil
+ case "2", "des":
+ return gosnmp.DES, nil
+ case "3", "aes":
+ return gosnmp.AES, nil
+ case "4", "aes192":
+ return gosnmp.AES192, nil
+ case "5", "aes256":
+ return gosnmp.AES256, nil
+ case "6", "aes192c":
+ return gosnmp.AES192C, nil
+ case "7", "aes256c":
+ return gosnmp.AES256C, nil
+ default:
+ return gosnmp.NoPriv, fmt.Errorf("invalid snmpv3 user priv protocol value (%s)", protocol)
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/snmp/integrations/snmp_devices.md b/src/go/collectors/go.d.plugin/modules/snmp/integrations/snmp_devices.md
new file mode 100644
index 000000000..cc15a6960
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/snmp/integrations/snmp_devices.md
@@ -0,0 +1,404 @@
+<!--startmeta
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/snmp/README.md"
+meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/snmp/metadata.yaml"
+sidebar_label: "SNMP devices"
+learn_status: "Published"
+learn_rel_path: "Collecting Metrics/Generic Collecting Metrics"
+most_popular: True
+message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE"
+endmeta-->
+
+# SNMP devices
+
+
+<img src="https://netdata.cloud/img/snmp.png" width="150"/>
+
+
+Plugin: go.d.plugin
+Module: snmp
+
+<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
+
+## Overview
+
+This collector monitors any SNMP devices and uses the [gosnmp](https://github.com/gosnmp/gosnmp) package.
+
+It supports:
+
+- all SNMP versions: SNMPv1, SNMPv2c and SNMPv3.
+- any number of SNMP devices.
+- each SNMP device can be used to collect data for any number of charts.
+- each chart may have any number of dimensions.
+- each SNMP device may have a different update frequency.
+- each SNMP device will accept one or more batches to report values (you can set `max_request_size` per SNMP server, to control the size of batches).
+
+Keep in mind that many SNMP switches and routers are very slow. They may not be able to report values per second.
+`go.d.plugin` reports the time it took for the SNMP device to respond when executed in the debug mode.
+
+Also, if many SNMP clients are used on the same SNMP device at the same time, values may be skipped.
+This is a problem of the SNMP device, not this collector. In this case, consider reducing the frequency of data collection (increasing `update_every`).
+
+
+
+
+This collector is supported on all platforms.
+
+This collector supports collecting metrics from multiple instances of this integration, including remote instances.
+
+
+### Default Behavior
+
+#### Auto-Detection
+
+This integration doesn't support auto-detection.
+
+#### Limits
+
+The default configuration for this integration does not impose any limits on data collection.
+
+#### Performance Impact
+
+The default configuration for this integration is not expected to impose a significant performance impact on the system.
+
+
+## Metrics
+
+The metrics that will be collected are defined in the configuration file.
+
+
+## Alerts
+
+There are no alerts configured by default for this integration.
+
+
+## Setup
+
+### Prerequisites
+
+#### Find OIDs
+
+Use `snmpwalk`, like this:
+
+```sh
+snmpwalk -t 20 -O fn -v 2c -c public 192.0.2.1
+```
+
+- `-t 20` is the timeout in seconds.
+- `-O fn` will display full OIDs in numeric format.
+- `-v 2c` is the SNMP version.
+- `-c public` is the SNMP community.
+- `192.0.2.1` is the SNMP device.
+
+
+
+### Configuration
+
+#### File
+
+The configuration file name for this integration is `go.d/snmp.conf`.
+
+
+You can edit the configuration file using the `edit-config` script from the
+Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory).
+
+```bash
+cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
+sudo ./edit-config go.d/snmp.conf
+```
+#### Options
+
+The following options can be defined globally: update_every, autodetection_retry.
+
+
+<details open><summary>Config options</summary>
+
+| Name | Description | Default | Required |
+|:----|:-----------|:-------|:--------:|
+| update_every | Data collection frequency. | 1 | no |
+| autodetection_retry | Recheck interval in seconds. Zero means no recheck will be scheduled. | 0 | no |
+| hostname | Target ipv4 address. | 127.0.0.1 | yes |
+| community | SNMPv1/2 community string. | public | no |
+| options.version | SNMP version. Available versions: 1, 2, 3. | 2 | no |
+| options.port | Target port. | 161 | no |
+| options.retries | Retries to attempt. | 1 | no |
+| options.timeout | SNMP request/response timeout. | 10 | no |
+| options.max_request_size | Maximum number of OIDs allowed in one one SNMP request. | 60 | no |
+| user.name | SNMPv3 user name. | | no |
+| user.name | Security level of SNMPv3 messages. | | no |
+| user.auth_proto | Security level of SNMPv3 messages. | | no |
+| user.name | Authentication protocol for SNMPv3 messages. | | no |
+| user.auth_key | Authentication protocol pass phrase. | | no |
+| user.priv_proto | Privacy protocol for SNMPv3 messages. | | no |
+| user.priv_key | Privacy protocol pass phrase. | | no |
+| charts | List of charts. | [] | yes |
+| charts.id | Chart ID. Used to uniquely identify the chart. | | yes |
+| charts.title | Chart title. | Untitled chart | no |
+| charts.units | Chart units. | num | no |
+| charts.family | Chart family. | charts.id | no |
+| charts.type | Chart type (line, area, stacked). | line | no |
+| charts.priority | Chart priority. | 70000 | no |
+| charts.multiply_range | Used when you need to define many charts using incremental OIDs. | [] | no |
+| charts.dimensions | List of chart dimensions. | [] | yes |
+| charts.dimensions.oid | Collected metric OID. | | yes |
+| charts.dimensions.name | Dimension name. | | yes |
+| charts.dimensions.algorithm | Dimension algorithm (absolute, incremental). | absolute | no |
+| charts.dimensions.multiplier | Collected value multiplier, applied to convert it properly to units. | 1 | no |
+| charts.dimensions.divisor | Collected value divisor, applied to convert it properly to units. | 1 | no |
+
+##### user.auth_proto
+
+The security of an SNMPv3 message as per RFC 3414 (`user.level`):
+
+| String value | Int value | Description |
+|:------------:|:---------:|------------------------------------------|
+| none | 1 | no message authentication or encryption |
+| authNoPriv | 2 | message authentication and no encryption |
+| authPriv | 3 | message authentication and encryption |
+
+
+##### user.name
+
+The digest algorithm for SNMPv3 messages that require authentication (`user.auth_proto`):
+
+| String value | Int value | Description |
+|:------------:|:---------:|-------------------------------------------|
+| none | 1 | no message authentication |
+| md5 | 2 | MD5 message authentication (HMAC-MD5-96) |
+| sha | 3 | SHA message authentication (HMAC-SHA-96) |
+| sha224 | 4 | SHA message authentication (HMAC-SHA-224) |
+| sha256 | 5 | SHA message authentication (HMAC-SHA-256) |
+| sha384 | 6 | SHA message authentication (HMAC-SHA-384) |
+| sha512 | 7 | SHA message authentication (HMAC-SHA-512) |
+
+
+##### user.priv_proto
+
+The encryption algorithm for SNMPv3 messages that require privacy (`user.priv_proto`):
+
+| String value | Int value | Description |
+|:------------:|:---------:|-------------------------------------------------------------------------|
+| none | 1 | no message encryption |
+| des | 2 | ES encryption (CBC-DES) |
+| aes | 3 | 128-bit AES encryption (CFB-AES-128) |
+| aes192 | 4 | 192-bit AES encryption (CFB-AES-192) with "Blumenthal" key localization |
+| aes256 | 5 | 256-bit AES encryption (CFB-AES-256) with "Blumenthal" key localization |
+| aes192c | 6 | 192-bit AES encryption (CFB-AES-192) with "Reeder" key localization |
+| aes256c | 7 | 256-bit AES encryption (CFB-AES-256) with "Reeder" key localization |
+
+
+</details>
+
+#### Examples
+
+##### SNMPv1/2
+
+In this example:
+
+- the SNMP device is `192.0.2.1`.
+- the SNMP version is `2`.
+- the SNMP community is `public`.
+- we will update the values every 10 seconds.
+- we define 2 charts `bandwidth_port1` and `bandwidth_port2`, each having 2 dimensions: `in` and `out`.
+
+> **SNMPv1**: just set `options.version` to 1.
+> **Note**: the algorithm chosen is `incremental`, because the collected values show the total number of bytes transferred, which we need to transform into kbps. To chart gauges (e.g. temperature), use `absolute` instead.
+
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: switch
+ update_every: 10
+ hostname: 192.0.2.1
+ community: public
+ options:
+ version: 2
+ charts:
+ - id: "bandwidth_port1"
+ title: "Switch Bandwidth for port 1"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10.1"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16.1"
+ multiplier: -8
+ divisor: 1000
+ - id: "bandwidth_port2"
+ title: "Switch Bandwidth for port 2"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10.2"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16.2"
+ multiplier: -8
+ divisor: 1000
+
+```
+</details>
+
+##### SNMPv3
+
+To use SNMPv3:
+
+- use `user` instead of `community`.
+- set `options.version` to 3.
+
+The rest of the configuration is the same as in the SNMPv1/2 example.
+
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: switch
+ update_every: 10
+ hostname: 192.0.2.1
+ options:
+ version: 3
+ user:
+ name: username
+ level: authPriv
+ auth_proto: sha256
+ auth_key: auth_protocol_passphrase
+ priv_proto: aes256
+ priv_key: priv_protocol_passphrase
+
+```
+</details>
+
+##### Multiply range
+
+If you need to define many charts using incremental OIDs, you can use the `charts.multiply_range` option.
+
+This is like the SNMPv1/2 example, but the option will multiply the current chart from 1 to 24 inclusive, producing 24 charts in total for the 24 ports of the switch `192.0.2.1`.
+
+Each of the 24 new charts will have its id (1-24) appended at:
+
+- its chart unique `id`, i.e. `bandwidth_port_1` to `bandwidth_port_24`.
+- its title, i.e. `Switch Bandwidth for port 1` to `Switch Bandwidth for port 24`.
+- its `oid` (for all dimensions), i.e. dimension in will be `1.3.6.1.2.1.2.2.1.10.1` to `1.3.6.1.2.1.2.2.1.10.24`.
+- its `priority` will be incremented for each chart so that the charts will appear on the dashboard in this order.
+
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: switch
+ update_every: 10
+ hostname: "192.0.2.1"
+ community: public
+ options:
+ version: 2
+ charts:
+ - id: "bandwidth_port"
+ title: "Switch Bandwidth for port"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ multiply_range: [1, 24]
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16"
+ multiplier: -8
+ divisor: 1000
+
+```
+</details>
+
+##### Multiple devices with a common configuration
+
+YAML supports [anchors](https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases).
+The `&` defines and names an anchor, and the `*` uses it. `<<: *anchor` means, inject the anchor, then extend. We can use anchors to share the common configuration for multiple devices.
+
+The following example:
+
+- adds an `anchor` to the first job.
+- injects (copies) the first job configuration to the second and updates `name` and `hostname` parameters.
+- injects (copies) the first job configuration to the third and updates `name` and `hostname` parameters.
+
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - &anchor
+ name: switch
+ update_every: 10
+ hostname: "192.0.2.1"
+ community: public
+ options:
+ version: 2
+ charts:
+ - id: "bandwidth_port1"
+ title: "Switch Bandwidth for port 1"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10.1"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16.1"
+ multiplier: -8
+ divisor: 1000
+ - <<: *anchor
+ name: switch2
+ hostname: "192.0.2.2"
+ - <<: *anchor
+ name: switch3
+ hostname: "192.0.2.3"
+
+```
+</details>
+
+
+
+## Troubleshooting
+
+### Debug Mode
+
+To troubleshoot issues with the `snmp` collector, run the `go.d.plugin` with the debug option enabled. The output
+should give you clues as to why the collector isn't working.
+
+- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on
+ your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`.
+
+ ```bash
+ cd /usr/libexec/netdata/plugins.d/
+ ```
+
+- Switch to the `netdata` user.
+
+ ```bash
+ sudo -u netdata -s
+ ```
+
+- Run the `go.d.plugin` to debug the collector:
+
+ ```bash
+ ./go.d.plugin -d -m snmp
+ ```
+
+
diff --git a/src/go/collectors/go.d.plugin/modules/snmp/metadata.yaml b/src/go/collectors/go.d.plugin/modules/snmp/metadata.yaml
new file mode 100644
index 000000000..a35b3190d
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/snmp/metadata.yaml
@@ -0,0 +1,398 @@
+plugin_name: go.d.plugin
+modules:
+ - meta:
+ id: collector-go.d.plugin-snmp
+ plugin_name: go.d.plugin
+ module_name: snmp
+ monitored_instance:
+ name: SNMP devices
+ link: ""
+ icon_filename: snmp.png
+ categories:
+ - data-collection.generic-data-collection
+ keywords:
+ - snmp
+ related_resources:
+ integrations:
+ list: []
+ info_provided_to_referring_integrations:
+ description: ""
+ most_popular: true
+ overview:
+ data_collection:
+ metrics_description: |
+ This collector monitors any SNMP devices and uses the [gosnmp](https://github.com/gosnmp/gosnmp) package.
+
+ It supports:
+
+ - all SNMP versions: SNMPv1, SNMPv2c and SNMPv3.
+ - any number of SNMP devices.
+ - each SNMP device can be used to collect data for any number of charts.
+ - each chart may have any number of dimensions.
+ - each SNMP device may have a different update frequency.
+ - each SNMP device will accept one or more batches to report values (you can set `max_request_size` per SNMP server, to control the size of batches).
+
+ Keep in mind that many SNMP switches and routers are very slow. They may not be able to report values per second.
+ `go.d.plugin` reports the time it took for the SNMP device to respond when executed in the debug mode.
+
+ Also, if many SNMP clients are used on the same SNMP device at the same time, values may be skipped.
+ This is a problem of the SNMP device, not this collector. In this case, consider reducing the frequency of data collection (increasing `update_every`).
+ method_description: ""
+ supported_platforms:
+ include: []
+ exclude: []
+ multi_instance: true
+ additional_permissions:
+ description: ""
+ default_behavior:
+ auto_detection:
+ description: ""
+ limits:
+ description: ""
+ performance_impact:
+ description: ""
+ setup:
+ prerequisites:
+ list:
+ - title: Find OIDs
+ description: |
+ Use `snmpwalk`, like this:
+
+ ```sh
+ snmpwalk -t 20 -O fn -v 2c -c public 192.0.2.1
+ ```
+
+ - `-t 20` is the timeout in seconds.
+ - `-O fn` will display full OIDs in numeric format.
+ - `-v 2c` is the SNMP version.
+ - `-c public` is the SNMP community.
+ - `192.0.2.1` is the SNMP device.
+ configuration:
+ file:
+ name: go.d/snmp.conf
+ options:
+ description: |
+ The following options can be defined globally: update_every, autodetection_retry.
+ folding:
+ title: Config options
+ enabled: true
+ list:
+ - name: update_every
+ description: Data collection frequency.
+ default_value: 1
+ required: false
+ - name: autodetection_retry
+ description: Recheck interval in seconds. Zero means no recheck will be scheduled.
+ default_value: 0
+ required: false
+ - name: hostname
+ description: Target ipv4 address.
+ default_value: 127.0.0.1
+ required: true
+ - name: community
+ description: SNMPv1/2 community string.
+ default_value: public
+ required: false
+ - name: options.version
+ description: "SNMP version. Available versions: 1, 2, 3."
+ default_value: 2
+ required: false
+ - name: options.port
+ description: Target port.
+ default_value: 161
+ required: false
+ - name: options.retries
+ description: Retries to attempt.
+ default_value: 1
+ required: false
+ - name: options.timeout
+ description: SNMP request/response timeout.
+ default_value: 10
+ required: false
+ - name: options.max_request_size
+ description: Maximum number of OIDs allowed in one one SNMP request.
+ default_value: 60
+ required: false
+ - name: user.name
+ description: SNMPv3 user name.
+ default_value: ""
+ required: false
+ - name: user.name
+ description: Security level of SNMPv3 messages.
+ default_value: ""
+ required: false
+ - name: user.auth_proto
+ description: Security level of SNMPv3 messages.
+ default_value: ""
+ required: false
+ detailed_description: |
+ The security of an SNMPv3 message as per RFC 3414 (`user.level`):
+
+ | String value | Int value | Description |
+ |:------------:|:---------:|------------------------------------------|
+ | none | 1 | no message authentication or encryption |
+ | authNoPriv | 2 | message authentication and no encryption |
+ | authPriv | 3 | message authentication and encryption |
+ - name: user.name
+ description: Authentication protocol for SNMPv3 messages.
+ default_value: ""
+ required: false
+ detailed_description: |
+ The digest algorithm for SNMPv3 messages that require authentication (`user.auth_proto`):
+
+ | String value | Int value | Description |
+ |:------------:|:---------:|-------------------------------------------|
+ | none | 1 | no message authentication |
+ | md5 | 2 | MD5 message authentication (HMAC-MD5-96) |
+ | sha | 3 | SHA message authentication (HMAC-SHA-96) |
+ | sha224 | 4 | SHA message authentication (HMAC-SHA-224) |
+ | sha256 | 5 | SHA message authentication (HMAC-SHA-256) |
+ | sha384 | 6 | SHA message authentication (HMAC-SHA-384) |
+ | sha512 | 7 | SHA message authentication (HMAC-SHA-512) |
+ - name: user.auth_key
+ description: Authentication protocol pass phrase.
+ default_value: ""
+ required: false
+ - name: user.priv_proto
+ description: Privacy protocol for SNMPv3 messages.
+ default_value: ""
+ required: false
+ detailed_description: |
+ The encryption algorithm for SNMPv3 messages that require privacy (`user.priv_proto`):
+
+ | String value | Int value | Description |
+ |:------------:|:---------:|-------------------------------------------------------------------------|
+ | none | 1 | no message encryption |
+ | des | 2 | ES encryption (CBC-DES) |
+ | aes | 3 | 128-bit AES encryption (CFB-AES-128) |
+ | aes192 | 4 | 192-bit AES encryption (CFB-AES-192) with "Blumenthal" key localization |
+ | aes256 | 5 | 256-bit AES encryption (CFB-AES-256) with "Blumenthal" key localization |
+ | aes192c | 6 | 192-bit AES encryption (CFB-AES-192) with "Reeder" key localization |
+ | aes256c | 7 | 256-bit AES encryption (CFB-AES-256) with "Reeder" key localization |
+ - name: user.priv_key
+ description: Privacy protocol pass phrase.
+ default_value: ""
+ required: false
+ - name: charts
+ description: List of charts.
+ default_value: "[]"
+ required: true
+ - name: charts.id
+ description: Chart ID. Used to uniquely identify the chart.
+ default_value: ""
+ required: true
+ - name: charts.title
+ description: Chart title.
+ default_value: "Untitled chart"
+ required: false
+ - name: charts.units
+ description: Chart units.
+ default_value: num
+ required: false
+ - name: charts.family
+ description: Chart family.
+ default_value: charts.id
+ required: false
+ - name: charts.type
+ description: Chart type (line, area, stacked).
+ default_value: line
+ required: false
+ - name: charts.priority
+ description: Chart priority.
+ default_value: 70000
+ required: false
+ - name: charts.multiply_range
+ description: Used when you need to define many charts using incremental OIDs.
+ default_value: "[]"
+ required: false
+ - name: charts.dimensions
+ description: List of chart dimensions.
+ default_value: "[]"
+ required: true
+ - name: charts.dimensions.oid
+ description: Collected metric OID.
+ default_value: ""
+ required: true
+ - name: charts.dimensions.name
+ description: Dimension name.
+ default_value: ""
+ required: true
+ - name: charts.dimensions.algorithm
+ description: Dimension algorithm (absolute, incremental).
+ default_value: absolute
+ required: false
+ - name: charts.dimensions.multiplier
+ description: Collected value multiplier, applied to convert it properly to units.
+ default_value: 1
+ required: false
+ - name: charts.dimensions.divisor
+ description: Collected value divisor, applied to convert it properly to units.
+ default_value: 1
+ required: false
+ examples:
+ folding:
+ title: Config
+ enabled: true
+ list:
+ - name: SNMPv1/2
+ description: |
+ In this example:
+
+ - the SNMP device is `192.0.2.1`.
+ - the SNMP version is `2`.
+ - the SNMP community is `public`.
+ - we will update the values every 10 seconds.
+ - we define 2 charts `bandwidth_port1` and `bandwidth_port2`, each having 2 dimensions: `in` and `out`.
+
+ > **SNMPv1**: just set `options.version` to 1.
+ > **Note**: the algorithm chosen is `incremental`, because the collected values show the total number of bytes transferred, which we need to transform into kbps. To chart gauges (e.g. temperature), use `absolute` instead.
+ config: |
+ jobs:
+ - name: switch
+ update_every: 10
+ hostname: 192.0.2.1
+ community: public
+ options:
+ version: 2
+ charts:
+ - id: "bandwidth_port1"
+ title: "Switch Bandwidth for port 1"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10.1"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16.1"
+ multiplier: -8
+ divisor: 1000
+ - id: "bandwidth_port2"
+ title: "Switch Bandwidth for port 2"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10.2"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16.2"
+ multiplier: -8
+ divisor: 1000
+ - name: SNMPv3
+ description: |
+ To use SNMPv3:
+
+ - use `user` instead of `community`.
+ - set `options.version` to 3.
+
+ The rest of the configuration is the same as in the SNMPv1/2 example.
+ config: |
+ jobs:
+ - name: switch
+ update_every: 10
+ hostname: 192.0.2.1
+ options:
+ version: 3
+ user:
+ name: username
+ level: authPriv
+ auth_proto: sha256
+ auth_key: auth_protocol_passphrase
+ priv_proto: aes256
+ priv_key: priv_protocol_passphrase
+ - name: Multiply range
+ description: |
+ If you need to define many charts using incremental OIDs, you can use the `charts.multiply_range` option.
+
+ This is like the SNMPv1/2 example, but the option will multiply the current chart from 1 to 24 inclusive, producing 24 charts in total for the 24 ports of the switch `192.0.2.1`.
+
+ Each of the 24 new charts will have its id (1-24) appended at:
+
+ - its chart unique `id`, i.e. `bandwidth_port_1` to `bandwidth_port_24`.
+ - its title, i.e. `Switch Bandwidth for port 1` to `Switch Bandwidth for port 24`.
+ - its `oid` (for all dimensions), i.e. dimension in will be `1.3.6.1.2.1.2.2.1.10.1` to `1.3.6.1.2.1.2.2.1.10.24`.
+ - its `priority` will be incremented for each chart so that the charts will appear on the dashboard in this order.
+ config: |
+ jobs:
+ - name: switch
+ update_every: 10
+ hostname: "192.0.2.1"
+ community: public
+ options:
+ version: 2
+ charts:
+ - id: "bandwidth_port"
+ title: "Switch Bandwidth for port"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ multiply_range: [1, 24]
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16"
+ multiplier: -8
+ divisor: 1000
+ - name: Multiple devices with a common configuration
+ description: |
+ YAML supports [anchors](https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases).
+ The `&` defines and names an anchor, and the `*` uses it. `<<: *anchor` means, inject the anchor, then extend. We can use anchors to share the common configuration for multiple devices.
+
+ The following example:
+
+ - adds an `anchor` to the first job.
+ - injects (copies) the first job configuration to the second and updates `name` and `hostname` parameters.
+ - injects (copies) the first job configuration to the third and updates `name` and `hostname` parameters.
+ config: |
+ jobs:
+ - &anchor
+ name: switch
+ update_every: 10
+ hostname: "192.0.2.1"
+ community: public
+ options:
+ version: 2
+ charts:
+ - id: "bandwidth_port1"
+ title: "Switch Bandwidth for port 1"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10.1"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16.1"
+ multiplier: -8
+ divisor: 1000
+ - <<: *anchor
+ name: switch2
+ hostname: "192.0.2.2"
+ - <<: *anchor
+ name: switch3
+ hostname: "192.0.2.3"
+ troubleshooting:
+ problems:
+ list: []
+ alerts: []
+ metrics:
+ folding:
+ title: Metrics
+ enabled: false
+ description: The metrics that will be collected are defined in the configuration file.
+ availability: []
+ scopes: []
diff --git a/src/go/collectors/go.d.plugin/modules/snmp/snmp.go b/src/go/collectors/go.d.plugin/modules/snmp/snmp.go
new file mode 100644
index 000000000..6f4081f50
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/snmp/snmp.go
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package snmp
+
+import (
+ _ "embed"
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+
+ "github.com/gosnmp/gosnmp"
+)
+
+//go:embed "config_schema.json"
+var configSchema string
+
+func init() {
+ module.Register("snmp", module.Creator{
+ JobConfigSchema: configSchema,
+ Defaults: module.Defaults{
+ UpdateEvery: defaultUpdateEvery,
+ },
+ Create: func() module.Module { return New() },
+ Config: func() any { return &Config{} },
+ })
+}
+
+const (
+ defaultUpdateEvery = 10
+ defaultHostname = "127.0.0.1"
+ defaultCommunity = "public"
+ defaultVersion = gosnmp.Version2c
+ defaultPort = 161
+ defaultRetries = 1
+ defaultTimeout = defaultUpdateEvery
+ defaultMaxOIDs = 60
+)
+
+func New() *SNMP {
+ return &SNMP{
+ Config: Config{
+ Hostname: defaultHostname,
+ Community: defaultCommunity,
+ Options: Options{
+ Port: defaultPort,
+ Retries: defaultRetries,
+ Timeout: defaultUpdateEvery,
+ Version: defaultVersion.String(),
+ MaxOIDs: defaultMaxOIDs,
+ },
+ User: User{
+ Name: "",
+ SecurityLevel: "authPriv",
+ AuthProto: "sha512",
+ AuthKey: "",
+ PrivProto: "aes192c",
+ PrivKey: "",
+ },
+ },
+ }
+}
+
+type (
+ Config struct {
+ UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
+ Hostname string `yaml:"hostname" json:"hostname"`
+ Community string `yaml:"community,omitempty" json:"community"`
+ User User `yaml:"user,omitempty" json:"user"`
+ Options Options `yaml:"options,omitempty" json:"options"`
+ ChartsInput []ChartConfig `yaml:"charts,omitempty" json:"charts"`
+ }
+ User struct {
+ Name string `yaml:"name,omitempty" json:"name"`
+ SecurityLevel string `yaml:"level,omitempty" json:"level"`
+ AuthProto string `yaml:"auth_proto,omitempty" json:"auth_proto"`
+ AuthKey string `yaml:"auth_key,omitempty" json:"auth_key"`
+ PrivProto string `yaml:"priv_proto,omitempty" json:"priv_proto"`
+ PrivKey string `yaml:"priv_key,omitempty" json:"priv_key"`
+ }
+ Options struct {
+ Port int `yaml:"port,omitempty" json:"port"`
+ Retries int `yaml:"retries,omitempty" json:"retries"`
+ Timeout int `yaml:"timeout,omitempty" json:"timeout"`
+ Version string `yaml:"version,omitempty" json:"version"`
+ MaxOIDs int `yaml:"max_request_size,omitempty" json:"max_request_size"`
+ }
+ ChartConfig struct {
+ ID string `yaml:"id" json:"id"`
+ Title string `yaml:"title" json:"title"`
+ Units string `yaml:"units" json:"units"`
+ Family string `yaml:"family" json:"family"`
+ Type string `yaml:"type" json:"type"`
+ Priority int `yaml:"priority" json:"priority"`
+ IndexRange []int `yaml:"multiply_range,omitempty" json:"multiply_range"`
+ Dimensions []DimensionConfig `yaml:"dimensions" json:"dimensions"`
+ }
+ DimensionConfig struct {
+ OID string `yaml:"oid" json:"oid"`
+ Name string `yaml:"name" json:"name"`
+ Algorithm string `yaml:"algorithm" json:"algorithm"`
+ Multiplier int `yaml:"multiplier" json:"multiplier"`
+ Divisor int `yaml:"divisor" json:"divisor"`
+ }
+)
+
+type SNMP struct {
+ module.Base
+ Config `yaml:",inline" json:""`
+
+ charts *module.Charts
+
+ snmpClient gosnmp.Handler
+
+ oids []string
+}
+
+func (s *SNMP) Configuration() any {
+ return s.Config
+}
+
+func (s *SNMP) Init() error {
+ err := s.validateConfig()
+ if err != nil {
+ s.Errorf("config validation: %v", err)
+ return err
+ }
+
+ snmpClient, err := s.initSNMPClient()
+ if err != nil {
+ s.Errorf("SNMP client initialization: %v", err)
+ return err
+ }
+
+ s.Info(snmpClientConnInfo(snmpClient))
+
+ err = snmpClient.Connect()
+ if err != nil {
+ s.Errorf("SNMP client connect: %v", err)
+ return err
+ }
+ s.snmpClient = snmpClient
+
+ charts, err := newCharts(s.ChartsInput)
+ if err != nil {
+ s.Errorf("Population of charts failed: %v", err)
+ return err
+ }
+ s.charts = charts
+
+ s.oids = s.initOIDs()
+
+ return nil
+}
+
+func (s *SNMP) Check() error {
+ mx, err := s.collect()
+ if err != nil {
+ s.Error(err)
+ return err
+ }
+ if len(mx) == 0 {
+ return errors.New("no metrics collected")
+ }
+ return nil
+}
+
+func (s *SNMP) Charts() *module.Charts {
+ return s.charts
+}
+
+func (s *SNMP) Collect() map[string]int64 {
+ mx, err := s.collect()
+ if err != nil {
+ s.Error(err)
+ }
+
+ if len(mx) == 0 {
+ return nil
+ }
+ return mx
+}
+
+func (s *SNMP) Cleanup() {
+ if s.snmpClient != nil {
+ _ = s.snmpClient.Close()
+ }
+}
+
+func snmpClientConnInfo(c gosnmp.Handler) string {
+ var info strings.Builder
+ info.WriteString(fmt.Sprintf("hostname=%s,port=%d,snmp_version=%s", c.Target(), c.Port(), c.Version()))
+ switch c.Version() {
+ case gosnmp.Version1, gosnmp.Version2c:
+ info.WriteString(fmt.Sprintf(",community=%s", c.Community()))
+ case gosnmp.Version3:
+ info.WriteString(fmt.Sprintf(",security_level=%d,%s", c.MsgFlags(), c.SecurityParameters().Description()))
+ }
+ return info.String()
+}
diff --git a/src/go/collectors/go.d.plugin/modules/snmp/snmp_test.go b/src/go/collectors/go.d.plugin/modules/snmp/snmp_test.go
new file mode 100644
index 000000000..04d9db3f9
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/snmp/snmp_test.go
@@ -0,0 +1,520 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package snmp
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/golang/mock/gomock"
+ "github.com/gosnmp/gosnmp"
+ snmpmock "github.com/gosnmp/gosnmp/mocks"
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ dataConfigJSON, _ = os.ReadFile("testdata/config.json")
+ dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
+)
+
+func Test_testDataIsValid(t *testing.T) {
+ for name, data := range map[string][]byte{
+ "dataConfigJSON": dataConfigJSON,
+ "dataConfigYAML": dataConfigYAML,
+ } {
+ require.NotNil(t, data, name)
+ }
+}
+
+func TestSNMP_ConfigurationSerialize(t *testing.T) {
+ module.TestConfigurationSerialize(t, &SNMP{}, dataConfigJSON, dataConfigYAML)
+}
+
+func TestSNMP_Init(t *testing.T) {
+ tests := map[string]struct {
+ prepareSNMP func() *SNMP
+ wantFail bool
+ }{
+ "fail with default config": {
+ wantFail: true,
+ prepareSNMP: func() *SNMP {
+ return New()
+ },
+ },
+ "fail when 'charts' not set": {
+ wantFail: true,
+ prepareSNMP: func() *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+ snmp.ChartsInput = nil
+ return snmp
+ },
+ },
+ "fail when using SNMPv3 but 'user.name' not set": {
+ wantFail: true,
+ prepareSNMP: func() *SNMP {
+ snmp := New()
+ snmp.Config = prepareV3Config()
+ snmp.User.Name = ""
+ return snmp
+ },
+ },
+ "fail when using SNMPv3 but 'user.level' is invalid": {
+ wantFail: true,
+ prepareSNMP: func() *SNMP {
+ snmp := New()
+ snmp.Config = prepareV3Config()
+ snmp.User.SecurityLevel = "invalid"
+ return snmp
+ },
+ },
+ "fail when using SNMPv3 but 'user.auth_proto' is invalid": {
+ wantFail: true,
+ prepareSNMP: func() *SNMP {
+ snmp := New()
+ snmp.Config = prepareV3Config()
+ snmp.User.AuthProto = "invalid"
+ return snmp
+ },
+ },
+ "fail when using SNMPv3 but 'user.priv_proto' is invalid": {
+ wantFail: true,
+ prepareSNMP: func() *SNMP {
+ snmp := New()
+ snmp.Config = prepareV3Config()
+ snmp.User.PrivProto = "invalid"
+ return snmp
+ },
+ },
+ "success when using SNMPv1 with valid config": {
+ wantFail: false,
+ prepareSNMP: func() *SNMP {
+ snmp := New()
+ snmp.Config = prepareV1Config()
+ return snmp
+ },
+ },
+ "success when using SNMPv2 with valid config": {
+ wantFail: false,
+ prepareSNMP: func() *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+ return snmp
+ },
+ },
+ "success when using SNMPv3 with valid config": {
+ wantFail: false,
+ prepareSNMP: func() *SNMP {
+ snmp := New()
+ snmp.Config = prepareV3Config()
+ return snmp
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ snmp := test.prepareSNMP()
+
+ if test.wantFail {
+ assert.Error(t, snmp.Init())
+ } else {
+ assert.NoError(t, snmp.Init())
+ }
+ })
+ }
+}
+
+func TestSNMP_Check(t *testing.T) {
+ tests := map[string]struct {
+ prepareSNMP func(m *snmpmock.MockHandler) *SNMP
+ wantFail bool
+ }{
+ "success when 'max_request_size' > returned OIDs": {
+ wantFail: false,
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+
+ m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
+ Variables: []gosnmp.SnmpPDU{
+ {Value: 10, Type: gosnmp.Gauge32},
+ {Value: 20, Type: gosnmp.Gauge32},
+ },
+ }, nil).Times(1)
+
+ return snmp
+ },
+ },
+ "success when 'max_request_size' < returned OIDs": {
+ wantFail: false,
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+ snmp.Config.Options.MaxOIDs = 1
+
+ m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
+ Variables: []gosnmp.SnmpPDU{
+ {Value: 10, Type: gosnmp.Gauge32},
+ {Value: 20, Type: gosnmp.Gauge32},
+ },
+ }, nil).Times(2)
+
+ return snmp
+ },
+ },
+ "success when using 'multiply_range'": {
+ wantFail: false,
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareConfigWithIndexRange(prepareV2Config, 0, 1)
+
+ m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
+ Variables: []gosnmp.SnmpPDU{
+ {Value: 10, Type: gosnmp.Gauge32},
+ {Value: 20, Type: gosnmp.Gauge32},
+ {Value: 30, Type: gosnmp.Gauge32},
+ {Value: 40, Type: gosnmp.Gauge32},
+ },
+ }, nil).Times(1)
+
+ return snmp
+ },
+ },
+ "fail when snmp client Get fails": {
+ wantFail: true,
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+
+ m.EXPECT().Get(gomock.Any()).Return(nil, errors.New("mock Get() error")).Times(1)
+
+ return snmp
+ },
+ },
+ "fail when all OIDs type is unsupported": {
+ wantFail: true,
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+
+ m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
+ Variables: []gosnmp.SnmpPDU{
+ {Value: nil, Type: gosnmp.NoSuchInstance},
+ {Value: nil, Type: gosnmp.NoSuchInstance},
+ },
+ }, nil).Times(1)
+
+ return snmp
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ mockSNMP, cleanup := mockInit(t)
+ defer cleanup()
+
+ newSNMPClient = func() gosnmp.Handler { return mockSNMP }
+ defaultMockExpects(mockSNMP)
+
+ snmp := test.prepareSNMP(mockSNMP)
+ require.NoError(t, snmp.Init())
+
+ if test.wantFail {
+ assert.Error(t, snmp.Check())
+ } else {
+ assert.NoError(t, snmp.Check())
+ }
+ })
+ }
+}
+
+func TestSNMP_Collect(t *testing.T) {
+ tests := map[string]struct {
+ prepareSNMP func(m *snmpmock.MockHandler) *SNMP
+ wantCollected map[string]int64
+ }{
+ "success when collecting supported type": {
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareConfigWithIndexRange(prepareV2Config, 0, 3)
+
+ m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
+ Variables: []gosnmp.SnmpPDU{
+ {Value: 10, Type: gosnmp.Counter32},
+ {Value: 20, Type: gosnmp.Counter64},
+ {Value: 30, Type: gosnmp.Gauge32},
+ {Value: 1, Type: gosnmp.Boolean},
+ {Value: 40, Type: gosnmp.Gauge32},
+ {Value: 50, Type: gosnmp.TimeTicks},
+ {Value: 60, Type: gosnmp.Uinteger32},
+ {Value: 70, Type: gosnmp.Integer},
+ },
+ }, nil).Times(1)
+
+ return snmp
+ },
+ wantCollected: map[string]int64{
+ "1.3.6.1.2.1.2.2.1.10.0": 10,
+ "1.3.6.1.2.1.2.2.1.16.0": 20,
+ "1.3.6.1.2.1.2.2.1.10.1": 30,
+ "1.3.6.1.2.1.2.2.1.16.1": 1,
+ "1.3.6.1.2.1.2.2.1.10.2": 40,
+ "1.3.6.1.2.1.2.2.1.16.2": 50,
+ "1.3.6.1.2.1.2.2.1.10.3": 60,
+ "1.3.6.1.2.1.2.2.1.16.3": 70,
+ },
+ },
+ "success when collecting supported and unsupported type": {
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareConfigWithIndexRange(prepareV2Config, 0, 2)
+
+ m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
+ Variables: []gosnmp.SnmpPDU{
+ {Value: 10, Type: gosnmp.Counter32},
+ {Value: 20, Type: gosnmp.Counter64},
+ {Value: 30, Type: gosnmp.Gauge32},
+ {Value: nil, Type: gosnmp.NoSuchInstance},
+ {Value: nil, Type: gosnmp.NoSuchInstance},
+ {Value: nil, Type: gosnmp.NoSuchInstance},
+ },
+ }, nil).Times(1)
+
+ return snmp
+ },
+ wantCollected: map[string]int64{
+ "1.3.6.1.2.1.2.2.1.10.0": 10,
+ "1.3.6.1.2.1.2.2.1.16.0": 20,
+ "1.3.6.1.2.1.2.2.1.10.1": 30,
+ },
+ },
+ "fails when collecting unsupported type": {
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareConfigWithIndexRange(prepareV2Config, 0, 2)
+
+ m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
+ Variables: []gosnmp.SnmpPDU{
+ {Value: nil, Type: gosnmp.NoSuchInstance},
+ {Value: nil, Type: gosnmp.NoSuchInstance},
+ {Value: nil, Type: gosnmp.NoSuchObject},
+ {Value: "192.0.2.0", Type: gosnmp.NsapAddress},
+ {Value: []uint8{118, 101, 116}, Type: gosnmp.OctetString},
+ {Value: ".1.3.6.1.2.1.4.32.1.5.2.1.4.10.19.0.0.16", Type: gosnmp.ObjectIdentifier},
+ },
+ }, nil).Times(1)
+
+ return snmp
+ },
+ wantCollected: nil,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ mockSNMP, cleanup := mockInit(t)
+ defer cleanup()
+
+ newSNMPClient = func() gosnmp.Handler { return mockSNMP }
+ defaultMockExpects(mockSNMP)
+
+ snmp := test.prepareSNMP(mockSNMP)
+ require.NoError(t, snmp.Init())
+
+ collected := snmp.Collect()
+
+ assert.Equal(t, test.wantCollected, collected)
+ })
+ }
+}
+
+func TestSNMP_Cleanup(t *testing.T) {
+ tests := map[string]struct {
+ prepareSNMP func(t *testing.T, m *snmpmock.MockHandler) *SNMP
+ }{
+ "cleanup call if snmpClient initialized": {
+ prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+ require.NoError(t, snmp.Init())
+
+ m.EXPECT().Close().Times(1)
+
+ return snmp
+ },
+ },
+ "cleanup call does not panic if snmpClient not initialized": {
+ prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+ require.NoError(t, snmp.Init())
+ snmp.snmpClient = nil
+
+ return snmp
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ mockSNMP, cleanup := mockInit(t)
+ defer cleanup()
+
+ newSNMPClient = func() gosnmp.Handler { return mockSNMP }
+ defaultMockExpects(mockSNMP)
+
+ snmp := test.prepareSNMP(t, mockSNMP)
+ assert.NotPanics(t, snmp.Cleanup)
+ })
+ }
+}
+
+func TestSNMP_Charts(t *testing.T) {
+ tests := map[string]struct {
+ prepareSNMP func(t *testing.T, m *snmpmock.MockHandler) *SNMP
+ wantNumCharts int
+ }{
+ "without 'multiply_range': got expected number of charts": {
+ wantNumCharts: 1,
+ prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+ require.NoError(t, snmp.Init())
+
+ return snmp
+ },
+ },
+ "with 'multiply_range': got expected number of charts": {
+ wantNumCharts: 10,
+ prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareConfigWithIndexRange(prepareV2Config, 0, 9)
+ require.NoError(t, snmp.Init())
+
+ return snmp
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ mockSNMP, cleanup := mockInit(t)
+ defer cleanup()
+
+ newSNMPClient = func() gosnmp.Handler { return mockSNMP }
+ defaultMockExpects(mockSNMP)
+
+ snmp := test.prepareSNMP(t, mockSNMP)
+ assert.Equal(t, test.wantNumCharts, len(*snmp.Charts()))
+ })
+ }
+}
+
+func mockInit(t *testing.T) (*snmpmock.MockHandler, func()) {
+ mockCtl := gomock.NewController(t)
+ cleanup := func() { mockCtl.Finish() }
+ mockSNMP := snmpmock.NewMockHandler(mockCtl)
+
+ return mockSNMP, cleanup
+}
+
+func defaultMockExpects(m *snmpmock.MockHandler) {
+ m.EXPECT().Target().AnyTimes()
+ m.EXPECT().Port().AnyTimes()
+ m.EXPECT().Retries().AnyTimes()
+ m.EXPECT().Timeout().AnyTimes()
+ m.EXPECT().MaxOids().AnyTimes()
+ m.EXPECT().Version().AnyTimes()
+ m.EXPECT().Community().AnyTimes()
+ m.EXPECT().SetTarget(gomock.Any()).AnyTimes()
+ m.EXPECT().SetPort(gomock.Any()).AnyTimes()
+ m.EXPECT().SetRetries(gomock.Any()).AnyTimes()
+ m.EXPECT().SetMaxOids(gomock.Any()).AnyTimes()
+ m.EXPECT().SetLogger(gomock.Any()).AnyTimes()
+ m.EXPECT().SetTimeout(gomock.Any()).AnyTimes()
+ m.EXPECT().SetCommunity(gomock.Any()).AnyTimes()
+ m.EXPECT().SetVersion(gomock.Any()).AnyTimes()
+ m.EXPECT().SetSecurityModel(gomock.Any()).AnyTimes()
+ m.EXPECT().SetMsgFlags(gomock.Any()).AnyTimes()
+ m.EXPECT().SetSecurityParameters(gomock.Any()).AnyTimes()
+ m.EXPECT().Connect().Return(nil).AnyTimes()
+}
+
+func prepareConfigWithIndexRange(p func() Config, start, end int) Config {
+ if start > end || start < 0 || end < 1 {
+ panic(fmt.Sprintf("invalid index range ('%d'-'%d')", start, end))
+ }
+ cfg := p()
+ for i := range cfg.ChartsInput {
+ cfg.ChartsInput[i].IndexRange = []int{start, end}
+ }
+ return cfg
+}
+
+func prepareV3Config() Config {
+ cfg := prepareV2Config()
+ cfg.Options.Version = gosnmp.Version3.String()
+ cfg.User = User{
+ Name: "name",
+ SecurityLevel: "authPriv",
+ AuthProto: strings.ToLower(gosnmp.MD5.String()),
+ AuthKey: "auth_key",
+ PrivProto: strings.ToLower(gosnmp.AES.String()),
+ PrivKey: "priv_key",
+ }
+ return cfg
+}
+
+func prepareV2Config() Config {
+ cfg := prepareV1Config()
+ cfg.Options.Version = gosnmp.Version2c.String()
+ return cfg
+}
+
+func prepareV1Config() Config {
+ return Config{
+ UpdateEvery: defaultUpdateEvery,
+ Hostname: defaultHostname,
+ Community: defaultCommunity,
+ Options: Options{
+ Port: defaultPort,
+ Retries: defaultRetries,
+ Timeout: defaultTimeout,
+ Version: gosnmp.Version1.String(),
+ MaxOIDs: defaultMaxOIDs,
+ },
+ ChartsInput: []ChartConfig{
+ {
+ ID: "test_chart1",
+ Title: "This is Test Chart1",
+ Units: "kilobits/s",
+ Family: "family",
+ Type: module.Area.String(),
+ Priority: module.Priority,
+ Dimensions: []DimensionConfig{
+ {
+ OID: "1.3.6.1.2.1.2.2.1.10",
+ Name: "in",
+ Algorithm: module.Incremental.String(),
+ Multiplier: 8,
+ Divisor: 1000,
+ },
+ {
+ OID: "1.3.6.1.2.1.2.2.1.16",
+ Name: "out",
+ Algorithm: module.Incremental.String(),
+ Multiplier: 8,
+ Divisor: 1000,
+ },
+ },
+ },
+ },
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/snmp/testdata/config.json b/src/go/collectors/go.d.plugin/modules/snmp/testdata/config.json
new file mode 100644
index 000000000..c0fff4868
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/snmp/testdata/config.json
@@ -0,0 +1,42 @@
+{
+ "update_every": 123,
+ "hostname": "ok",
+ "community": "ok",
+ "user": {
+ "name": "ok",
+ "level": "ok",
+ "auth_proto": "ok",
+ "auth_key": "ok",
+ "priv_proto": "ok",
+ "priv_key": "ok"
+ },
+ "options": {
+ "port": 123,
+ "retries": 123,
+ "timeout": 123,
+ "version": "ok",
+ "max_request_size": 123
+ },
+ "charts": [
+ {
+ "id": "ok",
+ "title": "ok",
+ "units": "ok",
+ "family": "ok",
+ "type": "ok",
+ "priority": 123,
+ "multiply_range": [
+ 123
+ ],
+ "dimensions": [
+ {
+ "oid": "ok",
+ "name": "ok",
+ "algorithm": "ok",
+ "multiplier": 123,
+ "divisor": 123
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/go/collectors/go.d.plugin/modules/snmp/testdata/config.yaml b/src/go/collectors/go.d.plugin/modules/snmp/testdata/config.yaml
new file mode 100644
index 000000000..98620fb9c
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/snmp/testdata/config.yaml
@@ -0,0 +1,31 @@
+update_every: 123
+hostname: "ok"
+community: "ok"
+user:
+ name: "ok"
+ level: "ok"
+ auth_proto: "ok"
+ auth_key: "ok"
+ priv_proto: "ok"
+ priv_key: "ok"
+options:
+ port: 123
+ retries: 123
+ timeout: 123
+ version: "ok"
+ max_request_size: 123
+charts:
+ - id: "ok"
+ title: "ok"
+ units: "ok"
+ family: "ok"
+ type: "ok"
+ priority: 123
+ multiply_range:
+ - 123
+ dimensions:
+ - oid: "ok"
+ name: "ok"
+ algorithm: "ok"
+ multiplier: 123
+ divisor: 123