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

fix(inputs.gnmi): Add option to guess path tag from subscription #14951

Merged
merged 3 commits into from
Mar 11, 2024
Merged
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
5 changes: 5 additions & 0 deletions migrations/all/inputs_gnmi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build !custom || (migrations && (inputs || inputs.gnmi))

package all

import _ "github.com/influxdata/telegraf/migrations/inputs_gnmi" // register migration
47 changes: 47 additions & 0 deletions migrations/inputs_gnmi/migration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package inputs_gnmi

import (
"github.com/influxdata/toml"
"github.com/influxdata/toml/ast"

"github.com/influxdata/telegraf/migrations"
)

// Migration function
func migrate(tbl *ast.Table) ([]byte, string, error) {
// Decode the old data structure
var plugin map[string]interface{}
if err := toml.UnmarshalTable(tbl, &plugin); err != nil {
return nil, "", err
}

// Check for deprecated option(s) and migrate them
var applied bool
if raw, found := plugin["guess_path_tag"]; found {
applied = true

if v, ok := raw.(bool); ok && v {
plugin["path_guessing_strategy"] = "common path"
}

// Remove the ignored setting
delete(plugin, "guess_path_tag")
}

// No options migrated so we can exit early
if !applied {
return nil, "", migrations.ErrNotApplicable
}

// Create the corresponding plugin configurations
cfg := migrations.CreateTOMLStruct("inputs", "gnmi")
cfg.Add("inputs", "gnmi", plugin)

output, err := toml.Marshal(cfg)
return output, "", err
}

// Register the migration function for the plugin type
func init() {
migrations.AddPluginOptionMigration("inputs.gnmi", migrate)
}
73 changes: 73 additions & 0 deletions migrations/inputs_gnmi/migration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package inputs_gnmi_test

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"

"github.com/influxdata/telegraf/config"
_ "github.com/influxdata/telegraf/migrations/inputs_gnmi" // register migration
"github.com/influxdata/telegraf/plugins/inputs/gnmi"
)

func TestNoMigration(t *testing.T) {
plugin := &gnmi.GNMI{}
defaultCfg := []byte(plugin.SampleConfig())

// Migrate and check that nothing changed
output, n, err := config.ApplyMigrations(defaultCfg)
require.NoError(t, err)
require.NotEmpty(t, output)
require.Zero(t, n)
require.Equal(t, string(defaultCfg), string(output))
}

