Add resource properties to stack-preview

Certain resource details may influence the final price of the
infrastructure being created by a stack (e.g. instance flavors), so this
adds resources properties to the output of stack-preview.

Implements: blueprint stack-preview-details
Change-Id: I6ea892dedfc96aeda8e8a21e67562820fd21c7bb
This commit is contained in:
Anderson Mesquita 2014-07-01 16:29:47 -04:00
parent 9f26fc53e7
commit 1d7ba6e4a1
4 changed files with 91 additions and 4 deletions

View File

@ -111,7 +111,18 @@ def format_stack(stack):
return info return info
def format_stack_resource(resource, detail=True): def format_resource_properties(resource):
def get_property(prop):
try:
return resource.properties[prop]
except (KeyError, ValueError):
return None
return dict((prop, get_property(prop))
for prop in resource.properties_schema.keys())
def format_stack_resource(resource, detail=True, with_props=False):
''' '''
Return a representation of the given resource that matches the API output Return a representation of the given resource that matches the API output
expectations. expectations.
@ -142,6 +153,9 @@ def format_stack_resource(resource, detail=True):
res[api.RES_DESCRIPTION] = resource.t.description res[api.RES_DESCRIPTION] = resource.t.description
res[api.RES_METADATA] = resource.metadata_get() res[api.RES_METADATA] = resource.metadata_get()
if with_props:
res[api.RES_SCHEMA_PROPERTIES] = format_resource_properties(resource)
return res return res
@ -149,7 +163,7 @@ def format_stack_preview(stack):
def format_resource(res): def format_resource(res):
if isinstance(res, list): if isinstance(res, list):
return map(format_resource, res) return map(format_resource, res)
return format_stack_resource(res) return format_stack_resource(res, with_props=True)
fmt_stack = format_stack(stack) fmt_stack = format_stack(stack)
fmt_resources = map(format_resource, stack.preview_resources()) fmt_resources = map(format_resource, stack.preview_resources())

View File

@ -108,7 +108,15 @@ class TemplateResource(stack_resource.StackResource):
if not pval.implemented(): if not pval.implemented():
continue continue
try:
val = self.properties[pname] val = self.properties[pname]
except ValueError:
if self.action == self.INIT:
prop = self.properties.props[pname]
val = prop.get_value(None)
else:
raise
if val is not None: if val is not None:
# take a list and create a CommaDelimitedList # take a list and create a CommaDelimitedList
if pval.type() == properties.Schema.LIST: if pval.type() == properties.Schema.LIST:

View File

@ -46,6 +46,8 @@ class FormatTest(HeatTestCase):
}) })
resource._register_class('GenericResourceType', resource._register_class('GenericResourceType',
generic_rsrc.GenericResource) generic_rsrc.GenericResource)
resource._register_class('ResWithComplexPropsAndAttrs',
generic_rsrc.ResWithComplexPropsAndAttrs)
self.stack = parser.Stack(utils.dummy_context(), 'test_stack', self.stack = parser.Stack(utils.dummy_context(), 'test_stack',
template, stack_id=str(uuid.uuid4())) template, stack_id=str(uuid.uuid4()))
@ -82,6 +84,49 @@ class FormatTest(HeatTestCase):
formatted = api.format_stack_resource(res, False) formatted = api.format_stack_resource(res, False)
self.assertEqual(resource_keys, set(formatted.keys())) self.assertEqual(resource_keys, set(formatted.keys()))
@mock.patch.object(api, 'format_resource_properties')
def test_format_stack_resource_with_props(self, mock_format_props):
mock_format_props.return_value = 'formatted_res_props'
res = self.stack['generic1']
formatted = api.format_stack_resource(res, True, with_props=True)
formatted_props = formatted[rpc_api.RES_SCHEMA_PROPERTIES]
self.assertEqual('formatted_res_props', formatted_props)
def _get_formatted_resource_properties(self, res_name):
tmpl = parser.Template(template_format.parse('''
heat_template_version: 2013-05-23
resources:
resource1:
type: ResWithComplexPropsAndAttrs
resource2:
type: ResWithComplexPropsAndAttrs
properties:
a_string: foobar
resource3:
type: ResWithComplexPropsAndAttrs
properties:
a_string: { get_attr: [ resource2, string] }
'''))
stack = parser.Stack(utils.dummy_context(), 'test_stack_for_preview',
tmpl, stack_id=str(uuid.uuid4()))
res = stack[res_name]
return api.format_resource_properties(res)
def test_format_resource_properties_empty(self):
props = self._get_formatted_resource_properties('resource1')
self.assertIsNone(props['a_string'])
self.assertIsNone(props['a_list'])
self.assertIsNone(props['a_map'])
def test_format_resource_properties_direct_props(self):
props = self._get_formatted_resource_properties('resource2')
self.assertEqual('foobar', props['a_string'])
def test_format_resource_properties_get_attr(self):
props = self._get_formatted_resource_properties('resource3')
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['generic1']
nested_id = {'foo': 'bar'} nested_id = {'foo': 'bar'}
@ -157,7 +202,7 @@ class FormatTest(HeatTestCase):
@mock.patch.object(api, 'format_stack_resource') @mock.patch.object(api, 'format_stack_resource')
def test_format_stack_preview(self, mock_fmt_resource): def test_format_stack_preview(self, mock_fmt_resource):
def mock_format_resources(res): def mock_format_resources(res, **kwargs):
return 'fmt%s' % res return 'fmt%s' % res
mock_fmt_resource.side_effect = mock_format_resources mock_fmt_resource.side_effect = mock_format_resources
@ -170,6 +215,9 @@ class FormatTest(HeatTestCase):
self.assertIn('resources', stack) self.assertIn('resources', stack)
self.assertEqual(['fmt1', ['fmt2', ['fmt3']]], stack['resources']) self.assertEqual(['fmt1', ['fmt2', ['fmt3']]], stack['resources'])
kwargs = mock_fmt_resource.call_args[1]
self.assertTrue(kwargs['with_props'])
def test_format_stack(self): def test_format_stack(self):
self.stack.created_time = datetime(1970, 1, 1) self.stack.created_time = datetime(1970, 1, 1)
info = api.format_stack(self.stack) info = api.format_stack(self.stack)

View File

@ -16,6 +16,8 @@ import os
import uuid import uuid
import yaml import yaml
import mock
from heat.common import exception from heat.common import exception
from heat.common import template_format from heat.common import template_format
from heat.common import urlfetch from heat.common import urlfetch
@ -157,6 +159,21 @@ class ProviderTemplateTest(HeatTestCase):
# verify Map conversion # verify Map conversion
self.assertEqual(map_prop_val, converted_params.get("AMap")) self.assertEqual(map_prop_val, converted_params.get("AMap"))
with mock.patch.object(properties.Properties, '__getitem__') as m_get:
m_get.side_effect = ValueError('boom')
# If the property doesn't exist on INIT, return default value
temp_res.action = temp_res.INIT
converted_params = temp_res.child_params()
for key in DummyResource.properties_schema:
self.assertIn(key, converted_params)
self.assertEqual({}, converted_params['AMap'])
self.assertEqual(0, converted_params['ANum'])
# If the property doesn't exist past INIT, then error out
temp_res.action = temp_res.CREATE
self.assertRaises(ValueError, temp_res.child_params)
def test_attributes_extra(self): def test_attributes_extra(self):
provider = { provider = {
'HeatTemplateFormatVersion': '2012-12-12', 'HeatTemplateFormatVersion': '2012-12-12',