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

Conversation

itdependsnetworks
Copy link
Contributor

Added canonical_interface and updated some linter complaints when I first had everything in string_parsers.py.

Didn't want to go to crazy until I had consensus on naming and implementation.

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.

@dbarrosop
Copy link
Member

I have a question regarding this PR:

Does cisco only hire sociopaths that hate people and deliberately make things horrible so they are harder to consume?

],
"VLAN_short": "Vl"
},
"os_map": {
Copy link
Member

Choose a reason for hiding this comment

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

Is there any reason to break this down by platform? Why not just have a single mapping for all?

@dbarrosop
Copy link
Member

dbarrosop commented Nov 6, 2017

I am a bit confused by the implementation. I might be missing something but I am not sure why:

  1. we have to split this up by interface.
  2. we have such a complicated dictionary.

I think this could be extremely simplified by doing something like:

def canonical_interface(interface, device_os, short=False):
    mapping = {
        'Po': 'Port-channel',
        'PO': 'POS',
        'Fa': 'FastEthernet',
        'Gi': 'GigabitEthernet',
        'Te': 'TenGigabitEthernet',
        ...
    }
    if short:
        interface_type = re.match('(\w+)\d.*', interface_name).groups(0)
        mapping = {v: k for k, v in map.items()}
    else:
        interface_type = interface_name[0:2]

    return mapping[interface_type]

Unless I am missing something, of course.

@mirceaulinic
Copy link
Member

Under napalm-automation/hackathon-2017#5 I had the feeling we have agreed this would be a method defined under each driver. Of course I wouldn't have a problem having this helper, but this way we'd solve a couple of things:

  • Avoids having such a complicated dictionary. (David's comment above) Instead, each driver keeps its own mapping.
  • You don't need to pass explicitly the OS -- it's more uniform to call driver. canonical_interface than canonical_interface(..., 'ios'), as that's already there, which will be useful for various things.
  • Solve platform / os / minor release version / other stupid differences locally, inside the driver. We'll probably need sooner or later to check if you're running a certain hardware/software platform combination to determine the right name, and inside the driver instance you already have these details.

@dbarrosop
Copy link
Member

dbarrosop commented Nov 6, 2017

  1. If there is a need for separation between OSes I agree with @mirceaulinic in that the mappings should live in each specific driver but I don't really see why we would need this separation so we could have the mapping in the base driver and override if necessary.
  2. Regarding the API, I think we agreed on two things:
    a. Specific method for this. I.e. device.get_canonical_interface_name('et1')
    b. A driver-wide optional arg that defaults to False and that tells the driver to return the canonical name instead of whatever the vendor provided.

@itdependsnetworks
Copy link
Contributor Author

  1. Separate per is fine, I was simply trying to de-duplicate where applicable, (e.g. Ethernet on eos, ios, xr, nxos, etc...). I see this as the opposite of inheritance, instead as subscribing to an interface type.

  2. I missed A, but will implement. I actually started on B with optional args, it didn't work on first try, and called it a night, but I will implement.

@itdependsnetworks
Copy link
Contributor Author

itdependsnetworks commented Nov 6, 2017

In regards to:

def canonical_interface(interface, device_os, short=False):
    mapping = {
        'Po': 'Port-channel',
        'PO': 'POS',

I think I am missing something, how does that account for PortChannel? The only way I see is to enumerate each potential, which seems worse than I have.

@dbarrosop
Copy link
Member

dbarrosop commented Nov 6, 2017

ffs why won't people stop buying cisco crap...

Anyway, you can add extra k,v pairs to the reverse mapping. But what's the use case for the short naming anyway? Are you expecting people to do something like:

# so much inception
my_short_normalized_name = device.get_normalized_name(device.get_normalized_name(iface), short=True)

@itdependsnetworks
Copy link
Contributor Author

So thought it if I wanted to use neighbors data to generate a diagram, I would want to use short name in diagram.

So some kind of toggle to force short name usage.

@dbarrosop
Copy link
Member

Ok, makes sense.

@dbarrosop dbarrosop closed this Nov 6, 2017
@itdependsnetworks
Copy link
Contributor Author

@dbarrosop Closed as in not gonna happen, hold off until reunification done, or need to update implementation??

@dbarrosop dbarrosop changed the base branch from reunification_core to develop November 6, 2017 16:48
@dbarrosop
Copy link
Member

no, sorry, I didn't close it. It was a side effect of deleting the branch. Reopining against develop

@itdependsnetworks
Copy link
Contributor Author

My todo:

  • create a method device.get_canonical_interface_name('et1')
  • create a setting that is applied globally

Outstanding questions:

  • Do I move the mappings to the specific driver? my initial response Separate per is fine, I was simply trying to de-duplicate where applicable, (e.g. Ethernet on eos, ios, xr, nxos, etc...). I see this as the opposite of inheritance, instead as subscribing to an interface type.
  • Are you ok with my non-regex approach?

Other than that, is everything good to go here?

@dbarrosop
Copy link
Member

I think the method should provide a predefined dictionary that doesn't care about vendors ("po" is going to a port-channel regardless of the platform or is simply not have any meaning).

Regarding non-regex approach, sure, if you can make it simpler than this #493 (comment) be my guest.

@itdependsnetworks
Copy link
Contributor Author

from napalm import get_network_driver

ios_device='10.1.100.1'
ios_user='ntc'
ios_password='ntc123'
driver = get_network_driver('ios')
optional_args = {"canonical_int": True}
device = driver(ios_device, ios_user, ios_password, optional_args=optional_args)
device.open()
device.get_mac_address_table()

Works as expected:
without:
{u'vlan': 1, u'last_move': -1.0, u'active': True, u'mac': u'2C:6B:F5:53:9D:8F', u'static': False, u'interface': u'Fa1/0/2', u'moves': -1}
with:
{u'vlan': 1, u'last_move': -1.0, u'active': True, u'mac': u'2C:6B:F5:53:9D:8F', u'static': False, u'interface': u'FastEthernet1/0/2', u'moves': -1}}

@coveralls
Copy link

coveralls commented Nov 16, 2017

Coverage Status

Coverage decreased (-0.3%) to 77.598% when pulling b2d2bfa on itdependsnetworks:canonical_interface into a04c348 on napalm-automation:develop.

@coveralls
Copy link

coveralls commented Nov 16, 2017

Coverage Status

Coverage decreased (-0.3%) to 77.598% when pulling b2d2bfa on itdependsnetworks:canonical_interface into a04c348 on napalm-automation:develop.

@@ -433,6 +435,13 @@ def commit_config(self):
# Save config to startup (both replace and merge)
output += self.device.send_command_expect("write mem")

def _canonical_int(self, interface):
Copy link
Member

Choose a reason for hiding this comment

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

I think this one should be in the base driver.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My thought process was

  • Within the os, the only variable is interface
  • Shorten from napalm.base.helpers.canonical_interface(interface, "cisco_ios", True) -> self._canonical_int(interface)

if not change:
return interface

if device_os == "cisco_nxos_ssh":
Copy link
Member

Choose a reason for hiding this comment

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

I'd get rid of the device_os tbh. I don't see much value in doing this check. What we could do instead is replace the device_os with a fail_on_miss bool that does something like:

try:
    return reverse_mapping[long_int] + str(interface_number)
except KeyError:
    if fail_on_miss:
          raise
    return long_int+str(interface_number)

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 super cautious in making sure I know I am not messing up something here. I'm fine either way, and in reality this only get's turned on if implemented in said os driver, so maybe not that big of an issue.

I would like to keep the transposing of cisco_nxos_ssh -> cisco_nxos in case there needs to be a base_interfaces = base_interfaces.update(interface_map.get(device_os)) on the nxos.

@@ -255,3 +256,47 @@ def as_number(as_number_val):
return (int(big) << 16) + int(little)
else:
return int(as_number_str)


def canonical_interface(interface, device_os, change, short=False):
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be two different methods; one for each direction. @mirceaulinic @ktbyers thoughts?

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'm ok either way. To clarify to M & K, one way is short to long (et -> Ethernet), the other is long to short (Ethernet -> eth ).

from __future__ import unicode_literals


def _interface_map():
Copy link
Member

Choose a reason for hiding this comment

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

Why don't you just expose the variables directly so you can do something like:

from napalm.base import interface_map

...
short_name = interface_map.long_to_short[iface_name]
long_name = interface_map.short_to_long[short_name]

int_map = _interface_map()
base_interfaces = int_map['base_interfaces']
reverse_mapping = int_map['reverse_mapping']
if int_map.get(device_os):
Copy link
Member

Choose a reason for hiding this comment

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

This should come from the driver itself.

class IOSDriver(NetworkDriver):
    # probably needs a better name but this is a class attribute
    long_to_short = {"my_unique_iface_name_that_collides_with_something_else": "ohmy"}
...

def canonical_interface(interface, device, change, short=False):
    ...
    base_interfaces = base_interfaces.update(device.long_to_short)
    ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Missed this before.

@coveralls
Copy link

coveralls commented Nov 17, 2017

Coverage Status

Coverage decreased (-0.3%) to 77.712% when pulling bec16e1 on itdependsnetworks:canonical_interface into 4b2fcbf on napalm-automation:develop.

@coveralls
Copy link

coveralls commented Nov 17, 2017

Coverage Status

Coverage decreased (-0.2%) to 77.726% when pulling 9d7487c on itdependsnetworks:canonical_interface into 4b2fcbf on napalm-automation:develop.

@ktbyers
Copy link
Contributor

ktbyers commented Nov 17, 2017

Per conversations with @itdependsnetworks I think Ken is going to move this to the parent class:

def _canonical_int(self, interface):

I will review after that is completed.

@coveralls
Copy link

coveralls commented Nov 17, 2017

Coverage Status

Coverage decreased (-0.2%) to 77.776% when pulling a5fac96 on itdependsnetworks:canonical_interface into 4b2fcbf on napalm-automation:develop.

Copy link
Contributor

@ktbyers ktbyers left a comment

Choose a reason for hiding this comment

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

See feedback.

@@ -128,6 +128,8 @@ def __init__(self, hostname, username, password, timeout=60, optional_args=None)

self.profile = ["ios"]

self.set_canonical_interface = optional_args.get('canonical_int', False)
Copy link
Contributor

Choose a reason for hiding this comment

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

How about self.use_canonical_int? I don't like set_canonical_interface as it makes me think we are doing a setter on an attribute.

@@ -255,3 +256,41 @@ def as_number(as_number_val):
return (int(big) << 16) + int(little)
else:
return int(as_number_str)


def canonical_interface(interface, change, short=False, update_long_to_short=None):
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not make this be?

def canonical_interface(interface, change=True, short=False, update_long_to_short=None):

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we even need change argument since we would always call this with True?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also you need to document the arguments here? Particularly short and update_long_to_short?

So update_long_to_short is a dictionary of additional names someone could feed in going from abbreviated names to full names? I don't think that name makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

np with it being change=False, but the idea was to not change it so drastically at first. @mirceaulinic (and I) had concerns about changing outcome so abruptly, perhaps next major version update, after we know it is good?

Documentation will be easier once everything has been agreed upon.

@@ -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.interface_map import interface_map
Copy link
Contributor

Choose a reason for hiding this comment

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

We shouldn't use the name interface_map since there is already an existing interface_map in the ios.py code.

@@ -1583,3 +1583,11 @@ 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):
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this probably should be a private method, for end-user calls people can just directly call what is in helpers.

from __future__ import unicode_literals


interface_map = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't be named interface_map due to existing name conflict.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also file name should not be interface_map.py.



interface_map = {
"base_interfaces": {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why a nested dictionary as opposed to two separate dictionaries? I think they should be two separate dictionaries.

if base_interfaces.get(interface_type):
long_int = base_interfaces.get(interface_type)
# return short form if set
if short is True:
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to overload this method with returning the abbreviated name or have a second method? I think it is more logical to have these as two separate methods.

  1. Method one returns the canonical name.
  2. Method two returns the abbreviated name (from the canonical name).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Was waiting on consensus on this

@coveralls
Copy link

coveralls commented Nov 17, 2017

Coverage Status

Coverage decreased (-0.2%) to 77.805% when pulling 24c5b39 on itdependsnetworks:canonical_interface into 4b2fcbf on napalm-automation:develop.

@itdependsnetworks
Copy link
Contributor Author

@mirceaulinic and @dbarrosop I think I have a +1 from Kirk. Let me know if anything should change, going to start implementing everywhere tomorrow my AM.

I will probably work on a proposal PR for writing output files tonight.

Copy link
Contributor

@ktbyers ktbyers left a comment

Choose a reason for hiding this comment

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

I reviewed this extensively with @itdependsnetworks.

@coveralls
Copy link

coveralls commented Nov 18, 2017

Coverage Status

Coverage decreased (-0.2%) to 77.787% when pulling 09a4772 on itdependsnetworks:canonical_interface into 4b2fcbf on napalm-automation:develop.

@ktbyers ktbyers merged commit bc4e6d8 into napalm-automation:develop Nov 18, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants