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.
(cherry-picked from b101190f32
)
Closes-Bug: #1479579
Change-Id: I2540ca13572dfe628e6fc596815bee427b41290f
This commit is contained in:
parent
7248ec4252
commit
e889d66e07
|
@ -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):
|
||||
|
|
|
@ -176,9 +176,9 @@ def format_stack_resource(resource, detail=True, with_props=False,
|
|||
rpc_api.RES_REQUIRED_BY: resource.required_by(),
|
||||
}
|
||||
|
||||
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())
|
||||
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
|
||||
|
|
|
@ -294,6 +294,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 _
|
||||
|
@ -92,6 +93,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):
|
||||
|
|
|
@ -214,6 +214,8 @@ class ResourceGroup(stack_resource.StackResource):
|
|||
def _name_blacklist(self):
|
||||
"""Resolve the remove_policies to names for removal."""
|
||||
|
||||
nested = self.nested()
|
||||
|
||||
# To avoid reusing names after removal, we store a comma-separated
|
||||
# blacklist in the resource data
|
||||
db_rsrc_names = self.data().get('name_blacklist')
|
||||
|
@ -225,17 +227,18 @@ class ResourceGroup(stack_resource.StackResource):
|
|||
# Now we iterate over the removal policies, and update the blacklist
|
||||
# with any additional names
|
||||
rsrc_names = list(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 self.nested() and str_n not in rsrc_names:
|
||||
rsrc_names.append(str_n)
|
||||
continue
|
||||
rsrc = self.nested().resource_by_refid(str_n)
|
||||
if rsrc and str_n not in rsrc_names:
|
||||
rsrc_names.append(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 and str_n not in rsrc_names:
|
||||
rsrc_names.append(str_n)
|
||||
continue
|
||||
rsrc = self.nested().resource_by_refid(str_n)
|
||||
if rsrc and str_n not in rsrc_names:
|
||||
rsrc_names.append(rsrc.name)
|
||||
|
||||
# If the blacklist has changed, update the resource data
|
||||
if rsrc_names != current_blacklist:
|
||||
|
|
|
@ -83,8 +83,15 @@ class StackResource(resource.Resource):
|
|||
# resources in it decide if they need updating.
|
||||
return True
|
||||
|
||||
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
|
||||
|
@ -94,13 +101,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
|
||||
|
||||
|
@ -282,18 +289,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
|
||||
|
||||
|
@ -399,11 +402,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
|
||||
|
||||
|
@ -462,7 +461,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):
|
||||
'''
|
||||
|
|
|
@ -212,14 +212,10 @@ class Stack(collections.Mapping):
|
|||
for res in self.values():
|
||||
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
|
||||
|
||||
|
@ -791,7 +787,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())
|
||||
|
|
|
@ -362,6 +362,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):
|
||||
|
|
|
@ -20,6 +20,7 @@ from heat.engine import constraints
|
|||
from heat.engine import properties
|
||||
from heat.engine import resource
|
||||
from heat.engine.resources import signal_responder
|
||||
from heat.engine.resources import stack_resource
|
||||
from heat.engine.resources import stack_user
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -162,3 +163,30 @@ class ResourceWithCustomConstraint(GenericResource):
|
|||
'Foo': properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
constraints=[constraints.CustomConstraint('neutron.network')])}
|
||||
|
||||
|
||||
class StackResourceType(stack_resource.StackResource, GenericResource):
|
||||
def physical_resource_name(self):
|
||||
return "cb2f2b28-a663-4683-802c-4b40c916e1ff"
|
||||
|
||||
def set_template(self, nested_template, params):
|
||||
self.nested_template = nested_template
|
||||
self.nested_params = params
|
||||
|
||||
def handle_create(self):
|
||||
return self.create_with_template(self.nested_template,
|
||||
self.nested_params)
|
||||
|
||||
def handle_adopt(self, resource_data):
|
||||
return self.create_with_template(self.nested_template,
|
||||
self.nested_params,
|
||||
adopt_data=resource_data)
|
||||
|
||||
def handle_delete(self):
|
||||
self.delete_nested()
|
||||
|
||||
def has_nested(self):
|
||||
if self.nested() is not None:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
|
|
@ -19,6 +19,7 @@ import mock
|
|||
from oslo_utils import timeutils
|
||||
import six
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common import identifier
|
||||
from heat.common import template_format
|
||||
from heat.engine import api
|
||||
|
@ -44,11 +45,14 @@ class FormatTest(common.HeatTestCase):
|
|||
'generic1': {'Type': 'GenericResourceType'},
|
||||
'generic2': {
|
||||
'Type': 'GenericResourceType',
|
||||
'DependsOn': 'generic1'}
|
||||
'DependsOn': 'generic1'},
|
||||
'generic4': {'Type': 'StackResourceType'}
|
||||
}
|
||||
})
|
||||
resource._register_class('GenericResourceType',
|
||||
generic_rsrc.GenericResource)
|
||||
resource._register_class('StackResourceType',
|
||||
generic_rsrc.StackResourceType)
|
||||
resource._register_class('ResWithComplexPropsAndAttrs',
|
||||
generic_rsrc.ResWithComplexPropsAndAttrs)
|
||||
self.stack = parser.Stack(utils.dummy_context(), 'test_stack',
|
||||
|
@ -179,7 +183,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
|
||||
|
@ -188,7 +192,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
|
||||
|
||||
|
@ -206,10 +210,32 @@ class FormatTest(common.HeatTestCase):
|
|||
rpc_api.RES_REQUIRED_BY))
|
||||
|
||||
formatted = api.format_stack_resource(res, False)
|
||||
self.assertEqual(resource_keys, set(formatted.keys()))
|
||||
self.assertEqual(resource_keys, set(six.iterkeys(formatted)))
|
||||
|
||||
def test_format_stack_resource_with_nested_stack_not_found(self):
|
||||
res = self.stack['generic4']
|
||||
self.patchobject(parser.Stack, 'load',
|
||||
side_effect=exception.NotFound())
|
||||
|
||||
resource_keys = set((
|
||||
rpc_api.RES_UPDATED_TIME,
|
||||
rpc_api.RES_NAME,
|
||||
rpc_api.RES_PHYSICAL_ID,
|
||||
rpc_api.RES_ACTION,
|
||||
rpc_api.RES_STATUS,
|
||||
rpc_api.RES_STATUS_DATA,
|
||||
rpc_api.RES_TYPE,
|
||||
rpc_api.RES_ID,
|
||||
rpc_api.RES_STACK_ID,
|
||||
rpc_api.RES_STACK_NAME,
|
||||
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()
|
||||
|
|
|
@ -53,6 +53,8 @@ class StackTest(common.HeatTestCase):
|
|||
|
||||
resource._register_class('GenericResourceType',
|
||||
generic_rsrc.GenericResource)
|
||||
resource._register_class('StackResourceType',
|
||||
generic_rsrc.StackResourceType)
|
||||
resource._register_class('ResourceWithPropsType',
|
||||
generic_rsrc.ResourceWithProps)
|
||||
resource._register_class('ResWithComplexPropsAndAttrs',
|
||||
|
@ -197,7 +199,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),
|
||||
|
|
|
@ -198,6 +198,11 @@ class StackResourceTest(common.HeatTestCase):
|
|||
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)
|
||||
|
||||
def test_implementation_signature(self):
|
||||
self.parent_resource.child_template = mock.Mock(
|
||||
return_value=self.simple_template)
|
||||
|
@ -460,10 +465,11 @@ class StackResourceTest(common.HeatTestCase):
|
|||
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):
|
||||
|
@ -490,10 +496,10 @@ class StackResourceTest(common.HeatTestCase):
|
|||
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…
Reference in New Issue