cc7e2d6cb3
Change-Id: I5924db9fa2a49825baef2faf88c533aca76ea081
1069 lines
40 KiB
Python
1069 lines
40 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 copy
|
|
|
|
import mock
|
|
import six
|
|
|
|
from heat.common import exception
|
|
from heat.common import template_format
|
|
from heat.engine import function
|
|
from heat.engine.resources.openstack.heat import resource_group
|
|
from heat.engine import rsrc_defn
|
|
from heat.engine import scheduler
|
|
from heat.engine import stack as stackm
|
|
from heat.tests import common
|
|
from heat.tests import utils
|
|
|
|
template = {
|
|
"heat_template_version": "2013-05-23",
|
|
"resources": {
|
|
"group1": {
|
|
"type": "OS::Heat::ResourceGroup",
|
|
"properties": {
|
|
"count": 2,
|
|
"resource_def": {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"Foo": "Bar"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template2 = {
|
|
"heat_template_version": "2013-05-23",
|
|
"resources": {
|
|
"dummy": {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"Foo": "baz"
|
|
}
|
|
},
|
|
"group1": {
|
|
"type": "OS::Heat::ResourceGroup",
|
|
"properties": {
|
|
"count": 2,
|
|
"resource_def": {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"Foo": {"get_attr": ["dummy", "Foo"]}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template_repl = {
|
|
"heat_template_version": "2013-05-23",
|
|
"resources": {
|
|
"group1": {
|
|
"type": "OS::Heat::ResourceGroup",
|
|
"properties": {
|
|
"count": 2,
|
|
"resource_def": {
|
|
"type": "ResourceWithListProp%index%",
|
|
"properties": {
|
|
"Foo": "Bar_%index%",
|
|
"listprop": [
|
|
"%index%_0",
|
|
"%index%_1",
|
|
"%index%_2"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template_attr = {
|
|
"heat_template_version": "2014-10-16",
|
|
"resources": {
|
|
"group1": {
|
|
"type": "OS::Heat::ResourceGroup",
|
|
"properties": {
|
|
"count": 2,
|
|
"resource_def": {
|
|
"type": "ResourceWithComplexAttributesType",
|
|
"properties": {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"outputs": {
|
|
"nested_strings": {
|
|
"value": {"get_attr": ["group1", "nested_dict", "string"]}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class ResourceGroupTest(common.HeatTestCase):
|
|
|
|
def setUp(self):
|
|
common.HeatTestCase.setUp(self)
|
|
self.m.StubOutWithMock(stackm.Stack, 'validate')
|
|
|
|
def test_assemble_nested(self):
|
|
"""
|
|
Tests that the nested stack that implements the group is created
|
|
appropriately based on properties.
|
|
"""
|
|
stack = utils.parse_stack(template)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resg = resource_group.ResourceGroup('test', snip, stack)
|
|
templ = {
|
|
"heat_template_version": "2015-04-30",
|
|
"resources": {
|
|
"0": {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"Foo": "Bar"
|
|
}
|
|
},
|
|
"1": {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"Foo": "Bar"
|
|
}
|
|
},
|
|
"2": {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"Foo": "Bar"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.assertEqual(templ, resg._assemble_nested(['0', '1', '2']))
|
|
|
|
def test_assemble_nested_include(self):
|
|
templ = copy.deepcopy(template)
|
|
res_def = templ["resources"]["group1"]["properties"]['resource_def']
|
|
res_def['properties']['Foo'] = None
|
|
stack = utils.parse_stack(templ)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resg = resource_group.ResourceGroup('test', snip, stack)
|
|
expect = {
|
|
"heat_template_version": "2015-04-30",
|
|
"resources": {
|
|
"0": {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {}
|
|
}
|
|
}
|
|
}
|
|
self.assertEqual(expect, resg._assemble_nested(['0']))
|
|
expect['resources']["0"]['properties'] = {"Foo": None}
|
|
self.assertEqual(
|
|
expect, resg._assemble_nested(['0'], include_all=True))
|
|
|
|
def test_assemble_nested_zero(self):
|
|
templ = copy.deepcopy(template)
|
|
templ['resources']['group1']['properties']['count'] = 0
|
|
stack = utils.parse_stack(templ)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resg = resource_group.ResourceGroup('test', snip, stack)
|
|
expect = {
|
|
"heat_template_version": "2015-04-30",
|
|
"resources": {}
|
|
}
|
|
self.assertEqual(expect, resg._assemble_nested([]))
|
|
|
|
def test_assemble_nested_with_metadata(self):
|
|
templ = copy.deepcopy(template)
|
|
res_def = templ["resources"]["group1"]["properties"]['resource_def']
|
|
res_def['properties']['Foo'] = None
|
|
res_def['metadata'] = {
|
|
'priority': 'low',
|
|
'role': 'webserver'
|
|
}
|
|
stack = utils.parse_stack(templ)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resg = resource_group.ResourceGroup('test', snip, stack)
|
|
expect = {
|
|
"heat_template_version": "2015-04-30",
|
|
"resources": {
|
|
"0": {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {},
|
|
"metadata": {
|
|
'priority': 'low',
|
|
'role': 'webserver'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
self.assertEqual(expect, resg._assemble_nested(['0']))
|
|
|
|
def test_assemble_nested_rolling_update(self):
|
|
expect = {
|
|
"heat_template_version": "2015-04-30",
|
|
"resources": {
|
|
"0": {
|
|
"depends_on": [],
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"foo": "baz"
|
|
}
|
|
},
|
|
"1": {
|
|
"depends_on": [],
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"foo": "bar"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
resource_def = {
|
|
"depends_on": [],
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"foo": "baz"
|
|
}
|
|
}
|
|
stack = utils.parse_stack(template)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resg = resource_group.ResourceGroup('test', snip, stack)
|
|
resg._nested = get_fake_nested_stack(['0', '1'])
|
|
resg._build_resource_definition = mock.Mock(return_value=resource_def)
|
|
self.assertEqual(expect, resg._assemble_for_rolling_update(['0'], []))
|
|
|
|
def test_assemble_nested_rolling_update_none(self):
|
|
expect = {
|
|
"heat_template_version": "2015-04-30",
|
|
"resources": {
|
|
"0": {
|
|
"depends_on": [],
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"foo": "bar"
|
|
}
|
|
},
|
|
"1": {
|
|
"depends_on": [],
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"foo": "bar"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
resource_def = {
|
|
"depends_on": [],
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"foo": "baz"
|
|
}
|
|
}
|
|
stack = utils.parse_stack(template)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resg = resource_group.ResourceGroup('test', snip, stack)
|
|
resg._nested = get_fake_nested_stack(['0', '1'])
|
|
resg._build_resource_definition = mock.Mock(return_value=resource_def)
|
|
self.assertEqual(expect, resg._assemble_for_rolling_update([], []))
|
|
|
|
def test_index_var(self):
|
|
stack = utils.parse_stack(template_repl)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resg = resource_group.ResourceGroup('test', snip, stack)
|
|
expect = {
|
|
"heat_template_version": "2015-04-30",
|
|
"resources": {
|
|
"0": {
|
|
"type": "ResourceWithListProp%index%",
|
|
"properties": {
|
|
"Foo": "Bar_0",
|
|
"listprop": [
|
|
"0_0", "0_1", "0_2"
|
|
]
|
|
}
|
|
},
|
|
"1": {
|
|
"type": "ResourceWithListProp%index%",
|
|
"properties": {
|
|
"Foo": "Bar_1",
|
|
"listprop": [
|
|
"1_0", "1_1", "1_2"
|
|
]
|
|
}
|
|
},
|
|
"2": {
|
|
"type": "ResourceWithListProp%index%",
|
|
"properties": {
|
|
"Foo": "Bar_2",
|
|
"listprop": [
|
|
"2_0", "2_1", "2_2"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
nested = resg._assemble_nested(['0', '1', '2'])
|
|
for res in nested['resources']:
|
|
nested['resources'][res]['properties']['listprop'] = \
|
|
list(nested['resources'][res]['properties']['listprop'])
|
|
self.assertEqual(expect, nested)
|
|
|
|
def test_custom_index_var(self):
|
|
templ = copy.deepcopy(template_repl)
|
|
templ['resources']['group1']['properties']['index_var'] = "__foo__"
|
|
stack = utils.parse_stack(templ)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resg = resource_group.ResourceGroup('test', snip, stack)
|
|
expect = {
|
|
"heat_template_version": "2015-04-30",
|
|
"resources": {
|
|
"0": {
|
|
"type": "ResourceWithListProp%index%",
|
|
"properties": {
|
|
"Foo": "Bar_%index%",
|
|
"listprop": [
|
|
"%index%_0", "%index%_1", "%index%_2"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
nested = resg._assemble_nested(['0'])
|
|
nested['resources']['0']['properties']['listprop'] = \
|
|
list(nested['resources']['0']['properties']['listprop'])
|
|
self.assertEqual(expect, nested)
|
|
|
|
res_def = snip['Properties']['resource_def']
|
|
res_def['properties']['Foo'] = "Bar___foo__"
|
|
res_def['properties']['listprop'] = ["__foo___0", "__foo___1",
|
|
"__foo___2"]
|
|
res_def['type'] = "ResourceWithListProp__foo__"
|
|
resg = resource_group.ResourceGroup('test', snip, stack)
|
|
expect = {
|
|
"heat_template_version": "2015-04-30",
|
|
"resources": {
|
|
"0": {
|
|
"type": "ResourceWithListProp__foo__",
|
|
"properties": {
|
|
"Foo": "Bar_0",
|
|
"listprop": [
|
|
"0_0", "0_1", "0_2"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
nested = resg._assemble_nested(['0'])
|
|
nested['resources']['0']['properties']['listprop'] = \
|
|
list(nested['resources']['0']['properties']['listprop'])
|
|
self.assertEqual(expect, nested)
|
|
|
|
def test_assemble_no_properties(self):
|
|
templ = copy.deepcopy(template)
|
|
res_def = templ["resources"]["group1"]["properties"]['resource_def']
|
|
del res_def['properties']
|
|
stack = utils.parse_stack(templ)
|
|
resg = stack.resources['group1']
|
|
self.assertIsNone(resg.validate())
|
|
|
|
def test_invalid_res_type(self):
|
|
"""Test that error raised for unknown resource type."""
|
|
tmp = copy.deepcopy(template)
|
|
grp_props = tmp['resources']['group1']['properties']
|
|
grp_props['resource_def']['type'] = "idontexist"
|
|
stack = utils.parse_stack(tmp)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resg = resource_group.ResourceGroup('test', snip, stack)
|
|
exc = self.assertRaises(exception.ResourceTypeNotFound,
|
|
resg.validate)
|
|
exp_msg = 'The Resource Type (idontexist) could not be found.'
|
|
self.assertIn(exp_msg, six.text_type(exc))
|
|
|
|
def test_reference_attr(self):
|
|
stack = utils.parse_stack(template2)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resgrp = resource_group.ResourceGroup('test', snip, stack)
|
|
self.assertIsNone(resgrp.validate())
|
|
|
|
def test_invalid_removal_policies_nolist(self):
|
|
"""Test that error raised for malformed removal_policies."""
|
|
tmp = copy.deepcopy(template)
|
|
grp_props = tmp['resources']['group1']['properties']
|
|
grp_props['removal_policies'] = 'notallowed'
|
|
stack = utils.parse_stack(tmp)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resg = resource_group.ResourceGroup('test', snip, stack)
|
|
exc = self.assertRaises(exception.StackValidationFailed,
|
|
resg.validate)
|
|
errstr = "removal_policies: \"'notallowed'\" is not a list"
|
|
self.assertIn(errstr, six.text_type(exc))
|
|
|
|
def test_invalid_removal_policies_nomap(self):
|
|
"""Test that error raised for malformed removal_policies."""
|
|
tmp = copy.deepcopy(template)
|
|
grp_props = tmp['resources']['group1']['properties']
|
|
grp_props['removal_policies'] = ['notallowed']
|
|
stack = utils.parse_stack(tmp)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resg = resource_group.ResourceGroup('test', snip, stack)
|
|
exc = self.assertRaises(exception.StackValidationFailed,
|
|
resg.validate)
|
|
errstr = '"notallowed" is not a map'
|
|
self.assertIn(errstr, six.text_type(exc))
|
|
|
|
def test_child_template(self):
|
|
stack = utils.parse_stack(template2)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
|
|
def check_res_names(names):
|
|
self.assertEqual(list(names), ['0', '1'])
|
|
return 'tmpl'
|
|
|
|
resgrp = resource_group.ResourceGroup('test', snip, stack)
|
|
resgrp._assemble_nested = mock.Mock()
|
|
resgrp._assemble_nested.side_effect = check_res_names
|
|
resgrp.properties.data[resgrp.COUNT] = 2
|
|
|
|
self.assertEqual('tmpl', resgrp.child_template())
|
|
self.assertEqual(1, resgrp._assemble_nested.call_count)
|
|
|
|
def test_child_params(self):
|
|
stack = utils.parse_stack(template2)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resgrp = resource_group.ResourceGroup('test', snip, stack)
|
|
self.assertEqual({}, resgrp.child_params())
|
|
|
|
def test_handle_create(self):
|
|
stack = utils.parse_stack(template2)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resgrp = resource_group.ResourceGroup('test', snip, stack)
|
|
resgrp.create_with_template = mock.Mock(return_value=None)
|
|
resgrp.handle_create()
|
|
self.assertEqual(1, resgrp.create_with_template.call_count)
|
|
|
|
def test_update_in_failed(self):
|
|
stack = utils.parse_stack(template2)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resgrp = resource_group.ResourceGroup('test', snip, stack)
|
|
resgrp.state_set('CREATE', 'FAILED')
|
|
resgrp._assemble_nested = mock.Mock(return_value='tmpl')
|
|
resgrp.properties.data[resgrp.COUNT] = 2
|
|
resgrp._assemble_nested_for_size = mock.Mock(return_value=None)
|
|
self.patchobject(scheduler.TaskRunner, 'start')
|
|
resgrp.handle_update(snip, None, None)
|
|
resgrp._assemble_nested_for_size.assert_called_once_with(2)
|
|
|
|
def test_handle_delete(self):
|
|
stack = utils.parse_stack(template2)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resgrp = resource_group.ResourceGroup('test', snip, stack)
|
|
resgrp.delete_nested = mock.Mock(return_value=None)
|
|
resgrp.handle_delete()
|
|
resgrp.delete_nested.assert_called_once_with()
|
|
|
|
def test_handle_update_size(self):
|
|
stack = utils.parse_stack(template2)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resgrp = resource_group.ResourceGroup('test', snip, stack)
|
|
resgrp._assemble_nested_for_size = mock.Mock(return_value=None)
|
|
resgrp.properties.data[resgrp.COUNT] = 5
|
|
self.patchobject(scheduler.TaskRunner, 'start')
|
|
resgrp.handle_update(snip, None, None)
|
|
resgrp._assemble_nested_for_size.assert_called_once_with(5)
|
|
|
|
|
|
class ResourceGroupBlackList(common.HeatTestCase):
|
|
"""This class tests ResourceGroup._name_blacklist()."""
|
|
|
|
# 1) no resource_list, empty blacklist
|
|
# 2) no resource_list, existing blacklist
|
|
# 3) resource_list not in nested()
|
|
# 4) resource_list (refid) not in nested()
|
|
# 5) resource_list in nested() -> saved
|
|
# 6) resource_list (refid) in nested() -> saved
|
|
scenarios = [
|
|
('1', dict(data_in=None, rm_list=[],
|
|
nested_rsrcs=[], expected=[],
|
|
saved=False)),
|
|
('2', dict(data_in='0,1,2', rm_list=[],
|
|
nested_rsrcs=[], expected=['0', '1', '2'],
|
|
saved=False)),
|
|
('3', dict(data_in='1,3', rm_list=['6'],
|
|
nested_rsrcs=['0', '1', '3'],
|
|
expected=['1', '3'],
|
|
saved=False)),
|
|
('4', dict(data_in='0,1', rm_list=['id-7'],
|
|
nested_rsrcs=['0', '1', '3'],
|
|
expected=['0', '1'],
|
|
saved=False)),
|
|
('5', dict(data_in='0,1', rm_list=['3'],
|
|
nested_rsrcs=['0', '1', '3'],
|
|
expected=['0', '1', '3'],
|
|
saved=True)),
|
|
('6', dict(data_in='0,1', rm_list=['id-3'],
|
|
nested_rsrcs=['0', '1', '3'],
|
|
expected=['0', '1', '3'],
|
|
saved=True)),
|
|
]
|
|
|
|
def test_blacklist(self):
|
|
stack = utils.parse_stack(template)
|
|
resg = stack['group1']
|
|
|
|
# mock properties
|
|
resg.properties = mock.MagicMock()
|
|
resg.properties.__getitem__.return_value = [
|
|
{'resource_list': self.rm_list}]
|
|
|
|
# mock data get/set
|
|
resg.data = mock.Mock()
|
|
resg.data.return_value.get.return_value = self.data_in
|
|
resg.data_set = mock.Mock()
|
|
|
|
# mock nested access
|
|
def stack_contains(name):
|
|
return name in self.nested_rsrcs
|
|
|
|
def by_refid(name):
|
|
rid = name.replace('id-', '')
|
|
if rid not in self.nested_rsrcs:
|
|
return None
|
|
res = mock.Mock()
|
|
res.name = rid
|
|
return res
|
|
|
|
nested = mock.MagicMock()
|
|
nested.__contains__.side_effect = stack_contains
|
|
nested.__iter__.side_effect = iter(self.nested_rsrcs)
|
|
nested.resource_by_refid.side_effect = by_refid
|
|
resg.nested = mock.Mock(return_value=nested)
|
|
|
|
blacklist = resg._name_blacklist()
|
|
self.assertEqual(set(self.expected), blacklist)
|
|
if self.saved:
|
|
resg.data_set.assert_called_once_with('name_blacklist',
|
|
','.join(blacklist))
|
|
|
|
|
|
class ResourceGroupEmptyParams(common.HeatTestCase):
|
|
"""This class tests ResourceGroup._build_resource_definition()."""
|
|
|
|
scenarios = [
|
|
('non_empty', dict(value='Bar', expected={'Foo': 'Bar'},
|
|
expected_include={'Foo': 'Bar'})),
|
|
('empty_None', dict(value=None, expected={},
|
|
expected_include={'Foo': None})),
|
|
('empty_boolean', dict(value=False, expected={'Foo': False},
|
|
expected_include={'Foo': False})),
|
|
('empty_string', dict(value='', expected={'Foo': ''},
|
|
expected_include={'Foo': ''})),
|
|
('empty_number', dict(value=0, expected={'Foo': 0},
|
|
expected_include={'Foo': 0})),
|
|
('empty_json', dict(value={}, expected={'Foo': {}},
|
|
expected_include={'Foo': {}})),
|
|
('empty_list', dict(value=[], expected={'Foo': []},
|
|
expected_include={'Foo': []}))
|
|
]
|
|
|
|
def test_definition(self):
|
|
templ = copy.deepcopy(template)
|
|
res_def = templ["resources"]["group1"]["properties"]['resource_def']
|
|
res_def['properties']['Foo'] = self.value
|
|
stack = utils.parse_stack(templ)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resg = resource_group.ResourceGroup('test', snip, stack)
|
|
exp1 = {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": self.expected,
|
|
}
|
|
exp2 = {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": self.expected_include,
|
|
}
|
|
self.assertEqual(exp1, resg._build_resource_definition())
|
|
self.assertEqual(
|
|
exp2, resg._build_resource_definition(include_all=True))
|
|
|
|
|
|
class ResourceGroupNameListTest(common.HeatTestCase):
|
|
"""This class tests ResourceGroup._resource_names()."""
|
|
|
|
# 1) no blacklist, 0 count
|
|
# 2) no blacklist, x count
|
|
# 3) blacklist (not effecting)
|
|
# 4) blacklist with pruning
|
|
scenarios = [
|
|
('1', dict(blacklist=[], count=0,
|
|
expected=[])),
|
|
('2', dict(blacklist=[], count=4,
|
|
expected=['0', '1', '2', '3'])),
|
|
('3', dict(blacklist=['5', '6'], count=3,
|
|
expected=['0', '1', '2'])),
|
|
('4', dict(blacklist=['2', '4'], count=4,
|
|
expected=['0', '1', '3', '5'])),
|
|
]
|
|
|
|
def test_names(self):
|
|
stack = utils.parse_stack(template)
|
|
resg = stack['group1']
|
|
|
|
resg.properties = mock.MagicMock()
|
|
resg.properties.get.return_value = self.count
|
|
resg._name_blacklist = mock.MagicMock(return_value=self.blacklist)
|
|
self.assertEqual(self.expected, list(resg._resource_names()))
|
|
|
|
|
|
class ResourceGroupAttrTest(common.HeatTestCase):
|
|
|
|
def test_aggregate_attribs(self):
|
|
"""
|
|
Test attribute aggregation and that we mimic the nested resource's
|
|
attributes.
|
|
"""
|
|
resg = self._create_dummy_stack()
|
|
expected = ['0', '1']
|
|
self.assertEqual(expected, resg.FnGetAtt('foo'))
|
|
self.assertEqual(expected, resg.FnGetAtt('Foo'))
|
|
|
|
def test_index_dotted_attribs(self):
|
|
"""
|
|
Test attribute aggregation and that we mimic the nested resource's
|
|
attributes.
|
|
"""
|
|
resg = self._create_dummy_stack()
|
|
self.assertEqual('0', resg.FnGetAtt('resource.0.Foo'))
|
|
self.assertEqual('1', resg.FnGetAtt('resource.1.Foo'))
|
|
|
|
def test_index_path_attribs(self):
|
|
"""
|
|
Test attribute aggregation and that we mimic the nested resource's
|
|
attributes.
|
|
"""
|
|
resg = self._create_dummy_stack()
|
|
self.assertEqual('0', resg.FnGetAtt('resource.0', 'Foo'))
|
|
self.assertEqual('1', resg.FnGetAtt('resource.1', 'Foo'))
|
|
|
|
def test_index_deep_path_attribs(self):
|
|
"""
|
|
Test attribute aggregation and that we mimic the nested resource's
|
|
attributes.
|
|
"""
|
|
resg = self._create_dummy_stack(template_attr,
|
|
expect_attrs={'0': 2, '1': 2})
|
|
self.assertEqual(2, resg.FnGetAtt('resource.0',
|
|
'nested_dict', 'dict', 'b'))
|
|
self.assertEqual(2, resg.FnGetAtt('resource.1',
|
|
'nested_dict', 'dict', 'b'))
|
|
|
|
def test_aggregate_deep_path_attribs(self):
|
|
"""
|
|
Test attribute aggregation and that we mimic the nested resource's
|
|
attributes.
|
|
"""
|
|
resg = self._create_dummy_stack(template_attr,
|
|
expect_attrs={'0': 3, '1': 3})
|
|
expected = [3, 3]
|
|
self.assertEqual(expected, resg.FnGetAtt('nested_dict', 'list', 2))
|
|
|
|
def test_aggregate_refs(self):
|
|
"""
|
|
Test resource id aggregation
|
|
"""
|
|
resg = self._create_dummy_stack()
|
|
expected = ['ID-0', 'ID-1']
|
|
self.assertEqual(expected, resg.FnGetAtt("refs"))
|
|
|
|
def test_aggregate_refs_with_index(self):
|
|
"""
|
|
Test resource id aggregation with index
|
|
"""
|
|
resg = self._create_dummy_stack()
|
|
expected = ['ID-0', 'ID-1']
|
|
self.assertEqual(expected[0], resg.FnGetAtt("refs", 0))
|
|
self.assertEqual(expected[1], resg.FnGetAtt("refs", 1))
|
|
self.assertIsNone(resg.FnGetAtt("refs", 2))
|
|
|
|
def test_aggregate_outputs(self):
|
|
"""
|
|
Test outputs aggregation
|
|
"""
|
|
expected = {'0': ['foo', 'bar'], '1': ['foo', 'bar']}
|
|
resg = self._create_dummy_stack(template_attr, expect_attrs=expected)
|
|
self.assertEqual(expected, resg.FnGetAtt('attributes', 'list'))
|
|
|
|
def test_aggregate_outputs_no_path(self):
|
|
"""
|
|
Test outputs aggregation with missing path
|
|
"""
|
|
resg = self._create_dummy_stack(template_attr)
|
|
self.assertRaises(exception.InvalidTemplateAttribute,
|
|
resg.FnGetAtt, 'attributes')
|
|
|
|
def test_index_refs(self):
|
|
"""Tests getting ids of individual resources."""
|
|
resg = self._create_dummy_stack()
|
|
self.assertEqual("ID-0", resg.FnGetAtt('resource.0'))
|
|
self.assertEqual("ID-1", resg.FnGetAtt('resource.1'))
|
|
self.assertRaises(exception.InvalidTemplateAttribute, resg.FnGetAtt,
|
|
'resource.2')
|
|
|
|
def _create_dummy_stack(self, template_data=template, expect_count=2,
|
|
expect_attrs=None):
|
|
stack = utils.parse_stack(template_data)
|
|
resg = stack['group1']
|
|
fake_res = {}
|
|
if expect_attrs is None:
|
|
expect_attrs = {}
|
|
for resc in range(expect_count):
|
|
res = str(resc)
|
|
fake_res[res] = mock.Mock()
|
|
fake_res[res].FnGetRefId.return_value = 'ID-%s' % res
|
|
if res in expect_attrs:
|
|
fake_res[res].FnGetAtt.return_value = expect_attrs[res]
|
|
else:
|
|
fake_res[res].FnGetAtt.return_value = res
|
|
resg.nested = mock.Mock(return_value=fake_res)
|
|
|
|
names = [str(name) for name in range(expect_count)]
|
|
resg._resource_names = mock.Mock(return_value=names)
|
|
return resg
|
|
|
|
|
|
class ReplaceTest(common.HeatTestCase):
|
|
# 1. no min_in_service
|
|
# 2. min_in_service > count and existing with no blacklist
|
|
# 3. min_in_service > count and existing with blacklist
|
|
# 4. existing > count and min_in_service with blacklist
|
|
# 5. existing > count and min_in_service with no blacklist
|
|
# 6. all existing blacklisted
|
|
# 7. count > existing and min_in_service with no blacklist
|
|
# 8. count > existing and min_in_service with blacklist
|
|
# 9. count < existing - blacklisted
|
|
# 10. pause_sec > 0
|
|
|
|
scenarios = [
|
|
('1', dict(min_in_service=0, count=2,
|
|
existing=['0', '1'], black_listed=['0'],
|
|
batch_size=1, pause_sec=0, tasks=1)),
|
|
('2', dict(min_in_service=3, count=2,
|
|
existing=['0', '1'], black_listed=[],
|
|
batch_size=2, pause_sec=0, tasks=2)),
|
|
('3', dict(min_in_service=3, count=2,
|
|
existing=['0', '1'], black_listed=['0'],
|
|
batch_size=2, pause_sec=0, tasks=2)),
|
|
('4', dict(min_in_service=3, count=2,
|
|
existing=['0', '1', '2', '3'], black_listed=['2', '3'],
|
|
batch_size=1, pause_sec=0, tasks=3)),
|
|
('5', dict(min_in_service=2, count=2,
|
|
existing=['0', '1', '2', '3'], black_listed=[],
|
|
batch_size=2, pause_sec=0, tasks=1)),
|
|
('6', dict(min_in_service=2, count=3,
|
|
existing=['0', '1'], black_listed=['0', '1'],
|
|
batch_size=2, pause_sec=0, tasks=0)),
|
|
('7', dict(min_in_service=0, count=5,
|
|
existing=['0', '1'], black_listed=[],
|
|
batch_size=1, pause_sec=0, tasks=2)),
|
|
('8', dict(min_in_service=0, count=5,
|
|
existing=['0', '1'], black_listed=['0'],
|
|
batch_size=1, pause_sec=0, tasks=1)),
|
|
('9', dict(min_in_service=0, count=3,
|
|
existing=['0', '1', '2', '3', '4', '5'],
|
|
black_listed=['0'],
|
|
batch_size=2, pause_sec=0, tasks=2)),
|
|
('10', dict(min_in_service=0, count=3,
|
|
existing=['0', '1', '2', '3', '4', '5'],
|
|
black_listed=['0'],
|
|
batch_size=2, pause_sec=10, tasks=3))]
|
|
|
|
def setUp(self):
|
|
super(ReplaceTest, self).setUp()
|
|
templ = copy.deepcopy(template)
|
|
self.stack = utils.parse_stack(templ)
|
|
snip = self.stack.t.resource_definitions(self.stack)['group1']
|
|
self.group = resource_group.ResourceGroup('test', snip, self.stack)
|
|
self.group.update_with_template = mock.Mock()
|
|
self.group.check_update_complete = mock.Mock()
|
|
|
|
def test_rolling_updates(self):
|
|
self.group._nested = get_fake_nested_stack(self.existing)
|
|
self.group.get_size = mock.Mock(return_value=self.count)
|
|
self.group._name_blacklist = mock.Mock(
|
|
return_value=set(self.black_listed))
|
|
tasks = self.group._replace(self.min_in_service, self.batch_size,
|
|
self.pause_sec)
|
|
self.assertEqual(self.tasks,
|
|
len(tasks))
|
|
|
|
|
|
def tmpl_with_bad_updt_policy():
|
|
t = copy.deepcopy(template)
|
|
rg = t['resources']['group1']
|
|
rg["update_policy"] = {"foo": {}}
|
|
return t
|
|
|
|
|
|
def tmpl_with_default_updt_policy():
|
|
t = copy.deepcopy(template)
|
|
rg = t['resources']['group1']
|
|
rg["update_policy"] = {"rolling_update": {}}
|
|
return t
|
|
|
|
|
|
def tmpl_with_updt_policy():
|
|
t = copy.deepcopy(template)
|
|
rg = t['resources']['group1']
|
|
rg["update_policy"] = {"rolling_update": {
|
|
"min_in_service": "1",
|
|
"max_batch_size": "2",
|
|
"pause_time": "1"
|
|
}}
|
|
return t
|
|
|
|
|
|
def get_fake_nested_stack(names):
|
|
nested_t = '''
|
|
heat_template_version: 2015-04-30
|
|
description: Resource Group
|
|
resources:
|
|
'''
|
|
resource_snip = '''
|
|
'%s':
|
|
type: OverwrittenFnGetRefIdType
|
|
properties:
|
|
foo: bar
|
|
'''
|
|
resources = [nested_t]
|
|
for res_name in names:
|
|
resources.extend([resource_snip % res_name])
|
|
|
|
nested_t = ''.join(resources)
|
|
return utils.parse_stack(template_format.parse(nested_t))
|
|
|
|
|
|
class RollingUpdatePolicyTest(common.HeatTestCase):
|
|
def setUp(self):
|
|
super(RollingUpdatePolicyTest, self).setUp()
|
|
|
|
def test_parse_without_update_policy(self):
|
|
stack = utils.parse_stack(template)
|
|
stack.validate()
|
|
grp = stack['group1']
|
|
self.assertFalse(grp.update_policy['rolling_update'])
|
|
|
|
def test_parse_with_update_policy(self):
|
|
tmpl = tmpl_with_updt_policy()
|
|
stack = utils.parse_stack(tmpl)
|
|
stack.validate()
|
|
tmpl_grp = tmpl['resources']['group1']
|
|
tmpl_policy = tmpl_grp['update_policy']['rolling_update']
|
|
tmpl_batch_sz = int(tmpl_policy['max_batch_size'])
|
|
grp = stack['group1']
|
|
self.assertTrue(grp.update_policy)
|
|
self.assertEqual(1, len(grp.update_policy))
|
|
self.assertIn('rolling_update', grp.update_policy)
|
|
policy = grp.update_policy['rolling_update']
|
|
self.assertTrue(policy and len(policy) > 0)
|
|
self.assertEqual(1, int(policy['min_in_service']))
|
|
self.assertEqual(tmpl_batch_sz, int(policy['max_batch_size']))
|
|
self.assertEqual(1, policy['pause_time'])
|
|
|
|
def test_parse_with_default_update_policy(self):
|
|
tmpl = tmpl_with_default_updt_policy()
|
|
stack = utils.parse_stack(tmpl)
|
|
stack.validate()
|
|
grp = stack['group1']
|
|
self.assertTrue(grp.update_policy)
|
|
self.assertEqual(1, len(grp.update_policy))
|
|
self.assertIn('rolling_update', grp.update_policy)
|
|
policy = grp.update_policy['rolling_update']
|
|
self.assertTrue(policy and len(policy) > 0)
|
|
self.assertEqual(0, int(policy['min_in_service']))
|
|
self.assertEqual(1, int(policy['max_batch_size']))
|
|
self.assertEqual(0, policy['pause_time'])
|
|
|
|
def test_parse_with_bad_update_policy(self):
|
|
tmpl = tmpl_with_bad_updt_policy()
|
|
stack = utils.parse_stack(tmpl)
|
|
error = self.assertRaises(
|
|
exception.StackValidationFailed, stack.validate)
|
|
self.assertIn("foo", six.text_type(error))
|
|
|
|
def test_parse_with_max_pausetime_in_update_policy(self):
|
|
tmpl = tmpl_with_default_updt_policy()
|
|
group = tmpl['resources']['group1']
|
|
policy = group['update_policy']['rolling_update']
|
|
policy['pause_time'] = '7200'
|
|
stack = utils.parse_stack(tmpl)
|
|
error = self.assertRaises(
|
|
exception.StackValidationFailed, stack.validate)
|
|
self.assertIn("Maximum pause_time allowed is 1hr(3600s), "
|
|
"provided 7200 seconds.", six.text_type(error))
|
|
|
|
|
|
class RollingUpdatePolicyDiffTest(common.HeatTestCase):
|
|
def setUp(self):
|
|
super(RollingUpdatePolicyDiffTest, self).setUp()
|
|
|
|
def validate_update_policy_diff(self, current, updated):
|
|
# load current stack
|
|
current_stack = utils.parse_stack(current)
|
|
current_grp = current_stack['group1']
|
|
current_grp_json = function.resolve(
|
|
current_grp.t)
|
|
|
|
updated_stack = utils.parse_stack(updated)
|
|
updated_grp = updated_stack['group1']
|
|
updated_grp_json = function.resolve(
|
|
updated_grp.t)
|
|
|
|
# identify the template difference
|
|
tmpl_diff = updated_grp.update_template_diff(
|
|
updated_grp_json, current_grp_json)
|
|
updated_policy = (updated_grp_json['UpdatePolicy']
|
|
if 'UpdatePolicy' in updated_grp_json else None)
|
|
expected = {u'UpdatePolicy': updated_policy}
|
|
self.assertEqual(expected, tmpl_diff)
|
|
|
|
# test application of the new update policy in handle_update
|
|
update_snippet = rsrc_defn.ResourceDefinition(
|
|
current_grp.name,
|
|
current_grp.type(),
|
|
properties=updated_grp_json['Properties'],
|
|
update_policy=updated_policy)
|
|
|
|
current_grp._try_rolling_update = mock.Mock()
|
|
current_grp._assemble_nested_for_size = mock.Mock()
|
|
self.patchobject(scheduler.TaskRunner, 'start')
|
|
current_grp.handle_update(update_snippet, tmpl_diff, None)
|
|
if updated_policy is None:
|
|
self.assertEqual({}, current_grp.update_policy.data)
|
|
else:
|
|
self.assertEqual(updated_policy, current_grp.update_policy.data)
|
|
|
|
def test_update_policy_added(self):
|
|
self.validate_update_policy_diff(template,
|
|
tmpl_with_updt_policy())
|
|
|
|
def test_update_policy_updated(self):
|
|
updt_template = tmpl_with_updt_policy()
|
|
grp = updt_template['resources']['group1']
|
|
policy = grp['update_policy']['rolling_update']
|
|
policy['min_in_service'] = '2'
|
|
policy['max_batch_size'] = '4'
|
|
policy['pause_time'] = '90'
|
|
self.validate_update_policy_diff(tmpl_with_updt_policy(),
|
|
updt_template)
|
|
|
|
def test_update_policy_removed(self):
|
|
self.validate_update_policy_diff(tmpl_with_updt_policy(),
|
|
template)
|
|
|
|
|
|
class RollingUpdateTest(common.HeatTestCase):
|
|
def setUp(self):
|
|
super(RollingUpdateTest, self).setUp()
|
|
|
|
def check_with_update(self, with_policy=False, with_diff=False):
|
|
current = copy.deepcopy(template)
|
|
self.current_stack = utils.parse_stack(current)
|
|
self.current_grp = self.current_stack['group1']
|
|
current_grp_json = function.resolve(
|
|
self.current_grp.t)
|
|
prop_diff, tmpl_diff = None, None
|
|
updated = tmpl_with_updt_policy() if (
|
|
with_policy) else copy.deepcopy(template)
|
|
if with_diff:
|
|
res_def = updated['resources']['group1'][
|
|
'properties']['resource_def']
|
|
res_def['properties']['Foo'] = 'baz'
|
|
prop_diff = dict(
|
|
{'count': 2,
|
|
'resource_def': {'properties': {'Foo': 'baz'},
|
|
'type': 'OverwrittenFnGetRefIdType'}})
|
|
updated_stack = utils.parse_stack(updated)
|
|
updated_grp = updated_stack['group1']
|
|
updated_grp_json = function.resolve(updated_grp.t)
|
|
tmpl_diff = updated_grp.update_template_diff(
|
|
updated_grp_json, current_grp_json)
|
|
|
|
updated_policy = updated_grp_json[
|
|
'UpdatePolicy']if 'UpdatePolicy' in updated_grp_json else None
|
|
update_snippet = rsrc_defn.ResourceDefinition(
|
|
self.current_grp.name,
|
|
self.current_grp.type(),
|
|
properties=updated_grp_json['Properties'],
|
|
update_policy=updated_policy)
|
|
self.current_grp._replace = mock.Mock(return_value=[])
|
|
self.current_grp._assemble_nested_for_size = mock.Mock()
|
|
self.patchobject(scheduler.TaskRunner, 'start')
|
|
self.current_grp.handle_update(update_snippet, tmpl_diff, prop_diff)
|
|
|
|
def test_update_without_policy_prop_diff(self):
|
|
self.check_with_update(with_diff=True)
|
|
self.current_grp._assemble_nested_for_size.assert_called_once_with(2)
|
|
|
|
def test_update_with_policy_prop_diff(self):
|
|
self.check_with_update(with_policy=True, with_diff=True)
|
|
self.current_grp._replace.assert_called_once_with(1, 2, 1)
|
|
self.current_grp._assemble_nested_for_size.assert_called_once_with(2)
|
|
|
|
def test_update_time_not_sufficient(self):
|
|
efft_capacity, efft_bat_sz, pause_sec = 5, 2, 100
|
|
current = copy.deepcopy(template)
|
|
self.stack = utils.parse_stack(current)
|
|
self.current_grp = self.stack['group1']
|
|
self.stack.timeout_secs = mock.Mock(return_value=200)
|
|
err = self.assertRaises(ValueError, self.current_grp._update_timeout,
|
|
efft_capacity, efft_bat_sz, pause_sec)
|
|
self.assertIn('The current UpdatePolicy will result in stack update '
|
|
'timeout.', six.text_type(err))
|
|
|
|
def test_update_time_sufficient(self):
|
|
efft_capacity, efft_bat_sz, pause_sec = 5, 2, 100
|
|
current = copy.deepcopy(template)
|
|
self.stack = utils.parse_stack(current)
|
|
self.current_grp = self.stack['group1']
|
|
self.stack.timeout_secs = mock.Mock(return_value=400)
|
|
self.assertEqual(200, self.current_grp._update_timeout(
|
|
efft_capacity, efft_bat_sz, pause_sec))
|
|
|
|
|
|
class TestUtils(common.HeatTestCase):
|
|
# 1. No existing no blacklist
|
|
# 2. Existing with no blacklist
|
|
# 3. Existing with blacklist
|
|
scenarios = [
|
|
('1', dict(existing=[], black_listed=[], count=0)),
|
|
('2', dict(existing=['0', '1'], black_listed=[], count=0)),
|
|
('3', dict(existing=['0', '1'], black_listed=['0'], count=1)),
|
|
('4', dict(existing=['0', '1'], black_listed=['1', '2'], count=1))
|
|
|
|
]
|
|
|
|
def setUp(self):
|
|
super(TestUtils, self).setUp()
|
|
|
|
def test_count_black_listed(self):
|
|
stack = utils.parse_stack(template2)
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
resgrp = resource_group.ResourceGroup('test', snip, stack)
|
|
resgrp._nested = get_fake_nested_stack(self.existing)
|
|
resgrp._name_blacklist = mock.Mock(return_value=set(self.black_listed))
|
|
rcount = resgrp._count_black_listed()
|
|
self.assertEqual(self.count, rcount)
|