blob: e8e67f0636abd3dd8eb78b5ccc8c6e1fe1fcfc19 [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
Chris Sosa6b0c6172013-08-05 17:01:33 -070095 def Download(self, artifacts, files, 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
Chris Sosa6b0c6172013-08-05 17:01:33 -0700103 artifacts: A list of artifact names that correspond to
104 artifacts defined in artifact_info.py to stage.
105 files: A list of filenames to stage from an archive_url.
106 async: If True, return without waiting for download to complete.
Dan Shif8eb0d12013-08-01 17:52:06 -0700107
Gilad Arnold6f99b982012-09-12 10:49:40 -0700108 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800109 common_util.MkDirP(self._build_dir)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700110
Chris Sosa76e44b92013-01-31 12:11:38 -0800111 # We are doing some work on this build -- let's touch it to indicate that
112 # we shouldn't be cleaning it up anytime soon.
113 Downloader._TouchTimestampForStaged(self._build_dir)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700114
Chris Sosa76e44b92013-01-31 12:11:38 -0800115 # Create factory to create build_artifacts from artifact names.
116 build = self.ParseUrl(self._archive_url)[1]
Chris Sosa6b0c6172013-08-05 17:01:33 -0700117 factory = build_artifact.ArtifactFactory(
118 self._build_dir, self._archive_url, artifacts, files,
119 build)
Chris Sosa76e44b92013-01-31 12:11:38 -0800120 background_artifacts = factory.OptionalArtifacts()
121 if background_artifacts:
122 self._DownloadArtifactsInBackground(background_artifacts)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700123
Chris Sosa76e44b92013-01-31 12:11:38 -0800124 required_artifacts = factory.RequiredArtifacts()
125 str_repr = [str(a) for a in required_artifacts]
126 self._Log('Downloading artifacts %s.', ' '.join(str_repr))
Dan Shie37f8fe2013-08-09 16:10:29 -0700127
Dan Shiba0e6742013-06-26 17:39:05 -0700128 try:
Dan Shif8eb0d12013-08-01 17:52:06 -0700129 if async:
Dan Shie37f8fe2013-08-09 16:10:29 -0700130 # Make sure all artifacts exist before starting downloading in a new
131 # thread. This prevents caller from waiting indefinitely for any
132 # nonexistent artifact.
133 for artifact in required_artifacts:
134 artifact.WaitForArtifactToExist(timeout=10)
Dan Shif8eb0d12013-08-01 17:52:06 -0700135 self._DownloadArtifactsInBackground(required_artifacts)
136 else:
137 self._DownloadArtifactsSerially(required_artifacts, no_wait=True)
Dan Shiba0e6742013-06-26 17:39:05 -0700138 except gsutil_util.GSUtilError:
139 Downloader._TryRemoveStageDir(self._build_dir)
140 raise
Chris Sosa76e44b92013-01-31 12:11:38 -0800141
Chris Sosa6b0c6172013-08-05 17:01:33 -0700142 def IsStaged(self, artifacts, files):
Dan Shif8eb0d12013-08-01 17:52:06 -0700143 """Check if all artifacts have been downloaded.
144
Chris Sosa6b0c6172013-08-05 17:01:33 -0700145 artifacts: A list of artifact names that correspond to
146 artifacts defined in artifact_info.py to stage.
147 files: A list of filenames to stage from an archive_url.
Dan Shif8eb0d12013-08-01 17:52:06 -0700148 @returns: True if all artifacts are staged.
149
150 """
151 # Create factory to create build_artifacts from artifact names.
152 build = self.ParseUrl(self._archive_url)[1]
Chris Sosa6b0c6172013-08-05 17:01:33 -0700153 factory = build_artifact.ArtifactFactory(
154 self._build_dir, self._archive_url, artifacts, files, build)
Dan Shif8eb0d12013-08-01 17:52:06 -0700155 required_artifacts = factory.RequiredArtifacts()
156 return all([artifact.ArtifactStaged() for artifact in required_artifacts])
157
Chris Sosa76e44b92013-01-31 12:11:38 -0800158 def _DownloadArtifactsSerially(self, artifacts, no_wait):
159 """Simple function to download all the given artifacts serially.
160
Dan Shif8eb0d12013-08-01 17:52:06 -0700161 @param artifacts: A list of build_artifact.BuildArtifact instances to
162 download.
163 @param no_wait: If True, don't block waiting for artifact to exist if we
164 fail to immediately find it.
165
Gilad Arnold6f99b982012-09-12 10:49:40 -0700166 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800167 for artifact in artifacts:
168 artifact.Process(no_wait)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700169
Chris Sosa76e44b92013-01-31 12:11:38 -0800170 def _DownloadArtifactsInBackground(self, artifacts):
171 """Downloads |artifacts| in the background.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700172
Chris Sosa76e44b92013-01-31 12:11:38 -0800173 Downloads |artifacts| in the background. As these are backgrounded
174 artifacts, they are done best effort and may not exist.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700175
Chris Sosa76e44b92013-01-31 12:11:38 -0800176 Args:
177 artifacts: List of build_artifact.BuildArtifact instances to download.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700178 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800179 self._Log('Invoking background download of artifacts for %r', artifacts)
180 thread = threading.Thread(target=self._DownloadArtifactsSerially,
181 args=(artifacts, False))
182 thread.start()