Skip to content

Commit

Permalink
Merge pull request #37 from napalm-automation-community/develop
Browse files Browse the repository at this point in the history
Merge Develop into master
  • Loading branch information
ppieprzycki authored May 17, 2020
2 parents 1d11f45 + fe3d556 commit dd96e7d
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 77 deletions.
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
language: python
python:
- 2.7
- 3.4
- 3.5
- 3.6
- 3.7
- 3.8
install:
- pip install tox-travis
- pip install coveralls
Expand Down
133 changes: 72 additions & 61 deletions napalm_vyos/vyos.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@

# 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, CommitError, \
Expand Down Expand Up @@ -179,7 +178,7 @@ def load_merge_candidate(self, filename=None, config=None):
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 ""]
cfg = [x for x in self._new_config.split("\n") if x]
output_loadcmd = self.device.send_config_set(cfg)
match_setfailed = re.findall("Delete failed", output_loadcmd)
match_delfailed = re.findall("Set failed", output_loadcmd)
Expand Down Expand Up @@ -309,7 +308,7 @@ def get_interfaces(self):
output_iface = self.device.send_command("show interfaces")

# Collect all interfaces' name and status
match = re.findall("(\S+)\s+[:\-\d/\.]+\s+([uAD])/([uAD])", output_iface)
match = re.findall(r"(\S+)\s+[:\-\d/\.]+\s+([uAD])/([uAD])", output_iface)

# 'match' example:
# [("br0", "u", "D"), ("eth0", "u", "u"), ("eth1", "u", "u")...]
Expand All @@ -328,17 +327,12 @@ def get_interfaces(self):
ifaces_detail = config["interfaces"][iface_type]

for iface_name in ifaces_detail:
description = self._get_value("description", ifaces_detail[iface_name])
if description is None:
description = ""
speed = self._get_value("speed", ifaces_detail[iface_name])
if speed is None:
speed = 0
details = ifaces_detail[iface_name]
description = details.get("description", "")
speed = details.get("speed", "0")
if speed == "auto":
speed = 0
hw_id = self._get_value("hw-id", ifaces_detail[iface_name])
if hw_id is None:
hw_id = "00:00:00:00:00:00"
hw_id = details.get("hw-id", "00:00:00:00:00:00")

is_up = (iface_state[iface_name]["Link"] == "u")
is_enabled = (iface_state[iface_name]["State"] == "u")
Expand All @@ -347,22 +341,16 @@ def get_interfaces(self):
iface_name: {
"is_up": bool(is_up),
"is_enabled": bool(is_enabled),
"description": py23_compat.text_type(description),
"description": description,
"last_flapped": float(-1),
"mtu": -1,
"speed": int(speed),
"mac_address": py23_compat.text_type(hw_id)
"mac_address": hw_id,
}
})

return iface_dict

@staticmethod
def _get_value(key, target_dict):
if key in target_dict:
return target_dict[key]
else:
return None

def get_arp_table(self, vrf=""):
# 'age' is not implemented yet

Expand All @@ -376,7 +364,7 @@ def get_arp_table(self, vrf=""):
192.168.1.3 ether 00:50:56:86:7b:06 C eth1
"""

if vrf:
if vrf:
raise NotImplementedError(
"VRF support has not been added for this getter on this platform."
)
Expand All @@ -395,16 +383,16 @@ def get_arp_table(self, vrf=""):
# ["10.129.2.254", "ether", "00:50:56:97:af:b1", "C", "eth0"]
# [u'10.0.12.33', u'(incomplete)', u'eth1']
if "incomplete" in line[1]:
macaddr = py23_compat.text_type("00:00:00:00:00:00")
macaddr = "00:00:00:00:00:00"
else:
macaddr = py23_compat.text_type(line[2])
macaddr = line[2]

arp_table.append(
{
'interface': py23_compat.text_type(line[-1]),
'interface': line[-1],
'mac': macaddr,
'ip': py23_compat.text_type(line[0]),
'age': 0.0
'ip': line[0],
'age': 0.0,
}
)

Expand Down Expand Up @@ -432,23 +420,23 @@ def get_ntp_stats(self):
# 'remote' contains '*' if the machine synchronized with NTP server
synchronized = "*" in remote

match = re.search("(\d+\.\d+\.\d+\.\d+)", remote)
match = re.search(r"(\d+\.\d+\.\d+\.\d+)", remote)
ip = match.group(1)

when = when if when != '-' else 0

ntp_stats.append({
"remote": py23_compat.text_type(ip),
"referenceid": py23_compat.text_type(refid),
"remote": ip,
"referenceid": refid,
"synchronized": bool(synchronized),
"stratum": int(st),
"type": py23_compat.text_type(t),
"when": py23_compat.text_type(when),
"type": t,
"when": when,
"hostpoll": int(hostpoll),
"reachability": int(reachability),
"delay": float(delay),
"offset": float(offset),
"jitter": float(jitter)
"jitter": float(jitter),
})

return ntp_stats
Expand All @@ -460,9 +448,9 @@ def get_ntp_peers(self):

for line in output_peers:
if len(line) > 0:
match = re.search("(\d+\.\d+\.\d+\.\d+)\s+", line)
match = re.search(r"(\d+\.\d+\.\d+\.\d+)\s+", line)
ntp_peers.update({
py23_compat.text_type(match.group(1)): {}
match.group(1): {}
})

return ntp_peers
Expand All @@ -486,11 +474,11 @@ def get_bgp_neighbors(self):
output = self.device.send_command("show ip bgp summary")
output = output.split("\n")

match = re.search(".* router identifier (\d+\.\d+\.\d+\.\d+), local AS number (\d+)",
match = re.search(r".* router identifier (\d+\.\d+\.\d+\.\d+), local AS number (\d+)",
output[0])
if not match:
return {}
router_id = py23_compat.text_type(match.group(1))
router_id = match.group(1)
local_as = int(match.group(2))

bgp_neighbor_data = dict()
Expand All @@ -499,7 +487,7 @@ def get_bgp_neighbors(self):
bgp_neighbor_data["global"]["peers"] = {}

# delete the header and empty element
bgp_info = [i.strip() for i in output[6:-2] if i is not ""]
bgp_info = [i.strip() for i in output[6:-2] if i]

for i in bgp_info:
if len(i) > 0:
Expand Down Expand Up @@ -538,19 +526,19 @@ def get_bgp_neighbors(self):
"""
bgp_detail = self.device.send_command("show ip bgp neighbors %s" % peer_id)

