diff --git a/napalm_panos/panos.py b/napalm_panos/panos.py index d6074ce..f282d0d 100644 --- a/napalm_panos/panos.py +++ b/napalm_panos/panos.py @@ -26,18 +26,24 @@ import time # local modules -from napalm_base.utils.string_parsers import convert_uptime_string_seconds -from napalm_base.exceptions import ConnectionException, ReplaceConfigException,\ - MergeConfigException - try: + from napalm.base.utils.string_parsers import convert_uptime_string_seconds + from napalm.base.exceptions import ConnectionException + from napalm.base.exceptions import ReplaceConfigException + from napalm.base.exceptions import MergeConfigException from napalm.base import NetworkDriver + from napalm.base.utils import py23_compat except ImportError: + from napalm_base.utils.string_parsers import convert_uptime_string_seconds + from napalm_base.exceptions import ConnectionException + from napalm_base.exceptions import ReplaceConfigException + from napalm_base.exceptions import MergeConfigException from napalm_base.base import NetworkDriver + from napalm_base.utils import py23_compat + from napalm_base.utils import py23_compat from napalm_base.helpers import mac as standardize_mac - from netmiko import ConnectHandler from netmiko import __version__ as netmiko_version @@ -445,7 +451,7 @@ def get_route_to(self, destination='', protocol=''): routes_table_xml = xmltodict.parse(self.device.xml_root()) routes_table_json = json.dumps(routes_table_xml['response']['result']['entry']) routes_table = json.loads(routes_table_json) - except AttributeError: + except (AttributeError, KeyError): routes_table = [] if isinstance(routes_table, dict): @@ -554,3 +560,104 @@ def get_interfaces(self): interface_dict[intf] = interface return interface_dict + + def get_interfaces_ip(self): + '''Return IP interface data.''' + + def extract_ip_info(parsed_intf_dict): + ''' + IPv4: + - Primary IP is in the '' tag. If no v4 is configured the return value is 'N/A'. + - Secondary IP's are in ''. If no secondaries, this field is not returned by + the xmltodict.parse() method. + + IPv6: + - All addresses are returned in ''. If no v6 configured, this is not returned + either by xmltodict.parse(). + + Example of XML response for an intf with multiple IPv4 and IPv6 addresses: + + + + + + ethernet1/5 + + N/A + 1 + + + fe80::d61d:71ff:fed8:fe14/64 + 2001::1234/120 + + 0 + 169.254.0.1/30 + 20 + + 1.1.1.1/28 + + + {...} + + + {...} + + + + ''' + intf = parsed_intf_dict['name'] + _ip_info = {intf: {}} + + v4_ip = parsed_intf_dict.get('ip') + secondary_v4_ip = parsed_intf_dict.get('addr') + v6_ip = parsed_intf_dict.get('addr6') + + if v4_ip != 'N/A': + address, pref = v4_ip.split('/') + _ip_info[intf].setdefault('ipv4', {})[address] = {'prefix_length': int(pref)} + + if secondary_v4_ip is not None: + members = secondary_v4_ip['member'] + if not isinstance(members, list): + # If only 1 secondary IP is present, xmltodict converts field to a string, else + # it converts it to a list of strings. + members = [members] + for address in members: + address, pref = address.split('/') + _ip_info[intf].setdefault('ipv4', {})[address] = {'prefix_length': int(pref)} + + if v6_ip is not None: + members = v6_ip['member'] + if not isinstance(members, list): + # Same "1 vs many -> string vs list of strings" comment. + members = [members] + for address in members: + address, pref = address.split('/') + _ip_info[intf].setdefault('ipv6', {})[address] = {'prefix_length': int(pref)} + + # Reset dictionary if no addresses were found. + if _ip_info == {intf: {}}: + _ip_info = {} + + return _ip_info + + ip_interfaces = {} + cmd = "all" + + self.device.op(cmd=cmd) + interface_info_xml = xmltodict.parse(self.device.xml_root()) + interface_info_json = json.dumps( + interface_info_xml['response']['result']['ifnet']['entry'] + ) + interface_info = json.loads(interface_info_json) + + if isinstance(interface_info, dict): + # Same "1 vs many -> dict vs list of dicts" comment. + interface_info = [interface_info] + + for interface_dict in interface_info: + ip_info = extract_ip_info(interface_dict) + if ip_info: + ip_interfaces.update(ip_info) + + return ip_interfaces diff --git a/requirements.txt b/requirements.txt index e791047..048bf64 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -napalm-base>=0.18.0 +napalm>=2.3.0 pan-python netmiko>=1.0.0 requests-toolbelt diff --git a/setup.py b/setup.py index 31f2005..0ea227e 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="napalm-panos", - version="0.4.2", + version="0.5.1", packages=find_packages(), author="Gabriele Gerbino", author_email="gabriele@networktocode.com", diff --git a/test/unit/TestDriver.py b/test/unit/TestDriver.py index fd495a3..2304fd5 100644 --- a/test/unit/TestDriver.py +++ b/test/unit/TestDriver.py @@ -16,7 +16,7 @@ import unittest from napalm_panos import panos -from napalm_base.test.base import TestConfigNetworkDriver +from napalm.base.test.base import TestConfigNetworkDriver class TestConfigDriver(unittest.TestCase, TestConfigNetworkDriver): diff --git a/test/unit/conftest.py b/test/unit/conftest.py index 037731e..d188320 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -2,8 +2,8 @@ from builtins import super import pytest -from napalm_base.test import conftest as parent_conftest -from napalm_base.test.double import BaseTestDouble +from napalm.base.test import conftest as parent_conftest +from napalm.base.test.double import BaseTestDouble from napalm_panos import PANOSDriver as OriginalDriver diff --git a/test/unit/mocked_data/test_get_interfaces_ip/normal/_show__interface_all__interface___show_.xml b/test/unit/mocked_data/test_get_interfaces_ip/normal/_show__interface_all__interface___show_.xml new file mode 100644 index 0000000..9b20b3a --- /dev/null +++ b/test/unit/mocked_data/test_get_interfaces_ip/normal/_show__interface_all__interface___show_.xml @@ -0,0 +1,189 @@ + + + + +ethernet1/1 +foo +N/A +1 + + +0 +N/A +16 + + + +ethernet1/1.202 +foo +vr:default +1 + + +202 +157.191.8.17/28 +257 + + + +ethernet1/2 +Internet +vr:default +1 + + +0 +157.191.6.156/24 +18 + +2.2.2.2/24 + + + +ethernet1/3 + +N/A +1 + + +0 +N/A +19 + + + +ethernet1/3.301 + +vr:default +1 + + +301 +172.24.255.1/26 +258 + + + +ethernet1/4 + +N/A +1 + + +fe80::d61d:71ff:fed8:fe14/64 +2001::1234/120 + +0 +169.254.0.1/30 +20 + +1.1.1.1/28 + + + +loopback + +N/A +1 + + +0 +N/A +3 + + + +loopback.100 +Mbar +vr:default +1 + + +0 +N/A +256 +2001::7890/84 + + +tunnel + +N/A +1 + + +0 +N/A +4 + + + + + +ethernet1/1 +full +0 +up +10000/full/up +d4:1d:71:d8:fe:10 +(autoneg) +10000 +16 + + +ethernet1/2 +full +0 +up +10000/full/up +d4:1d:71:d8:fe:11 +(autoneg) +10000 +17 + + +ethernet1/3 +full +0 +up +10000/full/up +d4:1d:71:d8:fe:12 +(autoneg) +10000 +18 + + +ethernet1/4 +full +0 +up +10000/full/up +d4:1d:71:d8:fe:13 +(autoneg) +10000 +19 + + +loopback +[n/a] +5 +up +[n/a]/[n/a]/up +d4:1d:71:d8:fe:03 +(unknown) +[n/a] +3 + + +tunnel +[n/a] +6 +up +[n/a]/[n/a]/up +d4:1d:71:d8:fe:04 +(unknown) +[n/a] +4 + + + + \ No newline at end of file diff --git a/test/unit/mocked_data/test_get_interfaces_ip/normal/expected_result.json b/test/unit/mocked_data/test_get_interfaces_ip/normal/expected_result.json new file mode 100644 index 0000000..5ab3051 --- /dev/null +++ b/test/unit/mocked_data/test_get_interfaces_ip/normal/expected_result.json @@ -0,0 +1,44 @@ +{ + "ethernet1/2": { + "ipv4": { + "2.2.2.2": { + "prefix_length": 24 + }, + "157.191.6.156": { + "prefix_length": 24 + } + } + }, + "ethernet1/3.301": { + "ipv4": { + "172.24.255.1": { + "prefix_length": 26 + } + } + }, + "ethernet1/4": { + "ipv4": { + "1.1.1.1": { + "prefix_length": 28 + }, + "169.254.0.1": { + "prefix_length": 30 + } + }, + "ipv6": { + "fe80::d61d:71ff:fed8:fe14": { + "prefix_length": 64 + }, + "2001::1234": { + "prefix_length": 120 + } + } + }, + "ethernet1/1.202": { + "ipv4": { + "157.191.8.17": { + "prefix_length": 28 + } + } + } +} \ No newline at end of file diff --git a/test/unit/test_getters.py b/test/unit/test_getters.py index 6509001..4e4cb4e 100644 --- a/test/unit/test_getters.py +++ b/test/unit/test_getters.py @@ -1,6 +1,6 @@ """Tests for getters.""" -from napalm_base.test.getters import BaseTestGetters +from napalm.base.test.getters import BaseTestGetters import pytest