Merge "add unittest for list image members interface"
diff --git a/.zuul.yaml b/.zuul.yaml
index 641bb85..403c93d 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -352,6 +352,10 @@
     vars:
       devstack_localrc:
         USE_PYTHON3: true
+    group-vars:
+      subnode:
+        devstack_localrc:
+          USE_PYTHON3: true
 
 - job:
     name: tempest-full-py3-opensuse15
@@ -416,6 +420,10 @@
         s-proxy: false
         # without Swift, c-bak cannot run (in the Gate at least)
         c-bak: false
+    group-vars:
+      subnode:
+        devstack_localrc:
+          USE_PYTHON3: true
 
 - job:
     name: tempest-full-train
@@ -690,8 +698,6 @@
             irrelevant-files: *tempest-irrelevant-files
         - neutron-grenade-multinode:
             irrelevant-files: *tempest-irrelevant-files
-        - neutron-grenade:
-            irrelevant-files: *tempest-irrelevant-files
         - grenade-py3:
             irrelevant-files: *tempest-irrelevant-files
         - devstack-plugin-ceph-tempest:
@@ -721,7 +727,8 @@
         - tempest-full-test-account-no-admin-py3:
             voting: false
             irrelevant-files: *tempest-irrelevant-files
-        - openstack-tox-bashate
+        - openstack-tox-bashate:
+            irrelevant-files: *tempest-irrelevant-files-2
     gate:
       jobs:
         - tempest-slow-py3:
@@ -730,8 +737,6 @@
             irrelevant-files: *tempest-irrelevant-files
         - tempest-full:
             irrelevant-files: *tempest-irrelevant-files
-        - neutron-grenade:
-            irrelevant-files: *tempest-irrelevant-files
         - grenade-py3:
             irrelevant-files: *tempest-irrelevant-files
         - tempest-ipv6-only:
diff --git a/doc/source/overview.rst b/doc/source/overview.rst
index 423214d..e51b90b 100644
--- a/doc/source/overview.rst
+++ b/doc/source/overview.rst
@@ -116,7 +116,7 @@
     $ stestr run --black-regex '\[.*\bslow\b.*\]' '^tempest\.(api|scenario)'
 
    will run the same set of tests as the default gate jobs. Or you can
-   use `unittest`_ compatible test runners such as `testr`_, `pytest`_ etc.
+   use `unittest`_ compatible test runners such as `stestr`_, `pytest`_ etc.
 
    Tox also contains several existing job configurations. For example::
 
@@ -130,7 +130,6 @@
    to run the tests tagged as smoke.
 
 .. _unittest: https://docs.python.org/3/library/unittest.html
-.. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html
 .. _stestr: https://stestr.readthedocs.org/en/latest/MANUAL.html
 .. _pytest: https://docs.pytest.org/en/latest/
 
@@ -269,14 +268,14 @@
     will have a configuration file already set up to work with your
     DevStack installation.
 
-Tempest is not tied to any single test runner, but `testr`_ is the most commonly
+Tempest is not tied to any single test runner, but `stestr`_ is the most commonly
 used tool. Also, the nosetests test runner is **not** recommended to run Tempest.
 
 After setting up your configuration file, you can execute the set of Tempest
-tests by using ``testr`` ::
+tests by using ``stestr``. By default, ``stestr`` runs tests in parallel ::
 
-    $ testr run --parallel
+    $ stestr run
 
 To run one single test serially ::
 
-    $ testr run tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server
+    $ stestr run --serial tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server
diff --git a/releasenotes/notes/subunit_describe_calls-ad7df689b9d63e3f.yaml b/releasenotes/notes/subunit_describe_calls-ad7df689b9d63e3f.yaml
new file mode 100644
index 0000000..e7fc3a0
--- /dev/null
+++ b/releasenotes/notes/subunit_describe_calls-ad7df689b9d63e3f.yaml
@@ -0,0 +1,8 @@
+---
+deprecations:
+  - |
+    Deprecated command for subunit-describe-calls
+
+features:
+  - |
+    Added new tempest subcommand for subunit-describe-calls
diff --git a/setup.cfg b/setup.cfg
index 5c1d24c..1e9b8e9 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -36,6 +36,7 @@
     subunit-describe-calls = tempest.cmd.subunit_describe_calls:entry_point
 tempest.cm =
     account-generator = tempest.cmd.account_generator:TempestAccountGenerator
