blob: 0b9a0351ce2b2a56241a340a172e28504e8779d6 [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
7import cherrypy
Chris Sosa47a7d4e2012-03-28 11:26:55 -07008import multiprocessing
Chris Sosa9164ca32012-03-28 11:04:50 -07009import os
Frank Farzan37761d12011-12-01 14:29:08 -080010import shutil
11import tempfile
12
13import devserver_util
14
15
16class Downloader(object):
17 """Download images to the devsever.
18
19 Given a URL to a build on the archive server:
20
21 - Determine if the build already exists.
22 - Download and extract the build to a staging directory.
23 - Package autotest tests.
24 - Install components to static dir.
25 """
26
27 def __init__(self, static_dir):
28 self._static_dir = static_dir
Chris Sosa47a7d4e2012-03-28 11:26:55 -070029 self._build_dir = None
30 self._staging_dir = None
31 self._status_queue = multiprocessing.Queue()
32 self._lock_tag = None
33 self._archive_url = None
Frank Farzan37761d12011-12-01 14:29:08 -080034
Chris Sosa9164ca32012-03-28 11:04:50 -070035 @staticmethod
36 def BuildStaged(archive_url, static_dir):
37 """Returns True if the build is already staged."""
38 target, short_build = archive_url.rsplit('/', 2)[-2:]
39 sub_directory = '/'.join([target, short_build])
40 return os.path.isdir(os.path.join(static_dir, sub_directory))
41
Chris Sosa47a7d4e2012-03-28 11:26:55 -070042 def Download(self, archive_url, background=False):
43 """Downloads the given build artifacts defined by the |archive_url|.
44
45 If background is set to True, will return back early before all artifacts
46 have been downloaded. The artifacts that can be backgrounded are all those
47 that are not set as synchronous.
48 """
Chris Sosa9164ca32012-03-28 11:04:50 -070049 # Parse archive_url into target and short_build.
50 # e.g. gs://chromeos-image-archive/{target}/{short_build}
Chris Sosa47a7d4e2012-03-28 11:26:55 -070051 self._archive_url = archive_url.strip('/')
Chris Sosa9164ca32012-03-28 11:04:50 -070052 target, short_build = self._archive_url.rsplit('/', 2)[-2:]
Frank Farzan37761d12011-12-01 14:29:08 -080053
54 # Bind build_dir and staging_dir here so we can tell if we need to do any
55 # cleanup after an exception occurs before build_dir is set.
Chris Sosa9164ca32012-03-28 11:04:50 -070056 self._lock_tag = '/'.join([target, short_build])
Frank Farzan37761d12011-12-01 14:29:08 -080057 try:
58 # Create Dev Server directory for this build and tell other Downloader
59 # instances we have processed this build.
60 try:
Chris Sosa47a7d4e2012-03-28 11:26:55 -070061 self._build_dir = devserver_util.AcquireLock(
62 static_dir=self._static_dir, tag=self._lock_tag)
Frank Farzan37761d12011-12-01 14:29:08 -080063 except devserver_util.DevServerUtilError, e:
Chris Sosa9164ca32012-03-28 11:04:50 -070064 if Downloader.BuildStaged(archive_url, self._static_dir):
65 cherrypy.log(
66 'Build %s has already been processed.' % self._lock_tag,
67 'DOWNLOAD')
68 self._status_queue.put('Success')
69 return 'Success'
70 else:
71 raise
Frank Farzan37761d12011-12-01 14:29:08 -080072
Chris Sosa9164ca32012-03-28 11:04:50 -070073 self._staging_dir = tempfile.mkdtemp(suffix='_'.join([target,
74 short_build]))
Chris Sosa47a7d4e2012-03-28 11:26:55 -070075 cherrypy.log('Gathering download requirements %s' % self._archive_url,
76 'DOWNLOAD')
77 artifacts = devserver_util.GatherArtifactDownloads(
Chris Sosa9164ca32012-03-28 11:04:50 -070078 self._staging_dir, self._archive_url, short_build, self._build_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -070079 devserver_util.PrepareBuildDirectory(self._build_dir)
Frank Farzan37761d12011-12-01 14:29:08 -080080
Chris Sosa47a7d4e2012-03-28 11:26:55 -070081 cherrypy.log('Downloading foreground artifacts from %s' % archive_url,
82 'DOWNLOAD')
83 background_artifacts = []
84 for artifact in artifacts:
85 if artifact.Synchronous():
86 artifact.Download()
87 artifact.Stage()
88 else:
89 background_artifacts.append(artifact)
Frank Farzan37761d12011-12-01 14:29:08 -080090
Chris Sosa47a7d4e2012-03-28 11:26:55 -070091 if background:
92 self._DownloadArtifactsInBackground(background_artifacts)
93 else:
94 self._DownloadArtifactsSerially(background_artifacts)
95
96 self._status_queue.put('Success')
97 except Exception, e:
Frank Farzan37761d12011-12-01 14:29:08 -080098 # Release processing lock, which will remove build components directory
99 # so future runs can retry.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700100 if self._build_dir:
101 devserver_util.ReleaseLock(static_dir=self._static_dir,
102 tag=self._lock_tag)
103
104 self._status_queue.put(e)
105 self._Cleanup()
Frank Farzan37761d12011-12-01 14:29:08 -0800106 raise
Frank Farzan37761d12011-12-01 14:29:08 -0800107
108 return 'Success'
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700109
110 def _Cleanup(self):
111 """Cleans up the staging dir for this downloader instanfce."""
112 if self._staging_dir:
113 cherrypy.log('Cleaning up staging directory %s' % self._staging_dir,
114 'DOWNLOAD')
115 shutil.rmtree(self._staging_dir)
116
117 self._staging_dir = None
118
119 def _DownloadArtifactsSerially(self, artifacts):
120 """Simple function to download all the given artifacts serially."""
121 cherrypy.log('Downloading background artifacts for %s' % self._archive_url,
122 'DOWNLOAD')
123 try:
124 for artifact in artifacts:
125 artifact.Download()
126 artifact.Stage()
127 except Exception, e:
128 self._status_queue.put(e)
129
130 # Release processing lock, which will remove build components directory
131 # so future runs can retry.
132 if self._build_dir:
133 devserver_util.ReleaseLock(static_dir=self._static_dir,
134 tag=self._lock_tag)
135 else:
136 self._status_queue.put('Success')
137 finally:
138 self._Cleanup()
139
140 def _DownloadArtifactsInBackground(self, artifacts):
141 """Downloads |artifacts| in the background and signals when complete."""
142 proc = multiprocessing.Process(target=self._DownloadArtifactsSerially,
143 args=(artifacts,))
Chris Sosab65973e2012-03-29 18:31:02 -0700144 proc.start()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700145
146 def GetStatusOfBackgroundDownloads(self):
147 """Returns the status of the background downloads.
148
149 This commands returns the status of the background downloads and blocks
150 until a status is returned.
151 """
152 status = self._status_queue.get()
153 # In case anyone else is calling.
154 self._status_queue.put(status)
155 # It's possible we received an exception, if so, re-raise it here.
156 if isinstance(status, Exception):
157 raise status
158
159 return status