Merge "Pass keystone session to clients"
diff --git a/common/config.py b/common/config.py
index e99d034..b3615d2 100644
--- a/common/config.py
+++ b/common/config.py
@@ -35,6 +35,9 @@
                default=(os.environ.get('OS_PROJECT_NAME') or
                         os.environ.get('OS_TENANT_NAME')),
                help="Tenant name to use for API requests."),
+    cfg.StrOpt('admin_tenant_name',
+               default='admin',
+               help="Admin tenant name to use for admin API requests."),
     cfg.StrOpt('auth_url',
                default=os.environ.get('OS_AUTH_URL'),
                help="Full URI of the OpenStack Identity API (Keystone)"),
@@ -65,7 +68,6 @@
                 default=False,
                 help="Set to True if using self-signed SSL certificates."),
     cfg.StrOpt('ca_file',
-               default=None,
                help="CA certificate to pass for servers that have "
                     "https endpoint."),
     cfg.IntOpt('build_interval',
@@ -119,7 +121,7 @@
     cfg.ListOpt('skip_scenario_test_list',
                 help="List of scenario test class or class.method "
                      "names to skip ex. NeutronLoadBalancerTest, "
-                     "CeilometerAlarmTest.test_alarm"),
+                     "AodhAlarmTest.test_alarm"),
     cfg.ListOpt('skip_test_stack_action_list',
                 help="List of stack actions in tests to skip "
                      "ex. ABANDON, ADOPT, SUSPEND, RESUME"),
diff --git a/common/test.py b/common/test.py
index 16d5920..c864d3b 100644
--- a/common/test.py
+++ b/common/test.py
@@ -81,8 +81,16 @@
                              'No username configured')
         self.assertIsNotNone(self.conf.password,
                              'No password configured')
+        self.setup_clients(self.conf)
+        self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT))
+        self.updated_time = {}
+        if self.conf.disable_ssl_certificate_validation:
+            self.verify_cert = False
+        else:
+            self.verify_cert = self.conf.ca_file or True
 
-        self.manager = clients.ClientManager(self.conf)
+    def setup_clients(self, conf):
+        self.manager = clients.ClientManager(conf)
         self.identity_client = self.manager.identity_client
         self.orchestration_client = self.manager.orchestration_client
         self.compute_client = self.manager.compute_client
@@ -90,12 +98,19 @@
         self.volume_client = self.manager.volume_client
         self.object_client = self.manager.object_client
         self.metering_client = self.manager.metering_client
-        self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT))
-        self.updated_time = {}
-        if self.conf.disable_ssl_certificate_validation:
-            self.verify_cert = False
-        else:
-            self.verify_cert = self.conf.ca_file or True
+
+        self.client = self.orchestration_client
+
+    def setup_clients_for_admin(self):
+        self.assertIsNotNone(self.conf.admin_username,
+                             'No admin username configured')
+        self.assertIsNotNone(self.conf.admin_password,
+                             'No admin password configured')
+        conf = config.init_conf()
+        conf.username = self.conf.admin_username
+        conf.password = self.conf.admin_password
+        conf.tenant_name = self.conf.admin_tenant_name
+        self.setup_clients(conf)
 
     def get_remote_client(self, server_or_ip, username, private_key=None):
         if isinstance(server_or_ip, six.string_types):
diff --git a/functional/functional_base.py b/functional/functional_base.py
index 9f76011..73ccf1d 100644
--- a/functional/functional_base.py
+++ b/functional/functional_base.py
@@ -20,7 +20,6 @@
     def setUp(self):
         super(FunctionalTestsBase, self).setUp()
         self.check_skip()
-        self.client = self.orchestration_client
 
     def check_skip(self):
         test_cls_name = reflection.get_class_name(self, fully_qualified=False)
