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:
parent
9f26fc53e7
commit
1d7ba6e4a1
@ -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())
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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',
|
||||||
|
Loading…
Reference in New Issue
Block a user