#
#    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 json
import os
import uuid

import mock
import six

from heat.common import exception
from heat.common.i18n import _
from heat.common import identifier
from heat.common import template_format
from heat.common import urlfetch
from heat.engine import attributes
from heat.engine import environment
from heat.engine import parser
from heat.engine import properties
from heat.engine import resource
from heat.engine import resources
from heat.engine.resources import template_resource
from heat.engine import rsrc_defn
from heat.engine import support
from heat.tests import common
from heat.tests import generic_resource as generic_rsrc
from heat.tests import utils


empty_template = {"HeatTemplateFormatVersion": "2012-12-12"}


class MyCloudResource(generic_rsrc.GenericResource):
    pass


class ProviderTemplateTest(common.HeatTestCase):
    def setUp(self):
        super(ProviderTemplateTest, self).setUp()
        resource._register_class('OS::ResourceType',
                                 generic_rsrc.GenericResource)
        resource._register_class('myCloud::ResourceType',
                                 MyCloudResource)

    def test_get_os_empty_registry(self):
        # assertion: with an empty environment we get the correct
        # default class.
        env_str = {'resource_registry': {}}
        env = environment.Environment(env_str)
        cls = env.get_class('OS::ResourceType', 'fred')
        self.assertEqual(generic_rsrc.GenericResource, cls)

    def test_get_mine_global_map(self):
        # assertion: with a global rule we get the "mycloud" class.
        env_str = {'resource_registry': {"OS::*": "myCloud::*"}}
        env = environment.Environment(env_str)
        cls = env.get_class('OS::ResourceType', 'fred')
        self.assertEqual(MyCloudResource, cls)

    def test_get_mine_type_map(self):
        # assertion: with a global rule we get the "mycloud" class.
        env_str = {'resource_registry': {
            "OS::ResourceType": "myCloud::ResourceType"}}
        env = environment.Environment(env_str)
        cls = env.get_class('OS::ResourceType', 'fred')
        self.assertEqual(MyCloudResource, cls)

    def test_get_mine_resource_map(self):
        # assertion: with a global rule we get the "mycloud" class.
        env_str = {'resource_registry': {'resources': {'fred': {
            "OS::ResourceType": "myCloud::ResourceType"}}}}
        env = environment.Environment(env_str)
        cls = env.get_class('OS::ResourceType', 'fred')
        self.assertEqual(MyCloudResource, cls)

    def test_get_os_no_match(self):
        # assertion: make sure 'fred' doesn't match 'jerry'.
        env_str = {'resource_registry': {'resources': {'jerry': {
            "OS::ResourceType": "myCloud::ResourceType"}}}}
        env = environment.Environment(env_str)
        cls = env.get_class('OS::ResourceType', 'fred')
        self.assertEqual(generic_rsrc.GenericResource, cls)

    def test_to_parameters(self):
        """Tests property conversion to parameter values."""
        provider = {
            'HeatTemplateFormatVersion': '2012-12-12',
            'Parameters': {
                'Foo': {'Type': 'String'},
                'AList': {'Type': 'CommaDelimitedList'},
                'MemList': {'Type': 'CommaDelimitedList'},
                'ListEmpty': {'Type': 'CommaDelimitedList'},
                'ANum': {'Type': 'Number'},
                'AMap': {'Type': 'Json'},
            },
            'Outputs': {
                'Foo': {'Value': 'bar'},
            },
        }

        files = {'test_resource.template': json.dumps(provider)}

        class DummyResource(object):
            support_status = support.SupportStatus()

            attributes_schema = {"Foo": attributes.Schema("A test attribute")}
            properties_schema = {
                "Foo": {"Type": "String"},
                "AList": {"Type": "List"},
                "MemList": {"Type": "List"},
                "ListEmpty": {"Type": "List"},
                "ANum": {"Type": "Number"},
                "AMap": {"Type": "Map"}
            }

        env = environment.Environment()
        resource._register_class('DummyResource', DummyResource)
        env.load({'resource_registry':
                  {'DummyResource': 'test_resource.template'}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(empty_template, files=files,
                             env=env),
                             stack_id=str(uuid.uuid4()))

        map_prop_val = {
            "key1": "val1",
            "key2": ["lval1", "lval2", "lval3"],
            "key3": {
                "key4": 4,
                "key5": False
            }
        }
        prop_vals = {
            "Foo": "Bar",
            "AList": ["one", "two", "three"],
            "MemList": [{"key": "name", "value": "three"},
                        {"key": "name", "value": "four"}],
            "ListEmpty": [],
            "ANum": 5,
            "AMap": map_prop_val,
        }
        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  'DummyResource',
                                                  prop_vals)
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition, stack)
        temp_res.validate()
        converted_params = temp_res.child_params()
        self.assertTrue(converted_params)
        for key in DummyResource.properties_schema:
            self.assertIn(key, converted_params)
        # verify String conversion
        self.assertEqual("Bar", converted_params.get("Foo"))
        # verify List conversion
        self.assertEqual("one,two,three", converted_params.get("AList"))
        # verify Member List conversion
        mem_exp = ('.member.0.key=name,'
                   '.member.0.value=three,'
                   '.member.1.key=name,'
                   '.member.1.value=four')
        self.assertEqual(mem_exp, converted_params.get("MemList"))
        # verify Number conversion
        self.assertEqual(5, converted_params.get("ANum"))
        # verify Map conversion
        self.assertEqual(map_prop_val, converted_params.get("AMap"))

        with mock.patch.object(properties.Properties, '__getitem__') as m_get:
            m_get.side_effect = ValueError('boom')

            # If the property doesn't exist on INIT, return default value
            temp_res.action = temp_res.INIT
            converted_params = temp_res.child_params()
            for key in DummyResource.properties_schema:
                self.assertIn(key, converted_params)
            self.assertEqual({}, converted_params['AMap'])
            self.assertEqual(0, converted_params['ANum'])

            # If the property doesn't exist past INIT, then error out
            temp_res.action = temp_res.CREATE
            self.assertRaises(ValueError, temp_res.child_params)

    def test_attributes_extra(self):
        provider = {
            'HeatTemplateFormatVersion': '2012-12-12',
            'Outputs': {
                'Foo': {'Value': 'bar'},
                'Blarg': {'Value': 'wibble'},
            },
        }
        files = {'test_resource.template': json.dumps(provider)}

        class DummyResource(object):
            support_status = support.SupportStatus()
            properties_schema = {}
            attributes_schema = {"Foo": attributes.Schema("A test attribute")}

        env = environment.Environment()
        resource._register_class('DummyResource', DummyResource)
        env.load({'resource_registry':
                  {'DummyResource': 'test_resource.template'}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(empty_template, files=files,
                             env=env),
                             stack_id=str(uuid.uuid4()))

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  "DummyResource")
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition, stack)
        self.assertIsNone(temp_res.validate())

    def test_attributes_missing(self):
        provider = {
            'HeatTemplateFormatVersion': '2012-12-12',
            'Outputs': {
                'Blarg': {'Value': 'wibble'},
            },
        }
        files = {'test_resource.template': json.dumps(provider)}

        class DummyResource(object):
            support_status = support.SupportStatus()
            properties_schema = {}
            attributes_schema = {"Foo": attributes.Schema("A test attribute")}

        env = environment.Environment()
        resource._register_class('DummyResource', DummyResource)
        env.load({'resource_registry':
                  {'DummyResource': 'test_resource.template'}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(empty_template, files=files,
                             env=env),
                             stack_id=str(uuid.uuid4()))

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  "DummyResource")
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition, stack)
        self.assertRaises(exception.StackValidationFailed,
                          temp_res.validate)

    def test_properties_normal(self):
        provider = {
            'HeatTemplateFormatVersion': '2012-12-12',
            'Parameters': {
                'Foo': {'Type': 'String'},
                'Blarg': {'Type': 'String', 'Default': 'wibble'},
            },
        }
        files = {'test_resource.template': json.dumps(provider)}

        class DummyResource(object):
            support_status = support.SupportStatus()
            properties_schema = {"Foo":
                                 properties.Schema(properties.Schema.STRING,
                                                   required=True)}
            attributes_schema = {}

        env = environment.Environment()
        resource._register_class('DummyResource', DummyResource)
        env.load({'resource_registry':
                  {'DummyResource': 'test_resource.template'}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(empty_template, files=files,
                             env=env),
                             stack_id=str(uuid.uuid4()))

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  "DummyResource",
                                                  {"Foo": "bar"})
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition, stack)
        self.assertIsNone(temp_res.validate())

    def test_properties_missing(self):
        provider = {
            'HeatTemplateFormatVersion': '2012-12-12',
            'Parameters': {
                'Blarg': {'Type': 'String', 'Default': 'wibble'},
            },
        }
        files = {'test_resource.template': json.dumps(provider)}

        class DummyResource(object):
            support_status = support.SupportStatus()
            properties_schema = {"Foo":
                                 properties.Schema(properties.Schema.STRING,
                                                   required=True)}
            attributes_schema = {}

        env = environment.Environment()
        resource._register_class('DummyResource', DummyResource)
        env.load({'resource_registry':
                  {'DummyResource': 'test_resource.template'}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(empty_template, files=files,
                             env=env),
                             stack_id=str(uuid.uuid4()))

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  "DummyResource")
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition, stack)
        self.assertRaises(exception.StackValidationFailed,
                          temp_res.validate)

    def test_properties_extra_required(self):
        provider = {
            'HeatTemplateFormatVersion': '2012-12-12',
            'Parameters': {
                'Blarg': {'Type': 'String'},
            },
        }
        files = {'test_resource.template': json.dumps(provider)}

        class DummyResource(object):
            support_status = support.SupportStatus()
            properties_schema = {}
            attributes_schema = {}

        env = environment.Environment()
        resource._register_class('DummyResource', DummyResource)
        env.load({'resource_registry':
                  {'DummyResource': 'test_resource.template'}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(empty_template, files=files,
                             env=env),
                             stack_id=str(uuid.uuid4()))

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  "DummyResource",
                                                  {"Blarg": "wibble"})
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition, stack)
        self.assertRaises(exception.StackValidationFailed,
                          temp_res.validate)

    def test_properties_type_mismatch(self):
        provider = {
            'HeatTemplateFormatVersion': '2012-12-12',
            'Parameters': {
                'Foo': {'Type': 'String'},
            },
        }
        files = {'test_resource.template': json.dumps(provider)}

        class DummyResource(object):
            support_status = support.SupportStatus()
            properties_schema = {"Foo":
                                 properties.Schema(properties.Schema.MAP)}
            attributes_schema = {}

        env = environment.Environment()
        resource._register_class('DummyResource', DummyResource)
        env.load({'resource_registry':
                  {'DummyResource': 'test_resource.template'}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(
                                 {'HeatTemplateFormatVersion': '2012-12-12'},
                                 files=files, env=env),
                             stack_id=str(uuid.uuid4()))

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  "DummyResource",
                                                  {"Foo": "bar"})
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition, stack)
        ex = self.assertRaises(exception.StackValidationFailed,
                               temp_res.validate)
        self.assertEqual("Property Foo type mismatch between facade "
                         "DummyResource (Map) and provider (String)",
                         six.text_type(ex))

    def test_properties_type_match(self):
        provider = {
            'HeatTemplateFormatVersion': '2012-12-12',
            'Parameters': {
                'Length': {'Type': 'Number'},
            },
        }
        files = {'test_resource.template': json.dumps(provider)}

        class DummyResource(object):
            support_status = support.SupportStatus()
            properties_schema = {"Length":
                                 properties.Schema(properties.Schema.INTEGER)}
            attributes_schema = {}

        env = environment.Environment()
        resource._register_class('DummyResource', DummyResource)
        env.load({'resource_registry':
                  {'DummyResource': 'test_resource.template'}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(
                                 {'HeatTemplateFormatVersion': '2012-12-12'},
                                 files=files, env=env),
                             stack_id=str(uuid.uuid4()))

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  "DummyResource",
                                                  {"Length": 10})
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition, stack)
        self.assertIsNone(temp_res.validate())

    def test_boolean_type_provider(self):
        provider = {
            'HeatTemplateFormatVersion': '2012-12-12',
            'Parameters': {
                'Foo': {'Type': 'Boolean'},
            },
        }
        files = {'test_resource.template': json.dumps(provider)}

        class DummyResource(object):
            support_status = support.SupportStatus()
            properties_schema = {"Foo":
                                 properties.Schema(properties.Schema.BOOLEAN)}
            attributes_schema = {}

        env = environment.Environment()
        resource._register_class('DummyResource', DummyResource)
        env.load({'resource_registry':
                  {'DummyResource': 'test_resource.template'}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(
                                 {'HeatTemplateFormatVersion': '2012-12-12'},
                                 files=files, env=env),
                             stack_id=str(uuid.uuid4()))

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  "DummyResource",
                                                  {"Foo": "False"})
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition, stack)
        self.assertIsNone(temp_res.validate())

    def test_resource_info_general(self):
        provider = {
            'HeatTemplateFormatVersion': '2012-12-12',
            'Parameters': {
                'Foo': {'Type': 'Boolean'},
            },
        }
        files = {'test_resource.template': json.dumps(provider),
                 'foo.template': json.dumps(provider)}

        class DummyResource(object):
            support_status = support.SupportStatus()
            properties_schema = {"Foo":
                                 properties.Schema(properties.Schema.BOOLEAN)}
            attributes_schema = {}

        env = environment.Environment()
        resource._register_class('DummyResource', DummyResource)
        env.load({'resource_registry':
                  {'DummyResource': 'test_resource.template',
                   'resources': {'foo': 'foo.template'}}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(
                                 {'HeatTemplateFormatVersion': '2012-12-12'},
                                 files=files, env=env),
                             stack_id=str(uuid.uuid4()))

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  "DummyResource",
                                                  {"Foo": "False"})
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition, stack)
        self.assertEqual('test_resource.template',
                         temp_res.template_name)

    def test_resource_info_special(self):
        provider = {
            'HeatTemplateFormatVersion': '2012-12-12',
            'Parameters': {
                'Foo': {'Type': 'Boolean'},
            },
        }
        files = {'test_resource.template': json.dumps(provider),
                 'foo.template': json.dumps(provider)}

        class DummyResource(object):
            support_status = support.SupportStatus()
            properties_schema = {"Foo":
                                 properties.Schema(properties.Schema.BOOLEAN)}
            attributes_schema = {}

        env = environment.Environment()
        resource._register_class('DummyResource', DummyResource)
        env.load({'resource_registry':
                  {'DummyResource': 'test_resource.template',
                   'resources': {'foo': {'DummyResource': 'foo.template'}}}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(
                                 {'HeatTemplateFormatVersion': '2012-12-12'},
                                 files=files, env=env),
                             stack_id=str(uuid.uuid4()))

        definition = rsrc_defn.ResourceDefinition('foo',
                                                  'DummyResource',
                                                  {"Foo": "False"})
        temp_res = template_resource.TemplateResource('foo',
                                                      definition, stack)
        self.assertEqual('foo.template',
                         temp_res.template_name)

    def test_get_error_for_invalid_template_name(self):
        # assertion: if the name matches {.yaml|.template} and is valid
        # we get the TemplateResource class, otherwise error will be raised.
        env_str = {'resource_registry': {'resources': {'fred': {
            "OS::ResourceType": "some_magic.yaml"}}}}
        env = environment.Environment(env_str)
        ex = self.assertRaises(exception.NotFound, env.get_class,
                               'OS::ResourceType', 'fred')
        self.assertIn('Could not fetch remote template "some_magic.yaml"',
                      six.text_type(ex))

    def test_metadata_update_called(self):
        provider = {
            'HeatTemplateFormatVersion': '2012-12-12',
            'Parameters': {
                'Foo': {'Type': 'Boolean'},
            },
        }
        files = {'test_resource.template': json.dumps(provider)}

        class DummyResource(object):
            support_status = support.SupportStatus()
            properties_schema = {"Foo":
                                 properties.Schema(properties.Schema.BOOLEAN)}
            attributes_schema = {}

        env = environment.Environment()
        resource._register_class('DummyResource', DummyResource)
        env.load({'resource_registry':
                  {'DummyResource': 'test_resource.template'}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(
                                 {'HeatTemplateFormatVersion': '2012-12-12'},
                                 files=files, env=env),
                             stack_id=str(uuid.uuid4()))

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  "DummyResource",
                                                  {"Foo": "False"})
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition, stack)
        temp_res.metadata_set = mock.Mock()
        temp_res.metadata_update()
        temp_res.metadata_set.assert_called_once_with({})

    def test_get_template_resource_class(self):
        test_templ_name = 'file:///etc/heatr/frodo.yaml'
        minimal_temp = json.dumps({'HeatTemplateFormatVersion': '2012-12-12',
                                   'Parameters': {},
                                   'Resources': {}})
        self.m.StubOutWithMock(urlfetch, "get")
        urlfetch.get(test_templ_name,
                     allowed_schemes=('file',)).AndReturn(minimal_temp)
        self.m.ReplayAll()

        env_str = {'resource_registry': {'resources': {'fred': {
            "OS::ResourceType": test_templ_name}}}}
        env = environment.Environment(env_str)
        cls = env.get_class('OS::ResourceType', 'fred')
        self.assertNotEqual(template_resource.TemplateResource, cls)
        self.assertTrue(issubclass(cls, template_resource.TemplateResource))
        self.assertTrue(hasattr(cls, "properties_schema"))
        self.assertTrue(hasattr(cls, "attributes_schema"))
        self.m.VerifyAll()

    def test_template_as_resource(self):
        """
        Test that the resulting resource has the right prop and attrib schema.

        Note that this test requires the Wordpress_Single_Instance.yaml
        template in the templates directory since we want to test using a
        non-trivial template.
        """
        test_templ_name = "WordPress_Single_Instance.yaml"
        path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                            'templates', test_templ_name)
        # check if its in the directory list vs. exists to work around
        # case-insensitive file systems
        self.assertIn(test_templ_name, os.listdir(os.path.dirname(path)))
        with open(path) as test_templ_file:
            test_templ = test_templ_file.read()
        self.assertTrue(test_templ, "Empty test template")
        self.m.StubOutWithMock(urlfetch, "get")
        urlfetch.get(test_templ_name,
                     allowed_schemes=('file',)
                     ).AndRaise(urlfetch.URLFetchError(
                         _('Failed to retrieve template')))
        urlfetch.get(test_templ_name,
                     allowed_schemes=('http', 'https')).AndReturn(test_templ)
        parsed_test_templ = template_format.parse(test_templ)
        self.m.ReplayAll()

        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(empty_template),
                             stack_id=str(uuid.uuid4()))

        properties = {
            "KeyName": "mykeyname",
            "DBName": "wordpress1",
            "DBUsername": "wpdbuser",
            "DBPassword": "wpdbpass",
            "DBRootPassword": "wpdbrootpass",
            "LinuxDistribution": "U10"
        }
        definition = rsrc_defn.ResourceDefinition("test_templ_resource",
                                                  test_templ_name,
                                                  properties)
        templ_resource = resource.Resource("test_templ_resource", definition,
                                           stack)
        self.m.VerifyAll()
        self.assertIsInstance(templ_resource,
                              template_resource.TemplateResource)
        for prop in parsed_test_templ.get("Parameters", {}):
            self.assertIn(prop, templ_resource.properties)
        for attrib in parsed_test_templ.get("Outputs", {}):
            self.assertIn(attrib, templ_resource.attributes)
        for k, v in properties.items():
            self.assertEqual(v, templ_resource.properties[k])
        self.assertEqual(
            {'WordPress_Single_Instance.yaml':
             'WordPress_Single_Instance.yaml', 'resources': {}},
            stack.env.user_env_as_dict()["resource_registry"])
        self.assertNotIn('WordPress_Single_Instance.yaml',
                         resources.global_env().registry._registry)

    def test_persisted_unregistered_provider_templates(self):
        """
        Test that templates persisted in the database prior to
        https://review.openstack.org/#/c/79953/1 are registered correctly.
        """
        env = {'resource_registry': {'http://example.com/test.template': None,
                                     'resources': {}}}
        # A KeyError will be thrown prior to this fix.
        environment.Environment(env=env)

    def test_system_template_retrieve_by_file(self):
        # make sure that a TemplateResource defined in the global environment
        # can be created and the template retrieved using the "file:"
        # scheme.
        g_env = resources.global_env()
        test_templ_name = 'file:///etc/heatr/frodo.yaml'
        g_env.load({'resource_registry':
                   {'Test::Frodo': test_templ_name}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(empty_template),
                             stack_id=str(uuid.uuid4()))

        minimal_temp = json.dumps({'HeatTemplateFormatVersion': '2012-12-12',
                                   'Parameters': {},
                                   'Resources': {}})
        self.m.StubOutWithMock(urlfetch, "get")
        urlfetch.get(test_templ_name,
                     allowed_schemes=('http', 'https',
                                      'file')).AndReturn(minimal_temp)
        self.m.ReplayAll()

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  'Test::Frodo')
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition,
                                                      stack)
        self.assertIsNone(temp_res.validate())
        self.m.VerifyAll()

    def test_user_template_not_retrieved_by_file(self):
        # make sure that a TemplateResource defined in the user environment
        # can NOT be retrieved using the "file:" scheme, validation should fail
        env = environment.Environment()
        test_templ_name = 'file:///etc/heatr/flippy.yaml'
        env.load({'resource_registry':
                  {'Test::Flippy': test_templ_name}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(empty_template, env=env),
                             stack_id=str(uuid.uuid4()))

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  'Test::Flippy')
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition,
                                                      stack)

        self.assertRaises(exception.StackValidationFailed, temp_res.validate)

    def test_system_template_retrieve_fail(self):
        # make sure that a TemplateResource defined in the global environment
        # fails gracefully if the template file specified is inaccessible
        # we should be able to create the TemplateResource object, but
        # validation should fail, when the second attempt to access it is
        # made in validate()
        g_env = resources.global_env()
        test_templ_name = 'file:///etc/heatr/frodo.yaml'
        g_env.load({'resource_registry':
                   {'Test::Frodo': test_templ_name}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(empty_template),
                             stack_id=str(uuid.uuid4()))

        self.m.StubOutWithMock(urlfetch, "get")
        urlfetch.get(test_templ_name,
                     allowed_schemes=('http', 'https',
                                      'file')
                     ).AndRaise(urlfetch.URLFetchError(
                         _('Failed to retrieve template')))
        self.m.ReplayAll()

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  'Test::Frodo')
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition,
                                                      stack)
        self.assertRaises(exception.StackValidationFailed, temp_res.validate)
        self.m.VerifyAll()

    def test_user_template_retrieve_fail(self):
        # make sure that a TemplateResource defined in the user environment
        # fails gracefully if the template file specified is inaccessible
        # we should be able to create the TemplateResource object, but
        # validation should fail, when the second attempt to access it is
        # made in validate()
        env = environment.Environment()
        test_templ_name = 'http://heatr/noexist.yaml'
        env.load({'resource_registry':
                  {'Test::Flippy': test_templ_name}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(empty_template, env=env),
                             stack_id=str(uuid.uuid4()))

        self.m.StubOutWithMock(urlfetch, "get")
        urlfetch.get(test_templ_name,
                     allowed_schemes=('http', 'https')
                     ).AndRaise(urlfetch.URLFetchError(
                         _('Failed to retrieve template')))
        self.m.ReplayAll()

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  'Test::Flippy')
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition,
                                                      stack)
        self.assertRaises(exception.StackValidationFailed, temp_res.validate)
        self.m.VerifyAll()

    def test_user_template_retrieve_fail_ext(self):
        # make sure that a TemplateResource defined in the user environment
        # fails gracefully if the template file is the wrong extension
        # we should be able to create the TemplateResource object, but
        # validation should fail, when the second attempt to access it is
        # made in validate()
        env = environment.Environment()
        test_templ_name = 'http://heatr/letter_to_granny.docx'
        env.load({'resource_registry':
                  {'Test::Flippy': test_templ_name}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(empty_template, env=env),
                             stack_id=str(uuid.uuid4()))

        self.m.ReplayAll()

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  'Test::Flippy')
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition,
                                                      stack)
        self.assertRaises(exception.StackValidationFailed, temp_res.validate)
        self.m.VerifyAll()

    def test_incorrect_template_provided_with_url(self):
        wrong_template = '''
         <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#
        '''

        env = environment.Environment()
        test_templ_name = 'http://heatr/bad_tmpl.yaml'
        env.load({'resource_registry':
                  {'Test::Tmpl': test_templ_name}})
        stack = parser.Stack(utils.dummy_context(), 'test_stack',
                             parser.Template(empty_template, env=env),
                             stack_id=str(uuid.uuid4()))

        self.m.StubOutWithMock(urlfetch, "get")
        urlfetch.get(test_templ_name,
                     allowed_schemes=('http', 'https')
                     ).AndReturn(wrong_template)
        self.m.ReplayAll()

        definition = rsrc_defn.ResourceDefinition('test_t_res',
                                                  'Test::Tmpl')
        temp_res = template_resource.TemplateResource('test_t_res',
                                                      definition,
                                                      stack)
        err = self.assertRaises(exception.StackValidationFailed,
                                temp_res.validate)
        self.assertIn('Error parsing template: ', six.text_type(err))

        self.m.VerifyAll()


