Merge "Minor change to comment"
diff --git a/README.rst b/README.rst
index d109a2d..13f4f61 100644
--- a/README.rst
+++ b/README.rst
@@ -223,7 +223,7 @@
 
     $ cd $TEMPEST_ROOT_DIR
     $ oslo-config-generator --config-file \
-        etc/config-generator.tempest.conf \
+        tempest/cmd/config-generator.tempest.conf \
         --output-file etc/tempest.conf
 
 After that, open up the ``etc/tempest.conf`` file and edit the
diff --git a/releasenotes/notes/add_subunit_describe_calls-5498a37e6cd66c4b.yaml b/releasenotes/notes/add_subunit_describe_calls-5498a37e6cd66c4b.yaml
index b457ddd..092014e 100644
--- a/releasenotes/notes/add_subunit_describe_calls-5498a37e6cd66c4b.yaml
+++ b/releasenotes/notes/add_subunit_describe_calls-5498a37e6cd66c4b.yaml
@@ -1,4 +1,8 @@
 ---
 features:
-  - Adds subunit-describe-calls. A parser for subunit streams to determine what
+  - |
+    Adds subunit-describe-calls. A parser for subunit streams to determine what
     REST API calls are made inside of a test and in what order they are called.
+
+      * Input can be piped in or a file can be specified
+      * Output is shortened for stdout, the output file has more information
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 37aa5ac..5e75493 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -359,7 +359,7 @@
             for address in addresses:
                 if address['version'] == CONF.validation.ip_version_for_ssh:
                     return address['addr']
-            raise exceptions.ServerUnreachable()
+            raise exceptions.ServerUnreachable(server_id=server['id'])
         else:
             raise exceptions.InvalidConfiguration()
 
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index 60bb314..1879e46 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -1,4 +1,4 @@
-# Copyright 2013 OpenStack, LLC
+# Copyright 2013 OpenStack Foundation
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
diff --git a/tempest/api/identity/admin/v3/test_projects_negative.py b/tempest/api/identity/admin/v3/test_projects_negative.py
index e661f42..c76b9ee 100644
--- a/tempest/api/identity/admin/v3/test_projects_negative.py
+++ b/tempest/api/identity/admin/v3/test_projects_negative.py
@@ -1,4 +1,4 @@
-# Copyright 2013 OpenStack, LLC
+# Copyright 2013 OpenStack Foundation
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index b6dc488..b144c7c 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -166,6 +166,23 @@
         self.admin_backups_client.wait_for_backup_status(import_backup['id'],
                                                          'available')
 
+    @test.idempotent_id('47a35425-a891-4e13-961c-c45deea21e94')
+    def test_volume_backup_reset_status(self):
+        # Create a backup
+        backup_name = data_utils.rand_name('Backup')
+        backup = self.admin_backups_client.create_backup(
+            volume_id=self.volume['id'], name=backup_name)['backup']
+        self.addCleanup(self.admin_backups_client.delete_backup,
+                        backup['id'])
+        self.assertEqual(backup_name, backup['name'])
+        self.admin_backups_client.wait_for_backup_status(backup['id'],
+                                                         'available')
+        # Reset backup status to error
+        self.admin_backups_client.reset_backup_status(backup_id=backup['id'],
+                                                      status="error")
+        self.admin_backups_client.wait_for_backup_status(backup['id'],
+                                                         'error')
+
 
 class VolumesBackupsAdminV1Test(VolumesBackupsAdminV2Test):
     _api_version = 1
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index 9391823..0f868a9 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -21,13 +21,14 @@
 Runtime Arguments
 -----------------
 
-**--subunit, -s**: (Required) The path to the subunit file being parsed
+**--subunit, -s**: (Optional) The path to the subunit file being parsed,
+defaults to stdin
 
 **--non-subunit-name, -n**: (Optional) The file_name that the logs are being
 stored in
 
-**--output-file, -o**: (Required) The path where the JSON output will be
-written to
+**--output-file, -o**: (Optional) The path where the JSON output will be
+written to. This contains more information than is present in stdout.
 
 **--ports, -p**: (Optional) The path to a JSON file describing the ports being
 used by different services
@@ -35,13 +36,14 @@
 Usage
 -----
 
