egl: fix _eglMatchDriver() return type
[mesa.git] / .gitlab-ci / tracie / tracie.py
index efedcbc4880fd616ec9374758192ffa57280223a..2c96bf4ec7de1df8854a5074d46c1a37121f32c4 100644 (file)
@@ -1,7 +1,11 @@
 import argparse
+import base64
+import datetime
 import enum
 import glob
 import hashlib
+import hmac
+import json
 import os
 import requests
 import sys
@@ -9,15 +13,21 @@ import tempfile
 import time
 import yaml
 import shutil
+import xml.etree.ElementTree as ET
 
+from email.utils import formatdate
 from pathlib import Path
 from PIL import Image
 from urllib import parse
 
 import dump_trace_images
 
-TRACES_DB_PATH = os.getcwd() + "/traces-db/"
-RESULTS_PATH = os.getcwd() + "/results/"
+TRACES_DB_PATH = "./traces-db/"
+RESULTS_PATH = "./results/"
+MINIO_HOST = "minio-packet.freedesktop.org"
+DASHBOARD_URL = "https://tracie.freedesktop.org/dashboard"
+
+minio_credentials = None
 
 def replay(trace_path, device_name):
     success = dump_trace_images.dump_from_trace(trace_path, [], device_name)
@@ -36,133 +46,187 @@ def replay(trace_path, device_name):
         log_file = files[0]
         return hashlib.md5(Image.open(image_file).tobytes()).hexdigest(), image_file, log_file
 
-def download_metadata(repo_url, repo_commit, trace_path):
-    # The GitLab API doesn't want the .git postfix
-    url = repo_url
-    if url.endswith(".git"):
-        url = url[:-4]
-    url = parse.urlparse(url)
-
-    url_path = url.path
-    if url_path.startswith("/"):
-        url_path = url_path[1:]
-
-    gitlab_api_url = url.scheme + "://" + url.netloc + "/api/v4/projects/" + parse.quote_plus(url_path)
-
-    r = requests.get(gitlab_api_url + "/repository/files/%s/raw?ref=%s" % (parse.quote_plus(trace_path), repo_commit))
-    metadata_raw = r.text.strip().split('\n')
-    metadata = dict(line.split(' ', 1) for line in metadata_raw[1:])
-    oid = metadata["oid"][7:] if metadata["oid"].startswith('sha256:') else metadata["oid"]
-    size = int(metadata['size'])
-
-    return oid, size
-
-def download_trace(repo_url, repo_commit, trace_path, oid, size):
-    headers = {
-        "Accept": "application/vnd.git-lfs+json",
-        "Content-Type": "application/vnd.git-lfs+json"
-    }
-    json = {
-        "operation": "download",
-        "transfers": [ "basic" ],
-        "ref": { "name": "refs/heads/%s" % repo_commit },
-        "objects": [
-            {
-                "oid": oid,
-                "size": size
-            }
-        ]
-    }
-
-    # The LFS API really wants the .git postfix...
-    if not repo_url.endswith(".git"):
-        repo_url += ".git"
-
-    r = requests.post(repo_url + "/info/lfs/objects/batch", headers=headers, json=json)
-    url = r.json()["objects"][0]["actions"]["download"]["href"]
-    open(TRACES_DB_PATH + trace_path, "wb").write(requests.get(url).content)
-
-def checksum(filename, hash_factory=hashlib.sha256, chunk_num_blocks=128):
-    h = hash_factory()
-    with open(filename,'rb') as f:
-        for chunk in iter(lambda: f.read(chunk_num_blocks*h.block_size), b''):
-            h.update(chunk)
-    return h.hexdigest()
-
-def ensure_trace(repo_url, repo_commit, trace):
+def gitlab_ensure_trace(project_url, trace):
     trace_path = TRACES_DB_PATH + trace['path']
-    if repo_url is None:
-        assert(repo_commit is None)
-        assert(os.path.exists(trace_path))
+    if project_url is None:
+        if not os.path.exists(trace_path):
+            print("{} missing".format(trace_path))
+            sys.exit(1)
         return
 
     os.makedirs(os.path.dirname(trace_path), exist_ok=True)
 
     if os.path.exists(trace_path):
-        local_oid = checksum(trace_path)
+        return
 
