fix(inputs.gnmi): Handle YANG namespaces in paths correctly (#15565)
This commit is contained in:
parent
f40578fc16
commit
1b849ebf92
|
|
@ -442,7 +442,7 @@ func (s *Subscription) buildAlias(aliases map[*pathInfo]string) error {
|
||||||
// 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
|
||||||
if name == "" && len(info.segments) > 0 {
|
if name == "" && len(info.segments) > 0 {
|
||||||
name = info.segments[len(info.segments)-1]
|
name = info.segments[len(info.segments)-1].id
|
||||||
}
|
}
|
||||||
if name != "" {
|
if name != "" {
|
||||||
aliases[info] = name
|
aliases[info] = name
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ func (h *handler) handleSubscribeResponseUpdate(acc telegraf.Accumulator, respon
|
||||||
// Add info to the tags
|
// Add info to the tags
|
||||||
headerTags["source"], _, _ = net.SplitHostPort(h.address)
|
headerTags["source"], _, _ = net.SplitHostPort(h.address)
|
||||||
if !prefix.empty() {
|
if !prefix.empty() {
|
||||||
headerTags["path"] = prefix.String()
|
headerTags["path"] = prefix.FullPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process and remove tag-updates from the response first so we can
|
// Process and remove tag-updates from the response first so we can
|
||||||
|
|
@ -270,15 +270,11 @@ func (h *handler) handleSubscribeResponseUpdate(acc telegraf.Accumulator, respon
|
||||||
// shorter than the full path to avoid an empty key, then strip the
|
// shorter than the full path to avoid an empty key, then strip the
|
||||||
// common part of the field is prefixed with the alias path. Note
|
// common part of the field is prefixed with the alias path. Note
|
||||||
// the origins can match or be empty and be considered equal.
|
// the origins can match or be empty and be considered equal.
|
||||||
if aliasInfo.isSubPathOf(field.path) && len(aliasInfo.segments) < len(field.path.segments) {
|
if relative := aliasInfo.relative(field.path, true); relative != "" {
|
||||||
relative := field.path.segments[len(aliasInfo.segments):len(field.path.segments)]
|
key = relative
|
||||||
key = strings.Join(relative, "/")
|
|
||||||
} else {
|
} else {
|
||||||
// Otherwise use the last path element as the field key if it
|
// Otherwise use the last path element as the field key
|
||||||
// exists.
|
key = field.path.Base()
|
||||||
if len(field.path.segments) > 0 {
|
|
||||||
key = field.path.segments[len(field.path.segments)-1]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
key = strings.ReplaceAll(key, "-", "_")
|
key = strings.ReplaceAll(key, "-", "_")
|
||||||
}
|
}
|
||||||
|
|
@ -328,12 +324,11 @@ func guessPrefixFromUpdate(fields []updateField) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if len(fields) == 1 {
|
if len(fields) == 1 {
|
||||||
dir, _ := fields[0].path.split()
|
return fields[0].path.Dir()
|
||||||
return dir
|
|
||||||
}
|
}
|
||||||
commonPath := &pathInfo{
|
commonPath := &pathInfo{
|
||||||
origin: fields[0].path.origin,
|
origin: fields[0].path.origin,
|
||||||
segments: append([]string{}, fields[0].path.segments...),
|
segments: append([]segment{}, fields[0].path.segments...),
|
||||||
}
|
}
|
||||||
for _, f := range fields[1:] {
|
for _, f := range fields[1:] {
|
||||||
commonPath.keepCommonPart(f.path)
|
commonPath.keepCommonPart(f.path)
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,26 @@
|
||||||
package gnmi
|
package gnmi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
gnmiLib "github.com/openconfig/gnmi/proto/gnmi"
|
gnmiLib "github.com/openconfig/gnmi/proto/gnmi"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Regular expression to see if a path element contains an origin
|
|
||||||
var originPattern = regexp.MustCompile(`^([\w-]+):`)
|
|
||||||
|
|
||||||
type keySegment struct {
|
type keySegment struct {
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
kv map[string]string
|
kv map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type segment struct {
|
||||||
|
namespace string
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
type pathInfo struct {
|
type pathInfo struct {
|
||||||
origin string
|
origin string
|
||||||
target string
|
target string
|
||||||
segments []string
|
segments []segment
|
||||||
keyValues []keySegment
|
keyValues []keySegment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,10 +30,11 @@ func newInfoFromString(path string) *pathInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
info := &pathInfo{}
|
info := &pathInfo{}
|
||||||
for _, s := range strings.Split(path, "/") {
|
for _, part := range strings.Split(path, "/") {
|
||||||
if s != "" {
|
if part == "" {
|
||||||
info.segments = append(info.segments, s)
|
continue
|
||||||
}
|
}
|
||||||
|
info.segments = append(info.segments, segment{id: part})
|
||||||
}
|
}
|
||||||
info.normalize()
|
info.normalize()
|
||||||
|
|
||||||
|
|
@ -42,13 +44,13 @@ func newInfoFromString(path string) *pathInfo {
|
||||||
func newInfoFromPathWithoutKeys(path *gnmiLib.Path) *pathInfo {
|
func newInfoFromPathWithoutKeys(path *gnmiLib.Path) *pathInfo {
|
||||||
info := &pathInfo{
|
info := &pathInfo{
|
||||||
origin: path.Origin,
|
origin: path.Origin,
|
||||||
segments: make([]string, 0, len(path.Elem)),
|
segments: make([]segment, 0, len(path.Elem)),
|
||||||
}
|
}
|
||||||
for _, elem := range path.Elem {
|
for _, elem := range path.Elem {
|
||||||
if elem.Name == "" {
|
if elem.Name == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
info.segments = append(info.segments, elem.Name)
|
info.segments = append(info.segments, segment{id: elem.Name})
|
||||||
}
|
}
|
||||||
info.normalize()
|
info.normalize()
|
||||||
|
|
||||||
|
|
@ -74,7 +76,7 @@ func newInfoFromPath(paths ...*gnmiLib.Path) *pathInfo {
|
||||||
if elem.Name == "" {
|
if elem.Name == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
info.segments = append(info.segments, elem.Name)
|
info.segments = append(info.segments, segment{id: elem.Name})
|
||||||
|
|
||||||
if len(elem.Key) == 0 {
|
if len(elem.Key) == 0 {
|
||||||
continue
|
continue
|
||||||
|
|
@ -104,7 +106,7 @@ func (pi *pathInfo) append(paths ...*gnmiLib.Path) *pathInfo {
|
||||||
path := &pathInfo{
|
path := &pathInfo{
|
||||||
origin: pi.origin,
|
origin: pi.origin,
|
||||||
target: pi.target,
|
target: pi.target,
|
||||||
segments: append([]string{}, pi.segments...),
|
segments: append([]segment{}, pi.segments...),
|
||||||
keyValues: make([]keySegment, 0, len(pi.keyValues)),
|
keyValues: make([]keySegment, 0, len(pi.keyValues)),
|
||||||
}
|
}
|
||||||
for _, elem := range pi.keyValues {
|
for _, elem := range pi.keyValues {
|
||||||
|
|
@ -125,7 +127,7 @@ func (pi *pathInfo) append(paths ...*gnmiLib.Path) *pathInfo {
|
||||||
if elem.Name == "" {
|
if elem.Name == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
path.segments = append(path.segments, elem.Name)
|
path.segments = append(path.segments, segment{id: elem.Name})
|
||||||
|
|
||||||
if len(elem.Key) == 0 {
|
if len(elem.Key) == 0 {
|
||||||
continue
|
continue
|
||||||
|
|
@ -151,7 +153,7 @@ func (pi *pathInfo) appendSegments(segments ...string) *pathInfo {
|
||||||
path := &pathInfo{
|
path := &pathInfo{
|
||||||
origin: pi.origin,
|
origin: pi.origin,
|
||||||
target: pi.target,
|
target: pi.target,
|
||||||
segments: append([]string{}, pi.segments...),
|
segments: append([]segment{}, pi.segments...),
|
||||||
keyValues: make([]keySegment, 0, len(pi.keyValues)),
|
keyValues: make([]keySegment, 0, len(pi.keyValues)),
|
||||||
}
|
}
|
||||||
for _, elem := range pi.keyValues {
|
for _, elem := range pi.keyValues {
|
||||||
|
|
@ -171,7 +173,7 @@ func (pi *pathInfo) appendSegments(segments ...string) *pathInfo {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
path.segments = append(path.segments, s)
|
path.segments = append(path.segments, segment{id: s})
|
||||||
}
|
}
|
||||||
path.normalize()
|
path.normalize()
|
||||||
|
|
||||||
|
|
@ -183,18 +185,28 @@ func (pi *pathInfo) normalize() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some devices supply the origin as part of the first path element,
|
// Extract namespaces from segments
|
||||||
// so try to find and extract it there.
|
for i, s := range pi.segments {
|
||||||
groups := originPattern.FindStringSubmatch(pi.segments[0])
|
if ns, id, found := strings.Cut(s.id, ":"); found {
|
||||||
if len(groups) == 2 {
|
pi.segments[i].namespace = ns
|
||||||
pi.origin = groups[1]
|
pi.segments[i].id = id
|
||||||
pi.segments[0] = pi.segments[0][len(groups[1])+1:]
|
|
||||||
|
|
||||||
// if we get empty string back, remove the segment
|
|
||||||
if pi.segments[0] == "" {
|
|
||||||
pi.segments = pi.segments[1:]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
if s.id != "" {
|
||||||
|
segments = append(segments, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pi.segments = segments
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pi *pathInfo) equalsPathNoKeys(path *gnmiLib.Path) bool {
|
func (pi *pathInfo) equalsPathNoKeys(path *gnmiLib.Path) bool {
|
||||||
|
|
@ -202,7 +214,7 @@ func (pi *pathInfo) equalsPathNoKeys(path *gnmiLib.Path) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for i, s := range pi.segments {
|
for i, s := range pi.segments {
|
||||||
if s != path.Elem[i].Name {
|
if s.id != path.Elem[i].Name {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -223,7 +235,11 @@ func (pi *pathInfo) isSubPathOf(path *pathInfo) bool {
|
||||||
|
|
||||||
// Compare the elements and exit if we find a mismatch
|
// Compare the elements and exit if we find a mismatch
|
||||||
for i, p := range pi.segments {
|
for i, p := range pi.segments {
|
||||||
if p != path.segments[i] {
|
ps := path.segments[i]
|
||||||
|
if p.namespace != "" && ps.namespace != "" && p.namespace != ps.namespace {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if p.id != ps.id {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -231,6 +247,28 @@ func (pi *pathInfo) isSubPathOf(path *pathInfo) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pi *pathInfo) relative(path *pathInfo, withNamespace bool) string {
|
||||||
|
if !pi.isSubPathOf(path) || len(pi.segments) == len(path.segments) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
segments := path.segments[len(pi.segments):len(path.segments)]
|
||||||
|
var r string
|
||||||
|
if withNamespace && segments[0].namespace != "" {
|
||||||
|
r = segments[0].namespace + ":" + segments[0].id
|
||||||
|
} else {
|
||||||
|
r = segments[0].id
|
||||||
|
}
|
||||||
|
for _, s := range segments[1:] {
|
||||||
|
if withNamespace && s.namespace != "" {
|
||||||
|
r += "/" + s.namespace + ":" + s.id
|
||||||
|
} else {
|
||||||
|
r += "/" + s.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func (pi *pathInfo) keepCommonPart(path *pathInfo) {
|
func (pi *pathInfo) keepCommonPart(path *pathInfo) {
|
||||||
shortestLen := len(pi.segments)
|
shortestLen := len(pi.segments)
|
||||||
if len(path.segments) < shortestLen {
|
if len(path.segments) < shortestLen {
|
||||||
|
|
@ -252,31 +290,35 @@ func (pi *pathInfo) keepCommonPart(path *pathInfo) {
|
||||||
pi.segments = pi.segments[:matchLen]
|
pi.segments = pi.segments[:matchLen]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pi *pathInfo) split() (dir, base string) {
|
func (pi *pathInfo) Dir() string {
|
||||||
if len(pi.segments) == 0 {
|
if len(pi.segments) <= 1 {
|
||||||
return "", ""
|
return ""
|
||||||
}
|
|
||||||
if len(pi.segments) == 1 {
|
|
||||||
return "", pi.segments[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dir = "/" + strings.Join(pi.segments[:len(pi.segments)-1], "/")
|
var dir string
|
||||||
if pi.origin != "" {
|
if pi.origin != "" {
|
||||||
dir = pi.origin + ":" + dir
|
dir = pi.origin + ":"
|
||||||
}
|
}
|
||||||
return dir, pi.segments[len(pi.segments)-1]
|
for _, s := range pi.segments[:len(pi.segments)-1] {
|
||||||
|
if s.namespace != "" {
|
||||||
|
dir += "/" + s.namespace + ":" + s.id
|
||||||
|
} else {
|
||||||
|
dir += "/" + s.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pi *pathInfo) String() string {
|
func (pi *pathInfo) Base() string {
|
||||||
if len(pi.segments) == 0 {
|
if len(pi.segments) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
out := "/" + strings.Join(pi.segments, "/")
|
s := pi.segments[len(pi.segments)-1]
|
||||||
if pi.origin != "" {
|
if s.namespace != "" {
|
||||||
out = pi.origin + ":" + out
|
return s.namespace + ":" + s.id
|
||||||
}
|
}
|
||||||
return out
|
return s.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pi *pathInfo) Path() (origin, path string) {
|
func (pi *pathInfo) Path() (origin, path string) {
|
||||||
|
|
@ -284,7 +326,45 @@ func (pi *pathInfo) Path() (origin, path string) {
|
||||||
return pi.origin, "/"
|
return pi.origin, "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
return pi.origin, "/" + strings.Join(pi.segments, "/")
|
for _, s := range pi.segments {
|
||||||
|
path += "/" + s.id
|
||||||
|
}
|
||||||
|
|
||||||
|
return pi.origin, path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pi *pathInfo) FullPath() string {
|
||||||
|
var path string
|
||||||
|
if pi.origin != "" {
|
||||||
|
path = pi.origin + ":"
|
||||||
|
}
|
||||||
|
if len(pi.segments) == 0 {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
path += "/" + pi.segments[0].id
|
||||||
|
|
||||||
|
for _, s := range pi.segments[1:] {
|
||||||
|
if s.namespace != "" {
|
||||||
|
path += "/" + s.namespace + ":" + s.id
|
||||||
|
} else {
|
||||||
|
path += "/" + s.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pi *pathInfo) String() string {
|
||||||
|
if len(pi.segments) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
origin, path := pi.Path()
|
||||||
|
if origin != "" {
|
||||||
|
return origin + ":" + path
|
||||||
|
}
|
||||||
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pi *pathInfo) Tags(pathPrefix bool) map[string]string {
|
func (pi *pathInfo) Tags(pathPrefix bool) map[string]string {
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ func (s *tagStore) insert(subscription TagSubscription, path *pathInfo, values [
|
||||||
for _, f := range values {
|
for _, f := range values {
|
||||||
tagName := subscription.Name
|
tagName := subscription.Name
|
||||||
if len(f.path.segments) > 0 {
|
if len(f.path.segments) > 0 {
|
||||||
key := f.path.segments[len(f.path.segments)-1]
|
key := f.path.Base()
|
||||||
key = strings.ReplaceAll(key, "-", "_")
|
key = strings.ReplaceAll(key, "-", "_")
|
||||||
tagName += "/" + key
|
tagName += "/" + key
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +74,7 @@ func (s *tagStore) insert(subscription TagSubscription, path *pathInfo, values [
|
||||||
for _, f := range values {
|
for _, f := range values {
|
||||||
tagName := subscription.Name
|
tagName := subscription.Name
|
||||||
if len(f.path.segments) > 0 {
|
if len(f.path.segments) > 0 {
|
||||||
key := f.path.segments[len(f.path.segments)-1]
|
key := f.path.Base()
|
||||||
key = strings.ReplaceAll(key, "-", "_")
|
key = strings.ReplaceAll(key, "-", "_")
|
||||||
tagName += "/" + key
|
tagName += "/" + key
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +103,7 @@ func (s *tagStore) insert(subscription TagSubscription, path *pathInfo, values [
|
||||||
for _, f := range values {
|
for _, f := range values {
|
||||||
tagName := subscription.Name
|
tagName := subscription.Name
|
||||||
if len(f.path.segments) > 0 {
|
if len(f.path.segments) > 0 {
|
||||||
key := f.path.segments[len(f.path.segments)-1]
|
key := f.path.Base()
|
||||||
key = strings.ReplaceAll(key, "-", "_")
|
key = strings.ReplaceAll(key, "-", "_")
|
||||||
tagName += "/" + key
|
tagName += "/" + key
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
event-stats,path=openconfig-system:/system/openconfig-events:event-stats/state,source=127.0.0.1 state/acked=0u,state/cleared=0u,state/events=4u,state/raised=0u 1718942414831832038
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"update": {
|
||||||
|
"timestamp": "1718942414831832038",
|
||||||
|
"prefix": {
|
||||||
|
"elem": [
|
||||||
|
{
|
||||||
|
"name": "openconfig-system:system"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "openconfig-events:event-stats"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "state"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"update": [
|
||||||
|
{
|
||||||
|
"path": {
|
||||||
|
"elem": [
|
||||||
|
{
|
||||||
|
"name": "acked"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"val": {
|
||||||
|
"uintVal": "0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": {
|
||||||
|
"elem": [
|
||||||
|
{
|
||||||
|
"name": "cleared"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"val": {
|
||||||
|
"uintVal": "0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": {
|
||||||
|
"elem": [
|
||||||
|
{
|
||||||
|
"name": "events"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"val": {
|
||||||
|
"uintVal": "4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": {
|
||||||
|
"elem": [
|
||||||
|
{
|
||||||
|
"name": "raised"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"val": {
|
||||||
|
"uintVal": "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
[[inputs.gnmi]]
|
||||||
|
addresses = ["dummy"]
|
||||||
|
prefix_tag_key_with_path = true
|
||||||
|
|
||||||
|
[[inputs.gnmi.subscription]]
|
||||||
|
name = "event-stats"
|
||||||
|
origin = "openconfig-system"
|
||||||
|
path = "/system/event-stats"
|
||||||
|
subscription_mode = "sample"
|
||||||
|
sample_interval = "10s"
|
||||||
Loading…
Reference in New Issue