From 69718554bba8c844e72147df0bb0fe11fd11baae Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Mon, 8 Jun 2020 09:34:46 +0000 Subject: [PATCH 1/3] Add support of running general Ansible modules on EosHost * Add support of running general Ansible modules on EosHost The EOS hosts support two types of command line interface: 1. Linux shell 2. EOS shell This change is to add the support of running general Ansible modules that can only be executed under Linux shell. * If not able to get the explicitly defined username and password, fallback to use the value of "fanout_admin_user" and "fanout_admin_password". It is unlikely to break the existing code. Signed-off-by: Xin Wang --- ansible/group_vars/all/creds.yml | 1 + tests/common/devices.py | 90 +++++++++++++++++++++----------- tests/conftest.py | 31 +++++++---- 3 files changed, 81 insertions(+), 41 deletions(-) diff --git a/ansible/group_vars/all/creds.yml b/ansible/group_vars/all/creds.yml index 1709aca5745..fdf69d1097c 100644 --- a/ansible/group_vars/all/creds.yml +++ b/ansible/group_vars/all/creds.yml @@ -2,6 +2,7 @@ eos_default_login: "admin" eos_default_password: "" eos_login: admin eos_password: 123456 +eos_root_user: root eos_root_password: 123456 sonic_login: "admin" diff --git a/tests/common/devices.py b/tests/common/devices.py index f92697f4b3b..410c370a1c4 100644 --- a/tests/common/devices.py +++ b/tests/common/devices.py @@ -567,6 +567,7 @@ def get_version(self): output = dut.command("sonic-cfggen -y /etc/sonic/sonic_version.yml -v build_version") return output["stdout_lines"][0].strip() + class EosHost(AnsibleHostBase): """ @summary: Class for Eos switch @@ -574,54 +575,83 @@ class EosHost(AnsibleHostBase): For running ansible module on the Eos switch """ - def __init__(self, ansible_adhoc, hostname, user, passwd, gather_facts=False): - AnsibleHostBase.__init__(self, ansible_adhoc, hostname, connection="network_cli") - evars = { 'ansible_connection':'network_cli', \ - 'ansible_network_os':'eos', \ - 'ansible_user': user, \ - 'ansible_password': passwd, \ - 'ansible_ssh_user': user, \ - 'ansible_ssh_pass': passwd, \ - 'ansible_become_method': 'enable' } - self.host.options['variable_manager'].extra_vars.update(evars) + def __init__(self, ansible_adhoc, hostname, eos_user, eos_passwd, shell_user=None, shell_passwd=None, gather_facts=False): + '''Initialize an object for interacting with EoS type device using ansible modules + + Args: + ansible_adhoc (): The pytest-ansible fixture + hostname (string): hostname of the EOS device + eos_user (string): Username for accessing the EOS CLI interface + eos_passwd (string): Password for the eos_user + shell_user (string, optional): Username for accessing the Linux shell CLI interface. Defaults to None. + shell_passwd (string, optional): Password for the shell_user. Defaults to None. + gather_facts (bool, optional): Whether to gather some basic facts. Defaults to False. + ''' + self.eos_user = eos_user + self.eos_passwd = eos_passwd + self.shell_user = shell_user + self.shell_passwd = shell_passwd + AnsibleHostBase.__init__(self, ansible_adhoc, hostname) self.localhost = ansible_adhoc(inventory='localhost', connection='local', host_pattern="localhost")["localhost"] + def __getattr__(self, item): + if item.startswith('eos_'): + evars = { + 'ansible_connection':'network_cli', + 'ansible_network_os':'eos', + 'ansible_user': self.eos_user, + 'ansible_password': self.eos_passwd, + 'ansible_ssh_user': self.eos_user, + 'ansible_ssh_pass': self.eos_passwd, + 'ansible_become_method': 'enable' + } + else: + if not self.shell_user or not self.shell_passwd: + raise Exception("Please specify shell_user and shell_passwd for {}".format(self.hostname)) + evars = { + 'ansible_connection':'ssh', + 'ansible_network_os':'linux', + 'ansible_user': self.shell_user, + 'ansible_password': self.shell_passwd, + 'ansible_ssh_user': self.shell_user, + 'ansible_ssh_pass': self.shell_passwd, + 'ansible_become_method': 'sudo' + } + self.host.options['variable_manager'].extra_vars.update(evars) + return super(EosHost, self).__getattr__(item) + def shutdown(self, interface_name): - out = self.host.eos_config( + out = self.eos_config( lines=['shutdown'], parents='interface %s' % interface_name) logging.info('Shut interface [%s]' % interface_name) return out def no_shutdown(self, interface_name): - out = self.host.eos_config( + out = self.eos_config( lines=['no shutdown'], parents='interface %s' % interface_name) logging.info('No shut interface [%s]' % interface_name) return out def check_intf_link_state(self, interface_name): - show_int_result = self.host.eos_command( - commands=['show interface %s' % interface_name])[self.hostname] + show_int_result = self.eos_command( + commands=['show interface %s' % interface_name]) return 'Up' in show_int_result['stdout_lines'][0] - def command(self, cmd): - out = self.host.eos_command(commands=[cmd]) - return out - def set_interface_lacp_rate_mode(self, interface_name, mode): - out = self.host.eos_config( + out = self.eos_config( lines=['lacp rate %s' % mode], parents='interface %s' % interface_name) logging.info("Set interface [%s] lacp rate to [%s]" % (interface_name, mode)) return out def kill_bgpd(self): - out = self.host.eos_config(lines=['agent Rib shutdown']) + out = self.eos_config(lines=['agent Rib shutdown']) return out def start_bgpd(self): - out = self.host.eos_config(lines=['no agent Rib shutdown']) + out = self.eos_config(lines=['no agent Rib shutdown']) return out def check_bgp_session_state(self, neigh_ips, neigh_desc, state="established"): @@ -634,12 +664,12 @@ def check_bgp_session_state(self, neigh_ips, neigh_desc, state="established"): """ neigh_ips_ok = [] neigh_desc_ok = [] - out_v4 = self.host.eos_command( - commands=['show ip bgp summary | json'])[self.hostname] + out_v4 = self.eos_command( + commands=['show ip bgp summary | json']) logging.info("ip bgp summary: {}".format(out_v4)) - out_v6 = self.host.eos_command( - commands=['show ipv6 bgp summary | json'])[self.hostname] + out_v6 = self.eos_command( + commands=['show ipv6 bgp summary | json']) logging.info("ipv6 bgp summary: {}".format(out_v6)) for k, v in out_v4['stdout'][0]['vrfs']['default']['peers'].items(): @@ -742,7 +772,7 @@ class FanoutHost(): For running ansible module on the Fanout switch """ - def __init__(self, ansible_adhoc, os, hostname, device_type, user, passwd): + def __init__(self, ansible_adhoc, os, hostname, device_type, user, passwd, shell_user=None, shell_passwd=None): self.hostname = hostname self.type = device_type self.host_to_fanout_port_map = {} @@ -756,7 +786,10 @@ def __init__(self, ansible_adhoc, os, hostname, device_type, user, passwd): else: # Use eos host if the os type is unknown self.os = 'eos' - self.host = EosHost(ansible_adhoc, hostname, user, passwd) + self.host = EosHost(ansible_adhoc, hostname, user, passwd, shell_user=shell_user, shell_passwd=shell_passwd) + + def __getattr__(self, item): + return getattr(self.host, item) def get_fanout_os(self): return self.os @@ -770,9 +803,6 @@ def shutdown(self, interface_name): def no_shutdown(self, interface_name): return self.host.no_shutdown(interface_name)[self.hostname] - def command(self, cmd): - return self.host.command(cmd)[self.hostname] - def __str__(self): return "{ os: '%s', hostname: '%s', device_type: '%s' }" % (self.os, self.hostname, self.type) diff --git a/tests/conftest.py b/tests/conftest.py index 902f5c4f349..1c9049897ad 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -249,10 +249,12 @@ def nbrhosts(ansible_adhoc, testbed, creds): vm_base = int(testbed['vm_base'][2:]) devices = {} for k, v in testbed['topo']['properties']['topology']['VMs'].items(): - devices[k] = {'host': EosHost(ansible_adhoc, \ - "VM%04d" % (vm_base + v['vm_offset']), \ - creds['eos_login'], \ - creds['eos_password']), + devices[k] = {'host': EosHost(ansible_adhoc, + "VM%04d" % (vm_base + v['vm_offset']), + creds['eos_login'], + creds['eos_password'], + shell_user=creds['eos_root_user'] if 'eos_root_user' in creds else None, + shell_passwd=creds['eos_root_password'] if 'eos_root_password' in creds else None), 'conf': testbed['topo']['properties']['configuration'][k]} return devices @@ -275,13 +277,20 @@ def fanouthosts(ansible_adhoc, conn_graph_facts, creds): else: host_vars = ansible_adhoc().options['inventory_manager'].get_host(fanout_host).vars os_type = 'eos' if 'os' not in host_vars else host_vars['os'] - if os_type == "eos": - user = creds['fanout_admin_user'] - pswd = creds['fanout_admin_password'] - elif os_type == "onyx": - user = creds["fanout_mlnx_user"] - pswd = creds["fanout_mlnx_password"] - fanout = FanoutHost(ansible_adhoc, os_type, fanout_host, 'FanoutLeaf', user, pswd) + + eos_user = creds['fanout_eos_user'] if 'fanout_eos_user' in creds else creds['fanout_admin_user'] + eos_password = creds['fanout_eos_password'] if 'fanout_eos_password' in creds else creds['fanout_admin_password'] + shell_user = creds['fanout_shell_user'] if 'fanout_shell_user' in creds else creds['fanout_admin_user'] + shell_password = creds['fanout_shell_password'] if 'fanout_shell_password' in creds else creds['fanout_admin_password'] + + fanout = FanoutHost(ansible_adhoc, + os_type, + fanout_host, + 'FanoutLeaf', + eos_user, + eos_password, + shell_user=shell_user, + shell_passwd=shell_password) fanout_hosts[fanout_host] = fanout fanout.add_port_map(dut_port, fanout_port) except: From 3b3493e08cbc40b5a79ef2f459ac2844411ff746 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Wed, 10 Jun 2020 12:55:07 +0000 Subject: [PATCH 2/3] Change fanout credential name The fanout device may be EOS type or other type. The credential name should be more generic. Signed-off-by: Xin Wang --- tests/conftest.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b31c8bea9e0..7f3030f8c40 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -278,8 +278,11 @@ def fanouthosts(ansible_adhoc, conn_graph_facts, creds): host_vars = ansible_adhoc().options['inventory_manager'].get_host(fanout_host).vars os_type = 'eos' if 'os' not in host_vars else host_vars['os'] - eos_user = creds['fanout_eos_user'] if 'fanout_eos_user' in creds else creds['fanout_admin_user'] - eos_password = creds['fanout_eos_password'] if 'fanout_eos_password' in creds else creds['fanout_admin_password'] + # `fanout_network_user` and `fanout_network_password` are for accessing the non-shell CLI of fanout + # Ansible will use this set of credentail for establishing `network_cli` connection with device + # when applicable. + network_user = creds['fanout_network_user'] if 'fanout_network_user' in creds else creds['fanout_admin_user'] + network_password = creds['fanout_network_password'] if 'fanout_network_password' in creds else creds['fanout_admin_password'] shell_user = creds['fanout_shell_user'] if 'fanout_shell_user' in creds else creds['fanout_admin_user'] shell_password = creds['fanout_shell_password'] if 'fanout_shell_password' in creds else creds['fanout_admin_password'] @@ -287,8 +290,8 @@ def fanouthosts(ansible_adhoc, conn_graph_facts, creds): os_type, fanout_host, 'FanoutLeaf', - eos_user, - eos_password, + network_user, + network_password, shell_user=shell_user, shell_passwd=shell_password) fanout_hosts[fanout_host] = fanout From a7fc7e01b0a84cd8aa35c2d4726e2a832ab18f08 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Thu, 18 Jun 2020 09:32:56 +0000 Subject: [PATCH 3/3] Change `item` to `module_name` to improve readability --- tests/common/devices.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/common/devices.py b/tests/common/devices.py index 8ff96c13189..53d1182b098 100644 --- a/tests/common/devices.py +++ b/tests/common/devices.py @@ -40,10 +40,10 @@ def __init__(self, ansible_adhoc, hostname, connection=None): self.host = ansible_adhoc(become=True, connection=connection)[hostname] self.hostname = hostname - def __getattr__(self, item): - if self.host.has_module(item): - self.module_name = item - self.module = getattr(self.host, item) + def __getattr__(self, module_name): + if self.host.has_module(module_name): + self.module_name = module_name + self.module = getattr(self.host, module_name) return self._run else: @@ -144,7 +144,7 @@ def facts(self): def os_version(self): """ The OS version running on this SONiC device. - + Returns: str: The SONiC OS version (e.g. "20181130.31") """ @@ -623,8 +623,8 @@ def __init__(self, ansible_adhoc, hostname, eos_user, eos_passwd, shell_user=Non AnsibleHostBase.__init__(self, ansible_adhoc, hostname) self.localhost = ansible_adhoc(inventory='localhost', connection='local', host_pattern="localhost")["localhost"] - def __getattr__(self, item): - if item.startswith('eos_'): + def __getattr__(self, module_name): + if module_name.startswith('eos_'): evars = { 'ansible_connection':'network_cli', 'ansible_network_os':'eos', @@ -647,7 +647,7 @@ def __getattr__(self, item): 'ansible_become_method': 'sudo' } self.host.options['variable_manager'].extra_vars.update(evars) - return super(EosHost, self).__getattr__(item) + return super(EosHost, self).__getattr__(module_name) def shutdown(self, interface_name): out = self.eos_config( @@ -817,8 +817,8 @@ def __init__(self, ansible_adhoc, os, hostname, device_type, user, passwd, shell self.os = 'eos' self.host = EosHost(ansible_adhoc, hostname, user, passwd, shell_user=shell_user, shell_passwd=shell_passwd) - def __getattr__(self, item): - return getattr(self.host, item) + def __getattr__(self, module_name): + return getattr(self.host, module_name) def get_fanout_os(self): return self.os