Merge "Don't raise exception when get nested stack"
This commit is contained in:
commit
8caccf0470
|
@ -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):
|
||||||
|
|
|
@ -15,7 +15,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
|
||||||
|
@ -210,13 +209,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
|
||||||
|
|
|
@ -403,6 +403,10 @@ class Resource(object):
|
||||||
self.action, self.status,
|
self.action, self.status,
|
||||||
"Failure occurred while waiting.")
|
"Failure occurred 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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -303,17 +303,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):
|
||||||
|
|
|
@ -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):
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -254,6 +254,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):
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
@ -172,7 +173,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
|
||||||
|
@ -181,7 +182,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
|
||||||
|
|
||||||
|
@ -203,9 +204,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,
|
||||||
|
@ -222,10 +223,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()
|
||||||
|
|
|
@ -249,7 +249,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),
|
||||||
|
@ -276,7 +276,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),
|
||||||
|
|
|
@ -195,6 +195,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(
|
||||||
|
@ -464,10 +469,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):
|
||||||
|
@ -494,10 +500,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):
|
||||||
|
|
Loading…
Reference in New Issue