Skip to content

Commit

Permalink
improvements to data resolution (#623)
Browse files Browse the repository at this point in the history
  • Loading branch information
dbarrosop committed Jan 11, 2021
1 parent 492f5e6 commit 1494e1d
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 66 deletions.
2 changes: 1 addition & 1 deletion docs/tutorial/inventory.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,7 @@
{
"data": {
"text/plain": [
"'acme.local'"
"'global.local'"
]
},
"execution_count": 13,
Expand Down
76 changes: 59 additions & 17 deletions nornir/core/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ def schema(cls) -> Dict[str, Any]:

def dict(self) -> Dict[str, Any]:
return {
"hostname": self.hostname,
"port": self.port,
"username": self.username,
"password": self.password,
"platform": self.platform,
"hostname": object.__getattribute__(self, "hostname"),
"port": object.__getattribute__(self, "port"),
"username": object.__getattribute__(self, "username"),
"password": object.__getattribute__(self, "password"),
"platform": object.__getattribute__(self, "platform"),
}


Expand Down Expand Up @@ -162,6 +162,45 @@ def dict(self) -> Dict[str, Any]:
**super().dict(),
}

def extended_groups(self) -> List["Group"]:
"""
Returns the groups this host belongs to by virtue of inheritance.
This list is ordered based on the inheritance rules and groups are not
duplicated. For instance, given a host with the following groups:
hostA:
groups:
- group_a
- group_b
group_a:
groups:
- group_1
- group_2
group_b:
groups:
- group_2
- group_3
group_1:
groups:
- group_X
this will return [group_a, group_1, group_X, group_2, group_b, group_3]
"""
groups: List["Group"] = []

for g in self.groups:
if g not in groups:
groups.append(g)

for sg in g.extended_groups():
if sg not in groups:
groups.append(sg)

return groups


class Defaults(BaseAttributes):
__slots__ = ("data", "connection_options")
Expand Down Expand Up @@ -234,14 +273,17 @@ def __init__(
connection_options=connection_options,
)

def _resolve_data(self) -> Dict[str, Any]:
def extended_data(self) -> Dict[str, Any]:
"""
Returns the data associated with the object including inherited data
"""
processed = []
result = {}
for k, v in self.data.items():
processed.append(k)
result[k] = v
for g in self.groups:
for k, v in g.items():
for g in self.extended_groups():
for k, v in g.data.items():
if k not in processed:
processed.append(k)
result[k] = v
Expand Down Expand Up @@ -270,18 +312,18 @@ def dict(self) -> Dict[str, Any]:

def keys(self) -> KeysView[str]:
"""Returns the keys of the attribute ``data`` and of the parent(s) groups."""
return self._resolve_data().keys()
return self.extended_data().keys()

def values(self) -> ValuesView[Any]:
"""Returns the values of the attribute ``data`` and of the parent(s) groups."""
return self._resolve_data().values()
return self.extended_data().values()

def items(self) -> ItemsView[str, Any]:
"""
Returns all the data accessible from a device, including
the one inherited from parent groups
"""
return self._resolve_data().items()
return self.extended_data().items()

def has_parent_group(self, group: Union[str, "Group"]) -> bool:
"""Returns whether the object is a child of the :obj:`Group` ``group``"""
Expand All @@ -308,9 +350,9 @@ def __getitem__(self, item: str) -> Any:
return self.data[item]

except KeyError:
for g in self.groups:
for g in self.extended_groups():
try:
r = g[item]
r = g.data[item]
return r
except KeyError:
continue
Expand All @@ -326,8 +368,8 @@ def __getattribute__(self, name: str) -> Any:
return object.__getattribute__(self, name)
v = object.__getattribute__(self, name)
if v is None:
for g in self.groups:
r = getattr(g, name)
for g in self.extended_groups():
r = object.__getattribute__(g, name)
if r is not None:
return r

Expand All @@ -342,10 +384,10 @@ def __setitem__(self, item: str, value: Any) -> None:
self.data[item] = value

def __len__(self) -> int:
return len(self._resolve_data().keys())
return len(self.extended_data().keys())

def __iter__(self) -> Iterator[str]:
return self.data.__iter__()
return self.extended_data().__iter__()

def __str__(self) -> str:
return self.name
Expand Down
30 changes: 26 additions & 4 deletions tests/core/test_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ def test_combined(self, nornir):
f = F(site="site2") | (F(role="www") & F(my_var="comes_from_dev1.group_1"))
filtered = sorted(list((nornir.inventory.filter(f).hosts.keys())))

assert filtered == ["dev1.group_1", "dev3.group_2", "dev4.group_2"]
assert filtered == [
"dev1.group_1",
"dev3.group_2",
"dev4.group_2",
"dev6.group_3",
]

f = (F(site="site2") | F(role="www")) & F(my_var="comes_from_dev1.group_1")
filtered = sorted(list((nornir.inventory.filter(f).hosts.keys())))
Expand All @@ -41,7 +46,12 @@ def test_negate(self, nornir):
f = ~F(groups__contains="group_1")
filtered = sorted(list((nornir.inventory.filter(f).hosts.keys())))

assert filtered == ["dev3.group_2", "dev4.group_2", "dev5.no_group"]
assert filtered == [
"dev3.group_2",
"dev4.group_2",
"dev5.no_group",
"dev6.group_3",
]

def test_negate_and_second_negate(self, nornir):
f = F(site="site1") & ~F(role="www")
Expand All @@ -58,6 +68,7 @@ def test_negate_or_both_negate(self, nornir):
"dev3.group_2",
"dev4.group_2",
"dev5.no_group",
"dev6.group_3",
]

def test_nested_data_a_string(self, nornir):
Expand Down Expand Up @@ -93,6 +104,7 @@ def test_nested_data_a_dict_doesnt_contain(self, nornir):
"dev3.group_2",
"dev4.group_2",
"dev5.no_group",
"dev6.group_3",
]

def test_nested_data_a_list_contains(self, nornir):
Expand All @@ -105,7 +117,12 @@ def test_filtering_by_callable_has_parent_group(self, nornir):
f = F(has_parent_group="parent_group")
filtered = sorted(list((nornir.inventory.filter(f).hosts.keys())))

assert filtered == ["dev1.group_1", "dev2.group_1", "dev4.group_2"]
assert filtered == [
"dev1.group_1",
"dev2.group_1",
"dev4.group_2",
"dev6.group_3",
]

def test_filtering_by_attribute_name(self, nornir):
f = F(name="dev1.group_1")
Expand All @@ -117,7 +134,12 @@ def test_filtering_string_in_list(self, nornir):
f = F(platform__in=["linux", "mock"])
filtered = sorted(list((nornir.inventory.filter(f).hosts.keys())))

assert filtered == ["dev3.group_2", "dev4.group_2", "dev5.no_group"]
assert filtered == [
"dev3.group_2",
"dev4.group_2",
"dev5.no_group",
"dev6.group_3",
]

def test_filtering_list_any(self, nornir):
f = F(nested_data__a_list__any=[1, 3])
Expand Down

0 comments on commit 1494e1d

Please sign in to comment.