
In some cases, there is no information about resource and section, where Property error raised. This patch improves StackValidationFailed msg, so this msg look like "Property error : resource_name.section_name.key_name: error_msg", where section_name is section, where Property error raised, e.g. 'update_policy'. Change-Id: Iab2a6acdec254b39983de420ab03f994cff48d89 Closes-bug: #1358512
287 lines
10 KiB
Python
287 lines
10 KiB
Python
#
|
|
# 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.
|
|
|
|
import mock
|
|
import six
|
|
|
|
from heat.common import exception as exc
|
|
from heat.common import template_format
|
|
from heat.engine.resources.openstack.heat import software_component as sc
|
|
from heat.engine import stack
|
|
from heat.engine import template
|
|
from heat.tests import common
|
|
from heat.tests import utils
|
|
|
|
|
|
class SoftwareComponentTest(common.HeatTestCase):
|
|
|
|
def setUp(self):
|
|
super(SoftwareComponentTest, self).setUp()
|
|
self.ctx = utils.dummy_context()
|
|
|
|
tpl = '''
|
|
heat_template_version: 2013-05-23
|
|
resources:
|
|
mysql_component:
|
|
type: OS::Heat::SoftwareComponent
|
|
properties:
|
|
configs:
|
|
- actions: [CREATE]
|
|
config: |
|
|
#!/bin/bash
|
|
echo "Create MySQL"
|
|
tool: script
|
|
- actions: [UPDATE]
|
|
config: |
|
|
#!/bin/bash
|
|
echo "Update MySQL"
|
|
tool: script
|
|
inputs:
|
|
- name: mysql_port
|
|
outputs:
|
|
- name: root_password
|
|
'''
|
|
|
|
self.template = template_format.parse(tpl)
|
|
self.stack = stack.Stack(
|
|
self.ctx, 'software_component_test_stack',
|
|
template.Template(self.template))
|
|
self.component = self.stack['mysql_component']
|
|
self.rpc_client = mock.MagicMock()
|
|
self.component._rpc_client = self.rpc_client
|
|
|
|
def test_resource_mapping(self):
|
|
mapping = sc.resource_mapping()
|
|
self.assertEqual(1, len(mapping))
|
|
self.assertEqual(sc.SoftwareComponent,
|
|
mapping['OS::Heat::SoftwareComponent'])
|
|
self.assertIsInstance(self.component, sc.SoftwareComponent)
|
|
|
|
def test_handle_create(self):
|
|
config_id = 'c8a19429-7fde-47ea-a42f-40045488226c'
|
|
value = {'id': config_id}
|
|
self.rpc_client.create_software_config.return_value = value
|
|
props = dict(self.component.properties)
|
|
self.component.handle_create()
|
|
self.rpc_client.create_software_config.assert_called_with(
|
|
self.ctx,
|
|
group='component',
|
|
name=None,
|
|
inputs=props['inputs'],
|
|
outputs=props['outputs'],
|
|
config={'configs': props['configs']},
|
|
options=None)
|
|
self.assertEqual(config_id, self.component.resource_id)
|
|
|
|
def test_handle_delete(self):
|
|
self.resource_id = None
|
|
self.assertIsNone(self.component.handle_delete())
|
|
config_id = 'c8a19429-7fde-47ea-a42f-40045488226c'
|
|
self.component.resource_id = config_id
|
|
self.rpc_client.delete_software_config.return_value = None
|
|
self.assertIsNone(self.component.handle_delete())
|
|
self.rpc_client.delete_software_config.side_effect = exc.NotFound
|
|
self.assertIsNone(self.component.handle_delete())
|
|
|
|
def test_resolve_attribute(self):
|
|
self.assertIsNone(self.component._resolve_attribute('others'))
|
|
self.component.resource_id = None
|
|
self.assertIsNone(self.component._resolve_attribute('configs'))
|
|
self.component.resource_id = 'c8a19429-7fde-47ea-a42f-40045488226c'
|
|
configs = self.template['resources']['mysql_component'
|
|
]['properties']['configs']
|
|
# configs list is stored in 'config' property of SoftwareConfig
|
|
value = {'config': {'configs': configs}}
|
|
self.rpc_client.show_software_config.return_value = value
|
|
self.assertEqual(configs, self.component._resolve_attribute('configs'))
|
|
self.rpc_client.show_software_config.side_effect = exc.NotFound
|
|
self.assertIsNone(self.component._resolve_attribute('configs'))
|
|
|
|
|
|
class SoftwareComponentValidationTest(common.HeatTestCase):
|
|
|
|
scenarios = [
|
|
(
|
|
'component_full',
|
|
dict(snippet='''
|
|
component:
|
|
type: OS::Heat::SoftwareComponent
|
|
properties:
|
|
configs:
|
|
- actions: [CREATE]
|
|
config: |
|
|
#!/bin/bash
|
|
echo CREATE $foo
|
|
tool: script
|
|
inputs:
|
|
- name: foo
|
|
outputs:
|
|
- name: bar
|
|
options:
|
|
opt1: blah
|
|
''',
|
|
err=None,
|
|
err_msg=None)
|
|
),
|
|
(
|
|
'no_input_output_options',
|
|
dict(snippet='''
|
|
component:
|
|
type: OS::Heat::SoftwareComponent
|
|
properties:
|
|
configs:
|
|
- actions: [CREATE]
|
|
config: |
|
|
#!/bin/bash
|
|
echo CREATE $foo
|
|
tool: script
|
|
''',
|
|
err=None,
|
|
err_msg=None)
|
|
),
|
|
(
|
|
'wrong_property_config',
|
|
dict(snippet='''
|
|
component:
|
|
type: OS::Heat::SoftwareComponent
|
|
properties:
|
|
config: #!/bin/bash
|
|
configs:
|
|
- actions: [CREATE]
|
|
config: |
|
|
#!/bin/bash
|
|
echo CREATE $foo
|
|
tool: script
|
|
''',
|
|
err=exc.StackValidationFailed,
|
|
err_msg='Unknown Property config')
|
|
),
|
|
(
|
|
'missing_configs',
|
|
dict(snippet='''
|
|
component:
|
|
type: OS::Heat::SoftwareComponent
|
|
properties:
|
|
inputs:
|
|
- name: foo
|
|
''',
|
|
err=exc.StackValidationFailed,
|
|
err_msg='Property configs not assigned')
|
|
),
|
|
# do not test until bug #1350840
|
|
# (
|
|
# 'empty_configs',
|
|
# dict(snippet='''
|
|
# component:
|
|
# type: OS::Heat::SoftwareComponent
|
|
# properties:
|
|
# configs:
|
|
# ''',
|
|
# err=exception.StackValidationFailed,
|
|
# err_msg='configs length (0) is out of range '
|
|
# '(min: 1, max: None)')
|
|
# ),
|
|
(
|
|
'invalid_configs',
|
|
dict(snippet='''
|
|
component:
|
|
type: OS::Heat::SoftwareComponent
|
|
properties:
|
|
configs:
|
|
actions: [CREATE]
|
|
config: #!/bin/bash
|
|
tool: script
|
|
''',
|
|
err=exc.StackValidationFailed,
|
|
err_msg='is not a list')
|
|
),
|
|
(
|
|
'config_empty_actions',
|
|
dict(snippet='''
|
|
component:
|
|
type: OS::Heat::SoftwareComponent
|
|
properties:
|
|
configs:
|
|
- actions: []
|
|
config: #!/bin/bash
|
|
tool: script
|
|
''',
|
|
err=exc.StackValidationFailed,
|
|
err_msg='component.properties.configs[0].actions: '
|
|
'length (0) is out of range (min: 1, max: None)')
|
|
),
|
|
(
|
|
'multiple_configs_per_action_single',
|
|
dict(snippet='''
|
|
component:
|
|
type: OS::Heat::SoftwareComponent
|
|
properties:
|
|
configs:
|
|
- actions: [CREATE]
|
|
config: #!/bin/bash
|
|
tool: script
|
|
- actions: [CREATE]
|
|
config: #!/bin/bash
|
|
tool: script
|
|
''',
|
|
err=exc.StackValidationFailed,
|
|
err_msg='Defining more than one configuration for the same '
|
|
'action in SoftwareComponent "component" is not '
|
|
'allowed.')
|
|
),
|
|
(
|
|
'multiple_configs_per_action_overlapping_list',
|
|
dict(snippet='''
|
|
component:
|
|
type: OS::Heat::SoftwareComponent
|
|
properties:
|
|
configs:
|
|
- actions: [CREATE, UPDATE, RESUME]
|
|
config: #!/bin/bash
|
|
tool: script
|
|
- actions: [UPDATE]
|
|
config: #!/bin/bash
|
|
tool: script
|
|
''',
|
|
err=exc.StackValidationFailed,
|
|
err_msg='Defining more than one configuration for the same '
|
|
'action in SoftwareComponent "component" is not '
|
|
'allowed.')
|
|
),
|
|
]
|
|
|
|
def setUp(self):
|
|
super(SoftwareComponentValidationTest, self).setUp()
|
|
self.ctx = utils.dummy_context()
|
|
|
|
tpl = '''
|
|
heat_template_version: 2013-05-23
|
|
resources:
|
|
%s
|
|
''' % self.snippet
|
|
|
|
self.template = template_format.parse(tpl)
|
|
self.stack = stack.Stack(
|
|
self.ctx, 'software_component_test_stack',
|
|
template.Template(self.template))
|
|
self.component = self.stack['component']
|
|
self.component._rpc_client = mock.MagicMock()
|
|
|
|
def test_properties_schema(self):
|
|
if self.err:
|
|
err = self.assertRaises(self.err, self.stack.validate)
|
|
if self.err_msg:
|
|
self.assertIn(self.err_msg, six.text_type(err))
|
|
else:
|
|
self.assertIsNone(self.stack.validate())
|