
The part of a ResourceDefinition that lists explicit dependencies was not round-trip stable. As a result, when we copied a new resource definition into the existing template during a stack update, we would end up rewriting the template unnecesarily (i.e. even though we check for changes) every time if depends_on was not specified in the resource originally. At the end of each update, we write the new template to the DB in its entirety, which removes these extra lines again, ensuring that we will experience the same problem on every update. This was causing a *lot* of unnecessary writes. This change ensures that the definition remains stable across a round-trip, so that no unnecessary changes appear in the template. Change-Id: If7292e49755db0153d7d0db9f7d3875fa9c1d408 Closes-Bug: #1494108
1365 lines
53 KiB
Python
1365 lines
53 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": {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"foo": "bar"
|
|
}
|
|
},
|
|
"1": {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"foo": "baz"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
resource_def = {
|
|
"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(2, 1))
|
|
|
|
def test_assemble_nested_rolling_update_none(self):
|
|
expect = {
|
|
"heat_template_version": "2015-04-30",
|
|
"resources": {
|
|
"0": {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"foo": "bar"
|
|
}
|
|
},
|
|
"1": {
|
|
"type": "OverwrittenFnGetRefIdType",
|
|
"properties": {
|
|
"foo": "bar"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
resource_def = {
|
|
"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(2, 0))
|
|
|
|
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)
|
|
self.patchobject(scheduler.TaskRunner, 'start')
|
|
checkers = resgrp.handle_create()
|
|
self.assertEqual(1, len(checkers))
|
|
|
|
def test_handle_create_with_batching(self):
|
|
stack = utils.parse_stack(tmpl_with_default_updt_policy())
|
|
snip = stack.t.resource_definitions(stack)['group1']
|
|
snip['UpdatePolicy']['batch_create'] = {'max_batch_size': 3}
|
|
snip['Properties']['count'] = 10
|
|
resgrp = resource_group.ResourceGroup('test', snip, stack)
|
|
resgrp.create_with_template = mock.Mock(return_value=None)
|
|
self.patchobject(scheduler.TaskRunner, 'start')
|
|
checkers = resgrp.handle_create()
|
|
self.assertEqual(4, len(checkers))
|
|
|
|
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
|
|
self.patchobject(scheduler.TaskRunner, 'start')
|
|
resgrp.handle_update(snip, None, None)
|
|
self.assertTrue(resgrp._assemble_nested.called)
|
|
|
|
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 = mock.Mock(return_value=None)
|
|
resgrp.properties.data[resgrp.COUNT] = 5
|
|
self.patchobject(scheduler.TaskRunner, 'start')
|
|
resgrp.handle_update(snip, None, None)
|
|
self.assertTrue(resgrp._assemble_nested.called)
|
|
|
|
|
|
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=2)),
|
|
('2', dict(min_in_service=3, count=2,
|
|
existing=['0', '1'], black_listed=[],
|
|
batch_size=2, pause_sec=0, tasks=3)),
|
|
('3', dict(min_in_service=3, count=2,
|
|
existing=['0', '1'], black_listed=['0'],
|
|
batch_size=2, pause_sec=0, tasks=3)),
|
|
('4', dict(min_in_service=3, count=2,
|
|
existing=['0', '1', '2', '3'], black_listed=['2', '3'],
|
|
batch_size=1, pause_sec=0, tasks=4)),
|
|
('5', dict(min_in_service=2, count=2,
|
|
existing=['0', '1', '2', '3'], black_listed=[],
|
|
batch_size=2, pause_sec=0, tasks=2)),
|
|
('6', dict(min_in_service=2, count=3,
|
|
existing=['0', '1'], black_listed=['0', '1'],
|
|
batch_size=2, pause_sec=0, tasks=2)),
|
|
('7', dict(min_in_service=0, count=5,
|
|
existing=['0', '1'], black_listed=[],
|
|
batch_size=1, pause_sec=0, tasks=5)),
|
|
('8', dict(min_in_service=0, count=5,
|
|
existing=['0', '1'], black_listed=['0'],
|
|
batch_size=1, pause_sec=0, tasks=5)),
|
|
('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(2, 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(2, 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))
|
|
|
|
|
|
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 = 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.assertTrue(self.current_grp._assemble_nested.called)
|
|
|
|
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.assertTrue(self.current_grp._assemble_nested.called)
|
|
|
|
def test_update_time_not_sufficient(self):
|
|
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,
|
|
3, 100)
|
|
self.assertIn('The current UpdatePolicy will result in stack update '
|
|
'timeout.', six.text_type(err))
|
|
|
|
def test_update_time_sufficient(self):
|
|
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(3, 100))
|
|
|
|
|
|
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)
|
|
|
|
|
|
class TestGetBatches(common.HeatTestCase):
|
|
|
|
scenarios = [
|
|
('4_4_1_0', dict(targ_cap=4, init_cap=4, bat_size=1, min_serv=0,
|
|
batches=[
|
|
(4, 1, ['4']),
|
|
(4, 1, ['3']),
|
|
(4, 1, ['2']),
|
|
(4, 1, ['1']),
|
|
])),
|
|
('4_4_1_4', dict(targ_cap=4, init_cap=4, bat_size=1, min_serv=4,
|
|
batches=[
|
|
(5, 1, ['5']),
|
|
(5, 1, ['4']),
|
|
(5, 1, ['3']),
|
|
(5, 1, ['2']),
|
|
(5, 1, ['1']),
|
|
(4, 0, []),
|
|
])),
|
|
('4_4_1_5', dict(targ_cap=4, init_cap=4, bat_size=1, min_serv=5,
|
|
batches=[
|
|
(5, 1, ['5']),
|
|
(5, 1, ['4']),
|
|
(5, 1, ['3']),
|
|
(5, 1, ['2']),
|
|
(5, 1, ['1']),
|
|
(4, 0, []),
|
|
])),
|
|
('4_4_2_0', dict(targ_cap=4, init_cap=4, bat_size=2, min_serv=0,
|
|
batches=[
|
|
(4, 2, ['4', '3']),
|
|
(4, 2, ['2', '1']),
|
|
])),
|
|
('4_4_2_4', dict(targ_cap=4, init_cap=4, bat_size=2, min_serv=4,
|
|
batches=[
|
|
(6, 2, ['6', '5']),
|
|
(6, 2, ['4', '3']),
|
|
(6, 2, ['2', '1']),
|
|
(4, 0, []),
|
|
])),
|
|
('5_5_2_0', dict(targ_cap=5, init_cap=5, bat_size=2, min_serv=0,
|
|
batches=[
|
|
(5, 2, ['5', '4']),
|
|
(5, 2, ['3', '2']),
|
|
(5, 1, ['1']),
|
|
])),
|
|
('5_5_2_4', dict(targ_cap=5, init_cap=5, bat_size=2, min_serv=4,
|
|
batches=[
|
|
(6, 2, ['6', '5']),
|
|
(6, 2, ['4', '3']),
|
|
(6, 2, ['2', '1']),
|
|
(5, 0, []),
|
|
])),
|
|
('3_3_2_0', dict(targ_cap=3, init_cap=3, bat_size=2, min_serv=0,
|
|
batches=[
|
|
(3, 2, ['3', '2']),
|
|
(3, 1, ['1']),
|
|
])),
|
|
('3_3_2_4', dict(targ_cap=3, init_cap=3, bat_size=2, min_serv=4,
|
|
batches=[
|
|
(5, 2, ['5', '4']),
|
|
(5, 2, ['3', '2']),
|
|
(4, 1, ['1']),
|
|
(3, 0, []),
|
|
])),
|
|
('4_4_4_0', dict(targ_cap=4, init_cap=4, bat_size=4, min_serv=0,
|
|
batches=[
|
|
(4, 4, ['4', '3', '2', '1']),
|
|
])),
|
|
('4_4_5_0', dict(targ_cap=4, init_cap=4, bat_size=5, min_serv=0,
|
|
batches=[
|
|
(4, 4, ['4', '3', '2', '1']),
|
|
])),
|
|
('4_4_4_1', dict(targ_cap=4, init_cap=4, bat_size=4, min_serv=1,
|
|
batches=[
|
|
(5, 4, ['5', '4', '3', '2']),
|
|
(4, 1, ['1']),
|
|
])),
|
|
('4_4_6_1', dict(targ_cap=4, init_cap=4, bat_size=6, min_serv=1,
|
|
batches=[
|
|
(5, 4, ['5', '4', '3', '2']),
|
|
(4, 1, ['1']),
|
|
])),
|
|
('4_4_4_2', dict(targ_cap=4, init_cap=4, bat_size=4, min_serv=2,
|
|
batches=[
|
|
(6, 4, ['6', '5', '4', '3']),
|
|
(4, 2, ['2', '1']),
|
|
])),
|
|
('4_4_4_4', dict(targ_cap=4, init_cap=4, bat_size=4, min_serv=4,
|
|
batches=[
|
|
(8, 4, ['8', '7', '6', '5']),
|
|
(8, 4, ['4', '3', '2', '1']),
|
|
(4, 0, []),
|
|
])),
|
|
('4_4_5_6', dict(targ_cap=4, init_cap=4, bat_size=5, min_serv=6,
|
|
batches=[
|
|
(8, 4, ['8', '7', '6', '5']),
|
|
(8, 4, ['4', '3', '2', '1']),
|
|
(4, 0, []),
|
|
])),
|
|
|
|
('4_7_1_0', dict(targ_cap=4, init_cap=7, bat_size=1, min_serv=0,
|
|
batches=[
|
|
(4, 1, ['4']),
|
|
(4, 1, ['3']),
|
|
(4, 1, ['2']),
|
|
(4, 1, ['1']),
|
|
])),
|
|
('4_7_1_4', dict(targ_cap=4, init_cap=7, bat_size=1, min_serv=4,
|
|
batches=[
|
|
(5, 1, ['4']),
|
|
(5, 1, ['3']),
|
|
(5, 1, ['2']),
|
|
(5, 1, ['1']),
|
|
(4, 0, []),
|
|
])),
|
|
('4_7_1_5', dict(targ_cap=4, init_cap=7, bat_size=1, min_serv=5,
|
|
batches=[
|
|
(5, 1, ['4']),
|
|
(5, 1, ['3']),
|
|
(5, 1, ['2']),
|
|
(5, 1, ['1']),
|
|
(4, 0, []),
|
|
])),
|
|
('4_7_2_0', dict(targ_cap=4, init_cap=7, bat_size=2, min_serv=0,
|
|
batches=[
|
|
(4, 2, ['4', '3']),
|
|
(4, 2, ['2', '1']),
|
|
])),
|
|
('4_7_2_4', dict(targ_cap=4, init_cap=7, bat_size=2, min_serv=4,
|
|
batches=[
|
|
(6, 2, ['4', '3']),
|
|
(6, 2, ['2', '1']),
|
|
(4, 0, []),
|
|
])),
|
|
('5_7_2_0', dict(targ_cap=5, init_cap=7, bat_size=2, min_serv=0,
|
|
batches=[
|
|
(5, 2, ['5', '4']),
|
|
(5, 2, ['3', '2']),
|
|
(5, 1, ['1']),
|
|
])),
|
|
('5_7_2_4', dict(targ_cap=5, init_cap=7, bat_size=2, min_serv=4,
|
|
batches=[
|
|
(6, 2, ['5', '4']),
|
|
(6, 2, ['3', '2']),
|
|
(5, 1, ['1']),
|
|
])),
|
|
('4_7_4_4', dict(targ_cap=4, init_cap=7, bat_size=4, min_serv=4,
|
|
batches=[
|
|
(8, 4, ['8', '4', '3', '2']),
|
|
(5, 1, ['1']),
|
|
(4, 0, []),
|
|
])),
|
|
('4_7_5_6', dict(targ_cap=4, init_cap=7, bat_size=5, min_serv=6,
|
|
batches=[
|
|
(8, 4, ['8', '4', '3', '2']),
|
|
(5, 1, ['1']),
|
|
(4, 0, []),
|
|
])),
|
|
|
|
('6_4_1_0', dict(targ_cap=6, init_cap=4, bat_size=1, min_serv=0,
|
|
batches=[
|
|
(5, 1, ['5']),
|
|
(6, 1, ['6']),
|
|
(6, 1, ['4']),
|
|
(6, 1, ['3']),
|
|
(6, 1, ['2']),
|
|
(6, 1, ['1']),
|
|
])),
|
|
('6_4_1_4', dict(targ_cap=6, init_cap=4, bat_size=1, min_serv=4,
|
|
batches=[
|
|
(5, 1, ['5']),
|
|
(6, 1, ['6']),
|
|
(6, 1, ['4']),
|
|
(6, 1, ['3']),
|
|
(6, 1, ['2']),
|
|
(6, 1, ['1']),
|
|
])),
|
|
('6_4_1_5', dict(targ_cap=6, init_cap=4, bat_size=1, min_serv=5,
|
|
batches=[
|
|
(5, 1, ['5']),
|
|
(6, 1, ['6']),
|
|
(6, 1, ['4']),
|
|
(6, 1, ['3']),
|
|
(6, 1, ['2']),
|
|
(6, 1, ['1']),
|
|
])),
|
|
('6_4_2_0', dict(targ_cap=6, init_cap=4, bat_size=2, min_serv=0,
|
|
batches=[
|
|
(6, 2, ['5', '6']),
|
|
(6, 2, ['4', '3']),
|
|
(6, 2, ['2', '1']),
|
|
])),
|
|
('6_4_2_4', dict(targ_cap=6, init_cap=4, bat_size=2, min_serv=4,
|
|
batches=[
|
|
(6, 2, ['5', '6']),
|
|
(6, 2, ['4', '3']),
|
|
(6, 2, ['2', '1']),
|
|
])),
|
|
('6_5_2_0', dict(targ_cap=6, init_cap=5, bat_size=2, min_serv=0,
|
|
batches=[
|
|
(6, 2, ['6', '5']),
|
|
(6, 2, ['4', '3']),
|
|
(6, 2, ['2', '1']),
|
|
])),
|
|
('6_5_2_4', dict(targ_cap=6, init_cap=5, bat_size=2, min_serv=4,
|
|
batches=[
|
|
(6, 2, ['6', '5']),
|
|
(6, 2, ['4', '3']),
|
|
(6, 2, ['2', '1']),
|
|
])),
|
|
('6_3_2_0', dict(targ_cap=6, init_cap=3, bat_size=2, min_serv=0,
|
|
batches=[
|
|
(5, 2, ['4', '5']),
|
|
(6, 2, ['6', '3']),
|
|
(6, 2, ['2', '1']),
|
|
])),
|
|
('6_3_2_4', dict(targ_cap=6, init_cap=3, bat_size=2, min_serv=4,
|
|
batches=[
|
|
(5, 2, ['4', '5']),
|
|
(6, 2, ['6', '3']),
|
|
(6, 2, ['2', '1']),
|
|
])),
|
|
('6_4_4_0', dict(targ_cap=6, init_cap=4, bat_size=4, min_serv=0,
|
|
batches=[
|
|
(6, 4, ['5', '6', '4', '3']),
|
|
(6, 2, ['2', '1']),
|
|
])),
|
|
('6_4_5_0', dict(targ_cap=6, init_cap=4, bat_size=5, min_serv=0,
|
|
batches=[
|
|
(6, 5, ['5', '6', '4', '3', '2']),
|
|
(6, 1, ['1']),
|
|
])),
|
|
('6_4_4_1', dict(targ_cap=6, init_cap=4, bat_size=4, min_serv=1,
|
|
batches=[
|
|
(6, 4, ['5', '6', '4', '3']),
|
|
(6, 2, ['2', '1']),
|
|
])),
|
|
('6_4_6_1', dict(targ_cap=6, init_cap=4, bat_size=6, min_serv=1,
|
|
batches=[
|
|
(7, 6, ['5', '6', '7', '4', '3', '2']),
|
|
(6, 1, ['1']),
|
|
])),
|
|
('6_4_4_2', dict(targ_cap=6, init_cap=4, bat_size=4, min_serv=2,
|
|
batches=[
|
|
(6, 4, ['5', '6', '4', '3']),
|
|
(6, 2, ['2', '1']),
|
|
])),
|
|
('6_4_4_4', dict(targ_cap=6, init_cap=4, bat_size=4, min_serv=4,
|
|
batches=[
|
|
(8, 4, ['8', '7', '6', '5']),
|
|
(8, 4, ['4', '3', '2', '1']),
|
|
(6, 0, []),
|
|
])),
|
|
('6_4_5_6', dict(targ_cap=6, init_cap=4, bat_size=5, min_serv=6,
|
|
batches=[
|
|
(9, 5, ['9', '8', '7', '6', '5']),
|
|
(10, 4, ['10', '4', '3', '2']),
|
|
(7, 1, ['1']),
|
|
(6, 0, []),
|
|
])),
|
|
]
|
|
|
|
def setUp(self):
|
|
super(TestGetBatches, self).setUp()
|
|
|
|
self.stack = utils.parse_stack(template)
|
|
self.grp = self.stack['group1']
|
|
self.grp._name_blacklist = mock.Mock(return_value={'0'})
|
|
|
|
def test_get_batches(self):
|
|
batches = list(self.grp._get_batches(self.targ_cap,
|
|
self.init_cap,
|
|
self.bat_size,
|
|
self.min_serv))
|
|
self.assertEqual([(s, u) for s, u, n in self.batches], batches)
|
|
|
|
def test_assemble(self):
|
|
resources = [(str(i), False) for i in range(self.init_cap + 1)]
|
|
|
|
self.grp.get_size = mock.Mock(return_value=self.targ_cap)
|
|
self.grp._build_resource_definition = mock.Mock(return_value=True)
|
|
self.grp._get_resources = mock.Mock(return_value=resources)
|
|
self.grp._do_prop_replace = mock.Mock(side_effect=lambda g, d: d)
|
|
|
|
all_updated_names = set()
|
|
|
|
for size, max_upd, names in self.batches:
|
|
|
|
template = self.grp._assemble_for_rolling_update(size,
|
|
max_upd,
|
|
names)
|
|
res_dict = template['resources']
|
|
|
|
expected_names = set(map(str, range(1, size + 1)))
|
|
self.assertEqual(expected_names, set(res_dict))
|
|
|
|
all_updated_names &= expected_names
|
|
all_updated_names |= set(names)
|
|
updated = set(n for n, v in res_dict.items() if v is True)
|
|
self.assertEqual(all_updated_names, updated)
|
|
|
|
resources[:] = sorted(res_dict.items(), key=lambda i: int(i[0]))
|