Don't raise exception when get nested stack

1. Stack.load() won't return None, instead will raise
notFound exception if we can't get the stack from db.
So we should catch the notFound exception in
StackResource.nested() when loading.
2. Return None if we catch the notFound when get the
nested, due there are many places where we call the
nested(), and raise the notFound exception makes no sense.

Closes-Bug: #1479579
Change-Id: I2540ca13572dfe628e6fc596815bee427b41290f
This commit is contained in:
huangtianhua 2015-07-29 14:25:22 +08:00
parent 850963839c
commit b101190f32
12 changed files with 84 additions and 61 deletions

View File

@ -67,6 +67,8 @@ def get_member_names(group):
def get_resource(stack, resource_name, use_indices, key): def get_resource(stack, resource_name, use_indices, key):
nested_stack = stack.nested() nested_stack = stack.nested()
if not nested_stack:
return None
try: try:
if use_indices: if use_indices:
return get_members(stack)[int(resource_name)] return get_members(stack)[int(resource_name)]
@ -79,12 +81,14 @@ def get_resource(stack, resource_name, use_indices, key):
def get_rsrc_attr(stack, key, use_indices, resource_name, *attr_path): def get_rsrc_attr(stack, key, use_indices, resource_name, *attr_path):
resource = get_resource(stack, resource_name, use_indices, key) resource = get_resource(stack, resource_name, use_indices, key)
return resource.FnGetAtt(*attr_path) if resource:
return resource.FnGetAtt(*attr_path)
def get_rsrc_id(stack, key, use_indices, resource_name): def get_rsrc_id(stack, key, use_indices, resource_name):
resource = get_resource(stack, resource_name, use_indices, key) resource = get_resource(stack, resource_name, use_indices, key)
return resource.FnGetRefId() if resource:
return resource.FnGetRefId()
def get_nested_attrs(stack, key, use_indices, *path): def get_nested_attrs(stack, key, use_indices, *path):

View File

@ -17,7 +17,6 @@ from oslo_log import log as logging
from oslo_utils import timeutils from oslo_utils import timeutils
import six import six
from heat.common import exception
from heat.common.i18n import _ from heat.common.i18n import _
from heat.common.i18n import _LE from heat.common.i18n import _LE
from heat.common import param_utils from heat.common import param_utils
@ -211,13 +210,9 @@ def format_stack_resource(resource, detail=True, with_props=False,
rpc_api.RES_REQUIRED_BY: resource.required_by(), rpc_api.RES_REQUIRED_BY: resource.required_by(),
} }
try: if resource.has_nested():
if (hasattr(resource, 'nested') and callable(resource.nested) and res[rpc_api.RES_NESTED_STACK_ID] = dict(
resource.nested() is not None): resource.nested().identifier())
res[rpc_api.RES_NESTED_STACK_ID] = dict(
resource.nested().identifier())
except exception.NotFound:
pass
if resource.stack.parent_resource_name: if resource.stack.parent_resource_name:
res[rpc_api.RES_PARENT_RESOURCE] = resource.stack.parent_resource_name res[rpc_api.RES_PARENT_RESOURCE] = resource.stack.parent_resource_name

View File

@ -393,6 +393,10 @@ class Resource(object):
self.action, self.status, self.action, self.status,
"Failure occured while waiting.") "Failure occured while waiting.")
def has_nested(self):
# common resources have not nested, StackResource overrides it
return False
def has_hook(self, hook): def has_hook(self, hook):
# Clear the cache to make sure the data is up to date: # Clear the cache to make sure the data is up to date:
self._data = None self._data = None

View File

@ -12,6 +12,7 @@
# under the License. # under the License.
from requests import exceptions from requests import exceptions
import six
from heat.common import exception from heat.common import exception
from heat.common.i18n import _ from heat.common.i18n import _
@ -89,6 +90,9 @@ class NestedStack(stack_resource.StackResource):
return attributes.select_from_attribute(attribute, path) return attributes.select_from_attribute(attribute, path)
def FnGetRefId(self): def FnGetRefId(self):
if self.nested() is None:
return six.text_type(self.name)
return self.nested().identifier().arn() return self.nested().identifier().arn()
def handle_update(self, json_snippet, tmpl_diff, prop_diff): def handle_update(self, json_snippet, tmpl_diff, prop_diff):

