Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/topology/validate.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ The **config** parameter can be a string (the template to deploy) or a dictionar
**Notes:**

* Every test entry should have **show**, **exec**, **config**, **suzieq**, **ansible** or **wait** parameter.
* The device types specified in **show**, **exec**, or **valid** dictionaries can be *parent devices* (for example, `ios` to cover all Cisco IOS platforms).
* A test entry with just the **wait** parameter is valid and can be used to delay the test procedure.
* Test entries with **show** parameter must have **valid** expression.
* Test entries with **valid** expression must have **show**, **exec**, **suzieq**, or **ansible** parameter.
Expand Down
3 changes: 2 additions & 1 deletion netsim/cli/validate/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ def execute_node_validation(
node = topology.nodes[n_name]
result = data.get_empty_box()

action = utils.find_test_action(v_entry,node) # Find the action to show/execute/wait
# Find the action to show/execute/wait
action = utils.find_test_action(v_entry,node,topology)
if action == 'wait': # Test with pure 'wait'
return (True,True) # is assumed to be successful

Expand Down
55 changes: 39 additions & 16 deletions netsim/cli/validate/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,46 @@

from box import Box, BoxList

from ...augment import devices as a_devices
from ...utils import log, templates
from . import plugin, report

'''
Get generic or per-device action from a validation entry

* If the validation entry is a string, use that
* If the validation entry is a dictionary, use device-specific item
* If the result of the above is not a string we have a failure, get out
* If the resulting string contains '{{' run it through Jinja2 engine
def get_device_inheritance_list(node: Box, topology: Box) -> list:
'''
Get the device inheritance list for the current node, starting with the
device type and ending with the most generic parent. If the device has no
'parent' attribute, the list has one element (the device type)
'''
dev_data = a_devices.get_consolidated_device_data(node,topology.defaults)
dev_search: list = dev_data.get('_parents',[]) + [ node.device ]
return list(reversed(dev_search))

If we try to get the string to pass to the device from a plugin, then any
plugin evaluation errors indicate something is badly broken, so we log the
error with as much data as feasible... and if the end-user ever sees that
error message, the author of the validation plugin did a lousy job.
'''
def get_entry_value(v_entry: Box, action: str, node: Box, topology: Box) -> typing.Any:
n_device = node.device
'''
Get generic or per-device action from a validation entry

* If the validation entry is a string, use that
* If the validation entry is a dictionary, use device-specific item
* If the result of the above is not a string we have a failure, get out
* If the resulting string contains '{{' run it through Jinja2 engine

If we try to get the string to pass to the device from a plugin, then any
plugin evaluation errors indicate something is badly broken, so we log the
error with as much data as feasible... and if the end-user ever sees that
error message, the author of the validation plugin did a lousy job.
'''
if action in v_entry:
value = v_entry[action][n_device] if isinstance(v_entry[action],dict) else v_entry[action]
value = None
if not isinstance(v_entry[action],dict):
value = v_entry[action]
else:
for device in get_device_inheritance_list(node,topology):
if device in v_entry[action]:
if log.VERBOSE and device != node.device:
log.info(f'Using {device} {action} definition for node {node.name}/{node.device}')
value = v_entry[action][device]
break
elif 'plugin' in v_entry:
try:
value = plugin.exec_plugin_function(action,v_entry,node)
Expand Down Expand Up @@ -81,7 +101,7 @@ def get_exec_list(v_entry: Box, action: str, node: Box, topology: Box) -> list:
* If there's no plugin, but we have 'wait' action, return 'wait'
* If everything fails, return None (nothing usable for the current node)
'''
def find_test_action(v_entry: Box, node: Box) -> typing.Optional[str]:
def find_test_action(v_entry: Box, node: Box, topology: Box) -> typing.Optional[str]:
action_kw_found = False
for kw in ('show','exec','config','suzieq','ansible'):
if kw not in v_entry:
Expand All @@ -90,8 +110,11 @@ def find_test_action(v_entry: Box, node: Box) -> typing.Optional[str]:
action_kw_found = True
if kw in ['suzieq','config'] or isinstance(v_entry[kw],(str,int)):
return kw
if node.device in v_entry[kw]:
return kw

# Iterate device options from most-specific (device) to most generic (top parent)
for device in get_device_inheritance_list(node,topology):
if device in v_entry[kw]:
return kw

if 'plugin' in v_entry:
return plugin.find_plugin_action(v_entry,node)
Expand Down
2 changes: 1 addition & 1 deletion netsim/defaults/attributes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ _v_option:
expression to evaluate.
type: dict
_alt_types: [ str ]
_keytype: device
_keytype: { type: device, include_templates: true }

_r_import:
_description: |
Expand Down
8 changes: 1 addition & 7 deletions tests/integration/services/01-dns-client.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,8 @@ validate:
wait_msg: Waiting for interfaces/DNS to wake up
exec:
eos: ping t1 repeat 3
iol: ping t1
ioll2: ping t1
iosv: ping t1
iosvl2: ping t1
csr: ping t1
cat8000v: ping t1
ios: ping t1
linux: ping -c 3 t1
bird: ping -c 3 t1
frr: ping -c 3 t1
valid: >-
"172.16.0.4" in stdout
Expand Down