Quick Trigger (Blitz)¶
The Blitz also known as Quick Trigger is a YAML-driven template that makes it easy for you to run a test case without having to know any knowledge of programming. The Quick Trigger — called Blitz because it’s lightning fast — does the following actions:
Configure a device.
Parse the device output to verify if the device state is as expected.
Unconfig or modify the initial configuration.
Parse the output to check the operational state.
Learn a feature and verify the result of the action
Calling different apis and use their outputs on other actions and other devices
Yang integration
It is fully customizable and new actions can be added
Many more features that will be discussed thoroughly in the upcoming sections
Designed for quick development, the quick trigger verifies key-value pairs and
uses parsing for full flexibility across OS and platforms. As with any
pyATS Library trigger, other triggers can inherit from the Blitz
class. Go
ahead and build on top of it!
To use the quick trigger template, add the YAML content to a trigger_datafile.yaml as shown in the following example for BGP on a router. The yaml is commented out explaining what each section does. See example below.
# Name of the testcase
TestBgpShutdown:
# Location of the blitz trigger
source:
pkg: genie.libs.sdk
class: triggers.blitz.blitz.Blitz
# Devices to run on - Default is uut
devices: ['uut']
# Field containing all the Testcase sections
test_sections:
# Section name - Can be any name, it will show as the first section of
# the testcase
- apply_configuration:
# List of actions
- configure:
device: R3_nx
command: |
router bgp 65000
shutdown
- sleep:
sleep_time: 5
# Second section name
- verify_configuration:
# Action #1
# Send show command to the device and verify if part
# of a string is in the output or not
- execute:
device: R3_nx
command: show bgp process vrf all
include:
# Verify Shutdown is within the show run output
- 'Shutdown'
exclude:
# Verify Running is not within the show run output
- 'Running'
# Action #2
# Send show command and use our available parsers to make sure
# the bgp protocol state is shutdown
- parse:
device: R3_nx
# All action supports banner field to add to the log
banner: Verify bgp process is shutdown
command: show bgp process vrf all
include:
- get_values('shutdown')
exclude:
- not_contains('running')
- Revert_configuration:
# Configure action, which accepts command as an argument
- configure:
device: R3_nx
banner: Un-Shutting down bgp 65000
command: |
router bgp 65000
no shutdown
- verify_revert:
# Send show command and verify if part of a string is in the output or not
- execute:
device: R3_nx
command: show bgp process vrf all
include:
# Verify Running is within the show run output
- 'Running'
exclude:
# Verify Shutdown is not within the show run output
- 'Shutdown'
# Send show command and use our available parsers to make sure
# it is the bgp protocol state which is running
- parse:
device: R3_nx
command: show bgp process vrf all
Actions¶
Here is the list of all available actions. These actions are to be placed at this level:
# Name of the testcase
Testcase1:
# Leave this as is for most use cases
source:
pkg: genie.libs.sdk
class: triggers.blitz.blitz.Blitz
# Field containing all the sections
test_sections:
# Section name - Can be any name, it will show as the first section
# of the testcase
- section_one:
- ">>>> <ACTION> <<<<"
- ">>>> <ACTION> <<<<"
- ">>>> <ACTION> <<<<"
- section_two:
- ">>>> <ACTION> <<<<"
- ">>>> <ACTION> <<<<"
...
Below you can find the list of all available actions
execute¶
The execute
action is used to send a command to the device. Keywords include
and exclude are to be used to verify if specific string exists or do not
exists in the output. You also, have the option to check if a specific
regex
exists within the output of the action.
- execute: # ACTION
# (Either device hostname or device alias)
device: R1
# Send show version to the device
command: show version
# Can have as many items under include or exclude that you want
include:
- '12.9.1'
- 'CSR1000V'
# Regular expression can also be provided
- '\d+'
exclude:
- 'Should not be in the output'
Both include and exclude keywords are optional to use.
You can apply additional arguments to execute
command.
List of arguments that can be applied to execute command can be found at this link.
Example can be seen below.
# A timeout of 10 second is applied to execute action,
# Now if the device has not executed the command within 10 seconds, the step will fail.
- execute:
command: show version
device: PE1
timeout: 10
configure¶
The configure action is used to configure the device.
- configure: # ACTION
device: device_name
command: |
router bgp 65000
shutdown
You can apply additional arguments to configure
command.
List of arguments for the configure command can be found at this link.
Example can be seen below.
# A timeout of 10 second is applied to configure action,
# Now if the device is not configured within 10 seconds, the step will fail.
- configure:
command: feature bgp
device: PE1
timeout: 10
parse¶
The parse
action use pyATS Parsers.
The parsers return structured data in a dictionary format. It allows to verify
if certain key have an expected output, where execute verify that it is
somewhere in the output, irrelevant of the structure. You can use the keywords
include and exclude to query the output of your parser. You can learn, how
to use include/exclude keywords in a parse action by reading through
this section.
- parse: # ACTION
device: R2
command: show version
# Can have as many items under include or exclude that you want
include:
- raw("[version][version]")
- contains("version").value_operator('mem_size' '>=', 1217420)
# Make sure the memory is greater than 1217420
...
api¶
The api
action use pyATS Api.
You can use include/exclude to query the results of the apis that their outputs are dictionary
.
See section.
- api: # ACTION
function: get_interface_mtu_config_range
arguments:
interface: GigabitEthernet1
include:
- contains('max')
- get_values('range')
exclude:
- contains('min-max')
...
The output of the apis that are numerical or string can be also verified using the include/exclude keywords. See section.
tgn¶
The tgn
action now allows you to call traffic generator (tgn) apis in addition to the
other existing apis.
- tgn: # ACTION
function: get_traffic_stream_objects
...
rest¶
The rest
action allows to make rest call to any endpoint on a device. Rest uses http method to
transfer data. Five http protocols are supported, get, post, put, patch and delete.
You can find additional information on rest, using this tutorial.
test_sections:
- plain_actions:
- rest:
method: get
dn: '/api/mo/sys/intf/phys-[eth1/1].json'
device: N93_3
- rest:
method: delete
device: N93_3
dn: '/api/mo/sys/bgp/inst.json'
- rest:
method: put
dn: '/api/mo/sys/bgp/inst/dom-default/af-ipv4-mvpn.json'
device: N93_3
payload: {
"intf-items": {
"phys-items": {
"PhysIf-list": [
{
"adminSt": "down",
"id": "eth1/2",
"userCfgdFlags": "admin_layer,admin_state"
}
]
}
}
}
- rest:
method: post
dn: 'api/mo/sys/bgp/inst.json'
device: N93_3
payload: {
"bgpInst": {
"attributes": {
"isolate": "disabled",
"adminSt": "enabled",
"fabricSoo": "unknown:unknown:0:0",
"ctrl": "fastExtFallover",
"medDampIntvl": "0",
"affGrpActv": "0",
"disPolBatch": "disabled",
"flushRoutes": "disabled"
}
}
}
- rest:
method: patch
dn: '/api/mo/sys/bgp/inst/dom-default/af-ipv4-mvpn.json'
device: N93_3
payload: {
"intf-items": {
"phys-items": {
"PhysIf-list": [
{
"adminSt": "down",
"id": "eth1/2",
"userCfgdFlags": "admin_layer,admin_state"
}
]
}
}
}
sleep¶
The sleep
action is used to pause the execution for a specified amount of time.
- sleep: # ACTION
# Sleep for 5 seconds
sleep_time: 5
...
learn¶
The learn
action is used to learn a feature on a specific device, returning an
OS agnostic structure. You also can query the outcome of this action
similar to api action and parse action.
- learn:
device: R1
feature: bgp
include:
- raw("[info][instance][default][vrf][default][cluster_id]")
...
print¶
print
action allows you to print messages, variables and actions output into the console.
- print:
print_item1: "%VARIABLES{parse_output}"
print_item2: "%VARIABLES{configure_output}"
...
yang¶
The yang action is designed to work with differing underlying protocols, but, at the time of this writing, only NETCONF and gNMI are supported. Changing the connection and protocol determines the message format.
Example of configuration using NETCONF (with automated verification of edit-config on device)
- yang:
device: uut2
connection: netconf
operation: edit-config
protocol: netconf
datastore: candidate
banner: YANG EDIT-CONFIG MESSAGE
content:
namespace:
ios-l2vpn: http://cisco.com/ns/yang/Cisco-IOS-XE-l2vpn
nodes:
- value: 10.10.10.2
xpath: /native/l2vpn-config/ios-l2vpn:l2vpn/ios-l2vpn:router-id
edit-op: merge
bash_console¶
Using this action, now you can run various bash command on the device. You can save output of each command, and apply include/exclude verification on the output of each command. Below example shows how to use bash_console action.
- verify_config:
- bash_console:
device: csr1000v-1
target: standby
timeout: 45
save:
- variable_name: second_cmd
filter: contains('ls')
- variable_name: everything
commands:
- pwd
- ls
- |
cd ~
echo A string of text
include:
- contains('ls')
configure_replace¶
The configure_replace
action is used to replace the running-config. Users only needs
to provide the location of the saved configuration.
- configure_replace:
device: my_device
config: bootflash:/golden_config
# Iteration and interval is used for a retry mechanism
iteration: <int> #optional, default is 2
interval: <int> #optional, default is 30
save_config_snapshot¶
The save_config_snapshot
action is used to save a snapshot of the current
device configuration. The config can later be used with the
restore_config_snapshot
action.
- save_config_snapshot:
device: my_device
restore_config_snapshot¶
The restore_config_snapshot
action is used to restore a snapshot taken
from the save_config_snapshot
action. If you want to re-use the same
snapshot you can specify to not delete it. See example below.
- restore_config_snapshot:
device: my_device
delete_snapshot: False #optional, default is True
run_genie_sdk¶
The run_genie_sdk
action is used to run other triggers from within
Blitz
. All you have to do is to mention the trigger name and its arguments
in your Blitz
datafile.
Note
You must extend the main trigger_datafile for any of those triggers to be accessible. Put this at the top of your trigger_datafile: extends: ‘%ENV{VIRTUAL_ENV}/genie_yamls/trigger_datafile.yaml’
- run_genie_sdk:
<trigger_name>:
<any trigger arguments>
# An example of running TriggerSleep
TriggerSleep:
devices: [my_device]
diff¶
Allow to diff two variables (Dictionary or Ops object).
By default it will just print the difference, but can also fail the section if they are different with the argument fail_different=True.
command
or feature
to diff will gather pre-defined exclude list from
the parser or Ops.
mode
can be specified only what you want to check. mode
has add
,
remote
and modified
. By default, it will show all the differences,
for the case add
, will show only added difference.
- snapshot_pre_configuration:
- parse:
device: R3_nx
command: show interface
save:
- variable_name: pre_snapshot_nxos
- configure_interface:
# List of actions
- configure:
device: R3_nx
command: |
interface Ethernet1/56
no switchport
ip address 10.5.5.5 255.255.255.0
no shutdown
- parse:
device: R3_nx
command: show interface
save:
- variable_name: post_snapshot_nxos
- diff:
pre: "%VARIABLES{pre_snapshot_nxos}"
post: "%VARIABLES{post_snapshot_nxos}"
device: R3_nx
command: show interface
mode: modified
Example with feature
.
- diff:
pre: "%VARIABLES{pre_interface_ops}"
post: "%VARIABLES{post_interface_ops}"
device: R3_nx
feature: interface
mode: add
Note
Please find more detail for diff
from below document.
Diff
compare¶
Action compare
allows you to verify the values of the saved variables. Below example shows how you can actually use this action.
# assume you already saved values in the variable bios, os, date_created and bootflash
- compare:
items:
- "'%VARIABLES{os}' == 'NX-OS' and '%VARIABLES{date_created}' == '10/22/2019 10:00:00 [10/22/2019 16:57:31]'"
- " %VARIABLES{bootflash} >= 290000 or '%VARIABLES{bios}' == '07.33'"
Negative testing¶
You can get a Passed result for an action that is expected to fail by setting the key; expected_failure: True
.
Actions, [configure
, execute
, parse
, learn
, api
, rest
, bash_console
] support this feature.
# The command doesnt exist so action should error out but since it was anticipated that the command wouldn't work.
The results would finally be shown as passed.
- execute:
command: banana
device: PE1
expected_failure: True
timeout: 100
Failing actions and sections upon failure¶
By default blitz actions and sections continue to work even after a failure. However, users can manually adjust their testscripts so the script stop upon failure. Below example shows how to achieve that.
- test_sections:
- apply_configuration:
- continue: False
- configure:
command: router bgp 6500
device: PE2
- confirm_actions:
- execute:
continue: False
command: show interface
device: PE2
- execute:
command: show module
device: P2
In the section apply_configuration in action level - continue: False
is set, so if the result of the section is
a failure the script stops the run of the rest of the sections in the testscript.
In the section confirm_actions, in the first action execute
a keyword continue
is added with value False
.
That would send the signal that upon failure of an action the rest of the actions in that section should not be running.
Verifying action JSON output¶
As it was mentioned when introducing different actions, users can query the action outputs that are dictionary using a tool called Dq. You can find the complete tutorial of Dq by following this link.
Actions parse
, learn
and api
are benefiting from this feature the most, as they are
the one that are most likely to have a dictionary output. You can query a dictionary using Dq
and see whether the result of a query is included or excluded in our output.
Below you can see an example of using include and exclude on the parsed output of the
command show version
.
- apply_configuration:
- parse:
command: show version
device: PE2
include:
# we want to se if the result of this query
# is not a empty dictionary
- contains('WebUI[\S\s]+', regex=True)
exclude:
# The output of the query is 'VIRTUAL XE'
# but we hope that the key 'platform' has no value
# or does not exist within the dictionary by using
# the exclude keyword
- get_values('platform')
Below you can see an example of calling the get_interface_mtu_config_range api within the trigger_datafile and checking if certain query results are included or excluded in the output.
- apply_configuration:
- api: #
function: get_interface_mtu_config_range
arguments:
interface: GigabitEthernet1
include:
# Check if the output of this query is not an empty dictionary
- contains('max')
# Check if the key 'range' has the value of <1200, 1800>
- contains_key_value('range', <1200, 1800>)
exclude:
# Check if the output of these queries are actually an empty dictionary
- contains('min-max')
Note
There is no need to use Dq to validate if a dictionary output is equal to an expected dictionary. See below example.
# Description: This would check whether the output of the parser is equal to the specified dictionary.
# No Dq query is needed to perform such validation.
- parse:
device: 'N93_3'
command: 'show module'
save:
- variable_name: banana
filter: contains('lc')
include:
- {'slot': {'lc': {'2': {'40G Ethernet Expansion Module': {'ports': '12',
'model': 'N9K-M12PQ',
'status': 'ok',
'software': 'NA',
'hardware': '1.2',
'slot/world_wide_name': 'GEM',
'mac_address': '88-1d-fc-71-de-38 to 88-1d-fc-71-de-43',
'serial_number': 'SAL1928K4EG',
'online_diag_status': 'Pass'}}},
'rp': {'1': {'1/10G SFP+ Ethernet Module': {'ports': '48',
'model': 'N9K-C9396PX',
'status': 'active',
'software': '9.3(3)IDI9(0.509)',
'hardware': '2.2',
'slot/world_wide_name': 'NA',
'mac_address': '84-b8-02-f0-83-90 to 84-b8-02-f0-83-c7',
'serial_number': 'SAL1914CNL6',
'online_diag_status': 'Pass'}}}}}
- contains('lc')
- get_values('rp')
Verifying action string output¶
At this moment, it is only action api that supports this feature, as it is the only
action that have integer
, float
and string
outputs.
In below example , we want to verify that the numerical output of get_interface_mtu_size is smaller or equal 2000
# code_block_5
- api: # ACTION
function: get_interface_mtu_size
arguments:
interface: GigabitEthernet1
include:
- <= 2000
...
For numerical outputs we support all the common mathematical operations {=, >=, <=, >, <, !=}
.
You also can check whether a value is within a certain range. Below is an example of this feature. We want to see if the action output is greater than 1200 and smaller or equal 1500.
- api: # ACTION
function: get_interface_mtu_size
arguments:
interface: GigabitEthernet1
include:
- ">1200 && <=1500"
If you use the keyword include without specifying any operation the default operation would be
set to ==
and by using keyword exclude the operation would be set to !=
.
Below you can see an example of this.
- api: # ACTION
function: get_interface_mtu_size
arguments:
interface: GigabitEthernet1
include:
- 1500
exclude:
- 9999
Verifying action list output¶
It is also possible to check and see if certain items exist within a output that is a list.
- api:
device: PE1
function: get_list_items
arguments:
name: [1,2,3,4,5,6,7] # the output is [1,2,3,4,5,6,7]
include:
- 5 # checks if 5 is in the list
- "6" # checks if 6 is in the list
exclude:
- 99 # checks if 99 is NOT in the list
Additionally, you can set a regex and see if that regex is within that output
- api:
device: PE1
function: get_platform_logging
include:
- \*Dec 10 03:2.* # Check if any item within a list matches this regex
- "23:31:16.651"
exclude:
- name # Check if any item within a list not matches this regex
- \*Dec 10 03:2.*
Replying to the prompt dialogue¶
When executing or configuring commands on some devices, it is possible that you receive
a prompt message that needs to be replied. In Blitz
, you can handle these prompt messages
automatically by using the keyword reply in your action. In order to reply a message,
you need to know the regex pattern of the message that would show up in the console.
Below you can see an example of the action execute
handling a prompt message.
# Looking for the parse_output variable in the action execute
- apply_configuration:
- execute:
device: PE1
command: write erase
reply:
- pattern: .*Do you wish to proceed anyway\? \(y/n\)\s*\[n\]
action: sendline(y)
loop_continue: True
continue_timer: False
Filter, Save and Load variables¶
Another very useful feature that Blitz has, is the ability to save actions output or a variation of the output. You can save values to a variable name and later use that variable in other actions. There are different ways to save values to a variable:
Save the entire output of an action to a variable name.
Save a part of the output of an action to a variable name.
Below you can find examples of how to save the entire output to a variable name.
# Description: Saving the entire output of an execute action into a variable
# The type of output is string
- Execute:
device: '%{testbed.devices.uut.alias}'
command: show platform
save:
- variable_name: execute_output
# Description: Saving the entire output of an execute action into a variable
# The type of output is dictionary/JSON data.
- parse:
device: '%{testbed.devices.uut.alias}'
command: show platform
save:
- variable_name: execute_output
For actions that has outputs with JSON
datatype It is possible to apply a filter (Dq queries)
and save a part of dictionary into a variable
# Description: Applying a dq query and save the outcome into the variable parse_output.
# Later on checking if that value exist in action execute output.
# Dq query only works on outputs that are dictionary
- apply_configuration:
- parse:
command: show module
device: PE2
save:
- variable_name: parse_output
filter: contains('ok').get_values('lc', index=2)
# The output is '4'
- execute:
device: PE1
command: show version
include:
- "w"
# check if '4' exists within the result of this action
- "%VARIABLES{parse_output}"
For actions that has string outputs you can apply a regex filter. If regex matches the output, the grouped value,
that has a variable name specified like (?P<variable_name>)
, will be stored into that variable_name.
Below you can see the example of regex filter
# first saving values from execute action output
# later on printing those values
- execute:
device: N93_3
command: show version
save:
- filter: BIOS:\s+version\s+(?P<bios>[0-9A-Za-z()./]+).* # bios version is 07.33
regex: true
- filter: bootflash:\s+(?P<bootflash>[0-9A-Za-z()./]+)\s+(?P<measure>\w+).* # bootflash is 51496280 and measure is KB
regex: true
- print:
bios:
value: "The bios version is %VARIABLES{bios}"
bootflash:
value: "The bootflash is %VARIABLES{bootflash} and %VARIABLES{measure}"
For actions that has list outputs you can get an index or a part of a list and save it into a list with a desired variable_name. You can also create a regex filter and match it against all the items within that list, and get a list of all the matched items.
Below you can see the example of list filter.
# saves various items of a list with a variable
- api:
device: PE1
function: get_list_items
arguments:
name: [{'a': 1}, {'d': {'c': 'name1'}}, [1,2,34], {'e': ['a', 'b', 'c']}]
index: 0
index_end: 5
save:
- variable_name: list_int5 # the output is [{'a': 1}, {'d': {'c': 'name1'}}, [1,2,34], {'e': ['a', 'b', 'c']}]
list_index: "[0:2]" # saves items 0,1 from the above array of itmes => [{'a': 1}, {'d': {'c': 'name1'}}]
# into a list named list_int5
- variable_name: list_int7 # saves item #2 in the array =>[[1,2,34]] into a list name list_int7
list_index: 2
- variable_name: list_int8 # saves the entire array in a list named list_int8
- api:
device: PE1
function: get_platform_logging
save:
# apply regex filter to items and save a list of matches
- variable_name: platform_log # The output to save value from is a list of platform logs
filter: Oct\s+15[\S\s]+Configured from console by console$ # checks if any item in the list matches this filter and
# save it in a list named platform_log
The following example is showing how to use our specific markup language
to load the saved variable in another action. In this example we save the output
of the get_interface_mtu_size api and later use it within the command
of the action configure
.
- apply_configuration:
- api:
device: PE1
function: get_interface_mtu_size
save:
- variable_name: api_output
arguments:
interface: GigabitEthernet1
- configure:
device: PE1
command: |
router bgp '%VARIABLES{api_output}'
Another example of how to use our markup language is provided below. In this example the output of the learn
action is saved on variable main_learn_output. Also, a filter is applied on this output and is saved
in variable filtered_learn_output. We later check the inclusion of the filtered_learn_output
in action execute
output and print the main_learn_output into the console.
- apply_configuration:
- learn:
device: PE1
feature: bgp
save:
- variable_name: main_learn_output
- variable_name: filtered_learn_output
filter: raw("[info][instance][default][vrf][default][cluster_id]")
- execute:
device: PE1
command: show version
include:
- "w"
- "%VARIABLES{filtered_learn_output}"
- print:
print_item1: "%VARIABLES{main_learn_output}"
Note
Both filter and include/exclude features are using our dictionary querying tool Dq.
Quick Trigger parallel¶
Up to this point of this tutorial, we were mainly talking about how to operate with Blitz
and execute
different actions in a sequential manner. This means that upon running the trigger_datafile
actions are getting executed one after the other and each action should completely finish its job before
another action starts. In some testcases executing actions sequentially could be quite time consuming.
In this section we will discuss how to execute multiple actions in parallel and at the same time. Running actions in parallel allows you to execute numerous actions all together, which make the execution of a trigger_datafile way more faster.
You can run multiple actions concurrently by defining your actions after the keyword parallel within
your trigger_datafile. Below you can see an example of multiple actions that are running in parallel.
In below example actions api
and learn
are executed on device PE1
and parse
is executed on device PE2
and all at the same time.
- verify_configuration
- parallel:
- api:
device: PE1
function: get_interface_mtu_size
arguments:
interface: GigabitEthernet1
- parse:
command: show version
device: PE2
include:
- contains("version_short")
- learn:
device: PE1
feature: bgp
include:
- contains("info")
...
While you can execute actions in parallel to make the execution of a trigger_datafile faster,
you can still run some other actions in the same sequential manner. In below example action execute
gets executed first and then two actions api
and parse
start their work in parallel, and finally
the action sleep
start its work for 5 seconds.
# Actions 'execute' and 'sleep' are being executed on a sequential manner
# While 'api' and 'parse' are executed at the same time
- apply_configuration:
- execute:
device: PE1
command: show version
- parallel:
- api:
device: PE1
function: get_interface_mtu_config_range
arguments:
device: P2
interface: GigabitEthernet1
- parse:
command: show bgp process vrf all
device: P1
- sleep:
sleep_time: 5
...
Please note that you cannot save a variable in parallel and immediately use it in another action that is being executed in the same parallel block. However, you still can save a variable in an action that being executed in a parallel block, and use it outside that parallel block later. If you want to use a variable in an action that is being executed in parallel, you need to save that variable beforehand in an action outside of that parallel block.
In below example value min
and max
are saved from the output of the get_interface_mtu_config_range
api action and later is being used in get_interface_mtu_size api that is going to be executed in parallel
along with a configure
action. Within the same parallel block the output of the action configure
is being saved
to be used later in other actions.
test_sections:
- apply_configuration:
- api:
device: PE2
function: get_interface_mtu_config_range
save:
- variable_name: min
filter: contains('min')
- variable_name: max
filter: contains('max')
- parallel:
- api:
device: PE1
function: get_interface_mtu_size
arguments:
interface: GigabitEthernet1
include:
- ">= %VARIABLES{min} && <= %VARIABLES{max} "
- configure:
device: PE1
save:
- variable_name: another_configure_output
command: |
router bgp 65000
- execute:
device: PE1
command: show interface
include:
- "%VARIABLES{another_configure_output}"
Trigger timeout/interval ratio adjustments¶
Each action performs verification to make sure it has performed as expected.
These timeouts can be modified with a ratio from the testbed datafile.
This feature is supported by actions api
, execute
, parse
, learn
and rest
.
# Name of the testcase
Testcase1:
source:
pkg: genie.libs.sdk
class: triggers.blitz.blitz.Blitz
# Field containing all the sections
test_sections:
# Section name - Can be any name, it will show as the first section
# of the testcase
- apply_configuration:
- execute:
command: show version
include:
- 'w'
max_time: 5
check_interval: 1
...
devices:
PE2:
connections:
ssh:
ip: 10.255.1.17
protocol: ssh
credentials:
default:
password: cisco
username: cisco
enable:
password: cisco
custom:
max_time_ratio: '0.5'
check_interval_ratio: 0.5
os: iosxe
type: CSR1000v
Now the max_time and will half’d.
Running conditional statements¶
It is possible to run (or not run) a set of actions with regards to a conditional statement. This can be achieved by running actions below the keyword run_condition. To run actions with a conditional statement, Blitz expects:
An if statement with boolean value (True or False statement).
A function that can be the result of all the actions under run_condition if the boolean condition is equal True.
A set of actions (e.g parse, execute etc.) that would be specified under keyword
actions
.
The function can be one from this list [passed, failed, aborted, skipped, blocked, errored, passx]
.
The function will be applied only if the if statement is equal True, otherwise actions will be running normally.
To better understand the use of this feature lets look at the following example.
- run_condition:
if: "2000 == 2000" # if statement boolean value True
function: failed # function that would be applied to actions
actions: # All the actions that are under this keyword will be conditioned and the results of them will be set as failed
- api: # output as Failed
description: get the api value and verify the output
device: "%{testbed.devices.PE1.alias}"
function: get_interface_mtu_size
save:
- variable_name: nbc
arguments:
interface: GigabitEthernet1
include:
- ">= 1400 && <= 1600"
- sleep: # output as Failed
sleep_time: 1
- run_condition:
if: "2000 != 2000" # if statement boolean value False
function: passed # function that would be applied to actions
actions:
- api: # will call the api
description: get the api value and verify the output
device: "%{testbed.devices.PE1.alias}"
function: get_interface_mtu_size
save:
- variable_name: nbc
arguments:
interface: GigabitEthernet1
include:
- ">= 1400 && <= 1600"
- sleep: # will sleep for a sec
sleep_time: 1
Note
Be noted, actions would run only if the condition statement is False. If the statement is True, the result of all the actions underneath the run_condition would be as same as the function value.
Using the run_condition, users can evaluate various conditional statements before running their actions. Examples are provided below for these conditional statements.
# Description: You can check whether a section that has previously ran has a `passed`
# results and run your actions if that sections functioned properly.
test:
source:
pkg: genie.libs.sdk
class: triggers.blitz.blitz.Blitz
devices: ['uut']
test_sections:
- plain_actions: # the section.results is == passed
- sleep:
sleep_time: 10
- apply_config:
- run_condition:
if: "%VARIABLES{plain_actions} == failed" # if section plain_actions has failed, fail all the actions below
function: failed # The condtion above is False so the actions below will run
actions:
- execute:
command: show version
device: uut
- sleep:
sleep_time: 1
# Description: You can check whether if an action that has previously ran has `passed`
# and run your actions if that action functioned properly.
# To be able to reference an action, you need to define an action alias for that action
test:
source:
pkg: genie.libs.sdk
class: triggers.blitz.blitz.Blitz
devices: ['uut']
test_sections:
- apply_config:
- execute: # execute result is a failure because parser does not include in execute output
alias: execute_alias
command: show vrf
device: uut
include:
- parser
- run_condition:
if: "%VARIABLES{execute_alias} == failed"
function: skipped # The action execute_alias failed so all the actions below will be skipped
actions:
- parse:
command: show version
device: uut
- sleep:
sleep_time: 1
# Description: You can check whether if a saved_variable has the appropriate output
test:
source:
pkg: genie.libs.sdk
class: triggers.blitz.blitz.Blitz
devices: ['uut']
test_sections:
- apply_config:
- api: # api output is equal to 1500
device: uut
function: get_interface_mtu_size
save:
- variable_name: gims_output # the 1500 is stored in gims_output
arguments:
interface: GigabitEthernet1
- run_condition:
if: "%VARIABLES{gims_output} != 1500" # if action gims_output is not equal 1500 the function should abort the section
function: aborted # the if statement is false hence, won't the actions
actions:
- parse:
command: show version
device: uut
- sleep:
sleep_time: 1
# Description: You can check multiple conditional statements all at once and run actions with regards to their output
test:
source:
pkg: genie.libs.sdk
class: triggers.blitz.blitz.Blitz
devices: ['uut']
test_sections:
- apply_config:
- api: # api output is equal to 1500
device: uut
function: get_interface_mtu_size
save:
- variable_name: gims_output # the 1500 is stored in gims_output
arguments:
interface: GigabitEthernet1
- api: # api output is equal to 1500
device: uut
function: get_interface_mtu_size
save:
- variable_name: gims_output_1 # the 2500 is stored in gims_output
arguments:
interface: GigabitEthernet10
- run_condition:
if: "%VARIABLES{gims_output} != 1500 and %VARIABLES{gims_output} != 2500" # if gims_output and gime_output_1 are not storing the proper value
function: skipped # the if statement is false hence, skipping actions
actions:
- parse:
command: show version
device: uut
- sleep:
sleep_time: 1
Looping in Blitz¶
In Blitz, a loop is a sequence of actions that is iterated until a certain terminating condition is reached. Looping allows the development of more dynamic testcases.
Lets take a look at a basic examples of looping before diving deeper into looping in Blitz. In the below Blitz section, the loop is above an execute action.
The goal is to run this action twice on the same device using 2 different commands, without writing two separate execute actions with 2 different commands. This can be achieved simply by using loop like below.
In the below example The loop_variable_name will be the name of the loop value that will be reused in the action. The value here is a list of show commands. Here each show commands get saved into the variable_name “command” and in the execute action would be loaded as the actual command. The execute action would run twice once executing show version command and once executing show vrf command both times on the device PE1.
Note
An iteration here means, one execution of all the actions below the keyword loop. In below example we have 2 iterations.
- apply_config:
- loop:
loop_variable_name: command
value:
- show version
- show vrf
actions:
- execute:
alias: execute_
device: PE1
command: "%VARIABLES{command}"
Each loop can contains the following keywords:
loop_variable_name
: It is variable name of the variable that will be reused during the loop lifecycle.value
: A value is a list or hash of items. For each iteration of a loop, an item in the list/hash will be stored into the loop_variable_name.range
: It is an integer. When range specified a list of integers is created containing values from 0 to range integer. The items of the list can be reused during the loop lifecycle similar to what stated previously in value.until
: A terminating condition, that upon reach the loop would stop working.do_until
: Another terminating condition, with one slight difference. If specified the loop will run once even if the terminating condition is met.max_time
: A max_time that should be specified in case of defining an until or do_until so the loop would finish at a certain point, without falling into infinite loop.every_seconds
: A value to set so each iteration of the loop run exactly to that amount of seconds.loop_until
: It could be set to (passed/failed). If set, loop will iterate until the result of the last iteration is as same as the value.
Note
A loop can only have one of the value
, range
, until
, do_until
.
There are a lot of use cases for looping with various features. Examples can be found below.
# Description: Loop over a dictionary/hash.
# each dictionary is a collection of key value pairs.
# To use the keys and values of the dictionary you can use the keywords ._keys and ._values
- loop:
# looping over a dictionary and applying values within action in same level and actions that re in the nested loop
loop_variable_name: l_dict
value: # l_dict will represent each item upon iteration in this dictionary
inventory_save: inventory
module_save: vrf
actions:
- execute:
device: PE1
command: show %VARIABLES{l_dict._values} # l_dict.values will be inventory and vrf in order
save: # The output of the action gets saved respectively in the specified values.
- variable_name: "%VARIABLES{l_dict._keys}" # l_dict.keys will be inventory_save and module_save in order.
include:
- "state"
# Description: Loop over a list of device names, and run actions on the various devices without duplicating that action.
- loop:
# A loop that runs one action over different devices
loop_variable_name: devices
value: # a list of device names
- PE1
- PE2
actions:
- execute:
# The action name
alias: execute_
device: "%VARIABLES{devices}" # load the device here and execute show platform sequentially on various devices
command: show platform
# Description: Loop over actions for maximum time of 5 seconds, execute actions once (one iteration).
# If the result of first action was not equal to "passed", terminate the loop, else continue until the condition is met or
# max_time is reached
- loop:
# Loop over an action at least running it once and if a condition met terminate the loop
do_until: "%VARIABLES{api_mtu_size} != passed"
max_time: 5
actions:
- api:
alias: api_mtu_size
description: get the api value and verify the output
device: "%{testbed.devices.PE1.alias}"
function: get_interface_mtu_size
save:
- variable_name: nbc
arguments:
interface: GigabitEthernet1
- execute:
command: show vrf
device: PE2
# Description: Looping over an action twice (two iteration) since the range is 2, and each time,
# and run a couple of actions in parallel
# Also after each parallel call sleep for amount of the range value, so once for one second and the other for two seconds.
- loop:
# Looping on a range of value, this instance it runs twice, you still can use the range number in your actions
range: 2
loop_variable_name: range_name
actions:
- parallel:
- parse:
device: PE1
command: show version
- execute:
device: PE2
command: show version
- sleep:
sleep_time: "%VARIABLES{range_name}"
# running a loop with loop_until: passed
# The iteration stops the second the last iteration is equal to passed.
- loop:
range: 2
loop_variable_name: range_name
loop_until: passed
actions:
- parallel:
- parse:
device: PE1
command: show version
- execute:
device: PE2
command: show version
- sleep:
sleep_time: "%VARIABLES{range_name}"
The keyword every_seconds
is defined so users can manage their loop and if possible run it with synchronized timing.
If the execution of an iteration of a loop exceeds the time assigned for every_seconds, the loop would still continue its work but a warning would be
printed into the log. Below you can see the example of how every_seconds
work.
# Description: this action is looping over a list of size two, hence two iteration and each iteration should take 8 seconds
# if the iteration ends in less than 8 seconds, the loop would sleep for the remaining of that time and after reaching 8 seconds
# it would execute the other iteration. The total time of execution in this case would be 16 seconds
# Keep in mind if an iteration takes more than 8 seconds the loop continue the work and it wont stop
- loop:
loop_variable_name: banana
value:
- version
- vrf
every_seconds: 8
actions:
- execute:
alias: execute_
device: uut
command: show %VARIABLES{banana}
- parse:
alias: parse_
device: uut
command: show version
Another feature that Looping in Blitz supports is nested loops. There are cases that the users might want to iterate over various values. Using nested loop would provide users with that functionality. Below shows the example of how you can implement nested loops in your script.
# Description: in this example, the first loop has a dictionary value. The item of the second loop that is nested
# in the first loop have access to both the values of the dictionary in the first loop and the list in the second loop.
- loop:
# looping over a dictionary and applying values within action in same level and actions that re in the nested loop
loop_variable_name: l_dict
value:
inventory_save: inventory
module_save: vrf
actions:
- api:
device: PE2
function: get_interface_mtu_config_range
arguments:
interface: GigabitEthernet1
save:
- variable_name: max
filter: get_values('max')
- loop:
# Looping on a range of value, this instance it runs twice, you still can use the range number in your actions
value:
- show version
- show vrf
loop_variable_name: list_name
actions:
- parallel:
- execute:
device: PE1
command: show %VARIABLES{l_dict._values}
save:
- variable_name: "%VARIABLES{l_dict._keys}"
include:
- "state"
- execute:
command: "%VARIABLES{list_name}"
device: PE2
Creating a custom action in Blitz¶
Blitz its not limited to its built-in actions. It is possible to create various custom actions and still utilize Blitz framework. The structure needed to create a custom action in Blitz is pretty straight forward. A new module (e.g. customBlitz.py) with a new class should be created. Within the said class, Blitz class should be inherited and the action can be developed. The content of that action can be anything that helps users with their testing. Look at example below
import logging
from pyats import aetest
from genie.libs.sdk.triggers.blitz.blitz import Blitz
log = logging.getLogger()
class CustomBlitz(Blitz): # <- inheriting Blitz
def my_custom_action(self, steps, device. **kwargs):
log.info("This is my custom action")
Later on the custom action can be called within the trigger datafile, with the same name as the function name.
TestCustomAction:
source:
pkg: genie.libs.sdk
class: triggers.blitz.blitz.Blitz
devices: ['uut']
test_sections:
- section_name:
- my_custom_action:
device: PE1
key1: val1
key2: val2
Creating a custom section in Blitz¶
The behavior of a Blitz section also can be customized. Just like custom actions, to create a customized section, a class that inherits Blitz class should be created.
A function that represent the custom section should be created within said class and be decorated with @aetest.test
. Look at the below example.
import logging
from pyats import aetest
from genie.libs.sdk.triggers.blitz.blitz import Blitz
log = logging.getLogger()
class CustomBlitz(Blitz): # <- inheriting Blitz
@aetest.test
def my_custom_section(self, steps, testbed, data):
log.info("This is my custom section")
TestCustomAction:
source:
pkg: genie.libs.sdk
class: triggers.blitz.blitz.Blitz
devices: ['uut']
test_sections:
- my_custom_section:
- my_custom_action:
device: PE1
key1: val1
key2: val2
Useful tips and tricks in Blitz¶
Note
1- The name of the device that the action is being executed on will be saved automatically upon
execution of the action and stay usable till the end of that action life-cycle. You can use that
name as a variable using %VARIABLES{device.name}
for various purposes in your action.
2- Task id and transcript name also can be accessed by using %VARIABLES{task.id}
, %VARIABLES{transcript.name}
.
3- The result of a section (whether it is passed, failed etc.) will be saved automatically into a variable
same as the section name. You can use that name using %VARIABLES{<section_name>}
.
4- Also in your YAML file, it is possible to have access the section’s uid simply by using %VARIABLES{section.uid}
.
5- Job file related values, such as job file path or job file name can be accessed by using %VARIABLES{runtime.job.file}
and %VARIABLES{runtime.job.name}
. Any other job file related value can be accessed in similar fashion
%VARIABLES{runtime.job.<value>
Note
The starting message of a Step can be modified by specifying a custom message like the example below. This can be applied to all the actions supported in Blitz.
Note
`&&`
and and
have different functionalities. &&
is only useful to check if the result of an action is within a range of number
and
as well as or
should be used to write conditional statements.
# Blitz action with custom message
- execute:
command: show version
device: PE1
custom_start_step_message: My own message instead of the default one
timeout: 100
as shown in the image you can see how in the logs the starting message is customized.