diff --git a/.travis.yml b/.travis.yml index b1d6ce0..a253f08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: python python: - '2.7' - - '3.4' + - '3.8' install: pip install -r dev-requirements.txt diff --git a/Makefile b/Makefile index 7cbd456..76150fa 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ VERSION := $(shell cat VERSION) all: clean check pep8 flake8 tests pep8: - -pep8 -r --ignore=E402,E731,E501,E221,W291,W391,E302,E251,E203,W293,E231,E303,E201,E225,E261,E241 pyeapi/ test/ + pycodestyle -r --ignore=E402,E731,E501,E221,W291,W391,E302,E251,E203,W293,E231,E303,E201,E225,E261,E241 pyeapi/ test/ pyflakes: pyflakes pyeapi/ test/ diff --git a/VERSION b/VERSION index 100435b..ee94dd8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.8.2 +0.8.3 diff --git a/dev-requirements.txt b/dev-requirements.txt index aa05d1f..903885c 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,9 +1,9 @@ -netaddr +-r requirements.txt mock coveralls twine check-manifest -pep8 +pycodestyle pyflakes coverage sphinx @@ -11,6 +11,3 @@ sphinxcontrib-napoleon flake8 flake8-print flake8-debugger -pep8-naming - - diff --git a/docs/configfile.rst b/docs/configfile.rst index 49689c3..b34a304 100644 --- a/docs/configfile.rst +++ b/docs/configfile.rst @@ -39,13 +39,15 @@ The following configuration options are available for defining node entries: - http_local (available in EOS 4.14.5 or later) - http - https + - https_certs :port: Configures the port to use for the eAPI connection. A default port is used if this parameter is absent, based on the transport setting using the following values: - transport: http, default port: 80 - - transport: https, deafult port: 443 + - transport: https, default port: 443 + - transport: https_certs, default port: 443 - transport: http_local, default port: 8080 - transport: socket, default port: n/a @@ -62,6 +64,7 @@ Transport eapi.conf Required Script run from Authentication Required =========== ================== =============== ======================== http Yes On/Off-switch Yes https Yes On/Off-switch Yes +https_certs Yes On/Off-switch Yes (Auth done via certs, not un/pw) http_local Yes On-switch only No socket No On-switch only No =========== ================== =============== ======================== @@ -169,6 +172,29 @@ As the table above indicates, a pyeapi configuration file is required in username: admin password: admin +Using HTTPS with Certificates +============================= + +The https_certs transport options allows users to do authentication for pyeapi +with certificates instead of username/password. This requires functional +certificate chains are setup, copied to the proper location and trusted by +EOS beforehand. The ca_file parameter is optional. If provided the switches +certificate will also be validated against the provided CA cert. If no CA cert +is provided then no server side validation will be done. + +.. code-block:: console + + [connection:veos01] + transport: https_certs + cert_file: /path/to/certificate/file + key_file: /path/to/private/key/file + ca_file: /path/to/CA/certificate/file + + [connection:veos02] + transport: https_certs + cert_file: /path/to/certificate/file + key_file: /path/to/private/key/file + ******************* The DEFAULT Section ******************* diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 8934cd6..333bd54 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -19,6 +19,7 @@ include: - HTTP - HTTPS + - HTTPS Certificates - HTTP Local - Unix Socket diff --git a/docs/release-notes-0.8.3.rst b/docs/release-notes-0.8.3.rst new file mode 100644 index 0000000..bc21a05 --- /dev/null +++ b/docs/release-notes-0.8.3.rst @@ -0,0 +1,25 @@ +Release 0.8.3 +------------- + +2020-01-26 + +New Modules +^^^^^^^^^^^ + + +Enhancements +^^^^^^^^^^^^ + +* Support eapi command revision syntax (`181 `_) + +* Add ability to use pyeapi with certificates + +* Added check for 'match' statement to be valid before parsing regex ACL + +Fixed +^^^^^ + +Known Caveats +^^^^^^^^^^^^^ + + diff --git a/pyeapi/__init__.py b/pyeapi/__init__.py index 0c069f4..8708596 100644 --- a/pyeapi/__init__.py +++ b/pyeapi/__init__.py @@ -29,7 +29,7 @@ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -__version__ = '0.8.2' +__version__ = '0.8.3' __author__ = 'Arista EOS+' diff --git a/pyeapi/api/abstract.py b/pyeapi/api/abstract.py index a628b9b..f0a4eec 100644 --- a/pyeapi/api/abstract.py +++ b/pyeapi/api/abstract.py @@ -67,6 +67,10 @@ class BaseEntity(object): def __init__(self, node): self.node = node + @property + def version_number(self): + return self.node.version_number + @property def config(self): return self.node.running_config diff --git a/pyeapi/api/acl.py b/pyeapi/api/acl.py index 498e544..4b45139 100644 --- a/pyeapi/api/acl.py +++ b/pyeapi/api/acl.py @@ -243,17 +243,18 @@ def _parse_entries(self, config): entries = dict() for item in re.finditer(r'\d+ [p|d].*$', config, re.M): match = self.entry_re.match(item.group(0)) - entry = dict() - entry['action'] = match.group(2) - entry['protocol'] = match.group(3) - entry['srcaddr'] = match.group(5) or 'any' - entry['srclen'] = match.group(6) - entry['srcport'] = match.group(7) - entry['dstaddr'] = match.group(9) or 'any' - entry['dstlen'] = match.group(10) - entry['dstport'] = match.group(12) - entry['other'] = match.group(13) - entries[match.group(1)] = entry + if match: + entry = dict() + entry['action'] = match.group(2) + entry['protocol'] = match.group(3) + entry['srcaddr'] = match.group(5) or 'any' + entry['srclen'] = match.group(6) + entry['srcport'] = match.group(7) + entry['dstaddr'] = match.group(9) or 'any' + entry['dstlen'] = match.group(10) + entry['dstport'] = match.group(12) + entry['other'] = match.group(13) + entries[match.group(1)] = entry return dict(entries=entries) def create(self, name): diff --git a/pyeapi/api/bgp.py b/pyeapi/api/bgp.py index 54e74f9..8322212 100644 --- a/pyeapi/api/bgp.py +++ b/pyeapi/api/bgp.py @@ -205,7 +205,10 @@ def getall(self): return collection def _parse_peer_group(self, config, name): - regexp = r'neighbor {} peer-group ([^\s]+)'.format(name) + if self.version_number >= '4.23': + regexp = r'neighbor {} peer group ([^\s]+)'.format(name) + else: + regexp = r'neighbor {} peer-group ([^\s]+)'.format(name) match = re.search(regexp, config) value = match.group(1) if match else None return dict(peer_group=value) @@ -234,7 +237,7 @@ def _parse_description(self, config, name): return dict(description=value) def _parse_next_hop_self(self, config, name): - exp = 'no neighobr {} next-hop-self'.format(name) + exp = 'no neighbor {} next-hop-self'.format(name) value = exp in config return dict(next_hop_self=not value) @@ -263,7 +266,12 @@ def create(self, name): def delete(self, name): response = self.configure('no neighbor {}'.format(name)) if not response: - response = self.configure('no neighbor {} peer-group'.format(name)) + if self.version_number >= '4.23': + response = self.configure('no neighbor {} ' + 'peer group'.format(name)) + else: + response = self.configure('no neighbor {} ' + 'peer-group'.format(name)) return response def configure(self, cmd): @@ -280,8 +288,12 @@ def command_builder(self, name, cmd, value, default, disable): def set_peer_group(self, name, value=None, default=False, disable=False): if not self.ispeergroup(name): - cmd = self.command_builder(name, 'peer-group', value, default, - disable) + if self.version_number >= '4.23': + cmd = self.command_builder(name, 'peer group', value, default, + disable) + else: + cmd = self.command_builder(name, 'peer-group', value, default, + disable) return self.configure(cmd) return False diff --git a/pyeapi/api/interfaces.py b/pyeapi/api/interfaces.py index 2492624..c6f88c2 100644 --- a/pyeapi/api/interfaces.py +++ b/pyeapi/api/interfaces.py @@ -565,11 +565,16 @@ def set_vrf(self, name, vrf, default=False, disable=False): True if the operation succeeds otherwise False is returned """ commands = ['interface %s' % name] - commands.append(self.command_builder('vrf forwarding', vrf, - default=default, disable=disable)) + if self.version_number >= '4.23': + commands.append(self.command_builder('vrf', vrf, + default=default, + disable=disable)) + else: + commands.append(self.command_builder('vrf forwarding', vrf, + default=default, + disable=disable)) return self.configure(commands) - class PortchannelInterface(BaseInterface): def __str__(self): diff --git a/pyeapi/api/mlag.py b/pyeapi/api/mlag.py index 6c523d8..6bbb8a0 100644 --- a/pyeapi/api/mlag.py +++ b/pyeapi/api/mlag.py @@ -144,7 +144,7 @@ def _parse_peer_address(self, config): dict: A dict object that is intended to be merged into the resource dict """ - match = re.search(r'peer-address ([^\s]+)', config) + match = re.search(r'peer-address (\d+\.\d+\.\d+\.\d+)$', config) value = match.group(1) if match else None return dict(peer_address=value) diff --git a/pyeapi/api/ntp.py b/pyeapi/api/ntp.py index d4a4ae4..f8fe9ed 100644 --- a/pyeapi/api/ntp.py +++ b/pyeapi/api/ntp.py @@ -88,7 +88,10 @@ def get(self): return response def _parse_source_interface(self, config): - match = re.search(r'^ntp source ([^\s]+)', config, re.M) + if self.version_number >= '4.23': + match = re.search(r'^ntp local-interface ([^\s]+)', config, re.M) + else: + match = re.search(r'^ntp source ([^\s]+)', config, re.M) value = match.group(1) if match else None return dict(source_interface=value) @@ -118,7 +121,10 @@ def delete(self): Returns: True if the operation succeeds, otherwise False. """ - cmd = self.command_builder('ntp source', disable=True) + if self.version_number >= '4.23': + cmd = self.command_builder('ntp local-interface', disable=True) + else: + cmd = self.command_builder('ntp source', disable=True) return self.configure(cmd) def default(self): @@ -127,7 +133,10 @@ def default(self): Returns: True if the operation succeeds, otherwise False. """ - cmd = self.command_builder('ntp source', default=True) + if self.version_number >= '4.23': + cmd = self.command_builder('ntp local-interface', default=True) + else: + cmd = self.command_builder('ntp source', default=True) return self.configure(cmd) def set_source_interface(self, name): @@ -139,7 +148,10 @@ def set_source_interface(self, name): Returns: True if the operation succeeds, otherwise False. """ - cmd = self.command_builder('ntp source', value=name) + if self.version_number >= '4.23': + cmd = self.command_builder('ntp local-interface', value=name) + else: + cmd = self.command_builder('ntp source', value=name) return self.configure(cmd) def add_server(self, name, prefer=False): diff --git a/pyeapi/api/routemaps.py b/pyeapi/api/routemaps.py index 6c513c8..6addc7a 100644 --- a/pyeapi/api/routemaps.py +++ b/pyeapi/api/routemaps.py @@ -239,7 +239,7 @@ def set_match_statements(self, name, action, seqno, statements): """ try: current_statements = self.get(name)[action][seqno]['match'] - except: + except Exception: current_statements = [] commands = list() @@ -275,7 +275,7 @@ def set_set_statements(self, name, action, seqno, statements): """ try: current_statements = self.get(name)[action][seqno]['set'] - except: + except Exception: current_statements = [] commands = list() diff --git a/pyeapi/api/staticroute.py b/pyeapi/api/staticroute.py index 5b1ce13..f5a5637 100644 --- a/pyeapi/api/staticroute.py +++ b/pyeapi/api/staticroute.py @@ -158,13 +158,13 @@ def getall(self): # Get the four identifying components ip_dest = match[0] next_hop = match[1] - next_hop_ip = None if match[2] is '' else match[2] + next_hop_ip = None if match[2] == '' else match[2] distance = int(match[3]) # Create the data dict with the remaining components data = {} - data['tag'] = None if match[4] is '' else int(match[4]) - data['route_name'] = None if match[5] is '' else match[5] + data['tag'] = None if match[4] == '' else int(match[4]) + data['route_name'] = None if match[5] == '' else match[5] # Build the complete dict entry from the four components # and the data. diff --git a/pyeapi/api/system.py b/pyeapi/api/system.py index 01de13d..ef9107b 100644 --- a/pyeapi/api/system.py +++ b/pyeapi/api/system.py @@ -105,7 +105,7 @@ def _parse_banners(self): into the resource dict """ motd_value = login_value = None - matches = re.findall('^banner\s+(login|motd)\s?$\n(.*?)$\nEOF$\n', + matches = re.findall(r'^banner\s+(login|motd)\s?$\n(.*?)$\nEOF$\n', self.config, re.DOTALL | re.M) for match in matches: if match[0].strip() == "motd": diff --git a/pyeapi/api/users.py b/pyeapi/api/users.py index d840b75..5bc1411 100644 --- a/pyeapi/api/users.py +++ b/pyeapi/api/users.py @@ -82,12 +82,6 @@ class Users(EntityCollection): following configuration line that might contain the users sshkey. """ - users_re = re.compile(r'username (?P[^\s]+) privilege (\d+)' - r'(?: role ([^\s]+))?' - r'(?: (nopassword))?' - r'(?: secret (0|5|7|sha512) (.+))?' - r'.*$\n(?:username (?P=user) sshkey (.+)$)?', re.M) - def get(self, name): """Returns the local user configuration as a resource dict @@ -108,6 +102,23 @@ def getall(self): Returns: dict: A dict of usernames with a nested resource dict object """ + if self.version_number >= '4.23': + self.users_re = re.compile(r'username (?P[^\s]+) ' + r'privilege (\d+)' + r'(?: role ([^\s]+))?' + r'(?: (nopassword))?' + r'(?: secret (0|5|7|sha512) (.+))?' + r'.*$\n(?:username (?P=user) ' + r'ssh.key (.+)$)?', re.M) + else: + self.users_re = re.compile(r'username (?P[^\s]+) ' + r'privilege (\d+)' + r'(?: role ([^\s]+))?' + r'(?: (nopassword))?' + r'(?: secret (0|5|7|sha512) (.+))?' + r'.*$\n(?:username (?P=user) ' + r'sshkey (.+)$)?', re.M) + users = self.users_re.findall(self.config, re.M) resources = dict() for user in users: @@ -131,7 +142,10 @@ def _parse_username(self, config): resource['nopassword'] = nopass == 'nopassword' resource['format'] = fmt resource['secret'] = secret - resource['sshkey'] = sshkey + if self.version_number >= '4.23': + resource['ssh-key'] = sshkey + else: + resource['sshkey'] = sshkey return {username: resource} def create(self, name, nopassword=None, secret=None, encryption=None): @@ -287,8 +301,14 @@ def set_sshkey(self, name, value=None, default=False, disable=False): Returns: True if the operation was successful otherwise False """ - cmd = self.command_builder('username %s sshkey' % name, value=value, - default=default, disable=disable) + if self.version_number >= '4.23': + cmd = self.command_builder('username %s ssh-key' % name, + value=value, + default=default, disable=disable) + else: + cmd = self.command_builder('username %s sshkey' % name, + value=value, + default=default, disable=disable) return self.configure(cmd) diff --git a/pyeapi/api/varp.py b/pyeapi/api/varp.py index b68b126..c2715d4 100644 --- a/pyeapi/api/varp.py +++ b/pyeapi/api/varp.py @@ -178,7 +178,7 @@ def set_addresses(self, name, addresses=None, default=False, elif addresses is not None: try: current_addresses = self.get(name)['addresses'] - except: + except Exception: current_addresses = [] # remove virtual-router addresses not present in addresses list diff --git a/pyeapi/api/vrfs.py b/pyeapi/api/vrfs.py index b09b529..d3a3802 100644 --- a/pyeapi/api/vrfs.py +++ b/pyeapi/api/vrfs.py @@ -76,7 +76,10 @@ def get(self, value): key/value pairs. """ - config = self.get_block('vrf definition %s' % value) + if self.version_number >= '4.23': + config = self.get_block('vrf instance %s' % value) + else: + config = self.get_block('vrf definition %s' % value) if not config: return None response = dict(vrf_name=value) @@ -136,7 +139,10 @@ def getall(self): A dict object of VRF attributes """ - vrfs_re = re.compile(r'(?<=^vrf definition\s)(\w+)', re.M) + if self.version_number >= '4.23': + vrfs_re = re.compile(r'(?<=^vrf instance\s)(\w+)', re.M) + else: + vrfs_re = re.compile(r'(?<=^vrf definition\s)(\w+)', re.M) response = dict() for vrf in vrfs_re.findall(self.config): @@ -160,7 +166,10 @@ def create(self, vrf_name, rd=None): Returns: True if create was successful otherwise False """ - commands = ['vrf definition %s' % vrf_name] + if self.version_number >= '4.23': + commands = ['vrf instance %s' % vrf_name] + else: + commands = ['vrf definition %s' % vrf_name] if rd: commands.append('rd %s' % rd) return self.configure(commands) @@ -174,7 +183,10 @@ def delete(self, vrf_name): Returns: True if the operation was successful otherwise False """ - command = 'no vrf definition %s' % vrf_name + if self.version_number >= '4.23': + command = 'no vrf instance %s' % vrf_name + else: + command = 'no vrf definition %s' % vrf_name return self.configure(command) def default(self, vrf_name): @@ -186,7 +198,10 @@ def default(self, vrf_name): Returns: True if the operation was successful otherwise False """ - command = 'default vrf definition %s' % vrf_name + if self.version_number >= '4.23': + command = 'default vrf instance %s' % vrf_name + else: + command = 'default vrf definition %s' % vrf_name return self.configure(command) def configure_vrf(self, vrf_name, commands): @@ -200,7 +215,11 @@ def configure_vrf(self, vrf_name, commands): True if the commands completed successfully """ commands = make_iterable(commands) - commands.insert(0, 'vrf definition %s' % vrf_name) + if self.version_number >= '4.23': + commands.insert(0, 'vrf instance %s' % vrf_name) + else: + commands.insert(0, 'vrf definition %s' % vrf_name) + return self.configure(commands) def set_rd(self, vrf_name, rd): @@ -301,8 +320,12 @@ def set_interface(self, vrf_name, interface, default=False, disable=False): True if the operation was successful otherwise False """ cmds = ['interface %s' % interface] - cmds.append(self.command_builder('vrf forwarding', value=vrf_name, - default=default, disable=disable)) + if self.version_number >= '4.23': + cmds.append(self.command_builder('vrf', value=vrf_name, + default=default, disable=disable)) + else: + cmds.append(self.command_builder('vrf forwarding', value=vrf_name, + default=default, disable=disable)) return self.configure(cmds) diff --git a/pyeapi/api/vrrp.py b/pyeapi/api/vrrp.py index a63af21..b875ece 100644 --- a/pyeapi/api/vrrp.py +++ b/pyeapi/api/vrrp.py @@ -251,25 +251,41 @@ def getall(self): return vrrps def _parse_enable(self, config, vrid): - match = re.search(r'^\s+vrrp %s shutdown$' % vrid, config, re.M) + if self.version_number >= '4.21.3': + match = re.search(r'^\s+vrrp %s disabled$' % vrid, config, re.M) + else: + match = re.search(r'^\s+vrrp %s shutdown$' % vrid, config, re.M) if match: return dict(enable=False) return dict(enable=True) def _parse_primary_ip(self, config, vrid): - match = re.search(r'^\s+vrrp %s ip (\d+\.\d+\.\d+\.\d+)$' % - vrid, config, re.M) + if self.version_number >= '4.21.3': + match = re.search(r'^\s+vrrp %s ipv4 (\d+\.\d+\.\d+\.\d+)$' % + vrid, config, re.M) + else: + match = re.search(r'^\s+vrrp %s ip (\d+\.\d+\.\d+\.\d+)$' % + vrid, config, re.M) value = match.group(1) if match else None return dict(primary_ip=value) def _parse_priority(self, config, vrid): - match = re.search(r'^\s+vrrp %s priority (\d+)$' % vrid, config, re.M) + if self.version_number >= '4.21.3': + match = re.search(r'^\s+vrrp %s priority-level (\d+)$' % + vrid, config, re.M) + else: + match = re.search(r'^\s+vrrp %s priority (\d+)$' % + vrid, config, re.M) value = int(match.group(1)) if match else None return dict(priority=value) def _parse_timers_advertise(self, config, vrid): - match = re.search(r'^\s+vrrp %s timers advertise (\d+)$' % - vrid, config, re.M) + if self.version_number >= '4.21.3': + match = re.search(r'^\s+vrrp %s advertisement interval (\d+)$' % + vrid, config, re.M) + else: + match = re.search(r'^\s+vrrp %s timers advertise (\d+)$' % + vrid, config, re.M) value = int(match.group(1)) if match else None return dict(timers_advertise=value) @@ -280,14 +296,22 @@ def _parse_preempt(self, config, vrid): return dict(preempt=False) def _parse_secondary_ip(self, config, vrid): - matches = re.findall(r'^\s+vrrp %s ip (\d+\.\d+\.\d+\.\d+) ' - r'secondary$' % vrid, config, re.M) + if self.version_number >= '4.21.3': + matches = re.findall(r'^\s+vrrp %s ipv4 (\d+\.\d+\.\d+\.\d+) ' + r'secondary$' % vrid, config, re.M) + else: + matches = re.findall(r'^\s+vrrp %s ip (\d+\.\d+\.\d+\.\d+) ' + r'secondary$' % vrid, config, re.M) value = matches if matches else [] return dict(secondary_ip=value) def _parse_description(self, config, vrid): - match = re.search(r'^\s+vrrp %s description(.*)$' % - vrid, config, re.M) + if self.version_number >= '4.21.3': + match = re.search(r'^\s+vrrp %s session description(.*)$' % + vrid, config, re.M) + else: + match = re.search(r'^\s+vrrp %s description(.*)$' % + vrid, config, re.M) if match: return dict(description=match.group(1).lstrip()) return dict(description='') @@ -319,21 +343,34 @@ def _parse_bfd_ip(self, config, vrid): return dict(bfd_ip='') def _parse_ip_version(self, config, vrid): - match = re.search(r'^\s+vrrp %s ip version (\d+)$' % - vrid, config, re.M) + if self.version_number >= '4.21.3': + match = re.search(r'^\s+vrrp %s ipv4 version (\d+)$' % + vrid, config, re.M) + else: + match = re.search(r'^\s+vrrp %s ip version (\d+)$' % + vrid, config, re.M) value = int(match.group(1)) if match else None return dict(ip_version=value) def _parse_delay_reload(self, config, vrid): - match = re.search(r'^\s+vrrp %s delay reload (\d+)$' % - vrid, config, re.M) + if self.version_number >= '4.21.3': + match = re.search(r'^\s+vrrp %s timers delay reload (\d+)$' % + vrid, config, re.M) + else: + match = re.search(r'^\s+vrrp %s delay reload (\d+)$' % + vrid, config, re.M) value = int(match.group(1)) if match else None return dict(delay_reload=value) def _parse_track(self, config, vrid): - matches = re.findall(r'^\s+vrrp %s track (\S+) ' - r'(decrement|shutdown)(?:( \d+$|$))' % - vrid, config, re.M) + if self.version_number >= '4.21.3': + matches = re.findall(r'^\s+vrrp %s tracked-object (\S+) ' + r'(decrement|shutdown)(?:( \d+$|$))' % + vrid, config, re.M) + else: + matches = re.findall(r'^\s+vrrp %s track (\S+) ' + r'(decrement|shutdown)(?:( \d+$|$))' % + vrid, config, re.M) value = [] for match in matches: tr_obj = match[0] @@ -441,9 +478,15 @@ def set_enable(self, name, vrid, value=False, run=True): """ if value is False: - cmd = "vrrp %d shutdown" % vrid + if self.version_number >= '4.21.3': + cmd = "vrrp %d disabled" % vrid + else: + cmd = "vrrp %d shutdown" % vrid elif value is True: - cmd = "no vrrp %d shutdown" % vrid + if self.version_number >= '4.21.3': + cmd = "no vrrp %d disabled" % vrid + else: + cmd = "no vrrp %d shutdown" % vrid else: raise ValueError("vrrp property 'enable' must be " "True or False") @@ -484,13 +527,22 @@ def set_primary_ip(self, name, vrid, value=None, disable=False, if default is True: vrrps = self.get(name) primary_ip = vrrps[vrid]['primary_ip'] - cmd = "default vrrp %d ip %s" % (vrid, primary_ip) + if self.version_number >= '4.21.3': + cmd = "default vrrp %d ipv4 %s" % (vrid, primary_ip) + else: + cmd = "default vrrp %d ip %s" % (vrid, primary_ip) elif disable is True or value is None: vrrps = self.get(name) primary_ip = vrrps[vrid]['primary_ip'] - cmd = "no vrrp %d ip %s" % (vrid, primary_ip) + if self.version_number >= '4.21.3': + cmd = "no vrrp %d ipv4 %s" % (vrid, primary_ip) + else: + cmd = "no vrrp %d ip %s" % (vrid, primary_ip) elif re.match(r'^\d+\.\d+\.\d+\.\d+$', str(value)): - cmd = "vrrp %d ip %s" % (vrid, value) + if self.version_number >= '4.21.3': + cmd = "vrrp %d ipv4 %s" % (vrid, value) + else: + cmd = "vrrp %d ip %s" % (vrid, value) else: raise ValueError("vrrp property 'primary_ip' must be " "a properly formatted IP address") @@ -532,9 +584,14 @@ def set_priority(self, name, vrid, value=None, disable=False, if not str(value).isdigit() or value < 1 or value > 254: raise ValueError("vrrp property 'priority' must be " "an integer in the range 1-254") - - cmd = self.command_builder('vrrp %d priority' % vrid, value=value, - default=default, disable=disable) + if self.version_number >= '4.21.3': + cmd = self.command_builder('vrrp %d priority-level' % + vrid, value=value, + default=default, disable=disable) + else: + cmd = self.command_builder('vrrp %d priority' % + vrid, value=value, + default=default, disable=disable) # Run the command if requested if run: @@ -568,9 +625,14 @@ def set_description(self, name, vrid, value=None, disable=False, be passed to the node """ - - cmd = self.command_builder('vrrp %d description' % vrid, value=value, - default=default, disable=disable) + if self.version_number >= '4.21.3': + cmd = self.command_builder('vrrp %d session description' % + vrid, value=value, + default=default, disable=disable) + else: + cmd = self.command_builder('vrrp %d description' % + vrid, value=value, + default=default, disable=disable) # Run the command if requested if run: @@ -608,9 +670,14 @@ def set_ip_version(self, name, vrid, value=None, disable=False, if not default and not disable: if value not in (2, 3): raise ValueError("vrrp property 'ip_version' must be 2 or 3") - - cmd = self.command_builder('vrrp %d ip version' % vrid, value=value, - default=default, disable=disable) + if self.version_number >= '4.21.3': + cmd = self.command_builder('vrrp %d ipv4 version' % + vrid, value=value, + default=default, disable=disable) + else: + cmd = self.command_builder('vrrp %d ip version' % + vrid, value=value, + default=default, disable=disable) # Run the command if requested if run: @@ -675,10 +742,16 @@ def set_secondary_ips(self, name, vrid, secondary_ips, run=True): # Build the commands to add and remove the secondary ip addresses for sec_ip in remove: - cmds.append("no vrrp %d ip %s secondary" % (vrid, sec_ip)) + if self.version_number >= '4.21.3': + cmds.append("no vrrp %d ipv4 %s secondary" % (vrid, sec_ip)) + else: + cmds.append("no vrrp %d ip %s secondary" % (vrid, sec_ip)) for sec_ip in add: - cmds.append("vrrp %d ip %s secondary" % (vrid, sec_ip)) + if self.version_number >= '4.21.3': + cmds.append("vrrp %d ipv4 %s secondary" % (vrid, sec_ip)) + else: + cmds.append("vrrp %d ip %s secondary" % (vrid, sec_ip)) cmds = sorted(cmds) @@ -719,10 +792,16 @@ def set_timers_advertise(self, name, vrid, value=None, disable=False, if not int(value) or int(value) < 1 or int(value) > 255: raise ValueError("vrrp property 'timers_advertise' must be" "in the range 1-255") - - cmd = self.command_builder('vrrp %d timers advertise' % vrid, - value=value, default=default, - disable=disable) + if self.version_number >= '4.21.3': + cmd = self.command_builder('vrrp %d advertisement interval' % + vrid, + value=value, default=default, + disable=disable) + else: + cmd = self.command_builder('vrrp %d timers advertise' % + vrid, + value=value, default=default, + disable=disable) # Run the command if requested if run: @@ -932,9 +1011,14 @@ def set_delay_reload(self, name, vrid, value=None, disable=False, if not int(value) or int(value) < 1 or int(value) > 3600: raise ValueError("vrrp property 'delay_reload' must be" "in the range 0-3600 %r" % value) - - cmd = self.command_builder('vrrp %d delay reload' % vrid, value=value, - default=default, disable=disable) + if self.version_number >= '4.21.3': + cmd = self.command_builder('vrrp %d timers delay reload' % + vrid, value=value, + default=default, disable=disable) + else: + cmd = self.command_builder('vrrp %d delay reload' % + vrid, value=value, + default=default, disable=disable) # Run the command if requested if run: @@ -1068,8 +1152,12 @@ def set_tracks(self, name, vrid, tracks, run=True): if amount == unset: amount = '' - t_cmd = ("no vrrp %d track %s %s %s" - % (vrid, tr_obj, action, amount)) + if self.version_number >= '4.21.3': + t_cmd = ("no vrrp %d tracked-object %s %s %s" + % (vrid, tr_obj, action, amount)) + else: + t_cmd = ("no vrrp %d track %s %s %s" + % (vrid, tr_obj, action, amount)) cmds.append(t_cmd.rstrip()) for track in add: @@ -1080,8 +1168,12 @@ def set_tracks(self, name, vrid, tracks, run=True): if amount == unset: amount = '' - t_cmd = ("vrrp %d track %s %s %s" - % (vrid, tr_obj, action, amount)) + if self.version_number >= '4.21.3': + t_cmd = ("vrrp %d tracked-object %s %s %s" + % (vrid, tr_obj, action, amount)) + else: + t_cmd = ("vrrp %d track %s %s %s" + % (vrid, tr_obj, action, amount)) cmds.append(t_cmd.rstrip()) cmds = sorted(cmds) @@ -1156,7 +1248,7 @@ def _vrrp_set(self, name, vrid, **kwargs): if primary_ip in ('no', None): cmd = self.set_primary_ip(name, vrid, value=None, disable=True, run=False) - elif primary_ip is 'default': + elif primary_ip == 'default': cmd = self.set_primary_ip(name, vrid, value=None, default=True, run=False) else: diff --git a/pyeapi/client.py b/pyeapi/client.py index 52e80d6..ff17b6b 100644 --- a/pyeapi/client.py +++ b/pyeapi/client.py @@ -106,6 +106,7 @@ from pyeapi.utils import load_module, make_iterable, debug from pyeapi.eapilib import HttpEapiConnection, HttpsEapiConnection +from pyeapi.eapilib import HttpsEapiCertConnection from pyeapi.eapilib import SocketEapiConnection, HttpLocalEapiConnection from pyeapi.eapilib import CommandError @@ -115,7 +116,8 @@ 'socket': SocketEapiConnection, 'http_local': HttpLocalEapiConnection, 'http': HttpEapiConnection, - 'https': HttpsEapiConnection + 'https': HttpsEapiConnection, + 'https_certs': HttpsEapiCertConnection } DEFAULT_TRANSPORT = 'https' @@ -327,6 +329,7 @@ def load_config(filename): """ return config.load(filename) + def config_for(name): """ Function to get settings for named config @@ -344,6 +347,7 @@ def config_for(name): """ return config.get_connection(name) + def hosts_for_tag(tag): """ Returns the hosts assocated with the specified tag @@ -361,6 +365,7 @@ def hosts_for_tag(tag): """ return config.tags.get(tag) + def make_connection(transport, **kwargs): """ Creates a connection instance based on the transport @@ -386,7 +391,8 @@ def make_connection(transport, **kwargs): def connect(transport=None, host='localhost', username='admin', - password='', port=None, timeout=60, return_node=False, **kwargs): + password='', port=None, key_file=None, cert_file=None, + ca_file=None, timeout=60, return_node=False, **kwargs): """ Creates a connection using the supplied settings This function will create a connection to an Arista EOS node using @@ -405,6 +411,10 @@ def connect(transport=None, host='localhost', username='admin', port (int): The TCP port of the endpoint for the eAPI connection. If this keyword is not specified, the default value is automatically determined by the transport type. (http=80, https=443) + key_file (str): Path to private key file for ssl validation + cert_file (str): Path to PEM formatted cert file for ssl validation + ca_file (str): Path to CA PEM formatted cert file for ssl validation + timeout (int): timeout return_node (bool): Returns a Node object if True, otherwise returns an EapiConnection object. @@ -415,10 +425,13 @@ def connect(transport=None, host='localhost', username='admin', """ transport = transport or DEFAULT_TRANSPORT connection = make_connection(transport, host=host, username=username, - password=password, port=port, timeout=timeout) + password=password, key_file=key_file, + cert_file=cert_file, ca_file=ca_file, + port=port, timeout=timeout) if return_node: return Node(connection, transport=transport, host=host, - username=username, password=password, port=port, **kwargs) + username=username, password=password, key_file=key_file, + cert_file=cert_file, ca_file=ca_file, port=port, **kwargs) return connection @@ -517,13 +530,13 @@ def _get_version_properties(self): # Parse out version info output = self.enable('show version') self._version = str(output[0]['result']['version']) - match = re.match('[\d.\d]+', output[0]['result']['version']) + match = re.match(r'[\d.\d]+', str(output[0]['result']['version'])) if match: self._version_number = str(match.group(0)) else: self._version_number = str(output[0]['result']['version']) # Parse out model number - match = re.search('\d\d\d\d', output[0]['result']['modelName']) + match = re.search(r'\d\d\d\d', str(output[0]['result']['modelName'])) if match: self._model = str(match.group(0)) else: diff --git a/pyeapi/eapilib.py b/pyeapi/eapilib.py index 26eed35..3e0f679 100644 --- a/pyeapi/eapilib.py +++ b/pyeapi/eapilib.py @@ -58,6 +58,7 @@ DEFAULT_HTTP_PORT = 80 DEFAULT_HTTPS_PORT = 443 DEFAULT_HTTP_LOCAL_PORT = 8080 +DEFAULT_HTTPS_LOCAL_PORT = 8443 DEFAULT_HTTP_PATH = '/command-api' DEFAULT_UNIX_SOCKET = '/var/run/command-api.sock' @@ -207,6 +208,55 @@ def __repr__(self): return 'https://%s:%s/%s' % (self.host, self.port, self.path) +class HTTPSCertConnection(HTTPSConnection): + """ Class to make a HTTPS connection, with support + for full client-based SSL Authentication. + """ + + def __init__(self, path, host, port, key_file, cert_file, ca_file, + timeout=None): + HTTPSConnection.__init__(self, host, key_file=key_file, + cert_file=cert_file) + self.key_file = key_file + self.cert_file = cert_file + self.ca_file = ca_file + self.timeout = timeout + self.path = path + self.port = port + + def __str__(self): + return 'https://%s:%s/%s - %s,%s' % (self.host, self.port, self.path, + self.key_file, self.cert_file) + + def __repr__(self): + return 'https://%s:%s/%s - %s,%s' % (self.host, self.port, self.path, + self.key_file, self.cert_file) + + def connect(self): + """ Connect to a host on a given (SSL) port. + If ca_file is pointing somewhere, use it + to check Server Certificate. + + Redefined/copied and extended from httplib.py:1105 (Python 2.6.x). + This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter + to ssl.wrap_socket(), which forces SSL to check server certificate + against our client certificate. + """ + sock = socket.create_connection((self.host, self.port), self.timeout) + if self._tunnel_host: + self.sock = sock + self._tunnel() + # If there's no CA File, don't force Server Certificate Check + if self.ca_file: + self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, + ca_certs=self.ca_file, + cert_reqs=ssl.CERT_REQUIRED) + else: + self.sock = ssl.wrap_socket(sock, self.key_file, + self.cert_file, + cert_reqs=ssl.CERT_NONE) + + class EapiConnection(object): """Creates a connection to eAPI for sending and receiving eAPI requests @@ -240,20 +290,21 @@ def authentication(self, username, password): the eAPI connection with """ + _auth_text = '{}:{}'.format(username, password) + # Work around for Python 2.7/3.x compatibility if int(sys.version[0]) > 2: # For Python 3.x - _auth_text = '{}:{}'.format(username, password) _auth_bin = base64.encodebytes(_auth_text.encode()) _auth = _auth_bin.decode() _auth = _auth.replace('\n', '') self._auth = _auth else: # For Python 2.7 - _auth = base64.encodestring('{}:{}'.format(username, password)) + _auth = base64.encodestring(_auth_text) self._auth = str(_auth).replace('\n', '') - _LOGGER.debug('Autentication string is: {}'.format(self._auth)) + _LOGGER.debug('Autentication string is: {}:***'.format(username)) def request(self, commands, encoding=None, reqid=None, **kwargs): """Generates an eAPI request object @@ -550,7 +601,7 @@ def __init__(self, host, port=None, path=None, username=None, def disable_certificate_verification(self): # SSL/TLS certificate verification is enabled by default in latest # Python releases and causes self-signed certificates generated - # on EOS to fail validation (unless explicitely imported). + # on EOS to fail validation (unless explicitly imported). # Disable the SSL/TLS certificate verification for now. # Use the approach in PEP476 to disable certificate validation. # TODO: @@ -559,3 +610,20 @@ def disable_certificate_verification(self): # temporary until a proper fix is implemented. if hasattr(ssl, '_create_unverified_context'): return ssl._create_unverified_context() + + +class HttpsEapiCertConnection(EapiConnection): + def __init__(self, host, port=None, path=None, key_file=None, + cert_file=None, ca_file=None, timeout=60, **kwargs): + if key_file is None or cert_file is None: + raise ValueError("For https_cert connections both a key_file and " + "cert_file are required. A ca_file is also " + "recommended") + super(HttpsEapiCertConnection, self).__init__() + port = port or DEFAULT_HTTPS_PORT + path = path or DEFAULT_HTTP_PATH + + self.transport = HTTPSCertConnection(path, host, int(port), + key_file=key_file, + cert_file=cert_file, + ca_file=ca_file, timeout=timeout) diff --git a/pyeapi/utils.py b/pyeapi/utils.py index 4386cad..14d7931 100644 --- a/pyeapi/utils.py +++ b/pyeapi/utils.py @@ -31,7 +31,7 @@ # import os import sys -import imp +import importlib import inspect import logging import logging.handlers @@ -78,28 +78,11 @@ def import_module(name): The module that was imported """ - parts = name.split('.') - path = None - module_name = '' - fhandle = None - - for index, part in enumerate(parts): - module_name = part if index == 0 else '%s.%s' % (module_name, part) - path = [path] if path is not None else path - - try: - fhandle, path, descr = imp.find_module(part, path) - if module_name in sys.modules: - # since imp.load_module works like reload, need to be sure not - # to reload a previously loaded module - mod = sys.modules[module_name] - else: - mod = imp.load_module(module_name, fhandle, path, descr) - finally: - # lets be sure to clean up after ourselves - if fhandle: - fhandle.close() - + if name in sys.modules: + # Be sure not to reload a previously loaded module + mod = sys.modules[name] + else: + mod = importlib.import_module(name) return mod diff --git a/test/fixtures/eapi.conf.yaml b/test/fixtures/eapi.conf.yaml index 9102805..8a5a94e 100644 --- a/test/fixtures/eapi.conf.yaml +++ b/test/fixtures/eapi.conf.yaml @@ -4,3 +4,4 @@ :use_ssl: true :port: 199 :hostname: bogus + diff --git a/test/fixtures/running_config.text b/test/fixtures/running_config.text index 9b0a503..1f609f4 100644 --- a/test/fixtures/running_config.text +++ b/test/fixtures/running_config.text @@ -315,8 +315,8 @@ spanning-tree transmit hold-count 6 spanning-tree max-hops 20 no spanning-tree portfast bpduguard default no spanning-tree portfast bpdufilter default -spanning-tree bridge assurance -no spanning-tree loopguard default +spanning-tree transmit active +no spanning-tree guard loop default no spanning-tree portchannel guard misconfig spanning-tree bpduguard rate-limit default logging event spanning-tree global @@ -348,7 +348,7 @@ no aaa accounting system default no aaa accounting dot1x default no aaa accounting commands all default ! -no enable secret +no enable password no aaa root aaa authentication policy local allow-nopassword-remote-login no aaa authorization policy local default-role diff --git a/test/fixtures/running_config.vrrp b/test/fixtures/running_config.vrrp index d847eff..83b05c4 100644 --- a/test/fixtures/running_config.vrrp +++ b/test/fixtures/running_config.vrrp @@ -531,3 +531,4 @@ interface Vlan50 vrrp 30 ip version 2 ! ! + diff --git a/test/lib/testlib.py b/test/lib/testlib.py index 5ec9bc1..989fb4d 100644 --- a/test/lib/testlib.py +++ b/test/lib/testlib.py @@ -34,7 +34,7 @@ import string import unittest -from mock import Mock +from mock import MagicMock as Mock from pyeapi.client import Node @@ -71,7 +71,7 @@ def __init__(self, *args, **kwargs): def setUp(self): self.node = Node(None) - + self.node._version_number = '4.17.1.1' self.node._running_config = self.config self.mock_config = Mock(name='node.config') diff --git a/test/system/test_api_interfaces.py b/test/system/test_api_interfaces.py index 129106a..5b3ed60 100644 --- a/test/system/test_api_interfaces.py +++ b/test/system/test_api_interfaces.py @@ -226,21 +226,32 @@ def test_set_vrf(self): # Verify set_vrf returns False if no vrf by name is configured result = dut.api('interfaces').set_vrf(intf, 'test') self.assertFalse(result) - dut.config('vrf definition test') + if dut.version_number >= '4.23': + dut.config('vrf instance test') + else: + dut.config('vrf definition test') # Verify interface has vrf applied result = dut.api('interfaces').set_vrf(intf, 'test') self.assertTrue(result) config = dut.run_commands('show running-config interfaces %s' % intf, 'text') - self.assertIn('vrf forwarding test', config[0]['output']) + if dut.version_number >= '4.23': + self.assertIn('vrf test', config[0]['output']) + else: + self.assertIn('vrf forwarding test', config[0]['output']) # Verify interface has vrf removed result = dut.api('interfaces').set_vrf(intf, 'test', disable=True) self.assertTrue(result) config = dut.run_commands('show running-config interfaces %s' % intf, 'text') - self.assertNotIn('vrf forwarding test', config[0]['output']) - # Remove test vrf - dut.config('no vrf definition test') + if dut.version_number >= '4.23': + self.assertIn('vrf test', config[0]['output']) + # Remove test vrf + dut.config('no vrf instance test') + else: + self.assertIn('vrf forwarding test', config[0]['output']) + # Remove test vrf + dut.config('no vrf definition test') class TestPortchannelInterface(DutSystemTest): diff --git a/test/system/test_api_ipinterfaces.py b/test/system/test_api_ipinterfaces.py index acf352a..a26731c 100644 --- a/test/system/test_api_ipinterfaces.py +++ b/test/system/test_api_ipinterfaces.py @@ -31,6 +31,7 @@ # import os import unittest +import time import sys sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) @@ -56,6 +57,7 @@ def test_get_interface_wo_ip_adddress(self): intf = random_interface(dut) dut.config(['default interface %s' % intf, 'interface %s' % intf, 'no switchport']) + time.sleep(2) result = dut.api('ipinterfaces').get(intf) self.assertIsNone(result['address']) diff --git a/test/system/test_api_ntp.py b/test/system/test_api_ntp.py index c33060f..341b567 100644 --- a/test/system/test_api_ntp.py +++ b/test/system/test_api_ntp.py @@ -43,14 +43,20 @@ class TestApiNtp(DutSystemTest): def test_get(self): for dut in self.duts: - dut.config(['ntp source Ethernet1', 'ntp server 99.99.1.1']) + if dut.version_number >= '4.23': + dut.config(['ntp local-interface Ethernet1', 'ntp server 99.99.1.1']) + else: + dut.config(['ntp source Ethernet1', 'ntp server 99.99.1.1']) response = dut.api('ntp').get() self.assertIsNotNone(response) def test_create(self): intf = 'Ethernet1' for dut in self.duts: - dut.config(['no ntp source']) + if dut.version_number >= '4.23': + dut.config(['no ntp local-interface']) + else: + dut.config(['no ntp source']) response = dut.api('ntp').create(intf) self.assertTrue(response) response = dut.api('ntp').get() @@ -58,7 +64,10 @@ def test_create(self): def test_delete(self): for dut in self.duts: - dut.config(['ntp source Ethernet1']) + if dut.version_number >= '4.23': + dut.config(['ntp local-interface Ethernet1']) + else: + dut.config(['ntp source Ethernet1']) response = dut.api('ntp').delete() self.assertTrue(response) response = dut.api('ntp').get() @@ -66,7 +75,10 @@ def test_delete(self): def test_default(self): for dut in self.duts: - dut.config(['ntp source Ethernet1']) + if dut.version_number >= '4.23': + dut.config(['ntp local-interface Ethernet1']) + else: + dut.config(['ntp source Ethernet1']) response = dut.api('ntp').default() self.assertTrue(response) response = dut.api('ntp').get() @@ -75,7 +87,10 @@ def test_default(self): def test_set_source_interface(self): intf = 'Ethernet1' for dut in self.duts: - dut.config(['ntp source Loopback0']) + if dut.version_number >= '4.23': + dut.config(['ntp local-interface Loopback0']) + else: + dut.config(['ntp source Loopback0']) response = dut.api('ntp').set_source_interface(intf) self.assertTrue(response) response = dut.api('ntp').get() @@ -84,7 +99,10 @@ def test_set_source_interface(self): def test_add_server_single(self): server = '10.10.10.35' for dut in self.duts: - dut.config(['ntp source Ethernet1', 'no ntp']) + if dut.version_number >= '4.23': + dut.config(['ntp local-interface Ethernet1', 'no ntp']) + else: + dut.config(['ntp source Ethernet1', 'no ntp']) response = dut.api('ntp').add_server(server) self.assertTrue(response) response = dut.api('ntp').get() @@ -95,7 +113,10 @@ def test_add_server_single(self): def test_add_server_multiple(self): servers = ['10.10.10.37', '10.10.10.36', '10.10.10.34'] for dut in self.duts: - dut.config(['ntp source Ethernet1', 'no ntp']) + if dut.version_number >= '4.23': + dut.config(['ntp local-interface Ethernet1', 'no ntp']) + else: + dut.config(['ntp source Ethernet1', 'no ntp']) for server in servers: response = dut.api('ntp').add_server(server) self.assertTrue(response) @@ -107,7 +128,10 @@ def test_add_server_multiple(self): def test_add_server_prefer(self): server = '10.10.10.35' for dut in self.duts: - dut.config(['ntp source Ethernet1', 'no ntp']) + if dut.version_number >= '4.23': + dut.config(['ntp local-interface Ethernet1', 'no ntp']) + else: + dut.config(['ntp source Ethernet1', 'no ntp']) response = dut.api('ntp').add_server(server, prefer=False) self.assertTrue(response) response = dut.api('ntp').get() @@ -120,7 +144,10 @@ def test_add_server_prefer(self): def test_add_server_invalid(self): for dut in self.duts: - dut.config(['ntp source Ethernet1', 'no ntp']) + if dut.version_number >= '4.23': + dut.config(['ntp local-interface Ethernet1', 'no ntp']) + else: + dut.config(['ntp source Ethernet1', 'no ntp']) with self.assertRaises(ValueError): dut.api('ntp').add_server(None) dut.api('ntp').add_server('') @@ -130,8 +157,12 @@ def test_remove_server(self): server = '10.10.10.35' servers = ['10.10.10.37', '10.10.10.36', '10.10.10.34'] for dut in self.duts: - dut.config(['ntp source Ethernet1', 'no ntp', - 'ntp server %s' % server]) + if dut.version_number >= '4.23': + dut.config(['ntp local-interface Ethernet1', 'no ntp', + 'ntp server %s' % server]) + else: + dut.config(['ntp source Ethernet1', 'no ntp', + 'ntp server %s' % server]) for addserver in servers: dut.config(['ntp server %s' % addserver]) response = dut.api('ntp').remove_server(server) @@ -144,7 +175,10 @@ def test_remove_server(self): def test_remove_all_servers(self): servers = ['10.10.10.37', '10.10.10.36', '10.10.10.34'] for dut in self.duts: - dut.config(['ntp source Ethernet1', 'no ntp']) + if dut.version_number >= '4.23': + dut.config(['ntp local-interface Ethernet1', 'no ntp']) + else: + dut.config(['ntp source Ethernet1', 'no ntp']) for addserver in servers: dut.config(['ntp server %s' % addserver]) response = dut.api('ntp').remove_all_servers() diff --git a/test/system/test_api_staticroute.py b/test/system/test_api_staticroute.py index 1082812..2b4395f 100644 --- a/test/system/test_api_staticroute.py +++ b/test/system/test_api_staticroute.py @@ -52,14 +52,14 @@ def _ip_addr(): def _next_hop(): next_hop = choice(NEXT_HOPS) - if next_hop is 'Null0': + if next_hop == 'Null0': return (next_hop, None) ip1 = random_int(0, 223) ip2 = random_int(0, 255) ip3 = random_int(0, 255) ip4 = random_int(0, 255) ip_addr = "%s.%s.%s.%s" % (ip1, ip2, ip3, ip4) - if next_hop is 'IP': + if next_hop == 'IP': return (ip_addr, None) return (next_hop, ip_addr) diff --git a/test/system/test_api_system.py b/test/system/test_api_system.py index c281079..b58a020 100644 --- a/test/system/test_api_system.py +++ b/test/system/test_api_system.py @@ -73,7 +73,7 @@ def test_get_check_banners(self): def test_get_banner_with_EOF(self): for dut in self.duts: - motd_banner_value = '!!!newlinebaner\n\nSecondLIneEOF!!!newlinebanner\n' + motd_banner_value = '!!!newlinebaner\nSecondLIneEOF!!!newlinebanner\n' dut.config([dict(cmd="banner motd", input=motd_banner_value)]) resp = dut.api('system').get() self.assertEqual(resp['banner_motd'], motd_banner_value.rstrip()) @@ -160,7 +160,7 @@ def test_set_banner_motd(self): def test_set_banner_motd_donkey(self): for dut in self.duts: - donkey_chicken = """ + donkey_chicken = r""" /\ /\ ( \\ // ) \ \\ // / @@ -187,8 +187,6 @@ def test_set_banner_motd_donkey(self): self.assertTrue(resp, 'dut=%s' % dut) self.assertIn(donkey_chicken, dut.running_config) - - def test_set_banner_motd_default(self): for dut in self.duts: dut.config([dict(cmd="banner motd", diff --git a/test/system/test_api_users.py b/test/system/test_api_users.py index 5ee6e57..ae54087 100644 --- a/test/system/test_api_users.py +++ b/test/system/test_api_users.py @@ -49,14 +49,21 @@ class TestApiUsers(DutSystemTest): def test_get(self): for dut in self.duts: - dut.config(['no username test', 'username test nopassword', - 'username test sshkey %s' % TEST_SSH_KEY]) + if dut.version_number >= '4.23': + dut.config(['no username test', 'username test nopassword', + 'username test ssh-key %s' % TEST_SSH_KEY]) + else: + dut.config(['no username test', 'username test nopassword', + 'username test sshkey %s' % TEST_SSH_KEY]) result = dut.api('users').get('test') - values = dict(nopassword=True, privilege='1', secret='', - role='', format='', - sshkey=TEST_SSH_KEY) - + if dut.version_number >= '4.23': + values = dict(nopassword=True, privilege='1', secret='', + role='', format='') + values["ssh-key"] = TEST_SSH_KEY + else: + values = dict(nopassword=True, privilege='1', secret='', + role='', format='', sshkey=TEST_SSH_KEY) result = self.sort_dict_by_keys(result) values = self.sort_dict_by_keys(values) @@ -139,32 +146,52 @@ def test_set_sshkey_with_value(self): dut.config(['no username test', 'username test nopassword']) api = dut.api('users') self.assertIn('username test privilege 1 nopassword', api.config) - self.assertNotIn('username test sshkey', api.config) + if dut.version_number >= '4.23': + self.assertNotIn('username test ssh-key', api.config) + else: + self.assertNotIn('username test sshkey', api.config) result = api.set_sshkey('test', TEST_SSH_KEY) self.assertTrue(result) - self.assertIn('username test sshkey %s' % TEST_SSH_KEY, api.config) + if dut.version_number >= '4.23': + self.assertIn('username test ssh-key %s' % TEST_SSH_KEY, api.config) + else: + self.assertIn('username test sshkey %s' % TEST_SSH_KEY, api.config) def test_set_sshkey_with_empty_string(self): for dut in self.duts: dut.config(['no username test', 'username test nopassword']) api = dut.api('users') self.assertIn('username test privilege 1 nopassword', api.config) - self.assertNotIn('username test sshkey', api.config) + if dut.version_number >= '4.23': + self.assertNotIn('username test ssh-key', api.config) + else: + self.assertNotIn('username test sshkey', api.config) result = api.set_sshkey('test', '') self.assertTrue(result) - self.assertNotIn('username test sshkey %s' - % TEST_SSH_KEY, api.config) + if dut.version_number >= '4.23': + self.assertNotIn('username test ssh-key %s' + % TEST_SSH_KEY, api.config) + else: + self.assertNotIn('username test sshkey %s' + % TEST_SSH_KEY, api.config) def test_set_sshkey_with_None(self): for dut in self.duts: dut.config(['no username test', 'username test nopassword']) api = dut.api('users') self.assertIn('username test privilege 1 nopassword', api.config) - self.assertNotIn('username test sshkey', api.config) + if dut.version_number >= '4.23': + self.assertNotIn('username test ssh-key', api.config) + else: + self.assertNotIn('username test sshkey', api.config) result = api.set_sshkey('test', None) self.assertTrue(result) - self.assertNotIn('username test sshkey %s' - % TEST_SSH_KEY, api.config) + if dut.version_number >= '4.23': + self.assertNotIn('username test ssh-key %s' + % TEST_SSH_KEY, api.config) + else: + self.assertNotIn('username test sshkey %s' + % TEST_SSH_KEY, api.config) def test_set_sshkey_with_no_value(self): for dut in self.duts: @@ -174,8 +201,12 @@ def test_set_sshkey_with_no_value(self): self.assertIn('username test privilege 1 nopassword', api.config) result = api.set_sshkey('test', disable=True) self.assertTrue(result) - self.assertNotIn('username test sshkey %s' % TEST_SSH_KEY, - api.config) + if dut.version_number >= '4.23': + self.assertNotIn('username test ssh-key %s' % TEST_SSH_KEY, + api.config) + else: + self.assertNotIn('username test sshkey %s' % TEST_SSH_KEY, + api.config) if __name__ == '__main__': diff --git a/test/system/test_api_vrfs.py b/test/system/test_api_vrfs.py index df2d7b8..6d888fb 100644 --- a/test/system/test_api_vrfs.py +++ b/test/system/test_api_vrfs.py @@ -43,45 +43,75 @@ class TestApiVrfs(DutSystemTest): def test_get(self): for dut in self.duts: - dut.config(['no vrf definition blah', 'vrf definition blah', - 'rd 10:10', 'description blah desc']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah', 'vrf instance blah', + 'rd 10:10', 'description blah desc']) + else: + dut.config(['no vrf definition blah', 'vrf definition blah', + 'rd 10:10', 'description blah desc']) response = dut.api('vrfs').get('blah') values = dict(rd='10:10', vrf_name='blah', description='blah desc', ipv4_routing=False, ipv6_routing=False) self.assertEqual(values, response) - dut.config(['no vrf definition blah']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah']) + else: + dut.config(['no vrf definition blah']) def test_getall(self): for dut in self.duts: - dut.config(['no vrf definition blah', 'vrf definition blah', - 'no vrf definition second', 'vrf definition second']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah', 'vrf instance blah', + 'no vrf instance second', 'vrf instance second']) + else: + dut.config(['no vrf definition blah', 'vrf definition blah', + 'no vrf definition second', 'vrf definition second']) response = dut.api('vrfs').getall() self.assertIsInstance(response, dict, 'dut=%s' % dut) self.assertEqual(len(response), 2) for vrf_name in ['blah', 'second']: self.assertIn(vrf_name, response, 'dut=%s' % dut) - dut.config(['no vrf definition blah', 'no vrf definition second']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah', 'no vrf instance second']) + else: + dut.config(['no vrf definition blah', 'no vrf definition second']) def test_create_and_return_true(self): for dut in self.duts: - dut.config(['no vrf definition blah', 'vrf definition blah']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah', 'vrf instance blah']) + else: + dut.config(['no vrf definition blah', 'vrf definition blah']) result = dut.api('vrfs').create('blah') self.assertTrue(result, 'dut=%s' % dut) config = dut.run_commands('show vrf', encoding='text') self.assertIn('blah', config[0]['output'], 'dut=%s' % dut) - dut.config(['no vrf definition blah']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah']) + else: + dut.config(['no vrf definition blah']) def test_create_with_valid_rd(self): for dut in self.duts: - dut.config(['no vrf definition blah', 'vrf definition blah']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah', 'vrf instance blah']) + else: + dut.config(['no vrf definition blah', 'vrf definition blah']) result = dut.api('vrfs').create('blah', rd='10:10') self.assertTrue(result, 'dut=%s' % dut) command = 'show running-config section vrf' config = dut.run_commands(command, encoding='text') - self.assertIn('vrf definition blah', config[0]['output'], - 'dut=%s' % dut) + if dut.version_number >= '4.23': + self.assertIn('vrf instance blah', config[0]['output'], + 'dut=%s' % dut) + else: + self.assertIn('vrf definition blah', config[0]['output'], + 'dut=%s' % dut) self.assertIn('rd 10:10', config[0]['output'], 'dut=%s' % dut) - dut.config(['no vrf definition blah']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah']) + else: + dut.config(['no vrf definition blah']) def test_create_and_return_false(self): for dut in self.duts: @@ -90,26 +120,43 @@ def test_create_and_return_false(self): def test_create_with_invalid_rd(self): for dut in self.duts: - dut.config(['no vrf definition blah', 'vrf definition blah']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah', 'vrf instance blah']) + else: + dut.config(['no vrf definition blah', 'vrf definition blah']) result = dut.api('vrfs').create('blah', rd='192.168.1.1:99999999') self.assertFalse(result, 'dut=%s' % dut) command = 'show running-config section vrf' config = dut.run_commands(command, encoding='text') - self.assertIn('vrf definition blah', config[0]['output'], - 'dut=%s' % dut) + if dut.version_number >= '4.23': + self.assertIn('vrf instance blah', config[0]['output'], + 'dut=%s' % dut) + else: + self.assertIn('vrf definition blah', config[0]['output'], + 'dut=%s' % dut) self.assertNotIn('rd', config[0]['output'], 'dut=%s' % dut) - dut.config(['no vrf definition blah']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah']) + else: + dut.config(['no vrf definition blah']) def test_delete_and_return_true(self): for dut in self.duts: - dut.config('vrf definition blah') + if dut.version_number >= '4.23': + dut.config('vrf instance blah') + else: + dut.config('vrf definition blah') result = dut.api('vrfs').delete('blah') self.assertTrue(result, 'dut=%s' % dut) command = 'show running-config section vrf' config = dut.run_commands(command, encoding='text') - self.assertNotIn('vrf definition blah', config[0]['output'], - 'dut=%s' % dut) + if dut.version_number >= '4.23': + self.assertNotIn('vrf instance blah', config[0]['output'], + 'dut=%s' % dut) + else: + self.assertNotIn('vrf definition blah', config[0]['output'], + 'dut=%s' % dut) def test_delete_and_return_false(self): for dut in self.duts: @@ -118,31 +165,49 @@ def test_delete_and_return_false(self): def test_default(self): for dut in self.duts: - dut.config(['no vrf definition blah', 'vrf definition blah', - 'description test desc']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah', 'vrf instance blah', + 'description test desc']) + else: + dut.config(['no vrf definition blah', 'vrf definition blah', + 'description test desc']) result = dut.api('vrfs').default('blah') self.assertTrue(result, 'dut=%s' % dut) command = 'show running-config section vrf' config = dut.run_commands(command, encoding='text') - self.assertNotIn('vrf definition blah', config[0]['output'], - 'dut=%s' % dut) - dut.config(['no vrf definition blah']) + if dut.version_number >= '4.23': + self.assertNotIn('vrf instance blah', config[0]['output'], + 'dut=%s' % dut) + dut.config(['no vrf instance blah']) + else: + self.assertNotIn('vrf definition blah', config[0]['output'], + 'dut=%s' % dut) + dut.config(['no vrf definition blah']) def test_set_rd(self): for dut in self.duts: - dut.config(['no vrf definition blah', 'vrf definition blah']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah', 'vrf instance blah']) + else: + dut.config(['no vrf definition blah', 'vrf definition blah']) result = dut.api('vrfs').set_rd('blah', '10:10') self.assertTrue(result, 'dut=%s' % dut) command = 'show running-config section vrf' config = dut.run_commands(command, encoding='text') self.assertIn('blah', config[0]['output'], 'dut=%s' % dut) self.assertIn('10:10', config[0]['output'], 'dut=%s' % dut) - dut.config(['no vrf definition blah']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah']) + else: + dut.config(['no vrf definition blah']) def test_set_description(self): for dut in self.duts: description = random_string() - dut.config(['no vrf definition blah', 'vrf definition blah']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah', 'vrf instance blah']) + else: + dut.config(['no vrf definition blah', 'vrf definition blah']) result = dut.api('vrfs').set_description('blah', description) self.assertTrue(result, 'dut=%s' % dut) command = 'show running-config section vrf' @@ -154,12 +219,19 @@ def test_set_description(self): config = dut.run_commands(command, encoding='text') self.assertNotIn('description %s' % description, config[0]['output'], 'dut=%s' % dut) - dut.config(['no vrf definition blah']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah']) + else: + dut.config(['no vrf definition blah']) def test_set_ipv4_routing(self): for dut in self.duts: - dut.config(['no vrf definition blah', 'vrf definition blah', - 'rd 10:10', 'description test']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah', 'vrf instance blah', + 'rd 10:10', 'description test']) + else: + dut.config(['no vrf definition blah', 'vrf definition blah', + 'rd 10:10', 'description test']) result = dut.api('vrfs').set_ipv4_routing('blah') self.assertTrue(result, 'dut=%s' % dut) command = 'show running-config section vrf' @@ -171,12 +243,19 @@ def test_set_ipv4_routing(self): config = dut.run_commands(command, encoding='text') self.assertIn('no ip routing vrf blah', config[0]['output'], 'dut=%s' % dut) - dut.config(['no vrf definition blah']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah']) + else: + dut.config(['no vrf definition blah']) def test_set_ipv6_routing(self): for dut in self.duts: - dut.config(['no vrf definition blah', 'vrf definition blah', - 'rd 10:10', 'description test']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah', 'vrf instance blah', + 'rd 10:10', 'description test']) + else: + dut.config(['no vrf definition blah', 'vrf definition blah', + 'rd 10:10', 'description test']) result = dut.api('vrfs').set_ipv6_routing('blah') self.assertTrue(result, 'dut=%s' % dut) command = 'show running-config all section vrf' @@ -188,27 +267,45 @@ def test_set_ipv6_routing(self): config = dut.run_commands(command, encoding='text') self.assertIn('no ipv6 unicast-routing vrf blah', config[0]['output'], 'dut=%s' % dut) - dut.config(['no vrf definition blah']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah']) + else: + dut.config(['no vrf definition blah']) def test_set_interface(self): for dut in self.duts: - dut.config(['no vrf definition blah', 'vrf definition blah', - 'rd 10:10', 'default interface Ethernet1', - 'interface Ethernet1', 'no switchport']) + if dut.version_number >= '4.23': + dut.config(['no vrf instance blah', 'vrf instance blah', + 'rd 10:10', 'default interface Ethernet1', + 'interface Ethernet1', 'no switchport']) + else: + dut.config(['no vrf definition blah', 'vrf definition blah', + 'rd 10:10', 'default interface Ethernet1', + 'interface Ethernet1', 'no switchport']) result = dut.api('vrfs').set_interface('blah', 'Ethernet1') self.assertTrue(result, 'dut=%s' % dut) command = 'show running-config interfaces Ethernet1' config = dut.run_commands(command, encoding='text') - self.assertIn('vrf forwarding blah', config[0]['output'], - 'dut=%s' % dut) + if dut.version_number >= '4.23': + self.assertIn('vrf blah', config[0]['output'], + 'dut=%s' % dut) + else: + self.assertIn('vrf forwarding blah', config[0]['output'], + 'dut=%s' % dut) result = dut.api('vrfs').set_interface('blah', 'Ethernet1', disable=True) self.assertTrue(result, 'dut=%s' % dut) config = dut.run_commands(command, encoding='text') - self.assertNotIn('vrf forwarding blah', config[0]['output'], - 'dut=%s' % dut) - dut.config(['no vrf definition blah', - 'default interface Ethernet1']) + if dut.version_number >= '4.23': + self.assertNotIn('vrf blah', config[0]['output'], + 'dut=%s' % dut) + dut.config(['no vrf instance blah', + 'default interface Ethernet1']) + else: + self.assertNotIn('vrf forwarding blah', config[0]['output'], + 'dut=%s' % dut) + dut.config(['no vrf definition blah', + 'default interface Ethernet1']) if __name__ == '__main__': diff --git a/test/system/test_api_vrrp.py b/test/system/test_api_vrrp.py index fd1ee10..9a20060 100644 --- a/test/system/test_api_vrrp.py +++ b/test/system/test_api_vrrp.py @@ -73,9 +73,14 @@ def test_get(self): vrid = 98 for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'vrrp %d shutdown' % vrid, + 'exit']) response = dut.api('vrrp').get(interface) self.assertIsNotNone(response) @@ -85,13 +90,22 @@ def test_getall(self): vrid2 = 198 for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'vrrp %d shutdown' % vrid, - 'exit', - 'interface vlan $', - 'vrrp %d shutdown' % vrid, - 'vrrp %d shutdown' % vrid2, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'vrrp %d disabled' % vrid, + 'exit', + 'interface vlan $', + 'vrrp %d disabled' % vrid, + 'vrrp %d disabled' % vrid2, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'vrrp %d shutdown' % vrid, + 'exit', + 'interface vlan $', + 'vrrp %d shutdown' % vrid, + 'vrrp %d shutdown' % vrid2, + 'exit']) response = dut.api('vrrp').getall() self.assertIsNotNone(response) @@ -122,10 +136,16 @@ def test_delete(self): vrid = 101 for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) response = dut.api('vrrp').delete(interface, vrid) self.assertIs(response, True) @@ -134,10 +154,16 @@ def test_default(self): vrid = 102 for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) response = dut.api('vrrp').delete(interface, vrid) self.assertIs(response, True) @@ -198,11 +224,18 @@ def test_set_enable(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'vrrp %d ip 10.10.10.2' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'vrrp %d ipv4 10.10.10.2' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'vrrp %d ip 10.10.10.2' % vrid, + 'exit']) for enable in enable_cases: response = dut.api('vrrp').set_enable( @@ -220,10 +253,16 @@ def test_set_primary_ip(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) for p_ip in primary_ip_cases: response = dut.api('vrrp').set_primary_ip( @@ -241,10 +280,16 @@ def test_set_priority(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) for priority in priority_cases: response = dut.api('vrrp').set_priority( @@ -262,10 +307,16 @@ def test_set_description(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) for description in desc_cases: response = dut.api('vrrp').set_description( @@ -281,10 +332,16 @@ def test_set_secondary_ips(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) for s_ip_list in secondary_ip_cases: response = dut.api('vrrp').set_secondary_ips( @@ -303,10 +360,16 @@ def test_set_ip_version(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) for ip_version in ip_version_cases: response = dut.api('vrrp').set_ip_version( @@ -324,10 +387,16 @@ def test_set_timers_advertise(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) for timers_advertise in timers_adv_cases: response = dut.api('vrrp').set_timers_advertise( @@ -345,10 +414,16 @@ def test_set_mac_addr_adv_interval(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) for mac_addr_adv_intvl in mac_addr_adv_int_cases: response = dut.api('vrrp').set_mac_addr_adv_interval( @@ -367,10 +442,16 @@ def test_set_preempt(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) for preempt in preempt_cases: response = dut.api('vrrp').set_preempt( @@ -388,10 +469,16 @@ def test_set_preempt_delay_min(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) for preempt_delay_min in preempt_delay_min_cases: response = dut.api('vrrp').set_preempt_delay_min( @@ -409,10 +496,16 @@ def test_set_preempt_delay_reload(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) for preempt_delay_reload in preempt_delay_reload_cases: response = dut.api('vrrp').set_preempt_delay_reload( @@ -430,10 +523,16 @@ def test_set_delay_reload(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) for delay_reload in delay_reload_cases: response = dut.api('vrrp').set_delay_reload( @@ -463,10 +562,16 @@ def test_set_tracks(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) for track_list in track_cases: response = dut.api('vrrp').set_tracks( @@ -484,10 +589,16 @@ def test_set_bfd_ip(self): ] for dut in self.duts: interface = self._vlan_setup(dut) - dut.config(['interface %s' % interface, - 'no vrrp %d' % vrid, - 'vrrp %d shutdown' % vrid, - 'exit']) + if dut.version_number >= '4.23': + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d disabled' % vrid, + 'exit']) + else: + dut.config(['interface %s' % interface, + 'no vrrp %d' % vrid, + 'vrrp %d shutdown' % vrid, + 'exit']) for bfd_ip in bfd_ip_cases: response = dut.api('vrrp').set_bfd_ip( diff --git a/test/system/test_client.py b/test/system/test_client.py index 200ef59..c36c418 100644 --- a/test/system/test_client.py +++ b/test/system/test_client.py @@ -29,6 +29,7 @@ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # + import os import unittest @@ -57,7 +58,10 @@ def setUp(self): if dut._enablepwd is not None: # If enable password defined for dut, set the # enable password on the dut and clear it on tearDown - dut.config("enable secret %s" % dut._enablepwd) + if dut.version_number >= '4.23': + dut.config("enable password %s" % dut._enablepwd) + else: + dut.config("enable secret %s" % dut._enablepwd) def test_unauthorized_user(self): error_string = ('Unauthorized. Unable to authenticate user: Bad' @@ -249,7 +253,10 @@ def _dut_eos_version(self, dut): def tearDown(self): for dut in self.duts: - dut.config("no enable secret") + if dut.version_number >= '4.23': + dut.config("no enable password") + else: + dut.config("no enable secret") class TestNode(unittest.TestCase): @@ -274,11 +281,11 @@ def test_exception_trace(self): # Send an incomplete command cases.append(('show run', rfmt % (1002, 'invalid command', - 'incomplete token \(at token \d+: \'.*\'\)'))) + r'incomplete token \(at token \d+: \'.*\'\)'))) # Send a mangled command cases.append(('shwo version', rfmt % (1002, 'invalid command', - 'Invalid input \(at token \d+: \'.*\'\)'))) + r'Invalid input \(at token \d+: \'.*\'\)'))) # Send a command that cannot be run through the api # note the command for reload looks to change in new EOS # in 4.15 the reload now is replaced with 'force' if you are @@ -288,15 +295,10 @@ def test_exception_trace(self): cases.append(('reload', rfmt % (1004, 'incompatible command', 'Command not permitted via API access..*'))) - # Send a continuous command that requires a break - cases.append(('watch 10 show int e1 count rates', rfmt - % (1000, 'could not run command', - 'init error.*'))) # Send a command that has insufficient priv cases.append(('show running-config', rfmt % (1002, 'invalid command', - 'Invalid input \(privileged mode required\)'))) - + r'Invalid input \(privileged mode required\)'))) for dut in self.duts: for (cmd, regex) in cases: diff --git a/test/unit/test_api_vrrp.py b/test/unit/test_api_vrrp.py index 15c8f15..0d653ec 100644 --- a/test/unit/test_api_vrrp.py +++ b/test/unit/test_api_vrrp.py @@ -419,7 +419,6 @@ def test_set_description(self): def test_set_ip_version(self): # vrrp 10 ip version 2 - # Test set_description gives properly formatted commands cases = [ (2, None, None, 'vrrp %d ip version 2' % upd_vrid), diff --git a/test/unit/test_client.py b/test/unit/test_client.py index 1cf3977..b5c6ecc 100644 --- a/test/unit/test_client.py +++ b/test/unit/test_client.py @@ -310,7 +310,8 @@ def test_hosts_for_tag_returns_names(self): def test_connect_types(self, connection): transports = list(pyeapi.client.TRANSPORTS.keys()) kwargs = dict(host='localhost', username='admin', password='', - port=None, timeout=60) + port=None, key_file=None, cert_file=None, + ca_file=None, timeout=60) for transport in transports: pyeapi.client.connect(transport) @@ -408,7 +409,8 @@ def test_connect_default_type(self): with patch.dict(pyeapi.client.TRANSPORTS, {'https': transport}): pyeapi.client.connect() kwargs = dict(host='localhost', username='admin', password='', - port=None, timeout=60) + port=None, key_file=None, cert_file=None, + ca_file=None, timeout=60) transport.assert_called_once_with(**kwargs) def test_connect_return_node(self): @@ -420,7 +422,8 @@ def test_connect_return_node(self): password='password', port=None, timeout=60, return_node=True) kwargs = dict(host='192.168.1.16', username='eapi', - password='password', port=None, timeout=60) + password='password', port=None, key_file=None, + cert_file=None, ca_file=None, timeout=60) transport.assert_called_once_with(**kwargs) self.assertIsNone(node._enablepwd) @@ -434,7 +437,8 @@ def test_connect_return_node_enablepwd(self): timeout=60, enablepwd='enablepwd', return_node=True) kwargs = dict(host='192.168.1.16', username='eapi', - password='password', port=None, timeout=60) + password='password', port=None, key_file=None, + cert_file=None, ca_file=None, timeout=60) transport.assert_called_once_with(**kwargs) self.assertEqual(node._enablepwd, 'enablepwd') @@ -445,7 +449,8 @@ def test_connect_to_with_config(self): pyeapi.client.load_config(filename=conf) node = pyeapi.client.connect_to('test1') kwargs = dict(host='192.168.1.16', username='eapi', - password='password', port=None, timeout=60) + password='password', port=None, key_file=None, + cert_file=None, ca_file=None, timeout=60) transport.assert_called_once_with(**kwargs) self.assertEqual(node._enablepwd, 'enablepwd') diff --git a/test/unit/test_utils.py b/test/unit/test_utils.py index b04de5e..46d2d01 100644 --- a/test/unit/test_utils.py +++ b/test/unit/test_utils.py @@ -5,6 +5,7 @@ import pyeapi.utils + class TestUtils(unittest.TestCase): @patch('pyeapi.utils.import_module') @@ -23,17 +24,17 @@ def test_load_module_raises_import_error(self, mock_import_module): def test_make_iterable_from_string(self): result = pyeapi.utils.make_iterable('test') self.assertIsInstance(result, collections.Iterable) - self.assertEquals(len(result), 1) + self.assertEqual(len(result), 1) def test_make_iterable_from_unicode(self): result = pyeapi.utils.make_iterable(u'test') self.assertIsInstance(result, collections.Iterable) - self.assertEquals(len(result), 1) + self.assertEqual(len(result), 1) def test_make_iterable_from_iterable(self): result = pyeapi.utils.make_iterable(['test']) self.assertIsInstance(result, collections.Iterable) - self.assertEquals(len(result), 1) + self.assertEqual(len(result), 1) def test_make_iterable_raises_type_error(self): with self.assertRaises(TypeError):