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 +