Skip to content

Commit

Permalink
Path format support.
Browse files Browse the repository at this point in the history
1. GNMI spec e.g. /protocols/protocol[identifier=ISIS][name=65497]
2. Standard XPath spec e.g. /protocols/protocol[identifier=ISIS and name=65497]
  • Loading branch information
nsimariaj committed Feb 14, 2024
1 parent 6fe6147 commit 37232bd
Show file tree
Hide file tree
Showing 11 changed files with 1,545 additions and 1,379 deletions.
2,587 changes: 1,294 additions & 1,293 deletions coverage.out

Large diffs are not rendered by default.

90 changes: 71 additions & 19 deletions gnmi_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"math"
"regexp"
"strings"

gnmi "github.com/Juniper/jtimon/gnmi/gnmi"
Expand Down Expand Up @@ -71,6 +72,50 @@ type gnmiParseOutputT struct {
inKvs uint64
}


// parseKeyValuePairs takes an input string in the format `prefix[key = "value"]`
// and returns the prefix as a string and the key-value pairs as a map.
//
// The input string can contain multiple key-value pairs in the same or different brackets.
// The keys and values are trimmed of quotes and spaces.
//
// If the input string does not contain any brackets, the function returns the entire string
// as the prefix and an empty map.
func parseKeyValuePairs(input string) (string, map[string]string) {
// Initialize a map to hold the key-value pairs
result := make(map[string]string)

// Split the input string by the first occurrence of [" to get the prefix and the rest
parts := strings.SplitN(input, "[", 2)
prefix := parts[0]

// Match anything inside square brackets
re := regexp.MustCompile(`\[(.*?)\]`)
matches := re.FindAllString(input, -1)

for _, match := range matches {
// Remove the square brackets
match = strings.Trim(match, "[]")

// Split by "and" to support multiple key-value pairs in the same brackets
pairs := strings.Split(match, "and")

for _, pair := range pairs {
// Split by "=" to separate the key and value
kv := strings.Split(pair, "=")

// Trim the quotes and spaces from the key and value
key := strings.Trim(kv[0], " \"")
value := strings.Trim(kv[1], " \"")

// Add the key-value pair to the result map
result[key] = value
}
}

return prefix, result
}

