blob: 95b0d7d9cde01a56436d33e8055f0edb8c3ecd4f [file] [log] [blame]
Frank Farzan37761d12011-12-01 14:29:08 -08001#!/usr/bin/python
2#
Chris Sosa47a7d4e2012-03-28 11:26:55 -07003# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Frank Farzan37761d12011-12-01 14:29:08 -08004# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Gilad Arnold0b8c3f32012-09-19 14:35:44 -07007import Queue
Chris Sosa9164ca32012-03-28 11:04:50 -07008import os
Frank Farzan37761d12011-12-01 14:29:08 -08009import shutil
10import tempfile
Gilad Arnold0b8c3f32012-09-19 14:35:44 -070011import threading
Frank Farzan37761d12011-12-01 14:29:08 -080012
Gilad Arnoldc65330c2012-09-20 15:17:48 -070013import common_util
14import log_util
Frank Farzan37761d12011-12-01 14:29:08 -080015
16
Gilad Arnoldc65330c2012-09-20 15:17:48 -070017class Downloader(log_util.Loggable):
Frank Farzan37761d12011-12-01 14:29:08 -080018 """Download images to the devsever.
19
20 Given a URL to a build on the archive server:
21
22 - Determine if the build already exists.
23 - Download and extract the build to a staging directory.
24 - Package autotest tests.
25 - Install components to static dir.
26 """
27
Alex Millera44d5022012-07-27 11:34:16 -070028 # This filename must be kept in sync with clean_staged_images.py
29 _TIMESTAMP_FILENAME = 'staged.timestamp'
Chris Masonea22d9382012-05-18 12:38:51 -070030
Frank Farzan37761d12011-12-01 14:29:08 -080031 def __init__(self, static_dir):
32 self._static_dir = static_dir
Chris Sosa47a7d4e2012-03-28 11:26:55 -070033 self._build_dir = None
34 self._staging_dir = None
Gilad Arnold0b8c3f32012-09-19 14:35:44 -070035 self._status_queue = Queue.Queue(maxsize=1)
Chris Sosa47a7d4e2012-03-28 11:26:55 -070036 self._lock_tag = None
Chris Masone816e38c2012-05-02 12:22:36 -070037
38 @staticmethod
Chris Sosacde6bf42012-05-31 18:36:39 -070039 def ParseUrl(archive_url):
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070040 """Parse archive_url into rel_path and short_build
41 e.g. gs://chromeos-image-archive/{rel_path}/{short_build}
Chris Masone816e38c2012-05-02 12:22:36 -070042
43 @param archive_url: a URL at which build artifacts are archived.
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070044 @return a tuple of (build relative path, short build name)
Chris Masone816e38c2012-05-02 12:22:36 -070045 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070046 # The archive_url is of the form gs://server/[some_path/target]/...]/build
47 # This function discards 'gs://server/' and extracts the [some_path/target]
48 # as rel_path and the build as short_build.
49 sub_url = archive_url.partition('://')[2]
50 split_sub_url = sub_url.split('/')
51 rel_path = '/'.join(split_sub_url[1:-1])
52 short_build = split_sub_url[-1]
53 return rel_path, short_build
Chris Masone816e38c2012-05-02 12:22:36 -070054
55 @staticmethod
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070056 def GenerateLockTag(rel_path, short_build):
57 """Generate a name for a lock scoped to this rel_path/build pair.
Chris Masone816e38c2012-05-02 12:22:36 -070058
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070059 @param rel_path: the relative path for the build.
Chris Masone816e38c2012-05-02 12:22:36 -070060 @param short_build: short build name
61 @return a name to use with AcquireLock that will scope the lock.
62 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070063 return '/'.join([rel_path, short_build])
Frank Farzan37761d12011-12-01 14:29:08 -080064
Chris Sosa9164ca32012-03-28 11:04:50 -070065 @staticmethod
Alex Millera44d5022012-07-27 11:34:16 -070066 def _TouchTimestampForStaged(directory_path):
67 file_name = os.path.join(directory_path, Downloader._TIMESTAMP_FILENAME)
68 # Easiest python version of |touch file_name|
69 with file(file_name, 'a'):
70 os.utime(file_name, None)
71
72 @staticmethod
Chris Sosa9164ca32012-03-28 11:04:50 -070073 def BuildStaged(archive_url, static_dir):
74 """Returns True if the build is already staged."""
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070075 rel_path, short_build = Downloader.ParseUrl(archive_url)
76 sub_directory = Downloader.GenerateLockTag(rel_path, short_build)
Alex Millera44d5022012-07-27 11:34:16 -070077 directory_path = os.path.join(static_dir, sub_directory)
78 exists = os.path.isdir(directory_path)
79 # If the build exists, then touch the timestamp to tell
80 # clean_stages_images.py that we're using this build.
81 if exists:
82 Downloader._TouchTimestampForStaged(directory_path)
83 return exists
Chris Sosa9164ca32012-03-28 11:04:50 -070084
Chris Sosa47a7d4e2012-03-28 11:26:55 -070085 def Download(self, archive_url, background=False):
86 """Downloads the given build artifacts defined by the |archive_url|.
87
88 If background is set to True, will return back early before all artifacts
89 have been downloaded. The artifacts that can be backgrounded are all those
90 that are not set as synchronous.
Chris Masone816e38c2012-05-02 12:22:36 -070091
92 TODO: refactor this into a common Download method, once unit tests are
93 fixed up to make iterating on the code easier.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070094 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070095 # Parse archive_url into rel_path (contains the build target) and
96 # short_build.
97 # e.g. gs://chromeos-image-archive/{rel_path}/{short_build}
98 rel_path, short_build = self.ParseUrl(archive_url)
Chris Sosacde6bf42012-05-31 18:36:39 -070099 # This should never happen. The Devserver should only try to call this
100 # method if no previous downloads have been staged for this archive_url.
101 assert not Downloader.BuildStaged(archive_url, self._static_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800102 # Bind build_dir and staging_dir here so we can tell if we need to do any
103 # cleanup after an exception occurs before build_dir is set.
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700104 self._lock_tag = self.GenerateLockTag(rel_path, short_build)
Frank Farzan37761d12011-12-01 14:29:08 -0800105 try:
106 # Create Dev Server directory for this build and tell other Downloader
Gilad Arnold5174ca22012-09-12 10:49:09 -0700107 # instances we have processed this build. Note that during normal
108 # execution, this lock is only released in the actual downloading
109 # procedure called below.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700110 self._build_dir = common_util.AcquireLock(
Chris Masone816e38c2012-05-02 12:22:36 -0700111 static_dir=self._static_dir, tag=self._lock_tag)
Frank Farzan37761d12011-12-01 14:29:08 -0800112
Yu-Ju Hong1a83a712012-06-27 09:11:34 -0700113 # Replace '/' with '_' in rel_path because it may contain multiple levels
114 # which would not be qualified as part of the suffix.
115 self._staging_dir = tempfile.mkdtemp(suffix='_'.join(
Chris Sosaf0975642012-06-29 13:53:42 -0700116 [rel_path.replace('/', '_'), short_build]))
Chris Sosa27e56642012-10-15 19:37:44 -0700117 Downloader._TouchTimestampForStaged(self._build_dir)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700118 self._Log('Gathering download requirements %s' % archive_url)
Chris Masone816e38c2012-05-02 12:22:36 -0700119 artifacts = self.GatherArtifactDownloads(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700120 self._staging_dir, archive_url, self._build_dir, short_build)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700121 common_util.PrepareBuildDirectory(self._build_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800122
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700123 self._Log('Downloading foreground artifacts from %s' % archive_url)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700124 background_artifacts = []
125 for artifact in artifacts:
126 if artifact.Synchronous():
127 artifact.Download()
128 artifact.Stage()
129 else:
130 background_artifacts.append(artifact)
Frank Farzan37761d12011-12-01 14:29:08 -0800131
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700132 if background:
Chris Sosacde6bf42012-05-31 18:36:39 -0700133 self._DownloadArtifactsInBackground(background_artifacts)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700134 else:
135 self._DownloadArtifactsSerially(background_artifacts)
136
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700137 except Exception, e:
Frank Farzan37761d12011-12-01 14:29:08 -0800138 # Release processing lock, which will remove build components directory
139 # so future runs can retry.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700140 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700141 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag,
142 destroy=True)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700143
144 self._status_queue.put(e)
145 self._Cleanup()
Frank Farzan37761d12011-12-01 14:29:08 -0800146 raise
Frank Farzan37761d12011-12-01 14:29:08 -0800147 return 'Success'
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700148
149 def _Cleanup(self):
150 """Cleans up the staging dir for this downloader instanfce."""
151 if self._staging_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700152 self._Log('Cleaning up staging directory %s' % self._staging_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700153 shutil.rmtree(self._staging_dir)
154
155 self._staging_dir = None
156
157 def _DownloadArtifactsSerially(self, artifacts):
158 """Simple function to download all the given artifacts serially."""
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700159 self._Log('Downloading artifacts serially.')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700160 try:
161 for artifact in artifacts:
162 artifact.Download()
163 artifact.Stage()
164 except Exception, e:
165 self._status_queue.put(e)
166
167 # Release processing lock, which will remove build components directory
168 # so future runs can retry.
169 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700170 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag,
171 destroy=True)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700172 else:
Gilad Arnold5174ca22012-09-12 10:49:09 -0700173 # Release processing lock, keeping directory intact.
174 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700175 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700176 self._status_queue.put('Success')
177 finally:
178 self._Cleanup()
179
Chris Sosacde6bf42012-05-31 18:36:39 -0700180 def _DownloadArtifactsInBackground(self, artifacts):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700181 """Downloads |artifacts| in the background and signals when complete."""
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700182 self._Log('Invoking background download of artifacts')
Gilad Arnold0b8c3f32012-09-19 14:35:44 -0700183 thread = threading.Thread(target=self._DownloadArtifactsSerially,
184 args=(artifacts,))
185 thread.start()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700186
Gilad Arnold6f99b982012-09-12 10:49:40 -0700187 def GatherArtifactDownloads(self, main_staging_dir, archive_url, build_dir,
188 short_build):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700189 """Wrapper around common_util.GatherArtifactDownloads().
Chris Masone816e38c2012-05-02 12:22:36 -0700190
191 The wrapper allows mocking and overriding in derived classes.
192 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700193 return common_util.GatherArtifactDownloads(
194 main_staging_dir, archive_url, build_dir, short_build)
Chris Masone816e38c2012-05-02 12:22:36 -0700195
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700196 def GetStatusOfBackgroundDownloads(self):
197 """Returns the status of the background downloads.
198
199 This commands returns the status of the background downloads and blocks
200 until a status is returned.
201 """
202 status = self._status_queue.get()
203 # In case anyone else is calling.
204 self._status_queue.put(status)
Alex Miller92ed3592012-08-15 16:27:46 -0700205 # If someone is curious about the status of a build, then we should
206 # probably keep it around for a bit longer.
Chris Sosa27e56642012-10-15 19:37:44 -0700207 if self._build_dir and os.path.exists(self._build_dir):
208 Downloader._TouchTimestampForStaged(self._build_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700209 # It's possible we received an exception, if so, re-raise it here.
210 if isinstance(status, Exception):
211 raise status
212
213 return status
Chris Masone816e38c2012-05-02 12:22:36 -0700214
215
216class SymbolDownloader(Downloader):
217 """Download and stage debug symbols for a build on the devsever.
218
219 Given a URL to a build on the archive server:
220
221 - Determine if the build already exists.
222 - Download and extract the debug symbols to a staging directory.
223 - Install symbols to static dir.
224 """
225
226 _DONE_FLAG = 'done'
227
228 @staticmethod
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700229 def GenerateLockTag(rel_path, short_build):
230 return '/'.join([rel_path, short_build, 'symbols'])
Chris Masone816e38c2012-05-02 12:22:36 -0700231
Chris Sosacde6bf42012-05-31 18:36:39 -0700232 def Download(self, archive_url, _background=False):
Chris Masone816e38c2012-05-02 12:22:36 -0700233 """Downloads debug symbols for the build defined by the |archive_url|.
234
235 The symbols will be downloaded synchronously
236 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700237 # Parse archive_url into rel_path (contains the build target) and
238 # short_build.
239 # e.g. gs://chromeos-image-archive/{rel_path}/{short_build}
240 rel_path, short_build = self.ParseUrl(archive_url)
Chris Masone816e38c2012-05-02 12:22:36 -0700241
242 # Bind build_dir and staging_dir here so we can tell if we need to do any
243 # cleanup after an exception occurs before build_dir is set.
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700244 self._lock_tag = self.GenerateLockTag(rel_path, short_build)
Chris Masone816e38c2012-05-02 12:22:36 -0700245 if self.SymbolsStaged(archive_url, self._static_dir):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700246 self._Log('Symbols for build %s have already been staged.' %
247 self._lock_tag)
Chris Masone816e38c2012-05-02 12:22:36 -0700248 return 'Success'
249
250 try:
251 # Create Dev Server directory for this build and tell other Downloader
252 # instances we have processed this build.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700253 self._build_dir = common_util.AcquireLock(
Chris Masone816e38c2012-05-02 12:22:36 -0700254 static_dir=self._static_dir, tag=self._lock_tag)
255
Yu-Ju Hong1a83a712012-06-27 09:11:34 -0700256 # Replace '/' with '_' in rel_path because it may contain multiple levels
257 # which would not be qualified as part of the suffix.
258 self._staging_dir = tempfile.mkdtemp(suffix='_'.join(
Chris Sosaf0975642012-06-29 13:53:42 -0700259 [rel_path.replace('/', '_'), short_build]))
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700260 self._Log('Downloading debug symbols from %s' % archive_url)
Chris Masone816e38c2012-05-02 12:22:36 -0700261
262 [symbol_artifact] = self.GatherArtifactDownloads(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700263 self._staging_dir, archive_url, self._static_dir)
Chris Masone816e38c2012-05-02 12:22:36 -0700264 symbol_artifact.Download()
265 symbol_artifact.Stage()
Chris Masonea22d9382012-05-18 12:38:51 -0700266 self.MarkSymbolsStaged()
Chris Masone816e38c2012-05-02 12:22:36 -0700267
268 except Exception:
269 # Release processing "lock", which will indicate to future runs that we
270 # did not succeed, and so they should try again.
271 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700272 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag,
273 destroy=True)
Gilad Arnold5174ca22012-09-12 10:49:09 -0700274
Chris Masone816e38c2012-05-02 12:22:36 -0700275 raise
Gilad Arnold5174ca22012-09-12 10:49:09 -0700276 else:
277 # Release processing "lock", keeping directory intact.
278 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700279 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag)
Chris Masonea22d9382012-05-18 12:38:51 -0700280 finally:
281 self._Cleanup()
Chris Sosa4d9c4d42012-06-29 15:23:23 -0700282
Chris Masone816e38c2012-05-02 12:22:36 -0700283 return 'Success'
284
Gilad Arnold6f99b982012-09-12 10:49:40 -0700285 def GatherArtifactDownloads(self, temp_download_dir, archive_url, static_dir,
286 short_build=None):
Chris Masone816e38c2012-05-02 12:22:36 -0700287 """Call SymbolDownloader-appropriate artifact gathering method.
288
289 @param temp_download_dir: the tempdir into which we're downloading artifacts
290 prior to staging them.
291 @param archive_url: the google storage url of the bucket where the debug
292 symbols for the desired build are stored.
Chris Masone816e38c2012-05-02 12:22:36 -0700293 @param staging_dir: the dir into which to stage the symbols
Gilad Arnold6f99b982012-09-12 10:49:40 -0700294 @param short_build: (ignored)
Chris Masone816e38c2012-05-02 12:22:36 -0700295
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700296 @return an iterable of one DebugTarballBuildArtifact pointing to the right
297 debug symbols. This is an iterable so that it's similar to
298 GatherArtifactDownloads. Also, it's possible that someday we might
299 have more than one.
Chris Masone816e38c2012-05-02 12:22:36 -0700300 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700301 return common_util.GatherSymbolArtifactDownloads(
302 temp_download_dir, archive_url, static_dir)
Chris Masone816e38c2012-05-02 12:22:36 -0700303
304 def MarkSymbolsStaged(self):
305 """Puts a flag file on disk to signal that symbols are staged."""
306 with open(os.path.join(self._build_dir, self._DONE_FLAG), 'w') as flag:
307 flag.write(self._DONE_FLAG)
308
309 def SymbolsStaged(self, archive_url, static_dir):
310 """Returns True if the build is already staged."""
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700311 rel_path, short_build = self.ParseUrl(archive_url)
312 sub_directory = self.GenerateLockTag(rel_path, short_build)
Chris Masone816e38c2012-05-02 12:22:36 -0700313 return os.path.isfile(os.path.join(static_dir,
314 sub_directory,
315 self._DONE_FLAG))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700316
317
318class ImagesDownloader(Downloader):
319 """Download and stage prebuilt images for a given build.
320
321 Given a URL to a build on the archive server and a list of images:
322 - Determine which images have not been staged yet.
323 - Download the image archive.
324 - Extract missing images to the staging directory.
325
326 """
327 _DONE_FLAG = 'staged'
Gilad Arnold6f99b982012-09-12 10:49:40 -0700328
329 # List of images to be staged; empty (default) means all.
330 _image_list = []
331
332 # A mapping from test image types to their archived file names.
333 _IMAGE_TO_FNAME = {
334 'test': 'chromiumos_test_image.bin',
335 'base': 'chromiumos_base_image.bin',
336 'recovery': 'recovery_image.bin',
337 }
338
339 @staticmethod
340 def GenerateLockTag(rel_path, short_build):
341 return os.path.join('images', rel_path, short_build)
342
343 def Download(self, archive_url, image_list, _background=False):
344 """Downloads images in |image_list| from the build defined by |archive_url|.
345
346 Download happens synchronously. |images| may include any of those in
347 self._IMAGE_TO_FNAME.keys().
348
349 """
350 # Check correctness of image list, remove duplicates.
351 if not image_list:
352 raise DevServerError('empty list of image types')
353 invalid_images = list(set(image_list) - set(self._IMAGE_TO_FNAME.keys()))
354 if invalid_images:
355 raise DevServerError('invalid images requested: %s' % invalid_images)
356 image_list = list(set(image_list))
357
358 # Parse archive_url into rel_path (contains the build target) and
359 # short_build.
360 # e.g. gs://chromeos-image-archive/{rel_path}/{short_build}
361 rel_path, short_build = self.ParseUrl(archive_url)
362
363 # Bind build_dir and staging_dir here so we can tell if we need to do any
364 # cleanup after an exception occurs before build_dir is set.
365 self._lock_tag = self.GenerateLockTag(rel_path, short_build)
366 staged_image_list = self._CheckStagedImages(archive_url, self._static_dir)
367 unstaged_image_list = [image for image in image_list
368 if image not in staged_image_list]
369 if not unstaged_image_list:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700370 self._Log(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700371 'All requested images (%s) for build %s have already been staged.' %
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700372 (common_util.CommaSeparatedList(image_list, is_quoted=True)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700373 if image_list else 'none',
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700374 self._lock_tag))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700375 return 'Success'
376
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700377 self._Log(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700378 'Image(s) %s for build %s will be staged' %
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700379 (common_util.CommaSeparatedList(unstaged_image_list, is_quoted=True),
380 self._lock_tag))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700381 self._image_list = unstaged_image_list
382
383 try:
384 # Create a static target directory and lock it for processing. We permit
385 # the directory to preexist, as different images might be downloaded and
386 # extracted at different times.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700387 self._build_dir = common_util.AcquireLock(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700388 static_dir=self._static_dir, tag=self._lock_tag,
389 create_once=False)
390
391 # Replace '/' with '_' in rel_path because it may contain multiple levels
392 # which would not be qualified as part of the suffix.
393 self._staging_dir = tempfile.mkdtemp(suffix='_'.join(
394 [rel_path.replace('/', '_'), short_build]))
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700395 self._Log('Downloading image archive from %s' % archive_url)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700396 dest_static_dir = os.path.join(self._static_dir, self._lock_tag)
397 [image_archive_artifact] = self.GatherArtifactDownloads(
398 self._staging_dir, archive_url, dest_static_dir)
399 image_archive_artifact.Download()
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700400 self._Log('Staging images to %s' % dest_static_dir)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700401 image_archive_artifact.Stage()
402 self._MarkStagedImages(unstaged_image_list)
403
404 except Exception:
405 # Release processing "lock", which will indicate to future runs that we
406 # did not succeed, and so they should try again.
407 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700408 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag,
409 destroy=True)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700410 raise
411 else:
412 # Release processing "lock", keeping directory intact.
413 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700414 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700415 finally:
416 self._Cleanup()
417
418 return 'Success'
419
420 def GatherArtifactDownloads(self, temp_download_dir, archive_url, static_dir,
421 short_build=None):
422 """Call appropriate artifact gathering method.
423
424 Args:
425 temp_download_dir: temporary directory for downloading artifacts to
426 archive_url: URI to the bucket where image archive is stored
427 staging_dir: directory into which to stage extracted images
428 short_build: (ignored)
429 Returns:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700430 list of downloadable artifacts (of type ZipfileBuildArtifact), currently
431 containing a single object, configured for extracting a predetermined
432 list of images
Gilad Arnold6f99b982012-09-12 10:49:40 -0700433 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700434 return common_util.GatherImageArchiveArtifactDownloads(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700435 temp_download_dir, archive_url, static_dir,
436 [self._IMAGE_TO_FNAME[image] for image in self._image_list])
437
438 def _MarkStagedImages(self, image_list):
439 """Update the on-disk flag file with the list of newly staged images.
440
441 This does not check for duplicates against already listed images, and will
442 add any listed images regardless.
443
444 """
445 flag_fname = os.path.join(self._build_dir, self._DONE_FLAG)
446 with open(flag_fname, 'a') as flag_file:
447 flag_file.writelines([image + '\n' for image in image_list])
448
449 def _CheckStagedImages(self, archive_url, static_dir):
450 """Returns a list of images that were already staged.
451
452 Reads the list of images from a flag file, if one is present, and returns
453 after removing duplicates.
454
455 """
456 rel_path, short_build = self.ParseUrl(archive_url)
457 sub_directory = self.GenerateLockTag(rel_path, short_build)
458 flag_fname = os.path.join(static_dir, sub_directory, self._DONE_FLAG)
459 staged_image_list = []
460 # TODO(garnold) make this code immune to race conditions, probably by
461 # acquiring a lock around the file access code.
462 if os.path.isfile(flag_fname):
463 with open(flag_fname) as flag_file:
464 staged_image_list = [image.strip() for image in flag_file.readlines()]
465 return list(set(staged_image_list))