blob: dfbd1a78ccac3fcd228ff0b51f6e35c82d386217 [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 Sosa7cd23202013-10-15 17:22:57 -070019GSUTIL_ATTEMPTS = 1
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
Yu-Ju Hongc8974c02014-11-18 11:40:30 -080060
61 not_exist_messages = ('matched no objects', 'non-existent object',
62 'no urls matched')
63 if (stderr and any(x in stderr.lower() for x in not_exist_messages) or
64 stdout and any(x in stdout.lower() for x in not_exist_messages)):
65 # If the object does not exist, exit now instead of wasting time
66 # on retrying. Note that `gsutil stat` prints error message to
67 # stdout instead (b/16020252), so we check both stdout and
68 # stderr.
Chris Sosa407e8c52013-02-27 15:33:35 -080069 break
Yu-Ju Hongc8974c02014-11-18 11:40:30 -080070
71 if proc.returncode == 127:
Chris Sosa6de29302013-03-14 15:27:36 -070072 raise GSUtilError('gsutil tool not found in your path.')
Chris Sosa101fd862012-06-12 17:44:53 -070073
74 time.sleep(sleep_timeout)
75 sleep_timeout *= 2
76
Chris Sosa407e8c52013-02-27 15:33:35 -080077 raise GSUtilError('%s GSUTIL cmd %s failed with return code %d:\n\n%s' % (
78 err_msg, cmd, proc.returncode, stderr))
Chris Sosa47a7d4e2012-03-28 11:26:55 -070079
80
81def DownloadFromGS(src, dst):
82 """Downloads object from gs_url |src| to |dst|.
83
Gilad Arnold950569b2013-08-27 14:38:01 -070084 Args:
85 src: source file on GS that needs to be downloaded.
86 dst: file to copy the source file to.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070087 Raises:
88 GSUtilError: if an error occurs during the download.
89 """
90 cmd = 'gsutil cp %s %s' % (src, dst)
91 msg = 'Failed to download "%s".' % src
92 GSUtilRun(cmd, msg)
Chris Sosa76e44b92013-01-31 12:11:38 -080093
94
Gilad Arnold950569b2013-08-27 14:38:01 -070095def _GlobHasWildcards(pattern):
96 """Returns True if a glob pattern contains any wildcards."""
Gilad Arnoldc703c8c2013-08-29 10:41:27 -070097 return len(pattern) > len(pattern.translate(None, '*?[]'))
Chris Sosa76e44b92013-01-31 12:11:38 -080098
99
Gilad Arnold950569b2013-08-27 14:38:01 -0700100def GetGSNamesWithWait(pattern, archive_url, err_str, timeout=600, delay=10,
101 is_regex_pattern=False):
Chris Sosa76e44b92013-01-31 12:11:38 -0800102 """Returns the google storage names specified by the given pattern.
103
104 This method polls Google Storage until the target artifacts specified by the
105 pattern is available or until the timeout occurs. Because we may not know the
106 exact name of the target artifacts, the method accepts a filename pattern,
107 to identify whether an artifact whose name matches the pattern exists (e.g.
Gilad Arnold950569b2013-08-27 14:38:01 -0700108 use pattern '*_full_*' to search for the full payload
Chris Sosa76e44b92013-01-31 12:11:38 -0800109 'chromeos_R17-1413.0.0-a1_x86-mario_full_dev.bin'). Returns the name only if
110 found before the timeout.
111
112 Args:
Gilad Arnold950569b2013-08-27 14:38:01 -0700113 pattern: a path pattern (glob or regex) identifying the files we need.
Chris Sosa76e44b92013-01-31 12:11:38 -0800114 archive_url: URL of the Google Storage bucket.
115 err_str: String to display in the error message on error.
Gilad Arnold950569b2013-08-27 14:38:01 -0700116 timeout: how long are we allowed to keep trying.
117 delay: how long to wait between attempts.
Chris Sosa6317e9a2013-09-06 16:43:44 -0700118 is_regex_pattern: Whether the pattern is a regex (otherwise a glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800119 Returns:
120 The list of artifacts matching the pattern in Google Storage bucket or None
Gilad Arnold950569b2013-08-27 14:38:01 -0700121 if not found.
Chris Sosa76e44b92013-01-31 12:11:38 -0800122
Chris Sosa76e44b92013-01-31 12:11:38 -0800123 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700124 # Define the different methods used for obtaining the list of files on the
Gilad Arnoldc703c8c2013-08-29 10:41:27 -0700125 # archive directory, in the order in which they are attempted. Each method is
126 # defined by a tuple consisting of (i) the gsutil command-line to be
127 # executed; (ii) the error message to use in case of a failure (returned in
128 # the corresponding exception); (iii) the desired return value to use in case
129 # of success, or None if the actual command output should be used.
Gilad Arnold950569b2013-08-27 14:38:01 -0700130 get_methods = []
Gilad Arnoldc703c8c2013-08-29 10:41:27 -0700131 # If the pattern is a glob and contains no wildcards, we'll first attempt to
Chris Sosa6317e9a2013-09-06 16:43:44 -0700132 # stat the file via du.
Gilad Arnoldc703c8c2013-08-29 10:41:27 -0700133 if not (is_regex_pattern or _GlobHasWildcards(pattern)):
Yu-Ju Hongc853b6e2014-11-17 15:04:55 -0800134 get_methods.append(('gsutil stat %s/%s' % (archive_url, pattern),
135 'Failed to stat on the artifact file.', pattern))
Gilad Arnoldc703c8c2013-08-29 10:41:27 -0700136
Gilad Arnold950569b2013-08-27 14:38:01 -0700137 # The default method is to check the manifest file in the archive directory.
138 get_methods.append(('gsutil cat %s/%s' % (archive_url, UPLOADED_LIST),
Gilad Arnoldc703c8c2013-08-29 10:41:27 -0700139 'Failed to get a list of uploaded files.',
140 None))
Gilad Arnold950569b2013-08-27 14:38:01 -0700141 # For backward compatibility, we fall back to using "gsutil ls" when the
142 # manifest file is not present.
143 get_methods.append(('gsutil ls %s/*' % archive_url,
Gilad Arnoldc703c8c2013-08-29 10:41:27 -0700144 'Failed to list archive directory contents.',
145 None))
Gilad Arnold950569b2013-08-27 14:38:01 -0700146
Chris Sosa76e44b92013-01-31 12:11:38 -0800147 deadline = time.time() + timeout
Chris Sosa6de29302013-03-14 15:27:36 -0700148 while True:
Chris Sosa76e44b92013-01-31 12:11:38 -0800149 uploaded_list = []
Gilad Arnoldc703c8c2013-08-29 10:41:27 -0700150 for cmd, msg, override_result in get_methods:
Gilad Arnold950569b2013-08-27 14:38:01 -0700151 try:
152 result = GSUtilRun(cmd, msg)
153 except GSUtilError:
154 continue # It didn't work, try the next method.
Chris Sosa76e44b92013-01-31 12:11:38 -0800155
Gilad Arnoldc703c8c2013-08-29 10:41:27 -0700156 if override_result:
157 result = override_result
158
Gilad Arnold950569b2013-08-27 14:38:01 -0700159 # Make sure we're dealing with artifact base names only.
160 uploaded_list = [os.path.basename(p) for p in result.splitlines()]
161 break
Chris Sosa76e44b92013-01-31 12:11:38 -0800162
Gilad Arnold950569b2013-08-27 14:38:01 -0700163 # Only keep files matching the target artifact name/pattern.
164 if is_regex_pattern:
165 filter_re = re.compile(pattern)
166 matching_names = [f for f in uploaded_list
167 if filter_re.search(f) is not None]
168 else:
169 matching_names = fnmatch.filter(uploaded_list, pattern)
170
171 if matching_names:
172 return matching_names
Chris Sosa76e44b92013-01-31 12:11:38 -0800173
Chris Sosa6de29302013-03-14 15:27:36 -0700174 # Don't delay past deadline.
175 to_delay = random.uniform(1.5 * delay, 2.5 * delay)
176 if to_delay < (deadline - time.time()):
177 _Log('Retrying in %f seconds...%s', to_delay, err_str)
178 time.sleep(to_delay)
179 else:
180 return None
joychenf8f07e22013-07-12 17:45:51 -0700181
182
joychen562699a2013-08-13 15:22:14 -0700183def GetLatestVersionFromGSDir(gsutil_dir, with_release=True):
joychenf8f07e22013-07-12 17:45:51 -0700184 """Returns most recent version number found in a GS directory.
185
186 This lists out the contents of the given GS bucket or regex to GS buckets,
187 and tries to grab the newest version found in the directory names.
Gilad Arnold950569b2013-08-27 14:38:01 -0700188
189 Args:
190 gsutil_dir: directory location on GS to check.
191 with_release: whether versions include a release milestone (e.g. R12).
192 Returns:
193 The most recent version number found.
194
joychenf8f07e22013-07-12 17:45:51 -0700195 """
196 cmd = 'gsutil ls %s' % gsutil_dir
197 msg = 'Failed to find most recent builds at %s' % gsutil_dir
198 dir_names = [p.split('/')[-2] for p in GSUtilRun(cmd, msg).splitlines()]
199 try:
Gilad Arnold950569b2013-08-27 14:38:01 -0700200 filter_re = re.compile(devserver_constants.VERSION_RE if with_release
201 else devserver_constants.VERSION)
202 versions = filter(filter_re.match, dir_names)
joychenf8f07e22013-07-12 17:45:51 -0700203 latest_version = max(versions, key=distutils.version.LooseVersion)
204 except ValueError:
205 raise GSUtilError(msg)
206
207 return latest_version