lib: retry openRemoteFile and check size

This commit is contained in:
Tomas Kopecek 2020-05-18 14:44:36 +02:00
parent 360ccd248a
commit 8ea4a7245b
2 changed files with 50 additions and 1 deletions

View file

@ -55,6 +55,8 @@ import six
import six.moves.configparser import six.moves.configparser
import six.moves.http_client import six.moves.http_client
import six.moves.urllib import six.moves.urllib
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
from six.moves import range, zip from six.moves import range, zip
from koji.xmlrpcplus import Fault, dumps, getparser, loads, xmlrpc_client from koji.xmlrpcplus import Fault, dumps, getparser, loads, xmlrpc_client
@ -1748,6 +1750,18 @@ def format_exc_plus():
return rv return rv
def request_with_retry(retries=3, backoff_factor=0.3,
status_forcelist=(500, 502, 504, 408, 429), session=None):
# stolen from https://www.peterbe.com/plog/best-practice-with-retries-with-requests
session = session or requests.Session()
retry = Retry(total=retries, read=retries, connect=retries,
backoff_factor=backoff_factor,
status_forcelist=status_forcelist)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
return session
def openRemoteFile(relpath, topurl=None, topdir=None, tempdir=None): def openRemoteFile(relpath, topurl=None, topdir=None, tempdir=None):
"""Open a file on the main server (read-only) """Open a file on the main server (read-only)
@ -1756,12 +1770,15 @@ def openRemoteFile(relpath, topurl=None, topdir=None, tempdir=None):
if topurl: if topurl:
url = "%s/%s" % (topurl, relpath) url = "%s/%s" % (topurl, relpath)
fo = tempfile.TemporaryFile(dir=tempdir) fo = tempfile.TemporaryFile(dir=tempdir)
resp = requests.get(url, stream=True) resp = request_with_retry().get(url, stream=True)
try: try:
for chunk in resp.iter_content(chunk_size=8192): for chunk in resp.iter_content(chunk_size=8192):
fo.write(chunk) fo.write(chunk)
finally: finally:
resp.close() resp.close()
if resp.headers.get('Content-Length') and fo.tell() != int(resp.headers['Content-Length']):
raise GenericError("Downloaded file %s doesn't match expected size (%s vs %s)" %
(url, fo.tell(), resp.headers['Content-Length']))
fo.seek(0) fo.seek(0)
elif topdir: elif topdir:
fn = "%s/%s" % (topdir, relpath) fn = "%s/%s" % (topdir, relpath)

View file

@ -161,6 +161,38 @@ class MiscFunctionTestCase(unittest.TestCase):
for m in mocks: for m in mocks:
m.assert_not_called() m.assert_not_called()
for m in mocks:
m.reset_mock()
# downloaded size is larger than content-length
with requests_mock.Mocker() as m_requests:
text = 'random content'
m_requests.register_uri('GET', url, text=text,
headers = {'Content-Length': "3"})
m_TemporaryFile.return_value.tell.return_value = len(text)
# using neither
with self.assertRaises(koji.GenericError):
koji.openRemoteFile(path, topurl=topurl)
m_TemporaryFile.assert_called_once()
m_TemporaryFile.return_value.tell.assert_called()
m_open.assert_not_called()
for m in mocks:
m.reset_mock()
# downloaded size is shorter than content-length
with requests_mock.Mocker() as m_requests:
text = 'random content'
m_requests.register_uri('GET', url, text=text,
headers = {'Content-Length': "100"})
m_TemporaryFile.return_value.tell.return_value = len(text)
# using neither
with self.assertRaises(koji.GenericError):
koji.openRemoteFile(path, topurl=topurl)
m_TemporaryFile.assert_called_once()
m_TemporaryFile.return_value.tell.assert_called()
m_open.assert_not_called()
def test_joinpath_bad(self): def test_joinpath_bad(self):
bad_joins = [ bad_joins = [
['/foo', '../bar'], ['/foo', '../bar'],