match_rid = re.search("remote router ID (\d+\.\d+\.\d+\.\d+).*", bgp_detail)
match_rid = re.search(r"remote router ID (\d+\.\d+\.\d+\.\d+).*", bgp_detail)
remote_rid = match_rid.group(1)

match_prefix_accepted = re.search("(\d+) accepted prefixes", bgp_detail)
match_prefix_accepted = re.search(r"(\d+) accepted prefixes", bgp_detail)
accepted_prefixes = match_prefix_accepted.group(1)

bgp_neighbor_data["global"]["peers"].setdefault(peer_id, {})
peer_dict = {
"description": py23_compat.text_type(""),
"description": "",
"is_enabled": bool(is_enabled),
"local_as": int(local_as),
"is_up": bool(is_up),
"remote_id": py23_compat.text_type(remote_rid),
"remote_id": remote_rid,
"uptime": int(self._bgp_time_conversion(up_time)),
"remote_as": int(remote_as)
}
Expand All @@ -574,19 +562,19 @@ def _bgp_time_conversion(self, bgp_uptime):
return -1
else:
if "y" in bgp_uptime:
match = re.search("(\d+)(\w)(\d+)(\w)(\d+)(\w)", bgp_uptime)
match = re.search(r"(\d+)(\w)(\d+)(\w)(\d+)(\w)", bgp_uptime)
uptime = ((int(match.group(1)) * self._YEAR_SECONDS) +
(int(match.group(3)) * self._WEEK_SECONDS) +
(int(match.group(5)) * self._DAY_SECONDS))
return uptime
elif "w" in bgp_uptime:
match = re.search("(\d+)(\w)(\d+)(\w)(\d+)(\w)", bgp_uptime)
match = re.search(r"(\d+)(\w)(\d+)(\w)(\d+)(\w)", bgp_uptime)
uptime = ((int(match.group(1)) * self._WEEK_SECONDS) +
(int(match.group(3)) * self._DAY_SECONDS) +
(int(match.group(5)) * self._HOUR_SECONDS))
return uptime
elif "d" in bgp_uptime:
match = re.search("(\d+)(\w)(\d+)(\w)(\d+)(\w)", bgp_uptime)
match = re.search(r"(\d+)(\w)(\d+)(\w)(\d+)(\w)", bgp_uptime)
uptime = ((int(match.group(1)) * self._DAY_SECONDS) +
(int(match.group(3)) * self._HOUR_SECONDS) +
(int(match.group(5)) * self._MINUTE_SECONDS))
Expand All @@ -597,6 +585,29 @@ def _bgp_time_conversion(self, bgp_uptime):
(minutes * self._MINUTE_SECONDS) + seconds)
return uptime

def get_lldp_neighbors(self):
# Multiple neighbors per port are not implemented
# The show lldp neighbors commands lists port descriptions, not IDs
output = self.device.send_command("show lldp neighbors detail")
pattern = r'''(?s)Interface: +(?P<interface>\S+), [^\n]+
.+?
+SysName: +(?P<hostname>\S+)
.+?
+PortID: +ifname (?P<port>\S+)'''

def _get_interface(match):
return [
{
'hostname': match.group('hostname'),
'port': match.group('port'),
}
]

return {
match.group('interface'): _get_interface(match)
for match in re.finditer(pattern, output)
}

