Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ansible/group_vars/all/creds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
82 changes: 56 additions & 26 deletions tests/common/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,61 +567,91 @@ 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

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' }
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)

def __getattr__(self, item):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be getattribute? I think you want to setup credential for all methods, right?

Did you try to call both EOS and Linux method from a same instance?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea here is create NetworkCli based host if user call eos module and create LinuxShell based host if user call other module.
The judgement happens when invoking a module because we couldn't keep two types hosts in a single instance and that's why Xin use getattr.
The only overhead is we need to override extra-var and create a host every time when calling a module.

Copy link
Collaborator Author

@wangxin wangxin Jun 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Blueve for the explanation.
@yxieca According to https://stackoverflow.com/questions/3278077/difference-between-getattr-vs-getattribute:

A key difference between getattr and getattribute is that getattr is only invoked if the attribute wasn't found the usual ways.

When an attribute is not found on the EosHost or FanoutHost instance, the getattr will try to get a function for running the ansible module. This is the key technique for us to invoke ansible module using format like fanouthost.<ansible_module_name>(*args, **kwargs) or eoshost.<ansible_module_name>(*args, **kwargs)

Here the ansible module can a EOS module like eos_command, eos_config, or a regular module like command, shell, etc.

Yes, I have tested that both EOS and Linux method can be called from a same instance.

To support this, the EosHost need two sets of credential. But the credential for running Linux methods is optional.

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)
self.localhost = ansible_adhoc(inventory='localhost', connection='local', host_pattern="localhost")["localhost"]
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(
show_int_result = self.eos_command(
commands=['show interface %s' % interface_name])[self.hostname]
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"):
Expand All @@ -634,11 +664,11 @@ def check_bgp_session_state(self, neigh_ips, neigh_desc, state="established"):
"""
neigh_ips_ok = []
neigh_desc_ok = []
out_v4 = self.host.eos_command(
out_v4 = self.eos_command(
commands=['show ip bgp summary | json'])[self.hostname]
logging.info("ip bgp summary: {}".format(out_v4))

out_v6 = self.host.eos_command(
out_v6 = self.eos_command(
commands=['show ipv6 bgp summary | json'])[self.hostname]
logging.info("ipv6 bgp summary: {}".format(out_v6))

Expand Down Expand Up @@ -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 = {}
Expand All @@ -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
Expand All @@ -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)

Expand Down
31 changes: 20 additions & 11 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,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

Expand All @@ -260,13 +262,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:
Expand Down