Skip to content

Commit

Permalink
NDM: Add snmp.interface_status metric (#14797)
Browse files Browse the repository at this point in the history
* NDM: Add snmp.interface_status metric

* update test

* Add reno

* Address review

* Rename metric

* Address review

* Add InterfaceStatus enum

* Remove iota and use explicit values
  • Loading branch information
FlorianVeaux authored Jan 6, 2023
1 parent 80a7093 commit c221645
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 14 deletions.
29 changes: 29 additions & 0 deletions pkg/collector/corechecks/snmp/common/contants.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,32 @@ const SnmpIntegrationName = "snmp"

// SnmpExternalTagsSourceType is the source id used for external tags
const SnmpExternalTagsSourceType = "snmp"

type IfAdminStatus int

const (
AdminStatus_Up IfAdminStatus = 1
AdminStatus_Down IfAdminStatus = 2
AdminStatus_Testing IfAdminStatus = 3
)

type IfOperStatus int

const (
OperStatus_Up IfOperStatus = 1
OperStatus_Down IfOperStatus = 2
OperStatus_Testing IfOperStatus = 3
OperStatus_Unknown IfOperStatus = 4
OperStatus_Dormant IfOperStatus = 5
OperStatus_NotPresent IfOperStatus = 6
OperStatus_LowerLayerDown IfOperStatus = 7
)

type InterfaceStatus string

const (
InterfaceStatus_Up InterfaceStatus = "up"
InterfaceStatus_Down InterfaceStatus = "down"
InterfaceStatus_Warning InterfaceStatus = "warning"
InterfaceStatus_Off InterfaceStatus = "off"
)
20 changes: 11 additions & 9 deletions pkg/collector/corechecks/snmp/internal/metadata/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

package metadata

import "github.com/DataDog/datadog-agent/pkg/collector/corechecks/snmp/common"

// PayloadMetadataBatchSize is the number of resources per event payload
// Resources are devices, interfaces, etc
const PayloadMetadataBatchSize = 100
Expand Down Expand Up @@ -67,15 +69,15 @@ type DeviceMetadata struct {

// InterfaceMetadata contains interface metadata
type InterfaceMetadata struct {
DeviceID string `json:"device_id"`
IDTags []string `json:"id_tags"` // used to correlate with interface metrics
Index int32 `json:"index"` // IF-MIB ifIndex type is InterfaceIndex (Integer32 (1..2147483647))
Name string `json:"name,omitempty"`
Alias string `json:"alias,omitempty"`
Description string `json:"description,omitempty"`
MacAddress string `json:"mac_address,omitempty"`
AdminStatus int32 `json:"admin_status,omitempty"` // IF-MIB ifAdminStatus type is INTEGER
OperStatus int32 `json:"oper_status,omitempty"` // IF-MIB ifOperStatus type is INTEGER
DeviceID string `json:"device_id"`
IDTags []string `json:"id_tags"` // used to correlate with interface metrics
Index int32 `json:"index"` // IF-MIB ifIndex type is InterfaceIndex (Integer32 (1..2147483647))
Name string `json:"name,omitempty"`
Alias string `json:"alias,omitempty"`
Description string `json:"description,omitempty"`
MacAddress string `json:"mac_address,omitempty"`
AdminStatus common.IfAdminStatus `json:"admin_status,omitempty"` // IF-MIB ifAdminStatus type is INTEGER
OperStatus common.IfOperStatus `json:"oper_status,omitempty"` // IF-MIB ifOperStatus type is INTEGER
}

// IPAddressMetadata contains ip address metadata
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ package report

import (
json "encoding/json"
"github.com/DataDog/datadog-agent/pkg/collector/corechecks/snmp/internal/lldp"
"sort"
"strconv"
"strings"
"time"

"github.com/DataDog/datadog-agent/pkg/collector/corechecks/snmp/internal/lldp"

"github.com/DataDog/datadog-agent/pkg/epforwarder"
"github.com/DataDog/datadog-agent/pkg/util"
"github.com/DataDog/datadog-agent/pkg/util/log"
Expand All @@ -23,6 +24,8 @@ import (
"github.com/DataDog/datadog-agent/pkg/collector/corechecks/snmp/internal/valuestore"
)

const interfaceStatusMetric = "snmp.interface.status"

// ReportNetworkDeviceMetadata reports device metadata
func (ms *MetricSender) ReportNetworkDeviceMetadata(config *checkconfig.CheckConfig, store *valuestore.ResultValueStore, origTags []string, collectTime time.Time, deviceStatus metadata.DeviceStatus) {
tags := common.CopyStrings(origTags)
Expand All @@ -46,6 +49,42 @@ func (ms *MetricSender) ReportNetworkDeviceMetadata(config *checkconfig.CheckCon
}
ms.sender.EventPlatformEvent(string(payloadBytes), epforwarder.EventTypeNetworkDevicesMetadata)
}

// Telemetry
for _, interfaceStatus := range interfaces {
status := string(computeInterfaceStatus(interfaceStatus.AdminStatus, interfaceStatus.OperStatus))
interfaceTags := []string{"status:" + status, "interface:" + interfaceStatus.Name, "interface_alias:" + interfaceStatus.Alias, "interface_index:" + strconv.Itoa(int(interfaceStatus.Index))}
interfaceTags = append(interfaceTags, tags...)
ms.sender.Gauge(interfaceStatusMetric, 1, "", interfaceTags)
}
}

func computeInterfaceStatus(adminStatus common.IfAdminStatus, operStatus common.IfOperStatus) common.InterfaceStatus {
if adminStatus == common.AdminStatus_Up {
switch {
case operStatus == common.OperStatus_Up:
return common.InterfaceStatus_Up
case operStatus == common.OperStatus_Down:
return common.InterfaceStatus_Down
}
return common.InterfaceStatus_Warning
}
if adminStatus == common.AdminStatus_Down {
switch {
case operStatus == common.OperStatus_Up:
return common.InterfaceStatus_Down
case operStatus == common.OperStatus_Down:
return common.InterfaceStatus_Off
}
return common.InterfaceStatus_Warning
}
if adminStatus == common.AdminStatus_Testing {
switch {
case operStatus != common.OperStatus_Down:
return common.InterfaceStatus_Warning
}
}
return common.InterfaceStatus_Down
}

func buildMetadataStore(metadataConfigs checkconfig.MetadataConfig, values *valuestore.ResultValueStore) *metadata.Store {
Expand Down Expand Up @@ -183,8 +222,8 @@ func buildNetworkInterfacesMetadata(deviceID string, store *metadata.Store) []me
Alias: store.GetColumnAsString("interface.alias", strIndex),
Description: store.GetColumnAsString("interface.description", strIndex),
MacAddress: store.GetColumnAsString("interface.mac_address", strIndex),
AdminStatus: int32(store.GetColumnAsFloat("interface.admin_status", strIndex)),
OperStatus: int32(store.GetColumnAsFloat("interface.oper_status", strIndex)),
AdminStatus: common.IfAdminStatus((store.GetColumnAsFloat("interface.admin_status", strIndex))),
OperStatus: common.IfOperStatus((store.GetColumnAsFloat("interface.oper_status", strIndex))),
IDTags: ifIDTags,
}
interfaces = append(interfaces, networkInterface)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,15 @@ func Test_metricSender_reportNetworkDeviceMetadata_withInterfaces(t *testing.T)
"1": valuestore.ResultValue{Value: float64(21)},
"2": valuestore.ResultValue{Value: float64(22)},
},
"1.3.6.1.2.1.31.1.1.1.18": {
"1": valuestore.ResultValue{Value: "ifAlias1"},
"2": valuestore.ResultValue{Value: "ifAlias2"},
},
},
}
sender := mocksender.NewMockSender("testID") // required to initiate aggregator
sender.On("EventPlatformEvent", mock.Anything, mock.Anything).Return()
sender.On("Gauge", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
ms := &MetricSender{
sender: sender,
}
Expand All @@ -260,6 +265,12 @@ func Test_metricSender_reportNetworkDeviceMetadata_withInterfaces(t *testing.T)
Name: "ifName",
},
},
"alias": {
Symbol: checkconfig.SymbolConfig{
OID: "1.3.6.1.2.1.31.1.1.1.18",
Name: "ifAlias",
},
},
},
IDTags: checkconfig.MetricTagConfigList{
checkconfig.MetricTagConfig{
Expand All @@ -280,6 +291,11 @@ func Test_metricSender_reportNetworkDeviceMetadata_withInterfaces(t *testing.T)
assert.NoError(t, err)
ms.ReportNetworkDeviceMetadata(config, storeWithIfName, []string{"tag1", "tag2"}, collectTime, metadata.DeviceStatusReachable)

ifTags1 := []string{"tag1", "tag2", "status:down", "interface:21", "interface_alias:ifAlias1", "interface_index:1"}
ifTags2 := []string{"tag1", "tag2", "status:down", "interface:22", "interface_alias:ifAlias2", "interface_index:2"}

sender.AssertMetric(t, "Gauge", interfaceStatusMetric, 1., "", ifTags1)
sender.AssertMetric(t, "Gauge", interfaceStatusMetric, 1., "", ifTags2)
// language=json
event := []byte(`
{
Expand Down Expand Up @@ -307,15 +323,17 @@ func Test_metricSender_reportNetworkDeviceMetadata_withInterfaces(t *testing.T)
"interface:21"
],
"index": 1,
"name": "21"
"name": "21",
"alias": "ifAlias1"
},
{
"device_id": "1234",
"id_tags": [
"interface:22"
],
"index": 2,
"name": "22"
"name": "22",
"alias": "ifAlias2"
}
],
"collect_timestamp":1415792726
Expand Down Expand Up @@ -455,3 +473,62 @@ func Test_batchPayloads(t *testing.T) {
assert.Equal(t, 51, len(payloads[5].Links))
assert.Equal(t, topologyLinks[49:100], payloads[5].Links)
}

func TestComputeInterfaceStatus(t *testing.T) {
type testCase struct {
ifAdminStatus common.IfAdminStatus
ifOperStatus common.IfOperStatus
status common.InterfaceStatus
}

// Test the method with only valid input for ifAdminStatus and ifOperStatus
allTests := []testCase{
// Valid test cases
{common.AdminStatus_Up, common.OperStatus_Up, common.InterfaceStatus_Up},
{common.AdminStatus_Up, common.OperStatus_Down, common.InterfaceStatus_Down},
{common.AdminStatus_Up, common.OperStatus_Testing, common.InterfaceStatus_Warning},
{common.AdminStatus_Up, common.OperStatus_Unknown, common.InterfaceStatus_Warning},
{common.AdminStatus_Up, common.OperStatus_Dormant, common.InterfaceStatus_Warning},
{common.AdminStatus_Up, common.OperStatus_NotPresent, common.InterfaceStatus_Warning},
{common.AdminStatus_Up, common.OperStatus_LowerLayerDown, common.InterfaceStatus_Warning},
{common.AdminStatus_Down, common.OperStatus_Up, common.InterfaceStatus_Down},
{common.AdminStatus_Down, common.OperStatus_Down, common.InterfaceStatus_Off},
{common.AdminStatus_Down, common.OperStatus_Testing, common.InterfaceStatus_Warning},
{common.AdminStatus_Down, common.OperStatus_Unknown, common.InterfaceStatus_Warning},
{common.AdminStatus_Down, common.OperStatus_Dormant, common.InterfaceStatus_Warning},
{common.AdminStatus_Down, common.OperStatus_NotPresent, common.InterfaceStatus_Warning},
{common.AdminStatus_Down, common.OperStatus_LowerLayerDown, common.InterfaceStatus_Warning},
{common.AdminStatus_Testing, common.OperStatus_Up, common.InterfaceStatus_Warning},
{common.AdminStatus_Testing, common.OperStatus_Down, common.InterfaceStatus_Down},
{common.AdminStatus_Testing, common.OperStatus_Testing, common.InterfaceStatus_Warning},
{common.AdminStatus_Testing, common.OperStatus_Unknown, common.InterfaceStatus_Warning},
{common.AdminStatus_Testing, common.OperStatus_Dormant, common.InterfaceStatus_Warning},
{common.AdminStatus_Testing, common.OperStatus_NotPresent, common.InterfaceStatus_Warning},
{common.AdminStatus_Testing, common.OperStatus_LowerLayerDown, common.InterfaceStatus_Warning},

// Invalid ifOperStatus
{common.AdminStatus_Up, 0, common.InterfaceStatus_Warning},
{common.AdminStatus_Up, 8, common.InterfaceStatus_Warning},
{common.AdminStatus_Up, 100, common.InterfaceStatus_Warning},
{common.AdminStatus_Down, 0, common.InterfaceStatus_Warning},
{common.AdminStatus_Down, 8, common.InterfaceStatus_Warning},
{common.AdminStatus_Down, 100, common.InterfaceStatus_Warning},
{common.AdminStatus_Testing, 0, common.InterfaceStatus_Warning},
{common.AdminStatus_Testing, 8, common.InterfaceStatus_Warning},
{common.AdminStatus_Testing, 100, common.InterfaceStatus_Warning},

// Invalid ifAdminStatus
{0, common.OperStatus_Unknown, common.InterfaceStatus_Down},
{0, common.OperStatus_Down, common.InterfaceStatus_Down},
{0, common.OperStatus_Up, common.InterfaceStatus_Down},
{4, common.OperStatus_Up, common.InterfaceStatus_Down},
{4, common.OperStatus_Down, common.InterfaceStatus_Down},
{4, common.OperStatus_Testing, common.InterfaceStatus_Down},
{100, common.OperStatus_Up, common.InterfaceStatus_Down},
{100, common.OperStatus_Down, common.InterfaceStatus_Down},
{100, common.OperStatus_Testing, common.InterfaceStatus_Down},
}
for _, test := range allTests {
assert.Equal(t, test.status, computeInterfaceStatus(test.ifAdminStatus, test.ifOperStatus))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Each section from every release note are combined when the
# CHANGELOG.rst is rendered. So the text needs to be worded so that
# it does not depend on any information only available in another
# section. This may mean repeating some details, but each section
# must be readable independently of the other.
#
# Each section note must be formatted as reStructuredText.
---
enhancements:
- Adds a new snmp.interface_status metric reflecting the same status as within NDM.

0 comments on commit c221645

Please sign in to comment.