-subunit-describe-calls will take in a file path via the --subunit parameter
-which contains either a subunit v1 or v2 stream. This is then parsed checking
-for details contained in the file_bytes of the --non-subunit-name parameter
-(the default is pythonlogging which is what Tempest uses to store logs). By
-default the OpenStack Kilo release port defaults (http://bit.ly/22jpF5P)
-are used unless a file is provided via the --ports option. The resulting output
-is dumped in JSON output to the path provided in the --output-file option.
+subunit-describe-calls will take in either stdin subunit v1 or v2 stream or a
+file path which contains either a subunit v1 or v2 stream passed via the
+--subunit parameter. This is then parsed checking for details contained in the
+file_bytes of the --non-subunit-name parameter (the default is pythonlogging
+which is what Tempest uses to store logs). By default the OpenStack Kilo
+release port defaults (http://bit.ly/22jpF5P) are used unless a file is
+provided via the --ports option. The resulting output is dumped in JSON output
+to the path provided in the --output-file option.
 
 Ports file JSON structure
 ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -64,7 +66,11 @@
               "verb": "HTTP Verb",
               "service": "Name of the service",
               "url": "A shortened version of the URL called",
-              "status_code": "The status code of the response"
+              "status_code": "The status code of the response",
+              "request_headers": "The headers of the request",
+              "request_body": "The body of the request",
+              "response_headers": "The headers of the response",
+              "response_body": "The body of the response"
           }
       ]
   }
@@ -75,6 +81,7 @@
 import json
 import os
 import re
+import sys
 
 import subunit
 import testtools
@@ -91,6 +98,9 @@
                         '(?P<verb>\w*) (?P<url>.*) .*')
     port_re = re.compile(r'.*:(?P<port>\d+).*')
     path_re = re.compile(r'http[s]?://[^/]*/(?P<path>.*)')
+    request_re = re.compile(r'.* Request - Headers: (?P<headers>.*)')
+    response_re = re.compile(r'.* Response - Headers: (?P<headers>.*)')
+    body_re = re.compile(r'.*Body: (?P<body>.*)')
 
     # Based on mitaka defaults:
     # http://docs.openstack.org/mitaka/config-reference/
@@ -151,15 +161,46 @@
 
         calls = []
         for _, detail in details.items():
+            in_request = False
+            in_response = False
+            current_call = {}
             for line in detail.as_text().split("\n"):
-                match = self.url_re.match(line)
-                if match is not None:
-                    calls.append({
-                        "name": match.group("name"),
-                        "verb": match.group("verb"),
-                        "status_code": match.group("code"),
-                        "service": self.get_service(match.group("url")),
-                        "url": self.url_path(match.group("url"))})
+                url_match = self.url_re.match(line)
+                request_match = self.request_re.match(line)
+                response_match = self.response_re.match(line)
+                body_match = self.body_re.match(line)
+
+                if url_match is not None:
+                    if current_call != {}:
+                        calls.append(current_call.copy())
+                        current_call = {}
+                        in_request, in_response = False, False
+                    current_call.update({
+                        "name": url_match.group("name"),
+                        "verb": url_match.group("verb"),
+                        "status_code": url_match.group("code"),
+                        "service": self.get_service(url_match.group("url")),
+                        "url": self.url_path(url_match.group("url"))})
+                elif request_match is not None:
+                    in_request, in_response = True, False
+                    current_call.update(
+                        {"request_headers": request_match.group("headers")})
+                elif in_request and body_match is not None:
+                    in_request = False
+                    current_call.update(
+                        {"request_body": body_match.group(
+                            "body")})
+                elif response_match is not None:
+                    in_request, in_response = False, True
+                    current_call.update(
+                        {"response_headers": response_match.group(
+                            "headers")})
+                elif in_response and body_match is not None:
+                    in_response = False
+                    current_call.update(
+                        {"response_body": body_match.group("body")})
+            if current_call != {}:
+                calls.append(current_call.copy())
 
         return calls
 
@@ -206,8 +247,9 @@
         self.prog = "subunit-describe-calls"
 
         self.add_argument(
-            "-s", "--subunit", metavar="<subunit file>", required=True,
-            default=None, help="The path to the subunit output file.")
+            "-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>",
@@ -216,19 +258,18 @@
 
         self.add_argument(
             "-o", "--output-file", metavar="<output file>", default=None,
-            help="The output file name for the json.", required=True)
+            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.")
 
 
-def parse(subunit_file, non_subunit_name, ports):
+def parse(stream, non_subunit_name, ports):
     if ports is not None and os.path.exists(ports):
         ports = json.loads(open(ports).read())
 
     url_parser = UrlParser(ports)
