Skip to main content

Python Crumbs for Cisco routers - Part II

In my  previous post I discussed the basics of using Python scripts with Pretty Good Terminal. What I want to share now is a specific script I created to investigate an issue. 

One of our customer's network exhibited connection issues over DLS lines. There were around 20,000 Cisco 800 series routers connected to the network, and around 200 of these experienced intermittent reachability issues once in a while. 

To check whether the issue was related to the DSL line or not, I had the idea to use specific IOS commands to collect DSL contoller data from the routers. There were two types of routers involved : Cisco 857 and 867VAE.

On a 857 router one can use the "sh dsl interface atm 0" command, while on a 867VAE the "sh controllers vdsl 0" command needs to be issued.

So the first thing a script should do is to detect the router type and based on it issue the correct command. This is something I covered in my previous post, so I will simple reuse the code posted there.

Focusing on the data collection part, it is going to be a nice Python list processing tutorial. But let's start from the beginning and make use of the code we wrote earlier. Did you not save it ? Well, that's a pity. If you saved it, you would have now a similar script:


import re
commandResult = Terminal.ExecCommand("sh version")
_versions = [ n.strip(" ") for n in commandResult.splitlines() if n.startswith("Cisco") ]
CiscoVersionInfo = [ v.strip(" ")  for v in _versions[0].split(",")]
#Calculate IOS version numbers. 
_IOSVersion = CiscoVersionInfo[2]
_IOSVersionNumber = _IOSVersion.split(" ")[1]
_IOSSubVersionNumbers = re.findall(r"[\w']+", _IOSVersionNumber)
IOSMajorRelease = _IOSSubVersionNumbers[0]
IOSMinorRelease = _IOSSubVersionNumbers[1]
IOSMaintenanceNumber = _IOSSubVersionNumbers[2]
IOSTrainID = _IOSSubVersionNumbers[3]
# Calculate Router HW version
HWVersionInfo = [ v for v in _versions[1].strip(" ").split(" ")]
HWPlatform = HWVersionInfo[0] + " " + HWVersionInfo[1]

Now we will only need the HW version, so the rest can be removed. And its also time to make this a function in order to better organize the code. Let's have our function named as GetHWInfo :

import re
import sys

def GetHWVersion():
  commandResult = Terminal.ExecCommand("sh version")
  _versions = [ n.strip(" ") for n in commandResult.splitlines() if n.startswith("Cisco") ]
  HWVersionInfo = [ v for v in _versions[1].strip(" ").split(" ")]
  HWPlatform = HWVersionInfo[1]

  return HWPlatform

The important thing when you create function is to take care of indentation : after the function declaration header, lines are indented by two characters (or as many as you like, just keep it). This is how Python understands blocks of code. Later we will call this function to get the HW version string of the currently connected router. Based on the version got we will construct the necessary IOS command to be issued. A little bit frustrating thing is,.that unlike most programming languages has switch or case statements, Python does not. Instead one must use if-elif blocks :





This command when executed will return a result consisting of many lines, like this short example below:
...
Vendor Country: 0x0F 0xB5
Chip ID: C196 (0)
DFE BOM: DFE3.0 Annex A (1)
Capacity Used: 74% 50%
Noise Margin: 17.0 dB 27.0 dB
Output Power: 10.5 dBm  9.5 dBm
Attenuation:  5.5 dB  3.0 dB
Defect Status: None                            None                        
Last Fail Code: None
Watchdog Counter: 0x46
Watchdog Resets: 7
Selftest Result: 0x00

Subfunction: 0x00 
...
As our purpose is to check line quality, only a couple of lines are interesting. We can identify these lines by substrings occurring in the line text. To reduce the output to the interesting lines, we can first declare the interesting lines as a list of substrings, like :


InterestingLines = ["Attenuation","Noise Margin","Attainable Rate","Actual Power","Capacity Used","Defect status","Last Fail Code","Modem FW Version","Modem PHY Version","Operation FW","Init FW"]

As the lines returned by the router depends on the box type, we can designate different interesting line lists based on the model the script is dealing with at a given time:



Now we can script command execution using rCommand variable and then filter the result:



Easy, clear and concise :-) Is it not ? Let me explain....

The DSLResult variable will contain the command result as returned by the router :




We filter this list of text lines somehow, to get the result to be returned to the PGT scripting engine setting the ActionResult variable :



But how filtering works ? I think some explanation is needed here...
On one hand we have the DLSResult text in which each line is delimited by CRLF (\r\n) characters, and on the other hand we have a list of strings in variable InteresingLines what we want to use to filter the text lines.
If you think over what you would do as a human, your will realize that it was nothing else than checking each line of the text hold b the DSLResult variable whether it matches the required text or not, and include in result if a match was found. Actually, you would perform the same operation for each line of text by checking each and every item in the InteresetingLines list for a match. 
This is eactly what the map() Python function can arrange. Using this function, you can pass each element of a list to a function as a parameter, and use the function result to build a resulting list. 
So the syntax is:

 map(function(listitem), list) -> resultinglist

