Add unit tests
This commit is contained in:
parent
502cd9fd15
commit
983929d169
6
.coveragerc
Normal file
6
.coveragerc
Normal 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
17
.project
Normal 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
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>/ceilometer-agent/hooks</path>
|
||||||
|
<path>/ceilometer-agent/unit_tests</path>
|
||||||
|
</pydev_pathproperty>
|
||||||
|
</pydev_project>
|
@ -1,10 +1,7 @@
|
|||||||
import os
|
|
||||||
import uuid
|
|
||||||
from charmhelpers.core.hookenv import (
|
from charmhelpers.core.hookenv import (
|
||||||
relation_ids,
|
relation_ids,
|
||||||
relation_get,
|
relation_get,
|
||||||
related_units,
|
related_units,
|
||||||
config
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from charmhelpers.contrib.openstack.context import (
|
from charmhelpers.contrib.openstack.context import (
|
||||||
@ -12,55 +9,6 @@ from charmhelpers.contrib.openstack.context import (
|
|||||||
context_complete
|
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):
|
class CeilometerServiceContext(OSContextGenerator):
|
||||||
interfaces = ['ceilometer-service']
|
interfaces = ['ceilometer-service']
|
||||||
|
@ -7,9 +7,6 @@ from charmhelpers.fetch import (
|
|||||||
)
|
)
|
||||||
from charmhelpers.core.hookenv import (
|
from charmhelpers.core.hookenv import (
|
||||||
config,
|
config,
|
||||||
relation_ids,
|
|
||||||
related_units,
|
|
||||||
relation_get,
|
|
||||||
Hooks, UnregisteredHookError,
|
Hooks, UnregisteredHookError,
|
||||||
log
|
log
|
||||||
)
|
)
|
||||||
@ -37,9 +34,9 @@ def install():
|
|||||||
filter_installed_packages(CEILOMETER_AGENT_PACKAGES),
|
filter_installed_packages(CEILOMETER_AGENT_PACKAGES),
|
||||||
fatal=True)
|
fatal=True)
|
||||||
|
|
||||||
# TODO(jamespage): Locally scoped relation for nova and others
|
# TODO(jamespage): Locally scoped relation for nova and others
|
||||||
#ceilometer_utils.modify_config_file(ceilometer_utils.NOVA_CONF,
|
#ceilometer_utils.modify_config_file(ceilometer_utils.NOVA_CONF
|
||||||
# ceilometer_utils.NOVA_SETTINGS)
|
# ceilometer_utils.NOVA_SETTINGS)
|
||||||
|
|
||||||
|
|
||||||
@hooks.hook("ceilometer-service-relation-changed",
|
@hooks.hook("ceilometer-service-relation-changed",
|
||||||
|
@ -44,7 +44,7 @@ def register_configs():
|
|||||||
# just default to earliest supported release. configs dont get touched
|
# just default to earliest supported release. configs dont get touched
|
||||||
# till post-install, anyway.
|
# till post-install, anyway.
|
||||||
release = get_os_codename_package('ceilometer-common', fatal=False) \
|
release = get_os_codename_package('ceilometer-common', fatal=False) \
|
||||||
or 'grizzly'
|
or 'grizzly'
|
||||||
configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
|
configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
|
||||||
openstack_release=release)
|
openstack_release=release)
|
||||||
|
|
||||||
|
1
hooks/start
Symbolic link
1
hooks/start
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
ceilometer_hooks.py
|
1
hooks/stop
Symbolic link
1
hooks/stop
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
ceilometer_hooks.py
|
@ -11,6 +11,9 @@ description: |
|
|||||||
.
|
.
|
||||||
This charm should be used in conjunction with the ceilometer and nova charm to collect
|
This charm should be used in conjunction with the ceilometer and nova charm to collect
|
||||||
Openstack measures.
|
Openstack measures.
|
||||||
|
categories:
|
||||||
|
- miscellanous
|
||||||
|
- openstack
|
||||||
requires:
|
requires:
|
||||||
container:
|
container:
|
||||||
interface: juju-info
|
interface: juju-info
|
||||||
|
6
setup.cfg
Normal file
6
setup.cfg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[nosetests]
|
||||||
|
verbosity=1
|
||||||
|
with-coverage=1
|
||||||
|
cover-erase=1
|
||||||
|
cover-package=hooks
|
||||||
|
|
2
unit_tests/__init__.py
Normal file
2
unit_tests/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import sys
|
||||||
|
sys.path.append('hooks')
|
35
unit_tests/test_ceilometer_contexts.py
Normal file
35
unit_tests/test_ceilometer_contexts.py
Normal 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()(), {})
|
48
unit_tests/test_ceilometer_hooks.py
Normal file
48
unit_tests/test_ceilometer_hooks.py
Normal 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)
|
34
unit_tests/test_ceilometer_utils.py
Normal file
34
unit_tests/test_ceilometer_utils.py
Normal 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
111
unit_tests/test_utils.py
Normal 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
|
Loading…
x
Reference in New Issue
Block a user