Merge "Fix image namespace test logging"
diff --git a/.zuul.yaml b/.zuul.yaml
index f2ca000..b5cda87 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -10,6 +10,19 @@
     vars:
       devstack_services:
         tempest: true
+      test_results_stage_name: 'test_results'
+      zuul_copy_output:
+        '{{ devstack_base_dir }}/tempest/etc/tempest.conf': 'logs'
+        '{{ devstack_base_dir }}/tempest/etc/accounts.yaml': 'logs'
+        '{{ devstack_base_dir }}/tempest/tempest.log': 'logs'
+        '{{ stage_dir }}/{{ test_results_stage_name }}.subunit': 'logs'
+        '{{ stage_dir }}/{{ test_results_stage_name }}.html': 'logs'
+        '{{ stage_dir }}/stackviz': 'logs'
+      extensions_to_txt:
+        conf: True
+        log: True
+        yaml: True
+        yml: True
     run: playbooks/devstack-tempest.yaml
     post-run: playbooks/post-tempest.yaml
 
diff --git a/playbooks/post-tempest.yaml b/playbooks/post-tempest.yaml
index 70dac09..ab7a1bb 100644
--- a/playbooks/post-tempest.yaml
+++ b/playbooks/post-tempest.yaml
@@ -1,23 +1,7 @@
 - hosts: all
   become: true
-  vars:
-    logs_root: "{{ devstack_base_dir|default('/opt/stack') }}"
-    test_results_stage_name: 'test_results'
   roles:
     - role: process-test-results
-      test_results_dir: '{{ logs_root }}/tempest'
+      test_results_dir: '{{ devstack_base_dir }}/tempest'
       tox_envdir: tempest
     - role: process-stackviz
-    - role: stage-output
-      zuul_copy_output:
-        { '{{ logs_root }}/tempest/etc/tempest.conf': 'logs',
-          '{{ logs_root }}/tempest/etc/accounts.yaml': 'logs',
-          '{{ logs_root }}/tempest/tempest.log': 'logs',
-          '{{ stage_dir }}/{{ test_results_stage_name }}.subunit': 'logs',
-          '{{ stage_dir }}/{{ test_results_stage_name }}.html': 'logs',
-          '{{ stage_dir }}/stackviz': 'logs' }
-      extensions_to_txt:
-        - conf
-        - log
-        - yaml
-        - yml
diff --git a/releasenotes/notes/add-show-encryption-specs-item-api-to-v2-encryption-types-client-290b421cd4bc0c0e.yaml b/releasenotes/notes/add-show-encryption-specs-item-api-to-v2-encryption-types-client-290b421cd4bc0c0e.yaml
new file mode 100644
index 0000000..9e13afc
--- /dev/null
+++ b/releasenotes/notes/add-show-encryption-specs-item-api-to-v2-encryption-types-client-290b421cd4bc0c0e.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add show encryption specs item API to v2 encryption_types_client library.
+    This feature enables the possibility to show specific encryption specs for
+    a volume type.
diff --git a/releasenotes/notes/add-show-quota-details-api-to-network-quotas-client-3fffd302cc5d335f.yaml b/releasenotes/notes/add-show-quota-details-api-to-network-quotas-client-3fffd302cc5d335f.yaml
new file mode 100644
index 0000000..406e282
--- /dev/null
+++ b/releasenotes/notes/add-show-quota-details-api-to-network-quotas-client-3fffd302cc5d335f.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Add extension API show quota details to network quotas_client library.
+    This feature enables the possibility to show a quota set for a specified
+    project that includes the quota’s used, limit and reserved counts for per
+    resource
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index 404fd94..b23c59f 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -17,12 +17,12 @@
 from tempest.lib import decorators
 
 
-class HypervisorAdminTestJSON(base.BaseV2ComputeAdminTest):
+class HypervisorAdminTestBase(base.BaseV2ComputeAdminTest):
     """Tests Hypervisors API that require admin privileges"""
 
     @classmethod
     def setup_clients(cls):
-        super(HypervisorAdminTestJSON, cls).setup_clients()
+        super(HypervisorAdminTestBase, cls).setup_clients()
         cls.client = cls.os_admin.hypervisor_client
 
     def _list_hypervisors(self):
@@ -30,6 +30,10 @@
         hypers = self.client.list_hypervisors()['hypervisors']
         return hypers
 