diff --git a/functional/test_admin_actions.py b/functional/test_admin_actions.py
new file mode 100644
index 0000000..2c9ff6e
--- /dev/null
+++ b/functional/test_admin_actions.py
@@ -0,0 +1,101 @@
+#    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
+
+# Simple stack
+test_template = {
+    'heat_template_version': '2013-05-23',
+    'resources': {
+        'test1': {
+            'type': 'OS::Heat::TestResource',
+            'properties': {
+                'value': 'Test1'
+            }
+        }
+    }
+}
+
+# Nested stack
+rsg_template = {
+    'heat_template_version': '2013-05-23',
+    'resources': {
+        'random_group': {
+            'type': 'OS::Heat::ResourceGroup',
+            'properties': {
+                'count': 2,
+                'resource_def': {
+                    'type': 'OS::Heat::RandomString',
+                    'properties': {
+                        'length': 30,
+                        'salt': 'initial'
+                    }
+                }
+            }
+        }
+    }
+}
+
+
+class AdminActionsTest(functional_base.FunctionalTestsBase):
+
+    def setUp(self):
+        super(AdminActionsTest, self).setUp()
+        if not self.conf.admin_username or not self.conf.admin_password:
+            self.skipTest('No admin creds found, skipping')
+
+    def create_stack_setup_admin_client(self, template=test_template):
+        # Create the stack with the default user
+        self.stack_identifier = self.stack_create(template=template)
+
+        # Setup admin clients
+        self.setup_clients_for_admin()
+
+    def test_admin_simple_stack_actions(self):
+        self.create_stack_setup_admin_client()
+
+        updated_template = test_template.copy()
+        props = updated_template['resources']['test1']['properties']
+        props['value'] = 'new_value'
+
+        # Update, suspend and resume stack
+        self.update_stack(self.stack_identifier,
+                          template=updated_template)
+        self.stack_suspend(self.stack_identifier)
+        self.stack_resume(self.stack_identifier)
+
+        # List stack resources
+        initial_resources = {'test1': 'OS::Heat::TestResource'}
+        self.assertEqual(initial_resources,
+                         self.list_resources(self.stack_identifier))
+        # Delete stack
+        self._stack_delete(self.stack_identifier)
+
+    def test_admin_complex_stack_actions(self):
+        self.create_stack_setup_admin_client(template=rsg_template)
+
+        updated_template = rsg_template.copy()
+        props = updated_template['resources']['random_group']['properties']
+        props['count'] = 3
+
+        # Update, suspend and resume stack
+        self.update_stack(self.stack_identifier,
+                          template=updated_template)
+        self.stack_suspend(self.stack_identifier)
+        self.stack_resume(self.stack_identifier)
+
+        # List stack resources
+        resources = {'random_group': 'OS::Heat::ResourceGroup'}
+        self.assertEqual(resources,
+                         self.list_resources(self.stack_identifier))
+        # Delete stack
+        self._stack_delete(self.stack_identifier)
diff --git a/functional/test_autoscaling.py b/functional/test_autoscaling.py
index ebc7f61..e97ae89 100644
--- a/functional/test_autoscaling.py
+++ b/functional/test_autoscaling.py
@@ -726,8 +726,9 @@
 
         # suspend the top level stack.
         self.client.actions.suspend(stack_id=stack_identifier)
