Skip to content

Commit

Permalink
Merge pull request #25 from napalm-automation-community/master
Browse files Browse the repository at this point in the history
Update develop branch
  • Loading branch information
ppieprzycki authored Apr 8, 2018
2 parents 6e68dba + 2278bdd commit f1549d8
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 42 deletions.
96 changes: 96 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,99 @@
[![Build Status](https://travis-ci.org/napalm-automation-community/napalm-vyos.svg?branch=master)](https://travis-ci.org/napalm-automation-community/napalm-vyos)

# napalm-vyos

Community NAPALM driver for the VyOS. https://vyos.io/

Also it can be used for other similar systems: Vyatta or EdgeOS


General support matrix
----------------------


| | VyOS |
|--------------------|--------------|
|**Module Name** | napalm-vyos |
|**Driver Name** | vyos |
|**Structured data** | Yes |
|**Minimum version** | 1.1.6 |
|**Backend library** | netmiko |



Configuration support matrix
----------------------------

| | VyOS |
| ------------------- | ----- |
| **Config. replace** | Yes |
| **Config. merge** | Yes |
|**Compare config** | Yes |
| **Atomic Changes** | Yes |
| **Rollback** | Yes |



Optional arguments
------------------

NAPALM supports passing certain optional arguments to some drivers. To do that you have to pass a dictionary via the
:code:`optional_args` parameter when creating the object::

>>> from napalm import get_network_driver
>>> driver = get_network_driver('vyos')
>>> optional_args = {'my_optional_arg1': 'my_value1', 'my_optional_arg2': 'my_value2'}
>>> device = driver('192.168.76.10', 'vagrant', 'this_is_not_a_secure_password', optional_args=optional_args)
>>> device.open()

List of supported optional arguments
____________________________________

* :code:`port` (vyos) - Allows you to specify a port other than the default.
* :code:`key_file` (vyos) - Netmiko/Paramiko argument, path to a private key file (default: 'False').



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


VyOS has no native HTTP API or NETCONF capability.
We are using Netmiko for ssh connections and scp file transfers.
Having Netmiko installed in your working box is a prerequisite.

VyOS in version 1.1.x (tested 1.1.7)

napalm==2.*
paramiko
netmiko>=1.1.0
vyattaconfparser



Configuration file
------------------

Currently VyOS driver supports two different configuration formats:
* load_replace_candidate - Full config file (with brackets) like in /config/config.boot
Due to the OS nature, we do not support a replace using
a set-style configuration format.
* load_merge_candidate - Currently driver supports set-style configuration format.
Example

`set system login banner pre-login "test"`

Vyos require configuration file (load_replace) to contain comment like following

`/* Warning: Do not remove the following line. */
/* === vyatta-config-version: "cluster@1:config-management@1:conntrack-sync@1:conntrack@1:cron@1:dhcp-relay@1:dhcp-server@4:firewall@5:ipsec@4:nat@4:qos@1:quagga@2:system@6:vrrp@1:wanloadbalance@3:webgui@1:webproxy@1:zone-policy@1" === */
/* Release version: VyOS 1.1.7 */`

Otherwise VyOS reject `load` operation

Notes
------------------
* The NAPALM-vyos driver supports all Netmiko arguments as either standard arguments (hostname, username, password, timeout) or as optional_args (everything else).

* The NAPALM-vyos driver supports authentication with ssh key. Please specify path to a key in optional_args
`'optional_args': {'key_file': '/home/user/ssh_private_key'}`
110 changes: 69 additions & 41 deletions napalm_vyos/vyos.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import re
import os
import tempfile

import vyattaconfparser

Expand All @@ -33,8 +34,9 @@
import napalm.base.constants as C
from napalm.base.utils import py23_compat
from napalm.base.base import NetworkDriver
from napalm.base.exceptions import ConnectionException, \
MergeConfigException, ReplaceConfigException
from napalm.base.exceptions import ConnectionException, MergeConfigException, \
ReplaceConfigException, CommitError, \
CommandErrorException


class VyOSDriver(NetworkDriver):
Expand Down Expand Up @@ -121,53 +123,73 @@ def load_replace_candidate(self, filename=None, config=None):
Due to the OS nature, we do not
support a replace using a configuration string.
"""
if filename is not None:
if os.path.exists(filename) is True:
self._scp_client.scp_transfer_file(filename, self._DEST_FILENAME)
self.device.send_command("cp "+self._BOOT_FILENAME+" "+self._BACKUP_FILENAME)
output_loadcmd = self.device.send_config_set(['load '+self._DEST_FILENAME])
match_loaded = re.findall("Load complete.", output_loadcmd)
match_notchanged = re.findall("No configuration changes to commit", output_loadcmd)
match_failed = re.findall("Failed to parse specified config file", output_loadcmd)

if match_failed:
if not filename and not config:
raise ReplaceConfigException('filename or config param must be provided.')

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



if os.path.exists(cfg_filename) is True:
self._scp_client.scp_transfer_file(cfg_filename, self._DEST_FILENAME)
self.device.send_command("cp "+self._BOOT_FILENAME+" "+self._BACKUP_FILENAME)
output_loadcmd = self.device.send_config_set(['load '+self._DEST_FILENAME])
match_loaded = re.findall("Load complete.", output_loadcmd)
match_notchanged = re.findall("No configuration changes to commit", output_loadcmd)
match_failed = re.findall("Failed to parse specified config file", output_loadcmd)

if match_failed:
raise ReplaceConfigException("Failed replace config: "
+ output_loadcmd)

if not match_loaded:
if not match_notchanged:
raise ReplaceConfigException("Failed replace config: "
+ output_loadcmd)

if not match_loaded:
if not match_notchanged:
raise ReplaceConfigException("Failed replace config: "
+ output_loadcmd)

else:
raise ReplaceConfigException("config file is not found")
else:
raise ReplaceConfigException("no configuration found")
raise ReplaceConfigException("config file is not found")


def load_merge_candidate(self, filename=None, config=None):
"""
Only configuration in set-format is supported with load_merge_candidate.
"""
if filename is not None:
if os.path.exists(filename) is True:
with open(filename) as f:
self.device.send_command("cp "+self._BOOT_FILENAME+" "
+ self._BACKUP_FILENAME)
self._new_config = f.read()
cfg = [x for x in self._new_config.split("\n") if x is not ""]
output_loadcmd = self.device.send_config_set(cfg)
match_setfailed = re.findall("Delete failed", output_loadcmd)
match_delfailed = re.findall("Set failed", output_loadcmd)

if match_setfailed or match_delfailed:
raise MergeConfigException("Failed merge config: "
+ output_loadcmd)
else:
raise MergeConfigException("config file is not found")
elif config is not None:
self._new_config = config

if not filename and not config:
raise MergeConfigException('filename or config param must be provided.')

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


if os.path.exists(cfg_filename) is True:
with open(cfg_filename) as f:
self.device.send_command("cp "+self._BOOT_FILENAME+" "
+ self._BACKUP_FILENAME)
self._new_config = f.read()
cfg = [x for x in self._new_config.split("\n") if x is not ""]
output_loadcmd = self.device.send_config_set(cfg)
match_setfailed = re.findall("Delete failed", output_loadcmd)
match_delfailed = re.findall("Set failed", output_loadcmd)

if match_setfailed or match_delfailed:
raise MergeConfigException("Failed merge config: "
+ output_loadcmd)
else:
raise MergeConfigException("no configuration found")
raise MergeConfigException("config file is not found")


def discard_config(self):
self.device.exit_config_mode()
Expand All @@ -183,8 +205,12 @@ def compare_config(self):
return diff

def commit_config(self):
if self.device.commit():
self.device.send_config_set(['save'])
try:
self.device.commit()
except ValueError:
raise CommitError("Failed to commit config on the device")

self.device.send_config_set(['save'])
self.device.exit_config_mode()

def rollback(self):
Expand Down Expand Up @@ -478,6 +504,8 @@ def get_bgp_neighbors(self):
received_prefixes = int(state_prefix)
is_up = True
except ValueError:
state_prefix = -1
received_prefixes = -1
is_up = False

if bgp_version == "4":
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

setup(
name="napalm-vyos",
version="0.1.4",
version="0.1.5",
packages=find_packages(),
author="Piotr Pieprzycki",
author_email="[email protected]",
Expand Down

0 comments on commit f1549d8

Please sign in to comment.