diff --git a/napalm/base/base.py b/napalm/base/base.py index e1f023601..0fcae3bce 100644 --- a/napalm/base/base.py +++ b/napalm/base/base.py @@ -178,12 +178,19 @@ 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. + + :param confirmed (optional): Number of minutes until rollback occurs. Default is None \ + (no confirmation). """ raise NotImplementedError + def commit_confirm(self): + """Confirm pending commit.""" + raise NotImplementedError + def discard_config(self): """ Discards the configuration loaded into the candidate. @@ -1587,6 +1594,6 @@ def compliance_report(self, validation_file=None, validation_source=None): def _canonical_int(self, interface): """Expose the helper function within this class.""" if self.use_canonical_interface is True: - return napalm.base.helpers.canonical_interface_name(interface, update_os_mapping=None) + return napalm.base.helpers.canonical_interface_name(interface, addl_name_map=None) else: return interface diff --git a/napalm/base/helpers.py b/napalm/base/helpers.py index d8fe510d5..f00a31c9f 100644 --- a/napalm/base/helpers.py +++ b/napalm/base/helpers.py @@ -258,52 +258,55 @@ def as_number(as_number_val): return int(as_number_str) -def int_split_on_match(split_interface): - ''' - simple fuction to split on first digit, slash, or space match - ''' - head = split_interface.rstrip(r'/\0123456789 ') - tail = split_interface[len(head):].lstrip() - return head, tail - - -def canonical_interface_name(interface, update_os_mapping=None): - ''' - Function to retun interface canonical name +def split_interface(intf_name): + """Split an interface name based on first digit, slash, or space match.""" + head = intf_name.rstrip(r'/\0123456789 ') + tail = intf_name[len(head):].lstrip() + return (head, tail) + + +def canonical_interface_name(interface, addl_name_map=None): + """Function to return an interface's canonical name (fully expanded name). + This puposely does not use regex, or first X characters, to ensure there is no chance for false positives. As an example, Po = PortChannel, and PO = POS. With either character or regex, that would produce a false positive. - ''' - - interface_type, interface_number = int_split_on_match(interface) + """ + name_map = {} + name_map.update(base_interfaces) + interface_type, interface_number = split_interface(interface) - if isinstance(update_os_mapping, dict): - base_interfaces.update(update_os_mapping) + if isinstance(addl_name_map, dict): + name_map.update(addl_name_map) # check in dict for mapping - if base_interfaces.get(interface_type): - long_int = base_interfaces.get(interface_type) + if name_map.get(interface_type): + long_int = name_map.get(interface_type) return long_int + str(interface_number) - # if nothing matched, at least return the original + # if nothing matched, return the original name else: return interface -def abbreviated_interface_name(interface, update_os_mapping=None): - ''' - Function to retun interface canonical name - This puposely does not use regex, or first X characters, to ensure - there is no chance for false positives. As an example, Po = PortChannel, and - PO = POS. With either character or regex, that would produce a false positive. - ''' +def abbreviated_interface_name(interface, addl_name_map=None): + """Function to return an abbreviated representation of the interface name.""" + reverse_name_map = {} + reverse_name_map.update(reverse_mapping) + interface_type, interface_number = split_interface(interface) - interface_type, interface_number = int_split_on_match(interface) + if isinstance(addl_name_map, dict): + reverse_name_map.update(addl_name_map) - if isinstance(update_os_mapping, dict): - base_interfaces.update(update_os_mapping) - # check in dict for mapping + # Try to ensure canonical type. if base_interfaces.get(interface_type): - long_int = base_interfaces.get(interface_type) - return reverse_mapping[long_int] + str(interface_number) - # if nothing matched, at least return the original + canonical_type = base_interfaces.get(interface_type) else: - return interface + canonical_type = interface_type + + try: + abbreviated_name = reverse_mapping[canonical_type] + str(interface_number) + return abbreviated_name + except KeyError: + pass + + # If abbreviated name lookup fails, return original name + return interface 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..e00a7666a 100644 --- a/napalm/eos/eos.py +++ b/napalm/eos/eos.py @@ -187,8 +187,10 @@ 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..dacbb277b 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..f63a6bda7 100644 --- a/napalm/junos/junos.py +++ b/napalm/junos/junos.py @@ -234,8 +234,10 @@ 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 a16fed869..5c0a134cf 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