+    subunit-describe-calls = tempest.cmd.subunit_describe_calls:TempestSubunitDescribeCalls
     init = tempest.cmd.init:TempestInit
     cleanup = tempest.cmd.cleanup:TempestCleanup
     list-plugins = tempest.cmd.list_plugins:TempestListPlugins
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index f83e62c..92524fc 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -41,11 +41,6 @@
         cls.prepare_instance_network()
         super(BaseAttachVolumeTest, cls).setup_credentials()
 
-    @classmethod
-    def resource_setup(cls):
-        super(BaseAttachVolumeTest, cls).resource_setup()
-        cls.device = CONF.compute.volume_device_name
-
     def _create_server(self):
         # Start a server and wait for it to become ready
         validation_resources = self.get_test_validation_resources(
@@ -84,15 +79,18 @@
             # NOTE(andreaf) We need to ensure the ssh key has been
             # injected in the guest before we power cycle
             linux_client.validate_authentication()
+            disks_before_attach = linux_client.count_disks()
 
         volume = self.create_volume()
 
         # NOTE: As of the 12.0.0 Liberty release, the Nova libvirt driver
-        # no longer honors a user-supplied device name, in that case
-        # CONF.compute.volume_device_name must be set the equal value as
-        # the libvirt auto-assigned one
-        attachment = self.attach_volume(server, volume,
-                                        device=('/dev/%s' % self.device))
+        # no longer honors a user-supplied device name, and there can be
+        # a mismatch between libvirt provide disk name and actual disk name
+        # on instance, hence we no longer validate this test with the supplied
+        # device name rather we count number of disk before attach
+        # detach to validate the testcase.
+
+        attachment = self.attach_volume(server, volume)
 
         self.servers_client.stop_server(server['id'])
         waiters.wait_for_server_status(self.servers_client, server['id'],
@@ -103,9 +101,8 @@
                                        'ACTIVE')
 
         if CONF.validation.run_validation:
-            disks = linux_client.get_disks()
-            device_name_to_match = '\n' + self.device + ' '
-            self.assertIn(device_name_to_match, disks)
+            disks_after_attach = linux_client.count_disks()
+            self.assertGreater(disks_after_attach, disks_before_attach)
 
         self.servers_client.detach_volume(server['id'], attachment['volumeId'])
         waiters.wait_for_volume_resource_status(
@@ -120,8 +117,8 @@
                                        'ACTIVE')
 
         if CONF.validation.run_validation:
-            disks = linux_client.get_disks()
-            self.assertNotIn(device_name_to_match, disks)
+            disks_after_detach = linux_client.count_disks()
+            self.assertEqual(disks_before_attach, disks_after_detach)
 
     @decorators.idempotent_id('7fa563fe-f0f7-43eb-9e22-a1ece036b513')
     def test_list_get_volume_attachments(self):
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index 081fa7a..e029538 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -81,13 +81,19 @@
 import os
 import re
 import sys
+import traceback
 
+from cliff.command import Command
 from oslo_serialization import jsonutils as json
 import subunit
 import testtools
 
 
+DESCRIPTION = "Outputs all HTTP calls a given test made that were logged."
+
+
 class UrlParser(testtools.TestResult):
+
     uuid_re = re.compile(r'(^|[^0-9a-f])[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-'
                          '[0-9a-f]{4}-[0-9a-f]{12}([^0-9a-f]|$)')
     id_re = re.compile(r'(^|[^0-9a-z])[0-9a-z]{8}[0-9a-z]{4}[0-9a-z]{4}'
@@ -241,33 +247,12 @@
 
 
 class ArgumentParser(argparse.ArgumentParser):
+
     def __init__(self):
-        desc = "Outputs all HTTP calls a given test made that were logged."
+        desc = DESCRIPTION
         super(ArgumentParser, self).__init__(description=desc)
-
         self.prog = "subunit-describe-calls"
-
-        self.add_argument(
-            "-s", "--subunit", metavar="<subunit file>",
-            nargs="?", type=argparse.FileType('rb'), default=sys.stdin,
-            help="The path to the subunit output file.")
-
-        self.add_argument(
-            "-n", "--non-subunit-name", metavar="<non subunit name>",
-            default="pythonlogging",
-            help="The name used in subunit to describe the file contents.")
-
-        self.add_argument(
-            "-o", "--output-file", metavar="<output file>", default=None,
-            help="The output file name for the json.")
-
-        self.add_argument(
-            "-p", "--ports", metavar="<ports file>", default=None,
-            help="A JSON file describing the ports for each service.")
-
-        self.add_argument(
-            "-v", "--verbose", action='store_true', default=False,
-            help="Add Request and Response header and body data to stdout.")
+        _parser_add_args(self)
 
 
 def parse(stream, non_subunit_name, ports):
@@ -321,11 +306,63 @@
         sys.stdout.write('\n')
 
 
-def entry_point():
-    cl_args = ArgumentParser().parse_args()
+def entry_point(cl_args=None):
+    print('Running subunit_describe_calls ...')
+    if not cl_args:
+        print("Use of: 'subunit-describe-calls' is deprecated, "
+              "please use: 'tempest subunit-describe-calls'")
+        cl_args = ArgumentParser().parse_args()
     parser = parse(cl_args.subunit, cl_args.non_subunit_name, cl_args.ports)
     output(parser, cl_args.output_file, cl_args.verbose)
 
 
+def _parser_add_args(parser):
+    parser.add_argument(
+        "-s", "--subunit", metavar="<subunit file>",
+        nargs="?", type=argparse.FileType('rb'), default=sys.stdin,
+        help="The path to the subunit output file(default:stdin v1/v2 stream)"
+    )
+
+    parser.add_argument(
+        "-n", "--non-subunit-name", metavar="<non subunit name>",
+        default="pythonlogging",
+        help="The name used in subunit to describe the file contents."
+    )
+
+    parser.add_argument(
+        "-o", "--output-file", metavar="<output file>", default=None,
+        help="The output file name for the json."
+    )
+
+    parser.add_argument(
+        "-p", "--ports", metavar="<ports file>", default=None,
+        help="A JSON file describing the ports for each service."
+    )
+
+    parser.add_argument(
+        "-v", "--verbose", action='store_true', default=False,
+        help="Add Request and Response header and body data to stdout."
+    )
+
+
+class TempestSubunitDescribeCalls(Command):
+
+    def get_parser(self, prog_name):
+        parser = super(TempestSubunitDescribeCalls, self).get_parser(prog_name)
+        _parser_add_args(parser)
+        return parser
+
+    def take_action(self, parsed_args):
+        try:
+            entry_point(parsed_args)
+
+        except Exception:
+            traceback.print_exc()
+            raise
+
+    def get_description(self):
+        return DESCRIPTION
+
+
 if __name__ == "__main__":
     entry_point()
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index dad710c..5875da3 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -73,6 +73,13 @@
             msg = "'TYPE' column is required but the output doesn't have it: "
             raise tempest.lib.exceptions.TempestException(msg + output)
 
+    def count_disks(self):
+        disks_list = self.get_disks()
+        disks_list = [line[0] for line in
+                      [device_name.split()
+                       for device_name in disks_list.splitlines()][1:]]
+        return len(disks_list)
+
     def get_boot_time(self):
         cmd = 'cut -f1 -d. /proc/uptime'
         boot_secs = self.exec_command(cmd)
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index 14a3c48..4fb6d2e 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -349,7 +349,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://docs.openstack.org/api-ref/block-storage/v3/index.html#force-delete-a-volume
+        https://docs.openstack.org/api-ref/block-storage/v3/index.html#force-detach-a-volume
         """
         post_body = json.dumps({'os-force_detach': kwargs})
         url = 'volumes/%s/action' % volume_id
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index 644a018..caad41c 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -106,6 +106,15 @@
         self.assertEqual(self.conn.get_disks(), result)
         self._assert_exec_called_with('lsblk -lb --nodeps')
 
+    def test_count_disk(self):
+        output_lsblk = """\
+NAME       MAJ:MIN    RM          SIZE RO TYPE MOUNTPOINT
+sda          8:0       0  128035676160  0 disk
+sdb          8:16      0 1000204886016  0 disk
+sr0         11:0       1    1073741312  0 rom"""
+        self.ssh_mock.mock.exec_command.return_value = output_lsblk
+        self.assertEqual(self.conn.count_disks(), 2)
+
     def test_get_boot_time(self):
         booted_at = 10000
         uptime_sec = 5000.02
diff --git a/tempest/tests/lib/services/volume/v3/test_groups_client.py b/tempest/tests/lib/services/volume/v3/test_groups_client.py
index 5a5ae88..8a2c4ea 100644
--- a/tempest/tests/lib/services/volume/v3/test_groups_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_groups_client.py
@@ -59,11 +59,11 @@
             "volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"],
             "status": "available",
             "availability_zone": "az1",
-            "created_at": "20127-06-20T03:50:07Z"
+            "created_at": "2017-06-20T03:50:07Z"
         }
     }
 
-    FAKE_LIST_GROUPS = {
+    FAKE_LIST_GROUP_DETAILS = {
         "groups": [
             {
                 "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578",
@@ -100,6 +100,19 @@
         ]
     }
 
+    FAKE_LIST_GROUPS = {
+        "groups": [
+            {
+                "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578",
+                "name": "group-001",
+            },
+            {
+                "id": "e479997c-650b-40a4-9dfe-77655818b0d2",
+                "name": "group-002",
+            }
+        ]
+    }
+
     def setUp(self):
         super(TestGroupsClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -123,13 +136,21 @@
             bytes_body,
             group_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5")
 
+    def _test_list_group_details(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_groups,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_GROUP_DETAILS,
+            bytes_body,
+            detail=True)
+
     def _test_list_groups(self, bytes_body=False):
         self.check_service_client_function(
             self.client.list_groups,
             'tempest.lib.common.rest_client.RestClient.get',
             self.FAKE_LIST_GROUPS,
             bytes_body,
-            detail=True)
+            detail=False)
 
     def test_create_group_with_str_body(self):
         self._test_create_group()
@@ -143,6 +164,12 @@
     def test_show_group_with_bytes_body(self):
         self._test_show_group(bytes_body=True)
 
+    def test_list_group_details_with_str_body(self):
+        self._test_list_group_details()
+
+    def test_list_group_details_with_bytes_body(self):
+        self._test_list_group_details(bytes_body=True)
+
     def test_list_groups_with_str_body(self):
         self._test_list_groups()
 
diff --git a/tempest/tests/lib/services/volume/v3/test_types_client.py b/tempest/tests/lib/services/volume/v3/test_types_client.py
index 7021a3f..336aa32 100644
--- a/tempest/tests/lib/services/volume/v3/test_types_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_types_client.py
@@ -46,10 +46,8 @@
     FAKE_UPDATE_VOLUME_TYPE = {
         'volume_type': {
             'id': '6685584b-1eac-4da6-b5c3-555430cf68ff',
-            'qos_specs_id': None,
             'name': 'volume-type-test',
             'description': 'default volume type',
-            'os-volume-type-access:is_public': True,
             'is_public': True,
             'extra_specs': {
                 'volume_backend_name': 'rbd'