Add base resolve method for attributes
Current patch adds two new methods to base class: * _resolve_all_attributes - for choosing path for resolving, which depends on type of attribute (base or custom). * _show_resource - method for resolving base 'show' attribute. This method should be re-defined for resources, which use clients with specific implementations of 'show' commands. New attribute for base class was added - entity, which is used for resolving 'show' attribute. Tests for Neutron resources and Server resource were updated corresponding changes mentioned above. Hook for stack_resource class is removed in this patch. Change-Id: I4ee0a53407739e5590c9a8d12bc7c1e84077f4c4
This commit is contained in:
parent
5903c53fcb
commit
02275f8901
@ -11,6 +11,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import collections
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import six
|
import six
|
||||||
@ -159,15 +161,20 @@ def format_resource_attributes(resource, with_attr=None):
|
|||||||
# 'console_urls' for nova server, user can view it by taking with_attr
|
# 'console_urls' for nova server, user can view it by taking with_attr
|
||||||
# parameter
|
# parameter
|
||||||
if 'show' in six.iterkeys(resolver):
|
if 'show' in six.iterkeys(resolver):
|
||||||
show_attr = resolver['show']
|
show_attr = resolve('show', resolver)
|
||||||
for a in with_attr:
|
# check if 'show' resolved to dictionary. so it's not None
|
||||||
if a not in show_attr:
|
if isinstance(show_attr, collections.Mapping):
|
||||||
show_attr[a] = resolve(a, resolver)
|
for a in with_attr:
|
||||||
return show_attr
|
if a not in show_attr:
|
||||||
else:
|
show_attr[a] = resolve(a, resolver)
|
||||||
attributes = set(list(six.iterkeys(resolver)) + with_attr)
|
return show_attr
|
||||||
return dict((attr, resolve(attr, resolver))
|
else:
|
||||||
for attr in attributes)
|
# remove 'show' attribute if it's None or not a mapping
|
||||||
|
# then resolve all attributes manually
|
||||||
|
del resolver._attributes['show']
|
||||||
|
attributes = set(list(six.iterkeys(resolver)) + with_attr)
|
||||||
|
return dict((attr, resolve(attr, resolver))
|
||||||
|
for attr in attributes)
|
||||||
|
|
||||||
|
|
||||||
def format_resource_properties(resource):
|
def format_resource_properties(resource):
|
||||||
|
@ -122,6 +122,10 @@ class Resource(object):
|
|||||||
# Resource implementations set this to update policies
|
# Resource implementations set this to update policies
|
||||||
update_policy_schema = {}
|
update_policy_schema = {}
|
||||||
|
|
||||||
|
# Default entity of resource, which is used for during resolving
|
||||||
|
# show attribute
|
||||||
|
entity = None
|
||||||
|
|
||||||
# Description dictionary, that describes the common attributes for all
|
# Description dictionary, that describes the common attributes for all
|
||||||
# resources
|
# resources
|
||||||
base_attributes_schema = {
|
base_attributes_schema = {
|
||||||
@ -194,7 +198,7 @@ class Resource(object):
|
|||||||
self.attributes_schema.update(self.base_attributes_schema)
|
self.attributes_schema.update(self.base_attributes_schema)
|
||||||
self.attributes = attributes.Attributes(self.name,
|
self.attributes = attributes.Attributes(self.name,
|
||||||
self.attributes_schema,
|
self.attributes_schema,
|
||||||
self._resolve_attribute)
|
self._resolve_all_attributes)
|
||||||
|
|
||||||
self.abandon_in_progress = False
|
self.abandon_in_progress = False
|
||||||
|
|
||||||
@ -1308,6 +1312,44 @@ class Resource(object):
|
|||||||
if not updated_ok:
|
if not updated_ok:
|
||||||
LOG.warn(_LW('Failed to unlock resource %s'), rsrc.name)
|
LOG.warn(_LW('Failed to unlock resource %s'), rsrc.name)
|
||||||
|
|
||||||
|
def _resolve_all_attributes(self, attr):
|
||||||
|
"""Method for resolving all attributes.
|
||||||
|
|
||||||
|
This method uses basic _resolve_attribute method for resolving
|
||||||
|
specific attributes. Base attributes will be resolved with
|
||||||
|
corresponding method, which should be defined in each resource
|
||||||
|
class.
|
||||||
|
|
||||||
|
:param attr: attribute name, which will be resolved
|
||||||
|
:returns: method of resource class, which resolve base attribute
|
||||||
|
"""
|
||||||
|
if attr in self.base_attributes_schema:
|
||||||
|
# check resource_id, because usually it is required for getting
|
||||||
|
# information about resource
|
||||||
|
if not self.resource_id:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return getattr(self, '_{0}_resource'.format(attr))()
|
||||||
|
except Exception as ex:
|
||||||
|
self.client_plugin().ignore_not_found(ex)
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self._resolve_attribute(attr)
|
||||||
|
|
||||||
|
def _show_resource(self):
|
||||||
|
"""Default implementation; should be overridden by resources
|
||||||
|
|
||||||
|
:returns: the map of resource information or None
|
||||||
|
"""
|
||||||
|
if self.entity:
|
||||||
|
try:
|
||||||
|
obj = getattr(self.client(), self.entity)
|
||||||
|
resource = obj.get(self.resource_id)
|
||||||
|
return resource.to_dict()
|
||||||
|
except AttributeError as ex:
|
||||||
|
LOG.warn(_LW("Resolving 'show' attribute has failed : %s"), ex)
|
||||||
|
return None
|
||||||
|
|
||||||
def _resolve_attribute(self, name):
|
def _resolve_attribute(self, name):
|
||||||
"""
|
"""
|
||||||
Default implementation; should be overridden by resources that expose
|
Default implementation; should be overridden by resources that expose
|
||||||
|
@ -119,16 +119,12 @@ class NeutronResource(resource.Resource):
|
|||||||
result=_('Resource is not built'))
|
result=_('Resource is not built'))
|
||||||
|
|
||||||
def _resolve_attribute(self, name):
|
def _resolve_attribute(self, name):
|
||||||
if self.resource_id:
|
try:
|
||||||
try:
|
attributes = self._show_resource()
|
||||||
attributes = self._show_resource()
|
except Exception as ex:
|
||||||
except Exception as ex:
|
self.client_plugin().ignore_not_found(ex)
|
||||||
self.client_plugin().ignore_not_found(ex)
|
return None
|
||||||
return None
|
return attributes[name]
|
||||||
if name == 'show':
|
|
||||||
return attributes
|
|
||||||
|
|
||||||
return attributes[name]
|
|
||||||
|
|
||||||
def FnGetRefId(self):
|
def FnGetRefId(self):
|
||||||
return six.text_type(self.resource_id)
|
return six.text_type(self.resource_id)
|
||||||
|
@ -516,6 +516,8 @@ class Server(stack_user.StackUser):
|
|||||||
|
|
||||||
default_client_name = 'nova'
|
default_client_name = 'nova'
|
||||||
|
|
||||||
|
entity = 'servers'
|
||||||
|
|
||||||
def translation_rules(self):
|
def translation_rules(self):
|
||||||
return [properties.TranslationRule(
|
return [properties.TranslationRule(
|
||||||
self.properties,
|
self.properties,
|
||||||
@ -934,8 +936,6 @@ class Server(stack_user.StackUser):
|
|||||||
return server.accessIPv4
|
return server.accessIPv4
|
||||||
if name == self.ACCESSIPV6:
|
if name == self.ACCESSIPV6:
|
||||||
return server.accessIPv6
|
return server.accessIPv6
|
||||||
if name == self.SHOW:
|
|
||||||
return server._info
|
|
||||||
if name == self.CONSOLE_URLS:
|
if name == self.CONSOLE_URLS:
|
||||||
return self.client_plugin('nova').get_console_urls(server)
|
return self.client_plugin('nova').get_console_urls(server)
|
||||||
|
|
||||||
|
@ -79,9 +79,11 @@ class StackResource(resource.Resource):
|
|||||||
if not self.attributes and outputs:
|
if not self.attributes and outputs:
|
||||||
self.attributes_schema = (
|
self.attributes_schema = (
|
||||||
attributes.Attributes.schema_from_outputs(outputs))
|
attributes.Attributes.schema_from_outputs(outputs))
|
||||||
self.attributes = attributes.Attributes(self.name,
|
# Note: it can be updated too and for show return dictionary
|
||||||
self.attributes_schema,
|
# with all available outputs
|
||||||
self._resolve_attribute)
|
self.attributes = attributes.Attributes(
|
||||||
|
self.name, self.attributes_schema,
|
||||||
|
self._resolve_all_attributes)
|
||||||
|
|
||||||
def _needs_update(self, after, before, after_props, before_props,
|
def _needs_update(self, after, before, after_props, before_props,
|
||||||
prev_resource):
|
prev_resource):
|
||||||
@ -525,10 +527,7 @@ class StackResource(resource.Resource):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _resolve_attribute(self, name):
|
def _resolve_attribute(self, name):
|
||||||
# NOTE(skraynev): should be removed in patch with methods,
|
return self.get_output(name)
|
||||||
# which resolve base attributes
|
|
||||||
if name != 'show':
|
|
||||||
return self.get_output(name)
|
|
||||||
|
|
||||||
def implementation_signature(self):
|
def implementation_signature(self):
|
||||||
schema_names = ([prop for prop in self.properties_schema] +
|
schema_names = ([prop for prop in self.properties_schema] +
|
||||||
|
@ -117,7 +117,7 @@ class TemplateResource(stack_resource.StackResource):
|
|||||||
self.attributes_schema.update(self.base_attributes_schema)
|
self.attributes_schema.update(self.base_attributes_schema)
|
||||||
self.attributes = attributes.Attributes(self.name,
|
self.attributes = attributes.Attributes(self.name,
|
||||||
self.attributes_schema,
|
self.attributes_schema,
|
||||||
self._resolve_attribute)
|
self._resolve_all_attributes)
|
||||||
|
|
||||||
def child_params(self):
|
def child_params(self):
|
||||||
'''
|
'''
|
||||||
|
@ -63,18 +63,10 @@ class GenericResource(resource.Resource):
|
|||||||
|
|
||||||
|
|
||||||
class ResWithShowAttr(GenericResource):
|
class ResWithShowAttr(GenericResource):
|
||||||
attributes_schema = {'foo': attributes.Schema('A generic attribute'),
|
def _show_resource(self):
|
||||||
'Foo': attributes.Schema('Another generic attribute'),
|
return {'foo': self.name,
|
||||||
'show': attributes.Schema('A dict of all details '
|
'Foo': self.name,
|
||||||
'of attributes')}
|
'Another': self.name}
|
||||||
|
|
||||||
def _resolve_attribute(self, name):
|
|
||||||
if name == 'show':
|
|
||||||
return {'foo': self.name,
|
|
||||||
'Foo': self.name,
|
|
||||||
'Another': self.name}
|
|
||||||
else:
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class ResWithComplexPropsAndAttrs(GenericResource):
|
class ResWithComplexPropsAndAttrs(GenericResource):
|
||||||
|
@ -120,19 +120,26 @@ class NeutronTest(common.HeatTestCase):
|
|||||||
mock_show_resource.side_effect = [{'attr1': 'val1', 'attr2': 'val2'},
|
mock_show_resource.side_effect = [{'attr1': 'val1', 'attr2': 'val2'},
|
||||||
{'attr1': 'val1', 'attr2': 'val2'},
|
{'attr1': 'val1', 'attr2': 'val2'},
|
||||||
{'attr1': 'val1', 'attr2': 'val2'},
|
{'attr1': 'val1', 'attr2': 'val2'},
|
||||||
qe.NotFound,
|
qe.NotFound]
|
||||||
qe.NeutronClientException]
|
|
||||||
res._show_resource = mock_show_resource
|
res._show_resource = mock_show_resource
|
||||||
nclientplugin = neutron.NeutronClientPlugin(mock.MagicMock())
|
nclientplugin = neutron.NeutronClientPlugin(mock.MagicMock())
|
||||||
res.client_plugin = mock.Mock(return_value=nclientplugin)
|
res.client_plugin = mock.Mock(return_value=nclientplugin)
|
||||||
|
|
||||||
self.assertEqual({'attr1': 'val1', 'attr2': 'val2'},
|
self.assertEqual({'attr1': 'val1', 'attr2': 'val2'},
|
||||||
res._resolve_attribute('show'))
|
res.FnGetAtt('show'))
|
||||||
self.assertEqual('val2', res._resolve_attribute('attr2'))
|
self.assertEqual('val2', res._resolve_attribute('attr2'))
|
||||||
self.assertRaises(KeyError, res._resolve_attribute, 'attr3')
|
self.assertRaises(KeyError, res._resolve_attribute, 'attr3')
|
||||||
self.assertIsNone(res._resolve_attribute('attr2'))
|
self.assertIsNone(res._resolve_attribute('attr2'))
|
||||||
|
|
||||||
res.resource_id = None
|
res.resource_id = None
|
||||||
self.assertIsNone(res._resolve_attribute('show'))
|
# use local cached object
|
||||||
|
self.assertEqual({'attr1': 'val1', 'attr2': 'val2'},
|
||||||
|
res.FnGetAtt('show'))
|
||||||
|
# reset cache and call 'show' again, so resolver should be used again
|
||||||
|
# and return None due to resource_id is None
|
||||||
|
with mock.patch.object(res.attributes, '_resolved_values') as res_vals:
|
||||||
|
res_vals.return_value = {}
|
||||||
|
self.assertIsNone(res.FnGetAtt('show'))
|
||||||
|
|
||||||
|
|
||||||
class GetSecGroupUuidTest(common.HeatTestCase):
|
class GetSecGroupUuidTest(common.HeatTestCase):
|
||||||
|
@ -110,12 +110,15 @@ class FormatTest(common.HeatTestCase):
|
|||||||
|
|
||||||
def test_format_resource_attributes(self):
|
def test_format_resource_attributes(self):
|
||||||
res = self.stack['generic1']
|
res = self.stack['generic1']
|
||||||
# the _resolve_attribute method of 'generic1' return res.name
|
# the _resolve_attribute method of 'generic1' returns map with all
|
||||||
|
# attributes except 'show' (because it's None in this test)
|
||||||
formatted_attributes = api.format_resource_attributes(res)
|
formatted_attributes = api.format_resource_attributes(res)
|
||||||
self.assertEqual(res.name, formatted_attributes)
|
expected = {'foo': 'generic1', 'Foo': 'generic1'}
|
||||||
|
self.assertEqual(expected, formatted_attributes)
|
||||||
|
|
||||||
def test_format_resource_attributes_show_attribute(self):
|
def test_format_resource_attributes_show_attribute(self):
|
||||||
res = self.stack['generic3']
|
res = self.stack['generic3']
|
||||||
|
res.resource_id = 'generic3_id'
|
||||||
formatted_attributes = api.format_resource_attributes(res)
|
formatted_attributes = api.format_resource_attributes(res)
|
||||||
self.assertEqual(3, len(formatted_attributes))
|
self.assertEqual(3, len(formatted_attributes))
|
||||||
self.assertIn('foo', formatted_attributes)
|
self.assertIn('foo', formatted_attributes)
|
||||||
@ -124,6 +127,7 @@ class FormatTest(common.HeatTestCase):
|
|||||||
|
|
||||||
def test_format_resource_attributes_show_attribute_with_attr(self):
|
def test_format_resource_attributes_show_attribute_with_attr(self):
|
||||||
res = self.stack['generic3']
|
res = self.stack['generic3']
|
||||||
|
res.resource_id = 'generic3_id'
|
||||||
formatted_attributes = api.format_resource_attributes(
|
formatted_attributes = api.format_resource_attributes(
|
||||||
res, with_attr=['c'])
|
res, with_attr=['c'])
|
||||||
self.assertEqual(4, len(formatted_attributes))
|
self.assertEqual(4, len(formatted_attributes))
|
||||||
|
@ -1645,6 +1645,80 @@ class ResourceTest(common.HeatTestCase):
|
|||||||
self.assertTrue(mock_get_obj.called)
|
self.assertTrue(mock_get_obj.called)
|
||||||
self.assertFalse(db_res.select_and_update.called)
|
self.assertFalse(db_res.select_and_update.called)
|
||||||
|
|
||||||
|
def create_resource_for_attributes_tests(self):
|
||||||
|
tmpl = template.Template({
|
||||||
|
'heat_template_version': '2013-05-23',
|
||||||
|
'resources': {
|
||||||
|
'res': {
|
||||||
|
'type': 'GenericResourceType'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
stack = parser.Stack(utils.dummy_context(), 'test', tmpl)
|
||||||
|
return stack
|
||||||
|
|
||||||
|
def test_resolve_attributes_stuff_base_attribute(self):
|
||||||
|
# check path with resolving base attributes (via 'show' attribute)
|
||||||
|
stack = self.create_resource_for_attributes_tests()
|
||||||
|
res = stack['res']
|
||||||
|
|
||||||
|
with mock.patch.object(res, '_show_resource') as show_attr:
|
||||||
|
# return None, if resource_id is None
|
||||||
|
self.assertIsNone(res.FnGetAtt('show'))
|
||||||
|
|
||||||
|
# set resource_id and recheck with re-written _show_resource
|
||||||
|
res.resource_id = mock.Mock()
|
||||||
|
|
||||||
|
show_attr.return_value = 'my attr'
|
||||||
|
self.assertEqual('my attr', res.FnGetAtt('show'))
|
||||||
|
self.assertEqual(1, show_attr.call_count)
|
||||||
|
|
||||||
|
# clean resolved_values
|
||||||
|
with mock.patch.object(res.attributes, '_resolved_values') as r_v:
|
||||||
|
with mock.patch.object(res, 'client_plugin') as client_plug:
|
||||||
|
r_v.return_value = {}
|
||||||
|
# generate error during calling _show_resource
|
||||||
|
show_attr.side_effect = [Exception]
|
||||||
|
self.assertIsNone(res.FnGetAtt('show'))
|
||||||
|
self.assertEqual(2, show_attr.call_count)
|
||||||
|
self.assertEqual(1, client_plug.call_count)
|
||||||
|
|
||||||
|
def test_resolve_attributes_stuff_custom_attribute(self):
|
||||||
|
# check path with resolve_attribute
|
||||||
|
stack = self.create_resource_for_attributes_tests()
|
||||||
|
res = stack['res']
|
||||||
|
|
||||||
|
with mock.patch.object(res, '_resolve_attribute') as res_attr:
|
||||||
|
res.FnGetAtt('Foo')
|
||||||
|
res_attr.assert_called_once_with('Foo')
|
||||||
|
|
||||||
|
def test_show_resource(self):
|
||||||
|
# check default function _show_resource
|
||||||
|
stack = self.create_resource_for_attributes_tests()
|
||||||
|
res = stack['res']
|
||||||
|
|
||||||
|
# check default value of entity
|
||||||
|
self.assertIsNone(res.entity)
|
||||||
|
self.assertIsNone(res.FnGetAtt('show'))
|
||||||
|
|
||||||
|
# set entity and recheck
|
||||||
|
res.resource_id = 'test_resource_id'
|
||||||
|
res.entity = 'test'
|
||||||
|
|
||||||
|
# mock gettring resource info
|
||||||
|
res.client = mock.Mock()
|
||||||
|
test_obj = mock.Mock()
|
||||||
|
test_resource = mock.Mock()
|
||||||
|
test_resource.to_dict.return_value = {'test': 'info'}
|
||||||
|
test_obj.get.return_value = test_resource
|
||||||
|
res.client().test = test_obj
|
||||||
|
|
||||||
|
self.assertEqual({'test': 'info'}, res._show_resource())
|
||||||
|
|
||||||
|
# check handling AttributeError exception
|
||||||
|
test_obj.get.side_effect = AttributeError
|
||||||
|
self.assertIsNone(res._show_resource())
|
||||||
|
|
||||||
|
|
||||||
class ResourceAdoptTest(common.HeatTestCase):
|
class ResourceAdoptTest(common.HeatTestCase):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user