blob: a264cf7103d2f4926578fff007b96d8e64c8b987 [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 Sosa407e8c52013-02-27 15:33:35 -080053 elif 'matched no objects' in stderr or 'non-existent object' in stderr:
54 # TODO(sosa): Note this is a heuristic that makes us not re-attempt
55 # unnecessarily. However, if it fails, the worst that can happen is just
56 # waiting longer than necessary.
57 break
Chris Sosa101fd862012-06-12 17:44:53 -070058
59 time.sleep(sleep_timeout)
60 sleep_timeout *= 2
61
Chris Sosa407e8c52013-02-27 15:33:35 -080062 raise GSUtilError('%s GSUTIL cmd %s failed with return code %d:\n\n%s' % (
63 err_msg, cmd, proc.returncode, stderr))
Chris Sosa47a7d4e2012-03-28 11:26:55 -070064
65
66def DownloadFromGS(src, dst):
67 """Downloads object from gs_url |src| to |dst|.
68
69 Raises:
70 GSUtilError: if an error occurs during the download.
71 """
72 cmd = 'gsutil cp %s %s' % (src, dst)
73 msg = 'Failed to download "%s".' % src
74 GSUtilRun(cmd, msg)
Chris Sosa76e44b92013-01-31 12:11:38 -080075
76
77def _GetGSNamesFromList(filename_list, pattern):
78 """Given a list of filenames, returns the filenames that match pattern."""
79 matches = []
80 re_pattern = re.compile(pattern)
81 for filename in filename_list:
82 if re_pattern.match(filename):
83 matches.append(filename)
84
85 return matches
86
87
88def GetGSNamesWithWait(pattern, archive_url, err_str, single_item=True,
89 timeout=600, delay=10):
90 """Returns the google storage names specified by the given pattern.
91
92 This method polls Google Storage until the target artifacts specified by the
93 pattern is available or until the timeout occurs. Because we may not know the
94 exact name of the target artifacts, the method accepts a filename pattern,
95 to identify whether an artifact whose name matches the pattern exists (e.g.
96 use pattern '_full_' to search for the full payload
97 'chromeos_R17-1413.0.0-a1_x86-mario_full_dev.bin'). Returns the name only if
98 found before the timeout.
99
100 Args:
101 pattern: Regular expression pattern to identify the target artifact.
102 archive_url: URL of the Google Storage bucket.
103 err_str: String to display in the error message on error.
104 single_item: Only a single item should be returned.
105 timeout/delay: optional and self-explanatory.
106
107 Returns:
108 The list of artifacts matching the pattern in Google Storage bucket or None
109 if not found.
110
111 Raises:
112 PatternNotSpecific: If caller sets single_item but multiple items match.
113 """
114 deadline = time.time() + timeout
115 while time.time() <= deadline:
116 uploaded_list = []
117 to_delay = delay + random.uniform(.5 * delay, 1.5 * delay)
118 try:
119 cmd = 'gsutil cat %s/%s' % (archive_url, UPLOADED_LIST)
120 msg = 'Failed to get a list of uploaded files.'
121 uploaded_list = GSUtilRun(cmd, msg).splitlines()
122 except GSUtilError:
123 # For backward compatibility, falling back to use "gsutil ls"
124 # when the manifest file is not present.
125 cmd = 'gsutil ls %s/*' % archive_url
126 msg = 'Failed to list payloads.'
127 returned_list = GSUtilRun(cmd, msg).splitlines()
128 for item in returned_list:
129 uploaded_list.append(item.rsplit('/', 1)[1])
130
131 # Check if all target artifacts are available.
132 found_names = _GetGSNamesFromList(uploaded_list, pattern)
133 if found_names:
134 if single_item and len(found_names) > 1:
135 raise PatternNotSpecific(
136 'Too many items %s returned by pattern %s in %s' % (
137 str(found_names), pattern, archive_url))
138
139 return found_names
140
141 _Log('Retrying in %f seconds...%s', to_delay, err_str)
142 time.sleep(to_delay)
143
144 return None