View File

@ -294,17 +294,19 @@ class ResourceGroup(stack_resource.StackResource):
# Now we iterate over the removal policies, and update the blacklist # Now we iterate over the removal policies, and update the blacklist
# with any additional names # with any additional names
rsrc_names = set(current_blacklist) rsrc_names = set(current_blacklist)
for r in self.properties[self.REMOVAL_POLICIES]:
if self.REMOVAL_RSRC_LIST in r: if nested:
# Tolerate string or int list values for r in self.properties[self.REMOVAL_POLICIES]:
for n in r[self.REMOVAL_RSRC_LIST]: if self.REMOVAL_RSRC_LIST in r:
str_n = six.text_type(n) # Tolerate string or int list values
if str_n in nested: for n in r[self.REMOVAL_RSRC_LIST]:
rsrc_names.add(str_n) str_n = six.text_type(n)
continue if str_n in nested:
rsrc = nested.resource_by_refid(str_n) rsrc_names.add(str_n)
if rsrc: continue
rsrc_names.add(rsrc.name) rsrc = nested.resource_by_refid(str_n)
if rsrc:
rsrc_names.add(rsrc.name)
# If the blacklist has changed, update the resource data # If the blacklist has changed, update the resource data
if rsrc_names != set(current_blacklist): if rsrc_names != set(current_blacklist):

View File

@ -114,8 +114,15 @@ class StackResource(resource.Resource):
self.rpc_client().stack_cancel_update(self.context, self.rpc_client().stack_cancel_update(self.context,
stack_identity) stack_identity)
def has_nested(self):
if self.nested() is not None:
return True
return False
def nested(self, force_reload=False, show_deleted=False): def nested(self, force_reload=False, show_deleted=False):
'''Return a Stack object representing the nested (child) stack. '''Return a Stack object representing the nested (child) stack.
if we catch NotFound exception when loading, return None.
:param force_reload: Forces reloading from the DB instead of returning :param force_reload: Forces reloading from the DB instead of returning
the locally cached Stack object the locally cached Stack object
@ -125,13 +132,13 @@ class StackResource(resource.Resource):
self._nested = None self._nested = None
if self._nested is None and self.resource_id is not None: if self._nested is None and self.resource_id is not None:
self._nested = parser.Stack.load(self.context, try:
self.resource_id, self._nested = parser.Stack.load(self.context,
show_deleted=show_deleted, self.resource_id,
force_reload=force_reload) show_deleted=show_deleted,
force_reload=force_reload)
if self._nested is None: except exception.NotFound:
raise exception.NotFound(_("Nested stack not found in DB")) return None
return self._nested return self._nested
@ -322,18 +329,14 @@ class StackResource(resource.Resource):
def _check_status_complete(self, action, show_deleted=False, def _check_status_complete(self, action, show_deleted=False,
cookie=None): cookie=None):
try: nested = self.nested(force_reload=True, show_deleted=show_deleted)
nested = self.nested(force_reload=True, show_deleted=show_deleted) if nested is None:
except exception.NotFound:
if action == resource.Resource.DELETE: if action == resource.Resource.DELETE:
return True return True
# It's possible the engine handling the create hasn't persisted # It's possible the engine handling the create hasn't persisted
# the stack to the DB when we first start polling for state # the stack to the DB when we first start polling for state
return False return False
if nested is None:
return True
if nested.action != action: if nested.action != action:
return False return False
@ -439,11 +442,7 @@ class StackResource(resource.Resource):
''' '''
Delete the nested stack. Delete the nested stack.
''' '''
try: stack = self.nested()
stack = self.nested()
except exception.NotFound:
return
if stack is None: if stack is None:
return return
@ -505,7 +504,11 @@ class StackResource(resource.Resource):
return self._check_status_complete(resource.Resource.CHECK) return self._check_status_complete(resource.Resource.CHECK)
def prepare_abandon(self): def prepare_abandon(self):
return self.nested().prepare_abandon() nested_stack = self.nested()
if nested_stack:
return self.nested().prepare_abandon()
return {}
def get_output(self, op): def get_output(self, op):
''' '''

View File

