+++ /dev/null
-
-import paramiko
-import subprocess
-
-from charmhelpers.core.hookenv import config
-
-
-class NetNS(object):
- def __init__(self, name):
- pass
-
- @classmethod
- def create(cls, name):
- # @TODO: Need to check if namespace exists already
- try:
- ip('netns', 'add', name)
- except Exception as e:
- raise Exception('could not create net namespace: %s' % e)
-
- return cls(name)
-
- def up(self, iface, cidr):
- self.do('ip', 'link', 'set', 'dev', iface, 'up')
- self.do('ip', 'address', 'add', cidr, 'dev', iface)
-
- def add_iface(self, iface):
- ip('link', 'set', 'dev', iface, 'netns', self.name)
-
- def do(self, *cmd):
- ip(*['netns', 'exec', self.name] + cmd)
-
-
-def ip(*args):
- return _run(['ip'] + list(args))
-
-
-def _run(cmd, env=None):
- if isinstance(cmd, str):
- cmd = cmd.split() if ' ' in cmd else [cmd]
-
- cfg = config()
- if all(k in cfg for k in ['pass', 'vpe-router', 'user']):
- router = cfg['vpe-router']
- user = cfg['user']
- passwd = cfg['pass']
-
- if router and user and passwd:
- return ssh(cmd, router, user, passwd)
-
- p = subprocess.Popen(cmd,
- env=env,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- stdout, stderr = p.communicate()
- retcode = p.poll()
- if retcode > 0:
- raise subprocess.CalledProcessError(returncode=retcode,
- cmd=cmd,
- output=stderr.decode("utf-8").strip())
- return (''.join(stdout), ''.join(stderr))
-
-
-def ssh(cmd, host, user, password=None):
- ''' Suddenly this project needs to SSH to something. So we replicate what
- _run was doing with subprocess using the Paramiko library. This is
- temporary until this charm /is/ the VPE Router '''
-
- cmds = ' '.join(cmd)
- client = paramiko.SSHClient()
- client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- client.connect(host, port=22, username=user, password=password)
-
- stdin, stdout, stderr = client.exec_command(cmds)
- retcode = stdout.channel.recv_exit_status()
- client.close() # @TODO re-use connections
- if retcode > 0:
- output = stderr.read().strip()
- raise subprocess.CalledProcessError(returncode=retcode, cmd=cmd,
- output=output)
- return (''.join(stdout), ''.join(stderr))
+++ /dev/null
-
-from charmhelpers.core.hookenv import (
- config,
- status_set,
- action_get,
- action_fail,
- log,
-)
-
-from charms.reactive import (
- hook,
- when,
- when_not,
- helpers,
- set_state,
- remove_state,
-)
-
-from charms import router
-import subprocess
-
-cfg = config()
-
-
-@hook('config-changed')
-def validate_config():
- try:
- """
- If the ssh credentials are available, we'll act as a proxy charm.
- Otherwise, we execute against the unit we're deployed on to.
- """
- if all(k in cfg for k in ['pass', 'vpe-router', 'user']):
- routerip = cfg['vpe-router']
- user = cfg['user']
- passwd = cfg['pass']
-
- if routerip and user and passwd:
- # Assumption: this will be a root user
- out, err = router.ssh(['whoami'], routerip,
- user, passwd)
- if out.strip() != user:
- remove_state('vpe.configured')
- status_set('blocked', 'vpe is not configured')
- raise Exception('invalid credentials')
-
- # Set the router's hostname
- try:
- if user == 'root' and 'hostname' in cfg:
- hostname = cfg['hostname']
- out, err = router.ssh(['hostname', hostname],
- routerip,
- user, passwd)
- out, err = router.ssh(['sed',
- '-i',
- '"s/hostname.*$/hostname %s/"'
- % hostname,
- '/usr/admin/global/hostname.sh'
- ],
- routerip,
- user, passwd)
- set_state('vpe.configured')
- status_set('active', 'ready!')
- else:
- remove_state('vpe.configured')
- status_set('blocked', 'vpe is not configured')
- except subprocess.CalledProcessError as e:
- remove_state('vpe.configured')
- status_set('blocked', 'validation failed: %s' % e)
- log('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
- raise
-
- except Exception as e:
- log(repr(e))
- remove_state('vpe.configured')
- status_set('blocked', 'validation failed: %s' % e)
-
-
-@when_not('vpe.configured')
-def not_ready_add():
- actions = [
- 'vpe.add-corporation',
- 'vpe.connect-domains',
- 'vpe.delete-domain-connections',
- 'vpe.remove-corporation',
- 'vpe.configure-interface',
- 'vpe.configure-ospf',
- ]
-
- if helpers.any_states(*actions):
- action_fail('VPE is not configured')
-
- status_set('blocked', 'vpe is not configured')
-
-
-def start_ospfd():
- # We may want to make this configurable via config setting
- ospfd = '/usr/local/bin/ospfd'
-
- try:
- (stdout, stderr) = router._run(['touch',
- '/usr/admin/global/ospfd.conf'])
- (stdout, stderr) = router._run([ospfd, '-d', '-f',
- '/usr/admin/global/ospfd.conf'])
- except subprocess.CalledProcessError as e:
- log('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
-
-
-def configure_ospf(domain, cidr, area, subnet_cidr, subnet_area, enable=True):
- """Configure the OSPF service"""
-
- # Check to see if the OSPF daemon is running, and start it if not
- try:
- (stdout, stderr) = router._run(['pgrep', 'ospfd'])
- except subprocess.CalledProcessError as e:
- # If pgrep fails, the process wasn't found.
- start_ospfd()
- log('Command failed (ospfd not running): %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
-
- upordown = ''
- if not enable:
- upordown = 'no'
- try:
- vrfctl = '/usr/local/bin/vrfctl'
- vtysh = '/usr/local/bin/vtysh'
-
- (stdout, stderr) = router._run([vrfctl, 'list'])
-
- domain_id = 0
- for line in stdout.split('\n'):
- if domain in line:
- domain_id = int(line[3:5])
-
- if domain_id > 0:
- router._run([vtysh,
- '-c',
- '"configure terminal"',
- '-c',
- '"router ospf %d vr %d"' % (domain_id, domain_id),
- '-c',
- '"%s network %s area %s"' % (upordown, cidr, area),
- '-c',
- '"%s network %s area %s"' % (upordown,
- subnet_cidr,
- subnet_area),
- ])
-
- else:
- log("Invalid domain id")
- except subprocess.CalledProcessError as e:
- action_fail('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
- finally:
- remove_state('vpe.configure-interface')
- status_set('active', 'ready!')
-
-
-@when('vpe.configured')
-@when('vpe.configure-interface')
-def configure_interface():
- """
- Configure an ethernet interface
- """
- iface_name = action_get('iface-name')
- cidr = action_get('cidr')
-
- # cidr is optional
- if cidr:
- try:
- # Add may fail, but change seems to add or update
- router.ip('address', 'change', cidr, 'dev', iface_name)
- except subprocess.CalledProcessError as e:
- action_fail('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
- return
- finally:
- remove_state('vpe.configure-interface')
- status_set('active', 'ready!')
-
- try:
- router.ip('link', 'set', 'dev', iface_name, 'up')
- except subprocess.CalledProcessError as e:
- action_fail('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
- finally:
- remove_state('vpe.configure-interface')
- status_set('active', 'ready!')
-
-
-@when('vpe.configured')
-@when('vpe.add-corporation')
-def add_corporation():
- '''
- Create and Activate the network corporation
- '''
- domain_name = action_get('domain-name')
- iface_name = action_get('iface-name')
- # HACK: python's list, used deeper, throws an exception on ints in a tuple
- vlan_id = str(action_get('vlan-id'))
- cidr = action_get('cidr')
- area = action_get('area')
- subnet_cidr = action_get('subnet-cidr')
- subnet_area = action_get('subnet-area')
-
- iface_vlanid = '%s.%s' % (iface_name, vlan_id)
-
- status_set('maintenance', 'adding corporation {}'.format(domain_name))
-
- """
- Attempt to run all commands to add the network corporation. If any step
- fails, abort and call `delete_corporation()` to undo.
- """
- try:
- """
- $ ip link add link eth3 name eth3.103 type vlan id 103
- """
- router.ip('link',
- 'add',
- 'link',
- iface_name,
- 'name',
- iface_vlanid,
- 'type',
- 'vlan',
- 'id',
- vlan_id)
-
- """
- $ ip netns add domain
- """
- router.ip('netns',
- 'add',
- domain_name)
-
- """
- $ ip link set dev eth3.103 netns corpB
- """
- router.ip('link',
- 'set',
- 'dev',
- iface_vlanid,
- 'netns',
- domain_name)
-
- """
- $ ifconfig eth3 up
- """
- router._run(['ifconfig', iface_name, 'up'])
-
- """
- $ ip netns exec corpB ip link set dev eth3.103 up
- """
- router.ip('netns',
- 'exec',
- domain_name,
- 'ip',
- 'link',
- 'set',
- 'dev',
- iface_vlanid,
- 'up')
-
- """
- $ ip netns exec corpB ip address add 10.0.1.1/24 dev eth3.103
- """
- mask = cidr.split("/")[1]
- ip = '%s/%s' % (area, mask)
- router.ip('netns',
- 'exec',
- domain_name,
- 'ip',
- 'address',
- 'add',
- ip,
- 'dev',
- iface_vlanid)
-
- configure_ospf(domain_name, cidr, area, subnet_cidr, subnet_area, True)
-
- except subprocess.CalledProcessError as e:
- delete_corporation()
- action_fail('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
- finally:
- remove_state('vpe.add-corporation')
- status_set('active', 'ready!')
-
-
-@when('vpe.configured')
-@when('vpe.delete-corporation')
-def delete_corporation():
-
- domain_name = action_get('domain-name')
- cidr = action_get('cidr')
- area = action_get('area')
- subnet_cidr = action_get('subnet-cidr')
- subnet_area = action_get('subnet-area')
-
- status_set('maintenance', 'deleting corporation {}'.format(domain_name))
-
- try:
- """
- Remove all tunnels defined for this domain
-
- $ ip netns exec domain_name ip tun show
- | grep gre
- | grep -v "remote any"
- | cut -d":" -f1
- """
- p = router.ip(
- 'netns',
- 'exec',
- domain_name,
- 'ip',
- 'tun',
- 'show',
- '|',
- 'grep',
- 'gre',
- '|',
- 'grep',
- '-v',
- '"remote any"',
- '|',
- 'cut -d":" -f1'
- )
-
- # `p` should be a tuple of (stdout, stderr)
- tunnels = p[0].split('\n')
-
- for tunnel in tunnels:
- try:
- """
- $ ip netns exec domain_name ip link set $tunnel_name down
- """
- router.ip(
- 'netns',
- 'exec',
- domain_name,
- 'ip',
- 'link',
- 'set',
- tunnel,
- 'down'
- )
- except subprocess.CalledProcessError as e:
- log('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
- pass
-
- try:
- """
- $ ip netns exec domain_name ip tunnel del $tunnel_name
- """
- router.ip(
- 'netns',
- 'exec',
- domain_name,
- 'ip',
- 'tunnel',
- 'del',
- tunnel
- )
- except subprocess.CalledProcessError as e:
- log('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
- pass
-
- """
- Remove all interfaces associated to the domain
-
- $ ip netns exec domain_name ifconfig | grep mtu | cut -d":" -f1
- """
- p = router.ip(
- 'netns',
- 'exec',
- domain_name,
- 'ifconfig',
- '|',
- 'grep mtu',
- '|',
- 'cut -d":" -f1'
- )
-
- ifaces = p[0].split('\n')
- for iface in ifaces:
-
- try:
- """
- $ ip netns exec domain_name ip link set $iface down
- """
- router.ip(
- 'netns',
- 'exec',
- domain_name,
- 'ip',
- 'link',
- 'set',
- iface,
- 'down'
- )
- except subprocess.CalledProcessError as e:
- log('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
-
- try:
- """
- $ ifconfig eth3 down
- """
- router._run(['ifconfig', iface, 'down'])
- except subprocess.CalledProcessError as e:
- log('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
- pass
-
- try:
- """
- $ ip link del dev $iface
- """
- router.ip(
- 'link',
- 'del',
- 'dev',
- iface
- )
- except subprocess.CalledProcessError as e:
- log('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
- pass
-
- try:
- """
- Remove the domain
-
- $ ip netns del domain_name
- """
- router.ip(
- 'netns',
- 'del',
- domain_name
- )
- except subprocess.CalledProcessError as e:
- log('Command failed: %s (%s)' % (' '.join(e.cmd), str(e.output)))
- pass
-
- try:
- configure_ospf(domain_name,
- cidr,
- area,
- subnet_cidr,
- subnet_area,
- False)
- except subprocess.CalledProcessError as e:
- action_fail('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
-
- except:
- # Do nothing
- log('delete-corporation failed.')
- pass
-
- finally:
- remove_state('vpe.delete-corporation')
- status_set('active', 'ready!')
-
-
-@when('vpe.configured')
-@when('vpe.connect-domains')
-def connect_domains():
-
- params = [
- 'domain-name',
- 'iface-name',
- 'tunnel-name',
- 'local-ip',
- 'remote-ip',
- 'tunnel-key',
- 'internal-local-ip',
- 'internal-remote-ip',
- 'tunnel-type',
- ]
-
- config = {}
- for p in params:
- config[p] = action_get(p)
-
- status_set('maintenance', 'connecting domains')
-
- try:
- """
- $ ip tunnel add tunnel_name mode gre local local_ip remote remote_ip
- dev iface_name key tunnel_key csum
- """
- router.ip(
- 'tunnel',
- 'add',
- config['tunnel-name'],
- 'mode',
- config['tunnel-type'],
- 'local',
- config['local-ip'],
- 'remote',
- config['remote-ip'],
- 'dev',
- config['iface-name'],
- 'key',
- config['tunnel-key'],
- 'csum'
- )
-
- except subprocess.CalledProcessError as e:
- log('Command failed (retrying with ip tunnel change): %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
- try:
- """
- If the tunnel already exists (like gre0) and can't be deleted,
- modify it instead of trying to add it.
- """
- router.ip(
- 'tunnel',
- 'change',
- config['tunnel-name'],
- 'mode',
- config['tunnel-type'],
- 'local',
- config['local-ip'],
- 'remote',
- config['remote-ip'],
- 'dev',
- config['iface-name'],
- 'key',
- config['tunnel-key'],
- 'csum'
- )
- except subprocess.CalledProcessError as e:
- delete_domain_connection()
- action_fail('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
- finally:
- remove_state('vpe.connect-domains')
- status_set('active', 'ready!')
-
- try:
- """
- $ ip link set dev tunnel_name netns domain_name
- """
- router.ip(
- 'link',
- 'set',
- 'dev',
- config['tunnel-name'],
- 'netns',
- config['domain-name']
- )
-
- """
- $ ip netns exec domain_name ip link set dev tunnel_name up
- """
- router.ip(
- 'netns',
- 'exec',
- config['domain-name'],
- 'ip',
- 'link',
- 'set',
- 'dev',
- config['tunnel-name'],
- 'up'
- )
-
- """
- $ ip netns exec domain_name ip address add internal_local_ip peer
- internal_remote_ip dev tunnel_name
- """
- router.ip(
- 'netns',
- 'exec',
- config['domain-name'],
- 'ip',
- 'address',
- 'add',
- config['internal-local-ip'],
- 'peer',
- config['internal-remote-ip'],
- 'dev',
- config['tunnel-name']
- )
- except subprocess.CalledProcessError as e:
- delete_domain_connection()
- action_fail('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
- finally:
- remove_state('vpe.connect-domains')
- status_set('active', 'ready!')
-
-
-@when('vpe.configured')
-@when('vpe.delete-domain-connection')
-def delete_domain_connection():
- ''' Remove the tunnel to another router where the domain is present '''
- domain = action_get('domain-name')
- tunnel_name = action_get('tunnel-name')
-
- status_set('maintenance', 'deleting domain connection: {}'.format(domain))
-
- try:
-
- try:
- """
- $ ip netns exec domain_name ip link set tunnel_name down
- """
- router.ip('netns',
- 'exec',
- domain,
- 'ip',
- 'link',
- 'set',
- tunnel_name,
- 'down')
- except subprocess.CalledProcessError as e:
- action_fail('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
-
- try:
- """
- $ ip netns exec domain_name ip tunnel del tunnel_name
- """
- router.ip('netns',
- 'exec',
- domain,
- 'ip',
- 'tunnel',
- 'del',
- tunnel_name)
- except subprocess.CalledProcessError as e:
- action_fail('Command failed: %s (%s)' %
- (' '.join(e.cmd), str(e.output)))
- except:
- pass
- finally:
- remove_state('vpe.delete-domain-connection')
- status_set('active', 'ready!')