Skip to content

Commit

Permalink
Merge pull request #1688 from napalm-automation/develop
Browse files Browse the repository at this point in the history
Merge forward from develop to master
  • Loading branch information
mirceaulinic authored Jul 11, 2022
2 parents bfe9778 + ef8326c commit ec15b3d
Show file tree
Hide file tree
Showing 22 changed files with 2,460 additions and 51 deletions.
2 changes: 1 addition & 1 deletion docs/support/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ _ EOS Junos IOS-XR (NETCONF) IOS-XR (XML-Agent)
===================== ========== ===== ================ ================== ============== ==============
**Config. replace** Yes Yes Yes Yes Yes Yes
**Config. merge** Yes Yes Yes Yes Yes Yes
**Commit Confirm** Yes Yes No No No No
**Commit Confirm** Yes Yes No No No Yes
**Compare config** Yes Yes Yes Yes [#c1]_ Yes [#c4]_ Yes
**Atomic Changes** Yes Yes Yes Yes Yes/No [#c5]_ Yes/No [#c5]_
**Rollback** Yes [#c2]_ Yes Yes Yes Yes/No [#c5]_ Yes
Expand Down
1 change: 1 addition & 0 deletions napalm/base/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
}
LLDP_CAPAB_TRANFORM_TABLE = {
"o": "other",
"n/a": "other",
"p": "repeater",
"b": "bridge",
"w": "wlan-access-point",
Expand Down
30 changes: 23 additions & 7 deletions napalm/eos/eos.py
Original file line number Diff line number Diff line change
Expand Up @@ -846,16 +846,32 @@ def extract_temperature_data(data):
# Matches either of
# Mem: 3844356k total, 3763184k used, 81172k free, 16732k buffers ( 4.16 > )
# KiB Mem: 32472080 total, 5697604 used, 26774476 free, 372052 buffers ( 4.16 < )
# MiB Mem : 3889.2 total, 150.3 free, 1104.5 used, 2634.4 buff/cache (4.27 < )
mem_regex = (
r"[^\d]*(?P<total>\d+)[k\s]+total,"
r"\s+(?P<used>\d+)[k\s]+used,"
r"\s+(?P<free>\d+)[k\s]+free,.*"
r"^(?:(?P<unit>\S+)\s+)?Mem\s*:"
r"\s+(?P<total>[0-9.]+)[k\s]+total,"
r"(?:\s+(?P<used1>[0-9.]+)[k\s]+used,)?"
r"\s+(?P<free>[0-9.]+)[k\s]+free,"
r"(?:\s+(?P<used2>[0-9.]+)[k\s]+used,)?"
r".*"
)
m = re.match(mem_regex, cpu_lines[3])
environment_counters["memory"] = {
"available_ram": int(m.group("total")),
"used_ram": int(m.group("used")),
}

def _parse_memory(unit, total, used):
if unit == "MiB":
return {
"available_ram": int(float(total) * 1024),
"used_ram": int(float(used) * 1024),
}
return {
"available_ram": int(total),
"used_ram": int(used),
}

environment_counters["memory"] = _parse_memory(
m.group("unit"), m.group("total"), m.group("used1") or m.group("used2")
)

return environment_counters

def _transform_lldp_capab(self, capabilities):
Expand Down
151 changes: 131 additions & 20 deletions napalm/ios/ios.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
MergeConfigException,
ConnectionClosedException,
CommandErrorException,
CommitConfirmException,
)
from napalm.base.helpers import (
canonical_interface_name,
Expand Down Expand Up @@ -502,15 +503,38 @@ def commit_config(self, message="", revert_in=None):
If merge operation, perform copy <file> running-config.
"""
if revert_in is not None:
raise NotImplementedError(
"Commit confirm has not been implemented on this platform."
)
CISCO_TIMER_MIN = 1
CISCO_TIMER_MAX = 120
ARCHIVE_DISABLED_MESSAGE = (
"For Cisco devices, revert_in requires 'archive' feature to be enabled."
)
revert_in_min = None

if message:
raise NotImplementedError(
"Commit message not implemented for this platform"
)

if revert_in is not None:
if not self._check_archive_feature():
raise CommitConfirmException(ARCHIVE_DISABLED_MESSAGE)
elif not CISCO_TIMER_MIN * 60 <= revert_in <= CISCO_TIMER_MAX * 60:
msg = (
"For Cisco IOS devices revert_in is rounded down to the nearest minute,"
"pass revert_in as a multiple of 60 between {} and {}".format(
CISCO_TIMER_MIN * 60, CISCO_TIMER_MAX * 60
)
)
raise CommitConfirmException(msg)
else:
revert_in_min = int(revert_in / 60)

if self.has_pending_commit():
raise CommandErrorException(
"Configuration session already in progress, cannot "
"perform configuration actions"
)

# Always generate a rollback config on commit
self._gen_rollback_cfg()

Expand All @@ -520,8 +544,16 @@ def commit_config(self, message="", revert_in=None):
cfg_file = self._gen_full_path(filename)
if not self._check_file_exists(cfg_file):
raise ReplaceConfigException("Candidate config file does not exist")
if self.auto_rollback_on_error:
if revert_in_min and self.auto_rollback_on_error:
cmd = "configure replace {} force revert trigger error timer {}".format(
cfg_file, revert_in_min
)
elif self.auto_rollback_on_error:
cmd = "configure replace {} force revert trigger error".format(cfg_file)
elif revert_in_min:
cmd = "configure replace {} force revert timer {}".format(
cfg_file, revert_in_min
)
else:
cmd = "configure replace {} force".format(cfg_file)
output = self._commit_handler(cmd)
Expand All @@ -534,14 +566,29 @@ def commit_config(self, message="", revert_in=None):
msg = "Candidate config could not be applied\n{}".format(output)
raise ReplaceConfigException(msg)
elif "%Please turn config archive on" in output:
msg = "napalm-ios replace() requires Cisco 'archive' feature to be enabled."
raise ReplaceConfigException(msg)
if revert_in_min:
raise CommitConfirmException(ARCHIVE_DISABLED_MESSAGE)
else:
msg = "napalm-ios replace() requires Cisco 'archive' feature to be enabled"
raise ReplaceConfigException(msg)
else:
# Merge operation
filename = self.merge_cfg
cfg_file = self._gen_full_path(filename)
if not self._check_file_exists(cfg_file):
raise MergeConfigException("Merge source config file does not exist")
if revert_in_min is not None:
# Enter config mode with a revert timer and exit config mode
try:
self.device.config_mode(
config_command="configure terminal revert timer {}".format(
revert_in_min
)
)
self.device.exit_config_mode()
except ValueError:
raise MergeConfigException(ARCHIVE_DISABLED_MESSAGE)

cmd = "copy {} running-config".format(cfg_file)
output = self._commit_handler(cmd)
if "Invalid input detected" in output:
Expand All @@ -553,8 +600,61 @@ def commit_config(self, message="", revert_in=None):
# After a commit - we no longer know whether this is configured or not.
self.prompt_quiet_configured = None

# Save config to startup (both replace and merge)
output += self.device.save_config()
if revert_in_min is None:
# Save config to startup (both replace and merge)
output += self.device.save_config()

def _check_archive_feature(self):
cmd = "show archive"
output = self.device.send_command(cmd)
if "Archive feature not enabled" in output:
return False
return True

def has_pending_commit(self):
pending_commits = self._get_pending_commits()
return bool(pending_commits)

def _get_pending_commits(self):
if self._check_archive_feature():
cmd = "show archive config rollback timer"
output = self.device.send_command(cmd)
else:
return {}
if "No Rollback Confirmed Change pending" in output:
return {}
match_strings = r"|".join(
[
r"Time configured.*?: (?P<configured>.*)",
r"Timer type: (?P<type>.*)",
r"Timer value: (?P<timer>.*)",
r"User: (?P<user>.*)",
]
)
keys = ["configured", "type", "timer", "user"]
matches = re.finditer(match_strings, output)
pending_commits = {}
for match in matches:
for key in keys:
if match.groupdict().get(key):
pending_commits.update({key: match.groupdict().get(key)})

return pending_commits

def confirm_commit(self):
"""Send final commit to confirm an in-proces commit that requires confirmation."""
if self.has_pending_commit():
pending = self._get_pending_commits()
if pending.get("user") == self.username:
self.device.send_command("configure confirm")
self.device.save_config()
else:
raise CommitConfirmException(
"Configuration session active but not owned by"
" {} cannot confirm commit".format(self.username)
)
else:
raise CommitConfirmException("No pending configuration")

def discard_config(self):
"""Discard loaded candidate configurations."""
Expand All @@ -572,18 +672,29 @@ def _discard_config(self):

def rollback(self):
"""Rollback configuration to filename or to self.rollback_cfg file."""
filename = self.rollback_cfg
cfg_file = self._gen_full_path(filename)
if not self._check_file_exists(cfg_file):
raise ReplaceConfigException("Rollback config file does not exist")
cmd = "configure replace {} force".format(cfg_file)
self._commit_handler(cmd)

# After a rollback - we no longer know whether this is configured or not.
self.prompt_quiet_configured = None
if self.has_pending_commit():
if self._get_pending_commits().get("user") == self.username:
cmd = "configure revert now"
self._commit_handler(cmd)
self.device.save_config()
else:
raise CommitConfirmException(
"Configuration session active but not owned by {} "
"cannot rollback".format(self.username)
)
else:
filename = self.rollback_cfg
cfg_file = self._gen_full_path(filename)
if not self._check_file_exists(cfg_file):
raise ReplaceConfigException("Rollback config file does not exist")
cmd = "configure replace {} force".format(cfg_file)
self._commit_handler(cmd)

# After a rollback - we no longer know whether this is configured or not.
self.prompt_quiet_configured = None

# Save config to startup
self.device.save_config()
# Save config to startup
self.device.save_config()

def _inline_tcl_xfer(
self, source_file=None, source_config=None, dest_file=None, file_system=None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Value REMOTE_SYSTEM_CAPAB (.*)
Value REMOTE_SYSTEM_ENABLE_CAPAB (.*)

Start
^Local Intf\s*?[:-]\s+${LOCAL_INTERFACE}
^Local Int(?:er)?f(?:ace)?\s*?[:-]\s+${LOCAL_INTERFACE}
^Chassis id\s*?[:-]\s+${REMOTE_CHASSIS_ID}
^Port id\s*?[:-]\s+${REMOTE_PORT}
^Port Description\s*?[:-]\s+${REMOTE_PORT_DESCRIPTION}
Expand Down
61 changes: 53 additions & 8 deletions napalm/iosxr/iosxr.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from napalm.pyIOSXR.exceptions import ConnectError
from napalm.pyIOSXR.exceptions import TimeoutError
from napalm.pyIOSXR.exceptions import InvalidInputError
from napalm.pyIOSXR.exceptions import XMLCLIError

# import NAPALM base
import napalm.base.helpers
Expand Down Expand Up @@ -165,16 +166,60 @@ def get_facts(self):
"interface_list": [],
}

facts_rpc_request = (
"<Get><Operational><SystemTime/><PlatformInventory><RackTable>"
"<Rack><Naming><Name>0</Name></Naming>"
"<Attributes><BasicInfo/></Attributes>"
"</Rack></RackTable></PlatformInventory></Operational></Get>"
)
facts_rpc_request = """
<Get>
<Operational>
<SystemTime/>
<PlatformInventory>
<RackTable>
<Rack>
<Naming>
<Name>0</Name>
</Naming>
<Attributes>
<BasicInfo/>
</Attributes>
</Rack>
</RackTable>
</PlatformInventory>
</Operational>
</Get>
"""

# IOS-XR 7.3.3 and possibly other 7.X versions have this located in
# different location in the XML tree
facts_rpc_request_alt = """
<Get>
<Operational>
<SystemTime/>
<Inventory>
<Entities>
<Entity>
<Naming>
<Name>Rack 0</Name>
</Naming>
<Attributes>
<InvBasicBag></InvBasicBag>
</Attributes>
</Entity>
</Entities>
</Inventory>
</Operational>
</Get>
"""

facts_rpc_reply = ETREE.fromstring(self.device.make_rpc_call(facts_rpc_request))
system_time_xpath = ".//SystemTime/Uptime"
platform_attr_xpath = ".//RackTable/Rack/Attributes/BasicInfo"
try:
facts_rpc_reply = ETREE.fromstring(
self.device.make_rpc_call(facts_rpc_request)
)
platform_attr_xpath = ".//RackTable/Rack/Attributes/BasicInfo"
except XMLCLIError:
facts_rpc_reply = ETREE.fromstring(
self.device.make_rpc_call(facts_rpc_request_alt)
)
platform_attr_xpath = ".//Entities/Entity/Attributes/InvBasicBag"

system_time_tree = facts_rpc_reply.xpath(system_time_xpath)[0]
try:
platform_attr_tree = facts_rpc_reply.xpath(platform_attr_xpath)[0]
Expand Down
2 changes: 1 addition & 1 deletion napalm/junos/junos.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ def _get_pending_commits(self):
commit_comment = commit_comment_element.text

sys_uptime_info = self.device.rpc.get_system_uptime_information()
current_time_element = sys_uptime_info.find("./current-time/date-time")
current_time_element = sys_uptime_info.find(".//current-time/date-time")
current_time = int(current_time_element.attrib["seconds"])

# Msg from Jnpr: 'commit confirmed, rollback in 5mins'
Expand Down
18 changes: 9 additions & 9 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
black==22.3.0
black==22.6.0
coveralls==3.3.1
ddt==1.5.0
flake8-import-order==0.18.1
Expand All @@ -7,11 +7,11 @@ pytest-cov==3.0.0
pytest-json==0.4.0
pylama==8.2.1
mock==4.0.3
tox==3.25.0
mypy==0.960
types-requests==2.27.30
types-six==1.16.15
types-setuptools==57.4.17
types-PyYAML==6.0.8
ttp==0.8.4
ttp_templates==0.1.4
tox==3.25.1
mypy==0.961
types-requests==2.28.0
types-six==1.16.17
types-setuptools==57.4.18
types-PyYAML==6.0.9
ttp==0.9.0
ttp_templates==0.3.0
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ cffi>=1.11.3
paramiko>=2.6.0
requests>=2.7.0
future
textfsm
textfsm<=1.1.2
jinja2
netaddr
pyYAML
Expand All @@ -16,3 +16,4 @@ ncclient
ttp
ttp_templates
netutils>=1.0.0
typing-extensions>=4.3.0
Loading

0 comments on commit ec15b3d

Please sign in to comment.