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:
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.
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
Post a Comment