Merge "Update global-requirement for tempest-lib"
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 12d1d40..7e4503d 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -127,8 +127,11 @@
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
-git_cmd = "git log --pretty=format:'%ad, commit %h' --date=local -n1"
-html_last_updated_fmt = os.popen(git_cmd).read()
+git_cmd = ["git", "log", "--pretty=format:'%ad, commit %h'", "--date=local",
+   "-n1"]
+html_last_updated_fmt = subprocess.Popen(git_cmd,
+                                         stdout=subprocess.PIPE).\
+                                         communicate()[0]
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
diff --git a/requirements.txt b/requirements.txt
index 031b600..aa304a4 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,9 +12,9 @@
 testrepository>=0.0.18
 pyOpenSSL>=0.14
 oslo.concurrency>=2.3.0 # Apache-2.0
-oslo.config>=2.7.0 # Apache-2.0
+oslo.config>=3.2.0 # Apache-2.0
 oslo.i18n>=1.5.0 # Apache-2.0
-oslo.log>=1.12.0 # Apache-2.0
+oslo.log>=1.14.0 # Apache-2.0
 oslo.serialization>=1.10.0 # Apache-2.0
 oslo.utils>=3.2.0 # Apache-2.0
 six>=1.9.0
@@ -24,3 +24,4 @@
 tempest-lib>=0.13.0
 PyYAML>=3.1.0
 stevedore>=1.5.0 # Apache-2.0
+PrettyTable<0.8,>=0.7
diff --git a/setup.cfg b/setup.cfg
index 4415063..b94a4f4 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -36,6 +36,7 @@
     init = tempest.cmd.init:TempestInit
     cleanup = tempest.cmd.cleanup:TempestCleanup
     run-stress = tempest.cmd.run_stress:TempestRunStress
+    list-plugins = tempest.cmd.list_plugins:TempestListPlugins
 oslo.config.opts =
     tempest.config = tempest.config:list_opts
 
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index f51c2db..e1c22e5 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -262,13 +262,16 @@
                           'Instance validation tests are disabled.')
     def test_verify_created_server_ephemeral_disk(self):
         # Verify that the ephemeral disk is created when creating server
+        flavor_base = self.flavors_client.show_flavor(
+            self.flavor_ref)['flavor']
 
         def create_flavor_with_extra_specs():
             flavor_with_eph_disk_name = data_utils.rand_name('eph_flavor')
             flavor_with_eph_disk_id = data_utils.rand_int_id(start=1000)
-            ram = 64
-            vcpus = 1
-            disk = 0
+
+            ram = flavor_base['ram']
+            vcpus = flavor_base['vcpus']
+            disk = flavor_base['disk']
 
             # Create a flavor with extra specs
             flavor = (self.flavor_client.
@@ -284,9 +287,9 @@
             flavor_no_eph_disk_name = data_utils.rand_name('no_eph_flavor')
             flavor_no_eph_disk_id = data_utils.rand_int_id(start=1000)
 
-            ram = 64
-            vcpus = 1
-            disk = 0
+            ram = flavor_base['ram']
+            vcpus = flavor_base['vcpus']
+            disk = flavor_base['disk']
 
             # Create a flavor without extra specs
             flavor = (self.flavor_client.
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index b4837f7..468c79a 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -75,7 +75,7 @@
 
         # Create a volume and wait for it to become ready
         self.volume = self.volumes_client.create_volume(
-            CONF.volume.volume_size, display_name='test')['volume']
+            size=CONF.volume.volume_size, display_name='test')['volume']
         self.addCleanup(self._delete_volume)
         self.volumes_client.wait_for_volume_status(self.volume['id'],
                                                    'available')
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index c3205ce..18d0446 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -62,18 +62,12 @@
     @classmethod
     def create_image(cls, **kwargs):
         """Wrapper that returns a test image."""
-        name = data_utils.rand_name(cls.__name__ + "-instance")
 
-        if 'name' in kwargs:
-            name = kwargs.pop('name')
+        if 'name' not in kwargs:
+            name = data_utils.rand_name(cls.__name__ + "-instance")
+            kwargs['name'] = name
 
-        container_format = kwargs.pop('container_format')
-        disk_format = kwargs.pop('disk_format')
-
-        image = cls.client.create_image(name=name,
-                                        container_format=container_format,
-                                        disk_format=disk_format,
-                                        **kwargs)
+        image = cls.client.create_image(**kwargs)
         # Image objects returned by the v1 client have the image
         # data inside a dict that is keyed against 'image'.
         if 'image' in image:
diff --git a/tempest/api/image/v1/test_images_negative.py b/tempest/api/image/v1/test_images_negative.py
index 3d94408..f16b80e 100644
--- a/tempest/api/image/v1/test_images_negative.py
+++ b/tempest/api/image/v1/test_images_negative.py
@@ -27,13 +27,17 @@
     def test_register_with_invalid_container_format(self):
         # Negative tests for invalid data supplied to POST /images
         self.assertRaises(lib_exc.BadRequest, self.client.create_image,
-                          'test', 'wrong', 'vhd')
+                          name='test',
+                          container_format='wrong',
+                          disk_format='vhd',)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('993face5-921d-4e84-aabf-c1bba4234a67')
     def test_register_with_invalid_disk_format(self):
         self.assertRaises(lib_exc.BadRequest, self.client.create_image,
-                          'test', 'bare', 'wrong')
+                          name='test',
+                          container_format='bare',
+                          disk_format='wrong',)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('bb016f15-0820-4f27-a92d-09b2f67d2488')
diff --git a/tempest/api/network/admin/test_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
index 2e8fd50..c64cf77 100644
--- a/tempest/api/network/admin/test_l3_agent_scheduler.py
+++ b/tempest/api/network/admin/test_l3_agent_scheduler.py
@@ -14,9 +14,11 @@
 
 from tempest.api.network import base
 from tempest.common.utils import data_utils
+from tempest import config
 from tempest import exceptions
 from tempest import test
 
+CONF = config.CONF
 AGENT_TYPE = 'L3 agent'
 AGENT_MODES = (
     'legacy',
@@ -80,6 +82,19 @@
                 cls.port = cls.create_port(cls.network)
                 cls.client.add_router_interface_with_port_id(
                     cls.router['id'], cls.port['id'])
+                # NOTE: Sometimes we have seen this test fail with dvr in,
+                # multinode tests, since the dhcp port is not created before
+                # the test gets executed and so the router is not scheduled
+                # on the given agent. By adding the external gateway info to
+                # the router, the router should be properly scheduled in the
+                # dvr_snat node.
+                # This is a temporary work around to prevent a race condition.
+                external_gateway_info = {
+                    'network_id': CONF.network.public_network_id,
+                    'enable_snat': True}
+                cls.admin_client.update_router_with_snat_gw_info(
+                    cls.router['id'],
+                    external_gateway_info=external_gateway_info)
 
     @classmethod
     def resource_cleanup(cls):
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index acb591d..c032d9c 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -70,7 +70,7 @@
 
         # Update volume with new volume_type
         self.volumes_client.retype_volume(volume['id'],
-                                          volume_type=volume_types[1]['id'])
+                                          new_type=volume_types[1]['id'])
         self.volumes_client.wait_for_volume_status(volume['id'], 'available')
 
         # Get volume details and Verify
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index 6c32321..253a3e1 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -48,12 +48,12 @@
     def _reset_volume_status(self, volume_id, status):
         # Reset the volume status
         body = self.admin_volume_client.reset_volume_status(volume_id,
-                                                            status)
+                                                            status=status)
         return body
 
     def tearDown(self):
         # Set volume's status to available after test
-        self._reset_volume_status(self.volume['id'], 'available')
+        self._reset_volume_status(self.volume['id'], status='available')
         super(VolumesActionsV2Test, self).tearDown()
 
     def _create_temp_volume(self):
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 539a077..dc53450 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -106,14 +106,14 @@
         super(BaseVolumeTest, cls).resource_cleanup()
 
     @classmethod
-    def create_volume(cls, size=None, **kwargs):
+    def create_volume(cls, **kwargs):
         """Wrapper utility that returns a test volume."""
         name = data_utils.rand_name('Volume')
 
         name_field = cls.special_fields['name_field']
 
         kwargs[name_field] = name
-        volume = cls.volumes_client.create_volume(size, **kwargs)['volume']
+        volume = cls.volumes_client.create_volume(**kwargs)['volume']
 
         cls.volumes.append(volume)
         cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
diff --git a/tempest/api/volume/test_snapshot_metadata.py b/tempest/api/volume/test_snapshot_metadata.py
index e50ca95..688baf5 100644
--- a/tempest/api/volume/test_snapshot_metadata.py
+++ b/tempest/api/volume/test_snapshot_metadata.py
@@ -45,7 +45,7 @@
 
     def tearDown(self):
         # Update the metadata to {}
-        self.client.update_snapshot_metadata(self.snapshot_id, {})
+        self.client.update_snapshot_metadata(self.snapshot_id, metadata={})
         super(SnapshotV2MetadataTestJSON, self).tearDown()
 
     @test.idempotent_id('a2f20f99-e363-4584-be97-bc33afb1a56c')
@@ -89,7 +89,7 @@
 
         # Update metadata item
         body = self.client.update_snapshot_metadata(
-            self.snapshot_id, update)['metadata']
+            self.snapshot_id, metadata=update)['metadata']
         # Get the metadata of the snapshot
         body = self.client.show_snapshot_metadata(
             self.snapshot_id)['metadata']
@@ -114,7 +114,7 @@
         self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
         # Update metadata item
         body = self.client.update_snapshot_metadata_item(
-            self.snapshot_id, "key3", update_item)['meta']
+            self.snapshot_id, "key3", meta=update_item)['meta']
         # Get the metadata of the snapshot
         body = self.client.show_snapshot_metadata(
             self.snapshot_id)['metadata']
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index c0b6b7e..7046dcf 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -47,7 +47,8 @@
         self.addCleanup(self._delete_volume, volume['id'])
 
         # Create a volume transfer
-        transfer = self.client.create_volume_transfer(volume['id'])['transfer']
+        transfer = self.client.create_volume_transfer(
+            volume_id=volume['id'])['transfer']
         transfer_id = transfer['id']
         auth_key = transfer['auth_key']
         self.client.wait_for_volume_status(volume['id'],
@@ -63,8 +64,8 @@
         self.assertThat(len(body), matchers.GreaterThan(0))
 
         # Accept a volume transfer by alt_tenant
-        body = self.alt_client.accept_volume_transfer(transfer_id,
-                                                      auth_key)['transfer']
+        body = self.alt_client.accept_volume_transfer(
+            transfer_id, auth_key=auth_key)['transfer']
         self.alt_client.wait_for_volume_status(volume['id'], 'available')
 
     @test.idempotent_id('ab526943-b725-4c07-b875-8e8ef87a2c30')
@@ -74,7 +75,8 @@
         self.addCleanup(self._delete_volume, volume['id'])
 
         # Create a volume transfer
-        body = self.client.create_volume_transfer(volume['id'])['transfer']
+        body = self.client.create_volume_transfer(
+            volume_id=volume['id'])['transfer']
         transfer_id = body['id']
         self.client.wait_for_volume_status(volume['id'],
                                            'awaiting-transfer')
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index d4636ee..5f9ea7f 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -62,8 +62,8 @@
         # Volume is attached and detached successfully from an instance
         mountpoint = '/dev/vdc'
         self.client.attach_volume(self.volume['id'],
-                                  self.server['id'],
-                                  mountpoint)
+                                  instance_uuid=self.server['id'],
+                                  mountpoint=mountpoint)
         self.client.wait_for_volume_status(self.volume['id'], 'in-use')
         self.client.detach_volume(self.volume['id'])
         self.client.wait_for_volume_status(self.volume['id'], 'available')
@@ -74,7 +74,8 @@
     def test_volume_bootable(self):
         # Verify that a volume bootable flag is retrieved
         for bool_bootable in [True, False]:
-            self.client.set_bootable_volume(self.volume['id'], bool_bootable)
+            self.client.set_bootable_volume(self.volume['id'],
+                                            bootable=bool_bootable)
             fetched_volume = self.client.show_volume(
                 self.volume['id'])['volume']
             # Get Volume information
@@ -88,8 +89,8 @@
         # Verify that a volume's attachment information is retrieved
         mountpoint = '/dev/vdc'
         self.client.attach_volume(self.volume['id'],
-                                  self.server['id'],
-                                  mountpoint)
+                                  instance_uuid=self.server['id'],
+                                  mountpoint=mountpoint)
         self.client.wait_for_volume_status(self.volume['id'], 'in-use')
         # NOTE(gfidente): added in reverse order because functions will be
         # called in reverse order to the order they are added (LIFO)
@@ -114,8 +115,8 @@
         # using the Glance image_client and from Cinder via tearDownClass.
         image_name = data_utils.rand_name('Image')
         body = self.client.upload_volume(
-            self.volume['id'], image_name,
-            CONF.volume.disk_format)['os-volume_upload_image']
+            self.volume['id'], image_name=image_name,
+            disk_format=CONF.volume.disk_format)['os-volume_upload_image']
         image_id = body["image_id"]
         self.addCleanup(self.image_client.delete_image, image_id)
         self.image_client.wait_for_image_status(image_id, 'active')
@@ -142,7 +143,7 @@
         # Update volume readonly true
         readonly = True
         self.client.update_volume_readonly(self.volume['id'],
-                                           readonly)
+                                           readonly=readonly)
         # Get Volume information
         fetched_volume = self.client.show_volume(self.volume['id'])['volume']
         bool_flag = self._is_true(fetched_volume['metadata']['readonly'])
@@ -150,7 +151,8 @@
 
         # Update volume readonly false
         readonly = False
-        self.client.update_volume_readonly(self.volume['id'], readonly)
+        self.client.update_volume_readonly(self.volume['id'],
+                                           readonly=readonly)
 
         # Get Volume information
         fetched_volume = self.client.show_volume(self.volume['id'])['volume']
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index 78f5571..ed1e5c5 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -32,7 +32,7 @@
         # Extend Volume Test.
         self.volume = self.create_volume()
         extend_size = int(self.volume['size']) + 1
-        self.client.extend_volume(self.volume['id'], extend_size)
+        self.client.extend_volume(self.volume['id'], new_size=extend_size)
         self.client.wait_for_volume_status(self.volume['id'], 'available')
         volume = self.client.show_volume(self.volume['id'])['volume']
         self.assertEqual(int(volume['size']), extend_size)
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 0af40ea..ad6f556 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -190,8 +190,8 @@
         self.assertRaises(lib_exc.NotFound,
                           self.client.attach_volume,
                           str(uuid.uuid4()),
-                          server['id'],
-                          self.mountpoint)
+                          instance_uuid=server['id'],
+                          mountpoint=self.mountpoint)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('9f9c24e4-011d-46b5-b992-952140ce237a')
@@ -206,7 +206,7 @@
         # Extend volume with smaller size than original size.
         extend_size = 0
         self.assertRaises(lib_exc.BadRequest, self.client.extend_volume,
-                          self.volume['id'], extend_size)
+                          self.volume['id'], new_size=extend_size)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('5d0b480d-e833-439f-8a5a-96ad2ed6f22f')
@@ -214,7 +214,7 @@
         # Extend volume when size is non number.
         extend_size = 'abc'
         self.assertRaises(lib_exc.BadRequest, self.client.extend_volume,
-                          self.volume['id'], extend_size)
+                          self.volume['id'], new_size=extend_size)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('355218f1-8991-400a-a6bb-971239287d92')
@@ -222,7 +222,7 @@
         # Extend volume with None size.
         extend_size = None
         self.assertRaises(lib_exc.BadRequest, self.client.extend_volume,
-                          self.volume['id'], extend_size)
+                          self.volume['id'], new_size=extend_size)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('8f05a943-013c-4063-ac71-7baf561e82eb')
@@ -230,7 +230,7 @@
         # Extend volume size when volume is nonexistent.
         extend_size = int(self.volume['size']) + 1
         self.assertRaises(lib_exc.NotFound, self.client.extend_volume,
-                          str(uuid.uuid4()), extend_size)
+                          str(uuid.uuid4()), new_size=extend_size)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('aff8ba64-6d6f-4f2e-bc33-41a08ee9f115')
@@ -238,7 +238,7 @@
         # Extend volume size when passing volume id is None.
         extend_size = int(self.volume['size']) + 1
         self.assertRaises(lib_exc.NotFound, self.client.extend_volume,
-                          None, extend_size)
+                          None, new_size=extend_size)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('ac6084c0-0546-45f9-b284-38a367e0e0e2')
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 6dfa0a7..9d889bd 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -1023,7 +1023,9 @@
         server_id = _get_server_by_name(client, volume['server'])['id']
         volume_id = _get_volume_by_name(client, volume['name'])['id']
         device = volume['device']
-        client.volumes.attach_volume(volume_id, server_id, device)
+        client.volumes.attach_volume(volume_id,
+                                     instance_uuid=server_id,
+                                     mountpoint=device)
 
 
 #######################
diff --git a/tempest/cmd/list_plugins.py b/tempest/cmd/list_plugins.py
new file mode 100644
index 0000000..1f1ff1a
--- /dev/null
+++ b/tempest/cmd/list_plugins.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+# 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.
+
+"""
+Utility for listing all currently installed Tempest plugins.
+
+**Usage:** ``tempest list-plugins``.
+"""
+
+from cliff import command
+from oslo_log import log as logging
+import prettytable
+
+from tempest.test_discover.plugins import TempestTestPluginManager
+
+LOG = logging.getLogger(__name__)
+
+
+class TempestListPlugins(command.Command):
+    def take_action(self, parsed_args):
+        self._list_plugins()
+        return 0
+
+    def get_description(self):
+        return 'List all tempest plugins'
+
+    def _list_plugins(self):
+        plugins = TempestTestPluginManager()
+
+        output = prettytable.PrettyTable(["Name", "EntryPoint"])
+        for plugin in plugins.ext_plugins.extensions:
+            output.add_row([
+                plugin.name, plugin.entry_point_target])
+
+        print(output)
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 5a14fbe..6e6ada8 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -48,11 +48,7 @@
 
     # TODO(jlanoux) add support of wait_until PINGABLE/SSHABLE
 
-    if 'name' in kwargs:
-        name = kwargs.pop('name')
-    else:
-        name = data_utils.rand_name(__name__ + "-instance")
-
+    name = kwargs.pop('name', data_utils.rand_name(__name__ + "-instance"))
     flavor = kwargs.pop('flavor', CONF.compute.flavor_ref)
     image_id = kwargs.pop('image_id', CONF.compute.image_ref)
 
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index a5a061a..bb4e521 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -49,8 +49,9 @@
         cls.flavors_client = cls.manager.flavors_client
         cls.compute_floating_ips_client = (
             cls.manager.compute_floating_ips_client)
-        # Glance image client v1
-        cls.image_client = cls.manager.image_client
+        if CONF.service_available.glance:
+            # Glance image client v1
+            cls.image_client = cls.manager.image_client
         # Compute image client
         cls.compute_images_client = cls.manager.compute_images_client
         cls.keypairs_client = cls.manager.keypairs_client
@@ -260,9 +261,13 @@
                       imageRef=None, volume_type=None, wait_on_delete=True):
         if name is None:
             name = data_utils.rand_name(self.__class__.__name__)
-        volume = self.volumes_client.create_volume(
-            size=size, display_name=name, snapshot_id=snapshot_id,
-            imageRef=imageRef, volume_type=volume_type)['volume']
+        kwargs = {'display_name': name,
+                  'snapshot_id': snapshot_id,
+                  'imageRef': imageRef,
+                  'volume_type': volume_type}
+        if size is not None:
+            kwargs.update({'size': size})
+        volume = self.volumes_client.create_volume(**kwargs)['volume']
 
         if wait_on_delete:
             self.addCleanup(self.volumes_client.wait_for_resource_deletion,
@@ -344,17 +349,13 @@
 
         return secgroup
 
-    def get_remote_client(self, server_or_ip, username=None, private_key=None,
-                          log_console_of_servers=None):
+    def get_remote_client(self, server_or_ip, username=None, private_key=None):
         """Get a SSH client to a remote server
 
         @param server_or_ip a server object as returned by Tempest compute
             client or an IP address to connect to
         @param username name of the Linux account on the remote server
         @param private_key the SSH private key to use
-        @param log_console_of_servers a list of server objects. Each server
-            in the list will have its console printed in the logs in case the
-            SSH connection failed to be established
         @return a RemoteClient object
         """
         if isinstance(server_or_ip, six.string_types):
@@ -391,10 +392,7 @@
             if caller:
                 message = '(%s) %s' % (caller, message)
             LOG.exception(message)
-            # If we don't explicitly set for which servers we want to
-            # log the console output then all the servers will be logged.
-            # See the definition of _log_console_output()
-            self._log_console_output(log_console_of_servers)
+            self._log_console_output()
             raise
 
         return linux_client
diff --git a/tempest/scenario/test_server_multinode.py b/tempest/scenario/test_server_multinode.py
index 7e0e41c..f21ff4f 100644
--- a/tempest/scenario/test_server_multinode.py
+++ b/tempest/scenario/test_server_multinode.py
@@ -31,6 +31,14 @@
     credentials = ['primary', 'admin']
 
     @classmethod
+    def skip_checks(cls):
+        super(TestServerMultinode, cls).skip_checks()
+
+        if CONF.compute.min_compute_nodes < 2:
+            raise cls.skipException(
+                "Less than 2 compute nodes, skipping multinode tests.")
+
+    @classmethod
     def setup_clients(cls):
         super(TestServerMultinode, cls).setup_clients()
         # Use admin client by default
diff --git a/tempest/services/image/v1/json/images_client.py b/tempest/services/image/v1/json/images_client.py
index 3406db8..5b6a394 100644
--- a/tempest/services/image/v1/json/images_client.py
+++ b/tempest/services/image/v1/json/images_client.py
@@ -147,50 +147,29 @@
             self._http = self._get_http()
         return self._http
 
-    def create_image(self, name, container_format, disk_format, **kwargs):
-        params = {
-            "name": name,
-            "container_format": container_format,
-            "disk_format": disk_format,
-        }
-
+    def create_image(self, **kwargs):
         headers = {}
+        data = kwargs.pop('data', None)
+        headers.update(self._image_meta_to_headers(kwargs))
 
-        for option in ['is_public', 'location', 'properties',
-                       'copy_from', 'min_ram']:
-            if option in kwargs:
-                params[option] = kwargs.get(option)
-
-        headers.update(self._image_meta_to_headers(params))
-
-        if 'data' in kwargs:
-            return self._create_with_data(headers, kwargs.get('data'))
+        if data is not None:
+            return self._create_with_data(headers, data)
 
         resp, body = self.post('v1/images', None, headers)
         self.expected_success(201, resp.status)
         body = json.loads(body)
         return service_client.ResponseBody(resp, body)
 
-    def update_image(self, image_id, name=None, container_format=None,
-                     data=None, properties=None):
-        params = {}
+    def update_image(self, image_id, **kwargs):
         headers = {}
-        if name is not None:
-            params['name'] = name
-
-        if container_format is not None:
-            params['container_format'] = container_format
-
-        if properties is not None:
-            params['properties'] = properties
-
-        headers.update(self._image_meta_to_headers(params))
+        data = kwargs.pop('data', None)
+        headers.update(self._image_meta_to_headers(kwargs))
 
         if data is not None:
             return self._update_with_data(image_id, headers, data)
 
         url = 'v1/images/%s' % image_id
-        resp, body = self.put(url, data, headers)
+        resp, body = self.put(url, None, headers)
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return service_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_snapshots_client.py b/tempest/services/volume/base/base_snapshots_client.py
index fac90e4..fb705e1 100644
--- a/tempest/services/volume/base/base_snapshots_client.py
+++ b/tempest/services/volume/base/base_snapshots_client.py
@@ -165,18 +165,26 @@
         self.expected_success(200, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def update_snapshot_metadata(self, snapshot_id, metadata):
+    def update_snapshot_metadata(self, snapshot_id, **kwargs):
         """Update metadata for the snapshot."""
-        put_body = json.dumps({'metadata': metadata})
+        # TODO(piyush): Current api-site doesn't contain this API description.
+        # After fixing the api-site, we need to fix here also for putting the
+        # link to api-site.
+        # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1529063
+        put_body = json.dumps(kwargs)
         url = "snapshots/%s/metadata" % str(snapshot_id)
         resp, body = self.put(url, put_body)
         body = json.loads(body)
         self.expected_success(200, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def update_snapshot_metadata_item(self, snapshot_id, id, meta_item):
+    def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs):
         """Update metadata item for the snapshot."""
-        put_body = json.dumps({'meta': meta_item})
+        # TODO(piyush): Current api-site doesn't contain this API description.
+        # After fixing the api-site, we need to fix here also for putting the
+        # link to api-site.
+        # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1529064
+        put_body = json.dumps(kwargs)
         url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
         resp, body = self.put(url, put_body)
         body = json.loads(body)
diff --git a/tempest/services/volume/base/base_volumes_client.py b/tempest/services/volume/base/base_volumes_client.py
index c7302e8..d4435bc 100644
--- a/tempest/services/volume/base/base_volumes_client.py
+++ b/tempest/services/volume/base/base_volumes_client.py
@@ -71,23 +71,15 @@
         self.expected_success(200, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def create_volume(self, size=None, **kwargs):
+    def create_volume(self, **kwargs):
         """Creates a new Volume.
 
-        size: Size of volume in GB.
-        Following optional keyword arguments are accepted:
-        display_name: Optional Volume Name(only for V1).
-        name: Optional Volume Name(only for V2).
-        metadata: A dictionary of values to be used as metadata.
-        volume_type: Optional Name of volume_type for the volume
-        snapshot_id: When specified the volume is created from this snapshot
-        imageRef: When specified the volume is created from this image
+        Available params: see http://developer.openstack.org/
+                              api-ref-blockstorage-v2.html#createVolume
         """
-        if size is None:
-            size = self.default_volume_size
-        post_body = {'size': size}
-        post_body.update(kwargs)
-        post_body = json.dumps({'volume': post_body})
+        if 'size' not in kwargs:
+            kwargs['size'] = self.default_volume_size
+        post_body = json.dumps({'volume': kwargs})
         resp, body = self.post('volumes', post_body)
         body = json.loads(body)
         self.expected_success(self.create_resp, resp.status)
@@ -107,35 +99,26 @@
         self.expected_success(202, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def upload_volume(self, volume_id, image_name, disk_format):
+    def upload_volume(self, volume_id, **kwargs):
         """Uploads a volume in Glance."""
-        post_body = {
-            'image_name': image_name,
-            'disk_format': disk_format
-        }
-        post_body = json.dumps({'os-volume_upload_image': post_body})
+        post_body = json.dumps({'os-volume_upload_image': kwargs})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
         body = json.loads(body)
         self.expected_success(202, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def attach_volume(self, volume_id, instance_uuid, mountpoint):
+    def attach_volume(self, volume_id, **kwargs):
         """Attaches a volume to a given instance on a given mountpoint."""
-        post_body = {
-            'instance_uuid': instance_uuid,
-            'mountpoint': mountpoint,
-        }
-        post_body = json.dumps({'os-attach': post_body})
+        post_body = json.dumps({'os-attach': kwargs})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
         self.expected_success(202, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def set_bootable_volume(self, volume_id, bootable):
+    def set_bootable_volume(self, volume_id, **kwargs):
         """set a bootable flag for a volume - true or false."""
-        post_body = {"bootable": bootable}
-        post_body = json.dumps({'os-set_bootable': post_body})
+        post_body = json.dumps({'os-set_bootable': kwargs})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
         self.expected_success(200, resp.status)
@@ -143,8 +126,7 @@
 
     def detach_volume(self, volume_id):
         """Detaches a volume from an instance."""
-        post_body = {}
-        post_body = json.dumps({'os-detach': post_body})
+        post_body = json.dumps({'os-detach': {}})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
         self.expected_success(202, resp.status)
@@ -152,8 +134,7 @@
 
     def reserve_volume(self, volume_id):
         """Reserves a volume."""
-        post_body = {}
-        post_body = json.dumps({'os-reserve': post_body})
+        post_body = json.dumps({'os-reserve': {}})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
         self.expected_success(202, resp.status)
@@ -161,8 +142,7 @@
 
     def unreserve_volume(self, volume_id):
         """Restore a reserved volume ."""
-        post_body = {}
-        post_body = json.dumps({'os-unreserve': post_body})
+        post_body = json.dumps({'os-unreserve': {}})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
         self.expected_success(202, resp.status)
@@ -184,20 +164,17 @@
         """Returns the primary type of resource this client works with."""
         return 'volume'
 
-    def extend_volume(self, volume_id, extend_size):
+    def extend_volume(self, volume_id, **kwargs):
         """Extend a volume."""
-        post_body = {
-            'new_size': extend_size
-        }
-        post_body = json.dumps({'os-extend': post_body})
+        post_body = json.dumps({'os-extend': kwargs})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
         self.expected_success(202, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def reset_volume_status(self, volume_id, status):
+    def reset_volume_status(self, volume_id, **kwargs):
         """Reset the Specified Volume's Status."""
-        post_body = json.dumps({'os-reset_status': {"status": status}})
+        post_body = json.dumps({'os-reset_status': kwargs})
         resp, body = self.post('volumes/%s/action' % volume_id, post_body)
         self.expected_success(202, resp.status)
         return service_client.ResponseBody(resp, body)
@@ -218,14 +195,9 @@
         self.expected_success(202, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def create_volume_transfer(self, vol_id, display_name=None):
+    def create_volume_transfer(self, **kwargs):
         """Create a volume transfer."""
-        post_body = {
-            'volume_id': vol_id
-        }
-        if display_name:
-            post_body['name'] = display_name
-        post_body = json.dumps({'transfer': post_body})
+        post_body = json.dumps({'transfer': kwargs})
         resp, body = self.post('os-volume-transfer', post_body)
         body = json.loads(body)
         self.expected_success(202, resp.status)
@@ -239,7 +211,7 @@
         self.expected_success(200, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def list_volume_transfers(self, params=None):
+    def list_volume_transfers(self, **params):
         """List all the volume transfers created."""
         url = 'os-volume-transfer'
         if params:
@@ -255,24 +227,18 @@
         self.expected_success(202, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def accept_volume_transfer(self, transfer_id, transfer_auth_key):
+    def accept_volume_transfer(self, transfer_id, **kwargs):
         """Accept a volume transfer."""
-        post_body = {
-            'auth_key': transfer_auth_key,
-        }
         url = 'os-volume-transfer/%s/accept' % transfer_id
-        post_body = json.dumps({'accept': post_body})
+        post_body = json.dumps({'accept': kwargs})
         resp, body = self.post(url, post_body)
         body = json.loads(body)
         self.expected_success(202, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def update_volume_readonly(self, volume_id, readonly):
+    def update_volume_readonly(self, volume_id, **kwargs):
         """Update the Specified Volume readonly."""
-        post_body = {
-            'readonly': readonly
-        }
-        post_body = json.dumps({'os-update_readonly_flag': post_body})
+        post_body = json.dumps({'os-update_readonly_flag': kwargs})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
         self.expected_success(202, resp.status)
@@ -327,10 +293,8 @@
         self.expected_success(200, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def retype_volume(self, volume_id, volume_type, **kwargs):
+    def retype_volume(self, volume_id, **kwargs):
         """Updates volume with new volume type."""
-        post_body = {'new_type': volume_type}
-        post_body.update(kwargs)
-        post_body = json.dumps({'os-retype': post_body})
+        post_body = json.dumps({'os-retype': kwargs})
         resp, body = self.post('volumes/%s/action' % volume_id, post_body)
         self.expected_success(202, resp.status)
diff --git a/tempest/tests/cmd/test_javelin.py b/tempest/tests/cmd/test_javelin.py
index 56bc96c..62409bf 100644
--- a/tempest/tests/cmd/test_javelin.py
+++ b/tempest/tests/cmd/test_javelin.py
@@ -78,8 +78,8 @@
         mocked_function = self.fake_client.volumes.attach_volume
         mocked_function.assert_called_once_with(
             self.fake_object.volume['id'],
-            self.fake_object.server['id'],
-            self.fake_object['device'])
+            instance_uuid=self.fake_object.server['id'],
+            mountpoint=self.fake_object['device'])
 
 
 class TestCreateResources(JavelinUnitTest):
diff --git a/tempest/tests/cmd/test_list_plugins.py b/tempest/tests/cmd/test_list_plugins.py
new file mode 100644
index 0000000..17ddb18
--- /dev/null
+++ b/tempest/tests/cmd/test_list_plugins.py
@@ -0,0 +1,24 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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 subprocess
+
+from tempest.tests import base
+
+
+class TestTempestListPlugins(base.TestCase):
+    def test_run_list_plugins(self):
+        return_code = subprocess.call(
+            ['tempest', 'list-plugins'], stdout=subprocess.PIPE)
+        self.assertEqual(return_code, 0)