+
+class HypervisorAdminTestJSON(HypervisorAdminTestBase):
+    """Tests Hypervisors API that require admin privileges"""
+
     @decorators.idempotent_id('7f0ceacd-c64d-4e96-b8ee-d02943142cc5')
     def test_get_hypervisor_list(self):
         # List of hypervisor and available hypervisors hostname
@@ -53,17 +57,6 @@
         self.assertEqual(details['hypervisor_hostname'],
                          hypers[0]['hypervisor_hostname'])
 
-    @decorators.idempotent_id('e81bba3f-6215-4e39-a286-d52d2f906862')
-    def test_get_hypervisor_show_servers(self):
-        # Show instances about the specific hypervisors
-        hypers = self._list_hypervisors()
-        self.assertNotEmpty(hypers, "No hypervisors found.")
-
-        hostname = hypers[0]['hypervisor_hostname']
-        hypervisors = (self.client.list_servers_on_hypervisor(hostname)
-                       ['hypervisors'])
-        self.assertNotEmpty(hypervisors)
-
     @decorators.idempotent_id('797e4f28-b6e0-454d-a548-80cc77c00816')
     def test_get_hypervisor_stats(self):
         # Verify the stats of the all hypervisor
@@ -110,6 +103,21 @@
             has_valid_uptime,
             "None of the hypervisors had a valid uptime: %s" % hypers)
 
+
+class HypervisorAdminUnderV252Test(HypervisorAdminTestBase):
+    max_microversion = '2.52'
+
+    @decorators.idempotent_id('e81bba3f-6215-4e39-a286-d52d2f906862')
+    def test_get_hypervisor_show_servers(self):
+        # Show instances about the specific hypervisors
+        hypers = self._list_hypervisors()
+        self.assertNotEmpty(hypers, "No hypervisors found.")
+
+        hostname = hypers[0]['hypervisor_hostname']
+        hypervisors = (self.client.list_servers_on_hypervisor(hostname)
+                       ['hypervisors'])
+        self.assertNotEmpty(hypervisors)
+
     @decorators.idempotent_id('d7e1805b-3b14-4a3b-b6fd-50ec6d9f361f')
     def test_search_hypervisor(self):
         hypers = self._list_hypervisors()
diff --git a/tempest/api/compute/admin/test_hypervisor_negative.py b/tempest/api/compute/admin/test_hypervisor_negative.py
index 431e823..0056376 100644
--- a/tempest/api/compute/admin/test_hypervisor_negative.py
+++ b/tempest/api/compute/admin/test_hypervisor_negative.py
@@ -19,12 +19,12 @@
 from tempest.lib import exceptions as lib_exc
 
 
-class HypervisorAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+class HypervisorAdminNegativeTestBase(base.BaseV2ComputeAdminTest):
     """Tests Hypervisors API that require admin privileges"""
 
     @classmethod
     def setup_clients(cls):
-        super(HypervisorAdminNegativeTestJSON, cls).setup_clients()
+        super(HypervisorAdminNegativeTestBase, cls).setup_clients()
         cls.client = cls.os_admin.hypervisor_client
         cls.non_adm_client = cls.hypervisor_client
 
@@ -33,6 +33,10 @@
         hypers = self.client.list_hypervisors()['hypervisors']
         return hypers
 
+
+class HypervisorAdminNegativeTestJSON(HypervisorAdminNegativeTestBase):
+    """Tests Hypervisors API that require admin privileges"""
+
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('c136086a-0f67-4b2b-bc61-8482bd68989f')
     def test_show_nonexistent_hypervisor(self):
@@ -55,27 +59,6 @@
             hypers[0]['id'])
 
     @decorators.attr(type=['negative'])
-    @decorators.idempotent_id('2a0a3938-832e-4859-95bf-1c57c236b924')
-    def test_show_servers_with_non_admin_user(self):
-        hypers = self._list_hypervisors()
-        self.assertNotEmpty(hypers)
-
-        self.assertRaises(
-            lib_exc.Forbidden,
-            self.non_adm_client.list_servers_on_hypervisor,
-            hypers[0]['id'])
-
-    @decorators.attr(type=['negative'])
-    @decorators.idempotent_id('02463d69-0ace-4d33-a4a8-93d7883a2bba')
-    def test_show_servers_with_nonexistent_hypervisor(self):
-        nonexistent_hypervisor_id = data_utils.rand_uuid()
-
-        self.assertRaises(
-            lib_exc.NotFound,
-            self.client.list_servers_on_hypervisor,
-            nonexistent_hypervisor_id)
-
-    @decorators.attr(type=['negative'])
     @decorators.idempotent_id('e2b061bb-13f9-40d8-9d6e-d5bf17595849')
     def test_get_hypervisor_stats_with_non_admin_user(self):
         self.assertRaises(
@@ -119,13 +102,30 @@
             lib_exc.Forbidden,
             self.non_adm_client.list_hypervisors, detail=True)
 
