blob: 25b7da887ed34aaa0525d1c8cbdfd1964fd06e23 [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
Chris Sosa76e44b92013-01-31 12:11:38 -08008import random
9import re
Chris Sosa47a7d4e2012-03-28 11:26:55 -070010import subprocess
Chris Sosa101fd862012-06-12 17:44:53 -070011import time
Chris Sosa47a7d4e2012-03-28 11:26:55 -070012
joychenf8f07e22013-07-12 17:45:51 -070013import devserver_constants
Chris Sosa76e44b92013-01-31 12:11:38 -080014import log_util
15
Gilad Arnoldabb352e2012-09-23 01:24:27 -070016
Chris Sosa47a7d4e2012-03-28 11:26:55 -070017GSUTIL_ATTEMPTS = 5
Chris Sosa76e44b92013-01-31 12:11:38 -080018UPLOADED_LIST = 'UPLOADED'
19
20
21# Module-local log function.
22def _Log(message, *args):
23 return log_util.LogWithTag('GSUTIL_UTIL', message, *args)
Chris Sosa47a7d4e2012-03-28 11:26:55 -070024
25
26class GSUtilError(Exception):
Chris Sosa76e44b92013-01-31 12:11:38 -080027 """Exception raised when we run into an error running gsutil."""
28 pass
29
30
31class PatternNotSpecific(Exception):
32 """Raised when unexpectedly more than one item is returned for a pattern."""
Chris Sosa47a7d4e2012-03-28 11:26:55 -070033 pass
34
35
36def GSUtilRun(cmd, err_msg):
37 """Runs a GSUTIL command up to GSUTIL_ATTEMPTS number of times.
38
Chris Sosa101fd862012-06-12 17:44:53 -070039 Attempts are tried with exponential backoff.
40
Chris Sosa47a7d4e2012-03-28 11:26:55 -070041 Returns:
42 stdout of the called gsutil command.
43 Raises:
44 subprocess.CalledProcessError if all attempt to run gsutil cmd fails.
45 """
46 proc = None
Chris Sosa101fd862012-06-12 17:44:53 -070047 sleep_timeout = 1
Chris Sosa407e8c52013-02-27 15:33:35 -080048 stderr = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -070049 for _attempt in range(GSUTIL_ATTEMPTS):
Chris Sosa407e8c52013-02-27 15:33:35 -080050 proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
51 stderr=subprocess.PIPE)
52 stdout, stderr = proc.communicate()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070053 if proc.returncode == 0:
54 return stdout
Chris Sosa6de29302013-03-14 15:27:36 -070055 elif stderr and ('matched no objects' in stderr or
56 'non-existent object' in stderr):
Chris Sosa407e8c52013-02-27 15:33:35 -080057 # TODO(sosa): Note this is a heuristic that makes us not re-attempt
58 # unnecessarily. However, if it fails, the worst that can happen is just
59 # waiting longer than necessary.
60 break
Chris Sosa6de29302013-03-14 15:27:36 -070061 elif proc.returncode == 127:
62 raise GSUtilError('gsutil tool not found in your path.')
Chris Sosa101fd862012-06-12 17:44:53 -070063
64 time.sleep(sleep_timeout)
65 sleep_timeout *= 2
66
Chris Sosa407e8c52013-02-27 15:33:35 -080067 raise GSUtilError('%s GSUTIL cmd %s failed with return code %d:\n\n%s' % (
68 err_msg, cmd, proc.returncode, stderr))
Chris Sosa47a7d4e2012-03-28 11:26:55 -070069
70
71def DownloadFromGS(src, dst):
72 """Downloads object from gs_url |src| to |dst|.
73
74 Raises:
75 GSUtilError: if an error occurs during the download.
76 """
77 cmd = 'gsutil cp %s %s' % (src, dst)
78 msg = 'Failed to download "%s".' % src
79 GSUtilRun(cmd, msg)
Chris Sosa76e44b92013-01-31 12:11:38 -080080
81
82def _GetGSNamesFromList(filename_list, pattern):
83 """Given a list of filenames, returns the filenames that match pattern."""
84 matches = []
85 re_pattern = re.compile(pattern)
86 for filename in filename_list:
87 if re_pattern.match(filename):
88 matches.append(filename)
89
90 return matches
91
92
93def GetGSNamesWithWait(pattern, archive_url, err_str, single_item=True,
94 timeout=600, delay=10):
95 """Returns the google storage names specified by the given pattern.
96
97 This method polls Google Storage until the target artifacts specified by the
98 pattern is available or until the timeout occurs. Because we may not know the
99 exact name of the target artifacts, the method accepts a filename pattern,
100 to identify whether an artifact whose name matches the pattern exists (e.g.
101 use pattern '_full_' to search for the full payload
102 'chromeos_R17-1413.0.0-a1_x86-mario_full_dev.bin'). Returns the name only if
103 found before the timeout.
104
105 Args:
106 pattern: Regular expression pattern to identify the target artifact.
107 archive_url: URL of the Google Storage bucket.
108 err_str: String to display in the error message on error.
109 single_item: Only a single item should be returned.
110 timeout/delay: optional and self-explanatory.
111
112 Returns:
113 The list of artifacts matching the pattern in Google Storage bucket or None
114 if not found.
115
116 Raises:
117 PatternNotSpecific: If caller sets single_item but multiple items match.
118 """
119 deadline = time.time() + timeout
Chris Sosa6de29302013-03-14 15:27:36 -0700120 while True:
Chris Sosa76e44b92013-01-31 12:11:38 -0800121 uploaded_list = []
Chris Sosa76e44b92013-01-31 12:11:38 -0800122 try:
123 cmd = 'gsutil cat %s/%s' % (archive_url, UPLOADED_LIST)
124 msg = 'Failed to get a list of uploaded files.'
125 uploaded_list = GSUtilRun(cmd, msg).splitlines()
126 except GSUtilError:
127 # For backward compatibility, falling back to use "gsutil ls"
128 # when the manifest file is not present.
129 cmd = 'gsutil ls %s/*' % archive_url
130 msg = 'Failed to list payloads.'
131 returned_list = GSUtilRun(cmd, msg).splitlines()
132 for item in returned_list:
133 uploaded_list.append(item.rsplit('/', 1)[1])
134
135 # Check if all target artifacts are available.
136 found_names = _GetGSNamesFromList(uploaded_list, pattern)
137 if found_names:
138 if single_item and len(found_names) > 1:
139 raise PatternNotSpecific(
140 'Too many items %s returned by pattern %s in %s' % (
141 str(found_names), pattern, archive_url))
142
143 return found_names
144
Chris Sosa6de29302013-03-14 15:27:36 -0700145 # Don't delay past deadline.
146 to_delay = random.uniform(1.5 * delay, 2.5 * delay)
147 if to_delay < (deadline - time.time()):
148 _Log('Retrying in %f seconds...%s', to_delay, err_str)
149 time.sleep(to_delay)
150 else:
151 return None
joychenf8f07e22013-07-12 17:45:51 -0700152
153
154def GetLatestVersionFromGSDir(gsutil_dir):
155 """Returns most recent version number found in a GS directory.
156
157 This lists out the contents of the given GS bucket or regex to GS buckets,
158 and tries to grab the newest version found in the directory names.
159 """
160 cmd = 'gsutil ls %s' % gsutil_dir
161 msg = 'Failed to find most recent builds at %s' % gsutil_dir
162 dir_names = [p.split('/')[-2] for p in GSUtilRun(cmd, msg).splitlines()]
163 try:
164 versions = filter(lambda x: re.match(devserver_constants.VERSION_RE, x),
165 dir_names)
166 latest_version = max(versions, key=distutils.version.LooseVersion)
167 except ValueError:
168 raise GSUtilError(msg)
169
170 return latest_version