-    remote_oid, size = download_metadata(repo_url, repo_commit, trace['path'])
+    print("[check_image] Downloading trace %s" % (trace['path']), end=" ", flush=True)
+    download_time = time.time()
+    r = requests.get(project_url + trace['path'])
+    open(trace_path, "wb").write(r.content)
+    print("took %ds." % (time.time() - download_time), flush=True)
+
+def sign_with_hmac(key, message):
+    key = key.encode("UTF-8")
+    message = message.encode("UTF-8")
+
+    signature = hmac.new(key, message, hashlib.sha1).digest()
+
+    return base64.encodebytes(signature).strip().decode()
+
+def ensure_minio_credentials():
+    global minio_credentials
+
+    if minio_credentials is None:
+        minio_credentials = {}
+
+    params = {'Action': 'AssumeRoleWithWebIdentity',
+              'Version': '2011-06-15',
+              'RoleArn': 'arn:aws:iam::123456789012:role/FederatedWebIdentityRole',
+              'RoleSessionName': '%s:%s' % (os.environ['CI_PROJECT_PATH'], os.environ['CI_JOB_ID']),
+              'DurationSeconds': 900,
+              'WebIdentityToken': os.environ['CI_JOB_JWT']}
+    r = requests.post('https://%s' % (MINIO_HOST), params=params)
+    if r.status_code >= 400:
+        print(r.text)
+    r.raise_for_status()
+
+    root = ET.fromstring(r.text)
+    for attr in root.iter():
+        if attr.tag == '{https://sts.amazonaws.com/doc/2011-06-15/}AccessKeyId':
+            minio_credentials['AccessKeyId'] = attr.text
+        elif attr.tag == '{https://sts.amazonaws.com/doc/2011-06-15/}SecretAccessKey':
+            minio_credentials['SecretAccessKey'] = attr.text
+        elif attr.tag == '{https://sts.amazonaws.com/doc/2011-06-15/}SessionToken':
+            minio_credentials['SessionToken'] = attr.text
+
+def upload_to_minio(file_name, resource, content_type):
+    ensure_minio_credentials()
+
+    minio_key = minio_credentials['AccessKeyId']
+    minio_secret = minio_credentials['SecretAccessKey']
+    minio_token = minio_credentials['SessionToken']
+
+    date = formatdate(timeval=None, localtime=False, usegmt=True)
+    url = 'https://%s%s' % (MINIO_HOST, resource)
+    to_sign = "PUT\n\n%s\n%s\nx-amz-security-token:%s\n%s" % (content_type, date, minio_token, resource)
+    signature = sign_with_hmac(minio_secret, to_sign)
+
+    with open(file_name, 'rb') as data:
+        headers = {'Host': MINIO_HOST,
+                   'Date': date,
+                   'Content-Type': content_type,
+                   'Authorization': 'AWS %s:%s' % (minio_key, signature),
+                   'x-amz-security-token': minio_token}
+        print("Uploading artifact to %s" % url);
+        r = requests.put(url, headers=headers, data=data)
+        if r.status_code >= 400:
+            print(r.text)
+        r.raise_for_status()
+
+def upload_artifact(file_name, key, content_type):
+    resource = '/artifacts/%s/%s/%s/%s' % (os.environ['CI_PROJECT_PATH'],
+                                           os.environ['CI_PIPELINE_ID'],
+                                           os.environ['CI_JOB_ID'],
+                                           key)
+    upload_to_minio(file_name, resource, content_type)
+
+def ensure_reference_image(file_name, checksum):
+    resource = '/mesa-tracie-results/%s/%s.png' % (os.environ['CI_PROJECT_PATH'], checksum)
+    url = 'https://%s%s' % (MINIO_HOST, resource)
+    r = requests.head(url, allow_redirects=True)
+    if r.status_code == 200:
+        return
+    upload_to_minio(file_name, resource, 'image/png')
 
-    if not os.path.exists(trace_path) or local_oid != remote_oid:
-        print("[check_image] Downloading trace %s" % (trace['path']), end=" ", flush=True)
-        download_time = time.time()
-        download_trace(repo_url, repo_commit, trace['path'], remote_oid, size)
-        print("took %ds." % (time.time() - download_time), flush=True)
+def gitlab_check_trace(project_url, device_name, trace, expectation):
+    gitlab_ensure_trace(project_url, trace)
 
-def check_trace(repo_url, repo_commit, device_name, trace, expectation):
-    ensure_trace(repo_url, repo_commit, trace)
+    result = {}
+    result[trace['path']] = {}
+    result[trace['path']]['expected'] = expectation['checksum']
 
     trace_path = Path(TRACES_DB_PATH + trace['path'])
     checksum, image_file, log_file = replay(trace_path, device_name)
     if checksum is None:
-            return False
+        result[trace['path']]['actual'] = 'error'
+        return False, result
     elif checksum == expectation['checksum']:
-            print("[check_image] Images match for %s" % (trace['path']))
-            ok = True
+        print("[check_image] Images match for %s" % (trace['path']))
+        ok = True
     else:
-            print("[check_image] Images differ for %s (expected: %s, actual: %s)" %
-                  (trace['path'], expectation['checksum'], checksum))
-            print("[check_image] For more information see "
-                  "https://gitlab.freedesktop.org/mesa/mesa/blob/master/.gitlab-ci/tracie/README.md")
-            ok = False
+        print("[check_image] Images differ for %s (expected: %s, actual: %s)" %
+                (trace['path'], expectation['checksum'], checksum))
+        print("[check_image] For more information see "
+                "https://gitlab.freedesktop.org/mesa/mesa/blob/master/.gitlab-ci/tracie/README.md")
+        image_diff_url = "%s/imagediff/%s/%s/%s/%s/%s" % (DASHBOARD_URL,
+                                                       os.environ['CI_PROJECT_PATH'],
+                                                       os.environ['CI_PIPELINE_ID'],
+                                                       os.environ['CI_JOB_ID'],
+                                                       expectation['checksum'],
+                                                       checksum)
+        print("[check_image] %s" % image_diff_url)
+        ok = False
 
     trace_dir = os.path.split(trace['path'])[0]
-    results_path = os.path.join(RESULTS_PATH, trace_dir, "test", device_name)
+    dir_in_results = os.path.join(trace_dir, "test", device_name)
+    results_path = os.path.join(RESULTS_PATH, dir_in_results)
     os.makedirs(results_path, exist_ok=True)
     shutil.move(log_file, os.path.join(results_path, os.path.split(log_file)[1]))
+    if os.environ.get('TRACIE_UPLOAD_TO_MINIO', '0') == '1':
+        if ok:
+            if os.environ['CI_PROJECT_PATH'] == 'mesa/mesa':
+                ensure_reference_image(image_file, checksum)
+        else:
+            upload_artifact(image_file, 'traces/%s.png' % checksum, 'image/png')
     if not ok or os.environ.get('TRACIE_STORE_IMAGES', '0') == '1':
-            shutil.move(image_file, os.path.join(results_path, os.path.split(image_file)[1]))
+        image_name = os.path.split(image_file)[1]
+        shutil.move(image_file, os.path.join(results_path, image_name))
+        result[trace['path']]['image'] = os.path.join(dir_in_results, image_name)
 
-    return ok
+    result[trace['path']]['actual'] = checksum
 
-def main():
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--file', required=True,
-                        help='the name of the traces.yml file listing traces and their checksums for each device')
-    parser.add_argument('--device-name', required=True,
-                        help="the name of the graphics device used to replay traces")
+    return ok, result
 
-    args = parser.parse_args()
+def run(filename, device_name):
 
-    with open(args.file, 'r') as f:
+    with open(filename, 'r') as f:
         y = yaml.safe_load(f)
 
     if "traces-db" in y:
-        repo = y["traces-db"]["repo"]
-        commit_id = y["traces-db"]["commit"]
+        project_url = y["traces-db"]["download-url"]
     else:
-        repo = None
-        commit_id = None
+        project_url = None
 
-    traces = y['traces']
+    traces = y['traces'] or []
     all_ok = True
+    results = {}
     for trace in traces:
         for expectation in trace['expectations']:
-                if expectation['device'] == args.device_name:
-                        ok = check_trace(repo, commit_id, args.device_name, trace, expectation)
-                        all_ok = all_ok and ok
+            if expectation['device'] == device_name:
+                ok, result = gitlab_check_trace(project_url,
+                                                device_name, trace,
+                                                expectation)
+                all_ok = all_ok and ok
+                results.update(result)
+
+    os.makedirs(RESULTS_PATH, exist_ok=True)
+    with open(os.path.join(RESULTS_PATH, 'results.yml'), 'w') as f:
+        yaml.safe_dump(results, f, default_flow_style=False)
+    if os.environ.get('TRACIE_UPLOAD_TO_MINIO', '0') == '1':
+        upload_artifact(os.path.join(RESULTS_PATH, 'results.yml'), 'traces/results.yml', 'text/yaml')
+
+    return all_ok
+
+def main(args):
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--file', required=True,
+                        help='the name of the traces.yml file listing traces and their checksums for each device')
+    parser.add_argument('--device-name', required=True,
+                        help="the name of the graphics device used to replay traces")
 
-    sys.exit(0 if all_ok else 1)
+    args = parser.parse_args(args)
+    return run(args.file, args.device_name)
 
 if __name__ == "__main__":
-    main()
+    all_ok = main(sys.argv[1:])
+    sys.exit(0 if all_ok else 1)