func TestCases(t *testing.T) {
// Get all directories in testdata
folders, err := os.ReadDir("testcases")
require.NoError(t, err)

for _, f := range folders {
// Only handle folders
if !f.IsDir() {
continue
}

t.Run(f.Name(), func(t *testing.T) {
testcasePath := filepath.Join("testcases", f.Name())
inputFile := filepath.Join(testcasePath, "telegraf.conf")
expectedFile := filepath.Join(testcasePath, "expected.conf")

// Read the expected output
expected := config.NewConfig()
require.NoError(t, expected.LoadConfig(expectedFile))
require.NotEmpty(t, expected.Inputs)

// Read the input data
input, remote, err := config.LoadConfigFile(inputFile)
require.NoError(t, err)
require.False(t, remote)
require.NotEmpty(t, input)

// Migrate
output, n, err := config.ApplyMigrations(input)
require.NoError(t, err)
require.NotEmpty(t, output)
require.GreaterOrEqual(t, n, uint64(1))
actual := config.NewConfig()
require.NoError(t, actual.LoadConfigData(output))

// Test the output
require.Len(t, actual.Inputs, len(expected.Inputs))
actualIDs := make([]string, 0, len(expected.Inputs))
expectedIDs := make([]string, 0, len(expected.Inputs))
for i := range actual.Inputs {
actualIDs = append(actualIDs, actual.Inputs[i].ID())
expectedIDs = append(expectedIDs, expected.Inputs[i].ID())
}
require.ElementsMatch(t, expectedIDs, actualIDs, string(output))
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[[inputs.gnmi]]
addresses = ["10.49.234.114:57777"]
password = "cisco"
path_guessing_strategy = "common path"
username = "cisco"
[[inputs.gnmi.subscription]]
name = "ifcounters"
origin = "openconfig-interfaces"
path = "/interfaces/interface/state/counters"
subscription_mode = "sample"
sample_interval = "10s"
109 changes: 109 additions & 0 deletions migrations/inputs_gnmi/testcases/deprecated_guess_path/telegraf.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# gNMI telemetry input plugin
[[inputs.gnmi]]
## Address and port of the gNMI GRPC server
addresses = ["10.49.234.114:57777"]

## define credentials
username = "cisco"
password = "cisco"

## gNMI encoding requested (one of: "proto", "json", "json_ietf", "bytes")
# encoding = "proto"

## redial in case of failures after
# redial = "10s"

## gRPC Maximum Message Size
# max_msg_size = "4MB"

## Enable to get the canonical path as field-name
# canonical_field_names = false

## Remove leading slashes and dots in field-name
# trim_field_names = false

## Guess the path-tag if an update does not contain a prefix-path
## If enabled, the common-path of all elements in the update is used.
guess_path_tag = true

## enable client-side TLS and define CA to authenticate the device
# enable_tls = false
# tls_ca = "/etc/telegraf/ca.pem"
## Minimal TLS version to accept by the client
# tls_min_version = "TLS12"
## Use TLS but skip chain & host verification
# insecure_skip_verify = true

## define client-side TLS certificate & key to authenticate to the device
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"

## gNMI subscription prefix (optional, can usually be left empty)
## See: https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#222-paths
# origin = ""
# prefix = ""
# target = ""

## Vendor specific options
## This defines what vendor specific options to load.
## * Juniper Header Extension (juniper_header): some sensors are directly managed by
## Linecard, which adds the Juniper GNMI Header Extension. Enabling this
## allows the decoding of the Extension header if present. Currently this knob
## adds component, component_id & sub_component_id as additional tags
# vendor_specific = []

## Define additional aliases to map encoding paths to measurement names
# [inputs.gnmi.aliases]
# ifcounters = "openconfig:/interfaces/interface/state/counters"

[[inputs.gnmi.subscription]]
## Name of the measurement that will be emitted
name = "ifcounters"

## Origin and path of the subscription
## See: https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#222-paths
##
## origin usually refers to a (YANG) data model implemented by the device
## and path to a specific substructure inside it that should be subscribed
## to (similar to an XPath). YANG models can be found e.g. here:
## https://github.com/YangModels/yang/tree/master/vendor/cisco/xr
origin = "openconfig-interfaces"
path = "/interfaces/interface/state/counters"

## Subscription mode ("target_defined", "sample", "on_change") and interval
subscription_mode = "sample"
sample_interval = "10s"

## Suppress redundant transmissions when measured values are unchanged
# suppress_redundant = false

## If suppression is enabled, send updates at least every X seconds anyway
# heartbeat_interval = "60s"

## Tag subscriptions are applied as tags to other subscriptions.
# [[inputs.gnmi.tag_subscription]]
# ## When applying this value as a tag to other metrics, use this tag name
# name = "descr"
#
# ## All other subscription fields are as normal
# origin = "openconfig-interfaces"
# path = "/interfaces/interface/state"
# subscription_mode = "on_change"
#
# ## Match strategy to use for the tag.
# ## Tags are only applied for metrics of the same address. The following
# ## settings are valid:
# ## unconditional -- always match
# ## name -- match by the "name" key
# ## This resembles the previsou 'tag-only' behavior.
# ## elements -- match by the keys in the path filtered by the path
# ## parts specified `elements` below
# ## By default, 'elements' is used if the 'elements' option is provided,
# ## otherwise match by 'name'.
# # match = ""
#
# ## For the 'elements' match strategy, at least one path-element name must
# ## be supplied containing at least one key to match on. Multiple path
# ## elements can be specified in any order. All given keys must be equal
# ## for a match.
# # elements = ["description", "interface"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[[inputs.gnmi]]
addresses = ["10.49.234.114:57777"]
password = "cisco"
username = "cisco"
[[inputs.gnmi.subscription]]
name = "ifcounters"
origin = "openconfig-interfaces"
path = "/interfaces/interface/state/counters"
subscription_mode = "sample"
sample_interval = "10s"
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# gNMI telemetry input plugin
[[inputs.gnmi]]
## Address and port of the gNMI GRPC server
addresses = ["10.49.234.114:57777"]

## define credentials
username = "cisco"
password = "cisco"

## gNMI encoding requested (one of: "proto", "json", "json_ietf", "bytes")
# encoding = "proto"

## redial in case of failures after
# redial = "10s"

## gRPC Maximum Message Size
# max_msg_size = "4MB"

## Enable to get the canonical path as field-name
# canonical_field_names = false

## Remove leading slashes and dots in field-name
# trim_field_names = false

## Guess the path-tag if an update does not contain a prefix-path
## If enabled, the common-path of all elements in the update is used.
guess_path_tag = false

## enable client-side TLS and define CA to authenticate the device
# enable_tls = false
# tls_ca = "/etc/telegraf/ca.pem"
## Minimal TLS version to accept by the client
# tls_min_version = "TLS12"
## Use TLS but skip chain & host verification
# insecure_skip_verify = true

## define client-side TLS certificate & key to authenticate to the device
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"

## gNMI subscription prefix (optional, can usually be left empty)
## See: https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#222-paths
# origin = ""
# prefix = ""
# target = ""

## Vendor specific options
## This defines what vendor specific options to load.
## * Juniper Header Extension (juniper_header): some sensors are directly managed by
## Linecard, which adds the Juniper GNMI Header Extension. Enabling this
## allows the decoding of the Extension header if present. Currently this knob
## adds component, component_id & sub_component_id as additional tags
# vendor_specific = []

## Define additional aliases to map encoding paths to measurement names
# [inputs.gnmi.aliases]
# ifcounters = "openconfig:/interfaces/interface/state/counters"

[[inputs.gnmi.subscription]]
## Name of the measurement that will be emitted
name = "ifcounters"

## Origin and path of the subscription
## See: https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#222-paths
##
## origin usually refers to a (YANG) data model implemented by the device
## and path to a specific substructure inside it that should be subscribed
## to (similar to an XPath). YANG models can be found e.g. here:
## https://github.com/YangModels/yang/tree/master/vendor/cisco/xr
origin = "openconfig-interfaces"
path = "/interfaces/interface/state/counters"

## Subscription mode ("target_defined", "sample", "on_change") and interval
subscription_mode = "sample"
sample_interval = "10s"

## Suppress redundant transmissions when measured values are unchanged
# suppress_redundant = false

## If suppression is enabled, send updates at least every X seconds anyway
# heartbeat_interval = "60s"

## Tag subscriptions are applied as tags to other subscriptions.
# [[inputs.gnmi.tag_subscription]]
# ## When applying this value as a tag to other metrics, use this tag name
# name = "descr"
#
# ## All other subscription fields are as normal
# origin = "openconfig-interfaces"
# path = "/interfaces/interface/state"
# subscription_mode = "on_change"
#
# ## Match strategy to use for the tag.
# ## Tags are only applied for metrics of the same address. The following
# ## settings are valid:
# ## unconditional -- always match
# ## name -- match by the "name" key
# ## This resembles the previsou 'tag-only' behavior.
# ## elements -- match by the keys in the path filtered by the path
# ## parts specified `elements` below
# ## By default, 'elements' is used if the 'elements' option is provided,
# ## otherwise match by 'name'.
# # match = ""
#
# ## For the 'elements' match strategy, at least one path-element name must
# ## be supplied containing at least one key to match on. Multiple path
# ## elements can be specified in any order. All given keys must be equal
# ## for a match.
# # elements = ["description", "interface"]
Loading
Loading