Skip to content

Commit 48b0013

Browse files
committed
Merge branch 'master' into 58-write-wrapper-so-that-path-fspath-deprecation-warning-in-pytest-is-not-triggered
2 parents efa2dda + cf1cf61 commit 48b0013

35 files changed

Lines changed: 871 additions & 659 deletions

mypy.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[mypy]
2-
python_version = 3.6
2+
python_version = 3.7
33

44
### --strict
55
# warn_unused_configs = True

nuts/base_tests/napalm_bgp_neighbors.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from nornir_napalm.plugins.tasks import napalm_get
88

99
from nuts.context import NornirNutsContext
10-
from nuts.helpers.result import nuts_result_wrapper, NutsResult
10+
from nuts.helpers.filters import filter_hosts
11+
from nuts.helpers.result import map_host_to_nutsresult, NutsResult
1112

1213

1314
class BgpNeighborsContext(NornirNutsContext):
@@ -18,13 +19,10 @@ def nuts_arguments(self) -> Dict[str, List]:
1819
return {"getters": ["bgp_neighbors"]}
1920

2021
def nornir_filter(self) -> F:
21-
hosts = {entry["host"] for entry in self.nuts_parameters["test_data"]}
22-
return F(name__any=hosts)
22+
return filter_hosts(self.nuts_parameters["test_data"])
2323

2424
def transform_result(self, general_result: AggregatedResult) -> Dict[str, NutsResult]:
25-
return {
26-
host: nuts_result_wrapper(result, self._transform_host_results) for host, result in general_result.items()
27-
}
25+
return map_host_to_nutsresult(general_result, self._transform_host_results)
2826

2927
def _transform_host_results(self, single_result: MultiResult) -> dict:
3028
assert single_result[0].result is not None

nuts/base_tests/napalm_get_users.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
from nornir.core.task import MultiResult, AggregatedResult
77
from nornir_napalm.plugins.tasks import napalm_get
88

9+
910
from nuts.context import NornirNutsContext
10-
from nuts.helpers.result import nuts_result_wrapper, NutsResult
11+
from nuts.helpers.filters import filter_hosts
12+
from nuts.helpers.result import map_host_to_nutsresult, NutsResult
1113

1214

1315
class UsersContext(NornirNutsContext):
@@ -18,13 +20,10 @@ def nuts_arguments(self) -> Dict[str, List[str]]:
1820
return {"getters": ["users"]}
1921

2022
def nornir_filter(self) -> F:
21-
hosts = {entry["host"] for entry in self.nuts_parameters["test_data"]}
22-
return F(name__any=hosts)
23+
return filter_hosts(self.nuts_parameters["test_data"])
2324

2425
def transform_result(self, general_result: AggregatedResult) -> Dict[str, NutsResult]:
25-
return {
26-
host: nuts_result_wrapper(result, self._transform_host_results) for host, result in general_result.items()
27-
}
26+
return map_host_to_nutsresult(general_result, self._transform_host_results)
2827

2928
def _transform_host_results(self, single_result: MultiResult) -> Dict[str, Dict]:
3029
assert single_result[0].result is not None
@@ -52,4 +51,4 @@ def test_privilege_level(self, single_result, username, level):
5251
class TestNapalmOnlyDefinedUsersExist:
5352
@pytest.mark.nuts("host,usernames")
5453
def test_no_rogue_users(self, single_result, usernames):
55-
assert list(single_result.result.keys()) == usernames
54+
assert list(single_result.result) == usernames

nuts/base_tests/napalm_interfaces.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
from nornir.core.task import MultiResult, AggregatedResult
77
from nornir_napalm.plugins.tasks import napalm_get
88

9+
910
from nuts.context import NornirNutsContext
10-
from nuts.helpers.converters import InterfaceNameConverter
11-
from nuts.helpers.result import nuts_result_wrapper, NutsResult
11+
from nuts.helpers.filters import filter_hosts
12+
from nuts.helpers.result import NutsResult, map_host_to_nutsresult
1213

1314

