Skip to content

Commit

Permalink
Merge pull request napalm-automation-community#58 from napalm-automat…
Browse files Browse the repository at this point in the history
…ion-community/develop

Merge to Master
  • Loading branch information
itdependsnetworks authored May 16, 2018
2 parents ebf731f + 9210a81 commit ef17ce2
Show file tree
Hide file tree
Showing 9 changed files with 511 additions and 43 deletions.
185 changes: 152 additions & 33 deletions napalm_panos/panos.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@
from napalm.base.exceptions import MergeConfigException
from napalm.base import NetworkDriver
from napalm.base.utils import py23_compat
from napalm.base.helpers import mac as standardize_mac
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.helpers import mac as standardize_mac


from netmiko import ConnectHandler
Expand Down Expand Up @@ -349,6 +351,25 @@ def rollback(self):
except: # noqa
ReplaceConfigException("Error while loading backup config")

def _extract_interface_list(self):
self.device.op(cmd='<show><interface>all</interface></show>')
interfaces_xml = xmltodict.parse(self.device.xml_root())
interfaces_json = json.dumps(interfaces_xml['response']['result'])
interfaces = json.loads(interfaces_json)

interface_set = set()

for entry in interfaces.values():
for entry_contents in entry.values():
if isinstance(entry_contents, dict):
# If only 1 interface is listed, xmltodict returns a dictionary, otherwise
# it returns a list of dictionaries.
entry_contents = [entry_contents]
for intf in entry_contents:
interface_set.add(intf['name'])

return list(interface_set)

def get_facts(self):
facts = {}

Expand All @@ -360,14 +381,6 @@ def get_facts(self):
except AttributeError:
system_info = {}

try:
self.device.op(cmd='<show><interface>all</interface></show>')
interfaces_xml = xmltodict.parse(self.device.xml_root())
interfaces_json = json.dumps(interfaces_xml['response']['result'])
interfaces = json.loads(interfaces_json)
except AttributeError:
interfaces = {}

if system_info:
facts['hostname'] = system_info['hostname']
facts['vendor'] = py23_compat.text_type('Palo Alto Networks')
Expand All @@ -376,18 +389,10 @@ def get_facts(self):
facts['serial_number'] = system_info['serial']
facts['model'] = system_info['model']
facts['fqdn'] = py23_compat.text_type('N/A')
facts['interface_list'] = []

for element in interfaces:
for entry in interfaces[element]:
if isinstance(interfaces[element][entry], list):
for intf in interfaces[element][entry]:
if intf['name'] not in facts['interface_list']:
facts['interface_list'].append(intf['name'])
else:
if interfaces[element][entry]['name'] not in facts['interface_list']:
facts['interface_list'].append(interfaces[element][entry]['name'])
facts['interface_list'].sort()
facts['interface_list'] = self._extract_interface_list()

facts['interface_list'].sort()

return facts

def get_lldp_neighbors(self):
Expand Down Expand Up @@ -502,8 +507,16 @@ def get_route_to(self, destination='', protocol=''):
return routes

def get_interfaces(self):
LOOPBACK_SUBIF_DEFAULTS = {
'is_up': True,
'is_enabled': True,
'speed': 0,
'last_flapped': -1.0,
'mac_address': '',
'description': 'N/A'
}
interface_dict = {}
interface_list = self.get_facts()['interface_list']
interface_list = self._extract_interface_list()

for intf in interface_list:
interface = {}
Expand All @@ -514,28 +527,134 @@ def get_interfaces(self):
interface_info_xml = xmltodict.parse(self.device.xml_root())
interface_info_json = json.dumps(interface_info_xml['response']['result']['hw'])
interface_info = json.loads(interface_info_json)
except AttributeError:
interface_info = {}
except KeyError as err:
if 'loopback.' in intf and 'hw' in str(err):
# loopback sub-ifs don't return a 'hw' key
interface_dict[intf] = LOOPBACK_SUBIF_DEFAULTS
continue
raise

name = interface_info.get('name')
state = interface_info.get('state')
interface['is_up'] = interface_info.get('state') == 'up'

if state == 'up':
interface['is_up'] = True
conf_state = interface_info.get('state_c')
if conf_state == 'down':
interface['is_enabled'] = False
elif conf_state in ('up', 'auto'):
interface['is_enabled'] = True
else:
interface['is_up'] = False
interface['is_enabled'] = False
msg = 'Unknown configured state {} for interface {}'.format(conf_state, intf)
raise RuntimeError(msg)

