← Back to team overview

canonical-ubuntu-qa team mailing list archive

[Merge] ~andersson123/autopkgtest-cloud:exit_code_suite_autopkgtest-web into autopkgtest-cloud:master

 

Tim Andersson has proposed merging ~andersson123/autopkgtest-cloud:exit_code_suite_autopkgtest-web 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/447647
-- 
Your team Canonical's Ubuntu QA is requested to review the proposed merge of ~andersson123/autopkgtest-cloud:exit_code_suite_autopkgtest-web into autopkgtest-cloud:master.
diff --git a/charms/focal/autopkgtest-web/webcontrol/helpers/exceptions.py b/charms/focal/autopkgtest-web/webcontrol/helpers/exceptions.py
new file mode 100644
index 0000000..302ea26
--- /dev/null
+++ b/charms/focal/autopkgtest-web/webcontrol/helpers/exceptions.py
@@ -0,0 +1,81 @@
+"""
+Http exceptions for autopkgtest-web
+"""
+
+
+class WebControlException(Exception):
+    def __init__(self, message, exit_code):
+        super().__init__(message)
+        self._code = exit_code
+
+    def exit_code(self):
+        return self._code
+
+
+class DuplicateRequestException(WebControlException):
+    def __init__(self, release, package, arch, triggers):
+        super().__init__(
+            (
+                "Test already queued:\nrelease: %s\npkg: %s\narch: %s\ntriggers: %s",
+                release,
+                package,
+                arch,
+                ",".join(triggers),
+            ),
+            429,
+        )
+
+
+class BadRequest(WebControlException):
+    def __init__(self, msg=None):
+        if msg is None:
+            super().__init__(
+                "Bad request - unnacceptable passed variables", 400
+            )
+        else:
+            super().__init__(msg, 400)
+
+
+class Unauthorized(WebControlException):
+    def __init__(self):
+        super().__init__("Authorization failure", 401)
+
+
+class ForbiddenRequest(WebControlException):
+    def __init__(self, package, trigger):
+        super().__init__(
+            (
+                "You are not allowed to upload %s or %s to Ubuntu, "
+                + "thus you are not allowed to use this service."
+            )
+            % (package, trigger),
+            403,
+        )
+
+
+class NotFound(WebControlException):
+    def __init__(self, element_name, element, msg=None):
+        if msg is None:
+            super().__init__(
+                "%s %s not found" % (element_name, element),
+                404,
+            )
+        else:
+            super().__init__(
+                "%s %s %s" % (element_name, element, msg),
+                404,
+            )
+
+
+class TooManyRequests(WebControlException):
+    def __init__(self, requester):
+        super().__init__(
+            "You, %s, have requested too many tests. Please try again later."
+            % requester,
+            429,
+        )
+
+
+class Teapot(WebControlException):
+    def __init__(self):
+        super().__init__("Sorry, I'm a teapot", 418)
diff --git a/charms/focal/autopkgtest-web/webcontrol/request/app.py b/charms/focal/autopkgtest-web/webcontrol/request/app.py
index 8ad5270..73fb0dc 100644
--- a/charms/focal/autopkgtest-web/webcontrol/request/app.py
+++ b/charms/focal/autopkgtest-web/webcontrol/request/app.py
@@ -8,8 +8,9 @@ from html import escape as _escape
 
 from flask import Flask, redirect, request, session
 from flask_openid import OpenID
+from helpers.exceptions import WebControlException
 from helpers.utils import setup_key
-from request.submit import DuplicateRequestException, Submit
+from request.submit import Submit
 from werkzeug.middleware.proxy_fix import ProxyFix
 
 # map multiple GET vars to AMQP JSON request parameter list
@@ -184,8 +185,8 @@ def index_root():
                     github_params["number"],
                 )
             s.validate_git_request(**params)
-        except (ValueError, TypeError) as e:
-            return invalid(e)
+        except WebControlException as e:
+            return invalid(e, e.exit_code())
         except KeyError as e:
             return invalid("Missing field in JSON data: %s" % e)
 
