#
#    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 grouputils
from heat.common import template_format
from heat.engine.clients.os import glance
from heat.engine.clients.os import nova
from heat.engine import node_data
from heat.engine.resources.openstack.heat import resource_group
from heat.engine import rsrc_defn
from heat.engine import scheduler
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"]}
        }
    }
}

template_server = {
    "heat_template_version": "2013-05-23",
    "resources": {
        "group1": {
            "type": "OS::Heat::ResourceGroup",
            "properties": {
                "count": 2,
                "resource_def": {
                    "type": "OS::Nova::Server",
                    "properties": {
                        "image": "image%index%",
                        "flavor": "flavor%index%"
                    }
                }
            }
        }
    }
}


class ResourceGroupTest(common.HeatTestCase):

    def setUp(self):
        super(ResourceGroupTest, self).setUp()

        self.inspector = mock.Mock(spec=grouputils.GroupInspector)
        self.patchobject(grouputils.GroupInspector, 'from_parent_resource',
                         return_value=self.inspector)

    def test_assemble_nested(self):
        """Tests nested stack creation based on props.

        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']).t)

    def test_assemble_nested_outputs(self):
        """Tests nested stack creation based on props.

        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"
                    }
                }
            },
            "outputs": {
                "foo": {
                    "value": [
                        {"get_attr": ["0", "foo"]},
                        {"get_attr": ["1", "foo"]},
                        {"get_attr": ["2", "foo"]},
                    ]
                }
            }
        }

        resg.referenced_attrs = mock.Mock(return_value=["foo"])
        self.assertEqual(templ, resg._assemble_nested(['0', '1', '2']).t)

    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']).t)
        expect['resources']["0"]['properties'] = {"Foo": None}
        self.assertEqual(
            expect, resg._assemble_nested(['0'], include_all=True).t)

    def test_assemble_nested_include_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",
        }
        self.assertEqual(expect, resg._assemble_nested([]).t)

    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']).t)

    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 = rsrc_defn.ResourceDefinition(
            None,
            "OverwrittenFnGetRefIdType",
            {"foo": "baz"})

        stack = utils.parse_stack(template)
        snip = stack.t.resource_definitions(stack)['group1']
        resg = resource_group.ResourceGroup('test', snip, stack)
        nested = get_fake_nested_stack(['0', '1'])
        self.inspector.template.return_value = nested.defn._template
        self.inspector.member_names.return_value = ['0', '1']
        resg.build_resource_definition = mock.Mock(return_value=resource_def)
        self.assertEqual(expect, resg._assemble_for_rolling_update(2, 1).t)

    def test_assemble_nested_rolling_update_outputs(self):
        expect = {
            "heat_template_version": "2015-04-30",
            "resources": {
                "0": {
                    "type": "OverwrittenFnGetRefIdType",
                    "properties": {
                        "foo": "bar"
                    }
                },
                "1": {
                    "type": "OverwrittenFnGetRefIdType",
                    "properties": {
                        "foo": "baz"
                    }
                }
            },
            "outputs": {
                "bar": {
                    "value": [
                        {"get_attr": ["0", "bar"]},
                        {"get_attr": ["1", "bar"]},
                    ]
                }
            }
        }
        resource_def = rsrc_defn.ResourceDefinition(
            None,
            "OverwrittenFnGetRefIdType",
            {"foo": "baz"})

        stack = utils.parse_stack(template)
        snip = stack.t.resource_definitions(stack)['group1']
        resg = resource_group.ResourceGroup('test', snip, stack)
        nested = get_fake_nested_stack(['0', '1'])
        self.inspector.template.return_value = nested.defn._template
        self.inspector.member_names.return_value = ['0', '1']
        resg.build_resource_definition = mock.Mock(return_value=resource_def)
        resg.referenced_attrs = mock.Mock(return_value=["bar"])
        self.assertEqual(expect, resg._assemble_for_rolling_update(2, 1).t)

    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 = rsrc_defn.ResourceDefinition(
            None,
            "OverwrittenFnGetRefIdType",
            {"foo": "baz"})

        stack = utils.parse_stack(template)
        snip = stack.t.resource_definitions(stack)['group1']
        resg = resource_group.ResourceGroup('test', snip, stack)
        nested = get_fake_nested_stack(['0', '1'])
        self.inspector.template.return_value = nested.defn._template
        self.inspector.member_names.return_value = ['0', '1']
        resg.build_resource_definition = mock.Mock(return_value=resource_def)
        self.assertEqual(expect, resg._assemble_for_rolling_update(2, 0).t)

    def test_assemble_nested_rolling_update_failed_resource(self):
        expect = {
            "heat_template_version": "2015-04-30",
            "resources": {
                "0": {
                    "type": "OverwrittenFnGetRefIdType",
                    "properties": {
                        "foo": "baz"
                    }
                },
                "1": {
                    "type": "OverwrittenFnGetRefIdType",
                    "properties": {
                        "foo": "bar"
                    }
                }
            }
        }
        resource_def = rsrc_defn.ResourceDefinition(
            None,
            "OverwrittenFnGetRefIdType",
            {"foo": "baz"})

        stack = utils.parse_stack(template)
        snip = stack.t.resource_definitions(stack)['group1']
        resg = resource_group.ResourceGroup('test', snip, stack)
        nested = get_fake_nested_stack(['0', '1'])
        self.inspector.template.return_value = nested.defn._template
        self.inspector.member_names.return_value = ['1']
        resg.build_resource_definition = mock.Mock(return_value=resource_def)
        self.assertEqual(expect, resg._assemble_for_rolling_update(2, 1).t)

    def test_assemble_nested_missing_param(self):
        # Setup

        # Change the standard testing template to use a get_param lookup
        # within the resource definition
        templ = copy.deepcopy(template)
        res_def = templ['resources']['group1']['properties']['resource_def']
        res_def['properties']['Foo'] = {'get_param': 'bar'}

        stack = utils.parse_stack(templ)
        snip = stack.t.resource_definitions(stack)['group1']
        resg = resource_group.ResourceGroup('test', snip, stack)

        # Test - This should not raise a ValueError about "bar" not being
        # provided
        nested_tmpl = resg._assemble_nested(['0', '1'])

        # Verify
        expected = {
            "heat_template_version": "2015-04-30",
            "resources": {
                "0": {
                    "type": "OverwrittenFnGetRefIdType",
                    "properties": {}
                },
                "1": {
                    "type": "OverwrittenFnGetRefIdType",
                    "properties": {}
                }
            }
        }
        self.assertEqual(expected, nested_tmpl.t)

    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']).t
        for res in nested['resources']:
            res_prop = nested['resources'][res]['properties']
            res_prop['listprop'] = list(res_prop['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']).t
        res_prop = nested['resources']['0']['properties']
        res_prop['listprop'] = list(res_prop['listprop'])
        self.assertEqual(expect, nested)

        props = copy.deepcopy(templ['resources']['group1']['properties'])
        res_def = props['resource_def']
        res_def['properties']['Foo'] = "Bar___foo__"
        res_def['properties']['listprop'] = ["__foo___0",
                                             "__foo___1",
                                             "__foo___2"]
        res_def['type'] = "ResourceWithListProp__foo__"
        snip = snip.freeze(properties=props)

        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']).t
        res_prop = nested['resources']['0']['properties']
        res_prop['listprop'] = list(res_prop['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_validate_with_blacklist(self):
        templ = copy.deepcopy(template_server)
        self.mock_flavor = mock.Mock(ram=4, disk=4)
        self.mock_active_image = mock.Mock(min_ram=1, min_disk=1,
                                           status='active')
        self.mock_inactive_image = mock.Mock(min_ram=1, min_disk=1,
                                             status='inactive')

        def get_image(image_identifier):
            if image_identifier == 'image0':
                return self.mock_inactive_image
            else:
                return self.mock_active_image

        self.patchobject(glance.GlanceClientPlugin, 'get_image',
                         side_effect=get_image)
        self.patchobject(nova.NovaClientPlugin, 'get_flavor',
                         return_value=self.mock_flavor)
        props = templ["resources"]["group1"]["properties"]
        props["removal_policies"] = [{"resource_list": ["0"]}]
        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.StackValidationFailed,
                                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_validate_reference_attr_with_none_ref(self):
        stack = utils.parse_stack(template_attr)
        snip = stack.t.resource_definitions(stack)['group1']
        resgrp = resource_group.ResourceGroup('test', snip, stack)
        self.patchobject(resgrp, 'referenced_attrs',
                         return_value=set([('nested_dict', None)]))
        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.assertIsNone(resgrp.handle_create())
        self.assertEqual(1, resgrp.create_with_template.call_count)

    def test_handle_create_with_batching(self):
        self.inspector.member_names.return_value = []
        self.inspector.size.return_value = 0
        stack = utils.parse_stack(tmpl_with_default_updt_policy())
        defn = stack.t.resource_definitions(stack)['group1']
        props = stack.t.t['resources']['group1']['properties'].copy()
        props['count'] = 10
        update_policy = {'batch_create': {'max_batch_size': 3}}
        snip = defn.freeze(properties=props, update_policy=update_policy)
        resgrp = resource_group.ResourceGroup('test', snip, stack)
        self.patchobject(scheduler.TaskRunner, 'start')
        checkers = resgrp.handle_create()
        self.assertEqual(4, len(checkers))

    def test_handle_create_with_batching_zero_count(self):
        self.inspector.member_names.return_value = []
        self.inspector.size.return_value = 0
        stack = utils.parse_stack(tmpl_with_default_updt_policy())
        defn = stack.t.resource_definitions(stack)['group1']
        props = stack.t.t['resources']['group1']['properties'].copy()
        props['count'] = 0
        update_policy = {'batch_create': {'max_batch_size': 1}}
        snip = defn.freeze(properties=props, update_policy=update_policy)
        resgrp = resource_group.ResourceGroup('test', snip, stack)
        self.patchobject(scheduler.TaskRunner, 'start')
        checkers = resgrp.handle_create()
        self.assertEqual(0, len(checkers))

    def test_run_to_completion(self):
        stack = utils.parse_stack(template2)
        snip = stack.t.resource_definitions(stack)['group1']
        resgrp = resource_group.ResourceGroup('test', snip, stack)
        resgrp._check_status_complete = mock.Mock(side_effect=[False, True])
        resgrp.update_with_template = mock.Mock(return_value=None)
        next(resgrp._run_to_completion(snip, 200))
        self.assertEqual(1, resgrp.update_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
        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, rm_mode='append')),
        ('2', dict(data_in='0,1,2', rm_list=[],
                   nested_rsrcs=[], expected=['0', '1', '2'],
                   saved=False, rm_mode='append')),
        ('3', dict(data_in='1,3', rm_list=['6'],
                   nested_rsrcs=['0', '1', '3'],
                   expected=['1', '3'],
                   saved=False, rm_mode='append')),
        ('4', dict(data_in='0,1', rm_list=['id-7'],
                   nested_rsrcs=['0', '1', '3'],
                   expected=['0', '1'],
                   saved=False, rm_mode='append')),
        ('5', dict(data_in='0,1', rm_list=['3'],
                   nested_rsrcs=['0', '1', '3'],
                   expected=['0', '1', '3'],
                   saved=True, rm_mode='append')),
        ('6', dict(data_in='0,1', rm_list=['id-3'],
                   nested_rsrcs=['0', '1', '3'],
                   expected=['0', '1', '3'],
                   saved=True, rm_mode='append')),
        ('7', dict(data_in='0,1', rm_list=['id-3'],
                   nested_rsrcs=['0', '1', '3'],
                   expected=['3'],
                   saved=True, rm_mode='update')),
        ('8', dict(data_in='1', rm_list=[],
                   nested_rsrcs=['0', '1', '2'],
                   expected=[],
                   saved=True, rm_mode='update')),
    ]

    def test_blacklist(self):
        stack = utils.parse_stack(template)
        resg = stack['group1']

        # mock properties
        resg.properties = mock.MagicMock()
        p_data = {'removal_policies': [{'resource_list': self.rm_list}],
                  'removal_policies_mode': self.rm_mode}
        resg.properties.__getitem__.side_effect = p_data.__getitem__

        # 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 = rsrc_defn.ResourceDefinition(
            None,
            "OverwrittenFnGetRefIdType",
            self.expected)

        exp2 = rsrc_defn.ResourceDefinition(
            None,
            "OverwrittenFnGetRefIdType",
            self.expected_include)

        rdef = resg.get_resource_def()
        self.assertEqual(exp1, resg.build_resource_definition('0', rdef))
        rdef = resg.get_resource_def(include_all=True)
        self.assertEqual(
            exp2, resg.build_resource_definition('0', rdef))


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.

        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.

        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.

        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.

        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.

        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_refs_map(self):
        resg = self._create_dummy_stack()
        found = resg.FnGetAtt("refs_map")
        expected = {'0': 'ID-0', '1': 'ID-1'}
        self.assertEqual(expected, found)

    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'))
        ex = self.assertRaises(exception.NotFound, resg.FnGetAtt,
                               'resource.2')
        self.assertIn("Member '2' not found in group resource 'group1'.",
                      six.text_type(ex))

    def test_get_attribute_convg(self):
        cache_data = {'group1': node_data.NodeData.from_dict({
            'uuid': mock.ANY,
            'id': mock.ANY,
            'action': 'CREATE',
            'status': 'COMPLETE',
            'attrs': {'refs': ['rsrc1', 'rsrc2']}
        })}
        stack = utils.parse_stack(template, cache_data=cache_data)
        rsrc = stack.defn['group1']
        self.assertEqual(['rsrc1', 'rsrc2'], rsrc.FnGetAtt('refs'))

    def test_get_attribute_blacklist(self):
        resg = self._create_dummy_stack()
        resg.data = mock.Mock(return_value={'name_blacklist': '3,5'})

        expected = ['3', '5']
        self.assertEqual(expected, resg.FnGetAtt(resg.REMOVED_RSRC_LIST))

    def _create_dummy_stack(self, template_data=template, expect_count=2,
                            expect_attrs=None):
        stack = utils.parse_stack(template_data)
        resg = stack['group1']
        attrs = {}
        refids = {}
        if expect_attrs is None:
            expect_attrs = {}
        for index in range(expect_count):
            res = str(index)
            attrs[index] = expect_attrs.get(res, res)
            refids[index] = 'ID-%s' % res

        names = [str(name) for name in range(expect_count)]
        resg._resource_names = mock.Mock(return_value=names)
        self._stub_get_attr(resg, refids, attrs)
        return resg

    def _stub_get_attr(self, resg, refids, attrs):
        def ref_id_fn(res_name):
            return refids[int(res_name)]

        def attr_fn(args):
            res_name = args[0]
            return attrs[int(res_name)]

        def get_output(output_name):
            outputs = resg._nested_output_defns(resg._resource_names(),
                                                attr_fn, ref_id_fn)
            op_defns = {od.name: od for od in outputs}
            self.assertIn(output_name, op_defns)
            return op_defns[output_name].get_value()

        orig_get_attr = resg.FnGetAtt

        def get_attr(attr_name, *path):
            if not path:
                attr = attr_name
            else:
                attr = (attr_name,) + path
            # Mock referenced_attrs() so that _nested_output_definitions()
            # will include the output required for this attribute
            resg.referenced_attrs = mock.Mock(return_value=[attr])

            # Pass through to actual function under test
            return orig_get_attr(attr_name, *path)

        resg.FnGetAtt = mock.Mock(side_effect=get_attr)
        resg.get_output = mock.Mock(side_effect=get_output)