// Convert xpath to gNMI path
func xPathTognmiPath(xpath string) (*gnmi.Path, error) {
var gpath gnmi.Path
Expand All @@ -80,27 +125,34 @@ func xPathTognmiPath(xpath string) (*gnmi.Path, error) {
continue
}

kvSplit := strings.Split(s, gXPathTokenIndexBegin)
if len(kvSplit) == 1 {
gpath.Elem = append(gpath.Elem, &gnmi.PathElem{Name: kvSplit[0]})
prefix, kv := parseKeyValuePairs(s)
if len(kv) == 0 {
gpath.Elem = append(gpath.Elem, &gnmi.PathElem{Name: prefix})
} else {
gpath.Elem = append(gpath.Elem, &gnmi.PathElem{Name: kvSplit[0], Key: map[string]string{}})
kvpairs := strings.Split(kvSplit[1], gXpathTokenMultiIndexSep)

pe := gpath.Elem[len(gpath.Elem)-1]
for _, kvpair := range kvpairs {
kvpair = strings.TrimSpace(kvpair)
kv := strings.Split(kvpair, gXPathTokenKVSep)

idxval := strings.TrimPrefix(kv[1], gXPathTokenValueWrapper)
if idxval[len(idxval)-1:] != gXPathTokenIndexEnd {
idxval = strings.TrimSuffix(idxval, gXPathTokenValueWrapper)
} else {
idxval = strings.TrimSuffix(idxval, gXPathTokenValueWrapper+gXPathTokenIndexEnd)
}
pe.Key[kv[0]] = idxval
}
gpath.Elem = append(gpath.Elem, &gnmi.PathElem{Name: prefix, Key: kv})
}

// kvSplit := strings.Split(s, gXPathTokenIndexBegin)
// if len(kvSplit) == 1 {
// gpath.Elem = append(gpath.Elem, &gnmi.PathElem{Name: kvSplit[0]})
// } else {
// gpath.Elem = append(gpath.Elem, &gnmi.PathElem{Name: kvSplit[0], Key: map[string]string{}})
// kvpairs := strings.Split(kvSplit[1], gXpathTokenMultiIndexSep)

// pe := gpath.Elem[len(gpath.Elem)-1]
// for _, kvpair := range kvpairs {
// kvpair = strings.TrimSpace(kvpair)
// kv := strings.Split(kvpair, gXPathTokenKVSep)

// idxval := strings.TrimPrefix(kv[1], gXPathTokenValueWrapper)
// if idxval[len(idxval)-1:] != gXPathTokenIndexEnd {
// idxval = strings.TrimSuffix(idxval, gXPathTokenValueWrapper)
// } else {
// idxval = strings.TrimSuffix(idxval, gXPathTokenValueWrapper+gXPathTokenIndexEnd)
// }
// pe.Key[kv[0]] = idxval
// }
// }
}

if len(gpath.Elem) == 0 {
Expand Down
118 changes: 117 additions & 1 deletion gnmi_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,117 @@ import (
google_protobuf "github.com/golang/protobuf/ptypes/any"
)


func TestParseKeyValuePairs(t *testing.T) {
tests := []struct {
name string
input string
wantPrefix string
want map[string]string
}{
{
name: "Test 1",
input: `foo["k1" = "v1"]["k2" = "v2"]`,
wantPrefix: "foo",
want: map[string]string{
"k1": "v1",
"k2": "v2",
},
},
{
name: "Test 2",
input: `bar["k3" = "v3"]["k4" = "v4"]`,
wantPrefix: "bar",
want: map[string]string{
"k3": "v3",
"k4": "v4",
},
},
{
name: "Test 3",
input: `foo["k1" = "v1"]["k2" = "v2"]["k3" = "v3"]`,
wantPrefix: "foo",
want: map[string]string{
"k1": "v1",
"k2": "v2",
"k3": "v3",
},
},
{
name: "Test 4 - Only Prefix",
input: `baz`,
wantPrefix: "baz",
want: map[string]string{},
},
{
name: "Test 5 - Multiple Pairs in Same Brackets",
input: `foo["k1" = "v1" and "k2" = "v2"]`,
wantPrefix: "foo",
want: map[string]string{
"k1": "v1",
"k2": "v2",
},
},
{
name: "Test 6 - Three Pairs in Same Brackets",
input: `foo["k1" = "v1" and "k2" = "v2" and "k3" = "v3"]`,
wantPrefix: "foo",
want: map[string]string{
"k1": "v1",
"k2": "v2",
"k3": "v3",
},
},
{
name: "Test 7 - Key Without Quotes",
input: `foo[k1 = "v1"]`,
wantPrefix: "foo",
want: map[string]string{
"k1": "v1",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotPrefix, got := parseKeyValuePairs(tt.input)
if gotPrefix != tt.wantPrefix || !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseKeyValuePairs() = %v, %v, want %v, %v", gotPrefix, got, tt.wantPrefix, tt.want)
}
})
}
}

func TestXPathTognmiPath(t *testing.T) {
tests := []struct {
name string
xpath string
err bool
path *gnmi.Path
}{
{
name: "no-keys",
xpath: "/interfaces/interface",
err: false,
path: &gnmi.Path{Origin: "",
Elem: []*gnmi.PathElem{
{Name: "interfaces"},
{Name: "interface"},
},
},
},
{
name: "one-key",
xpath: "/interfaces/interface[k1=v1]",
err: false,
path: &gnmi.Path{Origin: "",
Elem: []*gnmi.PathElem{
{Name: "interfaces"},
{Name: "interface", Key: map[string]string{"k1": "v1"}},
},
},
},

{
name: "multi-level-multi-keys",
xpath: "/interfaces/interface[k1=\"foo\"]/subinterfaces/subinterface[k1=\"foo1\" and k2=\"bar1\"]",
Expand All @@ -35,6 +139,18 @@ func TestXPathTognmiPath(t *testing.T) {
},
},
},

{
name: "multi-keys-nostd",
xpath: "/interfaces/interface[k1=\"v1\"][k2=\"v2\"]",
err: false,
path: &gnmi.Path{Origin: "",
Elem: []*gnmi.PathElem{
{Name: "interfaces"},
{Name: "interface", Key: map[string]string{"k1": "v1", "k2": "v2"}},
},
},
},
{
name: "multi-level-multi-keys-err",
xpath: "/interfaces/interface[k1=\"foo\"]/subinterfaces/subinterface[k1=\"foo1\" and k2=\"bar1\"]",
Expand Down Expand Up @@ -81,7 +197,7 @@ func TestXPathTognmiPath(t *testing.T) {
if test.err {
if err == nil && reflect.DeepEqual(test.path, gnmiPath) {
var errMsg string
errMsg = fmt.Sprintf("want error but got nil\n")
errMsg = "want error but got nil\n"
errMsg += fmt.Sprintf("\nexpected:%v\nGot:%v\n", test.path, gnmiPath)
t.Errorf(errMsg)
}
Expand Down
11 changes: 7 additions & 4 deletions tests/data/juniper-junos/config/jtisim-influx-alias.log
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,22 @@ Receiving telemetry data from 127.0.0.1:50051
+------------------------------+--------------------+--------------------+--------------------+--------------------+
| Timestamp | KV | Packets | Bytes | Bytes(wire) |
+------------------------------+--------------------+--------------------+--------------------+--------------------+
| Fri Feb 9 03:28:24 PST 2024 | 1980 | 40 | 87418 | 87618 |
| Wed Feb 14 14:19:54 PST 2024 | 1980 | 40 | 87418 | 87618 |

Batch processing: #packets:40 #points:40
Batch write successful! Post batch write available points: 0

| Fri Feb 9 03:28:26 PST 2024 | 1980 | 40 | 87418 | 87618 |
| Wed Feb 14 14:19:56 PST 2024 | 1980 | 40 | 87418 | 87618 |


| Fri Feb 9 03:28:28 PST 2024 | 1980 | 40 | 87418 | 87618 |
| Wed Feb 14 14:19:58 PST 2024 | 1980 | 40 | 87418 | 87618 |


| Wed Feb 14 14:20:00 PST 2024 | 1980 | 40 | 87418 | 87618 |

Collector Stats for 127.0.0.1:50051 (Run time : 8.004894958s)


Collector Stats for 127.0.0.1:50051 (Run time : 8.006234208s)
40 : in-packets
1980 : data points (KV pairs)
25 : in-header wirelength (bytes)
Expand Down
28 changes: 14 additions & 14 deletions tests/data/juniper-junos/config/jtisim-influx.log
Original file line number Diff line number Diff line change
Expand Up @@ -82,54 +82,54 @@ gRPC headers from host 127.0.0.1:50051
content-type: [application/grpc]
jtisim: [yes]
Receiving telemetry data from 127.0.0.1:50051
Batch processing: #packets:40 #points:40

+------------------------------+--------------------+--------------------+--------------------+--------------------+
| Timestamp | KV | Packets | Bytes | Bytes(wire) |
+------------------------------+--------------------+--------------------+--------------------+--------------------+
| Fri Feb 9 03:27:59 PST 2024 | 1980 | 40 | 87418 | 87618 |
| Wed Feb 14 14:19:29 PST 2024 | 1980 | 40 | 87418 | 87618 |

Batch processing: #packets:40 #points:40
Batch write successful! Post batch write available points: 0

| Fri Feb 9 03:28:01 PST 2024 | 1980 | 40 | 87418 | 87618 |
| Wed Feb 14 14:19:31 PST 2024 | 1980 | 40 | 87418 | 87618 |


| Fri Feb 9 03:28:03 PST 2024 | 1980 | 40 | 87418 | 87618 |
| Wed Feb 14 14:19:33 PST 2024 | 1980 | 40 | 87418 | 87618 |


| Fri Feb 9 03:28:05 PST 2024 | 1980 | 40 | 87418 | 87618 |
| Wed Feb 14 14:19:35 PST 2024 | 1980 | 40 | 87418 | 87618 |


| Fri Feb 9 03:28:07 PST 2024 | 2456 | 50 | 108260 | 108510 |
| Wed Feb 14 14:19:37 PST 2024 | 1980 | 40 | 87418 | 87618 |

Batch processing: #packets:40 #points:40

| Fri Feb 9 03:28:09 PST 2024 | 3960 | 80 | 174838 | 175238 |
| Wed Feb 14 14:19:39 PST 2024 | 3960 | 80 | 174838 | 175238 |

Batch write successful! Post batch write available points: 0

| Fri Feb 9 03:28:11 PST 2024 | 3960 | 80 | 174838 | 175238 |
| Wed Feb 14 14:19:41 PST 2024 | 3960 | 80 | 174838 | 175238 |


| Fri Feb 9 03:28:13 PST 2024 | 3960 | 80 | 174838 | 175238 |
| Wed Feb 14 14:19:43 PST 2024 | 3960 | 80 | 174838 | 175238 |


| Fri Feb 9 03:28:15 PST 2024 | 3960 | 80 | 174838 | 175238 |
| Wed Feb 14 14:19:45 PST 2024 | 3960 | 80 | 174838 | 175238 |


| Fri Feb 9 03:28:17 PST 2024 | 5881 | 119 | 259566 | 260161 |
| Wed Feb 14 14:19:47 PST 2024 | 4356 | 88 | 192322 | 192762 |

Batch processing: #packets:40 #points:40

| Fri Feb 9 03:28:19 PST 2024 | 5940 | 120 | 262258 | 262858 |
| Wed Feb 14 14:19:49 PST 2024 | 5940 | 120 | 262258 | 262858 |

Batch write successful! Post batch write available points: 0

| Fri Feb 9 03:28:21 PST 2024 | 5940 | 120 | 262258 | 262858 |
| Wed Feb 14 14:19:51 PST 2024 | 5940 | 120 | 262258 | 262858 |



Collector Stats for 127.0.0.1:50051 (Run time : 25.001658041s)
Collector Stats for 127.0.0.1:50051 (Run time : 25.003723666s)
120 : in-packets
5940 : data points (KV pairs)
25 : in-header wirelength (bytes)
Expand Down
Loading

0 comments on commit 37232bd

Please sign in to comment.