Rework the default handlers into layer-openstack-*

Due to commit 95493a4 in charms.reactive, the default handlers in
charms.openstack broke.  This is because charms.reactive no longer will
run handlers that are not in the hooks/ or reactive/ directory tree; the
default handlers in charms.openstack are in the library code, which is
pip installed into the module packages.

This patch, and related patches in layer-openstack and
layer-openstack-api enable the default handlers to function again.  Note
that from a charm author perspective, the API to default handlers is
identical.  This is merely an implementation change.

Change-Id: I7786a39ab29ab71a4cdb8bd652e8927cbd5fcf5c
Partial-Bug: #1707685
This commit is contained in:
Alex Kavanagh 2017-08-01 10:11:39 +01:00
parent 03ba9967c3
commit 698337c5ee
2 changed files with 79 additions and 186 deletions

View File

@ -1,5 +1,4 @@
import charmhelpers.contrib.openstack.utils as os_utils
import charmhelpers.core.hookenv as hookenv
import charmhelpers.core.unitdata as unitdata
import charms.reactive as reactive
@ -69,20 +68,12 @@ def _map_default_handler(state):
@_map_default_handler('charm.installed')
def make_default_install_handler():
@reactive.when_not('charm.installed')
def default_install():
"""Provide a default install handler
The instance automagically becomes the derived OpenStackCharm instance.
The kv() key charmers.openstack-release-version' is used to cache the
release being used for this charm. It is determined by the
default_select_release() function below, unless this is overriden by
the charm author
"""
unitdata.kv().unset(OPENSTACK_RELEASE_KEY)
OpenStackCharm.singleton.install()
reactive.set_state('charm.installed')
"""Set the default charm.installed state so that the default handler in
layer-openstack can run.
Convoluted, because charms.reactive will only run handlers in the reactive
or hooks directory.
"""
reactive.set_state('charms.openstack.do-default-charm.installed')
@_map_default_handler('charm.default-select-release')
@ -109,94 +100,64 @@ def make_default_select_release_handler():
@_map_default_handler('amqp.connected')
def make_default_amqp_connection_handler():
@reactive.when('amqp.connected')
def default_amqp_connection(amqp):
"""Handle the default amqp connection.
This requires that the charm implements get_amqp_credentials() to
provide a tuple of the (user, vhost) for the amqp server
"""
instance = OpenStackCharm.singleton
user, vhost = instance.get_amqp_credentials()
amqp.request_access(username=user, vhost=vhost)
instance.assess_status()
"""Set the default amqp.connected state so that the default handler in
layer-openstack can run.
Convoluted, because charms.reactive will only run handlers in the reactive
or hooks directory.
"""
reactive.set_state('charms.openstack.do-default-amqp.connected')
@_map_default_handler('shared-db.connected')
def make_default_setup_database_handler():
@reactive.when('shared-db.connected')
def default_setup_database(database):
"""Handle the default database connection setup
This requires that the charm implements get_database_setup() to provide
a list of dictionaries;
[{'database': ..., 'username': ..., 'hostname': ..., 'prefix': ...}]
The prefix can be missing: it defaults to None.
"""
instance = OpenStackCharm.singleton
for db in instance.get_database_setup():
database.configure(**db)
instance.assess_status()
"""Set the default shared-db.connected state so that the default handler in
layer-openstack can run.
Convoluted, because charms.reactive will only run handlers in the reactive
or hooks directory.
"""
reactive.set_state('charms.openstack.do-default-shared-db.connected')
@_map_default_handler('identity-service.connected')
def make_default_setup_endpoint_connection():
@reactive.when('identity-service.connected')
def default_setup_endpoint_connection(keystone):
"""When the keystone interface connects, register this unit into the
catalog. This is the default handler, and calls on the charm class to
provide the endpoint information. If multiple endpoints are needed,
then a custom endpoint handler will be needed.
"""
instance = OpenStackCharm.singleton
keystone.register_endpoints(instance.service_type,
instance.region,
instance.public_url,
instance.internal_url,
instance.admin_url)
instance.assess_status()
"""Set the default identity-service.connected state so that the default
handler in layer-openstack can run.
Convoluted, because charms.reactive will only run handlers in the reactive
or hooks directory.
"""
reactive.set_state(
'charms.openstack.do-default-identity-service.connected')
@_map_default_handler('identity-service.available')
def make_setup_endpoint_available_handler():
@reactive.when('identity-service.available')
def default_setup_endpoint_available(keystone):
"""When the identity-service interface is available, this default
handler switches on the SSL support.
"""
instance = OpenStackCharm.singleton
instance.configure_ssl(keystone)
instance.assess_status()
"""Set the default identity-service.available state so that the default
handler in layer-openstack can run.
Convoluted, because charms.reactive will only run handlers in the reactive
or hooks directory.
"""
reactive.set_state(
'charms.openstack.do-default-identity-service.available')
@_map_default_handler('config.changed')
def make_default_config_changed_handler():
@reactive.when('config.changed')
def default_config_changed():
"""Default handler for config.changed state from reactive. Just see if
our status has changed. This is just to clear any errors that may have
got stuck due to missing async handlers, etc.
"""
instance = OpenStackCharm.singleton
instance.config_changed()
instance.assess_status()
"""Set the default config.changed state so that the default handler in
layer-openstack can run.
Convoluted, because charms.reactive will only run handlers in the reactive
or hooks directory.
"""
reactive.set_state('charms.openstack.do-default-config.changed')
@_map_default_handler('upgrade-charm')
def make_default_upgrade_charm_handler():
@reactive.hook('upgrade-charm')
def default_upgrade_charm():
"""Default handler for the 'upgrade-charm' hook.
This calls the charm.singleton.upgrade_charm() function as a default.
"""
OpenStackCharm.singleton.upgrade_charm()
"""Set the default upgrade-charm state so that the default handler in
layer-openstack can run.
Convoluted, because charms.reactive will only run handlers in the reactive
or hooks directory.
"""
reactive.set_state('charms.openstack.do-default-upgrade-charm')
def default_render_configs(*interfaces):
@ -214,12 +175,9 @@ def default_render_configs(*interfaces):
@_map_default_handler('update-status')
def make_default_update_status_handler():
@reactive.hook('update-status')
def default_update_status():
"""Default handler for update-status state.
Just call update status.
"""
instance = OpenStackCharm.singleton
hookenv.application_version_set(instance.application_version)
instance.assess_status()
"""Set the default upgrade-status state so that the default handler in
layer-openstack can run.
Convoluted, because charms.reactive will only run handlers in the reactive
or hooks directory.
"""
reactive.set_state('charms.openstack.do-default-update-status')