class ResourceGroupAttrFallbackTest(ResourceGroupAttrTest):
    def _stub_get_attr(self, resg, refids, attrs):
        # Raise NotFound when getting output, to force fallback to old-school
        # grouputils functions
        resg.get_output = mock.Mock(side_effect=exception.NotFound)

        def make_fake_res(idx):
            fr = mock.Mock()
            fr.stack = resg.stack
            fr.FnGetRefId.return_value = refids[idx]
            fr.FnGetAtt.return_value = attrs[idx]
            return fr

        fake_res = {str(i): make_fake_res(i) for i in refids}
        resg.nested = mock.Mock(return_value=fake_res)

    @mock.patch.object(grouputils, 'get_rsrc_id')
    def test_get_attribute(self, mock_get_rsrc_id):
        stack = utils.parse_stack(template)
        mock_get_rsrc_id.side_effect = ['0', '1']
        rsrc = stack['group1']
        rsrc.get_output = mock.Mock(side_effect=exception.NotFound)
        self.assertEqual(['0', '1'], rsrc.FnGetAtt(rsrc.REFS))


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()

        inspector = mock.Mock(spec=grouputils.GroupInspector)
        self.patchobject(grouputils.GroupInspector, 'from_parent_resource',
                         return_value=inspector)
        inspector.member_names.return_value = self.existing
        inspector.size.return_value = len(self.existing)

    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 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.assertIsNotNone(policy)
        self.assertGreater(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.assertIsNotNone(policy)
        self.assertGreater(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 validate_update_policy_diff(self, current, updated):
        # load current stack
        current_stack = utils.parse_stack(current)
        current_grp = current_stack['group1']
        current_grp_json = current_grp.frozen_definition()

        updated_stack = utils.parse_stack(updated)
        updated_grp = updated_stack['group1']
        updated_grp_json = updated_grp.t.freeze()

        # identify the template difference
        tmpl_diff = updated_grp.update_template_diff(
            updated_grp_json, current_grp_json)
        self.assertTrue(tmpl_diff.update_policy_changed())

        # test application of the new update policy in handle_update
        current_grp._try_rolling_update = mock.Mock()
        current_grp._assemble_nested_for_size = mock.Mock()
        self.patchobject(scheduler.TaskRunner, 'start')
        current_grp.handle_update(updated_grp_json, tmpl_diff, None)
        self.assertEqual(updated_grp_json._update_policy or {},
                         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 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 = self.current_grp.frozen_definition()
        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 = updated_grp.t.freeze()
        tmpl_diff = updated_grp.update_template_diff(
            updated_grp_json, current_grp_json)

        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(updated_grp_json, 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 update policy 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 test_count_black_listed(self):
        inspector = mock.Mock(spec=grouputils.GroupInspector)
        self.patchobject(grouputils.GroupInspector, 'from_parent_resource',
                         return_value=inspector)
        inspector.member_names.return_value = self.existing

        stack = utils.parse_stack(template2)
        snip = stack.t.resource_definitions(stack)['group1']
        resgrp = resource_group.ResourceGroup('test', snip, stack)
        resgrp._name_blacklist = mock.Mock(return_value=set(self.black_listed))
        rcount = resgrp._count_black_listed(self.existing)
        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):
        old_def = rsrc_defn.ResourceDefinition(
            None,
            "OverwrittenFnGetRefIdType",
            {"foo": "baz"})

        new_def = rsrc_defn.ResourceDefinition(
            None,
            "OverwrittenFnGetRefIdType",
            {"foo": "bar"})

        resources = [(str(i), old_def) for i in range(self.init_cap + 1)]
        self.grp.get_size = mock.Mock(return_value=self.targ_cap)
        self.patchobject(grouputils, 'get_member_definitions',
                         return_value=resources)
        self.grp.build_resource_definition = mock.Mock(return_value=new_def)
        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.resource_definitions(self.stack)

            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 != old_def)
            self.assertEqual(all_updated_names, updated)

            resources[:] = sorted(res_dict.items(), key=lambda i: int(i[0]))
