Getting Started with Cisco YANG Suite

Back when I started diving into studying for the Cisco DevNet certifications, I thought a lot of the REST API stuff was fairly easy. But I always struggled a bit with wrapping my head around YANG models & how exactly NETCONF/RESTCONF worked. A lot of it seemed more abstract, and browsing through YANG models isn't quite as intuitive as most REST API documentation.

These days I end up working quite a bit with NETCONF - and if there's one tool that's helped me more than anything, it's certainly YANG suite. So in this post, I wanted to spend a little bit of time walking through how to get set up with this tool - and walk through a quick example of how it can be used.


What's YANG Suite?

To put it simply: YANG Suite is a web-based tool that allows you to easily browse through YANG models & find what you need. It also has a bunch of tools to run test queries against devices, which makes developing NETCONF automation easier. It can also auto-generate code snippets - and who doesn't like that? 😄

YANG suite is an open source tool maintained by Cisco & the GitHub repo here.

Initial Setup

YANG suite is a containerized application, so we'll need a container host to run it on. You could use something like Docker Desktop to run it on your local PC, but I'll be using a dedicated Ubuntu machine in my lab with Docker installed.

Note: YANG suite now supports a local Python pip-based install. Check out the GitHub repo for more info on that.

Deploy with Docker

First thing we'll do is pull down the code from GitHub:

matt@yangsuite:~/yangsuite/docker$ git clone https://github.com/CiscoDevNet/yangsuite.git

Next, we can execute the setup script to help provision the YANG Suite containers, which is located at yangsuite/docker/start_yang_suite.sh. The script will prompt you to set up an admin user and generate self-signed certificates:

matt@yangsuite:~/yangsuite/docker$ cd yangsuite/docker
matt@yangsuite:~/yangsuite/docker$ ./start_yang_suite.sh
Hello, please setup YANG Suite admin user.
username: matt
password:
confirm password:
email: [email protected]

Setup test certificates? (y/n): y
  -- output omitted -- 
  
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]: US
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:
Certificates generated...
Building docker containers...

Once all of the above info is provided, the script will build the YANG Suite contianer images & launch them. However, by default it will launch in the foreground - so we can kill the script with Ctrl-C & relaunch the containers with docker-compose up -d.

Allow Access from Remote IPs

We may need to make a quick change depending on how we'll be accessing YANG Suite.

By default, YANG Suite will only permit access to the web UI from localhost - so if you're running Docker on your local PC, then no change is necessary.

However, if you're running Docker on a dedicated VM like I am, then we'll need to allow external IPs to access the YANG Suite UI. This is controlled by the DJANGO_ALLOWED_HOSTS environment variable that gets supplied to the container.

As long as we've already run the setup script once - our environment variables will be saved to yangsuite/docker/yangsuite/setup.env:

DJANGO_SETTINGS_MODULE=yangsuite.settings.production
MEDIA_ROOT=/ys-data/
STATIC_ROOT=/ys-static/
DJANGO_STATIC_ROOT=/ys-static/
DJANGO_ALLOWED_HOSTS=localhost
YS_ADMIN_USER=<removed>
YS_ADMIN_PASS=<removed>
YS_ADMIN_EMAIL=<removed>

We'll need to change DJANGO_ALLOWED_HOSTS=localhost to the IP address of the Docker host. So if your Docker host is reachable at 192.168.1.1, then we'll set: DJANGO_ALLOWED_HOSTS=192.168.1.1.

After which, we can bring up the containers again with docker-compose up -d:

matt@yangsuite:~/yangsuite/docker$ docker-compose up -d
Recreating docker-yangsuite-1 ... done
Recreating docker-backup-1    ... done
Recreating docker-nginx-1     ... done

Once everything is running, the web UI can be reached at https://<ip address>:8443.

Loading YANG Models

After we get logged in, we'll need to load the appropriate YANG models for whatever devices we're using.

For example, I'll be using Cisco IOS-XE devices running version 17.6.x code - so I'll need to download the YANG models from: https://github.com/YangModels/yang/tree/main/vendor/cisco/xe/1761.

Within YANG Suite, we'll navigate to Setup > YANG files and repositories. Then click the button to add a New repository:

add-yang-repo-name-1

You can name the repo whatever you like. In my case, I named it ios-xe-1761 which describes the models it will hold.

Next we can tell YANG Suite where we want to pull our models from. There are a couple of options, including a direct upload from your local PC or querying models directly from a device - but I'll choose Git.

After which, we'll need to fill in the following info:

add-yang-repo-from-git

The Repository URL will be the base Git repo that we're pulling from. In this case, that's https://github.com/YangModels/yang.git.

We'll also need to specify which Git branch to pull from, which for the YangModels repo will be main.

Then we'll specify which directory to import. The YangModels repository holds models for a large number of vendors & devices - so we only want to pull in what is necessary. In my case, I've set this to vendor/cisco/xe/1761 - which will only pull in the YANG models for Cisco IOS-XE 17.6.x devices. I've also checked the box for Include subdirectories.

