Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add canonical_interface #493

Merged
7 changes: 7 additions & 0 deletions napalm/base/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1583,3 +1583,10 @@ def compliance_report(self, validation_file=None, validation_source=None):
"""
return validate.compliance_report(self, validation_file=validation_file,
validation_source=validation_source)

def _canonical_int(self, interface):
"""Expose the helper function within this class."""
if self.use_canonical_interface is True:
return napalm.base.helpers.canonical_interface_name(interface, update_os_mapping=None)
else:
return interface
101 changes: 101 additions & 0 deletions napalm/base/canonical_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from __future__ import print_function
from __future__ import unicode_literals


base_interfaces = {
"ATM": "ATM",
"AT": "ATM",
"EOBC": "EOBC",
"EO": "EOBC",
"Ethernet": "Ethernet",
"Eth": "Ethernet",
"Et": "Ethernet",
"FastEthernet": "FastEthernet",
"FastEth": "FastEthernet",
"FastE": "FastEthernet",
"Fast": "FastEthernet",
"Fas": "FastEthernet",
"FE": "FastEthernet",
"Fa": "FastEthernet",
"Fddi": "Fddi",
"FD": "Fddi",
"FortyGigabitEthernet": "FortyGigabitEthernet",
"FortyGigEthernet": "FortyGigabitEthernet",
"FortyGigEth": "FortyGigabitEthernet",
"FortyGigE": "FortyGigabitEthernet",
"FortyGig": "FortyGigabitEthernet",
"FGE": "FortyGigabitEthernet",
"FO": "FortyGigabitEthernet",
"Fo": "FortyGigabitEthernet",
"GigabitEthernet": "GigabitEthernet",
"GigEthernet": "GigabitEthernet",
"GigEth": "GigabitEthernet",
"GigE": "GigabitEthernet",
"Gig": "GigabitEthernet",
"GE": "GigabitEthernet",
"Gi": "GigabitEthernet",
"HundredGigabitEthernet": "HundredGigabitEthernet",
"HundredGigEthernet": "HundredGigabitEthernet",
"HundredGigEth": "HundredGigabitEthernet",
"HundredGigE": "HundredGigabitEthernet",
"HundredGig": "HundredGigabitEthernet",
"Hu": "HundredGigabitEthernet",
"Loopback": "Loopback",
"Lo": "Loopback",
"Management": "Management",
"Mgmt": "Management",
"Ma": "Management",
"Management_short": "Ma",
"MFR": "MFR",
"Multilink": "Multilink",
"Mu": "Multilink",
"PortChannel": "PortChannel",
"Port-Channel": "PortChannel",
"Po": "PortChannel",
"POS": "POS",
"PO": "POS",
"Serial": "Serial",
"Se": "Serial",
"S": "Serial",
"TenGigabitEthernet": "TenGigabitEthernet",
"TenGigEthernet": "TenGigabitEthernet",
"TenGigEth": "TenGigabitEthernet",
"TenGig": "TenGigabitEthernet",
"TeGig": "TenGigabitEthernet",
"Ten": "TenGigabitEthernet",
"T": "TenGigabitEthernet",
"Te": "TenGigabitEthernet",
"Tunnel": "Tunnel",
"Tun": "Tunnel",
"Tu": "Tunnel",
"Virtual-Access": "Virtual-Access",
"Vi": "Virtual-Access",
"Virtual-Template": "Virtual-Template",
"Vt": "Virtual-Template",
"VLAN": "VLAN",
"V": "VLAN",
"Vl": "VLAN"
}

reverse_mapping = {
"ATM": "At",
"EOBC": "EO",
"Ethernet": "Et",
"FastEthernet": "Fa",
"Fddi": "FD",
"FortyGigabitEthernet": "Fo",
"GigabitEthernet": "Gi",
"HundredGigabitEthernet": "Hu",
"Loopback": "Lo",
"Management": "Ma",
"MFR": "MFR",
"Multilink": "Mu",
"PortChannel": "Po",
"POS": "PO",
"Serial": "Se",
"TenGigabitEthernet": "Te",
"Tunnel": "Tu",
"Virtual-Access": "Vi",
"Virtual-Template": "Vt",
"VLAN": "Vl"
}
49 changes: 49 additions & 0 deletions napalm/base/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import napalm.base.exceptions
from napalm.base.utils.jinja_filters import CustomJinjaFilters
from napalm.base.utils import py23_compat
from napalm.base.canonical_map import base_interfaces, reverse_mapping


# ----------------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -255,3 +256,51 @@ def as_number(as_number_val):
return (int(big) << 16) + int(little)
else:
return int(as_number_str)

def int_split_on_match(split_interface):
'''
simple fuction to split on first digit, slash, or space match
'''
head = split_interface.rstrip(r'/\0123456789 ')
tail = split_interface[len(head):].lstrip()
return head, tail

def canonical_interface_name(interface, update_os_mapping=None):
'''
Function to retun interface canonical name
This puposely does not use regex, or first X characters, to ensure
there is no chance for false positives. As an example, Po = PortChannel, and
PO = POS. With either character or regex, that would produce a false positive.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regexes are case sensitive so po.* and Po.* are not going to cause any false positives. regexes has the advantage the code becomes simpler and more reliable as it should work if cisco in their next version decide to come up with yet another combination of short letters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am usually regex heavy, but having been bit by this issue nearly a decade ago, I am cautious about it. As an example, there is Virtual-Access and Virtual-Template, the idea of getting this right the first time, without being explicit does not seem likely.

This should be helpful only, and like I said, knowing the pain it has caused me before, I try and do as much as possible to to avoid false positive's.

'''

interface_type, interface_number = int_split_on_match(interface)

if isinstance(update_os_mapping, dict):
base_interfaces.update(update_os_mapping)
# check in dict for mapping
if base_interfaces.get(interface_type):
long_int = base_interfaces.get(interface_type)
return long_int + str(interface_number)
# if nothing matched, at least return the original
else:
return interface

def abbreviated_interface_name(interface, update_os_mapping=None):
'''
Function to retun interface canonical name
This puposely does not use regex, or first X characters, to ensure
there is no chance for false positives. As an example, Po = PortChannel, and
PO = POS. With either character or regex, that would produce a false positive.
'''

interface_type, interface_number = int_split_on_match(interface)

if isinstance(update_os_mapping, dict):
base_interfaces.update(update_os_mapping)
# check in dict for mapping
if base_interfaces.get(interface_type):
long_int = base_interfaces.get(interface_type)
return reverse_mapping[long_int] + str(interface_number)
# if nothing matched, at least return the original
else:
return interface
16 changes: 9 additions & 7 deletions napalm/base/utils/string_parsers.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
""" Common methods to normalize a string """
from __future__ import print_function
from __future__ import unicode_literals

import re


def convert(text):
"""Convert text to integer, if it is a digit."""
if text.isdigit():
return int(text)
else:
return text
return text


def alphanum_key(key):
""" split on end numbers."""
return [convert(c) for c in re.split('([0-9]+)', key)]


def sorted_nicely(l):
def sorted_nicely(sort_me):
""" Sort the given iterable in the way that humans expect."""
return sorted(l, key=alphanum_key)
return sorted(sort_me, key=alphanum_key)


def colon_separated_string_to_dict(string, separator=':'):
Expand Down Expand Up @@ -66,10 +68,10 @@ def hyphen_range(string):
if len(sub_element) == 1:
list_numbers.append(int(sub_element[0]))
elif len(sub_element) == 2:
for x in range(int(sub_element[0]), int(sub_element[1])+1):
list_numbers.append(x)
for number in range(int(sub_element[0]), int(sub_element[1])+1):
list_numbers.append(number)
else:
raise Exception('Something went wrong expanding the range'.format(string))
raise Exception('Something went wrong expanding the range {}'.format(string))

return list_numbers

Expand Down
4 changes: 3 additions & 1 deletion napalm/ios/ios.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ def __init__(self, hostname, username, password, timeout=60, optional_args=None)

self.profile = ["ios"]

self.use_canonical_interface = optional_args.get('canonical_int', False)

def open(self):
"""Open a connection to the device."""
device_type = 'cisco_ios'
Expand Down Expand Up @@ -1807,7 +1809,7 @@ def process_mac_fields(vlan, mac, mac_type, interface):
active = False
return {
'mac': napalm.base.helpers.mac(mac),
'interface': interface,
'interface': self._canonical_int(interface),
'vlan': int(vlan),
'static': static,
'active': active,
Expand Down