fix(inputs.gnmi): Allow to disable using first namespace as origin (#16507)

This commit is contained in:
Sven Rebhan 2025-02-24 15:48:33 +01:00 committed by GitHub
parent 01c633fbbe
commit 0dcdbe4ab4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 669 additions and 96 deletions

View File

@ -1,6 +1,18 @@
<!-- markdownlint-disable MD024 --> <!-- markdownlint-disable MD024 -->
# Changelog # 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] ## v1.33.2 [2025-02-10]
### Important Changes ### Important Changes

View File

@ -88,6 +88,11 @@ details on how to use them.
## Only receive updates for the state, also suppresses receiving the initial state ## Only receive updates for the state, also suppresses receiving the initial state
# updates_only = false # 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 ## Guess the path-tag if an update does not contain a prefix-path
## Supported values are ## Supported values are
## none -- do not add a 'path' tag ## none -- do not add a 'path' tag

View File

@ -41,32 +41,33 @@ including your device model and the following response data:
This message is only printed once.` This message is only printed once.`
type GNMI struct { type GNMI struct {
Addresses []string `toml:"addresses"` Addresses []string `toml:"addresses"`
Subscriptions []subscription `toml:"subscription"` Subscriptions []subscription `toml:"subscription"`
TagSubscriptions []tagSubscription `toml:"tag_subscription"` TagSubscriptions []tagSubscription `toml:"tag_subscription"`
Aliases map[string]string `toml:"aliases"` Aliases map[string]string `toml:"aliases"`
Encoding string `toml:"encoding"` Encoding string `toml:"encoding"`
Origin string `toml:"origin"` Origin string `toml:"origin"`
Prefix string `toml:"prefix"` Prefix string `toml:"prefix"`
Target string `toml:"target"` Target string `toml:"target"`
UpdatesOnly bool `toml:"updates_only"` UpdatesOnly bool `toml:"updates_only"`
VendorSpecific []string `toml:"vendor_specific"` VendorSpecific []string `toml:"vendor_specific"`
Username config.Secret `toml:"username"` Username config.Secret `toml:"username"`
Password config.Secret `toml:"password"` Password config.Secret `toml:"password"`
Redial config.Duration `toml:"redial"` Redial config.Duration `toml:"redial"`
MaxMsgSize config.Size `toml:"max_msg_size"` MaxMsgSize config.Size `toml:"max_msg_size"`
Depth int32 `toml:"depth"` Depth int32 `toml:"depth"`
Trace bool `toml:"dump_responses"` Trace bool `toml:"dump_responses"`
CanonicalFieldNames bool `toml:"canonical_field_names"` CanonicalFieldNames bool `toml:"canonical_field_names"`
TrimFieldNames bool `toml:"trim_field_names"` TrimFieldNames bool `toml:"trim_field_names"`
PrefixTagKeyWithPath bool `toml:"prefix_tag_key_with_path"` 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"` GuessPathTag bool `toml:"guess_path_tag" deprecated:"1.30.0;1.35.0;use 'path_guessing_strategy' instead"`
GuessPathStrategy string `toml:"path_guessing_strategy"` GuessPathStrategy string `toml:"path_guessing_strategy"`
EnableTLS bool `toml:"enable_tls" deprecated:"1.27.0;1.35.0;use 'tls_enable' instead"` EnableTLS bool `toml:"enable_tls" deprecated:"1.27.0;1.35.0;use 'tls_enable' instead"`
KeepaliveTime config.Duration `toml:"keepalive_time"` KeepaliveTime config.Duration `toml:"keepalive_time"`
KeepaliveTimeout config.Duration `toml:"keepalive_timeout"` KeepaliveTimeout config.Duration `toml:"keepalive_timeout"`
YangModelPaths []string `toml:"yang_model_paths"` YangModelPaths []string `toml:"yang_model_paths"`
Log telegraf.Logger `toml:"-"` EnforceFirstNamespaceAsOrigin bool `toml:"enforce_first_namespace_as_origin"`
Log telegraf.Logger `toml:"-"`
common_tls.ClientConfig common_tls.ClientConfig
// Internal state // Internal state
@ -101,6 +102,15 @@ func (*GNMI) SampleConfig() string {
func (c *GNMI) Init() error { func (c *GNMI) Init() error {
// Check options // 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 { if time.Duration(c.Redial) <= 0 {
return errors.New("redial duration must be positive") 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 // Invert explicit alias list and prefill subscription names
c.internalAliases = make(map[*pathInfo]string, len(c.Subscriptions)+len(c.Aliases)+len(c.TagSubscriptions)) c.internalAliases = make(map[*pathInfo]string, len(c.Subscriptions)+len(c.Aliases)+len(c.TagSubscriptions))
for _, s := range c.Subscriptions { 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 return err
} }
} }
for _, s := range c.TagSubscriptions { 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 return err
} }
} }
for alias, encodingPath := range c.Aliases { 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) c.Log.Debugf("Internal alias mapping: %+v", c.internalAliases)
@ -281,20 +295,21 @@ func (c *GNMI) Start(acc telegraf.Accumulator) error {
return return
} }
h := handler{ h := handler{
host: host, host: host,
port: port, port: port,
aliases: c.internalAliases, aliases: c.internalAliases,
tagsubs: c.TagSubscriptions, tagsubs: c.TagSubscriptions,
maxMsgSize: int(c.MaxMsgSize), maxMsgSize: int(c.MaxMsgSize),
vendorExt: c.VendorSpecific, vendorExt: c.VendorSpecific,
tagStore: newTagStore(c.TagSubscriptions), tagStore: newTagStore(c.TagSubscriptions),
trace: c.Trace, trace: c.Trace,
canonicalFieldNames: c.CanonicalFieldNames, canonicalFieldNames: c.CanonicalFieldNames,
trimSlash: c.TrimFieldNames, trimSlash: c.TrimFieldNames,
tagPathPrefix: c.PrefixTagKeyWithPath, tagPathPrefix: c.PrefixTagKeyWithPath,
guessPathStrategy: c.GuessPathStrategy, guessPathStrategy: c.GuessPathStrategy,
decoder: c.decoder, decoder: c.decoder,
log: c.Log, enforceFirstNamespaceAsOrigin: c.EnforceFirstNamespaceAsOrigin,
log: c.Log,
ClientParameters: keepalive.ClientParameters{ ClientParameters: keepalive.ClientParameters{
Time: time.Duration(c.KeepaliveTime), Time: time.Duration(c.KeepaliveTime),
Timeout: time.Duration(c.KeepaliveTimeout), Timeout: time.Duration(c.KeepaliveTimeout),
@ -436,13 +451,16 @@ func (s *subscription) buildFullPath(c *GNMI) error {
return nil 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 // Build the subscription path without keys
path, err := parsePath(s.Origin, s.Path, "") path, err := parsePath(s.Origin, s.Path, "")
if err != nil { if err != nil {
return err return err
} }
info := newInfoFromPathWithoutKeys(path) info := newInfoFromPathWithoutKeys(path)
if enforceFirstNamespaceAsOrigin {
info.enforceFirstNamespaceAsOrigin()
}
// If the user didn't provide a measurement name, use last path element // If the user didn't provide a measurement name, use last path element
name := s.Name name := s.Name
@ -455,15 +473,18 @@ func (s *subscription) buildAlias(aliases map[*pathInfo]string) error {
return nil return nil
} }
func newGNMI() telegraf.Input {
return &GNMI{
Encoding: "proto",
Redial: config.Duration(10 * time.Second),
}
}
func init() { 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: // 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,
}
})
} }

