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