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