← Back to team overview

canonical-ubuntu-qa team mailing list archive

[Merge] ~andersson123/autopkgtest-cloud:integration-tests into autopkgtest-cloud:master

 

Tim Andersson has proposed merging ~andersson123/autopkgtest-cloud:integration-tests 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/457239
-- 
Your team Canonical's Ubuntu QA is requested to review the proposed merge of ~andersson123/autopkgtest-cloud:integration-tests into autopkgtest-cloud:master.
diff --git a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/check-config-files b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/check-config-files
new file mode 100755
index 0000000..dbb27a1
--- /dev/null
+++ b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/check-config-files
@@ -0,0 +1,246 @@
+#!/usr/bin/python3
+import configparser
+import json
+import os
+import socket
+import subprocess
+import sys
+import urllib.parse
+import urllib.request
+
+import swiftclient
+from influxdb import InfluxDBClient
+
+INTEGRATION_KEY_FP = "/home/ubuntu/integration-key"
+
+bos_arches = ["arm64", "ppc64el", "s390x"]
+
+centres = {
+    "bos01": bos_arches,
+    "bos02": bos_arches,
+    "lcy02": None,
+}
+
+openstack_cmd = "openstack project list"
+
+openstack_commands = [
+    "openstack project list",
+    "openstack server list",
+    "openstack network list",
+    "openstack image list",
+]
+
+openstack_vars = [
+    "OS_USERNAME",
+    "OS_TENANT_NAME",
+    "OS_PASSWORD",
+    "OS_AUTH_URL",
+    "OS_REGION_NAME",
+]
+
+influx_vars = [
+    "INFLUXDB_HOSTNAME",
+    "INFLUXDB_PORT",
+    "INFLUXDB_USERNAME",
+    "INFLUXDB_PASSWORD",
+    "INFLUXDB_DATABASE",
+    "INFLUXDB_CONTEXT",
+]
+
+rabbit_vars = [
+    "RABBIT_HOST",
+    "RABBIT_USER",
+    "RABBIT_PASSWORD",
+]
+
+swift_vars = [
+    "SWIFT_AUTH_URL",
+    "SWIFT_AUTH_VERSION",
+    "SWIFT_PASSWORD",
+    "SWIFT_PROJECT_DOMAIN_NAME",
+    "SWIFT_PROJECT_NAME",
+    "SWIFT_REGION",
+    "SWIFT_TENANT",
+    "SWIFT_USER_DOMAIN_NAME",
+    "SWIFT_USERNAME",
+]
+
+worker_args = [
+    "[autopkgtest]",
+    "[virt]",
+    "checkout_dir",
+    "releases",
+    "setup_command",
+    "setup_command2",
+    "per_package_config_dir",
+    "architectures",
+    "package_size_default",
+    "package_size_big",
+    "args",
+]
+
+
+def check_cloudrcs(args):
+    centres, openstack_commands, openstack_vars = args
+    for centre, arches in centres.items():
+        for arch in arches:
+            if arch is None:
+                this_path = "~/cloudrcs/" + centre + ".rc"
+            else:
+                this_path = "~/cloudrcs/" + centre + "-" + arch + ".rc"
+            if not os.path.isfile(this_path):
+                return False
+            with open(this_path, "r") as f:
+                rc_file = f.read()
+            vars = []
+            for line in rc_file.splitlines():
+                if "export" in line:
+                    this_line = line.split(" ")
+                    var, value = this_line.split("=")
+                    vars.append(var)
+                    os.environ[var] = value
+            for var in openstack_vars:
+                if var not in rc_file:
+                    return False
+            for command in openstack_commands:
+                _ = subprocess.run(command.split(" "), check=True)
+    return True
+
+
+def check_influx_creds(influx_file, influx_keys):
+    creds = check_env_file_and_keys(influx_file, influx_keys)
+    for cred in creds.splitlines():
+        if "=" in cred:
+            var, value = cred.split("=")
+            os.environ[var] = value
+    influx_client = InfluxDBClient(
+        os.environ["INFLUXDB_HOSTNAME"],
+        os.environ["INFLUXDB_PORT"],
+        os.environ["INFLUXDB_USERNAME"],
+        os.environ["INFLUXDB_PASSWORD"],
+        os.environ["INFLUXDB_DATABASE"],
+    )
+    influx_client.ping()
+    return True
+
+
+def check_env_file_and_keys(file, file_keys):
+    mypath = os.path.expanduser("~/" + file)
+    if not os.path.isfile(mypath):
+        raise FileNotFoundError("file %s doesn't exist" % file)
+    with open(mypath, "r") as f:
+        myf = f.read()
+    for k in file_keys:
+        if k not in myf:
+            raise KeyError("key %s not found in %s" % (k, file))
+    return myf
+
+
+def check_mirror_rc(mirror_file, mirror_vars):
+    _ = check_env_file_and_keys(mirror_file, mirror_vars)
+
+
+def check_rabbitmq_creds(rabbit_file, rabbit_vars):
+    _ = check_env_file_and_keys(rabbit_file, rabbit_vars)
+
+
+def check_net_name(net_file, net_vars):
+    _ = check_env_file_and_keys(net_file, net_vars)
+
+
+def check_swift_creds(swift_file, swift_vars):
+    creds = check_env_file_and_keys(swift_file, swift_vars)
+    for line in creds.splitlines():
+        var, value = line.split("=")
+        os.environ[var] = value.replace('"', "")
+    swift_creds = {
+        "authurl": os.environ["SWIFT_AUTH_URL"],
+        "user": os.environ["SWIFT_USERNAME"],
+        "key": os.environ["SWIFT_PASSWORD"],
+        "os_options": {
+            "region_name": os.environ["SWIFT_REGION"],
+            "project_domain_name": os.environ["SWIFT_PROJECT_DOMAIN_NAME"],
+            "project_name": os.environ["SWIFT_PROJECT_NAME"],
+            "user_domain_name": os.environ["SWIFT_USER_DOMAIN_NAME"],
+        },
+        "auth_version": 3,
+    }
+    swift_conn = swiftclient.Connection(**swift_creds)
+    _ = swift_conn.get_account()
+    swift_conn.close()
+    return True
+
+
+def check_worker_conf_files(args):
+    worker_args, centres = args
+    for centre, arches in centres.items():
+        if arches is not None:
+            for a in arches:
+                workerfile = "-".join(["worker", centre, a]) + ".conf"
+                _ = check_env_file_and_keys(workerfile, worker_args)
+
+
+files_and_keys = {
+    "influx.cred": {
+        "vars": influx_vars,
+        "func": check_influx_creds,
+    },
+    "rabbitmq.cred": {
+        "vars": rabbit_vars,
+        "func": check_rabbitmq_creds,
+    },
+    "swift-password.cred": {
+        "vars": swift_vars,
+        "func": check_swift_creds,
+    },
+    "mirror.rc": {
+        "vars": ["MIRROR"],
+        "func": check_mirror_rc,
+    },
+    "net-name.rc": {
+        "vars": ["NET_NAME"],
+        "func": check_net_name,
+    },
+    "worker-configs": {
+        "vars": (worker_args, centres),
+        "func": check_worker_conf_files,
+    },
+    "cloudrcs": {
+        "vars": (centres, openstack_commands, openstack_vars),
+        "func": check_cloudrcs,
+    },
+}
+
+RESULTS = {}
+
+if __name__ == "__main__":
+    for file, item in files_and_keys.items():
+        try:
+            if "." in file:
+                item["func"](file, item["vars"])
+            else:
+                item["func"](item["vars"])
+            RESULTS[file] = True
+        except Exception as _:
+            RESULTS[file] = False
+    with open("/home/ubuntu/check-config-files-results.json", "w") as f:
+        f.write(json.dumps(RESULTS, indent=2))
+    if os.path.isfile("/home/ubuntu/autopkgtest-url"):
+        with open("/home/ubuntu/autopkgtest-url", "r") as f:
+            webpage = f.read().rstrip()
+        keypass = ""
+        with open(INTEGRATION_KEY_FP, "r") as f:
+            keypass = f.read().rstrip()
+        post_me = {
+            "type": "cloud",
+            "source": socket.gethostname(),
+            "pass": keypass,
+            "test": __file__,
+            "results": RESULTS,
+        }
+        results_url = webpage + "/post-integration-results"
+        req = urllib.request.Request(results_url)
+        req.add_header("Content-Type", "application/json; charset=utf-8")
+        jsondata = json.dumps(post_me).encode("utf-8")
+        req.add_header("Content-Length", len(jsondata))
+        response = urllib.request.urlopen(req, jsondata)
diff --git a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/test-run-autopkgtest b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/test-run-autopkgtest
new file mode 100755
index 0000000..03c645a
--- /dev/null
+++ b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/test-run-autopkgtest
@@ -0,0 +1,327 @@
+#!/usr/bin/python3
+
+
+import configparser
+import datetime
+import json
+import logging
+import os
+import socket
+import subprocess
+import sys
+import tarfile
+import time
+import urllib.parse
+import urllib.request
+
+import requests
+import swiftclient
+from distro_info import UbuntuDistroInfo
+
+WORKING_DIR = "/home/ubuntu/autopkgtest-cloud/tools/"
+SCRIPT = "run-autopkgtest"
+
+UDI = UbuntuDistroInfo()
+LATEST = UDI.supported()[-1]
+# ARCHES = ["amd64", "arm64", "ppc64el", "s390x", "armhf", "ppa"]
+ARCHES = ["ppa"]
+PACKAGE = "gzip"
+AUTOPKGTEST_SITE = "https://autopkgtest.ubuntu.com";
+QUEUED_JSON = "%s/queues.json" % AUTOPKGTEST_SITE
+RUNNING_PAGE = "%s/static/running.json" % AUTOPKGTEST_SITE
+JOURNAL_CMD = [
+    "journalctl",
+    "--since",
+    "5 minutes ago",
+    "--no-pager",
+    "-u",
+    "autopkgtest@*",
+]
+PPA_NAME = "andersson123/hello"
+
+ARGS = ""
+
+TIMEOUT = 60 * 60
+
+URL = "https://launchpad.net/ubuntu/%s/+source/%s"; % (LATEST, PACKAGE)
+SWIFT_CREDS_FP = "/home/ubuntu/swift-password.cred"
+
+INTEGRATION_KEY_FP = "/home/ubuntu/integration-key"
+
+
+def get_swift_creds():
+    swift_file = ""
+    file_vals = {}
+    with open(SWIFT_CREDS_FP, "r") as f:
+        swift_file = f.read()
+    for line in swift_file.splitlines():
+        key, val = line.split("=")
+        val = val.replace('"', "")
+        file_vals[key] = val
+    swift_creds = {
+        "authurl": file_vals["SWIFT_AUTH_URL"],
+        "user": file_vals["SWIFT_USERNAME"],
+        "key": file_vals["SWIFT_PASSWORD"],
+        "os_options": {
+            "region_name": file_vals["SWIFT_REGION"],
+            "project_domain_name": file_vals["SWIFT_PROJECT_DOMAIN_NAME"],
+            "project_name": file_vals["SWIFT_PROJECT_NAME"],
+            "user_domain_name": file_vals["SWIFT_USER_DOMAIN_NAME"],
+        },
+        "auth_version": file_vals["SWIFT_AUTH_VERSION"],
+    }
+    return swift_creds
+
+
+def find_result_in_swift(swift_con, arch):
+    time.sleep(15)
+    # presuming test_info will be useful
+    # add a sleep here so we can be sure the result is in swift
+    # Need to handle PPA case differently
+    # https://autopkgtest.ubuntu.com/results/autopkgtest-RELEASE-LPUSER-PPA/?format=plain
+    # results available like this
+    container_name = (
+        ("autopkgtest-" + LATEST)
+        if arch != "ppa"
+        else (
+            "autopkgtest-%s-%s-%s"
+            % (
+                LATEST,
+                PPA_NAME.split("/", maxsplit=1)[0],
+                PPA_NAME.split("/")[1],
+            )
+        )
+    )
+    arch_key = arch if arch != "ppa" else "amd64"
+    logging.info("Container name:\n%s" % container_name)
+
+    time_now = datetime.datetime.now()
+    while True:
+        _, objects = swift_con.get_container(container_name, full_listing=True)
+        for object in objects:
+            logging.info("Object:\n%s" % object["name"])
+            logging.info("Latest: %s" % LATEST)
+            logging.info("PPA Name: %s" % PPA_NAME)
+            logging.info("Package: %s" % PACKAGE)
+            logging.info("arch: %s" % arch_key)
+            ## check object name first
+            # ah yes i need to modify arch for this!
+            logging.info(
+                "Latest in object?: %s" % str(LATEST in object["name"])
+            )
+            logging.info(
+                "Package in object?: %s" % str(PACKAGE in object["name"])
+            )
+            logging.info(
+                "arch in object?: %s" % str(arch_key in object["name"])
+            )
+            if (
+                LATEST in object["name"]
+                and PACKAGE in object["name"]
+                and arch_key in object["name"]
+            ):
+                obj_time = object["last_modified"].split(".")[0]
+                datetime_obj_time = datetime.datetime.strptime(
+                    obj_time, "%Y-%m-%dT%H:%M:%S"
+                )
+                time_diff = abs(
+                    time_now.timestamp() - datetime_obj_time.timestamp()
+                )
+                logging.info("Are we getting here?")
+                logging.info("Time diff: %s" % str(time_diff))
+                if time_diff < 600:
+                    return object
+
+
+def get_trigger():
+    r = requests.get(URL)
+    ctr = 0
+    resp = r.content.decode("utf-8")
+    idx = 0
+    for line in resp.splitlines():
+        if "Current version" in line:
+            idx = ctr + 1
+        ctr += 1
+    curr_ver = resp.splitlines()[idx]
+    curr_ver = (
+        curr_ver.replace("<dd>", "").replace("</dd>", "").replace(" ", "")
+    )
+    return "%s/%s" % (PACKAGE, curr_ver)
+
+
+def check_logfile_is_accessible(url):
+    url = url.replace("artifacts.tar.gz", "log.gz")
+    try:
+        r = requests.get(url)
+    except requests.exceptions.HTTPError as err:
+        logging.info("Acquiring logfile failed with:\n%s" % err)
+        return False
+    logging.info("Acquiring logfile succeeded!")
+    logging.debug("Full logfile:\n%s" % r.content)
+    return True
+
+
+def check_result(swift_con, arch):
+    logging.info("Getting container and object...")
+    this_test_results = {}
+    result = find_result_in_swift(swift_con=swift_con, arch=arch)
+    logging.info("Found object in swift:\n%s" % str(result))
+    object_path_lst = result["name"].split("/")
+    object_path_lst = object_path_lst[:-1]
+    object = "/".join(object_path_lst)
+    container = "autopkgtest-" + LATEST
+    logging.info("container: %s\nobject: %s" % (container, object))
+    url = "%s/results/%s/%s/%s" % (
+        AUTOPKGTEST_SITE,
+        container,
+        object,
+        "artifacts.tar.gz",
+    )
+    logging.info("Results url: %s" % url)
+    r = requests.get(url)
+    if r.status_code == 200:
+        with open("/tmp/artifacts.tar.gz", "wb") as f:
+            f.write(r.content)
+    logging.info("Acquired results!")
+    file = tarfile.open("/tmp/artifacts.tar.gz")
+    file.extractall("/tmp/")
+    file.close()
+    with open("/tmp/exitcode", "r") as f:
+        code = f.read()
+    logging.info("code: %s" % str(code))
+    this_test_results["logfile-accessible"] = check_logfile_is_accessible(url)
+    this_test_results["test-passed"] = False
+    try:
+        if int(code) == 0:
+            this_test_results["test-passed"] = True
+    except TypeError as _:
+        pass
+    return this_test_results
+
+
+if __name__ == "__main__":
+    logging.getLogger().setLevel(logging.INFO)
+    logging.info("getting trigger...")
+    trigger = get_trigger()
+    swift_creds = get_swift_creds()
+    swift_con = swiftclient.Connection(**swift_creds)
+    logging.info("got trigger: %s" % trigger)
+    results = {}
+    # I should also queue a test from a ppa
+    for arch in ARCHES:
+        results[arch] = {}
+        args = "%s%s -s %s -a %s --trigger=%s %s" % (
+            WORKING_DIR,
+            SCRIPT,
+            LATEST,
+            arch,
+            trigger,
+            PACKAGE,
+        )
+        if arch == "ppa":
+            args = args.replace("ppa", "amd64")
+            args += " --ppa %s" % PPA_NAME
+        logging.info(
+            "run-autopkgtest args:\n%s\nRunning autopkgtest..." % args
+        )
+        # submit the test
+        p = subprocess.run(args.split(" "), check=True)
+        test_info = {
+            PACKAGE: {
+                "triggers": [trigger],
+            }
+        }
+        in_queue = False
+        saved_item = ""
+
+        logging.info("Checking running.json for test...")
+        # wait for the test to appear in running.json
+        # This needs a timeout I believe
+        start_time = datetime.datetime.now()
+        failed = False
+        is_running = False
+        saved_skey = ""
+        while not is_running and not failed:
+            loop_time = datetime.datetime.now()
+            duration = loop_time - start_time
+            if duration.total_seconds() > TIMEOUT:
+                failed = True
+                break
+            running = requests.get(RUNNING_PAGE)
+            running_json = json.loads(running.content)
+            for package, values in running_json.items():
+                if package == PACKAGE:
+                    for skey, details in values.items():
+                        num_triggers = len(test_info[PACKAGE]["triggers"])
+                        ctr = 0
+                        for trigger in test_info[PACKAGE]["triggers"]:
+                            if trigger in skey:
+                                ctr += 1
+                        if ctr == num_triggers:
+                            try:
+                                this_arch = arch if arch != "ppa" else "amd64"
+                                test_info[PACKAGE][
+                                    "submit-time"
+                                ] = running_json[package][skey][LATEST][
+                                    this_arch
+                                ][
+                                    0
+                                ][
+                                    "submit-time"
+                                ]
+                            except KeyError as _:
+                                continue
+                            saved_skey = skey
+                            is_running = True
+                            logging.info("Test found in running.json!")
+        logging.info("Waiting for test to leave running.json...")
+        # wait for the test to leave running.json
+        while is_running and not failed:
+            loop_time = datetime.datetime.now()
+            duration = loop_time - start_time
+            if duration.total_seconds() > TIMEOUT:
+                failed = True
+                break
+            running = requests.get(RUNNING_PAGE)
+            if saved_skey not in running.content.decode("utf-8"):
+                is_running = False
+                logging.info("Test has left running.json!")
+        logging.info("Getting results for test!")
+        if not failed:
+            results[arch] = check_result(swift_con, arch)
+        else:
+            results[arch] = False
+            pass
+    logging.info("Results:\n%s" % json.dumps(results, indent=2))
+    # this needs changing
+    cp = configparser.ConfigParser()
+    cp.read(os.path.expanduser("~ubuntu/autopkgtest-cloud.conf"))
+    try:
+        webpage = cp["web"]["ExternalURL"].replace("/results", "")
+    except KeyError:
+        # change to logging maybe ?
+        print("No external url found!")
+        sys.exit(1)
+    keypass = ""
+    with open(INTEGRATION_KEY_FP, "r") as f:
+        keypass = f.read().rstrip()
+    post_me = {
+        "type": "cloud",
+        "source": socket.gethostname(),
+        "pass": keypass,
+        "test": __file__,
+        "results": results,
+    }
+    results_url = webpage + "/post-integration-results"
+    req = urllib.request.Request(results_url)
+    req.add_header("Content-Type", "application/json; charset=utf-8")
+    jsondata = json.dumps(post_me).encode("utf-8")
+    req.add_header("Content-Length", len(jsondata))
+    response = urllib.request.urlopen(req, jsondata)
+
+    with open("/home/ubuntu/test-run-autopkgtest-results.json", "w") as f:
+        f.write(json.dumps(results, indent=2))
+    for arch, result in results.items():
+        for key, t_f in result.items():
+            if not t_f:
+                sys.exit(1)
diff --git a/charms/focal/autopkgtest-cloud-worker/config.yaml b/charms/focal/autopkgtest-cloud-worker/config.yaml
index 7f0ef60..257d075 100644
--- a/charms/focal/autopkgtest-cloud-worker/config.yaml
+++ b/charms/focal/autopkgtest-cloud-worker/config.yaml
@@ -110,3 +110,7 @@ options:
     description: Submit all metrics with this as the "context" tag,
                  to differentiate staging vs. production submissions
     type: string
