diff --git a/.chloggen/snmpreceiver-add-full_config.yaml b/.chloggen/snmpreceiver-add-full_config.yaml new file mode 100755 index 000000000000..594784faff9b --- /dev/null +++ b/.chloggen/snmpreceiver-add-full_config.yaml @@ -0,0 +1,16 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: snmpreceiver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: adds the full configuration that the SNMP Receiver will use + +# One or more tracking issues related to the change +issues: [13409] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: diff --git a/internal/components/receivers_test.go b/internal/components/receivers_test.go index 0c9d099a158b..7325b230db4c 100644 --- a/internal/components/receivers_test.go +++ b/internal/components/receivers_test.go @@ -42,6 +42,7 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/otlpjsonfilereceiver" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/snmpreceiver" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/syslogreceiver" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcplogreceiver" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/udplogreceiver" @@ -291,6 +292,19 @@ func TestDefaultReceivers(t *testing.T) { }, { receiver: "snmp", + getConfigFn: func() config.Receiver { + cfg := rcvrFactories["snmp"].CreateDefaultConfig().(*snmpreceiver.Config) + cfg.Metrics = map[string]*snmpreceiver.MetricConfig{ + "m1": { + Unit: "1", + Gauge: &snmpreceiver.GaugeMetric{ValueType: "int"}, + ScalarOIDs: []snmpreceiver.ScalarOID{{ + OID: ".1", + }}, + }, + } + return cfg + }, }, { receiver: "splunk_hec", diff --git a/receiver/snmpreceiver/config.go b/receiver/snmpreceiver/config.go index 7c3ff2833d07..869c8967adc2 100644 --- a/receiver/snmpreceiver/config.go +++ b/receiver/snmpreceiver/config.go @@ -14,17 +14,580 @@ package snmpreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/snmpreceiver" import ( + "errors" + "fmt" + "net/url" + "strings" "time" "go.opentelemetry.io/collector/receiver/scraperhelper" + "go.uber.org/multierr" ) // Config Defaults const ( defaultCollectionInterval = 10 * time.Second // In seconds + defaultEndpoint = "udp://localhost:161" + defaultVersion = "v2c" + defaultCommunity = "public" + defaultSecurityLevel = "no_auth_no_priv" + defaultAuthType = "MD5" + defaultPrivacyType = "DES" +) + +var ( + // Config error messages + errMsgInvalidEndpointWError = `invalid endpoint '%s': must be in '[scheme]://[host]:[port]' format: %w` + errMsgInvalidEndpoint = `invalid endpoint '%s': must be in '[scheme]://[host]:[port]' format` + errMsgAttributeConfigNoEnumOIDOrPrefix = `attribute '%s' must contain one of either an enum, oid, or indexed_value_prefix` + errMsgResourceAttributeNoOIDOrPrefix = `resource_attribute '%s' must contain one of either an oid or indexed_value_prefix` + errMsgMetricNoUnit = `metric '%s' must have a unit` + errMsgMetricNoGaugeOrSum = `metric '%s' must have one of either a gauge or sum` + errMsgMetricNoOIDs = `metric '%s' must have one of either scalar_oids or indexed_oids` + errMsgGaugeBadValueType = `metric '%s' gauge value_type must be either int, float, or bool` + errMsgSumBadValueType = `metric '%s' sum value_type must be either int, float, or bool` + errMsgSumBadAggregation = `metric '%s' sum aggregation value must be either cumulative or delta` + errMsgScalarOIDNoOID = `metric '%s' scalar_oid must contain an oid` + errMsgScalarAttributeNoName = `metric '%s' scalar_oid attribute must contain a name` + errMsgScalarAttributeBadName = `metric '%s' scalar_oid attribute name '%s' must match an attribute config` + errMsgScalarOIDBadAttribute = `metric '%s' scalar_oid attribute name '%s' must match attribute config with enum values` + errMsgScalarAttributeBadValue = `metric '%s' scalar_oid attribute '%s' value '%s' must match one of the possible enum values for the attribute config` + errMsgColumnOIDNoOID = `metric '%s' column_oid must contain an oid` + errMsgColumnAttributeNoName = `metric '%s' column_oid attribute must contain a name` + errMsgColumnAttributeBadName = `metric '%s' column_oid attribute name '%s' must match an attribute config` + errMsgColumnAttributeBadValue = `metric '%s' column_oid attribute '%s' value '%s' must match one of the possible enum values for the attribute config` + errMsgColumnResourceAttributeBadName = `metric '%s' column_oid resource_attribute '%s' must match a resource_attribute config` + errMsgColumnIndexedAttributeRequired = `metric '%s' column_oid must either have a resource_attribute or an indexed_value_prefix/oid attribute` + + // Config errors + errEmptyEndpoint = errors.New("endpoint must be specified") + errEndpointBadScheme = errors.New("endpoint scheme must be either tcp, tcp4, tcp6, udp, udp4, or udp6") + errEmptyVersion = errors.New("version must specified") + errBadVersion = errors.New("version must be either v1, v2c, or v3") + errEmptyUser = errors.New("user must be specified when version is v3") + errEmptySecurityLevel = errors.New("security_level must be specified when version is v3") + errBadSecurityLevel = errors.New("security_level must be either no_auth_no_priv, auth_no_priv, or auth_priv") + errEmptyAuthType = errors.New("auth_type must be specified when security_level is auth_no_priv or auth_priv") + errBadAuthType = errors.New("auth_type must be either MD5, SHA, SHA224, SHA256, SHA384, SHA512") + errEmptyAuthPassword = errors.New("auth_password must be specified when security_level is auth_no_priv or auth_priv") + errEmptyPrivacyType = errors.New("privacy_type must be specified when security_level is auth_priv") + errBadPrivacyType = errors.New("privacy_type must be either DES, AES, AES192, AES192C, AES256, AES256C") + errEmptyPrivacyPassword = errors.New("privacy_password must be specified when security_level is auth_priv") + errMetricRequired = errors.New("must have at least one config under metrics") ) // Config defines the configuration for the various elements of the receiver. type Config struct { scraperhelper.ScraperControllerSettings `mapstructure:",squash"` + + // Endpoint is the SNMP target to request data from. Must be formatted as [udp|tcp|][4|6|]://{host}:{port}. + // Default: udp://localhost:161 + // If no scheme is given, udp4 is assumed. + // If no port is given, 161 is assumed. + Endpoint string `mapstructure:"endpoint"` + + // Version is the version of SNMP to use for this connection. + // Valid options: v1, v2c, v3. + // Default: v2c + Version string `mapstructure:"version"` + + // Community is the SNMP community string to use. + // Only valid for versions "v1" and "v2c" + // Default: public + Community string `mapstructure:"community"` + + // User is the SNMP User for this connection. + // Only valid for version “v3” + User string `mapstructure:"user"` + + // SecurityLevel is the security level to use for this SNMP connection. + // Only valid for version “v3” + // Valid options: “no_auth_no_priv”, “auth_no_priv”, “auth_priv” + // Default: "no_auth_no_priv" + SecurityLevel string `mapstructure:"security_level"` + + // AuthType is the type of authentication protocol to use for this SNMP connection. + // Only valid for version “v3” and if “no_auth_no_priv” is not selected for SecurityLevel + // Valid options: “md5”, “sha”, “sha224”, “sha256”, “sha384”, “sha512” + // Default: "md5" + AuthType string `mapstructure:"auth_type"` + + // AuthPassword is the authentication password used for this SNMP connection. + // Only valid for version "v3" and if "no_auth_no_priv" is not selected for SecurityLevel + AuthPassword string `mapstructure:"auth_password"` + + // PrivacyType is the type of privacy protocol to use for this SNMP connection. + // Only valid for version “v3” and if "auth_priv" is selected for SecurityLevel + // Valid options: “des”, “aes”, “aes192”, “aes256”, “aes192c”, “aes256c” + // Default: "des" + PrivacyType string `mapstructure:"privacy_type"` + + // PrivacyPassword is the authentication password used for this SNMP connection. + // Only valid for version “v3” and if "auth_priv" is selected for SecurityLevel + PrivacyPassword string `mapstructure:"privacy_password"` + + // ResourceAttributes defines what resource attributes will be used for this receiver and is composed + // of resource attribute names along with their resource attribute configurations + ResourceAttributes map[string]*ResourceAttributeConfig `mapstructure:"resource_attributes"` + + // Attributes defines what attributes will be used on metrics for this receiver and is composed of + // attribute names along with their attribute configurations + Attributes map[string]*AttributeConfig `mapstructure:"attributes"` + + // Metrics defines what SNMP metrics will be collected for this receiver and is composed of metric + // names along with their metric configurations + Metrics map[string]*MetricConfig `mapstructure:"metrics"` +} + +// ResourceAttributeConfig contains config info about all of the resource attributes that will be used by this receiver. +type ResourceAttributeConfig struct { + // Description is optional and describes what the resource attribute represents + Description string `mapstructure:"description"` + // OID is required only if IndexedValuePrefix is not defined. + // This is the column OID which will provide indexed values to be used for this resource attribute. These indexed values + // will ultimately each be associated with a different "resource" as an attribute on that resource. Indexed metric values + // will then be used to associate metric datapoints to the matching "resource" (based on matching indexes). + OID string `mapstructure:"oid"` + // IndexedValuePrefix is required only if OID is not defined. + // This will be used alongside indexed metric values for this resource attribute. The prefix value concatenated with + // specific indexes of metric indexed values (Ex: prefix.1.2) will ultimately each be associated with a different "resource" + // as an attribute on that resource. The related indexed metric values will then be used to associate metric datapoints to + // those resources. + IndexedValuePrefix string `mapstructure:"indexed_value_prefix"` // required and valid if no oid field +} + +// AttributeConfig contains config info about all of the metric attributes that will be used by this receiver. +type AttributeConfig struct { + // Value is optional, and will allow for a different attribute key other than the attribute name + Value string `mapstructure:"value"` + // Description is optional and describes what the attribute represents + Description string `mapstructure:"description"` + // Enum is required only if OID and IndexedValuePrefix are not defined. + // This contains a list of possible values that can be associated with this attribute + Enum []string `mapstructure:"enum"` + // OID is required only if Enum and IndexedValuePrefix are not defined. + // This is the column OID which will provide indexed values to be uased for this attribute (alongside a metric with ColumnOIDs) + OID string `mapstructure:"oid"` + // IndexedValuePrefix is required only if Enum and OID are not defined. + // This is used alongside metrics with ColumnOIDs to assign attribute values using this prefix + the OID index of the metric value + IndexedValuePrefix string `mapstructure:"indexed_value_prefix"` +} + +// MetricConfig contains config info about a given metric +type MetricConfig struct { + // Description is optional and describes what this metric represents + Description string `mapstructure:"description"` + // Unit is required + Unit string `mapstructure:"unit"` + // Either Gauge or Sum config is required + Gauge *GaugeMetric `mapstructure:"gauge"` + Sum *SumMetric `mapstructure:"sum"` + // Either ScalarOIDs or ColumnOIDs is required. + // ScalarOIDs is used if one or more scalar OID values is used for this metric. + // ColumnOIDs is used if one or more column OID indexed set of values is used + // for this metric. + ScalarOIDs []ScalarOID `mapstructure:"scalar_oids"` + ColumnOIDs []ColumnOID `mapstructure:"column_oids"` +} + +// GaugeMetric contains info about the value of the gauge metric +type GaugeMetric struct { + // ValueType is required can can be either int or float + ValueType string `mapstructure:"value_type"` +} + +// SumMetric contains info about the value of the sum metric +type SumMetric struct { + // Aggregation is required and can be cumulative or delta + Aggregation string `mapstructure:"aggregation"` + // Monotonic is required and can be true or false + Monotonic bool `mapstructure:"monotonic"` + // ValueType is required can can be either int or float + ValueType string `mapstructure:"value_type"` +} + +// ScalarOID holds OID info for a scalar metric as well as any attributes +// that are attached to it +type ScalarOID struct { + // OID is required and is the scalar OID that is associated with a metric + OID string `mapstructure:"oid"` + // Attributes is optional and may contain names and values associated with enum + // AttributeConfigs to associate with the value of the scalar OID + Attributes []Attribute `mapstructure:"attributes"` +} + +// ColumnOID holds OID info for an indexed metric as well as any attributes +// or resource attributes that are attached to it +type ColumnOID struct { + // OID is required and is the column OID that is associated with a metric + OID string `mapstructure:"oid"` + // ResourceAttributes is required only if there are no Attributes associated with non enum + // AttributeConfigs defined here. Valid values are ResourceAttributeConfig names that will + // be used to differentiate the indexed values for the column OID + ResourceAttributes []string `mapstructure:"resource_attributes"` + // Attributes is required only if there are no ResourceAttributes associated defined here. + // Valid values are non enum AttributeConfig names that will be used to differentiate the + // indexed values for the column OID + Attributes []Attribute `mapstructure:"attributes"` +} + +// Attribute is a connection between a metric configuration and an AttributeConfig +type Attribute struct { + // Name is required and should match the key for an AttributeConfig + Name string `mapstructure:"name"` + // Value is optional and is only needed for a matched AttributeConfig's with enum value. + // Value should match one of the AttributeConfig's enum values in this case + Value string `mapstructure:"value"` +} + +// Validate validates the given config, returning an error specifying any issues with the config. +func (cfg *Config) Validate() error { + var combinedErr error + + combinedErr = multierr.Append(combinedErr, validateEndpoint(cfg)) + combinedErr = multierr.Append(combinedErr, validateVersion(cfg)) + if strings.ToUpper(cfg.Version) == "V3" { + combinedErr = multierr.Append(combinedErr, validateSecurity(cfg)) + } + combinedErr = multierr.Append(combinedErr, validateMetricConfigs(cfg)) + + return combinedErr +} + +// validateEndpoint validates the Endpoint +func validateEndpoint(cfg *Config) error { + if cfg.Endpoint == "" { + return errEmptyEndpoint + } + + // Ensure valid endpoint + u, err := url.Parse(cfg.Endpoint) + if err != nil { + return fmt.Errorf(errMsgInvalidEndpointWError, cfg.Endpoint, err) + } + if u.Host == "" || u.Port() == "" { + return fmt.Errorf(errMsgInvalidEndpoint, cfg.Endpoint) + } + + // Ensure valid scheme + switch strings.ToUpper(u.Scheme) { + case "TCP", "TCP4", "TCP6", "UDP", "UDP4", "UDP6": // ok + default: + return errEndpointBadScheme + } + + return nil +} + +// validateVersion validates the Version +func validateVersion(cfg *Config) error { + if cfg.Version == "" { + return errEmptyVersion + } + + // Ensure valid version + switch strings.ToUpper(cfg.Version) { + case "V1", "V2C", "V3": // ok + default: + return errBadVersion + } + + return nil +} + +// validateSecurity validates all v3 related security configs +func validateSecurity(cfg *Config) error { + var combinedErr error + + // Ensure valid user + if cfg.User == "" { + combinedErr = multierr.Append(combinedErr, errEmptyUser) + } + + if cfg.SecurityLevel == "" { + return multierr.Append(combinedErr, errEmptySecurityLevel) + } + + // Ensure valid security level + switch strings.ToUpper(cfg.SecurityLevel) { + case "NO_AUTH_NO_PRIV": + return combinedErr + case "AUTH_NO_PRIV": + // Ensure valid auth configs + return multierr.Append(combinedErr, validateAuth(cfg)) + case "AUTH_PRIV": // ok + // Ensure valid auth and privacy configs + combinedErr = multierr.Append(combinedErr, validateAuth(cfg)) + return multierr.Append(combinedErr, validatePrivacy(cfg)) + default: + return multierr.Append(combinedErr, errBadSecurityLevel) + } +} + +// validateAuth validates the AuthType and AuthPassword +func validateAuth(cfg *Config) error { + var combinedErr error + + // Ensure valid auth password + if cfg.AuthPassword == "" { + combinedErr = multierr.Append(combinedErr, errEmptyAuthPassword) + } + + // Ensure valid auth type + if cfg.AuthType == "" { + return multierr.Append(combinedErr, errEmptyAuthType) + } + + switch strings.ToUpper(cfg.AuthType) { + case "MD5", "SHA", "SHA224", "SHA256", "SHA384", "SHA512": // ok + default: + combinedErr = multierr.Append(combinedErr, errBadAuthType) + } + + return combinedErr +} + +// validatePrivacy validates the PrivacyType and PrivacyPassword +func validatePrivacy(cfg *Config) error { + var combinedErr error + + // Ensure valid privacy password + if cfg.PrivacyPassword == "" { + combinedErr = multierr.Append(combinedErr, errEmptyPrivacyPassword) + } + + // Ensure valid privacy type + if cfg.PrivacyType == "" { + return multierr.Append(combinedErr, errEmptyPrivacyType) + } + + switch strings.ToUpper(cfg.PrivacyType) { + case "DES", "AES", "AES192", "AES192C", "AES256", "AES256C": // ok + default: + combinedErr = multierr.Append(combinedErr, errBadPrivacyType) + } + + return combinedErr +} + +// validateMetricConfigs validates all MetricConfigs, AttributeConfigs, and ResourceAttributeConfigs +func validateMetricConfigs(cfg *Config) error { + var combinedErr error + + // Validate the Attribute and ResourceAttribute configs up front + combinedErr = multierr.Append(combinedErr, validateAttributeConfigs(cfg)) + combinedErr = multierr.Append(combinedErr, validateResourceAttributeConfigs(cfg)) + + // Ensure there is at least one MetricConfig + metrics := cfg.Metrics + if len(metrics) == 0 { + return multierr.Append(combinedErr, errMetricRequired) + } + + // Make sure each MetricConfig has valid info + for metricName, metricCfg := range metrics { + if metricCfg.Unit == "" { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgMetricNoUnit, metricName)) + } + + if metricCfg.Gauge == nil && metricCfg.Sum == nil { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgMetricNoGaugeOrSum, metricName)) + } + + if len(metricCfg.ScalarOIDs) == 0 && len(metricCfg.ColumnOIDs) == 0 { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgMetricNoOIDs, metricName)) + } + + if metricCfg.Gauge != nil { + combinedErr = multierr.Append(combinedErr, validateGauge(metricName, metricCfg.Gauge)) + } + + if metricCfg.Sum != nil { + combinedErr = multierr.Append(combinedErr, validateSum(metricName, metricCfg.Sum)) + } + + for _, scalarOID := range metricCfg.ScalarOIDs { + combinedErr = multierr.Append(combinedErr, validateScalarOID(metricName, scalarOID, cfg)) + } + + for _, columnOID := range metricCfg.ColumnOIDs { + combinedErr = multierr.Append(combinedErr, validateColumnOID(metricName, columnOID, cfg)) + } + } + + return combinedErr +} + +// validateColumnOID validates a ColumnOID +func validateColumnOID(metricName string, columnOID ColumnOID, cfg *Config) error { + var combinedErr error + + // Ensure that it contains an OID + if columnOID.OID == "" { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgColumnOIDNoOID, metricName)) + } + + // Keep track of whether the different indexed values can be differentiated by either attribute within the same metric + // or by different resource attributes (in different resources) + hasIndexedIdentifier := false + + // Check that any Attributes have a valid Name and a valid Value (if applicable) + if len(columnOID.Attributes) > 0 { + for _, attribute := range columnOID.Attributes { + if attribute.Name == "" { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgColumnAttributeNoName, metricName)) + continue + } + + attrCfg, ok := cfg.Attributes[attribute.Name] + if !ok { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgColumnAttributeBadName, metricName, attribute.Name)) + continue + } + + if len(attrCfg.Enum) > 0 { + if !contains(attrCfg.Enum, attribute.Value) { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgColumnAttributeBadValue, metricName, attribute.Name, attribute.Value)) + } + continue + } + + hasIndexedIdentifier = true + } + } + + // Check that any ResourceAttributes have a valid value + if len(columnOID.ResourceAttributes) > 0 { + hasIndexedIdentifier = true + for _, name := range columnOID.ResourceAttributes { + _, ok := cfg.ResourceAttributes[name] + if !ok { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgColumnResourceAttributeBadName, metricName, name)) + } + } + } + + // Check that there is either a column based attribute or resource attribute associated with it + if !hasIndexedIdentifier { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgColumnIndexedAttributeRequired, metricName)) + } + + return combinedErr +} + +// validateScalarOID validates a ScalarOID +func validateScalarOID(metricName string, scalarOID ScalarOID, cfg *Config) error { + var combinedErr error + + // Ensure that it contains an OID + if scalarOID.OID == "" { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgScalarOIDNoOID, metricName)) + } + + if len(scalarOID.Attributes) == 0 { + return combinedErr + } + + // Check that any Attributes have a valid Name and a valid Value + for _, attribute := range scalarOID.Attributes { + if attribute.Name == "" { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgScalarAttributeNoName, metricName)) + continue + } + + attrCfg, ok := cfg.Attributes[attribute.Name] + if !ok { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgScalarAttributeBadName, metricName, attribute.Name)) + continue + } + + if len(attrCfg.Enum) == 0 { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgScalarOIDBadAttribute, metricName, attribute.Name)) + continue + } + + if !contains(attrCfg.Enum, attribute.Value) { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgScalarAttributeBadValue, metricName, attribute.Name, attribute.Value)) + } + } + + return combinedErr +} + +// validateGauge validates a GaugeMetric +func validateGauge(metricName string, gauge *GaugeMetric) error { + // Ensure valid values for ValueType + upperValType := strings.ToUpper(gauge.ValueType) + if upperValType != "INT" && upperValType != "FLOAT" { + return fmt.Errorf(errMsgGaugeBadValueType, metricName) + } + + return nil +} + +// validateSum validates a SumMetric +func validateSum(metricName string, sum *SumMetric) error { + var combinedErr error + + // Ensure valid values for ValueType + upperValType := strings.ToUpper(sum.ValueType) + if upperValType != "INT" && upperValType != "FLOAT" { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgSumBadValueType, metricName)) + } + + // Ensure valid values for Aggregation + upperAggregation := strings.ToUpper(sum.Aggregation) + if upperAggregation != "CUMULATIVE" && upperAggregation != "DELTA" { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgSumBadAggregation, metricName)) + } + + return combinedErr +} + +// validateAttributeConfigs validates the AttributeConfigs +func validateAttributeConfigs(cfg *Config) error { + var combinedErr error + + attributes := cfg.Attributes + if len(attributes) == 0 { + return nil + } + + // Make sure each Attribute has either an OID, Enum, or IndexedValuePrefix + for attrName, attrCfg := range attributes { + if len(attrCfg.Enum) == 0 && attrCfg.OID == "" && attrCfg.IndexedValuePrefix == "" { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgAttributeConfigNoEnumOIDOrPrefix, attrName)) + } + } + + return combinedErr +} + +// validateResourceAttributeConfigs validates the ResourceAttributeConfigs +func validateResourceAttributeConfigs(cfg *Config) error { + var combinedErr error + + resourceAttributes := cfg.ResourceAttributes + if len(resourceAttributes) == 0 { + return nil + } + + // Make sure each Resource Attribute has either an OID or IndexedValuePrefix + for attrName, attrCfg := range resourceAttributes { + if attrCfg.OID == "" && attrCfg.IndexedValuePrefix == "" { + combinedErr = multierr.Append(combinedErr, fmt.Errorf(errMsgResourceAttributeNoOIDOrPrefix, attrName)) + } + } + + return combinedErr +} + +// contains checks if string slice contains a string value +func contains(elements []string, value string) bool { + for _, element := range elements { + if value == element { + return true + } + } + return false } diff --git a/receiver/snmpreceiver/config_test.go b/receiver/snmpreceiver/config_test.go new file mode 100644 index 000000000000..4dd7fa76422b --- /dev/null +++ b/receiver/snmpreceiver/config_test.go @@ -0,0 +1,992 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package snmpreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/snmpreceiver" + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/confmap/confmaptest" +) + +func TestLoadConfigConnectionConfigs(t *testing.T) { + cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) + require.NoError(t, err) + + factory := NewFactory() + + type testCase struct { + name string + nameVal string + expectedCfg *Config + expectedErr string + } + + metrics := map[string]*MetricConfig{ + "m3": { + Unit: "By", + Gauge: &GaugeMetric{ + ValueType: "float", + }, + ScalarOIDs: []ScalarOID{ + { + OID: "1", + }, + }, + }, + } + expectedConfigSimple := factory.CreateDefaultConfig().(*Config) + expectedConfigSimple.Metrics = metrics + + expectedConfigInvalidEndpoint := factory.CreateDefaultConfig().(*Config) + expectedConfigInvalidEndpoint.Endpoint = "udp://a:a:a:a:a:a" + expectedConfigInvalidEndpoint.Metrics = metrics + + expectedConfigNoPort := factory.CreateDefaultConfig().(*Config) + expectedConfigNoPort.Endpoint = "udp://localhost" + expectedConfigNoPort.Metrics = metrics + + expectedConfigNoPortTrailingColon := factory.CreateDefaultConfig().(*Config) + expectedConfigNoPortTrailingColon.Endpoint = "udp://localhost:" + expectedConfigNoPortTrailingColon.Metrics = metrics + + expectedConfigBadEndpointScheme := factory.CreateDefaultConfig().(*Config) + expectedConfigBadEndpointScheme.Endpoint = "http://localhost:161" + expectedConfigBadEndpointScheme.Metrics = metrics + + expectedConfigNoEndpointScheme := factory.CreateDefaultConfig().(*Config) + expectedConfigNoEndpointScheme.Endpoint = "localhost:161" + expectedConfigNoEndpointScheme.Metrics = metrics + + expectedConfigBadVersion := factory.CreateDefaultConfig().(*Config) + expectedConfigBadVersion.Version = "9999" + expectedConfigBadVersion.Metrics = metrics + + expectedConfigV3NoUser := factory.CreateDefaultConfig().(*Config) + expectedConfigV3NoUser.Version = "v3" + expectedConfigV3NoUser.SecurityLevel = "no_auth_no_priv" + expectedConfigV3NoUser.Metrics = metrics + + expectedConfigV3NoSecurityLevel := factory.CreateDefaultConfig().(*Config) + expectedConfigV3NoSecurityLevel.Version = "v3" + expectedConfigV3NoSecurityLevel.User = "u" + expectedConfigV3NoSecurityLevel.Metrics = metrics + + expectedConfigV3BadSecurityLevel := factory.CreateDefaultConfig().(*Config) + expectedConfigV3BadSecurityLevel.Version = "v3" + expectedConfigV3BadSecurityLevel.SecurityLevel = "super" + expectedConfigV3BadSecurityLevel.User = "u" + expectedConfigV3BadSecurityLevel.Metrics = metrics + + expectedConfigV3NoAuthType := factory.CreateDefaultConfig().(*Config) + expectedConfigV3NoAuthType.Version = "v3" + expectedConfigV3NoAuthType.User = "u" + expectedConfigV3NoAuthType.SecurityLevel = "auth_no_priv" + expectedConfigV3NoAuthType.AuthPassword = "p" + expectedConfigV3NoAuthType.Metrics = metrics + + expectedConfigV3BadAuthType := factory.CreateDefaultConfig().(*Config) + expectedConfigV3BadAuthType.Version = "v3" + expectedConfigV3BadAuthType.User = "u" + expectedConfigV3BadAuthType.SecurityLevel = "auth_no_priv" + expectedConfigV3BadAuthType.AuthType = "super" + expectedConfigV3BadAuthType.AuthPassword = "p" + expectedConfigV3BadAuthType.Metrics = metrics + + expectedConfigV3NoAuthPassword := factory.CreateDefaultConfig().(*Config) + expectedConfigV3NoAuthPassword.Version = "v3" + expectedConfigV3NoAuthPassword.User = "u" + expectedConfigV3NoAuthPassword.SecurityLevel = "auth_no_priv" + expectedConfigV3NoAuthPassword.Metrics = metrics + + expectedConfigV3Simple := factory.CreateDefaultConfig().(*Config) + expectedConfigV3Simple.Version = "v3" + expectedConfigV3Simple.User = "u" + expectedConfigV3Simple.SecurityLevel = "auth_priv" + expectedConfigV3Simple.AuthPassword = "p" + expectedConfigV3Simple.PrivacyPassword = "pp" + expectedConfigV3Simple.Metrics = metrics + + expectedConfigV3BadPrivacyType := factory.CreateDefaultConfig().(*Config) + expectedConfigV3BadPrivacyType.Version = "v3" + expectedConfigV3BadPrivacyType.User = "u" + expectedConfigV3BadPrivacyType.SecurityLevel = "auth_priv" + expectedConfigV3BadPrivacyType.AuthPassword = "p" + expectedConfigV3BadPrivacyType.PrivacyType = "super" + expectedConfigV3BadPrivacyType.PrivacyPassword = "pp" + expectedConfigV3BadPrivacyType.Metrics = metrics + + expectedConfigV3NoPrivacyPassword := factory.CreateDefaultConfig().(*Config) + expectedConfigV3NoPrivacyPassword.Version = "v3" + expectedConfigV3NoPrivacyPassword.User = "u" + expectedConfigV3NoPrivacyPassword.SecurityLevel = "auth_priv" + expectedConfigV3NoPrivacyPassword.AuthPassword = "p" + expectedConfigV3NoPrivacyPassword.Metrics = metrics + + testCases := []testCase{ + { + name: "NoEndpointUsesDefault", + nameVal: "no_endpoint", + expectedCfg: expectedConfigSimple, + expectedErr: "", + }, + { + name: "InvalidEndpointErrors", + nameVal: "invalid_endpoint", + expectedCfg: expectedConfigInvalidEndpoint, + expectedErr: fmt.Sprintf(errMsgInvalidEndpoint[:len(errMsgInvalidEndpoint)-2], "udp://a:a:a:a:a:a"), + }, + { + name: "NoPortErrors", + nameVal: "no_port", + expectedCfg: expectedConfigNoPort, + expectedErr: fmt.Sprintf(errMsgInvalidEndpoint[:len(errMsgInvalidEndpoint)-2], "udp://localhost"), + }, + { + name: "NoPortTrailingColonErrors", + nameVal: "no_port_trailing_colon", + expectedCfg: expectedConfigNoPortTrailingColon, + expectedErr: fmt.Sprintf(errMsgInvalidEndpoint[:len(errMsgInvalidEndpoint)-2], "udp://localhost:"), + }, + { + name: "BadEndpointSchemeErrors", + nameVal: "bad_endpoint_scheme", + expectedCfg: expectedConfigBadEndpointScheme, + expectedErr: errEndpointBadScheme.Error(), + }, + { + name: "NoEndpointSchemeErrors", + nameVal: "no_endpoint_scheme", + expectedCfg: expectedConfigNoEndpointScheme, + expectedErr: fmt.Sprintf(errMsgInvalidEndpoint[:len(errMsgInvalidEndpoint)-2], "localhost:161"), + }, + { + name: "NoVersionUsesDefault", + nameVal: "no_version", + expectedCfg: expectedConfigSimple, + expectedErr: "", + }, + { + name: "BadVersionErrors", + nameVal: "bad_version", + expectedCfg: expectedConfigBadVersion, + expectedErr: errBadVersion.Error(), + }, + { + name: "V3NoUserErrors", + nameVal: "v3_no_user", + expectedCfg: expectedConfigV3NoUser, + expectedErr: errEmptyUser.Error(), + }, + { + name: "V3NoSecurityLevelUsesDefault", + nameVal: "v3_no_security_level", + expectedCfg: expectedConfigV3NoSecurityLevel, + expectedErr: "", + }, + { + name: "V3BadSecurityLevelErrors", + nameVal: "v3_bad_security_level", + expectedCfg: expectedConfigV3BadSecurityLevel, + expectedErr: errBadSecurityLevel.Error(), + }, + { + name: "V3NoAuthTypeUsesDefault", + nameVal: "v3_no_auth_type", + expectedCfg: expectedConfigV3NoAuthType, + expectedErr: "", + }, + { + name: "V3BadAuthTypeErrors", + nameVal: "v3_bad_auth_type", + expectedCfg: expectedConfigV3BadAuthType, + expectedErr: errBadAuthType.Error(), + }, + { + name: "V3NoAuthPasswordErrors", + nameVal: "v3_no_auth_password", + expectedCfg: expectedConfigV3NoAuthPassword, + expectedErr: errEmptyAuthPassword.Error(), + }, + { + name: "V3NoPrivacyTypeUsesDefault", + nameVal: "v3_no_privacy_type", + expectedCfg: expectedConfigV3Simple, + expectedErr: "", + }, + { + name: "V3BadPrivacyTypeErrors", + nameVal: "v3_bad_privacy_type", + expectedCfg: expectedConfigV3BadPrivacyType, + expectedErr: errBadPrivacyType.Error(), + }, + { + name: "V3NoPrivacyPasswordErrors", + nameVal: "v3_no_privacy_password", + expectedCfg: expectedConfigV3NoPrivacyPassword, + expectedErr: errEmptyPrivacyPassword.Error(), + }, + { + name: "GoodV2CConnectionNoErrors", + nameVal: "v2c_connection_good", + expectedCfg: expectedConfigSimple, + expectedErr: "", + }, + { + name: "GoodV3ConnectionNoErrors", + nameVal: "v3_connection_good", + expectedCfg: expectedConfigV3Simple, + expectedErr: "", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + sub, err := cm.Sub(config.NewComponentIDWithName(typeStr, test.nameVal).String()) + require.NoError(t, err) + + cfg := factory.CreateDefaultConfig() + require.NoError(t, config.UnmarshalReceiver(sub, cfg)) + if test.expectedErr == "" { + require.NoError(t, cfg.Validate()) + } else { + require.ErrorContains(t, cfg.Validate(), test.expectedErr) + } + + require.Equal(t, test.expectedCfg, cfg) + }) + } +} + +func getBaseMetricConfig(gauge bool, scalar bool) map[string]*MetricConfig { + metricCfg := map[string]*MetricConfig{ + "m3": { + Unit: "By", + }, + } + + if gauge { + metricCfg["m3"].Gauge = &GaugeMetric{ + ValueType: "float", + } + } else { + metricCfg["m3"].Sum = &SumMetric{ + Aggregation: "cumulative", + Monotonic: true, + ValueType: "float", + } + } + + if scalar { + metricCfg["m3"].ScalarOIDs = []ScalarOID{ + { + OID: "1", + }, + } + } else { + metricCfg["m3"].ColumnOIDs = []ColumnOID{ + { + OID: "1", + }, + } + } + + return metricCfg +} + +func getBaseAttrConfig(attrType string) map[string]*AttributeConfig { + switch attrType { + case "oid": + return map[string]*AttributeConfig{ + "a2": { + OID: "1", + }, + } + case "prefix": + return map[string]*AttributeConfig{ + "a2": { + IndexedValuePrefix: "p", + }, + } + default: + return map[string]*AttributeConfig{ + "a2": { + Enum: []string{"val1", "val2"}, + }, + } + } +} + +func getBaseResourceAttrConfig(attrType string) map[string]*ResourceAttributeConfig { + switch attrType { + case "oid": + return map[string]*ResourceAttributeConfig{ + "ra1": { + OID: "2", + }, + } + default: + return map[string]*ResourceAttributeConfig{ + "ra1": { + IndexedValuePrefix: "p", + }, + } + } +} + +func TestLoadConfigMetricConfigs(t *testing.T) { + cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) + require.NoError(t, err) + + factory := NewFactory() + type testCase struct { + name string + nameVal string + expectedCfg *Config + expectedErr string + } + + expectedConfigNoMetricConfig := factory.CreateDefaultConfig().(*Config) + + expectedConfigNoMetricUnit := factory.CreateDefaultConfig().(*Config) + expectedConfigNoMetricUnit.Metrics = getBaseMetricConfig(true, true) + expectedConfigNoMetricUnit.Metrics["m3"].Unit = "" + + expectedConfigNoMetricGaugeOrSum := factory.CreateDefaultConfig().(*Config) + expectedConfigNoMetricGaugeOrSum.Metrics = getBaseMetricConfig(true, true) + expectedConfigNoMetricGaugeOrSum.Metrics["m3"].Gauge = nil + + expectedConfigNoMetricOIDs := factory.CreateDefaultConfig().(*Config) + expectedConfigNoMetricOIDs.Metrics = getBaseMetricConfig(true, true) + expectedConfigNoMetricOIDs.Metrics["m3"].ScalarOIDs = nil + + expectedConfigNoMetricGaugeType := factory.CreateDefaultConfig().(*Config) + expectedConfigNoMetricGaugeType.Metrics = getBaseMetricConfig(true, true) + expectedConfigNoMetricGaugeType.Metrics["m3"].Gauge.ValueType = "" + + expectedConfigBadMetricGaugeType := factory.CreateDefaultConfig().(*Config) + expectedConfigBadMetricGaugeType.Metrics = getBaseMetricConfig(true, true) + expectedConfigBadMetricGaugeType.Metrics["m3"].Gauge.ValueType = "Counter" + + expectedConfigNoMetricSumType := factory.CreateDefaultConfig().(*Config) + expectedConfigNoMetricSumType.Metrics = getBaseMetricConfig(false, true) + expectedConfigNoMetricSumType.Metrics["m3"].Sum.ValueType = "" + + expectedConfigBadMetricSumType := factory.CreateDefaultConfig().(*Config) + expectedConfigBadMetricSumType.Metrics = getBaseMetricConfig(false, true) + expectedConfigBadMetricSumType.Metrics["m3"].Sum.ValueType = "Counter" + + expectedConfigNoMetricSumAggregation := factory.CreateDefaultConfig().(*Config) + expectedConfigNoMetricSumAggregation.Metrics = getBaseMetricConfig(false, true) + expectedConfigNoMetricSumAggregation.Metrics["m3"].Sum.Aggregation = "" + + expectedConfigBadMetricSumAggregation := factory.CreateDefaultConfig().(*Config) + expectedConfigBadMetricSumAggregation.Metrics = getBaseMetricConfig(false, true) + expectedConfigBadMetricSumAggregation.Metrics["m3"].Sum.Aggregation = "Counter" + + expectedConfigNoScalarOIDOID := factory.CreateDefaultConfig().(*Config) + expectedConfigNoScalarOIDOID.Metrics = getBaseMetricConfig(true, true) + expectedConfigNoScalarOIDOID.Metrics["m3"].ScalarOIDs[0].OID = "" + + expectedConfigNoAttrOIDPrefixOrEnum := factory.CreateDefaultConfig().(*Config) + expectedConfigNoAttrOIDPrefixOrEnum.Metrics = getBaseMetricConfig(true, true) + expectedConfigNoAttrOIDPrefixOrEnum.Attributes = getBaseAttrConfig("oid") + expectedConfigNoAttrOIDPrefixOrEnum.Attributes["a2"].OID = "" + + expectedConfigNoScalarOIDAttrName := factory.CreateDefaultConfig().(*Config) + expectedConfigNoScalarOIDAttrName.Metrics = getBaseMetricConfig(true, true) + expectedConfigNoScalarOIDAttrName.Metrics["m3"].ScalarOIDs[0].Attributes = []Attribute{ + { + Value: "val1", + }, + } + + expectedConfigBadScalarOIDAttrName := factory.CreateDefaultConfig().(*Config) + expectedConfigBadScalarOIDAttrName.Metrics = getBaseMetricConfig(true, true) + expectedConfigBadScalarOIDAttrName.Attributes = getBaseAttrConfig("enum") + expectedConfigBadScalarOIDAttrName.Metrics["m3"].ScalarOIDs[0].Attributes = []Attribute{ + { + Name: "a1", + Value: "val1", + }, + } + + expectedConfigBadScalarOIDAttr := factory.CreateDefaultConfig().(*Config) + expectedConfigBadScalarOIDAttr.Metrics = getBaseMetricConfig(true, true) + expectedConfigBadScalarOIDAttr.Attributes = getBaseAttrConfig("oid") + expectedConfigBadScalarOIDAttr.Metrics["m3"].ScalarOIDs[0].Attributes = []Attribute{ + { + Name: "a2", + Value: "val1", + }, + } + + expectedConfigBadScalarOIDAttrValue := factory.CreateDefaultConfig().(*Config) + expectedConfigBadScalarOIDAttrValue.Metrics = getBaseMetricConfig(true, true) + expectedConfigBadScalarOIDAttrValue.Attributes = getBaseAttrConfig("enum") + expectedConfigBadScalarOIDAttrValue.Metrics["m3"].ScalarOIDs[0].Attributes = []Attribute{ + { + Name: "a2", + Value: "val3", + }, + } + + expectedConfigNoColumnOIDOID := factory.CreateDefaultConfig().(*Config) + expectedConfigNoColumnOIDOID.Metrics = getBaseMetricConfig(true, false) + expectedConfigNoColumnOIDOID.Metrics["m3"].ColumnOIDs[0].OID = "" + + expectedConfigNoColumnOIDAttrName := factory.CreateDefaultConfig().(*Config) + expectedConfigNoColumnOIDAttrName.Metrics = getBaseMetricConfig(true, false) + expectedConfigNoColumnOIDAttrName.Metrics["m3"].ColumnOIDs[0].Attributes = []Attribute{{}} + + expectedConfigBadColumnOIDAttrName := factory.CreateDefaultConfig().(*Config) + expectedConfigBadColumnOIDAttrName.Metrics = getBaseMetricConfig(true, false) + expectedConfigBadColumnOIDAttrName.Attributes = getBaseAttrConfig("oid") + expectedConfigBadColumnOIDAttrName.Metrics["m3"].ColumnOIDs[0].Attributes = []Attribute{ + { + Name: "a1", + }, + } + + expectedConfigBadColumnOIDAttrValue := factory.CreateDefaultConfig().(*Config) + expectedConfigBadColumnOIDAttrValue.Metrics = getBaseMetricConfig(true, false) + expectedConfigBadColumnOIDAttrValue.Attributes = getBaseAttrConfig("enum") + expectedConfigBadColumnOIDAttrValue.Metrics["m3"].ColumnOIDs[0].Attributes = []Attribute{ + { + Name: "a2", + Value: "val3", + }, + } + + expectedConfigBadColumnOIDResourceAttrName := factory.CreateDefaultConfig().(*Config) + expectedConfigBadColumnOIDResourceAttrName.Metrics = getBaseMetricConfig(true, false) + expectedConfigBadColumnOIDResourceAttrName.ResourceAttributes = getBaseResourceAttrConfig("oid") + expectedConfigBadColumnOIDResourceAttrName.Metrics["m3"].ColumnOIDs[0].ResourceAttributes = []string{"a2"} + + expectedConfigColumnOIDWithoutIndexAttributeOrResourceAttribute := factory.CreateDefaultConfig().(*Config) + expectedConfigColumnOIDWithoutIndexAttributeOrResourceAttribute.Metrics = getBaseMetricConfig(true, false) + expectedConfigColumnOIDWithoutIndexAttributeOrResourceAttribute.Attributes = getBaseAttrConfig("enum") + expectedConfigColumnOIDWithoutIndexAttributeOrResourceAttribute.Metrics["m3"].ColumnOIDs[0].Attributes = []Attribute{ + { + Name: "a2", + Value: "val1", + }, + } + + expectedConfigNoResourceAttributeOIDOrPrefix := factory.CreateDefaultConfig().(*Config) + expectedConfigNoResourceAttributeOIDOrPrefix.Metrics = getBaseMetricConfig(true, false) + expectedConfigNoResourceAttributeOIDOrPrefix.ResourceAttributes = getBaseResourceAttrConfig("oid") + expectedConfigNoResourceAttributeOIDOrPrefix.ResourceAttributes["ra1"].OID = "" + expectedConfigNoResourceAttributeOIDOrPrefix.Metrics["m3"].ColumnOIDs[0].ResourceAttributes = []string{"ra1"} + + expectedConfigComplexGood := factory.CreateDefaultConfig().(*Config) + expectedConfigComplexGood.ResourceAttributes = getBaseResourceAttrConfig("prefix") + expectedConfigComplexGood.ResourceAttributes["ra2"] = &ResourceAttributeConfig{OID: "1"} + expectedConfigComplexGood.Attributes = getBaseAttrConfig("enum") + expectedConfigComplexGood.Attributes["a1"] = &AttributeConfig{ + Value: "v", + Enum: []string{"val1"}, + } + expectedConfigComplexGood.Attributes["a3"] = &AttributeConfig{IndexedValuePrefix: "p"} + expectedConfigComplexGood.Attributes["a4"] = &AttributeConfig{OID: "1"} + expectedConfigComplexGood.Metrics = getBaseMetricConfig(true, true) + expectedConfigComplexGood.Metrics["m3"].ScalarOIDs[0].Attributes = []Attribute{ + { + Name: "a1", + Value: "val1", + }, + } + expectedConfigComplexGood.Metrics["m1"] = &MetricConfig{ + Unit: "1", + Sum: &SumMetric{ + Monotonic: true, + Aggregation: "cumulative", + ValueType: "int", + }, + ColumnOIDs: []ColumnOID{ + { + OID: "1", + Attributes: []Attribute{ + { + Name: "a4", + }, + }, + }, + }, + } + expectedConfigComplexGood.Metrics["m2"] = &MetricConfig{ + Unit: "By", + Gauge: &GaugeMetric{ + ValueType: "int", + }, + ColumnOIDs: []ColumnOID{ + { + OID: "1", + Attributes: []Attribute{ + { + Name: "a3", + }, + { + Name: "a2", + Value: "val1", + }, + }, + }, + { + OID: "2", + Attributes: []Attribute{ + { + Name: "a3", + }, + { + Name: "a2", + Value: "val2", + }, + }, + }, + }, + } + expectedConfigComplexGood.Metrics["m4"] = &MetricConfig{ + Unit: "{things}", + Sum: &SumMetric{ + Aggregation: "cumulative", + Monotonic: true, + ValueType: "int", + }, + ScalarOIDs: []ScalarOID{ + { + OID: "1", + }, + }, + } + expectedConfigComplexGood.Metrics["m5"] = &MetricConfig{ + Unit: "{things}", + Sum: &SumMetric{ + Aggregation: "cumulative", + Monotonic: false, + ValueType: "int", + }, + ScalarOIDs: []ScalarOID{ + { + OID: "1", + Attributes: []Attribute{ + { + Name: "a1", + Value: "val1", + }, + }, + }, + }, + } + expectedConfigComplexGood.Metrics["m6"] = &MetricConfig{ + Unit: "1", + Sum: &SumMetric{ + Aggregation: "delta", + Monotonic: true, + ValueType: "int", + }, + ScalarOIDs: []ScalarOID{ + { + OID: "1", + Attributes: []Attribute{ + { + Name: "a2", + Value: "val1", + }, + }, + }, + { + OID: "2", + Attributes: []Attribute{ + { + Name: "a2", + Value: "val2", + }, + }, + }, + }, + } + expectedConfigComplexGood.Metrics["m7"] = &MetricConfig{ + Unit: "By", + Gauge: &GaugeMetric{ + ValueType: "int", + }, + ColumnOIDs: []ColumnOID{ + { + OID: "1", + ResourceAttributes: []string{ + "ra1", + }, + }, + }, + } + expectedConfigComplexGood.Metrics["m8"] = &MetricConfig{ + Unit: "By", + Gauge: &GaugeMetric{ + ValueType: "int", + }, + ColumnOIDs: []ColumnOID{ + { + OID: "1", + ResourceAttributes: []string{ + "ra2", + }, + }, + }, + } + expectedConfigComplexGood.Metrics["m9"] = &MetricConfig{ + Unit: "By", + Gauge: &GaugeMetric{ + ValueType: "int", + }, + ColumnOIDs: []ColumnOID{ + { + OID: "1", + ResourceAttributes: []string{ + "ra1", + "ra2", + }, + }, + }, + } + expectedConfigComplexGood.Metrics["m10"] = &MetricConfig{ + Unit: "By", + Gauge: &GaugeMetric{ + ValueType: "int", + }, + ColumnOIDs: []ColumnOID{ + { + OID: "1", + ResourceAttributes: []string{ + "ra1", + "ra2", + }, + Attributes: []Attribute{ + { + Name: "a1", + Value: "val1", + }, + { + Name: "a3", + }, + { + Name: "a4", + }, + }, + }, + }, + } + + testCases := []testCase{ + { + name: "NoMetricConfigsErrors", + nameVal: "no_metric_config", + expectedCfg: expectedConfigNoMetricConfig, + expectedErr: errMetricRequired.Error(), + }, + { + name: "NoMetricUnitErrors", + nameVal: "no_metric_unit", + expectedCfg: expectedConfigNoMetricUnit, + expectedErr: fmt.Sprintf(errMsgMetricNoUnit, "m3"), + }, + { + name: "NoMetricGaugeOrSumErrors", + nameVal: "no_metric_gauge_or_sum", + expectedCfg: expectedConfigNoMetricGaugeOrSum, + expectedErr: fmt.Sprintf(errMsgMetricNoGaugeOrSum, "m3"), + }, + { + name: "NoMetricOIDsErrors", + nameVal: "no_metric_oids", + expectedCfg: expectedConfigNoMetricOIDs, + expectedErr: fmt.Sprintf(errMsgMetricNoOIDs, "m3"), + }, + { + name: "NoMetricGaugeTypeErrors", + nameVal: "no_metric_gauge_type", + expectedCfg: expectedConfigNoMetricGaugeType, + expectedErr: fmt.Sprintf(errMsgGaugeBadValueType, "m3"), + }, + { + name: "BadMetricGaugeTypeErrors", + nameVal: "bad_metric_gauge_type", + expectedCfg: expectedConfigBadMetricGaugeType, + expectedErr: fmt.Sprintf(errMsgGaugeBadValueType, "m3"), + }, + { + name: "NoMetricSumTypeErrors", + nameVal: "no_metric_sum_type", + expectedCfg: expectedConfigNoMetricSumType, + expectedErr: fmt.Sprintf(errMsgSumBadValueType, "m3"), + }, + { + name: "BadMetricSumTypeErrors", + nameVal: "bad_metric_sum_type", + expectedCfg: expectedConfigBadMetricSumType, + expectedErr: fmt.Sprintf(errMsgSumBadValueType, "m3"), + }, + { + name: "NoMetricSumAggregationErrors", + nameVal: "no_metric_sum_aggregation", + expectedCfg: expectedConfigNoMetricSumAggregation, + expectedErr: fmt.Sprintf(errMsgSumBadAggregation, "m3"), + }, + { + name: "BadMetricSumAggregationErrors", + nameVal: "bad_metric_sum_aggregation", + expectedCfg: expectedConfigBadMetricSumAggregation, + expectedErr: fmt.Sprintf(errMsgSumBadAggregation, "m3"), + }, + { + name: "NoScalarOIDOIDErrors", + nameVal: "no_scalar_oid_oid", + expectedCfg: expectedConfigNoScalarOIDOID, + expectedErr: fmt.Sprintf(errMsgScalarOIDNoOID, "m3"), + }, + { + name: "NoAttributeConfigOIDPrefixOrEnumsErrors", + nameVal: "no_attribute_oid_prefix_or_enums", + expectedCfg: expectedConfigNoAttrOIDPrefixOrEnum, + expectedErr: fmt.Sprintf(errMsgAttributeConfigNoEnumOIDOrPrefix, "a2"), + }, + { + name: "NoScalarOIDAttributeNameErrors", + nameVal: "no_scalar_oid_attribute_name", + expectedCfg: expectedConfigNoScalarOIDAttrName, + expectedErr: fmt.Sprintf(errMsgScalarAttributeNoName, "m3"), + }, + { + name: "BadScalarOIDAttributeNameErrors", + nameVal: "bad_scalar_oid_attribute_name", + expectedCfg: expectedConfigBadScalarOIDAttrName, + expectedErr: fmt.Sprintf(errMsgScalarAttributeBadName, "m3", "a1"), + }, + { + name: "BadScalarOIDAttributeErrors", + nameVal: "bad_scalar_oid_attribute", + expectedCfg: expectedConfigBadScalarOIDAttr, + expectedErr: fmt.Sprintf(errMsgScalarOIDBadAttribute, "m3", "a2"), + }, + { + name: "BadScalarOIDAttributeValueErrors", + nameVal: "bad_scalar_oid_attribute_value", + expectedCfg: expectedConfigBadScalarOIDAttrValue, + expectedErr: fmt.Sprintf(errMsgScalarAttributeBadValue, "m3", "a2", "val3"), + }, + { + name: "NoColumnOIDOIDErrors", + nameVal: "no_column_oid_oid", + expectedCfg: expectedConfigNoColumnOIDOID, + expectedErr: fmt.Sprintf(errMsgColumnOIDNoOID, "m3"), + }, + { + name: "NoColumnOIDAttributeNameErrors", + nameVal: "no_column_oid_attribute_name", + expectedCfg: expectedConfigNoColumnOIDAttrName, + expectedErr: fmt.Sprintf(errMsgColumnAttributeNoName, "m3"), + }, + { + name: "BadColumnOIDAttributeNameErrors", + nameVal: "bad_column_oid_attribute_name", + expectedCfg: expectedConfigBadColumnOIDAttrName, + expectedErr: fmt.Sprintf(errMsgColumnAttributeBadName, "m3", "a1"), + }, + { + name: "BadColumnOIDAttributeValueErrors", + nameVal: "bad_column_oid_attribute_value", + expectedCfg: expectedConfigBadColumnOIDAttrValue, + expectedErr: fmt.Sprintf(errMsgColumnAttributeBadValue, "m3", "a2", "val3"), + }, + { + name: "BadColumnOIDResourceAttributeNameErrors", + nameVal: "bad_column_oid_resource_attribute_name", + expectedCfg: expectedConfigBadColumnOIDResourceAttrName, + expectedErr: fmt.Sprintf(errMsgColumnResourceAttributeBadName, "m3", "a2"), + }, + { + name: "ColumnOIDWithoutIndexedAttributeOrResourceAttributeErrors", + nameVal: "column_oid_no_indexed_attribute_or_resource_attribute", + expectedCfg: expectedConfigColumnOIDWithoutIndexAttributeOrResourceAttribute, + expectedErr: fmt.Sprintf(errMsgColumnIndexedAttributeRequired, "m3"), + }, + { + name: "NoResourceAttributeConfigOIDOrPrefixErrors", + nameVal: "no_resource_attribute_oid_or_prefix", + expectedCfg: expectedConfigNoResourceAttributeOIDOrPrefix, + expectedErr: fmt.Sprintf(errMsgResourceAttributeNoOIDOrPrefix, "ra1"), + }, + { + name: "ComplexConfigGood", + nameVal: "complex_good", + expectedCfg: expectedConfigComplexGood, + expectedErr: "", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + sub, err := cm.Sub(config.NewComponentIDWithName(typeStr, test.nameVal).String()) + require.NoError(t, err) + + cfg := factory.CreateDefaultConfig() + require.NoError(t, config.UnmarshalReceiver(sub, cfg)) + if test.expectedErr == "" { + require.NoError(t, cfg.Validate()) + } else { + require.ErrorContains(t, cfg.Validate(), test.expectedErr) + } + + require.Equal(t, test.expectedCfg, cfg) + }) + } +} + +// Testing Validate directly to test that missing data errors when no defaults are provided +func TestValidate(t *testing.T) { + type testCase struct { + name string + cfg *Config + expectedErr string + } + + testCases := []testCase{ + { + name: "NoEndpointErrors", + cfg: &Config{ + Version: "v2c", + Community: "public", + Metrics: map[string]*MetricConfig{ + "m3": { + Unit: "By", + Gauge: &GaugeMetric{ + ValueType: "float", + }, + ScalarOIDs: []ScalarOID{ + { + OID: "1", + }, + }, + }, + }, + }, + expectedErr: errEmptyEndpoint.Error(), + }, + { + name: "NoVersionErrors", + cfg: &Config{ + Endpoint: "udp://localhost:161", + Community: "public", + Metrics: map[string]*MetricConfig{ + "m3": { + Unit: "By", + Gauge: &GaugeMetric{ + ValueType: "float", + }, + ScalarOIDs: []ScalarOID{ + { + OID: "1", + }, + }, + }, + }, + }, + expectedErr: errEmptyVersion.Error(), + }, + { + name: "V3NoSecurityLevelErrors", + cfg: &Config{ + Endpoint: "udp://localhost:161", + Version: "v3", + User: "u", + Metrics: map[string]*MetricConfig{ + "m3": { + Unit: "By", + Gauge: &GaugeMetric{ + ValueType: "float", + }, + ScalarOIDs: []ScalarOID{ + { + OID: "1", + }, + }, + }, + }, + }, + expectedErr: errEmptySecurityLevel.Error(), + }, + { + name: "V3NoAuthTypeErrors", + cfg: &Config{ + Endpoint: "udp://localhost:161", + Version: "v3", + SecurityLevel: "auth_no_priv", + User: "u", + AuthPassword: "p", + Metrics: map[string]*MetricConfig{ + "m3": { + Unit: "By", + Gauge: &GaugeMetric{ + ValueType: "float", + }, + ScalarOIDs: []ScalarOID{ + { + OID: "1", + }, + }, + }, + }, + }, + expectedErr: errEmptyAuthType.Error(), + }, + { + name: "V3NoPrivacyTypeErrors", + cfg: &Config{ + Endpoint: "udp://localhost:161", + Version: "v3", + SecurityLevel: "auth_priv", + User: "u", + AuthType: "md5", + AuthPassword: "p", + PrivacyPassword: "pp", + Metrics: map[string]*MetricConfig{ + "m3": { + Unit: "By", + Gauge: &GaugeMetric{ + ValueType: "float", + }, + ScalarOIDs: []ScalarOID{ + { + OID: "1", + }, + }, + }, + }, + }, + expectedErr: errEmptyPrivacyType.Error(), + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + err := test.cfg.Validate() + assert.ErrorContains(t, err, test.expectedErr) + }) + } +} diff --git a/receiver/snmpreceiver/factory.go b/receiver/snmpreceiver/factory.go index 13bdbc959c60..ad35c3a994ae 100644 --- a/receiver/snmpreceiver/factory.go +++ b/receiver/snmpreceiver/factory.go @@ -17,6 +17,9 @@ package snmpreceiver // import "github.com/open-telemetry/opentelemetry-collecto import ( "context" "errors" + "fmt" + "net/url" + "strings" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config" @@ -46,6 +49,12 @@ func createDefaultConfig() config.Receiver { ReceiverSettings: config.NewReceiverSettings(config.NewComponentID(typeStr)), CollectionInterval: defaultCollectionInterval, }, + Endpoint: defaultEndpoint, + Version: defaultVersion, + Community: defaultCommunity, + SecurityLevel: defaultSecurityLevel, + AuthType: defaultAuthType, + PrivacyType: defaultPrivacyType, } } @@ -61,6 +70,10 @@ func createMetricsReceiver( return nil, errConfigNotSNMP } + if err := addMissingConfigDefaults(snmpConfig); err != nil { + return nil, fmt.Errorf("failed to validate added config defaults: %w", err) + } + snmpScraper := newScraper(params.Logger, snmpConfig, params) scraper, err := scraperhelper.NewScraper(typeStr, snmpScraper.scrape, scraperhelper.WithStart(snmpScraper.start)) if err != nil { @@ -69,3 +82,41 @@ func createMetricsReceiver( return scraperhelper.NewScraperControllerReceiver(&snmpConfig.ScraperControllerSettings, params, consumer, scraperhelper.AddScraper(scraper)) } + +// addMissingConfigDefaults adds any missing comfig parameters that have defaults +func addMissingConfigDefaults(cfg *Config) error { + // Add the schema prefix to the endpoint if it doesn't contain one + if !strings.Contains(cfg.Endpoint, "://") { + cfg.Endpoint = "udp://" + cfg.Endpoint + } + + // Add default port to endpoint if it doesn't contain one + u, err := url.Parse(cfg.Endpoint) + if err == nil && u.Port() == "" { + portSuffix := "161" + if cfg.Endpoint[len(cfg.Endpoint)-1:] != ":" { + portSuffix = ":" + portSuffix + } + cfg.Endpoint += portSuffix + } + + // Set defaults for metric configs + for _, metricCfg := range cfg.Metrics { + if metricCfg.Unit == "" { + metricCfg.Unit = "1" + } + if metricCfg.Gauge != nil && metricCfg.Gauge.ValueType == "" { + metricCfg.Gauge.ValueType = "float" + } + if metricCfg.Sum != nil { + if metricCfg.Sum.ValueType == "" { + metricCfg.Sum.ValueType = "float" + } + if metricCfg.Sum.Aggregation == "" { + metricCfg.Sum.Aggregation = "cumulative" + } + } + } + + return cfg.Validate() +} diff --git a/receiver/snmpreceiver/factory_test.go b/receiver/snmpreceiver/factory_test.go new file mode 100644 index 000000000000..048d2cf252f5 --- /dev/null +++ b/receiver/snmpreceiver/factory_test.go @@ -0,0 +1,277 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package snmpreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/snmpreceiver" + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +func TestNewFactory(t *testing.T) { + testCases := []struct { + desc string + testFunc func(*testing.T) + }{ + { + desc: "creates a new factory with correct type", + testFunc: func(t *testing.T) { + factory := NewFactory() + require.EqualValues(t, typeStr, factory.Type()) + }, + }, + { + desc: "creates a new factory with valid default config", + testFunc: func(t *testing.T) { + factory := NewFactory() + + var expectedCfg config.Receiver = &Config{ + ScraperControllerSettings: scraperhelper.ScraperControllerSettings{ + ReceiverSettings: config.NewReceiverSettings(config.NewComponentID(typeStr)), + CollectionInterval: defaultCollectionInterval, + }, + Endpoint: defaultEndpoint, + Version: defaultVersion, + Community: defaultCommunity, + SecurityLevel: "no_auth_no_priv", + AuthType: "MD5", + PrivacyType: "DES", + } + + require.Equal(t, expectedCfg, factory.CreateDefaultConfig()) + }, + }, + { + desc: "creates a new factory and CreateMetricsReceiver returns no error", + testFunc: func(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + snmpCfg := cfg.(*Config) + snmpCfg.Metrics = map[string]*MetricConfig{ + "m1": { + Unit: "1", + Gauge: &GaugeMetric{ValueType: "int"}, + ScalarOIDs: []ScalarOID{{ + OID: ".1", + }}, + }, + } + _, err := factory.CreateMetricsReceiver( + context.Background(), + componenttest.NewNopReceiverCreateSettings(), + cfg, + consumertest.NewNop(), + ) + require.NoError(t, err) + }, + }, + { + desc: "creates a new factory and CreateMetricsReceiver returns error with incorrect config", + testFunc: func(t *testing.T) { + factory := NewFactory() + _, err := factory.CreateMetricsReceiver( + context.Background(), + componenttest.NewNopReceiverCreateSettings(), + nil, + consumertest.NewNop(), + ) + require.ErrorIs(t, err, errConfigNotSNMP) + }, + }, + { + desc: "CreateMetricsReceiver adds missing scheme to endpoint", + testFunc: func(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + snmpCfg := cfg.(*Config) + snmpCfg.Endpoint = "localhost:161" + snmpCfg.Metrics = map[string]*MetricConfig{ + "m1": { + Unit: "1", + Gauge: &GaugeMetric{ValueType: "int"}, + ScalarOIDs: []ScalarOID{{ + OID: ".1", + }}, + }, + } + _, err := factory.CreateMetricsReceiver( + context.Background(), + componenttest.NewNopReceiverCreateSettings(), + cfg, + consumertest.NewNop(), + ) + require.NoError(t, err) + require.Equal(t, "udp://localhost:161", snmpCfg.Endpoint) + }, + }, + { + desc: "CreateMetricsReceiver adds missing port to endpoint", + testFunc: func(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + snmpCfg := cfg.(*Config) + snmpCfg.Endpoint = "udp://localhost" + snmpCfg.Metrics = map[string]*MetricConfig{ + "m1": { + Unit: "1", + Gauge: &GaugeMetric{ValueType: "int"}, + ScalarOIDs: []ScalarOID{{ + OID: ".1", + }}, + }, + } + _, err := factory.CreateMetricsReceiver( + context.Background(), + componenttest.NewNopReceiverCreateSettings(), + cfg, + consumertest.NewNop(), + ) + require.NoError(t, err) + require.Equal(t, "udp://localhost:161", snmpCfg.Endpoint) + }, + }, + { + desc: "CreateMetricsReceiver adds missing port to endpoint with trailing colon", + testFunc: func(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + snmpCfg := cfg.(*Config) + snmpCfg.Endpoint = "udp://localhost:" + snmpCfg.Metrics = map[string]*MetricConfig{ + "m1": { + Unit: "1", + Gauge: &GaugeMetric{ValueType: "int"}, + ScalarOIDs: []ScalarOID{{ + OID: ".1", + }}, + }, + } + _, err := factory.CreateMetricsReceiver( + context.Background(), + componenttest.NewNopReceiverCreateSettings(), + cfg, + consumertest.NewNop(), + ) + require.NoError(t, err) + require.Equal(t, "udp://localhost:161", snmpCfg.Endpoint) + }, + }, + { + desc: "CreateMetricsReceiver adds missing metric gauge value type as float", + testFunc: func(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + snmpCfg := cfg.(*Config) + snmpCfg.Metrics = map[string]*MetricConfig{ + "m1": { + Gauge: &GaugeMetric{}, + ScalarOIDs: []ScalarOID{{ + OID: ".1", + }}, + }, + } + _, err := factory.CreateMetricsReceiver( + context.Background(), + componenttest.NewNopReceiverCreateSettings(), + cfg, + consumertest.NewNop(), + ) + require.NoError(t, err) + require.Equal(t, "float", snmpCfg.Metrics["m1"].Gauge.ValueType) + }, + }, + { + desc: "CreateMetricsReceiver adds missing metric sum value type as float", + testFunc: func(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + snmpCfg := cfg.(*Config) + snmpCfg.Metrics = map[string]*MetricConfig{ + "m1": { + Sum: &SumMetric{}, + ScalarOIDs: []ScalarOID{{ + OID: ".1", + }}, + }, + } + _, err := factory.CreateMetricsReceiver( + context.Background(), + componenttest.NewNopReceiverCreateSettings(), + cfg, + consumertest.NewNop(), + ) + require.NoError(t, err) + require.Equal(t, "float", snmpCfg.Metrics["m1"].Sum.ValueType) + }, + }, + { + desc: "CreateMetricsReceiver adds missing metric sum aggregation as cumulative", + testFunc: func(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + snmpCfg := cfg.(*Config) + snmpCfg.Metrics = map[string]*MetricConfig{ + "m1": { + Sum: &SumMetric{}, + ScalarOIDs: []ScalarOID{{ + OID: ".1", + }}, + }, + } + _, err := factory.CreateMetricsReceiver( + context.Background(), + componenttest.NewNopReceiverCreateSettings(), + cfg, + consumertest.NewNop(), + ) + require.NoError(t, err) + require.Equal(t, "cumulative", snmpCfg.Metrics["m1"].Sum.Aggregation) + }, + }, + { + desc: "CreateMetricsReceiver adds missing metric unit as 1", + testFunc: func(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + snmpCfg := cfg.(*Config) + snmpCfg.Metrics = map[string]*MetricConfig{ + "m1": { + Gauge: &GaugeMetric{ValueType: "int"}, + ScalarOIDs: []ScalarOID{{ + OID: ".1", + }}, + }, + } + _, err := factory.CreateMetricsReceiver( + context.Background(), + componenttest.NewNopReceiverCreateSettings(), + cfg, + consumertest.NewNop(), + ) + require.NoError(t, err) + require.Equal(t, "1", snmpCfg.Metrics["m1"].Unit) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, tc.testFunc) + } +} diff --git a/receiver/snmpreceiver/go.mod b/receiver/snmpreceiver/go.mod index 36eea8123296..b53a73a6af78 100644 --- a/receiver/snmpreceiver/go.mod +++ b/receiver/snmpreceiver/go.mod @@ -3,12 +3,15 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/receiver/snmpre go 1.18 require ( + github.com/stretchr/testify v1.8.0 go.opentelemetry.io/collector v0.61.1-0.20221006231412-05d1c5f5572d go.opentelemetry.io/collector/pdata v0.61.1-0.20221006231412-05d1c5f5572d + go.uber.org/multierr v1.8.0 go.uber.org/zap v1.23.0 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -18,16 +21,17 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect go.opencensus.io v0.23.0 // indirect go.opentelemetry.io/otel v1.10.0 // indirect go.opentelemetry.io/otel/metric v0.32.1 // indirect go.opentelemetry.io/otel/trace v1.10.0 // indirect go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.8.0 // indirect golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect google.golang.org/grpc v1.49.0 // indirect google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/receiver/snmpreceiver/go.sum b/receiver/snmpreceiver/go.sum index 440c3bf18e14..3c1b7dee94c7 100644 --- a/receiver/snmpreceiver/go.sum +++ b/receiver/snmpreceiver/go.sum @@ -160,8 +160,10 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -243,13 +245,16 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -423,6 +428,7 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -437,6 +443,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/receiver/snmpreceiver/testdata/config.yaml b/receiver/snmpreceiver/testdata/config.yaml index e69de29bb2d1..b34e981cf7f2 100644 --- a/receiver/snmpreceiver/testdata/config.yaml +++ b/receiver/snmpreceiver/testdata/config.yaml @@ -0,0 +1,711 @@ +snmp/v2c_connection_good: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/no_endpoint: + collection_interval: 10s + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/invalid_endpoint: + collection_interval: 10s + endpoint: udp://a:a:a:a:a:a + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/no_port: + collection_interval: 10s + endpoint: "udp://localhost" + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/no_port_trailing_colon: + collection_interval: 10s + endpoint: "udp://localhost:" + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/bad_endpoint_scheme: + collection_interval: 10s + endpoint: "http://localhost:161" + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/no_endpoint_scheme: + collection_interval: 10s + endpoint: "localhost:161" + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/no_version: + collection_interval: 10s + endpoint: "udp://localhost:161" + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/bad_version: + collection_interval: 10s + endpoint: "udp://localhost:161" + version: 9999 + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/v3_connection_good: + collection_interval: 10s + endpoint: udp://localhost:161 + version: "v3" + security_level: "auth_priv" + user: u + auth_type: "MD5" + auth_password: "p" + privacy_type: "DES" + privacy_password: "pp" + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/v3_no_user: + collection_interval: 10s + endpoint: "udp://localhost:161" + version: "v3" + security_level: "no_auth_no_priv" + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/v3_no_security_level: + collection_interval: 10s + endpoint: "udp://localhost:161" + version: "v3" + user: u + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/v3_bad_security_level: + collection_interval: 10s + endpoint: "udp://localhost:161" + version: "v3" + security_level: "super" + user: u + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/v3_no_auth_type: + collection_interval: 10s + endpoint: "udp://localhost:161" + version: "v3" + security_level: "auth_no_priv" + user: u + auth_password: "p" + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/v3_bad_auth_type: + collection_interval: 10s + endpoint: "udp://localhost:161" + version: "v3" + security_level: "auth_no_priv" + user: u + auth_type: "super" + auth_password: "p" + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/v3_no_auth_password: + collection_interval: 10s + endpoint: "udp://localhost:161" + version: "v3" + security_level: "auth_no_priv" + user: u + auth_type: "MD5" + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/v3_no_privacy_type: + collection_interval: 10s + endpoint: "udp://localhost:161" + version: "v3" + security_level: "auth_priv" + user: u + auth_type: "MD5" + auth_password: "p" + privacy_password: "pp" + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/v3_bad_privacy_type: + collection_interval: 10s + endpoint: "udp://localhost:161" + version: "v3" + security_level: "auth_priv" + user: u + auth_type: "MD5" + auth_password: "p" + privacy_type: "super" + privacy_password: "pp" + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/v3_no_privacy_password: + collection_interval: 10s + endpoint: "udp://localhost:161" + version: "v3" + security_level: "auth_priv" + user: u + auth_type: "MD5" + auth_password: "p" + privacy_type: "DES" + metrics: + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/no_metric_config: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public +snmp/no_metric_unit: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + gauge: + value_type: float + scalar_oids: + - oid: "1" +snmp/no_metric_gauge_or_sum: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + unit: "By" + scalar_oids: + - oid: "1" +snmp/no_metric_oids: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: float +snmp/no_metric_gauge_type: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: + scalar_oids: + - oid: "1" +snmp/bad_metric_gauge_type: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: "Counter" + scalar_oids: + - oid: "1" +snmp/no_metric_sum_type: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + unit: "By" + sum: + aggregation: cumulative + monotonic: true + scalar_oids: + - oid: "1" +snmp/bad_metric_sum_type: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + unit: "By" + sum: + aggregation: cumulative + monotonic: true + value_type: "Counter" + scalar_oids: + - oid: "1" +snmp/no_metric_sum_aggregation: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + unit: "By" + sum: + monotonic: true + value_type: float + scalar_oids: + - oid: "1" +snmp/bad_metric_sum_aggregation: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + unit: "By" + sum: + aggregation: Counter + monotonic: true + value_type: float + scalar_oids: + - oid: "1" +snmp/no_scalar_oid_oid: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: "float" + scalar_oids: + - attributes: +snmp/no_attribute_oid_prefix_or_enums: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + attributes: + a2: + metrics: + m3: + unit: "By" + gauge: + value_type: "float" + scalar_oids: + - oid: "1" +snmp/no_scalar_oid_attribute_name: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: "float" + scalar_oids: + - oid: "1" + attributes: + - value: val1 +snmp/bad_scalar_oid_attribute_name: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + attributes: + a2: + enum: + - val1 + - val2 + metrics: + m3: + unit: "By" + gauge: + value_type: "float" + scalar_oids: + - oid: "1" + attributes: + - name: a1 + value: val1 +snmp/bad_scalar_oid_attribute: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + attributes: + a2: + oid: "1" + metrics: + m3: + unit: "By" + gauge: + value_type: "float" + scalar_oids: + - oid: "1" + attributes: + - name: a2 + value: val1 +snmp/bad_scalar_oid_attribute_value: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + attributes: + a2: + enum: + - val1 + - val2 + metrics: + m3: + unit: "By" + gauge: + value_type: "float" + scalar_oids: + - oid: "1" + attributes: + - name: a2 + value: val3 +snmp/no_column_oid_oid: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: "float" + column_oids: + - attributes: +snmp/no_column_oid_attribute_name: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + metrics: + m3: + unit: "By" + gauge: + value_type: "float" + column_oids: + - oid: "1" + attributes: + - name: +snmp/bad_column_oid_attribute_name: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + attributes: + a2: + oid: "1" + metrics: + m3: + unit: "By" + gauge: + value_type: "float" + column_oids: + - oid: "1" + attributes: + - name: a1 +snmp/bad_column_oid_attribute_value: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + attributes: + a2: + enum: + - val1 + - val2 + metrics: + m3: + unit: "By" + gauge: + value_type: "float" + column_oids: + - oid: "1" + attributes: + - name: a2 + value: val3 +snmp/bad_column_oid_resource_attribute_name: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + resource_attributes: + ra1: + oid: "2" + metrics: + m3: + unit: "By" + gauge: + value_type: "float" + column_oids: + - oid: "1" + resource_attributes: + - a2 +snmp/column_oid_no_indexed_attribute_or_resource_attribute: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + attributes: + a2: + enum: + - val1 + - val2 + metrics: + m3: + unit: "By" + gauge: + value_type: "float" + column_oids: + - oid: "1" + attributes: + - name: a2 + value: val1 +snmp/no_resource_attribute_oid_or_prefix: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + resource_attributes: + ra1: + metrics: + m3: + unit: "By" + gauge: + value_type: "float" + column_oids: + - oid: "1" + resource_attributes: + - ra1 +snmp/complex_good: + collection_interval: 10s + endpoint: udp://localhost:161 + version: v2c + community: public + resource_attributes: + ra1: + indexed_value_prefix: p + ra2: + oid: "1" + attributes: + a1: + value: v + enum: + - val1 + a2: + enum: + - val1 + - val2 + a3: + indexed_value_prefix: p + a4: + oid: "1" + metrics: + m1: + unit: 1 + sum: + aggregation: cumulative + monotonic: true + value_type: int + column_oids: + - oid: "1" + attributes: + - name: a4 + m2: + unit: "By" + gauge: + value_type: int + column_oids: + - oid: "1" + attributes: + - name: a3 + - name: a2 + value: val1 + - oid: "2" + attributes: + - name: a3 + - name: a2 + value: val2 + m3: + unit: "By" + gauge: + value_type: float + scalar_oids: + - oid: "1" + attributes: + - name: a1 + value: val1 + m4: + unit: "{things}" + sum: + aggregation: cumulative + monotonic: true + value_type: int + scalar_oids: + - oid: "1" + m5: + unit: "{things}" + sum: + aggregation: cumulative + monotonic: false + value_type: int + scalar_oids: + - oid: "1" + attributes: + - name: a1 + value: val1 + m6: + unit: 1 + sum: + aggregation: delta + monotonic: true + value_type: int + scalar_oids: + - oid: "1" + attributes: + - name: a2 + value: val1 + - oid: "2" + attributes: + - name: a2 + value: val2 + m7: + unit: "By" + gauge: + value_type: int + column_oids: + - oid: "1" + resource_attributes: + - ra1 + m8: + unit: "By" + gauge: + value_type: int + column_oids: + - oid: "1" + resource_attributes: + - ra2 + m9: + unit: "By" + gauge: + value_type: int + column_oids: + - oid: "1" + resource_attributes: + - ra1 + - ra2 + m10: + unit: "By" + gauge: + value_type: int + column_oids: + - oid: "1" + resource_attributes: + - ra1 + - ra2 + attributes: + - name: a1 + value: val1 + - name: a3 + - name: a4