Merge "Follow up patch on missed IdentityV2 methods"
diff --git a/requirements.txt b/requirements.txt
index b25b9d3..811580a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
 # The order of packages is significant, because pip processes them in the order
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
-pbr<2.0,>=1.6
+pbr>=1.6
 cliff>=1.14.0 # Apache-2.0
 anyjson>=0.3.3
 httplib2>=0.7.5
@@ -22,6 +22,6 @@
 iso8601>=0.1.9
 fixtures>=1.3.1
 testscenarios>=0.4
-tempest-lib>=0.6.1
+tempest-lib>=0.8.0
 PyYAML>=3.1.0
 stevedore>=1.5.0 # Apache-2.0
diff --git a/setup.py b/setup.py
index d8080d0..782bb21 100644
--- a/setup.py
+++ b/setup.py
@@ -25,5 +25,5 @@
     pass
 
 setuptools.setup(
-    setup_requires=['pbr>=1.3'],
+    setup_requires=['pbr>=1.8'],
     pbr=True)
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 9d49124..34ec78d 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -14,6 +14,8 @@
 #    under the License.
 
 
+from collections import namedtuple
+
 import testtools
 
 from tempest.api.compute import base
@@ -24,6 +26,9 @@
 CONF = config.CONF
 
 
+CreatedServer = namedtuple('CreatedServer', 'server_id, volume_backed')
+
+
 class LiveBlockMigrationTestJSON(base.BaseV2ComputeAdminTest):
     _host_key = 'OS-EXT-SRV-ATTR:host'
 
@@ -38,7 +43,9 @@
     def resource_setup(cls):
         super(LiveBlockMigrationTestJSON, cls).resource_setup()
 
-        cls.created_server_ids = []
+        # list of CreatedServer namedtuples
+        # TODO(mriedem): Remove the instance variable and shared server re-use
+        cls.created_servers = []
 
     def _get_compute_hostnames(self):
         body = self.admin_hosts_client.list_hosts()['hosts']
@@ -56,7 +63,15 @@
         return self._get_server_details(server_id)[self._host_key]
 
     def _migrate_server_to(self, server_id, dest_host):
-        bmflm = CONF.compute_feature_enabled.block_migration_for_live_migration
+        # volume backed instances shouldn't be block migrated
+        for id, volume_backed in self.created_servers:
+            if server_id == id:
+                use_block_migration = not volume_backed
+                break
+        else:
+            raise ValueError('Server with id %s not found.' % server_id)
+        bmflm = (CONF.compute_feature_enabled.
+                 block_migration_for_live_migration and use_block_migration)
         body = self.admin_servers_client.live_migrate_server(
             server_id, host=dest_host, block_migration=bmflm,
             disk_over_commit=False)
@@ -70,14 +85,18 @@
     def _get_server_status(self, server_id):
         return self._get_server_details(server_id)['status']
 
-    def _get_an_active_server(self):
-        for server_id in self.created_server_ids:
-            if 'ACTIVE' == self._get_server_status(server_id):
+    def _get_an_active_server(self, volume_backed=False):
+        for server_id, vol_backed in self.created_servers:
+            if ('ACTIVE' == self._get_server_status(server_id) and
+                    volume_backed == vol_backed):
                 return server_id
         else:
-            server = self.create_test_server(wait_until="ACTIVE")
+            server = self.create_test_server(wait_until="ACTIVE",
+                                             volume_backed=volume_backed)
             server_id = server['id']
-            self.created_server_ids.append(server_id)
+            new_server = CreatedServer(server_id=server_id,
+                                       volume_backed=volume_backed)
+            self.created_servers.append(new_server)
             return server_id
 
     def _volume_clean_up(self, server_id, volume_id):
@@ -87,20 +106,22 @@
             self.volumes_client.wait_for_volume_status(volume_id, 'available')
         self.volumes_client.delete_volume(volume_id)
 
-    def _test_live_block_migration(self, state='ACTIVE'):
-        """Tests live block migration between two hosts.
+    def _test_live_migration(self, state='ACTIVE', volume_backed=False):
+        """Tests live migration between two hosts.
 
         Requires CONF.compute_feature_enabled.live_migration to be True.
 
         :param state: The vm_state the migrated server should be in before and
                       after the live migration. Supported values are 'ACTIVE'
                       and 'PAUSED'.
