Skip to content

Commit

Permalink
Merge pull request #17 from napalm-automation-community/develop
Browse files Browse the repository at this point in the history
Release 3.0.0
  • Loading branch information
bewing authored Jul 9, 2020
2 parents b71dc49 + 02ec82b commit 4c4f078
Show file tree
Hide file tree
Showing 14 changed files with 658 additions and 52 deletions.
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
dist: xenial
language: python
python:
- 2.7
- 3.5
- 3.6
- 3.7
- 3.8
env:
- NAPALM=2.4.0
- NAPALM=3.0.0
matrix:
include:
- python: 3.6
Expand Down
123 changes: 81 additions & 42 deletions napalm_mos/mos.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import pyeapi
import re
import time
import inspect

from datetime import timedelta, datetime
from distutils.version import LooseVersion
Expand All @@ -40,14 +41,15 @@

import napalm.base.helpers
from napalm.base import NetworkDriver
from napalm.base.utils import string_parsers, py23_compat
from napalm.base.utils import string_parsers
from napalm.base.exceptions import (
ConnectionException,
CommandErrorException,
SessionLockedException,
)

from napalm_mos.constants import LLDP_CAPAB_TRANFORM_TABLE
import napalm.base.constants as c


class MOSDriver(NetworkDriver):
Expand Down Expand Up @@ -87,7 +89,7 @@ def __init__(self, hostname, username, password, timeout=60, optional_args=None)
self._current_config = None
self._replace_config = False
self._ssh = None
self._MOSH_10017 = False
self._version = LooseVersion("0")

self._process_optional_args(optional_args or {})

Expand All @@ -102,7 +104,7 @@ def _process_optional_args(self, optional_args):
self.transport_class = pyeapi.client.TRANSPORTS[transport]
except KeyError:
raise ConnectionException("Unknown transport: {}".format(self.transport))
init_args = py23_compat.argspec(self.transport_class.__init__)[0]
init_args = inspect.getfullargspec(self.transport_class.__init__)[0]
init_args.pop(0) # Remove "self"
init_args.append("enforce_verification") # Not an arg for unknown reason

Expand All @@ -114,6 +116,24 @@ def _process_optional_args(self, optional_args):
if k in init_args and k not in filter_args
}

def _run_translated_commands(self, commands, **kwargs):
"""
In 0.22.0+ some commands had their syntax change. This function translates those command
syntaxs to their post 0.22.0 version
"""
if self._version >= LooseVersion("0.22.0"):
# Map of translate command syntax to 0.23.0+ syntax
translations = {
"show snmp chassis-id": "show snmp v2-mib chassis-id",
"show snmp location": "show snmp v2-mib location",
"show snmp contact": "show snmp v2-mib contact",
"show environment all": "show system environment all",
}
commands = [
i if i not in translations.keys() else translations[i] for i in commands
]
return self.device.run_commands(commands, **kwargs)

def open(self):
"""Implementation of NAPALM method open."""
try:
Expand All @@ -134,9 +154,7 @@ def open(self):
raise NotImplementedError(
"MOS Software Version 0.17.9 or better required"
)
# Waiting for fixed release
if LooseVersion(sw_version) < LooseVersion("0.19.2"):
self._MOSH_10017 = True
self._version = LooseVersion(sw_version)
except ConnectionError as ce:
raise ConnectionException(ce.message)

Expand Down Expand Up @@ -202,11 +220,11 @@ def _unlock(self):

