Skip to content

Commit

Permalink
Merge pull request #1252 from napalm-automation/develop
Browse files Browse the repository at this point in the history
Release 3.1.0
  • Loading branch information
mirceaulinic authored Jul 27, 2020
2 parents 456f736 + 70cf5bf commit 4af2aba
Show file tree
Hide file tree
Showing 21 changed files with 7,181 additions and 90 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[![PyPI](https://img.shields.io/pypi/v/napalm.svg)](https://pypi.python.org/pypi/napalm)
[![PyPI versions](https://img.shields.io/pypi/pyversions/napalm.svg)](https://pypi.python.org/pypi/napalm)
[![Build Status](https://travis-ci.org/napalm-automation/napalm.svg?branch=master)](https://travis-ci.org/napalm-automation/napalm)
[![Coverage Status](https://coveralls.io/repos/github/napalm-automation/napalm/badge.svg)](https://coveralls.io/github/napalm-automation/napalm)
[![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
Expand Down Expand Up @@ -31,6 +32,9 @@ Install
pip install napalm
```

*Note*: Beginning with release 3.0.0 and later, NAPALM offers support for
Python 3.6+ only.


Upgrading
=========
Expand Down
10 changes: 7 additions & 3 deletions docs/installation/index.rst
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
Installation
============


Full installation
-----------------

You can install napalm with pip:

.. code-block:: bash
pip install napalm
That will install all the drivers currently available.
That will install all the core drivers currently available.

.. note::

Beginning with release 3.0.0 and later, NAPALM offers support for Python
3.6+ only.


OS Package Managers
Expand Down
9 changes: 7 additions & 2 deletions napalm/base/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,14 @@ def _netmiko_open(self, device_type, netmiko_optional_args=None):
except NetMikoTimeoutException:
raise ConnectionException("Cannot connect to {}".format(self.hostname))

# ensure in enable mode if not force disable
if not self.force_no_enable:
# Disable enable mode if force_no_enable is true (for NAPALM drivers
# that support force_no_enable)
try:
if not self.force_no_enable:
self._netmiko_device.enable()
except AttributeError:
self._netmiko_device.enable()

return self._netmiko_device

def _netmiko_close(self):
Expand Down
7 changes: 5 additions & 2 deletions napalm/base/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ def textfsm_extractor(cls, template_name, raw_text):
def find_txt(xml_tree, path, default="", namespaces=None):
"""
Extracts the text value from an XML tree, using XPath.
In case of error, will return a default value.
In case of error or text element unavailability, will return a default value.
:param xml_tree: the XML Tree object. Assumed is <type 'lxml.etree._Element'>.
:param path: XPath to be applied, in order to extract the desired data.
Expand All @@ -265,7 +265,10 @@ def find_txt(xml_tree, path, default="", namespaces=None):
if xpath_length and xpath_applied[0] is not None:
xpath_result = xpath_applied[0]
if isinstance(xpath_result, type(xml_tree)):
value = xpath_result.text.strip()
if xpath_result.text:
value = xpath_result.text.strip()
else:
value = default
else:
value = xpath_result
else:
Expand Down
21 changes: 16 additions & 5 deletions napalm/ios/ios.py
Original file line number Diff line number Diff line change
Expand Up @@ -799,13 +799,21 @@ def get_optics(self):
for optics_entry in split_output.splitlines():
# Example, Te1/0/1 34.6 3.29 -2.0 -3.5
try:
optics_entry = optics_entry.strip("-")
split_list = optics_entry.split()
except ValueError:
return {}

int_brief = split_list[0]
output_power = split_list[3]
input_power = split_list[4]
current = 0
if len(split_list) == 5:
int_brief = split_list[0]
output_power = split_list[3]
input_power = split_list[4]
elif len(split_list) >= 6:
int_brief = split_list[0]
current = split_list[3]
output_power = split_list[4]
input_power = split_list[5]

port = canonical_interface_name(int_brief)

Expand Down Expand Up @@ -841,7 +849,7 @@ def get_optics(self):
"max": -100.0,
},
"laser_bias_current": {
"instant": 0.0,
"instant": (float(current) if "current" else -100.0),
"avg": 0.0,
"min": 0.0,
"max": 0.0,
Expand Down Expand Up @@ -3103,7 +3111,7 @@ def get_users(self):
"""
username_regex = (
r"^username\s+(?P<username>\S+)\s+(?:privilege\s+(?P<priv_level>\S+)"
r"\s+)?(?:secret \d+\s+(?P<pwd_hash>\S+))?$"
r"\s+)?(?:(password|secret) \d+\s+(?P<pwd_hash>\S+))?$"
)
pub_keychain_regex = (
r"^\s+username\s+(?P<username>\S+)(?P<keys>(?:\n\s+key-hash\s+"
Expand All @@ -3112,6 +3120,9 @@ def get_users(self):
users = {}
command = "show run | section username"
output = self._send_command(command)
if "Invalid input detected" in output:
command = "show run | include username"
output = self._send_command(command)
for match in re.finditer(username_regex, output, re.M):
users[match.groupdict()["username"]] = {
"level": int(match.groupdict()["priv_level"])
Expand Down
3 changes: 3 additions & 0 deletions napalm/junos/junos.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,9 @@ def get_environment(self):
structured_object_data["class"] = current_class

if structured_object_data["class"] == "Power":
# Make sure naming is consistent
sensor_object = sensor_object.replace("PEM", "Power Supply")

# Create a dict for the 'power' key
try:
environment_data["power"][sensor_object] = {}
Expand Down
7 changes: 3 additions & 4 deletions napalm/nxos/nxos.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from napalm.base.exceptions import CommandErrorException
from napalm.base.exceptions import ReplaceConfigException
from napalm.base.helpers import generate_regex_or
from napalm.base.helpers import as_number
from napalm.base.netmiko_helpers import netmiko_args
import napalm.base.constants as c

Expand Down Expand Up @@ -962,17 +963,15 @@ def get_bgp_neighbors(self):

for neighbor_dict in neighbors_list:
neighborid = napalm.base.helpers.ip(neighbor_dict["neighborid"])
remoteas = napalm.base.helpers.as_number(
neighbor_dict["neighboras"]
)
remoteas = as_number(neighbor_dict["neighboras"])
state = str(neighbor_dict["state"])

bgp_state = bgp_state_dict[state]
afid_dict = af_name_dict[int(af_dict["af-id"])]
safi_name = afid_dict[int(saf_dict["safi"])]

result_peer_dict = {
"local_as": int(vrf_dict["vrf-local-as"]),
"local_as": as_number(vrf_dict["vrf-local-as"]),
"remote_as": remoteas,
"remote_id": neighborid,
"is_enabled": bgp_state["is_enabled"],
Expand Down
113 changes: 113 additions & 0 deletions napalm/nxos_ssh/nxos_ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,17 @@ def __init__(self, hostname, username, password, timeout=60, optional_args=None)
hostname, username, password, timeout=timeout, optional_args=optional_args
)
self.platform = "nxos_ssh"
self.connector_type_map = {
"1000base-LH": "LC_CONNECTOR",
"1000base-SX": "LC_CONNECTOR",
"1000base-T": "Unknown",
"10Gbase-LR": "LC_CONNECTOR",
"10Gbase-SR": "LC_CONNECTOR",
"SFP-H10GB-CU1M": "DAC_CONNECTOR",
"SFP-H10GB-CU1.45M": "DAC_CONNECTOR",
"SFP-H10GB-CU3M": "DAC_CONNECTOR",
"SFP-H10GB-CU3.45M": "DAC_CONNECTOR",
}

def open(self):
self.device = self._netmiko_open(
Expand Down Expand Up @@ -1528,3 +1539,105 @@ def get_vlans(self):
"interfaces": self._parse_vlan_ports(vlan["vlanshowplist-ifidx"]),
}
return vlans

def get_optics(self):
command = "show interface transceiver details"
output = self._send_command(command)

# Formatting data into return data structure
optics_detail = {}

# Extraction Regexps
port_ts_re = re.compile(r"^Ether.*?(?=\nEther|\Z)", re.M | re.DOTALL)
port_re = re.compile(r"^(Ether.*)[ ]*?$", re.M)
vendor_re = re.compile("name is (.*)$", re.M)
vendor_part_re = re.compile("part number is (.*)$", re.M)
vendor_rev_re = re.compile("revision is (.*)$", re.M)
serial_no_re = re.compile("serial number is (.*)$", re.M)
type_no_re = re.compile("type is (.*)$", re.M)
rx_instant_re = re.compile(r"Rx Power[ ]+(?:(\S+?)[ ]+dBm|(N.A))", re.M)
tx_instant_re = re.compile(r"Tx Power[ ]+(?:(\S+?)[ ]+dBm|(N.A))", re.M)
current_instant_re = re.compile(r"Current[ ]+(?:(\S+?)[ ]+mA|(N.A))", re.M)

port_ts_l = port_ts_re.findall(output)

for port_ts in port_ts_l:
port = port_re.search(port_ts).group(1)
# No transceiver is present in those case
if "transceiver is not present" in port_ts:
continue
if "transceiver is not applicable" in port_ts:
continue
port_detail = {"physical_channels": {"channel": []}}
# No metric present
vendor = vendor_re.search(port_ts).group(1)
vendor_part = vendor_part_re.search(port_ts).group(1)
vendor_rev = vendor_rev_re.search(port_ts).group(1)
serial_no = serial_no_re.search(port_ts).group(1)
type_s = type_no_re.search(port_ts).group(1)
state = {
"vendor": vendor.strip(),
"vendor_part": vendor_part.strip(),
"vendor_rev": vendor_rev.strip(),
"serial_no": serial_no.strip(),
"connector_type": self.connector_type_map.get(type_s, "Unknown"),
}
if "DOM is not supported" not in port_ts:
res = rx_instant_re.search(port_ts)
input_power = res.group(1) or res.group(2)
res = tx_instant_re.search(port_ts)
output_power = res.group(1) or res.group(2)
res = current_instant_re.search(port_ts)
current = res.group(1) or res.group(2)

# If interface is shutdown it returns "N/A" as output power
# or "N/A" as input power
# Converting that to -100.0 float
try:
float(output_power)
except ValueError:
output_power = -100.0
try:
float(input_power)
except ValueError:
input_power = -100.0
try:
float(current)
except ValueError:
current = -100.0

# Defaulting avg, min, max values to -100.0 since device does not
# return these values
optic_states = {
"index": 0,
"state": {
"input_power": {
"instant": (
float(input_power) if "input_power" else -100.0
),
"avg": -100.0,
"min": -100.0,
"max": -100.0,
},
"output_power": {
"instant": (
float(output_power) if "output_power" else -100.0
),
"avg": -100.0,
"min": -100.0,
"max": -100.0,
},
"laser_bias_current": {
"instant": (float(current) if "current" else -100.0),
"avg": 0.0,
"min": 0.0,
"max": 0.0,
},
},
}
port_detail["physical_channels"]["channel"].append(optic_states)

port_detail["state"] = state
optics_detail[port] = port_detail

return optics_detail
8 changes: 4 additions & 4 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
black==19.10b0
coveralls==2.0.0
coveralls==2.1.1
ddt==1.4.1
flake8-import-order==0.18.1
pytest==5.4.2
pytest-cov==2.8.1
pytest==5.4.3
pytest-cov==2.10.0
pytest-json==0.4.0
pytest-pythonpath==0.7.3
pylama==7.7.1
mock==4.0.2
tox==3.15.0
tox==3.18.0
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",
version="3.0.1",
version="3.1.0",
packages=find_packages(exclude=("test*",)),
test_suite="test_base",
author="David Barroso, Kirk Byers, Mircea Ulinic",
Expand Down
Loading

0 comments on commit 4af2aba

Please sign in to comment.