Merge "Revert an accidental error modification of config item help message"
diff --git a/README.rst b/README.rst
index f1dac1c..fc4de5e 100644
--- a/README.rst
+++ b/README.rst
@@ -196,18 +196,18 @@
 from a remote system running python 2.7. (or deploy a cloud guest in your cloud
 that has python 2.7)
 
-Python 3.4
+Python 3.x
 ----------
 
 Starting during the Liberty release development cycle work began on enabling
 Tempest to run under both Python 2.7 and Python 3.4. Tempest strives to fully
-support running with Python 3.4. A gating unit test job was added to also run
-Tempest's unit tests under Python 3.4. This means that the Tempest code at
-least imports under Python 3.4 and things that have unit test coverage will
-work on Python 3.4. However, because large parts of Tempest are self-verifying
-there might be uncaught issues running on Python 3.4. So until there is a gating
-job which does a full Tempest run using Python 3.4 there isn't any guarantee
-that running Tempest under Python 3.4 is bug free.
+support running with Python 3.4 and newer. A gating unit test job was added to
+also run Tempest's unit tests under Python 3. This means that the Tempest
+code at least imports under Python 3.4 and things that have unit test coverage
+will work on Python 3.4. However, because large parts of Tempest are
+self-verifying there might be uncaught issues running on Python 3. So until
+there is a gating job which does a full Tempest run using Python 3 there
+isn't any guarantee that running Tempest under Python 3 is bug free.
 
 Legacy run method
 -----------------
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index 9686473..728a9e6 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -14,8 +14,6 @@
 #    under the License.
 
 from tempest.api.volume import base
-from tempest.common.utils import data_utils as utils
-from tempest.common import waiters
 from tempest import config
 from tempest import test
 
@@ -34,45 +32,20 @@
         super(VolumesActionsV2Test, cls).resource_setup()
 
         # Create a test shared volume for tests
-        vol_name = utils.rand_name(cls.__name__ + '-Volume')
-        cls.name_field = cls.special_fields['name_field']
-        params = {cls.name_field: vol_name, 'size': CONF.volume.volume_size}
-
-        cls.volume = cls.client.create_volume(**params)['volume']
-        waiters.wait_for_volume_status(cls.client,
-                                       cls.volume['id'], 'available')
-
-    @classmethod
-    def resource_cleanup(cls):
-        # Delete the test volume
-        cls.delete_volume(cls.client, cls.volume['id'])
-
-        super(VolumesActionsV2Test, cls).resource_cleanup()
+        cls.volume = cls.create_volume()
 
     def _reset_volume_status(self, volume_id, status):
         # Reset the volume status
-        body = self.admin_volume_client.reset_volume_status(volume_id,
-                                                            status=status)
-        return body
+        self.admin_volume_client.reset_volume_status(volume_id, status=status)
 
     def tearDown(self):
         # Set volume's status to available after test
         self._reset_volume_status(self.volume['id'], status='available')
         super(VolumesActionsV2Test, self).tearDown()
 
-    def _create_temp_volume(self):
-        # Create a temp volume for force delete tests
-        vol_name = utils.rand_name(self.__class__.__name__ + '-Volume')
-        params = {self.name_field: vol_name, 'size': CONF.volume.volume_size}
-        temp_volume = self.client.create_volume(**params)['volume']
-        waiters.wait_for_volume_status(self.client,
-                                       temp_volume['id'], 'available')
-
-        return temp_volume
-
     def _create_reset_and_force_delete_temp_volume(self, status=None):
         # Create volume, reset volume status, and force delete temp volume
-        temp_volume = self._create_temp_volume()
+        temp_volume = self.create_volume()
         if status:
             self._reset_volume_status(temp_volume['id'], status)
         self.admin_volume_client.force_delete_volume(temp_volume['id'])
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index baa36a2..99185d2 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -14,13 +14,13 @@
 
 import os
 import shutil
-import subprocess
 import sys
 
 from cliff import command
 from oslo_config import generator
 from oslo_log import log as logging
 from six import moves
+from testrepository import commands
 
 from tempest.cmd import workspace
 
@@ -167,7 +167,8 @@
         self.generate_testr_conf(local_dir)
         # setup local testr working dir
         if not os.path.isdir(testr_dir):
-            subprocess.call(['testr', 'init'], cwd=local_dir)
+            commands.run_argv(['testr', 'init', '-d', local_dir], sys.stdin,
+                              sys.stdout, sys.stderr)
 
     def take_action(self, parsed_args):
         workspace_manager = workspace.WorkspaceManager(
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 236953c..5fa8b74 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -135,6 +135,12 @@
             workspace_mgr = workspace.WorkspaceManager(
                 parsed_args.workspace_path)
             path = workspace_mgr.get_workspace(parsed_args.workspace)
+            if not path:
+                sys.exit(
+                    "The %r workspace isn't registered in "
+                    "%r. Use 'tempest init' to "
+                    "register the workspace." %
+                    (parsed_args.workspace, workspace_mgr.path))
             os.chdir(path)
             # NOTE(mtreinish): tempest init should create a .testrepository dir
             # but since workspaces can be imported let's sanity check and
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index b36cf4e..3c58648 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -72,7 +72,10 @@
 
     @lockutils.synchronized('workspaces', external=True)
     def get_workspace(self, name):
-        """Returns the workspace that has the given name"""
+        """Returns the workspace that has the given name
+
+        If the workspace isn't registered then `None` is returned.
+        """
         self._populate()
         return self.workspaces.get(name)
 
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index 772391f..7ac347d 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -18,6 +18,7 @@
 import subprocess
 import tempfile
 
+import fixtures
 import mock
 
 from tempest.cmd import run
@@ -122,3 +123,32 @@
         # too.
         subprocess.call(['git', 'init'], stderr=DEVNULL)
         self.assertRunExit(['tempest', 'run'], 1)
+
+
+class TestTakeAction(base.TestCase):
+    def test_workspace_not_registered(self):
+        class Exception_(Exception):
+            pass
+
+        m_exit = self.useFixture(fixtures.MockPatch('sys.exit')).mock
+        # sys.exit must not continue (or exit)
+        m_exit.side_effect = Exception_
+
+        workspace = self.getUniqueString()
+
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+        parsed_args.config_file = []
+
+        # Override $HOME so that empty workspace gets created in temp dir.
+        self.useFixture(fixtures.TempHomeDir())
+
+        # Force use of the temporary home directory.
+        parsed_args.workspace_path = None
+
+        # Simulate --workspace argument.
+        parsed_args.workspace = workspace
+
+        self.assertRaises(Exception_, tempest_run.take_action, parsed_args)
+        exit_msg = m_exit.call_args[0][0]
+        self.assertIn(workspace, exit_msg)