def _get_sessions(self):
return [
l.split()[-1]
for l in self.device.run_commands(["dir flash:"], encoding="text")[0][
line.split()[-1]
for line in self.device.run_commands(["dir flash:"], encoding="text")[0][
"output"
].splitlines()
if "napalm_" in l.split()[-1]
if "napalm_" in line.split()[-1]
]

def _load_config(self, filename=None, config=None, replace=False):
Expand Down Expand Up @@ -237,11 +255,14 @@ def _load_config(self, filename=None, config=None, replace=False):
self._candidate.append(line)

self._candidate.append("end")
if any("source mac" in l for l in self._candidate) and self._MOSH_10017:
if any(
"source mac" in line for line in self._candidate
) and self._version < LooseVersion("0.19.2"):
# Waiting for fixed release
raise CommandErrorException(
"Cannot set source mac in MOS versions prior to 0.19.2"
)
if any("banner motd" in l for l in self._candidate):
if any("banner motd" in line for line in self._candidate):
raise CommandErrorException("Cannot set banner via JSONRPC API")

def _wait_for_reload(self, timeout=None):
Expand All @@ -266,9 +287,9 @@ def compare_config(self):
# There's no good way to do this yet
if self._replace_config:
cur = self.get_config("running")["running"].splitlines()[4:]
return "\n".join(difflib.unified_diff(cur, self._candidate[4:]))
return "\n".join(difflib.unified_diff(cur, self._candidate[3:]))
else:
return "\n".join(self._candidate[3:])
return "\n".join(self._candidate[2:])

def discard_config(self):
if self.config_session is not None:
Expand Down Expand Up @@ -407,22 +428,35 @@ def get_interfaces_counters(self):
return interface_counters

def get_environment(self):

commands = ["show environment all"]
output = self.device.run_commands(commands, encoding="json")[0]
output = self._run_translated_commands(commands, encoding="json")[0]
environment_counters = {"fans": {}, "temperature": {}, "power": {}, "cpu": {}}

# Fans
for slot, data in output["systemCooling"]["fans"].items():
environment_counters["fans"][slot] = {"status": data["status"] == "OK"}
environment_counters["fans"][slot] = {
"status": False if data["status"] == "NOT WORKING" else True
}

# Temperature
temps = {}
for n, v in output["systemTemperature"]["sensors"].items():
# Make sure all the temperatures are numbers, allow floats as well
temp = v["temp(C)"] if v["temp(C)"].replace(".", "").isdigit() else -1
alert_thres = (
v["alertThreshold"]
if v["alertThreshold"].replace(".", "").isdigit()
else -1
)
crit_thres = (
v["criticalThreshold"]
if v["criticalThreshold"].replace(".", "").isdigit()
else -1
)
temps[v["description"]] = {
"temperature": float(v["temp(C)"]),
"is_alert": float(v["temp(C)"]) > float(v["alertThreshold"]),
"is_critical": float(v["temp(C)"]) > float(v["criticalThreshold"]),
"temperature": float(temp),
"is_alert": float(temp) > float(alert_thres),
"is_critical": float(temp) > float(crit_thres),
}
environment_counters["temperature"].update(temps)

Expand Down Expand Up @@ -493,7 +527,7 @@ def get_lldp_neighbors_detail(self, interface=""):
enabled_capab = capabilities.get("enabled", "").replace(",", ", ")

tlv_dict = {
"parent_interface": py23_compat.text_type(interface),
"parent_interface": interface,
"remote_port": re.sub(
r"\s*\([^)]*\)\s*", "", info_dict.get("port id", "")
),
Expand Down Expand Up @@ -523,23 +557,21 @@ def cli(self, commands):

for command in commands:
try:
cli_output[py23_compat.text_type(command)] = self.device.run_commands(
cli_output[command] = self.device.run_commands(
[command], encoding="text"
)[0].get("output")
# not quite fair to not exploit rum_commands
# but at least can have better control to point to wrong command in case of failure
except pyeapi.eapilib.CommandError:
# for sure this command failed
cli_output[
py23_compat.text_type(command)
] = 'Invalid command: "{cmd}"'.format(cmd=command)
cli_output[command] = 'Invalid command: "{cmd}"'.format(cmd=command)
raise CommandErrorException(str(cli_output))
except Exception as e:
# something bad happened
msg = 'Unable to execute command "{cmd}": {err}'.format(
cmd=command, err=e
)
cli_output[py23_compat.text_type(command)] = msg
cli_output[command] = msg
raise CommandErrorException(str(cli_output))

return cli_output
Expand All @@ -562,9 +594,9 @@ def get_arp_table(self, vrf=""):
match = self._RE_ARP.match(line)
if match:
neighbor = match.groupdict()
interface = py23_compat.text_type(neighbor.get("interface"))
interface = neighbor.get("interface")
mac_raw = neighbor.get("hwAddress")
ip = py23_compat.text_type(neighbor.get("address"))
ip = neighbor.get("address")
age = 0.0
arp_table.append(
{
Expand All @@ -582,7 +614,7 @@ def get_ntp_servers(self):

servers = self._RE_NTP_SERVERS.findall(config)

return {py23_compat.text_type(server): {} for server in servers}
return {server: {} for server in servers}

def get_ntp_stats(self):
ntp_stats = []
Expand Down Expand Up @@ -616,12 +648,12 @@ def get_ntp_stats(self):
try:
ntp_stats.append(
{
"remote": py23_compat.text_type(line_groups[1]),
"remote": line_groups[1],
"synchronized": (line_groups[0] == "*"),
"referenceid": py23_compat.text_type(line_groups[2]),
"referenceid": line_groups[2],
"stratum": int(line_groups[3]),
"type": py23_compat.text_type(line_groups[4]),
"when": py23_compat.text_type(line_groups[5]),
"type": line_groups[4],
"when": line_groups[5],
"hostpoll": int(line_groups[6]),
"reachability": int(line_groups[7]),
"delay": float(line_groups[8]),
Expand All @@ -646,7 +678,7 @@ def get_snmp_information(self):
"show snmp contact",
"show snmp community",
]
snmp_config = self.device.run_commands(commands, encoding="text")
snmp_config = self._run_translated_commands(commands, encoding="text")
snmp_dict["chassis_id"] = (
snmp_config[0]["output"].replace("Chassis: ", "").strip()
)
Expand All @@ -662,8 +694,8 @@ def get_snmp_information(self):
if match:
matches = match.groupdict("")
snmp_dict["community"][match.group("community")] = {
"acl": py23_compat.text_type(matches["v4_acl"]),
"mode": py23_compat.text_type(matches["mode"]),
"acl": matches["v4_acl"],
"mode": matches["mode"],
}

return snmp_dict
Expand Down Expand Up @@ -729,7 +761,7 @@ def get_optics(self):
optics_detail[port] = port_detail
return optics_detail

def get_config(self, retrieve="all", full=False):
def get_config(self, retrieve="all", full=False, sanitized=False):
"""get_config implementation for MOS."""

get_startup = False
Expand All @@ -747,12 +779,19 @@ def get_config(self, retrieve="all", full=False):
Exception("Wrong retrieve filter: {}".format(retrieve))

output = self.device.run_commands(commands, encoding="text")

if sanitized:
output = [
{
"output": napalm.base.helpers.sanitize_config(
config["output"], c.CISCO_SANITIZE_FILTERS
)
}
for config in output
]

return {
"startup": py23_compat.text_type(output[0]["output"])
if get_startup
else "",
"running": py23_compat.text_type(output[1]["output"])
if get_running
else "",
"startup": output[0]["output"] if get_startup else "",
"running": output[1]["output"] if get_running else "",
"candidate": "",
}
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
napalm==2.5.0
napalm>=3.0.0
pyeapi>=0.8.1
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setup(
name="napalm-mos",
version="2.5.0",
version="3.0.0",
packages=find_packages(),
author="Benny Holmgren, Brandon Ewing",
author_email="[email protected], [email protected]",
Expand All @@ -22,4 +22,5 @@
url="https://github.com/napalm-automation-community/napalm-mos",
include_package_data=True,
install_requires=reqs,
python_requires=">=3.6",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"running":"! command: show running-config\n! time: Fri 02 Jun 2017 00:15:54 UTC\n! device: test (C16-B2, MOS-0.14.0alpha2)\n!\n\nhostname test\nusername admin secret sha512 $6$01234567890ABCDEF01234567890ABCDEF01234567890ABCDEF01234567890ABCDEF01234567890ABCDEF01234567890ABC\ntacacs-server host 192.0.1.10 key <removed>\ntacacs-server host 192.0.1.11 key <removed>\ntacacs-server host 192.0.1.12 key <removed>\n\nntp server 192.0.1.100\nntp server 192.0.1.101\n\nlogging host 192.0.1.101\nalias wrm copy running-config startup-config\n\ninterface et1\n source et2\n\ninterface et2\n source et1\n\ninterface ma1\n ip address 192.0.2.201 255.255.255.0\n ip default-gateway 192.0.2.254\n\nmanagement api\n no shutdown\n\nmanagement snmp\n snmp-server community publ1c ro\n\nend\n", "startup":"! device: test (C16-B2, MOS-0.14.0alpha1)\n!\n\nhostname test\nusername admin secret sha512 $6$01234567890ABCDEF01234567890ABCDEF01234567890ABCDEF01234567890ABCDEF01234567890ABCDEF01234567890ABC\ntacacs-server host 192.0.1.10 key <removed>\ntacacs-server host 192.0.1.11 key <removed>\ntacacs-server host 192.0.1.12 key <removed>\n\nntp server 192.0.1.100\nntp server 192.0.1.101\n\nlogging host 192.0.1.101\nalias wrm copy running-config startup-config\n\ninterface et1\n source et2\n\ninterface et2\n source et1\n\ninterface ma1\n ip address 192.0.2.201 255.255.255.0\n ip default-gateway 192.0.2.254\n\nmanagement snmp\n snmp-server community publ1c ro\n\nend\n\n", "candidate": ""}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
! command: show running-config
! time: Fri 02 Jun 2017 00:15:54 UTC
! device: test (C16-B2, MOS-0.14.0alpha2)
!

hostname test
username admin secret sha512 $6$01234567890ABCDEF01234567890ABCDEF01234567890ABCDEF01234567890ABCDEF01234567890ABCDEF01234567890ABC
tacacs-server host 192.0.1.10 key 7 01234567890ABCDEF0123456789ABCDEF0123456789A
tacacs-server host 192.0.1.11 key 7 01234567890ABCDEF0123456789ABCDEF0123456789A
tacacs-server host 192.0.1.12 key 7 01234567890ABCDEF0123456789ABCDEF0123456789A

ntp server 192.0.1.100
ntp server 192.0.1.101

logging host 192.0.1.101
alias wrm copy running-config startup-config

interface et1
source et2

interface et2
source et1

interface ma1
ip address 192.0.2.201 255.255.255.0
ip default-gateway 192.0.2.254

management api
no shutdown

management snmp
snmp-server community publ1c ro

end
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
! device: test (C16-B2, MOS-0.14.0alpha1)
!

hostname test
username admin secret sha512 $6$01234567890ABCDEF01234567890ABCDEF01234567890ABCDEF01234567890ABCDEF01234567890ABCDEF01234567890ABC
tacacs-server host 192.0.1.10 key 7 01234567890ABCDEF0123456789ABCDEF0123456789A
tacacs-server host 192.0.1.11 key 7 01234567890ABCDEF0123456789ABCDEF0123456789A
tacacs-server host 192.0.1.12 key 7 01234567890ABCDEF0123456789ABCDEF0123456789A

ntp server 192.0.1.100
ntp server 192.0.1.101

logging host 192.0.1.101
alias wrm copy running-config startup-config

interface et1
source et2

interface et2
source et1

interface ma1
ip address 192.0.2.201 255.255.255.0
ip default-gateway 192.0.2.254

management snmp
snmp-server community publ1c ro

end

Loading

0 comments on commit 4c4f078

Please sign in to comment.