+
+class HypervisorAdminNegativeUnderV252Test(HypervisorAdminNegativeTestBase):
+    max_microversion = '2.52'
+
     @decorators.attr(type=['negative'])
-    @decorators.idempotent_id('19a45cc1-1000-4055-b6d2-28e8b2ec4faa')
-    def test_search_nonexistent_hypervisor(self):
+    @decorators.idempotent_id('2a0a3938-832e-4859-95bf-1c57c236b924')
+    def test_show_servers_with_non_admin_user(self):
+        hypers = self._list_hypervisors()
+        self.assertNotEmpty(hypers)
+
+        self.assertRaises(
+            lib_exc.Forbidden,
+            self.non_adm_client.list_servers_on_hypervisor,
+            hypers[0]['id'])
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('02463d69-0ace-4d33-a4a8-93d7883a2bba')
+    def test_show_servers_with_nonexistent_hypervisor(self):
+        nonexistent_hypervisor_id = data_utils.rand_uuid()
+
         self.assertRaises(
             lib_exc.NotFound,
-            self.client.search_hypervisor,
-            'nonexistent_hypervisor_name')
+            self.client.list_servers_on_hypervisor,
+            nonexistent_hypervisor_id)
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('5b6a6c79-5dc1-4fa5-9c58-9c8085948e74')
@@ -137,3 +137,11 @@
             lib_exc.Forbidden,
             self.non_adm_client.search_hypervisor,
             hypers[0]['hypervisor_hostname'])
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('19a45cc1-1000-4055-b6d2-28e8b2ec4faa')
+    def test_search_nonexistent_hypervisor(self):
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.client.search_hypervisor,
+            'nonexistent_hypervisor_name')
diff --git a/tempest/api/compute/admin/test_servers_on_multinodes.py b/tempest/api/compute/admin/test_servers_on_multinodes.py
index 2e7b07b..18c974a 100644
--- a/tempest/api/compute/admin/test_servers_on_multinodes.py
+++ b/tempest/api/compute/admin/test_servers_on_multinodes.py
@@ -111,3 +111,37 @@
         hostnames = list(hosts.values())
         self.assertNotEqual(hostnames[0], hostnames[1],
                             'Servers are on the same host: %s' % hosts)
+
+    @decorators.idempotent_id('9d2e924a-baf4-11e7-b856-fa163e65f5ce')
+    @testtools.skipUnless(
+        compute.is_scheduler_filter_enabled("ServerGroupAffinityFilter"),
+        'ServerGroupAffinityFilter is not available.')
+    def test_create_server_with_scheduler_hint_group_affinity(self):
+        """Tests the ServerGroupAffinityFilter
+
+        Creates two servers in an affinity server group and
+        asserts the servers are in the group and on same host.
+        """
+        group_id = self.create_test_server_group(policy=['affinity'])['id']
+        hints = {'group': group_id}
+        reservation_id = self.create_test_server(
+            scheduler_hints=hints, wait_until='ACTIVE', min_count=2,
+            return_reservation_id=True)['reservation_id']
+
+        # Get the servers using the reservation_id.
+        servers = self.servers_client.list_servers(
+            detail=True, reservation_id=reservation_id)['servers']
+        self.assertEqual(2, len(servers))
+
+        # Assert the servers are in the group.
+        server_group = self.server_groups_client.show_server_group(
+            group_id)['server_group']
+        hosts = {}
+        for server in servers:
+            self.assertIn(server['id'], server_group['members'])
+            hosts[server['id']] = self._get_host(server['id'])
+
+        # Assert the servers are on same host.
+        hostnames = hosts.values()
+        self.assertEqual(hostnames[0], hostnames[1],
+                         'Servers are on the different hosts: %s' % hosts)
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index d857fcb..d3b1350 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -82,7 +82,9 @@
             # A hypervisor may present multiple paths to a tagged disk, so
             # there may be duplicated tags in the metadata, use set() to
             # remove duplicated tags.
