blob: d9271817514cce14080215a9ba538a2238490008 [file] [log] [blame]
Chris Sosa76e44b92013-01-31 12:11:38 -08001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
Frank Farzan37761d12011-12-01 14:29:08 -08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Chris Sosa9164ca32012-03-28 11:04:50 -07005import os
Gilad Arnold0b8c3f32012-09-19 14:35:44 -07006import threading
Frank Farzan37761d12011-12-01 14:29:08 -08007
Chris Sosa76e44b92013-01-31 12:11:38 -08008import build_artifact
Gilad Arnoldc65330c2012-09-20 15:17:48 -07009import common_util
Dan Shiba0e6742013-06-26 17:39:05 -070010import gsutil_util
Gilad Arnoldc65330c2012-09-20 15:17:48 -070011import log_util
Frank Farzan37761d12011-12-01 14:29:08 -080012
13
Gilad Arnoldc65330c2012-09-20 15:17:48 -070014class Downloader(log_util.Loggable):
Chris Sosa76e44b92013-01-31 12:11:38 -080015 """Downloader of images to the devsever.
Frank Farzan37761d12011-12-01 14:29:08 -080016
17 Given a URL to a build on the archive server:
Chris Sosa76e44b92013-01-31 12:11:38 -080018 - Caches that build and the given artifacts onto the devserver.
19 - May also initiate caching of related artifacts in the background.
Frank Farzan37761d12011-12-01 14:29:08 -080020
Chris Sosa76e44b92013-01-31 12:11:38 -080021 Private class members:
22 archive_url: a URL where to download build artifacts from.
23 static_dir: local filesystem directory to store all artifacts.
24 build_dir: the local filesystem directory to store artifacts for the given
25 build defined by the archive_url.
Frank Farzan37761d12011-12-01 14:29:08 -080026 """
27
Alex Millera44d5022012-07-27 11:34:16 -070028 # This filename must be kept in sync with clean_staged_images.py
29 _TIMESTAMP_FILENAME = 'staged.timestamp'
Chris Masonea22d9382012-05-18 12:38:51 -070030
Chris Sosa76e44b92013-01-31 12:11:38 -080031 def __init__(self, static_dir, archive_url):
32 super(Downloader, self).__init__()
33 self._archive_url = archive_url
Frank Farzan37761d12011-12-01 14:29:08 -080034 self._static_dir = static_dir
Chris Sosa76e44b92013-01-31 12:11:38 -080035 self._build_dir = Downloader.GetBuildDir(static_dir, archive_url)
Chris Masone816e38c2012-05-02 12:22:36 -070036
37 @staticmethod
Chris Sosacde6bf42012-05-31 18:36:39 -070038 def ParseUrl(archive_url):
Chris Sosa76e44b92013-01-31 12:11:38 -080039 """Parses archive_url into rel_path and build.
Chris Masone816e38c2012-05-02 12:22:36 -070040
Chris Sosa76e44b92013-01-31 12:11:38 -080041 Parses archive_url into rel_path and build e.g.
42 gs://chromeos-image-archive/{rel_path}/{build}.
43
44 Args:
45 archive_url: a URL at which build artifacts are archived.
46
47 Returns:
48 A tuple of (build relative path, short build name)
Chris Masone816e38c2012-05-02 12:22:36 -070049 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070050 # The archive_url is of the form gs://server/[some_path/target]/...]/build
51 # This function discards 'gs://server/' and extracts the [some_path/target]
Chris Sosa76e44b92013-01-31 12:11:38 -080052 # as rel_path and the build as build.
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070053 sub_url = archive_url.partition('://')[2]
54 split_sub_url = sub_url.split('/')
55 rel_path = '/'.join(split_sub_url[1:-1])
Chris Sosa76e44b92013-01-31 12:11:38 -080056 build = split_sub_url[-1]
57 return rel_path, build
Chris Masone816e38c2012-05-02 12:22:36 -070058
59 @staticmethod
Chris Sosa76e44b92013-01-31 12:11:38 -080060 def GetBuildDir(static_dir, archive_url):
61 """Returns the path to where the artifacts will be staged.
Chris Masone816e38c2012-05-02 12:22:36 -070062
Chris Sosa76e44b92013-01-31 12:11:38 -080063 Args:
64 static_dir: The base static dir that will be used.
65 archive_url: The gs path to the archive url.
Chris Masone816e38c2012-05-02 12:22:36 -070066 """
Chris Sosa76e44b92013-01-31 12:11:38 -080067 # Parse archive_url into rel_path (contains the build target) and
68 # build e.g. gs://chromeos-image-archive/{rel_path}/{build}.
69 rel_path, build = Downloader.ParseUrl(archive_url)
70 return os.path.join(static_dir, rel_path, build)
Frank Farzan37761d12011-12-01 14:29:08 -080071
Chris Sosa9164ca32012-03-28 11:04:50 -070072 @staticmethod
Alex Millera44d5022012-07-27 11:34:16 -070073 def _TouchTimestampForStaged(directory_path):
74 file_name = os.path.join(directory_path, Downloader._TIMESTAMP_FILENAME)
75 # Easiest python version of |touch file_name|
76 with file(file_name, 'a'):
77 os.utime(file_name, None)
78
Dan Shiba0e6742013-06-26 17:39:05 -070079 @staticmethod
80 def _TryRemoveStageDir(directory_path):
81 """If download failed with GSUtilError, try to remove the stage dir.
82
83 If the download attempt failed with GSUtilError and staged.timestamp is the
84 only file in that directory. The build could be non-existing, and the
85 directory should be removed.
86
87 @param directory_path: directory used to stage the image.
88
89 """
90 file_name = os.path.join(directory_path, Downloader._TIMESTAMP_FILENAME)
91 if os.path.exists(file_name) and len(os.listdir(directory_path)) == 1:
92 os.remove(file_name)
93 os.rmdir(directory_path)
94
Dan Shif8eb0d12013-08-01 17:52:06 -070095 def Download(self, artifacts, async=False):
Chris Sosa76e44b92013-01-31 12:11:38 -080096 """Downloads and caches the |artifacts|.
Chris Sosa9164ca32012-03-28 11:04:50 -070097
Chris Sosa76e44b92013-01-31 12:11:38 -080098 Downloads and caches the |artifacts|. Returns once these
99 are present on the devserver. A call to this will attempt to cache
100 non-specified artifacts in the background following the principle of
101 spatial locality.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700102
Dan Shif8eb0d12013-08-01 17:52:06 -0700103 @params artifacts: A list of artifact names that correspond to artifacts to
104 stage.
105 @params async: True to return without waiting for download to complete.
106
Gilad Arnold6f99b982012-09-12 10:49:40 -0700107 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800108 common_util.MkDirP(self._build_dir)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700109
Chris Sosa76e44b92013-01-31 12:11:38 -0800110 # We are doing some work on this build -- let's touch it to indicate that
111 # we shouldn't be cleaning it up anytime soon.
112 Downloader._TouchTimestampForStaged(self._build_dir)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700113
Chris Sosa76e44b92013-01-31 12:11:38 -0800114 # Create factory to create build_artifacts from artifact names.
115 build = self.ParseUrl(self._archive_url)[1]
116 factory = build_artifact.ArtifactFactory(self._build_dir, self._archive_url,
117 artifacts, build)
118 background_artifacts = factory.OptionalArtifacts()
119 if background_artifacts:
120 self._DownloadArtifactsInBackground(background_artifacts)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700121
Chris Sosa76e44b92013-01-31 12:11:38 -0800122 required_artifacts = factory.RequiredArtifacts()
123 str_repr = [str(a) for a in required_artifacts]
124 self._Log('Downloading artifacts %s.', ' '.join(str_repr))
Dan Shiba0e6742013-06-26 17:39:05 -0700125 try:
Dan Shif8eb0d12013-08-01 17:52:06 -0700126 if async:
127 self._DownloadArtifactsInBackground(required_artifacts)
128 else:
129 self._DownloadArtifactsSerially(required_artifacts, no_wait=True)
Dan Shiba0e6742013-06-26 17:39:05 -0700130 except gsutil_util.GSUtilError:
131 Downloader._TryRemoveStageDir(self._build_dir)
132 raise
Chris Sosa76e44b92013-01-31 12:11:38 -0800133
Dan Shif8eb0d12013-08-01 17:52:06 -0700134 def IsStaged(self, artifacts):
135 """Check if all artifacts have been downloaded.
136
137 @param artifacts: A list of artifacts to be checked.
138 @returns: True if all artifacts are staged.
139
140 """
141 # Create factory to create build_artifacts from artifact names.
142 build = self.ParseUrl(self._archive_url)[1]
143 factory = build_artifact.ArtifactFactory(self._build_dir, self._archive_url,
144 artifacts, build)
145 required_artifacts = factory.RequiredArtifacts()
146 return all([artifact.ArtifactStaged() for artifact in required_artifacts])
147
Chris Sosa76e44b92013-01-31 12:11:38 -0800148 def _DownloadArtifactsSerially(self, artifacts, no_wait):
149 """Simple function to download all the given artifacts serially.
150
Dan Shif8eb0d12013-08-01 17:52:06 -0700151 @param artifacts: A list of build_artifact.BuildArtifact instances to
152 download.
153 @param no_wait: If True, don't block waiting for artifact to exist if we
154 fail to immediately find it.
155
Gilad Arnold6f99b982012-09-12 10:49:40 -0700156 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800157 for artifact in artifacts:
158 artifact.Process(no_wait)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700159
Chris Sosa76e44b92013-01-31 12:11:38 -0800160 def _DownloadArtifactsInBackground(self, artifacts):
161 """Downloads |artifacts| in the background.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700162
Chris Sosa76e44b92013-01-31 12:11:38 -0800163 Downloads |artifacts| in the background. As these are backgrounded
164 artifacts, they are done best effort and may not exist.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700165
Chris Sosa76e44b92013-01-31 12:11:38 -0800166 Args:
167 artifacts: List of build_artifact.BuildArtifact instances to download.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700168 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800169 self._Log('Invoking background download of artifacts for %r', artifacts)
170 thread = threading.Thread(target=self._DownloadArtifactsSerially,
171 args=(artifacts, False))
172 thread.start()