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
22 changes: 22 additions & 0 deletions tests/ansible_host.py
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions tests/bgp_speaker/announce_routes.py
Original file line number Diff line number Diff line change
@@ -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)

3 changes: 3 additions & 0 deletions tests/bgp_speaker/bgp_speaker_route.j2
Original file line number Diff line number Diff line change
@@ -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 %}
24 changes: 24 additions & 0 deletions tests/bgp_speaker/config.j2
Original file line number Diff line number Diff line change
@@ -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;
}

}
16 changes: 16 additions & 0 deletions tests/bgp_speaker/dump.py
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions tests/bgp_speaker/http_api.py
Original file line number Diff line number Diff line change
@@ -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])

3 changes: 3 additions & 0 deletions tests/bgp_speaker/routes.j2
Original file line number Diff line number Diff line change
@@ -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] }}
20 changes: 20 additions & 0 deletions tests/bgp_speaker/start.j2
Original file line number Diff line number Diff line change
@@ -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 &
48 changes: 48 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -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]
2 changes: 2 additions & 0 deletions tests/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool:pytest]
norecursedirs = ptftests
22 changes: 22 additions & 0 deletions tests/test_bgp_fact.py
Original file line number Diff line number Diff line change
@@ -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']
174 changes: 174 additions & 0 deletions tests/test_bgp_speaker.py
Original file line number Diff line number Diff line change
@@ -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)}}
Loading