-    stream = open(subunit_file, 'rb')
     suite = subunit.ByteStreamToStreamResult(
         stream, non_subunit_name=non_subunit_name)
     result = testtools.StreamToExtendedDecorator(url_parser)
@@ -248,8 +289,21 @@
 
 
 def output(url_parser, output_file):
-    with open(output_file, "w") as outfile:
-        outfile.write(json.dumps(url_parser.test_logs))
+    if output_file is not None:
+        with open(output_file, "w") as outfile:
+            outfile.write(json.dumps(url_parser.test_logs))
+        return
+
+    for test_name, items in url_parser.test_logs.iteritems():
+        sys.stdout.write('{0}\n'.format(test_name))
+        if not items:
+            sys.stdout.write('\n')
+            continue
+        for item in items:
+            sys.stdout.write('\t- {0} {1} request for {2} to {3}\n'.format(
+                item.get('status_code'), item.get('verb'),
+                item.get('service'), item.get('url')))
+        sys.stdout.write('\n')
 
 
 def entry_point():
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 67faad5..272f6e3 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -63,7 +63,8 @@
 
 
 class ServerUnreachable(exceptions.TempestException):
-    message = "The server is not reachable via the configured network"
+    message = ("Server %(server_id)s is not reachable via "
+               "the configured network")
 
 
 # NOTE(andreaf) This exception is added here to facilitate the migration
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
index 93382c0..6b6548e 100644
--- a/tempest/lib/common/utils/data_utils.py
+++ b/tempest/lib/common/utils/data_utils.py
@@ -75,7 +75,7 @@
     ascii_char = string.ascii_letters
     digits = string.digits
     digit = random.choice(string.digits)
-    puncs = '~!@#$%^&*_=+'
+    puncs = '~!@#%^&*_=+'
     punc = random.choice(puncs)
     seed = ascii_char + digits + puncs
     pre = upper + digit + punc
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index f889c44..b151375 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -664,7 +664,7 @@
             for address in addresses:
                 if address['version'] == CONF.validation.ip_version_for_ssh:
                     return address['addr']
-            raise exceptions.ServerUnreachable()
+            raise exceptions.ServerUnreachable(server_id=server['id'])
         else:
             raise exceptions.InvalidConfiguration()
 
diff --git a/tempest/services/volume/base/base_backups_client.py b/tempest/services/volume/base/base_backups_client.py
index 3842d66..63c5417 100644
--- a/tempest/services/volume/base/base_backups_client.py
+++ b/tempest/services/volume/base/base_backups_client.py
@@ -89,6 +89,13 @@
         self.expected_success(201, resp.status)
         return rest_client.ResponseBody(resp, body)
 
