Merge "Add post hooks"

This commit is contained in:
Jenkins 2016-02-22 03:49:43 +00:00 committed by Gerrit Code Review
commit 99f7c51dd3
7 changed files with 146 additions and 80 deletions

View File

@ -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
----------------- -----------------

View File

@ -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')

View File

@ -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."""

View File

@ -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'))

View File

@ -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):

View File

@ -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):

View File

@ -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',