1415
class InterfacesContext(NornirNutsContext):
@@ -19,13 +20,10 @@ def nuts_arguments(self) -> Dict[str, List[str]]:
1920
return {"getters": ["interfaces"]}
2021

2122
def nornir_filter(self) -> F:
22-
hosts = {entry["host"] for entry in self.nuts_parameters["test_data"]}
23-
return F(name__any=hosts)
23+
return filter_hosts(self.nuts_parameters["test_data"])
2424

2525
def transform_result(self, general_result: AggregatedResult) -> Dict[str, NutsResult]:
26-
return {
27-
host: nuts_result_wrapper(result, self._transform_host_results) for host, result in general_result.items()
28-
}
26+
return map_host_to_nutsresult(general_result, self._transform_host_results)
2927

3028
def _transform_host_results(self, single_result: MultiResult) -> dict:
3129
assert single_result[0].result is not None

nuts/base_tests/napalm_lldp_neighbors.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
from nuts.context import NornirNutsContext
1010
from nuts.helpers.converters import InterfaceNameConverter
11-
from nuts.helpers.result import nuts_result_wrapper, NutsResult
11+
from nuts.helpers.filters import filter_hosts
12+
from nuts.helpers.result import NutsResult, map_host_to_nutsresult
1213

1314

1415
class LldpNeighborsContext(NornirNutsContext):
@@ -19,13 +20,10 @@ def nuts_arguments(self) -> Dict[str, List]:
1920
return {"getters": ["lldp_neighbors_detail"]}
2021

2122
def nornir_filter(self) -> F:
22-
hosts = {entry["host"] for entry in self.nuts_parameters["test_data"]}
23-
return F(name__any=hosts)
23+
return filter_hosts(self.nuts_parameters["test_data"])
2424

2525
def transform_result(self, general_result: AggregatedResult) -> Dict[str, NutsResult]:
26-
return {
27-
host: nuts_result_wrapper(result, self._transform_host_results) for host, result in general_result.items()
28-
}
26+
return map_host_to_nutsresult(general_result, self._transform_host_results)
2927

3028
def _transform_host_results(self, single_result: MultiResult) -> dict:
3129
assert single_result[0].result is not None

nuts/base_tests/napalm_network_instances.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from nornir.core.task import MultiResult, AggregatedResult
77
from nornir_napalm.plugins.tasks import napalm_get
88

9-
from nuts.helpers.result import nuts_result_wrapper, NutsResult
9+
from nuts.helpers.filters import filter_hosts
10+
from nuts.helpers.result import nuts_result_wrapper, NutsResult, map_host_to_nutsresult
1011
from nuts.context import NornirNutsContext
1112

1213

@@ -18,15 +19,12 @@ def nuts_arguments(self) -> Dict[str, List[str]]:
1819
return {"getters": ["network_instances"]}
1920

2021
def nornir_filter(self) -> F:
21-
hosts = {entry["host"] for entry in self.nuts_parameters["test_data"]}
22-
return F(name__any=hosts)
22+
return filter_hosts(self.nuts_parameters["test_data"])
2323

2424
def transform_result(self, general_result: AggregatedResult) -> Dict[str, NutsResult]:
25-
return {
26-
host: nuts_result_wrapper(result, self._transform_single_result) for host, result in general_result.items()
27-
}
25+
return map_host_to_nutsresult(general_result, self._transform_host_results)
2826

29-
def _transform_single_result(self, single_result: MultiResult) -> dict:
27+
def _transform_host_results(self, single_result: MultiResult) -> dict:
3028
assert single_result[0].result is not None
3129
task_result = single_result[0].result
3230
network_instances = task_result["network_instances"]
@@ -45,7 +43,7 @@ def _extract_route_distinguisher(self, element: dict) -> str:
4543
return element["state"]["route_distinguisher"]
4644

4745
def _extract_interfaces(self, element: dict) -> List[str]:
48-
return list(element["interfaces"]["interface"].keys())
46+
return list(element["interfaces"]["interface"])
4947

5048

5149
CONTEXT = NetworkInstancesContext

nuts/base_tests/napalm_ping.py

