Don't set metadata for deleted resources

Change-Id: I809dd844797dc0d9a8f31226a5b2da65a20a345d
Partial-Bug: #1675286
This commit is contained in:
Crag Wolfe 2017-03-23 14:59:11 -04:00
parent d90d5e3be6
commit 8d7e3e41e8
4 changed files with 42 additions and 11 deletions

View File

@ -447,10 +447,16 @@ class Resource(object):
""" """
if self.id is None or self.action == self.INIT: if self.id is None or self.action == self.INIT:
raise exception.ResourceNotAvailable(resource_name=self.name) raise exception.ResourceNotAvailable(resource_name=self.name)
LOG.debug('Setting metadata for %s', six.text_type(self))
refresh = merge_metadata is not None refresh = merge_metadata is not None
db_res = resource_objects.Resource.get_obj(self.stack.context, self.id, db_res = resource_objects.Resource.get_obj(self.stack.context, self.id,
refresh=refresh) refresh=refresh)
if db_res.action == self.DELETE:
self._db_res_is_deleted = True
LOG.debug("resource %(name)s, id: %(id)s is DELETE_%(st)s, "
"not setting metadata",
{'name': self.name, 'id': self.id, 'st': db_res.status})
raise exception.ResourceNotAvailable(resource_name=self.name)
LOG.debug('Setting metadata for %s', six.text_type(self))
if refresh: if refresh:
metadata = merge_metadata(metadata, db_res.rsrc_metadata) metadata = merge_metadata(metadata, db_res.rsrc_metadata)
db_res.update_metadata(metadata) db_res.update_metadata(metadata)
@ -2179,6 +2185,9 @@ class Resource(object):
# Don't log an event as it just spams the user. # Don't log an event as it just spams the user.
pass pass
except Exception as ex: except Exception as ex:
if hasattr(self, '_db_res_is_deleted'):
# No spam required
return
LOG.info('signal %(name)s : %(msg)s', LOG.info('signal %(name)s : %(msg)s',
{'name': six.text_type(self), {'name': six.text_type(self),
'msg': six.text_type(ex)}, 'msg': six.text_type(ex)},

View File

@ -25,6 +25,7 @@ from heat.common import exception
from heat.common.i18n import _ from heat.common.i18n import _
from heat.db.sqlalchemy import api as db_api from heat.db.sqlalchemy import api as db_api
from heat.engine import api from heat.engine import api
from heat.engine import resource
from heat.engine import scheduler from heat.engine import scheduler
from heat.engine import software_config_io as swc_io from heat.engine import software_config_io as swc_io
from heat.objects import resource as resource_objects from heat.objects import resource as resource_objects
@ -99,7 +100,7 @@ class SoftwareConfigService(object):
def _push_metadata_software_deployments( def _push_metadata_software_deployments(
self, cnxt, server_id, stack_user_project_id): self, cnxt, server_id, stack_user_project_id):
rs = db_api.resource_get_by_physical_resource_id(cnxt, server_id) rs = db_api.resource_get_by_physical_resource_id(cnxt, server_id)
if not rs: if not rs or rs.action == resource.Resource.DELETE:
return return
deployments = self.metadata_software_deployments(cnxt, server_id) deployments = self.metadata_software_deployments(cnxt, server_id)
md = rs.rsrc_metadata or {} md = rs.rsrc_metadata or {}

View File

@ -635,6 +635,23 @@ class ResourceTest(common.HeatTestCase):
res = generic_rsrc.GenericResource('test_resource', tmpl, self.stack) res = generic_rsrc.GenericResource('test_resource', tmpl, self.stack)
self.assertEqual({}, res.metadata_get()) self.assertEqual({}, res.metadata_get())
def test_metadata_set_when_deleted(self):
tmpl = rsrc_defn.ResourceDefinition('test_resource', 'Foo')
res = generic_rsrc.GenericResource('test_resource', tmpl, self.stack)
res.store()
md = {"don't": "care"}
res.action = 'CREATE'
res.metadata_set(md)
self.assertFalse(hasattr(res, '_db_res_is_deleted'))
res_obj = self.stack.context.session.query(
models.Resource).get(res.id)
res_obj.update({'action': 'DELETE'})
self.assertRaises(exception.ResourceNotAvailable,
res.metadata_set, md)
self.assertTrue(res._db_res_is_deleted)
def test_equals_different_stacks(self): def test_equals_different_stacks(self):
tmpl1 = rsrc_defn.ResourceDefinition('test_resource', 'Foo') tmpl1 = rsrc_defn.ResourceDefinition('test_resource', 'Foo')
tmpl2 = rsrc_defn.ResourceDefinition('test_resource', 'Foo') tmpl2 = rsrc_defn.ResourceDefinition('test_resource', 'Foo')

View File

@ -20,9 +20,9 @@ from six.moves.urllib import parse as urlparse
from heat.common import exception from heat.common import exception
from heat.common import template_format from heat.common import template_format
from heat.db.sqlalchemy import models
from heat.engine.clients.os import heat_plugin from heat.engine.clients.os import heat_plugin
from heat.engine.clients.os import swift from heat.engine.clients.os import swift
from heat.engine import resource
from heat.engine import scheduler from heat.engine import scheduler
from heat.engine import stack as stk from heat.engine import stack as stk
from heat.engine import template from heat.engine import template
@ -528,26 +528,30 @@ class SignalTest(common.HeatTestCase):
mock_handle.assert_called_once_with(test_d) mock_handle.assert_called_once_with(test_d)
self.assertTrue(result) self.assertTrue(result)
@mock.patch.object(generic_resource.SignalResource, '_add_event')
@mock.patch.object(generic_resource.SignalResource, 'handle_signal') @mock.patch.object(generic_resource.SignalResource, 'handle_signal')
def test_signal_no_action(self, mock_handle, mock_add): def test_handle_signal_no_reraise_deleted(self, mock_handle):
# Setup # Setup
test_d = {'Data': 'foo', 'Reason': 'bar', test_d = {'Data': 'foo', 'Reason': 'bar',
'Status': 'SUCCESS', 'UniqueId': '123'} 'Status': 'SUCCESS', 'UniqueId': '123'}
stack = self._create_stack(TEMPLATE_CFN_SIGNAL) stack = self._create_stack(TEMPLATE_CFN_SIGNAL)
mock_handle.side_effect = resource.NoActionRequired() mock_handle.side_effect = exception.ResourceNotAvailable(
resource_name='test')
rsrc = stack['signal_handler'] rsrc = stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertTrue(rsrc.requires_deferred_auth)
# Test # In the midst of handling a signal, an update happens on the
mock_add.reset_mock() # clean up existing calls before test # db resource concurrently, deleting it
rsrc.signal(details=test_d)
# Test exception not re-raised in DELETE case
res_obj = stack.context.session.query(
models.Resource).get(rsrc.id)
res_obj.update({'action': 'DELETE'})
rsrc._db_res_is_deleted = True
rsrc._handle_signal(details=test_d)
mock_handle.assert_called_once_with(test_d) mock_handle.assert_called_once_with(test_d)
mock_add.assert_not_called()
@mock.patch.object(generic_resource.SignalResource, '_add_event') @mock.patch.object(generic_resource.SignalResource, '_add_event')
def test_signal_different_reason_types(self, mock_add): def test_signal_different_reason_types(self, mock_add):