From ca0023c4cc52d68787d9d206584587ccc29fa2b3 Mon Sep 17 00:00:00 2001 From: Dmitry Lyssenko Date: Mon, 13 Feb 2023 14:26:24 +0100 Subject: [PATCH 1/2] fix issue #161 - add parsing of secondary ip from interfaces --- pyeapi/api/ipinterfaces.py | 34 ++++++++++++++++-------------- test/fixtures/running_config.text | 6 ++++++ test/unit/test_api_ipinterfaces.py | 11 +++++++--- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/pyeapi/api/ipinterfaces.py b/pyeapi/api/ipinterfaces.py index 5b0e001..55d2a8c 100644 --- a/pyeapi/api/ipinterfaces.py +++ b/pyeapi/api/ipinterfaces.py @@ -53,16 +53,18 @@ SWITCHPORT_RE = re.compile(r'no switchport$', re.M) -class Ipinterfaces(EntityCollection): +class Ipinterfaces( EntityCollection ): - def get(self, name): + def get( self, name ): """Returns the specific IP interface properties The Ipinterface resource returns the following: * name (str): The name of the interface * address (str): The IP address of the interface in the form - of A.B.C.D/E + of A.B.C.D/E (None if no ip configured) + * secondary (list): The list of secondary IP addresses of the interface + (if any configured) * mtu (int): The configured value for IP MTU. @@ -75,22 +77,21 @@ def get(self, name): the current configuration of the node. If the specified interface does not exist then None is returned. """ - config = self.get_block('interface %s' % name) - - if name[0:2] in ['Et', 'Po'] and not SWITCHPORT_RE.search(config, - re.M): + config = self.get_block( 'interface %s' % name ) + if name[ 0:2 ] in [ + 'Et', 'Po' ] and not SWITCHPORT_RE.search( config, re.M ): return None - resource = dict(name=name) - resource.update(self._parse_address(config)) - resource.update(self._parse_mtu(config)) + resource = dict( name=name ) + resource.update( self._parse_address(config) ) + resource.update( self._parse_mtu(config) ) return resource - def _parse_address(self, config): + def _parse_address( self, config ): """Parses the config block and returns the ip address value - The provided configuration block is scaned and the configured value - for the IP address is returned as a dict object. If the IP address + The provided configuration block is scanned and the configured value + for the IP address is returned as a dict object. If the IP address value is not configured, then None is returned for the value Args: @@ -99,9 +100,10 @@ def _parse_address(self, config): Return: dict: A dict object intended to be merged into the resource dict """ - match = re.search(r'ip address ([^\s]+)', config) - value = match.group(1) if match else None - return dict(address=value) + match = re.findall( r'ip address ([^\s]+)', config, re.M ) + primary, secondary = ( match[0], match[1:] ) if match else ( None, None ) + return dict( address=primary, + secondary=secondary ) if secondary else dict( address=primary ) def _parse_mtu(self, config): """Parses the config block and returns the configured IP MTU value diff --git a/test/fixtures/running_config.text b/test/fixtures/running_config.text index 4e301bb..73c07a3 100644 --- a/test/fixtures/running_config.text +++ b/test/fixtures/running_config.text @@ -1581,6 +1581,12 @@ interface Loopback0 bfd interval 300 min_rx 300 multiplier 3 default ntp serve ! +interface Loopback2 + description test fixture with secondary ip + ip address 2.2.2.2/32 + ip address 3.255.255.1/24 secondary + ip address 4.255.255.1/24 secondary +! interface Ethernet8 ! the interface config is added separately covering test for issue #213 ! it might be inconsistend with the rest of config, though all unit tests pass diff --git a/test/unit/test_api_ipinterfaces.py b/test/unit/test_api_ipinterfaces.py index 00ed1b4..9350da6 100644 --- a/test/unit/test_api_ipinterfaces.py +++ b/test/unit/test_api_ipinterfaces.py @@ -53,14 +53,19 @@ def __init__(self, *args, **kwargs): self.config = open(get_fixture('running_config.text')).read() def test_get(self): - result = self.instance.get('Loopback0') - values = dict(name='Loopback0', address='1.1.1.1/32', mtu=1500) + result = self.instance.get( 'Loopback0' ) + values = dict( name='Loopback0', address='1.1.1.1/32', mtu=1500 ) + self.assertEqual( result, values ) + # test interface with secondary ip + result = self.instance.get( 'Loopback2' ) + values = dict( name='Loopback2', address='2.2.2.2/32', + secondary=['3.255.255.1/24', '4.255.255.1/24'], mtu=None ) self.assertEqual(result, values) def test_getall(self): result = self.instance.getall() self.assertIsInstance(result, dict) - self.assertEqual(len(result), 3) + self.assertEqual(len(result), 4) def test_instance_functions(self): for intf in self.INTERFACES: From d34349af5ffb7af4b1e0011fb643f12a23ab0629 Mon Sep 17 00:00:00 2001 From: Dmitry Lyssenko Date: Mon, 13 Feb 2023 16:33:01 +0100 Subject: [PATCH 2/2] making flake8 happy --- Makefile | 4 ++-- pyeapi/api/ipinterfaces.py | 7 ++++--- pyeapi/client.py | 3 ++- pyeapi/eapilib.py | 2 +- pyeapi/utils.py | 8 ++------ 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 417a7aa..4b445c0 100644 --- a/Makefile +++ b/Makefile @@ -37,8 +37,8 @@ pyflakes: pyflakes pyeapi/ test/ flake8: - flake8 --ignore=E201,E202,E302,E303,E402,E731,W391 --exit-zero pyeapi/ - flake8 --ignore=E201,E202,E302,E303,E402,E731,W391,N802 --max-line-length=100 test/ + flake8 --ignore=E128,E201,E202,E302,E303,E402,E731,W391 --exit-zero pyeapi/ + flake8 --ignore=E128,E201,E202,E302,E303,E402,E731,W391,N802 --max-line-length=100 test/ check: check-manifest diff --git a/pyeapi/api/ipinterfaces.py b/pyeapi/api/ipinterfaces.py index 55d2a8c..e94f1e3 100644 --- a/pyeapi/api/ipinterfaces.py +++ b/pyeapi/api/ipinterfaces.py @@ -63,8 +63,8 @@ def get( self, name ): * name (str): The name of the interface * address (str): The IP address of the interface in the form of A.B.C.D/E (None if no ip configured) - * secondary (list): The list of secondary IP addresses of the interface - (if any configured) + * secondary (list): The list of secondary IP addresses of the + interface (if any configured) * mtu (int): The configured value for IP MTU. @@ -101,7 +101,8 @@ def _parse_address( self, config ): dict: A dict object intended to be merged into the resource dict """ match = re.findall( r'ip address ([^\s]+)', config, re.M ) - primary, secondary = ( match[0], match[1:] ) if match else ( None, None ) + primary, secondary = ( match[0], + match[1:] ) if match else ( None, None ) return dict( address=primary, secondary=secondary ) if secondary else dict( address=primary ) diff --git a/pyeapi/client.py b/pyeapi/client.py index f7b965e..f267ea2 100644 --- a/pyeapi/client.py +++ b/pyeapi/client.py @@ -110,7 +110,8 @@ from pyeapi.eapilib import HttpEapiConnection, HttpsEapiConnection from pyeapi.eapilib import HttpsEapiCertConnection -from pyeapi.eapilib import HttpEapiSessionConnection, HttpsEapiSessionConnection +from pyeapi.eapilib import HttpEapiSessionConnection +from pyeapi.eapilib import HttpsEapiSessionConnection from pyeapi.eapilib import SocketEapiConnection, HttpLocalEapiConnection from pyeapi.eapilib import CommandError diff --git a/pyeapi/eapilib.py b/pyeapi/eapilib.py index 7c804f3..71fc39a 100644 --- a/pyeapi/eapilib.py +++ b/pyeapi/eapilib.py @@ -756,7 +756,7 @@ def authentication(self, username, password): _LOGGER.exception(exc) self.socket_error = exc self.error = exc - error_msg = 'Socket error during eAPI authentication: %s' % str(exc) + error_msg = f'Socket error during eAPI authentication: {exc}' raise ConnectionError(str(self), error_msg) except ValueError as exc: _LOGGER.exception(exc) diff --git a/pyeapi/utils.py b/pyeapi/utils.py index 011877a..ad21164 100644 --- a/pyeapi/utils.py +++ b/pyeapi/utils.py @@ -167,10 +167,6 @@ def make_iterable(value): Returns: An iterable object of type list """ - if sys.version_info <= (3, 0): - # Convert unicode values to strings for Python 2 - if isinstance(value, unicode): - value = str(value) if isinstance(value, str) or isinstance( value, dict) or isinstance(value, CliVariants): value = [value] @@ -259,5 +255,5 @@ class CliVariants: """ def __init__(self, *cli): assert len( cli ) >= 2, 'must be initialized with 2 or more arguments' - self.variants = [ v if not isinstance(v, str) and isinstance(v, Iterable) - else [v] for v in cli ] + self.variants = [ v if not isinstance(v, + str) and isinstance(v, Iterable) else [v] for v in cli ]