diff --git a/napalm/base/base.py b/napalm/base/base.py index c7c6ee347..748932b15 100644 --- a/napalm/base/base.py +++ b/napalm/base/base.py @@ -178,12 +178,20 @@ 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 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/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 9338cbe5a..d951f0bdd 100644 --- a/napalm/eos/eos.py +++ b/napalm/eos/eos.py @@ -187,8 +187,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 fee7dc053..b559d96ed 100644 --- a/napalm/ios/ios.py +++ b/napalm/ios/ios.py @@ -388,7 +388,17 @@ def _commit_hostname_handler(self, cmd): self.device.set_base_prompt() return output - def commit_config(self): + 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. @@ -403,10 +413,24 @@ def commit_config(self): 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 \ 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