Table of Contents
yaml payload in restconf protocol:
In the previous sections we have configured network devices using json payload sent through requests python library in restconf protocol.
But probably the easiest way to keep the configuration data is in yaml format. Then the Python “load_yaml” from “nornir utils” library has the option to automatically change yaml to json before sending it to the network devices.
This is what we are going to demonstrate in this section.
Edit configuration with YAML format in restconf protocol
In fact, it is not easy to keep the configuration data in XML or JSON format, and the easiest way to store the configuration data is in the YAML format.
But what is actually the acceptable format of the configuration to be sent to the network devices is xml and json format.
We will use python “load_yaml” from “nornir utils” library which has the option to change the YAML configuration data to JSON format which can be sent to network devices directly.
The question is how we prepare the configuration data in the format of YALM? And how we prepare final configuration data in the format of JSON?
What we receive as the output of the configuration from network devices are in the format of json or xml.
Then we can use the json to yaml online services like https://www.json2yaml.com/ to convert json configuration of network devices to the yaml format. Then it is much easier to edit, change and keep the configuration in the format of YAML.
Finally we use load_yaml from nornir_urils python library to convert the yaml-based configuration data again to the json format before sending to the network devices.
Prepare yaml-based configuration data
In the first step, let’s check how we convert and keep the configuration in the format of yaml.
As examples, we get sample configuration of ospf and bgp of a network device and then we convert it to yaml using json2yaml online service.
This is done using the script that we have already discussed in the previous sections.
First I select the url related to the ospf configuration and then we run the script to get ospf part of the configuration in json format.
majid@ubuntu2204tls:~/devnet/pyhton_nornir/2023/13.restconf$ python3 13.3.restconf_with_python_request.py { 'Cisco-IOS-XE-ospf:ospf': { 'process-id': [ {'id': 1, 'network': [{'ip': '10.0.0.0', 'wildcard': '0.255.255.255', 'area': 0}], 'router-id': '11.11.11.11'} ] } }
When we try to convert JSON OSPF configuration output to YAML we get the error.
By default the json output uses single quotes and we need to change all single quotes to double quotes before trying to convert it to yaml.
It can be done using the “sed” command to change all single quotes to double quotes. Then the output can be easily converted to yaml using online json2yaml web services.
majid@ubuntu2204tls:~/devnet/pyhton_nornir/2023/13.restconf$ python3 13.3.restconf_with_python_request.py | sed "s/'/\"/g" { "Cisco-IOS-XE-ospf:ospf": { "process-id": [ {"id": 1, "network": [{"ip": "10.0.0.0", "wildcard": "0.255.255.255", "area": 0}], "router-id": "11.11.11.11"} ] } }
https://www.json2yaml.com/
--- Cisco-IOS-XE-ospf:ospf: process-id: - id: 1 network: - ip: 10.0.0.0 wildcard: 0.255.255.255 area: 0 router-id: 11.11.11.11
This is a sample OSPF configuration in the format of YAML which can be edited and stored for further processing and uploading to network devices.
To see another sample, let’s receive and convert BGP configuration of network device.
We change the url to reach the bgp configuration of network device and then we run the script to receive the configuration in json format.
To convert the output to the yaml, here there are two issues that must be resolved. All single quotes must be changed to double quotes. And also we have to add double quotes to the values like “True” and “False”.
majid@ubuntu2204tls:~/devnet/pyhton_nornir/2023/13.restconf$ python3 13.3.restconf_with_python_request.py | sed "s/'/\"/g" { "Cisco-IOS-XE-bgp:bgp": [ { "id": 65500, "bgp": {"log-neighbor-changes": "True", "router-id": {"ip-id": "1.2.3.4"}}, "neighbor": [{"id": "5.6.7.8", "remote-as": 65501}] } ] }
https://www.json2yaml.com/
--- Cisco-IOS-XE-bgp:bgp: - id: 65500 bgp: log-neighbor-changes: 'True' router-id: ip-id: 1.2.3.4 neighbor: - id: 5.6.7.8 remote-as: 65501
change config through yaml-based configuration data
This is the script that I have already prepared to change the configuration of network devices via yaml based configuration payload instead of normal json configuration payload.
import requests from nornir import InitNornir from nornir_utils.plugins.functions import print_result from c.plugins.tasks.data import load_yaml from rich import print as rprint nr = InitNornir(config_file="config.yaml") requests.packages.urllib3.disable_warnings() headers = { "Accept": "application/yang-data+json", "Content-Type": "application/yang-data+json", } def load_data(task): data = task.run(task=load_yaml, file=f"host_vars/{task.host}.yaml") task.host["hdata"] = data.result def restconf_edit_config_with_yaml(task): USERNAME = f"{task.host.username}" PASSWORD = f"{task.host.password}" module1 = "Cisco-IOS-XE-native:native/router/router-ospf" url1 = f"https://{task.host.hostname}:443/restconf/data/{module1}" response = requests.put(url=url1, headers=headers, auth=(USERNAME, PASSWORD), verify=False, json=task.host["hdata"]["ospf_config"]) print(response) module2 = "Cisco-IOS-XE-native:native/ntp" url2 = f"https://{task.host.hostname}:443/restconf/data/{module2}" response = requests.put(url=url2, headers=headers, auth=(USERNAME, PASSWORD), verify=False, json=task.host["hdata"]["ntp_config"]) print(response) module3 = "openconfig-acl:acl" url3 = f"https://{task.host.hostname}:443/restconf/data/{module3}" response = requests.put(url=url3, headers=headers, auth=(USERNAME, PASSWORD), verify=False, json=task.host["hdata"]["acl_config"]) print(response) load_results = nr.run(task=load_data) configure_results = nr.run(task=restconf_edit_config_with_yaml)
You can see in the header of the script that in addition to the Python libraries “requests” and “nornir” that we used in the previous section, here we add the task “load_yaml” from the library “nornir_utils” which is responsible for loading the yaml based configuration data and preparing the JSON configuration payload, which can be uploaded directly to the network devices.
With the function “load_data”, we are loading the yaml based configuration data which is already stored in the folder “host_vars”. For each network device, we store the configuration data in a file with the name of device itself.
The loaded data is stored in a dictionary structure with the name of device and “hdata” key.
If we check inside yaml based configuration data of the router R1, we see the sample configuration of ospf, ntp and access-list inside the file.
majid@devnet:~/devnet/pyhton_nornir/14.restconf$ cat host_vars/R1.yaml --- ospf_config: Cisco-IOS-XE-ospf:router-ospf: ospf: process-id: - id: 1 network: - ip: 192.168.22.0 wildcard: 0.0.0.255 area: 0 - ip: 192.168.23.0 wildcard: 0.0.0.255 area: 1 - ip: 192.168.24.0 wildcard: 0.0.0.255 area: 1 - ip: 192.168.111.0 wildcard: 0.0.0.255 area: 0 router-id: 1.1.1.1 ntp_config: Cisco-IOS-XE-native:ntp: Cisco-IOS-XE-ntp:server: server-list: - ip-address: 13.15.17.18 - ip-address: 13.15.17.20 - ip-address: 13.15.17.21 acl_config: openconfig-acl:acl: acl-sets: acl-set: - name: '101' type: openconfig-acl:ACL_IPV4 config: name: '101' type: openconfig-acl:ACL_IPV4 acl-entries: acl-entry: - sequence-id: 10 config: sequence-id: 10 ipv4: config: protocol: openconfig-packet-match-types:IP_TCP transport: config: source-port: ANY destination-port: 80 actions: config: forwarding-action: openconfig-acl:ACCEPT log-action: openconfig-acl:LOG_NONE - sequence-id: 20 config: sequence-id: 20 ipv4: config: protocol: openconfig-packet-match-types:IP_UDP transport: config: source-port: ANY destination-port: 53 actions: config: forwarding-action: openconfig-acl:ACCEPT log-action: openconfig-acl:LOG_NONE - sequence-id: 30 config: sequence-id: 30 ipv4: config: protocol: openconfig-packet-match-types:IP_ICMP transport: config: source-port: ANY destination-port: ANY actions: config: forwarding-action: openconfig-acl:ACCEPT log-action: openconfig-acl:LOG_NONE - sequence-id: 40 config: sequence-id: 40 ipv4: config: source-address: 1.2.3.4/32 protocol: cisco-xe-openconfig-acl-ext:IP transport: config: source-port: ANY destination-port: ANY actions: config: forwarding-action: openconfig-acl:ACCEPT log-action: openconfig-acl:LOG_NONE
We just discussed how to extract yaml-based configuration data from any part of the network configuration. We have already showed the ospf and bgp configuration. The ntp and access list configuration is also prepared using the same method.
Here I have used a key for each part of the configuration to be used as the dictionary key to access the configuration data. The keys, “ospf_config”, “ntp_config” and “acl_config” are used in order for ospf, ntp and access-list configuration.
Looking again at the script, we see that these keys are used along with “hdata” to access each part of the configuration. For example, the key sequence [“hdata”][“ospf_config”] is used to access the network device’s ospf configuration inside dictionary structure.
The key sequences [“hdata”][“ntp_config”] and [“hdata”][“acl_config”] are used to access ntp and access-list configuration of network device in dictionary structure.
The output of the loaded configuration data along with the corresponding URL is used to upload the configuration through “requests.put” command.
Now let’s run the script and see the result of the new configuration in the network device.
majid@ubuntu2204tls:~/devnet/pyhton_nornir/2023/13.restconf$ python3 13.6.restconf_edit_config_with_yaml.py <Response [204]> <Response [204]> <Response [204]>
R1#show runn | sec router ospf|access-list|ntp router ospf 1 router-id 1.1.1.1 network 192.168.22.0 0.0.0.255 area 0 network 192.168.23.0 0.0.0.255 area 1 network 192.168.24.0 0.0.0.255 area 1 network 192.168.111.0 0.0.0.255 area 0 ip access-list extended 101 10 permit tcp any any eq www 20 permit udp any any eq domain 30 permit icmp any any 40 permit ip host 1.2.3.4 any ntp server 13.15.17.18 ntp server 13.15.17.20 ntp server 13.15.17.21
As we expected the configuration is uploaded to the network device correctly.