@@ -235,10 +236,8 @@ def index_root():
         s = Submit()
         try:
             s.validate_distro_request(**params)
-        except (ValueError, TypeError) as e:
-            return invalid(e)
-        except DuplicateRequestException as e:
-            return invalid(e, 403)
+        except WebControlException as e:
+            return invalid(e, e.exit_code())
 
         if params.get("delete"):
             del params["delete"]
diff --git a/charms/focal/autopkgtest-web/webcontrol/request/submit.py b/charms/focal/autopkgtest-web/webcontrol/request/submit.py
index aeeb9a9..b4f654a 100644
--- a/charms/focal/autopkgtest-web/webcontrol/request/submit.py
+++ b/charms/focal/autopkgtest-web/webcontrol/request/submit.py
@@ -17,11 +17,12 @@ from urllib.error import HTTPError
 
 import amqplib.client_0_8 as amqp
 from distro_info import UbuntuDistroInfo
-
-
-class DuplicateRequestException(Exception):
-    pass
-
+from helpers.exceptions import (  # TooManyRequests,; Unauthorized,
+    BadRequest,
+    DuplicateRequestException,
+    ForbiddenRequest,
+    NotFound,
+)
 
 # Launchpad REST API base
 LP = "https://api.launchpad.net/1.0/";
