Merge "Fix database purge query"
diff --git a/functional/test_autoscaling.py b/functional/test_autoscaling.py
index df8c54b..ebc7f61 100644
--- a/functional/test_autoscaling.py
+++ b/functional/test_autoscaling.py
@@ -631,16 +631,16 @@
         md = self.client.resources.metadata(stack_identifier, 'custom_lb')
         actual_md = len(md['IPs'].split(','))
         if actual_md != expected:
-            LOG.warn('check_instance_count exp:%d, meta:%s' % (expected,
-                                                               md['IPs']))
+            LOG.warning('check_instance_count exp:%d, meta:%s' % (expected,
+                                                                  md['IPs']))
             return False
 
         stack = self.client.stacks.get(stack_identifier)
         inst_list = self._stack_output(stack, 'InstanceList')
         actual = len(inst_list.split(','))
         if actual != expected:
-            LOG.warn('check_instance_count exp:%d, act:%s' % (expected,
-                                                              inst_list))
+            LOG.warning('check_instance_count exp:%d, act:%s' % (expected,
+                                                                 inst_list))
         return actual == expected
 
     def test_scaling_meta_update(self):
@@ -729,7 +729,7 @@
         self._wait_for_resource_status(
             stack_identifier, 'JobServerGroup', 'SUSPEND_COMPLETE')
 
-        # Send a signal and a exception will raise
+        # Send a signal and an exception will raise
         ex = self.assertRaises(exc.BadRequest,
                                self.client.resources.signal,
                                stack_identifier, 'ScaleUpPolicy')
diff --git a/functional/test_aws_stack.py b/functional/test_aws_stack.py
index ba4883f..678ce7e 100644
--- a/functional/test_aws_stack.py
+++ b/functional/test_aws_stack.py
@@ -15,7 +15,7 @@
 import random
 
 from oslo_log import log as logging
-import requests
+
 from six.moves.urllib import parse
 from swiftclient import utils as swiftclient_utils
 import yaml
@@ -76,49 +76,36 @@
 
     def setUp(self):
         super(AwsStackTest, self).setUp()
-        self.object_container_name = AwsStackTest.__name__
+        self.object_container_name = test.rand_name()
         self.project_id = self.identity_client.auth_ref.project_id
-        self.object_client.put_container(self.object_container_name)
-        self.nested_name = '%s.yaml' % test.rand_name()
+        self.swift_key = hashlib.sha224(
+            str(random.getrandbits(256))).hexdigest()[:32]
+        key_header = 'x-container-meta-temp-url-key'
+        self.object_client.put_container(self.object_container_name,
+                                         {key_header: self.swift_key})
+        self.addCleanup(self.object_client.delete_container,
+                        self.object_container_name)
 
-    def publish_template(self, name, contents):
+    def publish_template(self, contents, cleanup=True):
         oc = self.object_client
 
         # post the object
-        oc.put_object(self.object_container_name, name, contents)
-        # TODO(asalkeld) see if this is causing problems.
-        # self.addCleanup(self.object_client.delete_object,
-        #                self.object_container_name, name)
-
-        # make the tempurl
-        key_header = 'x-account-meta-temp-url-key'
-        if key_header not in oc.head_account():
-            swift_key = hashlib.sha224(
-                str(random.getrandbits(256))).hexdigest()[:32]
-            LOG.warn('setting swift key to %s' % swift_key)
-            oc.post_account({key_header: swift_key})
-        key = oc.head_account()[key_header]
+        oc.put_object(self.object_container_name, 'template.yaml', contents)
+        if cleanup:
+            self.addCleanup(oc.delete_object,
+                            self.object_container_name,
+                            'template.yaml')
         path = '/v1/AUTH_%s/%s/%s' % (self.project_id,
-                                      self.object_container_name, name)
+                                      self.object_container_name,
+                                      'template.yaml')
         timeout = self.conf.build_timeout * 10
         tempurl = swiftclient_utils.generate_temp_url(path, timeout,
-                                                      key, 'GET')
+                                                      self.swift_key, 'GET')
         sw_url = parse.urlparse(oc.url)
