Lint tidyup and unit tests

This commit is contained in:
Liam Young 2014-06-19 10:56:25 +01:00
parent 55de68b794
commit 3c85d2249a
11 changed files with 407 additions and 8 deletions

1
.bzrignore Normal file
View File

@ -0,0 +1 @@
.coverage

14
Makefile Normal file
View File

@ -0,0 +1,14 @@
#!/usr/bin/make
PYTHON := /usr/bin/env python
lint:
@flake8 --exclude hooks/charmhelpers hooks
@flake8 --exclude hooks/charmhelpers unit_tests
@charm proof
test:
@echo Starting tests...
@$(PYTHON) /usr/bin/nosetests --nologcapture unit_tests
sync:
@charm-helper-sync -c charm-helpers-sync.yaml

10
charm-helpers-sync.yaml Normal file
View File

@ -0,0 +1,10 @@
branch: lp:charm-helpers
destination: hooks/charmhelpers
include:
- core
- fetch
- contrib.openstack
- contrib.hahelpers
- contrib.network.ovs
- contrib.storage.linux
- payload.execd

View File

@ -1,9 +1,9 @@
from charmhelpers.core.hookenv import (
relation_ids,
related_units,
relation_get,
config,
unit_get,
relation_ids,
related_units,
relation_get,
config,
unit_get,
)
from charmhelpers.contrib.openstack import context
@ -12,6 +12,7 @@ from charmhelpers.contrib.network.ovs import add_bridge
from charmhelpers.contrib.openstack.utils import get_host_ip
OVS_BRIDGE = 'br-int'
def _neutron_security_groups():
'''
Inspects current neutron-plugin relation and determine if nova-c-c has
@ -19,11 +20,14 @@ def _neutron_security_groups():
'''
for rid in relation_ids('neutron-plugin-api'):
for unit in related_units(rid):
sec_group=relation_get('neutron_security_groups',rid=rid, unit=unit)
sec_group = relation_get('neutron_security_groups',
rid=rid,
unit=unit)
if sec_group is not None:
return sec_group
return False
class OVSPluginContext(context.NeutronContext):
interfaces = []

View File

