blob: 1b44abf1aee212a517e277af8efb0359c97b1d62 [file] [log] [blame]
Chetan Gaonker3533faa2016-04-25 17:50:14 -07001import os,time
2import io
3import json
4from pyroute2 import IPRoute
5from itertools import chain
6from nsenter import Namespace
7from docker import Client
8from shutil import copy
9
10class docker_netns(object):
11
12 dckr = Client()
13 def __init__(self, name):
14 pid = int(self.dckr.inspect_container(name)['State']['Pid'])
15 if pid == 0:
16 raise Exception('no container named {0}'.format(name))
17 self.pid = pid
18
19 def __enter__(self):
20 pid = self.pid
21 if not os.path.exists('/var/run/netns'):
22 os.mkdir('/var/run/netns')
23 os.symlink('/proc/{0}/ns/net'.format(pid), '/var/run/netns/{0}'.format(pid))
24 return str(pid)
25
26 def __exit__(self, type, value, traceback):
27 pid = self.pid
28 os.unlink('/var/run/netns/{0}'.format(pid))
29
30flatten = lambda l: chain.from_iterable(l)
31
32class Container(object):
33 dckr = Client()
34 def __init__(self, name, image, tag = 'latest', command = 'bash', quagga_config = None):
35 self.name = name
36 self.image = image
37 self.tag = tag
38 self.image_name = image + ':' + tag
39 self.id = None
40 self.command = command
Chetan Gaonker8e25e1b2016-05-02 13:42:21 -070041 self.quagga_config = quagga_config
Chetan Gaonker3533faa2016-04-25 17:50:14 -070042
43 @classmethod
44 def build_image(cls, dockerfile, tag, force=True, nocache=False):
45 f = io.BytesIO(dockerfile.encode('utf-8'))
46 if force or not cls.image_exists(tag):
47 print('Build {0}...'.format(tag))
48 for line in cls.dckr.build(fileobj=f, rm=True, tag=tag, decode=True, nocache=nocache):
49 if 'stream' in line:
50 print(line['stream'].strip())
51
52 @classmethod
53 def image_exists(cls, name):
54 return name in [ctn['RepoTags'][0] for ctn in cls.dckr.images()]
55
56 @classmethod
57 def create_host_config(cls, port_list = None, host_guest_map = None, privileged = False):
58 port_bindings = None
59 binds = None
60 if port_list:
61 port_bindings = {}
62 for p in port_list:
63 port_bindings[str(p)] = str(p)
64
65 if host_guest_map:
66 binds = []
67 for h, g in host_guest_map:
68 binds.append('{0}:{1}'.format(h, g))
69
70 return cls.dckr.create_host_config(binds = binds, port_bindings = port_bindings, privileged = privileged)
71
72 @classmethod
73 def cleanup(cls, image):
74 cnt_list = filter(lambda c: c['Image'] == image, cls.dckr.containers())
75 for cnt in cnt_list:
76 print('Cleaning container %s' %cnt['Id'])
77 cls.dckr.kill(cnt['Id'])
78 cls.dckr.remove_container(cnt['Id'], force=True)
79
80 @classmethod
81 def remove_container(cls, name, force=True):
82 try:
83 cls.dckr.remove_container(name, force = force)
84 except: pass
85
86 def exists(self):
87 return '/{0}'.format(self.name) in list(flatten(n['Names'] for n in self.dckr.containers()))
88
89 def img_exists(self):
90 return self.image_name in [ctn['RepoTags'][0] for ctn in self.dckr.images()]
91
92 def ip(self):
93 cnt_list = filter(lambda c: c['Image'] == self.image_name, self.dckr.containers())
94 cnt_settings = cnt_list.pop()
95 return cnt_settings['NetworkSettings']['Networks']['bridge']['IPAddress']
96
97 def kill(self, remove = True):
98 self.dckr.kill(self.name)
99 self.dckr.remove_container(self.name, force=True)
100
101 def start(self, rm = True, ports = None, volumes = None, host_config = None,
102 environment = None, tty = False, stdin_open = True):
103
104 if rm and self.exists():
105 print('Removing container:', self.name)
106 self.dckr.remove_container(self.name, force=True)
107
108 ctn = self.dckr.create_container(image=self.image_name, ports = ports, command=self.command,
109 detach=True, name=self.name,
110 environment = environment,
111 volumes = volumes,
112 host_config = host_config, stdin_open=stdin_open, tty = tty)
113 self.dckr.start(container=self.name)
Chetan Gaonker8e25e1b2016-05-02 13:42:21 -0700114 if self.quagga_config:
Chetan Gaonker3533faa2016-04-25 17:50:14 -0700115 self.connect_to_br()
116 self.id = ctn['Id']
117 return ctn
118
119 def connect_to_br(self):
Chetan Gaonker8e25e1b2016-05-02 13:42:21 -0700120 index = 0
Chetan Gaonker3533faa2016-04-25 17:50:14 -0700121 with docker_netns(self.name) as pid:
Chetan Gaonker8e25e1b2016-05-02 13:42:21 -0700122 for quagga_config in self.quagga_config:
Chetan Gaonker3533faa2016-04-25 17:50:14 -0700123 ip = IPRoute()
Chetan Gaonker8e25e1b2016-05-02 13:42:21 -0700124 br = ip.link_lookup(ifname=quagga_config['bridge'])
125 if len(br) == 0:
126 ip.link_create(ifname=quagga_config['bridge'], kind='bridge')
127 br = ip.link_lookup(ifname=quagga_config['bridge'])
128 br = br[0]
129 ip.link('set', index=br, state='up')
130 ifname = '{0}-{1}'.format(self.name, index)
131 ifs = ip.link_lookup(ifname=ifname)
132 if len(ifs) > 0:
133 ip.link_remove(ifs[0])
134 peer_ifname = '{0}-{1}'.format(pid, index)
135 ip.link_create(ifname=ifname, kind='veth', peer=peer_ifname)
136 host = ip.link_lookup(ifname=ifname)[0]
137 ip.link('set', index=host, master=br)
138 ip.link('set', index=host, state='up')
139 guest = ip.link_lookup(ifname=peer_ifname)[0]
140 ip.link('set', index=guest, net_ns_fd=pid)
141 with Namespace(pid, 'net'):
142 ip = IPRoute()
143 ip.link('set', index=guest, ifname='eth{}'.format(index+1))
144 ip.addr('add', index=guest, address=quagga_config['ip'], mask=quagga_config['mask'])
145 ip.link('set', index=guest, state='up')
146 index += 1
Chetan Gaonker3533faa2016-04-25 17:50:14 -0700147
148 def execute(self, cmd, tty = True, stream = False, shell = False):
149 res = 0
150 if type(cmd) == str:
151 cmds = (cmd,)
152 else:
153 cmds = cmd
154 if shell:
155 for c in cmds:
156 res += os.system('docker exec {0} {1}'.format(self.name, c))
157 return res
158 for c in cmds:
159 i = self.dckr.exec_create(container=self.name, cmd=c, tty = tty, privileged = True)
160 self.dckr.exec_start(i['Id'], stream = stream, detach=True)
161 result = self.dckr.exec_inspect(i['Id'])
162 res += 0 if result['ExitCode'] == None else result['ExitCode']
163 return res
164
Chetan Gaonker462d9fa2016-05-03 16:39:10 -0700165def get_mem():
166 with open('/proc/meminfo', 'r') as fd:
167 meminfo = fd.readlines()
168 mem = 0
169 for m in meminfo:
170 if m.startswith('MemTotal:') or m.startswith('SwapTotal:'):
171 mem += int(m.split(':')[1].strip().split()[0])
172
173 mem = max(mem/1024/1024, 1)
Chetan Gaonker6d0a7b02016-05-03 16:57:28 -0700174 mem = min(mem, 16)
Chetan Gaonker462d9fa2016-05-03 16:39:10 -0700175 return str(mem) + 'G'
176
Chetan Gaonker3533faa2016-04-25 17:50:14 -0700177class Onos(Container):
178
Chetan Gaonker8e25e1b2016-05-02 13:42:21 -0700179 quagga_config = ( { 'bridge' : 'quagga-br', 'ip': '10.10.0.4', 'mask' : 16 }, )
Chetan Gaonker462d9fa2016-05-03 16:39:10 -0700180 SYSTEM_MEMORY = (get_mem(),) * 2
181 JAVA_OPTS = '-Xms{} -Xmx{} -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode'.format(*SYSTEM_MEMORY)#-XX:+PrintGCDetails -XX:+PrintGCTimeStamps'
Chetan Gaonker68d95172016-05-03 11:16:59 -0700182 env = { 'ONOS_APPS' : 'drivers,openflow,proxyarp,aaa,igmp,vrouter', 'JAVA_OPTS' : JAVA_OPTS }
Chetan Gaonker3533faa2016-04-25 17:50:14 -0700183 ports = [ 8181, 8101, 9876, 6653, 6633, 2000, 2620 ]
184 host_config_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'setup/onos-config')
185 guest_config_dir = '/root/onos/config'
186 host_guest_map = ( (host_config_dir, guest_config_dir), )
187
188 def __init__(self, name = 'cord-onos', image = 'onosproject/onos', tag = 'latest',
189 boot_delay = 60, restart = False, network_cfg = None):
190 if restart is True:
191 ##Find the right image to restart
192 running_image = filter(lambda c: c['Names'][0] == '/{}'.format(name), self.dckr.containers())
193 if running_image:
194 image_name = running_image[0]['Image']
195 try:
196 image = image_name.split(':')[0]
197 tag = image_name.split(':')[1]
198 except: pass
199
200 super(Onos, self).__init__(name, image, tag = tag, quagga_config = self.quagga_config)
201 if restart is True and self.exists():
202 self.kill()
203 if not self.exists():
204 self.remove_container(name, force=True)
205 host_config = self.create_host_config(port_list = self.ports,
206 host_guest_map = self.host_guest_map)
207 volumes = []
208 for _,g in self.host_guest_map:
209 volumes.append(g)
210 if network_cfg is not None:
211 json_data = json.dumps(network_cfg)
212 with open('{}/network-cfg.json'.format(self.host_config_dir), 'w') as f:
213 f.write(json_data)
214 print('Starting ONOS container %s' %self.name)
215 self.start(ports = self.ports, environment = self.env,
216 host_config = host_config, volumes = volumes, tty = True)
217 print('Waiting %d seconds for ONOS to boot' %(boot_delay))
218 time.sleep(boot_delay)
219
220class Radius(Container):
221 ports = [ 1812, 1813 ]
222 env = {'TIMEZONE':'America/Los_Angeles',
223 'DEBUG': 'true', 'cert_password':'whatever', 'primary_shared_secret':'radius_password'
224 }
Chetan Gaonker7f4bf742016-05-04 15:56:08 -0700225 host_db_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'setup/radius-config/db')
Chetan Gaonker3533faa2016-04-25 17:50:14 -0700226 guest_db_dir = os.path.join(os.path.sep, 'opt', 'db')
Chetan Gaonker7f4bf742016-05-04 15:56:08 -0700227 host_config_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'setup/radius-config/freeradius')
Chetan Gaonker3533faa2016-04-25 17:50:14 -0700228 guest_config_dir = os.path.join(os.path.sep, 'etc', 'freeradius')
Chetan Gaonker7f4bf742016-05-04 15:56:08 -0700229 start_command = os.path.join(guest_config_dir, 'start-radius.py')
Chetan Gaonker3533faa2016-04-25 17:50:14 -0700230 host_guest_map = ( (host_db_dir, guest_db_dir),
231 (host_config_dir, guest_config_dir)
232 )
Chetan Gaonker7f4bf742016-05-04 15:56:08 -0700233 def __init__(self, name = 'cord-radius', image = 'cord-test/radius', tag = 'latest',
234 boot_delay = 10, restart = False):
Chetan Gaonker3533faa2016-04-25 17:50:14 -0700235 super(Radius, self).__init__(name, image, tag = tag, command = self.start_command)
Chetan Gaonker7f4bf742016-05-04 15:56:08 -0700236 if not self.img_exists():
237 self.build_image(image)
238 if restart is True and self.exists():
239 self.kill()
Chetan Gaonker3533faa2016-04-25 17:50:14 -0700240 if not self.exists():
241 self.remove_container(name, force=True)
242 host_config = self.create_host_config(port_list = self.ports,
243 host_guest_map = self.host_guest_map)
244 volumes = []
245 for _,g in self.host_guest_map:
246 volumes.append(g)
247 self.start(ports = self.ports, environment = self.env,
248 volumes = volumes,
249 host_config = host_config, tty = True)
Chetan Gaonker7f4bf742016-05-04 15:56:08 -0700250 time.sleep(boot_delay)
251
252 @classmethod
253 def build_image(cls, image):
254 print('Building Radius image %s' %image)
255 dockerfile = '''
256FROM hbouvier/docker-radius
257MAINTAINER chetan@ciena.com
258LABEL RUN docker pull hbouvier/docker-radius
259LABEL RUN docker run -it --name cord-radius hbouvier/docker-radius
260RUN apt-get update
261RUN apt-get -y install python python-pexpect strace
262WORKDIR /root
263CMD ["/etc/freeradius/start-radius.py"]
264'''
265 super(Radius, cls).build_image(dockerfile, image)
266 print('Done building image %s' %image)
Chetan Gaonker3533faa2016-04-25 17:50:14 -0700267
Chetan Gaonker6cf6e472016-04-26 14:41:51 -0700268class Quagga(Container):
Chetan Gaonker8e25e1b2016-05-02 13:42:21 -0700269 quagga_config = ( { 'bridge' : 'quagga-br', 'ip': '10.10.0.3', 'mask' : 16 },
270 { 'bridge' : 'quagga-br', 'ip': '192.168.10.3', 'mask': 16 },
271 )
Chetan Gaonker6cf6e472016-04-26 14:41:51 -0700272 ports = [ 179, 2601, 2602, 2603, 2604, 2605, 2606 ]
273 host_quagga_config = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'setup/quagga-config')
274 guest_quagga_config = '/root/config'
275 quagga_config_file = os.path.join(guest_quagga_config, 'testrib.conf')
276 host_guest_map = ( (host_quagga_config, guest_quagga_config), )
277
278 def __init__(self, name = 'cord-quagga', image = 'cord-test/quagga', tag = 'latest',
Chetan Gaonkerfd3d6502016-05-03 13:23:07 -0700279 boot_delay = 15, restart = False, config_file = quagga_config_file):
Chetan Gaonker6cf6e472016-04-26 14:41:51 -0700280 super(Quagga, self).__init__(name, image, tag = tag, quagga_config = self.quagga_config)
281 if not self.img_exists():
282 self.build_image(image)
283 if restart is True and self.exists():
284 self.kill()
285 if not self.exists():
286 self.remove_container(name, force=True)
287 host_config = self.create_host_config(port_list = self.ports,
288 host_guest_map = self.host_guest_map,
289 privileged = True)
290 volumes = []
291 for _,g in self.host_guest_map:
292 volumes.append(g)
293 self.start(ports = self.ports,
294 host_config = host_config,
295 volumes = volumes, tty = True)
296 print('Starting Quagga on container %s' %self.name)
297 self.execute('{0}/start.sh {1}'.format(self.guest_quagga_config, config_file))
298 time.sleep(boot_delay)
299
300 @classmethod
301 def build_image(cls, image):
Chetan Gaonker2a6601b2016-05-02 17:28:26 -0700302 onos_quagga_ip = Onos.quagga_config[0]['ip']
Chetan Gaonker6cf6e472016-04-26 14:41:51 -0700303 print('Building Quagga image %s' %image)
304 dockerfile = '''
305FROM ubuntu:latest
306WORKDIR /root
307RUN useradd -M quagga
308RUN mkdir /var/log/quagga && chown quagga:quagga /var/log/quagga
309RUN mkdir /var/run/quagga && chown quagga:quagga /var/run/quagga
310RUN apt-get update && apt-get install -qy git autoconf libtool gawk make telnet libreadline6-dev
311RUN git clone git://git.sv.gnu.org/quagga.git quagga && \
312(cd quagga && git checkout HEAD && ./bootstrap.sh && \
313sed -i -r 's,htonl.*?\(INADDR_LOOPBACK\),inet_addr\("{0}"\),g' zebra/zebra_fpm.c && \
314./configure --enable-fpm --disable-doc --localstatedir=/var/run/quagga && make && make install)
315RUN ldconfig
316'''.format(onos_quagga_ip)
317 super(Quagga, cls).build_image(dockerfile, image)
318 print('Done building image %s' %image)
319