Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(852)

Side by Side Diff: recipe_engine/fetch.py

Issue 2365693002: Add logging to fetch, retry if gitiles gives us weird unicode (Closed)
Patch Set: Add logging for gitiles. Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2016 The LUCI Authors. All rights reserved. 1 # Copyright 2016 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file. 3 # that can be found in the LICENSE file.
4 4
5 import base64 5 import base64
6 import functools 6 import functools
7 import json 7 import json
8 import logging 8 import logging
9 import os 9 import os
10 import random 10 import random
11 import shutil 11 import shutil
12 import sys 12 import sys
13 import tarfile 13 import tarfile
14 import tempfile 14 import tempfile
15 import time 15 import time
16 16
17 # Add third party paths. 17 # Add third party paths.
18 from . import env 18 from . import env
19 from . import requests_ssl 19 from . import requests_ssl
20 from .requests_ssl import requests 20 from .requests_ssl import requests
21 21
22 import subprocess42 22 import subprocess42
23 from google.protobuf import text_format 23 from google.protobuf import text_format
24 24
25 from . import package_pb2 25 from . import package_pb2
26 26
27 class RetryFetch(Exception):
28 """Raised to indicate that the fetch operation should be retried"""
29 def __init__(self, e):
30 super(RetryFetch, self).__init__(
31 'Retrying fetch because of exception: %r' % e)
32 self.exc = e
27 33
28 class FetchError(Exception): 34 class FetchError(Exception):
29 pass 35 pass
30 36
31 37
32 class UncleanFilesystemError(FetchError): 38 class UncleanFilesystemError(FetchError):
33 pass 39 pass
34 40
35 41
36 class FetchNotAllowedError(FetchError): 42 class FetchNotAllowedError(FetchError):
(...skipping 15 matching lines...) Expand all
52 58
53 59
54 def _retry(f): 60 def _retry(f):
55 @functools.wraps(f) 61 @functools.wraps(f)
56 def wrapper(*args, **kwargs): 62 def wrapper(*args, **kwargs):
57 delay = random.uniform(2, 5) 63 delay = random.uniform(2, 5)
58 for _ in range(5): 64 for _ in range(5):
59 try: 65 try:
60 return f(*args, **kwargs) 66 return f(*args, **kwargs)
61 except (requests.exceptions.RequestException, 67 except (requests.exceptions.RequestException,
62 subprocess42.CalledProcessError): 68 subprocess42.CalledProcessError,
69 RetryFetch) as e:
63 # Only retry specific errors that may be transient. 70 # Only retry specific errors that may be transient.
64 logging.exception('retrying') 71 logging.warning('retrying because of %s', e)
65 time.sleep(delay) 72 time.sleep(delay)
66 delay *= 2 73 delay *= 2
67 return f(*args, **kwargs) 74 return f(*args, **kwargs)
68 return wrapper 75 return wrapper
69 76
70 77
71 class Backend(object): 78 class Backend(object):
72 @property 79 @property
73 def repo_type(self): 80 def repo_type(self):
74 """Returns repo type (see package_pb2.DepSpec).""" 81 """Returns repo type (see package_pb2.DepSpec)."""
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after
183 'need to download %s from gitiles but fetch not allowed' % repo) 190 'need to download %s from gitiles but fetch not allowed' % repo)
184 191
185 revision = self._resolve_revision(repo, revision) 192 revision = self._resolve_revision(repo, revision)
186 193
187 shutil.rmtree(checkout_dir, ignore_errors=True) 194 shutil.rmtree(checkout_dir, ignore_errors=True)
188 195
189 recipes_cfg_url = '%s/+/%s/infra/config/recipes.cfg?format=TEXT' % ( 196 recipes_cfg_url = '%s/+/%s/infra/config/recipes.cfg?format=TEXT' % (
190 repo, requests.utils.quote(revision)) 197 repo, requests.utils.quote(revision))
191 logging.info('fetching %s' % recipes_cfg_url) 198 logging.info('fetching %s' % recipes_cfg_url)
192 recipes_cfg_request = requests.get(recipes_cfg_url) 199 recipes_cfg_request = requests.get(recipes_cfg_url)
193 recipes_cfg_text = base64.b64decode(recipes_cfg_request.text) 200 try:
201 recipes_cfg_text = base64.b64decode(recipes_cfg_request.text)
202 except UnicodeEncodeError as e:
203 # UnicodeEncodeError can happen sometimes, if gitiles gives us some weird
204 # response. Just retry the whole thing.
205 logging.exception(
206 "got strange response from gitiles: %s", recipes_cfg_request.text)
207 raise RetryFetch(e)
208
194 recipes_cfg_proto = package_pb2.Package() 209 recipes_cfg_proto = package_pb2.Package()
195 text_format.Merge(recipes_cfg_text, recipes_cfg_proto) 210 text_format.Merge(recipes_cfg_text, recipes_cfg_proto)
196 recipes_path_rel = recipes_cfg_proto.recipes_path 211 recipes_path_rel = recipes_cfg_proto.recipes_path
197 212
198 # Re-create recipes.cfg in |checkout_dir| so that the repo's recipes.py 213 # Re-create recipes.cfg in |checkout_dir| so that the repo's recipes.py
199 # can look it up. 214 # can look it up.
200 recipes_cfg_path = os.path.join( 215 recipes_cfg_path = os.path.join(
201 checkout_dir, 'infra', 'config', 'recipes.cfg') 216 checkout_dir, 'infra', 'config', 'recipes.cfg')
202 os.makedirs(os.path.dirname(recipes_cfg_path)) 217 os.makedirs(os.path.dirname(recipes_cfg_path))
203 with open(recipes_cfg_path, 'w') as f: 218 with open(recipes_cfg_path, 'w') as f:
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
270 """Returns a git sha corresponding to given revision. 285 """Returns a git sha corresponding to given revision.
271 286
272 Examples of non-sha revision: origin/master, HEAD.""" 287 Examples of non-sha revision: origin/master, HEAD."""
273 rev_json = self._revision_metadata(repo, revision) 288 rev_json = self._revision_metadata(repo, revision)
274 logging.info('resolved %s to %s', revision, rev_json['commit']) 289 logging.info('resolved %s to %s', revision, rev_json['commit'])
275 return rev_json['commit'] 290 return rev_json['commit']
276 291
277 def _fetch_gitiles_json(self, url): 292 def _fetch_gitiles_json(self, url):
278 """Fetches JSON from Gitiles and returns parsed result.""" 293 """Fetches JSON from Gitiles and returns parsed result."""
279 logging.info('fetching %s', url) 294 logging.info('fetching %s', url)
280 raw = requests.get(url).text 295 resp = requests.get(url)
296 raw = resp.text
281 if not raw.startswith(')]}\'\n'): 297 if not raw.startswith(')]}\'\n'):
282 raise FetchError('Unexpected gitiles response: %s' % raw) 298 logging.warning("Unexpected gitiles response: %s", raw)
299 raise FetchError(
martiniss 2016/09/23 00:42:58 A (maybe unfounded) hypothesis is that the excepti
300 'Unexpected gitiles response (code %d): %s... (truncated, see log for'
301 'full details)' % (resp.status_code, raw[:100]))
302
283 return json.loads(raw.split('\n', 1)[1]) 303 return json.loads(raw.split('\n', 1)[1])
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698