Add unit tests

This commit is contained in:
James Page 2013-10-20 15:28:18 -07:00
parent 502cd9fd15
commit 983929d169
15 changed files with 277 additions and 59 deletions

6
.coveragerc Normal file
View File

@ -0,0 +1,6 @@
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
if __name__ == .__main__.:
include=
hooks/ceilometer_*

17
.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ceilometer-agent</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
View 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>/ceilometer-agent/hooks</path>
<path>/ceilometer-agent/unit_tests</path>
</pydev_pathproperty>
</pydev_project>

View File

@ -1,10 +1,7 @@
import os
import uuid
from charmhelpers.core.hookenv import (
relation_ids,
relation_get,
related_units,
config
)
from charmhelpers.contrib.openstack.context import (
@ -12,55 +9,6 @@ from charmhelpers.contrib.openstack.context import (
context_complete
)
CEILOMETER_DB = 'ceilometer'
class LoggingConfigContext(OSContextGenerator):
def __call__(self):
return {'debug': config('debug'), 'verbose': config('verbose')}
class MongoDBContext(OSContextGenerator):
interfaces = ['mongodb']
def __call__(self):
for relid in relation_ids('shared-db'):
for unit in related_units(relid):
conf = {
"db_host": relation_get('hostname', unit, relid),
"db_port": relation_get('port', unit, relid),
"db_name": CEILOMETER_DB
}
if context_complete(conf):
return conf
return {}
SHARED_SECRET = "/etc/ceilometer/secret.txt"
def get_shared_secret():
secret = None
if not os.path.exists(SHARED_SECRET):
secret = str(uuid.uuid4())
with open(SHARED_SECRET, 'w') as secret_file:
secret_file.write(secret)
else:
with open(SHARED_SECRET, 'r') as secret_file:
secret = secret_file.read().strip()
return secret
CEILOMETER_PORT = 8777
class CeilometerContext(OSContextGenerator):
def __call__(self):
ctxt = {
'port': CEILOMETER_PORT,
'metering_secret': get_shared_secret()
}
return ctxt
class CeilometerServiceContext(OSContextGenerator):
interfaces = ['ceilometer-service']

View File

@ -7,9 +7,6 @@ from charmhelpers.fetch import (
)
from charmhelpers.core.hookenv import (
config,
relation_ids,
related_units,
relation_get,
Hooks, UnregisteredHookError,
log
)
@ -37,9 +34,9 @@ def install():
filter_installed_packages(CEILOMETER_AGENT_PACKAGES),
fatal=True)
# TODO(jamespage): Locally scoped relation for nova and others
#ceilometer_utils.modify_config_file(ceilometer_utils.NOVA_CONF,
# ceilometer_utils.NOVA_SETTINGS)
# TODO(jamespage): Locally scoped relation for nova and others
#ceilometer_utils.modify_config_file(ceilometer_utils.NOVA_CONF
# ceilometer_utils.NOVA_SETTINGS)
@hooks.hook("ceilometer-service-relation-changed",

View File

@ -44,7 +44,7 @@ def register_configs():
# just default to earliest supported release. configs dont get touched
# till post-install, anyway.
release = get_os_codename_package('ceilometer-common', fatal=False) \
or 'grizzly'
or 'grizzly'
configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
openstack_release=release)

1
hooks/start Symbolic link
View File

@ -0,0 +1 @@
ceilometer_hooks.py

1
hooks/stop Symbolic link
View File

@ -0,0 +1 @@
ceilometer_hooks.py

View File

@ -11,6 +11,9 @@ description: |
.
This charm should be used in conjunction with the ceilometer and nova charm to collect
Openstack measures.
categories:
- miscellanous
- openstack
requires:
container:
interface: juju-info

6
setup.cfg Normal file
View File

@ -0,0 +1,6 @@
[nosetests]
verbosity=1
with-coverage=1
cover-erase=1
cover-package=hooks

2
unit_tests/__init__.py Normal file
View File

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

View File

