Add post hooks
This adds hooks after create, update and delete operations. Change-Id: Ie90d8881ec4b275e5513c21033ad004c52283ac0
This commit is contained in:
parent
7255eedc7d
commit
c1fd6c62e8
@ -137,15 +137,16 @@ template::
|
||||
my_db_server:
|
||||
"OS::DBInstance": file:///home/mine/all_my_cool_templates/db.yaml
|
||||
|
||||
Pause stack creation or update on a given resource
|
||||
--------------------------------------------------
|
||||
If you want to debug your stack as it's being created or updated, or if you want
|
||||
to run it in phases, you can set ``pre-create`` and ``pre-update`` hooks in the
|
||||
``resources`` section of ``resource_registry``.
|
||||
Pause stack creation, update or deletion on a given resource
|
||||
------------------------------------------------------------
|
||||
If you want to debug your stack as it's being created, updated or deleted, or
|
||||
if you want to run it in phases, you can set ``pre-create``, ``pre-update``,
|
||||
``pre-delete``, ``post-create``, ``post-update`` and ``post-delete`` hooks in
|
||||
the ``resources`` section of ``resource_registry``.
|
||||
|
||||
To set a hook, add either ``hooks: pre-create`` or ``hooks: pre-update`` to the
|
||||
resource's dictionary. You can also use ``[pre-create, pre-update]`` to stop
|
||||
on both actions.
|
||||
To set a hook, add either ``hooks: $hook_name`` (for example ``hooks:
|
||||
pre-update``) to the resource's dictionary. You can also use a list (``hooks:
|
||||
[pre-create, pre-update]``) to stop on several actions.
|
||||
|
||||
You can combine hooks with other ``resources`` properties such as provider
|
||||
templates or type mapping::
|
||||
@ -175,8 +176,8 @@ resource name. For example, the following entry pauses while creating
|
||||
"*_server":
|
||||
hooks: pre-create
|
||||
|
||||
Clear hooks by signaling the resource with ``{unset_hook: pre-create}``
|
||||
or ``{unset_hook: pre-update}``.
|
||||
Clear hooks by signaling the resource with ``{unset_hook: $hook_name}`` (for
|
||||
example ``{unset_hook: pre-update}``).
|
||||
|
||||
Retrieving events
|
||||
-----------------
|
||||
|
@ -35,9 +35,11 @@ LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
HOOK_TYPES = (
|
||||
HOOK_PRE_CREATE, HOOK_PRE_UPDATE, HOOK_PRE_DELETE
|
||||
HOOK_PRE_CREATE, HOOK_PRE_UPDATE, HOOK_PRE_DELETE, HOOK_POST_CREATE,
|
||||
HOOK_POST_UPDATE, HOOK_POST_DELETE
|
||||
) = (
|
||||
'pre-create', 'pre-update', 'pre-delete'
|
||||
'pre-create', 'pre-update', 'pre-delete', 'post-create',
|
||||
'post-update', 'post-delete'
|
||||
)
|
||||
|
||||
RESTRICTED_ACTIONS = (UPDATE, REPLACE) = ('update', 'replace')
|
||||
|
@ -790,7 +790,8 @@ class Resource(object):
|
||||
try:
|
||||
yield self._do_action(action, self.properties.validate)
|
||||
if action == self.CREATE:
|
||||
return
|
||||
first_failure = None
|
||||
break
|
||||
else:
|
||||
action = self.CREATE
|
||||
except exception.ResourceFailure as failure:
|
||||
@ -809,6 +810,10 @@ class Resource(object):
|
||||
if first_failure:
|
||||
raise first_failure
|
||||
|
||||
if self.stack.action == self.stack.CREATE:
|
||||
yield self._break_if_required(
|
||||
self.CREATE, environment.HOOK_POST_CREATE)
|
||||
|
||||
def prepare_abandon(self):
|
||||
self.abandon_in_progress = True
|
||||
return {
|
||||
@ -1084,6 +1089,9 @@ class Resource(object):
|
||||
raise failure
|
||||
raise
|
||||
|
||||
yield self._break_if_required(
|
||||
self.UPDATE, environment.HOOK_POST_UPDATE)
|
||||
|
||||
def prepare_for_replace(self):
|
||||
"""Prepare resource for replacing.
|
||||
|
||||
@ -1381,6 +1389,10 @@ class Resource(object):
|
||||
action_args = []
|
||||
yield self.action_handler_task(action, *action_args)
|
||||
|
||||
if self.stack.action == self.stack.DELETE:
|
||||
yield self._break_if_required(
|
||||
self.DELETE, environment.HOOK_POST_DELETE)
|
||||
|
||||
@scheduler.wrappertask
|
||||
def destroy(self):
|
||||
"""A task to delete the resource and remove it from the database."""
|
||||
|
@ -384,6 +384,7 @@ class AccessKeyTest(common.HeatTestCase):
|
||||
rsrc)
|
||||
|
||||
rsrc._secret = None
|
||||
rsrc._data = None
|
||||
self.assertEqual(self.fc.secret,
|
||||
rsrc.FnGetAtt('SecretAccessKey'))
|
||||
|
||||
|
@ -779,7 +779,8 @@ class ResourceRegistryTest(common.HeatTestCase):
|
||||
registry = environment.ResourceRegistry(None, {})
|
||||
msg = ('Invalid hook type "invalid-type" for resource breakpoint, '
|
||||
'acceptable hook types are: (\'pre-create\', \'pre-update\', '
|
||||
'\'pre-delete\')')
|
||||
'\'pre-delete\', \'post-create\', \'post-update\', '
|
||||
'\'post-delete\')')
|
||||
ex = self.assertRaises(exception.InvalidBreakPointHook,
|
||||
registry.load, {'resources': resources})
|
||||
self.assertEqual(msg, six.text_type(ex))
|
||||
@ -851,18 +852,23 @@ class ResourceRegistryTest(common.HeatTestCase):
|
||||
|
||||
class HookMatchTest(common.HeatTestCase):
|
||||
|
||||
scenarios = [(hook_type, {'hook': hook_type}) for hook_type in
|
||||
environment.HOOK_TYPES]
|
||||
|
||||
def test_plain_matches(self):
|
||||
other_hook = next(hook for hook in environment.HOOK_TYPES
|
||||
if hook != self.hook)
|
||||
resources = {
|
||||
u'a': {
|
||||
u'OS::Fruit': u'apples.yaml',
|
||||
u'hooks': [u'pre-create', u'pre-update'],
|
||||
u'hooks': [self.hook, other_hook]
|
||||
},
|
||||
u'b': {
|
||||
u'OS::Food': u'fruity.yaml',
|
||||
},
|
||||
u'nested': {
|
||||
u'res': {
|
||||
u'hooks': 'pre-create',
|
||||
u'hooks': self.hook,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -871,96 +877,59 @@ class HookMatchTest(common.HeatTestCase):
|
||||
u'OS::Fruit': u'apples.yaml',
|
||||
'resources': resources})
|
||||
|
||||
self.assertTrue(registry.matches_hook(
|
||||
'a', environment.HOOK_PRE_CREATE))
|
||||
self.assertFalse(registry.matches_hook(
|
||||
'b', environment.HOOK_PRE_CREATE))
|
||||
self.assertFalse(registry.matches_hook(
|
||||
'OS::Fruit', environment.HOOK_PRE_CREATE))
|
||||
self.assertFalse(registry.matches_hook(
|
||||
'res', environment.HOOK_PRE_CREATE))
|
||||
self.assertFalse(registry.matches_hook(
|
||||
'unknown', environment.HOOK_PRE_CREATE))
|
||||
self.assertTrue(registry.matches_hook('a', self.hook))
|
||||
self.assertFalse(registry.matches_hook('b', self.hook))
|
||||
self.assertFalse(registry.matches_hook('OS::Fruit', self.hook))
|
||||
self.assertFalse(registry.matches_hook('res', self.hook))
|
||||
self.assertFalse(registry.matches_hook('unknown', self.hook))
|
||||
|
||||
def test_wildcard_matches(self):
|
||||
other_hook = next(hook for hook in environment.HOOK_TYPES
|
||||
if hook != self.hook)
|
||||
resources = {
|
||||
u'prefix_*': {
|
||||
u'hooks': 'pre-create',
|
||||
u'hooks': self.hook
|
||||
},
|
||||
u'*_suffix': {
|
||||
u'hooks': 'pre-create',
|
||||
u'hooks': self.hook
|
||||
},
|
||||
u'*': {
|
||||
u'hooks': 'pre-update',
|
||||
u'hooks': other_hook
|
||||
},
|
||||
}
|
||||
registry = environment.ResourceRegistry(None, {})
|
||||
registry.load({'resources': resources})
|
||||
|
||||
self.assertTrue(registry.matches_hook(
|
||||
'prefix_', environment.HOOK_PRE_CREATE))
|
||||
self.assertTrue(registry.matches_hook(
|
||||
'prefix_some', environment.HOOK_PRE_CREATE))
|
||||
self.assertFalse(registry.matches_hook(
|
||||
'some_prefix', environment.HOOK_PRE_CREATE))
|
||||
self.assertTrue(registry.matches_hook('prefix_', self.hook))
|
||||
self.assertTrue(registry.matches_hook('prefix_some', self.hook))
|
||||
self.assertFalse(registry.matches_hook('some_prefix', self.hook))
|
||||
|
||||
self.assertTrue(registry.matches_hook(
|
||||
'_suffix', environment.HOOK_PRE_CREATE))
|
||||
self.assertTrue(registry.matches_hook(
|
||||
'some_suffix', environment.HOOK_PRE_CREATE))
|
||||
self.assertFalse(registry.matches_hook(
|
||||
'_suffix_blah', environment.HOOK_PRE_CREATE))
|
||||
self.assertTrue(registry.matches_hook('_suffix', self.hook))
|
||||
self.assertTrue(registry.matches_hook('some_suffix', self.hook))
|
||||
self.assertFalse(registry.matches_hook('_suffix_blah', self.hook))
|
||||
|
||||
self.assertTrue(registry.matches_hook(
|
||||
'some_prefix', environment.HOOK_PRE_UPDATE))
|
||||
self.assertTrue(registry.matches_hook(
|
||||
'_suffix_blah', environment.HOOK_PRE_UPDATE))
|
||||
self.assertTrue(registry.matches_hook('some_prefix', other_hook))
|
||||
self.assertTrue(registry.matches_hook('_suffix_blah', other_hook))
|
||||
|
||||
def test_hook_types(self):
|
||||
resources = {
|
||||
u'pre_create': {
|
||||
u'hooks': 'pre-create',
|
||||
u'hook': {
|
||||
u'hooks': self.hook
|
||||
},
|
||||
u'pre_update': {
|
||||
u'hooks': 'pre-update',
|
||||
},
|
||||
u'pre_delete': {
|
||||
u'hooks': 'pre-delete',
|
||||
u'not-hook': {
|
||||
u'hooks': [hook for hook in environment.HOOK_TYPES if hook !=
|
||||
self.hook]
|
||||
},
|
||||
u'all': {
|
||||
u'hooks': ['pre-create', 'pre-update', 'pre-delete'],
|
||||
u'hooks': environment.HOOK_TYPES
|
||||
},
|
||||
}
|
||||
registry = environment.ResourceRegistry(None, {})
|
||||
registry.load({'resources': resources})
|
||||
|
||||
self.assertTrue(registry.matches_hook(
|
||||
'pre_create', environment.HOOK_PRE_CREATE))
|
||||
self.assertFalse(registry.matches_hook(
|
||||
'pre_create', environment.HOOK_PRE_UPDATE))
|
||||
self.assertFalse(registry.matches_hook(
|
||||
'pre_create', environment.HOOK_PRE_DELETE))
|
||||
|
||||
self.assertTrue(registry.matches_hook(
|
||||
'pre_update', environment.HOOK_PRE_UPDATE))
|
||||
self.assertFalse(registry.matches_hook(
|
||||
'pre_update', environment.HOOK_PRE_CREATE))
|
||||
self.assertFalse(registry.matches_hook(
|
||||
'pre_update', environment.HOOK_PRE_DELETE))
|
||||
|
||||
self.assertTrue(registry.matches_hook(
|
||||
'pre_delete', environment.HOOK_PRE_DELETE))
|
||||
self.assertFalse(registry.matches_hook(
|
||||
'pre_delete', environment.HOOK_PRE_CREATE))
|
||||
self.assertFalse(registry.matches_hook(
|
||||
'pre_delete', environment.HOOK_PRE_UPDATE))
|
||||
|
||||
self.assertTrue(registry.matches_hook(
|
||||
'all', environment.HOOK_PRE_CREATE))
|
||||
self.assertTrue(registry.matches_hook(
|
||||
'all', environment.HOOK_PRE_UPDATE))
|
||||
self.assertTrue(registry.matches_hook(
|
||||
'all', environment.HOOK_PRE_DELETE))
|
||||
self.assertTrue(registry.matches_hook('hook', self.hook))
|
||||
self.assertFalse(registry.matches_hook('not-hook', self.hook))
|
||||
self.assertTrue(registry.matches_hook('all', self.hook))
|
||||
|
||||
|
||||
class ActionRestrictedTest(common.HeatTestCase):
|
||||
|
@ -2838,6 +2838,21 @@ class ResourceHookTest(common.HeatTestCase):
|
||||
self.assertFalse(res.has_hook('pre-update'))
|
||||
self.assertTrue(res.has_hook('pre-delete'))
|
||||
|
||||
res.data = mock.Mock(return_value={'post-create': 'True'})
|
||||
self.assertFalse(res.has_hook('post-delete'))
|
||||
self.assertFalse(res.has_hook('post-update'))
|
||||
self.assertTrue(res.has_hook('post-create'))
|
||||
|
||||
res.data = mock.Mock(return_value={'post-update': 'True'})
|
||||
self.assertFalse(res.has_hook('post-create'))
|
||||
self.assertFalse(res.has_hook('post-delete'))
|
||||
self.assertTrue(res.has_hook('post-update'))
|
||||
|
||||
res.data = mock.Mock(return_value={'post-delete': 'True'})
|
||||
self.assertFalse(res.has_hook('post-create'))
|
||||
self.assertFalse(res.has_hook('post-update'))
|
||||
self.assertTrue(res.has_hook('post-delete'))
|
||||
|
||||
def test_set_hook(self):
|
||||
snippet = rsrc_defn.ResourceDefinition('res',
|
||||
'GenericResourceType')
|
||||
@ -2918,6 +2933,38 @@ class ResourceHookTest(common.HeatTestCase):
|
||||
task.run_to_completion()
|
||||
self.assertEqual((res.DELETE, res.COMPLETE), res.state)
|
||||
|
||||
def test_post_create_hook_call(self):
|
||||
self.stack.env.registry.load(
|
||||
{'resources': {'res': {'hooks': 'post-create'}}})
|
||||
snippet = rsrc_defn.ResourceDefinition('res',
|
||||
'GenericResourceType')
|
||||
res = resource.Resource('res', snippet, self.stack)
|
||||
res.id = '1234'
|
||||
task = scheduler.TaskRunner(res.create)
|
||||
task.start()
|
||||
task.step()
|
||||
self.assertTrue(res.has_hook('post-create'))
|
||||
res.clear_hook('post-create')
|
||||
task.run_to_completion()
|
||||
self.assertEqual((res.CREATE, res.COMPLETE), res.state)
|
||||
|
||||
def test_post_delete_hook_call(self):
|
||||
self.stack.env.registry.load(
|
||||
{'resources': {'res': {'hooks': 'post-delete'}}})
|
||||
snippet = rsrc_defn.ResourceDefinition('res',
|
||||
'GenericResourceType')
|
||||
res = resource.Resource('res', snippet, self.stack)
|
||||
res.id = '1234'
|
||||
res.action = 'CREATE'
|
||||
self.stack.action = 'DELETE'
|
||||
task = scheduler.TaskRunner(res.delete)
|
||||
task.start()
|
||||
task.step()
|
||||
self.assertTrue(res.has_hook('post-delete'))
|
||||
res.clear_hook('post-delete')
|
||||
task.run_to_completion()
|
||||
self.assertEqual((res.DELETE, res.COMPLETE), res.state)
|
||||
|
||||
|
||||
class ResourceAvailabilityTest(common.HeatTestCase):
|
||||
def _mock_client_plugin(self, service_types=None, is_available=True):
|
||||
|
@ -484,6 +484,40 @@ class StackUpdateTest(common.HeatTestCase):
|
||||
self.stack.state)
|
||||
self.assertEqual('xyz', self.stack['AResource'].properties['Foo'])
|
||||
|
||||
def test_update_replace_post_hook(self):
|
||||
tmpl = {
|
||||
'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Parameters': {
|
||||
'foo': {'Type': 'String'}
|
||||
},
|
||||
'Resources': {
|
||||
'AResource': {
|
||||
'Type': 'ResWithComplexPropsAndAttrs',
|
||||
'Properties': {'an_int': {'Ref': 'foo'}}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.stack = stack.Stack(
|
||||
self.ctx, 'update_test_stack',
|
||||
template.Template(tmpl, env=environment.Environment({'foo': 1})))
|
||||
self.stack.store()
|
||||
self.stack.create()
|
||||
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
|
||||
env2 = environment.Environment({'foo': 2})
|
||||
env2.registry.load(
|
||||
{'resources': {'AResource': {'hooks': 'post-update'}}})
|
||||
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
||||
template.Template(tmpl, env=env2))
|
||||
mock_hook = self.patchobject(self.stack['AResource'], 'trigger_hook')
|
||||
|
||||
self.stack.update(updated_stack)
|
||||
mock_hook.assert_called_once_with('post-update')
|
||||
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
self.assertEqual(2, self.stack['AResource'].properties['an_int'])
|
||||
|
||||
def test_update_modify_update_failed(self):
|
||||
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
||||
|
Loading…
Reference in New Issue
Block a user