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:
parent
850963839c
commit
b101190f32
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
'''
|
||||
|
@ -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())
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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),
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user