Merge "Validate the input/output configs in Software Config"

This commit is contained in:
Jenkins 2016-08-18 04:42:48 +00:00 committed by Gerrit Code Review
commit d016928efd
8 changed files with 406 additions and 187 deletions

View File

@ -14,9 +14,9 @@
from heat.common.i18n import _
from heat.engine import attributes
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine import software_config_io as swc_io
from heat.engine import support
from heat.rpc import api as rpc_api
@ -54,66 +54,12 @@ class SoftwareConfig(resource.Resource):
rpc_api.SOFTWARE_CONFIG_INPUTS, rpc_api.SOFTWARE_CONFIG_OUTPUTS,
)
IO_PROPERTIES = (
NAME, DESCRIPTION, TYPE, DEFAULT, ERROR_OUTPUT
) = (
'name', 'description', 'type', 'default', 'error_output'
)
ATTRIBUTES = (
CONFIG_ATTR,
) = (
'config',
)
input_schema = {
NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the input.'),
required=True
),
DESCRIPTION: properties.Schema(
properties.Schema.STRING,
_('Description of the input.')
),
TYPE: properties.Schema(
properties.Schema.STRING,
_('Type of the value of the input.'),
default='String',
constraints=[constraints.AllowedValues((
'String', 'Number', 'CommaDelimitedList', 'Json', 'Boolean'))]
),
DEFAULT: properties.Schema(
properties.Schema.STRING,
_('Default value for the input if none is specified.'),
),
}
output_schema = {
NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the output.'),
required=True
),
DESCRIPTION: properties.Schema(
properties.Schema.STRING,
_('Description of the output.')
),
TYPE: properties.Schema(
properties.Schema.STRING,
_('Type of the value of the output.'),
default='String',
constraints=[constraints.AllowedValues((
'String', 'Number', 'CommaDelimitedList', 'Json', 'Boolean'))]
),
ERROR_OUTPUT: properties.Schema(
properties.Schema.BOOLEAN,
_('Denotes that the deployment is in an error state if this '
'output has a value.'),
default=False
)
}
properties_schema = {
GROUP: properties.Schema(
properties.Schema.STRING,
@ -137,14 +83,14 @@ class SoftwareConfig(resource.Resource):
_('Schema representing the inputs that this software config is '
'expecting.'),
schema=properties.Schema(properties.Schema.MAP,
schema=input_schema)
schema=swc_io.input_config_schema)
),
OUTPUTS: properties.Schema(
properties.Schema.LIST,
_('Schema representing the outputs that this software config '
'will produce.'),
schema=properties.Schema(properties.Schema.MAP,
schema=output_schema)
schema=swc_io.output_config_schema)
),
}

View File