+        :param volume_backed: If the instance is volume backed or not. If
+                              volume_backed, *block* migration is not used.
         """
         # Live block migrate an instance to another host
         if len(self._get_compute_hostnames()) < 2:
             raise self.skipTest(
                 "Less than 2 compute nodes, skipping migration test.")
-        server_id = self._get_an_active_server()
+        server_id = self._get_an_active_server(volume_backed=volume_backed)
         actual_host = self._get_host_for_server(server_id)
         target_host = self._get_host_other_than(actual_host)
 
@@ -127,7 +148,7 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.live_migration,
                           'Live migration not available')
     def test_live_block_migration(self):
-        self._test_live_block_migration()
+        self._test_live_migration()
 
     @test.idempotent_id('1e107f21-61b2-4988-8f22-b196e938ab88')
     @testtools.skipUnless(CONF.compute_feature_enabled.live_migration,
@@ -139,7 +160,14 @@
                           'Live migration of paused instances is not '
                           'available.')
     def test_live_block_migration_paused(self):
-        self._test_live_block_migration(state='PAUSED')
+        self._test_live_migration(state='PAUSED')
+
+    @test.idempotent_id('5071cf17-3004-4257-ae61-73a84e28badd')
+    @testtools.skipUnless(CONF.compute_feature_enabled.live_migration,
+                          'Live migration not available')
+    @test.services('volume')
+    def test_volume_backed_live_migration(self):
+        self._test_live_migration(volume_backed=True)
 
     @test.idempotent_id('e19c0cc6-6720-4ed8-be83-b6603ed5c812')
     @testtools.skipIf(not CONF.compute_feature_enabled.live_migration or not
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 3952439..b0fdbac 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -203,13 +203,17 @@
                               server_group_id)
 
     @classmethod
-    def create_test_server(cls, validatable=False, **kwargs):
+    def create_test_server(cls, validatable=False, volume_backed=False,
+                           **kwargs):
         """Wrapper utility that returns a test server.
 
         This wrapper utility calls the common create test server and
         returns a test server. The purpose of this wrapper is to minimize
         the impact on the code of the tests already using this
         function.
+
+        :param validatable: Whether the server will be pingable or sshable.
+        :param volume_backed: Whether the instance is volume backed or not.
         """
         tenant_network = cls.get_tenant_network()
         body, servers = compute.create_test_server(
@@ -217,6 +221,7 @@
             validatable,
             validation_resources=cls.validation_resources,
             tenant_network=tenant_network,
+            volume_backed=volume_backed,
             **kwargs)
 
         cls.servers.extend(servers)
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index c78b4c3..896352b 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -26,7 +26,7 @@
 
 
 class ContainerQuotasTest(base.BaseObjectTest):
-    """Attemps to test the perfect behavior of quotas in a container."""
+    """Attempts to test the perfect behavior of quotas in a container."""
 
     def setUp(self):
         """Creates and sets a container with quotas.
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index 5b6b0fa..f450b0c 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -50,9 +50,13 @@
         cls.servers_client = cls.os.servers_client
         cls.keypairs_client = cls.os.keypairs_client
         cls.network_client = cls.os.network_client
-        cls.volumes_client = cls.os.volumes_client
         cls.images_v2_client = cls.os.image_client_v2
 
+        if CONF.volume_feature_enabled.api_v2:
+            cls.volumes_client = cls.os.volumes_v2_client
+        else:
+            cls.volumes_client = cls.os.volumes_client
+
     @classmethod
     def resource_setup(cls):
         super(BaseOrchestrationTest, cls).resource_setup()
diff --git a/tempest/api/orchestration/stacks/test_volumes.py b/tempest/api/orchestration/stacks/test_volumes.py
index 4ba38ad..ae9a411 100644
--- a/tempest/api/orchestration/stacks/test_volumes.py
+++ b/tempest/api/orchestration/stacks/test_volumes.py
@@ -38,10 +38,19 @@
         self.assertEqual('available', volume.get('status'))
         self.assertEqual(template['resources']['volume']['properties'][
             'size'], volume.get('size'))
