blob: 51607342b3314855dd61ec62c6f6af66a0d7897c [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 Gaonker5209fe82016-04-19 10:09:53 -0700232 def __init__(self, ctlr_ip = None, image = 'cord-test/nose', tag = 'latest',
233 env = None, rm = False):
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700234 self.ctlr_ip = ctlr_ip
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700235 self.rm = rm
236 self.name = self.get_name()
237 super(CordTester, self).__init__(self.name, image = image, tag = tag)
238 host_config = self.create_host_config(host_guest_map = self.host_guest_map, privileged = True)
239 volumes = []
240 for h, g in self.host_guest_map:
241 volumes.append(g)
Chetan Gaonker7142a342016-04-07 14:53:12 -0700242 ##Remove test container if any
243 self.remove_container(self.name, force=True)
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700244 if env is not None and env.has_key('OLT_CONFIG'):
245 self.olt = True
Chetan Gaonker5209fe82016-04-19 10:09:53 -0700246 olt_conf_file = os.path.join(self.tester_base, 'olt_config.json')
247 olt_config = OltConfig(olt_conf_file)
248 self.port_map = olt_config.olt_port_map()
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700249 else:
250 self.olt = False
Chetan Gaonker5209fe82016-04-19 10:09:53 -0700251 self.port_map = None
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700252 print('Starting test container %s, image %s, tag %s' %(self.name, self.image, self.tag))
253 self.start(rm = False, volumes = volumes, environment = env,
254 host_config = host_config, tty = True)
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700255
256 def execute_switch(self, cmd, shell = False):
257 if self.olt:
258 return os.system(cmd)
259 return self.execute(cmd, shell = shell)
260
261 def start_switch(self, bridge = 'ovsbr0', boot_delay = 2):
262 """Start OVS"""
263 ##Determine if OVS has to be started locally or not
264 s_file,s_sandbox = ('of-bridge-local.sh',self.tester_base) if self.olt else ('of-bridge.sh',self.sandbox_setup)
265 ovs_cmd = os.path.join(s_sandbox, '{0}'.format(s_file)) + ' {0}'.format(bridge)
266 if self.olt:
267 ovs_cmd += ' {0}'.format(self.ctlr_ip)
268 print('Starting OVS on the host')
269 else:
270 print('Starting OVS on test container %s' %self.name)
271 self.execute_switch(ovs_cmd)
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700272 status = 1
273 ## Wait for the LLDP flows to be added to the switch
274 tries = 0
275 while status != 0 and tries < 100:
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700276 cmd = 'sudo ovs-ofctl dump-flows {0} | grep \"type=0x8942\"'.format(bridge)
277 status = self.execute_switch(cmd, shell = True)
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700278 tries += 1
279 if tries % 10 == 0:
280 print('Waiting for test switch to be connected to ONOS controller ...')
281
282 if status != 0:
283 print('Test Switch not connected to ONOS container.'
284 'Please remove ONOS container and restart the test')
285 if self.rm:
286 self.kill()
287 sys.exit(1)
288
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700289 if boot_delay:
290 time.sleep(boot_delay)
291
Chetan Gaonker5209fe82016-04-19 10:09:53 -0700292 def setup_intfs(self):
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700293 if not self.olt:
294 return 0
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700295 tester_intf_subnet = '192.168.100'
296 res = 0
Chetan Gaonker5209fe82016-04-19 10:09:53 -0700297 port_num = 0
298 host_intf = self.port_map['host']
299 start_vlan = self.port_map['start_vlan']
300 for port in self.port_map['ports']:
301 guest_if = port
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700302 local_if = guest_if
Chetan Gaonker5209fe82016-04-19 10:09:53 -0700303 guest_ip = '{0}.{1}/24'.format(tester_intf_subnet, str(port_num+1))
304 ##Use pipeworks to configure container interfaces on host/bridge interfaces
305 pipework_cmd = 'pipework {0} -i {1} -l {2} {3} {4}'.format(host_intf, guest_if, local_if, self.name, guest_ip)
306 if start_vlan != 0:
307 pipework_cmd += ' @{}'.format(str(start_vlan + port_num))
308
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700309 res += os.system(pipework_cmd)
Chetan Gaonker5209fe82016-04-19 10:09:53 -0700310 port_num += 1
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700311
312 return res
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700313
314 @classmethod
315 def get_name(cls):
316 cnt_name = '/{0}'.format(cls.basename)
317 cnt_name_len = len(cnt_name)
318 names = list(flatten(n['Names'] for n in cls.dckr.containers(all=True)))
319 test_names = filter(lambda n: n.startswith(cnt_name), names)
320 last_cnt_number = 0
321 if test_names:
322 last_cnt_name = reduce(lambda n1, n2: n1 if int(n1[cnt_name_len:]) > \
323 int(n2[cnt_name_len:]) else n2,
324 test_names)
325 last_cnt_number = int(last_cnt_name[cnt_name_len:])
326 test_cnt_name = cls.basename + str(last_cnt_number+1)
327 return test_cnt_name
328
329 @classmethod
330 def build_image(cls, image):
331 print('Building test container docker image %s' %image)
332 dockerfile = '''
333FROM ubuntu:14.04
334MAINTAINER chetan@ciena.com
335RUN apt-get update
336RUN apt-get -y install git python python-pip python-setuptools python-scapy tcpdump doxygen doxypy wget
337RUN easy_install nose
338RUN apt-get -y install openvswitch-common openvswitch-switch
339RUN mkdir -p /root/ovs
340WORKDIR /root
341RUN wget http://openvswitch.org/releases/openvswitch-2.4.0.tar.gz -O /root/ovs/openvswitch-2.4.0.tar.gz && \
342(cd /root/ovs && tar zxpvf openvswitch-2.4.0.tar.gz && \
343 cd openvswitch-2.4.0 && \
344 ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --disable-ssl && make && make install)
345RUN service openvswitch-switch restart || /bin/true
Chetan Gaonker3ff8eae2016-04-12 14:50:26 -0700346RUN apt-get -y install python-twisted python-sqlite sqlite3 python-pexpect
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700347RUN pip install scapy-ssl_tls
348RUN pip install -U scapy
349RUN pip install monotonic
Chetan Gaonker3ff8eae2016-04-12 14:50:26 -0700350RUN pip install configObj
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700351RUN mv /usr/sbin/tcpdump /sbin/
352RUN ln -sf /sbin/tcpdump /usr/sbin/tcpdump
353CMD ["/bin/bash"]
354'''
355 super(CordTester, cls).build_image(dockerfile, image)
356 print('Done building docker image %s' %image)
357
358 def run_tests(self, tests):
359 '''Run the list of tests'''
360 for t in tests:
361 test = t.split(':')[0]
362 if test == 'tls':
363 test_file = test + 'AuthTest.py'
364 else:
365 test_file = test + 'Test.py'
366
367 if t.find(':') >= 0:
368 test_case = test_file + ':' + t.split(':')[1]
369 else:
370 test_case = test_file
Chetan Gaonker7142a342016-04-07 14:53:12 -0700371 cmd = 'nosetests -v {0}/src/test/{1}/{2}'.format(self.sandbox, test, test_case)
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700372 status = self.execute(cmd, shell = True)
373 print('Test %s %s' %(test_case, 'Success' if status == 0 else 'Failure'))
374 print('Done running tests')
375 if self.rm:
376 print('Removing test container %s' %self.name)
377 self.kill(remove=True)
378
379
380##default onos/radius/test container images and names
381onos_image_default='onosproject/onos:latest'
382nose_image_default='cord-test/nose:latest'
383test_type_default='dhcp'
384onos_app_version = '1.0-SNAPSHOT'
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700385cord_tester_base = os.path.dirname(os.path.realpath(sys.argv[0]))
386onos_app_file = os.path.abspath('{0}/../apps/ciena-cordigmp-'.format(cord_tester_base) + onos_app_version + '.oar')
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700387zebra_quagga_config = { 'bridge' : 'quagga-br', 'ip': '10.10.0.1', 'mask': 16 }
388
389def runTest(args):
390 onos_cnt = {'tag':'latest'}
391 radius_cnt = {'tag':'latest'}
392 nose_cnt = {'image': 'cord-test/nose','tag': 'latest'}
Chetan Gaonker5209fe82016-04-19 10:09:53 -0700393 radius_ip = None
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700394 #print('Test type %s, onos %s, radius %s, app %s, olt %s, cleanup %s, kill flag %s, build image %s'
395 # %(args.test_type, args.onos, args.radius, args.app, args.olt, args.cleanup, args.kill, args.build))
396 if args.cleanup:
397 cleanup_container = args.cleanup
398 if cleanup_container.find(':') < 0:
399 cleanup_container += ':latest'
400 print('Cleaning up containers %s' %cleanup_container)
401 Container.cleanup(cleanup_container)
402 sys.exit(0)
403
Chetan Gaonker5209fe82016-04-19 10:09:53 -0700404 #don't spawn onos if the user has specified external test controller with test interface config
405 if args.test_controller:
406 ips = args.test_controller.split('/')
407 onos_ip = ips[0]
408 if len(ips) > 1:
409 radius_ip = ips[1]
410 else:
411 radius_ip = None
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700412 else:
Chetan Gaonker5209fe82016-04-19 10:09:53 -0700413 onos_cnt['image'] = args.onos.split(':')[0]
414 if args.onos.find(':') >= 0:
415 onos_cnt['tag'] = args.onos.split(':')[1]
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700416
Chetan Gaonker5209fe82016-04-19 10:09:53 -0700417 onos = Onos(image = onos_cnt['image'], tag = onos_cnt['tag'], boot_delay = 60)
418 onos_ip = onos.ip()
419
420 ##Start Radius container if specified
421 if args.radius:
422 radius_cnt['image'] = args.radius.split(':')[0]
423 if args.radius.find(':') >= 0:
424 radius_cnt['tag'] = args.radius.split(':')[1]
425 radius = Radius(image = radius_cnt['image'], tag = radius_cnt['tag'])
426 radius_ip = radius.ip()
427 print('Started Radius server with IP %s' %radius_ip)
428 else:
429 radius_ip = None
430
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700431 print('Onos IP %s, Test type %s' %(onos_ip, args.test_type))
432 print('Installing ONOS app %s' %onos_app_file)
Chetan Gaonker5209fe82016-04-19 10:09:53 -0700433 OnosCtrl.install_app(args.app, onos_ip = onos_ip)
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700434
435 build_cnt_image = args.build.strip()
436 if build_cnt_image:
437 CordTester.build_image(build_cnt_image)
438 nose_cnt['image']= build_cnt_image.split(':')[0]
439 if build_cnt_image.find(':') >= 0:
440 nose_cnt['tag'] = build_cnt_image.split(':')[1]
441
442 test_cnt_env = { 'ONOS_CONTROLLER_IP' : onos_ip,
443 'ONOS_AAA_IP' : radius_ip,
444 }
445 if args.olt:
Chetan Gaonker7142a342016-04-07 14:53:12 -0700446 olt_conf_test_loc = os.path.join(CordTester.sandbox_setup, 'olt_config.json')
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700447 test_cnt_env['OLT_CONFIG'] = olt_conf_test_loc
448
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700449 test_cnt = CordTester(ctlr_ip = onos_ip, image = nose_cnt['image'], tag = nose_cnt['tag'],
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700450 env = test_cnt_env,
451 rm = args.kill)
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700452 if args.start_switch or not args.olt:
453 test_cnt.start_switch()
Chetan Gaonker5209fe82016-04-19 10:09:53 -0700454 test_cnt.setup_intfs()
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700455 tests = args.test_type.split('-')
456 test_cnt.run_tests(tests)
457
458if __name__ == '__main__':
459 parser = ArgumentParser(description='Cord Tester for ONOS')
460 parser.add_argument('-t', '--test-type', default=test_type_default, type=str)
461 parser.add_argument('-o', '--onos', default=onos_image_default, type=str, help='ONOS container image')
462 parser.add_argument('-r', '--radius',default='',type=str, help='Radius container image')
463 parser.add_argument('-a', '--app', default=onos_app_file, type=str, help='Cord ONOS app filename')
464 parser.add_argument('-l', '--olt', action='store_true', help='Use OLT config')
Chetan Gaonker5209fe82016-04-19 10:09:53 -0700465 parser.add_argument('-e', '--test-controller', default='', type=str, help='External test controller ip for Onos and/or radius server.'
466 'Eg: 10.0.0.2/10.0.0.3 to specify ONOS and Radius ip to connect')
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700467 parser.add_argument('-c', '--cleanup', default='', type=str, help='Cleanup test containers')
468 parser.add_argument('-k', '--kill', action='store_true', help='Remove test container after tests')
469 parser.add_argument('-b', '--build', default='', type=str)
Chetan Gaonker4ca5cca2016-04-11 13:59:35 -0700470 parser.add_argument('-s', '--start-switch', action='store_true', help='Start OVS')
Chetan Gaonker93e302d2016-04-05 10:51:07 -0700471 parser.set_defaults(func=runTest)
472 args = parser.parse_args()
473 args.func(args)