diff --git a/.gitignore b/.gitignore index ac6eb6cc..5f393815 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ tags .tox .undodir -go.sum +*go.sum diff --git a/CHANGES.md b/CHANGES.md index 72758a9f..525b8706 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,11 +4,18 @@ - 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 HSRP decrement parsing + - Fix `README.md` example ## Version: 1.9.6 diff --git a/README.md b/README.md index acc202df..c78d0ae8 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 @@ -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 2b9124d6..be0b4688 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/dev_tools/deploy_docs/Makefile b/dev_tools/deploy_docs/Makefile index 3c1cb51b..2eb283ed 100644 --- a/dev_tools/deploy_docs/Makefile +++ b/dev_tools/deploy_docs/Makefile @@ -99,14 +99,6 @@ lint: dep: @echo "$(COL_GREEN)>> getting deploy_docs dependencies$(COL_END)" -<<<<<<< HEAD - # Remove unused dependencies that may be lingering in go.mod... - go mod tidy - # Get required dependencies... -||||||| b4f0f68 -======= - go mod tidy ->>>>>>> develop go get github.com/melbahja/goph@latest go get github.com/gleich/logoru@latest .PHONY: dep @@ -119,13 +111,7 @@ backup: # Delete backups older than 30 days... dont crash if directory is empty -find ./_bak/*tgz -maxdepth 1 -type f -mtime +30 -delete # Create a timestamped backup tarball... exclude the _bak directory -<<<<<<< HEAD - tar --exclude=$(GO_BIN_DIR) --exclude=_bak -zcv -f _bak/$(shell date "+%Y%m%d_%H.%M.%S").tgz . -||||||| b4f0f68 - tar --exclude=_bak -zcv -f _bak/$(shell date "+%Y%m%d_%H.%M.%S").tgz . -======= tar --exclude=bin --exclude=_bak -zcv -f _bak/$(shell date "+%Y%m%d_%H.%M.%S").tgz . ->>>>>>> develop .PHONY: backup clean: diff --git a/dev_tools/deploy_docs/go.mod b/dev_tools/deploy_docs/go.mod index dcdd49d6..d0195255 100644 --- a/dev_tools/deploy_docs/go.mod +++ b/dev_tools/deploy_docs/go.mod @@ -6,3 +6,14 @@ require ( github.com/gleich/logoru v0.0.0-20230101033757-d86cd895c7a1 github.com/melbahja/goph v1.4.0 ) + +require ( + github.com/fatih/color v1.10.0 // indirect + github.com/kr/fs v0.1.0 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/sftp v1.13.5 // indirect + golang.org/x/crypto v0.6.0 // indirect + golang.org/x/sys v0.5.0 // indirect +) diff --git a/dev_tools/deploy_docs/go.sum b/dev_tools/deploy_docs/go.sum index 38c4fd06..10b227c3 100644 --- a/dev_tools/deploy_docs/go.sum +++ b/dev_tools/deploy_docs/go.sum @@ -2,10 +2,20 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/gleich/logoru v0.0.0-20230101033757-d86cd895c7a1 h1:KkEP5qFmoabNkkRD+Fz4O3GHDwpsecWoZ2J45Luw3n0= +github.com/gleich/logoru v0.0.0-20230101033757-d86cd895c7a1/go.mod h1:sGBOiXpXj5KPWwXHhzyGJBe4+UC8NpIfQf7pTlTwMYI= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/melbahja/goph v1.3.1 h1:FxFevAwCCpLkM4WBmnVVxcJBcBz6lKQpsN5biV2hA6w= github.com/melbahja/goph v1.3.1/go.mod h1:uG+VfK2Dlhk+O32zFrRlc3kYKTlV6+BtvPWd/kK7U68= +github.com/melbahja/goph v1.4.0 h1:z0PgDbBFe66lRYl3v5dGb9aFgPy0kotuQ37QOwSQFqs= +github.com/melbahja/goph v1.4.0/go.mod h1:uG+VfK2Dlhk+O32zFrRlc3kYKTlV6+BtvPWd/kK7U68= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go= @@ -38,6 +48,9 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -61,4 +74,5 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/tests/fixtures/configs/sample_08.ios b/tests/fixtures/configs/sample_08.ios index 7c1d89c0..52e20080 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 @@ -242,7 +244,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 diff --git a/tests/test_Ccp_Abc.py b/tests/test_Ccp_Abc.py new file mode 100755 index 00000000..0c0ebedc --- /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 diff --git a/tests/test_CiscoConfParse.py b/tests/test_CiscoConfParse.py index 6c5fd065..0f8ebcf7 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 a46d58c6..57c3f51c 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 @@ -1480,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 @@ -1825,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 -------- @@ -1939,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 +