Created
November 3, 2019 07:05
-
-
Save afaqk9394/f593d6c6d09dbd07d6d43d7a02c53269 to your computer and use it in GitHub Desktop.
display router NETCONF capabilities
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from ncclient import manager | |
| HOST = 'ios-xe-mgmt-latest.cisco.com' | |
| PORT = '10000' | |
| USER = 'developer' | |
| PASS = 'C1sco12345' | |
| with manager.connect(host=HOST, port=PORT, username=USER, | |
| password=PASS, hostkey_verify=False, | |
| device_params={'name': 'default'}, | |
| look_for_keys=False, allow_agent=False) as m: | |
| # print all NETCONF capabilities | |
| print('***Full List of Capabilities***') | |
| for capability in m.server_capabilities: | |
| print(capability.split('?')[0]) | |
| Code Output (truncated) | |
| Now, we try to download the running configuration using NETCONF API call. While the output is natively in XML form, but we convert it to JSON and display it too. | |
| Python Code (XML) | |
| import xmltodict | |
| from ncclient import manager | |
| import json | |
| import xml.dom.minidom | |
| HOST = 'ios-xe-mgmt-latest.cisco.com' | |
| PORT = '10000' | |
| USER = 'developer' | |
| PASS = 'C1sco12345' | |
| m = manager.connect(host=HOST, port=PORT, username=USER, | |
| password=PASS, hostkey_verify=False, | |
| device_params={'name': 'default'}, | |
| look_for_keys=False, allow_agent=False) | |
| running_config_xml = m.get_config('running') | |
| running_config_json = xmltodict.parse(str(running_config_xml)) | |
| #printing running-config in XML | |
| print(xml.dom.minidom.parseString(str(running_config_xml)).toprettyxml()) | |
| Code Output (truncated) | |
| Learn, Build, Fork, and Share with Our Instant IDE. | |
| Hit the Green Play Button to Execute. | |
| Python Code (JSON) | |
| import xmltodict | |
| from ncclient import manager | |
| import json | |
| HOST = 'ios-xe-mgmt-latest.cisco.com' | |
| PORT = '10000' | |
| USER = 'developer' | |
| PASS = 'C1sco12345' | |
| m = manager.connect(host=HOST, port=PORT, username=USER, | |
| password=PASS, hostkey_verify=False, | |
| device_params={'name': 'default'}, | |
| look_for_keys=False, allow_agent=False) | |
| running_config_xml = m.get_config('running') | |
| running_config_json = xmltodict.parse(str(running_config_xml)) | |
| #printing running-config in JSON | |
| print(json.dumps(running_config_json, indent=4, sort_keys=True)) | |
| Code Output (Truncated) | |
| Learn, Build, Fork, and Share with Our Instant IDE. | |
| Hit the Green Play Button to Execute. | |
| Before, we move on from NETCONF examples, let us go over the device handlers that ncclient supports. If you noticed, in all our code, we used ‘default’ device handler which is the same as “csr” for Cisco CSR1000v device. | |
| Juniper: device_params={‘name’:’junos’} | |
| Cisco CSR: device_params={‘name’:’csr’} | |
| Cisco Nexus: device_params={‘name’:’nexus’} | |
| Huawei: device_params={‘name’:’huawei’} | |
| Alcatel Lucent: device_params={‘name’:’alu’} | |
| H3C: device_params={‘name’:’h3c’} | |
| HP Comware: device_params={‘name’:’hpcomware’} | |
| Let me note down some code choices that we have made when using ncclient and why. | |
| Manager object is used to lookup operations, e.g. get_config etc. | |
| Mostly, we’re using the following template to connect into the device via NETCONF | |
| m = manager.connect( | |
| host=env_lab.IOS_XE_1["host"], | |
| port=env_lab.IOS_XE_1["netconf_port"], | |
| username=env_lab.IOS_XE_1["username"], | |
| password=env_lab.IOS_XE_1["password"], | |
| hostkey_verify=False | |
| ) | |
| close_session() method is used to close the NETCONF session, i.e. m.close_session(). However, when you use “with manage.connect <> as blah:”, it automatically closes the connection once the transaction is done. | |
| xmltodict comes handy to parse XML to dictionary and thus making it compatible with JSON encoding for further processing. Remember, JSON is a serialization construct thus making it easier to sort and transmit between systems, whereas dictionary is a data structure to use inside your code. However, they both represent objects as name/value pairs. | |
| We’ve already discussed xml.dom.minidom library, which is one of the two popular methods to process XML format. | |
| XML to dictionary can be done with a single line of code. “xml_data” below represents raw xml data. | |
| Netconf_info = xmltodict.parse(str(xml_data))[“rpc-reply”][“data”] | |
| You can also add or delete IOS XE configuration using Manager object, however in both cases, you need to first construct an XML configuration template. | |
| YANG and RESTCONF Example | |
| RESTCONF uses REST APIs so it uses HTTPS port 443 for transport. In order to configure RESTCONF, besides priv 15 user, you also need to enable HTTPS server and restconf. | |
| Enabling HTTPS server and RESTCONF | |
| In order to connect and execute our RESTCONF API calls, we can various python libraries. | |
| netmiko | |
| requests (REST get/post call etc.) | |
| json (encoding support) | |
| We will simply use requests and json libraries in our example below. | |
| Python Code | |
| import json | |
| import requests | |
| import sys | |
| from argparse import ArgumentParser | |
| from collections import OrderedDict | |
| import urllib3 | |
| # Disable SSL Warnings, optional. | |
| urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) | |
| # These variables target the RESTCONF Always-On Sandbox hosted by Cisco DevNet | |
| HOST = 'ios-xe-mgmt-latest.cisco.com' | |
| PORT = '9443' | |
| USER = 'developer' | |
| PASS = 'C1sco12345' | |
| # Identifies the interface on the device used for management access | |
| # Used to ensure the script isn't used to update the IP leveraged to manage device | |
| MANAGEMENT_INTERFACE = "GigabitEthernet1" | |
| # Create the base URL for RESTCONF calls | |
| url_base = "https://{h}:{p}/restconf".format(h=HOST, p=PORT) | |
| # Identify yang+json as the data formats | |
| headers = {'Content-Type': 'application/yang-data+json', | |
| 'Accept': 'application/yang-data+json'} | |
| #Define our 5 functions | |
| # Function to retrieve the list of interfaces on a device | |
| def get_configured_interfaces(): | |
| url = url_base + "/data/ietf-interfaces:interfaces" | |
| # this statement performs a GET on the specified url | |
| response = requests.get(url, | |
| auth=(USER, PASS), | |
| headers=headers, | |
| verify=False | |
| ) | |
| # return the json as text | |
| return response.json()["ietf-interfaces:interfaces"]["interface"] | |
| # Used to configure the IP address on an interface | |
| def configure_ip_address(interface, ip): | |
| # RESTCONF URL for specific interface | |
| url = url_base + "/data/ietf-interfaces:interfaces/interface={i}".format(i=interface) | |
| # Create the data payload to reconfigure IP address | |
| # Need to use OrderedDicts to maintain the order of elements | |
| data = OrderedDict([('ietf-interfaces:interface', | |
| OrderedDict([ | |
| ('name', interface), | |
| ('type', 'iana-if-type:ethernetCsmacd'), | |
| ('ietf-ip:ipv4', | |
| OrderedDict([ | |
| ('address', [OrderedDict([ | |
| ('ip', ip["address"]), | |
| ('netmask', ip["mask"]) | |
| ])] | |
| ) | |
| ]) | |
| ), | |
| ]) | |
| )]) | |
| # Use PUT request to update data | |
| response = requests.put(url, | |
| auth=(USER, PASS), | |
| headers=headers, | |
| verify=False, | |
| json=data | |
| ) | |
| print(response.text) | |
| # Retrieve and print the current configuration of an interface | |
| def print_interface_details(interface): | |
| url = url_base + "/data/ietf-interfaces:interfaces/interface={i}".format(i=interface) | |
| # this statement performs a GET on the specified url | |
| response = requests.get(url, | |
| auth=(USER, PASS), | |
| headers=headers, | |
| verify=False | |
| ) | |
| intf = response.json()["ietf-interfaces:interface"] | |
| # return the json as text | |
| print("Name: ", intf["name"]) | |
| try: | |
| print("IP Address: ", intf["ietf-ip:ipv4"]["address"][0]["ip"], "/", | |
| intf["ietf-ip:ipv4"]["address"][0]["netmask"]) | |
| except KeyError: | |
| print("IP Address: UNCONFIGURED") | |
| print() | |
| return(intf) | |
| # Ask the user to select an interface to configure. Ensures input is valid | |
| def interface_selection(interfaces): | |
| # Ask User which interface to configure | |
| sel = input("Which Interface do you want to configure? ") | |
| # Validate interface input | |
| # Must be an interface on the device AND NOT be the Management Interface | |
| while sel == MANAGEMENT_INTERFACE or not sel in [intf["name"] for intf in interfaces]: | |
| print("INVALID: Select an available interface.") | |
| print(" " + MANAGEMENT_INTERFACE + " is used for management.") | |
| print(" Choose another Interface") | |
| sel = input("Which Interface do you want to configure? ") | |
| return(sel) | |
| # Asks the user to provide an IP address and Mask. Data is NOT validated. | |
| def get_ip_info(): | |
| # Ask User for IP and Mask | |
| ip = {} | |
| ip["address"] = input("What IP address do you want to set? ") | |
| ip["mask"] = input("What Subnet Mask do you want to set? ") | |
| return(ip) | |
| #try out functions with user input | |
| # Get a List of Interfaces | |
| interfaces = get_configured_interfaces() | |
| print("The router has the following interfaces: \n") | |
| for interface in interfaces: | |
| print(" * {name:25}".format(name=interface["name"])) | |
| print("") | |
| # Ask User which interface to configure | |
| selected_interface = interface_selection(interfaces) | |
| print(selected_interface) | |
| # Print Starting Interface Details | |
| print("Starting Interface Configuration") | |
| print_interface_details(selected_interface) | |
| # As User for IP Address to set | |
| ip = get_ip_info() | |
| # Configure interface | |
| configure_ip_address(selected_interface, ip) | |
| # Print Ending Interface Details | |
| print("Ending Interface Configuration") | |
| print_interface_details(selected_interface) | |
| # Get a List of Interfaces | |
| interfaces = get_configured_interfaces() | |
| print("The router has the following interfaces: \n") | |
| for interface in interfaces: | |
| print(" * {name:25}".format(name=interface["name"])) | |
| print("") | |
| # Ask User which interface to configure | |
| selected_interface = interface_selection(interfaces) | |
| print(selected_interface) | |
| # Print Starting Interface Details | |
| print("Starting Interface Configuration") | |
| print_interface_details(selected_interface) | |
| # As User for IP Address to set | |
| ip = get_ip_info() | |
| # Configure interface | |
| configure_ip_address(selected_interface, ip) | |
| # Print Ending Interface Details | |
| print("Ending Interface Configuration") | |
| print_interface_details(selected_interface) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment