Programatically Enable NETCONF and MD-CLI on Nokia – SROS

Hi everyone,

First things first, the code lives here:

https://github.com/h4ndzdatm0ld/sros-enable-netconf

I wanted to put together a mini-series of posts on how to programatically enable netconf across many ALU/Nokia – SROS devices.

The theoretical problem we are trying to solve:

  • Company Avifi has recently decided to enable NETCONF across their entire 7750 platform. They would like to do this all in one maintenance night.
  • All of Avifi’s network is currently documented and stored in Netbox. We must extract a list of 7750’s and their IP addresses using the API requests.
  • Programatically SSH into all the necessary devices:
    • Enable NETCONF
    • Create a NETCONF USER/PASSWORD
    • Enable Model-Driven CLI.

As a network engineer that’s constantly having to re-use scripts, templates, etc – I’d see this as an opportunity to create two things:

  1. A tool I can easily use in my lab environment before I take this to production.
  2. A production ready tool that my team can use.

We’ll start with a command line driven tool to easily target a single node, ssh into it and programatically enable NETCONF as well as change from the standard CLI to the new Model-Driven CLI that Nokia offers on their 7750’s routers.

As I’m getting more into the Dev/NET/OPS side of the house, I’m starting to think about CI/CD, unit tests, version control and the extensive amount of testing and variables that may change when implementing network wide changes via automation.

Let’s discuss some of the packages i’ll be using with Python 3.

Everyone should be familiar with Netmiko by now. We’ll use this to connect via SSH to our devices and manipulate our configurations.  As the starting point to this will be to build from a command line driven utility which targets a single node and expand into extracting a list of devices via Netbox, we will use argparse to send arguements from the CLI to our python script. NCCLIENT will be used to establish NETCONF connections. In order to not store passwords on our script, we will use getpass to prompt our users for passwords. On our future updated post, we’ll call the pynetbox package / API client to interact with Netbox and extract the correct device IP addresses and run the script against it. xmltodict to convert the xml extracted file and parse to a dcitionary.

Screen Shot 2020-05-18 at 12.07.21 PM

The tool will accept the arguements above, but the SSH username is defaulted to ‘admin’.

Once ran, the script will request for the SSH Password to the device, it will connect and send a list of commands to enable the NETCONF service and also switch from the Classic CLI to the new Model Driven CLI. Once this is complete, the SSH connection will be dropped and a new connection on port 830, the default NETCONF port will be established utilizing the new credentials. The tool will proceed to extract the running configuration, it will save a temp file and re-open it to parse it into a dictionary. We’ll extract the system name and use it as a var to create a folder directory of configurations and save the XML configuration by system name.

Before running, open the script and edit the new usser credentials that you wish to pass for NETCONF connections. 

At this point, i’m able to run this against a multitude of devices individually to test functionallity and make any adjustments before I implement the API connection into our Netbox server.

Below is the entire code, at beta. This command line driven utility will utilize NETMIKO to establish the initial connection to the device.  On the next post, we will take this code and change quite a bit to dynamically pass in a list of hosts from the NETBOX API.

