blob: 32ab58e049016a1d93e3249530b9e83ee6567157 [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
Chris Sosa76e44b92013-01-31 12:11:38 -08007import random
8import re
Chris Sosa47a7d4e2012-03-28 11:26:55 -07009import subprocess
Chris Sosa101fd862012-06-12 17:44:53 -070010import time
Chris Sosa47a7d4e2012-03-28 11:26:55 -070011
Chris Sosa76e44b92013-01-31 12:11:38 -080012import log_util
13
Gilad Arnoldabb352e2012-09-23 01:24:27 -070014
Chris Sosa47a7d4e2012-03-28 11:26:55 -070015GSUTIL_ATTEMPTS = 5
Chris Sosa76e44b92013-01-31 12:11:38 -080016UPLOADED_LIST = 'UPLOADED'
17
18
19# Module-local log function.
20def _Log(message, *args):
21 return log_util.LogWithTag('GSUTIL_UTIL', message, *args)
Chris Sosa47a7d4e2012-03-28 11:26:55 -070022
23
24class GSUtilError(Exception):
Chris Sosa76e44b92013-01-31 12:11:38 -080025 """Exception raised when we run into an error running gsutil."""
26 pass
27
28
29class PatternNotSpecific(Exception):
30 """Raised when unexpectedly more than one item is returned for a pattern."""
Chris Sosa47a7d4e2012-03-28 11:26:55 -070031 pass
32
33
34def GSUtilRun(cmd, err_msg):
35 """Runs a GSUTIL command up to GSUTIL_ATTEMPTS number of times.
36
Chris Sosa101fd862012-06-12 17:44:53 -070037 Attempts are tried with exponential backoff.
38
Chris Sosa47a7d4e2012-03-28 11:26:55 -070039 Returns:
40 stdout of the called gsutil command.
41 Raises:
42 subprocess.CalledProcessError if all attempt to run gsutil cmd fails.
43 """
44 proc = None
Chris Sosa101fd862012-06-12 17:44:53 -070045 sleep_timeout = 1
Chris Sosa407e8c52013-02-27 15:33:35 -080046 stderr = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -070047 for _attempt in range(GSUTIL_ATTEMPTS):
Chris Sosa407e8c52013-02-27 15:33:35 -080048 proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
49 stderr=subprocess.PIPE)
50 stdout, stderr = proc.communicate()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070051 if proc.returncode == 0:
52 return stdout
Chris Sosa6de29302013-03-14 15:27:36 -070053 elif stderr and ('matched no objects' in stderr or
54 'non-existent object' in stderr):
Chris Sosa407e8c52013-02-27 15:33:35 -080055 # TODO(sosa): Note this is a heuristic that makes us not re-attempt
56 # unnecessarily. However, if it fails, the worst that can happen is just
57 # waiting longer than necessary.
58 break
Chris Sosa6de29302013-03-14 15:27:36 -070059 elif proc.returncode == 127:
60 raise GSUtilError('gsutil tool not found in your path.')
Chris Sosa101fd862012-06-12 17:44:53 -070061
62 time.sleep(sleep_timeout)
63 sleep_timeout *= 2
64
Chris Sosa407e8c52013-02-27 15:33:35 -080065 raise GSUtilError('%s GSUTIL cmd %s failed with return code %d:\n\n%s' % (
66 err_msg, cmd, proc.returncode, stderr))
Chris Sosa47a7d4e2012-03-28 11:26:55 -070067
68
69def DownloadFromGS(src, dst):
70 """Downloads object from gs_url |src| to |dst|.
71
72 Raises:
73 GSUtilError: if an error occurs during the download.
74 """
75 cmd = 'gsutil cp %s %s' % (src, dst)
76 msg = 'Failed to download "%s".' % src
77 GSUtilRun(cmd, msg)
Chris Sosa76e44b92013-01-31 12:11:38 -080078
79
80def _GetGSNamesFromList(filename_list, pattern):
81 """Given a list of filenames, returns the filenames that match pattern."""
82 matches = []
83 re_pattern = re.compile(pattern)
84 for filename in filename_list:
85 if re_pattern.match(filename):
86 matches.append(filename)
87
88 return matches
89
90
91def GetGSNamesWithWait(pattern, archive_url, err_str, single_item=True,
92 timeout=600, delay=10):
93 """Returns the google storage names specified by the given pattern.
94
95 This method polls Google Storage until the target artifacts specified by the
96 pattern is available or until the timeout occurs. Because we may not know the
97 exact name of the target artifacts, the method accepts a filename pattern,
98 to identify whether an artifact whose name matches the pattern exists (e.g.
99 use pattern '_full_' to search for the full payload
100 'chromeos_R17-1413.0.0-a1_x86-mario_full_dev.bin'). Returns the name only if
101 found before the timeout.
102
103 Args:
104 pattern: Regular expression pattern to identify the target artifact.
105 archive_url: URL of the Google Storage bucket.
106 err_str: String to display in the error message on error.
107 single_item: Only a single item should be returned.
108 timeout/delay: optional and self-explanatory.
109
110 Returns:
111 The list of artifacts matching the pattern in Google Storage bucket or None
112 if not found.
113
114 Raises:
115 PatternNotSpecific: If caller sets single_item but multiple items match.
116 """
117 deadline = time.time() + timeout
Chris Sosa6de29302013-03-14 15:27:36 -0700118 while True:
Chris Sosa76e44b92013-01-31 12:11:38 -0800119 uploaded_list = []
Chris Sosa76e44b92013-01-31 12:11:38 -0800120 try:
121 cmd = 'gsutil cat %s/%s' % (archive_url, UPLOADED_LIST)
122 msg = 'Failed to get a list of uploaded files.'
123 uploaded_list = GSUtilRun(cmd, msg).splitlines()
124 except GSUtilError:
125 # For backward compatibility, falling back to use "gsutil ls"
126 # when the manifest file is not present.
127 cmd = 'gsutil ls %s/*' % archive_url
128 msg = 'Failed to list payloads.'
129 returned_list = GSUtilRun(cmd, msg).splitlines()
130 for item in returned_list:
131 uploaded_list.append(item.rsplit('/', 1)[1])
132
133 # Check if all target artifacts are available.
134 found_names = _GetGSNamesFromList(uploaded_list, pattern)
135 if found_names:
136 if single_item and len(found_names) > 1:
137 raise PatternNotSpecific(
138 'Too many items %s returned by pattern %s in %s' % (
139 str(found_names), pattern, archive_url))
140
141 return found_names
142
Chris Sosa6de29302013-03-14 15:27:36 -0700143 # Don't delay past deadline.
144 to_delay = random.uniform(1.5 * delay, 2.5 * delay)
145 if to_delay < (deadline - time.time()):
146 _Log('Retrying in %f seconds...%s', to_delay, err_str)
147 time.sleep(to_delay)
148 else:
149 return None