-            found_devices = [d['tags'][0] for d in md_dict['devices']]
+            # Some hypervisors might report devices with no tags as well.
+            found_devices = [d['tags'][0] for d in md_dict['devices']
+                             if d.get('tags')]
             self.assertEqual(set(found_devices), set(['port-1', 'port-2',
                                                       'net-1', 'net-2-100',
                                                       'net-2-200', 'boot',
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index c9ee671..2904976 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -167,6 +167,18 @@
         server = self.client.show_server(server['id'])['server']
         self.assertEqual('2001:2001::3', server['accessIPv6'])
 
+    @decorators.related_bug('1730756')
+    @decorators.idempotent_id('defbaca5-d611-49f5-ae21-56ee25d2db49')
+    def test_create_server_specify_multibyte_character_name(self):
+        # prefix character is:
+        # http://unicode.org/cldr/utility/character.jsp?a=20A1
+
+        # We use a string with 3 byte utf-8 character due to nova
+        # will return 400(Bad Request) if we attempt to send a name which has
+        # 4 byte utf-8 character.
+        utf8_name = data_utils.rand_name(b'\xe2\x82\xa1'.decode('utf-8'))
+        self.create_test_server(name=utf8_name, wait_until='ACTIVE')
+
 
 class ServerShowV247Test(base.BaseV2ComputeTest):
     min_microversion = '2.47'
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index 57a28bf..b1e4a58 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import testtools
+
 from tempest.api.network import base
 from tempest.common import identity
 from tempest.common import utils
@@ -89,3 +91,15 @@
     def test_quotas(self):
         new_quotas = {'network': 0, 'port': 0}
         self._check_quotas(new_quotas)
+
+    @testtools.skipUnless(utils.is_extension_enabled(
+        'quota_details', 'network'), 'Quota details extension not enabled.')
+    @decorators.idempotent_id('7b05ec5f-bf44-43cb-b28f-ddd72a824288')
+    def test_show_quota_details(self):
+        # Show quota details for an existing project
+        quota_details = self.admin_quotas_client.show_quota_details(
+            self.admin_quotas_client.tenant_id)['quota']
+        expected_keys = ['used', 'limit', 'reserved']
+        for resource_type in quota_details:
+            for key in expected_keys:
+                self.assertIn(key, quota_details[resource_type])
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index af1024c..1077524 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -161,6 +161,12 @@
                              'The fetched encryption_type %s is different '
                              'from the updated encryption_type' % key)
 
+        # Get encryption specs item
+        key = 'cipher'
+        item = self.admin_encryption_types_client.show_encryption_specs_item(
+            encrypt_type_id, key)
+        self.assertEqual(update_kwargs[key], item[key])
+
         # Delete encryption type
         self.admin_encryption_types_client.delete_encryption_type(
             encrypt_type_id)
diff --git a/tempest/lib/services/network/quotas_client.py b/tempest/lib/services/network/quotas_client.py
index f23af88..e9666de 100644
--- a/tempest/lib/services/network/quotas_client.py
+++ b/tempest/lib/services/network/quotas_client.py
@@ -46,3 +46,8 @@
         """List default quotas for a project."""
         uri = '/quotas/%s/default' % tenant_id
         return self.show_resource(uri)
+
+    def show_quota_details(self, tenant_id):
+        """Show quota details for a project."""
+        uri = '/quotas/%s/details.json' % tenant_id
+        return self.show_resource(uri)
diff --git a/tempest/lib/services/volume/v2/encryption_types_client.py b/tempest/lib/services/volume/v2/encryption_types_client.py
index 20f3356..b99d1fe 100644
--- a/tempest/lib/services/volume/v2/encryption_types_client.py
+++ b/tempest/lib/services/volume/v2/encryption_types_client.py
@@ -47,6 +47,14 @@
         self.expected_success(200, resp.status)
         return rest_client.ResponseBody(resp, body)
 
+    def show_encryption_specs_item(self, volume_type_id, key):
+        """Get the encryption specs item for the specified volume type."""
+        url = "/types/%s/encryption/%s" % (volume_type_id, key)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
     def create_encryption_type(self, volume_type_id, **kwargs):
         """Create encryption type.
 
diff --git a/tempest/lib/services/volume/v3/group_types_client.py b/tempest/lib/services/volume/v3/group_types_client.py
index 1b47201..ecbcba1 100644
--- a/tempest/lib/services/volume/v3/group_types_client.py
+++ b/tempest/lib/services/volume/v3/group_types_client.py
@@ -94,7 +94,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://developer.openstack.org/api-ref/block-storage/v3/#create-group-specs-for-a-group-type
+        https://developer.openstack.org/api-ref/block-storage/v3/#create-or-update-group-specs-for-a-group-type
         """
         url = "group_types/%s/group_specs" % group_type_id
         post_body = json.dumps({'group_specs': group_specs})
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 06b4b59..06aa531 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -1174,7 +1174,7 @@
         LOG.debug("Creating an encryption type for volume type: %s", type_id)
         client.create_encryption_type(
             type_id, provider=provider, key_size=key_size, cipher=cipher,
-            control_location=control_location)['encryption']
+            control_location=control_location)
 
     def create_encrypted_volume(self, encryption_provider, volume_type,
                                 key_size=256, cipher='aes-xts-plain64',
diff --git a/tempest/tests/lib/services/network/test_quotas_client.py b/tempest/tests/lib/services/network/test_quotas_client.py
index 5a09911..aa6c1a1 100644
--- a/tempest/tests/lib/services/network/test_quotas_client.py
+++ b/tempest/tests/lib/services/network/test_quotas_client.py
@@ -54,6 +54,46 @@
 
     FAKE_QUOTA_TENANT_ID = "bab7d5c60cd041a0a36f7c4b6e1dd978"
 
+    FAKE_QUOTA_DETAILS = {
+        "quota": {
+            "rbac_policy": {
+                "used": 4,
+                "limit": 10,
+                "reserved": 0
+            },
+            "subnetpool": {
+                "used": 2,
+                "limit": -1,
+                "reserved": 0
+            },
+            "security_group_rule": {
+                "used": 10,
+                "limit": 100,
+                "reserved": 1
+            },
+            "security_group": {
+                "used": 3,
+                "limit": 10,
+                "reserved": 0
+            },
+            "subnet": {
+                "used": 3,
+                "limit": 100,
+                "reserved": 0
+            },
+            "port": {
+                "used": 21,
+                "limit": 500,
+                "reserved": 3
+            },
+            "network": {
+                "used": 9,
+                "limit": 100,
+                "reserved": 2
+            }
+        }
+    }
+
     def setUp(self):
         super(TestQuotasClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -95,6 +135,15 @@
             200,
             tenant_id=self.FAKE_QUOTA_TENANT_ID)
 
+    def _test_show_quota_details(self, bytes_body=False):
+        self.check_service_client_function(
+            self.quotas_client.show_quota_details,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_QUOTA_DETAILS,
+            bytes_body,
+            200,
+            tenant_id=self.FAKE_QUOTA_TENANT_ID)
+
     def test_reset_quotas(self):
         self.check_service_client_function(
             self.quotas_client.reset_quotas,
@@ -126,3 +175,9 @@
 
     def test_update_quotas_with_bytes_body(self):
         self._test_update_quotas(bytes_body=True)
+
+    def test_show_quota_details_with_str_body(self):
+        self._test_show_quota_details()
+
+    def test_show_quota_details_with_bytes_body(self):
+        self._test_show_quota_details(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py b/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py
index d029091..8de9fb4 100644
--- a/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py
+++ b/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py
@@ -43,6 +43,10 @@
         }
     }
 
+    FAKE_ENCRYPTION_SPECS_ITEM = {
+        "cipher": "aes-xts-plain64"
+    }
+
     def setUp(self):
         super(TestEncryptionTypesClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -65,6 +69,13 @@
             self.FAKE_INFO_ENCRYPTION_TYPE,
             bytes_body, volume_type_id="cbc36478b0bd8e67e89")
 
+    def _test_show_encryption_specs_item(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_encryption_specs_item,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_ENCRYPTION_SPECS_ITEM,
+            bytes_body, volume_type_id="cbc36478b0bd8e67e89", key="cipher")
+
     def test_create_encryption_type_with_str_body(self):
         self._test_create_encryption()
 
@@ -77,6 +88,12 @@
     def test_show_encryption_type_with_bytes_body(self):
         self._test_show_encryption_type(bytes_body=True)
 
+    def test_show_encryption_specs_item_with_str_body(self):
+        self._test_show_encryption_specs_item()
+
+    def test_show_encryption_specs_item_with_bytes_body(self):
+        self._test_show_encryption_specs_item(bytes_body=True)
+
     def test_delete_encryption_type(self):
         self.check_service_client_function(
             self.client.delete_encryption_type,