+
+        # Some volume properties have been renamed with Cinder v2
+        if CONF.volume_feature_enabled.api_v2:
+            description_field = 'description'
+            name_field = 'name'
+        else:
+            description_field = 'display_description'
+            name_field = 'display_name'
+
         self.assertEqual(template['resources']['volume']['properties'][
-            'description'], volume.get('display_description'))
+            'description'], volume.get(description_field))
         self.assertEqual(template['resources']['volume']['properties'][
-            'name'], volume.get('display_name'))
+            'name'], volume.get(name_field))
 
     def _outputs_verify(self, stack_identifier, template):
         self.assertEqual('available',
diff --git a/tempest/common/accounts.py b/tempest/common/accounts.py
index 9e6bee3..5fab85b 100644
--- a/tempest/common/accounts.py
+++ b/tempest/common/accounts.py
@@ -102,7 +102,7 @@
                 if resource == 'network':
                     hash_dict['networks'][temp_hash_key] = resources[resource]
                 else:
-                    LOG.warning('Unkown resource type %s, ignoring this field'
+                    LOG.warning('Unknown resource type %s, ignoring this field'
                                 % resource)
         return hash_dict
 
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 176be51..41b0529 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -28,7 +28,8 @@
 
 
 def create_test_server(clients, validatable=False, validation_resources=None,
-                       tenant_network=None, wait_until=None, **kwargs):
+                       tenant_network=None, wait_until=None,
+                       volume_backed=False, **kwargs):
     """Common wrapper utility returning a test server.
 
     This method is a common wrapper returning a test server that can be
@@ -41,6 +42,7 @@
     :param tenant_network: Tenant network to be used for creating a server.
     :param wait_until: Server status to wait for the server to reach after
     its creation.
+    :param volume_backed: Whether the instance is volume backed or not.
     :returns a tuple
     """
 
@@ -85,6 +87,29 @@
             if wait_until is None:
                 wait_until = 'ACTIVE'
 
+    if volume_backed:
+        volume_name = data_utils.rand_name('volume')
+        volumes_client = clients.volumes_v2_client
+        if CONF.volume_feature_enabled.api_v1:
+            volumes_client = clients.volumes_client
+        volume = volumes_client.create_volume(
+            display_name=volume_name,
+            imageRef=image_id)
+        volumes_client.wait_for_volume_status(volume['volume']['id'],
+                                              'available')
+
+        bd_map_v2 = [{
+            'uuid': volume['volume']['id'],
+            'source_type': 'volume',
+            'destination_type': 'volume',
+            'boot_index': 0,
+            'delete_on_termination': True}]
+        kwargs['block_device_mapping_v2'] = bd_map_v2
+
+        # Since this is boot from volume an image does not need
+        # to be specified.
+        image_id = ''
+
     body = clients.servers_client.create_server(name=name, imageRef=image_id,
                                                 flavorRef=flavor,
                                                 **kwargs)
diff --git a/tempest/tests/services/compute/test_flavors_client.py b/tempest/tests/services/compute/test_flavors_client.py
new file mode 100644
index 0000000..6c0edb8
--- /dev/null
+++ b/tempest/tests/services/compute/test_flavors_client.py
@@ -0,0 +1,255 @@
+# Copyright 2015 IBM Corp.
+#
+#    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 copy
+import httplib2
+
+from oslo_serialization import jsonutils as json
+from oslotest import mockpatch
+
+from tempest.services.compute.json import flavors_client
+from tempest.tests import fake_auth_provider
+from tempest.tests.services.compute import base
+
+
+class TestFlavorsClient(base.BaseComputeServiceTest):
+
+    FAKE_FLAVOR = {
+        "disk": 1,
+        "id": "1",
+        "links": [{
+            "href": "http://openstack.example.com/v2/openstack/flavors/1",
+            "rel": "self"}, {
+            "href": "http://openstack.example.com/openstack/flavors/1",
+            "rel": "bookmark"}],
+        "name": "m1.tiny",
+        "ram": 512,
+        "swap": 1,
+        "vcpus": 1
+    }
+
+    EXTRA_SPECS = {"extra_specs": {
+        "key1": "value1",
+        "key2": "value2"}
+    }
+
+    FAKE_FLAVOR_ACCESS = {
+        "flavor_id": "10",
+        "tenant_id": "1a951d988e264818afe520e78697dcbf"
+    }
+
+    def setUp(self):
+        super(TestFlavorsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = flavors_client.FlavorsClient(fake_auth,
+                                                   'compute', 'regionOne')
+
+    def _test_list_flavors(self, bytes_body=False):
+        flavor = copy.deepcopy(TestFlavorsClient.FAKE_FLAVOR)
+        # Remove extra attributes
+        for attribute in ('disk', 'vcpus', 'ram', 'swap'):
+            del flavor[attribute]
+        expected = {'flavors': [flavor]}
+        self.check_service_client_function(
+            self.client.list_flavors,
+            'tempest.common.service_client.ServiceClient.get',
+            expected,
+            bytes_body)
+
+    def test_list_flavors_str_body(self):
+        self._test_list_flavors(bytes_body=False)
+
+    def test_list_flavors_byte_body(self):
+        self._test_list_flavors(bytes_body=True)
+
+    def _test_show_flavor(self, bytes_body=False):
+        expected = {"flavor": TestFlavorsClient.FAKE_FLAVOR}
+        self.check_service_client_function(
+            self.client.show_flavor,
+            'tempest.common.service_client.ServiceClient.get',
+            expected,
+            bytes_body,
+            flavor_id='fake-id')
+
+    def test_show_flavor_str_body(self):
+        self._test_show_flavor(bytes_body=False)
+
+    def test_show_flavor_byte_body(self):
+        self._test_show_flavor(bytes_body=True)
+
+    def _test_create_flavor(self, bytes_body=False):
+        expected = {"flavor": TestFlavorsClient.FAKE_FLAVOR}
+        request = copy.deepcopy(TestFlavorsClient.FAKE_FLAVOR)
+        # The 'links' parameter should not be passed in
+        del request['links']
+        self.check_service_client_function(
+            self.client.create_flavor,
+            'tempest.common.service_client.ServiceClient.post',
+            expected,
+            bytes_body,
+            **request)
+
+    def test_create_flavor_str_body(self):
+        self._test_create_flavor(bytes_body=False)
+
+    def test_create_flavor__byte_body(self):
+        self._test_create_flavor(bytes_body=True)
+
+    def test_delete_flavor(self):
+        self.check_service_client_function(
+            self.client.delete_flavor,
+            'tempest.common.service_client.ServiceClient.delete',
+            {}, status=202, flavor_id='c782b7a9-33cd-45f0-b795-7f87f456408b')
+
+    def _test_is_resource_deleted(self, flavor_id, is_deleted=True,
+                                  bytes_body=False):
+        body = json.dumps({'flavors': [TestFlavorsClient.FAKE_FLAVOR]})
+        if bytes_body:
+            body = body.encode('utf-8')
+        response = (httplib2.Response({'status': 200}), body)
+        self.useFixture(mockpatch.Patch(
+            'tempest.common.service_client.ServiceClient.get',
+            return_value=response))
+        self.assertEqual(is_deleted,
+                         self.client.is_resource_deleted(flavor_id))
+
+    def test_is_resource_deleted_true_str_body(self):
+        self._test_is_resource_deleted('2', bytes_body=False)
+
+    def test_is_resource_deleted_true_byte_body(self):
+        self._test_is_resource_deleted('2', bytes_body=True)
+
+    def test_is_resource_deleted_false_str_body(self):
+        self._test_is_resource_deleted('1', is_deleted=False, bytes_body=False)
+
+    def test_is_resource_deleted_false_byte_body(self):
+        self._test_is_resource_deleted('1', is_deleted=False, bytes_body=True)
+
+    def _test_set_flavor_extra_spec(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.set_flavor_extra_spec,
+            'tempest.common.service_client.ServiceClient.post',
+            TestFlavorsClient.EXTRA_SPECS,
+            bytes_body,
+            flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+            **TestFlavorsClient.EXTRA_SPECS)
+
+    def test_set_flavor_extra_spec_str_body(self):
+        self._test_set_flavor_extra_spec(bytes_body=False)
+
+    def test_set_flavor_extra_spec_byte_body(self):
+        self._test_set_flavor_extra_spec(bytes_body=True)
+
+    def _test_list_flavor_extra_specs(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_flavor_extra_specs,
+            'tempest.common.service_client.ServiceClient.get',
+            TestFlavorsClient.EXTRA_SPECS,
+            bytes_body,
+            flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6')
+
+    def test_list_flavor_extra_specs_str_body(self):
+        self._test_list_flavor_extra_specs(bytes_body=False)
+
+    def test_list_flavor_extra_specs__byte_body(self):
+        self._test_list_flavor_extra_specs(bytes_body=True)
+
+    def _test_show_flavor_extra_spec(self, bytes_body=False):
+        expected = {"key": "value"}
+        self.check_service_client_function(
+            self.client.show_flavor_extra_spec,
+            'tempest.common.service_client.ServiceClient.get',
+            expected,
+            bytes_body,
+            flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+            key='key')
+
+    def test_show_flavor_extra_spec_str_body(self):
+        self._test_show_flavor_extra_spec(bytes_body=False)
+
+    def test_show_flavor_extra_spec__byte_body(self):
+        self._test_show_flavor_extra_spec(bytes_body=True)
+
+    def _test_update_flavor_extra_spec(self, bytes_body=False):
+        expected = {"key1": "value"}
+        self.check_service_client_function(
+            self.client.update_flavor_extra_spec,
+            'tempest.common.service_client.ServiceClient.put',
+            expected,
+            bytes_body,
+            flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+            key='key1', **expected)
+
+    def test_update_flavor_extra_spec_str_body(self):
+        self._test_update_flavor_extra_spec(bytes_body=False)
+
+    def test_update_flavor_extra_spec_byte_body(self):
+        self._test_update_flavor_extra_spec(bytes_body=True)
+
+    def test_unset_flavor_extra_spec(self):
+        self.check_service_client_function(
+            self.client.unset_flavor_extra_spec,
+            'tempest.common.service_client.ServiceClient.delete', {},
+            flavor_id='c782b7a9-33cd-45f0-b795-7f87f456408b', key='key')
+
+    def _test_list_flavor_access(self, bytes_body=False):
+        expected = {'flavor_access': [TestFlavorsClient.FAKE_FLAVOR_ACCESS]}
+        self.check_service_client_function(
+            self.client.list_flavor_access,
+            'tempest.common.service_client.ServiceClient.get',
+            expected,
+            bytes_body,
+            flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6')
+
+    def test_list_flavor_access_str_body(self):
+        self._test_list_flavor_access(bytes_body=False)
+
+    def test_list_flavor_access_byte_body(self):
+        self._test_list_flavor_access(bytes_body=True)
+
+    def _test_add_flavor_access(self, bytes_body=False):
+        expected = {
+            "flavor_access": [TestFlavorsClient.FAKE_FLAVOR_ACCESS]
+        }
+        self.check_service_client_function(
+            self.client.add_flavor_access,
+            'tempest.common.service_client.ServiceClient.post',
+            expected,
+            bytes_body,
+            flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+            tenant_id='1a951d988e264818afe520e78697dcbf')
+
+    def test_add_flavor_access_str_body(self):
+        self._test_add_flavor_access(bytes_body=False)
+
+    def test_add_flavor_access_byte_body(self):
+        self._test_add_flavor_access(bytes_body=True)
+
+    def _test_remove_flavor_access(self, bytes_body=False):
+        expected = {
+            "flavor_access": [TestFlavorsClient.FAKE_FLAVOR_ACCESS]
+        }
+        self.check_service_client_function(
+            self.client.remove_flavor_access,
+            'tempest.common.service_client.ServiceClient.post',
+            expected,
+            bytes_body,
+            flavor_id='10',
+            tenant_id='a6edd4d66ad04245b5d2d8716ecc91e3')
+
+    def test_remove_flavor_access_str_body(self):
+        self._test_remove_flavor_access(bytes_body=False)
+
+    def test_remove_flavor_access_byte_body(self):
+        self._test_remove_flavor_access(bytes_body=True)