2 from charmhelpers.core.hookenv import (
10 from charms.reactive import (
19 from charms import router
25 @hook('config-changed')
26 def validate_config():
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.
32 if all(k in cfg for k in ['pass', 'vpe-router', 'user']):
33 routerip = cfg['vpe-router']
37 if routerip and user and passwd:
38 # Assumption: this will be a root user
39 out, err = router.ssh(['whoami'], routerip,
41 if out.strip() != user:
42 raise Exception('invalid credentials')
44 # Set the router's hostname
46 if user == 'root' and 'hostname' in cfg:
47 hostname = cfg['hostname']
48 out, err = router.ssh(['hostname', hostname],
51 out, err = router.ssh(['sed',
53 '"s/hostname.*$/hostname %s/"'
55 '/usr/admin/global/hostname.sh'
60 except subprocess.CalledProcessError as e:
61 log('Command failed: %s (%s)' %
62 (' '.join(e.cmd), str(e.output)))
65 set_state('vpe.configured')
66 status_set('active', 'ready!')
68 except Exception as e:
70 remove_state('vpe.configured')
71 status_set('blocked', 'validation failed: %s' % e)
74 @when_not('vpe.configured')
77 'vpe.add-corporation',
78 'vpe.connect-domains',
79 'vpe.delete-domain-connections',
80 'vpe.remove-corporation',
81 'vpe.configure-interface',
85 if helpers.any_states(*actions):
86 action_fail('VPE is not configured')
88 status_set('blocked', 'vpe is not configured')
92 # We may want to make this configurable via config setting
93 ospfd = '/usr/local/bin/ospfd'
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)))
105 def configure_ospf(domain, cidr, area, subnet_cidr, subnet_area, enable=True):
106 """Configure the OSPF service"""
108 # Check to see if the OSPF daemon is running, and start it if not
110 (stdout, stderr) = router._run(['pgrep', 'ospfd'])
111 except subprocess.CalledProcessError as e:
112 # If pgrep fails, the process wasn't found.
114 log('Command failed (ospfd not running): %s (%s)' %
115 (' '.join(e.cmd), str(e.output)))
121 vrfctl = '/usr/local/bin/vrfctl'
122 vtysh = '/usr/local/bin/vtysh'
124 (stdout, stderr) = router._run([vrfctl, 'list'])
127 for line in stdout.split('\n'):
129 domain_id = int(line[3:5])
134 '"configure terminal"',
136 '"router ospf %d vr %d"' % (domain_id, domain_id),
138 '"%s network %s area %s"' % (upordown, cidr, area),
140 '"%s network %s area %s"' % (upordown,
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)))
151 remove_state('vpe.configure-interface')
152 status_set('active', 'ready!')
155 @when('vpe.configured')
156 @when('vpe.configure-interface')
157 def configure_interface():
159 Configure an ethernet interface
161 iface_name = action_get('iface-name')
162 cidr = action_get('cidr')
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)))
174 remove_state('vpe.configure-interface')
175 status_set('active', 'ready!')
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)))
183 remove_state('vpe.configure-interface')
184 status_set('active', 'ready!')
187 @when('vpe.configured')
188 @when('vpe.add-corporation')
189 def add_corporation():
191 Create and Activate the network corporation
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')
202 iface_vlanid = '%s.%s' % (iface_name, vlan_id)
204 status_set('maintenance', 'adding corporation {}'.format(domain_name))
207 Attempt to run all commands to add the network corporation. If any step
208 fails, abort and call `delete_corporation()` to undo.
212 $ ip link add link eth3 name eth3.103 type vlan id 103
226 $ ip netns add domain
233 $ ip link set dev eth3.103 netns corpB
245 router._run(['ifconfig', iface_name, 'up'])
248 $ ip netns exec corpB ip link set dev eth3.103 up
261 $ ip netns exec corpB ip address add 10.0.1.1/24 dev eth3.103
263 mask = cidr.split("/")[1]
264 ip = '%s/%s' % (area, mask)
275 configure_ospf(domain_name, cidr, area, subnet_cidr, subnet_area, True)
277 except subprocess.CalledProcessError as e:
279 action_fail('Command failed: %s (%s)' %
280 (' '.join(e.cmd), str(e.output)))
282 remove_state('vpe.add-corporation')
283 status_set('active', 'ready!')
286 @when('vpe.configured')
287 @when('vpe.delete-corporation')
288 def delete_corporation():
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')
296 status_set('maintenance', 'deleting corporation {}'.format(domain_name))
300 Remove all tunnels defined for this domain
302 $ ip netns exec domain_name ip tun show
304 | grep -v "remote any"
325 # `p` should be a tuple of (stdout, stderr)
326 tunnels = p[0].split('\n')
328 for tunnel in tunnels:
331 $ ip netns exec domain_name ip link set $tunnel_name down
343 except subprocess.CalledProcessError as e:
344 log('Command failed: %s (%s)' %
345 (' '.join(e.cmd), str(e.output)))
350 $ ip netns exec domain_name ip tunnel del $tunnel_name
361 except subprocess.CalledProcessError as e:
362 log('Command failed: %s (%s)' %
363 (' '.join(e.cmd), str(e.output)))
367 Remove all interfaces associated to the domain
369 $ ip netns exec domain_name ifconfig | grep mtu | cut -d":" -f1
382 ifaces = p[0].split('\n')
387 $ ip netns exec domain_name ip link set $iface down
399 except subprocess.CalledProcessError as e:
400 log('Command failed: %s (%s)' %
401 (' '.join(e.cmd), str(e.output)))
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)))
415 $ ip link del dev $iface
423 except subprocess.CalledProcessError as e:
424 log('Command failed: %s (%s)' %
425 (' '.join(e.cmd), str(e.output)))
432 $ ip netns del domain_name
439 except subprocess.CalledProcessError as e:
440 log('Command failed: %s (%s)' % (' '.join(e.cmd), str(e.output)))
444 configure_ospf(domain_name,
450 except subprocess.CalledProcessError as e:
451 action_fail('Command failed: %s (%s)' %
452 (' '.join(e.cmd), str(e.output)))
456 log('delete-corporation failed.')
460 remove_state('vpe.delete-corporation')
461 status_set('active', 'ready!')
464 @when('vpe.configured')
465 @when('vpe.connect-domains')
466 def connect_domains():
476 'internal-remote-ip',
482 config[p] = action_get(p)
484 status_set('maintenance', 'connecting domains')
488 $ ip tunnel add tunnel_name mode gre local local_ip remote remote_ip
489 dev iface_name key tunnel_key csum
494 config['tunnel-name'],
496 config['tunnel-type'],
502 config['iface-name'],
504 config['tunnel-key'],
508 except subprocess.CalledProcessError as e:
509 log('Command failed (retrying with ip tunnel change): %s (%s)' %
510 (' '.join(e.cmd), str(e.output)))
513 If the tunnel already exists (like gre0) and can't be deleted,
514 modify it instead of trying to add it.
519 config['tunnel-name'],
521 config['tunnel-type'],
527 config['iface-name'],
529 config['tunnel-key'],
532 except subprocess.CalledProcessError as e:
533 delete_domain_connection()
534 action_fail('Command failed: %s (%s)' %
535 (' '.join(e.cmd), str(e.output)))
537 remove_state('vpe.connect-domains')
538 status_set('active', 'ready!')
542 $ ip link set dev tunnel_name netns domain_name
548 config['tunnel-name'],
550 config['domain-name']
554 $ ip netns exec domain_name ip link set dev tunnel_name up
559 config['domain-name'],
564 config['tunnel-name'],
569 $ ip netns exec domain_name ip address add internal_local_ip peer
570 internal_remote_ip dev tunnel_name
575 config['domain-name'],
579 config['internal-local-ip'],
581 config['internal-remote-ip'],
583 config['tunnel-name']
585 except subprocess.CalledProcessError as e:
586 delete_domain_connection()
587 action_fail('Command failed: %s (%s)' %
588 (' '.join(e.cmd), str(e.output)))
590 remove_state('vpe.connect-domains')
591 status_set('active', 'ready!')
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')
601 status_set('maintenance', 'deleting domain connection: {}'.format(domain))
607 $ ip netns exec domain_name ip link set tunnel_name down
617 except subprocess.CalledProcessError as e:
618 action_fail('Command failed: %s (%s)' %
619 (' '.join(e.cmd), str(e.output)))
623 $ ip netns exec domain_name ip tunnel del tunnel_name
632 except subprocess.CalledProcessError as e:
633 action_fail('Command failed: %s (%s)' %
634 (' '.join(e.cmd), str(e.output)))
638 remove_state('vpe.delete-domain-connection')
639 status_set('active', 'ready!')