Once that's all set, we can hit Import YANG files. Depending on how many models are being downloaded & how quick your Docker host is, this may take a few minutes.

After it finishes, mine displayed a message showing that 827 models had been added:

add-yang-repo-complete

Selecting YANG Module Sets

Once we have all of our models downloaded into YANG Suite, it's time to set up some module sets. You can think of these as filters for the YANG models.

For example, later on we'll show how to change a wireless PSK with NETCONF. So since I already know what type of information I'll be working with, I can create a module set to only contain those models.

YANG Suite will require at least one module set to be configured, even if you don't want to filter anything out.

To do this, we'll navigate to Setup > YANG model sets. Then click New YANG set:

add-yang-module-set

Again, since we'll be working with wireless data in a bit, I'll name my model set wireless. We'll also have to select which YANG repository this will pull from, which will be ios-xe-1761.

Next, we'll get to select which YANG models we want to include in our module set. To make things easy, I'll filter the models by wireless and then click Add selected.

add-wireless-models

Once those are added, you'll likely see an error about missing dependencies. Some of the models we selected may have sub-models or data types that are located in other models.

In order to quickly add these, YANG Suite provides a one-click-button to Locate and add missing dependencies:

add-dependencies

That's it - our model set is ready to use.

Adding Devices

Okay, so now that we have all of our necessary YANG models set up, let's take a add a device that we can use for testing. I'll be adding a Catalyst 9800 Wireless controller.

To do this, we'll head over to Setup > Device Profiles. Then click on Create new device.

add-device

Here we'll fill in a name for our device profile, along with the device's IP/FQDN and login credentials.

Scrolling down a bit, we'll also need to make sure we enable the appropriate management protocols. So I'll be checking the box for both NETCONF and RESTCONF, since I have both enabled. I'll also add SSH, which isn't shown in the screenshot below.

add-device-enable-netconf

Please note, you'll likely want to check the box for Skip SSH key validation for this device under the NETCONF settings.

After that's all done, we can click Create Profile at the bottom.

Then, to validate everything works - we'll select our device & click the button for Check selected device's reachability.

With any luck, you should see a similar result as shown below:

check-device-reachability

Example: Changing Wireless PSK

Alright, now let's take a look at how we might use YANG Suite to test NETCONF / RESTCONF calls to a wireless controller.

In this example, perhaps we're developing automation to rotate the guest wireless network password on a regular basis. We need to figure out how specifically to accomplish that by finding the right YANG models & understanding what data is sent and received with those calls.

We'll explore how to do this with both NETCONF and RESTCONF.

Via NETCONF

To start off, we'll navigate to Protocols > NETCONF. On this page, we'll have full access to explore models, build XML payloads, and test them against our configured device.

Get-config

First thing we'll need is to select our YANG set & Modules. In my case, the YANG set will be wireless, and I'll select the module: Cisco-IOS-XE-wireless-wlan-cfg.

Then click the button to Load Modules:

netconf-select-models

By default, our NETCONF Operation will be set to get-config, which will work for now since we only want to retrieve configuration and not change anything yet.

Under the Nodes pane, we can expand the tree a bit & dig in to find which call we need.

For our example, we'll need to examine the current configured WLAN profiles. So we'll check the box next to wlan-cfg-entries:

netconf-nodes

This will retrieve all configured WLAN profiles on the controller. For now, let's do that so we can figure out what data we'll get back.

We can now click the button to Build RPC, and YANG Suite will automatically build the appropriate XML payload for us:

netconf-build-rpc

You'll also notice that I selected the catalyst 9800 as the target device. Let's go ahead and run this with the Run RPC(s) button, which should pop open a new window & establish a connection to our device.

netconf-rpc-response-1

We can see in the response above, that we have two WLAN profiles configured: test-lab-network and test-guest-network. For the purposes of this demo, I have opted to have the guest network PSK stored in plain text, so we can validate our changes more easily.

Okay, so what if we wanted to refine our query XML to only retrieve data on the test-guest-network? Maybe we also want to filter our the additional information returned, and only see the current PSK?

Back on the payload editor, we can expand wlan-cfg-entries and input our desired profile-name:

netconf-build-rpc-filtered-1

We'll also find the psk item further down in the list. Now we want to pull back this field, regardless of what the PSK is currently set to. So we'll click the value field, but leave it blank:

netconf-build-rpc-filtered-psk

Once we click Build RPC, we can see our payload now shows the additional XML filters asking only for a single profile & it's associated PSK:

netconf-rpc-payload-filtered

If we run that RPC call, we'll now see that we only get back a limited set of data:

netconf-filtered-rpc-response

Now we know exactly what payload to send if we want to get back the current configured PSK for any specific WLAN profile.

Let's try changing it!

Edit-config

Lucky for us, we're already nearly set up for that. We'll need to make two quick changes. First, set the NETCONF Operation to edit-config:

netconf-op-edit-config

Then, under the YANG tree, we'll set a new PSK:

netconf-new-psk

Again, we'll click Build RPC - and take a quick look at the proposed change:

netconf-new-psk-payload

Look good to you? Okay, let's send it:

netconf-new-psk-response

Sure enough, the new configuration was sent to the wireless controller and we got a response of: ok.

But of course, we can check quickly by switching our operation back to get-config and re-running our previous XML payload:

netconf-new-psk-validation

Auto-generated snippets

Well if you're new to NETCONF, you might be asking yourself "Okay great, but how do I get this into Python?!?!"

YANG Suite has some built in tools to auto-generate Python code or Ansible playbooks, which can help us get started on our code more quickly.

To generate these, click the Replays button:

netconf-python-ansible

For example, I selected to auto-generate a Python script. Here's what that looks like:

#! /usr/bin/env python
import traceback
import lxml.etree as et
from argparse import ArgumentParser
from ncclient import manager
from ncclient.operations import RPCError

payload = [
'''
<get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
    <source>
      <running/>
    </source>
    <filter>
      <wlan-cfg-data xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-wireless-wlan-cfg">
        <wlan-cfg-entries>
          <wlan-cfg-entry>
            <profile-name>test-guest-network</profile-name>
            <psk/>
          </wlan-cfg-entry>
        </wlan-cfg-entries>
      </wlan-cfg-data>
    </filter>
  </get-config>

''',
]

if __name__ == '__main__':

    parser = ArgumentParser(description='Usage:')

    # script arguments
    parser.add_argument('-a', '--host', type=str, required=True,
                        help="Device IP address or Hostname")
    parser.add_argument('-u', '--username', type=str, required=True,
                        help="Device Username (netconf agent username)")
    parser.add_argument('-p', '--password', type=str, required=True,
                        help="Device Password (netconf agent password)")
    parser.add_argument('--port', type=int, default=830,
                        help="Netconf agent port")
    args = parser.parse_args()

    # connect to netconf agent
    with manager.connect(host=args.host,
                         port=args.port,
                         username=args.username,
                         password=args.password,
                         timeout=90,
                         hostkey_verify=False,
                         device_params={'name': 'csr'}) as m:

        # execute netconf operation
        for rpc in payload:
            try:
                response = m.dispatch(et.fromstring(rpc))
                data = response.xml
            except RPCError as e:
                data = e.xml
                pass
            except Exception as e:
                traceback.print_exc()
                exit(1)

            # beautify output
            if et.iselement(data):
                data = et.tostring(data, pretty_print=True).decode()

            try:
                out = et.tostring(
                    et.fromstring(data.encode('utf-8')),
                    pretty_print=True
                ).decode()
            except Exception as e:
                traceback.print_exc()
                exit(1)

            print(out)

Via RESTCONF

What if we prefer working with REST & JSON instead of XML? This time we'll navigate to Protocols > RESTCONF.

Here, we'll select our YANG set & modules again. In case you skipped the NETCONF section, we're working with the wireless YANG set & the Cisco-IOS-XE-wireless-wlan-cfg module.

We'll also select a device to use, then click Load Module(s):

restconf-nodes

Again, we'll see a similar structure to the NETCONF side. But this time, we'll utilize it quite a bit differently.

In the screenshot above, I've already selected the wlan-cfg-entries entry. Next, we'll click the button to Generate API(s):

restconf-wlan-all

We'll see auto-generated API documentation for all of the available calls. This list will contain every variation possible, so it can be quite long. Since we already went through the NETCONF method, we know what data we need to find / use which should make this easier.

To start with, I'll expand the GET for /data/Cisco-IOS-XE-wireless-wlan-cfg:wlan-cfg-data/wlan-cfg-entries. We'll have the ability to test the RESTCONF calls directly from this interface as well by clicking on Try it out:

restconf-example-query-wlan

In the screenshot above, I've already submitted the test request & gotten a response from the controller. While the screenshot is cut off a bit, we can see both of our wireless profile names.

If we wanted to see only the PSK, like we did with our previous example - we could scroll down quite a bit to find the GET request for /data/Cisco-IOS-XE-wireless-wlan-cfg:wlan-cfg-data/wlan-cfg-entries/wlan-cfg-entry={wlan-cfg-entry-profile-name}/psk

In the screenshot below, I've already input our test-guest-network profile name & executed the API call:

restconf-example-query-psk

Sure enough, we see the PSK that we had just configured via NETCONF a few minutes ago.

Now let's change it. We'll expand the PATCH API call for the same endpoint, and fill in the request body with our new PSK:

restconf-patch-psk

Executing that call, the wireless controller returned a status code of 204.

So let's check again to see if that updated properly:

restconf-updated-psk

Now that we have all the appropriate REST calls, it should be much easier to start developing our automation.


Okay, I think that's about all I wanted to cover in this post. I've seen a lot of people struggle with NETCONF much like I used to, and I think the real key to getting familiar is just having the right tools & spending enough time with it. YANG Suite has really helped me get more familiar with the various model structures and how to use them.

Hopefully this information helps others to get started. NETCONF & YANG are certainly big hills to climb, but the right tools can make all the difference.