import netmiko, ncclient, argparse, getpass, sys, time, xmltodict, os
from netmiko import ConnectHandler
from ncclient import manager
from ncclient.xml_ import *
from xml.etree import ElementTree
def get_arguments():
parser = argparse.ArgumentParser(description='Command Line Driven Utility To Enable NETCONF\
On SROS Devices And MD-CLI.')
parser.add_argument("-n", "--node", help="Target NODE IP", required=True)
parser.add_argument("-u", "--user", help="SSH Username", required=False, default='admin')
parser.add_argument("-p", "--port", help="NETCONF TCP Port", required=False, default='830')
args = parser.parse_args()
return args
# Lets make it easier to send and receive the output to the screen.
# We'll create a function to pass in a list of commands as arguements.
def send_cmmdz(node_conn,list_of_cmds):
''' This function will unpack the dictionary created for the remote host to establish a connection with
and send a LIST of commands. The output will be printed to the screen.
Establish the 'node_conn' var first by unpacking the device connection dictionary. Pass it in as an args.
'''
try:
x = node_conn.send_config_set(list_of_cmds)
print(x)
exceptExceptionas e:
print(f"Issue with list of cmdz, {e}")
def send_single(node_conn, command):
''' This function will unpack the dictionary created for the remote host to establish a connection with
and send a single command. The output will be printed to the screen.
Establish the 'node_conn' var first by unpacking the device connection dictionary. Pass it in as an args.[]
'''
try:
x = node_conn.send_command(command)
print (x)
exceptExceptionas e:
sys.exit(e)
def disconnect(node_conn):
try:
node_conn.disconnect()
exceptExceptionas e:
print(e)
def netconfconn(args,ncusername,ncpassword):
conn = manager.connect(host=args.node,
port=args.port,
username=ncusername,
password=ncpassword,
hostkey_verify=False,
device_params={'name':'alu'})
return conn
def saveFile(filename, contents):
''' Save the contents to a file in the PWD.
'''
try:
f = open(filename, 'w+')
f.write(contents)
f.close()
exceptExceptionas e:
print(e)
def createFolder(directory):
try:
ifnot os.path.exists(directory):
os.makedirs(directory)
exceptOSError:
print('Error: Creating directory. '+ directory)
def main():
# Extract the Arguements from ARGSPARSE:
args = get_arguments()
# Define the NETCONF USERNAME / PASSWORD:
NETCONF_USER = 'netconf'
NETCONF_PASS = 'NCadmin123'
# # Create a dictionary for our device.
sros = {
'device_type': 'alcatel_sros',
'host': args.node,
'username': args.user,
'password': getpass.getpass(),
}
# Pass in the dict and create the connection.
sros_conn = net_connect = ConnectHandler(**sros)
# Establish a list of pre and post check commands.
print('Connecting to device and executing script...')
send_single(sros_conn, 'show system information | match Name')
send_single(sros_conn, 'show system netconf | match State')
enableNetconf = ['system security profile "netconf" netconf base-op-authorization lock',
'system security profile "netconf" netconf base-op-authorization kill-session',
f'system security user {NETCONF_USER} access netconf',
f'system security user {NETCONF_USER} password {NETCONF_PASS}',
f'system security user {NETCONF_USER} console member {NETCONF_USER}',
f'system security user {NETCONF_USER} console member "administrative"',
'system management-interface yang-modules nokia-modules',
'system management-interface yang-modules no base-r13-modules',
'system netconf auto-config-save',
'system netconf no shutdown',
'system management-interface cli md-cli auto-config-save',
'system management-interface configuration-mode model-driven']
# Execute Script.
send_cmmdz(sros_conn, enableNetconf)
# Validate NETCONF is enabled and Operational.
send_single(sros_conn,'show system netconf')
# Disconnect from the SSH Connection to our far-end remote device.
# We need to disconnect to open the pipe for python3 to establish netconf connection.
disconnect(sros_conn)
time.sleep(2)
try:
# Now let's connect to the device via NETCONF and pull the config to validate.
nc = netconfconn(args, NETCONF_USER, NETCONF_PASS)
# Grab the running configuration on our device, as an NCElement.
config = nc.get_config(source='running')
# XML elemnent as a str.
xmlconfig = to_xml(config.xpath('data')[0])
# Write the running configuration to a temp-file (from the data/configure xpath).
saveFile('temp-config.xml', xmlconfig)
# Lets open the XML file, read it, and convert to a python dictionary and extract some info.
withopen('temp-config.xml', 'r') as temp:
content = temp.read()
xml = xmltodict.parse(content)
sys_name = xml['data']['configure']['system']['name']
createFolder('Configs')
saveFile(f"Configs/{sys_name}.txt", xmlconfig)
exceptExceptionas e:
print(f"Issue with NETCONF connection, {e}")
if __name__ == "__main__":
main()

Leave a comment