@ -34,6 +34,7 @@ def install():
apt_update()
apt_install(determine_packages(), fatal=True)
@hooks.hook('upgrade-charm')
@hooks.hook('neutron-plugin-relation-changed')
@hooks.hook('config-changed')
@ -41,6 +42,7 @@ def install():
def config_changed():
CONFIGS.write_all()
@hooks.hook('amqp-relation-joined')
def amqp_joined(relation_id=None):
relation_set(relation_id=relation_id,
@ -57,6 +59,7 @@ def amqp_changed():
return
CONFIGS.write(NEUTRON_CONF)
def main():
try:
hooks.execute(sys.argv)

View File

@ -4,10 +4,9 @@ from copy import deepcopy
from charmhelpers.contrib.openstack import context, templating
from collections import OrderedDict
from charmhelpers.contrib.openstack.utils import (
os_release,
os_release,
)
import neutron_ovs_context
from charmhelpers.core.hookenv import is_relation_made
NOVA_CONF_DIR = "/etc/nova"
NEUTRON_CONF_DIR = "/etc/neutron"
@ -28,6 +27,7 @@ BASE_RESOURCE_MAP = OrderedDict([
])
TEMPLATES = 'templates/'
def determine_packages():
ovs_pkgs = []
pkgs = neutron_plugin_attribute('ovs', 'packages',
@ -37,6 +37,7 @@ def determine_packages():
return set(ovs_pkgs)
def register_configs(release=None):
release = release or os_release('nova-common')
configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
@ -45,6 +46,7 @@ def register_configs(release=None):
configs.register(cfg, rscs['contexts'])
return configs
def resource_map():
'''
Dynamically generate a map of resources that will be managed for a single
@ -53,6 +55,7 @@ def resource_map():
resource_map = deepcopy(BASE_RESOURCE_MAP)
return resource_map
def restart_map():
'''
Constructs a restart map based on charm config settings and relation

2
unit_tests/__init__.py Normal file
View File

@ -0,0 +1,2 @@
import sys
sys.path.append('hooks/')

View File

@ -0,0 +1,71 @@
from test_utils import CharmTestCase
from mock import patch
import neutron_ovs_context as context
import charmhelpers
TO_PATCH = [
'relation_get',
'relation_ids',
'related_units',
'config',
'unit_get',
'add_bridge',
'service_running',
'service_start',
'get_host_ip',
]
class OVSPluginContextTest(CharmTestCase):
def setUp(self):
super(OVSPluginContextTest, self).setUp(context, TO_PATCH)
self.relation_get.side_effect = self.test_relation.get
self.config.side_effect = self.test_config.get
self.test_config.set('debug', True)
self.test_config.set('verbose', True)
self.test_config.set('use-syslog', True)
def tearDown(self):
super(OVSPluginContextTest, self).tearDown()
@patch.object(charmhelpers.contrib.openstack.context, 'config')
@patch.object(charmhelpers.contrib.openstack.context, 'unit_get')
@patch.object(charmhelpers.contrib.openstack.context, 'is_clustered')
@patch.object(charmhelpers.contrib.openstack.context, 'https')
@patch.object(context.OVSPluginContext, '_save_flag_file')
@patch.object(context.OVSPluginContext, '_ensure_packages')
@patch.object(charmhelpers.contrib.openstack.context, 'neutron_plugin_attribute')
@patch.object(charmhelpers.contrib.openstack.context, 'unit_private_ip')
def test_neutroncc_context_api_rel(self, _unit_priv_ip, _npa, _ens_pkgs, _save_ff, _https, _is_clus, _unit_get, _config):
def mock_npa(plugin, section, manager):
if section == "driver":
return "neutron.randomdriver"
if section == "config":
return "neutron.randomconfig"
_npa.side_effect = mock_npa
_config.return_value = 'ovs'
_unit_get.return_value = '127.0.0.13'
_unit_priv_ip.return_value = '127.0.0.14'
_is_clus.return_value = False
self.related_units.return_value = ['unit1']
self.relation_ids.return_value = ['rid2']
self.test_relation.set({'neutron_security_groups': 'yes'})
self.get_host_ip.return_value = '127.0.0.15'
self.service_running.return_value = False
napi_ctxt = context.OVSPluginContext()
expect = {
'neutron_alchemy_flags': {},
'neutron_security_groups': 'yes',
'verbose': True,
'local_ip': '127.0.0.15',
'config': 'neutron.randomconfig',
'use_syslog': True,
'network_manager': 'neutron',
'debug': True,
'core_plugin': 'neutron.randomdriver',
'neutron_plugin': 'ovs',
'neutron_url': 'https://127.0.0.13:9696'
}
self.assertEquals(expect, napi_ctxt())
self.service_start.assertCalled()

View File

@ -0,0 +1,75 @@
from mock import MagicMock, patch, call
from test_utils import CharmTestCase
with patch('charmhelpers.core.hookenv.config') as config:
config.return_value = 'neutron'
import neutron_ovs_utils as utils
_reg = utils.register_configs
_map = utils.restart_map
utils.register_configs = MagicMock()
utils.restart_map = MagicMock()
import neutron_ovs_hooks as hooks
utils.register_configs = _reg
utils.restart_map = _map
TO_PATCH = [
'apt_update',
'apt_install',
'config',
'CONFIGS',
'determine_packages',
'log',
'relation_set',
]
NEUTRON_CONF_DIR = "/etc/neutron"
NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR
class NeutronOVSHooksTests(CharmTestCase):
def setUp(self):
super(NeutronOVSHooksTests, self).setUp(hooks, TO_PATCH)
self.config.side_effect = self.test_config.get
def _call_hook(self, hookname):
hooks.hooks.execute([
'hooks/{}'.format(hookname)])
def test_install_hook(self):
_pkgs = ['foo', 'bar']
self.determine_packages.return_value = _pkgs
self._call_hook('install')
self.apt_update.assert_called_with()
self.apt_install.assert_has_calls([
call(_pkgs, fatal=True),
])
def test_config_changed(self):
self._call_hook('config-changed')
self.assertTrue(self.CONFIGS.write_all.called)
def test_amqp_joined(self):
self._call_hook('amqp-relation-joined')
self.relation_set.assert_called_with(
username='neutron',
vhost='openstack',
relation_id=None
)
def test_amqp_changed(self):
self.CONFIGS.complete_contexts.return_value = ['amqp']
self._call_hook('amqp-relation-changed')
self.assertTrue(self.CONFIGS.write.called_with(NEUTRON_CONF))
def test_amqp_departed(self):
self._call_hook('amqp-relation-departed')
self.assertTrue(self.CONFIGS.write.called_with(NEUTRON_CONF))

View File

@ -0,0 +1,95 @@
from mock import MagicMock, call, patch
from collections import OrderedDict
import charmhelpers.contrib.openstack.templating as templating
templating.OSConfigRenderer = MagicMock()
import neutron_ovs_utils as nutils
from test_utils import (
CharmTestCase,
patch_open,
)
import charmhelpers
import charmhelpers.core.hookenv as hookenv
TO_PATCH = [
'os_release',
'neutron_plugin_attribute',
]
head_pkg = 'linux-headers-3.15.0-5-generic'
def _mock_npa(plugin, attr, net_manager=None):
plugins = {
'ovs': {
'config': '/etc/neutron/plugins/ml2/ml2_conf.ini',
'driver': 'neutron.plugins.ml2.plugin.Ml2Plugin',
'contexts': [],
'services': ['neutron-plugin-openvswitch-agent'],
'packages': [[head_pkg], ['neutron-plugin-openvswitch-agent']],
'server_packages': ['neutron-server',
'neutron-plugin-ml2'],
'server_services': ['neutron-server']
},
}
return plugins[plugin][attr]
class TestNeutronOVSUtils(CharmTestCase):
def setUp(self):
super(TestNeutronOVSUtils, self).setUp(nutils, TO_PATCH)
self.neutron_plugin_attribute.side_effect = _mock_npa
def tearDown(self):
# Reset cached cache
hookenv.cache = {}
@patch.object(charmhelpers.contrib.openstack.neutron,'os_release')
@patch.object(charmhelpers.contrib.openstack.neutron,'headers_package')
def test_determine_packages(self, _head_pkgs, _os_rel):
_os_rel.return_value = 'trusty'
_head_pkgs.return_value = head_pkg
pkg_list = nutils.determine_packages()
expect = ['neutron-plugin-openvswitch-agent', head_pkg]
self.assertItemsEqual(pkg_list, expect)
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 = ['/etc/neutron/neutron.conf',
'/etc/neutron/plugins/ml2/ml2_conf.ini']
self.assertItemsEqual(_regconfs.configs, confs)
def test_resource_map(self):
_map = nutils.resource_map()
confs = [nutils.NEUTRON_CONF]
[self.assertIn(q_conf, _map.keys()) for q_conf in confs]
def test_restart_map(self):
_restart_map = nutils.restart_map()
ML2CONF = "/etc/neutron/plugins/ml2/ml2_conf.ini"
expect = OrderedDict([
(nutils.NEUTRON_CONF,
['neutron-plugin-openvswitch-agent'],
),
(ML2CONF,
['neutron-plugin-openvswitch-agent'],
),
])
self.assertTrue(len(expect) == len(_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
View 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