Repository: napalm-automation/napalm

Issue:
get_interfaces_ip for NXOS does not handle unnumbered interfaces
When an interface is an unnumbered interface (https://www.cisco.com/c/en/us/support/docs/ip/hot-standby-router-protocol-hsrp/13786-20.html#unnumbered_iface), napalm will raise an exception: 
```netaddr.core.AddrFormatError: failed to detect a valid IP address from None```
This is due to https://github.com/napalm-automation/napalm/blob/develop/napalm/nxos/nxos.py#L700, which passes None to IPAddress.

Either we should skip an interface that does not have a prefix assigned, and is designated unnumbered, or the prefix from the interface it is borrowing from should be used.


Discussion:

Resolving patch:
diff --git a/napalm/nxos/nxos.py b/napalm/nxos/nxos.py
--- a/napalm/nxos/nxos.py
+++ b/napalm/nxos/nxos.py
@@ -809,17 +809,36 @@ def get_interfaces_ip(self):
 
         for interface in ipv4_interf_table_vrf:
             interface_name = py23_compat.text_type(interface.get('intf-name', ''))
-            address = napalm.base.helpers.ip(interface.get('prefix'))
-            prefix = int(interface.get('masklen', ''))
-            if interface_name not in interfaces_ip.keys():
-                interfaces_ip[interface_name] = {}
-            if 'ipv4' not in interfaces_ip[interface_name].keys():
-                interfaces_ip[interface_name]['ipv4'] = {}
-            if address not in interfaces_ip[interface_name].get('ipv4'):
-                interfaces_ip[interface_name]['ipv4'][address] = {}
-            interfaces_ip[interface_name]['ipv4'][address].update({
-                'prefix_length': prefix
-            })
+            addr_str = interface.get('prefix')
+            unnumbered = py23_compat.text_type(interface.get('unnum-intf', ''))
+            if addr_str:
+                address = napalm.base.helpers.ip(addr_str)
+                prefix = int(interface.get('masklen', ''))
+                if interface_name not in interfaces_ip.keys():
+                    interfaces_ip[interface_name] = {}
+                if 'ipv4' not in interfaces_ip[interface_name].keys():
+                    interfaces_ip[interface_name]['ipv4'] = {}
+                if address not in interfaces_ip[interface_name].get('ipv4'):
+                    interfaces_ip[interface_name]['ipv4'][address] = {}
+                interfaces_ip[interface_name]['ipv4'][address].update({
+                    'prefix_length': prefix
+                })
+            elif unnumbered:
+                for interf in ipv4_interf_table_vrf:
+                    interf_name = py23_compat.text_type(interf.get('intf-name', ''))
+                    if interf_name == unnumbered:
+                        address = napalm.base.helpers.ip(interf.get('prefix'))
+                        prefix = int(interf.get('masklen', ''))
+                        if interface_name not in interfaces_ip.keys():
+                            interfaces_ip[interface_name] = {}
+                        if 'ipv4' not in interfaces_ip[interface_name].keys():
+                            interfaces_ip[interface_name]['ipv4'] = {}
+                        if address not in interfaces_ip[interface_name].get('ipv4'):
+                            interfaces_ip[interface_name]['ipv4'][address] = {}
+                        interfaces_ip[interface_name]['ipv4'][address].update({
+                            'prefix_length': prefix
+                        })
+
             secondary_addresses = interface.get('TABLE_secondary_address', {})\
                                            .get('ROW_secondary_address', [])
             if type(secondary_addresses) is dict:
@@ -840,7 +859,10 @@ def get_interfaces_ip(self):
 
         for interface in ipv6_interf_table_vrf:
             interface_name = py23_compat.text_type(interface.get('intf-name', ''))
-            address = napalm.base.helpers.ip(interface.get('addr', '').split('/')[0])
+            addr_str = interface.get('addr', '').split('/')[0]
+            if not addr_str:
+                continue
+            address = napalm.base.helpers.ip(addr_str)
             prefix = interface.get('prefix', '').split('/')[-1]
             if prefix:
                 prefix = int(interface.get('prefix', '').split('/')[-1])


Test patch:
diff --git a/test/nxos/mocked_data/test_get_interfaces_ip/normal/expected_result.json b/test/nxos/mocked_data/test_get_interfaces_ip/normal/expected_result.json
--- a/test/nxos/mocked_data/test_get_interfaces_ip/normal/expected_result.json
+++ b/test/nxos/mocked_data/test_get_interfaces_ip/normal/expected_result.json
@@ -5,6 +5,13 @@
                 "prefix_length": 24
             }
         }
+    },
+    "Ethernet1/2": {
+        "ipv4": {
+            "192.168.1.100": {
+                "prefix_length": 24
+            }
+        }
     }, 
     "Ethernet1/7": {
         "ipv6": {
diff --git a/test/nxos/mocked_data/test_get_interfaces_ip/normal/show_ip_interface.json b/test/nxos/mocked_data/test_get_interfaces_ip/normal/show_ip_interface.json
--- a/test/nxos/mocked_data/test_get_interfaces_ip/normal/show_ip_interface.json
+++ b/test/nxos/mocked_data/test_get_interfaces_ip/normal/show_ip_interface.json
@@ -159,6 +159,30 @@
             "urpf-mode": "none",
             "ip-ls-type": "none"
         }
+    }, {
+        "ROW_intf": {
+            "intf-name": "Ethernet1/2",
+            "proto-state": "up",
+            "link-state": "up",
+            "admin-state": "up",
+            "iod": "5",
+            "num-addr": "0",
+            "ip-disabled": "FALSE",
+            "num-maddr": "0",
+            "mtu": "1500",
+            "pref": "0",
+            "tag": "0",
+            "proxy-arp": "disabled",
+            "lcl-proxy-arp": "disabled",
+            "mrouting": "disabled",
+            "icmp-redirect": "enabled",
+            "dir-bcast": "disabled",
+            "ip-unreach": "disabled",
+            "port-unreach": "enabled",
+            "urpf-mode": "none",
+            "ip-ls-type": "none",
+            "unnum-intf": "loopback101"
+        }
     }, {
         "ROW_intf": {
             "intf-name": "Ethernet2/5",