diff --git a/annet/annlib/netdev/devdb/data/devdb.json b/annet/annlib/netdev/devdb/data/devdb.json index 233849d9..7e51bbf1 100644 --- a/annet/annlib/netdev/devdb/data/devdb.json +++ b/annet/annlib/netdev/devdb/data/devdb.json @@ -53,6 +53,7 @@ "Huawei.CE.CE8800.CE8850": " CE8850", "Huawei.CE.CE8800.CE8851": " CE8851", "Huawei.CE.CE8800.CE8855": " CE8855", + "Huawei.CE.CE8800.CE8875": " CE8875", "Huawei.CE.CE9800": " CE98\\d\\d", "Huawei.CE.CE9800.CE9855": " CE9855", "Huawei.CE.CE9800.CE9860": " CE9860", diff --git a/annet/annlib/tabparser.py b/annet/annlib/tabparser.py index 416bc5ab..fb583488 100644 --- a/annet/annlib/tabparser.py +++ b/annet/annlib/tabparser.py @@ -2,7 +2,7 @@ import itertools import re from collections import OrderedDict as odict -from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Tuple, Union, List from .types import Op @@ -271,8 +271,63 @@ class CiscoFormatter(BlockExitFormatter): def __init__(self, indent=" "): super().__init__("exit", indent) + def _split_indent( + self, line: str, indent: int, block_exit_strings: List[str] + ) -> Tuple[List[str], int]: + """ + The small helper calculates indent shift based on block exit string. + If configuration line has non-default block exit string it means that + new subsection is started and indent should be increased. + If configuration line exists in list of block exit strings, + it means that subsection is finished and indent should be decreased + + Args: + line: just configuration line + indent: current indent + block_exit_strings: list of previously seen block exit strings + Returns: + new indent and list of previously seen block exit strings + """ + + if line.strip() in block_exit_strings: + indent -= 1 + block_exit_strings.remove(line.strip()) + return block_exit_strings, indent + + block_exit_wrapped = [ + v for v in self.block_exit(FormatterContext(current=(line.strip(), {}))) + ] + + if not block_exit_wrapped or len(block_exit_wrapped) != 3: + return block_exit_strings, indent + if not isinstance(block_exit_wrapped[1], str): + return block_exit_strings, indent + if block_exit_wrapped[1] == self._block_exit: + return block_exit_strings, indent + + indent += 1 + block_exit_strings.append(block_exit_wrapped[1]) + return block_exit_strings, indent + def split(self, text): - return self.split_remove_spaces(text) + additional_indent = 0 + block_exit_strings = [self._block_exit] + tree = self.split_remove_spaces(text) + for i, item in enumerate(tree): + block_exit_strings, new_indent = self._split_indent( + item, additional_indent, block_exit_strings + ) + tree[i] = f"{' ' * additional_indent}{item}" + additional_indent = new_indent + return tree + + def block_exit(self, context: Optional[FormatterContext]) -> str: + current = context and context.row or "" + + if current.startswith(("address-family")): + yield from block_wrapper("exit-address-family") + else: + yield from super().block_exit(context) class AsrFormatter(BlockExitFormatter): diff --git a/annet/rulebook/cisco/misc.py b/annet/rulebook/cisco/misc.py index 2573ac21..a9b56937 100644 --- a/annet/rulebook/cisco/misc.py +++ b/annet/rulebook/cisco/misc.py @@ -57,93 +57,3 @@ def banner_login(rule, key, diff, **_): yield (False, f"banner login {key}", None) else: yield from common.default(rule, key, diff) - - -def bgp_diff(old, new, diff_pre, _pops=(Op.AFFECTED,)): - """ - Some oder versions of Cisco IOS doesn't create subsection for address family block. - - it looks like: - - router bgp 65111 - bgp router-id 1.1.1.1 - bgp log-neighbor-changes - neighbor SPINE peer-group - ! - address-family ipv4 - neighbor SPINE send-community both - neighbor SPINE soft-reconfiguration inbound - neighbor SPINE route-map TOR_IMPORT_SPINE in - neighbor SPINE route-map TOR_EXPORT_SPINE out - exit-address-family - - but should be - - router bgp 65111 - bgp router-id 1.1.1.1 - bgp log-neighbor-changes - neighbor SPINE peer-group - ! - address-family ipv4 - neighbor SPINE send-community both - neighbor SPINE soft-reconfiguration inbound - neighbor SPINE route-map TOR_IMPORT_SPINE in - neighbor SPINE route-map TOR_EXPORT_SPINE out - exit-address-family - - The diff_logic func do it before make diff. - """ - corrected_old = _create_subsections(old, "address-family") - - yield from common.default_diff(corrected_old, new, diff_pre, _pops) - - -def _create_subsections(data: OrderedDict[str, Any], sub_section_prefix: str) -> OrderedDict[str, Any]: - """ - Reorganizes the given OrderedDict to nest commands under their respective - sub_section_prefix keys. - - This function traverses the entries in the provided OrderedDict and groups - together all entries that are between keys with sub_section_prefix under those - keys as nested OrderedDicts. The reorganization keeps the order of entries - stable, only adding nesting where appropriate. - - Args: - data (OrderedDict): The original configuration to be transformed. - sub_section_prefix (str): Prefix of subsection key - - Returns: - OrderedDict: A new OrderedDict with nested 'address-family' sections. - """ - - result = OrderedDict() - sub_section = None - temp: OrderedDict = OrderedDict() - - for key, value in data.items(): - # make nested loop if found nested values - if value: - fixed_value: OrderedDict[str, Any] = _create_subsections(value, sub_section_prefix) - else: - fixed_value = value - if key.startswith(sub_section_prefix): - # in case of data has already had subsections - if value: - result[key] = fixed_value - continue - # if previous subsection present save collected data from temporary dict - if sub_section: - result[sub_section] = temp - # find a new subsection and initialize new dict - sub_section = key - temp = OrderedDict() - # put found data to temporary dict - elif sub_section: - temp[key] = fixed_value - else: - result[key] = fixed_value - # if data is finished save collected data from temporary dict - if sub_section: - result[sub_section] = temp - - return result diff --git a/annet/rulebook/texts/cisco.rul b/annet/rulebook/texts/cisco.rul index 28e49990..b2d78142 100644 --- a/annet/rulebook/texts/cisco.rul +++ b/annet/rulebook/texts/cisco.rul @@ -73,7 +73,7 @@ interface */\w*Ethernet[0-9\/]+$/ %logic=common.permanent %diff_logic=cisco. storm-control * level spanning-tree portfast -router bgp * %diff_logic=cisco.misc.bgp_diff +router bgp router-id vrf * router-id diff --git a/tests/annet/test_patch/cisco_bgp_address_family.yaml b/tests/annet/test_patch/cisco_bgp_address_family.yaml index 83beb23b..1beeec43 100644 --- a/tests/annet/test_patch/cisco_bgp_address_family.yaml +++ b/tests/annet/test_patch/cisco_bgp_address_family.yaml @@ -15,6 +15,7 @@ neighbor SPINE route-map TOR_EXPORT_SPINE out neighbor 10.1.1.11 activate neighbor 10.2.1.11 activate + exit-address-family after: | router bgp 65111 bgp router-id 1.1.1.1 @@ -38,6 +39,7 @@ neighbor 10.2.1.11 activate no neighbor 2001:DB8:1:1::11 activate no neighbor 2001:DB8:2:1::11 activate + exit-address-family address-family ipv6 neighbor SPINEv6 send-community both neighbor SPINEv6 soft-reconfiguration inbound @@ -45,6 +47,7 @@ neighbor SPINEv6 route-map TOR_EXPORT_SPINE out neighbor 2001:DB8:1:1::11 activate neighbor 2001:DB8:2:1::11 activate + exit-address-family patch: | conf t router bgp 65111 @@ -56,7 +59,7 @@ address-family ipv4 no neighbor 2001:DB8:1:1::11 activate no neighbor 2001:DB8:2:1::11 activate - exit + exit-address-family address-family ipv6 neighbor SPINEv6 send-community both neighbor SPINEv6 soft-reconfiguration inbound @@ -64,7 +67,7 @@ neighbor SPINEv6 route-map TOR_EXPORT_SPINE out neighbor 2001:DB8:1:1::11 activate neighbor 2001:DB8:2:1::11 activate - exit + exit-address-family exit exit copy running-config startup-config