@ -26,9 +26,9 @@ from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine.resources.openstack.heat import resource_group
from heat.engine.resources.openstack.heat import software_config as sc
from heat.engine.resources import signal_responder
from heat.engine import rsrc_defn
from heat.engine import software_config_io as swc_io
from heat.engine import support
from heat.rpc import api as rpc_api
@ -230,6 +230,15 @@ class SoftwareDeployment(signal_responder.SignalResponder):
else:
config = {}
config[rpc_api.SOFTWARE_CONFIG_INPUTS] = [
swc_io.InputConfig(**i)
for i in config.get(rpc_api.SOFTWARE_CONFIG_INPUTS, [])
]
config[rpc_api.SOFTWARE_CONFIG_OUTPUTS] = [
swc_io.OutputConfig(**o)
for o in config.get(rpc_api.SOFTWARE_CONFIG_OUTPUTS, [])
]
if config.get(rpc_api.SOFTWARE_CONFIG_GROUP) == 'component':
valid_actions = set()
for conf in config[rpc_api.SOFTWARE_CONFIG_CONFIG]['configs']:
@ -305,9 +314,10 @@ class SoftwareDeployment(signal_responder.SignalResponder):
rpc_api.SOFTWARE_CONFIG_CONFIG:
derived_config or self.empty_config(),
rpc_api.SOFTWARE_CONFIG_OPTIONS: derived_options,
rpc_api.SOFTWARE_CONFIG_INPUTS: derived_inputs,
rpc_api.SOFTWARE_CONFIG_INPUTS:
[i.as_dict() for i in derived_inputs],
rpc_api.SOFTWARE_CONFIG_OUTPUTS:
source.get(rpc_api.SOFTWARE_CONFIG_OUTPUTS),
[o.as_dict() for o in source[rpc_api.SOFTWARE_CONFIG_OUTPUTS]],
rpc_api.SOFTWARE_CONFIG_NAME:
derived_name or self.physical_resource_name()
}
@ -320,121 +330,89 @@ class SoftwareDeployment(signal_responder.SignalResponder):
return source.get(rpc_api.SOFTWARE_CONFIG_OPTIONS)
def _build_derived_inputs(self, action, source):
scl = sc.SoftwareConfig
inputs = copy.deepcopy(source.get(rpc_api.SOFTWARE_CONFIG_INPUTS) or
[])
input_values = dict(self.properties.get(self.INPUT_VALUES) or {})
inputs = source[rpc_api.SOFTWARE_CONFIG_INPUTS]
input_values = dict(self.properties[self.INPUT_VALUES] or {})
for inp in inputs:
input_key = inp[scl.NAME]
inp['value'] = input_values.pop(input_key, inp[scl.DEFAULT])
def derive_inputs():
for input_config in inputs:
value = input_values.pop(input_config.name(),
input_config.default())
yield swc_io.InputConfig(value=value, **input_config.as_dict())
# for any input values that do not have a declared input, add
# a derived declared input so that they can be used as config
# inputs
for inpk, inpv in input_values.items():
inputs.append({
scl.NAME: inpk,
scl.TYPE: 'String',
'value': inpv
})
# for any input values that do not have a declared input, add
# a derived declared input so that they can be used as config
# inputs
for inpk, inpv in input_values.items():
yield swc_io.InputConfig(name=inpk, value=inpv)
inputs.extend([{
scl.NAME: self.DEPLOY_SERVER_ID,
scl.DESCRIPTION: _('ID of the server being deployed to'),
scl.TYPE: 'String',
'value': self.properties[self.SERVER]
}, {
scl.NAME: self.DEPLOY_ACTION,
scl.DESCRIPTION: _('Name of the current action being deployed'),
scl.TYPE: 'String',
'value': action
}, {
scl.NAME: self.DEPLOY_STACK_ID,
scl.DESCRIPTION: _('ID of the stack this deployment belongs to'),
scl.TYPE: 'String',
'value': self.stack.identifier().stack_path()
}, {
scl.NAME: self.DEPLOY_RESOURCE_NAME,
scl.DESCRIPTION: _('Name of this deployment resource in the '
'stack'),
scl.TYPE: 'String',
'value': self.name
}, {
scl.NAME: self.DEPLOY_SIGNAL_TRANSPORT,
scl.DESCRIPTION: _('How the server should signal to heat with '
'the deployment output values.'),
scl.TYPE: 'String',
'value': self.properties[self.SIGNAL_TRANSPORT]
}])
if self._signal_transport_cfn():
inputs.append({
scl.NAME: self.DEPLOY_SIGNAL_ID,
scl.DESCRIPTION: _('ID of signal to use for signaling '
'output values'),
scl.TYPE: 'String',
'value': self._get_ec2_signed_url()
})
inputs.append({
scl.NAME: self.DEPLOY_SIGNAL_VERB,
scl.DESCRIPTION: _('HTTP verb to use for signaling '
'output values'),
scl.TYPE: 'String',
'value': 'POST'
})
elif self._signal_transport_temp_url():
inputs.append({
scl.NAME: self.DEPLOY_SIGNAL_ID,
scl.DESCRIPTION: _('ID of signal to use for signaling '
'output values'),
scl.TYPE: 'String',
'value': self._get_swift_signal_url()
})
inputs.append({
scl.NAME: self.DEPLOY_SIGNAL_VERB,
scl.DESCRIPTION: _('HTTP verb to use for signaling '
'output values'),
scl.TYPE: 'String',
'value': 'PUT'
})
elif self._signal_transport_heat() or self._signal_transport_zaqar():
creds = self._get_heat_signal_credentials()
inputs.extend([{
scl.NAME: self.DEPLOY_AUTH_URL,
scl.DESCRIPTION: _('URL for API authentication'),
scl.TYPE: 'String',
'value': creds['auth_url']
}, {
scl.NAME: self.DEPLOY_USERNAME,
scl.DESCRIPTION: _('Username for API authentication'),
scl.TYPE: 'String',
'value': creds['username']
}, {
scl.NAME: self.DEPLOY_USER_ID,
scl.DESCRIPTION: _('User ID for API authentication'),
scl.TYPE: 'String',
'value': creds['user_id']
}, {
scl.NAME: self.DEPLOY_PASSWORD,
scl.DESCRIPTION: _('Password for API authentication'),
scl.TYPE: 'String',
'value': creds['password']
}, {
scl.NAME: self.DEPLOY_PROJECT_ID,
scl.DESCRIPTION: _('ID of project for API authentication'),
scl.TYPE: 'String',
'value': creds['project_id']
}])
if self._signal_transport_zaqar():
inputs.append({
scl.NAME: self.DEPLOY_QUEUE_ID,
scl.DESCRIPTION: _('ID of queue to use for signaling '
'output values'),
scl.TYPE: 'String',
'value': self._get_zaqar_signal_queue_id()
})
yield swc_io.InputConfig(
name=self.DEPLOY_SERVER_ID, value=self.properties[self.SERVER],
description=_('ID of the server being deployed to'))
yield swc_io.InputConfig(
name=self.DEPLOY_ACTION, value=action,
description=_('Name of the current action being deployed'))
yield swc_io.InputConfig(
name=self.DEPLOY_STACK_ID,
value=self.stack.identifier().stack_path(),
description=_('ID of the stack this deployment belongs to'))
yield swc_io.InputConfig(
name=self.DEPLOY_RESOURCE_NAME, value=self.name,
description=_('Name of this deployment resource in the stack'))
yield swc_io.InputConfig(
name=self.DEPLOY_SIGNAL_TRANSPORT,
value=self.properties[self.SIGNAL_TRANSPORT],
description=_('How the server should signal to heat with '
'the deployment output values.'))
return inputs
if self._signal_transport_cfn():
yield swc_io.InputConfig(
name=self.DEPLOY_SIGNAL_ID,
value=self._get_ec2_signed_url(),
description=_('ID of signal to use for signaling output '
'values'))
yield swc_io.InputConfig(
name=self.DEPLOY_SIGNAL_VERB, value='POST',
description=_('HTTP verb to use for signaling output'
'values'))
elif self._signal_transport_temp_url():
yield swc_io.InputConfig(
name=self.DEPLOY_SIGNAL_ID,
value=self._get_swift_signal_url(),
description=_('ID of signal to use for signaling output '
'values'))
yield swc_io.InputConfig(
name=self.DEPLOY_SIGNAL_VERB, value='PUT',
description=_('HTTP verb to use for signaling output'
'values'))
elif (self._signal_transport_heat() or
self._signal_transport_zaqar()):
creds = self._get_heat_signal_credentials()
yield swc_io.InputConfig(
name=self.DEPLOY_AUTH_URL, value=creds['auth_url'],
description=_('URL for API authentication'))
yield swc_io.InputConfig(
name=self.DEPLOY_USERNAME, value=creds['username'],
description=_('Username for API authentication'))
yield swc_io.InputConfig(
name=self.DEPLOY_USER_ID, value=creds['user_id'],
description=_('User ID for API authentication'))
yield swc_io.InputConfig(
name=self.DEPLOY_PASSWORD, value=creds['password'],
description=_('Password for API authentication'))
yield swc_io.InputConfig(
name=self.DEPLOY_PROJECT_ID, value=creds['project_id'],
description=_('ID of project for API authentication'))
if self._signal_transport_zaqar():
yield swc_io.InputConfig(
name=self.DEPLOY_QUEUE_ID,
value=self._get_zaqar_signal_queue_id(),
description=_('ID of queue to use for signaling output '
'values'))
return list(derive_inputs())
def handle_create(self):
return self._handle_action(self.CREATE)

