diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..ed08ec9
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,6 @@
+[report]
+# Regexes for lines to exclude from consideration
+exclude_lines =
+ if __name__ == .__main__.:
+include=
+ hooks/ceilometer_*
diff --git a/.project b/.project
new file mode 100644
index 0000000..3c0a6a8
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ ceilometer-agent
+
+
+
+
+
+ org.python.pydev.PyDevBuilder
+
+
+
+
+
+ org.python.pydev.pythonNature
+
+
diff --git a/.pydevproject b/.pydevproject
new file mode 100644
index 0000000..ce82d9d
--- /dev/null
+++ b/.pydevproject
@@ -0,0 +1,9 @@
+
+
+python 2.7
+Default
+
+/ceilometer-agent/hooks
+/ceilometer-agent/unit_tests
+
+
diff --git a/hooks/ceilometer_contexts.py b/hooks/ceilometer_contexts.py
index 78d0709..3c4f143 100644
--- a/hooks/ceilometer_contexts.py
+++ b/hooks/ceilometer_contexts.py
@@ -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']
diff --git a/hooks/ceilometer_hooks.py b/hooks/ceilometer_hooks.py
index 880e672..c554877 100755
--- a/hooks/ceilometer_hooks.py
+++ b/hooks/ceilometer_hooks.py
@@ -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",
diff --git a/hooks/ceilometer_utils.py b/hooks/ceilometer_utils.py
index e312f23..e5db4af 100644
--- a/hooks/ceilometer_utils.py
+++ b/hooks/ceilometer_utils.py
@@ -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)
diff --git a/hooks/start b/hooks/start
new file mode 120000
index 0000000..c948469
--- /dev/null
+++ b/hooks/start
@@ -0,0 +1 @@
+ceilometer_hooks.py
\ No newline at end of file
diff --git a/hooks/stop b/hooks/stop
new file mode 120000
index 0000000..c948469
--- /dev/null
+++ b/hooks/stop
@@ -0,0 +1 @@
+ceilometer_hooks.py
\ No newline at end of file
diff --git a/metadata.yaml b/metadata.yaml
index 3230679..d759696 100644
--- a/metadata.yaml
+++ b/metadata.yaml
@@ -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
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..bb0670f
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,6 @@
+[nosetests]
+verbosity=1
+with-coverage=1
+cover-erase=1
+cover-package=hooks
+
diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py
new file mode 100644
index 0000000..f80aab3
--- /dev/null
+++ b/unit_tests/__init__.py
@@ -0,0 +1,2 @@
+import sys
+sys.path.append('hooks')
diff --git a/unit_tests/test_ceilometer_contexts.py b/unit_tests/test_ceilometer_contexts.py
new file mode 100644
index 0000000..ca9724e
--- /dev/null
+++ b/unit_tests/test_ceilometer_contexts.py
@@ -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()(), {})
diff --git a/unit_tests/test_ceilometer_hooks.py b/unit_tests/test_ceilometer_hooks.py
new file mode 100644
index 0000000..35db78b
--- /dev/null
+++ b/unit_tests/test_ceilometer_hooks.py
@@ -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)
diff --git a/unit_tests/test_ceilometer_utils.py b/unit_tests/test_ceilometer_utils.py
new file mode 100644
index 0000000..34e8b65
--- /dev/null
+++ b/unit_tests/test_ceilometer_utils.py
@@ -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']})
diff --git a/unit_tests/test_utils.py b/unit_tests/test_utils.py
new file mode 100644
index 0000000..e90679e
--- /dev/null
+++ b/unit_tests/test_utils.py
@@ -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