openconfig / gnmic

gNMIc is a gNMI CLI client and collector
https://gnmic.openconfig.net
Apache License 2.0
182 stars 57 forks source link

Junos VMX set error on YAML file format but not JSON format #32

Closed gwoodwa1 closed 1 year ago

gwoodwa1 commented 1 year ago

I seem to have hit a strange issue where if I apply some simple Juniper OSPF configuration using a JSON file then all works as expected but when converting the JSON to YAML then it doesn't work. I've figured out the root cause of the issue since it's visible when checking what is being applied from the GNMIC logs.

Here is what is sent from a working JSON file:

val:{json_ietf_val:"{\"ospf\":{\"area\":[{\"name\":\"0.0.0.0\",\"interface\":[{\"name\":\"ge-0/0/1.0\"}]}]}}"}]'

{
  "source": "172.21.20.7:57400",
  "timestamp": 1672749977491496118,
  "time": "2023-01-03T12:46:17.491496118Z",
  "results": [
    {
      "operation": "UPDATE",
      "path": "juniper:configuration/protocols"
    }
  ]
}

Now observe when using YAML how the format is slightly different when the YAML is serialized back to JSON. We can see name:0.0.0.0 is in a different place or order:

val:{json_ietf_val:"{\"ospf\":{\"area\":[{\"interface\":[{\"name\":\"ge-0/0/1.0\"}],\"name\":\"0.0.0.0\"}]}}"}]

2023/01/03 12:46:24.795500 [gnmic] target "172.21.20.7:57400" set request failed: target "172.21.20.7:57400" SetRequest failed: rpc error: code = InvalidArgument desc = syntax error

I can see the difference in the way the YAML is serialized back to JSON is causing the difference in the set operation. However, I don't know how can I manipulate the serialization from YAML to JSON in GNMIC?

JSON file (working):

{
  "ospf": {
    "area": [
      {
        "name": "0.0.0.0",
        "interface": [
          {
            "name": "ge-0/0/1.0"
          }
        ]
      }
    ]
  }
}

YAML File which is an exact representation of the same JSON file (not working):

---
ospf:
  area:
  - name: 0.0.0.0
    interface:
    - name: ge-0/0/1.0
gwoodwa1 commented 1 year ago

Here is the full logs for the working one i.e. JSON:

gnmic -a 172.21.20.7:57400 -u admin -p admin@123 \                                                                                                                         
    --insecure set \
    --update-path 'juniper:/configuration/protocols/'\
    --update-file 'junos_bgp_ospf.json' \       
    -e json_ietf --log
2023/01/03 12:46:17.300603 [gnmic] version=0.27.1, commit=bad7879, date=2022-11-08T16:12:22Z, gitURL=https://github.com/openconfig/gnmic, docs=https://gnmic.openconfig.net
2023/01/03 12:46:17.300613 [gnmic] using config file ""
2023/01/03 12:46:17.300747 [gnmic] adding target {"name":"172.21.20.7:57400","address":"172.21.20.7:57400","username":"admin","password":"****","timeout":10000000000,"insecure":true,"skip-verify":false,"buffer-size":100,"retry-timer":10000000000,"log-tls-secret":false,"gzip":false,"token":""}
2023/01/03 12:46:17.301072 [gnmic] sending gNMI SetRequest: prefix='<nil>', delete='[]', replace='[]', update='[path:{origin:"juniper"  elem:{name:"configuration"}  elem:{name:"protocols"}}  val:{json_ietf_val:"{\"ospf\":{\"area\":[{\"name\":\"0.0.0.0\",\"interface\":[{\"name\":\"ge-0/0/1.0\"}]}]}}"}]', extension='[]' to 172.21.20.7:57400
2023/01/03 12:46:17.301095 [gnmic] creating gRPC client for target "172.21.20.7:57400"
{
  "source": "172.21.20.7:57400",
  "timestamp": 1672749977491496118,
  "time": "2023-01-03T12:46:17.491496118Z",
  "results": [
    {
      "operation": "UPDATE",
      "path": "juniper:configuration/protocols"
    }
  ]
}

versus non-working using YAML:

gnmic -a 172.21.20.7:57400 -u admin -p admin@123 \                                                                                                                         
    --insecure set \
    --update-path 'juniper:/configuration/protocols/'\
    --update-file 'junos_bgp_ospf.yaml' \
    -e json_ietf --log
