Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NDMII-3236] Update devicecheck profile when ProfileProvider has changed #32185

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

package checkconfig

import (
"fmt"
"github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition"
"github.com/DataDog/datadog-agent/pkg/util/log"
"slices"
)

// BuildProfile builds the fetchable profile for this config.
//
// If ProfileName == ProfileNameInline, then the result just contains the inline
// metrics and tags from the initconfig. This is also true if ProfileName ==
// ProfileNameAuto and sysObjectID == "" (this is useful when you want basic
// metadata for a device that you can't yet get the sysObjectID from).
//
// Otherwise, the result will be a copy of the profile from ProfileProvider that
// matches this config, either by sysObjectID if ProfileName == ProfileNameAuto
// or by ProfileName directly otherwise.
//
// The error will be non-nil if ProfileProvider doesn't know ProfileName, or if
// ProfileName is ProfileNameAuto and ProfileProvider finds no match for
// sysObjectID. In this case the returned profile will still be non-nil, and
// will be the same as what you'd get for an inline profile.
func (c *CheckConfig) BuildProfile(sysObjectID string) (profiledefinition.ProfileDefinition, error) {
var parentProfile *profiledefinition.ProfileDefinition
var profileErr error

switch c.ProfileName {
case ProfileNameInline: // inline profile -> no parent
parentProfile = nil
case ProfileNameAuto: // determine based on sysObjectID
// empty sysObjectID happens when we need the profile but couldn't connect to the device.
if sysObjectID != "" {
if profileConfig, err := c.ProfileProvider.GetProfileForSysObjectID(sysObjectID); err != nil {
profileErr = fmt.Errorf("failed to get profile for sysObjectID %q: %v", sysObjectID, err)
} else {
parentProfile = &profileConfig.Definition
log.Debugf("detected profile %q for sysobjectid %q", parentProfile.Name, sysObjectID)
}
}
default:
if profile := c.ProfileProvider.GetProfile(c.ProfileName); profile == nil {
profileErr = fmt.Errorf("unknown profile %q", c.ProfileName)
} else {
parentProfile = &profile.Definition
}
}

profile := *profiledefinition.NewProfileDefinition()
profile.Metrics = slices.Clone(c.RequestedMetrics)
profile.MetricTags = slices.Clone(c.RequestedMetricTags)
if parentProfile != nil {
profile.Name = parentProfile.Name
profile.Version = parentProfile.Version
profile.StaticTags = append(profile.StaticTags, "snmp_profile:"+parentProfile.Name)
vendor := parentProfile.GetVendor()
if vendor != "" {
profile.StaticTags = append(profile.StaticTags, "device_vendor:"+vendor)
}
profile.StaticTags = append(profile.StaticTags, parentProfile.StaticTags...)
profile.Metadata = parentProfile.Metadata
profile.Metrics = append(profile.Metrics, parentProfile.Metrics...)
profile.MetricTags = append(profile.MetricTags, parentProfile.MetricTags...)
}
profile.Metadata = updateMetadataDefinitionWithDefaults(profile.Metadata, c.CollectTopology)

return profile, profileErr
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2024-present Datadog, Inc.

package checkconfig

import (
"github.com/DataDog/datadog-agent/pkg/collector/corechecks/snmp/internal/profile"
"github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

func TestBuildProfile(t *testing.T) {
metrics := []profiledefinition.MetricsConfig{
{Symbol: profiledefinition.SymbolConfig{OID: "1.2.3.4.5", Name: "someMetric"}},
{
Symbols: []profiledefinition.SymbolConfig{
{
OID: "1.2.3.4.6",
Name: "abc",
},
},
MetricTags: profiledefinition.MetricTagConfigList{
profiledefinition.MetricTagConfig{
Symbol: profiledefinition.SymbolConfigCompat{
OID: "1.2.3.4.7",
},
},
},
},
}
profile1 := profiledefinition.ProfileDefinition{
Name: "profile1",
Version: 12,
Metrics: metrics,
MetricTags: []profiledefinition.MetricTagConfig{
{Tag: "location", Symbol: profiledefinition.SymbolConfigCompat{OID: "1.3.6.1.2.1.1.6.0", Name: "sysLocation"}},
},
Metadata: profiledefinition.MetadataConfig{
"device": {
Fields: map[string]profiledefinition.MetadataField{
"vendor": {
Value: "a-vendor",
},
"description": {
Symbol: profiledefinition.SymbolConfig{
OID: "1.3.6.1.2.1.1.99.3.0",
Name: "sysDescr",
},
},
"name": {
Symbols: []profiledefinition.SymbolConfig{
{
OID: "1.3.6.1.2.1.1.99.1.0",
Name: "symbol1",
},
{
OID: "1.3.6.1.2.1.1.99.2.0",
Name: "symbol2",
},
},
},
},
},
"interface": {
Fields: map[string]profiledefinition.MetadataField{
"oper_status": {
Symbol: profiledefinition.SymbolConfig{
OID: "1.3.6.1.2.1.2.2.1.99",
Name: "someIfSymbol",
},
},
},
IDTags: profiledefinition.MetricTagConfigList{
{
Tag: "interface",
Symbol: profiledefinition.SymbolConfigCompat{
OID: "1.3.6.1.2.1.31.1.1.1.1",
Name: "ifName",
},
},
},
},
},
SysObjectIDs: profiledefinition.StringArray{"1.1.1.*"},
}

mergedMetadata := make(profiledefinition.MetadataConfig)
mergeMetadata(mergedMetadata, profile1.Metadata)
mergedMetadata["ip_addresses"] = LegacyMetadataConfig["ip_addresses"]

mockProfiles := profile.StaticProvider(profile.ProfileConfigMap{
"profile1": profile.ProfileConfig{
Definition: profile1,
},
})

type testCase struct {
name string
config *CheckConfig
sysObjectID string
expected profiledefinition.ProfileDefinition
expectedError string
}
for _, tc := range []testCase{
{
name: "inline",
config: &CheckConfig{
IPAddress: "1.2.3.4",
RequestedMetrics: metrics,
RequestedMetricTags: []profiledefinition.MetricTagConfig{
{Tag: "location", Symbol: profiledefinition.SymbolConfigCompat{OID: "1.3.6.1.2.1.1.6.0", Name: "sysLocation"}},
},
ProfileName: ProfileNameInline,
},
expected: profiledefinition.ProfileDefinition{
Metrics: metrics,
MetricTags: []profiledefinition.MetricTagConfig{
{Tag: "location", Symbol: profiledefinition.SymbolConfigCompat{OID: "1.3.6.1.2.1.1.6.0", Name: "sysLocation"}},
},
Metadata: LegacyMetadataConfig,
},
}, {
name: "static",
config: &CheckConfig{
IPAddress: "1.2.3.4",
ProfileProvider: mockProfiles,
ProfileName: "profile1",
},
expected: profiledefinition.ProfileDefinition{
Name: "profile1",
Version: 12,
Metrics: metrics,
MetricTags: []profiledefinition.MetricTagConfig{
{Tag: "location", Symbol: profiledefinition.SymbolConfigCompat{OID: "1.3.6.1.2.1.1.6.0", Name: "sysLocation"}},
},
StaticTags: []string{"snmp_profile:profile1", "device_vendor:a-vendor"},
Metadata: mergedMetadata,
},
}, {
name: "dynamic",
config: &CheckConfig{
IPAddress: "1.2.3.4",
ProfileProvider: mockProfiles,
ProfileName: ProfileNameAuto,
},
sysObjectID: "1.1.1.1",
expected: profiledefinition.ProfileDefinition{
Name: "profile1",
Version: 12,
Metrics: metrics,
MetricTags: []profiledefinition.MetricTagConfig{
{Tag: "location", Symbol: profiledefinition.SymbolConfigCompat{OID: "1.3.6.1.2.1.1.6.0", Name: "sysLocation"}},
},
StaticTags: []string{"snmp_profile:profile1", "device_vendor:a-vendor"},
Metadata: mergedMetadata,
},
}, {
name: "static with requested metrics",
config: &CheckConfig{
IPAddress: "1.2.3.4",
ProfileProvider: mockProfiles,
CollectDeviceMetadata: true,
CollectTopology: false,
ProfileName: "profile1",
RequestedMetrics: []profiledefinition.MetricsConfig{
{Symbol: profiledefinition.SymbolConfig{OID: "3.1", Name: "global-metric"}}},
RequestedMetricTags: []profiledefinition.MetricTagConfig{
{Tag: "global-tag", Symbol: profiledefinition.SymbolConfigCompat{OID: "3.2", Name: "globalSymbol"}},
},
},
expected: profiledefinition.ProfileDefinition{
Name: "profile1",
Version: 12,
Metrics: append([]profiledefinition.MetricsConfig{
{Symbol: profiledefinition.SymbolConfig{OID: "3.1", Name: "global-metric"}}},
metrics...),
MetricTags: []profiledefinition.MetricTagConfig{
{Tag: "global-tag", Symbol: profiledefinition.SymbolConfigCompat{OID: "3.2", Name: "globalSymbol"}},
{Tag: "location", Symbol: profiledefinition.SymbolConfigCompat{OID: "1.3.6.1.2.1.1.6.0", Name: "sysLocation"}},
},
Metadata: mergedMetadata,
StaticTags: []string{"snmp_profile:profile1", "device_vendor:a-vendor"},
},
}, {
name: "static unknown",
config: &CheckConfig{
IPAddress: "1.2.3.4",
ProfileProvider: mockProfiles,
ProfileName: "f5",
},
expectedError: "unknown profile \"f5\"",
}, {
name: "dynamic unknown",
config: &CheckConfig{
IPAddress: "1.2.3.4",
ProfileProvider: mockProfiles,
ProfileName: ProfileNameAuto,
},
sysObjectID: "3.3.3.3",
expectedError: "failed to get profile for sysObjectID \"3.3.3.3\": no profiles found for sysObjectID \"3." +
"3.3.3\"",
},
} {
t.Run(tc.name, func(t *testing.T) {
profile, err := tc.config.BuildProfile(tc.sysObjectID)
if tc.expectedError != "" {
assert.EqualError(t, err, tc.expectedError)
} else {
require.NoError(t, err)
if !assert.Equal(t, tc.expected, profile) {
for k, v := range tc.expected.Metadata["device"].Fields {
t.Log(k, v)
}
t.Log("===")
for k, v := range profile.Metadata["device"].Fields {
t.Log(k, v)
}
}
}
})
}
}
Loading
Loading