#
#    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 six
import uuid
import yaml

import mock

from heat.common import exception
from heat.common.i18n import _
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'},
                '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"},
                "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"],
            "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 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_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_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 NestedProvider(common.HeatTestCase):
    """Prove that we can use the registry in a nested provider."""

    def setUp(self):
        super(NestedProvider, self).setUp()
        utils.setup_dummy_db()

    def test_nested_env(self):
        main_templ = '''
heat_template_version: 2013-05-23
resources:
  secret2:
    type: My::NestedSecret
outputs:
  secret1:
    value: { get_attr: [secret1, value] }
'''

        nested_templ = '''
heat_template_version: 2013-05-23
resources:
  secret2:
    type: My::Secret
outputs:
  value:
    value: { get_attr: [secret2, value] }
'''

        env_templ = '''
resource_registry:
  "My::Secret": "OS::Heat::RandomString"
  "My::NestedSecret": nested.yaml
'''

        env = environment.Environment()
        env.load(yaml.load(env_templ))
        templ = parser.Template(template_format.parse(main_templ),
                                files={'nested.yaml': nested_templ})
        stack = parser.Stack(utils.dummy_context(),
                             utils.random_name(),
                             templ, env=env)
        stack.store()
        stack.create()
        self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state)

    def test_no_infinite_recursion(self):
        """Prove that we can override a python resource.

        And use that resource within the template resource.
        """

        main_templ = '''
heat_template_version: 2013-05-23
resources:
  secret2:
    type: OS::Heat::RandomString
outputs:
  secret1:
    value: { get_attr: [secret1, value] }
'''

        nested_templ = '''
heat_template_version: 2013-05-23
resources:
  secret2:
    type: OS::Heat::RandomString
outputs:
  value:
    value: { get_attr: [secret2, value] }
'''

        env_templ = '''
resource_registry:
  "OS::Heat::RandomString": nested.yaml
'''

        env = environment.Environment()
        env.load(yaml.load(env_templ))
        templ = parser.Template(template_format.parse(main_templ),
                                files={'nested.yaml': nested_templ})
        stack = parser.Stack(utils.dummy_context(),
                             utils.random_name(),
                             templ, env=env)
        stack.store()
        stack.create()
        self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state)


class NestedAttributes(common.HeatTestCase):
    """Prove that we can use the template resource references."""

    main_templ = '''
heat_template_version: 2014-10-16
resources:
  secret2:
    type: My::NestedSecret
outputs:
  old_way:
    value: { get_attr: [secret2, nested_str]}
  test_attr1:
    value: { get_attr: [secret2, resource.secret1, value]}
  test_attr2:
    value: { get_attr: [secret2, resource.secret1.value]}
  test_ref:
    value: { get_resource: secret2 }
'''

    env_templ = '''
resource_registry:
  "My::NestedSecret": nested.yaml
'''

    def setUp(self):
        super(NestedAttributes, self).setUp()
        utils.setup_dummy_db()

    def _create_dummy_stack(self, nested_templ):
        env = environment.Environment()
        env.load(yaml.load(self.env_templ))
        templ = parser.Template(template_format.parse(self.main_templ),
                                files={'nested.yaml': nested_templ})
        stack = parser.Stack(utils.dummy_context(),
                             utils.random_name(),
                             templ, env=env)
        stack.store()
        stack.create()
        self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state)
        return stack

    def test_stack_ref(self):
        nested_templ = '''
heat_template_version: 2014-10-16
resources:
  secret1:
    type: OS::Heat::RandomString
'''
        stack = self._create_dummy_stack(nested_templ)
        test_ref = stack.output('test_ref')
        self.assertIn('arn:openstack:heat:', test_ref)

    def test_transparent_ref(self):
        """With the addition of OS::stack_id we can now use the nested resource
        more transparently.
        """
        nested_templ = '''
heat_template_version: 2014-10-16
resources:
  secret1:
    type: OS::Heat::RandomString
outputs:
  OS::stack_id:
    value: {get_resource: secret1}
  nested_str:
    value: {get_attr: [secret1, value]}
'''
        stack = self._create_dummy_stack(nested_templ)
        test_ref = stack.output('test_ref')
        test_attr = stack.output('old_way')

        self.assertNotIn('arn:openstack:heat', test_ref)
        self.assertEqual(test_attr, test_ref)

    def test_nested_attributes(self):
        nested_templ = '''
heat_template_version: 2014-10-16
resources:
  secret1:
    type: OS::Heat::RandomString
outputs:
  nested_str:
    value: {get_attr: [secret1, value]}
'''
        stack = self._create_dummy_stack(nested_templ)
        old_way = stack.output('old_way')
        test_attr1 = stack.output('test_attr1')
        test_attr2 = stack.output('test_attr2')

        self.assertEqual(old_way, test_attr1)
        self.assertEqual(old_way, test_attr2)


class ProviderTemplateUpdateTest(common.HeatTestCase):
    main_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
  the_nested:
    Type: the.yaml
    Properties:
      one: my_name

Outputs:
  identifier:
    Value: {Ref: the_nested}
  value:
    Value: {'Fn::GetAtt': [the_nested, the_str]}
'''

    main_template_2 = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
  the_nested:
    Type: the.yaml
    Properties:
      one: updated_name

Outputs:
  identifier:
    Value: {Ref: the_nested}
  value:
    Value: {'Fn::GetAtt': [the_nested, the_str]}
'''

    initial_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
  one:
    Default: foo
    Type: String
