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