interface['last_flapped'] = -1.0
interface['speed'] = interface_info.get('speed')
# Quick fix for loopback interfaces
if interface['speed'] == '[n/a]':
# Loopback and down interfaces
if interface['speed'] in ('[n/a]', 'unknown'):
interface['speed'] = 0
else:
interface['speed'] = int(interface['speed'])
interface['mac_address'] = interface_info.get('mac')
interface['mac_address'] = standardize_mac(interface_info.get('mac'))
interface['description'] = py23_compat.text_type('N/A')
interface_dict[name] = interface
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 '<ip>' tag. If no v4 is configured the return value is 'N/A'.
- Secondary IP's are in '<addr>'. If no secondaries, this field is not returned by
the xmltodict.parse() method.
IPv6:
- All addresses are returned in '<addr6>'. 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:
<response status="success">
<result>
<ifnet>
<entry>
<name>ethernet1/5</name>
<zone/>
<fwd>N/A</fwd>
<vsys>1</vsys>
<dyn-addr/>
<addr6>
<member>fe80::d61d:71ff:fed8:fe14/64</member>
<member>2001::1234/120</member>
</addr6>
<tag>0</tag>
<ip>169.254.0.1/30</ip>
<id>20</id>
<addr>
<member>1.1.1.1/28</member>
</addr>
</entry>
{...}
</ifnet>
<hw>
{...}
</hw>
</result>
</response>
'''
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 = "<show><interface>all</interface></show>"

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
9 changes: 3 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
"""setup.py file."""

import uuid

from setuptools import setup, find_packages
from pip.req import parse_requirements

__author__ = 'Gabriele Gerbino <[email protected]>'

install_reqs = parse_requirements('requirements.txt', session=uuid.uuid1())
reqs = [str(ir.req) for ir in install_reqs]
with open("requirements.txt", "r") as fs:
reqs = [r for r in fs.read().splitlines() if (len(r) > 0 and not r.startswith("#"))]

setup(
name="napalm-panos",
version="0.5.1",
version="0.5.2",
packages=find_packages(),
author="Gabriele Gerbino",
author_email="[email protected]",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@
<id>3</id>
<addr/>
</entry>
<entry>
<name>loopback.100</name>
<zone>foobar</zone>
<fwd>vr:default</fwd>
<vsys>1</vsys>
<dyn-addr/>
<addr6/>
<tag>0</tag>
<ip>155.1.1.1/32</ip>
<id>256</id>
<addr/>
</entry>
</ifnet>
<hw>
<entry>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<name>ethernet1/3</name>
<duplex>full</duplex>
<type>0</type>
<state_c>auto</state_c>
<state_c>down</state_c>
<mac>ba:db:ee:fb:ad:12</mac>
<state>up</state>
<state>down</state>
<duplex_c>auto</duplex_c>
<mode>none</mode>
<speed_c>auto</speed_c>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<type>0</type>
<state_c>auto</state_c>
<mac>ba:db:ee:fb:ad:13</mac>
<state>up</state>
<state>down</state>
<duplex_c>auto</duplex_c>
<mode>none</mode>
<speed_c>auto</speed_c>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<response status="success">
<result>
<ifnet>
<mssadjv4>0</mssadjv4>
<name>loopback.100</name>
<service/>
<tunnel/>
<vsys>vsys1</vsys>
<counters>
<ifnet>
<entry>
<icmp_frag>0</icmp_frag>
<ifwderrors>0</ifwderrors>
<ierrors>0</ierrors>
<macspoof>0</macspoof>
<pod>0</pod>
<flowstate>0</flowstate>
<ipspoof>0</ipspoof>
<teardrop>0</teardrop>
<ibytes>0</ibytes>
<noarp>0</noarp>
<noroute>0</noroute>
<noneigh>0</noneigh>
<nomac>0</nomac>
<l2_encap>0</l2_encap>
<zonechange>0</zonechange>
<obytes>0</obytes>
<land>0</land>
<name>loopback.100</name>
<neighpend>0</neighpend>
<ipackets>0</ipackets>
<opackets>0</opackets>
<l2_decap>0</l2_decap>
<idrops>0</idrops>
</entry>
</ifnet>
<hw/>
</counters>
<dyn-addr/>
<tcpmss>False</tcpmss>
<mtu>1500</mtu>
<addr6/>
<ra>False</ra>
<vr>default</vr>
<tag>0</tag>
<mode>layer3</mode>
<fwd_type>vr</fwd_type>
<mssadjv6>0</mssadjv6>
<zone>foobar</zone>
<id>256</id>
<mgt_subnet>False</mgt_subnet>
<addr>
<member>155.1.1.1/32</member>
</addr>
</ifnet>
<dp>dp0</dp>
</result>
</response>
Loading

0 comments on commit ef17ce2

Please sign in to comment.