Compiled charm published to cs:~nfv/vpe-router-1
[osm/devops.git] / builds / vpe-router / reactive / vpe_router.py
1
2 from charmhelpers.core.hookenv import (
3 config,
4 status_set,
5 action_get,
6 action_fail,
7 log,
8 )
9
10 from charms.reactive import (
11 hook,
12 when,
13 when_not,
14 helpers,
15 set_state,
16 remove_state,
17 )
18
19 from charms import router
20 import subprocess
21
22 cfg = config()
23
24
25 @hook('config-changed')
26 def validate_config():
27 try:
28 """
29 If the ssh credentials are available, we'll act as a proxy charm.
30 Otherwise, we execute against the unit we're deployed on to.
31 """
32 if all(k in cfg for k in ['pass', 'vpe-router', 'user']):
33 routerip = cfg['vpe-router']
34 user = cfg['user']
35 passwd = cfg['pass']
36
37 if routerip and user and passwd:
38 # Assumption: this will be a root user
39 out, err = router.ssh(['whoami'], routerip,
40 user, passwd)
41 if out.strip() != user:
42 remove_state('vpe.configured')
43 status_set('blocked', 'vpe is not configured')
44 raise Exception('invalid credentials')
45
46 # Set the router's hostname
47 try:
48 if user == 'root' and 'hostname' in cfg:
49 hostname = cfg['hostname']
50 out, err = router.ssh(['hostname', hostname],
51 routerip,
52 user, passwd)
53 out, err = router.ssh(['sed',
54 '-i',
55 '"s/hostname.*$/hostname %s/"'
56 % hostname,
57 '/usr/admin/global/hostname.sh'
58 ],
59 routerip,
60 user, passwd)
61 set_state('vpe.configured')
62 status_set('active', 'ready!')
63 else:
64 remove_state('vpe.configured')
65 status_set('blocked', 'vpe is not configured')
66 except subprocess.CalledProcessError as e:
67 remove_state('vpe.configured')
68 status_set('blocked', 'validation failed: %s' % e)
69 log('Command failed: %s (%s)' %
70 (' '.join(e.cmd), str(e.output)))
71 raise
72
73 except Exception as e:
74 log(repr(e))
75 remove_state('vpe.configured')
76 status_set('blocked', 'validation failed: %s' % e)
77
78
79 @when_not('vpe.configured')
80 def not_ready_add():
81 actions = [
82 'vpe.add-corporation',
83 'vpe.connect-domains',
84 'vpe.delete-domain-connections',
85 'vpe.remove-corporation',
86 'vpe.configure-interface',
87 'vpe.configure-ospf',
88 ]
89
90 if helpers.any_states(*actions):
91 action_fail('VPE is not configured')
92
93 status_set('blocked', 'vpe is not configured')
94
95
96 def start_ospfd():
97 # We may want to make this configurable via config setting
98 ospfd = '/usr/local/bin/ospfd'
99
100 try:
101 (stdout, stderr) = router._run(['touch',
102 '/usr/admin/global/ospfd.conf'])
103 (stdout, stderr) = router._run([ospfd, '-d', '-f',
104 '/usr/admin/global/ospfd.conf'])
105 except subprocess.CalledProcessError as e:
106 log('Command failed: %s (%s)' %
107 (' '.join(e.cmd), str(e.output)))
108
109
110 def configure_ospf(domain, cidr, area, subnet_cidr, subnet_area, enable=True):
111 """Configure the OSPF service"""
112
113 # Check to see if the OSPF daemon is running, and start it if not
114 try:
115 (stdout, stderr) = router._run(['pgrep', 'ospfd'])
116 except subprocess.CalledProcessError as e:
117 # If pgrep fails, the process wasn't found.
118 start_ospfd()
119 log('Command failed (ospfd not running): %s (%s)' %
120 (' '.join(e.cmd), str(e.output)))
121
122 upordown = ''
123 if not enable:
124 upordown = 'no'
125 try:
126 vrfctl = '/usr/local/bin/vrfctl'
127 vtysh = '/usr/local/bin/vtysh'
128
129 (stdout, stderr) = router._run([vrfctl, 'list'])
130
131 domain_id = 0
132 for line in stdout.split('\n'):
133 if domain in line:
134 domain_id = int(line[3:5])
135
136 if domain_id > 0:
137 router._run([vtysh,
138 '-c',
139 '"configure terminal"',
140 '-c',
141 '"router ospf %d vr %d"' % (domain_id, domain_id),
142 '-c',
143 '"%s network %s area %s"' % (upordown, cidr, area),
144 '-c',
145 '"%s network %s area %s"' % (upordown,
146 subnet_cidr,
147 subnet_area),
148 ])
149
150 else:
151 log("Invalid domain id")
152 except subprocess.CalledProcessError as e:
153 action_fail('Command failed: %s (%s)' %
154 (' '.join(e.cmd), str(e.output)))
155 finally:
156 remove_state('vpe.configure-interface')
157 status_set('active', 'ready!')
158
159
160 @when('vpe.configured')
161 @when('vpe.configure-interface')
162 def configure_interface():
163 """
164 Configure an ethernet interface
165 """
166 iface_name = action_get('iface-name')
167 cidr = action_get('cidr')
168
169 # cidr is optional
170 if cidr:
171 try:
172 # Add may fail, but change seems to add or update
173 router.ip('address', 'change', cidr, 'dev', iface_name)
174 except subprocess.CalledProcessError as e:
175 action_fail('Command failed: %s (%s)' %
176 (' '.join(e.cmd), str(e.output)))
177 return
178 finally:
179 remove_state('vpe.configure-interface')
180 status_set('active', 'ready!')
181
182 try:
183 router.ip('link', 'set', 'dev', iface_name, 'up')
184 except subprocess.CalledProcessError as e:
185 action_fail('Command failed: %s (%s)' %
186 (' '.join(e.cmd), str(e.output)))
187 finally:
188 remove_state('vpe.configure-interface')
189 status_set('active', 'ready!')
190
191
192 @when('vpe.configured')
193 @when('vpe.add-corporation')
194 def add_corporation():
195 '''
196 Create and Activate the network corporation
197 '''
198 domain_name = action_get('domain-name')
199 iface_name = action_get('iface-name')
200 # HACK: python's list, used deeper, throws an exception on ints in a tuple
201 vlan_id = str(action_get('vlan-id'))
202 cidr = action_get('cidr')
203 area = action_get('area')
204 subnet_cidr = action_get('subnet-cidr')
205 subnet_area = action_get('subnet-area')
206
207 iface_vlanid = '%s.%s' % (iface_name, vlan_id)
208
209 status_set('maintenance', 'adding corporation {}'.format(domain_name))
210
211 """
212 Attempt to run all commands to add the network corporation. If any step
213 fails, abort and call `delete_corporation()` to undo.
214 """
215 try:
216 """
217 $ ip link add link eth3 name eth3.103 type vlan id 103
218 """
219 router.ip('link',
220 'add',
221 'link',
222 iface_name,
223 'name',
224 iface_vlanid,
225 'type',
226 'vlan',
227 'id',
228 vlan_id)
229
230 """
231 $ ip netns add domain
232 """
233 router.ip('netns',
234 'add',
235 domain_name)
236
237 """
238 $ ip link set dev eth3.103 netns corpB
239 """
240 router.ip('link',
241 'set',
242 'dev',
243 iface_vlanid,
244 'netns',
245 domain_name)
246
247 """
248 $ ifconfig eth3 up
249 """
250 router._run(['ifconfig', iface_name, 'up'])
251
252 """
253 $ ip netns exec corpB ip link set dev eth3.103 up
254 """
255 router.ip('netns',
256 'exec',
257 domain_name,
258 'ip',
259 'link',
260 'set',
261 'dev',
262 iface_vlanid,
263 'up')
264
265 """
266 $ ip netns exec corpB ip address add 10.0.1.1/24 dev eth3.103
267 """
268 mask = cidr.split("/")[1]
269 ip = '%s/%s' % (area, mask)
270 router.ip('netns',
271 'exec',
272 domain_name,
273 'ip',
274 'address',
275 'add',
276 ip,
277 'dev',
278 iface_vlanid)
279
280 configure_ospf(domain_name, cidr, area, subnet_cidr, subnet_area, True)
281
282 except subprocess.CalledProcessError as e:
283 delete_corporation()
284 action_fail('Command failed: %s (%s)' %
285 (' '.join(e.cmd), str(e.output)))
286 finally:
287 remove_state('vpe.add-corporation')
288 status_set('active', 'ready!')
289
290
291 @when('vpe.configured')
292 @when('vpe.delete-corporation')
293 def delete_corporation():
294
295 domain_name = action_get('domain-name')
296 cidr = action_get('cidr')
297 area = action_get('area')
298 subnet_cidr = action_get('subnet-cidr')
299 subnet_area = action_get('subnet-area')
300
301 status_set('maintenance', 'deleting corporation {}'.format(domain_name))
302
303 try:
304 """
305 Remove all tunnels defined for this domain
306
307 $ ip netns exec domain_name ip tun show
308 | grep gre
309 | grep -v "remote any"
310 | cut -d":" -f1
311 """
312 p = router.ip(
313 'netns',
314 'exec',
315 domain_name,
316 'ip',
317 'tun',
318 'show',
319 '|',
320 'grep',
321 'gre',
322 '|',
323 'grep',
324 '-v',
325 '"remote any"',
326 '|',
327 'cut -d":" -f1'
328 )
329
330 # `p` should be a tuple of (stdout, stderr)
331 tunnels = p[0].split('\n')
332
333 for tunnel in tunnels:
334 try:
335 """
336 $ ip netns exec domain_name ip link set $tunnel_name down
337 """
338 router.ip(
339 'netns',
340 'exec',
341 domain_name,
342 'ip',
343 'link',
344 'set',
345 tunnel,
346 'down'
347 )
348 except subprocess.CalledProcessError as e:
349 log('Command failed: %s (%s)' %
350 (' '.join(e.cmd), str(e.output)))
351 pass
352
353 try:
354 """
355 $ ip netns exec domain_name ip tunnel del $tunnel_name
356 """
357 router.ip(
358 'netns',
359 'exec',
360 domain_name,
361 'ip',
362 'tunnel',
363 'del',
364 tunnel
365 )
366 except subprocess.CalledProcessError as e:
367 log('Command failed: %s (%s)' %
368 (' '.join(e.cmd), str(e.output)))
369 pass
370
371 """
372 Remove all interfaces associated to the domain
373
374 $ ip netns exec domain_name ifconfig | grep mtu | cut -d":" -f1
375 """
376 p = router.ip(
377 'netns',
378 'exec',
379 domain_name,
380 'ifconfig',
381 '|',
382 'grep mtu',
383 '|',
384 'cut -d":" -f1'
385 )
386
387 ifaces = p[0].split('\n')
388 for iface in ifaces:
389
390 try:
391 """
392 $ ip netns exec domain_name ip link set $iface down
393 """
394 router.ip(
395 'netns',
396 'exec',
397 domain_name,
398 'ip',
399 'link',
400 'set',
401 iface,
402 'down'
403 )
404 except subprocess.CalledProcessError as e:
405 log('Command failed: %s (%s)' %
406 (' '.join(e.cmd), str(e.output)))
407
408 try:
409 """
410 $ ifconfig eth3 down
411 """
412 router._run(['ifconfig', iface, 'down'])
413 except subprocess.CalledProcessError as e:
414 log('Command failed: %s (%s)' %
415 (' '.join(e.cmd), str(e.output)))
416 pass
417
418 try:
419 """
420 $ ip link del dev $iface
421 """
422 router.ip(
423 'link',
424 'del',
425 'dev',
426 iface
427 )
428 except subprocess.CalledProcessError as e:
429 log('Command failed: %s (%s)' %
430 (' '.join(e.cmd), str(e.output)))
431 pass
432
433 try:
434 """
435 Remove the domain
436
437 $ ip netns del domain_name
438 """
439 router.ip(
440 'netns',
441 'del',
442 domain_name
443 )
444 except subprocess.CalledProcessError as e:
445 log('Command failed: %s (%s)' % (' '.join(e.cmd), str(e.output)))
446 pass
447
448 try:
449 configure_ospf(domain_name,
450 cidr,
451 area,
452 subnet_cidr,
453 subnet_area,
454 False)
455 except subprocess.CalledProcessError as e:
456 action_fail('Command failed: %s (%s)' %
457 (' '.join(e.cmd), str(e.output)))
458
459 except:
460 # Do nothing
461 log('delete-corporation failed.')
462 pass
463
464 finally:
465 remove_state('vpe.delete-corporation')
466 status_set('active', 'ready!')
467
468
469 @when('vpe.configured')
470 @when('vpe.connect-domains')
471 def connect_domains():
472
473 params = [
474 'domain-name',
475 'iface-name',
476 'tunnel-name',
477 'local-ip',
478 'remote-ip',
479 'tunnel-key',
480 'internal-local-ip',
481 'internal-remote-ip',
482 'tunnel-type',
483 ]
484
485 config = {}
486 for p in params:
487 config[p] = action_get(p)
488
489 status_set('maintenance', 'connecting domains')
490
491 try:
492 """
493 $ ip tunnel add tunnel_name mode gre local local_ip remote remote_ip
494 dev iface_name key tunnel_key csum
495 """
496 router.ip(
497 'tunnel',
498 'add',
499 config['tunnel-name'],
500 'mode',
501 config['tunnel-type'],
502 'local',
503 config['local-ip'],
504 'remote',
505 config['remote-ip'],
506 'dev',
507 config['iface-name'],
508 'key',
509 config['tunnel-key'],
510 'csum'
511 )
512
513 except subprocess.CalledProcessError as e:
514 log('Command failed (retrying with ip tunnel change): %s (%s)' %
515 (' '.join(e.cmd), str(e.output)))
516 try:
517 """
518 If the tunnel already exists (like gre0) and can't be deleted,
519 modify it instead of trying to add it.
520 """
521 router.ip(
522 'tunnel',
523 'change',
524 config['tunnel-name'],
525 'mode',
526 config['tunnel-type'],
527 'local',
528 config['local-ip'],
529 'remote',
530 config['remote-ip'],
531 'dev',
532 config['iface-name'],
533 'key',
534 config['tunnel-key'],
535 'csum'
536 )
537 except subprocess.CalledProcessError as e:
538 delete_domain_connection()
539 action_fail('Command failed: %s (%s)' %
540 (' '.join(e.cmd), str(e.output)))
541 finally:
542 remove_state('vpe.connect-domains')
543 status_set('active', 'ready!')
544
545 try:
546 """
547 $ ip link set dev tunnel_name netns domain_name
548 """
549 router.ip(
550 'link',
551 'set',
552 'dev',
553 config['tunnel-name'],
554 'netns',
555 config['domain-name']
556 )
557
558 """
559 $ ip netns exec domain_name ip link set dev tunnel_name up
560 """
561 router.ip(
562 'netns',
563 'exec',
564 config['domain-name'],
565 'ip',
566 'link',
567 'set',
568 'dev',
569 config['tunnel-name'],
570 'up'
571 )
572
573 """
574 $ ip netns exec domain_name ip address add internal_local_ip peer
575 internal_remote_ip dev tunnel_name
576 """
577 router.ip(
578 'netns',
579 'exec',
580 config['domain-name'],
581 'ip',
582 'address',
583 'add',
584 config['internal-local-ip'],
585 'peer',
586 config['internal-remote-ip'],
587 'dev',
588 config['tunnel-name']
589 )
590 except subprocess.CalledProcessError as e:
591 delete_domain_connection()
592 action_fail('Command failed: %s (%s)' %
593 (' '.join(e.cmd), str(e.output)))
594 finally:
595 remove_state('vpe.connect-domains')
596 status_set('active', 'ready!')
597
598
599 @when('vpe.configured')
600 @when('vpe.delete-domain-connection')
601 def delete_domain_connection():
602 ''' Remove the tunnel to another router where the domain is present '''
603 domain = action_get('domain-name')
604 tunnel_name = action_get('tunnel-name')
605
606 status_set('maintenance', 'deleting domain connection: {}'.format(domain))
607
608 try:
609
610 try:
611 """
612 $ ip netns exec domain_name ip link set tunnel_name down
613 """
614 router.ip('netns',
615 'exec',
616 domain,
617 'ip',
618 'link',
619 'set',
620 tunnel_name,
621 'down')
622 except subprocess.CalledProcessError as e:
623 action_fail('Command failed: %s (%s)' %
624 (' '.join(e.cmd), str(e.output)))
625
626 try:
627 """
628 $ ip netns exec domain_name ip tunnel del tunnel_name
629 """
630 router.ip('netns',
631 'exec',
632 domain,
633 'ip',
634 'tunnel',
635 'del',
636 tunnel_name)
637 except subprocess.CalledProcessError as e:
638 action_fail('Command failed: %s (%s)' %
639 (' '.join(e.cmd), str(e.output)))
640 except:
641 pass
642 finally:
643 remove_state('vpe.delete-domain-connection')
644 status_set('active', 'ready!')