-        self._wait_for_resource_status(
-            stack_identifier, 'JobServerGroup', 'SUSPEND_COMPLETE')
+
+        # Wait for stack to reach SUSPEND_COMPLETE
+        self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
 
         # Send a signal and an exception will raise
         ex = self.assertRaises(exc.BadRequest,
diff --git a/functional/test_create_update.py b/functional/test_create_update.py
index aa4bdbf..446c9a3 100644
--- a/functional/test_create_update.py
+++ b/functional/test_create_update.py
@@ -26,7 +26,7 @@
                 'value': 'Test1',
                 'fail': False,
                 'update_replace': False,
-                'wait_secs': 0,
+                'wait_secs': 1,
                 'action_wait_secs': {'create': 1},
                 'client_name': 'nova',
                 'entity_name': 'servers',
diff --git a/functional/test_create_update_neutron_port.py b/functional/test_create_update_neutron_port.py
index 575d21c..2109012 100644
--- a/functional/test_create_update_neutron_port.py
+++ b/functional/test_create_update_neutron_port.py
@@ -26,6 +26,7 @@
   subnet:
     type: OS::Neutron::Subnet
     properties:
+      enable_dhcp: false
       network: { get_resource: net }
       cidr: 11.11.11.0/24
   port:
@@ -50,9 +51,6 @@
 
 class UpdatePortTest(functional_base.FunctionalTestsBase):
 
-    def setUp(self):
-        super(UpdatePortTest, self).setUp()
-
     def get_port_id_and_ip(self, stack_identifier):
         resources = self.client.resources.list(stack_identifier)
         port_id = [res.physical_resource_id for res in resources
@@ -72,9 +70,8 @@
         self.update_stack(stack_identifier, templ_no_ip,
                           parameters=parameters)
 
-        new_id, new_ip = self.get_port_id_and_ip(stack_identifier)
-        # port id and ip should be different
-        self.assertNotEqual(_ip, new_ip)
+        new_id, _ = self.get_port_id_and_ip(stack_identifier)
+        # port id should be different
         self.assertNotEqual(_id, new_id)
 
     def test_stack_update_replace_with_ip(self):
@@ -161,6 +158,5 @@
         self.update_stack(stack_identifier, templ_no_ip)
 
         new_id, new_ip = self.get_port_id_and_ip(stack_identifier)
-        # port should be updated with the same id, but different ip
-        self.assertNotEqual(_ip, new_ip)
+        # port should be updated with the same id
         self.assertEqual(_id, new_id)
diff --git a/functional/test_encryption_vol_type.py b/functional/test_encryption_vol_type.py
index 2679990..b34b094 100644
--- a/functional/test_encryption_vol_type.py
+++ b/functional/test_encryption_vol_type.py
@@ -11,8 +11,6 @@
 #    under the License.
 
 
-from heat_integrationtests.common import clients
-from heat_integrationtests.common import config
 from heat_integrationtests.functional import functional_base
 
 test_encryption_vol_type = {
@@ -44,15 +42,10 @@
         super(EncryptionVolTypeTest, self).setUp()
         if not self.conf.admin_username or not self.conf.admin_password:
             self.skipTest('No admin creds found, skipping')
-        self.conf = config.init_conf()
         # cinder security policy usage of volume type is limited
         # to being used by administrators only.
-        # Temporarily switch to admin
-        self.conf.username = self.conf.admin_username
-        self.conf.password = self.conf.admin_password
-        self.manager = clients.ClientManager(self.conf)
-        self.client = self.manager.orchestration_client
-        self.volume_client = self.manager.volume_client
+        # Switch to admin
+        self.setup_clients_for_admin()
 
     def check_stack(self, sid):
         vt = 'my_volume_type'
diff --git a/functional/test_lbaasv2.py b/functional/test_lbaasv2.py
index 983c48a..c1bdde6 100644
--- a/functional/test_lbaasv2.py
+++ b/functional/test_lbaasv2.py
@@ -21,12 +21,15 @@
 
     create_template = '''
 heat_template_version: 2016-04-08
+parameters:
+    subnet:
+        type: string
 resources:
   loadbalancer:
     type: OS::Neutron::LBaaS::LoadBalancer
     properties:
       description: aLoadBalancer
-      vip_subnet: private-subnet
+      vip_subnet: { get_param: subnet }
   listener:
     type: OS::Neutron::LBaaS::Listener
     properties:
@@ -48,7 +51,7 @@
       address: 1.1.1.1
       pool: { get_resource: pool }
       protocol_port: 1111
-      subnet: private-subnet
+      subnet: { get_param: subnet }
       weight: 255
   # pm2
   healthmonitor:
@@ -79,7 +82,7 @@
       address: 2.2.2.2
       pool: { get_resource: pool }
       protocol_port: 2222
-      subnet: private-subnet
+      subnet: { get_param: subnet }
       weight: 222
 '''
 
@@ -89,7 +92,11 @@
             self.skipTest('LBaasv2 extension not available, skipping')
 
     def test_create_update_loadbalancer(self):
-        stack_identifier = self.stack_create(template=self.create_template)
+        parameters = {
+            'subnet': self.conf.fixed_subnet_name,
+        }
+        stack_identifier = self.stack_create(template=self.create_template,
+                                             parameters=parameters)
         stack = self.client.stacks.get(stack_identifier)
         output = self._stack_output(stack, 'loadbalancer')
         self.assertEqual('ONLINE', output['operating_status'])
@@ -101,7 +108,8 @@
         template = template.replace('aLoadBalancer', 'updatedLoadBalancer')
         template = template.replace('aPool', 'updatedPool')
         template = template.replace('aListener', 'updatedListener')
-        self.update_stack(stack_identifier, template=template)
+        self.update_stack(stack_identifier, template=template,
+                          parameters=parameters)
         stack = self.client.stacks.get(stack_identifier)
 
         output = self._stack_output(stack, 'loadbalancer')
@@ -121,7 +129,11 @@
         self.assertEqual('updatedListener', output['description'])
 
     def test_add_delete_poolmember(self):
-        stack_identifier = self.stack_create(template=self.create_template)
+        parameters = {
+            'subnet': self.conf.fixed_subnet_name,
+        }
+        stack_identifier = self.stack_create(template=self.create_template,
+                                             parameters=parameters)
         stack = self.client.stacks.get(stack_identifier)
         output = self._stack_output(stack, 'loadbalancer')
         self.assertEqual('ONLINE', output['operating_status'])
@@ -129,14 +141,16 @@
         self.assertEqual(1, len(output['members']))
         # add pool member
         template = self.create_template.replace('# pm2', self.add_member)
-        self.update_stack(stack_identifier, template=template)
+        self.update_stack(stack_identifier, template=template,
+                          parameters=parameters)
         stack = self.client.stacks.get(stack_identifier)
         output = self._stack_output(stack, 'loadbalancer')
         self.assertEqual('ONLINE', output['operating_status'])
         output = self._stack_output(stack, 'pool')
         self.assertEqual(2, len(output['members']))
         # delete pool member
-        self.update_stack(stack_identifier, template=self.create_template)
+        self.update_stack(stack_identifier, template=self.create_template,
+                          parameters=parameters)
         stack = self.client.stacks.get(stack_identifier)
         output = self._stack_output(stack, 'loadbalancer')
         self.assertEqual('ONLINE', output['operating_status'])
diff --git a/functional/test_purge.py b/functional/test_purge.py
index 42feee3..fd652a9 100644
--- a/functional/test_purge.py
+++ b/functional/test_purge.py
@@ -10,6 +10,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import time
+
 from oslo_concurrency import processutils
 
 from heat_integrationtests.functional import functional_base
@@ -30,6 +32,7 @@
         stacks = dict((stack.id, stack) for stack in
                       self.client.stacks.list(show_deleted=True))
         self.assertIn(stack_identifier.split('/')[1], stacks)
+        time.sleep(1)
         cmd = "heat-manage purge_deleted 0"
         processutils.execute(cmd, shell=True)
         stacks = dict((stack.id, stack) for stack in
@@ -40,6 +43,7 @@
         stack_identifier = self.stack_create(template=self.template,
                                              tags="foo,bar")
         self._stack_delete(stack_identifier)
+        time.sleep(1)
         cmd = "heat-manage purge_deleted 0"
         processutils.execute(cmd, shell=True)
         stacks = dict((stack.id, stack) for stack in
diff --git a/functional/test_resource_group.py b/functional/test_resource_group.py
index 1e9edd5..f1ae923 100644
--- a/functional/test_resource_group.py
+++ b/functional/test_resource_group.py
@@ -48,7 +48,7 @@
     def test_resource_group_zero_novalidate(self):
         # Nested resources should be validated only when size > 0
         # This allows features to be disabled via size=0 without
-        # triggering validation of nested resource custom contraints
+        # triggering validation of nested resource custom constraints
         # e.g images etc in the nested schema.
         nested_template_fail = '''
 heat_template_version: 2013-05-23
diff --git a/functional/test_templates.py b/functional/test_templates.py
index 82a1af4..9d36391 100644
--- a/functional/test_templates.py
+++ b/functional/test_templates.py
@@ -54,7 +54,7 @@
         supported_template_versions = ["2013-05-23", "2014-10-16",
                                        "2015-04-30", "2015-10-15",
                                        "2012-12-12", "2010-09-09",
-                                       "2016-04-08", "2016-10-14"]
+                                       "2016-04-08", "2016-10-14", "newton"]
         for template in template_versions:
             self.assertIn(template.version.split(".")[1],
                           supported_template_versions)
diff --git a/functional/test_update_restricted.py b/functional/test_update_restricted.py
index ae1907b..7087c0c 100644
--- a/functional/test_update_restricted.py
+++ b/functional/test_update_restricted.py
@@ -10,6 +10,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import time
+
 from heat_integrationtests.functional import functional_base
 
 test_template = {
@@ -71,6 +73,9 @@
             self._check_for_restriction_reason(resource_events,
                                                reason_update_restrict))
 
+        # Ensure the timestamp changes, since this will be very quick
+        time.sleep(1)
+
         # check update succeeds - with only 'replace' restricted
         self.update_stack(stack_identifier, update_template,
                           env_replace_restrict,
@@ -95,7 +100,7 @@
 
         # check replace fails - with 'both' restricted
         self.update_stack(stack_identifier, update_template,
-                          env_replace_restrict,
+                          env_both_restrict,
                           expected_status='UPDATE_FAILED')
 
         self.assertTrue(self.verify_resource_status(stack_identifier, 'bar',
@@ -105,6 +110,9 @@
             self._check_for_restriction_reason(resource_events,
                                                reason_replace_restrict))
 
+        # Ensure the timestamp changes, since this will be very quick
+        time.sleep(1)
+
         # check replace fails - with only 'replace' restricted
         self.update_stack(stack_identifier, update_template,
                           env_replace_restrict,
@@ -139,6 +147,9 @@
             self._check_for_restriction_reason(resource_events,
                                                reason_replace_restrict))
 
+        # Ensure the timestamp changes, since this will be very quick
+        time.sleep(1)
+
         # check replace fails - with only 'replace' restricted
         self.update_stack(stack_identifier, update_template,
                           env_replace_restrict,
diff --git a/scenario/scenario_base.py b/scenario/scenario_base.py
index d41c9a1..df86e9e 100644
--- a/scenario/scenario_base.py
+++ b/scenario/scenario_base.py
@@ -21,8 +21,6 @@
     def setUp(self):
         super(ScenarioTestsBase, self).setUp()
         self.check_skip()
-
-        self.client = self.orchestration_client
         self.sub_dir = 'templates'
         self.assign_keypair()
 
diff --git a/scenario/templates/test_ceilometer_alarm.yaml b/scenario/templates/test_aodh_alarm.yaml
similarity index 96%
rename from scenario/templates/test_ceilometer_alarm.yaml
rename to scenario/templates/test_aodh_alarm.yaml
index 01bc790..9218f56 100644
--- a/scenario/templates/test_ceilometer_alarm.yaml
+++ b/scenario/templates/test_aodh_alarm.yaml
@@ -15,7 +15,7 @@
       cooldown: 0
       scaling_adjustment: 1
   alarm:
-    type: OS::Ceilometer::Alarm
+    type: OS::Aodh::Alarm
     properties:
       description: Scale-up if the average CPU > 50% for 1 minute
       meter_name: test_meter
diff --git a/scenario/test_ceilometer_alarm.py b/scenario/test_aodh_alarm.py
similarity index 89%
rename from scenario/test_ceilometer_alarm.py
rename to scenario/test_aodh_alarm.py
index aa29861..90288a2 100644
--- a/scenario/test_ceilometer_alarm.py
+++ b/scenario/test_aodh_alarm.py
@@ -18,12 +18,12 @@
 LOG = logging.getLogger(__name__)
 
 
-class CeilometerAlarmTest(scenario_base.ScenarioTestsBase):
-    """Class is responsible for testing of ceilometer usage."""
+class AodhAlarmTest(scenario_base.ScenarioTestsBase):
+    """Class is responsible for testing of aodh usage."""
     def setUp(self):
-        super(CeilometerAlarmTest, self).setUp()
+        super(AodhAlarmTest, self).setUp()
         self.template = self._load_template(__file__,
-                                            'test_ceilometer_alarm.yaml',
+                                            'test_aodh_alarm.yaml',
                                             'templates')
 
     def check_instance_count(self, stack_identifier, expected):
diff --git a/scenario/test_volumes.py b/scenario/test_volumes.py
index 7980d81..603c8f2 100644
--- a/scenario/test_volumes.py
+++ b/scenario/test_volumes.py
@@ -77,9 +77,8 @@
                 template_name='test_volumes_create_from_backup.yaml',
                 add_parameters={'backup_id': backup.id})
             stack2 = self.client.stacks.get(stack_identifier2)
-        except exceptions.StackBuildErrorException as e:
-            LOG.error("Halting test due to bug: #1382300")
-            LOG.exception(e)
+        except exceptions.StackBuildErrorException:
+            LOG.exception("Halting test due to bug: #1382300")
             return
 
         # Verify with cinder that the volume exists, with matching details