Tim Andersson has proposed merging ~andersson123/autopkgtest-cloud:make-killing-tests-less-painful into autopkgtest-cloud:master.
Requested reviews: Canonical's Ubuntu QA (canonical-ubuntu-qa) For more details, see: https://code.launchpad.net/~andersson123/autopkgtest-cloud/+git/autopkgtest-cloud/+merge/464740 -- Your team Canonical's Ubuntu QA is requested to review the proposed merge of ~andersson123/autopkgtest-cloud:make-killing-tests-less-painful into autopkgtest-cloud:master.
diff --git a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/worker/worker b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/worker/worker index bfa35e7..40cb8a3 100755 --- a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/worker/worker +++ b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/worker/worker @@ -29,10 +29,14 @@ from urllib.error import HTTPError import amqplib.client_0_8 as amqp import distro_info +import novaclient.client +import novaclient.exceptions import swiftclient import systemd.journal from influxdb import InfluxDBClient from influxdb.exceptions import InfluxDBClientError +from keystoneauth1 import session +from keystoneauth1.identity import v2, v3 ALL_RELEASES = distro_info.UbuntuDistroInfo().get_all(result="object") @@ -621,6 +625,36 @@ def cleanup_and_sleep(out_dir): time.sleep(300) +def kill_openstack_server(test_uuid: str): + if int(os.environ.get("OS_IDENTITY_API_VERSION")) == 3: + auth = v3.Password( + auth_url=os.environ["OS_AUTH_URL"], + username=os.environ["OS_USERNAME"], + password=os.environ["OS_PASSWORD"], + project_name=os.environ["OS_PROJECT_NAME"], + user_domain_name=os.environ["OS_USER_DOMAIN_NAME"], + project_domain_name=os.environ["OS_PROJECT_DOMAIN_NAME"], + ) + else: + auth = v2.Password( + auth_url=os.environ["OS_AUTH_URL"], + username=os.environ["OS_USERNAME"], + password=os.environ["OS_PASSWORD"], + tenant_name=os.environ["OS_TENANT_NAME"], + ) + sess = session.Session(auth=auth) + nova = novaclient.client.Client( + "2", + session=sess, + region_name=os.environ["OS_REGION_NAME"], + ) + for instance in nova.servers.list(): + if test_uuid in instance.name: + instance.delete() + return instance.name + return None + + def request(msg): """Callback for AMQP queue request""" @@ -1118,6 +1152,8 @@ def request(msg): test_uuid, private, ) + if code == -10: + exit_requested = 99 is_failure = code in FAIL_CODES files = set(os.listdir(out_dir)) is_unknown_version = "testpkg-version" not in files @@ -1174,12 +1210,38 @@ def request(msg): elif code == 16 or code < 0: contents = log_contents(out_dir) if exit_requested is not None: - logging.warning( - "Testbed failure and exit %i requested. Log follows:", - exit_requested, - ) - logging.error(contents) - sys.exit(exit_requested) + # exit_requested is set to 99 when the test is requested to be killed + if exit_requested != 99: + logging.warning( + "Testbed failure and exit %i requested. Log follows:", + exit_requested, + ) + logging.error(contents) + sys.exit(exit_requested) + else: + # Test has been requested to be killed + logging.info( + "Test has been killed by test-killer, exiting." + ) + running_test = False + # ack the message so it doesn't go back in the queue + msg.channel.basic_ack(msg.delivery_tag) + # make this a function + logging.info( + "Killing openstack server with uuid %s", + test_uuid, + ) + server_name = kill_openstack_server(test_uuid) + if server_name is not None: + logging.info( + "Deleted test server: %s", server_name + ) + else: + logging.info( + "Failed to delete openstack server: %s" + % server_name + ) + return # Get the package-specific string for triggers too, since they might have broken the run trigs = [ t.split("/", 1)[0] for t in params.get("triggers", []) diff --git a/charms/focal/autopkgtest-web/webcontrol/browse.cgi b/charms/focal/autopkgtest-web/webcontrol/browse.cgi index 309fb82..85024bf 100755 --- a/charms/focal/autopkgtest-web/webcontrol/browse.cgi +++ b/charms/focal/autopkgtest-web/webcontrol/browse.cgi @@ -16,24 +16,13 @@ from helpers.utils import ( get_all_releases, get_autopkgtest_cloud_conf, get_supported_releases, - setup_key, + initialise_app, ) -from werkzeug.middleware.proxy_fix import ProxyFix # Initialize app -PATH = os.path.join( - os.path.sep, os.getenv("XDG_RUNTIME_DIR", "/run"), "autopkgtest_webcontrol" -) -os.makedirs(PATH, exist_ok=True) -app = flask.Flask("browse") -# we don't want a long cache, as we only serve files that are regularly updated +PATH, app, secret_path, _ = initialise_app("browse") app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 60 -app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1) - -secret_path = os.path.join(PATH, "secret_key") -setup_key(app, secret_path) - db_con = None swift_container_url = None diff --git a/charms/focal/autopkgtest-web/webcontrol/helpers/utils.py b/charms/focal/autopkgtest-web/webcontrol/helpers/utils.py index 4e26eb8..a92cc51 100644 --- a/charms/focal/autopkgtest-web/webcontrol/helpers/utils.py +++ b/charms/focal/autopkgtest-web/webcontrol/helpers/utils.py @@ -14,12 +14,37 @@ import typing # introduced in python3.7, we use 3.8 from dataclasses import dataclass +from html import escape as _escape import distro_info +from flask import Flask +from flask_openid import OpenID +from werkzeug.middleware.proxy_fix import ProxyFix sqlite3.paramstyle = "named" +def initialise_app(app_name): + PATH = os.path.join( + os.path.sep, + os.getenv("XDG_RUNTIME_DIR", "/run"), + "autopkgtest_webcontrol", + ) + os.makedirs(PATH, exist_ok=True) + app = Flask(app_name) + app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1) + # keep secret persistent between CGI invocations + secret_path = os.path.join(PATH, "secret_key") + setup_key(app, secret_path) + oid = OpenID(app, os.path.join(PATH, "openid"), safe_roots=[]) + return PATH, app, secret_path, oid + + +def maybe_escape(value): + """Escape the value if it is True-ish""" + return _escape(value) if value else value + + @dataclass class SqliteWriterConfig: writer_exchange_name = "sqlite-write-me.fanout" @@ -220,3 +245,16 @@ def get_test_id(db_con, release, arch, src): get_test_id._cache = {} + +HTML = """ +<!doctype html> +<html> +<head> +<meta charset="utf-8"> +<title>Autopkgtest Test Request</title> +</head> +<body> +{} +</body> +</html> +""" diff --git a/charms/focal/autopkgtest-web/webcontrol/request/app.py b/charms/focal/autopkgtest-web/webcontrol/request/app.py index 4fca679..8ee33d4 100644 --- a/charms/focal/autopkgtest-web/webcontrol/request/app.py +++ b/charms/focal/autopkgtest-web/webcontrol/request/app.py @@ -5,33 +5,17 @@ import logging import os import pathlib from collections import ChainMap -from html import escape as _escape -from flask import Flask, redirect, request, session -from flask_openid import OpenID +from flask import redirect, request, session from helpers.exceptions import WebControlException -from helpers.utils import setup_key +from helpers.utils import HTML, initialise_app, maybe_escape from request.submit import Submit -from werkzeug.middleware.proxy_fix import ProxyFix # map multiple GET vars to AMQP JSON request parameter list MULTI_ARGS = {"trigger": "triggers", "ppa": "ppas", "env": "env"} EMPTY = "" -HTML = """ -<!doctype html> -<html> -<head> -<meta charset="utf-8"> -<title>Autopkgtest Test Request</title> -</head> -<body> -{} -</body> -</html> -""" - LOGIN = """ <form action="/login" method="post"> <input type="submit" value="Log in with Ubuntu SSO"> @@ -106,11 +90,6 @@ def invalid(inv_exception, code=400): return HTML.format(html), code -def maybe_escape(value): - """Escape the value if it is True-ish""" - return _escape(value) if value else value - - def get_api_keys(): """ API keys is a json file like this: @@ -132,17 +111,7 @@ def get_api_keys(): # Initialize app -PATH = os.path.join( - os.path.sep, os.getenv("XDG_RUNTIME_DIR", "/run"), "autopkgtest_webcontrol" -) -os.makedirs(PATH, exist_ok=True) -app = Flask("request") -app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1) -# keep secret persistent between CGI invocations -secret_path = os.path.join(PATH, "secret_key") -setup_key(app, secret_path) -oid = OpenID(app, os.path.join(PATH, "openid"), safe_roots=[]) - +PATH, app, secret_path, oid = initialise_app("request") # # Flask routes
-- 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