Skip to content

Commit

Permalink
Merge pull request #82 from networktocode/master
Browse files Browse the repository at this point in the history
Added NXOS module
  • Loading branch information
dbarrosop committed Nov 24, 2015
2 parents 0ed0ba1 + 5afdad7 commit f636c16
Show file tree
Hide file tree
Showing 13 changed files with 2,971 additions and 26 deletions.
55 changes: 29 additions & 26 deletions docs/support/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@ General support matrix
----------------------


===================== ========== ============= =========== ============== =============
_ EOS JunOS IOS-XR FortiOS IBM
===================== ========== ============= =========== ============== =============
**Driver Name** eos junos iosxr fortios ibm
**Structured data** Yes Yes No No Yes
**Minimum version** 4.15.0F 12.1 5.1.0 5.2.0 ???
**Backend library** `pyeapi`_ `junos-eznc`_ `pyIOSXR`_ `pyFG`_ `bnclient`_
**Caveats** :doc:`eos` :doc:`fortios` :doc:`ibm`
===================== ========== ============= =========== ============== =============
===================== ========== ============= =========== ============== ============= ============
_ EOS JunOS IOS-XR FortiOS IBM NXOS
===================== ========== ============= =========== ============== ============= ============
**Driver Name** eos junos iosxr fortios ibm nxos
**Structured data** Yes Yes No No Yes Yes
**Minimum version** 4.15.0F 12.1 5.1.0 5.2.0 ??? 6.1
**Backend library** `pyeapi`_ `junos-eznc`_ `pyIOSXR`_ `pyFG`_ `bnclient`_ `pycsco`_
**Caveats** :doc:`eos` :doc:`fortios` :doc:`ibm` :doc:`nxos`
===================== ========== ============= =========== ============== ============= ============

.. _pyeapi: https://github.com/arista-eosplus/pyeapi
.. _junos-eznc: https://github.com/Juniper/py-junos-eznc
.. _pyIOSXR: https://github.com/fooelisa/pyiosxr
.. _pyFG: https://github.com/spotify/pyfg
.. _bnclient: https://github.com/kderynski/blade-netconf-python-client
.. _pycsco: https://github.com/jedelman8/pycsco


.. warning:: Please, make sure you understand the caveats for your particular platforms before using the library.
Expand All @@ -28,19 +29,21 @@ _ EOS JunOS IOS-XR FortiOS
Configuration support matrix
----------------------------

