October 29, 2019

Here at Network to Code we work with network devices’ APIs every day. APIs are critical to enable our customers working with Network Automation. APIs are also usually the first (if applicable) choice in our solutions - even if that’s the hard way.

As we push network devices’ APIs to their limits, we go beyond the examples available on GitHub or in vendor’s documentation. One of our past use cases included RESTCONF (*) protocol usage on Cisco NX-OS 9000 series. While this was an opportunity to work with YANG, Openconfig, Postman, Python, etc. this was also a chance to understand differences between implementation of the protocol between Cisco’s platforms. For tests we used NX-OS v9.3-1 and IOS-XE 16.9.3.

Note: Jason Edelman described NETCONF and RESTCONF’s principles in 2016 in a blog post

Examining IOS-XE RESTCONF and NX-OS RESTCONF

NX-OS implementation is based on draft-ietf-netconf-restconf-10. This draft was published in March 2016, however RFC 8040 was published in January, 2017. One of the changes between those versions was a change in headers needed in the HTTP request.

For IOS-XE, the headers are declared using a dash notation in yang-data:

restconf_headers = {
    'Accept': 'application/yang-data+json',
    'Content-Type': 'application/yang-data+json'
}   

However, for NX-OS based on a draft, Cisco implemented a dot notation in yang.data:

restconf_headers = {
    'Accept': 'application/yang.data+json',
    'Content-Type': 'application/yang.data+json'
}

YANG models are different

Supported YANG models are also different between IOS-XE and NX-OS devices. It means, native models are platform specific, not necessarily vendor specific. First step when examing supported models is to retrieve advertised capabilities from the device (you might use Hank Preston’s get_capabilities.py code to do that).

Once we confirm a particular capability is supported, let’s see how to programmatically get BGP peers from NX-OS:

#!/usr/bin/env python
import requests

username = 'admin'
password = ''
device = 'nxos1'

restconf_headers = {
    'Accept': 'application/yang.data+json',
    'Content-Type': 'application/yang.data+json'
}

bgp_url = 'https://{device}/restconf/data/Cisco-NX-OS-device:System/bgp-items/inst-items/dom-items/Dom-list=default/peer-items/Peer-list'

get_response = requests.get(bgp_url.format(device=device),
                            auth=(username, password),
                            headers=restconf_headers,
                            verify=False,
                            )

Same operation for IOS-XE based platform would require a change in bgp_url variable:

#!/usr/bin/env python
import requests

username = 'admin'
password = ''
device = 'csr1'

restconf_headers = {
    'Accept': 'application/yang-data+json',
    'Content-Type': 'application/yang-data+json'
}

bgp_url = 'https://{device}/restconf/data/Cisco-IOS-XE-bgp-oper:bgp-state-data'

get_response = requests.get(bgp_url.format(device=device),
                            auth=(username, password),
                            headers=restconf_headers,
                            verify=False,
                            )

You might notice the difference in the requested URI (endpoints), as you would also notice the difference in the RESTCONF response - both responses will have different data structures, meaning, the data can not be accessed in the same way.

That being said, to add a new BGP peer under NX-OS we could use a native module and create a data structure as follows:

#!/usr/bin/env python

import json

import requests

username = 'admin'
password = ''
device = 'nxos1'

restconf_headers = {
    'Accept': 'application/yang.data+json',
    'Content-Type': 'application/yang.data+json'
}


def bgp_add(peer_address, peer_asn):
    bgp_payload = {
        "bgp-items": {
            "inst-items": {
                "dom-items": {
                    "Dom-list": [
                        {
                            "name": "default",
                            "peer-items": {
                                "Peer-list": [
                                    {
                                        "addr": peer_address,
                                        "asn": peer_asn
                                    }
                                ]
                            }
                        }
                    ]
                }
            }
        }
    }

    bgp_url = 'https://{device}/restconf/data/Cisco-NX-OS-device:System'

    requests.patch(bgp_url.format(device=device),
                   auth=(username, password),
                   headers=restconf_headers,
                   verify=False,
                   data=json.dumps(bgp_payload)
                   )

Same operation on IOS-XE would be similar, however the data payload and the URI itself looks much simpler while using XE’s native YANG model:

#!/usr/bin/env python

import json

import requests

username = 'admin'
password = ''
device = 'csr1'

restconf_headers = {
    'Accept': 'application/yang-data+json',
    'Content-Type': 'application/yang-data+json'
}


def bgp_add(peer_address, peer_asn):
    bgp_payload = {"Cisco-IOS-XE-bgp:neighbor": {'id': peer_address,
                                                 'remote-as': peer_asn}}

    # 65000 in the URL represents the ASN number
    bgp_url = 'https://{device}/restconf/data/Cisco-IOS-XE-native:native/Cisco-IOS-XE-native:router=bgp/65000/neighbor'

    requests.patch(bgp_url.format(device=device),
                   auth=(username, password),
                   headers=restconf_headers,
                   verify=False,
                   data=json.dumps(bgp_payload)
                   )

The reason of differences is the following:

In NX-OS, it is the “device” module that describes the BGP. However, in the IOS-XE it is the native BGP module, which is different than NX-OS’.

Note: The list of supported YANG modules is available on GitHub: https://github.com/YangModels/yang/tree/master/vendor/cisco

Openconfig Adoption is Progressing

The Openconfig working group produces set of YANG modules that are vendor neutral and agnostic - the overall idea of Openconfig project is, you could use the same YANG model to communicate with different vendors (and platforms), keeping the same data structures. Openconfig might sound like an alternative solution to the differences between Cisco’s native modules on NX-OS and IOS-XE.

To understand similarities and differences while using Openconfig on different platforms, let’s examine existing loopback200 interface on nxos1 device (NX-OS):

nxos1# show run int lo200

!Command: show running-config interface loopback200
!Running configuration last done at: Thu Oct 24 12:06:42 2019
!Time: Thu Oct 24 12:07:04 2019

version 9.3(1) Bios:version  

interface loopback200
  description NTC
  ip address 192.0.2.2/32

Getting url https://nxos1/restconf/data/openconfig-interfaces:interfaces/interface=lo200 would result in the following response:

{
   "interface": [
      {
         "name": "lo200",
         "config": {
            "description": "NTC",
            "enabled": "true",
            "name": "lo200",
            "type": "softwareLoopback"
         },
         "state": {
            "admin-status": "UP",
            "ifindex": "335544520",
            "oper-status": "UP",
            "description": "NTC",
            "enabled": "true",
            "mtu": "1500",
            "type": "softwareLoopback"
         }
      }
   ]
}

nxos1 response contained information about the configuration and state, structured accordingly to the openconfig-interfaces model.

Similar configuration is present on csr1 device (IOS-XE):

csr1#show run int lo200
Building configuration...

Current configuration : 89 bytes
!
interface Loopback200
 description NTC - XE
 ip address 192.0.2.2 255.255.255.255
end

Getting url https://csr1/restconf/data/openconfig-interfaces:interfaces/interface=Loopback200 would result in the following response:

