11"""Let a device ping another device."""
22from enum import Enum
3- from typing import Dict , List , Callable
3+ from typing import Dict , Callable , Any , List
44
55import pytest
66from nornir .core import Task
77from nornir .core .filter import F
8- from nornir .core .task import Result , MultiResult , AggregatedResult
8+ from nornir .core .task import Result , AggregatedResult
99from nornir_napalm .plugins .tasks import napalm_ping
1010
1111from nuts .context import NornirNutsContext
12- from nuts .helpers .result import nuts_result_wrapper , NutsResult
12+ from nuts .helpers .filters import filter_hosts
13+ from nuts .helpers .result import (
14+ NutsResult ,
15+ map_host_to_dest_to_nutsresult ,
16+ )
17+
18+
19+ class Ping (Enum ):
20+ FAIL = 0
21+ SUCCESS = 1
22+ FLAPPING = 2
1323
1424
1525class PingContext (NornirNutsContext ):
1626 def nuts_task (self ) -> Callable :
17- return napalm_ping_multi_host
18-
19- def nuts_arguments (self ) -> dict :
20- args = super ().nuts_arguments ()
21- args ["destinations_per_host" ] = _destinations_per_host (self .nuts_parameters ["test_data" ])
22- return args
27+ return self .napalm_ping_multi_dests
2328
2429 def nornir_filter (self ) -> F :
25- hosts = {entry ["host" ] for entry in self .nuts_parameters ["test_data" ]}
26- return F (name__any = hosts )
30+ return filter_hosts (self .nuts_parameters ["test_data" ])
2731
2832 def transform_result (self , general_result : AggregatedResult ) -> Dict [str , Dict [str , NutsResult ]]:
29- test_data = self .nuts_parameters ["test_data" ]
30- return {
31- host : _parse_ping_results (host , task_results , test_data ) for host , task_results in general_result .items ()
32- }
33+ return map_host_to_dest_to_nutsresult (general_result , self ._transform_single_entry )
34+
35+ def _transform_single_entry (self , single_result : Result ) -> Ping :
36+ assert single_result .host is not None
37+ assert single_result .result is not None
38+ max_drop = self ._allowed_max_drop_for_destination (single_result .host .name , single_result .destination ) # type: ignore[attr-defined] # see below
39+ return _map_result_to_enum (single_result .result , max_drop )
40+
41+ def _allowed_max_drop_for_destination (self , host : str , dest : str ) -> int :
42+ """
43+ Matches the host-destination pair from a nornir task to the
44+ host-destination pair from test_data and retrieves its
45+ max_drop value that has been defined for that pair.
46+
47+ :param host: host entry from the nornir task
48+ :param dest: destination that was pinged by a host from the nornir task
49+ :return: max_drop value from test_data
50+ """
51+ test_data : List [Dict [str , Any ]] = self .nuts_parameters ["test_data" ]
52+ for entry in test_data :
53+ if entry ["host" ] == host and entry ["destination" ] == dest :
54+ return entry ["max_drop" ]
55+ return 0
56+
57+ def napalm_ping_multi_dests (self , task : Task , ** kwargs ) -> Result :
58+ """
59+ One host pings all destinations as defined in the test bundle.
60+
61+ Note: The destination is not included in the nornir result if the ping fails.
62+ Therefore we cannot know which destination was not reachable,
63+ so we must patch the destination onto the result object to know later which
64+ host-destination pair actually failed.
65+
66+ :param task: nornir task for ping
67+ :param kwargs: arguments from the test bundle for the napalm ping task, such as
68+ count, ttl, timeout
69+ :return: all pinged destinations per host
70+ """
71+ destinations_per_hosts = [
72+ entry ["destination" ] for entry in self .nuts_parameters ["test_data" ] if entry ["host" ] == task .host .name
73+ ]
74+ for destination in destinations_per_hosts :
75+ result = task .run (task = napalm_ping , dest = destination , ** kwargs )
76+ result [0 ].destination = destination # type: ignore[attr-defined]
77+ return Result (host = task .host , result = "All pings executed" )
3378
3479
3580CONTEXT = PingContext
@@ -49,41 +94,19 @@ def test_ping(self, single_result, expected):
4994 assert single_result .result .name == expected
5095
5196
52- class Ping (Enum ):
53- FAIL = 0
54- SUCCESS = 1
55- FLAPPING = 2
56-
57-
58- def napalm_ping_multi_host (task : Task , destinations_per_host , ** kwargs ) -> Result :
59- destinations = destinations_per_host (task .host .name )
60- for destination in destinations :
61- result = task .run (task = napalm_ping , dest = destination , ** kwargs )
62- result [0 ].destination = destination # type: ignore[attr-defined]
63- return Result (host = task .host , result = "All pings executed" )
64-
65-
66- def _destinations_per_host (test_data ):
67- return lambda host_name : [entry ["destination" ] for entry in test_data if entry ["host" ] == host_name ]
68-
69-
70- def _parse_ping_results (host : str , task_results : MultiResult , test_data : List [dict ]) -> dict :
71- maxdrop_per_destination = {
72- entry ["destination" ]: entry .get ("max_drop" , 0 ) for entry in test_data if entry ["host" ] == host
73- }
74- return {
75- ping_task .destination : nuts_result_wrapper ( # type: ignore[attr-defined]
76- ping_task , _get_transform_single_entry (maxdrop_per_destination [ping_task .destination ]) # type: ignore[attr-defined]
77- )
78- for ping_task in task_results [1 :]
79- }
80-
81-
82- def _get_transform_single_entry (max_drop ):
83- return lambda ping_task : _map_result_to_enum (ping_task .result , max_drop )
97+ def _map_result_to_enum (result : Dict [str , Dict [Any , Any ]], max_drop : int ) -> Ping :
98+ """
99+ Evaluates the ping that has been conducted with nornir and matches it
100+ to a Ping-Enum which can be either FAIL, SUCCESS or FLAPPING.
84101
102+ FAIL: Packet loss equals probes sent.
103+ SUCCESS: Packet loss is below or equal max_drop.
104+ FLAPPING: Everything else.
85105
86- def _map_result_to_enum (result : dict , max_drop : int ) -> Ping :
106+ :param result: a single nornir Result
107+ :param max_drop: max_drop threshold
108+ :return: evaluated ping result
109+ """
87110 if result ["success" ]["packet_loss" ] == result ["success" ]["probes_sent" ]:
88111 return Ping .FAIL
89112 if result ["success" ]["packet_loss" ] <= max_drop :
0 commit comments