diff --git a/novaclient/tests/functional/base.py b/novaclient/tests/functional/base.py
index 27015bda8..2ccdbf448 100644
--- a/novaclient/tests/functional/base.py
+++ b/novaclient/tests/functional/base.py
@@ -12,10 +12,44 @@
 
 import os
 
-from tempest_lib.cli import base
+import fixtures
+import tempest_lib.cli.base
+import testtools
+
+import novaclient.client
 
 
-class ClientTestBase(base.ClientTestBase):
+# The following are simple filter functions that filter our available
+# image / flavor list so that they can be used in standard testing.
+def pick_flavor(flavors):
+    """Given a flavor list pick a reasonable one."""
+    for flavor in flavors:
+        if flavor.name == 'm1.tiny':
+            return flavor
+    for flavor in flavors:
+        if flavor.name == 'm1.small':
+            return flavor
+    raise NoFlavorException()
+
+
+def pick_image(images):
+    for image in images:
+        if image.name.startswith('cirros') and image.name.endswith('-uec'):
+            return image
+    raise NoImageException()
+
+
+class NoImageException(Exception):
+    """We couldn't find an acceptable image."""
+    pass
+
+
+class NoFlavorException(Exception):
+    """We couldn't find an acceptable flavor."""
+    pass
+
+
+class ClientTestBase(testtools.TestCase):
     """
     This is a first pass at a simple read only python-novaclient test. This
     only exercises client commands that are read only.
@@ -27,12 +61,65 @@ class ClientTestBase(base.ClientTestBase):
     * initially just check return codes, and later test command outputs
 
     """
-    def _get_clients(self):
+    log_format = ('%(asctime)s %(process)d %(levelname)-8s '
+                  '[%(name)s] %(message)s')
+
+    def setUp(self):
+        super(ClientTestBase, self).setUp()
+
+        test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
+        try:
+            test_timeout = int(test_timeout)
+        except ValueError:
+            test_timeout = 0
+        if test_timeout > 0:
+            self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
+
+        if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
+                os.environ.get('OS_STDOUT_CAPTURE') == '1'):
+            stdout = self.useFixture(fixtures.StringStream('stdout')).stream
+            self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
+        if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
+                os.environ.get('OS_STDERR_CAPTURE') == '1'):
+            stderr = self.useFixture(fixtures.StringStream('stderr')).stream
+            self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
+
+        if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
+                os.environ.get('OS_LOG_CAPTURE') != '0'):
+            self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
+                                                   format=self.log_format,
+                                                   level=None))
+
+        # TODO(sdague): while we collect this information in
+        # tempest-lib, we do it in a way that's not available for top
+        # level tests. Long term this probably needs to be in the base
+        # class.
+        user = os.environ['OS_USERNAME']
+        passwd = os.environ['OS_PASSWORD']
+        tenant = os.environ['OS_TENANT_NAME']
+        auth_url = os.environ['OS_AUTH_URL']
+
+        # TODO(sdague): we made a lot of fun of the glanceclient team
+        # for version as int in first parameter. I guess we know where
+        # they copied it from.
+        self.client = novaclient.client.Client(
+            2, user, passwd, tenant,
+            auth_url=auth_url)
+
+        # pick some reasonable flavor / image combo
+        self.flavor = pick_flavor(self.client.flavors.list())
+        self.image = pick_image(self.client.images.list())
+
+        # create a CLI client in case we'd like to do CLI
+        # testing. tempest_lib does this realy weird thing where it
+        # builds a giant factory of all the CLIs that it knows
+        # about. Eventually that should really be unwound into
+        # something more sensible.
         cli_dir = os.environ.get(
             'OS_NOVACLIENT_EXEC_DIR',
             os.path.join(os.path.abspath('.'), '.tox/functional/bin'))
 
-        return base.CLIClient(
+        self.cli_clients = tempest_lib.cli.base.CLIClient(
             username=os.environ.get('OS_USERNAME'),
             password=os.environ.get('OS_PASSWORD'),
             tenant_name=os.environ.get('OS_TENANT_NAME'),
@@ -40,5 +127,5 @@ class ClientTestBase(base.ClientTestBase):
             cli_dir=cli_dir)
 
     def nova(self, *args, **kwargs):
-        return self.clients.nova(*args,
-                                 **kwargs)
+        return self.cli_clients.nova(*args,
+                                     **kwargs)
diff --git a/novaclient/tests/functional/test_instances.py b/novaclient/tests/functional/test_instances.py
index c963a6b71..8df28d136 100644
--- a/novaclient/tests/functional/test_instances.py
+++ b/novaclient/tests/functional/test_instances.py
@@ -10,33 +10,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import os
 import time
 import uuid
 
-import novaclient.client
 from novaclient.tests.functional import base
 
 
-# TODO(sdague): content that probably should be in utils, also throw
-# Exceptions when they fail.
-def pick_flavor(flavors):
-    """Given a flavor list pick a reasonable one."""
-    for flavor in flavors:
-        if flavor.name == 'm1.tiny':
-            return flavor
-
-    for flavor in flavors:
-        if flavor.name == 'm1.small':
-            return flavor
-
-
-def pick_image(images):
-    for image in images:
-        if image.name.startswith('cirros') and image.name.endswith('-uec'):
-            return image
-
-
 def volume_id_from_cli_create(output):
     """Scrape the volume id out of the 'volume create' command
 
@@ -67,27 +46,6 @@ def volume_at_status(output, volume_id, status):
 
 
 class TestInstanceCLI(base.ClientTestBase):
-    def setUp(self):
-        super(TestInstanceCLI, self).setUp()
-        # TODO(sdague): while we collect this information in
-        # tempest-lib, we do it in a way that's not available for top
-        # level tests. Long term this probably needs to be in the base
-        # class.
-        user = os.environ['OS_USERNAME']
-        passwd = os.environ['OS_PASSWORD']
-        tenant = os.environ['OS_TENANT_NAME']
-        auth_url = os.environ['OS_AUTH_URL']
-
-        # TODO(sdague): we made a lot of fun of the glanceclient team
-        # for version as int in first parameter. I guess we know where
-        # they copied it from.
-        self.client = novaclient.client.Client(
-            2, user, passwd, tenant,
-            auth_url=auth_url)
-
-        # pick some reasonable flavor / image combo
-        self.flavor = pick_flavor(self.client.flavors.list())
-        self.image = pick_image(self.client.images.list())
 
     def test_attach_volume(self):
         """Test we can attach a volume via the cli.
diff --git a/novaclient/tests/functional/test_volumes_api.py b/novaclient/tests/functional/test_volumes_api.py
index e427c2e21..38c63bbf5 100644
--- a/novaclient/tests/functional/test_volumes_api.py
+++ b/novaclient/tests/functional/test_volumes_api.py
@@ -10,13 +10,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import os
 import time
 import uuid
 
 import six.moves
 
-from novaclient import client
 from novaclient import exceptions
 from novaclient.tests.functional import base
 
@@ -35,15 +33,6 @@ def wait_for_delete(test, name, thing, get_func):
 
 class TestVolumesAPI(base.ClientTestBase):
 
-    def setUp(self):
-        super(TestVolumesAPI, self).setUp()
-        user = os.environ['OS_USERNAME']
-        passwd = os.environ['OS_PASSWORD']
-        tenant = os.environ['OS_TENANT_NAME']
-        auth_url = os.environ['OS_AUTH_URL']
-
-        self.client = client.Client(2, user, passwd, tenant, auth_url=auth_url)
-
     def test_volumes_snapshots_types_create_get_list_delete(self):
         # Create a volume
         volume = self.client.volumes.create(1)