Repository: napalm-automation/napalm

Issue:
get_interfaces_ip fails when multiple ipv6 addresses configured
This was configured:

```
interface Ethernet2/4
  no switchport
  mac-address 2cc2.607d.5725
  ipv6 address 2001:db8:800:200c::2/64
  ipv6 address 2001:db8:800:200d::2/64
  no shutdown
```

Ansible playbook had the following:

```
    - name: NAPALM get interface config
      napalm_get_facts: 
        provider: "{{ creds_napalm }}"
        filter: "interfaces_ip"
```

Would get this error message back:

```
    "msg": "[interfaces_ip] cannot retrieve device data: 'list' object has no attribute 'split'"
```

Using NX-API

---

Issue moved from https://github.com/napalm-automation/napalm-nxos/issues/127


Discussion:
@mirceaulinic 
do you know how it looks it in json? I mean, I would test it adapting the tests in nxos/mocked_data/test_get_interfaces_ip:
```

"TABLE_intf": {
        "ROW_intf": {
            "intf-name": "Ethernet1/7",
            "proto-state": "down",
            "link-state": "down",
            "admin-state": "up",
            "addr": "fe80::5287:89ff:fea1:de75",
            "linklocal-addr": "fe80::5287:89ff:fea1:de75",
            "linklocal-configured": "FALSE",
            "mrouting-enabled": "disabled",
            "mgroup-locally-joined": "TRUE",
            "TABLE_maddr": {
                "ROW_maddr": [{
                    "m-addr": "ff02::2"
                }, {
                    "m-addr": "ff02::1"
                }, {
                    "m-addr": "ff02::1:ffa1:de75"
                }
```
I asked in case you had this info, but I will download a NXOS  to get real data 😉 

Resolving patch:
diff --git a/napalm/nxos/nxos.py b/napalm/nxos/nxos.py
--- a/napalm/nxos/nxos.py
+++ b/napalm/nxos/nxos.py
@@ -861,37 +861,33 @@ def get_interfaces_ip(self):
 
         for interface in ipv6_interf_table_vrf:
             interface_name = py23_compat.text_type(interface.get('intf-name', ''))
-            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])
-            else:
-                prefix = 128
+
             if interface_name not in interfaces_ip.keys():
                 interfaces_ip[interface_name] = {}
             if 'ipv6' not in interfaces_ip[interface_name].keys():
                 interfaces_ip[interface_name]['ipv6'] = {}
