blob: 536c6a83b99fcc07b8a9181924dc2cbed8868921 [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
14import devserver_util
15
16
17class Downloader(object):
18 """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
Chris Masonea22d9382012-05-18 12:38:51 -070028 _LOG_TAG = 'DOWNLOAD'
Alex Millera44d5022012-07-27 11:34:16 -070029 # This filename must be kept in sync with clean_staged_images.py
30 _TIMESTAMP_FILENAME = 'staged.timestamp'
Chris Masonea22d9382012-05-18 12:38:51 -070031
Frank Farzan37761d12011-12-01 14:29:08 -080032 def __init__(self, static_dir):
33 self._static_dir = static_dir
Chris Sosa47a7d4e2012-03-28 11:26:55 -070034 self._build_dir = None
35 self._staging_dir = None
Gilad Arnold0b8c3f32012-09-19 14:35:44 -070036 self._status_queue = Queue.Queue(maxsize=1)
Chris Sosa47a7d4e2012-03-28 11:26:55 -070037 self._lock_tag = None
Chris Masone816e38c2012-05-02 12:22:36 -070038
39 @staticmethod
Chris Sosacde6bf42012-05-31 18:36:39 -070040 def ParseUrl(archive_url):
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070041 """Parse archive_url into rel_path and short_build
42 e.g. gs://chromeos-image-archive/{rel_path}/{short_build}
Chris Masone816e38c2012-05-02 12:22:36 -070043
44 @param archive_url: a URL at which build artifacts are archived.
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070045 @return a tuple of (build relative path, short build name)
Chris Masone816e38c2012-05-02 12:22:36 -070046 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070047 # The archive_url is of the form gs://server/[some_path/target]/...]/build
48 # This function discards 'gs://server/' and extracts the [some_path/target]
49 # as rel_path and the build as short_build.
50 sub_url = archive_url.partition('://')[2]
51 split_sub_url = sub_url.split('/')
52 rel_path = '/'.join(split_sub_url[1:-1])
53 short_build = split_sub_url[-1]
54 return rel_path, short_build
Chris Masone816e38c2012-05-02 12:22:36 -070055
56 @staticmethod
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070057 def GenerateLockTag(rel_path, short_build):
58 """Generate a name for a lock scoped to this rel_path/build pair.
Chris Masone816e38c2012-05-02 12:22:36 -070059
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070060 @param rel_path: the relative path for the build.
Chris Masone816e38c2012-05-02 12:22:36 -070061 @param short_build: short build name
62 @return a name to use with AcquireLock that will scope the lock.
63 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070064 return '/'.join([rel_path, short_build])
Frank Farzan37761d12011-12-01 14:29:08 -080065
Chris Sosa9164ca32012-03-28 11:04:50 -070066 @staticmethod
Alex Millera44d5022012-07-27 11:34:16 -070067 def _TouchTimestampForStaged(directory_path):
68 file_name = os.path.join(directory_path, Downloader._TIMESTAMP_FILENAME)
69 # Easiest python version of |touch file_name|
70 with file(file_name, 'a'):
71 os.utime(file_name, None)
72
73 @staticmethod
Chris Sosa9164ca32012-03-28 11:04:50 -070074 def BuildStaged(archive_url, static_dir):
75 """Returns True if the build is already staged."""
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070076 rel_path, short_build = Downloader.ParseUrl(archive_url)
77 sub_directory = Downloader.GenerateLockTag(rel_path, short_build)
Alex Millera44d5022012-07-27 11:34:16 -070078 directory_path = os.path.join(static_dir, sub_directory)
79 exists = os.path.isdir(directory_path)
80 # If the build exists, then touch the timestamp to tell
81 # clean_stages_images.py that we're using this build.
82 if exists:
83 Downloader._TouchTimestampForStaged(directory_path)
84 return exists
Chris Sosa9164ca32012-03-28 11:04:50 -070085
Chris Sosa47a7d4e2012-03-28 11:26:55 -070086 def Download(self, archive_url, background=False):
87 """Downloads the given build artifacts defined by the |archive_url|.
88
89 If background is set to True, will return back early before all artifacts
90 have been downloaded. The artifacts that can be backgrounded are all those
91 that are not set as synchronous.
Chris Masone816e38c2012-05-02 12:22:36 -070092
93 TODO: refactor this into a common Download method, once unit tests are
94 fixed up to make iterating on the code easier.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070095 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070096 # Parse archive_url into rel_path (contains the build target) and
97 # short_build.
98 # e.g. gs://chromeos-image-archive/{rel_path}/{short_build}
99 rel_path, short_build = self.ParseUrl(archive_url)
Chris Sosacde6bf42012-05-31 18:36:39 -0700100 # This should never happen. The Devserver should only try to call this
101 # method if no previous downloads have been staged for this archive_url.
102 assert not Downloader.BuildStaged(archive_url, self._static_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800103 # Bind build_dir and staging_dir here so we can tell if we need to do any
104 # cleanup after an exception occurs before build_dir is set.
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700105 self._lock_tag = self.GenerateLockTag(rel_path, short_build)
Frank Farzan37761d12011-12-01 14:29:08 -0800106 try:
107 # Create Dev Server directory for this build and tell other Downloader
Gilad Arnold5174ca22012-09-12 10:49:09 -0700108 # instances we have processed this build. Note that during normal
109 # execution, this lock is only released in the actual downloading
110 # procedure called below.
Chris Masone816e38c2012-05-02 12:22:36 -0700111 self._build_dir = devserver_util.AcquireLock(
112 static_dir=self._static_dir, tag=self._lock_tag)
Frank Farzan37761d12011-12-01 14:29:08 -0800113
Yu-Ju Hong1a83a712012-06-27 09:11:34 -0700114 # Replace '/' with '_' in rel_path because it may contain multiple levels
115 # which would not be qualified as part of the suffix.
116 self._staging_dir = tempfile.mkdtemp(suffix='_'.join(
Chris Sosaf0975642012-06-29 13:53:42 -0700117 [rel_path.replace('/', '_'), short_build]))
Alex Millera44d5022012-07-27 11:34:16 -0700118 Downloader._TouchTimestampForStaged(self._staging_dir)
Chris Masone816e38c2012-05-02 12:22:36 -0700119 cherrypy.log('Gathering download requirements %s' % archive_url,
Chris Masonea22d9382012-05-18 12:38:51 -0700120 self._LOG_TAG)
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)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700123 devserver_util.PrepareBuildDirectory(self._build_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800124
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700125 cherrypy.log('Downloading foreground artifacts from %s' % archive_url,
Chris Masonea22d9382012-05-18 12:38:51 -0700126 self._LOG_TAG)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700127 background_artifacts = []
128 for artifact in artifacts:
129 if artifact.Synchronous():
130 artifact.Download()
131 artifact.Stage()
132 else:
133 background_artifacts.append(artifact)
Frank Farzan37761d12011-12-01 14:29:08 -0800134
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700135 if background:
Chris Sosacde6bf42012-05-31 18:36:39 -0700136 self._DownloadArtifactsInBackground(background_artifacts)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700137 else:
138 self._DownloadArtifactsSerially(background_artifacts)
139
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700140 except Exception, e:
Frank Farzan37761d12011-12-01 14:29:08 -0800141 # Release processing lock, which will remove build components directory
142 # so future runs can retry.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700143 if self._build_dir:
144 devserver_util.ReleaseLock(static_dir=self._static_dir,
Gilad Arnold5174ca22012-09-12 10:49:09 -0700145 tag=self._lock_tag, destroy=True)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700146
147 self._status_queue.put(e)
148 self._Cleanup()
Frank Farzan37761d12011-12-01 14:29:08 -0800149 raise
Frank Farzan37761d12011-12-01 14:29:08 -0800150 return 'Success'
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700151
152 def _Cleanup(self):
153 """Cleans up the staging dir for this downloader instanfce."""
154 if self._staging_dir:
155 cherrypy.log('Cleaning up staging directory %s' % self._staging_dir,
Chris Masonea22d9382012-05-18 12:38:51 -0700156 self._LOG_TAG)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700157 shutil.rmtree(self._staging_dir)
158
159 self._staging_dir = None
160
161 def _DownloadArtifactsSerially(self, artifacts):
162 """Simple function to download all the given artifacts serially."""
Gilad Arnold0b8c3f32012-09-19 14:35:44 -0700163 cherrypy.log('Downloading artifacts serially.', self._LOG_TAG)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700164 try:
165 for artifact in artifacts:
166 artifact.Download()
167 artifact.Stage()
168 except Exception, e:
169 self._status_queue.put(e)
170
171 # Release processing lock, which will remove build components directory
172 # so future runs can retry.
173 if self._build_dir:
174 devserver_util.ReleaseLock(static_dir=self._static_dir,
Gilad Arnold5174ca22012-09-12 10:49:09 -0700175 tag=self._lock_tag, destroy=True)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700176 else:
Gilad Arnold5174ca22012-09-12 10:49:09 -0700177 # Release processing lock, keeping directory intact.
178 if self._build_dir:
179 devserver_util.ReleaseLock(static_dir=self._static_dir,
180 tag=self._lock_tag)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700181 self._status_queue.put('Success')
182 finally:
183 self._Cleanup()
184
Chris Sosacde6bf42012-05-31 18:36:39 -0700185 def _DownloadArtifactsInBackground(self, artifacts):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700186 """Downloads |artifacts| in the background and signals when complete."""
Gilad Arnold0b8c3f32012-09-19 14:35:44 -0700187 cherrypy.log('Invoking background download of artifacts', self._LOG_TAG)
188 thread = threading.Thread(target=self._DownloadArtifactsSerially,
189 args=(artifacts,))
190 thread.start()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700191
Gilad Arnold6f99b982012-09-12 10:49:40 -0700192 def GatherArtifactDownloads(self, main_staging_dir, archive_url, build_dir,
193 short_build):
Chris Masone816e38c2012-05-02 12:22:36 -0700194 """Wrapper around devserver_util.GatherArtifactDownloads().
195
196 The wrapper allows mocking and overriding in derived classes.
197 """
198 return devserver_util.GatherArtifactDownloads(main_staging_dir, archive_url,
Gilad Arnold6f99b982012-09-12 10:49:40 -0700199 build_dir, short_build)
Chris Masone816e38c2012-05-02 12:22:36 -0700200
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700201 def GetStatusOfBackgroundDownloads(self):
202 """Returns the status of the background downloads.
203
204 This commands returns the status of the background downloads and blocks
205 until a status is returned.
206 """
207 status = self._status_queue.get()
208 # In case anyone else is calling.
209 self._status_queue.put(status)
Alex Miller92ed3592012-08-15 16:27:46 -0700210 # If someone is curious about the status of a build, then we should
211 # probably keep it around for a bit longer.
212 if os.path.exists(self._staging_dir):
213 Downloader._TouchTimestampForStaged(self._staging_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700214 # It's possible we received an exception, if so, re-raise it here.
215 if isinstance(status, Exception):
216 raise status
217
218 return status
Chris Masone816e38c2012-05-02 12:22:36 -0700219
220
221class SymbolDownloader(Downloader):
222 """Download and stage debug symbols for a build on the devsever.
223
224 Given a URL to a build on the archive server:
225
226 - Determine if the build already exists.
227 - Download and extract the debug symbols to a staging directory.
228 - Install symbols to static dir.
229 """
230
231 _DONE_FLAG = 'done'
Chris Masonea22d9382012-05-18 12:38:51 -0700232 _LOG_TAG = 'SYMBOL_DOWNLOAD'
Chris Masone816e38c2012-05-02 12:22:36 -0700233
234 @staticmethod
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700235 def GenerateLockTag(rel_path, short_build):
236 return '/'.join([rel_path, short_build, 'symbols'])
Chris Masone816e38c2012-05-02 12:22:36 -0700237
Chris Sosacde6bf42012-05-31 18:36:39 -0700238 def Download(self, archive_url, _background=False):
Chris Masone816e38c2012-05-02 12:22:36 -0700239 """Downloads debug symbols for the build defined by the |archive_url|.
240
241 The symbols will be downloaded synchronously
242 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700243 # Parse archive_url into rel_path (contains the build target) and
244 # short_build.
245 # e.g. gs://chromeos-image-archive/{rel_path}/{short_build}
246 rel_path, short_build = self.ParseUrl(archive_url)
Chris Masone816e38c2012-05-02 12:22:36 -0700247
248 # Bind build_dir and staging_dir here so we can tell if we need to do any
249 # cleanup after an exception occurs before build_dir is set.
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700250 self._lock_tag = self.GenerateLockTag(rel_path, short_build)
Chris Masone816e38c2012-05-02 12:22:36 -0700251 if self.SymbolsStaged(archive_url, self._static_dir):
252 cherrypy.log(
253 'Symbols for build %s have already been staged.' % self._lock_tag,
Chris Masonea22d9382012-05-18 12:38:51 -0700254 self._LOG_TAG)
Chris Masone816e38c2012-05-02 12:22:36 -0700255 return 'Success'
256
257 try:
258 # Create Dev Server directory for this build and tell other Downloader
259 # instances we have processed this build.
260 self._build_dir = devserver_util.AcquireLock(
261 static_dir=self._static_dir, tag=self._lock_tag)
262
Yu-Ju Hong1a83a712012-06-27 09:11:34 -0700263 # Replace '/' with '_' in rel_path because it may contain multiple levels
264 # which would not be qualified as part of the suffix.
265 self._staging_dir = tempfile.mkdtemp(suffix='_'.join(
Chris Sosaf0975642012-06-29 13:53:42 -0700266 [rel_path.replace('/', '_'), short_build]))
Chris Masone816e38c2012-05-02 12:22:36 -0700267 cherrypy.log('Downloading debug symbols from %s' % archive_url,
Chris Masonea22d9382012-05-18 12:38:51 -0700268 self._LOG_TAG)
Chris Masone816e38c2012-05-02 12:22:36 -0700269
270 [symbol_artifact] = self.GatherArtifactDownloads(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700271 self._staging_dir, archive_url, self._static_dir)
Chris Masone816e38c2012-05-02 12:22:36 -0700272 symbol_artifact.Download()
273 symbol_artifact.Stage()
Chris Masonea22d9382012-05-18 12:38:51 -0700274 self.MarkSymbolsStaged()
Chris Masone816e38c2012-05-02 12:22:36 -0700275
276 except Exception:
277 # Release processing "lock", which will indicate to future runs that we
278 # did not succeed, and so they should try again.
279 if self._build_dir:
280 devserver_util.ReleaseLock(static_dir=self._static_dir,
Gilad Arnold5174ca22012-09-12 10:49:09 -0700281 tag=self._lock_tag, destroy=True)
282
Chris Masone816e38c2012-05-02 12:22:36 -0700283 raise
Gilad Arnold5174ca22012-09-12 10:49:09 -0700284 else:
285 # Release processing "lock", keeping directory intact.
286 if self._build_dir:
287 devserver_util.ReleaseLock(static_dir=self._static_dir,
288 tag=self._lock_tag)
Chris Masonea22d9382012-05-18 12:38:51 -0700289 finally:
290 self._Cleanup()
Chris Sosa4d9c4d42012-06-29 15:23:23 -0700291
Chris Masone816e38c2012-05-02 12:22:36 -0700292 return 'Success'
293
Gilad Arnold6f99b982012-09-12 10:49:40 -0700294 def GatherArtifactDownloads(self, temp_download_dir, archive_url, static_dir,
295 short_build=None):
Chris Masone816e38c2012-05-02 12:22:36 -0700296 """Call SymbolDownloader-appropriate artifact gathering method.
297
298 @param temp_download_dir: the tempdir into which we're downloading artifacts
299 prior to staging them.
300 @param archive_url: the google storage url of the bucket where the debug
301 symbols for the desired build are stored.
Chris Masone816e38c2012-05-02 12:22:36 -0700302 @param staging_dir: the dir into which to stage the symbols
Gilad Arnold6f99b982012-09-12 10:49:40 -0700303 @param short_build: (ignored)
Chris Masone816e38c2012-05-02 12:22:36 -0700304
305 @return an iterable of one DebugTarball pointing to the right debug symbols.
306 This is an iterable so that it's similar to GatherArtifactDownloads.
307 Also, it's possible that someday we might have more than one.
308 """
309 return devserver_util.GatherSymbolArtifactDownloads(temp_download_dir,
310 archive_url,
311 static_dir)
312
313 def MarkSymbolsStaged(self):
314 """Puts a flag file on disk to signal that symbols are staged."""
315 with open(os.path.join(self._build_dir, self._DONE_FLAG), 'w') as flag:
316 flag.write(self._DONE_FLAG)
317
318 def SymbolsStaged(self, archive_url, static_dir):
319 """Returns True if the build is already staged."""
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -0700320 rel_path, short_build = self.ParseUrl(archive_url)
321 sub_directory = self.GenerateLockTag(rel_path, short_build)
Chris Masone816e38c2012-05-02 12:22:36 -0700322 return os.path.isfile(os.path.join(static_dir,
323 sub_directory,
324 self._DONE_FLAG))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700325
326
327class ImagesDownloader(Downloader):
328 """Download and stage prebuilt images for a given build.
329
330 Given a URL to a build on the archive server and a list of images:
331 - Determine which images have not been staged yet.
332 - Download the image archive.
333 - Extract missing images to the staging directory.
334
335 """
336 _DONE_FLAG = 'staged'
337 _LOG_TAG = 'IMAGE_DOWNLOAD'
338
339 # List of images to be staged; empty (default) means all.
340 _image_list = []
341
342 # A mapping from test image types to their archived file names.
343 _IMAGE_TO_FNAME = {
344 'test': 'chromiumos_test_image.bin',
345 'base': 'chromiumos_base_image.bin',
346 'recovery': 'recovery_image.bin',
347 }
348
349 @staticmethod
350 def GenerateLockTag(rel_path, short_build):
351 return os.path.join('images', rel_path, short_build)
352
353 def Download(self, archive_url, image_list, _background=False):
354 """Downloads images in |image_list| from the build defined by |archive_url|.
355
356 Download happens synchronously. |images| may include any of those in
357 self._IMAGE_TO_FNAME.keys().
358
359 """
360 # Check correctness of image list, remove duplicates.
361 if not image_list:
362 raise DevServerError('empty list of image types')
363 invalid_images = list(set(image_list) - set(self._IMAGE_TO_FNAME.keys()))
364 if invalid_images:
365 raise DevServerError('invalid images requested: %s' % invalid_images)
366 image_list = list(set(image_list))
367
368 # Parse archive_url into rel_path (contains the build target) and
369 # short_build.
370 # e.g. gs://chromeos-image-archive/{rel_path}/{short_build}
371 rel_path, short_build = self.ParseUrl(archive_url)
372
373 # Bind build_dir and staging_dir here so we can tell if we need to do any
374 # cleanup after an exception occurs before build_dir is set.
375 self._lock_tag = self.GenerateLockTag(rel_path, short_build)
376 staged_image_list = self._CheckStagedImages(archive_url, self._static_dir)
377 unstaged_image_list = [image for image in image_list
378 if image not in staged_image_list]
379 if not unstaged_image_list:
380 cherrypy.log(
381 'All requested images (%s) for build %s have already been staged.' %
382 (devserver_util.CommaSeparatedList(image_list, is_quoted=True)
383 if image_list else 'none',
384 self._lock_tag),
385 self._LOG_TAG)
386 return 'Success'
387
388 cherrypy.log(
389 'Image(s) %s for build %s will be staged' %
390 (devserver_util.CommaSeparatedList(unstaged_image_list, is_quoted=True),
391 self._lock_tag),
392 self._LOG_TAG)
393 self._image_list = unstaged_image_list
394
395 try:
396 # Create a static target directory and lock it for processing. We permit
397 # the directory to preexist, as different images might be downloaded and
398 # extracted at different times.
399 self._build_dir = devserver_util.AcquireLock(
400 static_dir=self._static_dir, tag=self._lock_tag,
401 create_once=False)
402
403 # Replace '/' with '_' in rel_path because it may contain multiple levels
404 # which would not be qualified as part of the suffix.
405 self._staging_dir = tempfile.mkdtemp(suffix='_'.join(
406 [rel_path.replace('/', '_'), short_build]))
407 cherrypy.log('Downloading image archive from %s' % archive_url,
408 self._LOG_TAG)
409 dest_static_dir = os.path.join(self._static_dir, self._lock_tag)
410 [image_archive_artifact] = self.GatherArtifactDownloads(
411 self._staging_dir, archive_url, dest_static_dir)
412 image_archive_artifact.Download()
413 cherrypy.log('Staging images to %s' % dest_static_dir)
414 image_archive_artifact.Stage()
415 self._MarkStagedImages(unstaged_image_list)
416
417 except Exception:
418 # Release processing "lock", which will indicate to future runs that we
419 # did not succeed, and so they should try again.
420 if self._build_dir:
421 devserver_util.ReleaseLock(static_dir=self._static_dir,
422 tag=self._lock_tag, destroy=True)
423 raise
424 else:
425 # Release processing "lock", keeping directory intact.
426 if self._build_dir:
427 devserver_util.ReleaseLock(static_dir=self._static_dir,
428 tag=self._lock_tag)
429 finally:
430 self._Cleanup()
431
432 return 'Success'
433
434 def GatherArtifactDownloads(self, temp_download_dir, archive_url, static_dir,
435 short_build=None):
436 """Call appropriate artifact gathering method.
437
438 Args:
439 temp_download_dir: temporary directory for downloading artifacts to
440 archive_url: URI to the bucket where image archive is stored
441 staging_dir: directory into which to stage extracted images
442 short_build: (ignored)
443 Returns:
444 list of downloadable artifacts (of type Zipfile), currently containing a
445 single object, configured for extracting a predetermined list of images
446 """
447 return devserver_util.GatherImageArchiveArtifactDownloads(
448 temp_download_dir, archive_url, static_dir,
449 [self._IMAGE_TO_FNAME[image] for image in self._image_list])
450
451 def _MarkStagedImages(self, image_list):
452 """Update the on-disk flag file with the list of newly staged images.
453
454 This does not check for duplicates against already listed images, and will
455 add any listed images regardless.
456
457 """
458 flag_fname = os.path.join(self._build_dir, self._DONE_FLAG)
459 with open(flag_fname, 'a') as flag_file:
460 flag_file.writelines([image + '\n' for image in image_list])
461
462 def _CheckStagedImages(self, archive_url, static_dir):
463 """Returns a list of images that were already staged.
464
465 Reads the list of images from a flag file, if one is present, and returns
466 after removing duplicates.
467
468 """
469 rel_path, short_build = self.ParseUrl(archive_url)
470 sub_directory = self.GenerateLockTag(rel_path, short_build)
471 flag_fname = os.path.join(static_dir, sub_directory, self._DONE_FLAG)
472 staged_image_list = []
473 # TODO(garnold) make this code immune to race conditions, probably by
474 # acquiring a lock around the file access code.
475 if os.path.isfile(flag_fname):
476 with open(flag_fname) as flag_file:
477 staged_image_list = [image.strip() for image in flag_file.readlines()]
478 return list(set(staged_image_list))