View File

@ -86,22 +86,11 @@ class TestDefaults(BaseOpenStackCharmTest):
def test_default_install_handler(self):
self.assertIn('charm.installed', chm._default_handler_map)
self.patch_object(chm.reactive, 'when_not')
h = self.mock_decorator_gen()
self.when_not.side_effect = h.decorator
# call the default handler installer function, and check its map.
self.patch_object(chm.reactive, 'set_state')
f = chm._default_handler_map['charm.installed']
f()
self.assertIn('charm.installed', h.map)
# verify that the installed function calls the charm installer
self.patch_object(chm, 'OpenStackCharm', name='charm')
kv = mock.MagicMock()
self.patch_object(chm.unitdata, 'kv', new=lambda: kv)
self.patch_object(chm.reactive, 'set_state')
h.map['charm.installed']()
kv.unset.assert_called_once_with(chm.OPENSTACK_RELEASE_KEY)
self.charm.singleton.install.assert_called_once_with()
self.set_state.assert_called_once_with('charm.installed')
self.set_state.assert_called_once_with(
'charms.openstack.do-default-charm.installed')
def test_default_select_release_handler(self):
self.assertIn('charm.default-select-release', chm._default_handler_map)
@ -133,116 +122,62 @@ class TestDefaults(BaseOpenStackCharmTest):
def test_default_amqp_connection_handler(self):
self.assertIn('amqp.connected', chm._default_handler_map)
self.patch_object(chm.reactive, 'when')
h = self.mock_decorator_gen()
self.when.side_effect = h.decorator
self.patch_object(chm.reactive, 'set_state')
# call the default handler installer function, and check its map.
f = chm._default_handler_map['amqp.connected']
f()
self.assertIn('amqp.connected', h.map)
# verify that the installed function works
self.patch_object(chm, 'OpenStackCharm', name='charm')
self.charm.singleton.get_amqp_credentials.return_value = \
('user', 'vhost')
amqp = mock.MagicMock()
h.map['amqp.connected'](amqp)
self.charm.singleton.get_amqp_credentials.assert_called_once_with()
amqp.request_access.assert_called_once_with(username='user',
vhost='vhost')
self.charm.singleton.assess_status.assert_called_once_with()
self.set_state.assert_called_once_with(
'charms.openstack.do-default-amqp.connected')
def test_default_setup_datatbase_handler(self):
self.assertIn('shared-db.connected', chm._default_handler_map)
self.patch_object(chm.reactive, 'when')
h = self.mock_decorator_gen()
self.when.side_effect = h.decorator
self.patch_object(chm.reactive, 'set_state')
# call the default handler installer function, and check its map.
f = chm._default_handler_map['shared-db.connected']
f()
self.assertIn('shared-db.connected', h.map)
# verify that the installed function works
self.patch_object(chm, 'OpenStackCharm', name='charm')
self.charm.singleton.get_database_setup.return_value = [
{'database': 'configuration'}]
database = mock.MagicMock()
h.map['shared-db.connected'](database)
self.charm.singleton.get_database_setup.assert_called_once_with()
database.configure.assert_called_once_with(database='configuration')
self.charm.singleton.assess_status.assert_called_once_with()
self.set_state.assert_called_once_with(
'charms.openstack.do-default-shared-db.connected')
def test_default_setup_endpoint_handler(self):
self.assertIn('identity-service.connected', chm._default_handler_map)
self.patch_object(chm.reactive, 'when')
h = self.mock_decorator_gen()
self.when.side_effect = h.decorator
# call the default handler installer function, and check its map.
self.patch_object(chm.reactive, 'set_state')
f = chm._default_handler_map['identity-service.connected']
f()
self.assertIn('identity-service.connected', h.map)
# verify that the installed function works
OpenStackCharm = mock.MagicMock()
class Instance(object):
service_type = 'type1'
region = 'region1'
public_url = 'public_url'
internal_url = 'internal_url'
admin_url = 'admin_url'
assess_status = mock.MagicMock()
OpenStackCharm.singleton = Instance
with mock.patch.object(chm, 'OpenStackCharm', new=OpenStackCharm):
keystone = mock.MagicMock()
h.map['identity-service.connected'](keystone)
keystone.register_endpoints.assert_called_once_with(
'type1', 'region1', 'public_url', 'internal_url', 'admin_url')
Instance.assess_status.assert_called_once_with()
self.set_state.assert_called_once_with(
'charms.openstack.do-default-identity-service.connected')
def test_default_setup_endpoint_available_handler(self):
self.assertIn('identity-service.available', chm._default_handler_map)
self.patch_object(chm.reactive, 'when')
h = self.mock_decorator_gen()
self.when.side_effect = h.decorator
self.patch_object(chm.reactive, 'set_state')
# call the default handler installer function, and check its map.
f = chm._default_handler_map['identity-service.available']
f()
self.assertIn('identity-service.available', h.map)
# verify that the installed function works
self.patch_object(chm, 'OpenStackCharm', name='charm')
h.map['identity-service.available']('keystone')
self.charm.singleton.configure_ssl.assert_called_once_with('keystone')
self.charm.singleton.assess_status.assert_called_once_with()
self.set_state.assert_called_once_with(
'charms.openstack.do-default-identity-service.available')
def test_default_config_changed_handler(self):
self.assertIn('config.changed', chm._default_handler_map)
self.patch_object(chm.reactive, 'when')
h = self.mock_decorator_gen()
self.when.side_effect = h.decorator
# call the default handler installer function, and check its map.
self.patch_object(chm.reactive, 'set_state')
f = chm._default_handler_map['config.changed']
f()
self.assertIn('config.changed', h.map)
# verify that the installed function works
self.patch_object(chm, 'OpenStackCharm', name='charm')
h.map['config.changed']()
self.charm.singleton.assess_status.assert_called_once_with()
self.set_state.assert_called_once_with(
'charms.openstack.do-default-config.changed')
def test_default_update_status_handler(self):
self.assertIn('update-status', chm._default_handler_map)
self.patch_object(chm.reactive, 'hook')
h = self.mock_decorator_gen()
self.hook.side_effect = h.decorator
# call the default handler installer function, and check its map.
self.patch_object(chm.reactive, 'set_state')
f = chm._default_handler_map['update-status']
f()
self.assertIn('update-status', h.map)
# verify that the installed function works
self.patch_object(chm, 'OpenStackCharm', name='charm')
self.patch_object(chm.hookenv, 'application_version_set')
h.map['update-status']()
self.charm.singleton.assess_status.assert_called_once_with()
self.application_version_set.assert_called_once_with(mock.ANY)
self.set_state.assert_called_once_with(
'charms.openstack.do-default-update-status')
def test_default_upgrade_charm_handler(self):
self.assertIn('upgrade-charm', chm._default_handler_map)
self.patch_object(chm.reactive, 'set_state')
f = chm._default_handler_map['upgrade-charm']
f()
self.set_state.assert_called_once_with(
'charms.openstack.do-default-upgrade-charm')
def test_default_render_configs(self):
self.patch_object(chm, 'OpenStackCharm', name='charm')