Skip to content

Commit 28adad0

Browse files
npigginhuth
authored andcommitted
tests/functional/asset: Add AssetError exception class
Assets are uniquely identified by human-readable-ish url, so make an AssetError exception class that prints url with error message. A property 'transient' is used to capture whether the client may retry or try again later, or if it is a serious and likely permanent error. This is used to retain the existing behaviour of treating HTTP errors other than 404 as 'transient' and not causing precache step to fail. Additionally, partial-downloads and stale asset caches that fail to resolve after the retry limit are now treated as transient and do not cause precache step to fail. For background: The NetBSD archive is, at the time of writing, failing with short transfer. Retrying the fetch at that position (as wget does) results in a "503 backend unavailable" error. We would like to get that error code directly, but I have not found a way to do that with urllib, so treating the short-copy as a transient failure covers that case (and seems like a reasonable way to handle it in general). Reviewed-by: Thomas Huth <[email protected]> Reviewed-by: Daniel P. Berrangé <[email protected]> Signed-off-by: Nicholas Piggin <[email protected]> Message-ID: <[email protected]> Signed-off-by: Thomas Huth <[email protected]>
1 parent 7524e1b commit 28adad0

File tree

1 file changed

+28
-15
lines changed

1 file changed

+28
-15
lines changed

tests/functional/qemu_test/asset.py

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717
from shutil import copyfileobj
1818
from urllib.error import HTTPError
1919

20+
class AssetError(Exception):
21+
def __init__(self, asset, msg, transient=False):
22+
self.url = asset.url
23+
self.msg = msg
24+
self.transient = transient
25+
26+
def __str__(self):
27+
return "%s: %s" % (self.url, self.msg)
2028

2129
# Instances of this class must be declared as class level variables
2230
# starting with a name "ASSET_". This enables the pre-caching logic
@@ -51,7 +59,7 @@ def _check(self, cache_file):
5159
elif len(self.hash) == 128:
5260
hl = hashlib.sha512()
5361
else:
54-
raise Exception("unknown hash type")
62+
raise AssetError(self, "unknown hash type")
5563

5664
# Calculate the hash of the file:
5765
with open(cache_file, 'rb') as file:
@@ -111,7 +119,8 @@ def fetch(self):
111119
return str(self.cache_file)
112120

113121
if not self.fetchable():
114-
raise Exception("Asset cache is invalid and downloads disabled")
122+
raise AssetError(self,
123+
"Asset cache is invalid and downloads disabled")
115124

116125
self.log.info("Downloading %s to %s...", self.url, self.cache_file)
117126
tmp_cache_file = self.cache_file.with_suffix(".download")
@@ -147,13 +156,23 @@ def fetch(self):
147156
tmp_cache_file)
148157
tmp_cache_file.unlink()
149158
continue
159+
except HTTPError as e:
160+
tmp_cache_file.unlink()
161+
self.log.error("Unable to download %s: HTTP error %d",
162+
self.url, e.code)
163+
# Treat 404 as fatal, since it is highly likely to
164+
# indicate a broken test rather than a transient
165+
# server or networking problem
166+
if e.code == 404:
167+
raise AssetError(self, "Unable to download: "
168+
"HTTP error %d" % e.code)
169+
continue
150170
except Exception as e:
151-
self.log.error("Unable to download %s: %s", self.url, e)
152171
tmp_cache_file.unlink()
153-
raise
172+
raise AssetError(self, "Unable to download: " % e)
154173

155174
if not os.path.exists(tmp_cache_file):
156-
raise Exception("Retries exceeded downloading %s", self.url)
175+
raise AssetError(self, "Download retries exceeded", transient=True)
157176

158177
try:
159178
# Set these just for informational purposes
@@ -167,8 +186,7 @@ def fetch(self):
167186

168187
if not self._check(tmp_cache_file):
169188
tmp_cache_file.unlink()
170-
raise Exception("Hash of %s does not match %s" %
171-
(self.url, self.hash))
189+
raise AssetError(self, "Hash does not match %s" % self.hash)
172190
tmp_cache_file.replace(self.cache_file)
173191
# Remove write perms to stop tests accidentally modifying them
174192
os.chmod(self.cache_file, stat.S_IRUSR | stat.S_IRGRP)
@@ -190,15 +208,10 @@ def precache_test(test):
190208
log.info("Attempting to cache '%s'" % asset)
191209
try:
192210
asset.fetch()
193-
except HTTPError as e:
194-
# Treat 404 as fatal, since it is highly likely to
195-
# indicate a broken test rather than a transient
196-
# server or networking problem
197-
if e.code == 404:
211+
except AssetError as e:
212+
if not e.transient:
198213
raise
199-
200-
log.debug(f"HTTP error {e.code} from {asset.url} " +
201-
"skipping asset precache")
214+
log.error("%s: skipping asset precache" % e)
202215

203216
log.removeHandler(handler)
204217

0 commit comments

Comments
 (0)