Merge ~andersson123/autopkgtest-cloud:no_queue_duplicate_requests into autopkgtest-cloud:master

Proposed by Tim Andersson
Status: Merged
Merged at revision: 66b81baafda8429a43fb42399e928e3660a8c6f9
Proposed branch: ~andersson123/autopkgtest-cloud:no_queue_duplicate_requests
Merge into: autopkgtest-cloud:master
Diff against target: 173 lines (+118/-1)
2 files modified
charms/focal/autopkgtest-web/webcontrol/request/app.py (+3/-1)
charms/focal/autopkgtest-web/webcontrol/request/submit.py (+115/-0)
Reviewer Review Type Date Requested Status
Brian Murray Approve
Review via email: mp+447108@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Tim Andersson (andersson123) wrote :

Just going to quickly test this in staging then it's good to go.

Revision history for this message
Tim Andersson (andersson123) wrote :

Ready for re-review.

Revision history for this message
Brian Murray (brian-murray) wrote :

Is there any point in time where either json file will not exist or be empty? If the answer is yes or we aren't sure then the code should be modified to handle this situation as it currently looks like it'll produce a traceback in those situations.

Additionally, https://bugs.launchpad.net/auto-package-testing/+bug/1654761/comments/4 makes reference to adding the check to validate_distro_request() which makes sense to me given the other checks in that function.

Otherwise the content of the functions you've added looks great.

review: Needs Fixing
Revision history for this message
Tim Andersson (andersson123) wrote :

I added this too and removed it because I came to the conclusion if those two files don't exist, nothing will be working anyway and the problem will be elsewhere. It could almost be a helpful alarm bell.

Revision history for this message
Tim Andersson (andersson123) wrote :

Do you think we also need to add it to validate_git_request as well?

Revision history for this message
Tim Andersson (andersson123) wrote :

All amended based on your comments, tested and works as intended. Let me know what you think re validate_git_request. I think it should also go in there

Revision history for this message
Brian Murray (brian-murray) wrote :

> I added this too and removed it because I came to the conclusion if those two
> files don't exist, nothing will be working anyway and the problem will be
> elsewhere. It could almost be a helpful alarm bell.

What would the failure scenario look like though? Would the web service completely fall over preventing any requests from being accepted? If that's the case I'd rather accept any request, including a duplicate one, rather then having the service fail.

Revision history for this message
Brian Murray (brian-murray) wrote :

Please modify this to return a 403 instead of a 400 and yes I think it should be added to validate_git_request. We don't want duplicate tests queued regardless of the type.

review: Needs Fixing
Revision history for this message
Tim Andersson (andersson123) wrote :

Amended and rebased. What do you think about my exit code implementation?

Revision history for this message
Brian Murray (brian-murray) wrote :

> Amended and rebased. What do you think about my exit code implementation?

Looking at submit.py there are multiple cases where ValueError is used that seem more like a 400 than a 403, so if we were to look at a simple solution I'd suggest using TypeError for a duplicate request and return a 403 with that.

However, that doesn't feel very clean and I think we should instead look at using a custom exception so that we can more be more granular with our exit / return codes. (I thought I also saw some cases where we'd ideally return a 401 instead of a 400.)

In the interest of just getting this done though let's do the simple solution (or something like it) now and create a card, or bug, for improving the way in which we use http status codes.

Revision history for this message
Tim Andersson (andersson123) wrote :

I've added the check in validate_git_request, still not sure how to test though. Although I think the code is somewhat fool-proof.

Revision history for this message
Brian Murray (brian-murray) wrote :

I imagine we'll hear pretty quickly if validate_git_request is broken so I'm not too concerned about that. The changes look great and I was able to build the charm locally so the error here is a false positive.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/charms/focal/autopkgtest-web/webcontrol/request/app.py b/charms/focal/autopkgtest-web/webcontrol/request/app.py
2index c8791a5..beeb22d 100644
3--- a/charms/focal/autopkgtest-web/webcontrol/request/app.py
4+++ b/charms/focal/autopkgtest-web/webcontrol/request/app.py
5@@ -9,7 +9,7 @@ from html import escape as _escape
6 from flask import Flask, redirect, request, session
7 from flask_openid import OpenID
8 from helpers.utils import setup_key
9-from request.submit import Submit
10+from request.submit import DuplicateRequestException, Submit
11 from werkzeug.middleware.proxy_fix import ProxyFix
12
13 # map multiple GET vars to AMQP JSON request parameter list
14@@ -236,6 +236,8 @@ def index_root():
15 s.validate_distro_request(**params)
16 except (ValueError, TypeError) as e:
17 return invalid(e)
18+ except DuplicateRequestException as e:
19+ return invalid(e, 403)
20
21 if params.get("delete"):
22 del params["delete"]
23diff --git a/charms/focal/autopkgtest-web/webcontrol/request/submit.py b/charms/focal/autopkgtest-web/webcontrol/request/submit.py
24index 04d7f61..aeeb9a9 100644
25--- a/charms/focal/autopkgtest-web/webcontrol/request/submit.py
26+++ b/charms/focal/autopkgtest-web/webcontrol/request/submit.py
27@@ -18,6 +18,11 @@ from urllib.error import HTTPError
28 import amqplib.client_0_8 as amqp
29 from distro_info import UbuntuDistroInfo
30
31+
32+class DuplicateRequestException(Exception):
33+ pass
34+
35+
36 # Launchpad REST API base
37 LP = "https://api.launchpad.net/1.0/"
38 NAME = re.compile("^[a-z0-9][a-z0-9.+-]+$")
39@@ -31,6 +36,11 @@ ALLOWED_TEAMS = ["canonical-kernel-distro-team", "autopkgtest-requestors"]
40 # not teams
41 ALLOWED_USERS_PERPACKAGE = {"snapcraft": ["snappy-m-o"]}
42
43+# Path to json file detailing the queue
44+QUEUE_FP = "/var/lib/cache-amqp/queued.json"
45+# Path to json file detailing the running tests
46+RUNNING_FP = "/run/amqp-status-collector/running.json"
47+
48
49 class Submit:
50 def __init__(self):
51@@ -77,6 +87,12 @@ class Submit:
52 Raise ValueError with error messsage if the request is invalid,
53 otherwise return.
54 """
55+ not_needed, msg = self.is_request_queued_or_running(
56+ release, arch, package, triggers
57+ )
58+ if not_needed:
59+ raise DuplicateRequestException(msg)
60+
61 can_upload_any_trigger = False
62
63 try:
64@@ -213,6 +229,21 @@ class Submit:
65 Raise ValueError with error messsage if the request is invalid,
66 otherwise return.
67 """
68+ triggers = []
69+ for env_var in env:
70+ if "trigger" in env_var:
71+ if "," in env_var:
72+ for trig in env_var.split("=")[1].split(","):
73+ triggers.append(trig)
74+ else:
75+ triggers.append(env_var.split("=")[1])
76+
77+ not_needed, msg = self.is_request_queued_or_running(
78+ release, arch, package, triggers
79+ )
80+ if not_needed:
81+ raise DuplicateRequestException(msg)
82+
83 if release not in self.releases:
84 raise ValueError("Unknown release " + release)
85 if arch not in self.architectures:
86@@ -509,3 +540,87 @@ class Submit:
87 return (500, None)
88 logging.debug("lp_request %s succeeded: %s", url, response)
89 return (code, response)
90+
91+ def is_test_running(self, req_series, req_arch, req_package, req_triggers):
92+ if not os.path.isfile(RUNNING_FP):
93+ return False
94+ data = {}
95+ with open(RUNNING_FP, "r") as f:
96+ data = json.load(f)
97+ for pkg in data:
98+ for submitted in data[pkg]:
99+ releases = data[pkg][submitted].keys()
100+ for release in data[pkg][submitted]:
101+ architectures = data[pkg][submitted][release].keys()
102+ triggers = submitted[submitted.find(";triggers_") + 1 :]
103+ triggers = triggers[: triggers.find(";")]
104+ triggers = triggers[
105+ triggers.find("[") + 1 : triggers.find("]")
106+ ]
107+ triggers = triggers.replace("'", "").split(", ")
108+ if (
109+ req_arch in architectures
110+ and req_series in releases
111+ and req_package == pkg
112+ and sorted(triggers) == sorted(req_triggers)
113+ ):
114+ return True
115+ return False
116+
117+ def is_test_in_queue(
118+ self, req_series, req_arch, req_package, req_triggers
119+ ):
120+ if not os.path.isfile(QUEUE_FP):
121+ return False
122+ data = {}
123+ with open(QUEUE_FP, "r") as f:
124+ data = json.load(f)
125+ data = data["queues"]
126+ this_test = {
127+ "release": req_series,
128+ "arch": req_arch,
129+ "package": req_package,
130+ "triggers": sorted(req_triggers),
131+ }
132+ for test_type in data:
133+ for release in data[test_type]:
134+ for arch in data[test_type][release]:
135+ packages = data[test_type][release][arch]
136+ if packages["size"] != 0:
137+ for req in packages["requests"]:
138+ pkg = req[: req.find("{")].rstrip()
139+ details = json.loads(req[req.find("{") :])
140+ triggers = details["triggers"]
141+ test = {
142+ "release": release,
143+ "arch": arch,
144+ "package": pkg,
145+ "triggers": sorted(triggers),
146+ }
147+ if test == this_test:
148+ return True
149+ return False
150+
151+ def is_request_queued_or_running(
152+ self, req_series, req_arch, req_package, req_triggers=[]
153+ ):
154+ if self.is_test_running(
155+ req_series, req_arch, req_package, req_triggers
156+ ):
157+ o_msg = "Requested test is already running."
158+ o_msg += "\nRelease: " + req_series
159+ o_msg += "\nArchitecture: " + req_arch
160+ o_msg += "\nPackage: " + req_package
161+ o_msg += "\nTriggers: " + ",".join(req_triggers)
162+ return True, o_msg
163+
164+ if self.is_test_in_queue(
165+ req_series, req_arch, req_package, req_triggers
166+ ):
167+ o_msg = "Requested test is already queued."
168+ o_msg += "\nRelease: " + req_series
169+ o_msg += "\nArchitecture: " + req_arch
170+ o_msg += "\nPackage: " + req_package
171+ o_msg += "\nTriggers: " + ",".join(req_triggers)
172+ return True, o_msg
173+ return False, ""

Subscribers

People subscribed via source and target branches