Merge "Add post hooks"
This commit is contained in:
commit
99f7c51dd3
@ -137,15 +137,16 @@ template::
|
|||||||
my_db_server:
|
my_db_server:
|
||||||
"OS::DBInstance": file:///home/mine/all_my_cool_templates/db.yaml
|
"OS::DBInstance": file:///home/mine/all_my_cool_templates/db.yaml
|
||||||
|
|
||||||
Pause stack creation or update on a given resource
|
Pause stack creation, update or deletion on a given resource
|
||||||
--------------------------------------------------
|
------------------------------------------------------------
|
||||||
If you want to debug your stack as it's being created or updated, or if you want
|
If you want to debug your stack as it's being created, updated or deleted, or
|
||||||
to run it in phases, you can set ``pre-create`` and ``pre-update`` hooks in the
|
if you want to run it in phases, you can set ``pre-create``, ``pre-update``,
|
||||||
``resources`` section of ``resource_registry``.
|
``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
|
To set a hook, add either ``hooks: $hook_name`` (for example ``hooks:
|
||||||
resource's dictionary. You can also use ``[pre-create, pre-update]`` to stop
|
pre-update``) to the resource's dictionary. You can also use a list (``hooks:
|
||||||
on both actions.
|
[pre-create, pre-update]``) to stop on several actions.
|
||||||
|
|
||||||
You can combine hooks with other ``resources`` properties such as provider
|
You can combine hooks with other ``resources`` properties such as provider
|
||||||
templates or type mapping::
|
templates or type mapping::
|
||||||
@ -175,8 +176,8 @@ resource name. For example, the following entry pauses while creating
|
|||||||
"*_server":
|
"*_server":
|
||||||
hooks: pre-create
|
hooks: pre-create
|
||||||
|
|
||||||
Clear hooks by signaling the resource with ``{unset_hook: pre-create}``
|
Clear hooks by signaling the resource with ``{unset_hook: $hook_name}`` (for
|
||||||
or ``{unset_hook: pre-update}``.
|
example ``{unset_hook: pre-update}``).
|
||||||
|
|
||||||
Retrieving events
|
Retrieving events
|
||||||
-----------------
|
-----------------
|
||||||
|
@ -35,9 +35,11 @@ LOG = log.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
HOOK_TYPES = (
|
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')
|
RESTRICTED_ACTIONS = (UPDATE, REPLACE) = ('update', 'replace')
|
||||||
|
@ -793,7 +793,8 @@ class Resource(object):
|
|||||||
try:
|
try:
|
||||||
yield self._do_action(action, self.properties.validate)
|
yield self._do_action(action, self.properties.validate)
|
||||||
if action == self.CREATE:
|
if action == self.CREATE:
|
||||||
return
|
first_failure = None
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
action = self.CREATE
|
action = self.CREATE
|
||||||
except exception.ResourceFailure as failure:
|
except exception.ResourceFailure as failure:
|
||||||
@ -812,6 +813,10 @@ class Resource(object):
|
|||||||
if first_failure:
|
if first_failure:
|
||||||
raise 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):
|
def prepare_abandon(self):
|
||||||
self.abandon_in_progress = True
|
self.abandon_in_progress = True
|
||||||
return {
|
return {
|
||||||
@ -1092,6 +1097,9 @@ class Resource(object):
|
|||||||
raise failure
|
raise failure
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
yield self._break_if_required(
|
||||||
|
self.UPDATE, environment.HOOK_POST_UPDATE)
|
||||||
|
|
||||||
def prepare_for_replace(self):
|
def prepare_for_replace(self):
|
||||||
"""Prepare resource for replacing.
|
"""Prepare resource for replacing.
|
||||||
|
|
||||||
@ -1389,6 +1397,10 @@ class Resource(object):
|
|||||||
action_args = []
|
action_args = []
|
||||||
yield self.action_handler_task(action, *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
|
@scheduler.wrappertask
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
"""A task to delete the resource and remove it from the database."""
|
"""A task to delete the resource and remove it from the database."""
|
||||||
|
@ -384,6 +384,7 @@ class AccessKeyTest(common.HeatTestCase):
|
|||||||
rsrc)
|
rsrc)
|
||||||
|
|
||||||
rsrc._secret = None
|
rsrc._secret = None
|
||||||
|
rsrc._data = None
|
||||||
self.assertEqual(self.fc.secret,
|
self.assertEqual(self.fc.secret,
|
||||||
rsrc.FnGetAtt('SecretAccessKey'))
|
rsrc.FnGetAtt('SecretAccessKey'))
|
||||||
|
|
||||||
|
@ -780,7 +780,8 @@ class ResourceRegistryTest(common.HeatTestCase):
|
|||||||
registry = environment.ResourceRegistry(None, {})
|
registry = environment.ResourceRegistry(None, {})
|
||||||
msg = ('Invalid hook type "invalid-type" for resource breakpoint, '
|
msg = ('Invalid hook type "invalid-type" for resource breakpoint, '
|
||||||
'acceptable hook types are: (\'pre-create\', \'pre-update\', '
|
'acceptable hook types are: (\'pre-create\', \'pre-update\', '
|
||||||
'\'pre-delete\')')
|
'\'pre-delete\', \'post-create\', \'post-update\', '
|
||||||
|
'\'post-delete\')')
|
||||||
ex = self.assertRaises(exception.InvalidBreakPointHook,
|
ex = self.assertRaises(exception.InvalidBreakPointHook,
|
||||||
registry.load, {'resources': resources})
|
registry.load, {'resources': resources})
|
||||||
self.assertEqual(msg, six.text_type(ex))
|
self.assertEqual(msg, six.text_type(ex))
|
||||||
@ -861,18 +862,23 @@ class ResourceRegistryTest(common.HeatTestCase):
|
|||||||
|
|
||||||
class HookMatchTest(common.HeatTestCase):
|
class HookMatchTest(common.HeatTestCase):
|
||||||
|
|
||||||
|
scenarios = [(hook_type, {'hook': hook_type}) for hook_type in
|
||||||
|
environment.HOOK_TYPES]
|
||||||
|
|
||||||
def test_plain_matches(self):
|
def test_plain_matches(self):
|
||||||
|
other_hook = next(hook for hook in environment.HOOK_TYPES
|
||||||
|
if hook != self.hook)
|
||||||
resources = {
|
resources = {
|
||||||
u'a': {
|
u'a': {
|
||||||
u'OS::Fruit': u'apples.yaml',
|
u'OS::Fruit': u'apples.yaml',
|
||||||
u'hooks': [u'pre-create', u'pre-update'],
|
u'hooks': [self.hook, other_hook]
|
||||||
},
|
},
|
||||||
u'b': {
|
u'b': {
|
||||||
u'OS::Food': u'fruity.yaml',
|
u'OS::Food': u'fruity.yaml',
|
||||||
},
|
},
|
||||||
u'nested': {
|
u'nested': {
|
||||||
u'res': {
|
u'res': {
|
||||||
u'hooks': 'pre-create',
|
u'hooks': self.hook,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -881,96 +887,59 @@ class HookMatchTest(common.HeatTestCase):
|
|||||||
u'OS::Fruit': u'apples.yaml',
|
u'OS::Fruit': u'apples.yaml',
|
||||||
'resources': resources})
|
'resources': resources})
|
||||||
|
|
||||||
self.assertTrue(registry.matches_hook(
|
self.assertTrue(registry.matches_hook('a', self.hook))
|
||||||
'a', environment.HOOK_PRE_CREATE))
|
self.assertFalse(registry.matches_hook('b', self.hook))
|
||||||
self.assertFalse(registry.matches_hook(
|
self.assertFalse(registry.matches_hook('OS::Fruit', self.hook))
|
||||||
'b', environment.HOOK_PRE_CREATE))
|
self.assertFalse(registry.matches_hook('res', self.hook))
|
||||||
self.assertFalse(registry.matches_hook(
|
self.assertFalse(registry.matches_hook('unknown', self.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))
|
|
||||||
|
|
||||||
def test_wildcard_matches(self):
|
def test_wildcard_matches(self):
|
||||||
|
other_hook = next(hook for hook in environment.HOOK_TYPES
|
||||||
|
if hook != self.hook)
|
||||||
resources = {
|
resources = {
|
||||||
u'prefix_*': {
|
u'prefix_*': {
|
||||||
u'hooks': 'pre-create',
|
u'hooks': self.hook
|
||||||
},
|
},
|
||||||
u'*_suffix': {
|
u'*_suffix': {
|
||||||
u'hooks': 'pre-create',
|
u'hooks': self.hook
|
||||||
},
|
},
|
||||||
u'*': {
|
u'*': {
|
||||||
u'hooks': 'pre-update',
|
u'hooks': other_hook
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
registry = environment.ResourceRegistry(None, {})
|
registry = environment.ResourceRegistry(None, {})
|
||||||
registry.load({'resources': resources})
|
registry.load({'resources': resources})
|
||||||
|
|
||||||
self.assertTrue(registry.matches_hook(
|
self.assertTrue(registry.matches_hook('prefix_', self.hook))
|
||||||
'prefix_', environment.HOOK_PRE_CREATE))
|
self.assertTrue(registry.matches_hook('prefix_some', self.hook))
|
||||||
self.assertTrue(registry.matches_hook(
|
self.assertFalse(registry.matches_hook('some_prefix', self.hook))
|
||||||
'prefix_some', environment.HOOK_PRE_CREATE))
|
|
||||||
self.assertFalse(registry.matches_hook(
|
|
||||||
'some_prefix', environment.HOOK_PRE_CREATE))
|
|
||||||
|
|
||||||
self.assertTrue(registry.matches_hook(
|
self.assertTrue(registry.matches_hook('_suffix', self.hook))
|
||||||
'_suffix', environment.HOOK_PRE_CREATE))
|
self.assertTrue(registry.matches_hook('some_suffix', self.hook))
|
||||||
self.assertTrue(registry.matches_hook(
|
self.assertFalse(registry.matches_hook('_suffix_blah', self.hook))
|
||||||
'some_suffix', environment.HOOK_PRE_CREATE))
|
|
||||||
self.assertFalse(registry.matches_hook(
|
|
||||||
'_suffix_blah', environment.HOOK_PRE_CREATE))
|
|
||||||
|
|
||||||
self.assertTrue(registry.matches_hook(
|
self.assertTrue(registry.matches_hook('some_prefix', other_hook))
|
||||||
'some_prefix', environment.HOOK_PRE_UPDATE))
|
self.assertTrue(registry.matches_hook('_suffix_blah', other_hook))
|
||||||
self.assertTrue(registry.matches_hook(
|
|
||||||
'_suffix_blah', environment.HOOK_PRE_UPDATE))
|
|
||||||
|
|
||||||
def test_hook_types(self):
|
def test_hook_types(self):
|
||||||
resources = {
|
resources = {
|
||||||
u'pre_create': {
|
u'hook': {
|
||||||
u'hooks': 'pre-create',
|
u'hooks': self.hook
|
||||||
},
|
},
|
||||||
u'pre_update': {
|
u'not-hook': {
|
||||||
u'hooks': 'pre-update',
|
u'hooks': [hook for hook in environment.HOOK_TYPES if hook !=
|
||||||
},
|
self.hook]
|
||||||
u'pre_delete': {
|
|
||||||
u'hooks': 'pre-delete',
|
|
||||||
},
|
},
|
||||||
u'all': {
|
u'all': {
|
||||||
u'hooks': ['pre-create', 'pre-update', 'pre-delete'],
|
u'hooks': environment.HOOK_TYPES
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
registry = environment.ResourceRegistry(None, {})
|
registry = environment.ResourceRegistry(None, {})
|
||||||
registry.load({'resources': resources})
|
registry.load({'resources': resources})
|
||||||
|
|
||||||
self.assertTrue(registry.matches_hook(
|
self.assertTrue(registry.matches_hook('hook', self.hook))
|
||||||
'pre_create', environment.HOOK_PRE_CREATE))
|
self.assertFalse(registry.matches_hook('not-hook', self.hook))
|
||||||
self.assertFalse(registry.matches_hook(
|
self.assertTrue(registry.matches_hook('all', self.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))
|
|
||||||
|
|
||||||
|
|
||||||
class ActionRestrictedTest(common.HeatTestCase):
|
class ActionRestrictedTest(common.HeatTestCase):
|
||||||
|
@ -2891,6 +2891,21 @@ class ResourceHookTest(common.HeatTestCase):
|
|||||||
self.assertFalse(res.has_hook('pre-update'))
|
self.assertFalse(res.has_hook('pre-update'))
|
||||||
self.assertTrue(res.has_hook('pre-delete'))
|
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):
|
def test_set_hook(self):
|
||||||
snippet = rsrc_defn.ResourceDefinition('res',
|
snippet = rsrc_defn.ResourceDefinition('res',
|
||||||
'GenericResourceType')
|
'GenericResourceType')
|
||||||
@ -2971,6 +2986,38 @@ class ResourceHookTest(common.HeatTestCase):
|
|||||||
task.run_to_completion()
|
task.run_to_completion()
|
||||||
self.assertEqual((res.DELETE, res.COMPLETE), res.state)
|
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):
|
class ResourceAvailabilityTest(common.HeatTestCase):
|
||||||
def _mock_client_plugin(self, service_types=None, is_available=True):
|
def _mock_client_plugin(self, service_types=None, is_available=True):
|
||||||
|
@ -484,6 +484,40 @@ class StackUpdateTest(common.HeatTestCase):
|
|||||||
self.stack.state)
|
self.stack.state)
|
||||||
self.assertEqual('xyz', self.stack['AResource'].properties['Foo'])
|
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):
|
def test_update_modify_update_failed(self):
|
||||||
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||||
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
||||||
|
Loading…
Reference in New Issue
Block a user