blob: 3c23c84d8854a1dba1472044973b06021b3b7b4a [file] [log] [blame]
Frank Farzan37761d12011-12-01 14:29:08 -08001#!/usr/bin/python
2#
Chris Sosab82107a2012-03-13 18:43:21 -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
7import cherrypy
Chris Sosab82107a2012-03-13 18:43:21 -07008import multiprocessing
Frank Farzan37761d12011-12-01 14:29:08 -08009import shutil
10import tempfile
11
12import devserver_util
13
14
15class Downloader(object):
16 """Download images to the devsever.
17
18 Given a URL to a build on the archive server:
19
20 - Determine if the build already exists.
21 - Download and extract the build to a staging directory.
22 - Package autotest tests.
23 - Install components to static dir.
24 """
25
26 def __init__(self, static_dir):
27 self._static_dir = static_dir
Chris Sosab82107a2012-03-13 18:43:21 -070028 self._build_dir = None
29 self._staging_dir = None
30 self._status_queue = multiprocessing.Queue()
31 self._lock_tag = None
32 self._archive_url = None
Frank Farzan37761d12011-12-01 14:29:08 -080033
Chris Sosab82107a2012-03-13 18:43:21 -070034 def Download(self, archive_url, background=False):
35 """Downloads the given build artifacts defined by the |archive_url|.
36
37 If background is set to True, will return back early before all artifacts
38 have been downloaded. The artifacts that can be backgrounded are all those
39 that are not set as synchronous.
40 """
Frank Farzan37761d12011-12-01 14:29:08 -080041 # Parse archive_url into board and build.
42 # e.g. gs://chromeos-image-archive/{board}/{build}
Chris Sosab82107a2012-03-13 18:43:21 -070043 self._archive_url = archive_url.strip('/')
44 board, build = self._archive_url.rsplit('/', 2)[-2:]
Frank Farzan37761d12011-12-01 14:29:08 -080045
46 # Bind build_dir and staging_dir here so we can tell if we need to do any
47 # cleanup after an exception occurs before build_dir is set.
Chris Sosab82107a2012-03-13 18:43:21 -070048 self._lock_tag = '/'.join([board, build])
Frank Farzan37761d12011-12-01 14:29:08 -080049 try:
50 # Create Dev Server directory for this build and tell other Downloader
51 # instances we have processed this build.
52 try:
Chris Sosab82107a2012-03-13 18:43:21 -070053 self._build_dir = devserver_util.AcquireLock(
54 static_dir=self._static_dir, tag=self._lock_tag)
Frank Farzan37761d12011-12-01 14:29:08 -080055 except devserver_util.DevServerUtilError, e:
56 cherrypy.log('Refused lock "%s". Assuming build has already been'
Chris Sosab82107a2012-03-13 18:43:21 -070057 'processed: %s' % (self._lock_tag, str(e)), 'DOWNLOAD')
58 self._status_queue.put('Success')
Frank Farzan37761d12011-12-01 14:29:08 -080059 return 'Success'
60
Chris Sosab82107a2012-03-13 18:43:21 -070061 self._staging_dir = tempfile.mkdtemp(suffix='_'.join([board, build]))
62 cherrypy.log('Gathering download requirements %s' % self._archive_url,
63 'DOWNLOAD')
64 artifacts = devserver_util.GatherArtifactDownloads(
65 self._staging_dir, self._archive_url, build, self._build_dir)
66 devserver_util.PrepareBuildDirectory(self._build_dir)
Frank Farzan37761d12011-12-01 14:29:08 -080067
Chris Sosab82107a2012-03-13 18:43:21 -070068 cherrypy.log('Downloading foreground artifacts from %s' % archive_url,
69 'DOWNLOAD')
70 background_artifacts = []
71 for artifact in artifacts:
72 if artifact.Synchronous():
73 artifact.Download()
74 artifact.Stage()
75 else:
76 background_artifacts.append(artifact)
Frank Farzan37761d12011-12-01 14:29:08 -080077
Chris Sosab82107a2012-03-13 18:43:21 -070078 if background:
79 self._DownloadArtifactsInBackground(background_artifacts)
80 else:
81 self._DownloadArtifactsSerially(background_artifacts)
82
83 self._status_queue.put('Success')
84 except Exception, e:
Frank Farzan37761d12011-12-01 14:29:08 -080085 # Release processing lock, which will remove build components directory
86 # so future runs can retry.
Chris Sosab82107a2012-03-13 18:43:21 -070087 if self._build_dir:
88 devserver_util.ReleaseLock(static_dir=self._static_dir,
89 tag=self._lock_tag)
90
91 self._status_queue.put(e)
92 self._Cleanup()
Frank Farzan37761d12011-12-01 14:29:08 -080093 raise
Frank Farzan37761d12011-12-01 14:29:08 -080094
95 return 'Success'
Chris Sosab82107a2012-03-13 18:43:21 -070096
97 def _Cleanup(self):
98 """Cleans up the staging dir for this downloader instanfce."""
99 if self._staging_dir:
100 cherrypy.log('Cleaning up staging directory %s' % self._staging_dir,
101 'DOWNLOAD')
102 shutil.rmtree(self._staging_dir)
103
104 self._staging_dir = None
105
106 def _DownloadArtifactsSerially(self, artifacts):
107 """Simple function to download all the given artifacts serially."""
108 cherrypy.log('Downloading background artifacts for %s' % self._archive_url,
109 'DOWNLOAD')
110 try:
111 for artifact in artifacts:
112 artifact.Download()
113 artifact.Stage()
114 except Exception, e:
115 self._status_queue.put(e)
116
117 # Release processing lock, which will remove build components directory
118 # so future runs can retry.
119 if self._build_dir:
120 devserver_util.ReleaseLock(static_dir=self._static_dir,
121 tag=self._lock_tag)
122 else:
123 self._status_queue.put('Success')
124 finally:
125 self._Cleanup()
126
127 def _DownloadArtifactsInBackground(self, artifacts):
128 """Downloads |artifacts| in the background and signals when complete."""
129 proc = multiprocessing.Process(target=self._DownloadArtifactsSerially,
130 args=(artifacts,))
131 proc.run()
132
133 def GetStatusOfBackgroundDownloads(self):
134 """Returns the status of the background downloads.
135
136 This commands returns the status of the background downloads and blocks
137 until a status is returned.
138 """
139 status = self._status_queue.get()
140 # In case anyone else is calling.
141 self._status_queue.put(status)
142 # It's possible we received an exception, if so, re-raise it here.
143 if isinstance(status, Exception):
144 raise status
145
146 return status