c62983d860a5b635ad120d87ad0a2575017e2ae4
[osm/riftware.git] /
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!')