View File

@ -779,9 +779,10 @@ func TestNotification(t *testing.T) {
{ {
name: "issue #12257 Sonic", name: "issue #12257 Sonic",
plugin: &GNMI{ plugin: &GNMI{
Log: testutil.Logger{}, Log: testutil.Logger{},
Encoding: "proto", Encoding: "proto",
Redial: config.Duration(1 * time.Second), Redial: config.Duration(1 * time.Second),
EnforceFirstNamespaceAsOrigin: true,
Subscriptions: []subscription{ Subscriptions: []subscription{
{ {
Name: "temperature", Name: "temperature",
@ -910,10 +911,11 @@ func TestNotification(t *testing.T) {
{ {
name: "Juniper Extension", name: "Juniper Extension",
plugin: &GNMI{ plugin: &GNMI{
Log: testutil.Logger{}, Log: testutil.Logger{},
Encoding: "proto", Encoding: "proto",
VendorSpecific: []string{"juniper_header"}, VendorSpecific: []string{"juniper_header"},
Redial: config.Duration(1 * time.Second), Redial: config.Duration(1 * time.Second),
EnforceFirstNamespaceAsOrigin: true,
Subscriptions: []subscription{ Subscriptions: []subscription{
{ {
Name: "type", Name: "type",
@ -1105,7 +1107,12 @@ func TestCases(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// Register the plugin // 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 { for _, f := range folders {
// Only handle folders // Only handle folders
@ -1158,12 +1165,6 @@ func TestCases(t *testing.T) {
// Prepare the server response // Prepare the server response
responseFunction := func(server gnmi.GNMI_SubscribeServer) error { responseFunction := func(server gnmi.GNMI_SubscribeServer) error {
sync := &gnmi.SubscribeResponse{
Response: &gnmi.SubscribeResponse_SyncResponse{
SyncResponse: true,
},
}
_ = sync
for i := range responses { for i := range responses {
if err := server.Send(&responses[i]); err != nil { if err := server.Send(&responses[i]); err != nil {
return err return err

View File

@ -33,21 +33,22 @@ import (
const eidJuniperTelemetryHeader = 1 const eidJuniperTelemetryHeader = 1
type handler struct { type handler struct {
host string host string
port string port string
aliases map[*pathInfo]string aliases map[*pathInfo]string
tagsubs []tagSubscription tagsubs []tagSubscription
maxMsgSize int maxMsgSize int
emptyNameWarnShown bool emptyNameWarnShown bool
vendorExt []string vendorExt []string
tagStore *tagStore tagStore *tagStore
trace bool trace bool
canonicalFieldNames bool canonicalFieldNames bool
trimSlash bool trimSlash bool
tagPathPrefix bool tagPathPrefix bool
guessPathStrategy string guessPathStrategy string
decoder *yangmodel.Decoder decoder *yangmodel.Decoder
log telegraf.Logger enforceFirstNamespaceAsOrigin bool
log telegraf.Logger
keepalive.ClientParameters 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 // Extract the path part valid for the whole set of updates if any
prefix := newInfoFromPath(response.Update.Prefix) prefix := newInfoFromPath(response.Update.Prefix)
if h.enforceFirstNamespaceAsOrigin {
prefix.enforceFirstNamespaceAsOrigin()
}
// Add info to the tags // Add info to the tags
headerTags["source"] = h.host headerTags["source"] = h.host
@ -173,6 +177,9 @@ func (h *handler) handleSubscribeResponseUpdate(acc telegraf.Accumulator, respon
var valueFields []updateField var valueFields []updateField
for _, update := range response.Update.Update { for _, update := range response.Update.Update {
fullPath := prefix.append(update.Path) fullPath := prefix.append(update.Path)
if h.enforceFirstNamespaceAsOrigin {
prefix.enforceFirstNamespaceAsOrigin()
}
if update.Path.Origin != "" { if update.Path.Origin != "" {
fullPath.origin = update.Path.Origin fullPath.origin = update.Path.Origin
} }
@ -251,7 +258,11 @@ func (h *handler) handleSubscribeResponseUpdate(acc telegraf.Accumulator, respon
h.emptyNameWarnShown = true h.emptyNameWarnShown = true
} }
} }
aliasInfo := newInfoFromString(aliasPath) aliasInfo := newInfoFromString(aliasPath)
if h.enforceFirstNamespaceAsOrigin {
aliasInfo.enforceFirstNamespaceAsOrigin()
}
if tags["path"] == "" && h.guessPathStrategy == "subscription" { if tags["path"] == "" && h.guessPathStrategy == "subscription" {
tags["path"] = aliasInfo.String() tags["path"] = aliasInfo.String()

View File

@ -29,8 +29,16 @@ func newInfoFromString(path string) *pathInfo {
return &pathInfo{} return &pathInfo{}
} }
info := &pathInfo{} parts := strings.Split(path, "/")
for _, part := range 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 == "" { if part == "" {
continue 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 // Remove empty segments
segments := make([]segment, 0, len(pi.segments)) segments := make([]segment, 0, len(pi.segments))
for _, s := range pi.segments { for _, s := range pi.segments {
@ -211,6 +213,19 @@ func (pi *pathInfo) normalize() {
pi.segments = segments 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 { func (pi *pathInfo) equalsPathNoKeys(path *gnmi.Path) bool {
if len(pi.segments) != len(path.Elem) { if len(pi.segments) != len(path.Elem) {
return false return false
@ -344,9 +359,7 @@ func (pi *pathInfo) fullPath() string {
return path return path
} }
path += "/" + pi.segments[0].id for _, s := range pi.segments {
for _, s := range pi.segments[1:] {
if s.namespace != "" { if s.namespace != "" {
path += "/" + s.namespace + ":" + s.id path += "/" + s.namespace + ":" + s.id
} else { } else {

View File

@ -41,6 +41,11 @@
## Only receive updates for the state, also suppresses receiving the initial state ## Only receive updates for the state, also suppresses receiving the initial state
# updates_only = false # 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 ## Guess the path-tag if an update does not contain a prefix-path
## Supported values are ## Supported values are
## none -- do not add a 'path' tag ## none -- do not add a 'path' tag

View File

@ -41,6 +41,11 @@
## Only receive updates for the state, also suppresses receiving the initial state ## Only receive updates for the state, also suppresses receiving the initial state
# updates_only = false # 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 ## Guess the path-tag if an update does not contain a prefix-path
## Supported values are ## Supported values are
## none -- do not add a 'path' tag ## none -- do not add a 'path' tag

View File

@ -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

View File

@ -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
}
}
]
}
}
]

View File

@ -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"

View File

@ -30,7 +30,7 @@ func (h *handler) newFieldsFromUpdate(path *pathInfo, update *gnmi.Update) ([]up
case *gnmi.TypedValue_AsciiVal: // not handled in ToScalar case *gnmi.TypedValue_AsciiVal: // not handled in ToScalar
return []updateField{{path, v.AsciiVal}}, nil return []updateField{{path, v.AsciiVal}}, nil
case *gnmi.TypedValue_JsonVal: // requires special path handling 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 case *gnmi.TypedValue_JsonIetfVal: // requires special path handling
return h.processJSONIETF(path, v.JsonIetfVal) 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 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{} var nested interface{}
if err := json.Unmarshal(data, &nested); err != nil { if err := json.Unmarshal(data, &nested); err != nil {
return nil, fmt.Errorf("failed to parse JSON value: %w", err) 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 // Create an update-field with the complete path for all entries
fields := make([]updateField, 0, len(entries)) fields := make([]updateField, 0, len(entries))
for _, entry := range entries { for _, entry := range entries {
p := path.appendSegments(entry.key...)
if h.enforceFirstNamespaceAsOrigin {
p.enforceFirstNamespaceAsOrigin()
}
fields = append(fields, updateField{ fields = append(fields, updateField{
path: path.appendSegments(entry.key...), path: p,
value: entry.value, value: entry.value,
}) })
} }
@ -105,6 +110,9 @@ func (h *handler) processJSONIETF(path *pathInfo, data []byte) ([]updateField, e
fields := make([]updateField, 0, len(entries)) fields := make([]updateField, 0, len(entries))
for _, entry := range entries { for _, entry := range entries {
p := path.appendSegments(entry.key...) p := path.appendSegments(entry.key...)
if h.enforceFirstNamespaceAsOrigin {
p.enforceFirstNamespaceAsOrigin()
}
// Try to lookup the full path to decode the field according to the // Try to lookup the full path to decode the field according to the
// YANG model if any // YANG model if any