@ -235,14 +235,10 @@ class Stack(collections.Mapping):
for res in six.itervalues(self): for res in six.itervalues(self):
yield res yield res
get_nested = getattr(res, 'nested', None) if not res.has_nested() or nested_depth == 0:
if not callable(get_nested) or nested_depth == 0:
continue
nested_stack = get_nested()
if nested_stack is None:
continue continue
nested_stack = res.nested()
for nested_res in nested_stack.iter_resources(nested_depth - 1): for nested_res in nested_stack.iter_resources(nested_depth - 1):
yield nested_res yield nested_res
@ -860,7 +856,7 @@ class Stack(collections.Mapping):
def supports_check_action(self): def supports_check_action(self):
def is_supported(stack, res): def is_supported(stack, res):
if hasattr(res, 'nested'): if res.has_nested():
return res.nested().supports_check_action() return res.nested().supports_check_action()
else: else:
return hasattr(res, 'handle_%s' % self.CHECK.lower()) return hasattr(res, 'handle_%s' % self.CHECK.lower())

View File

@ -437,6 +437,7 @@ class HeatScalingGroupAttrTest(common.HeatTestCase):
def test_index_dotted_attribute(self): def test_index_dotted_attribute(self):
mock_members = self.patchobject(grouputils, 'get_members') mock_members = self.patchobject(grouputils, 'get_members')
self.group.nested = mock.Mock()
members = [] members = []
output = [] output = []
for ip_ex in six.moves.range(0, 2): for ip_ex in six.moves.range(0, 2):

View File

@ -252,6 +252,12 @@ class StackResourceType(stack_resource.StackResource, GenericResource):
def handle_delete(self): def handle_delete(self):
self.delete_nested() self.delete_nested()
def has_nested(self):
if self.nested() is not None:
return True
return False
class ResourceWithRestoreType(ResWithComplexPropsAndAttrs): class ResourceWithRestoreType(ResWithComplexPropsAndAttrs):

View File

