From a03705a167551e727b6b0c07b6ba5a7ae86c1eb7 Mon Sep 17 00:00:00 2001 From: mpenning Date: Thu, 26 Oct 2023 16:58:47 -0500 Subject: [PATCH 1/8] Add space in tracked interface name --- tests/fixtures/configs/sample_08.ios | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fixtures/configs/sample_08.ios b/tests/fixtures/configs/sample_08.ios index 7c1d89c..8bfbffa 100644 --- a/tests/fixtures/configs/sample_08.ios +++ b/tests/fixtures/configs/sample_08.ios @@ -242,7 +242,7 @@ interface FastEthernet0/0 standby 110 priority 150 standby 110 preempt delay minimum 15 standby 110 track Dialer1 75 - standby 110 track FastEthernet0/1 20 + standby 110 track FastEthernet 0/1 standby 110 track FastEthernet1/0 30 ! ipv4-only HSRP group... standby 111 ip 172.16.2.253 From 7154ec4ba3be716c72a33928b7f4cbebf9be2c59 Mon Sep 17 00:00:00 2001 From: mpenning Date: Thu, 26 Oct 2023 17:06:42 -0500 Subject: [PATCH 2/8] Update with latest config changes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index acc202d..026a5e9 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ from ciscoconfparse import CiscoConfParse # standby 110 priority 150 # standby 110 preempt delay minimum 15 # standby 110 track Dialer1 75 -# standby 110 track FastEthernet0/1 20 +# standby 110 track FastEthernet 0/1 # standby 110 track FastEthernet1/0 30 # standby 111 ip 172.16.2.253 # standby 111 priority 150 From 47d38e56b6042ff91bbde16c4c00fcab5120faae Mon Sep 17 00:00:00 2001 From: mpenning Date: Thu, 26 Oct 2023 17:07:07 -0500 Subject: [PATCH 3/8] Remove debug print statement --- tests/test_Models_Cisco.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_Models_Cisco.py b/tests/test_Models_Cisco.py index a46d58c..bd451db 100755 --- a/tests/test_Models_Cisco.py +++ b/tests/test_Models_Cisco.py @@ -1073,7 +1073,6 @@ def testVal_IOSIntfLine_ipv4_addr_object01(parse_c03_factory): test_result = dict() ## Parse all interface objects in c01 and check ipv4_addr_object for intf_obj in cfg.find_objects("^interface"): - print(" UUT IPV4_ADDR_OBJECT", intf_obj.name, intf_obj.ipv4_addr_object) test_result[intf_obj.text] = intf_obj.ipv4_addr_object assert test_result == result_correct From d712cd739896ae606abc8d0fb761c381ff1fb709 Mon Sep 17 00:00:00 2001 From: mpenning Date: Thu, 26 Oct 2023 17:12:22 -0500 Subject: [PATCH 4/8] Remove print() debugging --- tests/test_CiscoConfParse.py | 2 -- tests/test_Models_Cisco.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/tests/test_CiscoConfParse.py b/tests/test_CiscoConfParse.py index 6c5fd06..0f8ebcf 100644 --- a/tests/test_CiscoConfParse.py +++ b/tests/test_CiscoConfParse.py @@ -2206,8 +2206,6 @@ def testValues_HDiff_10(): test_result = test_diff.unified_diffs(header=False) for idx, correct_result_line in enumerate(correct_result_unified_diff): test_result_line = test_result[idx] - print("TEST_RESULT" + test_result_line, file=sys.stderr) - print("RESULT_CORR" + correct_result_line, file=sys.stderr) assert test_result_line == correct_result_line diff --git a/tests/test_Models_Cisco.py b/tests/test_Models_Cisco.py index bd451db..5737ccc 100755 --- a/tests/test_Models_Cisco.py +++ b/tests/test_Models_Cisco.py @@ -1479,8 +1479,6 @@ def testVal_IOSIntfLine_in_ipv4_subnet(parse_c03_factory): ## where the subnet is 1.1.0.0/22 test_network = IPv4Obj("1.1.0.0/22", strict=False) for intf_obj in cfg.find_objects("^interface"): - print("CiscoConfParse().ipv4_addr_object", intf_obj.ipv4_addr_object) - print("CiscoConfParse().ipv4_network_object", intf_obj.ipv4_network_object) test_result[intf_obj.text] = intf_obj.in_ipv4_subnet(test_network) assert test_result == result_correct From e44975265e1304c613dd24bd3e7c340af621446c Mon Sep 17 00:00:00 2001 From: mpenning Date: Thu, 26 Oct 2023 17:18:55 -0500 Subject: [PATCH 5/8] Add secondary addresses --- tests/fixtures/configs/sample_08.ios | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/fixtures/configs/sample_08.ios b/tests/fixtures/configs/sample_08.ios index 8bfbffa..52e2008 100644 --- a/tests/fixtures/configs/sample_08.ios +++ b/tests/fixtures/configs/sample_08.ios @@ -232,6 +232,8 @@ interface ATM0/0.32 point-to-point interface FastEthernet0/0 description [IPv4 and IPv6 desktop / laptop hosts on 2nd-floor North LAN] ip address 172.16.2.1 255.255.255.0 + ip address 172.16.21.1 255.255.255.0 secondary + ip address 172.16.22.1 255.255.255.0 secondary ipv6 dhcp server IPV6_2FL_NORTH_LAN ipv6 address fd01:ab00::/64 eui-64 ipv6 address fe80::1 link-local From 1ba69511de3e2288cb79d6040030dab2ec862ab2 Mon Sep 17 00:00:00 2001 From: mpenning Date: Thu, 26 Oct 2023 19:24:28 -0500 Subject: [PATCH 6/8] Initial commit --- tests/test_Ccp_Abc.py | 452 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100755 tests/test_Ccp_Abc.py diff --git a/tests/test_Ccp_Abc.py b/tests/test_Ccp_Abc.py new file mode 100755 index 0000000..0c0ebed --- /dev/null +++ b/tests/test_Ccp_Abc.py @@ -0,0 +1,452 @@ +#!/usr/bin/env python + +import sys +import os + +sys.path.insert(0, "..") + +from ciscoconfparse.ccp_util import IPv4Obj, CiscoRange, CiscoInterface +from ciscoconfparse.errors import DynamicAddressException +from ciscoconfparse.ciscoconfparse import CiscoConfParse +import pytest + +from loguru import logger + +r""" test_Ccp_Abc.py - Parse, Query, Build, and Modify IOS-style configs + + Copyright (C) 2023 David Michael Pennington + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + If you need to contact the author, you can do so by emailing: + mike [~at~] pennington [/dot\] net +""" + +def testVal_re_match_iter_typed_parent_default_type_norecurse(): + """Test that re_match_iter_typed(recurse=False) finds the parent and returns the default `result_type`, which is `str`""" + config = """! +interface GigabitEthernet 1/1 + switchport mode trunk + switchport trunk native vlan 911 + channel-group 25 mode active +! +! +class-map match-all IP_PREC_MEDIUM + match ip precedence 2 3 4 5 +class-map match-all IP_PREC_HIGH + match ip precedence 6 7 +class-map match-all TEST +class-map match-all TO_ATM + match access-group name NOT_INTERNAL +class-map match-any ALL + match any +! +! +policy-map EXTERNAL_CBWFQ + class IP_PREC_HIGH + priority percent 10 + police cir percent 10 + conform-action transmit + exceed-action drop + class IP_PREC_MEDIUM + bandwidth percent 50 + queue-limit 100 + class class-default + bandwidth percent 40 + queue-limit 100 +policy-map SHAPE_HEIR + class ALL + shape average 630000 + service-policy EXTERNAL_CBWFQ +! +!""" + cfg = CiscoConfParse(config.splitlines(), factory=True) + obj = cfg.find_objects("^interface")[0] + + uut_result = obj.re_match_iter_typed( + r"^interface\s+(\S.+)$", + group=1, + default="_no_match", + recurse=False, + debug=False, + ) + # Check that base assumption is True... we are checking the right parent + assert obj.text == "interface GigabitEthernet 1/1" + assert uut_result == "GigabitEthernet 1/1" + +def testVal_re_match_iter_typed_first_child_default_type_norecurse(): + """Test that re_match_iter_typed(recurse=False) finds the first child and returns the default `result_type`, which is `str`""" + config = """! +interface GigabitEthernet 1/1 + switchport mode trunk + switchport trunk native vlan 911 + channel-group 25 mode active +! +! +class-map match-all IP_PREC_MEDIUM + match ip precedence 2 3 4 5 +class-map match-all IP_PREC_HIGH + match ip precedence 6 7 +class-map match-all TEST +class-map match-all TO_ATM + match access-group name NOT_INTERNAL +class-map match-any ALL + match any +! +! +policy-map EXTERNAL_CBWFQ + class IP_PREC_HIGH + priority percent 10 + police cir percent 10 + conform-action transmit + exceed-action drop + class IP_PREC_MEDIUM + bandwidth percent 50 + queue-limit 100 + class class-default + bandwidth percent 40 + queue-limit 100 +policy-map SHAPE_HEIR + class ALL + shape average 630000 + service-policy EXTERNAL_CBWFQ +! +!""" + cfg = CiscoConfParse(config.splitlines(), factory=True) + obj = cfg.find_objects("^interface")[0] + + uut_result = obj.re_match_iter_typed( + r"switchport\s+mode\s+(\S+)$", + group=1, + default="_no_match", + recurse=False, + debug=False, + ) + # Check that base assumption is True... we are checking the right parent + assert obj.text == "interface GigabitEthernet 1/1" + assert uut_result == "trunk" + +def testVal_re_match_iter_typed_first_child_default_type_recurse(): + """Test that re_match_iter_typed(recurse=True) finds the first child and returns the default `result_type`, which is `str`""" + config = """! +interface GigabitEthernet 1/1 + switchport mode trunk + switchport trunk native vlan 911 + channel-group 25 mode active +! +! +class-map match-all IP_PREC_MEDIUM + match ip precedence 2 3 4 5 +class-map match-all IP_PREC_HIGH + match ip precedence 6 7 +class-map match-all TEST +class-map match-all TO_ATM + match access-group name NOT_INTERNAL +class-map match-any ALL + match any +! +! +policy-map EXTERNAL_CBWFQ + class IP_PREC_HIGH + priority percent 10 + police cir percent 10 + conform-action transmit + exceed-action drop + class IP_PREC_MEDIUM + bandwidth percent 50 + queue-limit 100 + class class-default + bandwidth percent 40 + queue-limit 100 +policy-map SHAPE_HEIR + class ALL + shape average 630000 + service-policy EXTERNAL_CBWFQ +! +!""" + cfg = CiscoConfParse(config.splitlines(), factory=True) + obj = cfg.find_objects("^interface")[0] + + uut_result = obj.re_match_iter_typed( + r"switchport\s+mode\s+(\S+)$", + group=1, + recurse=True, + default="_no_match", + debug=False, + ) + # Check that base assumption is True... we are checking the right parent + assert obj.text == "interface GigabitEthernet 1/1" + assert uut_result == "trunk" + +def testVal_re_match_iter_typed_child_deep_fail_norecurse(): + """Test that re_match_iter_typed(recurse=False) fails on a deep recurse through multiple children""" + config = """! +interface GigabitEthernet 1/1 + switchport mode trunk + switchport trunk native vlan 911 + channel-group 25 mode active +! +! +class-map match-all IP_PREC_MEDIUM + match ip precedence 2 3 4 5 +class-map match-all IP_PREC_HIGH + match ip precedence 6 7 +class-map match-all TEST +class-map match-all TO_ATM + match access-group name NOT_INTERNAL +class-map match-any ALL + match any +! +! +policy-map EXTERNAL_CBWFQ + class IP_PREC_HIGH + priority percent 10 + police cir percent 10 + conform-action transmit + exceed-action drop + class IP_PREC_MEDIUM + bandwidth percent 50 + queue-limit 100 + class class-default + bandwidth percent 40 + queue-limit 100 +policy-map SHAPE_HEIR + class ALL + shape average 630000 + service-policy EXTERNAL_CBWFQ +! +!""" + cfg = CiscoConfParse(config.splitlines(), factory=True) + obj = cfg.find_objects("^policy.map\s+EXTERNAL_CBWFQ")[0] + + uut_result = obj.re_match_iter_typed( + r"exceed\-action\s+(\S+)$", + group=1, + recurse=False, + default="_no_match", + debug=False, + ) + # Check that base assumption is True... we are checking the right parent + assert obj.text == "policy-map EXTERNAL_CBWFQ" + assert uut_result == "_no_match" + +def testVal_re_match_iter_typed_child_deep_pass_recurse(): + """Test that re_match_iter_typed(recurse=False) finds during a deep recurse through multiple levels of children""" + config = """! +interface GigabitEthernet 1/1 + switchport mode trunk + switchport trunk native vlan 911 + channel-group 25 mode active +! +! +class-map match-all IP_PREC_MEDIUM + match ip precedence 2 3 4 5 +class-map match-all IP_PREC_HIGH + match ip precedence 6 7 +class-map match-all TEST +class-map match-all TO_ATM + match access-group name NOT_INTERNAL +class-map match-any ALL + match any +! +! +policy-map EXTERNAL_CBWFQ + class IP_PREC_HIGH + priority percent 10 + police cir percent 10 + conform-action transmit + exceed-action drop + class IP_PREC_MEDIUM + bandwidth percent 50 + queue-limit 100 + class class-default + bandwidth percent 40 + queue-limit 100 +policy-map SHAPE_HEIR + class ALL + shape average 630000 + service-policy EXTERNAL_CBWFQ +! +!""" + cfg = CiscoConfParse(config.splitlines(), factory=True) + obj = cfg.find_objects("^policy.map\s+EXTERNAL_CBWFQ")[0] + + uut_result = obj.re_match_iter_typed( + r"exceed\-action\s+(\S+)$", + group=1, + recurse=True, + default="_no_match", + debug=False, + ) + # Check that base assumption is True... we are checking the right parent + assert obj.text == "policy-map EXTERNAL_CBWFQ" + assert uut_result == "drop" + +def testVal_re_match_iter_typed_second_child_default_type_recurse(): + """Test that re_match_iter_typed(recurse=False) finds the second child and returns the default `result_type`, which is `str`""" + config = """! +interface GigabitEthernet 1/1 + switchport mode trunk + switchport trunk native vlan 911 + channel-group 25 mode active +! +! +class-map match-all IP_PREC_MEDIUM + match ip precedence 2 3 4 5 +class-map match-all IP_PREC_HIGH + match ip precedence 6 7 +class-map match-all TEST +class-map match-all TO_ATM + match access-group name NOT_INTERNAL +class-map match-any ALL + match any +! +! +policy-map EXTERNAL_CBWFQ + class IP_PREC_HIGH + priority percent 10 + police cir percent 10 + conform-action transmit + exceed-action drop + class IP_PREC_MEDIUM + bandwidth percent 50 + queue-limit 100 + class class-default + bandwidth percent 40 + queue-limit 100 +policy-map SHAPE_HEIR + class ALL + shape average 630000 + service-policy EXTERNAL_CBWFQ +! +!""" + cfg = CiscoConfParse(config.splitlines(), factory=True) + obj = cfg.find_objects("^interface")[0] + + uut_result = obj.re_match_iter_typed( + r"switchport\s+trunk\s+native\s+vlan\s+(\S+)$", + group=1, + recurse=True, + default="_no_match", + debug=False, + ) + # Check that base assumption is True... we are checking the right parent + assert obj.text == "interface GigabitEthernet 1/1" + assert uut_result == "911" + +def testVal_re_match_iter_typed_second_child_int_type_recurse(): + """Test that re_match_iter_typed(recurse=False) finds the second child and returns the default `result_type`, which is `str`""" + config = """! +interface GigabitEthernet 1/1 + switchport mode trunk + switchport trunk native vlan 911 + channel-group 25 mode active +! +! +class-map match-all IP_PREC_MEDIUM + match ip precedence 2 3 4 5 +class-map match-all IP_PREC_HIGH + match ip precedence 6 7 +class-map match-all TEST +class-map match-all TO_ATM + match access-group name NOT_INTERNAL +class-map match-any ALL + match any +! +! +policy-map EXTERNAL_CBWFQ + class IP_PREC_HIGH + priority percent 10 + police cir percent 10 + conform-action transmit + exceed-action drop + class IP_PREC_MEDIUM + bandwidth percent 50 + queue-limit 100 + class class-default + bandwidth percent 40 + queue-limit 100 +policy-map SHAPE_HEIR + class ALL + shape average 630000 + service-policy EXTERNAL_CBWFQ +! +!""" + cfg = CiscoConfParse(config.splitlines(), factory=True) + obj = cfg.find_objects("^interface")[0] + + uut_result = obj.re_match_iter_typed( + r"switchport\s+trunk\s+native\s+vlan\s+(\S+)$", + group=1, + result_type=int, + recurse=True, + default="_no_match", + debug=False, + ) + # Check that base assumption is True... we are checking the right parent + assert obj.text == "interface GigabitEthernet 1/1" + assert uut_result == 911 + +def testVal_re_match_iter_typed_named_regex_group_second_child_int_type_recurse(): + """Test that re_match_iter_typed(recurse=False) finds the second child with a named regex""" + config = """! +interface GigabitEthernet 1/1 + switchport mode trunk + switchport trunk native vlan 911 + channel-group 25 mode active +! +! +class-map match-all IP_PREC_MEDIUM + match ip precedence 2 3 4 5 +class-map match-all IP_PREC_HIGH + match ip precedence 6 7 +class-map match-all TEST +class-map match-all TO_ATM + match access-group name NOT_INTERNAL +class-map match-any ALL + match any +! +! +policy-map EXTERNAL_CBWFQ + class IP_PREC_HIGH + priority percent 10 + police cir percent 10 + conform-action transmit + exceed-action drop + class IP_PREC_MEDIUM + bandwidth percent 50 + queue-limit 100 + class class-default + bandwidth percent 40 + queue-limit 100 +policy-map SHAPE_HEIR + class ALL + shape average 630000 + service-policy EXTERNAL_CBWFQ +! +!""" + cfg = CiscoConfParse(config.splitlines(), factory=True) + obj = cfg.find_objects("^interface")[0] + + uut_result = obj.re_match_iter_typed( + r"switchport\s+trunk\s+native\s+vlan\s+(?P\S+)$", + group=1, + result_type=int, + recurse=True, + default="_no_match", + debug=False, + ) + # Check that base assumption is True... we are checking the right parent + assert obj.text == "interface GigabitEthernet 1/1" + assert uut_result == 911 From f3785bf79d2a0a12eca584956ed051fb3bb826cb Mon Sep 17 00:00:00 2001 From: mpenning Date: Thu, 26 Oct 2023 19:58:50 -0500 Subject: [PATCH 7/8] Add IPv4 secondary address / network parsing --- CHANGES.md | 13 +++++++ README.md | 10 +++-- ciscoconfparse/models_cisco.py | 68 ++++++++++++++++++++++++++++++---- tests/test_Models_Cisco.py | 44 ++++++++++++++++++++++ 4 files changed, 124 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index feb95e8..525b870 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,19 @@ - Summary: - Insert something here +## Version: 1.9.8 + +- Released: 2023-10-26 +- Summary: + - Add IPv4 secondary addresses / networks to `ciscoconfparse/models_cisco.py` + - Update `README.md` example + +## Version: 1.9.7 + +- Released: 2023-10-26 +- Summary: + - Fix `README.md` example + ## Version: 1.9.6 - Released: 2023-10-26 diff --git a/README.md b/README.md index 026a5e9..c78d0ae 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,12 @@ for ccp_obj in parse.find_objects('^interface'): # IPv4 netmask object: ipaddress.IPv4Address() intf_v4masklength = ccp_obj.ipv4_addr_object.masklength + # set() of IPv4 secondary address/prefixlen strings + intf_v4secondary_networks = ccp_obj.ip_secondary_networks + + # set() of IPv4 secondary address strings + intf_v4secondary_addresses = ccp_obj.ip_secondary_addresses + # List of HSRP IPv4 addrs from the ciscoconfpasre/models_cisco.py HSRPInterfaceGroup() intf_hsrp_addresses = [hsrp_grp.ip for hsrp_grp in ccp_obj.hsrp_interfaces] @@ -352,10 +358,8 @@ If you already git cloned the repo and want to manually run tests either run wit ```shell $ cd tests -$ pytest -vvs ./test_CiscoConfParse.py +$ pytest -vvs ./test_*py ... -$ pytest -vvs ./test_Ccp_Util.py -etc... ``` ## Editing the Package diff --git a/ciscoconfparse/models_cisco.py b/ciscoconfparse/models_cisco.py index 2b9124d..be0b468 100644 --- a/ciscoconfparse/models_cisco.py +++ b/ciscoconfparse/models_cisco.py @@ -179,6 +179,7 @@ class HSRPInterfaceGroup(BaseCfgLine): # This method is on HSRPInterfaceGroup() @logger.catch(reraise=True) def __init__(self, group, parent): + """A HSRP Interface Group object""" super().__init__() self.feature = "hsrp" self._group = int(group) @@ -217,24 +218,28 @@ def __eq__(self, other): @property @logger.catch(reraise=True) def hsrp_group(self): + """Return the integer HSRP group number for this HSRP group""" return int(self._group) # This method is on HSRPInterfaceGroup() @property @logger.catch(reraise=True) def group(self): + """Return the integer HSRP group number for this HSRP group""" return self.hsrp_group # This method is on HSRPInterfaceGroup() @property @logger.catch(reraise=True) def ip(self): + """Return the string IPv4 HSRP address for this HSRP group""" return self.ipv4 # This method is on HSRPInterfaceGroup() @property @logger.catch(reraise=True) def ipv4(self): + """Return the string IPv4 HSRP address for this HSRP group""" ## NOTE: I have no intention of checking self.is_shutdown here ## People should be able to check the sanity of interfaces ## before they put them into production @@ -250,6 +255,7 @@ def ipv4(self): @property @logger.catch(reraise=True) def has_ipv6(self): + """Return a boolean for whether this interface is configured with an IPv6 HSRP address""" ## NOTE: I have no intention of checking self.is_shutdown here ## People should be able to check the sanity of interfaces ## before they put them into production @@ -280,23 +286,25 @@ def interface_name(self): @property @logger.catch(reraise=True) def interface_tracking(self): - return self.get_hsrp_tracked_interfaces() + """Return a list of HSRP TrackingInterface() objects for this HSRPInterfaceGroup()""" + return self.get_hsrp_tracking_interfaces() # This method is on HSRPInterfaceGroup() @logger.catch(reraise=True) - def get_glbp_tracked_interfaces(self): + def get_glbp_tracking_interfaces(self): """Get a list of unique GLBP tracked interfaces. This may never be supported by HSRPInterfaceGroup()""" raise NotImplementedError() # This method is on HSRPInterfaceGroup() @logger.catch(reraise=True) - def get_vrrp_tracked_interfaces(self): + def get_vrrp_tracking_interfaces(self): """Get a list of unique VRRP tracked interfaces. This may never be supported by HSRPInterfaceGroup()""" raise NotImplementedError() # This method is on HSRPInterfaceGroup() @logger.catch(reraise=True) - def get_hsrp_tracked_interfaces(self): + def get_hsrp_tracking_interfaces(self): + """Return a list of HSRP TrackingInterface() interfaces for this HSRPInterfaceGroup()""" ###################################################################### # Find decrement and interface ###################################################################### @@ -1290,32 +1298,76 @@ def ipv4_addr_object(self): logger.warning(f"intf='{self.name}' ipv4_addr='{self.ipv4_addr}' ipv4_netmask='{self.ipv4_netmask}'") return self.default_ipv4_addr_object + # This method is on BaseIOSIntfLine() + @property + @logger.catch(reraise=True) + def has_ip_secondary(self): + r"""Return an boolean for whether this interface has IPv4 secondary addresses""" + retval = self.re_match_iter_typed( + r"^\s*ip\s+address\s+\S+\s+\S+\s+(?Psecondary)\s*$", + groupdict={"secondary": bool}, + default=False + ) + return retval["secondary"] + + # This method is on BaseIOSIntfLine() + @property + @logger.catch(reraise=True) + def ip_secondary_addresses(self): + r"""Return a set of IPv4 secondary addresses (as strings)""" + retval = set() + for obj in self.parent.all_children: + _gg = obj.re_match_iter_typed( + r"^\s*ip\s+address\s+(?P\S+\s+\S+)\s+secondary\s*$", + groupdict={"secondary": IPv4Obj}, + default=False + ) + if _gg["secondary"]: + retval.add(str(_gg["secondary"].ip)) + return retval + + # This method is on BaseIOSIntfLine() + @property + @logger.catch(reraise=True) + def ip_secondary_networks(self): + r"""Return a set of IPv4 secondary addresses / prefixlen""" + retval = set() + for obj in self.parent.all_children: + _gg = obj.re_match_iter_typed( + r"^\s*ip\s+address\s+(?P\S+\s+\S+)\s+secondary\s*$", + groupdict={"secondary": IPv4Obj}, + default=False + ) + if _gg["secondary"]: + retval.add(f"{_gg['secondary'].ip}/{_gg['secondary'].prefixlen}") + return retval + # This method is on BaseIOSIntfLine() @property @logger.catch(reraise=True) def has_no_ipv4(self): - r"""Return an ccp_util.IPv4Obj object representing the subnet on this interface; if there is no address, return ccp_util.IPv4Obj('0.0.0.1/32')""" + r"""Return an ccp_util.IPv4Obj object representing the subnet on this interface; if there is no address, return ccp_util.IPv4Obj()""" return self.ipv4_addr_object == IPv4Obj() # This method is on BaseIOSIntfLine() @property @logger.catch(reraise=True) def ip(self): - r"""Return an ccp_util.IPv4Obj object representing the IPv4 address on this interface; if there is no address, return ccp_util.IPv4Obj('0.0.0.1/32')""" + r"""Return an ccp_util.IPv4Obj object representing the IPv4 address on this interface; if there is no address, return ccp_util.IPv4Obj()""" return self.ipv4_addr_object # This method is on BaseIOSIntfLine() @property @logger.catch(reraise=True) def ipv4(self): - r"""Return an ccp_util.IPv4Obj object representing the IPv4 address on this interface; if there is no address, return ccp_util.IPv4Obj('0.0.0.1/32')""" + r"""Return an ccp_util.IPv4Obj object representing the IPv4 address on this interface; if there is no address, return ccp_util.IPv4Obj()""" return self.ipv4_addr_object # This method is on BaseIOSIntfLine() @property @logger.catch(reraise=True) def ipv4_network_object(self): - r"""Return an ccp_util.IPv4Obj object representing the subnet on this interface; if there is no address, return ccp_util.IPv4Obj('0.0.0.1/32')""" + r"""Return an ccp_util.IPv4Obj object representing the subnet on this interface; if there is no address, return ccp_util.IPv4Obj()""" return self.ip_network_object # This method is on BaseIOSIntfLine() diff --git a/tests/test_Models_Cisco.py b/tests/test_Models_Cisco.py index 5737ccc..57c3f51 100755 --- a/tests/test_Models_Cisco.py +++ b/tests/test_Models_Cisco.py @@ -1822,6 +1822,49 @@ def testVal_IOSRouteLine_12(): assert 1 == obj.admin_distance assert "" == obj.tag +### +### ------ IPv4 secondary addresses +### + +def testVal_IOSIntfLine_ip_secondary01(): + """Test that a single secondary IPv4 address is detected""" + config = """! +interface Vlan21 + ip address 172.16.1.1 255.255.255.0 + ip address 172.16.21.1 255.255.255.0 secondary + no ip proxy-arp +! +""" + cfg = CiscoConfParse(config.splitlines(), factory=True) + intf_obj = cfg.find_objects("^interface")[0] + assert intf_obj.has_ip_secondary is True + +def testVal_IOSIntfLine_ip_secondary02(): + """Test that a multiple secondary IPv4 addresses are detected""" + config = """! +interface Vlan21 + ip address 172.16.1.1 255.255.255.0 + ip address 172.16.21.1 255.255.255.0 secondary + ip address 172.16.31.1 255.255.255.0 secondary + no ip proxy-arp +! +""" + cfg = CiscoConfParse(config.splitlines(), factory=True) + intf_obj = cfg.find_objects("^interface")[0] + assert intf_obj.has_ip_secondary is True + +def testVal_IOSIntfLine_ip_secondary03(): + """Test that a missing secondary IPv4 addresses are detected""" + config = """! +interface Vlan21 + ip address 172.16.1.1 255.255.255.0 + no ip proxy-arp +! +""" + cfg = CiscoConfParse(config.splitlines(), factory=True) + intf_obj = cfg.find_objects("^interface")[0] + assert intf_obj.has_ip_secondary is False + ### ### ------ IPv4 Helper-Addresses -------- @@ -1936,3 +1979,4 @@ def testVal_IOSAaaGroupServerLine_02(): assert set(["192.0.2.10", "192.0.2.11"]) == obj.server_private assert "VRF_001" == obj.vrf assert "FastEthernet0/48" == obj.source_interface + From 38e4d83c63cbafefe94d414e0e322008b9983d8b Mon Sep 17 00:00:00 2001 From: mpenning Date: Thu, 26 Oct 2023 19:58:59 -0500 Subject: [PATCH 8/8] Roll version number --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0c3e2ea..50ca326 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ requires-python = ">=3.8.0" [tool.poetry] name = "ciscoconfparse" -version = "1.9.6" +version = "1.9.7" description = "Parse, Audit, Query, Build, and Modify Cisco IOS-style and JunOS-style configs" license = "GPL-3.0-only" authors = [