@@ -87,11 +88,11 @@ class Submit:
         Raise ValueError with error messsage if the request is invalid,
         otherwise return.
         """
-        not_needed, msg = self.is_request_queued_or_running(
+        not_needed = self.is_request_queued_or_running(
             release, arch, package, triggers
         )
         if not_needed:
-            raise DuplicateRequestException(msg)
+            raise DuplicateRequestException(release, package, arch, triggers)
 
         can_upload_any_trigger = False
 
@@ -118,12 +119,12 @@ class Submit:
             raise ValueError("Invalid argument %s" % list(kwargs)[0])
 
         if release not in self.releases:
-            raise ValueError("Unknown release " + release)
+            raise NotFound("release", release)
         if arch not in self.architectures:
-            raise ValueError("Unknown architecture " + arch)
+            raise NotFound("arch", arch)
         for ppa in ppas:
             if not self.is_valid_ppa(ppa):
-                raise ValueError("Unknown PPA " + ppa)
+                raise NotFound("ppa", ppa)
         # allow kernel tests for EOL vivid
         skip_result_check = (
             release == "vivid" and triggers and triggers[0].startswith("linux")
@@ -131,31 +132,33 @@ class Submit:
         if not self.is_valid_package_with_results(
             None if (ppas or skip_result_check) else release, arch, package
         ):
-            raise ValueError(
-                "Package %s does not have any test results" % package
+            raise NotFound(
+                "package", package, "does not have any test results"
             )
 
         if "migration-reference/0" in triggers:
             if len(triggers) != 1:
-                raise ValueError(
+                raise BadRequest(
                     "Cannot use additional triggers with migration-reference/0"
                 )
             if ppas:
-                raise ValueError("Cannot use PPAs with migration-reference/0")
+                raise BadRequest("Cannot use PPAs with migration-reference/0")
             if "all-proposed" in kwargs:
-                raise ValueError(
+                raise BadRequest(
                     'Cannot use "all-proposed" with migration-reference/0'
                 )
         for trigger in triggers:
             try:
                 trigsrc, trigver = trigger.split("/")
-            except ValueError as exc:
-                raise ValueError(
+            except ValueError as e:
+                raise BadRequest(
                     "Malformed trigger, must be srcpackage/version"
-                ) from exc
+                ) from e
             # Debian Policy 5.6.1 and 5.6.12
             if not NAME.match(trigsrc) or not VERSION.match(trigver):
-                raise ValueError("Malformed trigger")
+                raise BadRequest(
+                    "Malformed trigger: %s\nversion: %s" % (trigsrc, trigver)
+                )
 
             # Special snowflake
             if trigger in ("qemu-efi-noacpi/0", "migration-reference/0"):
@@ -165,7 +168,7 @@ class Submit:
                 if not self.is_valid_package_version(
                     release, trigsrc, trigver, ppas and ppas[-1] or None
                 ):
-                    raise ValueError(
+                    raise BadRequest(
                         "%s is not published in PPA %s %s"
                         % (trigger, ppas[-1], release)
                     )
@@ -180,7 +183,7 @@ class Submit:
                     release, trigsrc, trigver
                 )
                 if not trigsrc_component:
-                    raise ValueError(
+                    raise BadRequest(
                         "%s is not published in %s" % (trigger, release)
                     )
 
@@ -197,7 +200,7 @@ class Submit:
                 release, package, None
             )
             if not package_component:
-                raise ValueError(
+                raise BadRequest(
                     "%s is not published in %s" % (package, release)
                 )
 
@@ -208,11 +211,7 @@ class Submit:
             and requester not in ALLOWED_USERS_PERPACKAGE.get(package, [])
             and not self.in_allowed_team(requester, package)
         ):
-            raise ValueError(
-                "You are not allowed to upload %s or %s to "
-                "Ubuntu, thus you are not allowed to use this "
-                "service." % (package, trigsrc)
-            )
+            raise ForbiddenRequest(package, ",".join(triggers))
 
     # pylint: disable=dangerous-default-value
     def validate_git_request(
@@ -238,40 +237,41 @@ class Submit:
                 else:
                     triggers.append(env_var.split("=")[1])
 
-        not_needed, msg = self.is_request_queued_or_running(
+        not_needed = self.is_request_queued_or_running(
             release, arch, package, triggers
         )
         if not_needed:
-            raise DuplicateRequestException(msg)
+            raise DuplicateRequestException(release, package, arch, triggers)
 
         if release not in self.releases:
-            raise ValueError("Unknown release " + release)
+            raise NotFound("release", release)
         if arch not in self.architectures:
-            raise ValueError("Unknown architecture " + arch)
+            raise NotFound("arch", arch)
         if not NAME.match(package):
-            raise ValueError("Malformed package")
+            raise NotFound("package", package)
+            # raise ValueError("Malformed package")
         if not ppas:
-            raise ValueError(
+            raise BadRequest(
                 "Must specify at least one PPA (to associate results with)"
             )
         for ppa in ppas:
             if not self.is_valid_ppa(ppa):
-                raise ValueError("Unknown PPA " + ppa)
+                raise NotFound("ppa", ppa)
         for e in env:
             if not ENV.match(e):
-                raise ValueError(
+                raise BadRequest(
                     'Invalid environment variable format "%s"' % e
                 )
         # we should only be called in this mode
         assert "build-git" in kwargs
         if not GIT.match(kwargs["build-git"]):
-            raise ValueError("Malformed build-git")
+            raise BadRequest("Malformed build-git")
         if "testname" in kwargs and not NAME.match(kwargs["testname"]):
-            raise ValueError("Malformed testname")
+            raise BadRequest("Malformed testname")
 
         unsupported_keys = set(kwargs.keys()) - {"build-git", "testname"}
         if unsupported_keys:
-            raise ValueError(
+            raise BadRequest(
                 "Unsupported arguments: %s" % " ".join(unsupported_keys)
             )
 
@@ -607,20 +607,10 @@ class Submit:
         if self.is_test_running(
             req_series, req_arch, req_package, req_triggers
         ):
-            o_msg = "Requested test is already running."
-            o_msg += "\nRelease: " + req_series
-            o_msg += "\nArchitecture: " + req_arch
-            o_msg += "\nPackage: " + req_package
-            o_msg += "\nTriggers: " + ",".join(req_triggers)
-            return True, o_msg
+            return True
 
         if self.is_test_in_queue(
             req_series, req_arch, req_package, req_triggers
         ):
-            o_msg = "Requested test is already queued."
-            o_msg += "\nRelease: " + req_series
-            o_msg += "\nArchitecture: " + req_arch
-            o_msg += "\nPackage: " + req_package
-            o_msg += "\nTriggers: " + ",".join(req_triggers)
-            return True, o_msg
-        return False, ""
+            return True
+        return False

Follow ups