+  autopkgtest-hostname:
+    default: ~
+    description: URL for autopkgtest (prod or staging)
+    type: string
diff --git a/charms/focal/autopkgtest-cloud-worker/reactive/autopkgtest_cloud_worker.py b/charms/focal/autopkgtest-cloud-worker/reactive/autopkgtest_cloud_worker.py
index 2e8f376..51101a6 100644
--- a/charms/focal/autopkgtest-cloud-worker/reactive/autopkgtest_cloud_worker.py
+++ b/charms/focal/autopkgtest-cloud-worker/reactive/autopkgtest_cloud_worker.py
@@ -285,6 +285,13 @@ def clear_old_rcs():
 
     log("...done", "INFO")
 
+@when_any(
+        "config.set.autopkgtest-hostname",
+        "config.changed.autopkgtest-hostname",
+)
+def write_hostname_file():
+    with open(os.path.expanduser("~ubuntu/autopkgtest-url"), "w") as f:
+        f.write("https://"; + config().get("autopkgtest-hostname"))
 
 @when_all(
     "autopkgtest.autopkgtest_cloud_symlinked",
diff --git a/charms/focal/autopkgtest-cloud-worker/units/check-config-files.service b/charms/focal/autopkgtest-cloud-worker/units/check-config-files.service
new file mode 100644
index 0000000..85da744
--- /dev/null
+++ b/charms/focal/autopkgtest-cloud-worker/units/check-config-files.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=Check all necessary config files for autopkgtest-cloud-worker charm
+
+[Service]
+Type=oneshot
+User=ubuntu
+Group=ubuntu
+ExecStart=/home/ubuntu/autopkgtest-cloud/tools/check-config-files
diff --git a/charms/focal/autopkgtest-cloud-worker/units/check-config-files.timer b/charms/focal/autopkgtest-cloud-worker/units/check-config-files.timer
new file mode 100644
index 0000000..fbe8b79
--- /dev/null
+++ b/charms/focal/autopkgtest-cloud-worker/units/check-config-files.timer
@@ -0,0 +1,9 @@
+[Unit]
+Description=Check all necessary config files for autopkgtest-cloud-worker charm
+
+[Timer]
+OnBootSec=2min
+OnCalendar=00 00 * * *
+
+[Install]
+WantedBy=autopkgtest.target
diff --git a/charms/focal/autopkgtest-cloud-worker/units/test-run-autopkgtest.service b/charms/focal/autopkgtest-cloud-worker/units/test-run-autopkgtest.service
new file mode 100644
index 0000000..3df205d
--- /dev/null
+++ b/charms/focal/autopkgtest-cloud-worker/units/test-run-autopkgtest.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=Run script which checks tests pass on all architectures and with a ppa
+
+[Service]
+Type=oneshot
+User=ubuntu
+Group=ubuntu
+ExecStart=/home/ubuntu/autopkgtest-cloud/tools/test-run-autopkgtest
\ No newline at end of file
diff --git a/charms/focal/autopkgtest-cloud-worker/units/test-run-autopkgtest.timer b/charms/focal/autopkgtest-cloud-worker/units/test-run-autopkgtest.timer
new file mode 100644
index 0000000..c4b64ca
--- /dev/null
+++ b/charms/focal/autopkgtest-cloud-worker/units/test-run-autopkgtest.timer
@@ -0,0 +1,9 @@
+[Unit]
+Description=Run script which checks tests pass on all architectures and with a ppa
+
+[Timer]
+OnBootSec=2min
+OnCalendar=00 00 * * *
+
+[Install]
+WantedBy=autopkgtest.target
diff --git a/charms/focal/autopkgtest-web/units/check-config-files.service b/charms/focal/autopkgtest-web/units/check-config-files.service
new file mode 100644
index 0000000..45837d5
--- /dev/null
+++ b/charms/focal/autopkgtest-web/units/check-config-files.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=Check all necessary config files for autopkgtest-web charm
+
+[Service]
+Type=oneshot
+User=ubuntu
+Group=ubuntu
+ExecStart=/home/ubuntu/webcontrol/check-config-files
diff --git a/charms/focal/autopkgtest-web/units/check-config-files.timer b/charms/focal/autopkgtest-web/units/check-config-files.timer
new file mode 100644
index 0000000..b89aa60
--- /dev/null
+++ b/charms/focal/autopkgtest-web/units/check-config-files.timer
@@ -0,0 +1,9 @@
+[Unit]
+Description=Check all necessary config files for autopkgtest-web charm
+
+[Timer]
+OnBootSec=2min
+OnCalendar=00 00 * * *
+
+[Install]
+WantedBy=autopkgtest-web.target
diff --git a/charms/focal/autopkgtest-web/units/endpoint-checker.service b/charms/focal/autopkgtest-web/units/endpoint-checker.service
new file mode 100644
index 0000000..b3ce9a3
--- /dev/null
+++ b/charms/focal/autopkgtest-web/units/endpoint-checker.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=Check all endpoints for autopkgtest-web
+
+[Service]
+Type=oneshot
+User=ubuntu
+Group=ubuntu
+ExecStart=/home/ubuntu/webcontrol/endpoint-checker
\ No newline at end of file
diff --git a/charms/focal/autopkgtest-web/units/endpoint-checker.timer b/charms/focal/autopkgtest-web/units/endpoint-checker.timer
new file mode 100644
index 0000000..568d80f
--- /dev/null
+++ b/charms/focal/autopkgtest-web/units/endpoint-checker.timer
@@ -0,0 +1,9 @@
+[Unit]
+Description=Check all endpoints for autopkgtest-web
+
+[Timer]
+OnBootSec=2min
+OnCalendar=00 00 * * *
+
+[Install]
+WantedBy=autopkgtest-web.target
diff --git a/charms/focal/autopkgtest-web/webcontrol/browse.cgi b/charms/focal/autopkgtest-web/webcontrol/browse.cgi
index 7885e91..6faaafd 100755
--- a/charms/focal/autopkgtest-web/webcontrol/browse.cgi
+++ b/charms/focal/autopkgtest-web/webcontrol/browse.cgi
@@ -12,6 +12,7 @@ from wsgiref.handlers import CGIHandler
 
 import distro_info
 import flask
+from flask import request
 from werkzeug.middleware.proxy_fix import ProxyFix
 
 app = flask.Flask("browse")
@@ -25,7 +26,14 @@ SUPPORTED_UBUNTU_RELEASES = sorted(
     set(UDI.supported() + UDI.supported_esm()), key=ALL_UBUNTU_RELEASES.index
 )
 
+<<<<<<< charms/focal/autopkgtest-web/webcontrol/browse.cgi
 INDEXED_PACKAGES_FP = ""
+=======
+INTEGRATION_TEST_RES_FP = (
+    "/run/autopkgtest_webcontrol/integration-test-results.json"
+)
+INTEGRATION_TESTS_PASS_FP = "/home/ubuntu/integration-key"
+>>>>>>> charms/focal/autopkgtest-web/webcontrol/browse.cgi
 
 
 def init_config():
@@ -433,6 +441,100 @@ def testlist():
     return render("browse-testlist.html", indexed_pkgs=indexed_pkgs)
 
 
+@app.route("/post-integration-results", methods=["POST"])
+def handle_results():
+    # need to check authentication using password or something
+    global INTEGRATION_TEST_RES_FP
+    results = {}
+    if os.path.isfile(INTEGRATION_TEST_RES_FP):
+        with open(INTEGRATION_TEST_RES_FP, "r") as f:
+            results = json.load(f)
+    data = request.json
+
+    # key check
+    keys = ["type", "source", "pass", "test", "results"]
+    # make this check more extensive ?
+    for k in keys:
+        if k not in data.keys():
+            return
+    if data["type"] not in ["cloud", "web"]:
+        return
+    # authenticate here
+    keypass = ""
+    if os.path.isfile(INTEGRATION_TESTS_PASS_FP):
+        with open(INTEGRATION_TESTS_PASS_FP, "r") as f:
+            keypass = f.read()
+    if data["pass"].rstrip() != keypass.rstrip():
+        return (
+            json.dumps({"success": False, "reason": "incorrect pass"}),
+            403,
+            {"ContentType": "application/json"},
+        )
+
+    if "cloud" not in results.keys():
+        results["cloud"] = {}
+    if "web" not in results.keys():
+        results["web"] = {}
+    if data["source"] not in results[data["type"]].keys():
+        results[data["type"]][data["source"]] = {}
+    results[data["type"]][data["source"]][data["test"]] = data["results"]
+    with open(INTEGRATION_TEST_RES_FP, "w") as f:
+        json.dump(results, f, indent=2)
+    return (
+        json.dumps({"success": True}),
+        200,
+        {"ContentType": "application/json"},
+    )
+
+    # results being posted
+    # results = {
+    #     "type": "cloud/web",
+    #     "source": "machine-name",
+    #     "pass": "pass",
+    #     "test": "config-files/endpoints/test-run-autopkgtest",
+    #     "results": {}, # <- json of actual test results
+    # }
+
+    # results going out:
+    # results = {
+    #     "cloud": {
+    #         "machine1": {
+    #             "test-name-1": {}, # <- results
+    #             "test-name-2": {}, # <- results
+    #         },
+    #         "machine2": {
+    #             "test-name-1": {}, # <- results
+    #             "test-name-2": {}, # <- results
+    #         }
+    #     },
+    #     "web" : {
+    #         "machine1": {
+    #             "test-name-1": {}, # <- results
+    #             "test-name-2": {}, # <- results
+    #         },
+    #         "machine1": {
+    #             "test-name-1": {}, # <- results
+    #             "test-name-2": {}, # <- results
+    #         },
+    #     }
+    # }
+
+
+@app.route("/integration-test-results.json", methods=["GET"])
+def get_integration_test_results():
+    global INTEGRATION_TEST_RES_FP
+    results = {}
+    if os.path.isfile(INTEGRATION_TEST_RES_FP):
+        with open(INTEGRATION_TEST_RES_FP, "r") as f:
+            results = json.load(f)
+        return flask.Response(
+            json.dumps(results, indent=2), mimetype="application/json"
+        )
+    return flask.Response(
+        json.dumps({}, indent=2), mimetype="application/json"
+    )
+
+
 @app.route("/statistics")
 def statistics():
     release_arches = get_release_arches()
diff --git a/charms/focal/autopkgtest-web/webcontrol/check-config-files b/charms/focal/autopkgtest-web/webcontrol/check-config-files
new file mode 100755
index 0000000..b002d40
--- /dev/null
+++ b/charms/focal/autopkgtest-web/webcontrol/check-config-files
@@ -0,0 +1,180 @@
+#!/usr/bin/python3
+import configparser
+import os
+import socket
+import sys
+import urllib.parse
+import urllib.request
+import json
+
+import requests
+from distro_info import UbuntuDistroInfo
+
+# openstack-creds <- todo: part of the update-github-jobs-swiftclient-refactor mp
+UDI = UbuntuDistroInfo()
+
+INTEGRATION_KEY_FP = "/home/ubuntu/integration-key"
+
+
+def check_env_file_and_keys(file, file_keys):
+    mypath = os.path.expanduser("~/" + file)
+    if not os.path.isfile(mypath):
+        raise FileNotFoundError("file %s doesn't exist" % file)
+    with open(mypath, "r") as f:
+        myf = f.read()
+    for k in file_keys:
+        if k not in myf:
+            raise KeyError("key %s not found in %s" % (k, file))
+    return myf
+
+
+def check_autopkgtest_cloud_conf(conf_file, conf_keys):
+    myf = check_env_file_and_keys(conf_file, conf_keys)
+    sw_url = ""
+    for line in myf.splitlines():
+        if "SwiftURL" in line:
+            sw_url = line.split("=")[1]
+    for supported in UDI.supported():
+        req = requests.get(sw_url + "/autopkgtest-" + supported)
+        if req.status_code != 200:
+            raise requests.ConnectionError(
+                "Container autopkgtest-%s is unreachable - something wrong with the SwiftURL: %s"
+                % (supported, sw_url)
+            )
+    cp = configparser.ConfigParser()
+    cp.read(os.path.expanduser("~/" + conf_file))
+
+
+def check_github_secrets(secrets_file, vars):
+    _ = check_env_file_and_keys(secrets_file, vars)
+
+
+def check_github_status_creds(creds_file, vars):
+    _ = check_env_file_and_keys(creds_file, vars)
+
+
+def check_swift_web_creds(creds_file, vars):
+    _ = check_env_file_and_keys(creds_file, vars)
+    cp = configparser.ConfigParser()
+    cp.read(os.path.expanduser("~/" + creds_file))
+    for var in vars[1:]:
+        _ = cp.get("swift", var)
+
+
+def check_openstack_creds(creds_file, vars):
+    _ = check_env_file_and_keys(creds_file, vars)
+
+
+autopkgtest_cloud_vars = [
+    "[web]",
+    "database",
+    "database_ro",
+    "SwiftURL",
+    "ExternalURL",
+    "cookies",
+    "[amqp]",
+    "uri",
+]
+
+github_secrets_keys = [
+    "systemd-upstream",
+    "ovs-upstream",
+    "ubuntu-image-autopkgtest",
+    "snapcraft",
+    "snapd",
+]
+
+github_status_credentials = [
+    "systemd-upstream",
+    "ubuntu-image-autopkgtest",
+    "snapcraft",
+    "snapd",
+]
+
+swift_web_creds = [
+    "[swift]",
+    "auth_url",
+    "username",
+    "password",
+    "tenant",
+    "region_name",
+    "project_name",
+    "project_domain_name",
+    "user_domain_name",
+    "obj_storage_url",
+]
+
+openstack_creds = [
+    "OS_REGION_NAME",
+    "OS_INTERFACE",
+    "OS_AUTH_URL",
+    "OS_PROJECT_DOMAIN_NAME",
+    "OS_USERNAME",
+    "OS_USER_DOMAIN_NAME",
+    "OS_PROJECT_NAME",
+    "OS_PASSWORD",
+    "OS_IDENTITY_API_VERSION",
+]
+
+
+files_and_keys = {
+    "autopkgtest-cloud.conf": {
+        "vars": autopkgtest_cloud_vars,
+        "func": check_autopkgtest_cloud_conf,
+    },
+    "github-secrets.json": {
+        "vars": github_secrets_keys,
+        "func": check_github_secrets,
+    },
+    "github-status-credentials.txt": {
+        "vars": github_status_credentials,
+        "func": check_github_status_creds,
+    },
+    "swift-web-credentials.conf": {
+        "vars": swift_web_creds,
+        "func": check_swift_web_creds,
+    },
+    "openstack-creds": {
+        "vars": openstack_creds,
+        "func": check_openstack_creds,
+    },
+}
+
+RESULTS = {}
+
+if __name__ == "__main__":
+    for file, item in files_and_keys.items():
+        try:
+            item["func"](file, item["vars"])
+            RESULTS[file] = True
+        except Exception as _:
+            RESULTS[file] = False
+    
+    cp = configparser.ConfigParser()
+    cp.read(os.path.expanduser("~ubuntu/autopkgtest-cloud.conf"))
+    try:
+        webpage = cp["web"]["ExternalURL"].replace("/results", "")
+    except KeyError:
+        # change to logging maybe ?
+        print("No external url found!")
+        sys.exit(1)
+    keypass = ""
+    with open(INTEGRATION_KEY_FP, "r") as f:
+        keypass = f.read().rstrip()
+    post_me = {
+        "type": "web",
+        "source": socket.gethostname(),
+        "pass": keypass,
+        "test": __file__,
+        "results": RESULTS,
+    }
+    results_url = webpage + "/post-integration-results"
+    req = urllib.request.Request(results_url)
+    req.add_header('Content-Type', 'application/json; charset=utf-8')
+    jsondata = json.dumps(post_me).encode('utf-8')
+    req.add_header('Content-Length', len(jsondata))
+    response = urllib.request.urlopen(req, jsondata)
+
+    with open("/home/ubuntu/check-config-files-results.json", "w") as f:
+        f.write(json.dumps(RESULTS, indent=2))
+
diff --git a/charms/focal/autopkgtest-web/webcontrol/endpoint-checker b/charms/focal/autopkgtest-web/webcontrol/endpoint-checker
new file mode 100755
index 0000000..e4f3dc5
--- /dev/null
+++ b/charms/focal/autopkgtest-web/webcontrol/endpoint-checker
@@ -0,0 +1,92 @@
+#!/usr/bin/python3
+
+import argparse
+import configparser
+import socket
+import json
+import logging
+import os
+import sys
+import urllib.parse
+import urllib.request
+
+ENDPOINTS = [
+    "/",
+    "/queues.json",
+    "/queued.json",
+    "/packages/gzip",
+    "/packages/gzip/noble/amd64",
+    "/running",
+    "/static/running.json",
+    "/queue_size.json",
+    "/testlist",
+    "/statistics",
+]
+RESULTS = {}
+INTEGRATION_KEY_FP = "/home/ubuntu/integration-key"
+
+
+if __name__ == "__main__":
+    logging.getLogger().setLevel(logging.INFO)
+    cp = configparser.ConfigParser()
+    cp.read(os.path.expanduser("~ubuntu/autopkgtest-cloud.conf"))
+    try:
+        webpage = cp["web"]["ExternalURL"].replace("/results", "")
+    except KeyError:
+        # change to logging maybe ?
+        print("No external url found!")
+        sys.exit(1)
+    try:
+        cookies = cp["web"]["cookies"]
+    except KeyError:
+        print("No cookies in config!")
+        sys.exit(1)
+    logging.info("Webpage: %s" % webpage)
+    logging.info("Cookies: %s" % cookies)
+
+    for srvname_cookie in cookies.split(" "):
+        RESULTS[srvname_cookie] = {}
+        for endpoint in ENDPOINTS:
+            logging.info("Trying endpoint: %s" % endpoint)
+            try:
+                req = urllib.request.Request(webpage + endpoint)
+                req.add_header("Cookie", "SRVNAME=" + srvname_cookie)
+                output = urllib.request.urlopen(req).read()
+                if ".json" in endpoint:
+                    try:
+                        my_json = json.loads(output)
+                        RESULTS[srvname_cookie][endpoint] = True
+                        logging.info("Endpoint %s succeeded!")
+                    except json.JSONDecodeError as e:
+                        RESULTS[srvname_cookie][endpoint] = False
+                        logging.info("Endpoint %s failed!")
+                else:
+                    logging.info("Endpoint %s succeeded!")
+                    # maybe needs work
+                    RESULTS[srvname_cookie][endpoint] = True
+                    # process ????? just make sure no failures ig?
+                    # idk what to do here
+                    pass
+            except urllib.error.HTTPError as _:
+                logging.info("Endpoint %s failed!")
+                # log here
+                RESULTS[srvname_cookie][endpoint] = False
+    with open("/home/ubuntu/endpoint-checker-results.json", "w") as f:
+        f.write(json.dumps(RESULTS, indent=2))
+    keypass = ""
+    with open(INTEGRATION_KEY_FP, "r") as f:
+        keypass = f.read().rstrip()
+    post_me = {
+        "type": "web",
+        "source": socket.gethostname(),
+        "pass": keypass,
+        "test": __file__,
+        "results": RESULTS,
+    }
+    results_url = webpage + "/post-integration-results"
+    req = urllib.request.Request(results_url)
+    req.add_header('Content-Type', 'application/json; charset=utf-8')
+    jsondata = json.dumps(post_me).encode('utf-8')
+    req.add_header('Content-Length', len(jsondata))
+    response = urllib.request.urlopen(req, jsondata)
+    logging.info("Results:\n%s" % json.dumps(RESULTS, indent=2))
diff --git a/charms/focal/autopkgtest-web/webcontrol/new-test-request b/charms/focal/autopkgtest-web/webcontrol/new-test-request
new file mode 100755
index 0000000..4098846
--- /dev/null
+++ b/charms/focal/autopkgtest-web/webcontrol/new-test-request
@@ -0,0 +1,124 @@
+#!/usr/bin/python3
+
+
+import json
+
+from distro_info import UbuntuDistroInfo
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+
+UDI = UbuntuDistroInfo()
+LATEST = UDI.supported()[-1]
+ARGS = {
+    "migration-reference-all-proposed-check": {
+        "args": [
+            "release=noble",
+            "arch=amd64",
+            "package=gzip",
+            "trigger=migration-reference/0",
+            "all-proposed=1",
+        ],
+        "expected-response": "migration-reference/0 and all-proposed=1 are not compatible arguments.",
+    },
+    "invalid-release": {
+        "args": [
+            "release=bocal",
+            "arch=amd64",
+            "package=gzip",
+            "trigger=gzip/1.12-1ubuntu1",
+        ],
+        "expected-response": "release bocal not found",
+    },
+    "invalid-arch": {
+        "args": [
+            "release=noble",
+            "arch=amz64",
+            "package=gzip",
+            "trigger=gzip/1.12-1ubuntu1",
+        ],
+        "expected-response": "arch amz64 not found",
+    },
+    "invalid-ppa": {
+        "args": [
+            "release=noble",
+            "arch=amd64",
+            "package=gzip",
+            "trigger=gzip/1.12-1ubuntu1",
+            "ppa=andersson123/hell",
+        ],
+        "expected-response": "ppa andersson123/hell not found",
+    },
+    "invalid-package": {
+        "args": [
+            "release=noble",
+            "arch=amd64",
+            "package=gsip",
+            "trigger=gzip/1.12-1ubuntu1",
+        ],
+        "expected-response": "package gsip does not have any test results",
+    },
+    "ppa-migration-reference-0": {
+        "args": [
+            "release=noble",
+            "arch=amd64",
+            "package=gzip",
+            "trigger=migration-reference/0",
+            "ppa=andersson123/autopkgtest",
+        ],
+        "expected-response": "Cannot use PPAs with migration-reference/0",
+    },
+    "migration-reference-0-additional-triggers": {
+        "args": [
+            "release=noble",
+            "arch=amd64",
+            "package=gzip",
+            "trigger=gzip/1.12-1ubuntu1",
+            "trigger=migration-reference/0",
+        ],
+        "expected-response": "Cannot use additional triggers with migration-reference/0",
+    },
+    "malformed-trigger": {
+        "args": [
+            "release=noble",
+            "arch=amd64",
+            "package=gzip",
+            "trigger=gzip-1.12-1ubuntu1",
+        ],
+        "expected-response": "Malformed trigger, must be srcpackage/version",
+    },
+    "trigger-not-published-in-release": {
+        "args": [
+            "release=noble",
+            "arch=amd64",
+            "package=gzip",
+            "trigger=gzip/1.10-4ubuntu4.1",
+        ],
+        "expected-response": "gzip/1.10-4ubuntu4.1 is not published in noble",
+    },
+}
+
+
+webpage = "https://autopkgtest.ubuntu.com/request.cgi?";
+
+
+if __name__ == "__main__":
+    # need to authenticate first
+    browser = webdriver.Chrome()
+    login_url = webpage + "/login"
+    browser.get(login_url)
+    input("Press enter once you are authenticated.")
+    results = {}
+    for key, val in ARGS.items():
+        print("Running " + key)
+        this_req = webpage + "&".join(val["args"])
+        browser.get(this_req)
+        # our login button request.cgi?/login
+        # browser.find_element(By.XPATH, "/html/body/form/input[1]").click()
+        # "Yes log me in" on login.ubuntu.com
+        # browser.find_element(By.XPATH, "/html/body/div[1]/div/div/div[2]/section/div/form/div[1]/button").click()
+        html = browser.page_source
+        if val["expected-response"] in html:
+            results[key] = True
+        else:
+            results[key] = False
+    print(json.dumps(results, indent=2))
diff --git a/charms/focal/autopkgtest-web/webcontrol/test-request-cgi b/charms/focal/autopkgtest-web/webcontrol/test-request-cgi
new file mode 100755
index 0000000..659fd5b
--- /dev/null
+++ b/charms/focal/autopkgtest-web/webcontrol/test-request-cgi
@@ -0,0 +1,109 @@
+#!/usr/bin/python3
+
+import configparser
+import logging
+import os
+import sys
+import urllib.parse
+import urllib.request
+import webbrowser
+
+import requests
+from distro_info import UbuntuDistroInfo
+
+# what do i need to do here?
+# Request a test via webpage (gzip on amd64), have it run and pass
+# maybe check the is_request_queued_or_running call
+# use nick=andersson123 for now
+
+# Then request tests with disallowed arguments and check for the correct responses
+# - migration reference all proposed check (migration-reference=0 and all-proposed=0)
+# - invalid release?
+# - invalid architecture
+# - invalid ppa
+# - invalid package (no results)
+# - ppas with migration-reference=0
+# - migration reference=0 with additional triggers
+# - malformed trigger (invalid format, not srcpackage/version)
+# - trigger not published in PPA (not sure of this one)
+# - trigger not published in release (use an old trigger or something)
+# - requester not allowed - try setting nick to nothing
+
+# https://autopkgtest.staging.ubuntu.com/request.cgi?release=mantic&arch=amd64&package=gzip&trigger=gzip/1.12-1ubuntu1
+UDI = UbuntuDistroInfo()
+LATEST = UDI.supported()[-1]
+# SESSION_COOKIE = ""
+# this filepath will need changing
+# with open("/home/ubuntu/tim/session-cookie", "r") as f:
+#     SESSION_COOKIE = f.read()
+
+ARGS = {
+    "migration-reference-all-proposed-check": {
+        "args": [
+            "release=%s" % LATEST,
+            "arch=amd64",
+            "package=gzip",
+            "trigger=migration-reference/0",
+            "all-proposed=1",
+        ],
+        "expected-response": "migration-reference/0 and all-proposed=1 are not compatible arguments.",
+    },
+    # add more after I've tested this works
+}
+
+
+if __name__ == "__main__":
+    logging.getLogger().setLevel(logging.INFO)
+    try:
+        cp = configparser.ConfigParser()
+        cp.read(os.path.expanduser("~ubuntu/autopkgtest-cloud.conf"))
+    except Exception as _:
+        pass
+    try:
+        webpage = cp["web"]["ExternalURL"].replace("/results", "/request.cgi?")
+    except KeyError:
+        # change to logging maybe ?
+        print("No external url found!")
+        webpage = "https://autopkgtest.ubuntu.com/request.cgi?";
+        # sys.exit(1)
+    try:
+        cookies = cp["web"]["cookies"]
+    except KeyError:
+        print("No cookies in config!")
+        cookies = "S0 S1"
+        # sys.exit(1)
+    # try:
+    #     with open(os.path.expanduser("~ubuntu/nickname"), "r") as f:
+    #         nickname = f.read()
+    # except FileNotFoundError:
+    #     print("No nickname file found")
+    #     sys.exit(1)
+    logging.info("Webpage: %s" % webpage)
+    logging.info("Cookies: %s" % cookies)
+    # logging.info("Nickname: %s" % nickname)
+    for key, val in ARGS.items():
+        logging.info("Running integration test for %s" % key)
+        this_req = webpage + "&".join(val["args"])
+        logging.info("Running the following request:\n%s" % this_req)
+        # passman = urllib.request.HTTPPasswordMgrWithDefaultRealm()
+        # passman.add_password(None, this_req, "andersson123", "Visionamdw7!")
+        # authhandler = urllib.request.HTTPBasicAuthHandler(passman)
+        # opener = urllib.request.install_opener(authhandler)
+        # urllib.request.install_opener(opener)
+        # res = urllib.request.urlopen(this_req)
+        # res_body = res.read()
+        # print(str(res_body))
+        # print(requests.get(this_req))
+        webbrowser.open(this_req)
+        # try:
+        # req = urllib.request.Request(this_req)
+        # need to add cookies here lol
+        # req.add_header("Cookie", "SRVNAME=S0")
+        # req.add_header("Cookie", "session=" + SESSION_COOKIE)
+        # req.add_header("Cookie", "nickname=" + nickname)
+        # output = urllib.request.urlopen(req).read()
+        # logging.info("Response:\n%s" % output)
+        # except urllib.error.HTTPError as e:
+        #     logging.info("E:\n%s" % e)
+
+        # req = urllib.request.Requ
diff --git a/mojo/service-bundle b/mojo/service-bundle
index 534bebe..a5d22bb 100644
--- a/mojo/service-bundle
+++ b/mojo/service-bundle
@@ -40,6 +40,7 @@ applications:
             influxdb-hostname: include-file://{{ local_dir }}/influx-hostname.txt
             influxdb-password: include-file://{{ local_dir }}/influx-password.txt
             influxdb-database: metrics
+            autopkgtest-hostname: {{ hostname }}
 {%- if stage_name == "production" %}
             influxdb-username: prod_proposed_migration
             influxdb-context: production

Follow ups