===================== ========== ===== ========== ============== =============
_ EOS JunOS IOS-XR FortiOS IBM
===================== ========== ===== ========== ============== =============
**Config. replace** Yes Yes Yes Yes Yes [#c3]_
**Config. merge** Yes Yes Yes Yes Yes
**Compare config** Yes Yes Yes [#c1]_ Yes [#c1]_ Yes [#c1]_
**Atomic Changes** Yes Yes Yes No [#c2]_ No [#c2]_
**Rollback** Yes [#c2]_ Yes Yes Yes Yes [#c2]_
===================== ========== ===== ========== ============== =============
===================== ========== ===== ========== ============== ============= ==============
_ EOS JunOS IOS-XR FortiOS IBM NXOS
===================== ========== ===== ========== ============== ============= ==============
**Config. replace** Yes Yes Yes Yes Yes [#c3]_ Yes
**Config. merge** Yes Yes Yes Yes Yes Yes
**Compare config** Yes Yes Yes [#c1]_ Yes [#c1]_ Yes [#c1]_ Yes [#c4]_
**Atomic Changes** Yes Yes Yes No [#c2]_ No [#c2]_ Yes/No [#c5]_
**Rollback** Yes [#c2]_ Yes Yes Yes Yes [#c2]_ Yes/No [#c5]_
===================== ========== ===== ========== ============== ============= ==============

.. [#c1] Hand-crafted by the API as the device doesn't support the feature.
.. [#c2] Not supported but emulated. Check caveats.
.. [#c3] Check the caveats, this is a dangerous operation in this device.
.. [#c4] For merges, the diff is simply the merge config itself. See caveats.
.. [#c5] No for merges. See caveats.
.. warning:: Before building a workflow to deploy configuration it is important you understand what the table above means;
what are atomic changes and which devices support it, what does replacing or merging configuration mean, etc.
Expand All @@ -52,13 +55,13 @@ Getters support matrix
.. |yes| unicode:: U+02705 .. Yes
.. |no| unicode:: U+0274C .. No

====================== ===== ===== ====== ======= ======
_ EOS JunOS IOS-XR FortiOS IBM
====================== ===== ===== ====== ======= ======
**get_facts** |yes| |yes| |yes| |yes| |no|
**get_interfaces** |yes| |yes| |yes| |yes| |no|
**get_lldp_neighbors** |yes| |yes| |yes| |yes| |no|
====================== ===== ===== ====== ======= ======
====================== ===== ===== ====== ======= ====== ======
_ EOS JunOS IOS-XR FortiOS IBM NXOS
====================== ===== ===== ====== ======= ====== ======
**get_facts** |yes| |yes| |yes| |yes| |no| |yes|
**get_interfaces** |yes| |yes| |yes| |yes| |no| |yes|
**get_lldp_neighbors** |yes| |yes| |yes| |yes| |no| |yes|
====================== ===== ===== ====== ======= ====== ======

Caveats
-------
Expand All @@ -68,4 +71,4 @@ Caveats

eos
fortios
ibm
ibm
57 changes: 57 additions & 0 deletions docs/support/nxos.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
Notes on configuration replacement
==================================

Config files aren't aren't normal config files but special "checkpoint" files.
That's because on NXOS the only way to replace a config without reboot is to rollback to a checkpoint (which could be a file).
These files explicitly list a lot of normally implicit config lines, some of them starting with ``!#``.
The ``!#`` part isn't necessary for the rollback to work, but leaving these lines out can cause erratic behavior.
See the "Known gotchas" section below.

Prerequisites
-------------

Your device must be running NXOS 6.1. The features ``nxapi`` server ``scp-server`` must be enabled.
On the device and any checkpoint file you push, you must have the lines::
feature scp-server
feature nxapi


Getting a base checkpoint file
------------------------------

An example of a checkpoint file can be seen in ``test/unit/nxos/new_good.conf``.
You can get a checkpoint file representing your device's current config by running the ``get_checkpoint_file()``
function in the ``napalm.nxos`` driver.

Known gotchas
------------------------------

- Leaving out a ``shutdown`` or ``no shutdown`` line will cause the switch to toggle the up/down state of an interface, depending on it's current state.

- ``!#switchport trunk allowed vlan 1-4094`` is required even if the switchport is in ``switchport mode access``. However if ``!#switchport trunk allowed vlan 1-4094`` is included with ``no switchport``, the configuration replacement will fail.

- Vlans are listed vertically. For example ``vlan 1, 10, 20, 30`` will fail. To succeed, you need:
::

vlan 1
vlan 10
vlan 20
vlan 30

Diffs
---------

Diffs for config replacement are a list of commands that would be needed to take the device from it's current state
to the desired config state. See ``test/unit/nxos/new_good.diff`` as an example.

Notes on configuration merging
==============================

Merges are currently implemented by simply applying the the merge config line by line.
This doesn't use the checkpoint/rollback functionality.
As a result, merges are **not atomic**.

Diffs
-------

Diffs for merges are simply the lines in the merge candidate config.
2 changes: 2 additions & 0 deletions napalm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from iosxr import IOSXRDriver
from junos import JunOSDriver
from fortios import FortiOSDriver
from nxos import NXOSDriver
from ibm import IBMDriver

def get_network_driver(vendor):
Expand All @@ -27,6 +28,7 @@ def get_network_driver(vendor):
'JUNOS': JunOSDriver,
'JUNIPER': JunOSDriver,
'FORTIOS': FortiOSDriver,
'NXOS': NXOSDriver,
'IBM': IBMDriver,
}
try:
Expand Down
198 changes: 198 additions & 0 deletions napalm/nxos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Copyright 2015 Spotify AB. All rights reserved.
#
# The contents of this file are licensed under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

import tempfile
import re
from datetime import datetime


from base import NetworkDriver

from pycsco.nxos.device import Device as NXOSDevice
from pycsco.nxos.utils.file_copy import FileCopy
from pycsco.nxos.utils import install_config
from pycsco.nxos.utils import nxapi_lib
from pycsco.nxos.error import DiffError, FileTransferError, CLIError

from exceptions import MergeConfigException, ReplaceConfigException

def strip_trailing(string):
lines = list(x.rstrip(' ') for x in string.splitlines())
return '\n'.join(lines)


class NXOSDriver(NetworkDriver):

def __init__(self, hostname, username, password, timeout=60):
self.hostname = hostname
self.username = username
self.password = password
self.timeout = timeout
self.device = NXOSDevice(username=username,
password=password,
ip=hostname,
timeout=timeout)
self.replace = True
self.loaded = False
self.fc = None
self.changed = False

def open(self):
pass

def close(self):
if self.changed:
self._delete_file(self.backup_file)

def load_replace_candidate(self, filename=None, config=None):
self.replace = True
self.loaded = True

if filename is None:
temp_file = tempfile.NamedTemporaryFile()
temp_file.write(config)
temp_file.flush()
cfg_file_path = temp_file.name
else:
cfg_file_path = filename

self.fc = FileCopy(self.device, cfg_file_path)
if not self.fc.file_already_exists():
try:
self.fc.transfer_file()
except FileTransferError as fte:
raise ReplaceConfigException(fte.message)

def load_merge_candidate(self, filename=None, config=None):
self.replace = False
self.loaded = True
if filename is not None:
with open(filename, "r") as f:
self.merge_candidate = f.read()
else:
self.merge_candidate = config

def compare_config(self):
if self.loaded:
if not self.replace:
return self.merge_candidate
messy_diff = install_config.get_diff(self.device, self.fc.dst)
clean_diff = strip_trailing(messy_diff)
return clean_diff

return ''

def _commit_merge(self):
commands = self.merge_candidate.splitlines()
command_string = ';'.join(list(' %s ' % x.strip() for x in commands))
self.device.config(command_string)

def commit_config(self):
if self.loaded:
self.backup_file = 'config_' + str(datetime.now()).replace(' ', '_')
install_config.save_config(self.device, self.backup_file)
if self.replace:
if install_config.rollback(self.device, self.fc.dst) is False:
raise ReplaceConfigException
else:
try:
self._commit_merge()
except Exception as e:
raise MergeConfigException(str(e))

self.changed = True
self.loaded = False
else:
raise ReplaceConfigException('No config loaded.')

def _delete_file(self, filename):
self.device.show('terminal dont-ask', text=True)
self.device.show('delete {}'.format(filename), text=True)
self.device.show('no terminal dont-ask', text=True)

def discard_config(self):
if self.loaded and self.replace:
try:
self._delete_file(self.fc.dst)
except CLIError:
pass

self.loaded = False

def rollback(self):
if self.changed:
install_config.rollback(self.device, self.backup_file)
self.changed = False

def get_facts(self):
results = {}
facts_dict = nxapi_lib.get_facts(self.device)
results['uptime'] = -1 # not implemented
results['vendor'] = unicode('Cisco')
results['os_version'] = facts_dict.get('os')
results['serial_number'] = unicode('N/A')
results['model'] = facts_dict.get('platform')
results['hostname'] = facts_dict.get('hostname')
results['fqdn'] = unicode('N/A')
iface_list = results['interface_list'] = []

intf_dict = nxapi_lib.get_interfaces_dict(self.device)
for intf_list in intf_dict.values():
for intf in intf_list:
iface_list.append(intf)

return results

def get_interfaces(self):
results = {}
intf_dict = nxapi_lib.get_interfaces_dict(self.device)
for intf_list in intf_dict.values():
for intf in intf_list:
intf_info = nxapi_lib.get_interface(self.device, intf)
formatted_info = results[intf] = {}
formatted_info['is_up'] = 'up' in intf_info.get('state', intf_info.get('admin_state', '')).lower()
formatted_info['is_enabled'] = 'up' in intf_info.get('admin_state').lower()
formatted_info['description'] = unicode(intf_info.get('description'))
formatted_info['last_flapped'] = -1.0 #not implemented

speed = intf_info.get('speed', '0')
try:
speed = int(re.sub(r'[^\d]', '', speed).strip())
except ValueError:
speed = -1

formatted_info['speed'] = speed
formatted_info['mac_address'] = unicode(intf_info.get('mac_address', 'N/A'))

return results

def get_lldp_neighbors(self):
results = {}
neighbor_list = nxapi_lib.get_neighbors(self.device, 'lldp')
for neighbor in neighbor_list:
local_iface = neighbor.get('local_interface')
if neighbor.get(local_iface) is None:
if local_iface not in results:
results[local_iface] = []

neighbor_dict = {}
neighbor_dict['hostname'] = unicode(neighbor.get('neighbor'))
neighbor_dict['port'] = unicode(neighbor.get('neighbor_interface'))

results[local_iface].append(neighbor_dict)

return results

def get_checkpoint_file(self):
return install_config.get_checkpoint(self.device)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ ncclient
junos-eznc
pyIOSXR
pyFG
pycsco
bnclient

0 comments on commit f636c16

Please sign in to comment.