From 9f9c5224f65297c67cdab60091be5ae50932dacb Mon Sep 17 00:00:00 2001 From: Xenia Mountrouidou Date: Wed, 3 Aug 2022 15:38:55 -0400 Subject: [PATCH 1/6] adds utility functions ios --- netutils/config/parser.py | 68 +++++ .../get_path/certificate_with_parents.txt | 14 + .../get_path/certificate_wo_parents.txt | 10 + .../parser/get_path/ios_full_config.txt | 279 ++++++++++++++++++ tests/unit/test_parser.py | 33 +++ 5 files changed, 404 insertions(+) create mode 100644 tests/unit/mock/config/parser/get_path/certificate_with_parents.txt create mode 100644 tests/unit/mock/config/parser/get_path/certificate_wo_parents.txt create mode 100644 tests/unit/mock/config/parser/get_path/ios_full_config.txt diff --git a/netutils/config/parser.py b/netutils/config/parser.py index 0f3227e1..88b0750c 100644 --- a/netutils/config/parser.py +++ b/netutils/config/parser.py @@ -3,6 +3,7 @@ import re import typing as t +import itertools as it from collections import namedtuple from netutils.banner import normalise_delimiter_caret_c @@ -588,6 +589,73 @@ def build_config_relationship(self) -> t.List[ConfigLine]: self._update_same_line_children_configs() return self.config_lines + def _get_groups(self, pattern: str) -> t.Any: + """Groups children based on parent pattern.""" + children_list = [] + for line in self.config_lines: + for parent in line.parents: + if re.match(pattern, parent): + children_list.append(line) + grouped_data = it.groupby(children_list, key=lambda x: x.parents) # type: ignore + return grouped_data + + def get_path(self, pattern: str) -> t.List[str]: + """Returns configuration part for a specific pattern not including parents. + + Args: + pattern: pattern that describes for parent. + + Returns: + configuration under that parent pattern. + + Example: + >>> config = ''' + ... router bgp 45000 + ... address-family ipv4 unicast + ... neighbor 192.168.1.2 activate + ... network 172.17.1.0 mas''' + >>> bgp_conf = IOSConfigParser(str(config)).get_path("router bgp") + >>> print(bgp_conf) + [' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mas'] + """ + children = [] + grouped_data = self._get_groups(pattern) + for _, grp in grouped_data: + for line in list(grp): + children.append(line.config_line) + return children + + def get_path_with_parents(self, pattern: str) -> t.List[str]: + """Returns configuration part for a specific pattern including parents and children. + + Args: + pattern: pattern that describes for parent. + + Returns: + configuration under that parent pattern. + + Example: + >>> config = ''' + ... router bgp 45000 + ... address-family ipv4 unicast + ... neighbor 192.168.1.2 activate + ... network 172.17.1.0 mas''' + >>> bgp_conf = IOSConfigParser(str(config)).get_path_with_parents("router bgp") + >>> print(bgp_conf) + ['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mas'] + """ + parents_and_children = [] + previous_parent = "" + grouped_data = self._get_groups(pattern) + for key, grp in grouped_data: + # special case for nested parents, we do not want to repeat these multiple times + if previous_parent != key[0]: + parents_and_children.append(key[0]) + previous_parent = key[0] + for line in list(grp): + parents_and_children.append(line.config_line) + return parents_and_children + class NXOSConfigParser(CiscoConfigParser, BaseSpaceConfigParser): """NXOS implementation of ConfigParser Class.""" diff --git a/tests/unit/mock/config/parser/get_path/certificate_with_parents.txt b/tests/unit/mock/config/parser/get_path/certificate_with_parents.txt new file mode 100644 index 00000000..fc57c973 --- /dev/null +++ b/tests/unit/mock/config/parser/get_path/certificate_with_parents.txt @@ -0,0 +1,14 @@ +crypto pki trustpoint TP-self-signed-1088426642 + enrollment selfsigned + subject-name cn=IOS-Self-Signed-Certificate-1088426642 + revocation-check none + rsakeypair TP-self-signed-1088426642 +crypto pki trustpoint SLA-TrustPoint + enrollment pkcs12 + revocation-check crl +crypto pki certificate chain TP-self-signed-1088426642 + certificate self-signed 01 + quit +crypto pki certificate chain SLA-TrustPoint + certificate ca 01 + quit \ No newline at end of file diff --git a/tests/unit/mock/config/parser/get_path/certificate_wo_parents.txt b/tests/unit/mock/config/parser/get_path/certificate_wo_parents.txt new file mode 100644 index 00000000..cc7d521c --- /dev/null +++ b/tests/unit/mock/config/parser/get_path/certificate_wo_parents.txt @@ -0,0 +1,10 @@ + enrollment selfsigned + subject-name cn=IOS-Self-Signed-Certificate-1088426642 + revocation-check none + rsakeypair TP-self-signed-1088426642 + enrollment pkcs12 + revocation-check crl + certificate self-signed 01 + quit + certificate ca 01 + quit \ No newline at end of file diff --git a/tests/unit/mock/config/parser/get_path/ios_full_config.txt b/tests/unit/mock/config/parser/get_path/ios_full_config.txt new file mode 100644 index 00000000..c5776a40 --- /dev/null +++ b/tests/unit/mock/config/parser/get_path/ios_full_config.txt @@ -0,0 +1,279 @@ +! +version 17.1 +service timestamps debug datetime msec +service timestamps log datetime msec +! Call-home is enabled by Smart-Licensing. +service call-home +platform qfp utilization monitor load 80 +platform punt-keepalive disable-kernel-core +platform console serial +! +hostname jcy-bb-01 +! +boot-start-marker +boot-end-marker +! +! +vrf definition MANAGEMENT + ! + address-family ipv4 + exit-address-family + ! + address-family ipv6 + exit-address-family +! +logging userinfo +! +no aaa new-model +call-home + ! If contact email address in call-home is configured as sch-smart-licensing@cisco.com + ! the email address configured in Cisco Smart License Portal will be used as contact email address to send SCH notifications. + contact-email-addr sch-smart-licensing@cisco.com + profile "CiscoTAC-1" + active + destination transport-method http +! +! +! +! +! +! +! +no ip domain lookup +ip domain name infra.ntc.com +! +! +! +login on-success log +! +! +! +! +! +! +! +subscriber templating +! +! +! +! +! +! +multilink bundle-name authenticated +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +crypto pki trustpoint TP-self-signed-1088426642 + enrollment selfsigned + subject-name cn=IOS-Self-Signed-Certificate-1088426642 + revocation-check none + rsakeypair TP-self-signed-1088426642 +! +crypto pki trustpoint SLA-TrustPoint + enrollment pkcs12 + revocation-check crl +! +! +crypto pki certificate chain TP-self-signed-1088426642 + certificate self-signed 01 + quit +crypto pki certificate chain SLA-TrustPoint + certificate ca 01 + quit +! +license udi pid CSR1000V sn 9SAGBHTUEE9 +diagnostic bootup level minimal +archive + path bootflash:archive +memory free low-watermark processor 72107 +! +! +spanning-tree extend system-id +! +username ntc privilege 15 password 0 ntc123 +! +redundancy +! +! +! +! +! +lldp run +cdp run +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +interface Loopback0 + ip address 10.0.10.3 255.255.255.255 +! +interface GigabitEthernet1 + description MANAGEMENT_DO_NOT_CHANGE + ip address 10.0.0.15 255.255.255.0 + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet2 + ip address 10.10.0.6 255.255.255.252 + ip access-group BLOCK_TRANSIT_LINKS in + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet3 + ip address 10.10.0.14 255.255.255.252 + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet4 + description backbone-to-vmx3-ge0/0/3 + ip address 10.10.0.17 255.255.255.252 + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet5 + no ip address + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet6 + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet7 + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet8 + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet9 + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid +! +router bgp 65251 + bgp router-id 10.0.10.3 + bgp log-neighbor-changes + redistribute connected + neighbor 10.10.0.5 remote-as 65251 + neighbor 10.10.0.13 remote-as 65251 + neighbor 10.10.0.18 remote-as 65252 +! +! +virtual-service csr_mgmt +! +ip forward-protocol nd +ip http server +ip http authentication local +ip http secure-server +! +ip route 0.0.0.0 0.0.0.0 10.0.0.2 +ip scp server enable +! +! +logging origin-id hostname +logging host 10.125.1.171 transport udp port 7004 +! +! +snmp-server community ntc-public RO +snmp-server community ntc-private RW +snmp-server community networktocode RO +snmp-server community secure RW +snmp-server location Network to Code - NYC | NY +snmp-server contact John Smith +snmp-server host 10.1.1.1 version 2c networktocode +! +! +! +control-plane +! +! +! +! +banner exec ^C +************************************************************************** +* IOSv is strictly limited to use for evaluation, demonstration and IOS * +* education. IOSv is provided as-is and is not supported by Cisco's * +* Technical Advisory Center. Any use or disclosure, in whole or in part, * +* of the IOSv Software or Documentation to any third party for any * +* purposes is expressly prohibited except as otherwise authorized by * +* Cisco in writing. * +**************************************************************************^C +banner incoming ^C +************************************************************************** +* IOSv is strictly limited to use for evaluation, demonstration and IOS * +* education. IOSv is provided as-is and is not supported by Cisco's * +* Technical Advisory Center. Any use or disclosure, in whole or in part, * +* of the IOSv Software or Documentation to any third party for any * +* purposes is expressly prohibited except as otherwise authorized by * +* Cisco in writing. * +**************************************************************************^C +! +alias exec ntcclear clear platform software vnic-if nv +! +line con 0 + stopbits 1 +line vty 0 4 + privilege level 15 + login local + transport preferred ssh + transport input all +line vty 5 15 + privilege level 15 + login local + transport preferred ssh + transport input all +! +ntp server 10.1.1.1 +ntp server 10.2.2.2 prefer +! +! +! +! +! +netconf-yang +restconf +end \ No newline at end of file diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py index 68ccd3fb..81df6071 100644 --- a/tests/unit/test_parser.py +++ b/tests/unit/test_parser.py @@ -4,6 +4,7 @@ import pytest from netutils.config import compliance +from netutils.config.parser import IOSConfigParser MOCK_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "mock", "config", "parser") TXT_FILE = "_sent.txt" @@ -14,6 +15,16 @@ parameters.append([_file, network_os]) +# TODO: add more tests with different patterns +get_path_parameters = [ + ("get_path/ios_full_config.txt", "crypto pki", "get_path/certificate_wo_parents.txt"), +] + +get_path_with_parents_parameters = [ + ("get_path/ios_full_config.txt", "crypto pki", "get_path/certificate_with_parents.txt"), +] + + @pytest.mark.parametrize("_file, network_os", parameters) def test_parser(_file, network_os, get_text_data, get_python_data): # pylint: disable=redefined-outer-name truncate_file = os.path.join(MOCK_DIR, _file[: -len(TXT_FILE)]) @@ -37,3 +48,25 @@ def test_incorrect_banner_ios(): ) with pytest.raises(ValueError): compliance.parser_map["cisco_ios"](banner_cfg).config_lines # pylint: disable=expression-not-assigned + + +@pytest.mark.parametrize("_file, pattern, expected", get_path_parameters) +def test_get_path(_file, pattern, expected, get_text_data): + """Tests get_path method.""" + device_cfg = get_text_data(os.path.join(MOCK_DIR, _file)) + config_tree = IOSConfigParser(str(device_cfg)) + returned_path = config_tree.get_path(pattern) + expected_path = get_text_data(os.path.join(MOCK_DIR, expected)) + + assert returned_path == expected_path.split("\n") + + +@pytest.mark.parametrize("_file, pattern, expected", get_path_with_parents_parameters) +def test_get_path_with_children(_file, pattern, expected, get_text_data): + """Tests get_path_with_children method.""" + device_cfg = get_text_data(os.path.join(MOCK_DIR, _file)) + config_tree = IOSConfigParser(str(device_cfg)) + returned_path = config_tree.get_path_with_parents(pattern) + expected_path = get_text_data(os.path.join(MOCK_DIR, expected)) + + assert returned_path == expected_path.split("\n") From a7041b71b2394b413f1da25720d4fec4ac3e0c4e Mon Sep 17 00:00:00 2001 From: Xenia Mountrouidou Date: Thu, 4 Aug 2022 11:42:03 -0400 Subject: [PATCH 2/6] addresses comments --- netutils/config/parser.py | 159 ++++++++++++++++++++++---------------- tests/unit/test_parser.py | 8 +- 2 files changed, 96 insertions(+), 71 deletions(-) diff --git a/netutils/config/parser.py b/netutils/config/parser.py index 88b0750c..77b7c704 100644 --- a/netutils/config/parser.py +++ b/netutils/config/parser.py @@ -332,6 +332,98 @@ def build_config_relationship(self) -> t.List[ConfigLine]: self._update_config_lines(line) return self.config_lines + @staticmethod + def _match_type_check(line: str, pattern: str, match_type: bool) -> bool: + """Checks pattern for exact match or regex.""" + if match_type == "exact" and line == pattern: + return True + if match_type == "startswith" and line.startswith(pattern): + return True + if match_type == "endswith" and line.endswith(pattern): + return True + if match_type == "regex" and re.match(pattern, line): + return True + return False + + def find_all_children(self, pattern: str, match_type="exact") -> t.List[str]: + """Returns configuration part for a specific pattern not including parents. + + Args: + pattern: pattern that describes parent. + match_type (str, optional): Exact or regex. Defaults to "exact". + + Returns: + configuration under that parent pattern. + Example: + >>> config = ''' + ... router bgp 45000 + ... address-family ipv4 unicast + ... neighbor 192.168.1.2 activate + ... network 172.17.1.0 mas''' + >>> bgp_conf = IOSConfigParser(str(config)).find_all_children("router bgp") + >>> print(bgp_conf) + [' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mas'] + """ + config = [] + for cfg_line in self.build_config_relationship(): + parents = cfg_line.parents[0] if cfg_line.parents else None + if ( + parents + and self._match_type_check(parents, pattern, match_type) + or self._match_type_check(cfg_line.config_line, pattern, match_type) + ): + config.append(cfg_line.config_line) + return config + + def find_children_w_parents(self, parent_pattern: str, child_pattern="", match_type="exact") -> t.List[str]: + """Returns configuration part for a specific pattern including parents and children. + + Args: + parent_pattern (str): pattern that describes parent. + child_pattern (str, optional): pattern that describes child. Defaults to "". + match_type (str, optional): Exact or regex. Defaults to "exact". + + Returns: + configuration under that parent pattern. + Example: + >>> config = ''' + ... router bgp 45000 + ... address-family ipv4 unicast + ... neighbor 192.168.1.2 activate + ... network 172.17.1.0 mas''' + >>> bgp_conf = IOSConfigParser(str(config)).find_children_w_parents("router bgp") + >>> print(bgp_conf) + ['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mas'] + """ + config = [] + potential_parents = [ + elem.parents[0] + for elem in self.build_config_relationship() + if self._match_type_check(elem.config_line, child_pattern, match_type) + ] + for cfg_line in self.build_config_relationship(): + parents = cfg_line.parents[0] if cfg_line.parents else None + if parents in potential_parents and self._match_type_check(parents, parent_pattern, match_type): + config.append(cfg_line.config_line) + return config + + # def find_blocks(): + # def find_children(): + # def find_children_w_parents(): + # def find_interface_objects(): + # def find_lineage(): + # def find_lines(): + # def find_object_branches(): + # def find_objects(): + # def find_objects_dna(): + # def find_objects_w_all_children(): + # def find_objects_w_child(): + # def find_objects_w_missing_children(): + # def find_objects_w_parents(): + # def find_objects_wo_child(): + # def find_parents_w_child(): + # def find_parents_wo_child(): + class BaseBraceConfigParser(BaseConfigParser): """Base parser class for config syntax that demarcates using braces.""" @@ -589,73 +681,6 @@ def build_config_relationship(self) -> t.List[ConfigLine]: self._update_same_line_children_configs() return self.config_lines - def _get_groups(self, pattern: str) -> t.Any: - """Groups children based on parent pattern.""" - children_list = [] - for line in self.config_lines: - for parent in line.parents: - if re.match(pattern, parent): - children_list.append(line) - grouped_data = it.groupby(children_list, key=lambda x: x.parents) # type: ignore - return grouped_data - - def get_path(self, pattern: str) -> t.List[str]: - """Returns configuration part for a specific pattern not including parents. - - Args: - pattern: pattern that describes for parent. - - Returns: - configuration under that parent pattern. - - Example: - >>> config = ''' - ... router bgp 45000 - ... address-family ipv4 unicast - ... neighbor 192.168.1.2 activate - ... network 172.17.1.0 mas''' - >>> bgp_conf = IOSConfigParser(str(config)).get_path("router bgp") - >>> print(bgp_conf) - [' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mas'] - """ - children = [] - grouped_data = self._get_groups(pattern) - for _, grp in grouped_data: - for line in list(grp): - children.append(line.config_line) - return children - - def get_path_with_parents(self, pattern: str) -> t.List[str]: - """Returns configuration part for a specific pattern including parents and children. - - Args: - pattern: pattern that describes for parent. - - Returns: - configuration under that parent pattern. - - Example: - >>> config = ''' - ... router bgp 45000 - ... address-family ipv4 unicast - ... neighbor 192.168.1.2 activate - ... network 172.17.1.0 mas''' - >>> bgp_conf = IOSConfigParser(str(config)).get_path_with_parents("router bgp") - >>> print(bgp_conf) - ['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mas'] - """ - parents_and_children = [] - previous_parent = "" - grouped_data = self._get_groups(pattern) - for key, grp in grouped_data: - # special case for nested parents, we do not want to repeat these multiple times - if previous_parent != key[0]: - parents_and_children.append(key[0]) - previous_parent = key[0] - for line in list(grp): - parents_and_children.append(line.config_line) - return parents_and_children - class NXOSConfigParser(CiscoConfigParser, BaseSpaceConfigParser): """NXOS implementation of ConfigParser Class.""" diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py index 81df6071..1a945a35 100644 --- a/tests/unit/test_parser.py +++ b/tests/unit/test_parser.py @@ -51,22 +51,22 @@ def test_incorrect_banner_ios(): @pytest.mark.parametrize("_file, pattern, expected", get_path_parameters) -def test_get_path(_file, pattern, expected, get_text_data): +def test_find_all_children(_file, pattern, expected, get_text_data): """Tests get_path method.""" device_cfg = get_text_data(os.path.join(MOCK_DIR, _file)) config_tree = IOSConfigParser(str(device_cfg)) - returned_path = config_tree.get_path(pattern) + returned_path = config_tree.find_all_children(pattern) expected_path = get_text_data(os.path.join(MOCK_DIR, expected)) assert returned_path == expected_path.split("\n") @pytest.mark.parametrize("_file, pattern, expected", get_path_with_parents_parameters) -def test_get_path_with_children(_file, pattern, expected, get_text_data): +def test_find_children_w_parents(_file, pattern, expected, get_text_data): """Tests get_path_with_children method.""" device_cfg = get_text_data(os.path.join(MOCK_DIR, _file)) config_tree = IOSConfigParser(str(device_cfg)) - returned_path = config_tree.get_path_with_parents(pattern) + returned_path = config_tree.find_children_w_parents(pattern) expected_path = get_text_data(os.path.join(MOCK_DIR, expected)) assert returned_path == expected_path.split("\n") From 894346125c7df3d8616cb5048a31787d1222ef35 Mon Sep 17 00:00:00 2001 From: Xenia Mountrouidou Date: Thu, 4 Aug 2022 14:16:32 -0400 Subject: [PATCH 3/6] addresses comments --- netutils/config/parser.py | 30 +++++++------------ ...icate_with_parents.txt => certificate.txt} | 0 .../get_path/certificate_wo_parents.txt | 10 ------- tests/unit/test_parser.py | 19 ++---------- 4 files changed, 13 insertions(+), 46 deletions(-) rename tests/unit/mock/config/parser/get_path/{certificate_with_parents.txt => certificate.txt} (100%) delete mode 100644 tests/unit/mock/config/parser/get_path/certificate_wo_parents.txt diff --git a/netutils/config/parser.py b/netutils/config/parser.py index 77b7c704..c6c130db 100644 --- a/netutils/config/parser.py +++ b/netutils/config/parser.py @@ -3,7 +3,6 @@ import re import typing as t -import itertools as it from collections import namedtuple from netutils.banner import normalise_delimiter_caret_c @@ -333,7 +332,7 @@ def build_config_relationship(self) -> t.List[ConfigLine]: return self.config_lines @staticmethod - def _match_type_check(line: str, pattern: str, match_type: bool) -> bool: + def _match_type_check(line: str, pattern: str, match_type: str) -> bool: """Checks pattern for exact match or regex.""" if match_type == "exact" and line == pattern: return True @@ -345,12 +344,12 @@ def _match_type_check(line: str, pattern: str, match_type: bool) -> bool: return True return False - def find_all_children(self, pattern: str, match_type="exact") -> t.List[str]: + def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]: """Returns configuration part for a specific pattern not including parents. Args: pattern: pattern that describes parent. - match_type (str, optional): Exact or regex. Defaults to "exact". + match_type (optional): Exact or regex. Defaults to "exact". Returns: configuration under that parent pattern. @@ -360,9 +359,9 @@ def find_all_children(self, pattern: str, match_type="exact") -> t.List[str]: ... address-family ipv4 unicast ... neighbor 192.168.1.2 activate ... network 172.17.1.0 mas''' - >>> bgp_conf = IOSConfigParser(str(config)).find_all_children("router bgp") + >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="regex") >>> print(bgp_conf) - [' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mas'] + ['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mas'] """ config = [] for cfg_line in self.build_config_relationship(): @@ -375,25 +374,18 @@ def find_all_children(self, pattern: str, match_type="exact") -> t.List[str]: config.append(cfg_line.config_line) return config - def find_children_w_parents(self, parent_pattern: str, child_pattern="", match_type="exact") -> t.List[str]: + def find_children_w_parents( + self, parent_pattern: str, child_pattern: str, match_type: str = "exact" + ) -> t.List[str]: """Returns configuration part for a specific pattern including parents and children. Args: - parent_pattern (str): pattern that describes parent. - child_pattern (str, optional): pattern that describes child. Defaults to "". - match_type (str, optional): Exact or regex. Defaults to "exact". + parent_pattern: pattern that describes parent. + child_pattern: pattern that describes child. + match_type (optional): Exact or regex. Defaults to "exact". Returns: configuration under that parent pattern. - Example: - >>> config = ''' - ... router bgp 45000 - ... address-family ipv4 unicast - ... neighbor 192.168.1.2 activate - ... network 172.17.1.0 mas''' - >>> bgp_conf = IOSConfigParser(str(config)).find_children_w_parents("router bgp") - >>> print(bgp_conf) - ['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mas'] """ config = [] potential_parents = [ diff --git a/tests/unit/mock/config/parser/get_path/certificate_with_parents.txt b/tests/unit/mock/config/parser/get_path/certificate.txt similarity index 100% rename from tests/unit/mock/config/parser/get_path/certificate_with_parents.txt rename to tests/unit/mock/config/parser/get_path/certificate.txt diff --git a/tests/unit/mock/config/parser/get_path/certificate_wo_parents.txt b/tests/unit/mock/config/parser/get_path/certificate_wo_parents.txt deleted file mode 100644 index cc7d521c..00000000 --- a/tests/unit/mock/config/parser/get_path/certificate_wo_parents.txt +++ /dev/null @@ -1,10 +0,0 @@ - enrollment selfsigned - subject-name cn=IOS-Self-Signed-Certificate-1088426642 - revocation-check none - rsakeypair TP-self-signed-1088426642 - enrollment pkcs12 - revocation-check crl - certificate self-signed 01 - quit - certificate ca 01 - quit \ No newline at end of file diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py index 1a945a35..28476e88 100644 --- a/tests/unit/test_parser.py +++ b/tests/unit/test_parser.py @@ -17,11 +17,7 @@ # TODO: add more tests with different patterns get_path_parameters = [ - ("get_path/ios_full_config.txt", "crypto pki", "get_path/certificate_wo_parents.txt"), -] - -get_path_with_parents_parameters = [ - ("get_path/ios_full_config.txt", "crypto pki", "get_path/certificate_with_parents.txt"), + ("get_path/ios_full_config.txt", "crypto pki", "get_path/certificate.txt"), ] @@ -55,18 +51,7 @@ def test_find_all_children(_file, pattern, expected, get_text_data): """Tests get_path method.""" device_cfg = get_text_data(os.path.join(MOCK_DIR, _file)) config_tree = IOSConfigParser(str(device_cfg)) - returned_path = config_tree.find_all_children(pattern) - expected_path = get_text_data(os.path.join(MOCK_DIR, expected)) - - assert returned_path == expected_path.split("\n") - - -@pytest.mark.parametrize("_file, pattern, expected", get_path_with_parents_parameters) -def test_find_children_w_parents(_file, pattern, expected, get_text_data): - """Tests get_path_with_children method.""" - device_cfg = get_text_data(os.path.join(MOCK_DIR, _file)) - config_tree = IOSConfigParser(str(device_cfg)) - returned_path = config_tree.find_children_w_parents(pattern) + returned_path = config_tree.find_all_children(pattern=pattern, match_type="regex") expected_path = get_text_data(os.path.join(MOCK_DIR, expected)) assert returned_path == expected_path.split("\n") From 3b2834d1ef6e4b985b3155410f607af364b53062 Mon Sep 17 00:00:00 2001 From: Xenia Mountrouidou Date: Thu, 4 Aug 2022 14:43:30 -0400 Subject: [PATCH 4/6] adds tests --- netutils/config/parser.py | 9 +++++++ .../mock/config/parser/get_path/interface.txt | 24 +++++++++++++++++++ tests/unit/test_parser.py | 17 +++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 tests/unit/mock/config/parser/get_path/interface.txt diff --git a/netutils/config/parser.py b/netutils/config/parser.py index c6c130db..0668ee01 100644 --- a/netutils/config/parser.py +++ b/netutils/config/parser.py @@ -386,6 +386,15 @@ def find_children_w_parents( Returns: configuration under that parent pattern. + Example: + >>> config = ''' + ... router bgp 45000 + ... address-family ipv4 unicast + ... neighbor 192.168.1.2 activate + ... network 172.17.1.0 mas''' + >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern=" address-family", match_type="regex") + >>> print(bgp_conf) + [' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mas'] """ config = [] potential_parents = [ diff --git a/tests/unit/mock/config/parser/get_path/interface.txt b/tests/unit/mock/config/parser/get_path/interface.txt new file mode 100644 index 00000000..232b74d2 --- /dev/null +++ b/tests/unit/mock/config/parser/get_path/interface.txt @@ -0,0 +1,24 @@ + no ip address + negotiation auto + no mop enabled + no mop sysid + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid \ No newline at end of file diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py index 28476e88..42880e0c 100644 --- a/tests/unit/test_parser.py +++ b/tests/unit/test_parser.py @@ -20,6 +20,10 @@ ("get_path/ios_full_config.txt", "crypto pki", "get_path/certificate.txt"), ] +get_path_with_parents_parameters = [ + ("get_path/ios_full_config.txt", "interface", " no ip", "get_path/interface.txt"), +] + @pytest.mark.parametrize("_file, network_os", parameters) def test_parser(_file, network_os, get_text_data, get_python_data): # pylint: disable=redefined-outer-name @@ -55,3 +59,16 @@ def test_find_all_children(_file, pattern, expected, get_text_data): expected_path = get_text_data(os.path.join(MOCK_DIR, expected)) assert returned_path == expected_path.split("\n") + + +@pytest.mark.parametrize("_file, parent_pattern, child_pattern, expected", get_path_with_parents_parameters) +def test_find_children_w_parents(_file, parent_pattern, child_pattern, expected, get_text_data): + """Tests get_path_with_children method.""" + device_cfg = get_text_data(os.path.join(MOCK_DIR, _file)) + config_tree = IOSConfigParser(str(device_cfg)) + returned_path = config_tree.find_children_w_parents( + parent_pattern=parent_pattern, child_pattern=child_pattern, match_type="regex" + ) + expected_path = get_text_data(os.path.join(MOCK_DIR, expected)) + + assert returned_path == expected_path.split("\n") From d56cd87538f95a5959f6f2c4b3a39b920de6d99b Mon Sep 17 00:00:00 2001 From: Xenia Mountrouidou Date: Thu, 4 Aug 2022 18:07:48 -0400 Subject: [PATCH 5/6] addresses comments --- netutils/config/parser.py | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/netutils/config/parser.py b/netutils/config/parser.py index 0668ee01..72bad867 100644 --- a/netutils/config/parser.py +++ b/netutils/config/parser.py @@ -358,10 +358,10 @@ def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[s ... router bgp 45000 ... address-family ipv4 unicast ... neighbor 192.168.1.2 activate - ... network 172.17.1.0 mas''' - >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="regex") + ... network 172.17.1.0 mask''' + >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith") >>> print(bgp_conf) - ['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mas'] + ['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask'] """ config = [] for cfg_line in self.build_config_relationship(): @@ -391,10 +391,10 @@ def find_children_w_parents( ... router bgp 45000 ... address-family ipv4 unicast ... neighbor 192.168.1.2 activate - ... network 172.17.1.0 mas''' + ... network 172.17.1.0 mask''' >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern=" address-family", match_type="regex") >>> print(bgp_conf) - [' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mas'] + [' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask'] """ config = [] potential_parents = [ @@ -408,23 +408,6 @@ def find_children_w_parents( config.append(cfg_line.config_line) return config - # def find_blocks(): - # def find_children(): - # def find_children_w_parents(): - # def find_interface_objects(): - # def find_lineage(): - # def find_lines(): - # def find_object_branches(): - # def find_objects(): - # def find_objects_dna(): - # def find_objects_w_all_children(): - # def find_objects_w_child(): - # def find_objects_w_missing_children(): - # def find_objects_w_parents(): - # def find_objects_wo_child(): - # def find_parents_w_child(): - # def find_parents_wo_child(): - class BaseBraceConfigParser(BaseConfigParser): """Base parser class for config syntax that demarcates using braces.""" From 651980f2a5fcf3abb5fc12e06e5eaa605f92b498 Mon Sep 17 00:00:00 2001 From: Xenia Mountrouidou Date: Tue, 9 Aug 2022 14:14:01 -0400 Subject: [PATCH 6/6] addresses comment parametrize tests --- .../cisco_ios}/certificate.txt | 0 .../cisco_ios/full_config.txt} | 0 .../cisco_ios}/interface.txt | 0 tests/unit/test_parser.py | 33 ++++++++++++++----- 4 files changed, 25 insertions(+), 8 deletions(-) rename tests/unit/mock/config/parser/{get_path => find_children/cisco_ios}/certificate.txt (100%) rename tests/unit/mock/config/parser/{get_path/ios_full_config.txt => find_children/cisco_ios/full_config.txt} (100%) rename tests/unit/mock/config/parser/{get_path => find_children/cisco_ios}/interface.txt (100%) diff --git a/tests/unit/mock/config/parser/get_path/certificate.txt b/tests/unit/mock/config/parser/find_children/cisco_ios/certificate.txt similarity index 100% rename from tests/unit/mock/config/parser/get_path/certificate.txt rename to tests/unit/mock/config/parser/find_children/cisco_ios/certificate.txt diff --git a/tests/unit/mock/config/parser/get_path/ios_full_config.txt b/tests/unit/mock/config/parser/find_children/cisco_ios/full_config.txt similarity index 100% rename from tests/unit/mock/config/parser/get_path/ios_full_config.txt rename to tests/unit/mock/config/parser/find_children/cisco_ios/full_config.txt diff --git a/tests/unit/mock/config/parser/get_path/interface.txt b/tests/unit/mock/config/parser/find_children/cisco_ios/interface.txt similarity index 100% rename from tests/unit/mock/config/parser/get_path/interface.txt rename to tests/unit/mock/config/parser/find_children/cisco_ios/interface.txt diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py index 42880e0c..16e702f1 100644 --- a/tests/unit/test_parser.py +++ b/tests/unit/test_parser.py @@ -7,7 +7,11 @@ from netutils.config.parser import IOSConfigParser MOCK_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "mock", "config", "parser") +MOCK_GETPATH_DIR = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "mock", "config", "parser", "find_children" +) TXT_FILE = "_sent.txt" +CONFIG_FILE = "full_config.txt" parameters = [] for network_os in list(compliance.parser_map.keys()): @@ -15,14 +19,27 @@ parameters.append([_file, network_os]) -# TODO: add more tests with different patterns -get_path_parameters = [ - ("get_path/ios_full_config.txt", "crypto pki", "get_path/certificate.txt"), +find_all_children_parameters = [] +find_all_children_test_cases = [ + ("crypto pki", "certificate.txt"), ] - -get_path_with_parents_parameters = [ - ("get_path/ios_full_config.txt", "interface", " no ip", "get_path/interface.txt"), +for network_os in list(compliance.parser_map.keys()): + for _file in glob.glob(f"{MOCK_GETPATH_DIR}/{network_os}/{CONFIG_FILE}"): + for test_case in find_all_children_test_cases: + find_all_children_parameters.append( + (_file, test_case[0], f"{MOCK_GETPATH_DIR}/{network_os}/{test_case[1]}") + ) + +find_children_parents_parameters = [] +find_children_parents_test_cases = [ + ("interface", " no ip", "interface.txt"), ] +for network_os in list(compliance.parser_map.keys()): + for _file in glob.glob(f"{MOCK_GETPATH_DIR}/{network_os}/{CONFIG_FILE}"): + for test_case in find_children_parents_test_cases: + find_children_parents_parameters.append( + (_file, test_case[0], test_case[1], f"{MOCK_GETPATH_DIR}/{network_os}/{test_case[2]}") + ) @pytest.mark.parametrize("_file, network_os", parameters) @@ -50,7 +67,7 @@ def test_incorrect_banner_ios(): compliance.parser_map["cisco_ios"](banner_cfg).config_lines # pylint: disable=expression-not-assigned -@pytest.mark.parametrize("_file, pattern, expected", get_path_parameters) +@pytest.mark.parametrize("_file, pattern, expected", find_all_children_parameters) def test_find_all_children(_file, pattern, expected, get_text_data): """Tests get_path method.""" device_cfg = get_text_data(os.path.join(MOCK_DIR, _file)) @@ -61,7 +78,7 @@ def test_find_all_children(_file, pattern, expected, get_text_data): assert returned_path == expected_path.split("\n") -@pytest.mark.parametrize("_file, parent_pattern, child_pattern, expected", get_path_with_parents_parameters) +@pytest.mark.parametrize("_file, parent_pattern, child_pattern, expected", find_children_parents_parameters) def test_find_children_w_parents(_file, parent_pattern, child_pattern, expected, get_text_data): """Tests get_path_with_children method.""" device_cfg = get_text_data(os.path.join(MOCK_DIR, _file))