parallel¶
In some testcases executing actions in parallel can save huge amount of time. In this section we will discuss how to execute multiple actions in parallel.
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 of configuring multiple devices in parallel.
- verify_configuration
- parallel:
- configure:
device: PE1
command: feature bgp
- configure:
device: PE2
command: feature bgp
- configure:
device: xe_rx
command: feature bgp
...
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
...
Note
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}"
Note
You can run multiple actions on one device in parallel. However to make sure that actions are actually running in parallel you need to have multiple sessions of your device open. You can learn how to have multiple sessions open on a device bt following this link.
loop¶
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 as explained in the table:
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.
Example-1: Loop over 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"
Example-2: Loop over a list of device names
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
parallel: True
actions:
- configure:
# The action name
alias: execute_
device: "%VARIABLES{devices}"
command: feature bgp
Note
In Example-2 script would configure 2 devices with same command in parallel, without writing duplicate blocks of text.
Example-3: Executing actions until the api output is passed
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
Example-4: Executing actions twice with range: 2
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:
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}"
Example-5: Looping over actions with regards to range that has an starting and ending point
In this example the range number will be printed. The starting number is 1 and the final number will be 5. Number 6 wont be included in the range.
- loop:
loop_variable_name: range_num
range: 1,6
actions:
- print:
item:
value: "%VARIABLES{range_num}"
Example-6: Synchronizing with every_seconds
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. Example-6 shows how every_seconds
work.
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.
Note
Keep in mind if an iteration takes more than 8 seconds the action will continue, it will not terminate after 8 seconds. The 8 second is the minimal interval between the loop.
- 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
Example-7: Looping over multiple values
In case that the goal is to loop over more than one iterable at the same time (over 2 or more list, a combination of lists or dict etc.),
you can define your loop_variable_name
to have a list of variable names along with a list of iterables. Blitz then would zip each iterable to its variable name
accordingly, and use items of multiple iterable within your actions that are iterating.
Below example attempts to reuse items of 3 different lists and print each list item. variable name a
is going to represent list [1,2]
, b
is going to represent list ['d', 'e']
,
and c
will map to [0, 98]
This way you are iterating over 3 different lists at the same using one single loop.
- loop:
loop_variable_name: ['a', 'b', 'c']
value:
- [1, 2]
- ['d', 'e']
- [0, 98]
actions:
- print:
item_a:
value: "%VARIABLES{a}"
item_b:
value: "%VARIABLES{b}"
item_c:
value: "%VARIABLES{c}"
The print action here would print [1, 'd', 0]
in the first iteration and in the next iteration it print [2, 'e', 98]
.
Note
Make sure that you have a variable name for each iterable that you are defining. Failure to do so would results in failure of the testcase.
Example-8: Nested looping in Blitz
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.
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
run_condition¶
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).
(Optional) An elif statement with boolean value (True or False statement).
(Optional) An else statement, the actions under else are executed when the booleans for the if and elif statements are all False.
A set of actions (e.g parse, execute etc.) that would be specified under keyword
actions
.(Optional) A function that can be the result of all the actions under run_condition if the boolean condition is equal True.
(Optional) A description for logging purposes
The function can be one from this list [passed, failed, aborted, skipped, blocked, errored, passx]
.
Note
The function will be applied only if the if statement is equal True, otherwise actions will be running normally.
Example-1: if statment == True
All the actions that are under this keyword will be executed
- run_condition:
if: "2000 == 2000"
actions:
- api:
device: "%{testbed.devices.PE1.alias}"
function: get_interface_mtu_size
save:
- variable_name: nbc
arguments:
interface: GigabitEthernet1
include:
- ">= 1400 && <= 1600"
- sleep:
sleep_time: 1
Example-2: if statment == False
All the actions that are under this keyword wont be executed
- run_condition:
if: "2000 != 2000"
actions:
- api:
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
Using the run_condition, users can evaluate various conditional statements before running their actions. Examples are provided below for these conditional statements.
Example-3: running an action if another section has passed
test:
source:
pkg: genie.libs.sdk
class: triggers.blitz.blitz.Blitz
devices: ['uut']
test_sections:
- plain_actions:
- sleep:
sleep_time: 10
- apply_config:
- run_condition:
if: "%VARIABLES{plain_actions} == passed"
actions:
- execute:
command: show version
device: uut
- sleep:
sleep_time: 1
Example-3: running an action if another action has passed
test:
source:
pkg: genie.libs.sdk
class: triggers.blitz.blitz.Blitz
devices: ['uut']
test_sections:
- apply_config:
- execute:
alias: execute_alias
command: show vrf
device: uut
include:
- parser
- run_condition:
if: "%VARIABLES{execute_alias} == passed"
actions:
- parse:
command: show version
device: uut
- sleep:
sleep_time: 1
Example-4: running an action if another saved_variable has the appropriate output
# 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"
actions:
- parse:
command: show version
device: uut
- sleep:
sleep_time: 1
Example-5: check multiple conditional statement
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"
actions:
- parse:
command: show version
device: uut
- sleep:
sleep_time: 1
Example-6: Running actions with if else condition
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: mtu1 # the 1500 is stored in mtu1
arguments:
interface: GigabitEthernet1
- run_condition:
- if: "%VARIABLES{mtu1} == 1200" # mtu1 output is 1500, if condition fails
actions:
- parse:
command: show version
device: uut
- sleep:
sleep_time: 1
- else: # actions under else is executed here
actions:
- print:
item1:
value: The mtu is not 1200
Example-7: Running actions with if, elif and else condition
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: mtu1 # the 1500 is stored in mtu1
arguments:
interface: GigabitEthernet1
- run_condition:
- if: "%VARIABLES{mtu1} == 1300"
actions:
- parse:
command: show version
device: uut
- sleep:
sleep_time: 1
- elif: "%VARIABLES{mtu1} == 1000"
actions:
- parse:
command: show ip interface brief
device: uut
- elif: "%VARIABLES{mtu1} == 1500" # mtu1 output is 1500, elif is executed
actions:
- parse:
command: show version
device: uut
- else:
actions:
- print:
item1:
value: The mtu does not match any given values.