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

Side by Side Diff: run_isolated.py

Issue 23657003: Move url_open with dependencies to utils.net module. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/swarm_client
Patch Set: Created 7 years, 3 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 | Annotate | Revision Log
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Reads a .isolated, creates a tree of hardlinks and runs the test. 6 """Reads a .isolated, creates a tree of hardlinks and runs the test.
7 7
8 Keeps a local cache. 8 Keeps a local cache.
9 """ 9 """
10 10
11 import cookielib
12 import ctypes 11 import ctypes
13 import functools 12 import functools
14 import hashlib 13 import hashlib
15 import httplib 14 import httplib
16 import itertools
17 import json 15 import json
18 import logging 16 import logging
19 import math
20 import optparse 17 import optparse
21 import os 18 import os
22 import Queue 19 import Queue
23 import random 20 import random
24 import re 21 import re
25 import shutil 22 import shutil
26 import socket
27 import ssl
28 import stat 23 import stat
29 import subprocess 24 import subprocess
30 import sys 25 import sys
31 import tempfile 26 import tempfile
32 import threading
33 import time 27 import time
34 import urllib
35 import urllib2
36 import urlparse
37 import zlib 28 import zlib
38 29
39 from third_party.rietveld import upload
40 from third_party.depot_tools import fix_encoding 30 from third_party.depot_tools import fix_encoding
41 31
42 from utils import lru 32 from utils import lru
33 from utils import net
43 from utils import threading_utils 34 from utils import threading_utils
44 from utils import tools 35 from utils import tools
45 from utils import zip_package 36 from utils import zip_package
46 37
47 38
48 # Hack out upload logging.info()
49 upload.logging = logging.getLogger('upload')
50 # Mac pylint choke on this line.
51 upload.logging.setLevel(logging.WARNING) # pylint: disable=E1103
52
53
54 # Absolute path to this file (can be None if running from zip on Mac). 39 # Absolute path to this file (can be None if running from zip on Mac).
55 THIS_FILE_PATH = os.path.abspath(__file__) if __file__ else None 40 THIS_FILE_PATH = os.path.abspath(__file__) if __file__ else None
56 41
57 # Directory that contains this file (might be inside zip package). 42 # Directory that contains this file (might be inside zip package).
58 BASE_DIR = os.path.dirname(THIS_FILE_PATH) if __file__ else None 43 BASE_DIR = os.path.dirname(THIS_FILE_PATH) if __file__ else None
59 44
60 # Directory that contains currently running script file. 45 # Directory that contains currently running script file.
61 MAIN_DIR = os.path.dirname(os.path.abspath(zip_package.get_main_script_path())) 46 MAIN_DIR = os.path.dirname(os.path.abspath(zip_package.get_main_script_path()))
62 47
63 # Types of action accepted by link_file(). 48 # Types of action accepted by link_file().
(...skipping 17 matching lines...) Expand all
81 # The delay (in seconds) to wait between logging statements when retrieving 66 # The delay (in seconds) to wait between logging statements when retrieving
82 # the required files. This is intended to let the user (or buildbot) know that 67 # the required files. This is intended to let the user (or buildbot) know that
83 # the program is still running. 68 # the program is still running.
84 DELAY_BETWEEN_UPDATES_IN_SECS = 30 69 DELAY_BETWEEN_UPDATES_IN_SECS = 30
85 70
86 # Maximum expected delay (in seconds) between successive file fetches 71 # Maximum expected delay (in seconds) between successive file fetches
87 # in run_tha_test. If it takes longer than that, a deadlock might be happening 72 # in run_tha_test. If it takes longer than that, a deadlock might be happening
88 # and all stack frames for all threads are dumped to log. 73 # and all stack frames for all threads are dumped to log.
89 DEADLOCK_TIMEOUT = 5 * 60 74 DEADLOCK_TIMEOUT = 5 * 60
90 75
91 # The name of the key to store the count of url attempts.
92 COUNT_KEY = 'UrlOpenAttempt'
93
94 # Default maximum number of attempts to trying opening a url before aborting.
95 URL_OPEN_MAX_ATTEMPTS = 30
96 # Default timeout when retrying.
97 URL_OPEN_TIMEOUT = 6*60.
98
99 # Read timeout in seconds for downloads from isolate storage. If there's no 76 # Read timeout in seconds for downloads from isolate storage. If there's no
100 # response from the server within this timeout whole download will be aborted. 77 # response from the server within this timeout whole download will be aborted.
101 DOWNLOAD_READ_TIMEOUT = 60 78 DOWNLOAD_READ_TIMEOUT = 60
102 79
103 # Global (for now) map: server URL (http://example.com) -> HttpService instance.
104 # Used by get_http_service to cache HttpService instances.
105 _http_services = {}
106 _http_services_lock = threading.Lock()
107 80
108 # Used by get_flavor(). 81 # Used by get_flavor().
109 FLAVOR_MAPPING = { 82 FLAVOR_MAPPING = {
110 'cygwin': 'win', 83 'cygwin': 'win',
111 'win32': 'win', 84 'win32': 'win',
112 'darwin': 'mac', 85 'darwin': 'mac',
113 'sunos5': 'solaris', 86 'sunos5': 'solaris',
114 'freebsd7': 'freebsd', 87 'freebsd7': 'freebsd',
115 'freebsd8': 'freebsd', 88 'freebsd8': 'freebsd',
116 } 89 }
117 90
118 91
119 class ConfigError(ValueError): 92 class ConfigError(ValueError):
120 """Generic failure to load a .isolated file.""" 93 """Generic failure to load a .isolated file."""
121 pass 94 pass
122 95
123 96
124 class MappingError(OSError): 97 class MappingError(OSError):
125 """Failed to recreate the tree.""" 98 """Failed to recreate the tree."""
126 pass 99 pass
127 100
128 101
129 class TimeoutError(IOError):
130 """Timeout while reading HTTP response."""
131
132 def __init__(self, inner_exc=None):
133 super(TimeoutError, self).__init__(str(inner_exc or 'Timeout'))
134 self.inner_exc = inner_exc
135
136
137 def get_as_zip_package(executable=True): 102 def get_as_zip_package(executable=True):
138 """Returns ZipPackage with this module and all its dependencies. 103 """Returns ZipPackage with this module and all its dependencies.
139 104
140 If |executable| is True will store run_isolated.py as __main__.py so that 105 If |executable| is True will store run_isolated.py as __main__.py so that
141 zip package is directly executable be python. 106 zip package is directly executable be python.
142 """ 107 """
143 # Building a zip package when running from another zip package is 108 # Building a zip package when running from another zip package is
144 # unsupported and probably unneeded. 109 # unsupported and probably unneeded.
145 assert not zip_package.is_zipped_module(sys.modules[__name__]) 110 assert not zip_package.is_zipped_module(sys.modules[__name__])
146 assert THIS_FILE_PATH 111 assert THIS_FILE_PATH
(...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after
364 raise ConfigError( 329 raise ConfigError(
365 'Expected \'os\' to be \'%s\' but got \'%s\'' % 330 'Expected \'os\' to be \'%s\' but got \'%s\'' %
366 (expected_value, value)) 331 (expected_value, value))
367 332
368 else: 333 else:
369 raise ConfigError('Unknown key %s' % key) 334 raise ConfigError('Unknown key %s' % key)
370 335
371 return data 336 return data
372 337
373 338
374 def url_open(url, **kwargs):
375 """Attempts to open the given url multiple times.
376
377 |data| can be either:
378 -None for a GET request
379 -str for pre-encoded data
380 -list for data to be encoded
381 -dict for data to be encoded (COUNT_KEY will be added in this case)
382
383 Returns HttpResponse object, where the response may be read from, or None
384 if it was unable to connect.
385 """
386 urlhost, urlpath = split_server_request_url(url)
387 service = get_http_service(urlhost)
388 return service.request(urlpath, **kwargs)
389
390
391 def url_read(url, **kwargs):
392 """Attempts to open the given url multiple times and read all data from it.
393
394 Accepts same arguments as url_open function.
395
396 Returns all data read or None if it was unable to connect or read the data.
397 """
398 response = url_open(url, **kwargs)
399 if not response:
400 return None
401 try:
402 return response.read()
403 except TimeoutError:
404 return None
405
406
407 def split_server_request_url(url):
408 """Splits the url into scheme+netloc and path+params+query+fragment."""
409 url_parts = list(urlparse.urlparse(url))
410 urlhost = '%s://%s' % (url_parts[0], url_parts[1])
411 urlpath = urlparse.urlunparse(['', ''] + url_parts[2:])
412 return urlhost, urlpath
413
414
415 def get_http_service(urlhost):
416 """Returns existing or creates new instance of HttpService that can send
417 requests to given base urlhost.
418 """
419 # Ensure consistency.
420 urlhost = str(urlhost).lower().rstrip('/')
421 with _http_services_lock:
422 service = _http_services.get(urlhost)
423 if not service:
424 service = AppEngineService(urlhost)
425 _http_services[urlhost] = service
426 return service
427
428
429 class HttpService(object):
430 """Base class for a class that provides an API to HTTP based service:
431 - Provides 'request' method.
432 - Supports automatic request retries.
433 - Supports persistent cookies.
434 - Thread safe.
435 """
436
437 # File to use to store all auth cookies.
438 COOKIE_FILE = os.path.join(os.path.expanduser('~'), '.isolated_cookies')
439
440 # CookieJar reused by all services + lock that protects its instantiation.
441 _cookie_jar = None
442 _cookie_jar_lock = threading.Lock()
443
444 def __init__(self, urlhost):
445 self.urlhost = urlhost
446 self.cookie_jar = self.load_cookie_jar()
447 self.opener = self.create_url_opener()
448
449 def authenticate(self): # pylint: disable=R0201
450 """Called when HTTP server asks client to authenticate.
451 Can be implemented in subclasses.
452 """
453 return False
454
455 @staticmethod
456 def load_cookie_jar():
457 """Returns global CoookieJar object that stores cookies in the file."""
458 with HttpService._cookie_jar_lock:
459 if HttpService._cookie_jar is not None:
460 return HttpService._cookie_jar
461 jar = ThreadSafeCookieJar(HttpService.COOKIE_FILE)
462 jar.load()
463 HttpService._cookie_jar = jar
464 return jar
465
466 @staticmethod
467 def save_cookie_jar():
468 """Called when cookie jar needs to be flushed to disk."""
469 with HttpService._cookie_jar_lock:
470 if HttpService._cookie_jar is not None:
471 HttpService._cookie_jar.save()
472
473 def create_url_opener(self): # pylint: disable=R0201
474 """Returns OpenerDirector that will be used when sending requests.
475 Can be reimplemented in subclasses."""
476 return urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie_jar))
477
478 def request(self, urlpath, data=None, content_type=None, **kwargs):
479 """Attempts to open the given url multiple times.
480
481 |urlpath| is relative to the server root, i.e. '/some/request?param=1'.
482
483 |data| can be either:
484 -None for a GET request
485 -str for pre-encoded data
486 -list for data to be encoded
487 -dict for data to be encoded (COUNT_KEY will be added in this case)
488
489 Returns a file-like object, where the response may be read from, or None
490 if it was unable to connect.
491 """
492 assert urlpath and urlpath[0] == '/'
493
494 if isinstance(data, dict) and COUNT_KEY in data:
495 logging.error('%s already existed in the data passed into UlrOpen. It '
496 'would be overwritten. Aborting UrlOpen', COUNT_KEY)
497 return None
498
499 method = 'GET' if data is None else 'POST'
500 assert not ((method != 'POST') and content_type), (
501 'Can\'t use content_type on GET')
502
503 def make_request(extra):
504 """Returns a urllib2.Request instance for this specific retry."""
505 if isinstance(data, str) or data is None:
506 payload = data
507 else:
508 if isinstance(data, dict):
509 payload = data.items()
510 else:
511 payload = data[:]
512 payload.extend(extra.iteritems())
513 payload = urllib.urlencode(payload)
514 new_url = urlparse.urljoin(self.urlhost, urlpath[1:])
515 if isinstance(data, str) or data is None:
516 # In these cases, add the extra parameter to the query part of the url.
517 url_parts = list(urlparse.urlparse(new_url))
518 # Append the query parameter.
519 if url_parts[4] and extra:
520 url_parts[4] += '&'
521 url_parts[4] += urllib.urlencode(extra)
522 new_url = urlparse.urlunparse(url_parts)
523 request = urllib2.Request(new_url, data=payload)
524 if payload is not None:
525 if content_type:
526 request.add_header('Content-Type', content_type)
527 request.add_header('Content-Length', len(payload))
528 return request
529
530 return self._retry_loop(make_request, **kwargs)
531
532 def _retry_loop(
533 self,
534 make_request,
535 max_attempts=URL_OPEN_MAX_ATTEMPTS,
536 retry_404=False,
537 retry_50x=True,
538 timeout=URL_OPEN_TIMEOUT,
539 read_timeout=None):
540 """Runs internal request-retry loop.
541
542 - Optionally retries HTTP 404 and 50x.
543 - Retries up to |max_attempts| times. If None or 0, there's no limit in the
544 number of retries.
545 - Retries up to |timeout| duration in seconds. If None or 0, there's no
546 limit in the time taken to do retries.
547 - If both |max_attempts| and |timeout| are None or 0, this functions retries
548 indefinitely.
549
550 If |read_timeout| is not None will configure underlying socket to
551 raise TimeoutError exception whenever there's no response from the server
552 for more than |read_timeout| seconds. It can happen during any read
553 operation so once you pass non-None |read_timeout| be prepared to handle
554 these exceptions in subsequent reads from the stream.
555 """
556 authenticated = False
557 last_error = None
558 attempt = 0
559 start = self._now()
560 for attempt in itertools.count():
561 if max_attempts and attempt >= max_attempts:
562 # Too many attempts.
563 break
564 if timeout and (self._now() - start) >= timeout:
565 # Retried for too long.
566 break
567 extra = {COUNT_KEY: attempt} if attempt else {}
568 request = make_request(extra)
569 try:
570 url_response = self._url_open(request, timeout=read_timeout)
571 logging.debug('url_open(%s) succeeded', request.get_full_url())
572 return HttpResponse(url_response, request.get_full_url())
573 except urllib2.HTTPError as e:
574 # Unauthorized. Ask to authenticate and then try again.
575 if e.code in (401, 403):
576 # Try to authenticate only once. If it doesn't help, then server does
577 # not support app engine authentication.
578 logging.error(
579 'Authentication is required for %s on attempt %d.\n%s',
580 request.get_full_url(), attempt,
581 self._format_exception(e, verbose=True))
582 if not authenticated and self.authenticate():
583 authenticated = True
584 # Do not sleep.
585 continue
586 # If authentication failed, return.
587 logging.error(
588 'Unable to authenticate to %s.\n%s',
589 request.get_full_url(), self._format_exception(e, verbose=True))
590 return None
591
592 if ((e.code < 500 and not (retry_404 and e.code == 404)) or
593 (e.code >= 500 and not retry_50x)):
594 # This HTTPError means we reached the server and there was a problem
595 # with the request, so don't retry.
596 logging.error(
597 'Able to connect to %s but an exception was thrown.\n%s',
598 request.get_full_url(), self._format_exception(e, verbose=True))
599 return None
600
601 # The HTTPError was due to a server error, so retry the attempt.
602 logging.warning('Able to connect to %s on attempt %d.\n%s',
603 request.get_full_url(), attempt,
604 self._format_exception(e))
605 last_error = e
606
607 except (urllib2.URLError, httplib.HTTPException,
608 socket.timeout, ssl.SSLError) as e:
609 logging.warning('Unable to open url %s on attempt %d.\n%s',
610 request.get_full_url(), attempt,
611 self._format_exception(e))
612 last_error = e
613
614 # Only sleep if we are going to try again.
615 if max_attempts and attempt != max_attempts:
616 remaining = None
617 if timeout:
618 remaining = timeout - (self._now() - start)
619 if remaining <= 0:
620 break
621 self.sleep_before_retry(attempt, remaining)
622
623 logging.error('Unable to open given url, %s, after %d attempts.\n%s',
624 request.get_full_url(), max_attempts,
625 self._format_exception(last_error, verbose=True))
626 return None
627
628 def _url_open(self, request, timeout=None):
629 """Low level method to execute urllib2.Request's.
630
631 To be mocked in tests.
632 """
633 if timeout is not None:
634 return self.opener.open(request, timeout=timeout)
635 else:
636 # Leave original default value for |timeout|. It's nontrivial.
637 return self.opener.open(request)
638
639 @staticmethod
640 def _now():
641 """To be mocked in tests."""
642 return time.time()
643
644 @staticmethod
645 def calculate_sleep_before_retry(attempt, max_duration):
646 # Maximum sleeping time. We're hammering a cloud-distributed service, it'll
647 # survive.
648 MAX_SLEEP = 10.
649 # random.random() returns [0.0, 1.0). Starts with relatively short waiting
650 # time by starting with 1.5/2+1.5^-1 median offset.
651 duration = (random.random() * 1.5) + math.pow(1.5, (attempt - 1))
652 assert duration > 0.1
653 duration = min(MAX_SLEEP, duration)
654 if max_duration:
655 duration = min(max_duration, duration)
656 return duration
657
658 @classmethod
659 def sleep_before_retry(cls, attempt, max_duration):
660 """Sleeps for some amount of time when retrying the request.
661
662 To be mocked in tests.
663 """
664 time.sleep(cls.calculate_sleep_before_retry(attempt, max_duration))
665
666 @staticmethod
667 def _format_exception(exc, verbose=False):
668 """Given an instance of some exception raised by urlopen returns human
669 readable piece of text with detailed information about the error.
670 """
671 out = ['Exception: %s' % (exc,)]
672 if verbose:
673 if isinstance(exc, urllib2.HTTPError):
674 out.append('-' * 10)
675 if exc.hdrs:
676 for header, value in exc.hdrs.items():
677 if not header.startswith('x-'):
678 out.append('%s: %s' % (header.capitalize(), value))
679 out.append('')
680 out.append(exc.read() or '<empty body>')
681 out.append('-' * 10)
682 return '\n'.join(out)
683
684
685 class HttpResponse(object):
686 """Response from HttpService."""
687
688 def __init__(self, url_response, url):
689 self._url_response = url_response
690 self._url = url
691 self._read = 0
692
693 @property
694 def content_length(self):
695 """Total length to the response or None if not known in advance."""
696 length = self._url_response.headers.get('Content-Length')
697 return int(length) if length is not None else None
698
699 def read(self, size=None):
700 """Reads up to |size| bytes from the stream and returns them.
701
702 If |size| is None reads all available bytes.
703
704 Raises TimeoutError on read timeout.
705 """
706 try:
707 data = self._url_response.read(size)
708 self._read += len(data)
709 return data
710 except (socket.timeout, ssl.SSLError) as e:
711 logging.error('Timeout while reading from %s, read %d of %s: %s',
712 self._url, self._read, self.content_length, e)
713 raise TimeoutError(e)
714
715
716 class AppEngineService(HttpService):
717 """This class implements authentication support for
718 an app engine based services.
719 """
720
721 # This lock ensures that user won't be confused with multiple concurrent
722 # login prompts.
723 _auth_lock = threading.Lock()
724
725 def __init__(self, urlhost, email=None, password=None):
726 super(AppEngineService, self).__init__(urlhost)
727 self.email = email
728 self.password = password
729 self._keyring = None
730
731 def authenticate(self):
732 """Authenticates in the app engine application.
733 Returns True on success.
734 """
735 if not upload:
736 logging.error('\'upload\' module is missing, '
737 'app engine authentication is disabled.')
738 return False
739 cookie_jar = self.cookie_jar
740 save_cookie_jar = self.save_cookie_jar
741 # RPC server that uses AuthenticationSupport's cookie jar.
742 class AuthServer(upload.AbstractRpcServer):
743 def _GetOpener(self):
744 # Authentication code needs to know about 302 response.
745 # So make OpenerDirector without HTTPRedirectHandler.
746 opener = urllib2.OpenerDirector()
747 opener.add_handler(urllib2.ProxyHandler())
748 opener.add_handler(urllib2.UnknownHandler())
749 opener.add_handler(urllib2.HTTPHandler())
750 opener.add_handler(urllib2.HTTPDefaultErrorHandler())
751 opener.add_handler(urllib2.HTTPSHandler())
752 opener.add_handler(urllib2.HTTPErrorProcessor())
753 opener.add_handler(urllib2.HTTPCookieProcessor(cookie_jar))
754 return opener
755 def PerformAuthentication(self):
756 self._Authenticate()
757 save_cookie_jar()
758 return self.authenticated
759 with AppEngineService._auth_lock:
760 rpc_server = AuthServer(self.urlhost, self.get_credentials)
761 return rpc_server.PerformAuthentication()
762
763 def get_credentials(self):
764 """Called during authentication process to get the credentials.
765 May be called mutliple times if authentication fails.
766 Returns tuple (email, password).
767 """
768 # 'authenticate' calls this only if 'upload' is present.
769 # Ensure other callers (if any) fail non-cryptically if 'upload' is missing.
770 assert upload, '\'upload\' module is required for this to work'
771 if self.email and self.password:
772 return (self.email, self.password)
773 if not self._keyring:
774 self._keyring = upload.KeyringCreds(self.urlhost,
775 self.urlhost,
776 self.email)
777 return self._keyring.GetUserCredentials()
778
779
780 class ThreadSafeCookieJar(cookielib.MozillaCookieJar):
781 """MozillaCookieJar with thread safe load and save."""
782
783 def load(self, filename=None, ignore_discard=False, ignore_expires=False):
784 """Loads cookies from the file if it exists."""
785 filename = os.path.expanduser(filename or self.filename)
786 with self._cookies_lock:
787 if os.path.exists(filename):
788 try:
789 cookielib.MozillaCookieJar.load(self, filename,
790 ignore_discard,
791 ignore_expires)
792 logging.debug('Loaded cookies from %s', filename)
793 except (cookielib.LoadError, IOError):
794 pass
795 else:
796 try:
797 fd = os.open(filename, os.O_CREAT, 0600)
798 os.close(fd)
799 except OSError:
800 logging.error('Failed to create %s', filename)
801 try:
802 os.chmod(filename, 0600)
803 except OSError:
804 logging.error('Failed to fix mode for %s', filename)
805
806 def save(self, filename=None, ignore_discard=False, ignore_expires=False):
807 """Saves cookies to the file, completely overwriting it."""
808 logging.debug('Saving cookies to %s', filename or self.filename)
809 with self._cookies_lock:
810 try:
811 cookielib.MozillaCookieJar.save(self, filename,
812 ignore_discard,
813 ignore_expires)
814 except OSError:
815 logging.error('Failed to save %s', filename)
816
817
818 def valid_file(filepath, size): 339 def valid_file(filepath, size):
819 """Determines if the given files appears valid (currently it just checks 340 """Determines if the given files appears valid (currently it just checks
820 the file's size).""" 341 the file's size)."""
821 if size == UNKNOWN_FILE_SIZE: 342 if size == UNKNOWN_FILE_SIZE:
822 return True 343 return True
823 actual_size = os.stat(filepath).st_size 344 actual_size = os.stat(filepath).st_size
824 if size != actual_size: 345 if size != actual_size:
825 logging.warning( 346 logging.warning(
826 'Found invalid item %s; %d != %d', 347 'Found invalid item %s; %d != %d',
827 os.path.basename(filepath), actual_size, size) 348 os.path.basename(filepath), actual_size, size)
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
917 def _download_file(base_url, item, dest): 438 def _download_file(base_url, item, dest):
918 # TODO(maruel): Reuse HTTP connections. The stdlib doesn't make this 439 # TODO(maruel): Reuse HTTP connections. The stdlib doesn't make this
919 # easy. 440 # easy.
920 try: 441 try:
921 zipped_source = base_url + item 442 zipped_source = base_url + item
922 logging.debug('download_file(%s)', zipped_source) 443 logging.debug('download_file(%s)', zipped_source)
923 444
924 # Because the app engine DB is only eventually consistent, retry 445 # Because the app engine DB is only eventually consistent, retry
925 # 404 errors because the file might just not be visible yet (even 446 # 404 errors because the file might just not be visible yet (even
926 # though it has been uploaded). 447 # though it has been uploaded).
927 connection = url_open(zipped_source, retry_404=True, 448 connection = net.url_open(zipped_source, retry_404=True,
M-A Ruel 2013/08/28 15:01:48 I'd align: connection = net.url_open( zipped_s
928 read_timeout=DOWNLOAD_READ_TIMEOUT) 449 read_timeout=DOWNLOAD_READ_TIMEOUT)
929 if not connection: 450 if not connection:
930 raise IOError('Unable to open connection to %s' % zipped_source) 451 raise IOError('Unable to open connection to %s' % zipped_source)
931 452
932 content_length = connection.content_length 453 content_length = connection.content_length
933 decompressor = zlib.decompressobj() 454 decompressor = zlib.decompressobj()
934 size = 0 455 size = 0
935 with open(dest, 'wb') as f: 456 with open(dest, 'wb') as f:
936 while True: 457 while True:
937 chunk = connection.read(ZIPPED_FILE_CHUNK) 458 chunk = connection.read(ZIPPED_FILE_CHUNK)
938 if not chunk: 459 if not chunk:
(...skipping 732 matching lines...) Expand 10 before | Expand all | Expand 10 after
1671 except Exception, e: 1192 except Exception, e:
1672 # Make sure any exception is logged. 1193 # Make sure any exception is logged.
1673 logging.exception(e) 1194 logging.exception(e)
1674 return 1 1195 return 1
1675 1196
1676 1197
1677 if __name__ == '__main__': 1198 if __name__ == '__main__':
1678 # Ensure that we are always running with the correct encoding. 1199 # Ensure that we are always running with the correct encoding.
1679 fix_encoding.fix_encoding() 1200 fix_encoding.fix_encoding()
1680 sys.exit(main()) 1201 sys.exit(main())
OLDNEW
« no previous file with comments | « isolateserver_archive.py ('k') | swarming.py » ('j') | swarming.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698