{
  "openconfig-interfaces:interface": {
    "name": "Loopback200",
    "config": {
      "type": "iana-if-type:softwareLoopback",
      "name": "Loopback200",
      "description": "NTC - XE",
      "enabled": true
    },
    "state": {
      "type": "iana-if-type:softwareLoopback",
      "name": "Loopback200",
      "description": "NTC - XE",
      "enabled": true,
      "ifindex": 29,
      "admin-status": "UP",
      "oper-status": "UP",
      "last-change": "2019-10-23T01:12:11.000147+00:00",
      "counters": {
        "in-octets": "0",
        "in-unicast-pkts": "0",
        "in-broadcast-pkts": "0",
        "in-multicast-pkts": "0",
        "in-discards": "0",
        "in-errors": "0",
        "in-unknown-protos": 0,
        "out-octets": "0",
        "out-unicast-pkts": "0",
        "out-broadcast-pkts": "0",
        "out-multicast-pkts": "0",
        "out-discards": "0",
        "out-errors": "0",
        "last-clear": "2019-10-22T23:13:05.000807+00:00"
      }
    },
    "subinterfaces": {
      "subinterface": [
        {
          "index": 0,
          "config": {
            "index": 0,
            "name": "Loopback200",
            "description": "NTC - XE",
            "enabled": true
          },
          "state": {
            "index": 0,
            "name": "Loopback200.0",
            "description": "NTC - XE",
            "enabled": true,
            "admin-status": "UP",
            "oper-status": "UP",
            "last-change": "2019-10-23T01:12:11.000147+00:00",
            "counters": {
              "in-octets": "0",
              "in-unicast-pkts": "0",
              "in-broadcast-pkts": "0",
              "in-multicast-pkts": "0",
              "in-discards": "0",
              "in-errors": "0",
              "out-octets": "0",
              "out-unicast-pkts": "0",
              "out-broadcast-pkts": "0",
              "out-multicast-pkts": "0",
              "out-discards": "0",
              "out-errors": "0",
              "last-clear": "2019-10-22T23:13:05.000807+00:00"
            }
          },
          "openconfig-if-ip:ipv4": {
            "addresses": {
              "address": [
                {
                  "ip": "192.0.2.2",
                  "config": {
                    "ip": "192.0.2.2",
                    "prefix-length": 32
                  },
                  "state": {
                    "ip": "192.0.2.2",
                    "prefix-length": 32
                  }
                }
              ]
            }
          },
          "openconfig-if-ip:ipv6": {
            "config": {
              "enabled": false
            },
            "state": {
              "enabled": false
            }
          }
        }
      ]
    }
  }
}

You might notice, that some parts of the responses have the same data structures with IOS-XE being more verbose in its response (state, counters and subinterfaces with IP addressing). It is due to the deviations from the openconfig-interfaces model - as sometimes not all features could be supported, YANG models can declare particular capability as “not supported”.

As version 9 of NX-OS supports tens of Openconfig YANG modules, there is still a difference in NX-OS and IOS-XE support for Openconfig. IOS-XE offers a broader support for Openconfig. The difference in supported modules should be considered while planning Openconfig usage in your Network Automation journey. Please also note, that Openconfig support is characterized by a list of guidelines, limitations and deviations from a published models.

PATCH vs. PUT

Some of the implementation details are worth checking during your code development process. Typically, in REST APIs a PUT method represents a “create or replace” operation, while PATCH represents a “merge” operation. On NX-OS we noticed a difference in behaviour of a PUT method, which was not behaving as per RFC 8040 (and draft 10).

According to the RFC 8040 PUT method is “create or replace”:

The RESTCONF server MUST support the PUT method. The PUT method is sent by the client to create or replace the target data resource. A request message-body MUST be present, representing the new data resource, or the server MUST return a “400 Bad Request” status-line. The error-tag value “invalid-value” is used in this case.

Using PUT method on NX-OS had the same result as using PATCH method during our deployment - it used to merge our BGP changes into existing BGP configuration. PUT on IOS-XE indeed was a “create or replace” operation as RFC defines it.

The moral of the story is that APIs are still new on platforms and clearly shows the importance of user testing of network automation elements like APIs.

Conclusion

Presented examples are just some of the differences we identified during our work. As we are glad to see model driven programmability support in both device types, there are lots of factors to consider before using a particular protocol in your Network Automation system.

-Marek

Does this all sound amazing? Want to know more about how Network to Code can help you do this, reach out to our sales team. If you want to help make this a reality for our clients, check out our careers page.