def get_interfaces_counters(self):
# 'rx_unicast_packet', 'rx_broadcast_packets', 'tx_unicast_packets',
# 'tx_multicast_packets' and 'tx_broadcast_packets' are not implemented yet
Expand All @@ -613,9 +624,9 @@ def get_interfaces_counters(self):
32776498 279273 0 0 0 0
"""
output = self.device.send_command("show interfaces detail")
interfaces = re.findall("(\S+): <.*", output)
interfaces = re.findall(r"(\S+): <.*", output)
# count = re.findall("(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+", output)
count = re.findall("(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)", output)
count = re.findall(r"(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)", output)
counters = dict()

j = 0
Expand Down Expand Up @@ -663,15 +674,15 @@ def get_snmp_information(self):
for i in config["service"]["snmp"]["community"]:
snmp["community"].update({
i: {
"acl": py23_compat.text_type(""),
"mode": py23_compat.text_type(config["service"]["snmp"]["community"][i]["authorization"])
"acl": "",
"mode": config["service"]["snmp"]["community"][i]["authorization"],
}
})

snmp.update({
"chassis_id": py23_compat.text_type(""),
"contact": py23_compat.text_type(config["service"]["snmp"]["contact"]),
"location": py23_compat.text_type(config["service"]["snmp"]["location"])
"chassis_id": "",
"contact": config["service"]["snmp"]["contact"],
"location": config["service"]["snmp"]["location"],
})

return snmp
Expand Down Expand Up @@ -713,13 +724,13 @@ def get_facts(self):

facts = {
"uptime": int(uptime),
"vendor": py23_compat.text_type("VyOS"),
"os_version": py23_compat.text_type(version),
"serial_number": py23_compat.text_type(snumber),
"model": py23_compat.text_type(hwmodel),
"hostname": py23_compat.text_type(hostname),
"fqdn": py23_compat.text_type(fqdn),
"interface_list": iface_list
"vendor": "VyOS",
"os_version": version,
"serial_number": snumber,
"model": hwmodel,
"hostname": hostname,
"fqdn": fqdn,
"interface_list": iface_list,
}

return facts
Expand Down Expand Up @@ -850,7 +861,7 @@ def ping(self,
else:
err = ""

if err is not "":
if err:
ping_result["error"] = err
else:
# 'packet_info' example:
Expand Down Expand Up @@ -878,7 +889,7 @@ def ping(self,
else:
rtt_info = rtt_info[-2]

match = re.search("([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+)", rtt_info)
match = re.search(r"([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+)", rtt_info)

if match is not None:
rtt_min = float(match.group(1))
Expand Down
1 change: 0 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
future
coveralls
pytest
pytest-cov
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
napalm==2.*
napalm>=3.0
paramiko
netmiko>=1.1.0
netmiko>=3.1.0
vyattaconfparser
8 changes: 3 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,18 @@

setup(
name="napalm-vyos",
version="0.1.6",
version="0.2.0",
packages=find_packages(),
author="Piotr Pieprzycki",
author_email="[email protected]",
description="Network Automation and Programmability Abstraction Layer with Multivendor support",
classifiers=[
'Topic :: Utilities',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Operating System :: POSIX :: Linux',
'Operating System :: MacOS',
],
Expand Down
4 changes: 1 addition & 3 deletions test/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
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_vyos import vyos

Expand Down Expand Up @@ -55,5 +54,4 @@ class FakeVyOSDevice(BaseTestDouble):
def send_command(self, command, **kwargs):
filename = '{}.text'.format(self.sanitize_text(command))
full_path = self.find_file(filename)
result = self.read_txt_file(full_path)
return py23_compat.text_type(result)
return self.read_txt_file(full_path)
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"lo": {"is_enabled": true, "description": "", "last_flapped": -1.0, "is_up": true, "mac_address": "00:00:00:00:00:00", "speed": 0}, "eth1": {"is_enabled": true, "description": "", "last_flapped": -1.0, "is_up": true, "mac_address": "...", "speed": 0}, "eth0": {"is_enabled": true, "description": "", "last_flapped": -1.0, "is_up": true, "mac_address": "...", "speed": 0}}
{"lo": {"is_enabled": true, "description": "", "last_flapped": -1.0, "is_up": true, "mac_address": "00:00:00:00:00:00", "mtu": -1, "speed": 0}, "eth1": {"is_enabled": true, "description": "", "last_flapped": -1.0, "is_up": true, "mac_address": "...", "mtu": -1, "speed": 0}, "eth0": {"is_enabled": true, "description": "", "last_flapped": -1.0, "is_up": true, "mac_address": "...", "mtu": -1, "speed": 0}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"eth1": [{"hostname": "branch", "port": "eth0"}], "eth2": [{"hostname": "dmz", "port": "eth0"}]}
Loading

0 comments on commit dd96e7d

Please sign in to comment.