+    def reset_backup_status(self, backup_id, status):
+        """Reset the specified backup's status."""
+        post_body = json.dumps({'os-reset_status': {"status": status}})
+        resp, body = self.post('backups/%s/action' % backup_id, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
     def wait_for_backup_status(self, backup_id, status):
         """Waits for a Backup to reach a given status."""
         body = self.show_backup(backup_id)['backup']
@@ -99,7 +106,7 @@
             time.sleep(self.build_interval)
             body = self.show_backup(backup_id)['backup']
             backup_status = body['status']
-            if backup_status == 'error':
+            if backup_status == 'error' and backup_status != status:
                 raise exceptions.VolumeBackupException(backup_id=backup_id)
 
             if int(time.time()) - start >= self.build_timeout:
diff --git a/tempest/tests/cmd/test_subunit_describe_calls.py b/tempest/tests/cmd/test_subunit_describe_calls.py
index 43b417a..1c24c37 100644
--- a/tempest/tests/cmd/test_subunit_describe_calls.py
+++ b/tempest/tests/cmd/test_subunit_describe_calls.py
@@ -38,46 +38,159 @@
             os.path.dirname(os.path.abspath(__file__)),
             'sample_streams/calls.subunit')
         parser = subunit_describe_calls.parse(
-            subunit_file, "pythonlogging", None)
+            open(subunit_file), "pythonlogging", None)
         expected_result = {
-            'bar': [{'name': 'AgentsAdminTestJSON:setUp',
-                     'service': 'Nova',
-                     'status_code': '200',
-                     'url': 'v2.1/<id>/os-agents',
-                     'verb': 'POST'},
-                    {'name': 'AgentsAdminTestJSON:test_create_agent',
-                     'service': 'Nova',
-                     'status_code': '200',
-                     'url': 'v2.1/<id>/os-agents',
-                     'verb': 'POST'},
-                    {'name': 'AgentsAdminTestJSON:tearDown',
-                     'service': 'Nova',
-                     'status_code': '200',
-                     'url': 'v2.1/<id>/os-agents/1',
-                     'verb': 'DELETE'},
-                    {'name': 'AgentsAdminTestJSON:_run_cleanups',
-                     'service': 'Nova',
-                     'status_code': '200',
-                     'url': 'v2.1/<id>/os-agents/2',
-                     'verb': 'DELETE'}],
-            'foo': [{'name': 'AgentsAdminTestJSON:setUp',
-                     'service': 'Nova',
-                     'status_code': '200',
-                     'url': 'v2.1/<id>/os-agents',
-                     'verb': 'POST'},
-                    {'name': 'AgentsAdminTestJSON:test_delete_agent',
-                     'service': 'Nova',
-                     'status_code': '200',
-                     'url': 'v2.1/<id>/os-agents/3',
-                     'verb': 'DELETE'},
-                    {'name': 'AgentsAdminTestJSON:test_delete_agent',
-                     'service': 'Nova',
-                     'status_code': '200',
-                     'url': 'v2.1/<id>/os-agents',
-                     'verb': 'GET'},
-                    {'name': 'AgentsAdminTestJSON:tearDown',
-                     'service': 'Nova',
-                     'status_code': '404',
-                     'url': 'v2.1/<id>/os-agents/3',
-                     'verb': 'DELETE'}]}
+            'bar': [{
+                'name': 'AgentsAdminTestJSON:setUp',
+                'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
+                '"hypervisor": "common", "md5hash": '
+                '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
+                '"architecture": "tempest-x86_64-424013832", "os": "linux"}}',
+                'request_headers': "{'Content-Type': 'application/json', "
+                "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+                'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
+                '"hypervisor": "common", "md5hash": '
+                '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
+                '"architecture": "tempest-x86_64-424013832", "os": "linux", '
+                '"agent_id": 1}}',
+                'response_headers': "{'status': '200', 'content-length': "
+                "'203', 'x-compute-request-id': "
+                "'req-25ddaae2-0ef1-40d1-8228-59bd64a7e75b', 'vary': "
+                "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+                "'x-openstack-nova-api-version': '2.1', 'date': "
+                "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
+                "'application/json'}",
+                'service': 'Nova',
+                'status_code': '200',
+                'url': 'v2.1/<id>/os-agents',
+                'verb': 'POST'}, {
+                'name': 'AgentsAdminTestJSON:test_create_agent',
+                'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
+                '"hypervisor": "kvm", "md5hash": '
+                '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
+                '"architecture": "tempest-x86-252246646", "os": "win"}}',
+                'request_headers': "{'Content-Type': 'application/json', "
+                "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+                'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
+                '"hypervisor": "kvm", "md5hash": '
+                '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
+                '"architecture": "tempest-x86-252246646", "os": "win", '
+                '"agent_id": 2}}',
+                'response_headers': "{'status': '200', 'content-length': "
+                "'195', 'x-compute-request-id': "
+                "'req-b4136f06-c015-4e7e-995f-c43831e3ecce', 'vary': "
+                "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+                "'x-openstack-nova-api-version': '2.1', 'date': "
+                "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
+                "'application/json'}",
+                'service': 'Nova',
+                'status_code': '200',
+                'url': 'v2.1/<id>/os-agents',
+                'verb': 'POST'}, {
+                'name': 'AgentsAdminTestJSON:tearDown',
+                'request_body': 'None',
+                'request_headers': "{'Content-Type': 'application/json', "
+                "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+                'response_body': '',
+                'response_headers': "{'status': '200', 'content-length': "
+                "'0', 'x-compute-request-id': "
+                "'req-ee905fd6-a5b5-4da4-8c37-5363cb25bd9d', 'vary': "
+                "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+                "'x-openstack-nova-api-version': '2.1', 'date': "
+                "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
+                "'application/json'}",
+                'service': 'Nova',
+                'status_code': '200',
+                'url': 'v2.1/<id>/os-agents/1',
+                'verb': 'DELETE'}, {
+                'name': 'AgentsAdminTestJSON:_run_cleanups',
+                'request_body': 'None',
+                'request_headers': "{'Content-Type': 'application/json', "
+                "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+                'response_headers': "{'status': '200', 'content-length': "
+                "'0', 'x-compute-request-id': "
+                "'req-e912cac0-63e0-4679-a68a-b6d18ddca074', 'vary': "
+                "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+                "'x-openstack-nova-api-version': '2.1', 'date': "
+                "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
+                "'application/json'}",
+                'service': 'Nova',
+                'status_code': '200',
+                'url': 'v2.1/<id>/os-agents/2',
+                'verb': 'DELETE'}],
+            'foo': [{
+                'name': 'AgentsAdminTestJSON:setUp',
+                'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
+                '"hypervisor": "common", "md5hash": '
+                '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
+                '"architecture": "tempest-x86_64-948635295", "os": "linux"}}',
+                'request_headers': "{'Content-Type': 'application/json', "
+                "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+                'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
+                '"hypervisor": "common", "md5hash": '
+                '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
+                '"architecture": "tempest-x86_64-948635295", "os": "linux", '
+                '"agent_id": 3}}',
+                'response_headers': "{'status': '200', 'content-length': "
+                "'203', 'x-compute-request-id': "
+                "'req-ccd2116d-04b1-4ffe-ae32-fb623f68bf1c', 'vary': "
+                "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+                "'x-openstack-nova-api-version': '2.1', 'date': "
+                "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
+                "'application/json'}",
+                'service': 'Nova',
+                'status_code': '200',
+                'url': 'v2.1/<id>/os-agents',
+                'verb': 'POST'}, {
+                'name': 'AgentsAdminTestJSON:test_delete_agent',
+                'request_body': 'None',
+                'request_headers': "{'Content-Type': 'application/json', "
+                "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+                'response_body': '',
+                'response_headers': "{'status': '200', 'content-length': "
+                "'0', 'x-compute-request-id': "
+                "'req-6e7fa28f-ae61-4388-9a78-947c58bc0588', 'vary': "
+                "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+                "'x-openstack-nova-api-version': '2.1', 'date': "
+                "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
+                "'application/json'}",
+                'service': 'Nova',
+                'status_code': '200',
+                'url': 'v2.1/<id>/os-agents/3',
+                'verb': 'DELETE'}, {
+                'name': 'AgentsAdminTestJSON:test_delete_agent',
+                'request_body': 'None',
+                'request_headers': "{'Content-Type': 'application/json', "
+                "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+                'response_body': '{"agents": []}',
+                'response_headers': "{'status': '200', 'content-length': "
+                "'14', 'content-location': "
+                "'http://23.253.76.97:8774/v2.1/"
+                "cf6b1933fe5b476fbbabb876f6d1b924/os-agents', "
+                "'x-compute-request-id': "
+                "'req-e41aa9b4-41a6-4138-ae04-220b768eb644', 'vary': "
+                "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+                "'x-openstack-nova-api-version': '2.1', 'date': "
+                "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
+                "'application/json'}",
+                'service': 'Nova',
+                'status_code': '200',
+                'url': 'v2.1/<id>/os-agents',
+                'verb': 'GET'}, {
+                'name': 'AgentsAdminTestJSON:tearDown',
+                'request_body': 'None',
+                'request_headers': "{'Content-Type': 'application/json', "
+                "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+                'response_headers': "{'status': '404', 'content-length': "
+                "'82', 'x-compute-request-id': "
+                "'req-e297aeea-91cf-4f26-b49c-8f46b1b7a926', 'vary': "
+                "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+                "'x-openstack-nova-api-version': '2.1', 'date': "
+                "'Tue, 02 Feb 2016 03:27:02 GMT', 'content-type': "
+                "'application/json; charset=UTF-8'}",
+                'service': 'Nova',
+                'status_code': '404',
+                'url': 'v2.1/<id>/os-agents/3',
+                'verb': 'DELETE'}]}
+
         self.assertEqual(expected_result, parser.test_logs)