#
#    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.

from unittest import mock

from heat.common import exception
from heat.common import short_id
from heat.common import template_format
from heat.engine.clients.os import nova
from heat.engine import node_data
from heat.engine import scheduler
from heat.tests.autoscaling import inline_templates
from heat.tests import common
from heat.tests import utils


class LaunchConfigurationTest(common.HeatTestCase):
    def validate_launch_config(self, stack, lc_name='LaunchConfig'):
        # create the launch configuration resource
        conf = stack[lc_name]
        self.assertIsNone(conf.validate())
        scheduler.TaskRunner(conf.create)()
        self.assertEqual((conf.CREATE, conf.COMPLETE), conf.state)
        # check bdm in configuration
        self.assertIsNotNone(conf.properties['BlockDeviceMappings'])

    def test_launch_config_get_ref_by_id(self):
        t = template_format.parse(inline_templates.as_template)
        stack = utils.parse_stack(t, params=inline_templates.as_params)
        rsrc = stack['LaunchConfig']
        self.stub_ImageConstraint_validate()
        self.stub_FlavorConstraint_validate()
        self.stub_SnapshotConstraint_validate()

        self.assertIsNone(rsrc.validate())
        scheduler.TaskRunner(rsrc.create)()
        self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)

        # use physical_resource_name when rsrc.id is not None
        self.assertIsNotNone(rsrc.uuid)
        expected = '%s-%s-%s' % (rsrc.stack.name,
                                 rsrc.name,
                                 short_id.get_id(rsrc.uuid))
        self.assertEqual(expected, rsrc.FnGetRefId())

        # otherwise use parent method
        rsrc.id = None
        self.assertIsNone(rsrc.resource_id)
        self.assertEqual('LaunchConfig', rsrc.FnGetRefId())

    def test_launch_config_refid_convergence_cache_data(self):
        t = template_format.parse(inline_templates.as_template)
        cache_data = {'LaunchConfig': node_data.NodeData.from_dict({
            'uuid': mock.ANY,
            'id': mock.ANY,
            'action': 'CREATE',
            'status': 'COMPLETE',
            'reference_id': 'convg_xyz'
        })}
        stack = utils.parse_stack(t, params=inline_templates.as_params,
                                  cache_data=cache_data)
        rsrc = stack.defn['LaunchConfig']
        self.assertEqual('convg_xyz', rsrc.FnGetRefId())

    def test_launch_config_create_with_instanceid(self):
        t = template_format.parse(inline_templates.as_template)
        lcp = t['Resources']['LaunchConfig']['Properties']
        lcp['InstanceId'] = '5678'
        stack = utils.parse_stack(t, params=inline_templates.as_params)
        rsrc = stack['LaunchConfig']
        # ImageId, InstanceType and BlockDeviceMappings keep the lc's values
        # KeyName and SecurityGroups are derived from the instance
        lc_props = {
            'ImageId': 'foo',
            'InstanceType': 'bar',
            'BlockDeviceMappings': lcp['BlockDeviceMappings'],
            'KeyName': 'hth_keypair',
            'SecurityGroups': ['hth_test']
        }
        rsrc.rebuild_lc_properties = mock.Mock(return_value=lc_props)
        self.stub_ImageConstraint_validate()
        self.stub_FlavorConstraint_validate()
        self.stub_SnapshotConstraint_validate()
        self.stub_ServerConstraint_validate()

        self.assertIsNone(rsrc.validate())
        scheduler.TaskRunner(rsrc.create)()
        self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)

    def test_lc_validate_without_InstanceId_and_ImageId(self):
        t = template_format.parse(inline_templates.as_template)
        lcp = t['Resources']['LaunchConfig']['Properties']
        lcp.pop('ImageId')
        stack = utils.parse_stack(t, inline_templates.as_params)
        rsrc = stack['LaunchConfig']
        self.stub_SnapshotConstraint_validate()
        self.stub_FlavorConstraint_validate()

        e = self.assertRaises(exception.StackValidationFailed,
                              rsrc.validate)
        ex_msg = ('If without InstanceId, '
                  'ImageId and InstanceType are required.')
        self.assertIn(ex_msg, str(e))

    def test_lc_validate_without_InstanceId_and_InstanceType(self):
        t = template_format.parse(inline_templates.as_template)
        lcp = t['Resources']['LaunchConfig']['Properties']
        lcp.pop('InstanceType')
        stack = utils.parse_stack(t, inline_templates.as_params)
        rsrc = stack['LaunchConfig']
        self.stub_SnapshotConstraint_validate()
        self.stub_ImageConstraint_validate()

        e = self.assertRaises(exception.StackValidationFailed,
                              rsrc.validate)
        ex_msg = ('If without InstanceId, '
                  'ImageId and InstanceType are required.')
        self.assertIn(ex_msg, str(e))

    def test_launch_config_create_with_instanceid_not_found(self):
        t = template_format.parse(inline_templates.as_template)
        lcp = t['Resources']['LaunchConfig']['Properties']
        lcp['InstanceId'] = '5678'
        stack = utils.parse_stack(t, params=inline_templates.as_params)
        rsrc = stack['LaunchConfig']
        self.stub_SnapshotConstraint_validate()
        self.stub_ImageConstraint_validate()
        self.stub_FlavorConstraint_validate()

        self.patchobject(nova.NovaClientPlugin, 'get_server',
                         side_effect=exception.EntityNotFound(
                             entity='Server', name='5678'))
        msg = ("Property error: "
               "Resources.LaunchConfig.Properties.InstanceId: "
               "Error validating value '5678': The Server (5678) "
               "could not be found.")

        exc = self.assertRaises(exception.StackValidationFailed,
                                rsrc.validate)
        self.assertIn(msg, str(exc))

    def test_validate_BlockDeviceMappings_without_Ebs_property(self):
        t = template_format.parse(inline_templates.as_template)
        lcp = t['Resources']['LaunchConfig']['Properties']
        bdm = [{'DeviceName': 'vdb'}]
        lcp['BlockDeviceMappings'] = bdm
        stack = utils.parse_stack(t, params=inline_templates.as_params)

        self.stub_ImageConstraint_validate()
        self.stub_FlavorConstraint_validate()

        e = self.assertRaises(exception.StackValidationFailed,
                              self.validate_launch_config, stack)

        self.assertIn("Ebs is missing, this is required",
                      str(e))

    def test_validate_BlockDeviceMappings_without_SnapshotId_property(self):
        t = template_format.parse(inline_templates.as_template)
        lcp = t['Resources']['LaunchConfig']['Properties']
        bdm = [{'DeviceName': 'vdb',
                'Ebs': {'VolumeSize': '1'}}]
        lcp['BlockDeviceMappings'] = bdm
        stack = utils.parse_stack(t, params=inline_templates.as_params)

        self.stub_ImageConstraint_validate()
        self.stub_FlavorConstraint_validate()

        e = self.assertRaises(exception.StackValidationFailed,
                              self.validate_launch_config, stack)

        self.assertIn("SnapshotId is missing, this is required",
                      str(e))

    def test_validate_BlockDeviceMappings_without_DeviceName_property(self):
        t = template_format.parse(inline_templates.as_template)
        lcp = t['Resources']['LaunchConfig']['Properties']
        bdm = [{'Ebs': {'SnapshotId': '1234',
                        'VolumeSize': '1'}}]
        lcp['BlockDeviceMappings'] = bdm
        stack = utils.parse_stack(t, params=inline_templates.as_params)
        self.stub_ImageConstraint_validate()
        self.stub_FlavorConstraint_validate()
        self.stub_SnapshotConstraint_validate()

        e = self.assertRaises(exception.StackValidationFailed,
                              self.validate_launch_config, stack)

        excepted_error = (
            'Property error: '
            'Resources.LaunchConfig.Properties.BlockDeviceMappings[0]: '
            'Property DeviceName not assigned')
        self.assertIn(excepted_error, str(e))
