add tempest report and logs parser initial script

Change-Id: I82bb5fdcc78e293359698b21537bf96be6e8451c
Related-prod: PRODX-19554
diff --git a/tempest_tests_resources/README.md b/tempest_tests_resources/README.md
new file mode 100644
index 0000000..551104d
--- /dev/null
+++ b/tempest_tests_resources/README.md
@@ -0,0 +1,19 @@
+Tempest Resources Parser
+======================
+
+This tool creating machine readable YAML file with all resources used in Tempest tests
+
+How to use
+----------
+
+Update your env variabled or add report and result file to artifacts dir
+```
+export REPORT_NAME='' \
+export TEMPORARY_FILE_NAME='' \
+export RESULT_FILE_NAME='' \
+export TEMPEST_REPORT_XML=''
+```
+
+Run report parser script:
+
+``python3 report_parser.py``
diff --git a/tempest_tests_resources/__init__.py b/tempest_tests_resources/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest_tests_resources/__init__.py
diff --git a/tempest_tests_resources/config.py b/tempest_tests_resources/config.py
new file mode 100644
index 0000000..9a3a450
--- /dev/null
+++ b/tempest_tests_resources/config.py
@@ -0,0 +1,10 @@
+import os
+
+REPORT_NAME = os.environ.get('REPORT_NAME', 'artifacts/tempest.log')
+TEMPEST_REPORT_XML = os.environ.get('TEMPEST_REPORT_XML', 'artifacts/tempest_report.xml')
+
+# Results machine readable file
+RESOURCES_FILE_NAME = os.environ.get('RESULT_FILE_NAME', 'artifacts/tempest_resources.yaml')
+
+# Temporary file
+TEMPORARY_FILE_NAME = os.environ.get('TEMPORARY_FILE_NAME', 'artifacts/tempest_temporary')
diff --git a/tempest_tests_resources/report_parser.py b/tempest_tests_resources/report_parser.py
new file mode 100644
index 0000000..4c43301
--- /dev/null
+++ b/tempest_tests_resources/report_parser.py
@@ -0,0 +1,314 @@
+import re
+import subprocess
+import json
+import yaml
+import os
+
+import xml.etree.ElementTree as ET
+
+import config
+
+
+# config.py
+REPORT_NAME = config.REPORT_NAME
+TEMPORARY_FILENAME = config.TEMPORARY_FILE_NAME
+TEMPEST_TESTS_RESOURCES = config.RESOURCES_FILE_NAME
+
+
+def simplify_logfile(report_name, temporary_filename):
+    """ Simplify full tempest log file and write it to temp file
+    After simplifying temp file looks like:
+      ...
+      Request...
+      Req_headers...
+        Body: {<request_body>}
+      Response:
+        Body: {<response_body>}
+      ---
+      ...
+    :param report_name: full tempest logs file
+    :param temporary_filename: simplified file
+    """
+    run_cmd = f"grep -vE '(auth/token|keystoneauth|connectionpool)' " \
+              f"{report_name} | grep -A4 -E '( POST| DELETE| PUT)' " \
+              f"> {temporary_filename}"
+    subprocess.check_output(run_cmd, shell=True)
+
+
+def get_request_response(temporary_filename):
+    """ Get request+testname+response
+    :param temporary_filename: simplified report filename
+    :return: list with lines that contains request and response
+    """
+    with open(temporary_filename, 'r') as temp_file:
+        request = []
+        lines = temp_file.readlines()
+        for line in lines:
+            if line.startswith('--'):
+                yield request
+                request = []
+            else:
+                request.append(line)
+
+
+def _get_test_name(request,
+                   methods_skip_list=[
+                       'tearDownClass',
+                       'tearDown',
+                       '_run_cleanups',
+                       'setUp',
+                       'setUpClass',
+                       'tearDownClass',]):
+    """
+    :param request: request body
+    :param methods_skip_list: what methods to skip
+    :return:
+    """
+    # Skip list to process requests from tests only
+
+    try:
+        # regex for: (ClassName:test_name)
+        test_name = re.search(r'\((\w+:.+\))', request)[0][1:-1]
+
+        # Skip if method name in skip list
+        if test_name.split(':')[1] in methods_skip_list:
+            return
+        return test_name.replace(':', '.')
+    except TypeError:
+        pass
+        # TODO(imenkov): add logging
+        # print(f"request: {request} failed to find name")
+
+
+def _get_response_body(response):
+    """ Method to get response body as dict
+    :param response: line with response
+    :return: dict with body or empty dict if
+             body is not readable
+    """
+    try:
+        # regex to search dict in response
+        body = re.search(
+            r'(\{.[a-zA-Z]+).+(}|])',
+            response)[0]
+        if body:
+            if 'badRequest' not in body:
+                res = json.loads(body)
+        return res
+    except Exception:
+        return response.split(
+            '_log_request_full')[0].strip(' ').replace(
+            "Body: b", "").strip("\'")
+
+
+def _get_openstack_service_name(request):
+    #TODO IMENKOV FIX ME
+    service_name = re.search(
+        r'(?<=\:\/\/).+?(?=(\.))',
+        request)[0]
+    return service_name
+
+
+def _get_status_code_and_method(request):
+    status_code = re.search(
+        r'(?<=\): ).+?(?=( http))',
+        request)[0]
+    return status_code.split()
+
+
+def _get_request_id(request):
+    """
+    :param request: request line from logs
+    :return: request-id like: req-93636f78-031b-41bc-abb5-9533ab7a3df4
+    """
+    try:
+        req_id = re.search(
+            r"(?<=\[)req-.+?(?= \])",
+            request)
+        if req_id:
+            return req_id[0]
+    except TypeError:
+        # TODO(imenkov) add logging to track not covered requests
+        # print(f"Request ID not found for request: {request}")
+        return 'req_id_not_found'
+
+
+def _get_resource_name_from_request_body(request, os_resource_name=None):
+    """
+    :param request: request body
+    :param os_resource_name: OpenStack resource name (server/volume e.t.c)
+    :return: resource name (tempest-*) or `resource_name_not_defined`
+    """
+    body = _get_response_body(request)
+    try:
+        name = body.get(os_resource_name, {}).get('name',
+                                                  'resource_name_not_defined')
+        return name
+    except AttributeError:
+        return 'resource_name_not_defined'
+
+
+def generate_tests_resources():
+    """
+    <test identifier>:
+      status: PASSED|FAILED
+      resources:
+        <openstack-service name (nova|neutron|glance|keystone)>:
+           <resource-name (port|server|security-group|router)>:
+           <request-id (req-xxxx) >:
+             name: <resource name (test-port-mytest|test-vm-)>
+             id/uuid: <resource id>
+    #        requst: <exact request>
+            http:
+               error: <if exists>
+               status_code: <>
+    """
+    result = {}
+
+    for request in get_request_response(TEMPORARY_FILENAME):
+
+        # Get test name from request
+        test_name = _get_test_name(request[0])
+        if not test_name:
+            continue
+
+        if not result.get(test_name):
+            result[test_name] = {"status": None,
+                                 "resources": {}}
+
+        status_and_method = _get_status_code_and_method(request[0])
+        status_code = status_and_method[0]
+        http_method = status_and_method[1]
+
+        openstack_service = _get_openstack_service_name(request[0])
+        if not result[test_name]['resources'].get(openstack_service):
+            result[test_name]['resources'][openstack_service] = {}
+
+
+        response_body = _get_response_body(request[-1])
+        if not isinstance(response_body, dict):
+            request_id = _get_request_id(request[0])
+
+            # Check request body
+            os_resource_name = _get_resource_name_from_request_body(
+                request[2])
+
+            if not result[test_name]['resources'][
+                openstack_service].get(os_resource_name):
+                result[test_name]['resources'][openstack_service][os_resource_name] = {}
+
+            result[test_name]['resources'][
+                openstack_service][os_resource_name][request_id] = {
+                'http': {'response_body': response_body,
+                         'status_code': status_code,
+                         'http_method': http_method}}
+            continue
+
+        for os_resource_name in response_body.keys():
+
+            if not result[test_name]['resources'][openstack_service].get(
+                    os_resource_name):
+                result[test_name]['resources'][openstack_service][
+                    os_resource_name] = {}
+
+            request_id = _get_request_id(request[0])
+
+            if not result[test_name]['resources'][openstack_service][
+                os_resource_name].get(request_id):
+
+                result[test_name]['resources'][openstack_service][
+                    os_resource_name][request_id] = {
+                    'http': {'status_code': status_code,
+                             'http_method': http_method}}
+
+            #TODO (IMENKOV) ADD 400/500
+            # Check that response is dict
+            # In some cases response can contain strings as
+            # instance logs, hash or lists
+            if isinstance(response_body[os_resource_name], dict):
+                resource_id = response_body[os_resource_name].get('id')
+                resource_name = response_body[os_resource_name].get('name')
+            else:
+                resource_id = None
+                resource_name = None
+
+            # Add resource id to yaml
+            # ...
+            # testname:
+            #   os_resource_name:
+            #     - resource_id1
+            #     - resource_id2
+            # ...
+
+            if resource_id:
+                result[test_name]['resources'][openstack_service][
+                    os_resource_name][request_id]['id'] = resource_id
+            if not resource_name:
+                resource_name = _get_resource_name_from_request_body(
+                    request[2], os_resource_name)
+                result[test_name]['resources'][openstack_service][
+                    os_resource_name][request_id]['name'] = resource_name
+            else:
+                result[test_name]['resources'][openstack_service][
+                    os_resource_name][request_id]['name'] = resource_name
+
+            # Check if resource doesn't contain IDs - cleanup it
+            if not result[test_name]['resources'][openstack_service][
+                os_resource_name][request_id]:
+                del result[test_name]['resources'][openstack_service][
+                    os_resource_name][request_id]
+
+    return result
+
+
+def add_test_result_from_xml(report_name, result):
+    tree = ET.parse(report_name)
+    root = tree.getroot()
+
+    if root[0].tag == 'testsuite':
+        root = root[0]
+
+    for child in root:
+        classname = child.attrib['classname']
+        name = child.attrib['name']
+        if classname and name:
+            short_classname = classname.split('.')[-1]
+            short_name = name.split('[')[0]
+            short_test_name = f"{short_classname}.{short_name}"
+
+            # (imenkov) mb use it as key
+            full_test_name = f"{classname}.{name}"
+
+        try:
+            test_status = child[0].tag
+        except IndexError:
+            test_status = 'passed'
+
+        if short_test_name and short_test_name in result:
+            # TODO(imenkov): how to avoid issue
+            # we can see in report 2 tests:
+            #   test_cannot_create_MX_with_1_empty_preference
+            #   test_cannot_create_MX_with_2_minus_zero_preference
+            #
+            # but they logged as one:
+            #   RecordsetValidationTest:test_cannot_create_MX_with
+            if not result[short_test_name].get('full_test_name'):
+                result[short_test_name]['full_test_name'] = []
+            result[short_test_name]['full_test_name'].append(full_test_name)
+            result[short_test_name]['status'] = test_status
+
+
+def delete_temporary_file(path_to_temp_file):
+    os.remove(path_to_temp_file)
+
+
+simplify_logfile(REPORT_NAME, TEMPORARY_FILENAME)
+result = generate_tests_resources()
+add_test_result_from_xml(config.TEMPEST_REPORT_XML, result)
+
+# NOTE(imenkov): currently skipped for debug
+# delete_temporary_file(TEMPORARY_FILENAME)
+
+# Write results to yaml file
+with open(TEMPEST_TESTS_RESOURCES, 'w') as res_file:
+    yaml.dump(result, res_file)