blob: 3cc884a949541534c7bf709ed2274827590a2ef8 [file] [log] [blame]
Chris Sosa47a7d4e2012-03-28 11:26:55 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Module containing gsutil helper methods."""
6
joychenf8f07e22013-07-12 17:45:51 -07007import distutils.version
Gilad Arnold950569b2013-08-27 14:38:01 -07008import fnmatch
9import os
Chris Sosa76e44b92013-01-31 12:11:38 -080010import random
11import re
Chris Sosa47a7d4e2012-03-28 11:26:55 -070012import subprocess
Chris Sosa101fd862012-06-12 17:44:53 -070013import time
Chris Sosa47a7d4e2012-03-28 11:26:55 -070014
joychenf8f07e22013-07-12 17:45:51 -070015import devserver_constants
Chris Sosa76e44b92013-01-31 12:11:38 -080016import log_util
17
Gilad Arnoldabb352e2012-09-23 01:24:27 -070018
Chris Sosa47a7d4e2012-03-28 11:26:55 -070019GSUTIL_ATTEMPTS = 5
Chris Sosa76e44b92013-01-31 12:11:38 -080020UPLOADED_LIST = 'UPLOADED'
21
22
23# Module-local log function.
24def _Log(message, *args):
25 return log_util.LogWithTag('GSUTIL_UTIL', message, *args)
Chris Sosa47a7d4e2012-03-28 11:26:55 -070026
27
28class GSUtilError(Exception):
Chris Sosa76e44b92013-01-31 12:11:38 -080029 """Exception raised when we run into an error running gsutil."""
30 pass
31
32
33class PatternNotSpecific(Exception):
34 """Raised when unexpectedly more than one item is returned for a pattern."""
Chris Sosa47a7d4e2012-03-28 11:26:55 -070035 pass
36
37
38def GSUtilRun(cmd, err_msg):
39 """Runs a GSUTIL command up to GSUTIL_ATTEMPTS number of times.
40
Chris Sosa101fd862012-06-12 17:44:53 -070041 Attempts are tried with exponential backoff.
42
Gilad Arnold950569b2013-08-27 14:38:01 -070043 Args:
44 cmd: a string containing the gsutil command to run.
45 err_msg: string prepended to the exception thrown in case of a failure.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070046 Returns:
47 stdout of the called gsutil command.
48 Raises:
Gilad Arnold950569b2013-08-27 14:38:01 -070049 GSUtilError: if all attempts to run gsutil have failed.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070050 """
51 proc = None
Chris Sosa101fd862012-06-12 17:44:53 -070052 sleep_timeout = 1
Chris Sosa407e8c52013-02-27 15:33:35 -080053 stderr = None
Gilad Arnold950569b2013-08-27 14:38:01 -070054 for _ in range(GSUTIL_ATTEMPTS):
Chris Sosa407e8c52013-02-27 15:33:35 -080055 proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
56 stderr=subprocess.PIPE)
57 stdout, stderr = proc.communicate()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070058 if proc.returncode == 0:
59 return stdout
Chris Sosa6de29302013-03-14 15:27:36 -070060 elif stderr and ('matched no objects' in stderr or
61 'non-existent object' in stderr):
Chris Sosa407e8c52013-02-27 15:33:35 -080062 # TODO(sosa): Note this is a heuristic that makes us not re-attempt
63 # unnecessarily. However, if it fails, the worst that can happen is just
64 # waiting longer than necessary.
65 break
Chris Sosa6de29302013-03-14 15:27:36 -070066 elif proc.returncode == 127:
67 raise GSUtilError('gsutil tool not found in your path.')
Chris Sosa101fd862012-06-12 17:44:53 -070068
69 time.sleep(sleep_timeout)
70 sleep_timeout *= 2
71
Chris Sosa407e8c52013-02-27 15:33:35 -080072 raise GSUtilError('%s GSUTIL cmd %s failed with return code %d:\n\n%s' % (
73 err_msg, cmd, proc.returncode, stderr))
Chris Sosa47a7d4e2012-03-28 11:26:55 -070074
75
76def DownloadFromGS(src, dst):
77 """Downloads object from gs_url |src| to |dst|.
78
Gilad Arnold950569b2013-08-27 14:38:01 -070079 Args:
80 src: source file on GS that needs to be downloaded.
81 dst: file to copy the source file to.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070082 Raises:
83 GSUtilError: if an error occurs during the download.
84 """
85 cmd = 'gsutil cp %s %s' % (src, dst)
86 msg = 'Failed to download "%s".' % src
87 GSUtilRun(cmd, msg)
Chris Sosa76e44b92013-01-31 12:11:38 -080088
89
Gilad Arnold950569b2013-08-27 14:38:01 -070090def _GlobHasWildcards(pattern):
91 """Returns True if a glob pattern contains any wildcards."""
92 return len(pattern) > len(pattern.translate(None, '*.[]'))
Chris Sosa76e44b92013-01-31 12:11:38 -080093
94
Gilad Arnold950569b2013-08-27 14:38:01 -070095def GetGSNamesWithWait(pattern, archive_url, err_str, timeout=600, delay=10,
96 is_regex_pattern=False):
Chris Sosa76e44b92013-01-31 12:11:38 -080097 """Returns the google storage names specified by the given pattern.
98
99 This method polls Google Storage until the target artifacts specified by the
100 pattern is available or until the timeout occurs. Because we may not know the
101 exact name of the target artifacts, the method accepts a filename pattern,
102 to identify whether an artifact whose name matches the pattern exists (e.g.
Gilad Arnold950569b2013-08-27 14:38:01 -0700103 use pattern '*_full_*' to search for the full payload
Chris Sosa76e44b92013-01-31 12:11:38 -0800104 'chromeos_R17-1413.0.0-a1_x86-mario_full_dev.bin'). Returns the name only if
105 found before the timeout.
106
107 Args:
Gilad Arnold950569b2013-08-27 14:38:01 -0700108 pattern: a path pattern (glob or regex) identifying the files we need.
Chris Sosa76e44b92013-01-31 12:11:38 -0800109 archive_url: URL of the Google Storage bucket.
110 err_str: String to display in the error message on error.
Gilad Arnold950569b2013-08-27 14:38:01 -0700111 timeout: how long are we allowed to keep trying.
112 delay: how long to wait between attempts.
113 is_regex_pattern: Whether the pattern is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800114 Returns:
115 The list of artifacts matching the pattern in Google Storage bucket or None
Gilad Arnold950569b2013-08-27 14:38:01 -0700116 if not found.
Chris Sosa76e44b92013-01-31 12:11:38 -0800117
Chris Sosa76e44b92013-01-31 12:11:38 -0800118 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700119 # Define the different methods used for obtaining the list of files on the
120 # archive directory, in the order in which they are attempted.
121 get_methods = []
122 # The default method is to check the manifest file in the archive directory.
123 get_methods.append(('gsutil cat %s/%s' % (archive_url, UPLOADED_LIST),
124 'Failed to get a list of uploaded files.'))
125 # For backward compatibility, we fall back to using "gsutil ls" when the
126 # manifest file is not present.
127 get_methods.append(('gsutil ls %s/*' % archive_url,
128 'Failed to list archive directory contents.'))
129
Chris Sosa76e44b92013-01-31 12:11:38 -0800130 deadline = time.time() + timeout
Chris Sosa6de29302013-03-14 15:27:36 -0700131 while True:
Chris Sosa76e44b92013-01-31 12:11:38 -0800132 uploaded_list = []
Gilad Arnold950569b2013-08-27 14:38:01 -0700133 for cmd, msg in get_methods:
134 try:
135 result = GSUtilRun(cmd, msg)
136 except GSUtilError:
137 continue # It didn't work, try the next method.
Chris Sosa76e44b92013-01-31 12:11:38 -0800138
Gilad Arnold950569b2013-08-27 14:38:01 -0700139 # Make sure we're dealing with artifact base names only.
140 uploaded_list = [os.path.basename(p) for p in result.splitlines()]
141 break
Chris Sosa76e44b92013-01-31 12:11:38 -0800142
Gilad Arnold950569b2013-08-27 14:38:01 -0700143 # Only keep files matching the target artifact name/pattern.
144 if is_regex_pattern:
145 filter_re = re.compile(pattern)
146 matching_names = [f for f in uploaded_list
147 if filter_re.search(f) is not None]
148 else:
149 matching_names = fnmatch.filter(uploaded_list, pattern)
150
151 if matching_names:
152 return matching_names
Chris Sosa76e44b92013-01-31 12:11:38 -0800153
Chris Sosa6de29302013-03-14 15:27:36 -0700154 # Don't delay past deadline.
155 to_delay = random.uniform(1.5 * delay, 2.5 * delay)
156 if to_delay < (deadline - time.time()):
157 _Log('Retrying in %f seconds...%s', to_delay, err_str)
158 time.sleep(to_delay)
159 else:
160 return None
joychenf8f07e22013-07-12 17:45:51 -0700161
162
joychen562699a2013-08-13 15:22:14 -0700163def GetLatestVersionFromGSDir(gsutil_dir, with_release=True):
joychenf8f07e22013-07-12 17:45:51 -0700164 """Returns most recent version number found in a GS directory.
165
166 This lists out the contents of the given GS bucket or regex to GS buckets,
167 and tries to grab the newest version found in the directory names.
Gilad Arnold950569b2013-08-27 14:38:01 -0700168
169 Args:
170 gsutil_dir: directory location on GS to check.
171 with_release: whether versions include a release milestone (e.g. R12).
172 Returns:
173 The most recent version number found.
174
joychenf8f07e22013-07-12 17:45:51 -0700175 """
176 cmd = 'gsutil ls %s' % gsutil_dir
177 msg = 'Failed to find most recent builds at %s' % gsutil_dir
178 dir_names = [p.split('/')[-2] for p in GSUtilRun(cmd, msg).splitlines()]
179 try:
Gilad Arnold950569b2013-08-27 14:38:01 -0700180 filter_re = re.compile(devserver_constants.VERSION_RE if with_release
181 else devserver_constants.VERSION)
182 versions = filter(filter_re.match, dir_names)
joychenf8f07e22013-07-12 17:45:51 -0700183 latest_version = max(versions, key=distutils.version.LooseVersion)
184 except ValueError:
185 raise GSUtilError(msg)
186
187 return latest_version