The Forward Networks data model has an extensive collection of normalized vendor agnostic data for each of your devices. There are times though, when you may need a piece of information about a device that you can’t seem to find within the data model, or you need to pull information from a configuration that is not part of the running config or standard collection. This is where custom commands come in. Forward Networks allows you to add additional show commands to each snapshot to tailor the platform to your organization’s specific needs. Knowing how to pull information out of these files gives you flexibility to extract what you need to answer urgent questions, taking your data management to the next level.
The basic formula that will get you started
foreach device in network.devices
foreach command in device.outputs.commands
where command.commandText == "<YOUR COMMAND HERE>"
let parsed_response = parseConfigBlocks(device.platform.os, command.response)
let my_data = blockMatches(parsed_response, pattern)
Almost all NQE’s are going to start with foreach device in network.devices
Then we’re going to look at the outputs of all the commands Forward runs on your device.
For a command that is standard within the data model we have commandType
, but for a custom command we need the commandText
. All data collected from devices are stored as a text file with type string
. We are going to be using the blockMatches
function which uses NQE specific data type List<ConfigLine>
which adds additional properties to each line of the raw text.
In order to achieve this, we use the parseConfigBlocks
function that takes the device OS and the output of the command and converts it to this format. And then we can use the pattern for the data we want to extract with the blockMatches
function to collect the information needed.
The Sample Dataset
The example I’m using will be on getting a list of APs out of an Aruba WLC. The command we want to run on the controller is show ap database
and the output is as follows.
Command: show ap database
Name Group AP Type IP Address Status Flags Switch IP Standby IP
---- ----- ------- ---------- ------ ----- --------- ----------
AP203R default 203R 192.168.1.9 Down 2 10.8.36.30 0.0.0.0
AP203RP default 203RP 192.168.1.10 Up 12h:21m:22s 2f 10.8.36.30 0.0.0.0
AP203RP default 203RP 192.168.1.8 Down N2 10.8.36.30 0.0.0.0
AP203RP-new default 203RP 192.168.1.12 Up 12h:21m:25s 2f 10.8.36.30 0.0.0.0
AP205 default 205 192.168.1.97 Down N 10.4.183.78 0.0.0.0
AP205 default 205 192.168.1.128 Down 10.4.183.78 0.0.0.0
AP207 default 207 192.168.1.4 Down 2 10.8.36.30 0.0.0.0
AP207 default 207 192.168.1.115 Down N2 10.4.183.78 0.0.0.0
AP207 default 207 192.168.1.123 Down N2 10.4.183.78 0.0.0.0
AP225 default 225 192.168.1.117 Down 2 10.4.183.78 0.0.0.0
AP303H default 303H 192.168.1.120 Down N2 10.4.183.78 0.0.0.0
AP303H default 303H 192.168.1.129 Down N2 10.4.183.78 0.0.0.0
AP303H default 303H 192.168.1.2 Up 12h:19m:13s 2 10.8.36.30 0.0.0.0
AP303H-Ash default 303H 1.1.1.2 Down Rc2 10.4.183.78 0.0.0.0
AP305 default 305 192.168.1.131 Down 2 10.4.183.78 0.0.0.0
AP315 default 315 192.168.1.100 Down 2 10.4.183.78 0.0.0.0
AP325 default 325 192.168.1.95 Down N2 10.4.183.78 0.0.0.0
AP325 default 325 192.168.1.10 Down 2 10.8.36.30 0.0.0.0
AP335 default 335 192.168.1.4 Down 2 10.8.36.30 0.0.0.0
mk-ap335 default 335 192.168.1.120 Down 2 10.4.183.78 0.0.0.0
X4 default 335 192.168.1.121 Down 2 10.4.183.78 0.0.0.0
The Data Pattern
Once we know what the output looks like, we can determine the pattern we’re going to search for. I like to start creating my block pattern variable by copying one section of the output and replacing each item with a variable. Each AP is its own line, so this one is easy.
ap_config =```
AP203R default 203R 192.168.1.9 Down 2 10.8.36.30 0.0.0.0
```;
After the replacement, the pattern that will catch our data will look like this:
ap_config =```
{name:string} {group:string} {type:string} {ip:ipv4Address} {status: ("Up" string | "Down")} {flag:string} {ipSwitch:ipv4Address} {ipStandby:ipv4Address}
```;
Notice that each item has a variable name and type so we can access the information later, but anything you don’t care to collect can be skipped by just adding the data type - {string}
is a good catch all.
The Final Query
ap_config =```
{name:string} {group:string} {type:string} {ip:ipv4Address} {status: ("Up" string | "Down")} {flag:string} {ipSwitch:ipv4Address} {ipStandby:ipv4Address}
```;
status(stat) =
if isPresent(stat) then "Up" else "Down"
;
foreach device in network.devices
foreach command in device.outputs.commands
where command.commandText == "show ap database"
let parsed_response = parseConfigBlocks(OS.ARUBA_WIFI, command.response)
let my_data = blockMatches(parsed_response, ap_config)
foreach ap in my_data
select {
name: ap.data.name,
group: ap.data.group,
type: ap.data.type,
ip: ap.data.ip,
status: status(ap.data.status.left),
uptime: ap.data.status.left,
flags: ap.data.flag,
switch_ip: ap.data.ipSwitch,
standby_ip: ap.data.ipStandby,
controller: device.name
}
With NQE, every foreach
statement takes us to a more granular level.
We want to pull the data for every AP, so the last line foreach ap in my_data
and our select
statement will then access the pattern variables.
Caveats
These are specific to this query to account for variable length output but will likely not be necessary in other queries.
Pattern matching is based on matching each item after a space. Since the status has 2 variants either with one string Down or 2 strings Up 12h:21m:22s
we can use an “or” notated by the “|” to account for both possibilities.
status(stat) =
if isPresent(stat) then "Up" else "Down"
;
This function is then called on by status: status(ap.data.status.left)
to determine the output by detecting whether uptime is present.
The Results
As you can see, we now have a complete list of all APs on the network and the information as it relates to them.
TLDR
This basic formula will provide you with a jumping off point when searching for data in any custom command output.
The example above searches at the data level, to search at the device level, you can create a list for every data point in the select statement.
Searching at the Device Level
pattern =```
<PATTERN YOU WANT TO MATCH - Possible variables: {name:string} {ip:ipv4Address} {other:string}>
```;
foreach device in network.devices
foreach command in device.outputs.commands
where command.commandText == "<YOUR COMMAND HERE>"
let parsed_response = parseConfigBlocks(device.platform.os, command.response)
let my_data = blockMatches(parsed_response, pattern)
select {
device: device.name,
dataName: foreach item in my_data select item.data.name,
dataIp: foreach item in my_data select item.data.ip,
dataOther: foreach item in my_data select item.data.other
}
I hope this enables you to enhance your network management by expanding the data you are able to collect from Forward Networks.
I’d love to hear if you found this useful and what custom commands you intend to run this against!