From 8f07761cba851988e901ca8f4d6334ccdd463da1 Mon Sep 17 00:00:00 2001 From: Sven Rebhan <36194019+srebhan@users.noreply.github.com> Date: Wed, 24 May 2023 16:14:06 +0200 Subject: [PATCH] feat(inputs.gnmi): Allow canonical field names (#13326) --- plugins/inputs/gnmi/README.md | 3 + plugins/inputs/gnmi/gnmi.go | 54 +++++++------ plugins/inputs/gnmi/handler.go | 65 ++++++--------- plugins/inputs/gnmi/sample.conf | 3 + .../canonical_field_names/expected.out | 1 + .../canonical_field_names/responses.json | 79 +++++++++++++++++++ .../canonical_field_names/telegraf.conf | 29 +++++++ 7 files changed, 169 insertions(+), 65 deletions(-) create mode 100644 plugins/inputs/gnmi/testcases/canonical_field_names/expected.out create mode 100644 plugins/inputs/gnmi/testcases/canonical_field_names/responses.json create mode 100644 plugins/inputs/gnmi/testcases/canonical_field_names/telegraf.conf diff --git a/plugins/inputs/gnmi/README.md b/plugins/inputs/gnmi/README.md index 0a7ea01b8..982a5a70d 100644 --- a/plugins/inputs/gnmi/README.md +++ b/plugins/inputs/gnmi/README.md @@ -52,6 +52,9 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details. ## gRPC Maximum Message Size # max_msg_size = "4MB" + ## Enable to get the canonical path as field-name + # canonical_field_names = false + ## enable client-side TLS and define CA to authenticate the device # enable_tls = false # tls_ca = "/etc/telegraf/ca.pem" diff --git a/plugins/inputs/gnmi/gnmi.go b/plugins/inputs/gnmi/gnmi.go index 4c3f4f799..2856509b8 100644 --- a/plugins/inputs/gnmi/gnmi.go +++ b/plugins/inputs/gnmi/gnmi.go @@ -42,23 +42,24 @@ var supportedExtensions = []string{"juniper_header"} // gNMI plugin instance type GNMI struct { - Addresses []string `toml:"addresses"` - Subscriptions []Subscription `toml:"subscription"` - TagSubscriptions []TagSubscription `toml:"tag_subscription"` - Aliases map[string]string `toml:"aliases"` - Encoding string `toml:"encoding"` - Origin string `toml:"origin"` - Prefix string `toml:"prefix"` - Target string `toml:"target"` - UpdatesOnly bool `toml:"updates_only"` - VendorSpecific []string `toml:"vendor_specific"` - Username string `toml:"username"` - Password string `toml:"password"` - Redial config.Duration `toml:"redial"` - MaxMsgSize config.Size `toml:"max_msg_size"` - Trace bool `toml:"dump_responses"` - EnableTLS bool `toml:"enable_tls"` - Log telegraf.Logger `toml:"-"` + Addresses []string `toml:"addresses"` + Subscriptions []Subscription `toml:"subscription"` + TagSubscriptions []TagSubscription `toml:"tag_subscription"` + Aliases map[string]string `toml:"aliases"` + Encoding string `toml:"encoding"` + Origin string `toml:"origin"` + Prefix string `toml:"prefix"` + Target string `toml:"target"` + UpdatesOnly bool `toml:"updates_only"` + VendorSpecific []string `toml:"vendor_specific"` + Username string `toml:"username"` + Password string `toml:"password"` + Redial config.Duration `toml:"redial"` + MaxMsgSize config.Size `toml:"max_msg_size"` + Trace bool `toml:"dump_responses"` + CanonicalFieldNames bool `toml:"canonical_field_names"` + EnableTLS bool `toml:"enable_tls"` + Log telegraf.Logger `toml:"-"` internaltls.ClientConfig // Internal state @@ -192,15 +193,18 @@ func (c *GNMI) Start(acc telegraf.Accumulator) error { for _, addr := range c.Addresses { go func(addr string) { defer c.wg.Done() - confHandler := configHandler{ - aliases: c.internalAliases, - subscriptions: c.TagSubscriptions, - maxSize: int(c.MaxMsgSize), - log: c.Log, - trace: c.Trace, - vendorExt: c.VendorSpecific, + + h := handler{ + address: addr, + aliases: c.internalAliases, + tagsubs: c.TagSubscriptions, + maxMsgSize: int(c.MaxMsgSize), + vendorExt: c.VendorSpecific, + tagStore: newTagStore(c.TagSubscriptions), + trace: c.Trace, + canonicalFieldNames: c.CanonicalFieldNames, + log: c.Log, } - h := newHandler(addr, confHandler) for ctx.Err() == nil { if err := h.subscribeGNMI(ctx, acc, tlscfg, request); err != nil && ctx.Err() == nil { acc.AddError(err) diff --git a/plugins/inputs/gnmi/handler.go b/plugins/inputs/gnmi/handler.go index 15947931b..15447f81c 100644 --- a/plugins/inputs/gnmi/handler.go +++ b/plugins/inputs/gnmi/handler.go @@ -28,38 +28,16 @@ import ( const eidJuniperTelemetryHeader = 1 type handler struct { - address string - aliases map[string]string - tagsubs []TagSubscription - maxMsgSize int - emptyNameWarnShown bool - vendorExt []string - tagStore *tagStore - trace bool - log telegraf.Logger -} - -// Allow to convey additionnal configuration elements -type configHandler struct { - aliases map[string]string - subscriptions []TagSubscription - maxSize int - log telegraf.Logger - trace bool - vendorExt []string -} - -func newHandler(addr string, confHandler configHandler) *handler { - return &handler{ - address: addr, - aliases: confHandler.aliases, - tagsubs: confHandler.subscriptions, - maxMsgSize: confHandler.maxSize, - vendorExt: confHandler.vendorExt, - tagStore: newTagStore(confHandler.subscriptions), - trace: confHandler.trace, - log: confHandler.log, - } + address string + aliases map[string]string + tagsubs []TagSubscription + maxMsgSize int + emptyNameWarnShown bool + vendorExt []string + tagStore *tagStore + trace bool + canonicalFieldNames bool + log telegraf.Logger } // SubscribeGNMI and extract telemetry data @@ -266,15 +244,22 @@ func (h *handler) handleSubscribeResponseUpdate(acc telegraf.Accumulator, respon // conversion on the key. key = key[len(aliasPath)+1:] } else if len(aliasPath) >= len(key) { - // Otherwise use the last path element as the field key. - key = path.Base(key) + if h.canonicalFieldNames { + // Strip the origin is any for the field names + if parts := strings.SplitN(key, ":", 2); len(parts) == 2 { + key = parts[1] + } + } else { + // Otherwise use the last path element as the field key. + key = path.Base(key) - // If there are no elements skip the item; this would be an - // invalid message. - key = strings.TrimLeft(key, "/.") - if key == "" { - h.log.Errorf("invalid empty path: %q", k) - continue + // If there are no elements skip the item; this would be an + // invalid message. + key = strings.TrimLeft(key, "/.") + if key == "" { + h.log.Errorf("invalid empty path: %q", k) + continue + } } } grouper.Add(name, tags, timestamp, key, v) diff --git a/plugins/inputs/gnmi/sample.conf b/plugins/inputs/gnmi/sample.conf index 8e87380b0..f5a5e6b96 100644 --- a/plugins/inputs/gnmi/sample.conf +++ b/plugins/inputs/gnmi/sample.conf @@ -16,6 +16,9 @@ ## gRPC Maximum Message Size # max_msg_size = "4MB" + ## Enable to get the canonical path as field-name + # canonical_field_names = false + ## enable client-side TLS and define CA to authenticate the device # enable_tls = false # tls_ca = "/etc/telegraf/ca.pem" diff --git a/plugins/inputs/gnmi/testcases/canonical_field_names/expected.out b/plugins/inputs/gnmi/testcases/canonical_field_names/expected.out new file mode 100644 index 000000000..ddf715d06 --- /dev/null +++ b/plugins/inputs/gnmi/testcases/canonical_field_names/expected.out @@ -0,0 +1 @@ +interfaces,name=eth42,path=openconfig-interfaces:/interfaces/interface,source=127.0.0.1 /interfaces/interface/descr="eth42",/interfaces/interface/config/id=42425u,/interfaces/interface/state/in_pkts=5678u,/interfaces/interface/state/out_pkts=5125u 1673608605875353770 \ No newline at end of file diff --git a/plugins/inputs/gnmi/testcases/canonical_field_names/responses.json b/plugins/inputs/gnmi/testcases/canonical_field_names/responses.json new file mode 100644 index 000000000..c7262722a --- /dev/null +++ b/plugins/inputs/gnmi/testcases/canonical_field_names/responses.json @@ -0,0 +1,79 @@ +[ + { + "update": { + "timestamp": "1673608605875353770", + "prefix": { + "origin": "openconfig-interfaces", + "elem": [ + { + "name": "interfaces" + }, + { + "name": "interface", + "key":{"name":"eth42"} + } + ], + "target": "subscription" + }, + "update": [ + { + "path": { + "elem": [ + { + "name": "descr" + } + ] + }, + "val": { + "stringVal": "eth42" + } + }, + { + "path": { + "elem": [ + { + "name": "config" + }, + { + "name": "id" + } + ] + }, + "val": { + "uintVal": "42425" + } + }, + { + "path": { + "elem": [ + { + "name": "state" + }, + { + "name": "in-pkts" + } + ] + }, + "val": { + "uintVal": "5678" + } + }, + { + "path": { + "elem": [ + { + "name": "state" + }, + { + "name": "out-pkts" + } + ] + }, + "val": { + "uintVal": "5125" + } + } + ] + } + } +] \ No newline at end of file diff --git a/plugins/inputs/gnmi/testcases/canonical_field_names/telegraf.conf b/plugins/inputs/gnmi/testcases/canonical_field_names/telegraf.conf new file mode 100644 index 000000000..7963e1e6e --- /dev/null +++ b/plugins/inputs/gnmi/testcases/canonical_field_names/telegraf.conf @@ -0,0 +1,29 @@ +[[inputs.gnmi]] + addresses = ["dummy"] + name_override = "gnmi" + redial = "10s" + canonical_field_names = true + [[inputs.gnmi.subscription]] + name = "interfaces" + origin = "openconfig-interfaces" + path = "/interfaces/interface/descr" + subscription_mode = "sample" + sample_interval = "10s" + [[inputs.gnmi.subscription]] + name = "interfaces" + origin = "openconfig-interfaces" + path = "/interfaces/interface/config/id" + subscription_mode = "sample" + sample_interval = "10s" + [[inputs.gnmi.subscription]] + name = "interfaces" + origin = "openconfig-interfaces" + path = "/interfaces/interface/state/in-pkts" + subscription_mode = "sample" + sample_interval = "10s" + [[inputs.gnmi.subscription]] + name = "interfaces" + origin = "openconfig-interfaces" + path = "/interfaces/interface/state/out-pkts" + subscription_mode = "sample" + sample_interval = "10s"