blob: 99988e1295af5be0da58b9cadc753311e9e4c98f [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
Chris Sosa47a7d4e2012-03-28 11:26:55 -070096 except Exception, e:
Frank Farzan37761d12011-12-01 14:29:08 -080097 # Release processing lock, which will remove build components directory
98 # so future runs can retry.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070099 if self._build_dir:
100 devserver_util.ReleaseLock(static_dir=self._static_dir,
101 tag=self._lock_tag)
102
103 self._status_queue.put(e)
104 self._Cleanup()
Frank Farzan37761d12011-12-01 14:29:08 -0800105 raise
Frank Farzan37761d12011-12-01 14:29:08 -0800106
107 return 'Success'
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700108
109 def _Cleanup(self):
110 """Cleans up the staging dir for this downloader instanfce."""
111 if self._staging_dir:
112 cherrypy.log('Cleaning up staging directory %s' % self._staging_dir,
113 'DOWNLOAD')
114 shutil.rmtree(self._staging_dir)
115
116 self._staging_dir = None
117
118 def _DownloadArtifactsSerially(self, artifacts):
119 """Simple function to download all the given artifacts serially."""
120 cherrypy.log('Downloading background artifacts for %s' % self._archive_url,
121 'DOWNLOAD')
122 try:
123 for artifact in artifacts:
124 artifact.Download()
125 artifact.Stage()
126 except Exception, e:
127 self._status_queue.put(e)
128
129 # Release processing lock, which will remove build components directory
130 # so future runs can retry.
131 if self._build_dir:
132 devserver_util.ReleaseLock(static_dir=self._static_dir,
133 tag=self._lock_tag)
134 else:
135 self._status_queue.put('Success')
136 finally:
137 self._Cleanup()
138
139 def _DownloadArtifactsInBackground(self, artifacts):
140 """Downloads |artifacts| in the background and signals when complete."""
141 proc = multiprocessing.Process(target=self._DownloadArtifactsSerially,
142 args=(artifacts,))
Chris Sosab65973e2012-03-29 18:31:02 -0700143 proc.start()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700144
145 def GetStatusOfBackgroundDownloads(self):
146 """Returns the status of the background downloads.
147
148 This commands returns the status of the background downloads and blocks
149 until a status is returned.
150 """
151 status = self._status_queue.get()
152 # In case anyone else is calling.
153 self._status_queue.put(status)
154 # It's possible we received an exception, if so, re-raise it here.
155 if isinstance(status, Exception):
156 raise status
157
158 return status