Skia has proposed merging ~hyask/autopkgtest-cloud:skia/improve_browsing into autopkgtest-cloud:master.
Requested reviews: Canonical's Ubuntu QA (canonical-ubuntu-qa) For more details, see: https://code.launchpad.net/~hyask/autopkgtest-cloud/+git/autopkgtest-cloud/+merge/461022 A bit of refactor to bring the feature of displaying currently running jobs on each package's page. Also allow easier local development. -- Your team Canonical's Ubuntu QA is requested to review the proposed merge of ~hyask/autopkgtest-cloud:skia/improve_browsing into autopkgtest-cloud:master.
diff --git a/charms/focal/autopkgtest-web/charmcraft.yaml b/charms/focal/autopkgtest-web/charmcraft.yaml index 93c0816..546437f 100644 --- a/charms/focal/autopkgtest-web/charmcraft.yaml +++ b/charms/focal/autopkgtest-web/charmcraft.yaml @@ -6,6 +6,7 @@ parts: build-snaps: [charm] build-packages: - libjs-jquery + - libjs-bootstrap - python3-dev bases: - build-on: diff --git a/charms/focal/autopkgtest-web/reactive/autopkgtest_web.py b/charms/focal/autopkgtest-web/reactive/autopkgtest_web.py index e1be1a2..6920fc1 100644 --- a/charms/focal/autopkgtest-web/reactive/autopkgtest_web.py +++ b/charms/focal/autopkgtest-web/reactive/autopkgtest_web.py @@ -338,20 +338,6 @@ def clear_github_status_credentials(): pass -@when_not("autopkgtest-web.bootstrap-symlinked") -def symlink_bootstrap(): - try: - os.symlink( - os.path.join( - os.path.sep, "usr", "share", "javascript", "bootstrap" - ), - os.path.join(charm_dir(), "webcontrol", "static", "bootstrap"), - ) - set_flag("autopkgtest-web.bootstrap-symlinked") - except FileExistsError: - pass - - @when_not("autopkgtest-web.runtime-dir-created") def make_runtime_tmpfiles(): with open("/etc/tmpfiles.d/autopkgtest-web-runtime.conf", "w") as r: diff --git a/charms/focal/autopkgtest-web/webcontrol/README.md b/charms/focal/autopkgtest-web/webcontrol/README.md new file mode 100644 index 0000000..e489c2a --- /dev/null +++ b/charms/focal/autopkgtest-web/webcontrol/README.md @@ -0,0 +1,12 @@ +# autopkgtest-cloud web frontend + +## Developing browse.cgi locally + +Install the dependencies: +`sudo apt install python3-flask python3-distro-info libjs-jquery libjs-bootstrap` + +Then simply run `./browse-test-py`, it will launch the flask application locally +with some mocked data. +As the import of `browse.cgi` is done trough `importlib`, changes in that file +will not be reloaded automatically, so you'll still need to restart the app +manually. diff --git a/charms/focal/autopkgtest-web/webcontrol/browse-test.py b/charms/focal/autopkgtest-web/webcontrol/browse-test.py new file mode 100755 index 0000000..a5c5b4e --- /dev/null +++ b/charms/focal/autopkgtest-web/webcontrol/browse-test.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +"""Run browse app in local debug mode for testing.""" + +import importlib +from pathlib import Path + +from helpers import tests, utils + +# import browse.cgi +browse_path = str(Path(__file__).parent / "browse.cgi") +loader = importlib.machinery.SourceFileLoader("browse", browse_path) +spec = importlib.util.spec_from_loader("browse", loader) +browse = importlib.util.module_from_spec(spec) +loader.exec_module(browse) + + +if __name__ == "__main__": + browse.db_con = utils.init_db(":memory:", check_same_thread=False) + with browse.db_con: + tests.populate_dummy_db(browse.db_con) + browse.swift_container_url = "swift-%s" + browse.AMQP_QUEUE_CACHE = Path("/dev/shm/queue.json") + tests.populate_dummy_amqp_cache(browse.AMQP_QUEUE_CACHE) + browse.RUNNING_CACHE = Path("/dev/shm/running.json") + tests.populate_dummy_running_cache(browse.RUNNING_CACHE) + + browse.app.run(host="0.0.0.0", debug=True) diff --git a/charms/focal/autopkgtest-web/webcontrol/browse.cgi b/charms/focal/autopkgtest-web/webcontrol/browse.cgi index f39b30f..f6be794 100755 --- a/charms/focal/autopkgtest-web/webcontrol/browse.cgi +++ b/charms/focal/autopkgtest-web/webcontrol/browse.cgi @@ -10,9 +10,9 @@ import sqlite3 from collections import OrderedDict from wsgiref.handlers import CGIHandler -import distro_info import flask from helpers.admin import select_abnormally_long_jobs +from helpers.utils import get_all_releases, get_supported_releases from werkzeug.middleware.proxy_fix import ProxyFix app = flask.Flask("browse") @@ -20,13 +20,11 @@ app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1) db_con = None swift_container_url = None -UDI = distro_info.UbuntuDistroInfo() -ALL_UBUNTU_RELEASES = UDI.all -SUPPORTED_UBUNTU_RELEASES = sorted( - set(UDI.supported() + UDI.supported_esm()), key=ALL_UBUNTU_RELEASES.index -) - +ALL_UBUNTU_RELEASES = get_all_releases() +SUPPORTED_UBUNTU_RELEASES = get_supported_releases() INDEXED_PACKAGES_FP = "" +AMQP_QUEUE_CACHE = "/var/lib/cache-amqp/queued.json" +RUNNING_CACHE = "/run/amqp-status-collector/running.json" def init_config(): @@ -60,6 +58,16 @@ def get_test_id(release, arch, src): return None +def get_running_jobs(): + try: + with open(RUNNING_CACHE) as f: + # package -> runhash -> release -> arch -> (params, duration, logtail) + running_info = json.load(f) + except FileNotFoundError: + running_info = {} + return running_info + + def render(template, code=200, **kwargs): # sort the values passed in, so that releases are in the right order try: @@ -81,7 +89,7 @@ def render(template, code=200, **kwargs): flask.render_template( template, base_url=flask.url_for("index_root"), - static_url=flask.url_for("static", filename="/"), + static_url=flask.url_for("static", filename=""), **kwargs ), code, @@ -147,7 +155,7 @@ def get_queue_info(): Return (releases, arches, context -> release -> arch -> (queue_size, [requests])). """ - with open("/var/lib/cache-amqp/queued.json", "r") as json_file: + with open(AMQP_QUEUE_CACHE, "r") as json_file: queue_info_j = json.load(json_file) arches = queue_info_j["arches"] @@ -268,6 +276,10 @@ def package_overview(package, _=None): arches.add(row[3]) results.setdefault(row[2], {})[row[3]] = human_exitcode(row[1]) + running_info = dict( + (k, v) for (k, v) in get_running_jobs().items() if k == package + ) + return render( "browse-package.html", package=package, @@ -280,6 +292,7 @@ def package_overview(package, _=None): arches=sorted(arches), results=results, title_suffix="- %s" % package, + running=running_info, ) @@ -371,12 +384,7 @@ def running(): a ] = queue_length - try: - with open("/run/amqp-status-collector/running.json") as f: - # package -> runhash -> release -> arch -> (params, duration, logtail) - running_info = json.load(f) - except FileNotFoundError: - running_info = {} + running_info = get_running_jobs() return render( "browse-running.html", @@ -391,13 +399,7 @@ def running(): @app.route("/admin") def admin(): - try: - with open("/run/amqp-status-collector/running.json") as f: - # package -> runhash -> release -> arch -> (params, duration, logtail) - running_info = json.load(f) - except FileNotFoundError as exc: - running_info = {} - raise FileNotFoundError("running.json doesn't exist!") from exc + running_info = get_running_jobs() pruned_running_info = select_abnormally_long_jobs( running_info, get_test_id=get_test_id, db_con=db_con ) @@ -439,7 +441,7 @@ def queues_json(): @app.route("/queued.json") def return_queued_exactly(): - with open("/var/lib/cache-amqp/queued.json") as json_file: + with open(AMQP_QUEUE_CACHE) as json_file: queue_info = json.load(json_file) return queue_info diff --git a/charms/focal/autopkgtest-web/webcontrol/helpers/tests.py b/charms/focal/autopkgtest-web/webcontrol/helpers/tests.py new file mode 100644 index 0000000..52017c2 --- /dev/null +++ b/charms/focal/autopkgtest-web/webcontrol/helpers/tests.py @@ -0,0 +1,148 @@ +import json +from datetime import datetime +from uuid import uuid4 + +from .utils import get_supported_releases + + +def populate_dummy_db(db_con): + supported_releases = get_supported_releases() + + c = db_con.cursor() + tests = [ + (1, supported_releases[0], "amd64", "hello"), + (2, supported_releases[1], "amd64", "hello"), + (3, supported_releases[0], "ppc64el", "hello"), + (4, supported_releases[1], "ppc64el", "hello"), + (5, supported_releases[2], "amd64", "hello"), + ] + c.executemany("INSERT INTO test values(?, ?, ?, ?)", tests) + results = [ + # fmt: off + # test_id | run_id | version | trigger | duration | exit_code | requester | env | uuid + (1, datetime.now(), "1.2.3", "hello/1.2.3", 42, 0, "hyask", "", str(uuid4())), + (1, datetime.now(), "1.2.3", "hello/1.2.3", 42, 0, "hyask", "all-proposed=1", str(uuid4())), + (2, datetime.now(), "1.2.3", "hello/1.2.3", 42, 0, "", "", str(uuid4())), + (3, datetime.now(), "1.2.3", "hello/1.2.3", 42, 20, "", "", str(uuid4())), + # fmt: on + ] + c.executemany( + "INSERT INTO result values(?, ?, ?, ?, ?, ?, ?, ?, ?)", results + ) + db_con.commit() + + +def populate_dummy_amqp_cache(path): + supported_releases = get_supported_releases() + with open(path, "w") as f: + # pylint: disable=line-too-long + json.dump( + { + "arches": ["amd64", "ppc64el"], + "queues": { + "ubuntu": { + supported_releases[0]: { + "amd64": { + "size": 1, + "requests": [ + 'hello\n{"triggers": ["hello/1.2.3ubuntu1"], "submit-time": "2024-02-22 01:55:03"}', + ], + } + } + }, + "huge": { + supported_releases[1]: { + "amd64": { + "size": 1, + "requests": [ + 'hello\n{"triggers": ["migration-reference/0"], "submit-time": "2024-02-22 01:55:03"}', + ], + } + } + }, + "ppa": { + supported_releases[2]: { + "amd64": { + "size": 2, + "requests": [ + 'hello\n{"triggers": ["hello/1.2.4~ppa1"], "submit-time": "2024-02-22 01:55:03"}', + 'hello2\n{"triggers": ["hello2/2.0.0~ppa1"], "submit-time": "2024-02-22 01:55:03"}', + ], + } + } + }, + "upstream": { + supported_releases[3]: { + "amd64": { + "size": 1, + "requests": [ + 'hello\n{"triggers": ["hello/1.2.4~ppa1"], "submit-time": "2024-02-22 01:55:03"}', + ], + } + } + }, + }, + }, + f, + ) + + +def populate_dummy_running_cache(path): + supported_releases = get_supported_releases() + with open(path, "w") as f: + json.dump( + { + "hello": { + "hash1": { + supported_releases[0]: { + "amd64": [ + { + "requester": "hyask", + "submit-time": "2024-02-21 11:00:51", + "triggers": [ + "hello/1.2.3", + ], + "uuid": "84669a9c-ac08-46a3-a5fd-6247d0d2021c", + }, + 3504, + """ +3071s hello/test_XYZ.hello . [ 54%] +3153s hello/test_XYZ.hello ...... [ 64%] +3271s hello/test_XYZ.hello .......... [ 74%] +3292s hello/test_XYZ.hello .................. [ 84%] +3493s hello/test_XYZ.hello ............................ [ 94%] +3494s hello/test_XYZ.hello .................................... [ 98%] +""", + ] + } + } + }, + "hello2": { + "hash1": { + supported_releases[4]: { + "amd64": [ + { + "all-proposed": "1", + "requester": "hyask", + "submit-time": "2024-02-21 11:01:21", + "triggers": [ + "hello2/1.2.3-0ubuntu1", + ], + "uuid": "42369a9c-ac08-46a3-a5fd-6247d0d2021c", + }, + 3504, + """ +3071s hello2/test_XYZ.hello [ 54%] +3153s hello2/test_XYZ.hello [ 64%] +3271s hello2/test_XYZ.hello [ 74%] +3292s hello2/test_XYZ.hello [ 84%] +3493s hello2/test_XYZ.hello [ 94%] +3494s hello2/test_XYZ.hello [ 98%] +""", + ] + } + } + }, + }, + f, + ) diff --git a/charms/focal/autopkgtest-web/webcontrol/helpers/utils.py b/charms/focal/autopkgtest-web/webcontrol/helpers/utils.py index 58a9514..12d93b5 100644 --- a/charms/focal/autopkgtest-web/webcontrol/helpers/utils.py +++ b/charms/focal/autopkgtest-web/webcontrol/helpers/utils.py @@ -8,6 +8,22 @@ import random import sqlite3 import time +import distro_info + + +def get_all_releases(): + udi = distro_info.UbuntuDistroInfo() + return udi.all + + +def get_supported_releases(): + udi = distro_info.UbuntuDistroInfo() + all_ubuntu_releases = get_all_releases() + return sorted( + set(udi.supported() + udi.supported_esm()), + key=all_ubuntu_releases.index, + ) + def setup_key(app, path): """Create or load app.secret_key for cookie encryption.""" @@ -22,10 +38,10 @@ def setup_key(app, path): app.secret_key = key -def init_db(path): +def init_db(path, **kwargs): """Create DB if it does not exist, and connect to it""" - db = sqlite3.connect(path) + db = sqlite3.connect(path, **kwargs) c = db.cursor() try: c.execute("PRAGMA journal_mode = WAL") diff --git a/charms/focal/autopkgtest-web/webcontrol/static/bootstrap b/charms/focal/autopkgtest-web/webcontrol/static/bootstrap new file mode 120000 index 0000000..fe0f86b --- /dev/null +++ b/charms/focal/autopkgtest-web/webcontrol/static/bootstrap @@ -0,0 +1 @@ +/usr/share/javascript/bootstrap \ No newline at end of file diff --git a/charms/focal/autopkgtest-web/webcontrol/templates/browse-admin.html b/charms/focal/autopkgtest-web/webcontrol/templates/browse-admin.html index 5bc2459..266f584 100644 --- a/charms/focal/autopkgtest-web/webcontrol/templates/browse-admin.html +++ b/charms/focal/autopkgtest-web/webcontrol/templates/browse-admin.html @@ -1,29 +1,14 @@ {% extends "browse-layout.html" %} +{% import "macros.html" as macros %} + {% block content %} <h1 class="page-header">Admin</h1> <p>Click on the package name to jump to the tests of the package for all arches/releases.</p> <p>This page is simply a bunch of heuristics filtering all running jobs to try to get the problematic ones. Feel free to come help improve the heuristics <a href="https://code.launchpad.net/~ubuntu-release/autopkgtest-cloud/+git/autopkgtest-cloud/+ref/master">here.</a></p> <!-- Running tests --> - {% for p in running|sort %} - <h2 id="pkg-{{p}}"><a href="/packages/{{p}}">{{p}}</a></h2> - {% for runhash, relinfo in running[p].items() %} - {% for release, archinfo in relinfo.items() %} - {% for arch, (params, duration, logtail) in archinfo.items() %} - <table class="table-condensed"> - <tr><th>Release:</th><td>{{release}}</td></tr> - <tr><th>Architecture:</th><td>{{arch}}</td></tr> - {% for param, v in params.items() %} - <tr><th>{{param|capitalize}}:</th><td>{{v}}</td></tr> - {% endfor %} - <tr><th>Running for:</th><td>{{duration//3600 }}h {{duration % 3600//60}}m {{duration % 60}}s</td></tr> - </table> - <pre> -{{logtail}} - </pre> - {% endfor %} - {% endfor %} - {% endfor %} + {% for p, info in running.items()|sort %} + {{ macros.display_running_job(p, info) }} {% endfor %} {% endblock %} diff --git a/charms/focal/autopkgtest-web/webcontrol/templates/browse-layout.html b/charms/focal/autopkgtest-web/webcontrol/templates/browse-layout.html index 0f90de3..c91457d 100644 --- a/charms/focal/autopkgtest-web/webcontrol/templates/browse-layout.html +++ b/charms/focal/autopkgtest-web/webcontrol/templates/browse-layout.html @@ -6,9 +6,9 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Ubuntu Autopkgtest Results {{title_suffix}}</title> <!-- <link rel="icon" type="image/png" href="/debian.png"/> --> - <link rel="stylesheet" type="text/css" href="{{static_url}}/bootstrap/css/bootstrap.css"/> - <link rel="stylesheet" type="text/css" href="{{static_url}}/bootstrap/css/bootstrap-theme.css"/> - <link rel="stylesheet" type="text/css" href="{{static_url}}/style.css"/> + <link rel="stylesheet" type="text/css" href="{{static_url}}bootstrap/css/bootstrap.css"/> + <link rel="stylesheet" type="text/css" href="{{static_url}}bootstrap/css/bootstrap-theme.css"/> + <link rel="stylesheet" type="text/css" href="{{static_url}}style.css"/> </head> <body> <div id='wrap'> @@ -44,7 +44,7 @@ {% block content %}{% endblock %} </div> - <script type="text/javascript" src="{{static_url}}/jquery/jquery.min.js"></script> - <script type="text/javascript" src="{{static_url}}/bootstrap/js/bootstrap.min.js"></script> + <script type="text/javascript" src="{{static_url}}jquery/jquery.min.js"></script> + <script type="text/javascript" src="{{static_url}}bootstrap/js/bootstrap.min.js"></script> </body> </html> diff --git a/charms/focal/autopkgtest-web/webcontrol/templates/browse-package.html b/charms/focal/autopkgtest-web/webcontrol/templates/browse-package.html index 3ac81c3..fb12afa 100644 --- a/charms/focal/autopkgtest-web/webcontrol/templates/browse-package.html +++ b/charms/focal/autopkgtest-web/webcontrol/templates/browse-package.html @@ -1,4 +1,6 @@ {% extends "browse-layout.html" %} +{% import "macros.html" as macros %} + {% block content %} <h2>{{package}}</h2> @@ -17,4 +19,8 @@ </tr> {% endfor %} </table> + + {% for p, info in running.items()|sort %} + {{ macros.display_running_job(p, info) }} + {% endfor %} {% endblock %} diff --git a/charms/focal/autopkgtest-web/webcontrol/templates/browse-running.html b/charms/focal/autopkgtest-web/webcontrol/templates/browse-running.html index 3711b97..53ae2f9 100644 --- a/charms/focal/autopkgtest-web/webcontrol/templates/browse-running.html +++ b/charms/focal/autopkgtest-web/webcontrol/templates/browse-running.html @@ -1,4 +1,6 @@ {% extends "browse-layout.html" %} +{% import "macros.html" as macros %} + {% block content %} <h1 class="page-header">Currently running tests</h1> <p>Click on the package name to jump to the currently running tests of that package.</p> @@ -36,31 +38,8 @@ {% endfor %} <!-- Running tests --> - {% for p in running|sort %} - <h2 id="pkg-{{p}}"><a href="/packages/{{p}}">{{p}}</a></h2> - {% for runhash, relinfo in running[p].items() %} - {% for release, archinfo in relinfo.items() %} - {% for arch, (params, duration, logtail) in archinfo.items() %} - <table class="table-condensed"> - <tr><th>Release:</th><td>{{release}}</td></tr> - <tr><th>Architecture:</th><td>{{arch}}</td></tr> - {% for param, v in params.items() %} - {% if param == "requester" %} - <tr><th>{{param|capitalize}}:</th><td><a href="https://launchpad.net/~{{v}}">{{v}}</a></td></tr> - {% elif param == "uuid" %} - <tr><th>{{param|capitalize}}:</th><td>{{v}}</td></tr> - {% else %} - <tr><th>{{param|capitalize}}:</th><td>{{v}}</td></tr> - {% endif %} - {% endfor %} - <tr><th>Running for:</th><td>{{duration//3600 }}h {{duration % 3600//60}}m {{duration % 60}}s</td></tr> - </table> - <pre> -{{logtail}} - </pre> - {% endfor %} - {% endfor %} - {% endfor %} + {% for p, info in running.items()|sort %} + {{ macros.display_running_job(p, info) }} {% endfor %} <!-- queue contents --> diff --git a/charms/focal/autopkgtest-web/webcontrol/templates/macros.html b/charms/focal/autopkgtest-web/webcontrol/templates/macros.html new file mode 100644 index 0000000..7d43d20 --- /dev/null +++ b/charms/focal/autopkgtest-web/webcontrol/templates/macros.html @@ -0,0 +1,26 @@ +{% macro display_running_job(package, info) -%} +<h2 id="pkg-{{ package }}"><a href="/packages/{{ package }}">{{ package }}</a></h2> + {% for runhash, relinfo in info.items() %} + {% for release, archinfo in relinfo.items() %} + {% for arch, (params, duration, logtail) in archinfo.items() %} + <table class="table-condensed"> + <tr><th>Release:</th><td>{{ release }}</td></tr> + <tr><th>Architecture:</th><td>{{ arch }}</td></tr> + {% for param, v in params.items() %} + {% if param == "requester" %} + <tr><th>{{ param|capitalize }}:</th><td><a href="https://launchpad.net/~{{ v }}">{{ v }}</a></td></tr> + {% elif param == "uuid" %} + <tr><th>{{ param|upper }}:</th><td>{{ v }}</td></tr> + {% else %} + <tr><th>{{ param|capitalize }}:</th><td>{{ v }}</td></tr> + {% endif %} + {% endfor %} + <tr><th>Running for:</th><td>{{ duration//3600 }}h {{ duration % 3600//60 }}m {{ duration % 60 }}s ({{ duration }}s)</td></tr> + </table> + <pre> +{{ logtail }} + </pre> + {% endfor %} + {% endfor %} + {% endfor %} +{%- endmacro %}
-- Mailing list: https://launchpad.net/~canonical-ubuntu-qa Post to : canonical-ubuntu-qa@lists.launchpad.net Unsubscribe : https://launchpad.net/~canonical-ubuntu-qa More help : https://help.launchpad.net/ListHelp