nornir-automation / nornir

Pluggable multi-threaded framework with inventory management to help operate collections of devices
https://nornir.readthedocs.io/
Apache License 2.0
1.35k stars 226 forks source link

hostname Attribute error when NetBoxInventory2 plugin in use #779

Open ignatievia opened 2 years ago

ignatievia commented 2 years ago

Similar issue was described here - https://github.com/nornir-automation/nornir/issues/279 In my case primary_ip is defined in Netbox and populates 'hostname' attribute

print (nr.inventory.hosts["eve_cisco_ios"].hostname)
10.0.10.71
nr = InitNornir(
        inventory={
            "plugin": "NetBoxInventory2",
            "options": {
                "nb_url": NB_URL,
                "nb_token": NB_TOKEN,
                "filter_parameters": filter_params
            }
        }

nr.run(netmiko_send_config, config_commands=commands) gets back error

Traceback (most recent call last): File "/Users/ivan/project_name/venv/lib/python3.9/site-packages/nornir/core/task.py", line 99, in start r = self.task(self, **self.params) File "/Users/ivan/project_name/venv/lib/python3.9/site-packages/nornir_netmiko/tasks/netmiko_send_config.py", line 28, in netmiko_send_config net_connect = task.host.get_connection(CONNECTION_NAME, task.nornir.config) File "/Users/ivan/project_name/venv/lib/python3.9/site-packages/nornir/core/inventory.py", line 493, in get_connection conn = self.get_connection_parameters(connection) File "/Users/ivan/project_name/venv/lib/python3.9/site-packages/nornir/core/inventory.py", line 427, in get_connection_parameters r = self._get_connection_options_recursively(connection) File "/Users/ivan/project_name/venv/lib/python3.9/site-packages/nornir/core/inventory.py", line 456, in _get_connection_options_recursively sp = g._get_connection_options_recursively(connection) File "/Users/ivan/project_name/venv/lib/python3.9/site-packages/nornir/core/inventory.py", line 467, in _get_connection_options_recursively p.hostname = p.hostname if p.hostname is not None else sp.hostname AttributeError: 'dict' object has no attribute 'hostname'

ktbyers commented 2 years ago

What does the following show?

for host_obj in nr.inventory.hosts:
    print(host_obj.hostname)

Are there any devices that do not have a hostname?

ignatievia commented 2 years ago
for host_obj in nr.inventory.hosts:
...     print(host_obj.hostname)
...     
Traceback (most recent call last):
  File "<input>", line 2, in <module>
AttributeError: 'str' object has no attribute 'hostname'

while

print (nr.inventory.hosts)
{'eve_cisco_ios': Host: eve_cisco_ios}
print (nr.inventory.hosts["eve_cisco_ios"].hostname)
10.0.10.71

In this particular case there's only 1 device in inventory

ignatievia commented 2 years ago

May the root of the problem be in object type?

for host_obj in nr.inventory.hosts:
...     print(type(host_obj))    
... 
<class 'str'>
type (nr.inventory.hosts["eve_cisco_ios"])
<class 'nornir.core.inventory.Host'>
ktbyers commented 2 years ago

My error above...so what do you get with this:

for name, host_obj in nr.inventory.hosts.items():
        print(f"{name} -> {host_obj.hostname}")
ktbyers commented 2 years ago

You might need to look at the Python debugger and see what things are at this line 467:

File "/Users/ivan/project_name/venv/lib/python3.9/site-packages/nornir/core/inventory.py", line 467, in _get_connection_options_recursively
p.hostname = p.hostname if p.hostname is not None else sp.hostname

i.e. print out both p and `sp.

ignatievia commented 2 years ago
for name, host_obj in nr.inventory.hosts.items():
...         print(f"{name} -> {host_obj.hostname}")
...         
eve_cisco_ios -> 10.0.10.71

Below is pycharm screenshot with p and sp variables at the line 467 (sorry, have not found more convenient way to show it) https://drive.google.com/file/d/1dZV-sxS9xh0pP_4whwfR4uD9htmwmQLl/view?usp=sharing

dbarrosop commented 2 years ago

sp shouldn't be a dict. sp should be a ConnectionOptions object. Whoever is populating defaults.connection_options is doing it wrong.

Fore reference, here is the type definition:

https://github.com/nornir-automation/nornir/blob/develop/nornir/core/inventory.py#L216

and where sp is coming from:

https://github.com/nornir-automation/nornir/blob/develop/nornir/core/inventory.py#L465

However, according to your traceback and debug information sp is a dict.

This is most likely a plugin issue so I'd suggest checking with them.

davama commented 2 years ago

Fyi,

Faced same issue. Created ticket https://github.com/wvandeun/nornir_netbox/issues/40

thanks

wvandeun commented 2 years ago

@dbarrosop I may have identified the issue, but need more details to properly identify how this is triggered and to be able to confirm it.

The problem is here https://github.com/wvandeun/nornir_netbox/blob/860d2f80e3fd2b2989c33044c560decdd2685cf1/nornir_netbox/plugins/inventory/netbox.py#L47

We try to retrieve the connection_options from the dictionary data, but that key does not exist. The connection options are instead defined in data["data"] instead. The same issue exists on line 66.

These functions were directly copied from Nornir's Simple inventory plugin, so the same issue should also exist in that plugin: https://github.com/nornir-automation/nornir/blob/33076fa2209ba69e188c0ab1c034bdeb7a8bf112/nornir/plugins/inventory/simple.py#L44 The nornir_ansible inventory plugin seems to have the exact same approach as well btw: https://github.com/carlmontanari/nornir_ansible/blob/d00fa4788bbca5b24a18b9c15ccc6361bd890bd0/nornir_ansible/plugins/inventory/ansible.py#L471 (probably also copied from simple inventory)

If I modify the line to:

        connection_options=_get_connection_options(data.get("data", {}).get("connection_options", {})),

then it seems to work properly

>>> nr.inventory.defaults.connection_options
{'netmiko': <nornir.core.inventory.ConnectionOptions object at 0x107e4aa90>}
>>> nr.inventory.defaults.connection_options.get("netmiko").hostname
"10.10.10.10"
dbarrosop commented 2 years ago
connection_options=_get_connection_options(data.get("data", {}).get("connection_options", {})),

This doesn't look correct though. This would mean that your defaults_dict looks like:

data:
   connection_options:
          ...

And it shouldn't as connection_options is an attribute of Defaults, it doesn't belong inside data . Look at the test file for reference. So I suspect that the issue is simply that the defaults.yaml you are using is wrong.

If I am wrong, I'd suggest you create a branch with a unit test that triggers the issue and I will try to help out but I am confident this is not a nornir issue as we are actively testing this scenario.

ignatievia commented 2 years ago

Appreciate your help @ktbyers @dbarrosop The problem was in connection_options population and was fixed this way nr.inventory.defaults.connection_options["netmiko"] = ConnectionOptions(extras={'device_type': device_type}) Looks like it's a documentation problem cause I've found similar issue by @dmfigol https://github.com/nornir-automation/nornir/issues/369