mirror of
https://github.com/napalm-automation-community/napalm-vyos.git
synced 2026-03-03 20:24:50 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2278bddd3a | ||
|
|
55a6b15c3e | ||
|
|
580da0edeb | ||
|
|
23fe045167 | ||
|
|
b8ec1396f7 | ||
|
|
5c3492f13c | ||
|
|
47227d1cf0 | ||
|
|
06f9fc28d3 | ||
|
|
6e68dba692 | ||
|
|
7d0e30fdba | ||
|
|
4d52216866 | ||
|
|
5c440b3df9 | ||
|
|
69c8c66652 | ||
|
|
b9bcbbc7bc |
98
README.md
98
README.md
@@ -1,5 +1,101 @@
|
||||
[](https://pypi.python.org/pypi/napalm-vyos)
|
||||
[](https://pypi.python.org/pypi/napalm-vyos)
|
||||
[](https://travis-ci.org/napalm-automation/napalm-vyos)
|
||||
[](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'}`
|
||||
|
||||
@@ -22,6 +22,7 @@ Read napalm.readthedocs.org for more information.
|
||||
|
||||
import re
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import vyattaconfparser
|
||||
|
||||
@@ -30,11 +31,12 @@ from netmiko import ConnectHandler
|
||||
from netmiko import SCPConn
|
||||
|
||||
# NAPALM base
|
||||
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
|
||||
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, CommitError, \
|
||||
CommandErrorException
|
||||
|
||||
|
||||
class VyOSDriver(NetworkDriver):
|
||||
@@ -121,53 +123,73 @@ class VyOSDriver(NetworkDriver):
|
||||
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 not filename and not config:
|
||||
raise ReplaceConfigException('filename or config param must be provided.')
|
||||
|
||||
if match_failed:
|
||||
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:
|
||||
raise MergeConfigException("no configuration found")
|
||||
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("config file is not found")
|
||||
|
||||
|
||||
def discard_config(self):
|
||||
self.device.exit_config_mode()
|
||||
@@ -183,8 +205,12 @@ class VyOSDriver(NetworkDriver):
|
||||
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):
|
||||
@@ -478,6 +504,8 @@ class VyOSDriver(NetworkDriver):
|
||||
received_prefixes = int(state_prefix)
|
||||
is_up = True
|
||||
except ValueError:
|
||||
state_prefix = -1
|
||||
received_prefixes = -1
|
||||
is_up = False
|
||||
|
||||
if bgp_version == "4":
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
napalm_base
|
||||
napalm==2.*
|
||||
paramiko
|
||||
netmiko>=1.1.0
|
||||
vyattaconfparser
|
||||
|
||||
2
setup.py
2
setup.py
@@ -12,7 +12,7 @@ reqs = [str(ir.req) for ir in install_reqs]
|
||||
|
||||
setup(
|
||||
name="napalm-vyos",
|
||||
version="0.1.3",
|
||||
version="0.1.5",
|
||||
packages=find_packages(),
|
||||
author="Piotr Pieprzycki",
|
||||
author_email="piotr.pieprzycki@dreamlab.pl",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import unittest
|
||||
|
||||
from napalm_vyos import vyos
|
||||
from napalm_base.test.base import TestConfigNetworkDriver
|
||||
from napalm.base.test.base import TestConfigNetworkDriver
|
||||
|
||||
|
||||
class TestConfigVyOSDriver(unittest.TestCase, TestConfigNetworkDriver):
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
from builtins import super
|
||||
|
||||
import pytest
|
||||
from napalm_base.test import conftest as parent_conftest
|
||||
from napalm.base.test import conftest as parent_conftest
|
||||
|
||||
from napalm_base.test.double import BaseTestDouble
|
||||
from napalm_base.utils import py23_compat
|
||||
from napalm.base.test.double import BaseTestDouble
|
||||
from napalm.base.utils import py23_compat
|
||||
|
||||
from napalm_vyos import vyos
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Tests for getters."""
|
||||
|
||||
from napalm_base.test.getters import BaseTestGetters
|
||||
from napalm.base.test.getters import BaseTestGetters
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
Reference in New Issue
Block a user