-            if address not in interfaces_ip[interface_name].get('ipv6'):
-                interfaces_ip[interface_name]['ipv6'][address] = {}
-            interfaces_ip[interface_name]['ipv6'][address].update({
-                'prefix_length': prefix
-            })
-            secondary_addresses = interface.get('TABLE_sec_addr', {}).get('ROW_sec_addr', [])
-            if type(secondary_addresses) is dict:
-                secondary_addresses = [secondary_addresses]
-            for secondary_address in secondary_addresses:
-                sec_prefix = secondary_address.get('sec-prefix', '').split('/')
-                secondary_address_ip = napalm.base.helpers.ip(sec_prefix[0])
-                secondary_address_prefix = int(sec_prefix[-1])
-                if 'ipv6' not in interfaces_ip[interface_name].keys():
-                    interfaces_ip[interface_name]['ipv6'] = {}
-                if secondary_address_ip not in interfaces_ip[interface_name].get('ipv6'):
-                    interfaces_ip[interface_name]['ipv6'][secondary_address_ip] = {}
-                interfaces_ip[interface_name]['ipv6'][secondary_address_ip].update({
-                    'prefix_length': secondary_address_prefix
+
+            if type(interface.get('addr', '')) is list:
+                for ipv6_address in interface.get('addr', ''):
+                    address = napalm.base.helpers.ip(ipv6_address.split('/')[0])
+                    prefix = int(ipv6_address.split('/')[-1])
+                    if address not in interfaces_ip[interface_name].get('ipv6'):
+                        interfaces_ip[interface_name]['ipv6'][address] = {}
+                    interfaces_ip[interface_name]['ipv6'][address].update({
+                        'prefix_length': prefix
+                    })
+            else:
+                address = napalm.base.helpers.ip(interface.get('addr', '').split('/')[0])
+                prefix = interface.get('prefix', '').split('/')[-1]
+                if prefix:
+                    prefix = int(interface.get('prefix', '').split('/')[-1])
+                else:
+                    prefix = 128
+
+                if address not in interfaces_ip[interface_name].get('ipv6'):
+                    interfaces_ip[interface_name]['ipv6'][address] = {}
+                interfaces_ip[interface_name]['ipv6'][address].update({
+                    'prefix_length': prefix
                 })
         return interfaces_ip
 


Test patch:
diff --git a/test/nxos/mocked_data/test_get_interfaces_ip_multiple_ipv6/normal/expected_result.json b/test/nxos/mocked_data/test_get_interfaces_ip_multiple_ipv6/normal/expected_result.json
new file mode 100644
--- /dev/null
+++ b/test/nxos/mocked_data/test_get_interfaces_ip_multiple_ipv6/normal/expected_result.json
@@ -0,0 +1,61 @@
+{
+    "Ethernet1/1": {
+        "ipv4": {
+            "172.16.100.1": {
+                "prefix_length": 24
+            }
+        }
+    }, 
+    "Ethernet1/7": {
+        "ipv6": {
+            "fe80::5287:89ff:fea1:de75": {
+                "prefix_length": 128
+            }
+        }
+    },
+    "Ethernet1/8": {
+        "ipv6": {
+            "2001:db8:800:200c::2": {
+                "prefix_length": 64
+            },
+            "2001:db8:800:200d::2": {
+                "prefix_length": 64
+            }
+        }
+    },
+    "loopback103": {
+        "ipv4": {
+            "192.168.3.100": {
+                "prefix_length": 24
+            }
+        }
+    }, 
+    "loopback102": {
+        "ipv4": {
+            "192.168.2.100": {
+                "prefix_length": 24
+            }
+        }
+    }, 
+    "loopback101": {
+        "ipv4": {
+            "192.168.1.100": {
+                "prefix_length": 24
+            }
+        }
+    }, 
+    "loopback100": {
+        "ipv4": {
+            "10.0.0.100": {
+                "prefix_length": 24
+            }
+        }
+    }, 
+    "Ethernet2/5": {
+        "ipv4": {
+            "1.1.1.1": {
+                "prefix_length": 24
+            }
+        }
+    }
+}
diff --git a/test/nxos/mocked_data/test_get_interfaces_ip_multiple_ipv6/normal/show_ip_interface.json b/test/nxos/mocked_data/test_get_interfaces_ip_multiple_ipv6/normal/show_ip_interface.json
new file mode 100644
--- /dev/null
+++ b/test/nxos/mocked_data/test_get_interfaces_ip_multiple_ipv6/normal/show_ip_interface.json
@@ -0,0 +1,190 @@
+{
+    "TABLE_vrf": [{
+        "ROW_vrf": {
+            "vrf-name-out": "default"
+        }
+    }, {
+        "ROW_vrf": {
+            "vrf-name-out": "default"
+        }
+    }, {
+        "ROW_vrf": {
+            "vrf-name-out": "default"
+        }
+    }, {
+        "ROW_vrf": {
+            "vrf-name-out": "default"
+        }
+    }, {
+        "ROW_vrf": {
+            "vrf-name-out": "default"
+        }
+    }, {
+        "ROW_vrf": {
+            "vrf-name-out": "default"
+        }
+    }],
+    "TABLE_intf": [{
+        "ROW_intf": {
+            "intf-name": "loopback100",
+            "proto-state": "up",
+            "link-state": "up",
+            "admin-state": "up",
+            "iod": "82",
+            "prefix": "10.0.0.100",
+            "subnet": "10.0.0.0",
+            "masklen": "24",
+            "num-addr": "1",
+            "ip-disabled": "FALSE",
+            "bcast-addr": "255.255.255.255",
+            "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"
+        }
+    }, {
+        "ROW_intf": {
+            "intf-name": "loopback101",
+            "proto-state": "up",
+            "link-state": "up",
+            "admin-state": "up",
+            "iod": "83",
+            "prefix": "192.168.1.100",
+            "subnet": "192.168.1.0",
+            "masklen": "24",
+            "num-addr": "1",
+            "ip-disabled": "FALSE",
+            "bcast-addr": "255.255.255.255",
+            "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"
+        }
+    }, {
+        "ROW_intf": {
+            "intf-name": "loopback102",
+            "proto-state": "up",
+            "link-state": "up",
+            "admin-state": "up",
+            "iod": "84",
+            "prefix": "192.168.2.100",
+            "subnet": "192.168.2.0",
+            "masklen": "24",
+            "num-addr": "1",
+            "ip-disabled": "FALSE",
+            "bcast-addr": "255.255.255.255",
+            "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"
+        }
+    }, {
+        "ROW_intf": {
+            "intf-name": "loopback103",
+            "proto-state": "up",
+            "link-state": "up",
+            "admin-state": "up",
+            "iod": "85",
+            "prefix": "192.168.3.100",
+            "subnet": "192.168.3.0",
+            "masklen": "24",
+            "num-addr": "1",
+            "ip-disabled": "FALSE",
+            "bcast-addr": "255.255.255.255",
+            "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"
+        }
+    }, {
+        "ROW_intf": {
+            "intf-name": "Ethernet1/1",
+            "proto-state": "up",
+            "link-state": "up",
+            "admin-state": "up",
+            "iod": "5",
+            "prefix": "172.16.100.1",
+            "subnet": "172.16.100.0",
+            "masklen": "24",
+            "num-addr": "1",
+            "ip-disabled": "FALSE",
+            "bcast-addr": "255.255.255.255",
+            "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"
+        }
+    }, {
+        "ROW_intf": {
+            "intf-name": "Ethernet2/5",
+            "proto-state": "down",
+            "link-state": "down",
+            "admin-state": "down",
+            "iod": "57",
+            "prefix": "1.1.1.1",
+            "subnet": "1.1.1.0",
+            "masklen": "24",
+            "num-addr": "1",
+            "ip-disabled": "FALSE",
+            "bcast-addr": "255.255.255.255",
+            "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"
+        }
+    }]
+}
diff --git a/test/nxos/mocked_data/test_get_interfaces_ip_multiple_ipv6/normal/show_ipv6_interface.json b/test/nxos/mocked_data/test_get_interfaces_ip_multiple_ipv6/normal/show_ipv6_interface.json
new file mode 100644
--- /dev/null
+++ b/test/nxos/mocked_data/test_get_interfaces_ip_multiple_ipv6/normal/show_ipv6_interface.json
@@ -0,0 +1,98 @@
+{
+    "TABLE_vrf": [{
+        "ROW_vrf": {
+            "vrf-name-out": "default"
+        }
+    }, {
+        "ROW_vrf": {
+            "vrf-name-out": "default"
+        }
+    }],
+    "TABLE_intf": [
+        {
+            "ROW_intf": {
+                "intf-name": "Ethernet1/7",
+                "proto-state": "down",
+                "link-state": "down",
+                "admin-state": "up",
+                "addr": "fe80::5287:89ff:fea1:de75",
+                "linklocal-addr": "fe80::5287:89ff:fea1:de75",
+                "linklocal-configured": "FALSE",
+                "mrouting-enabled": "disabled",
+                "mgroup-locally-joined": "TRUE",
+                "TABLE_maddr": {
+                    "ROW_maddr": [{
+                        "m-addr": "ff02::2"
+                    }, {
+                        "m-addr": "ff02::1"
+                    }, {
+                        "m-addr": "ff02::1:ffa1:de75"
+                    }]
+                },
+                "mtu": "1500",
+                "urpf-mode": "none",
+                "ipv6-lstype": "none",
+                "stats-last-reset": "never",
+                "upkt-fwd": "0",
+                "upkt-orig": "0",
+                "upkt-consumed": "0",
+                "ubyte-fwd": "0",
+                "ubyte-orig": "0",
+                "ubyte-consumed": "0",
+                "mpkt-fwd": "0",
+                "mpkt-orig": "0",
+                "mpkt-consumed": "0",
+                "mbyte-fwd": "0",
+                "mbyte-orig": "0",
+                "mbyte-consumed": "0"
+            }
+        },
+        {
+            "ROW_intf": {
+                "intf-name": "Ethernet1/8",
+                "proto-state": "down",
+                "link-state": "down",
+                "admin-state": "up",
+                "addr": [
+                    "2001:db8:800:200c::2/64",
+                    "2001:db8:800:200d::2/64"
+                ],
+                "prefix": "2001:db8:800:200c::/64",
+                "linklocal-addr": "fe80::200:ff:fe00:2f",
+                "linklocal-configured": "FALSE",
+                "mrouting-enabled": "disabled",
+                "mgroup-locally-joined": "TRUE",
+                "TABLE_maddr": {
+                    "ROW_maddr": [
+                    {
+                        "m-addr": "ff02::1:ff00:2"
+                    }, {
+                        "m-addr": "ff02::1:ff00:0"
+                    }, {
+                        "m-addr": "ff02::2"
+                    }, {
+                        "m-addr": "ff02::1"
+                    }, {
+                        "m-addr": "ff02::1:ff00:2f"
+                    }]
+                },
+                "mtu": "1500",
+                "urpf-mode": "none",
+                "ipv6-lstype": "none",
+                "stats-last-reset": "never",
+                "upkt-fwd": "0",
+                "upkt-orig": "0",
+                "upkt-consumed": "0",
+                "ubyte-fwd": "0",
+                "ubyte-orig": "0",
+                "ubyte-consumed": "0",
+                "mpkt-fwd": "0",
+                "mpkt-orig": "0",
+                "mpkt-consumed": "0",
+                "mbyte-fwd": "0",
+                "mbyte-orig": "0",
+                "mbyte-consumed": "0"
+            }
+        }
+    ]
+}
diff --git a/test/nxos/test_getters.py b/test/nxos/test_getters.py
--- a/test/nxos/test_getters.py
+++ b/test/nxos/test_getters.py
@@ -26,3 +26,19 @@ def test_get_interfaces(self, test_case):
             assert helpers.test_model(models.interface, interface_data)
 
         return get_interfaces
+
+    @wrap_test_cases
+    def test_get_interfaces_ip_multiple_ipv6(self, test_case):
+        """Test get_interfaces_ip. Covers cases when multiple IPv6 addresses are returned"""
+        get_interfaces_ip = self.device.get_interfaces_ip()
+        assert len(get_interfaces_ip) > 0
+
+        for interface, interface_details in get_interfaces_ip.items():
+            ipv4 = interface_details.get('ipv4', {})
+            ipv6 = interface_details.get('ipv6', {})
+            for ip, ip_details in ipv4.items():
+                assert helpers.test_model(models.interfaces_ip, ip_details)
+            for ip, ip_details in ipv6.items():
+                assert helpers.test_model(models.interfaces_ip, ip_details)
+
+        return get_interfaces_ip