fix(inputs.gnmi): Allow to disable using first namespace as origin (#16507)
This commit is contained in:
parent
01c633fbbe
commit
0dcdbe4ab4
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue