Merge "Enable one flavor tests"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index c4dad7e..2fdbb7e 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -421,6 +421,22 @@
 # Enable diagnostic commands (boolean value)
 #enable=true
 
+# A regex to determine which requests should be traced.  This
+# is a regex to match the caller for rest client requests to
+# be able to selectively trace calls out of specific classes
+# and methods. It largely exists for test development, and is
+# not expected to be used in a real deploy of tempest. This
+# will be matched against the discovered ClassName:method in
+# the test environment.  Expected values for this field are:
+# * ClassName:test_method_name - traces one test_method  *
+# ClassName:setUp(Class) - traces specific setup functions  *
+# ClassName:tearDown(Class) - traces specific teardown
+# functions  * ClassName:_run_cleanups - traces the cleanup
+# functions  If nothing is specified, this feature is not
+# enabled. To trace everything specify .* as the regex.
+# (string value)
+#trace_requests=
+
 
 [identity]
 
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 778294e..f0a8c8d 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -28,7 +28,6 @@
 
 
 class ServersTestJSON(base.BaseV2ComputeTest):
-    run_ssh = CONF.compute.run_ssh
     disk_config = 'AUTO'
 
     @classmethod
@@ -89,7 +88,8 @@
         found = any([i for i in servers if i['id'] == self.server['id']])
         self.assertTrue(found)
 
-    @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+    @testtools.skipUnless(CONF.compute.run_ssh,
+                          'Instance validation tests are disabled.')
     @test.attr(type='gate')
     def test_verify_created_server_vcpus(self):
         # Verify that the number of vcpus reported by the instance matches
@@ -99,7 +99,8 @@
                                                   self.password)
         self.assertEqual(flavor['vcpus'], linux_client.get_number_of_vcpus())
 
-    @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+    @testtools.skipUnless(CONF.compute.run_ssh,
+                          'Instance validation tests are disabled.')
     @test.attr(type='gate')
     def test_host_name_is_same_as_server_name(self):
         # Verify the instance host name is the same as the server name
@@ -109,7 +110,6 @@
 
 
 class ServersWithSpecificFlavorTestJSON(base.BaseV2ComputeAdminTest):
-    run_ssh = CONF.compute.run_ssh
     disk_config = 'AUTO'
 
     @classmethod
@@ -135,7 +135,8 @@
         cls.client.wait_for_server_status(cls.server_initial['id'], 'ACTIVE')
         resp, cls.server = cls.client.get_server(cls.server_initial['id'])
 
-    @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+    @testtools.skipUnless(CONF.compute.run_ssh,
+                          'Instance validation tests are disabled.')
     @test.attr(type='gate')
     def test_verify_created_server_ephemeral_disk(self):
         # Verify that the ephemeral disk is created when creating server
diff --git a/tempest/api/compute/v3/servers/test_attach_volume.py b/tempest/api/compute/v3/servers/test_attach_volume.py
index 8577aab..28d8517 100644
--- a/tempest/api/compute/v3/servers/test_attach_volume.py
+++ b/tempest/api/compute/v3/servers/test_attach_volume.py
@@ -24,7 +24,6 @@
 
 
 class AttachVolumeV3Test(base.BaseV3ComputeTest):
-    run_ssh = CONF.compute.run_ssh
 
     def __init__(self, *args, **kwargs):
         super(AttachVolumeV3Test, self).__init__(*args, **kwargs)
@@ -76,7 +75,7 @@
         self.attached = True
         self.addCleanup(self._detach, server['id'], volume['id'])
 
-    @testtools.skipIf(not run_ssh, 'SSH required for this test')
+    @testtools.skipUnless(CONF.compute.run_ssh, 'SSH required for this test')
     @test.attr(type='gate')
     def test_attach_detach_volume(self):
         # Stop and Start a server with an attached volume, ensuring that
diff --git a/tempest/api/compute/v3/servers/test_create_server.py b/tempest/api/compute/v3/servers/test_create_server.py
index 14a4338..80c40a2 100644
--- a/tempest/api/compute/v3/servers/test_create_server.py
+++ b/tempest/api/compute/v3/servers/test_create_server.py
@@ -28,7 +28,6 @@
 
 
 class ServersV3Test(base.BaseV3ComputeTest):
-    run_ssh = CONF.compute.run_ssh
     disk_config = 'AUTO'
 
     @classmethod
@@ -90,7 +89,8 @@
         found = any([i for i in servers if i['id'] == self.server['id']])
         self.assertTrue(found)
 
-    @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+    @testtools.skipUnless(CONF.compute.run_ssh,
+                          'Instance validation tests are disabled.')
     @test.attr(type='gate')
     def test_verify_created_server_vcpus(self):
         # Verify that the number of vcpus reported by the instance matches
@@ -100,7 +100,8 @@
                                                   self.ssh_user, self.password)
         self.assertEqual(flavor['vcpus'], linux_client.get_number_of_vcpus())
 
-    @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+    @testtools.skipUnless(CONF.compute.run_ssh,
+                          'Instance validation tests are disabled.')
     @test.attr(type='gate')
     def test_host_name_is_same_as_server_name(self):
         # Verify the instance host name is the same as the server name
@@ -110,7 +111,6 @@
 
 
 class ServersWithSpecificFlavorV3Test(base.BaseV3ComputeAdminTest):
-    run_ssh = CONF.compute.run_ssh
     disk_config = 'AUTO'
 
     @classmethod
@@ -136,7 +136,8 @@
         cls.client.wait_for_server_status(cls.server_initial['id'], 'ACTIVE')
         resp, cls.server = cls.client.get_server(cls.server_initial['id'])
 
-    @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+    @testtools.skipUnless(CONF.compute.run_ssh,
+                          'Instance validation tests are disabled.')
     @test.attr(type='gate')
     def test_verify_created_server_ephemeral_disk(self):
         # Verify that the ephemeral disk is created when creating server
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 3c5feed..ab9d144 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -24,7 +24,6 @@
 
 
 class AttachVolumeTestJSON(base.BaseV2ComputeTest):
-    run_ssh = CONF.compute.run_ssh
 
     def __init__(self, *args, **kwargs):
         super(AttachVolumeTestJSON, self).__init__(*args, **kwargs)
@@ -76,7 +75,7 @@
         self.attached = True
         self.addCleanup(self._detach, server['id'], volume['id'])
 
-    @testtools.skipIf(not run_ssh, 'SSH required for this test')
+    @testtools.skipUnless(CONF.compute.run_ssh, 'SSH required for this test')
     @test.attr(type='gate')
     def test_attach_detach_volume(self):
         # Stop and Start a server with an attached volume, ensuring that
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index 61dbb4d..83470be 100644
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ b/tempest/api/orchestration/stacks/test_neutron_resources.py
@@ -33,7 +33,6 @@
         super(NeutronResourcesTestJSON, cls).setUpClass()
         if not CONF.orchestration.image_ref:
             raise cls.skipException("No image available to test")
-        cls.client = cls.orchestration_client
         os = clients.Manager()
         if not CONF.service_available.neutron:
             raise cls.skipException("Neutron support is required")
diff --git a/tempest/api/orchestration/stacks/test_non_empty_stack.py b/tempest/api/orchestration/stacks/test_non_empty_stack.py
index 1da533b..4b1b5ef 100644
--- a/tempest/api/orchestration/stacks/test_non_empty_stack.py
+++ b/tempest/api/orchestration/stacks/test_non_empty_stack.py
@@ -25,7 +25,6 @@
     @classmethod
     def setUpClass(cls):
         super(StacksTestJSON, cls).setUpClass()
-        cls.client = cls.orchestration_client
         cls.stack_name = data_utils.rand_name('heat')
         template = cls.load_template('non_empty_stack')
 
diff --git a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
index 1edae72..60b8dc1 100644
--- a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
+++ b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
@@ -27,7 +27,6 @@
     @classmethod
     def setUpClass(cls):
         super(NovaKeyPairResourcesYAMLTest, cls).setUpClass()
-        cls.client = cls.orchestration_client
         cls.stack_name = data_utils.rand_name('heat')
         template = cls.load_template('nova_keypair', ext=cls._tpl_type)
 
diff --git a/tempest/api/orchestration/stacks/test_server_cfn_init.py b/tempest/api/orchestration/stacks/test_server_cfn_init.py
index b590f88..5f65193 100644
--- a/tempest/api/orchestration/stacks/test_server_cfn_init.py
+++ b/tempest/api/orchestration/stacks/test_server_cfn_init.py
@@ -34,7 +34,6 @@
         super(ServerCfnInitTestJSON, cls).setUpClass()
         if not CONF.orchestration.image_ref:
             raise cls.skipException("No image available to test")
-        cls.client = cls.orchestration_client
         template = cls.load_template('cfn_init_signal')
         stack_name = data_utils.rand_name('heat')
         if CONF.orchestration.keypair_name:
diff --git a/tempest/api/orchestration/stacks/test_stacks.py b/tempest/api/orchestration/stacks/test_stacks.py
index 3ffa0bc..867995c 100644
--- a/tempest/api/orchestration/stacks/test_stacks.py
+++ b/tempest/api/orchestration/stacks/test_stacks.py
@@ -25,7 +25,6 @@
     @classmethod
     def setUpClass(cls):
         super(StacksTestJSON, cls).setUpClass()
-        cls.client = cls.orchestration_client
 
     @attr(type='smoke')
     def test_stack_list_responds(self):
diff --git a/tempest/api/orchestration/stacks/test_swift_resources.py b/tempest/api/orchestration/stacks/test_swift_resources.py
index 5948e11..6d53fb2 100644
--- a/tempest/api/orchestration/stacks/test_swift_resources.py
+++ b/tempest/api/orchestration/stacks/test_swift_resources.py
@@ -29,7 +29,6 @@
     @test.safe_setup
     def setUpClass(cls):
         super(SwiftResourcesTestJSON, cls).setUpClass()
-        cls.client = cls.orchestration_client
         cls.stack_name = data_utils.rand_name('heat')
         template = cls.load_template('swift_basic')
         os = clients.Manager()
diff --git a/tempest/api/orchestration/stacks/test_templates.py b/tempest/api/orchestration/stacks/test_templates.py
index 22f66dc..74950a9 100644
--- a/tempest/api/orchestration/stacks/test_templates.py
+++ b/tempest/api/orchestration/stacks/test_templates.py
@@ -29,7 +29,6 @@
     @test.safe_setup
     def setUpClass(cls):
         super(TemplateYAMLTestJSON, cls).setUpClass()
-        cls.client = cls.orchestration_client
         cls.stack_name = data_utils.rand_name('heat')
         cls.stack_identifier = cls.create_stack(cls.stack_name, cls.template)
         cls.client.wait_for_stack_status(cls.stack_identifier,
diff --git a/tempest/api/orchestration/stacks/test_templates_negative.py b/tempest/api/orchestration/stacks/test_templates_negative.py
index a2a6f98..b325104 100644
--- a/tempest/api/orchestration/stacks/test_templates_negative.py
+++ b/tempest/api/orchestration/stacks/test_templates_negative.py
@@ -32,7 +32,6 @@
     @classmethod
     def setUpClass(cls):
         super(TemplateYAMLNegativeTestJSON, cls).setUpClass()
-        cls.client = cls.orchestration_client
         cls.parameters = {}
 
     @test.attr(type=['gate', 'negative'])
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 35d4ff2..830968e 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -281,24 +281,50 @@
                 return resp[i]
         return ""
 
-    def _log_request(self, method, req_url, resp, secs=""):
+    def _log_request(self, method, req_url, resp,
+                     secs="", req_headers=None,
+                     req_body=None, resp_body=None):
         # if we have the request id, put it in the right part of the log
         extra = dict(request_id=self._get_request_id(resp))
         # NOTE(sdague): while we still have 6 callers to this function
         # we're going to just provide work around on who is actually
         # providing timings by gracefully adding no content if they don't.
         # Once we're down to 1 caller, clean this up.
+        caller_name = self._find_caller()
         if secs:
             secs = " %.3fs" % secs
         self.LOG.info(
             'Request (%s): %s %s %s%s' % (
-                self._find_caller(),
+                caller_name,
                 resp['status'],
                 method,
                 req_url,
                 secs),
             extra=extra)
 
+        # We intentionally duplicate the info content because in a parallel
+        # world this is important to match
+        trace_regex = CONF.debug.trace_requests
+        if trace_regex and re.search(trace_regex, caller_name):
+            log_fmt = """Request (%s): %s %s %s%s
+    Request - Headers: %s
+        Body: %s
+    Response - Headers: %s
+        Body: %s"""
+
+            self.LOG.debug(
+                log_fmt % (
+                    caller_name,
+                    resp['status'],
+                    method,
+                    req_url,
+                    secs,
+                    str(req_headers),
+                    str(req_body)[:2048],
+                    str(resp),
+                    str(resp_body)[:2048]),
+                extra=extra)
+
     def _parse_resp(self, body):
         if self._get_type() is "json":
             body = json.loads(body)
@@ -382,7 +408,10 @@
         resp, resp_body = self.http_obj.request(
             req_url, method, headers=req_headers, body=req_body)
         end = time.time()
-        self._log_request(method, req_url, resp, secs=(end - start))
+        self._log_request(method, req_url, resp, secs=(end - start),
+                          req_headers=req_headers, req_body=req_body,
+                          resp_body=resp_body)
+
         # Verify HTTP response codes
         self.response_checker(method, url, req_headers, req_body, resp,
                               resp_body)
diff --git a/tempest/config.py b/tempest/config.py
index af96132..0212d8a 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -794,6 +794,26 @@
     cfg.BoolOpt('enable',
                 default=True,
                 help="Enable diagnostic commands"),
+    cfg.StrOpt('trace_requests',
+               default='',
+               help="""A regex to determine which requests should be traced.
+
+This is a regex to match the caller for rest client requests to be able to
+selectively trace calls out of specific classes and methods. It largely
+exists for test development, and is not expected to be used in a real deploy
+of tempest. This will be matched against the discovered ClassName:method
+in the test environment.
+
+Expected values for this field are:
+
+ * ClassName:test_method_name - traces one test_method
+ * ClassName:setUp(Class) - traces specific setup functions
+ * ClassName:tearDown(Class) - traces specific teardown functions
+ * ClassName:_run_cleanups - traces the cleanup functions
+
+If nothing is specified, this feature is not enabled. To trace everything
+specify .* as the regex.
+""")
 ]
 
 input_scenario_group = cfg.OptGroup(name="input-scenario",