blob: d827f20d3d1b850dae02caff0490281b544039ec [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 Sosa47a7d4e2012-03-28 11:26:55 -070046 for _attempt in range(GSUTIL_ATTEMPTS):
47 # Note processes can hang when capturing from stderr. This command
48 # specifically doesn't pipe stderr.
49 proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
50 stdout, _stderr = proc.communicate()
51 if proc.returncode == 0:
52 return stdout
Chris Sosa101fd862012-06-12 17:44:53 -070053
54 time.sleep(sleep_timeout)
55 sleep_timeout *= 2
56
Chris Sosa47a7d4e2012-03-28 11:26:55 -070057 else:
58 raise GSUtilError('%s GSUTIL cmd %s failed with return code %d' % (
59 err_msg, cmd, proc.returncode))
60
61
62def DownloadFromGS(src, dst):
63 """Downloads object from gs_url |src| to |dst|.
64
65 Raises:
66 GSUtilError: if an error occurs during the download.
67 """
68 cmd = 'gsutil cp %s %s' % (src, dst)
69 msg = 'Failed to download "%s".' % src
70 GSUtilRun(cmd, msg)
Chris Sosa76e44b92013-01-31 12:11:38 -080071
72
73def _GetGSNamesFromList(filename_list, pattern):
74 """Given a list of filenames, returns the filenames that match pattern."""
75 matches = []
76 re_pattern = re.compile(pattern)
77 for filename in filename_list:
78 if re_pattern.match(filename):
79 matches.append(filename)
80
81 return matches
82
83
84def GetGSNamesWithWait(pattern, archive_url, err_str, single_item=True,
85 timeout=600, delay=10):
86 """Returns the google storage names specified by the given pattern.
87
88 This method polls Google Storage until the target artifacts specified by the
89 pattern is available or until the timeout occurs. Because we may not know the
90 exact name of the target artifacts, the method accepts a filename pattern,
91 to identify whether an artifact whose name matches the pattern exists (e.g.
92 use pattern '_full_' to search for the full payload
93 'chromeos_R17-1413.0.0-a1_x86-mario_full_dev.bin'). Returns the name only if
94 found before the timeout.
95
96 Args:
97 pattern: Regular expression pattern to identify the target artifact.
98 archive_url: URL of the Google Storage bucket.
99 err_str: String to display in the error message on error.
100 single_item: Only a single item should be returned.
101 timeout/delay: optional and self-explanatory.
102
103 Returns:
104 The list of artifacts matching the pattern in Google Storage bucket or None
105 if not found.
106
107 Raises:
108 PatternNotSpecific: If caller sets single_item but multiple items match.
109 """
110 deadline = time.time() + timeout
111 while time.time() <= deadline:
112 uploaded_list = []
113 to_delay = delay + random.uniform(.5 * delay, 1.5 * delay)
114 try:
115 cmd = 'gsutil cat %s/%s' % (archive_url, UPLOADED_LIST)
116 msg = 'Failed to get a list of uploaded files.'
117 uploaded_list = GSUtilRun(cmd, msg).splitlines()
118 except GSUtilError:
119 # For backward compatibility, falling back to use "gsutil ls"
120 # when the manifest file is not present.
121 cmd = 'gsutil ls %s/*' % archive_url
122 msg = 'Failed to list payloads.'
123 returned_list = GSUtilRun(cmd, msg).splitlines()
124 for item in returned_list:
125 uploaded_list.append(item.rsplit('/', 1)[1])
126
127 # Check if all target artifacts are available.
128 found_names = _GetGSNamesFromList(uploaded_list, pattern)
129 if found_names:
130 if single_item and len(found_names) > 1:
131 raise PatternNotSpecific(
132 'Too many items %s returned by pattern %s in %s' % (
133 str(found_names), pattern, archive_url))
134
135 return found_names
136
137 _Log('Retrying in %f seconds...%s', to_delay, err_str)
138 time.sleep(to_delay)
139
140 return None