blob: 2e73bc32649489b07ff7b1db1e2f0b616bcb5be6 [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
Chetan Gaonker3ff8eae2016-04-12 14:50:26 -0700340RUN apt-get -y install python-twisted python-sqlite sqlite3 python-pexpect
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700341RUN pip install scapy-ssl_tls
342RUN pip install -U scapy
343RUN pip install monotonic
Chetan Gaonker3ff8eae2016-04-12 14:50:26 -0700344RUN pip install configObj
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700345RUN mv /usr/sbin/tcpdump /sbin/
346RUN ln -sf /sbin/tcpdump /usr/sbin/tcpdump
347CMD ["/bin/bash"]
348'''
349 super(CordTester, cls).build_image(dockerfile, image)
350 print('Done building docker image %s' %image)
351
352 def run_tests(self, tests):
353 '''Run the list of tests'''
354 for t in tests:
355 test = t.split(':')[0]
356 if test == 'tls':
357 test_file = test + 'AuthTest.py'
358 else:
359 test_file = test + 'Test.py'
360
361 if t.find(':') >= 0:
362 test_case = test_file + ':' + t.split(':')[1]
363 else:
364 test_case = test_file
Chetan Gaonker7142a342016-04-07 14:53:12 -0700365 cmd = 'nosetests -v {0}/src/test/{1}/{2}'.format(self.sandbox, test, test_case)
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700366 status = self.execute(cmd, shell = True)
367 print('Test %s %s' %(test_case, 'Success' if status == 0 else 'Failure'))
368 print('Done running tests')
369 if self.rm:
370 print('Removing test container %s' %self.name)
371 self.kill(remove=True)
372
373
374##default onos/radius/test container images and names
375onos_image_default='onosproject/onos:latest'
376nose_image_default='cord-test/nose:latest'
377test_type_default='dhcp'
378onos_app_version = '1.0-SNAPSHOT'
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700379cord_tester_base = os.path.dirname(os.path.realpath(sys.argv[0]))
380onos_app_file = os.path.abspath('{0}/../apps/ciena-cordigmp-'.format(cord_tester_base) + onos_app_version + '.oar')
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700381zebra_quagga_config = { 'bridge' : 'quagga-br', 'ip': '10.10.0.1', 'mask': 16 }
382
383def runTest(args):
384 onos_cnt = {'tag':'latest'}
385 radius_cnt = {'tag':'latest'}
386 nose_cnt = {'image': 'cord-test/nose','tag': 'latest'}
387
388 #print('Test type %s, onos %s, radius %s, app %s, olt %s, cleanup %s, kill flag %s, build image %s'
389 # %(args.test_type, args.onos, args.radius, args.app, args.olt, args.cleanup, args.kill, args.build))
390 if args.cleanup:
391 cleanup_container = args.cleanup
392 if cleanup_container.find(':') < 0:
393 cleanup_container += ':latest'
394 print('Cleaning up containers %s' %cleanup_container)
395 Container.cleanup(cleanup_container)
396 sys.exit(0)
397
398 onos_cnt['image'] = args.onos.split(':')[0]
399 if args.onos.find(':') >= 0:
400 onos_cnt['tag'] = args.onos.split(':')[1]
401
402 onos = Onos(image = onos_cnt['image'], tag = onos_cnt['tag'], boot_delay = 60)
403 onos_ip = onos.ip()
404
405 ##Start Radius container if specified
406 if args.radius:
407 radius_cnt['image'] = args.radius.split(':')[0]
408 if args.radius.find(':') >= 0:
409 radius_cnt['tag'] = args.radius.split(':')[1]
410 radius = Radius(image = radius_cnt['image'], tag = radius_cnt['tag'])
411 radius_ip = radius.ip()
412 print('Started Radius server with IP %s' %radius_ip)
413 else:
414 radius_ip = None
415
416 print('Onos IP %s, Test type %s' %(onos_ip, args.test_type))
417 print('Installing ONOS app %s' %onos_app_file)
418
419 OnosCtrl.install_app(args.app)
420
421 build_cnt_image = args.build.strip()
422 if build_cnt_image:
423 CordTester.build_image(build_cnt_image)
424 nose_cnt['image']= build_cnt_image.split(':')[0]
425 if build_cnt_image.find(':') >= 0:
426 nose_cnt['tag'] = build_cnt_image.split(':')[1]
427
428 test_cnt_env = { 'ONOS_CONTROLLER_IP' : onos_ip,
429 'ONOS_AAA_IP' : radius_ip,
430 }
431 if args.olt:
Chetan Gaonker7142a342016-04-07 14:53:12 -0700432 olt_conf_test_loc = os.path.join(CordTester.sandbox_setup, 'olt_config.json')
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700433 test_cnt_env['OLT_CONFIG'] = olt_conf_test_loc
434
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700435 test_cnt = CordTester(ctlr_ip = onos_ip, image = nose_cnt['image'], tag = nose_cnt['tag'],
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700436 env = test_cnt_env,
437 rm = args.kill)
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700438 if args.start_switch or not args.olt:
439 test_cnt.start_switch()
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700440 tests = args.test_type.split('-')
441 test_cnt.run_tests(tests)
442
443if __name__ == '__main__':
444 parser = ArgumentParser(description='Cord Tester for ONOS')
445 parser.add_argument('-t', '--test-type', default=test_type_default, type=str)
446 parser.add_argument('-o', '--onos', default=onos_image_default, type=str, help='ONOS container image')
447 parser.add_argument('-r', '--radius',default='',type=str, help='Radius container image')
448 parser.add_argument('-a', '--app', default=onos_app_file, type=str, help='Cord ONOS app filename')
449 parser.add_argument('-l', '--olt', action='store_true', help='Use OLT config')
450 parser.add_argument('-c', '--cleanup', default='', type=str, help='Cleanup test containers')
451 parser.add_argument('-k', '--kill', action='store_true', help='Remove test container after tests')
452 parser.add_argument('-b', '--build', default='', type=str)
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700453 parser.add_argument('-s', '--start-switch', action='store_true', help='Start OVS')
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700454 parser.set_defaults(func=runTest)
455 args = parser.parse_args()
456 args.func(args)