@ -45,7 +45,8 @@ class FormatTest(common.HeatTestCase):
'generic2': { 'generic2': {
'Type': 'GenericResourceType', 'Type': 'GenericResourceType',
'DependsOn': 'generic1'}, 'DependsOn': 'generic1'},
'generic3': {'Type': 'ResWithShowAttrType'} 'generic3': {'Type': 'ResWithShowAttrType'},
'generic4': {'Type': 'StackResourceType'}
} }
}) })
self.stack = parser.Stack(utils.dummy_context(), 'test_stack', self.stack = parser.Stack(utils.dummy_context(), 'test_stack',
@ -171,7 +172,7 @@ class FormatTest(common.HeatTestCase):
self.assertEqual('', props['a_string']) self.assertEqual('', props['a_string'])
def test_format_stack_resource_with_nested_stack(self): def test_format_stack_resource_with_nested_stack(self):
res = self.stack['generic1'] res = self.stack['generic4']
nested_id = {'foo': 'bar'} nested_id = {'foo': 'bar'}
res.nested = mock.Mock() res.nested = mock.Mock()
res.nested.return_value.identifier.return_value = nested_id res.nested.return_value.identifier.return_value = nested_id
@ -180,7 +181,7 @@ class FormatTest(common.HeatTestCase):
self.assertEqual(nested_id, formatted[rpc_api.RES_NESTED_STACK_ID]) self.assertEqual(nested_id, formatted[rpc_api.RES_NESTED_STACK_ID])
def test_format_stack_resource_with_nested_stack_none(self): def test_format_stack_resource_with_nested_stack_none(self):
res = self.stack['generic1'] res = self.stack['generic4']
res.nested = mock.Mock() res.nested = mock.Mock()
res.nested.return_value = None res.nested.return_value = None
@ -202,9 +203,9 @@ class FormatTest(common.HeatTestCase):
self.assertEqual(resource_keys, set(six.iterkeys(formatted))) self.assertEqual(resource_keys, set(six.iterkeys(formatted)))
def test_format_stack_resource_with_nested_stack_not_found(self): def test_format_stack_resource_with_nested_stack_not_found(self):
res = self.stack['generic1'] res = self.stack['generic4']
res.nested = mock.Mock() self.patchobject(parser.Stack, 'load',
res.nested.side_effect = exception.NotFound() side_effect=exception.NotFound())
resource_keys = set(( resource_keys = set((
rpc_api.RES_CREATION_TIME, rpc_api.RES_CREATION_TIME,
@ -221,10 +222,11 @@ class FormatTest(common.HeatTestCase):
rpc_api.RES_REQUIRED_BY)) rpc_api.RES_REQUIRED_BY))
formatted = api.format_stack_resource(res, False) formatted = api.format_stack_resource(res, False)
# 'nested_stack_id' is not in formatted
self.assertEqual(resource_keys, set(six.iterkeys(formatted))) self.assertEqual(resource_keys, set(six.iterkeys(formatted)))
def test_format_stack_resource_with_nested_stack_empty(self): def test_format_stack_resource_with_nested_stack_empty(self):
res = self.stack['generic1'] res = self.stack['generic4']
nested_id = {'foo': 'bar'} nested_id = {'foo': 'bar'}
res.nested = mock.MagicMock() res.nested = mock.MagicMock()

View File

@ -195,7 +195,7 @@ class StackTest(common.HeatTestCase):
def test_iter_resources(self): def test_iter_resources(self):
tpl = {'HeatTemplateFormatVersion': '2012-12-12', tpl = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': 'Resources':
{'A': {'Type': 'GenericResourceType'}, {'A': {'Type': 'StackResourceType'},
'B': {'Type': 'GenericResourceType'}}} 'B': {'Type': 'GenericResourceType'}}}
self.stack = stack.Stack(self.ctx, 'test_stack', self.stack = stack.Stack(self.ctx, 'test_stack',
template.Template(tpl), template.Template(tpl),
@ -222,7 +222,7 @@ class StackTest(common.HeatTestCase):
def test_iter_resources_cached(self, mock_drg): def test_iter_resources_cached(self, mock_drg):
tpl = {'HeatTemplateFormatVersion': '2012-12-12', tpl = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': 'Resources':
{'A': {'Type': 'GenericResourceType'}, {'A': {'Type': 'StackResourceType'},
'B': {'Type': 'GenericResourceType'}}} 'B': {'Type': 'GenericResourceType'}}}
self.stack = stack.Stack(self.ctx, 'test_stack', self.stack = stack.Stack(self.ctx, 'test_stack',
template.Template(tpl), template.Template(tpl),

View File

@ -181,6 +181,11 @@ class StackResourceTest(StackResourceBaseTest):
nest.return_value.prepare_abandon.assert_called_once_with() nest.return_value.prepare_abandon.assert_called_once_with()
self.assertEqual({'X': 'Y'}, ret) self.assertEqual({'X': 'Y'}, ret)
def test_nested_abandon_stack_not_found(self):
self.parent_resource.nested = mock.MagicMock(return_value=None)
ret = self.parent_resource.prepare_abandon()
self.assertEqual({}, ret)
@testtools.skipIf(six.PY3, "needs a separate change") @testtools.skipIf(six.PY3, "needs a separate change")
def test_implementation_signature(self): def test_implementation_signature(self):
self.parent_resource.child_template = mock.Mock( self.parent_resource.child_template = mock.Mock(
@ -450,10 +455,11 @@ class StackResourceTest(StackResourceBaseTest):
parser.Stack.load(self.parent_resource.context, parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id, self.parent_resource.resource_id,
show_deleted=False, show_deleted=False,
force_reload=False).AndReturn(None) force_reload=False).AndRaise(
exception.NotFound)
self.m.ReplayAll() self.m.ReplayAll()
self.assertRaises(exception.NotFound, self.parent_resource.nested) self.assertIsNone(self.parent_resource.nested())
self.m.VerifyAll() self.m.VerifyAll()
def test_load_nested_cached(self): def test_load_nested_cached(self):
@ -480,10 +486,10 @@ class StackResourceTest(StackResourceBaseTest):
parser.Stack.load(self.parent_resource.context, parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id, self.parent_resource.resource_id,
show_deleted=False, show_deleted=False,
force_reload=True).AndReturn(None) force_reload=True).AndRaise(
exception.NotFound)
self.m.ReplayAll() self.m.ReplayAll()
self.assertRaises(exception.NotFound, self.parent_resource.nested, self.assertIsNone(self.parent_resource.nested(force_reload=True))
force_reload=True)
self.m.VerifyAll() self.m.VerifyAll()
def test_delete_nested_none_nested_stack(self): def test_delete_nested_none_nested_stack(self):