PLUMgrid gateway initial charm
This commit is contained in:
commit
2e2935412a
17
.project
Normal file
17
.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>neutron-openvswitch</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.python.pydev.PyDevBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.python.pydev.pythonNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
9
.pydevproject
Normal file
9
.pydevproject
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?eclipse-pydev version="1.0"?><pydev_project>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
||||
<path>/neutron-openvswitch/hooks</path>
|
||||
<path>/neutron-openvswitch/unit_tests</path>
|
||||
</pydev_pathproperty>
|
||||
</pydev_project>
|
24
Makefile
Normal file
24
Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
#!/usr/bin/make
|
||||
PYTHON := /usr/bin/env python
|
||||
|
||||
lint:
|
||||
@flake8 --exclude hooks/charmhelpers hooks
|
||||
@flake8 --exclude hooks/charmhelpers unit_tests
|
||||
@charm proof
|
||||
|
||||
unit_test:
|
||||
@echo Starting tests...
|
||||
@$(PYTHON) /usr/bin/nosetests --nologcapture unit_tests
|
||||
|
||||
bin/charm_helpers_sync.py:
|
||||
@mkdir -p bin
|
||||
@bzr cat lp:charm-helpers/tools/charm_helpers_sync/charm_helpers_sync.py \
|
||||
> bin/charm_helpers_sync.py
|
||||
|
||||
sync: bin/charm_helpers_sync.py
|
||||
@$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-sync.yaml
|
||||
patch -p0 < ~/profsvcs/canonical/charms/charmhelpers.patch
|
||||
|
||||
publish: lint unit_test
|
||||
bzr push lp:charms/plumgrid-gateway
|
||||
bzr push lp:charms/trusty/plumgrid-gateway
|
16
README.md
Normal file
16
README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Overview
|
||||
|
||||
This charm provides the PLUMgrid Gateway configuration for a node.
|
||||
|
||||
|
||||
# Usage
|
||||
|
||||
To deploy (partial deployment of linked charms only):
|
||||
|
||||
juju deploy neutron-api
|
||||
juju deploy neutron-iovisor
|
||||
juju deploy plumgrid-director
|
||||
juju deploy plumgrid-gateway
|
||||
juju add-relation plumgrid-gateway neutron-iovisor
|
||||
juju add-relation plumgrid-gateway plumgrid-director
|
||||
|
253
bin/charm_helpers_sync.py
Normal file
253
bin/charm_helpers_sync.py
Normal file
@ -0,0 +1,253 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Copyright 2014-2015 Canonical Limited.
|
||||
#
|
||||
# This file is part of charm-helpers.
|
||||
#
|
||||
# charm-helpers is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# charm-helpers is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Authors:
|
||||
# Adam Gandelman <adamg@ubuntu.com>
|
||||
|
||||
import logging
|
||||
import optparse
|
||||
import os
|
||||
import subprocess
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import yaml
|
||||
from fnmatch import fnmatch
|
||||
|
||||
import six
|
||||
|
||||
CHARM_HELPERS_BRANCH = 'lp:charm-helpers'
|
||||
|
||||
|
||||
def parse_config(conf_file):
|
||||
if not os.path.isfile(conf_file):
|
||||
logging.error('Invalid config file: %s.' % conf_file)
|
||||
return False
|
||||
return yaml.load(open(conf_file).read())
|
||||
|
||||
|
||||
def clone_helpers(work_dir, branch):
|
||||
dest = os.path.join(work_dir, 'charm-helpers')
|
||||
logging.info('Checking out %s to %s.' % (branch, dest))
|
||||
cmd = ['bzr', 'checkout', '--lightweight', branch, dest]
|
||||
subprocess.check_call(cmd)
|
||||
return dest
|
||||
|
||||
|
||||
def _module_path(module):
|
||||
return os.path.join(*module.split('.'))
|
||||
|
||||
|
||||
def _src_path(src, module):
|
||||
return os.path.join(src, 'charmhelpers', _module_path(module))
|
||||
|
||||
|
||||
def _dest_path(dest, module):
|
||||
return os.path.join(dest, _module_path(module))
|
||||
|
||||
|
||||
def _is_pyfile(path):
|
||||
return os.path.isfile(path + '.py')
|
||||
|
||||
|
||||
def ensure_init(path):
|
||||
'''
|
||||
ensure directories leading up to path are importable, omitting
|
||||
parent directory, eg path='/hooks/helpers/foo'/:
|
||||
hooks/
|
||||
hooks/helpers/__init__.py
|
||||
hooks/helpers/foo/__init__.py
|
||||
'''
|
||||
for d, dirs, files in os.walk(os.path.join(*path.split('/')[:2])):
|
||||
_i = os.path.join(d, '__init__.py')
|
||||
if not os.path.exists(_i):
|
||||
logging.info('Adding missing __init__.py: %s' % _i)
|
||||
open(_i, 'wb').close()
|
||||
|
||||
|
||||
def sync_pyfile(src, dest):
|
||||
src = src + '.py'
|
||||
src_dir = os.path.dirname(src)
|
||||
logging.info('Syncing pyfile: %s -> %s.' % (src, dest))
|
||||
if not os.path.exists(dest):
|
||||
os.makedirs(dest)
|
||||
shutil.copy(src, dest)
|
||||
if os.path.isfile(os.path.join(src_dir, '__init__.py')):
|
||||
shutil.copy(os.path.join(src_dir, '__init__.py'),
|
||||
dest)
|
||||
ensure_init(dest)
|
||||
|
||||
|
||||
def get_filter(opts=None):
|
||||
opts = opts or []
|
||||
if 'inc=*' in opts:
|
||||
# do not filter any files, include everything
|
||||
return None
|
||||
|
||||
def _filter(dir, ls):
|
||||
incs = [opt.split('=').pop() for opt in opts if 'inc=' in opt]
|
||||
_filter = []
|
||||
for f in ls:
|
||||
_f = os.path.join(dir, f)
|
||||
|
||||
if not os.path.isdir(_f) and not _f.endswith('.py') and incs:
|
||||
if True not in [fnmatch(_f, inc) for inc in incs]:
|
||||
logging.debug('Not syncing %s, does not match include '
|
||||
'filters (%s)' % (_f, incs))
|
||||
_filter.append(f)
|
||||
else:
|
||||
logging.debug('Including file, which matches include '
|
||||
'filters (%s): %s' % (incs, _f))
|
||||
elif (os.path.isfile(_f) and not _f.endswith('.py')):
|
||||
logging.debug('Not syncing file: %s' % f)
|
||||
_filter.append(f)
|
||||
elif (os.path.isdir(_f) and not
|
||||
os.path.isfile(os.path.join(_f, '__init__.py'))):
|
||||
logging.debug('Not syncing directory: %s' % f)
|
||||
_filter.append(f)
|
||||
return _filter
|
||||
return _filter
|
||||
|
||||
|
||||
def sync_directory(src, dest, opts=None):
|
||||
if os.path.exists(dest):
|
||||
logging.debug('Removing existing directory: %s' % dest)
|
||||
shutil.rmtree(dest)
|
||||
logging.info('Syncing directory: %s -> %s.' % (src, dest))
|
||||
|
||||
shutil.copytree(src, dest, ignore=get_filter(opts))
|
||||
ensure_init(dest)
|
||||
|
||||
|
||||
def sync(src, dest, module, opts=None):
|
||||
|
||||
# Sync charmhelpers/__init__.py for bootstrap code.
|
||||
sync_pyfile(_src_path(src, '__init__'), dest)
|
||||
|
||||
# Sync other __init__.py files in the path leading to module.
|
||||
m = []
|
||||
steps = module.split('.')[:-1]
|
||||
while steps:
|
||||
m.append(steps.pop(0))
|
||||
init = '.'.join(m + ['__init__'])
|
||||
sync_pyfile(_src_path(src, init),
|
||||
os.path.dirname(_dest_path(dest, init)))
|
||||
|
||||
# Sync the module, or maybe a .py file.
|
||||
if os.path.isdir(_src_path(src, module)):
|
||||
sync_directory(_src_path(src, module), _dest_path(dest, module), opts)
|
||||
elif _is_pyfile(_src_path(src, module)):
|
||||
sync_pyfile(_src_path(src, module),
|
||||
os.path.dirname(_dest_path(dest, module)))
|
||||
else:
|
||||
logging.warn('Could not sync: %s. Neither a pyfile or directory, '
|
||||
'does it even exist?' % module)
|
||||
|
||||
|
||||
def parse_sync_options(options):
|
||||
if not options:
|
||||
return []
|
||||
return options.split(',')
|
||||
|
||||
|
||||
def extract_options(inc, global_options=None):
|
||||
global_options = global_options or []
|
||||
if global_options and isinstance(global_options, six.string_types):
|
||||
global_options = [global_options]
|
||||
if '|' not in inc:
|
||||
return (inc, global_options)
|
||||
inc, opts = inc.split('|')
|
||||
return (inc, parse_sync_options(opts) + global_options)
|
||||
|
||||
|
||||
def sync_helpers(include, src, dest, options=None):
|
||||
if not os.path.isdir(dest):
|
||||
os.makedirs(dest)
|
||||
|
||||
global_options = parse_sync_options(options)
|
||||
|
||||
for inc in include:
|
||||
if isinstance(inc, str):
|
||||
inc, opts = extract_options(inc, global_options)
|
||||
sync(src, dest, inc, opts)
|
||||
elif isinstance(inc, dict):
|
||||
# could also do nested dicts here.
|
||||
for k, v in six.iteritems(inc):
|
||||
if isinstance(v, list):
|
||||
for m in v:
|
||||
inc, opts = extract_options(m, global_options)
|
||||
sync(src, dest, '%s.%s' % (k, inc), opts)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option('-c', '--config', action='store', dest='config',
|
||||
default=None, help='helper config file')
|
||||
parser.add_option('-D', '--debug', action='store_true', dest='debug',
|
||||
default=False, help='debug')
|
||||
parser.add_option('-b', '--branch', action='store', dest='branch',
|
||||
help='charm-helpers bzr branch (overrides config)')
|
||||
parser.add_option('-d', '--destination', action='store', dest='dest_dir',
|
||||
help='sync destination dir (overrides config)')
|
||||
(opts, args) = parser.parse_args()
|
||||
|
||||
if opts.debug:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
if opts.config:
|
||||
logging.info('Loading charm helper config from %s.' % opts.config)
|
||||
config = parse_config(opts.config)
|
||||
if not config:
|
||||
logging.error('Could not parse config from %s.' % opts.config)
|
||||
sys.exit(1)
|
||||
else:
|
||||
config = {}
|
||||
|
||||
if 'branch' not in config:
|
||||
config['branch'] = CHARM_HELPERS_BRANCH
|
||||
if opts.branch:
|
||||
config['branch'] = opts.branch
|
||||
if opts.dest_dir:
|
||||
config['destination'] = opts.dest_dir
|
||||
|
||||
if 'destination' not in config:
|
||||
logging.error('No destination dir. specified as option or config.')
|
||||
sys.exit(1)
|
||||
|
||||
if 'include' not in config:
|
||||
if not args:
|
||||
logging.error('No modules to sync specified as option or config.')
|
||||
sys.exit(1)
|
||||
config['include'] = []
|
||||
[config['include'].append(a) for a in args]
|
||||
|
||||
sync_options = None
|
||||
if 'options' in config:
|
||||
sync_options = config['options']
|
||||
tmpd = tempfile.mkdtemp()
|
||||
try:
|
||||
checkout = clone_helpers(tmpd, config['branch'])
|
||||
sync_helpers(config['include'], checkout, config['destination'],
|
||||
options=sync_options)
|
||||
except Exception as e:
|
||||
logging.error("Could not sync: %s" % e)
|
||||
raise e
|
||||
finally:
|
||||
logging.debug('Cleaning up %s' % tmpd)
|
||||
shutil.rmtree(tmpd)
|
12
charm-helpers-sync.yaml
Normal file
12
charm-helpers-sync.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
branch: lp:charm-helpers
|
||||
destination: hooks/charmhelpers
|
||||
include:
|
||||
- core
|
||||
- fetch
|
||||
- contrib.openstack|inc=*
|
||||
- contrib.hahelpers
|
||||
- contrib.network.ovs
|
||||
- contrib.storage.linux
|
||||
- payload.execd
|
||||
- contrib.network.ip
|
||||
- contrib.python.packages
|
5
config.yaml
Normal file
5
config.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
options:
|
||||
external-interface:
|
||||
default: eth1
|
||||
type: string
|
||||
description: The interface that will provide external connectivity
|
9
copyright
Normal file
9
copyright
Normal file
@ -0,0 +1,9 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0
|
||||
|
||||
Files: *
|
||||
Copyright: 2012, Canonical Ltd.
|
||||
License: GPL-3
|
||||
|
||||
License: GPL-3
|
||||
On Debian GNU/Linux system you can find the complete text of the
|
||||
GPL-3 license in '/usr/share/common-licenses/GPL-3'
|
1
hooks/config-changed
Symbolic link
1
hooks/config-changed
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/install
Symbolic link
1
hooks/install
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/neutron-plugin-api-relation-broken
Symbolic link
1
hooks/neutron-plugin-api-relation-broken
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/neutron-plugin-api-relation-changed
Symbolic link
1
hooks/neutron-plugin-api-relation-changed
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/neutron-plugin-api-relation-departed
Symbolic link
1
hooks/neutron-plugin-api-relation-departed
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/neutron-plugin-api-relation-joined
Symbolic link
1
hooks/neutron-plugin-api-relation-joined
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
93
hooks/pg_gw_context.py
Normal file
93
hooks/pg_gw_context.py
Normal file
@ -0,0 +1,93 @@
|
||||
from charmhelpers.core.hookenv import (
|
||||
relation_ids,
|
||||
related_units,
|
||||
relation_get,
|
||||
config,
|
||||
)
|
||||
from charmhelpers.contrib.openstack import context
|
||||
|
||||
from socket import gethostname as get_unit_hostname
|
||||
|
||||
'''
|
||||
#This function will be used to get information from neutron-api
|
||||
def _neutron_api_settings():
|
||||
neutron_settings = {
|
||||
'neutron_security_groups': False,
|
||||
'l2_population': True,
|
||||
'overlay_network_type': 'gre',
|
||||
}
|
||||
for rid in relation_ids('neutron-plugin-api'):
|
||||
for unit in related_units(rid):
|
||||
rdata = relation_get(rid=rid, unit=unit)
|
||||
if 'l2-population' not in rdata:
|
||||
continue
|
||||
neutron_settings = {
|
||||
'l2_population': rdata['l2-population'],
|
||||
'neutron_security_groups': rdata['neutron-security-groups'],
|
||||
'overlay_network_type': rdata['overlay-network-type'],
|
||||
}
|
||||
# Override with configuration if set to true
|
||||
if config('disable-security-groups'):
|
||||
neutron_settings['neutron_security_groups'] = False
|
||||
return neutron_settings
|
||||
return neutron_settings
|
||||
'''
|
||||
|
||||
|
||||
def _pg_dir_settings():
|
||||
'''
|
||||
Inspects current neutron-plugin relation
|
||||
'''
|
||||
pg_settings = {
|
||||
'pg_dir_ip': '192.168.100.201',
|
||||
}
|
||||
for rid in relation_ids('plumgrid'):
|
||||
for unit in related_units(rid):
|
||||
rdata = relation_get(rid=rid, unit=unit)
|
||||
pg_settings = {
|
||||
'pg_dir_ip': rdata['private-address'],
|
||||
}
|
||||
return pg_settings
|
||||
|
||||
|
||||
class PGGwContext(context.NeutronContext):
|
||||
interfaces = []
|
||||
|
||||
@property
|
||||
def plugin(self):
|
||||
return 'plumgrid'
|
||||
|
||||
@property
|
||||
def network_manager(self):
|
||||
return 'neutron'
|
||||
|
||||
def _save_flag_file(self):
|
||||
pass
|
||||
|
||||
#@property
|
||||
#def neutron_security_groups(self):
|
||||
# neutron_api_settings = _neutron_api_settings()
|
||||
# return neutron_api_settings['neutron_security_groups']
|
||||
|
||||
def pg_ctxt(self):
|
||||
#Generated Config for all Plumgrid templates inside
|
||||
#the templates folder
|
||||
pg_ctxt = super(PGGwContext, self).pg_ctxt()
|
||||
if not pg_ctxt:
|
||||
return {}
|
||||
|
||||
conf = config()
|
||||
pg_dir_settings = _pg_dir_settings()
|
||||
pg_ctxt['local_ip'] = pg_dir_settings['pg_dir_ip']
|
||||
|
||||
#neutron_api_settings = _neutron_api_settings()
|
||||
#TODO: Either get this value from the director or neutron-api charm
|
||||
unit_hostname = get_unit_hostname()
|
||||
pg_ctxt['pg_hostname'] = unit_hostname
|
||||
pg_ctxt['interface'] = "juju-br0"
|
||||
pg_ctxt['label'] = unit_hostname
|
||||
pg_ctxt['fabric_mode'] = 'host'
|
||||
|
||||
pg_ctxt['ext_interface'] = conf['external-interface']
|
||||
|
||||
return pg_ctxt
|
47
hooks/pg_gw_hooks.py
Executable file
47
hooks/pg_gw_hooks.py
Executable file
@ -0,0 +1,47 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import sys
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
Hooks,
|
||||
UnregisteredHookError,
|
||||
log,
|
||||
)
|
||||
|
||||
from pg_gw_utils import (
|
||||
register_configs,
|
||||
ensure_files,
|
||||
restart_pg,
|
||||
stop_pg,
|
||||
)
|
||||
|
||||
hooks = Hooks()
|
||||
CONFIGS = register_configs()
|
||||
|
||||
|
||||
@hooks.hook()
|
||||
def install():
|
||||
ensure_files()
|
||||
|
||||
|
||||
@hooks.hook('plumgrid-plugin-relation-joined')
|
||||
def plumgrid_dir():
|
||||
ensure_files()
|
||||
CONFIGS.write_all()
|
||||
restart_pg()
|
||||
|
||||
|
||||
@hooks.hook('stop')
|
||||
def stop():
|
||||
stop_pg()
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
hooks.execute(sys.argv)
|
||||
except UnregisteredHookError as e:
|
||||
log('Unknown hook {} - skipping.'.format(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
128
hooks/pg_gw_utils.py
Normal file
128
hooks/pg_gw_utils.py
Normal file
@ -0,0 +1,128 @@
|
||||
from charmhelpers.contrib.openstack.neutron import neutron_plugin_attribute
|
||||
from copy import deepcopy
|
||||
from charmhelpers.core.hookenv import log
|
||||
from charmhelpers.core.host import (
|
||||
write_file,
|
||||
)
|
||||
from charmhelpers.contrib.openstack import templating
|
||||
from collections import OrderedDict
|
||||
from charmhelpers.contrib.openstack.utils import (
|
||||
os_release,
|
||||
)
|
||||
import pg_gw_context
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
#Dont need these right now
|
||||
NOVA_CONF_DIR = "/etc/nova"
|
||||
NEUTRON_CONF_DIR = "/etc/neutron"
|
||||
NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR
|
||||
NEUTRON_DEFAULT = '/etc/default/neutron-server'
|
||||
|
||||
#Puppet Files
|
||||
P_PGKA_CONF = '/opt/pg/etc/puppet/modules/sal/templates/keepalived.conf.erb'
|
||||
P_PG_CONF = '/opt/pg/etc/puppet/modules/plumgrid/templates/plumgrid.conf.erb'
|
||||
P_PGDEF_CONF = '/opt/pg/etc/puppet/modules/sal/templates/default.conf.erb'
|
||||
|
||||
#Plumgrid Files
|
||||
PGKA_CONF = '/var/lib/libvirt/filesystems/plumgrid/etc/keepalived/keepalived.conf'
|
||||
PG_CONF = '/var/lib/libvirt/filesystems/plumgrid/opt/pg/etc/plumgrid.conf'
|
||||
PGDEF_CONF = '/var/lib/libvirt/filesystems/plumgrid/opt/pg/sal/nginx/conf.d/default.conf'
|
||||
PGHN_CONF = '/var/lib/libvirt/filesystems/plumgrid-data/conf/etc/hostname'
|
||||
PGHS_CONF = '/var/lib/libvirt/filesystems/plumgrid-data/conf/etc/hosts'
|
||||
PGIFCS_CONF = '/var/lib/libvirt/filesystems/plumgrid-data/conf/pg/ifcs.conf'
|
||||
IFCTL_CONF = '/var/run/plumgrid/lxc/ifc_list_gateway'
|
||||
IFCTL_P_CONF = '/var/lib/libvirt/filesystems/plumgrid/var/run/plumgrid/lxc/ifc_list_gateway'
|
||||
|
||||
#EDGE SPECIFIC
|
||||
SUDOERS_CONF = '/etc/sudoers.d/ifc_ctl_sudoers'
|
||||
FILTERS_CONF_DIR = '/etc/nova/rootwrap.d'
|
||||
FILTERS_CONF = '%s/network.filters' % FILTERS_CONF_DIR
|
||||
|
||||
BASE_RESOURCE_MAP = OrderedDict([
|
||||
(PG_CONF, {
|
||||
'services': ['plumgrid'],
|
||||
'contexts': [pg_gw_context.PGGwContext()],
|
||||
}),
|
||||
(PGHN_CONF, {
|
||||
'services': ['plumgrid'],
|
||||
'contexts': [pg_gw_context.PGGwContext()],
|
||||
}),
|
||||
(PGHS_CONF, {
|
||||
'services': ['plumgrid'],
|
||||
'contexts': [pg_gw_context.PGGwContext()],
|
||||
}),
|
||||
(PGIFCS_CONF, {
|
||||
'services': [],
|
||||
'contexts': [pg_gw_context.PGGwContext()],
|
||||
}),
|
||||
(FILTERS_CONF, {
|
||||
'services': [],
|
||||
'contexts': [pg_gw_context.PGGwContext()],
|
||||
}),
|
||||
])
|
||||
|
||||
TEMPLATES = 'templates/'
|
||||
|
||||
|
||||
def determine_packages():
|
||||
return neutron_plugin_attribute('plumgrid', 'packages', 'neutron')
|
||||
|
||||
|
||||
def register_configs(release=None):
|
||||
release = release or os_release('neutron-common', base='icehouse')
|
||||
configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
|
||||
openstack_release=release)
|
||||
for cfg, rscs in resource_map().iteritems():
|
||||
configs.register(cfg, rscs['contexts'])
|
||||
return configs
|
||||
|
||||
|
||||
def resource_map():
|
||||
'''
|
||||
Dynamically generate a map of resources that will be managed for a single
|
||||
hook execution.
|
||||
'''
|
||||
resource_map = deepcopy(BASE_RESOURCE_MAP)
|
||||
return resource_map
|
||||
|
||||
|
||||
def restart_map():
|
||||
'''
|
||||
Constructs a restart map based on charm config settings and relation
|
||||
state.
|
||||
'''
|
||||
return {k: v['services'] for k, v in resource_map().iteritems()}
|
||||
|
||||
|
||||
def ensure_files():
|
||||
_exec_cmd(cmd=['cp', '--remove-destination', '-f', P_PG_CONF, PG_CONF])
|
||||
write_file(SUDOERS_CONF, "\nnova ALL=(root) NOPASSWD: /opt/pg/bin/ifc_ctl_pp *\n", owner='root', group='root', perms=0o644)
|
||||
_exec_cmd(cmd=['mkdir', '-p', FILTERS_CONF_DIR])
|
||||
_exec_cmd(cmd=['touch', FILTERS_CONF])
|
||||
|
||||
|
||||
def restart_pg():
|
||||
_exec_cmd(cmd=['virsh', '-c', 'lxc:', 'destroy', 'plumgrid'], error_msg='ERROR Destroying PLUMgrid')
|
||||
_exec_cmd(cmd=['rm', IFCTL_CONF, IFCTL_P_CONF], error_msg='ERROR Removing ifc_ctl_gateway file')
|
||||
_exec_cmd(cmd=['iptables', '-F'])
|
||||
_exec_cmd(cmd=['virsh', '-c', 'lxc:', 'start', 'plumgrid'], error_msg='ERROR Starting PLUMgrid')
|
||||
time.sleep(5)
|
||||
_exec_cmd(cmd=['service', 'plumgrid', 'start'], error_msg='ERROR starting PLUMgrid service')
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def stop_pg():
|
||||
_exec_cmd(cmd=['virsh', '-c', 'lxc:', 'destroy', 'plumgrid'], error_msg='ERROR Destroying PLUMgrid')
|
||||
time.sleep(2)
|
||||
_exec_cmd(cmd=['rm', IFCTL_CONF, IFCTL_P_CONF], error_msg='ERROR Removing ifc_ctl_gateway file')
|
||||
|
||||
|
||||
def _exec_cmd(cmd=None, error_msg='Command exited with ERRORs'):
|
||||
if cmd is None:
|
||||
log("NO command")
|
||||
else:
|
||||
try:
|
||||
subprocess.check_call(cmd)
|
||||
except subprocess.CalledProcessError, e:
|
||||
log(error_msg)
|
1
hooks/plumgrid-plugin-relation-broken
Symbolic link
1
hooks/plumgrid-plugin-relation-broken
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/plumgrid-plugin-relation-changed
Symbolic link
1
hooks/plumgrid-plugin-relation-changed
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/plumgrid-plugin-relation-departed
Symbolic link
1
hooks/plumgrid-plugin-relation-departed
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/plumgrid-plugin-relation-joined
Symbolic link
1
hooks/plumgrid-plugin-relation-joined
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/plumgrid-relation-broken
Symbolic link
1
hooks/plumgrid-relation-broken
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/plumgrid-relation-changed
Symbolic link
1
hooks/plumgrid-relation-changed
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/plumgrid-relation-departed
Symbolic link
1
hooks/plumgrid-relation-departed
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/plumgrid-relation-joined
Symbolic link
1
hooks/plumgrid-relation-joined
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/stop
Symbolic link
1
hooks/stop
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
1
hooks/upgrade-charm
Symbolic link
1
hooks/upgrade-charm
Symbolic link
@ -0,0 +1 @@
|
||||
pg_gw_hooks.py
|
304
icon.svg
Normal file
304
icon.svg
Normal file
@ -0,0 +1,304 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="96"
|
||||
height="96"
|
||||
id="svg6517"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="Gateway1.svg">
|
||||
<defs
|
||||
id="defs6519">
|
||||
<linearGradient
|
||||
id="Background">
|
||||
<stop
|
||||
id="stop4178"
|
||||
offset="0"
|
||||
style="stop-color:#b8b8b8;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop4180"
|
||||
offset="1"
|
||||
style="stop-color:#c9c9c9;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB;"
|
||||
inkscape:label="Inner Shadow"
|
||||
id="filter1121">
|
||||
<feFlood
|
||||
flood-opacity="0.59999999999999998"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood1123" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="out"
|
||||
result="composite1"
|
||||
id="feComposite1125" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="1"
|
||||
result="blur"
|
||||
id="feGaussianBlur1127" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="2"
|
||||
result="offset"
|
||||
id="feOffset1129" />
|
||||
<feComposite
|
||||
in="offset"
|
||||
in2="SourceGraphic"
|
||||
operator="atop"
|
||||
result="composite2"
|
||||
id="feComposite1131" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB;"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter950">
|
||||
<feFlood
|
||||
flood-opacity="0.25"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood952" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite954" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="1"
|
||||
result="blur"
|
||||
id="feGaussianBlur956" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="1"
|
||||
result="offset"
|
||||
id="feOffset958" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite960" />
|
||||
</filter>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath873">
|
||||
<g
|
||||
transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
|
||||
id="g875"
|
||||
inkscape:label="Layer 1"
|
||||
style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
|
||||
<path
|
||||
style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
|
||||
d="m 46.702703,898.22775 50.594594,0 C 138.16216,898.22775 144,904.06497 144,944.92583 l 0,50.73846 c 0,40.86071 -5.83784,46.69791 -46.702703,46.69791 l -50.594594,0 C 5.8378378,1042.3622 0,1036.525 0,995.66429 L 0,944.92583 C 0,904.06497 5.8378378,898.22775 46.702703,898.22775 Z"
|
||||
id="path877"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sssssssss" />
|
||||
</g>
|
||||
</clipPath>
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter891"
|
||||
inkscape:label="Badge Shadow">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="0.71999962"
|
||||
id="feGaussianBlur893" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="4.0745362"
|
||||
inkscape:cx="57.131043"
|
||||
inkscape:cy="49.018169"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="705"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
showborder="true"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:showpageshadow="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid821" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="16,48"
|
||||
id="guide823" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="64,80"
|
||||
id="guide825" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="80,40"
|
||||
id="guide827" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="64,16"
|
||||
id="guide829" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata6522">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="BACKGROUND"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(268,-635.29076)"
|
||||
style="display:inline">
|
||||
<path
|
||||
style="fill:#029bd6;fill-opacity:0.90980393;stroke:none;display:inline;filter:url(#filter1121)"
|
||||
d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 C -264.11215,731.29077 -268,727.39888 -268,700.15563 Z"
|
||||
id="path6455"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sssssssss" />
|
||||
<g
|
||||
transform="matrix(1.2554482,0,0,1.2247945,-280.29427,621.82638)"
|
||||
id="g3">
|
||||
<g
|
||||
id="g5">
|
||||
<g
|
||||
id="g7">
|
||||
<polygon
|
||||
style="fill:#ffffff"
|
||||
points="72.643,41.599 72.643,24.878 56.501,25.459 60.839,29.796 49.781,40.854 56.813,47.891 67.872,36.83 "
|
||||
id="polygon9" />
|
||||
<polygon
|
||||
style="fill:#ffffff"
|
||||
points="40.048,74.195 23.327,74.195 23.908,58.055 28.246,62.393 39.299,51.34 46.332,58.372 35.279,69.426 "
|
||||
id="polygon11" />
|
||||
</g>
|
||||
<g
|
||||
id="g13">
|
||||
<polygon
|
||||
style="fill:#ffffff"
|
||||
points="39.721,25.136 23.005,25.136 23.581,41.277 27.918,36.938 38.977,47.997 46.009,40.964 34.951,29.906 "
|
||||
id="polygon15" />
|
||||
<polygon
|
||||
style="fill:#ffffff"
|
||||
points="72.965,58.383 72.965,75.1 56.824,74.521 61.162,70.186 50.104,59.127 57.137,52.094 68.195,63.152 "
|
||||
id="polygon17" />
|
||||
</g>
|
||||
</g>
|
||||
<circle
|
||||
style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:10"
|
||||
stroke-miterlimit="10"
|
||||
cx="47.985001"
|
||||
cy="49.990002"
|
||||
r="10.165"
|
||||
id="circle19" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="PLACE YOUR PICTOGRAM HERE"
|
||||
style="display:inline" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="BADGE"
|
||||
style="display:none"
|
||||
sodipodi:insensitive="true">
|
||||
<g
|
||||
style="display:inline"
|
||||
transform="translate(-340.00001,-581)"
|
||||
id="g4394"
|
||||
clip-path="none">
|
||||
<g
|
||||
id="g855">
|
||||
<g
|
||||
inkscape:groupmode="maskhelper"
|
||||
id="g870"
|
||||
clip-path="url(#clipPath873)"
|
||||
style="opacity:0.6;filter:url(#filter891)">
|
||||
<path
|
||||
transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
|
||||
d="m 264,552.36218 a 12,12 0 0 1 -12,12 12,12 0 0 1 -12,-12 12,12 0 0 1 12,-12 12,12 0 0 1 12,12 z"
|
||||
sodipodi:ry="12"
|
||||
sodipodi:rx="12"
|
||||
sodipodi:cy="552.36218"
|
||||
sodipodi:cx="252"
|
||||
id="path844"
|
||||
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
id="g862">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path4398"
|
||||
sodipodi:cx="252"
|
||||
sodipodi:cy="552.36218"
|
||||
sodipodi:rx="12"
|
||||
sodipodi:ry="12"
|
||||
d="m 264,552.36218 a 12,12 0 0 1 -12,12 12,12 0 0 1 -12,-12 12,12 0 0 1 12,-12 12,12 0 0 1 12,12 z"
|
||||
transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" />
|
||||
<path
|
||||
transform="matrix(1.25,0,0,1.25,33,-100.45273)"
|
||||
d="m 264,552.36218 a 12,12 0 0 1 -12,12 12,12 0 0 1 -12,-12 12,12 0 0 1 12,-12 12,12 0 0 1 12,12 z"
|
||||
sodipodi:ry="12"
|
||||
sodipodi:rx="12"
|
||||
sodipodi:cy="552.36218"
|
||||
sodipodi:cx="252"
|
||||
id="path4400"
|
||||
style="color:#000000;fill:#dd4814;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path4459"
|
||||
sodipodi:sides="5"
|
||||
sodipodi:cx="666.19574"
|
||||
sodipodi:cy="589.50385"
|
||||
sodipodi:r1="7.2431178"
|
||||
sodipodi:r2="4.3458705"
|
||||
sodipodi:arg1="1.0471976"
|
||||
sodipodi:arg2="1.6755161"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0.1"
|
||||
inkscape:randomized="0"
|
||||
d="m 669.8173,595.77657 c -0.39132,0.22593 -3.62645,-1.90343 -4.07583,-1.95066 -0.44938,-0.0472 -4.05653,1.36297 -4.39232,1.06062 -0.3358,-0.30235 0.68963,-4.03715 0.59569,-4.47913 -0.0939,-0.44198 -2.5498,-3.43681 -2.36602,-3.8496 0.18379,-0.41279 4.05267,-0.59166 4.44398,-0.81759 0.39132,-0.22593 2.48067,-3.48704 2.93005,-3.4398 0.44938,0.0472 1.81505,3.67147 2.15084,3.97382 0.3358,0.30236 4.08294,1.2817 4.17689,1.72369 0.0939,0.44198 -2.9309,2.86076 -3.11469,3.27355 -0.18379,0.41279 0.0427,4.27917 -0.34859,4.5051 z"
|
||||
transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 10 KiB |
24
metadata.yaml
Normal file
24
metadata.yaml
Normal file
@ -0,0 +1,24 @@
|
||||
name: plumgrid-gateway
|
||||
subordinate: false
|
||||
maintainer: Bilal Baqar <bbaqar@plumgrid.com>
|
||||
summary: "OpenStack Neutron OpenvSwitch Agent"
|
||||
description: |
|
||||
Neutron is a virtual network service for Openstack, and a part of
|
||||
Netstack. Just like OpenStack Nova provides an API to dynamically
|
||||
request and configure virtual servers, Neutron provides an API to
|
||||
dynamically request and configure virtual networks. These networks
|
||||
connect "interfaces" from other OpenStack services (e.g., virtual NICs
|
||||
from Nova VMs). The Neutron API supports extensions to provide
|
||||
advanced network capabilities (e.g., QoS, ACLs, network monitoring,
|
||||
etc.)
|
||||
.
|
||||
This charm provides the Plumgrid Gateway
|
||||
tags:
|
||||
- openstack
|
||||
requires:
|
||||
plumgrid-plugin:
|
||||
interface: plumgrid-plugin
|
||||
plumgrid:
|
||||
interface: plumgrid
|
||||
neutron-plugin-api:
|
||||
interface: neutron-plugin-api
|
5
setup.cfg
Normal file
5
setup.cfg
Normal file
@ -0,0 +1,5 @@
|
||||
[nosetests]
|
||||
verbosity=1
|
||||
with-coverage=1
|
||||
cover-erase=1
|
||||
cover-package=hooks
|
2
templates/icehouse/hostname
Normal file
2
templates/icehouse/hostname
Normal file
@ -0,0 +1,2 @@
|
||||
{{ pg_hostname }}
|
||||
|
10
templates/icehouse/hosts
Normal file
10
templates/icehouse/hosts
Normal file
@ -0,0 +1,10 @@
|
||||
127.0.0.1 localhost
|
||||
127.0.1.1 {{ pg_hostname }}
|
||||
|
||||
# The following lines are desirable for IPv6 capable hosts
|
||||
::1 ip6-localhost ip6-loopback
|
||||
fe00::0 ip6-localnet
|
||||
ff00::0 ip6-mcastprefix
|
||||
ff02::1 ip6-allnodes
|
||||
ff02::2 ip6-allrouters
|
||||
|
6
templates/icehouse/ifcs.conf
Normal file
6
templates/icehouse/ifcs.conf
Normal file
@ -0,0 +1,6 @@
|
||||
{{ interface }} = fabric_core host
|
||||
{% if ext_interface -%}
|
||||
{{ ext_interface }} = access_phys
|
||||
|
||||
{% endif -%}
|
||||
|
94
templates/icehouse/network.filters
Normal file
94
templates/icehouse/network.filters
Normal file
@ -0,0 +1,94 @@
|
||||
# nova-rootwrap command filters for network nodes
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
[Filters]
|
||||
# nova/virt/libvirt/vif.py: 'ip', 'tuntap', 'add', dev, 'mode', 'tap'
|
||||
# nova/virt/libvirt/vif.py: 'ip', 'link', 'set', dev, 'up'
|
||||
# nova/virt/libvirt/vif.py: 'ip', 'link', 'delete', dev
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'add', str(floating_ip)+'/32'i..
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'del', str(floating_ip)+'/32'..
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'add', '169.254.169.254/32',..
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'show', 'dev', dev, 'scope',..
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'del/add', ip_params, dev)
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'del', params, fields[-1]
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'add', params, bridge
|
||||
# nova/network/linux_net.py: 'ip', '-f', 'inet6', 'addr', 'change', ..
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'set', 'dev', dev, 'promisc',..
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'add', 'link', bridge_if ...
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'set', interface, address,..
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'set', interface, 'up'
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'set', bridge, 'up'
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'show', 'dev', interface, ..
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'set', dev, address, ..
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'set', dev, 'up'
|
||||
# nova/network/linux_net.py: 'ip', 'route', 'add', ..
|
||||
# nova/network/linux_net.py: 'ip', 'route', 'del', .
|
||||
# nova/network/linux_net.py: 'ip', 'route', 'show', 'dev', dev
|
||||
ip: CommandFilter, ip, root
|
||||
|
||||
# nova/virt/libvirt/vif.py: 'ovs-vsctl', ...
|
||||
# nova/virt/libvirt/vif.py: 'ovs-vsctl', 'del-port', ...
|
||||
# nova/network/linux_net.py: 'ovs-vsctl', ....
|
||||
ovs-vsctl: CommandFilter, ovs-vsctl, root
|
||||
|
||||
# nova/network/linux_net.py: 'ovs-ofctl', ....
|
||||
ovs-ofctl: CommandFilter, ovs-ofctl, root
|
||||
|
||||
# nova/virt/libvirt/vif.py: 'ivs-ctl', ...
|
||||
# nova/virt/libvirt/vif.py: 'ivs-ctl', 'del-port', ...
|
||||
# nova/network/linux_net.py: 'ivs-ctl', ....
|
||||
ivs-ctl: CommandFilter, ivs-ctl, root
|
||||
|
||||
# nova/virt/libvirt/vif.py: 'ifc_ctl', ...
|
||||
ifc_ctl: CommandFilter, /opt/pg/bin/ifc_ctl, root
|
||||
|
||||
# nova/virt/libvirt/vif.py: 'ebrctl', ...
|
||||
ebrctl: CommandFilter, ebrctl, root
|
||||
|
||||
# nova/virt/libvirt/vif.py: 'mm-ctl', ...
|
||||
mm-ctl: CommandFilter, mm-ctl, root
|
||||
|
||||
# nova/network/linux_net.py: 'ebtables', '-D' ...
|
||||
# nova/network/linux_net.py: 'ebtables', '-I' ...
|
||||
ebtables: CommandFilter, ebtables, root
|
||||
ebtables_usr: CommandFilter, ebtables, root
|
||||
|
||||
# nova/network/linux_net.py: 'ip[6]tables-save' % (cmd, '-t', ...
|
||||
iptables-save: CommandFilter, iptables-save, root
|
||||
ip6tables-save: CommandFilter, ip6tables-save, root
|
||||
|
||||
# nova/network/linux_net.py: 'ip[6]tables-restore' % (cmd,)
|
||||
iptables-restore: CommandFilter, iptables-restore, root
|
||||
ip6tables-restore: CommandFilter, ip6tables-restore, root
|
||||
|
||||
# nova/network/linux_net.py: 'arping', '-U', floating_ip, '-A', '-I', ...
|
||||
# nova/network/linux_net.py: 'arping', '-U', network_ref['dhcp_server'],..
|
||||
arping: CommandFilter, arping, root
|
||||
|
||||
# nova/network/linux_net.py: 'dhcp_release', dev, address, mac_address
|
||||
dhcp_release: CommandFilter, dhcp_release, root
|
||||
|
||||
# nova/network/linux_net.py: 'kill', '-9', pid
|
||||
# nova/network/linux_net.py: 'kill', '-HUP', pid
|
||||
kill_dnsmasq: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP
|
||||
|
||||
# nova/network/linux_net.py: 'kill', pid
|
||||
kill_radvd: KillFilter, root, /usr/sbin/radvd
|
||||
|
||||
# nova/network/linux_net.py: dnsmasq call
|
||||
dnsmasq: EnvFilter, env, root, CONFIG_FILE=, NETWORK_ID=, dnsmasq
|
||||
|
||||
# nova/network/linux_net.py: 'radvd', '-C', '%s' % _ra_file(dev, 'conf'..
|
||||
radvd: CommandFilter, radvd, root
|
||||
|
||||
# nova/network/linux_net.py: 'brctl', 'addbr', bridge
|
||||
# nova/network/linux_net.py: 'brctl', 'setfd', bridge, 0
|
||||
# nova/network/linux_net.py: 'brctl', 'stp', bridge, 'off'
|
||||
# nova/network/linux_net.py: 'brctl', 'addif', bridge, interface
|
||||
brctl: CommandFilter, brctl, root
|
||||
|
||||
# nova/network/linux_net.py: 'sysctl', ....
|
||||
sysctl: CommandFilter, sysctl, root
|
||||
|
||||
# nova/network/linux_net.py: 'conntrack'
|
||||
conntrack: CommandFilter, conntrack, root
|
11
templates/icehouse/plumgrid.conf
Normal file
11
templates/icehouse/plumgrid.conf
Normal file
@ -0,0 +1,11 @@
|
||||
plumgrid_ip={{ local_ip }}
|
||||
plumgrid_port=8001
|
||||
mgmt_dev={{ interface }}
|
||||
label={{ label}}
|
||||
plumgrid_rsync_port=2222
|
||||
plumgrid_rest_addr=0.0.0.0:9180
|
||||
fabric_mode={{ fabric_mode }}
|
||||
start_plumgrid_iovisor=yes
|
||||
start_plumgrid=`/opt/pg/scripts/pg_is_director.sh $plumgrid_ip`
|
||||
location=
|
||||
|
21
templates/parts/rabbitmq
Normal file
21
templates/parts/rabbitmq
Normal file
@ -0,0 +1,21 @@
|
||||
{% if rabbitmq_host or rabbitmq_hosts -%}
|
||||
rabbit_userid = {{ rabbitmq_user }}
|
||||
rabbit_virtual_host = {{ rabbitmq_virtual_host }}
|
||||
rabbit_password = {{ rabbitmq_password }}
|
||||
{% if rabbitmq_hosts -%}
|
||||
rabbit_hosts = {{ rabbitmq_hosts }}
|
||||
{% if rabbitmq_ha_queues -%}
|
||||
rabbit_ha_queues = True
|
||||
rabbit_durable_queues = False
|
||||
{% endif -%}
|
||||
{% else -%}
|
||||
rabbit_host = {{ rabbitmq_host }}
|
||||
{% endif -%}
|
||||
{% if rabbit_ssl_port -%}
|
||||
rabbit_use_ssl = True
|
||||
rabbit_port = {{ rabbit_ssl_port }}
|
||||
{% if rabbit_ssl_ca -%}
|
||||
kombu_ssl_ca_certs = {{ rabbit_ssl_ca }}
|
||||
{% endif -%}
|
||||
{% endif -%}
|
||||
{% endif -%}
|
5
tests/00-setup
Executable file
5
tests/00-setup
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo add-apt-repository ppa:juju/stable -y
|
||||
sudo apt-get update
|
||||
sudo apt-get install amulet python3-requests juju-deployer -y
|
39
tests/14-juno
Executable file
39
tests/14-juno
Executable file
@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import amulet
|
||||
import requests
|
||||
import unittest
|
||||
|
||||
class TestDeployment(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.deployment = amulet.Deployment(series='trusty')
|
||||
cls.deployment.load_bundle_file(bundle_file='files/plumgrid-gateway.yaml', deployment_name='test')
|
||||
try:
|
||||
cls.deployment.setup(timeout=2000)
|
||||
cls.deployment.sentry.wait()
|
||||
except amulet.helpers.TimeoutError:
|
||||
amulet.raise_status(amulet.SKIP, msg="Environment wasn't stood up in time")
|
||||
except:
|
||||
raise
|
||||
|
||||
def test_plumgrid_gateway_external_interface(self):
|
||||
external_interface = self.deployment.services['plumgrid-gateway']['options']['external-interface']
|
||||
if not external_interface:
|
||||
amulet.raise_status(amulet.FAIL, msg='plumgrid external-interface parameter was not found.')
|
||||
output, code = self.deployment.sentry['plumgrid-gateway/0'].run("ethtool {}".format(external_interface))
|
||||
if code != 0:
|
||||
amulet.raise_status(amulet.FAIL, msg='external interface not found on the host')
|
||||
|
||||
def test_plumgrid_gateway_started(self):
|
||||
agent_state = self.deployment.sentry['plumgrid-gateway/0'].info['agent-state']
|
||||
if agent_state != 'started':
|
||||
amulet.raise_status(amulet.FAIL, msg='plumgrid gateway is not in a started state')
|
||||
|
||||
def test_plumgrid_gateway_relation(self):
|
||||
relation = self.deployment.sentry['plumgrid-gateway/0'].relation('plumgrid-plugin', 'neutron-iovisor:plumgrid-plugin')
|
||||
if not relation['private-address']:
|
||||
amulet.raise_status(amulet.FAIL, msg='private address was not set in the plumgrid gateway relation')
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
98
tests/files/plumgrid-gateway.yaml
Normal file
98
tests/files/plumgrid-gateway.yaml
Normal file
@ -0,0 +1,98 @@
|
||||
test:
|
||||
series: 'trusty'
|
||||
relations:
|
||||
- - neutron-api
|
||||
- neutron-iovisor
|
||||
- - neutron-iovisor
|
||||
- plumgrid-gateway
|
||||
- - nova-cloud-controller
|
||||
- nova-compute
|
||||
- - glance
|
||||
- nova-compute
|
||||
- - nova-compute
|
||||
- rabbitmq-server
|
||||
- - mysql
|
||||
- nova-compute
|
||||
- - cinder
|
||||
- nova-cloud-controller
|
||||
- - nova-cloud-controller
|
||||
- rabbitmq-server
|
||||
- - glance
|
||||
- nova-cloud-controller
|
||||
- - keystone
|
||||
- nova-cloud-controller
|
||||
- - mysql
|
||||
- nova-cloud-controller
|
||||
- - neutron-api
|
||||
- nova-cloud-controller
|
||||
services:
|
||||
cinder:
|
||||
charm: cs:trusty/cinder
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: cloud:trusty-juno
|
||||
to: 'lxc:0'
|
||||
glance:
|
||||
charm: cs:trusty/glance
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: cloud:trusty-juno
|
||||
to: 'lxc:0'
|
||||
keystone:
|
||||
charm: cs:trusty/keystone
|
||||
num_units: 1
|
||||
options:
|
||||
admin-password: plumgrid
|
||||
openstack-origin: cloud:trusty-juno
|
||||
to: 'lxc:0'
|
||||
mysql:
|
||||
charm: cs:trusty/mysql
|
||||
num_units: 1
|
||||
to: 'lxc:0'
|
||||
neutron-api:
|
||||
charm: cs:~juliann/trusty/neutron-api
|
||||
num_units: 1
|
||||
options:
|
||||
install_keys: 'null'
|
||||
install_sources: "deb http://10.22.24.200/debs ./"
|
||||
neutron-plugin: "plumgrid"
|
||||
neutron-security-groups: "true"
|
||||
openstack-origin: "cloud:trusty-juno"
|
||||
plumgrid-password: "plumgrid"
|
||||
plumgrid-username: "plumgrid"
|
||||
plumgrid-virtual-ip: "192.168.100.250"
|
||||
to: 'lxc:0'
|
||||
neutron-iovisor:
|
||||
charm: cs:~juliann/trusty/neutron-iovisor
|
||||
num_units: 1
|
||||
options:
|
||||
install_keys: 'null'
|
||||
install_sources: "deb http://10.22.24.200/debs ./"
|
||||
to: 'nova-compute'
|
||||
plumgrid-gateway:
|
||||
charm: cs:~juliann/trusty/plumgrid-gateway
|
||||
num_units: 1
|
||||
options:
|
||||
external-interface: 'eth1'
|
||||
to: 'nova-compute'
|
||||
nova-cloud-controller:
|
||||
charm: cs:trusty/nova-cloud-controller
|
||||
num_units: 1
|
||||
options:
|
||||
console-access-protocol: novnc
|
||||
network-manager: Neutron
|
||||
openstack-origin: cloud:trusty-juno
|
||||
quantum-security-groups: 'yes'
|
||||
to: 'lxc:0'
|
||||
nova-compute:
|
||||
charm: cs:~juliann/trusty/nova-compute
|
||||
num_units: 1
|
||||
options:
|
||||
enable-live-migration: true
|
||||
enable-resize: true
|
||||
migration-auth-type: ssh
|
||||
openstack-origin: cloud:trusty-juno
|
||||
rabbitmq-server:
|
||||
charm: cs:trusty/rabbitmq-server
|
||||
num_units: 1
|
||||
to: 'lxc:0'
|
4
unit_tests/__init__.py
Normal file
4
unit_tests/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
import sys
|
||||
|
||||
sys.path.append('actions/')
|
||||
sys.path.append('hooks/')
|
82
unit_tests/test_pg_gw_context.py
Normal file
82
unit_tests/test_pg_gw_context.py
Normal file
@ -0,0 +1,82 @@
|
||||
from test_utils import CharmTestCase
|
||||
from mock import patch
|
||||
import pg_gw_context as context
|
||||
import charmhelpers
|
||||
|
||||
TO_PATCH = [
|
||||
#'_pg_dir_settings',
|
||||
'config',
|
||||
'get_unit_hostname',
|
||||
]
|
||||
|
||||
|
||||
def fake_context(settings):
|
||||
def outer():
|
||||
def inner():
|
||||
return settings
|
||||
return inner
|
||||
return outer
|
||||
|
||||
|
||||
class PGGwContextTest(CharmTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PGGwContextTest, self).setUp(context, TO_PATCH)
|
||||
self.config.side_effect = self.test_config.get
|
||||
self.test_config.set('external-interface', 'eth1')
|
||||
|
||||
def tearDown(self):
|
||||
super(PGGwContextTest, self).tearDown()
|
||||
|
||||
@patch.object(context.PGGwContext, '_ensure_packages')
|
||||
@patch.object(charmhelpers.contrib.openstack.context, 'https')
|
||||
@patch.object(charmhelpers.contrib.openstack.context, 'is_clustered')
|
||||
@patch.object(charmhelpers.contrib.openstack.context, 'config')
|
||||
@patch.object(charmhelpers.contrib.openstack.context, 'unit_private_ip')
|
||||
@patch.object(charmhelpers.contrib.openstack.context, 'unit_get')
|
||||
@patch.object(charmhelpers.contrib.openstack.context, 'config_flags_parser')
|
||||
@patch.object(context.PGGwContext, '_save_flag_file')
|
||||
@patch.object(context, '_pg_dir_settings')
|
||||
@patch.object(charmhelpers.contrib.openstack.context, 'neutron_plugin_attribute')
|
||||
def test_neutroncc_context_api_rel(self, _npa, _pg_dir_settings, _save_flag_file,
|
||||
_config_flag, _unit_get, _unit_priv_ip, _config,
|
||||
_is_clus, _https, _ens_pkgs):
|
||||
def mock_npa(plugin, section, manager):
|
||||
if section == "driver":
|
||||
return "neutron.randomdriver"
|
||||
if section == "config":
|
||||
return "neutron.randomconfig"
|
||||
config = {'external-interface': "eth1"}
|
||||
|
||||
def mock_config(key=None):
|
||||
if key:
|
||||
return config.get(key)
|
||||
|
||||
return config
|
||||
|
||||
self.maxDiff = None
|
||||
self.config.side_effect = mock_config
|
||||
_npa.side_effect = mock_npa
|
||||
_unit_get.return_value = '192.168.100.201'
|
||||
_unit_priv_ip.return_value = '192.168.100.201'
|
||||
self.get_unit_hostname.return_value = 'node0'
|
||||
_is_clus.return_value = False
|
||||
_config_flag.return_value = False
|
||||
_pg_dir_settings.return_value = {'pg_dir_ip': '192.168.100.201'}
|
||||
napi_ctxt = context.PGGwContext()
|
||||
expect = {
|
||||
'ext_interface': "eth1",
|
||||
'config': 'neutron.randomconfig',
|
||||
'core_plugin': 'neutron.randomdriver',
|
||||
'local_ip': '192.168.100.201',
|
||||
'network_manager': 'neutron',
|
||||
'neutron_plugin': 'plumgrid',
|
||||
'neutron_security_groups': None,
|
||||
'neutron_url': 'https://192.168.100.201:9696',
|
||||
'pg_hostname': 'node0',
|
||||
'interface': 'juju-br0',
|
||||
'label': 'node0',
|
||||
'fabric_mode': 'host',
|
||||
'neutron_alchemy_flags': False,
|
||||
}
|
||||
self.assertEquals(expect, napi_ctxt())
|
68
unit_tests/test_pg_gw_hooks.py
Normal file
68
unit_tests/test_pg_gw_hooks.py
Normal file
@ -0,0 +1,68 @@
|
||||
from mock import MagicMock, patch
|
||||
|
||||
from test_utils import CharmTestCase
|
||||
|
||||
with patch('charmhelpers.core.hookenv.config') as config:
|
||||
config.return_value = 'neutron'
|
||||
import pg_gw_utils as utils
|
||||
|
||||
_reg = utils.register_configs
|
||||
_map = utils.restart_map
|
||||
|
||||
utils.register_configs = MagicMock()
|
||||
utils.restart_map = MagicMock()
|
||||
|
||||
import pg_gw_hooks as hooks
|
||||
|
||||
utils.register_configs = _reg
|
||||
utils.restart_map = _map
|
||||
|
||||
TO_PATCH = [
|
||||
#'apt_update',
|
||||
#'apt_install',
|
||||
#'apt_purge',
|
||||
#'config',
|
||||
'CONFIGS',
|
||||
#'determine_packages',
|
||||
#'determine_dvr_packages',
|
||||
#'get_shared_secret',
|
||||
#'git_install',
|
||||
'log',
|
||||
#'relation_ids',
|
||||
#'relation_set',
|
||||
#'configure_ovs',
|
||||
#'use_dvr',
|
||||
'ensure_files',
|
||||
'stop_pg',
|
||||
'restart_pg',
|
||||
]
|
||||
NEUTRON_CONF_DIR = "/etc/neutron"
|
||||
|
||||
NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR
|
||||
|
||||
|
||||
class PGGwHooksTests(CharmTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PGGwHooksTests, self).setUp(hooks, TO_PATCH)
|
||||
|
||||
#self.config.side_effect = self.test_config.get
|
||||
hooks.hooks._config_save = False
|
||||
|
||||
def _call_hook(self, hookname):
|
||||
hooks.hooks.execute([
|
||||
'hooks/{}'.format(hookname)])
|
||||
|
||||
def test_install_hook(self):
|
||||
self._call_hook('install')
|
||||
self.ensure_files.assert_called_with()
|
||||
|
||||
def test_plumgrid_edge_joined(self):
|
||||
self._call_hook('plumgrid-plugin-relation-joined')
|
||||
self.ensure_files.assert_called_with()
|
||||
self.CONFIGS.write_all.assert_called_with()
|
||||
self.restart_pg.assert_called_with()
|
||||
|
||||
def test_stop(self):
|
||||
self._call_hook('stop')
|
||||
self.stop_pg.assert_called_with()
|
79
unit_tests/test_pg_gw_utils.py
Normal file
79
unit_tests/test_pg_gw_utils.py
Normal file
@ -0,0 +1,79 @@
|
||||
from mock import MagicMock
|
||||
from collections import OrderedDict
|
||||
import charmhelpers.contrib.openstack.templating as templating
|
||||
|
||||
templating.OSConfigRenderer = MagicMock()
|
||||
|
||||
import pg_gw_utils as nutils
|
||||
|
||||
from test_utils import (
|
||||
CharmTestCase,
|
||||
)
|
||||
import charmhelpers.core.hookenv as hookenv
|
||||
|
||||
|
||||
TO_PATCH = [
|
||||
'os_release',
|
||||
'neutron_plugin_attribute',
|
||||
]
|
||||
|
||||
|
||||
class DummyContext():
|
||||
|
||||
def __init__(self, return_value):
|
||||
self.return_value = return_value
|
||||
|
||||
def __call__(self):
|
||||
return self.return_value
|
||||
|
||||
|
||||
class TestPGGwUtils(CharmTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPGGwUtils, self).setUp(nutils, TO_PATCH)
|
||||
#self.config.side_effect = self.test_config.get
|
||||
|
||||
def tearDown(self):
|
||||
# Reset cached cache
|
||||
hookenv.cache = {}
|
||||
|
||||
def test_register_configs(self):
|
||||
class _mock_OSConfigRenderer():
|
||||
def __init__(self, templates_dir=None, openstack_release=None):
|
||||
self.configs = []
|
||||
self.ctxts = []
|
||||
|
||||
def register(self, config, ctxt):
|
||||
self.configs.append(config)
|
||||
self.ctxts.append(ctxt)
|
||||
|
||||
self.os_release.return_value = 'trusty'
|
||||
templating.OSConfigRenderer.side_effect = _mock_OSConfigRenderer
|
||||
_regconfs = nutils.register_configs()
|
||||
confs = ['/var/lib/libvirt/filesystems/plumgrid/opt/pg/etc/plumgrid.conf',
|
||||
'/var/lib/libvirt/filesystems/plumgrid-data/conf/etc/hostname',
|
||||
'/var/lib/libvirt/filesystems/plumgrid-data/conf/etc/hosts',
|
||||
'/var/lib/libvirt/filesystems/plumgrid-data/conf/pg/ifcs.conf',
|
||||
'/etc/nova/rootwrap.d/network.filters']
|
||||
self.assertItemsEqual(_regconfs.configs, confs)
|
||||
|
||||
def test_resource_map(self):
|
||||
_map = nutils.resource_map()
|
||||
svcs = ['plumgrid']
|
||||
confs = [nutils.PG_CONF]
|
||||
[self.assertIn(q_conf, _map.keys()) for q_conf in confs]
|
||||
self.assertEqual(_map[nutils.PG_CONF]['services'], svcs)
|
||||
|
||||
def test_restart_map(self):
|
||||
_restart_map = nutils.restart_map()
|
||||
expect = OrderedDict([
|
||||
(nutils.PG_CONF, ['plumgrid']),
|
||||
(nutils.PGHN_CONF, ['plumgrid']),
|
||||
(nutils.PGHS_CONF, ['plumgrid']),
|
||||
(nutils.PGIFCS_CONF, []),
|
||||
(nutils.FILTERS_CONF, []),
|
||||
])
|
||||
self.assertEqual(expect, _restart_map)
|
||||
for item in _restart_map:
|
||||
self.assertTrue(item in _restart_map)
|
||||
self.assertTrue(expect[item] == _restart_map[item])
|
121
unit_tests/test_utils.py
Normal file
121
unit_tests/test_utils.py
Normal file
@ -0,0 +1,121 @@
|
||||
import logging
|
||||
import unittest
|
||||
import os
|
||||
import yaml
|
||||
|
||||
from contextlib import contextmanager
|
||||
from mock import patch, MagicMock
|
||||
|
||||
|
||||
def load_config():
|
||||
'''
|
||||
Walk backwords from __file__ looking for config.yaml, load and return the
|
||||
'options' section'
|
||||
'''
|
||||
config = None
|
||||
f = __file__
|
||||
while config is None:
|
||||
d = os.path.dirname(f)
|
||||
if os.path.isfile(os.path.join(d, 'config.yaml')):
|
||||
config = os.path.join(d, 'config.yaml')
|
||||
break
|
||||
f = d
|
||||
|
||||
if not config:
|
||||
logging.error('Could not find config.yaml in any parent directory '
|
||||
'of %s. ' % file)
|
||||
raise Exception
|
||||
|
||||
return yaml.safe_load(open(config).read())['options']
|
||||
|
||||
|
||||
def get_default_config():
|
||||
'''
|
||||
Load default charm config from config.yaml return as a dict.
|
||||
If no default is set in config.yaml, its value is None.
|
||||
'''
|
||||
default_config = {}
|
||||
config = load_config()
|
||||
for k, v in config.iteritems():
|
||||
if 'default' in v:
|
||||
default_config[k] = v['default']
|
||||
else:
|
||||
default_config[k] = None
|
||||
return default_config
|
||||
|
||||
|
||||
class CharmTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self, obj, patches):
|
||||
super(CharmTestCase, self).setUp()
|
||||
self.patches = patches
|
||||
self.obj = obj
|
||||
self.test_config = TestConfig()
|
||||
self.test_relation = TestRelation()
|
||||
self.patch_all()
|
||||
|
||||
def patch(self, method):
|
||||
_m = patch.object(self.obj, method)
|
||||
mock = _m.start()
|
||||
self.addCleanup(_m.stop)
|
||||
return mock
|
||||
|
||||
def patch_all(self):
|
||||
for method in self.patches:
|
||||
setattr(self, method, self.patch(method))
|
||||
|
||||
|
||||
class TestConfig(object):
|
||||
|
||||
def __init__(self):
|
||||
self.config = get_default_config()
|
||||
|
||||
def get(self, attr=None):
|
||||
if not attr:
|
||||
return self.get_all()
|
||||
try:
|
||||
return self.config[attr]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def get_all(self):
|
||||
return self.config
|
||||
|
||||
def set(self, attr, value):
|
||||
if attr not in self.config:
|
||||
raise KeyError
|
||||
self.config[attr] = value
|
||||
|
||||
|
||||
class TestRelation(object):
|
||||
|
||||
def __init__(self, relation_data={}):
|
||||
self.relation_data = relation_data
|
||||
|
||||
def set(self, relation_data):
|
||||
self.relation_data = relation_data
|
||||
|
||||
def get(self, attribute=None, unit=None, rid=None):
|
||||
if attribute is None:
|
||||
return self.relation_data
|
||||
elif attribute in self.relation_data:
|
||||
return self.relation_data[attribute]
|
||||
return None
|
||||
|
||||
|
||||
@contextmanager
|
||||
def patch_open():
|
||||
'''Patch open() to allow mocking both open() itself and the file that is
|
||||
yielded.
|
||||
|
||||
Yields the mock for "open" and "file", respectively.'''
|
||||
mock_open = MagicMock(spec=open)
|
||||
mock_file = MagicMock(spec=file)
|
||||
|
||||
@contextmanager
|
||||
def stub_open(*args, **kwargs):
|
||||
mock_open(*args, **kwargs)
|
||||
yield mock_file
|
||||
|
||||
with patch('__builtin__.open', stub_open):
|
||||
yield mock_open, mock_file
|
Loading…
x
Reference in New Issue
Block a user