Merge "Validate the input/output configs in Software Config"
This commit is contained in:
commit
d016928efd
|
@ -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)
|
||||
),
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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')
|
|
@ -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')])
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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'}
|
||||
|
|
Loading…
Reference in New Issue