2023/01/03 12:46:24.581163 [gnmic] version=0.27.1, commit=bad7879, date=2022-11-08T16:12:22Z, gitURL=https://github.com/openconfig/gnmic, docs=https://gnmic.openconfig.net
2023/01/03 12:46:24.581181 [gnmic] using config file ""
2023/01/03 12:46:24.581278 [gnmic] adding target {"name":"172.21.20.7:57400","address":"172.21.20.7:57400","username":"admin","password":"****","timeout":10000000000,"insecure":true,"skip-verify":false,"buffer-size":100,"retry-timer":10000000000,"log-tls-secret":false,"gzip":false,"token":""}
2023/01/03 12:46:24.581683 [gnmic] sending gNMI SetRequest: prefix='<nil>', delete='[]', replace='[]', update='[path:{origin:"juniper"  elem:{name:"configuration"}  elem:{name:"protocols"}}  val:{json_ietf_val:"{\"ospf\":{\"area\":[{\"interface\":[{\"name\":\"ge-0/0/1.0\"}],\"name\":\"0.0.0.0\"}]}}"}]', extension='[]' to 172.21.20.7:57400
2023/01/03 12:46:24.581710 [gnmic] creating gRPC client for target "172.21.20.7:57400"
2023/01/03 12:46:24.795500 [gnmic] target "172.21.20.7:57400" set request failed: target "172.21.20.7:57400" SetRequest failed: rpc error: code = InvalidArgument desc = syntax error, expecting "@";parse error;syntax error, expecting '}' or :;
target "172.21.20.7:57400" set request failed: target "172.21.20.7:57400" SetRequest failed: rpc error: code = InvalidArgument desc = syntax error, expecting "@";parse error;syntax error, expecting '}' or :;
Error: one or more requests failed
karimra commented 1 year ago

This is strange indeed, both those values are valid JSON objects. The order of keys in a JSON dict is arbitrary and shouldn't matter when parsing the payload. I would argue that JUNOS is not parsing the payload properly here.

gwoodwa1 commented 1 year ago

Junos seems to be very particular about the order in the JSON such that I can get it to work with YAML if I can extrapolate the xpath which will then automatically preserves that strict order e.g. use --update-path 'juniper:/configuration/protocols/ospf/area[name=0.0.0.0]' with the remaining config snippet in YAML

---
interface:
- name: ge-0/0/1.0
gnmic -a 172.21.20.7:57400 -u admin -p admin@123 \                                                                                                                         
    --insecure set \
    --update-path 'juniper:/configuration/protocols/ospf/area[name=0.0.0.0]'\
    --update-file 'test.yaml' \ 
    -e json_ietf --log
2023/01/03 19:03:05.674444 [gnmic] version=0.28.0, commit=8315400, date=2022-12-07T17:02:16Z, gitURL=https://github.com/openconfig/gnmic, docs=https://gnmic.openconfig.net
2023/01/03 19:03:05.674453 [gnmic] using config file ""
2023/01/03 19:03:05.674570 [gnmic] adding target {"name":"172.21.20.7:57400","address":"172.21.20.7:57400","username":"admin","password":"****","timeout":10000000000,"insecure":true,"skip-verify":false,"buffer-size":100,"retry-timer":10000000000,"log-tls-secret":false,"gzip":false,"token":""}
2023/01/03 19:03:05.674969 [gnmic] sending gNMI SetRequest: prefix='<nil>', delete='[]', replace='[]', update='[path:{origin:"juniper" elem:{name:"configuration"} elem:{name:"protocols"} elem:{name:"ospf"} elem:{name:"area" key:{key:"name" value:"0.0.0.0"}}} val:{json_ietf_val:"{\"interface\":[{\"name\":\"ge-0/0/1.0\"}]}"}]', extension='[]' to 172.21.20.7:57400
2023/01/03 19:03:05.674997 [gnmic] creating gRPC client for target "172.21.20.7:57400"
{
  "source": "172.21.20.7:57400",
  "timestamp": 1672772585700190272,
  "time": "2023-01-03T19:03:05.700190272Z",
  "results": [
    {
      "operation": "UPDATE",
      "path": "juniper:configuration/protocols/ospf/area[name=0.0.0.0]"
    }
  ]
}

This works fine since we are preserving a strict order which aligns to what Junos expects. My conclusion is Juniper expects a strict order whether that is by design or an unintended consequence is not clear. It would make me think that using YAML on Junos is probably a non-starter since the serialization back to JSON may not align to this strict order. As you say it shouldn't matter and I've been testing on other vendor platforms without seeing this issue. By the way this issue is not just on configuring OSPF and happens in other examples e.g. BGP. I just wanted to show a basic piece of config which is easier to troubleshoot. Not sure if there is a "fix" apart from always use JSON when working with JunOS to preserve that order and avoid using YAML. Unless you have any further thoughts or insights which I could try? Thank you by the way for a wonderful application.

hellt commented 1 year ago

@gwoodwa1 I think it makes sense to ask some juniper folks. Do you have any contacts? Otherwise we may link this to ntc slack where other juniper users might have some direct relationship with juniper personnel

gwoodwa1 commented 1 year ago

Agreed, it's probably best to ask some Juniper contacts. I have one or two contacts that I can approach. Will let you know what the feedback is on this.

gwoodwa1 commented 1 year ago

I've reached out to a number of Juniper experts but no one has any ideas on the root cause. Only that when you do a "get" operation the ordering of the json key/value pair output seems to always have the "name" key first. I had some feedback speculating that this strict ordering is needed for the translation back into XML which is what JunOS is natively running under the hood. There isn't an option for TAC Support on this issue so I am really drawing a blank at the moment. Since there is no clear way forward and this is not a GNMIC problem, I am happy to close this off. This issue can always be re-opened if someone with an understanding of the GNMI implementation in Juniper offers some further insight.