-        full_url = '%s://%s%s' % (sw_url.scheme, sw_url.netloc, tempurl)
-
-        def download():
-            r = requests.get(full_url)
-            LOG.info('GET: %s -> %s' % (full_url, r.status_code))
-            return r.status_code == requests.codes.ok
-
-        # make sure that the object is available.
-        test.call_until_true(self.conf.build_timeout,
-                             self.conf.build_interval, download)
-
-        return full_url
+        return '%s://%s%s' % (sw_url.scheme, sw_url.netloc, tempurl)
 
     def test_nested_stack_create(self):
-        url = self.publish_template(self.nested_name, self.nested_template)
+        url = self.publish_template(self.nested_template)
         self.template = self.test_template.replace('the.yaml', url)
         stack_identifier = self.stack_create(template=self.template)
         stack = self.client.stacks.get(stack_identifier)
@@ -126,7 +113,7 @@
         self.assertEqual('bar', self._stack_output(stack, 'output_foo'))
 
     def test_nested_stack_create_with_timeout(self):
-        url = self.publish_template(self.nested_name, self.nested_template)
+        url = self.publish_template(self.nested_template)
         self.template = self.test_template.replace('the.yaml', url)
         timeout_template = yaml.load(self.template)
         props = timeout_template['Resources']['the_nested']['Properties']
@@ -139,8 +126,7 @@
         self.assertEqual('bar', self._stack_output(stack, 'output_foo'))
 
     def test_nested_stack_adopt_ok(self):
-        url = self.publish_template(self.nested_name,
-                                    self.nested_with_res_template)
+        url = self.publish_template(self.nested_with_res_template)
         self.template = self.test_template.replace('the.yaml', url)
         adopt_data = {
             "resources": {
@@ -166,8 +152,7 @@
         self.assertEqual('goopie', self._stack_output(stack, 'output_foo'))
 
     def test_nested_stack_adopt_fail(self):
-        url = self.publish_template(self.nested_name,
-                                    self.nested_with_res_template)
+        url = self.publish_template(self.nested_with_res_template)
         self.template = self.test_template.replace('the.yaml', url)
         adopt_data = {
             "resources": {
@@ -187,7 +172,7 @@
         self.assertEqual('ADOPT_FAILED', rsrc.resource_status)
 
     def test_nested_stack_update(self):
-        url = self.publish_template(self.nested_name, self.nested_template)
+        url = self.publish_template(self.nested_template)
         self.template = self.test_template.replace('the.yaml', url)
         stack_identifier = self.stack_create(template=self.template)
         original_nested_id = self.assert_resource_is_a_stack(
@@ -197,8 +182,8 @@
 
         new_template = yaml.load(self.template)
         props = new_template['Resources']['the_nested']['Properties']
-        props['TemplateURL'] = self.publish_template(self.nested_name,
-                                                     self.update_template)
+        props['TemplateURL'] = self.publish_template(self.update_template,
+                                                     cleanup=False)
 
         self.update_stack(stack_identifier, new_template)
 
@@ -211,7 +196,7 @@
         self.assertEqual('foo', self._stack_output(updt_stack, 'output_foo'))
 
     def test_nested_stack_suspend_resume(self):
-        url = self.publish_template(self.nested_name, self.nested_template)
+        url = self.publish_template(self.nested_template)
         self.template = self.test_template.replace('the.yaml', url)
         stack_identifier = self.stack_create(template=self.template)
         self.stack_suspend(stack_identifier)
diff --git a/functional/test_create_update_neutron_subnet.py b/functional/test_create_update_neutron_subnet.py
index 6dad7f2..ceb74a9 100644
--- a/functional/test_create_update_neutron_subnet.py
+++ b/functional/test_create_update_neutron_subnet.py
@@ -24,10 +24,13 @@
     properties:
       network: { get_resource: net }
       cidr: 11.11.11.0/24
+      gateway_ip: 11.11.11.5
       allocation_pools: [{start: 11.11.11.10, end: 11.11.11.250}]
 outputs:
   alloc_pools:
     value: {get_attr: [subnet, allocation_pools]}
+  gateway_ip:
+    value: {get_attr: [subnet, gateway_ip]}
 '''
 
 
@@ -36,14 +39,14 @@
     def setUp(self):
         super(UpdateSubnetTest, self).setUp()
 
-    def get_alloc_pools(self, stack_identifier):
+    def get_outputs(self, stack_identifier, output_key):
         stack = self.client.stacks.get(stack_identifier)
-        alloc_pools = self._stack_output(stack, 'alloc_pools')
-        return alloc_pools
+        output = self._stack_output(stack, output_key)
+        return output
 
     def test_update_allocation_pools(self):
         stack_identifier = self.stack_create(template=test_template)
-        alloc_pools = self.get_alloc_pools(stack_identifier)
+        alloc_pools = self.get_outputs(stack_identifier, 'alloc_pools')
         self.assertEqual([{'start': '11.11.11.10', 'end': '11.11.11.250'}],
                          alloc_pools)
 
@@ -52,14 +55,14 @@
             'allocation_pools: [{start: 11.11.11.10, end: 11.11.11.250}]',
             'allocation_pools: [{start: 11.11.11.10, end: 11.11.11.100}]')
         self.update_stack(stack_identifier, templ_other_pool)
-        new_alloc_pools = self.get_alloc_pools(stack_identifier)
+        new_alloc_pools = self.get_outputs(stack_identifier, 'alloc_pools')
         # the new pools should be the new range
         self.assertEqual([{'start': '11.11.11.10', 'end': '11.11.11.100'}],
                          new_alloc_pools)
 
     def test_update_allocation_pools_to_empty(self):
         stack_identifier = self.stack_create(template=test_template)
-        alloc_pools = self.get_alloc_pools(stack_identifier)
+        alloc_pools = self.get_outputs(stack_identifier, 'alloc_pools')
         self.assertEqual([{'start': '11.11.11.10', 'end': '11.11.11.250'}],
                          alloc_pools)
 
@@ -68,13 +71,13 @@
             'allocation_pools: [{start: 11.11.11.10, end: 11.11.11.250}]',
             'allocation_pools: []')
         self.update_stack(stack_identifier, templ_empty_pools)
-        new_alloc_pools = self.get_alloc_pools(stack_identifier)
+        new_alloc_pools = self.get_outputs(stack_identifier, 'alloc_pools')
         # new_alloc_pools should be []
         self.assertEqual([], new_alloc_pools)
 
     def test_update_to_no_allocation_pools(self):
         stack_identifier = self.stack_create(template=test_template)
-        alloc_pools = self.get_alloc_pools(stack_identifier)
+        alloc_pools = self.get_outputs(stack_identifier, 'alloc_pools')
         self.assertEqual([{'start': '11.11.11.10', 'end': '11.11.11.250'}],
                          alloc_pools)
 
@@ -83,6 +86,45 @@
             'allocation_pools: [{start: 11.11.11.10, end: 11.11.11.250}]',
             '')
         self.update_stack(stack_identifier, templ_no_pools)
-        last_alloc_pools = self.get_alloc_pools(stack_identifier)
+        last_alloc_pools = self.get_outputs(stack_identifier, 'alloc_pools')
         # last_alloc_pools should be []
         self.assertEqual([], last_alloc_pools)
+
+    def test_update_gateway_ip(self):
+        stack_identifier = self.stack_create(template=test_template)
+        gw_ip = self.get_outputs(stack_identifier, 'gateway_ip')
+        self.assertEqual('11.11.11.5', gw_ip)
+
+        # Update gateway_ip
+        templ_other_gw_ip = test_template.replace(
+            'gateway_ip: 11.11.11.5', 'gateway_ip: 11.11.11.9')
+        self.update_stack(stack_identifier, templ_other_gw_ip)
+        new_gw_ip = self.get_outputs(stack_identifier, 'gateway_ip')
+        # the gateway_ip should be the new one
+        self.assertEqual('11.11.11.9', new_gw_ip)
+
+    def test_update_gateway_ip_to_empty(self):
+        stack_identifier = self.stack_create(template=test_template)
+        gw_ip = self.get_outputs(stack_identifier, 'gateway_ip')
+        self.assertEqual('11.11.11.5', gw_ip)
+
+        # Update gateway_ip to null(resolve to '')
+        templ_empty_gw_ip = test_template.replace(
+            'gateway_ip: 11.11.11.5', 'gateway_ip: null')
+        self.update_stack(stack_identifier, templ_empty_gw_ip)
+        new_gw_ip = self.get_outputs(stack_identifier, 'gateway_ip')
+        # new gateway_ip should be None
+        self.assertIsNone(new_gw_ip)
+
+    def test_update_to_no_gateway_ip(self):
+        stack_identifier = self.stack_create(template=test_template)
+        gw_ip = self.get_outputs(stack_identifier, 'gateway_ip')
+        self.assertEqual('11.11.11.5', gw_ip)
+
+        # Remove the gateway from template
+        templ_no_gw_ip = test_template.replace(
+            'gateway_ip: 11.11.11.5', '')
+        self.update_stack(stack_identifier, templ_no_gw_ip)
+        new_gw_ip = self.get_outputs(stack_identifier, 'gateway_ip')
+        # new gateway_ip should be None
+        self.assertIsNone(new_gw_ip)
diff --git a/functional/test_hooks.py b/functional/test_hooks.py
index ce1e7aa..d9ebd44 100644
--- a/functional/test_hooks.py
+++ b/functional/test_hooks.py
@@ -10,16 +10,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
-
 import yaml
 
 from heat_integrationtests.functional import functional_base
 
 
-LOG = logging.getLogger(__name__)
-
-
 class HooksTest(functional_base.FunctionalTestsBase):
 
     def setUp(self):
diff --git a/functional/test_resource_chain.py b/functional/test_resource_chain.py
new file mode 100644
index 0000000..c422a26
--- /dev/null
+++ b/functional/test_resource_chain.py
@@ -0,0 +1,148 @@
+#
+#    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 heat_integrationtests.functional import functional_base
+
+
+TEMPLATE_SIMPLE = '''
+heat_template_version: 2016-04-08
+parameters:
+  string-length:
+    type: number
+resources:
+  my-chain:
+    type: OS::Heat::ResourceChain
+    properties:
+      resources: ['OS::Heat::RandomString', 'OS::Heat::RandomString']
+      resource_properties:
+        length: { get_param: string-length }
+outputs:
+  resource-ids:
+    value: { get_attr: [my-chain, refs] }
+  resource-0-value:
+    value: { get_attr: [my-chain, resource.0, value] }
+  all-resource-attrs:
+    value: { get_attr: [my-chain, attributes, value] }
+'''
+
+TEMPLATE_PARAM_DRIVEN = '''
+heat_template_version: 2016-04-08
+parameters:
+  chain-types:
+    type: comma_delimited_list
+resources:
+  my-chain:
+    type: OS::Heat::ResourceChain
+    properties:
+      resources: { get_param: chain-types }
+'''
+
+
+class ResourceChainTests(functional_base.FunctionalTestsBase):
+
+    def test_create(self):
+        # Test
+        params = {'string-length': 8}
+        stack_id = self.stack_create(template=TEMPLATE_SIMPLE,
+                                     parameters=params)
+
+        # Verify
+        stack = self.client.stacks.get(stack_id)
+        self.assertTrue(stack is not None)
+
+        # Top-level resource for chain
+        expected = {'my-chain': 'OS::Heat::ResourceChain'}
+        found = self.list_resources(stack_id)
+        self.assertEqual(expected, found)
+
+        # Nested stack exists and has two resources
+        nested_id = self.group_nested_identifier(stack_id, 'my-chain')
+        expected = {'0': 'OS::Heat::RandomString',
+                    '1': 'OS::Heat::RandomString'}
+        found = self.list_resources(nested_id)
+        self.assertEqual(expected, found)
+
+        # Outputs
+        resource_ids = self._stack_output(stack, 'resource-ids')
+        self.assertTrue(resource_ids is not None)
+        self.assertEqual(2, len(resource_ids))
+
+        resource_value = self._stack_output(stack, 'resource-0-value')
+        self.assertTrue(resource_value is not None)
+        self.assertEqual(8, len(resource_value))  # from parameter
+
+        resource_attrs = self._stack_output(stack, 'all-resource-attrs')
+        self.assertTrue(resource_attrs is not None)
+        self.assertIsInstance(resource_attrs, dict)
+        self.assertEqual(2, len(resource_attrs))
+        self.assertEqual(8, len(resource_attrs['0']))
+        self.assertEqual(8, len(resource_attrs['1']))
+
+    def test_update(self):
+        # Setup
+        params = {'string-length': 8}
+        stack_id = self.stack_create(template=TEMPLATE_SIMPLE,
+                                     parameters=params)
+
+        update_tmpl = '''
+        heat_template_version: 2016-04-08
+        parameters:
+          string-length:
+            type: number
+        resources:
+          my-chain:
+            type: OS::Heat::ResourceChain
+            properties:
+              resources: ['OS::Heat::None']
+        '''
+
+        # Test
+        self.update_stack(stack_id, template=update_tmpl, parameters=params)
+
+        # Verify
+        # Nested stack only has the None resource
+        nested_id = self.group_nested_identifier(stack_id, 'my-chain')
+        expected = {'0': 'OS::Heat::None'}
+        found = self.list_resources(nested_id)
+        self.assertEqual(expected, found)
+
+    def test_resources_param_driven(self):
+        # Setup
+        params = {'chain-types':
+                  'OS::Heat::None,OS::Heat::RandomString,OS::Heat::None'}
+
+        # Test
+        stack_id = self.stack_create(template=TEMPLATE_PARAM_DRIVEN,
+                                     parameters=params)
+
+        # Verify
+        nested_id = self.group_nested_identifier(stack_id, 'my-chain')
+        expected = {'0': 'OS::Heat::None',
+                    '1': 'OS::Heat::RandomString',
+                    '2': 'OS::Heat::None'}
+        found = self.list_resources(nested_id)
+        self.assertEqual(expected, found)
+
+    def test_resources_env_defined(self):
+        # Setup
+        env = {'parameters': {'chain-types': 'OS::Heat::None'}}
+
+        # Test
+        stack_id = self.stack_create(template=TEMPLATE_PARAM_DRIVEN,
+                                     environment=env)
+
+        # Verify
+        nested_id = self.group_nested_identifier(stack_id, 'my-chain')
+        expected = {'0': 'OS::Heat::None'}
+        found = self.list_resources(nested_id)
+        self.assertEqual(expected, found)
diff --git a/functional/test_templates.py b/functional/test_templates.py
new file mode 100644
index 0000000..dfc9c4f
--- /dev/null
+++ b/functional/test_templates.py
@@ -0,0 +1,72 @@
+#    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 heat_integrationtests.functional import functional_base
+
+
+class TemplateAPITest(functional_base.FunctionalTestsBase):
+    """This will test the following template calls:
+
+    1. Get the template content for the specific stack
+    2. List template versions
+    3. List resource types
+    4. Show resource details for OS::Heat::TestResource
+    """
+
+    template = {
+        'heat_template_version': '2014-10-16',
+        'description': 'Test Template APIs',
+        'resources': {
+            'test1': {
+                'type': 'OS::Heat::TestResource',
+                'properties': {
+                    'update_replace': False,
+                    'wait_secs': 0,
+                    'value': 'Test1',
+                    'fail': False,
+                }
+            }
+        }
+    }
+
+    def setUp(self):
+        super(TemplateAPITest, self).setUp()
+
+    def test_get_stack_template(self):
+        stack_identifier = self.stack_create(
+            template=self.template
+        )
+        template_from_client = self.client.stacks.template(stack_identifier)
+        self.assertDictEqual(self.template, template_from_client)
+
+    def test_template_version(self):
+        template_versions = self.client.template_versions.list()
+        supported_template_versions = ["2013-05-23", "2014-10-16",
+                                       "2015-04-30", "2015-10-15",
+                                       "2012-12-12", "2010-09-09",
+                                       "2016-04-08"]
+        for template in template_versions:
+            self.assertIn(template.version.split(".")[1],
+                          supported_template_versions)
+
+    def test_resource_types(self):
+        resource_types = self.client.resource_types.list()
+        self.assertTrue(any(resource.resource_type == "OS::Heat::TestResource"
+                            for resource in resource_types))
+
+    def test_show_resource_template(self):
+        resource_details = self.client.resource_types.get(
+            resource_type="OS::Heat::TestResource"
+        )
+        self.assertEqual("OS::Heat::TestResource",
+                         resource_details['resource_type'])
diff --git a/scenario/test_ceilometer_alarm.py b/scenario/test_ceilometer_alarm.py
index e0523aa..aa29861 100644
--- a/scenario/test_ceilometer_alarm.py
+++ b/scenario/test_ceilometer_alarm.py
@@ -30,8 +30,8 @@
         stack = self.client.stacks.get(stack_identifier)
         actual = self._stack_output(stack, 'asg_size')
         if actual != expected:
-            LOG.warn('check_instance_count exp:%d, act:%s' % (expected,
-                                                              actual))
+            LOG.warning('check_instance_count exp:%d, act:%s' % (expected,
+                                                                 actual))
         return actual == expected
 
     def test_alarm(self):