In our case, the list is the InterestingLines variable and we want to have a function that returns a substring from DSLResult text matching to a given item in IterestingLines. Regex is good tool to filter text, so we will use it in our function.
The regex expresion re.findall("(" + pattern +".+)\r", DSLResult) would return part of the DSLResult text that starts with the text hold in "pattern" variable, followed by any character and ended by the \r - that is, the end of line - character. In other words, it will look for lines containing the "pattern" text and will return the part of the line beginning with "pattern" text.

As programmers are a lazy kind by nature, they invented function definitions without function names (some languages refer to them as anonymous function). The Python expression "lambda" is a similar thing : we define a function without a name and declare input parameters only. So the expression :



is basically a function definition for such a function that takes one parameter named "pattern" and returns the result of the re.findall() expression. 
If you put things together, you will realize that  the value of "pattern" is going to be each and every element of InterestingLines list, and we make a regex match test on DSLResult text for each and the collection of findall() function result will make up the returned list.

That's it. We filtered out from the CLI command result what we need.

The whole script looks like this:

import re

def GetHWVersion():
  commandResult = Terminal.ExecCommand("sh version")
  _versions = [ n.strip(" ") for n in commandResult.splitlines() if n.startswith("Cisco") ]
  HWVersionInfo = [ v for v in _versions[1].strip(" ").split(" ")]
  HWPlatform = HWVersionInfo[1]
  return HWPlatform

#Define the list we will later use for filtering
InterestingLines = []
#Get router HW version
rVersion = GetHWVersion()
rCommand = ""
#Based on the router version set rCommand and InterestingLines variables
if rVersion == "857" : 
  rCommand = "sh dsl interface atm 0"
  #List the line beginnings we are interested in
  InterestingLines = ["Attenuation","Noise Margin","Actual Power","Capacity Used","Defect status","Modem FW Version","Modem PHY Version","Operation FW","Init FW"]
  
elif rVersion == "867VAE-K9" or rVersion == "867VAE" or rVersion == "877" : 
  rCommand = "sh controllers vdsl 0"
  #List the line beginnings we are interested in
  InterestingLines = ["Attenuation","Noise Margin","Attainable Rate","Actual Power","Last Fail Code","Modem FW Version","Modem PHY Version"]

if rCommand != "": 
  DSLResult = Terminal.ExecCommand(rCommand)
  if len(DSLResult) > 0 :
    DSLDataLines = map(lambda pattern:re.findall("(" + pattern +".+)\r", DSLResult), InterestingLines)
    ActionResult = ";".join([",".join(l) for l in DSLDataLines])  
    ScriptSuccess = True
  else:
    ActionResult = "Did not get vdsl controller data"
    ScriptSuccess = False
else:
  ActionResult = "Router HW not supported"

  ScriptSuccess = False  

And what was the result ? Well. In the end we managed to collect enough evidence to say that line conditions were fairly poor to be considered as the root cause of reachability issues.

Should you have any question, I encourage you to use Pretty Good Terminal Forum to get assistance.

Comments

Popular posts from this blog

Python scripting lesson for Juniper switches

Collecting comprehensive switch interface information When working with Juniper EX / QFX switches it is often required to collect comprehensive interface status data. Not any single show command will provide all the details and merging the output of several commands is quite time consuming.  On top of this, it can be tricky to find free ports on switches, because unconfigured ports are simply not shown in many commands and the only way to find out available ports is examining the chassis hardware components. In the following article I would like to show you an easy way of collecting such information quickly by using Pretty Good Termina l.    PGT is completely free for private use so you don’t need to worry about any costs involved. What the script does As part of the standard installation, PGT contains a sample Visual Script designed for Juniper EX /QFX switches to collect switch interface information. The script will issue the following CLI commands and m

Python Crumbs for Cisco routers - Part I

In this series I want to show you how to use Python to solve some common tasks related to Cisco router scripting. Update, 2018.06 : I compiled a short training video that will teach you the basic Python scripting in PGT. If you prefer watching a video over reading the article, this is exactly for you :  PrettyGoodTerminal - Using Python Scripts This tutorial will focus only on - so to say - applied Python language and won't go into details with Python syntax or language elements itself for one very good reason : at the beginning you probably want to have results quickly without reading hundreds of pages about the language. I always affirmed that learning something new is much easier when you get  a grasp on it on a practical way. The most common task I came across is getting some basic information of  a router or switch, like system uptime, version, interface status, etc. Of course, you don't need Python for simple queries, but when you need to do some text process

Using Python to script Cisco routers

A year ago or so I posted an article on LinkedIn,  How to configure 50000+ CPE routers .  Now I want to go a step further in scripting and share you my way of using Python for scripting Cisco routers. I show you the software and its usage for large scale scripting deployment because I do believe it is an extremely powerful network scripting tool, and all who face similar challenges might find it a good friend. It is not a commercial, I do use the software every day and I develop  Pretty Good Terminal  as needed to achieve my goals quicker and simpler in a more & more elegant way. In this article I do not wan to dive deep into scripting details but rather just flash some cool features of PGT worth highlighting. Should you be interested in details, you will find quite detailed descriptions about these features on the website of the software. A Visual Script using Python The software I developed for router mass-configuration has evolved a lot since its first publication an