diff --git a/tests/ansible_host.py b/tests/ansible_host.py new file mode 100644 index 00000000000..b3b459bf456 --- /dev/null +++ b/tests/ansible_host.py @@ -0,0 +1,22 @@ +class ansible_host(): + + def __init__(self, ansible_adhoc, hostname, is_local = False): + if is_local: + self.host = ansible_adhoc(inventory='localhost', connection='local')[hostname] + else: + self.host = ansible_adhoc(become=True)[hostname] + self.hostname = hostname + + def __getattr__(self, item): + self.module_name = item + self.module = getattr(self.host, item) + + return self._run + + def _run(self, *module_args, **complex_args): + + res = self.module(*module_args, **complex_args)[self.hostname] + if res.has_key('failed') and res['failed']: + raise Exception("run module {} failed, errmsg {}".format(self.module_name, res)) + + return res diff --git a/tests/bgp_speaker/announce_routes.py b/tests/bgp_speaker/announce_routes.py new file mode 100644 index 00000000000..e13849ac5b3 --- /dev/null +++ b/tests/bgp_speaker/announce_routes.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +import cPickle +import os +import time +import sys + +with open(sys.argv[1]) as f: + routes = f.readlines() + +routes=[x.strip() for x in routes] +ports = set() + +for route in routes: + [command, port] = route.split(";") + port = port.strip() + ports.add(port) + os.system('curl -s --form "command=%s" http://localhost:%s/' % (command, port)) + +for n in range(0, 20): + time.sleep(10) + for port in ports: + os.system('curl -s --form "command=flush route" http://localhost:%s/' % port) + diff --git a/tests/bgp_speaker/bgp_speaker_route.j2 b/tests/bgp_speaker/bgp_speaker_route.j2 new file mode 100644 index 00000000000..1403d5042fa --- /dev/null +++ b/tests/bgp_speaker/bgp_speaker_route.j2 @@ -0,0 +1,3 @@ +0.0.0.0/0 {% for portchannel, v in minigraph_portchannels.iteritems() %}[{% for member in v.members %}{{ '%d' % minigraph_port_indices[member]}}{% if not loop.last %} {% endif %}{% endfor %}]{% if not loop.last %} {% endif %}{% endfor %} + +{{announce_prefix}} {% for vlan, v in minigraph_vlans.iteritems() %}{% for member in v.members %}[{{ '%d' % minigraph_port_indices[member]}}]{% if not loop.last %} {% endif %}{% endfor %}{% if not loop.last %} {% endif %}{% endfor %} diff --git a/tests/bgp_speaker/config.j2 b/tests/bgp_speaker/config.j2 new file mode 100644 index 00000000000..e890cd669e4 --- /dev/null +++ b/tests/bgp_speaker/config.j2 @@ -0,0 +1,24 @@ +group exabgp { + process dump { + encoder json; + receive { + parsed; + update; + } + run /usr/bin/python {{ helper_dir }}/dump.py; + } + + process http-api { + run /usr/bin/python {{ helper_dir }}/http_api.py {{ port_num[cidx] }}; + } + + neighbor {{ lo_addr }} { + router-id {{ speaker_ip }}; + local-address {{ speaker_ip }}; + peer-as {{ peer_asn }}; + local-as {{ my_asn }}; + auto-flush false; + group-updates true; + } + +} diff --git a/tests/bgp_speaker/dump.py b/tests/bgp_speaker/dump.py new file mode 100644 index 00000000000..4bca81fe6a7 --- /dev/null +++ b/tests/bgp_speaker/dump.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +from sys import stdin +import json +import os +import sys + +while True: + try: + line = stdin.readline() + obj = json.loads(line) + f = open("/root/exabgp/" + obj["neighbor"]["ip"], "a") + print >> f, line, + f.close() + except: + continue diff --git a/tests/bgp_speaker/http_api.py b/tests/bgp_speaker/http_api.py new file mode 100644 index 00000000000..fd719b28c86 --- /dev/null +++ b/tests/bgp_speaker/http_api.py @@ -0,0 +1,16 @@ +from flask import Flask, request +import sys + +app = Flask(__name__) + +# Setup a command route to listen for prefix advertisements +@app.route('/', methods=['POST']) +def run_command(): + command = request.form['command'] + sys.stdout.write('%s\n' % command) + sys.stdout.flush() + return 'OK\n' + +if __name__ == '__main__': + app.run(port=sys.argv[1]) + diff --git a/tests/bgp_speaker/routes.j2 b/tests/bgp_speaker/routes.j2 new file mode 100644 index 00000000000..01768f948ad --- /dev/null +++ b/tests/bgp_speaker/routes.j2 @@ -0,0 +1,3 @@ +neighbor {{ lo_addr }} announce route {{ announce_prefix }} next-hop {{ vlan_ips[1].split('/')[0] }};{{ port_num[0] }} +neighbor {{ lo_addr }} announce route {{ announce_prefix }} next-hop {{ vlan_ips[2].split('/')[0] }};{{ port_num[1] }} +neighbor {{ lo_addr }} announce route {{ peer_range }} next-hop {{ vlan_ips[0].split('/')[0] }};{{ port_num[2] }} diff --git a/tests/bgp_speaker/start.j2 b/tests/bgp_speaker/start.j2 new file mode 100644 index 00000000000..45ff79cbc62 --- /dev/null +++ b/tests/bgp_speaker/start.j2 @@ -0,0 +1,20 @@ +#!/bin/bash -ex + +ifconfig eth{{ '%d' % (vlan_ports[0]) }} {{ vlan_ips[0] }} +ifconfig eth{{ '%d' % (vlan_ports[0]) }}:0 {{ speaker_ips[0] }} +ifconfig eth{{ '%d' % (vlan_ports[0]) }}:1 {{ speaker_ips[1] }} + +{% set intf = 'eth%d' % (vlan_ports[1]) %} +ifconfig {{intf}} {{ vlan_ips[1] }} +# i=0; until [ $i -eq 10 ] || ping {{ vlan_addr }} -I {{intf}} -c 1 >/dev/null 2>&1; do i=`expr $i + 1`; done & + +{% set intf = 'eth%d' % (vlan_ports[2]) %} +ifconfig {{intf}} {{ vlan_ips[2] }} +# i=0; until [ $i -eq 10 ] || ping {{ vlan_addr }} -I {{intf}} -c 1 >/dev/null 2>&1; do i=`expr $i + 1`; done & + +ip route flush {{ lo_addr }}/{{ lo_addr_prefixlen }} +ip route add {{ lo_addr }}/{{ lo_addr_prefixlen }} via {{ vlan_addr }} + +env exabgp.daemon.user=root nohup exabgp {{ exabgp_dir }}/{{ cfnames[0] }} >/dev/null 2>&1 & +env exabgp.daemon.user=root nohup exabgp {{ exabgp_dir }}/{{ cfnames[1] }} >/dev/null 2>&1 & +env exabgp.daemon.user=root nohup exabgp {{ exabgp_dir }}/{{ cfnames[2] }} >/dev/null 2>&1 & diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000000..f8e7a311554 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,48 @@ +import pytest +import csv +import ipaddr as ipaddress + +class TestbedInfo(): + ''' + Parse the CSV file used to describe whole testbed info + Please refer to the example of the CSV file format + CSV file first line is title + The topology name in title is using uniq-name | conf-name + ''' + def __init__(self, testbed_file): + self.testbed_filename = testbed_file + self.testbed_topo = {} + + with open(self.testbed_filename) as f: + topo = csv.DictReader(f) + for line in topo: + tb_prop = {} + name = '' + for key in line: + if ('uniq-name' in key or 'conf-name' in key) and '#' in line[key]: + ### skip comment line + continue + elif 'uniq-name' in key or 'conf-name' in key: + name = line[key] + elif 'ptf_ip' in key and line[key]: + ptfaddress = ipaddress.IPNetwork(line[key]) + tb_prop['ptf_ip'] = str(ptfaddress.ip) + tb_prop['ptf_netmask'] = str(ptfaddress.netmask) + else: + tb_prop[key] = line[key] + if name: + self.testbed_topo[name] = tb_prop + +def pytest_addoption(parser): + parser.addoption("--testbed", action="store", default=None, help="testbed name") + parser.addoption("--testbed_file", action="store", default=None, help="testbed file name") + +@pytest.fixture +def testbed(request): + tbname = request.config.getoption("--testbed") + tbfile = request.config.getoption("--testbed_file") + if tbname == None or tbfile == None: + raise ValueError("testbed and testbed_file are required!") + + tbinfo = TestbedInfo(tbfile) + return tbinfo.testbed_topo[tbname] diff --git a/tests/setup.cfg b/tests/setup.cfg new file mode 100644 index 00000000000..6d6d72b613d --- /dev/null +++ b/tests/setup.cfg @@ -0,0 +1,2 @@ +[tool:pytest] +norecursedirs = ptftests diff --git a/tests/test_bgp_fact.py b/tests/test_bgp_fact.py new file mode 100644 index 00000000000..b369799e4bd --- /dev/null +++ b/tests/test_bgp_fact.py @@ -0,0 +1,22 @@ +from ansible_host import ansible_host + +def test_bgp_facts(ansible_adhoc, testbed): + """compare the bgp facts between observed states and target state""" + + hostname = testbed['dut'] + ans_host = ansible_host(ansible_adhoc, hostname) + + bgp_facts = ans_host.bgp_facts()['ansible_facts'] + mg_facts = ans_host.minigraph_facts(host=hostname)['ansible_facts'] + + for k, v in bgp_facts['bgp_neighbors'].items(): + # Verify bgp sessions are established + assert v['state'] == 'established' + # Verify locat ASNs in bgp sessions + assert v['local AS'] == mg_facts['minigraph_bgp_asn'] + + for v in mg_facts['minigraph_bgp']: + # Compare the bgp neighbors name with minigraph bgp neigbhors name + assert v['name'] == bgp_facts['bgp_neighbors'][v['addr'].lower()]['description'] + # Compare the bgp neighbors ASN with minigraph + assert v['asn'] == bgp_facts['bgp_neighbors'][v['addr'].lower()]['remote AS'] diff --git a/tests/test_bgp_speaker.py b/tests/test_bgp_speaker.py new file mode 100644 index 00000000000..0e1ec6231eb --- /dev/null +++ b/tests/test_bgp_speaker.py @@ -0,0 +1,174 @@ +from netaddr import * +import sys +import time +import ipaddress +from ansible_host import ansible_host + +def generate_ips(num, prefix, exclude_ips): + """ + Generate random ips within prefix + """ + prefix = IPNetwork(prefix) + exclude_ips.append(prefix.broadcast) + exclude_ips.append(prefix.network) + available_ips = list(prefix) + + if len(available_ips) - len(exclude_ips)< num: + raise Exception("Not enough available IPs") + + generated_ips = [] + for available_ip in available_ips: + if available_ip not in exclude_ips: + generated_ips.append(IPNetwork(str(available_ip) + '/' + str(prefix.prefixlen))) + if len(generated_ips) == num: + break + + return generated_ips + +def ptf_runner(host, testdir, testname, platform_dir, params={}, \ + platform="remote", qlen=0, relax=True, debug_level="info", log_file=None): + + ptf_test_params = ";".join(["{}=\"{}\"".format(k, v) for k, v in params.items()]) + + cmd = "ptf --test-dir {} {} --platform-dir {}".format(testdir, testname, platform_dir) + if qlen: + cmd += " --qlen={}".format(qlen) + if platform: + cmd += " --platform {}".format(platform) + if ptf_test_params: + cmd += " -t '{}'".format(ptf_test_params) + if relax: + cmd += " --relax" + if debug_level: + cmd += " --debug {}".format(debug_level) + if log_file: + cmd += " --log-file {}".format(log_file) + + res = host.shell(cmd, chdir="/root") + +def test_bgp_speaker(localhost, ansible_adhoc, testbed): + """setup bgp speaker on T0 topology and verify routes advertised + by bgp speaker is received by T0 TOR + """ + + hostname = testbed['dut'] + ptf_hostname = testbed['ptf'] + host = ansible_host(ansible_adhoc, hostname) + ptfhost = ansible_host(ansible_adhoc, ptf_hostname) + + mg_facts = host.minigraph_facts(host=hostname)['ansible_facts'] + host_facts = host.setup()['ansible_facts'] + + res = host.shell("sonic-cfggen -m -d -y /etc/sonic/deployment_id_asn_map.yml -v \"deployment_id_asn_map[DEVICE_METADATA['localhost']['deployment_id']]\"") + bgp_speaker_asn = res['stdout'] + + vlan_ips = generate_ips(3, \ + "%s/%s" % (mg_facts['minigraph_vlan_interfaces'][0]['addr'], mg_facts['minigraph_vlan_interfaces'][0]['prefixlen']), + [IPAddress(mg_facts['minigraph_vlan_interfaces'][0]['addr'])]) + + # three speaker ips, two from peer range, another is vlan ip [0] + speaker_ips = generate_ips(2, mg_facts['minigraph_bgp_peers_with_range'][0]['ip_range'][0], []) + speaker_ips.append(vlan_ips[0]) + + for ip in vlan_ips: + host.command("ip route flush %s/32" % ip.ip) + host.command("ip route add %s/32 dev %s" % (ip.ip, mg_facts['minigraph_vlan_interfaces'][0]['attachto'])) + + root_dir = "/root" + exabgp_dir = "/root/exabgp" + helper_dir = "/root/helpers" + port_num = [5000, 6000, 7000] + cfnames = ["config_1.ini", "config_2.ini", "config_3.ini"] + vlan_ports = [] + for i in range(0, 3): + vlan_ports.append(mg_facts['minigraph_port_indices'][mg_facts['minigraph_vlans'][mg_facts['minigraph_vlan_interfaces'][0]['attachto']]['members'][i]]) + + ptfhost.file(path=exabgp_dir, state="directory") + ptfhost.file(path=helper_dir, state="directory") + ptfhost.copy(src="bgp_speaker/dump.py", dest=helper_dir) + ptfhost.copy(src="bgp_speaker/http_api.py", dest=helper_dir) + ptfhost.copy(src="bgp_speaker/announce_routes.py", dest=helper_dir) + + # deploy config file + extra_vars = \ + { 'helper_dir': helper_dir, + 'exabgp_dir': exabgp_dir, + 'lo_addr' : mg_facts['minigraph_lo_interfaces'][0]['addr'], + 'lo_addr_prefixlen' : mg_facts['minigraph_lo_interfaces'][0]['prefixlen'], + 'vlan_addr' : mg_facts['minigraph_vlan_interfaces'][0]['addr'], + 'peer_range': mg_facts['minigraph_bgp_peers_with_range'][0]['ip_range'][0], + 'announce_prefix': '10.10.10.0/26', + 'minigraph_portchannels' : mg_facts['minigraph_portchannels'], + 'minigraph_vlans' : mg_facts['minigraph_vlans'], + 'minigraph_port_indices' : mg_facts['minigraph_port_indices'], + 'peer_asn' : mg_facts['minigraph_bgp_asn'], + 'peer_asn' : mg_facts['minigraph_bgp_asn'], + 'my_asn' : bgp_speaker_asn, + 'vlan_ports' : vlan_ports, + 'port_num' : port_num, + 'speaker_ips': [str(ip) for ip in speaker_ips], + 'vlan_ips': [str(ip) for ip in vlan_ips], + 'cfnames': cfnames } + + for i in range(0, 3): + extra_vars.update({ 'cidx':i }) + extra_vars.update({ 'speaker_ip': str(speaker_ips[i].ip) }) + ptfhost.host.options['variable_manager'].extra_vars = extra_vars + ptfhost.template(src="bgp_speaker/config.j2", dest="%s/%s" % (exabgp_dir, cfnames[i])) + + # deploy routes + ptfhost.template(src="bgp_speaker/routes.j2", dest="%s/%s" % (exabgp_dir, "routes")) + + # deploy start script + ptfhost.template(src="bgp_speaker/start.j2", dest="%s/%s" % (exabgp_dir, "start.sh"), mode="u+rwx") + # kill exabgp + res = ptfhost.shell("pkill exabgp || true") + print res + + # start exabgp instance + res = ptfhost.shell("bash %s/start.sh" % exabgp_dir) + print res + + time.sleep(10) + + # announce route + res = ptfhost.shell("nohup python %s/announce_routes.py %s/routes >/dev/null 2>&1 &" % (helper_dir, exabgp_dir)) + print res + + # make sure routes announced to dynamic bgp neighbors + time.sleep(60) + + bgp_facts = host.bgp_facts()['ansible_facts'] + + # Verify bgp sessions are established + for k, v in bgp_facts['bgp_neighbors'].items(): + assert v['state'] == 'established' + + # Verify accepted prefixes of the dynamic neighbors are correct + for ip in speaker_ips: + assert bgp_facts['bgp_neighbors'][str(ip.ip)]['accepted prefixes'] == 1 + assert bgp_facts['bgp_neighbors'][str(vlan_ips[0].ip)]['accepted prefixes'] == 1 + + + # Generate route-port map information + ptfhost.template(src="bgp_speaker/bgp_speaker_route.j2", dest="/root/bgp_speaker_route.txt") + + ptfhost.copy(src="ptftests", dest=root_dir) + + ptf_runner(ptfhost, \ + "ptftests", + "fib_test.FibTest", + platform_dir="ptftests", + params={"testbed_type": "t0", + "router_mac": host_facts['ansible_Ethernet0']['macaddress'], + "fib_info": "/root/bgp_speaker_route.txt", + "ipv4": True, + "ipv6": False }, + log_file="/tmp/bgp_speaker_test.FibTest.log") + + res = ptfhost.shell("pkill exabgp || true") + + for ip in vlan_ips: + host.command("ip route flush %s/32" % ip.ip) + + # ptfhost.shell("ip addr flush dev eth{{ '%d' % (minigraph_vlans[minigraph_vlan_interfaces[0]['attachto']]['members'][0] | replace("Ethernet", "") | int / 4)}} diff --git a/tests/test_lldp.py b/tests/test_lldp.py new file mode 100644 index 00000000000..75d61f993b1 --- /dev/null +++ b/tests/test_lldp.py @@ -0,0 +1,49 @@ +from ansible_host import ansible_host + +def test_lldp(localhost, ansible_adhoc, testbed): + """verify the lldp message on DUT and neighbors""" + + hostname = testbed['dut'] + ans_host = ansible_host(ansible_adhoc, hostname) + + mg_facts = ans_host.minigraph_facts(host=hostname)['ansible_facts'] + host_facts = ans_host.setup()['ansible_facts'] + lldp_facts = ans_host.lldp()['ansible_facts'] + res = ans_host.shell("docker exec -i lldp lldpcli show chassis | grep \"SysDescr:\" | sed -e 's/^\\s*SysDescr:\\s*//g'") + dut_system_description = res['stdout'] + + minigraph_lldp_nei = {} + for k, v in mg_facts['minigraph_neighbors'].items(): + if 'server' not in v['name'].lower(): + minigraph_lldp_nei[k] = v + + # Verify LLDP information is available on most interfaces + assert len(lldp_facts['lldp']) > len(minigraph_lldp_nei) * 0.8 + + for k, v in lldp_facts['lldp'].items(): + if k == 'eth0': + continue + # Compare the LLDP neighbor name with minigraph neigbhor name (exclude the management port) + assert v['chassis']['name'] == minigraph_lldp_nei[k]['name'] + # Compare the LLDP neighbor interface with minigraph neigbhor interface (exclude the management port) + assert v['port']['ifname'] == mg_facts['minigraph_neighbors'][k]['port'] + + lhost = ansible_host(ansible_adhoc, 'localhost', True) + + for k, v in lldp_facts['lldp'].items(): + hostip = v['chassis']['mgmt-ip'] + nei_lldp_facts = lhost.lldp_facts(host=hostip, version='v2c', community='strcommunity')['ansible_facts'] + print nei_lldp_facts + neighbor_interface = v['port']['ifname'] + # Verify the published DUT system name field is correct + assert nei_lldp_facts['ansible_lldp_facts'][neighbor_interface]['neighbor_sys_name'] == hostname + # Verify the published DUT chassis id field is not empty + assert nei_lldp_facts['ansible_lldp_facts'][neighbor_interface]['neighbor_chassis_id'] == \ + "0x%s" % (host_facts['ansible_eth0']['macaddress'].replace(':', '')) + # Verify the published DUT system description field is correct + assert nei_lldp_facts['ansible_lldp_facts'][neighbor_interface]['neighbor_sys_desc'] == dut_system_description + # Verify the published DUT port id field is correct + assert nei_lldp_facts['ansible_lldp_facts'][neighbor_interface]['neighbor_port_id'] == mg_facts['minigraph_ports'][k]['alias'] + # Verify the published DUT port description field is correct + assert nei_lldp_facts['ansible_lldp_facts'][neighbor_interface]['neighbor_port_desc'] == \ + "%s:%s" % (mg_facts['minigraph_neighbors'][k]['name'], mg_facts['minigraph_neighbors'][k]['port']) diff --git a/tests/veos.vtb b/tests/veos.vtb new file mode 100644 index 00000000000..54527ea9b88 --- /dev/null +++ b/tests/veos.vtb @@ -0,0 +1,35 @@ +[vm_host_1] +STR-ACS-VSERV-01 ansible_host=172.17.0.1 ansible_user=lgh + +[vm_host:children] +vm_host_1 + +[vms_1] +VM0100 ansible_host=10.250.0.51 +VM0101 ansible_host=10.250.0.52 +VM0102 ansible_host=10.250.0.53 +VM0103 ansible_host=10.250.0.54 + + +[eos:children] +vms_1 + +## The groups below are helper to limit running playbooks to server_1, server_2 or server_3 only +[server_1:children] +vm_host_1 +vms_1 + +[server_1:vars] +host_var_file=host_vars/STR-ACS-VSERV-01.yml + +[servers:children] +server_1 + +[servers:vars] +topologies=['t1', 't1-lag', 't1-64-lag', 't0', 't0-16', 't0-56', 't0-52', 'ptf32', 'ptf64', 't0-64', 't0-64-32', 't0-116'] + +[sonic] +vlab-01 ansible_host=10.250.0.101 type=kvm hwsku=Force10-S6000 ansible_password=password ansible_user=admin +vlab-02 ansible_host=10.250.0.102 type=kvm hwsku=Force10-S6100 ansible_password=password + +ptf-01 ansible_host=10.250.0.102 ansible_user=root ansible_password=root diff --git a/tests/vtestbed.csv b/tests/vtestbed.csv new file mode 100644 index 00000000000..dd5549cf297 --- /dev/null +++ b/tests/vtestbed.csv @@ -0,0 +1,3 @@ +# conf-name,group-name,topo,ptf_image_name,ptf,ptf_ip,server,vm_base,dut,comment +vms-kvm-t0,vms6-1,t0,docker-ptf-brcm,ptf-01,10.250.0.102/24,server_1,VM0100,vlab-01,Tests virtual switch vm +vms-kvm-t0-64,vms6-1,t0-64,docker-ptf-brcm,ptf-01,10.250.0.102/24,server_1,VM0100,vlab-02,Tests virtual switch vm