Lines changed: 75 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,80 @@
11
"""Let a device ping another device."""
22
from enum import Enum
3-
from typing import Dict, List, Callable
3+
from typing import Dict, Callable, Any, List
44

55
import pytest
66
from nornir.core import Task
77
from nornir.core.filter import F
8-
from nornir.core.task import Result, MultiResult, AggregatedResult
8+
from nornir.core.task import Result, AggregatedResult
99
from nornir_napalm.plugins.tasks import napalm_ping
1010

1111
from 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

1525
class 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

3580
CONTEXT = PingContext
@@ -38,52 +83,29 @@ def transform_result(self, general_result: AggregatedResult) -> Dict[str, Dict[s
3883
@pytest.mark.usefixtures("check_nuts_result")
3984
class TestNapalmPing:
4085
@pytest.fixture
41-
def single_result(self, nornir_nuts_ctx: NornirNutsContext, host: str, destination: str) -> NutsResult:
42-
transformed_result = nornir_nuts_ctx.transformed_result()
43-
assert host in transformed_result, f"Host {host} not found in aggregated result."
44-
assert destination in transformed_result[host], f"Destination {destination} not found in result."
45-
return transformed_result[host][destination]
86+
def single_result(self, nuts_ctx: NornirNutsContext, host: str, destination: str) -> NutsResult:
87+
assert host in nuts_ctx.transformed_result, f"Host {host} not found in aggregated result."
88+
assert destination in nuts_ctx.transformed_result[host], f"Destination {destination} not found in result."
89+
return nuts_ctx.transformed_result[host][destination]
4690

4791
@pytest.mark.nuts("host,destination,expected")
4892
def test_ping(self, single_result, expected):
4993
assert single_result.result.name == expected
5094

5195

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)
96+
def _map_result_to_enum(result: Dict[str, Dict[Any, Any]], max_drop: int) -> Ping:
97+
"""
98+
Evaluates the ping that has been conducted with nornir and matches it
99+
to a Ping-Enum which can be either FAIL, SUCCESS or FLAPPING.
84100
101+
FAIL: Packet loss equals probes sent.
102+
SUCCESS: Packet loss is below or equal max_drop.
103+
FLAPPING: Everything else.
85104
86-
def _map_result_to_enum(result: dict, max_drop: int) -> Ping:
105+
:param result: a single nornir Result
106+
:param max_drop: max_drop threshold
107+
:return: evaluated ping result
108+
"""
87109
if result["success"]["packet_loss"] == result["success"]["probes_sent"]:
88110
return Ping.FAIL
89111
if result["success"]["packet_loss"] <= max_drop:

nuts/base_tests/netmiko_cdp_neighbors.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from nornir.core.task import MultiResult, AggregatedResult
77
from nornir_netmiko import netmiko_send_command
88

9-
from nuts.helpers.result import nuts_result_wrapper, NutsResult
9+
from nuts.helpers.filters import filter_hosts
10+
from nuts.helpers.result import NutsResult, map_host_to_nutsresult
1011
from nuts.context import NornirNutsContext
1112

1213

@@ -18,17 +19,14 @@ def nuts_arguments(self) -> dict:
1819
return {"command_string": "show cdp neighbors detail", "use_textfsm": True}
1920

2021
def nornir_filter(self) -> F:
21-
hosts = {entry["host"] for entry in self.nuts_parameters["test_data"]}
22-
return F(name__any=hosts)
22+
return filter_hosts(self.nuts_parameters["test_data"])
2323

2424
def _transform_host_results(self, host_results: MultiResult) -> dict:
2525
assert host_results[0].result is not None
2626
return {neighbor["destination_host"]: neighbor for neighbor in host_results[0].result}
2727

2828
def transform_result(self, general_result: AggregatedResult) -> Dict[str, NutsResult]:
29-
return {
30-
host: nuts_result_wrapper(result, self._transform_host_results) for host, result in general_result.items()
31-
}
29+
return map_host_to_nutsresult(general_result, self._transform_host_results)
3230

3331

3432
CONTEXT = CdpNeighborsContext

0 commit comments

Comments
 (0)