class TemplateResourceCrudTest(common.HeatTestCase):
    provider = {
        'HeatTemplateFormatVersion': '2012-12-12',
        'Parameters': {
            'Foo': {'Type': 'String'},
            'Blarg': {'Type': 'String', 'Default': 'wibble'},
        },
    }

    def setUp(self):
        super(TemplateResourceCrudTest, self).setUp()
        files = {'test_resource.template': json.dumps(self.provider)}
        self.ctx = utils.dummy_context()

        class DummyResource(object):
            support_status = support.SupportStatus()
            properties_schema = {"Foo":
                                 properties.Schema(properties.Schema.STRING,
                                                   required=True)}
            attributes_schema = {}

        env = environment.Environment()
        resource._register_class('DummyResource', DummyResource)
        env.load({'resource_registry':
                  {'DummyResource': 'test_resource.template'}})
        stack = parser.Stack(self.ctx, 'test_stack',
                             parser.Template(empty_template, files=files,
                                             env=env),
                             stack_id=str(uuid.uuid4()))

        self.defn = rsrc_defn.ResourceDefinition('test_t_res',
                                                 "DummyResource",
                                                 {"Foo": "bar"})
        self.res = template_resource.TemplateResource('test_t_res',
                                                      self.defn, stack)
        self.assertIsNone(self.res.validate())

    def test_handle_create(self):
        self.res.create_with_template = mock.Mock(return_value=None)

        self.res.handle_create()

        self.res.create_with_template.assert_called_once_with(
            self.provider, {'Foo': 'bar'})

    def test_handle_adopt(self):
        self.res.create_with_template = mock.Mock(return_value=None)

        self.res.handle_adopt(resource_data={'resource_id': 'fred'})

        self.res.create_with_template.assert_called_once_with(
            self.provider, {'Foo': 'bar'}, adopt_data={'resource_id': 'fred'})

    def test_handle_update(self):
        self.res.update_with_template = mock.Mock(return_value=None)

        self.res.handle_update(self.defn, None, None)

        self.res.update_with_template.assert_called_once_with(
            self.provider, {'Foo': 'bar'})

    def test_handle_delete(self):
        self.res.rpc_client = mock.MagicMock()
        self.res.id = 55
        self.res.uuid = six.text_type(uuid.uuid4())
        self.res.resource_id = six.text_type(uuid.uuid4())
        self.res.action = self.res.CREATE
        ident = identifier.HeatIdentifier(self.ctx.tenant_id,
                                          self.res.physical_resource_name(),
                                          self.res.resource_id)
        self.res.handle_delete()
        rpcc = self.res.rpc_client.return_value
        rpcc.delete_stack.assert_called_once_with(self.ctx, ident)
