blob: ea7319e0bf17348ed854a28ad0807afe30e83dc8 [file] [log] [blame]
Chetan Gaonker93e302d2016-04-05 10:51:07 -07001#!/usr/bin/env python
2from argparse import ArgumentParser
3import os,sys,time
4import io
5import yaml
6from pyroute2 import IPRoute
7from itertools import chain
8from nsenter import Namespace
9from docker import Client
10from shutil import copy
Chetan Gaonker7142a342016-04-07 14:53:12 -070011utils_dir = os.path.join( os.path.dirname(os.path.realpath(sys.argv[0])), '../utils')
12sys.path.append(utils_dir)
Chetan Gaonker93e302d2016-04-05 10:51:07 -070013from OnosCtrl import OnosCtrl
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -070014from OltConfig import OltConfig
Chetan Gaonker93e302d2016-04-05 10:51:07 -070015
16class docker_netns(object):
17
18 dckr = Client()
19 def __init__(self, name):
20 pid = int(self.dckr.inspect_container(name)['State']['Pid'])
21 if pid == 0:
22 raise Exception('no container named {0}'.format(name))
23 self.pid = pid
24
25 def __enter__(self):
26 pid = self.pid
27 if not os.path.exists('/var/run/netns'):
28 os.mkdir('/var/run/netns')
29 os.symlink('/proc/{0}/ns/net'.format(pid), '/var/run/netns/{0}'.format(pid))
30 return str(pid)
31
32 def __exit__(self, type, value, traceback):
33 pid = self.pid
34 os.unlink('/var/run/netns/{0}'.format(pid))
35
36flatten = lambda l: chain.from_iterable(l)
37
38class Container(object):
39 dckr = Client()
40 def __init__(self, name, image, tag = 'latest', command = 'bash', quagga_config = None):
41 self.name = name
42 self.image = image
43 self.tag = tag
44 self.image_name = image + ':' + tag
45 self.id = None
46 self.command = command
47 if quagga_config is not None:
48 self.bridge = quagga_config['bridge']
49 self.ipaddress = quagga_config['ip']
50 self.mask = quagga_config['mask']
51 else:
52 self.bridge = None
53 self.ipaddress = None
54 self.mask = None
55
56 @classmethod
57 def build_image(cls, dockerfile, tag, force=True, nocache=False):
58 f = io.BytesIO(dockerfile.encode('utf-8'))
59 if force or not cls.image_exists(tag):
60 print('Build {0}...'.format(tag))
61 for line in cls.dckr.build(fileobj=f, rm=True, tag=tag, decode=True, nocache=nocache):
62 if 'stream' in line:
63 print(line['stream'].strip())
64
65 @classmethod
66 def image_exists(cls, name):
67 return name in [ctn['RepoTags'][0] for ctn in cls.dckr.images()]
68
69 @classmethod
70 def create_host_config(cls, port_list = None, host_guest_map = None, privileged = False):
71 port_bindings = None
72 binds = None
73 if port_list:
74 port_bindings = {}
75 for p in port_list:
76 port_bindings[str(p)] = str(p)
77
78 if host_guest_map:
79 binds = []
80 for h, g in host_guest_map:
81 binds.append('{0}:{1}'.format(h, g))
82
83 return cls.dckr.create_host_config(binds = binds, port_bindings = port_bindings, privileged = privileged)
84
85 @classmethod
86 def cleanup(cls, image):
87 cnt_list = filter(lambda c: c['Image'] == image, cls.dckr.containers())
88 for cnt in cnt_list:
89 print('Cleaning container %s' %cnt['Id'])
90 cls.dckr.kill(cnt['Id'])
91 cls.dckr.remove_container(cnt['Id'], force=True)
92
Chetan Gaonker7142a342016-04-07 14:53:12 -070093 @classmethod
94 def remove_container(cls, name, force=True):
95 try:
96 cls.dckr.remove_container(name, force = force)
97 except: pass
98
Chetan Gaonker93e302d2016-04-05 10:51:07 -070099 def exists(self):
100 return '/{0}'.format(self.name) in list(flatten(n['Names'] for n in self.dckr.containers()))
101
102 def img_exists(self):
103 return self.image_name in [ctn['RepoTags'][0] for ctn in self.dckr.images()]
104
105 def ip(self):
106 cnt_list = filter(lambda c: c['Image'] == self.image_name, self.dckr.containers())
107 cnt_settings = cnt_list.pop()
108 return cnt_settings['NetworkSettings']['Networks']['bridge']['IPAddress']
109
110 def kill(self, remove = True):
111 self.dckr.kill(self.name)
112 self.dckr.remove_container(self.name, force=True)
113
114 def start(self, rm = True, ports = None, volumes = None, host_config = None,
115 environment = None, tty = False, stdin_open = True):
116
117 if rm and self.exists():
118 print('Removing container:', self.name)
119 self.dckr.remove_container(self.name, force=True)
120
121 ctn = self.dckr.create_container(image=self.image_name, ports = ports, command=self.command,
122 detach=True, name=self.name,
123 environment = environment,
124 volumes = volumes,
125 host_config = host_config, stdin_open=stdin_open, tty = tty)
126 self.dckr.start(container=self.name)
127 if self.bridge:
128 self.connect_to_br()
129 self.id = ctn['Id']
130 return ctn
131
132 def connect_to_br(self):
133 with docker_netns(self.name) as pid:
134 ip = IPRoute()
135 br = ip.link_lookup(ifname=self.bridge)
136 if len(br) == 0:
137 ip.link_create(ifname=self.bridge, kind='bridge')
138 br = ip.link_lookup(ifname=self.bridge)
139 br = br[0]
140 ip.link('set', index=br, state='up')
141
142 ifs = ip.link_lookup(ifname=self.name)
143 if len(ifs) > 0:
144 ip.link_remove(ifs[0])
145
146 ip.link_create(ifname=self.name, kind='veth', peer=pid)
147 host = ip.link_lookup(ifname=self.name)[0]
148 ip.link('set', index=host, master=br)
149 ip.link('set', index=host, state='up')
150 guest = ip.link_lookup(ifname=pid)[0]
151 ip.link('set', index=guest, net_ns_fd=pid)
152 with Namespace(pid, 'net'):
153 ip = IPRoute()
154 ip.link('set', index=guest, ifname='eth1')
155 ip.link('set', index=guest, state='up')
156 ip.addr('add', index=guest, address=self.ipaddress, mask=self.mask)
157
158 def execute(self, cmd, tty = True, stream = False, shell = False):
159 res = 0
160 if type(cmd) == str:
161 cmds = (cmd,)
162 else:
163 cmds = cmd
164 if shell:
165 for c in cmds:
166 res += os.system('docker exec {0} {1}'.format(self.name, c))
167 return res
168 for c in cmds:
169 i = self.dckr.exec_create(container=self.name, cmd=c, tty = tty, privileged = True)
170 self.dckr.exec_start(i['Id'], stream = stream)
171 result = self.dckr.exec_inspect(i['Id'])
172 res += 0 if result['ExitCode'] == None else result['ExitCode']
173 return res
174
175class Onos(Container):
176
177 quagga_config = { 'bridge' : 'quagga-br', 'ip': '10.10.0.4', 'mask' : 16 }
178 env = { 'ONOS_APPS' : 'drivers,openflow,proxyarp,aaa,igmp,vrouter' }
179 ports = [ 8181, 8101, 9876, 6653, 6633, 2000, 2620 ]
180
181 def __init__(self, name = 'cord-onos', image = 'onosproject/onos', tag = 'latest', boot_delay = 60):
182 super(Onos, self).__init__(name, image, tag = tag, quagga_config = self.quagga_config)
183 if not self.exists():
Chetan Gaonker7142a342016-04-07 14:53:12 -0700184 self.remove_container(name, force=True)
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700185 host_config = self.create_host_config(port_list = self.ports)
186 print('Starting ONOS container %s' %self.name)
187 self.start(ports = self.ports, environment = self.env,
188 host_config = host_config, tty = True)
189 print('Waiting %d seconds for ONOS to boot' %(boot_delay))
190 time.sleep(boot_delay)
191
192class Radius(Container):
193 ports = [ 1812, 1813 ]
194 env = {'TIMEZONE':'America/Los_Angeles',
195 'DEBUG': 'true', 'cert_password':'whatever', 'primary_shared_secret':'radius_password'
196 }
197 host_db_dir = os.path.join(os.getenv('HOME'), 'services', 'radius', 'data', 'db')
198 guest_db_dir = os.path.join(os.path.sep, 'opt', 'db')
199 host_config_dir = os.path.join(os.getenv('HOME'), 'services', 'radius', 'freeradius')
200 guest_config_dir = os.path.join(os.path.sep, 'etc', 'freeradius')
201 start_command = '/root/start-radius.py'
202 host_guest_map = ( (host_db_dir, guest_db_dir),
203 (host_config_dir, guest_config_dir)
204 )
205 def __init__(self, name = 'cord-radius', image = 'freeradius', tag = 'podd'):
206 super(Radius, self).__init__(name, image, tag = tag, command = self.start_command)
207 if not self.exists():
Chetan Gaonker7142a342016-04-07 14:53:12 -0700208 self.remove_container(name, force=True)
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700209 host_config = self.create_host_config(port_list = self.ports,
210 host_guest_map = self.host_guest_map)
211 volumes = []
212 for h,g in self.host_guest_map:
213 volumes.append(g)
214 self.start(ports = self.ports, environment = self.env,
215 volumes = volumes,
216 host_config = host_config, tty = True)
217
218class CordTester(Container):
219
220 sandbox = '/root/test'
Chetan Gaonker7142a342016-04-07 14:53:12 -0700221 sandbox_setup = '/root/test/src/test/setup'
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700222 tester_base = os.path.dirname(os.path.realpath(sys.argv[0]))
Chetan Gaonker7142a342016-04-07 14:53:12 -0700223 tester_paths = os.path.realpath(sys.argv[0]).split(os.path.sep)
224 tester_path_index = tester_paths.index('cord-tester')
225 sandbox_host = os.path.sep.join(tester_paths[:tester_path_index+1])
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700226
227 host_guest_map = ( (sandbox_host, sandbox),
228 ('/lib/modules', '/lib/modules')
229 )
230 basename = 'cord-tester'
231
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700232 def __init__(self, ctlr_ip = None, image = 'cord-test/nose', tag = 'latest', env = None, rm = False):
233 self.ctlr_ip = ctlr_ip
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700234 self.rm = rm
235 self.name = self.get_name()
236 super(CordTester, self).__init__(self.name, image = image, tag = tag)
237 host_config = self.create_host_config(host_guest_map = self.host_guest_map, privileged = True)
238 volumes = []
239 for h, g in self.host_guest_map:
240 volumes.append(g)
Chetan Gaonker7142a342016-04-07 14:53:12 -0700241 ##Remove test container if any
242 self.remove_container(self.name, force=True)
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700243 if env is not None and env.has_key('OLT_CONFIG'):
244 self.olt = True
245 else:
246 self.olt = False
247 self.intf_ports = (1, 2)
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700248 print('Starting test container %s, image %s, tag %s' %(self.name, self.image, self.tag))
249 self.start(rm = False, volumes = volumes, environment = env,
250 host_config = host_config, tty = True)
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700251
252 def execute_switch(self, cmd, shell = False):
253 if self.olt:
254 return os.system(cmd)
255 return self.execute(cmd, shell = shell)
256
257 def start_switch(self, bridge = 'ovsbr0', boot_delay = 2):
258 """Start OVS"""
259 ##Determine if OVS has to be started locally or not
260 s_file,s_sandbox = ('of-bridge-local.sh',self.tester_base) if self.olt else ('of-bridge.sh',self.sandbox_setup)
261 ovs_cmd = os.path.join(s_sandbox, '{0}'.format(s_file)) + ' {0}'.format(bridge)
262 if self.olt:
263 ovs_cmd += ' {0}'.format(self.ctlr_ip)
264 print('Starting OVS on the host')
265 else:
266 print('Starting OVS on test container %s' %self.name)
267 self.execute_switch(ovs_cmd)
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700268 status = 1
269 ## Wait for the LLDP flows to be added to the switch
270 tries = 0
271 while status != 0 and tries < 100:
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700272 cmd = 'sudo ovs-ofctl dump-flows {0} | grep \"type=0x8942\"'.format(bridge)
273 status = self.execute_switch(cmd, shell = True)
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700274 tries += 1
275 if tries % 10 == 0:
276 print('Waiting for test switch to be connected to ONOS controller ...')
277
278 if status != 0:
279 print('Test Switch not connected to ONOS container.'
280 'Please remove ONOS container and restart the test')
281 if self.rm:
282 self.kill()
283 sys.exit(1)
284
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700285 if boot_delay:
286 time.sleep(boot_delay)
287
288 return self.setup_intfs(bridge)
289
290 def setup_intfs(self, bridge = 'ovsbr0'):
291 if not self.olt:
292 return 0
293 olt_conf_file = os.path.join(self.tester_base, 'olt_config.json')
294 olt_config = OltConfig(olt_conf_file)
295 port_map = olt_config.olt_port_map()
296 tester_intf_subnet = '192.168.100'
297 res = 0
298 for port in self.intf_ports:
299 guest_if = port_map[port]
300 local_if = guest_if
301 guest_ip = '{0}.{1}/24'.format(tester_intf_subnet, str(port))
302 ##Use pipeworks to configure container interfaces on OVS bridge
303 pipework_cmd = 'pipework {0} -i {1} -l {2} {3} {4}'.format(bridge, guest_if, local_if, self.name, guest_ip)
304 res += os.system(pipework_cmd)
305
306 return res
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700307
308 @classmethod
309 def get_name(cls):
310 cnt_name = '/{0}'.format(cls.basename)
311 cnt_name_len = len(cnt_name)
312 names = list(flatten(n['Names'] for n in cls.dckr.containers(all=True)))
313 test_names = filter(lambda n: n.startswith(cnt_name), names)
314 last_cnt_number = 0
315 if test_names:
316 last_cnt_name = reduce(lambda n1, n2: n1 if int(n1[cnt_name_len:]) > \
317 int(n2[cnt_name_len:]) else n2,
318 test_names)
319 last_cnt_number = int(last_cnt_name[cnt_name_len:])
320 test_cnt_name = cls.basename + str(last_cnt_number+1)
321 return test_cnt_name
322
323 @classmethod
324 def build_image(cls, image):
325 print('Building test container docker image %s' %image)
326 dockerfile = '''
327FROM ubuntu:14.04
328MAINTAINER chetan@ciena.com
329RUN apt-get update
330RUN apt-get -y install git python python-pip python-setuptools python-scapy tcpdump doxygen doxypy wget
331RUN easy_install nose
332RUN apt-get -y install openvswitch-common openvswitch-switch
333RUN mkdir -p /root/ovs
334WORKDIR /root
335RUN wget http://openvswitch.org/releases/openvswitch-2.4.0.tar.gz -O /root/ovs/openvswitch-2.4.0.tar.gz && \
336(cd /root/ovs && tar zxpvf openvswitch-2.4.0.tar.gz && \
337 cd openvswitch-2.4.0 && \
338 ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --disable-ssl && make && make install)
339RUN service openvswitch-switch restart || /bin/true
340RUN apt-get -y install python-twisted python-sqlite sqlite3
341RUN pip install scapy-ssl_tls
342RUN pip install -U scapy
343RUN pip install monotonic
344RUN mv /usr/sbin/tcpdump /sbin/
345RUN ln -sf /sbin/tcpdump /usr/sbin/tcpdump
346CMD ["/bin/bash"]
347'''
348 super(CordTester, cls).build_image(dockerfile, image)
349 print('Done building docker image %s' %image)
350
351 def run_tests(self, tests):
352 '''Run the list of tests'''
353 for t in tests:
354 test = t.split(':')[0]
355 if test == 'tls':
356 test_file = test + 'AuthTest.py'
357 else:
358 test_file = test + 'Test.py'
359
360 if t.find(':') >= 0:
361 test_case = test_file + ':' + t.split(':')[1]
362 else:
363 test_case = test_file
Chetan Gaonker7142a342016-04-07 14:53:12 -0700364 cmd = 'nosetests -v {0}/src/test/{1}/{2}'.format(self.sandbox, test, test_case)
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700365 status = self.execute(cmd, shell = True)
366 print('Test %s %s' %(test_case, 'Success' if status == 0 else 'Failure'))
367 print('Done running tests')
368 if self.rm:
369 print('Removing test container %s' %self.name)
370 self.kill(remove=True)
371
372
373##default onos/radius/test container images and names
374onos_image_default='onosproject/onos:latest'
375nose_image_default='cord-test/nose:latest'
376test_type_default='dhcp'
377onos_app_version = '1.0-SNAPSHOT'
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700378cord_tester_base = os.path.dirname(os.path.realpath(sys.argv[0]))
379onos_app_file = os.path.abspath('{0}/../apps/ciena-cordigmp-'.format(cord_tester_base) + onos_app_version + '.oar')
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700380zebra_quagga_config = { 'bridge' : 'quagga-br', 'ip': '10.10.0.1', 'mask': 16 }
381
382def runTest(args):
383 onos_cnt = {'tag':'latest'}
384 radius_cnt = {'tag':'latest'}
385 nose_cnt = {'image': 'cord-test/nose','tag': 'latest'}
386
387 #print('Test type %s, onos %s, radius %s, app %s, olt %s, cleanup %s, kill flag %s, build image %s'
388 # %(args.test_type, args.onos, args.radius, args.app, args.olt, args.cleanup, args.kill, args.build))
389 if args.cleanup:
390 cleanup_container = args.cleanup
391 if cleanup_container.find(':') < 0:
392 cleanup_container += ':latest'
393 print('Cleaning up containers %s' %cleanup_container)
394 Container.cleanup(cleanup_container)
395 sys.exit(0)
396
397 onos_cnt['image'] = args.onos.split(':')[0]
398 if args.onos.find(':') >= 0:
399 onos_cnt['tag'] = args.onos.split(':')[1]
400
401 onos = Onos(image = onos_cnt['image'], tag = onos_cnt['tag'], boot_delay = 60)
402 onos_ip = onos.ip()
403
404 ##Start Radius container if specified
405 if args.radius:
406 radius_cnt['image'] = args.radius.split(':')[0]
407 if args.radius.find(':') >= 0:
408 radius_cnt['tag'] = args.radius.split(':')[1]
409 radius = Radius(image = radius_cnt['image'], tag = radius_cnt['tag'])
410 radius_ip = radius.ip()
411 print('Started Radius server with IP %s' %radius_ip)
412 else:
413 radius_ip = None
414
415 print('Onos IP %s, Test type %s' %(onos_ip, args.test_type))
416 print('Installing ONOS app %s' %onos_app_file)
417
418 OnosCtrl.install_app(args.app)
419
420 build_cnt_image = args.build.strip()
421 if build_cnt_image:
422 CordTester.build_image(build_cnt_image)
423 nose_cnt['image']= build_cnt_image.split(':')[0]
424 if build_cnt_image.find(':') >= 0:
425 nose_cnt['tag'] = build_cnt_image.split(':')[1]
426
427 test_cnt_env = { 'ONOS_CONTROLLER_IP' : onos_ip,
428 'ONOS_AAA_IP' : radius_ip,
429 }
430 if args.olt:
Chetan Gaonker7142a342016-04-07 14:53:12 -0700431 olt_conf_test_loc = os.path.join(CordTester.sandbox_setup, 'olt_config.json')
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700432 test_cnt_env['OLT_CONFIG'] = olt_conf_test_loc
433
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700434 test_cnt = CordTester(ctlr_ip = onos_ip, image = nose_cnt['image'], tag = nose_cnt['tag'],
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700435 env = test_cnt_env,
436 rm = args.kill)
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700437 if args.start_switch or not args.olt:
438 test_cnt.start_switch()
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700439 tests = args.test_type.split('-')
440 test_cnt.run_tests(tests)
441
442if __name__ == '__main__':
443 parser = ArgumentParser(description='Cord Tester for ONOS')
444 parser.add_argument('-t', '--test-type', default=test_type_default, type=str)
445 parser.add_argument('-o', '--onos', default=onos_image_default, type=str, help='ONOS container image')
446 parser.add_argument('-r', '--radius',default='',type=str, help='Radius container image')
447 parser.add_argument('-a', '--app', default=onos_app_file, type=str, help='Cord ONOS app filename')
448 parser.add_argument('-l', '--olt', action='store_true', help='Use OLT config')
449 parser.add_argument('-c', '--cleanup', default='', type=str, help='Cleanup test containers')
450 parser.add_argument('-k', '--kill', action='store_true', help='Remove test container after tests')
451 parser.add_argument('-b', '--build', default='', type=str)
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700452 parser.add_argument('-s', '--start-switch', action='store_true', help='Start OVS')
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700453 parser.set_defaults(func=runTest)
454 args = parser.parse_args()
455 args.func(args)