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):
nested_stack = stack.nested()
if not nested_stack:
return None
try:
if use_indices:
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):
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):
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):

View File

@ -17,7 +17,6 @@ from oslo_log import log as logging
from oslo_utils import timeutils
import six
from heat.common import exception
from heat.common.i18n import _
from heat.common.i18n import _LE
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(),
}
try:
if (hasattr(resource, 'nested') and callable(resource.nested) and
resource.nested() is not None):
res[rpc_api.RES_NESTED_STACK_ID] = dict(
resource.nested().identifier())
except exception.NotFound:
pass
if resource.has_nested():
res[rpc_api.RES_NESTED_STACK_ID] = dict(
resource.nested().identifier())
if 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,
"Failure occured while waiting.")
def has_nested(self):
# common resources have not nested, StackResource overrides it
return False
def has_hook(self, hook):
# Clear the cache to make sure the data is up to date:
self._data = None

View File

@ -12,6 +12,7 @@
# under the License.
from requests import exceptions
import six
from heat.common import exception
from heat.common.i18n import _
@ -89,6 +90,9 @@ class NestedStack(stack_resource.StackResource):
return attributes.select_from_attribute(attribute, path)
def FnGetRefId(self):
if self.nested() is None:
return six.text_type(self.name)
return self.nested().identifier().arn()
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
# with any additional names
rsrc_names = set(current_blacklist)
for r in self.properties[self.REMOVAL_POLICIES]:
if self.REMOVAL_RSRC_LIST in r:
# Tolerate string or int list values
for n in r[self.REMOVAL_RSRC_LIST]:
str_n = six.text_type(n)
if str_n in nested:
rsrc_names.add(str_n)
continue
rsrc = nested.resource_by_refid(str_n)
if rsrc:
rsrc_names.add(rsrc.name)
if nested:
for r in self.properties[self.REMOVAL_POLICIES]:
if self.REMOVAL_RSRC_LIST in r:
# Tolerate string or int list values
for n in r[self.REMOVAL_RSRC_LIST]:
str_n = six.text_type(n)
if str_n in nested:
rsrc_names.add(str_n)
continue
rsrc = nested.resource_by_refid(str_n)
if rsrc:
rsrc_names.add(rsrc.name)
# If the blacklist has changed, update the resource data
if rsrc_names != set(current_blacklist):

View File

@ -114,8 +114,15 @@ class StackResource(resource.Resource):
self.rpc_client().stack_cancel_update(self.context,
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):
'''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
the locally cached Stack object
@ -125,13 +132,13 @@ class StackResource(resource.Resource):
self._nested = None
if self._nested is None and self.resource_id is not None:
self._nested = parser.Stack.load(self.context,
self.resource_id,
show_deleted=show_deleted,
force_reload=force_reload)
if self._nested is None:
raise exception.NotFound(_("Nested stack not found in DB"))
try:
self._nested = parser.Stack.load(self.context,
self.resource_id,
show_deleted=show_deleted,
force_reload=force_reload)
except exception.NotFound:
return None
return self._nested
@ -322,18 +329,14 @@ class StackResource(resource.Resource):
def _check_status_complete(self, action, show_deleted=False,
cookie=None):
try:
nested = self.nested(force_reload=True, show_deleted=show_deleted)
except exception.NotFound:
nested = self.nested(force_reload=True, show_deleted=show_deleted)
if nested is None:
if action == resource.Resource.DELETE:
return True
# It's possible the engine handling the create hasn't persisted
# the stack to the DB when we first start polling for state
return False
if nested is None:
return True
if nested.action != action:
return False
@ -439,11 +442,7 @@ class StackResource(resource.Resource):
'''
Delete the nested stack.
'''
try:
stack = self.nested()
except exception.NotFound:
return
stack = self.nested()
if stack is None:
return
@ -505,7 +504,11 @@ class StackResource(resource.Resource):
return self._check_status_complete(resource.Resource.CHECK)
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):
'''

View File

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

View File

@ -437,6 +437,7 @@ class HeatScalingGroupAttrTest(common.HeatTestCase):
def test_index_dotted_attribute(self):
mock_members = self.patchobject(grouputils, 'get_members')
self.group.nested = mock.Mock()
members = []
output = []
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):
self.delete_nested()
def has_nested(self):
if self.nested() is not None:
return True
return False
class ResourceWithRestoreType(ResWithComplexPropsAndAttrs):

View File

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

View File

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

View File

@ -181,6 +181,11 @@ class StackResourceTest(StackResourceBaseTest):
nest.return_value.prepare_abandon.assert_called_once_with()
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")
def test_implementation_signature(self):
self.parent_resource.child_template = mock.Mock(
@ -450,10 +455,11 @@ class StackResourceTest(StackResourceBaseTest):
parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id,
show_deleted=False,
force_reload=False).AndReturn(None)
force_reload=False).AndRaise(
exception.NotFound)
self.m.ReplayAll()
self.assertRaises(exception.NotFound, self.parent_resource.nested)
self.assertIsNone(self.parent_resource.nested())
self.m.VerifyAll()
def test_load_nested_cached(self):
@ -480,10 +486,10 @@ class StackResourceTest(StackResourceBaseTest):
parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id,
show_deleted=False,
force_reload=True).AndReturn(None)
force_reload=True).AndRaise(
exception.NotFound)
self.m.ReplayAll()
self.assertRaises(exception.NotFound, self.parent_resource.nested,
force_reload=True)
self.assertIsNone(self.parent_resource.nested(force_reload=True))
self.m.VerifyAll()
def test_delete_nested_none_nested_stack(self):