@ -0,0 +1,35 @@
from mock import patch
import ceilometer_contexts as contexts
from test_utils import CharmTestCase
TO_PATCH = [
'relation_get',
'relation_ids',
'related_units',
]
class CeilometerContextsTest(CharmTestCase):
def setUp(self):
super(CeilometerContextsTest, self).setUp(contexts, TO_PATCH)
self.relation_get.side_effect = self.test_relation.get
def tearDown(self):
super(CeilometerContextsTest, self).tearDown()
def test_ceilometer_service_context(self):
self.relation_ids.return_value = ['ceilometer-service:0']
self.related_units.return_value = ['ceilometer/0']
data = {
'metering_secret': 'mysecret',
'keystone_host': 'test'
}
self.test_relation.set(data)
self.assertEquals(contexts.CeilometerServiceContext()(), data)
def test_ceilometer_service_context_not_related(self):
self.relation_ids.return_value = []
self.assertEquals(contexts.CeilometerServiceContext()(), {})

View File

@ -0,0 +1,48 @@
from mock import patch, MagicMock
import ceilometer_utils
# Patch out register_configs for import of hooks
_register_configs = ceilometer_utils.register_configs
ceilometer_utils.register_configs = MagicMock()
import ceilometer_hooks as hooks
# Renable old function
ceilometer_utils.register_configs = _register_configs
from test_utils import CharmTestCase
TO_PATCH = [
'configure_installation_source',
'apt_install',
'apt_update',
'config',
'filter_installed_packages',
'CONFIGS',
]
class CeilometerHooksTest(CharmTestCase):
def setUp(self):
super(CeilometerHooksTest, self).setUp(hooks, TO_PATCH)
self.config.side_effect = self.test_config.get
def test_configure_source(self):
self.test_config.set('openstack-origin', 'cloud:precise-havana')
hooks.hooks.execute(['hooks/install'])
self.configure_installation_source.\
assert_called_with('cloud:precise-havana')
def test_install_hook(self):
self.filter_installed_packages.return_value = \
hooks.CEILOMETER_AGENT_PACKAGES
hooks.hooks.execute(['hooks/install'])
self.assertTrue(self.configure_installation_source.called)
self.apt_update.assert_called_with(fatal=True)
self.apt_install.assert_called_with(hooks.CEILOMETER_AGENT_PACKAGES,
fatal=True)
def test_ceilometer_changed(self):
hooks.hooks.execute(['hooks/ceilometer-service-relation-changed'])
self.assertTrue(self.CONFIGS.write_all.called)

View File

@ -0,0 +1,34 @@
from mock import patch, call
import ceilometer_utils as utils
from test_utils import CharmTestCase
TO_PATCH = [
'get_os_codename_package',
'templating',
'CeilometerServiceContext',
]
class CeilometerUtilsTest(CharmTestCase):
def setUp(self):
super(CeilometerUtilsTest, self).setUp(utils, TO_PATCH)
def tearDown(self):
super(CeilometerUtilsTest, self).tearDown()
def test_register_configs(self):
configs = utils.register_configs()
calls = []
for conf in utils.CONFIG_FILES:
calls.append(call(conf,
utils.CONFIG_FILES[conf]['hook_contexts']))
configs.register.assert_has_calls(calls, any_order=True)
def test_restart_map(self):
restart_map = utils.restart_map()
self.assertEquals(restart_map,
{'/etc/ceilometer/ceilometer.conf': [
'ceilometer-agent-compute']})

111
unit_tests/test_utils.py Normal file
View File

@ -0,0 +1,111 @@
import logging
import unittest
import os
import yaml
import io
from contextlib import contextmanager
from mock import patch
@contextmanager
def mock_open(filename, contents=None):
''' Slightly simpler mock of open to return contents for filename '''
def mock_file(*args):
if args[0] == filename:
return io.StringIO(contents)
else:
return open(*args)
with patch('__builtin__.open', mock_file):
yield
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):
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, attr=None, unit=None, rid=None):
if attr is None:
return self.relation_data
elif attr in self.relation_data:
return self.relation_data[attr]
return None