From 0dcdbe4ab4879e7610c20c89bc3fd6386c73676a Mon Sep 17 00:00:00 2001 From: Sven Rebhan <36194019+srebhan@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:48:33 +0100 Subject: [PATCH] fix(inputs.gnmi): Allow to disable using first namespace as origin (#16507) --- CHANGELOG.md | 12 + plugins/inputs/gnmi/README.md | 5 + plugins/inputs/gnmi/gnmi.go | 127 +++-- plugins/inputs/gnmi/gnmi_test.go | 29 +- plugins/inputs/gnmi/handler.go | 41 +- plugins/inputs/gnmi/path.go | 35 +- plugins/inputs/gnmi/sample.conf | 5 + plugins/inputs/gnmi/sample.conf.in | 5 + .../gnmi/testcases/issue_16476/expected.out | 1 + .../gnmi/testcases/issue_16476/responses.json | 481 ++++++++++++++++++ .../gnmi/testcases/issue_16476/telegraf.conf | 10 + plugins/inputs/gnmi/update_fields.go | 14 +- 12 files changed, 669 insertions(+), 96 deletions(-) create mode 100644 plugins/inputs/gnmi/testcases/issue_16476/expected.out create mode 100644 plugins/inputs/gnmi/testcases/issue_16476/responses.json create mode 100644 plugins/inputs/gnmi/testcases/issue_16476/telegraf.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index d78c175ed..f598d1da4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # Changelog +## Unreleased + +### Important Changes + +- PR [#16507](https://github.com/influxdata/telegraf/pull/16507) adds the + `enforce_first_namespace_as_origin` to the GNMI input plugin. This option + allows to disable mangling of the response `path` tag by _not_ using namespaces + as origin. It is highly recommended to disable the option. + However, disabling the behavior might change the `path` tag and + thus might break existing queries. Furthermore, the tag modification might + increase cardinality in your database. + ## v1.33.2 [2025-02-10] ### Important Changes diff --git a/plugins/inputs/gnmi/README.md b/plugins/inputs/gnmi/README.md index 7e7a13f14..b1d441f91 100644 --- a/plugins/inputs/gnmi/README.md +++ b/plugins/inputs/gnmi/README.md @@ -88,6 +88,11 @@ details on how to use them. ## Only receive updates for the state, also suppresses receiving the initial state # updates_only = false + ## Enforces the namespace of the first element as origin for aliases and + ## response paths, required for backward compatibility. + ## NOTE: Set to 'false' if possible but be aware that this might change the path tag! + # enforce_first_namespace_as_origin = true + ## Guess the path-tag if an update does not contain a prefix-path ## Supported values are ## none -- do not add a 'path' tag diff --git a/plugins/inputs/gnmi/gnmi.go b/plugins/inputs/gnmi/gnmi.go index 64736338f..aef5ffcc4 100644 --- a/plugins/inputs/gnmi/gnmi.go +++ b/plugins/inputs/gnmi/gnmi.go @@ -41,32 +41,33 @@ including your device model and the following response data: This message is only printed once.` 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 config.Secret `toml:"username"` - Password config.Secret `toml:"password"` - Redial config.Duration `toml:"redial"` - MaxMsgSize config.Size `toml:"max_msg_size"` - Depth int32 `toml:"depth"` - Trace bool `toml:"dump_responses"` - CanonicalFieldNames bool `toml:"canonical_field_names"` - TrimFieldNames bool `toml:"trim_field_names"` - PrefixTagKeyWithPath bool `toml:"prefix_tag_key_with_path"` - GuessPathTag bool `toml:"guess_path_tag" deprecated:"1.30.0;1.35.0;use 'path_guessing_strategy' instead"` - GuessPathStrategy string `toml:"path_guessing_strategy"` - EnableTLS bool `toml:"enable_tls" deprecated:"1.27.0;1.35.0;use 'tls_enable' instead"` - KeepaliveTime config.Duration `toml:"keepalive_time"` - KeepaliveTimeout config.Duration `toml:"keepalive_timeout"` - YangModelPaths []string `toml:"yang_model_paths"` - 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 config.Secret `toml:"username"` + Password config.Secret `toml:"password"` + Redial config.Duration `toml:"redial"` + MaxMsgSize config.Size `toml:"max_msg_size"` + Depth int32 `toml:"depth"` + Trace bool `toml:"dump_responses"` + CanonicalFieldNames bool `toml:"canonical_field_names"` + TrimFieldNames bool `toml:"trim_field_names"` + PrefixTagKeyWithPath bool `toml:"prefix_tag_key_with_path"` + GuessPathTag bool `toml:"guess_path_tag" deprecated:"1.30.0;1.35.0;use 'path_guessing_strategy' instead"` + GuessPathStrategy string `toml:"path_guessing_strategy"` + EnableTLS bool `toml:"enable_tls" deprecated:"1.27.0;1.35.0;use 'tls_enable' instead"` + KeepaliveTime config.Duration `toml:"keepalive_time"` + KeepaliveTimeout config.Duration `toml:"keepalive_timeout"` + YangModelPaths []string `toml:"yang_model_paths"` + EnforceFirstNamespaceAsOrigin bool `toml:"enforce_first_namespace_as_origin"` + Log telegraf.Logger `toml:"-"` common_tls.ClientConfig // Internal state @@ -101,6 +102,15 @@ func (*GNMI) SampleConfig() string { func (c *GNMI) Init() error { // Check options + switch c.Encoding { + case "": + c.Encoding = "proto" + case "proto", "json", "json_ietf", "bytes": + // Do nothing, those are valid + default: + return fmt.Errorf("unsupported encoding %s", c.Encoding) + } + if time.Duration(c.Redial) <= 0 { return errors.New("redial duration must be positive") } @@ -186,17 +196,21 @@ func (c *GNMI) Init() error { // Invert explicit alias list and prefill subscription names c.internalAliases = make(map[*pathInfo]string, len(c.Subscriptions)+len(c.Aliases)+len(c.TagSubscriptions)) for _, s := range c.Subscriptions { - if err := s.buildAlias(c.internalAliases); err != nil { + if err := s.buildAlias(c.internalAliases, c.EnforceFirstNamespaceAsOrigin); err != nil { return err } } for _, s := range c.TagSubscriptions { - if err := s.buildAlias(c.internalAliases); err != nil { + if err := s.buildAlias(c.internalAliases, c.EnforceFirstNamespaceAsOrigin); err != nil { return err } } for alias, encodingPath := range c.Aliases { - c.internalAliases[newInfoFromString(encodingPath)] = alias + path := newInfoFromString(encodingPath) + if c.EnforceFirstNamespaceAsOrigin { + path.enforceFirstNamespaceAsOrigin() + } + c.internalAliases[path] = alias } c.Log.Debugf("Internal alias mapping: %+v", c.internalAliases) @@ -281,20 +295,21 @@ func (c *GNMI) Start(acc telegraf.Accumulator) error { return } h := handler{ - host: host, - port: port, - aliases: c.internalAliases, - tagsubs: c.TagSubscriptions, - maxMsgSize: int(c.MaxMsgSize), - vendorExt: c.VendorSpecific, - tagStore: newTagStore(c.TagSubscriptions), - trace: c.Trace, - canonicalFieldNames: c.CanonicalFieldNames, - trimSlash: c.TrimFieldNames, - tagPathPrefix: c.PrefixTagKeyWithPath, - guessPathStrategy: c.GuessPathStrategy, - decoder: c.decoder, - log: c.Log, + host: host, + port: port, + aliases: c.internalAliases, + tagsubs: c.TagSubscriptions, + maxMsgSize: int(c.MaxMsgSize), + vendorExt: c.VendorSpecific, + tagStore: newTagStore(c.TagSubscriptions), + trace: c.Trace, + canonicalFieldNames: c.CanonicalFieldNames, + trimSlash: c.TrimFieldNames, + tagPathPrefix: c.PrefixTagKeyWithPath, + guessPathStrategy: c.GuessPathStrategy, + decoder: c.decoder, + enforceFirstNamespaceAsOrigin: c.EnforceFirstNamespaceAsOrigin, + log: c.Log, ClientParameters: keepalive.ClientParameters{ Time: time.Duration(c.KeepaliveTime), Timeout: time.Duration(c.KeepaliveTimeout), @@ -436,13 +451,16 @@ func (s *subscription) buildFullPath(c *GNMI) error { return nil } -func (s *subscription) buildAlias(aliases map[*pathInfo]string) error { +func (s *subscription) buildAlias(aliases map[*pathInfo]string, enforceFirstNamespaceAsOrigin bool) error { // Build the subscription path without keys path, err := parsePath(s.Origin, s.Path, "") if err != nil { return err } info := newInfoFromPathWithoutKeys(path) + if enforceFirstNamespaceAsOrigin { + info.enforceFirstNamespaceAsOrigin() + } // If the user didn't provide a measurement name, use last path element name := s.Name @@ -455,15 +473,18 @@ func (s *subscription) buildAlias(aliases map[*pathInfo]string) error { return nil } -func newGNMI() telegraf.Input { - return &GNMI{ - Encoding: "proto", - Redial: config.Duration(10 * time.Second), - } -} - func init() { - inputs.Add("gnmi", newGNMI) + inputs.Add("gnmi", func() telegraf.Input { + return &GNMI{ + Redial: config.Duration(10 * time.Second), + EnforceFirstNamespaceAsOrigin: true, + } + }) // Backwards compatible alias: - inputs.Add("cisco_telemetry_gnmi", newGNMI) + inputs.Add("cisco_telemetry_gnmi", func() telegraf.Input { + return &GNMI{ + Redial: config.Duration(10 * time.Second), + EnforceFirstNamespaceAsOrigin: true, + } + }) } diff --git a/plugins/inputs/gnmi/gnmi_test.go b/plugins/inputs/gnmi/gnmi_test.go index f9af63369..00c7d39d8 100644 --- a/plugins/inputs/gnmi/gnmi_test.go +++ b/plugins/inputs/gnmi/gnmi_test.go @@ -779,9 +779,10 @@ func TestNotification(t *testing.T) { { name: "issue #12257 Sonic", plugin: &GNMI{ - Log: testutil.Logger{}, - Encoding: "proto", - Redial: config.Duration(1 * time.Second), + Log: testutil.Logger{}, + Encoding: "proto", + Redial: config.Duration(1 * time.Second), + EnforceFirstNamespaceAsOrigin: true, Subscriptions: []subscription{ { Name: "temperature", @@ -910,10 +911,11 @@ func TestNotification(t *testing.T) { { name: "Juniper Extension", plugin: &GNMI{ - Log: testutil.Logger{}, - Encoding: "proto", - VendorSpecific: []string{"juniper_header"}, - Redial: config.Duration(1 * time.Second), + Log: testutil.Logger{}, + Encoding: "proto", + VendorSpecific: []string{"juniper_header"}, + Redial: config.Duration(1 * time.Second), + EnforceFirstNamespaceAsOrigin: true, Subscriptions: []subscription{ { Name: "type", @@ -1105,7 +1107,12 @@ func TestCases(t *testing.T) { require.NoError(t, err) // Register the plugin - inputs.Add("gnmi", newGNMI) + inputs.Add("gnmi", func() telegraf.Input { + return &GNMI{ + Redial: config.Duration(10 * time.Second), + EnforceFirstNamespaceAsOrigin: true, + } + }) for _, f := range folders { // Only handle folders @@ -1158,12 +1165,6 @@ func TestCases(t *testing.T) { // Prepare the server response responseFunction := func(server gnmi.GNMI_SubscribeServer) error { - sync := &gnmi.SubscribeResponse{ - Response: &gnmi.SubscribeResponse_SyncResponse{ - SyncResponse: true, - }, - } - _ = sync for i := range responses { if err := server.Send(&responses[i]); err != nil { return err diff --git a/plugins/inputs/gnmi/handler.go b/plugins/inputs/gnmi/handler.go index 03771fb8d..2cd55c52d 100644 --- a/plugins/inputs/gnmi/handler.go +++ b/plugins/inputs/gnmi/handler.go @@ -33,21 +33,22 @@ import ( const eidJuniperTelemetryHeader = 1 type handler struct { - host string - port string - aliases map[*pathInfo]string - tagsubs []tagSubscription - maxMsgSize int - emptyNameWarnShown bool - vendorExt []string - tagStore *tagStore - trace bool - canonicalFieldNames bool - trimSlash bool - tagPathPrefix bool - guessPathStrategy string - decoder *yangmodel.Decoder - log telegraf.Logger + host string + port string + aliases map[*pathInfo]string + tagsubs []tagSubscription + maxMsgSize int + emptyNameWarnShown bool + vendorExt []string + tagStore *tagStore + trace bool + canonicalFieldNames bool + trimSlash bool + tagPathPrefix bool + guessPathStrategy string + decoder *yangmodel.Decoder + enforceFirstNamespaceAsOrigin bool + log telegraf.Logger keepalive.ClientParameters } @@ -161,6 +162,9 @@ func (h *handler) handleSubscribeResponseUpdate(acc telegraf.Accumulator, respon // Extract the path part valid for the whole set of updates if any prefix := newInfoFromPath(response.Update.Prefix) + if h.enforceFirstNamespaceAsOrigin { + prefix.enforceFirstNamespaceAsOrigin() + } // Add info to the tags headerTags["source"] = h.host @@ -173,6 +177,9 @@ func (h *handler) handleSubscribeResponseUpdate(acc telegraf.Accumulator, respon var valueFields []updateField for _, update := range response.Update.Update { fullPath := prefix.append(update.Path) + if h.enforceFirstNamespaceAsOrigin { + prefix.enforceFirstNamespaceAsOrigin() + } if update.Path.Origin != "" { fullPath.origin = update.Path.Origin } @@ -251,7 +258,11 @@ func (h *handler) handleSubscribeResponseUpdate(acc telegraf.Accumulator, respon h.emptyNameWarnShown = true } } + aliasInfo := newInfoFromString(aliasPath) + if h.enforceFirstNamespaceAsOrigin { + aliasInfo.enforceFirstNamespaceAsOrigin() + } if tags["path"] == "" && h.guessPathStrategy == "subscription" { tags["path"] = aliasInfo.String() diff --git a/plugins/inputs/gnmi/path.go b/plugins/inputs/gnmi/path.go index e6600fb97..0392b9cec 100644 --- a/plugins/inputs/gnmi/path.go +++ b/plugins/inputs/gnmi/path.go @@ -29,8 +29,16 @@ func newInfoFromString(path string) *pathInfo { return &pathInfo{} } - info := &pathInfo{} - for _, part := range strings.Split(path, "/") { + parts := strings.Split(path, "/") + + var origin string + if strings.HasSuffix(parts[0], ":") { + origin = strings.TrimSuffix(parts[0], ":") + parts = parts[1:] + } + + info := &pathInfo{origin: origin} + for _, part := range parts { if part == "" { continue } @@ -195,12 +203,6 @@ func (pi *pathInfo) normalize() { } } - // Some devices supply the origin as part of the first path element, - // so try to find and extract it there. - if pi.segments[0].namespace != "" { - pi.origin = pi.segments[0].namespace - } - // Remove empty segments segments := make([]segment, 0, len(pi.segments)) for _, s := range pi.segments { @@ -211,6 +213,19 @@ func (pi *pathInfo) normalize() { pi.segments = segments } +func (pi *pathInfo) enforceFirstNamespaceAsOrigin() { + if len(pi.segments) == 0 { + return + } + + // Some devices supply the origin as part of the first path element, + // so try to find and extract it there. + if pi.segments[0].namespace != "" { + pi.origin = pi.segments[0].namespace + pi.segments[0].namespace = "" + } +} + func (pi *pathInfo) equalsPathNoKeys(path *gnmi.Path) bool { if len(pi.segments) != len(path.Elem) { return false @@ -344,9 +359,7 @@ func (pi *pathInfo) fullPath() string { return path } - path += "/" + pi.segments[0].id - - for _, s := range pi.segments[1:] { + for _, s := range pi.segments { if s.namespace != "" { path += "/" + s.namespace + ":" + s.id } else { diff --git a/plugins/inputs/gnmi/sample.conf b/plugins/inputs/gnmi/sample.conf index b7b6ef1fb..2f7af33a6 100644 --- a/plugins/inputs/gnmi/sample.conf +++ b/plugins/inputs/gnmi/sample.conf @@ -41,6 +41,11 @@ ## Only receive updates for the state, also suppresses receiving the initial state # updates_only = false + ## Enforces the namespace of the first element as origin for aliases and + ## response paths, required for backward compatibility. + ## NOTE: Set to 'false' if possible but be aware that this might change the path tag! + # enforce_first_namespace_as_origin = true + ## Guess the path-tag if an update does not contain a prefix-path ## Supported values are ## none -- do not add a 'path' tag diff --git a/plugins/inputs/gnmi/sample.conf.in b/plugins/inputs/gnmi/sample.conf.in index 8d5a3abdd..6daa345bc 100644 --- a/plugins/inputs/gnmi/sample.conf.in +++ b/plugins/inputs/gnmi/sample.conf.in @@ -41,6 +41,11 @@ ## Only receive updates for the state, also suppresses receiving the initial state # updates_only = false + ## Enforces the namespace of the first element as origin for aliases and + ## response paths, required for backward compatibility. + ## NOTE: Set to 'false' if possible but be aware that this might change the path tag! + # enforce_first_namespace_as_origin = true + ## Guess the path-tag if an update does not contain a prefix-path ## Supported values are ## none -- do not add a 'path' tag diff --git a/plugins/inputs/gnmi/testcases/issue_16476/expected.out b/plugins/inputs/gnmi/testcases/issue_16476/expected.out new file mode 100644 index 000000000..1bc3db06d --- /dev/null +++ b/plugins/inputs/gnmi/testcases/issue_16476/expected.out @@ -0,0 +1 @@ +ifcounters,path=Ciena:/oc-if:interfaces/oc-if:interface/oc-if:state/oc-if:counters,source=127.0.0.1 in_1024_to_1518_octet_pkts=15680405047u,in_128_to_255_octet_pkts=12809649942u,in_1519_to_2047_octet_pkts=35815850565u,in_2048_to_4095_octet_pkts=0u,in_256_to_511_octet_pkts=5257910993u,in_4096_to_9216_octet_pkts=0u,in_512_to_1023_octet_pkts=6139561818u,in_64_octet_pkts=4u,in_65_to_127_octet_pkts=146456592549u,in_broadcast_pkts=167166u,in_crc_error_pkts=0u,in_discards=236u,in_discards_octets=31492u,in_dropped_octets=31492u,in_dropped_pkts=236u,in_errors=0u,in_jabber_pkts=0u,in_multicast_pkts=76815719u,in_octets=95890972327359u,in_oversize_pkts=0u,in_pkts=222159970919u,in_undersize_pkts=0u,in_unicast_pkts=222082988034u,last_clear=1679547185677412529u,link_flap_events=0u,name="\"1\"",out_1519_to_2047_octet_pkts=211382493634u,out_2048_to_4095_octet_pkts=0u,out_4096_to_9216_octet_pkts=0u,out_broadcast_pkts=2609579674u,out_errors=0u,out_multicast_pkts=332069076u,out_octets=885293268981054u,out_pkts=677379680498u,out_unicast_pkts=674438031748u 1739206587847000000 diff --git a/plugins/inputs/gnmi/testcases/issue_16476/responses.json b/plugins/inputs/gnmi/testcases/issue_16476/responses.json new file mode 100644 index 000000000..946bfab6f --- /dev/null +++ b/plugins/inputs/gnmi/testcases/issue_16476/responses.json @@ -0,0 +1,481 @@ +[ + { + "update": { + "timestamp": 1739206587847000000, + "prefix": { + "origin": "Ciena", + "elem": [ + { + "name": "oc-if:interfaces" + }, + { + "name": "oc-if:interface" + }, + { + "name": "oc-if:state" + }, + { + "name": "oc-if:counters" + } + ] + }, + "update": [ + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-1024-to-1518-octet-pkts" + } + ] + }, + "val": { + "uintVal": 15680405047 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-128-to-255-octet-pkts" + } + ] + }, + "val": { + "uintVal": 12809649942 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-1519-to-2047-octet-pkts" + } + ] + }, + "val": { + "uintVal": 35815850565 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-2048-to-4095-octet-pkts" + } + ] + }, + "val": { + "uintVal": 0 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-256-to-511-octet-pkts" + } + ] + }, + "val": { + "uintVal": 5257910993 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-4096-to-9216-octet-pkts" + } + ] + }, + "val": { + "uintVal": 0 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-512-to-1023-octet-pkts" + } + ] + }, + "val": { + "uintVal": 6139561818 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-64-octet-pkts" + } + ] + }, + "val": { + "uintVal": 4 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-65-to-127-octet-pkts" + } + ] + }, + "val": { + "uintVal": 146456592549 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-broadcast-pkts" + } + ] + }, + "val": { + "uintVal": 167166 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-crc-error-pkts" + } + ] + }, + "val": { + "uintVal": 0 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-discards" + } + ] + }, + "val": { + "uintVal": 236 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-discards-octets" + } + ] + }, + "val": { + "uintVal": 31492 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-dropped-octets" + } + ] + }, + "val": { + "uintVal": 31492 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-dropped-pkts" + } + ] + }, + "val": { + "uintVal": 236 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-errors" + } + ] + }, + "val": { + "uintVal": 0 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-jabber-pkts" + } + ] + }, + "val": { + "uintVal": 0 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-multicast-pkts" + } + ] + }, + "val": { + "uintVal": 76815719 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-octets" + } + ] + }, + "val": { + "uintVal": 95890972327359 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-oversize-pkts" + } + ] + }, + "val": { + "uintVal": 0 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-pkts" + } + ] + }, + "val": { + "uintVal": 222159970919 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-undersize-pkts" + } + ] + }, + "val": { + "uintVal": 0 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "in-unicast-pkts" + } + ] + }, + "val": { + "uintVal": 222082988034 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "last-clear" + } + ] + }, + "val": { + "uintVal": 1679547185677412529 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "link-flap-events" + } + ] + }, + "val": { + "uintVal": 0 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "name" + } + ] + }, + "val": { + "stringVal": "\"1\"" + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "out-1519-to-2047-octet-pkts" + } + ] + }, + "val": { + "uintVal": 211382493634 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "out-2048-to-4095-octet-pkts" + } + ] + }, + "val": { + "uintVal": 0 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "out-4096-to-9216-octet-pkts" + } + ] + }, + "val": { + "uintVal": 0 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "out-broadcast-pkts" + } + ] + }, + "val": { + "uintVal": 2609579674 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "out-errors" + } + ] + }, + "val": { + "uintVal": 0 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "out-multicast-pkts" + } + ] + }, + "val": { + "uintVal": 332069076 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "out-octets" + } + ] + }, + "val": { + "uintVal": 885293268981054 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "out-pkts" + } + ] + }, + "val": { + "uintVal": 677379680498 + } + }, + { + "path": { + "origin": "Ciena", + "elem": [ + { + "name": "out-unicast-pkts" + } + ] + }, + "val": { + "uintVal": 674438031748 + } + } + ] + } + } +] \ No newline at end of file diff --git a/plugins/inputs/gnmi/testcases/issue_16476/telegraf.conf b/plugins/inputs/gnmi/testcases/issue_16476/telegraf.conf new file mode 100644 index 000000000..994543fb8 --- /dev/null +++ b/plugins/inputs/gnmi/testcases/issue_16476/telegraf.conf @@ -0,0 +1,10 @@ +[[inputs.gnmi]] + addresses = ["dummy"] + enforce_first_namespace_as_origin = false + + [[inputs.gnmi.subscription]] + name = "ifcounters" + origin = "Ciena" + path = "/oc-if:interfaces/oc-if:interface/oc-if:state/oc-if:counters" + subscription_mode = "sample" + sample_interval = "30s" \ No newline at end of file diff --git a/plugins/inputs/gnmi/update_fields.go b/plugins/inputs/gnmi/update_fields.go index b5202783b..3c562ec42 100644 --- a/plugins/inputs/gnmi/update_fields.go +++ b/plugins/inputs/gnmi/update_fields.go @@ -30,7 +30,7 @@ func (h *handler) newFieldsFromUpdate(path *pathInfo, update *gnmi.Update) ([]up case *gnmi.TypedValue_AsciiVal: // not handled in ToScalar return []updateField{{path, v.AsciiVal}}, nil case *gnmi.TypedValue_JsonVal: // requires special path handling - return processJSON(path, v.JsonVal) + return h.processJSON(path, v.JsonVal) case *gnmi.TypedValue_JsonIetfVal: // requires special path handling return h.processJSONIETF(path, v.JsonIetfVal) } @@ -43,7 +43,7 @@ func (h *handler) newFieldsFromUpdate(path *pathInfo, update *gnmi.Update) ([]up return []updateField{{path, nativeType}}, nil } -func processJSON(path *pathInfo, data []byte) ([]updateField, error) { +func (h *handler) processJSON(path *pathInfo, data []byte) ([]updateField, error) { var nested interface{} if err := json.Unmarshal(data, &nested); err != nil { return nil, fmt.Errorf("failed to parse JSON value: %w", err) @@ -55,8 +55,13 @@ func processJSON(path *pathInfo, data []byte) ([]updateField, error) { // Create an update-field with the complete path for all entries fields := make([]updateField, 0, len(entries)) for _, entry := range entries { + p := path.appendSegments(entry.key...) + if h.enforceFirstNamespaceAsOrigin { + p.enforceFirstNamespaceAsOrigin() + } + fields = append(fields, updateField{ - path: path.appendSegments(entry.key...), + path: p, value: entry.value, }) } @@ -105,6 +110,9 @@ func (h *handler) processJSONIETF(path *pathInfo, data []byte) ([]updateField, e fields := make([]updateField, 0, len(entries)) for _, entry := range entries { p := path.appendSegments(entry.key...) + if h.enforceFirstNamespaceAsOrigin { + p.enforceFirstNamespaceAsOrigin() + } // Try to lookup the full path to decode the field according to the // YANG model if any