Resources:
  NestedResource:
    Type: OS::Heat::RandomString
    Properties:
      salt: {Ref: one}
Outputs:
  the_str:
    Value: {'Fn::GetAtt': [NestedResource, value]}
'''
    prop_change_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
  one:
    Default: yikes
    Type: String
  two:
    Default: foo
    Type: String
Resources:
  NestedResource:
    Type: OS::Heat::RandomString
    Properties:
      salt: {Ref: one}
Outputs:
  the_str:
    Value: {'Fn::GetAtt': [NestedResource, value]}
'''
    attr_change_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
  one:
    Default: foo
    Type: String
Resources:
  NestedResource:
    Type: OS::Heat::RandomString
    Properties:
      salt: {Ref: one}
Outputs:
  the_str:
    Value: {'Fn::GetAtt': [NestedResource, value]}
  something_else:
    Value: just_a_string
'''
    content_change_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
  one:
    Default: foo
    Type: String
Resources:
  NestedResource:
    Type: OS::Heat::RandomString
    Properties:
      salt: yum
Outputs:
  the_str:
    Value: {'Fn::GetAtt': [NestedResource, value]}
'''

    EXPECTED = (UPDATE, NOCHANGE) = ('update', 'nochange')
    scenarios = [
        ('no_changes', dict(template=main_template,
                            provider=initial_tmpl,
                            expect=NOCHANGE)),
        ('main_tmpl_change', dict(template=main_template_2,
                                  provider=initial_tmpl,
                                  expect=UPDATE)),
        ('provider_change', dict(template=main_template,
                                 provider=content_change_tmpl,
                                 expect=UPDATE)),
        ('provider_props_change', dict(template=main_template,
                                       provider=prop_change_tmpl,
                                       expect=NOCHANGE)),
        ('provider_attr_change', dict(template=main_template,
                                      provider=attr_change_tmpl,
                                      expect=NOCHANGE)),
    ]

    def setUp(self):
        super(ProviderTemplateUpdateTest, self).setUp()
        self.ctx = utils.dummy_context('test_username', 'aaaa', 'password')

    def create_stack(self):
        t = template_format.parse(self.main_template)
        tmpl = parser.Template(t, files={'the.yaml': self.initial_tmpl})
        stack = parser.Stack(self.ctx, utils.random_name(), tmpl)
        stack.store()
        stack.create()
        self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state)
        return stack

    def test_template_resource_update_template_schema(self):
        stack = self.create_stack()
        self.stack = stack
        initial_id = stack.output('identifier')
        initial_val = stack.output('value')
        tmpl = parser.Template(template_format.parse(self.template),
                               files={'the.yaml': self.provider})
        updated_stack = parser.Stack(self.ctx, stack.name, tmpl)
        stack.update(updated_stack)
        self.assertEqual(('UPDATE', 'COMPLETE'), stack.state)
        self.assertEqual(initial_id,
                         stack.output('identifier'))
        if self.expect == self.NOCHANGE:
            self.assertEqual(initial_val,
                             stack.output('value'))
        else:
            self.assertNotEqual(initial_val,
                                stack.output('value'))
        self.m.VerifyAll()


class ProviderTemplateAdoptTest(common.HeatTestCase):
    main_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
  the_nested:
    Type: the.yaml
    Properties:
      one: my_name

Outputs:
  identifier:
    Value: {Ref: the_nested}
  value:
    Value: {'Fn::GetAtt': [the_nested, the_str]}
'''

    nested_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
  one:
    Default: foo
    Type: String
Resources:
  RealRandom:
    Type: OS::Heat::RandomString
    Properties:
      salt: {Ref: one}
Outputs:
  the_str:
    Value: {'Fn::GetAtt': [RealRandom, value]}
'''

    def setUp(self):
        super(ProviderTemplateAdoptTest, self).setUp()
        self.ctx = utils.dummy_context('test_username', 'aaaa', 'password')

    def _yaml_to_json(self, yaml_templ):
        return yaml.load(yaml_templ)

    def test_abandon(self):
        t = template_format.parse(self.main_template)
        tmpl = parser.Template(t, files={'the.yaml': self.nested_tmpl})
        stack = parser.Stack(self.ctx, utils.random_name(), tmpl)
        stack.store()
        stack.create()
        self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state)
        info = stack.prepare_abandon()
        self.assertEqual(self._yaml_to_json(self.main_template),
                         info['template'])
        self.assertEqual(self._yaml_to_json(self.nested_tmpl),
                         info['resources']['the_nested']['template'])
        self.m.VerifyAll()

    def test_adopt(self):
        data = {'resources': {u'the_nested': {
            'resources': {u'RealRandom': {
                'resource_data': {
                    u'value': u'N8hE5C7ijdGn4RwnuygbAokGHnTq4cFJ'},
                'resource_id': 'N8hE5C7ijdGn4RwnuygbAokGHnTq4cFJ'}}}}}

        t = template_format.parse(self.main_template)
        tmpl = parser.Template(t, files={'the.yaml': self.nested_tmpl})
        stack = parser.Stack(self.ctx, utils.random_name(), tmpl,
                             adopt_stack_data=data)
        self.stack = stack
        stack.store()
        stack.adopt()

        self.assertEqual(('ADOPT', 'COMPLETE'), stack.state)
        nested_res = data['resources']['the_nested']['resources']
        self.assertEqual(nested_res['RealRandom']['resource_data']['value'],
                         stack.output('value'))

        self.m.VerifyAll()