View File

@ -142,7 +142,7 @@ class StructuredDeployment(sd.SoftwareDeployment):
input_key = self.properties[self.INPUT_KEY]
check_input_val = self.properties[self.INPUT_VALUES_VALIDATE]
inputs = dict((i['name'], i['value']) for i in derived_inputs)
inputs = dict(i.input_data() for i in derived_inputs)
return self.parse(inputs, input_key, cfg, check_input_val)

View File

@ -28,6 +28,7 @@ from heat.common.i18n import _LI
from heat.db import api as db_api
from heat.engine import api
from heat.engine import scheduler
from heat.engine import software_config_io as swc_io
from heat.objects import resource as resource_objects
from heat.objects import software_config as software_config_object
from heat.objects import software_deployment as software_deployment_object
@ -55,12 +56,17 @@ class SoftwareConfigService(service.Service):
def create_software_config(self, cnxt, group, name, config,
inputs, outputs, options):
swc_io.check_io_schema_list(inputs)
in_conf = [swc_io.InputConfig(**i).as_dict() for i in inputs]
swc_io.check_io_schema_list(outputs)
out_conf = [swc_io.OutputConfig(**o).as_dict() for o in outputs]
sc = software_config_object.SoftwareConfig.create(cnxt, {
'group': group,
'name': name,
'config': {
rpc_api.SOFTWARE_CONFIG_INPUTS: inputs,
rpc_api.SOFTWARE_CONFIG_OUTPUTS: outputs,
rpc_api.SOFTWARE_CONFIG_INPUTS: in_conf,
rpc_api.SOFTWARE_CONFIG_OUTPUTS: out_conf,
rpc_api.SOFTWARE_CONFIG_OPTIONS: options,
rpc_api.SOFTWARE_CONFIG_CONFIG: config
},
@ -220,8 +226,8 @@ class SoftwareConfigService(service.Service):
cnxt, deployment_id)
if sd.status == rpc_api.SOFTWARE_DEPLOYMENT_IN_PROGRESS:
c = sd.config.config
input_values = {i['name']: i['value']
for i in c[rpc_api.SOFTWARE_CONFIG_INPUTS]}
input_values = dict(swc_io.InputConfig(**i).input_data()
for i in c[rpc_api.SOFTWARE_CONFIG_INPUTS])
transport = input_values.get('deploy_signal_transport')
if transport == 'TEMP_URL_SIGNAL':
sd = self._refresh_swift_software_deployment(

View File

@ -0,0 +1,164 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
APIs for dealing with input and output definitions for Software Configurations.
"""
import collections
import six
from heat.common.i18n import _
from heat.common import exception
from heat.engine import constraints
from heat.engine import properties
(
IO_NAME, DESCRIPTION, TYPE,
DEFAULT, VALUE,
ERROR_OUTPUT,
) = (
'name', 'description', 'type',
'default', 'value',
'error_output',
)
TYPES = (
STRING_TYPE, NUMBER_TYPE, LIST_TYPE, JSON_TYPE, BOOLEAN_TYPE,
) = (
'String', 'Number', 'CommaDelimitedList', 'Json', 'Boolean',
)
input_config_schema = {
IO_NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the input.'),
required=True
),
DESCRIPTION: properties.Schema(
properties.Schema.STRING,
_('Description of the input.')
),
TYPE: properties.Schema(
properties.Schema.STRING,
_('Type of the value of the input.'),
default=STRING_TYPE,
constraints=[constraints.AllowedValues(TYPES)]
),
DEFAULT: properties.Schema(
properties.Schema.STRING,
_('Default value for the input if none is specified.'),
),
}
output_config_schema = {
IO_NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the output.'),
required=True
),
DESCRIPTION: properties.Schema(
properties.Schema.STRING,
_('Description of the output.')
),
TYPE: properties.Schema(
properties.Schema.STRING,
_('Type of the value of the output.'),
default=STRING_TYPE,
constraints=[constraints.AllowedValues(TYPES)]
),
ERROR_OUTPUT: properties.Schema(
properties.Schema.BOOLEAN,
_('Denotes that the deployment is in an error state if this '
'output has a value.'),
default=False
)
}
class IOConfig(object):
"""Base class for the configuration data for a single input or output."""
def __init__(self, **config):
self._props = properties.Properties(self.schema, config)
try:
self._props.validate()
except exception.StackValidationFailed as exc:
raise ValueError(six.text_type(exc))
def name(self):
"""Return the name of the input or output."""
return self._props[IO_NAME]
def as_dict(self):
"""Return a dict representation suitable for persisting."""
return {k: v for k, v in self._props.items() if v is not None}
def __repr__(self):
return '%s(%s)' % (type(self).__name__,
', '.join('%s=%s' % (k, repr(v))
for k, v in self.as_dict().items()))
_no_value = object()
class InputConfig(IOConfig):
"""Class representing the configuration data for a single input."""
schema = input_config_schema
def __init__(self, value=_no_value, **config):
super(InputConfig, self).__init__(**config)
self._value = value
def default(self):
"""Return the default value of the input."""
return self._props[DEFAULT]
def as_dict(self):
"""Return a dict representation suitable for persisting."""
d = super(InputConfig, self).as_dict()
if self._value is not _no_value:
d[VALUE] = self._value
return d
def input_data(self):
"""Return a name, value pair for the input."""
value = self._value if self._value is not _no_value else None
return self.name(), value
class OutputConfig(IOConfig):
"""Class representing the configuration data for a single output."""
schema = output_config_schema
def error_output(self):
"""Return True if the presence of the output indicates an error."""
return self._props[ERROR_OUTPUT]
def check_io_schema_list(io_configs):
"""Check that an input or output schema list is of the correct type.
Raises TypeError if the list itself is not a list, or if any of the
members are not dicts.
"""
if (not isinstance(io_configs, collections.Sequence) or
isinstance(io_configs, collections.Mapping) or
isinstance(io_configs, six.string_types)):
raise TypeError('Software Config I/O Schema must be in a list')
if not all(isinstance(conf, collections.Mapping) for conf in io_configs):
raise TypeError('Software Config I/O Schema must be a dict')

View File

@ -28,6 +28,7 @@ from heat.engine.clients.os import swift
from heat.engine.clients.os import zaqar
from heat.engine import service
from heat.engine import service_software_config
from heat.engine import software_config_io as swc_io
from heat.objects import resource as resource_objects
from heat.objects import software_config as software_config_object
from heat.objects import software_deployment as software_deployment_object
@ -148,8 +149,11 @@ class SoftwareConfigServiceTest(common.HeatTestCase):
self.assertEqual(kwargs['group'], config['group'])
self.assertEqual(kwargs['name'], config['name'])
self.assertEqual(kwargs['config'], config['config'])
self.assertEqual(kwargs['inputs'], config['inputs'])
self.assertEqual(kwargs['outputs'], config['outputs'])
self.assertEqual([{'name': 'mode', 'type': 'String'}],
config['inputs'])
self.assertEqual([{'name': 'endpoint', 'type': 'String',
'error_output': False}],
config['outputs'])
self.assertEqual(kwargs['options'], config['options'])
def test_delete_software_config(self):
@ -910,3 +914,112 @@ class SoftwareConfigServiceTest(common.HeatTestCase):
zaqar_client.queue.assert_called_once_with('6789')
queue.pop.assert_called_once_with()
ssd.assert_called_once_with(self.ctx, deployment_id, 'ok', None)
class SoftwareConfigIOSchemaTest(common.HeatTestCase):
def test_input_config_empty(self):
name = 'foo'
inp = swc_io.InputConfig(name=name)
self.assertIsNone(inp.default())
self.assertEqual(name, inp.name())
self.assertEqual({'name': name, 'type': 'String'}, inp.as_dict())
self.assertEqual((name, None), inp.input_data())
def test_input_config(self):
name = 'bar'
inp = swc_io.InputConfig(name=name, description='test', type='Number',
default=0)
self.assertEqual('0', inp.default())
self.assertEqual(name, inp.name())
self.assertEqual({'name': name, 'type': 'Number',
'description': 'test', 'default': '0'},
inp.as_dict())
self.assertEqual((name, None), inp.input_data())
def test_input_config_value(self):
name = 'baz'
inp = swc_io.InputConfig(name=name, type='Number',
default=0, value=42)
self.assertEqual('0', inp.default())
self.assertEqual(name, inp.name())
self.assertEqual({'name': name, 'type': 'Number',
'default': '0', 'value': 42},
inp.as_dict())
self.assertEqual((name, 42), inp.input_data())
def test_input_config_no_name(self):
self.assertRaises(ValueError, swc_io.InputConfig, type='String')
def test_input_config_extra_key(self):
self.assertRaises(ValueError, swc_io.InputConfig,
name='test', bogus='wat')
def test_input_types(self):
swc_io.InputConfig(name='str', type='String').as_dict()
swc_io.InputConfig(name='num', type='Number').as_dict()
swc_io.InputConfig(name='list', type='CommaDelimitedList').as_dict()
swc_io.InputConfig(name='json', type='Json').as_dict()
swc_io.InputConfig(name='bool', type='Boolean').as_dict()
self.assertRaises(ValueError, swc_io.InputConfig,
name='bogus', type='BogusType')
def test_output_config_empty(self):
name = 'foo'
outp = swc_io.OutputConfig(name=name)
self.assertEqual(name, outp.name())
self.assertEqual({'name': name, 'type': 'String',
'error_output': False},
outp.as_dict())
def test_output_config(self):
name = 'bar'
outp = swc_io.OutputConfig(name=name, description='test',
type='Json', error_output=True)
self.assertEqual(name, outp.name())
self.assertIs(True, outp.error_output())
self.assertEqual({'name': name, 'type': 'Json',
'description': 'test', 'error_output': True},
outp.as_dict())
def test_output_config_no_name(self):
self.assertRaises(ValueError, swc_io.OutputConfig, type='String')
def test_output_config_extra_key(self):
self.assertRaises(ValueError, swc_io.OutputConfig,
name='test', bogus='wat')
def test_output_types(self):
swc_io.OutputConfig(name='str', type='String').as_dict()
swc_io.OutputConfig(name='num', type='Number').as_dict()
swc_io.OutputConfig(name='list', type='CommaDelimitedList').as_dict()
swc_io.OutputConfig(name='json', type='Json').as_dict()
swc_io.OutputConfig(name='bool', type='Boolean').as_dict()
self.assertRaises(ValueError, swc_io.OutputConfig,
name='bogus', type='BogusType')
def test_check_io_schema_empty_list(self):
swc_io.check_io_schema_list([])
def test_check_io_schema_string(self):
self.assertRaises(TypeError, swc_io.check_io_schema_list, '')
def test_check_io_schema_dict(self):
self.assertRaises(TypeError, swc_io.check_io_schema_list, {})
def test_check_io_schema_list_dict(self):
swc_io.check_io_schema_list([{'name': 'foo'}])
def test_check_io_schema_list_string(self):
self.assertRaises(TypeError, swc_io.check_io_schema_list, ['foo'])
def test_check_io_schema_list_list(self):
self.assertRaises(TypeError, swc_io.check_io_schema_list, [['foo']])
def test_check_io_schema_list_none(self):
self.assertRaises(TypeError, swc_io.check_io_schema_list, [None])
def test_check_io_schema_list_mixed(self):
self.assertRaises(TypeError, swc_io.check_io_schema_list,
[{'name': 'foo'}, ('name', 'bar')])

View File

@ -236,7 +236,12 @@ class SoftwareDeploymentTest(common.HeatTestCase):
}],
'outputs': [],
}
self.rpc_client.show_software_config.return_value = config
def copy_config(*args, **kwargs):
return config.copy()
self.rpc_client.show_software_config.side_effect = copy_config
return config
def mock_software_component(self):
@ -285,7 +290,11 @@ class SoftwareDeploymentTest(common.HeatTestCase):
}],
'outputs': [],
}
self.rpc_client.show_software_config.return_value = config
def copy_config(*args, **kwargs):
return config.copy()
self.rpc_client.show_software_config.side_effect = copy_config
return config
def mock_derived_software_config(self):
@ -420,7 +429,7 @@ class SoftwareDeploymentTest(common.HeatTestCase):
'value': 'bar'
}],
'options': None,
'outputs': None
'outputs': [],
}, call_arg)
self.assertEqual(

View File

@ -16,8 +16,10 @@ import mock
from heat.common import exception
from heat.engine.resources.openstack.heat import structured_config as sc
from heat.engine import rsrc_defn
from heat.engine import software_config_io as swc_io
from heat.engine import stack as parser
from heat.engine import template
from heat.rpc import api as rpc_api
from heat.tests import common
from heat.tests import utils
@ -171,13 +173,15 @@ class StructuredDeploymentDerivedTest(common.HeatTestCase):
source = {
'config': {"foo": {"get_input": "bar"}}
}
inputs = [{'name': 'bar', 'value': 'baz'}]
inputs = [swc_io.InputConfig(name='bar', value='baz')]
result = self.deployment._build_derived_config(
'CREATE', source, inputs, {})
self.assertEqual({"foo": "baz"}, result)
def test_build_derived_config_params_with_empty_config(self):
source = {}
source[rpc_api.SOFTWARE_CONFIG_INPUTS] = []
source[rpc_api.SOFTWARE_CONFIG_OUTPUTS] = []
result = self.deployment._build_derived_config_params(
'CREATE', source)
self.assertEqual('Heat::Ungrouped', result['group'])
@ -187,7 +191,7 @@ class StructuredDeploymentDerivedTest(common.HeatTestCase):
self.assertIn({'name': 'bar', 'type': 'String', 'value': 'baz'},
result['inputs'])
self.assertIsNone(result['options'])
self.assertIsNone(result['outputs'])
self.assertEqual([], result['outputs'])
class StructuredDeploymentWithStrictInputTest(common.HeatTestCase):
@ -207,8 +211,8 @@ class StructuredDeploymentWithStrictInputTest(common.HeatTestCase):
self.source = {'config':
{'foo': [{"get_input": "bar"},
{"get_input": "barz"}]}}
self.inputs = [{'name': 'bar', 'value': 'baz'},
{'name': 'barz', 'value': 'baz2'}]
self.inputs = [swc_io.InputConfig(name='bar', value='baz'),
swc_io.InputConfig(name='barz', value='baz2')]
def _stack_with_template(self, template_def):
self.ctx = utils.dummy_context()
@ -221,11 +225,10 @@ class StructuredDeploymentWithStrictInputTest(common.HeatTestCase):
props = {'input_values_validate': 'STRICT'}
self.template['Resources']['deploy_mysql']['Properties'] = props
self._stack_with_template(self.template)
inputs = [{'name': 'bar', 'value': 'baz'}]
self.assertRaises(exception.UserParameterMissing,
self.deployment._build_derived_config,
'CREATE', self.source, inputs, {})
'CREATE', self.source, self.inputs[:1], {})
def test_build_derived_config_success(self):
props = {'input_values_validate': 'STRICT'}