From 56324e4c5bf04d2b701de7836650dca8b4b9899f Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 19 Nov 2017 08:19:15 -0800 Subject: [PATCH 1/4] Add framework to support commit confirmed --- napalm/base/base.py | 6 +++++- napalm/base/test/base.py | 17 +++++++++++++++++ napalm/eos/eos.py | 5 ++++- napalm/ios/ios.py | 5 ++++- napalm/iosxr/iosxr.py | 5 ++++- napalm/junos/junos.py | 5 ++++- napalm/nxos/nxos.py | 5 ++++- napalm/nxos_ssh/nxos_ssh.py | 5 ++++- 8 files changed, 46 insertions(+), 7 deletions(-) diff --git a/napalm/base/base.py b/napalm/base/base.py index c7c6ee347..0b073ef63 100644 --- a/napalm/base/base.py +++ b/napalm/base/base.py @@ -178,12 +178,16 @@ def compare_config(self): """ raise NotImplementedError - def commit_config(self): + def commit_config(self, confirmed=None): """ Commits the changes requested by the method load_replace_candidate or load_merge_candidate. """ raise NotImplementedError + def commit_confirm(self): + """Confirm pending commit.""" + raise NotImplementedError + def discard_config(self): """ Discards the configuration loaded into the candidate. diff --git a/napalm/base/test/base.py b/napalm/base/test/base.py index 1f991412c..0b5fa8c92 100644 --- a/napalm/base/test/base.py +++ b/napalm/base/test/base.py @@ -69,6 +69,23 @@ def test_replacing_and_committing_config(self): self.assertEqual(len(diff), 0) + def test_replacing_and_committing_config_with_confirm(self): + try: + self.device.load_replace_candidate(filename='%s/new_good.conf' % self.vendor) + self.device.commit_config(confirmed=5) + self.device.commit_confirm() + except NotImplementedError: + raise SkipTest() + + # The diff should be empty as the configuration has been committed already + diff = self.device.compare_config() + + # Reverting changes + self.device.load_replace_candidate(filename='%s/initial.conf' % self.vendor) + self.device.commit_config() + + self.assertEqual(len(diff), 0) + def test_replacing_config_with_typo(self): result = False try: diff --git a/napalm/eos/eos.py b/napalm/eos/eos.py index 74bc678d1..ffca15641 100644 --- a/napalm/eos/eos.py +++ b/napalm/eos/eos.py @@ -192,8 +192,11 @@ def compare_config(self): return result.strip() - def commit_config(self): + def commit_config(self, confirmed=None): """Implementation of NAPALM method commit_config.""" + if confirmed is not None: + raise NotImplementedError + commands = [] commands.append('copy startup-config flash:rollback-0') commands.append('configure session {}'.format(self.config_session)) diff --git a/napalm/ios/ios.py b/napalm/ios/ios.py index 13dd1bab4..ee531b426 100644 --- a/napalm/ios/ios.py +++ b/napalm/ios/ios.py @@ -388,12 +388,15 @@ def _commit_hostname_handler(self, cmd): self.device.set_base_prompt() return output - def commit_config(self): + def commit_config(self, confirmed=None): """ If replacement operation, perform 'configure replace' for the entire config. If merge operation, perform copy running-config. """ + if confirmed is not None: + raise NotImplementedError + # Always generate a rollback config on commit self._gen_rollback_cfg() diff --git a/napalm/iosxr/iosxr.py b/napalm/iosxr/iosxr.py index 2b50e4875..3aedc9acf 100644 --- a/napalm/iosxr/iosxr.py +++ b/napalm/iosxr/iosxr.py @@ -145,7 +145,10 @@ def compare_config(self): else: return self.device.compare_config().strip() - def commit_config(self): + def commit_config(self, confirmed=None): + if confirmed is not None: + raise NotImplementedError + if self.replace: self.device.commit_replace_config() else: diff --git a/napalm/junos/junos.py b/napalm/junos/junos.py index a4a1fb100..ceda79ea1 100644 --- a/napalm/junos/junos.py +++ b/napalm/junos/junos.py @@ -234,8 +234,11 @@ def compare_config(self): else: return diff.strip() - def commit_config(self): + def commit_config(self, confirmed=None): """Commit configuration.""" + if confirmed is not None: + raise NotImplementedError + self.device.cu.commit(ignore_warning=self.ignore_warning) if not self.config_lock: self._unlock() diff --git a/napalm/nxos/nxos.py b/napalm/nxos/nxos.py index 9c4bd68f6..24e2eccb4 100644 --- a/napalm/nxos/nxos.py +++ b/napalm/nxos/nxos.py @@ -323,7 +323,10 @@ def _load_config(self): raise ReplaceConfigException(rollback_result['msg']) return True - def commit_config(self): + def commit_config(self, confirmed=None): + if confirmed is not None: + raise NotImplementedError + if self.loaded: self.backup_file = 'config_' + str(datetime.now()).replace(' ', '_') self._save_config(self.backup_file) diff --git a/napalm/nxos_ssh/nxos_ssh.py b/napalm/nxos_ssh/nxos_ssh.py index 2655d987d..a51ade535 100644 --- a/napalm/nxos_ssh/nxos_ssh.py +++ b/napalm/nxos_ssh/nxos_ssh.py @@ -672,7 +672,10 @@ def _load_cfg_from_checkpoint(self): return False return True - def commit_config(self): + def commit_config(self, confirmed=None): + if confirmed is not None: + raise NotImplementedError + if self.loaded: self.backup_file = 'config_' + str(datetime.now()).replace(' ', '_') # Create Checkpoint from current running-config From 34bbc19aa0428042f7c3c1c7812655a4c38a687f Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 19 Nov 2017 20:22:04 -0800 Subject: [PATCH 2/4] Cisco IOS proof of concept on commit confirmed --- napalm/base/base.py | 4 ++++ napalm/ios/ios.py | 35 ++++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/napalm/base/base.py b/napalm/base/base.py index 0b073ef63..748932b15 100644 --- a/napalm/base/base.py +++ b/napalm/base/base.py @@ -188,6 +188,10 @@ def commit_confirm(self): """Confirm pending commit.""" raise NotImplementedError + def commit_confirm_revert(self): + """Immediately revert pending commit confirm.""" + raise NotImplementedError + def discard_config(self): """ Discards the configuration loaded into the candidate. diff --git a/napalm/ios/ios.py b/napalm/ios/ios.py index ee531b426..d914661fb 100644 --- a/napalm/ios/ios.py +++ b/napalm/ios/ios.py @@ -388,15 +388,22 @@ def _commit_hostname_handler(self, cmd): self.device.set_base_prompt() return output + def commit_confirm(self): + """Confirm pending commit.""" + cmd = "configure confirm" + self.device.send_command_expect(cmd) + + def commit_abort(self): + """Immediately revert pending commit confirm.""" + cmd = "configure revert now" + self.device.send_command_expect(cmd) + def commit_config(self, confirmed=None): """ If replacement operation, perform 'configure replace' for the entire config. If merge operation, perform copy running-config. """ - if confirmed is not None: - raise NotImplementedError - # Always generate a rollback config on commit self._gen_rollback_cfg() @@ -406,10 +413,24 @@ def commit_config(self, confirmed=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: - cmd = 'configure replace {} force revert trigger error'.format(cfg_file) - else: - cmd = 'configure replace {} force'.format(cfg_file) + if not self.auto_rollback_on_error: + raise NotImplementedError("auto_rollback_on_error attribute has been removed") + + cmd = 'configure replace {} force revert trigger error'.format(cfg_file) + if confirmed is not None: + confirmed = int(confirmed) + # Ensure within Cisco IOS thresholds (minutes) + CISCO_LOW_THRESHOLD = 1 + CISCO_HIGH_THRESHOLD = 120 + if CISCO_LOW_THRESHOLD <= confirmed <= CISCO_HIGH_THRESHOLD: + cmd = 'configure replace flash:/candidate_config.txt force ' \ + 'revert trigger error timer {}'.format(confirmed) + else: + msg = "Invalid value for 'confirmed'; Cisco IOS requires a value " \ + "between: {}-{} minutes".format(CISCO_LOW_THRESHOLD, + CISCO_HIGH_THRESHOLD) + raise ValueError(msg) + output = self._commit_hostname_handler(cmd) if ('original configuration has been successfully restored' in output) or \ ('error' in output.lower()) or \ From 0b93f4fabc3b64533e2ee5e56d9c350d38c99bb1 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Tue, 21 Nov 2017 20:01:17 -0800 Subject: [PATCH 3/4] Adding message argument support --- napalm/base/base.py | 6 +----- napalm/eos/eos.py | 2 +- napalm/ios/ios.py | 7 +++---- napalm/iosxr/iosxr.py | 2 +- napalm/junos/junos.py | 2 +- napalm/nxos/nxos.py | 2 +- napalm/nxos_ssh/nxos_ssh.py | 2 +- 7 files changed, 9 insertions(+), 14 deletions(-) diff --git a/napalm/base/base.py b/napalm/base/base.py index 748932b15..8b084a40c 100644 --- a/napalm/base/base.py +++ b/napalm/base/base.py @@ -178,7 +178,7 @@ def compare_config(self): """ raise NotImplementedError - def commit_config(self, confirmed=None): + def commit_config(self, confirmed=None, message=None): """ Commits the changes requested by the method load_replace_candidate or load_merge_candidate. """ @@ -188,10 +188,6 @@ def commit_confirm(self): """Confirm pending commit.""" raise NotImplementedError - def commit_confirm_revert(self): - """Immediately revert pending commit confirm.""" - raise NotImplementedError - def discard_config(self): """ Discards the configuration loaded into the candidate. diff --git a/napalm/eos/eos.py b/napalm/eos/eos.py index ffca15641..6a6f3d1f8 100644 --- a/napalm/eos/eos.py +++ b/napalm/eos/eos.py @@ -192,7 +192,7 @@ def compare_config(self): return result.strip() - def commit_config(self, confirmed=None): + def commit_config(self, confirmed=None, message=None): """Implementation of NAPALM method commit_config.""" if confirmed is not None: raise NotImplementedError diff --git a/napalm/ios/ios.py b/napalm/ios/ios.py index d914661fb..404d8ac5b 100644 --- a/napalm/ios/ios.py +++ b/napalm/ios/ios.py @@ -393,14 +393,13 @@ def commit_confirm(self): cmd = "configure confirm" self.device.send_command_expect(cmd) - def commit_abort(self): + def _commit_abort(self): """Immediately revert pending commit confirm.""" cmd = "configure revert now" self.device.send_command_expect(cmd) - def commit_config(self, confirmed=None): - """ - If replacement operation, perform 'configure replace' for the entire config. + def commit_config(self, confirmed=None, message=None): + """If replacement operation, perform 'configure replace' for the entire config. If merge operation, perform copy running-config. """ diff --git a/napalm/iosxr/iosxr.py b/napalm/iosxr/iosxr.py index 3aedc9acf..fcd22775d 100644 --- a/napalm/iosxr/iosxr.py +++ b/napalm/iosxr/iosxr.py @@ -145,7 +145,7 @@ def compare_config(self): else: return self.device.compare_config().strip() - def commit_config(self, confirmed=None): + def commit_config(self, confirmed=None, message=None): if confirmed is not None: raise NotImplementedError diff --git a/napalm/junos/junos.py b/napalm/junos/junos.py index ceda79ea1..576117ba9 100644 --- a/napalm/junos/junos.py +++ b/napalm/junos/junos.py @@ -234,7 +234,7 @@ def compare_config(self): else: return diff.strip() - def commit_config(self, confirmed=None): + def commit_config(self, confirmed=None, message=None): """Commit configuration.""" if confirmed is not None: raise NotImplementedError diff --git a/napalm/nxos/nxos.py b/napalm/nxos/nxos.py index 24e2eccb4..af158d6b3 100644 --- a/napalm/nxos/nxos.py +++ b/napalm/nxos/nxos.py @@ -323,7 +323,7 @@ def _load_config(self): raise ReplaceConfigException(rollback_result['msg']) return True - def commit_config(self, confirmed=None): + def commit_config(self, confirmed=None, message=None): if confirmed is not None: raise NotImplementedError diff --git a/napalm/nxos_ssh/nxos_ssh.py b/napalm/nxos_ssh/nxos_ssh.py index a51ade535..b0fc37929 100644 --- a/napalm/nxos_ssh/nxos_ssh.py +++ b/napalm/nxos_ssh/nxos_ssh.py @@ -672,7 +672,7 @@ def _load_cfg_from_checkpoint(self): return False return True - def commit_config(self, confirmed=None): + def commit_config(self, confirmed=None, message=None): if confirmed is not None: raise NotImplementedError From b923e6ff1fdaca595cd79b34d2ad05e01c880ed4 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 21 Jan 2018 10:55:04 -0800 Subject: [PATCH 4/4] Add new methods for immediate revert and checking if pending commit confirms exist (#628) --- napalm/base/base.py | 16 +++++++++++----- napalm/ios/ios.py | 11 ++++++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/napalm/base/base.py b/napalm/base/base.py index 8b084a40c..f5e2121b7 100644 --- a/napalm/base/base.py +++ b/napalm/base/base.py @@ -188,16 +188,22 @@ def commit_confirm(self): """Confirm pending commit.""" raise NotImplementedError - def discard_config(self): + def commit_confirm_revert(self): + """Abort pending commit confirm immediately and rollback to previous state""" + raise NotImplementedError + + def has_pending_commit(self): """ - Discards the configuration loaded into the candidate. + :return A boolean indicating whether any pending commit confirms exist. """ raise NotImplementedError + def discard_config(self): + """Discards the configuration loaded into the candidate.""" + raise NotImplementedError + def rollback(self): - """ - If changes were made, revert changes to the original state. - """ + """If changes were made, revert changes to the original state.""" raise NotImplementedError def get_facts(self): diff --git a/napalm/ios/ios.py b/napalm/ios/ios.py index 27f34afb9..6b4ac6e73 100644 --- a/napalm/ios/ios.py +++ b/napalm/ios/ios.py @@ -425,11 +425,20 @@ def commit_confirm(self): cmd = "configure confirm" self.device.send_command_expect(cmd) - def _commit_abort(self): + def commit_confirm_revert(self): """Immediately revert pending commit confirm.""" cmd = "configure revert now" self.device.send_command_expect(cmd) + def has_pending_commit(self): + """ + :return A boolean indicating whether any pending commit confirms exist. + """ + cmd = "show archive config rollback timer" + search_pattern = r"No Rollback Confirmed Change pending" + output = self.device.send_command_expect(cmd) + return not bool(re.search(search_pattern, output, flags=re.I)) + def commit_config(self, confirmed=None, message=None): """If replacement operation, perform 'configure replace' for the entire config.