blob: e133f303fbc3a21e9e5f7399d5e99db9da682cc9 [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
Frank Farzan37761d12011-12-01 14:29:08 -08008import cherrypy
Chris Sosa9164ca32012-03-28 11:04:50 -07009import os
Frank Farzan37761d12011-12-01 14:29:08 -080010import shutil
11import tempfile
Gilad Arnold0b8c3f32012-09-19 14:35:44 -070012import threading
Frank Farzan37761d12011-12-01 14:29:08 -080013
Gilad Arnoldc65330c2012-09-20 15:17:48 -070014import build_artifact
15import common_util
16import log_util
Frank Farzan37761d12011-12-01 14:29:08 -080017
18
Gilad Arnoldc65330c2012-09-20 15:17:48 -070019class Downloader(log_util.Loggable):
Frank Farzan37761d12011-12-01 14:29:08 -080020 """Download images to the devsever.
21
22 Given a URL to a build on the archive server:
23
24 - Determine if the build already exists.
25 - Download and extract the build to a staging directory.
26 - Package autotest tests.
27 - Install components to static dir.
28 """
29
Alex Millera44d5022012-07-27 11:34:16 -070030 # This filename must be kept in sync with clean_staged_images.py
31 _TIMESTAMP_FILENAME = 'staged.timestamp'
Chris Masonea22d9382012-05-18 12:38:51 -070032
Frank Farzan37761d12011-12-01 14:29:08 -080033 def __init__(self, static_dir):
34 self._static_dir = static_dir
Chris Sosa47a7d4e2012-03-28 11:26:55 -070035 self._build_dir = None
36 self._staging_dir = None
Gilad Arnold0b8c3f32012-09-19 14:35:44 -070037 self._status_queue = Queue.Queue(maxsize=1)
Chris Sosa47a7d4e2012-03-28 11:26:55 -070038 self._lock_tag = None
Chris Masone816e38c2012-05-02 12:22:36 -070039
40 @staticmethod
Chris Sosacde6bf42012-05-31 18:36:39 -070041 def ParseUrl(archive_url):
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070042 """Parse archive_url into rel_path and short_build
43 e.g. gs://chromeos-image-archive/{rel_path}/{short_build}
Chris Masone816e38c2012-05-02 12:22:36 -070044
45 @param archive_url: a URL at which build artifacts are archived.
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070046 @return a tuple of (build relative path, short build name)
Chris Masone816e38c2012-05-02 12:22:36 -070047 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070048 # The archive_url is of the form gs://server/[some_path/target]/...]/build
49 # This function discards 'gs://server/' and extracts the [some_path/target]
50 # as rel_path and the build as short_build.
51 sub_url = archive_url.partition('://')[2]
52 split_sub_url = sub_url.split('/')
53 rel_path = '/'.join(split_sub_url[1:-1])
54 short_build = split_sub_url[-1]
55 return rel_path, short_build
Chris Masone816e38c2012-05-02 12:22:36 -070056
57 @staticmethod
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070058 def GenerateLockTag(rel_path, short_build):
59 """Generate a name for a lock scoped to this rel_path/build pair.
Chris Masone816e38c2012-05-02 12:22:36 -070060
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070061 @param rel_path: the relative path for the build.
Chris Masone816e38c2012-05-02 12:22:36 -070062 @param short_build: short build name
63 @return a name to use with AcquireLock that will scope the lock.
64 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070065 return '/'.join([rel_path, short_build])
Frank Farzan37761d12011-12-01 14:29:08 -080066
Chris Sosa9164ca32012-03-28 11:04:50 -070067 @staticmethod
Alex Millera44d5022012-07-27 11:34:16 -070068 def _TouchTimestampForStaged(directory_path):
69 file_name = os.path.join(directory_path, Downloader._TIMESTAMP_FILENAME)
70 # Easiest python version of |touch file_name|
71 with file(file_name, 'a'):
72 os.utime(file_name, None)
73
74 @staticmethod
Chris Sosa9164ca32012-03-28 11:04:50 -070075 def BuildStaged(archive_url, static_dir):
76 """Returns True if the build is already staged."""
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070077 rel_path, short_build = Downloader.ParseUrl(archive_url)
78 sub_directory = Downloader.GenerateLockTag(rel_path, short_build)
Alex Millera44d5022012-07-27 11:34:16 -070079 directory_path = os.path.join(static_dir, sub_directory)
80 exists = os.path.isdir(directory_path)
81 # If the build exists, then touch the timestamp to tell
82 # clean_stages_images.py that we're using this build.
83 if exists:
84 Downloader._TouchTimestampForStaged(directory_path)
85 return exists
Chris Sosa9164ca32012-03-28 11:04:50 -070086
Chris Sosa47a7d4e2012-03-28 11:26:55 -070087 def Download(self, archive_url, background=False):
88 """Downloads the given build artifacts defined by the |archive_url|.
89
90 If background is set to True, will return back early before all artifacts
91 have been downloaded. The artifacts that can be backgrounded are all those
92 that are not set as synchronous.
Chris Masone816e38c2012-05-02 12:22:36 -070093
94 TODO: refactor this into a common Download method, once unit tests are
95 fixed up to make iterating on the code easier.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070096 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070097 # Parse archive_url into rel_path (contains the build target) and
98 # short_build.
99 # e.g. gs://chromeos-image-archive/{rel_path}/{short_build}
100 rel_path, short_build = self.ParseUrl(archive_url)
Chris Sosacde6bf42012-05-31 18:36:39 -0700101 # This should never happen. The Devserver should only try to call this
102 # method if no previous downloads have been staged for this archive_url.
103 assert not Downloader.BuildStaged(archive_url, self._static_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800104 # Bind build_dir and staging_dir here so we can tell if we need to do any
105 # cleanup after an exception occurs before build_dir is set.
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700106 self._lock_tag = self.GenerateLockTag(rel_path, short_build)
Frank Farzan37761d12011-12-01 14:29:08 -0800107 try:
108 # Create Dev Server directory for this build and tell other Downloader
Gilad Arnold5174ca22012-09-12 10:49:09 -0700109 # instances we have processed this build. Note that during normal
110 # execution, this lock is only released in the actual downloading
111 # procedure called below.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700112 self._build_dir = common_util.AcquireLock(
Chris Masone816e38c2012-05-02 12:22:36 -0700113 static_dir=self._static_dir, tag=self._lock_tag)
Frank Farzan37761d12011-12-01 14:29:08 -0800114
Yu-Ju Hong1a83a712012-06-27 09:11:34 -0700115 # Replace '/' with '_' in rel_path because it may contain multiple levels
116 # which would not be qualified as part of the suffix.
117 self._staging_dir = tempfile.mkdtemp(suffix='_'.join(
Chris Sosaf0975642012-06-29 13:53:42 -0700118 [rel_path.replace('/', '_'), short_build]))
Alex Millera44d5022012-07-27 11:34:16 -0700119 Downloader._TouchTimestampForStaged(self._staging_dir)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700120 self._Log('Gathering download requirements %s' % archive_url)
Chris Masone816e38c2012-05-02 12:22:36 -0700121 artifacts = self.GatherArtifactDownloads(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700122 self._staging_dir, archive_url, self._build_dir, short_build)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700123 common_util.PrepareBuildDirectory(self._build_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800124
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700125 self._Log('Downloading foreground artifacts from %s' % archive_url)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700126 background_artifacts = []
127 for artifact in artifacts:
128 if artifact.Synchronous():
129 artifact.Download()
130 artifact.Stage()
131 else:
132 background_artifacts.append(artifact)
Frank Farzan37761d12011-12-01 14:29:08 -0800133
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700134 if background:
Chris Sosacde6bf42012-05-31 18:36:39 -0700135 self._DownloadArtifactsInBackground(background_artifacts)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700136 else:
137 self._DownloadArtifactsSerially(background_artifacts)
138
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700139 except Exception, e:
Frank Farzan37761d12011-12-01 14:29:08 -0800140 # Release processing lock, which will remove build components directory
141 # so future runs can retry.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700142 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700143 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag,
144 destroy=True)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700145
146 self._status_queue.put(e)
147 self._Cleanup()
Frank Farzan37761d12011-12-01 14:29:08 -0800148 raise
Frank Farzan37761d12011-12-01 14:29:08 -0800149 return 'Success'
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700150
151 def _Cleanup(self):
152 """Cleans up the staging dir for this downloader instanfce."""
153 if self._staging_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700154 self._Log('Cleaning up staging directory %s' % self._staging_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700155 shutil.rmtree(self._staging_dir)
156
157 self._staging_dir = None
158
159 def _DownloadArtifactsSerially(self, artifacts):
160 """Simple function to download all the given artifacts serially."""
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700161 self._Log('Downloading artifacts serially.')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700162 try:
163 for artifact in artifacts:
164 artifact.Download()
165 artifact.Stage()
166 except Exception, e:
167 self._status_queue.put(e)
168
169 # Release processing lock, which will remove build components directory
170 # so future runs can retry.
171 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700172 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag,
173 destroy=True)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700174 else:
Gilad Arnold5174ca22012-09-12 10:49:09 -0700175 # Release processing lock, keeping directory intact.
176 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700177 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700178 self._status_queue.put('Success')
179 finally:
180 self._Cleanup()
181
Chris Sosacde6bf42012-05-31 18:36:39 -0700182 def _DownloadArtifactsInBackground(self, artifacts):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700183 """Downloads |artifacts| in the background and signals when complete."""
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700184 self._Log('Invoking background download of artifacts')
Gilad Arnold0b8c3f32012-09-19 14:35:44 -0700185 thread = threading.Thread(target=self._DownloadArtifactsSerially,
186 args=(artifacts,))
187 thread.start()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700188
Gilad Arnold6f99b982012-09-12 10:49:40 -0700189 def GatherArtifactDownloads(self, main_staging_dir, archive_url, build_dir,
190 short_build):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700191 """Wrapper around common_util.GatherArtifactDownloads().
Chris Masone816e38c2012-05-02 12:22:36 -0700192
193 The wrapper allows mocking and overriding in derived classes.
194 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700195 return common_util.GatherArtifactDownloads(
196 main_staging_dir, archive_url, build_dir, short_build)
Chris Masone816e38c2012-05-02 12:22:36 -0700197
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700198 def GetStatusOfBackgroundDownloads(self):
199 """Returns the status of the background downloads.
200
201 This commands returns the status of the background downloads and blocks
202 until a status is returned.
203 """
204 status = self._status_queue.get()
205 # In case anyone else is calling.
206 self._status_queue.put(status)
Alex Miller92ed3592012-08-15 16:27:46 -0700207 # If someone is curious about the status of a build, then we should
208 # probably keep it around for a bit longer.
209 if os.path.exists(self._staging_dir):
210 Downloader._TouchTimestampForStaged(self._staging_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700211 # It's possible we received an exception, if so, re-raise it here.
212 if isinstance(status, Exception):
213 raise status
214
215 return status
Chris Masone816e38c2012-05-02 12:22:36 -0700216
217
218class SymbolDownloader(Downloader):
219 """Download and stage debug symbols for a build on the devsever.
220
221 Given a URL to a build on the archive server:
222
223 - Determine if the build already exists.
224 - Download and extract the debug symbols to a staging directory.
225 - Install symbols to static dir.
226 """
227
228 _DONE_FLAG = 'done'
229
230 @staticmethod
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700231 def GenerateLockTag(rel_path, short_build):
232 return '/'.join([rel_path, short_build, 'symbols'])
Chris Masone816e38c2012-05-02 12:22:36 -0700233
Chris Sosacde6bf42012-05-31 18:36:39 -0700234 def Download(self, archive_url, _background=False):
Chris Masone816e38c2012-05-02 12:22:36 -0700235 """Downloads debug symbols for the build defined by the |archive_url|.
236
237 The symbols will be downloaded synchronously
238 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700239 # Parse archive_url into rel_path (contains the build target) and
240 # short_build.
241 # e.g. gs://chromeos-image-archive/{rel_path}/{short_build}
242 rel_path, short_build = self.ParseUrl(archive_url)
Chris Masone816e38c2012-05-02 12:22:36 -0700243
244 # Bind build_dir and staging_dir here so we can tell if we need to do any
245 # cleanup after an exception occurs before build_dir is set.
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700246 self._lock_tag = self.GenerateLockTag(rel_path, short_build)
Chris Masone816e38c2012-05-02 12:22:36 -0700247 if self.SymbolsStaged(archive_url, self._static_dir):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700248 self._Log('Symbols for build %s have already been staged.' %
249 self._lock_tag)
Chris Masone816e38c2012-05-02 12:22:36 -0700250 return 'Success'
251
252 try:
253 # Create Dev Server directory for this build and tell other Downloader
254 # instances we have processed this build.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700255 self._build_dir = common_util.AcquireLock(
Chris Masone816e38c2012-05-02 12:22:36 -0700256 static_dir=self._static_dir, tag=self._lock_tag)
257
Yu-Ju Hong1a83a712012-06-27 09:11:34 -0700258 # Replace '/' with '_' in rel_path because it may contain multiple levels
259 # which would not be qualified as part of the suffix.
260 self._staging_dir = tempfile.mkdtemp(suffix='_'.join(
Chris Sosaf0975642012-06-29 13:53:42 -0700261 [rel_path.replace('/', '_'), short_build]))
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700262 self._Log('Downloading debug symbols from %s' % archive_url)
Chris Masone816e38c2012-05-02 12:22:36 -0700263
264 [symbol_artifact] = self.GatherArtifactDownloads(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700265 self._staging_dir, archive_url, self._static_dir)
Chris Masone816e38c2012-05-02 12:22:36 -0700266 symbol_artifact.Download()
267 symbol_artifact.Stage()
Chris Masonea22d9382012-05-18 12:38:51 -0700268 self.MarkSymbolsStaged()
Chris Masone816e38c2012-05-02 12:22:36 -0700269
270 except Exception:
271 # Release processing "lock", which will indicate to future runs that we
272 # did not succeed, and so they should try again.
273 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700274 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag,
275 destroy=True)
Gilad Arnold5174ca22012-09-12 10:49:09 -0700276
Chris Masone816e38c2012-05-02 12:22:36 -0700277 raise
Gilad Arnold5174ca22012-09-12 10:49:09 -0700278 else:
279 # Release processing "lock", keeping directory intact.
280 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700281 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag)
Chris Masonea22d9382012-05-18 12:38:51 -0700282 finally:
283 self._Cleanup()
Chris Sosa4d9c4d42012-06-29 15:23:23 -0700284
Chris Masone816e38c2012-05-02 12:22:36 -0700285 return 'Success'
286
Gilad Arnold6f99b982012-09-12 10:49:40 -0700287 def GatherArtifactDownloads(self, temp_download_dir, archive_url, static_dir,
288 short_build=None):
Chris Masone816e38c2012-05-02 12:22:36 -0700289 """Call SymbolDownloader-appropriate artifact gathering method.
290
291 @param temp_download_dir: the tempdir into which we're downloading artifacts
292 prior to staging them.
293 @param archive_url: the google storage url of the bucket where the debug
294 symbols for the desired build are stored.
Chris Masone816e38c2012-05-02 12:22:36 -0700295 @param staging_dir: the dir into which to stage the symbols
Gilad Arnold6f99b982012-09-12 10:49:40 -0700296 @param short_build: (ignored)
Chris Masone816e38c2012-05-02 12:22:36 -0700297
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700298 @return an iterable of one DebugTarballBuildArtifact pointing to the right
299 debug symbols. This is an iterable so that it's similar to
300 GatherArtifactDownloads. Also, it's possible that someday we might
301 have more than one.
Chris Masone816e38c2012-05-02 12:22:36 -0700302 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700303 return common_util.GatherSymbolArtifactDownloads(
304 temp_download_dir, archive_url, static_dir)
Chris Masone816e38c2012-05-02 12:22:36 -0700305
306 def MarkSymbolsStaged(self):
307 """Puts a flag file on disk to signal that symbols are staged."""
308 with open(os.path.join(self._build_dir, self._DONE_FLAG), 'w') as flag:
309 flag.write(self._DONE_FLAG)
310
311 def SymbolsStaged(self, archive_url, static_dir):
312 """Returns True if the build is already staged."""
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700313 rel_path, short_build = self.ParseUrl(archive_url)
314 sub_directory = self.GenerateLockTag(rel_path, short_build)
Chris Masone816e38c2012-05-02 12:22:36 -0700315 return os.path.isfile(os.path.join(static_dir,
316 sub_directory,
317 self._DONE_FLAG))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700318
319
320class ImagesDownloader(Downloader):
321 """Download and stage prebuilt images for a given build.
322
323 Given a URL to a build on the archive server and a list of images:
324 - Determine which images have not been staged yet.
325 - Download the image archive.
326 - Extract missing images to the staging directory.
327
328 """
329 _DONE_FLAG = 'staged'
Gilad Arnold6f99b982012-09-12 10:49:40 -0700330
331 # List of images to be staged; empty (default) means all.
332 _image_list = []
333
334 # A mapping from test image types to their archived file names.
335 _IMAGE_TO_FNAME = {
336 'test': 'chromiumos_test_image.bin',
337 'base': 'chromiumos_base_image.bin',
338 'recovery': 'recovery_image.bin',
339 }
340
341 @staticmethod
342 def GenerateLockTag(rel_path, short_build):
343 return os.path.join('images', rel_path, short_build)
344
345 def Download(self, archive_url, image_list, _background=False):
346 """Downloads images in |image_list| from the build defined by |archive_url|.
347
348 Download happens synchronously. |images| may include any of those in
349 self._IMAGE_TO_FNAME.keys().
350
351 """
352 # Check correctness of image list, remove duplicates.
353 if not image_list:
354 raise DevServerError('empty list of image types')
355 invalid_images = list(set(image_list) - set(self._IMAGE_TO_FNAME.keys()))
356 if invalid_images:
357 raise DevServerError('invalid images requested: %s' % invalid_images)
358 image_list = list(set(image_list))
359
360 # Parse archive_url into rel_path (contains the build target) and
361 # short_build.
362 # e.g. gs://chromeos-image-archive/{rel_path}/{short_build}
363 rel_path, short_build = self.ParseUrl(archive_url)
364
365 # Bind build_dir and staging_dir here so we can tell if we need to do any
366 # cleanup after an exception occurs before build_dir is set.
367 self._lock_tag = self.GenerateLockTag(rel_path, short_build)
368 staged_image_list = self._CheckStagedImages(archive_url, self._static_dir)
369 unstaged_image_list = [image for image in image_list
370 if image not in staged_image_list]
371 if not unstaged_image_list:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700372 self._Log(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700373 'All requested images (%s) for build %s have already been staged.' %
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700374 (common_util.CommaSeparatedList(image_list, is_quoted=True)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700375 if image_list else 'none',
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700376 self._lock_tag))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700377 return 'Success'
378
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700379 self._Log(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700380 'Image(s) %s for build %s will be staged' %
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700381 (common_util.CommaSeparatedList(unstaged_image_list, is_quoted=True),
382 self._lock_tag))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700383 self._image_list = unstaged_image_list
384
385 try:
386 # Create a static target directory and lock it for processing. We permit
387 # the directory to preexist, as different images might be downloaded and
388 # extracted at different times.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700389 self._build_dir = common_util.AcquireLock(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700390 static_dir=self._static_dir, tag=self._lock_tag,
391 create_once=False)
392
393 # Replace '/' with '_' in rel_path because it may contain multiple levels
394 # which would not be qualified as part of the suffix.
395 self._staging_dir = tempfile.mkdtemp(suffix='_'.join(
396 [rel_path.replace('/', '_'), short_build]))
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700397 self._Log('Downloading image archive from %s' % archive_url)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700398 dest_static_dir = os.path.join(self._static_dir, self._lock_tag)
399 [image_archive_artifact] = self.GatherArtifactDownloads(
400 self._staging_dir, archive_url, dest_static_dir)
401 image_archive_artifact.Download()
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700402 self._Log('Staging images to %s' % dest_static_dir)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700403 image_archive_artifact.Stage()
404 self._MarkStagedImages(unstaged_image_list)
405
406 except Exception:
407 # Release processing "lock", which will indicate to future runs that we
408 # did not succeed, and so they should try again.
409 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700410 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag,
411 destroy=True)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700412 raise
413 else:
414 # Release processing "lock", keeping directory intact.
415 if self._build_dir:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700416 common_util.ReleaseLock(static_dir=self._static_dir, tag=self._lock_tag)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700417 finally:
418 self._Cleanup()
419
420 return 'Success'
421
422 def GatherArtifactDownloads(self, temp_download_dir, archive_url, static_dir,
423 short_build=None):
424 """Call appropriate artifact gathering method.
425
426 Args:
427 temp_download_dir: temporary directory for downloading artifacts to
428 archive_url: URI to the bucket where image archive is stored
429 staging_dir: directory into which to stage extracted images
430 short_build: (ignored)
431 Returns:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700432 list of downloadable artifacts (of type ZipfileBuildArtifact), currently
433 containing a single object, configured for extracting a predetermined
434 list of images
Gilad Arnold6f99b982012-09-12 10:49:40 -0700435 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700436 return common_util.GatherImageArchiveArtifactDownloads(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700437 temp_download_dir, archive_url, static_dir,
438 [self._IMAGE_TO_FNAME[image] for image in self._image_list])
439
440 def _MarkStagedImages(self, image_list):
441 """Update the on-disk flag file with the list of newly staged images.
442
443 This does not check for duplicates against already listed images, and will
444 add any listed images regardless.
445
446 """
447 flag_fname = os.path.join(self._build_dir, self._DONE_FLAG)
448 with open(flag_fname, 'a') as flag_file:
449 flag_file.writelines([image + '\n' for image in image_list])
450
451 def _CheckStagedImages(self, archive_url, static_dir):
452 """Returns a list of images that were already staged.
453
454 Reads the list of images from a flag file, if one is present, and returns
455 after removing duplicates.
456
457 """
458 rel_path, short_build = self.ParseUrl(archive_url)
459 sub_directory = self.GenerateLockTag(rel_path, short_build)
460 flag_fname = os.path.join(static_dir, sub_directory, self._DONE_FLAG)
461 staged_image_list = []
462 # TODO(garnold) make this code immune to race conditions, probably by
463 # acquiring a lock around the file access code.
464 if os.path.isfile(flag_fname):
465 with open(flag_fname) as flag_file:
466 staged_image_list = [image.strip() for image in flag_file.readlines()]
467 return list(set(staged_image_list))