blob: 68192f60fcbdf4f6eea799d528696e3dd28e8751 [file] [log] [blame]
Chris Sosa76e44b92013-01-31 20:11:381# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
Frank Farzan37761d12011-12-01 22:29:082# 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 18:04:505import os
Gilad Arnold0b8c3f32012-09-19 21:35:446import threading
Frank Farzan37761d12011-12-01 22:29:087
Chris Sosa76e44b92013-01-31 20:11:388import build_artifact
Gilad Arnoldc65330c2012-09-20 22:17:489import common_util
Dan Shiba0e6742013-06-27 00:39:0510import gsutil_util
Gilad Arnoldc65330c2012-09-20 22:17:4811import log_util
Frank Farzan37761d12011-12-01 22:29:0812
13
Gilad Arnoldc65330c2012-09-20 22:17:4814class Downloader(log_util.Loggable):
Chris Sosa76e44b92013-01-31 20:11:3815 """Downloader of images to the devsever.
Frank Farzan37761d12011-12-01 22:29:0816
17 Given a URL to a build on the archive server:
Chris Sosa76e44b92013-01-31 20:11:3818 - 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 22:29:0820
Chris Sosa76e44b92013-01-31 20:11:3821 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 22:29:0826 """
27
Alex Millera44d5022012-07-27 18:34:1628 # This filename must be kept in sync with clean_staged_images.py
29 _TIMESTAMP_FILENAME = 'staged.timestamp'
Chris Masonea22d9382012-05-18 19:38:5130
Chris Sosa76e44b92013-01-31 20:11:3831 def __init__(self, static_dir, archive_url):
32 super(Downloader, self).__init__()
33 self._archive_url = archive_url
Frank Farzan37761d12011-12-01 22:29:0834 self._static_dir = static_dir
Chris Sosa76e44b92013-01-31 20:11:3835 self._build_dir = Downloader.GetBuildDir(static_dir, archive_url)
Chris Masone816e38c2012-05-02 19:22:3636
37 @staticmethod
Chris Sosacde6bf42012-06-01 01:36:3938 def ParseUrl(archive_url):
Chris Sosa76e44b92013-01-31 20:11:3839 """Parses archive_url into rel_path and build.
Chris Masone816e38c2012-05-02 19:22:3640
Chris Sosa76e44b92013-01-31 20:11:3841 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 19:22:3649 """
Yu-Ju Hongd49d7f42012-06-25 19:23:1150 # 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 20:11:3852 # as rel_path and the build as build.
Yu-Ju Hongd49d7f42012-06-25 19:23:1153 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 20:11:3856 build = split_sub_url[-1]
57 return rel_path, build
Chris Masone816e38c2012-05-02 19:22:3658
59 @staticmethod
Chris Sosa76e44b92013-01-31 20:11:3860 def GetBuildDir(static_dir, archive_url):
61 """Returns the path to where the artifacts will be staged.
Chris Masone816e38c2012-05-02 19:22:3662
Chris Sosa76e44b92013-01-31 20:11:3863 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 19:22:3666 """
Chris Sosa76e44b92013-01-31 20:11:3867 # 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 22:29:0871
Chris Sosa9164ca32012-03-28 18:04:5072 @staticmethod
Alex Millera44d5022012-07-27 18:34:1673 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-27 00:39:0579 @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-06 00:01:3395 def Download(self, artifacts, files, async=False):
Chris Sosa76e44b92013-01-31 20:11:3896 """Downloads and caches the |artifacts|.
Chris Sosa9164ca32012-03-28 18:04:5097
Chris Sosa76e44b92013-01-31 20:11:3898 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 17:49:40102
Chris Sosa6b0c6172013-08-06 00:01:33103 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-02 00:52:06107
Gilad Arnold6f99b982012-09-12 17:49:40108 """
Chris Sosa76e44b92013-01-31 20:11:38109 common_util.MkDirP(self._build_dir)
Gilad Arnold6f99b982012-09-12 17:49:40110
Chris Sosa76e44b92013-01-31 20:11:38111 # 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 17:49:40114
Chris Sosa76e44b92013-01-31 20:11:38115 # Create factory to create build_artifacts from artifact names.
116 build = self.ParseUrl(self._archive_url)[1]
Chris Sosa6b0c6172013-08-06 00:01:33117 factory = build_artifact.ArtifactFactory(
118 self._build_dir, self._archive_url, artifacts, files,
119 build)
Chris Sosa76e44b92013-01-31 20:11:38120 background_artifacts = factory.OptionalArtifacts()
121 if background_artifacts:
122 self._DownloadArtifactsInBackground(background_artifacts)
Gilad Arnold6f99b982012-09-12 17:49:40123
Chris Sosa76e44b92013-01-31 20:11:38124 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 23:10:29127
Dan Shiba0e6742013-06-27 00:39:05128 try:
Dan Shif8eb0d12013-08-02 00:52:06129 if async:
Dan Shie37f8fe2013-08-09 23:10:29130 # 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:
Chris Sosac4e87842013-08-17 01:04:14134 artifact.WaitForArtifactToExist(timeout=10, update_name=False)
Dan Shif8eb0d12013-08-02 00:52:06135 self._DownloadArtifactsInBackground(required_artifacts)
136 else:
137 self._DownloadArtifactsSerially(required_artifacts, no_wait=True)
Dan Shiba0e6742013-06-27 00:39:05138 except gsutil_util.GSUtilError:
139 Downloader._TryRemoveStageDir(self._build_dir)
140 raise
Chris Sosa76e44b92013-01-31 20:11:38141
Chris Sosa6b0c6172013-08-06 00:01:33142 def IsStaged(self, artifacts, files):
Dan Shif8eb0d12013-08-02 00:52:06143 """Check if all artifacts have been downloaded.
144
Chris Sosa6b0c6172013-08-06 00:01:33145 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-02 00:52:06148 @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-06 00:01:33153 factory = build_artifact.ArtifactFactory(
154 self._build_dir, self._archive_url, artifacts, files, build)
Dan Shif8eb0d12013-08-02 00:52:06155 required_artifacts = factory.RequiredArtifacts()
156 return all([artifact.ArtifactStaged() for artifact in required_artifacts])
157
Chris Sosa76e44b92013-01-31 20:11:38158 def _DownloadArtifactsSerially(self, artifacts, no_wait):
159 """Simple function to download all the given artifacts serially.
160
Dan Shif8eb0d12013-08-02 00:52:06161 @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 17:49:40166 """
Chris Sosa76e44b92013-01-31 20:11:38167 for artifact in artifacts:
168 artifact.Process(no_wait)
Gilad Arnold6f99b982012-09-12 17:49:40169
Chris Sosa76e44b92013-01-31 20:11:38170 def _DownloadArtifactsInBackground(self, artifacts):
171 """Downloads |artifacts| in the background.
Gilad Arnold6f99b982012-09-12 17:49:40172
Chris Sosa76e44b92013-01-31 20:11:38173 Downloads |artifacts| in the background. As these are backgrounded
174 artifacts, they are done best effort and may not exist.
Gilad Arnold6f99b982012-09-12 17:49:40175
Chris Sosa76e44b92013-01-31 20:11:38176 Args:
177 artifacts: List of build_artifact.BuildArtifact instances to download.
Gilad Arnold6f99b982012-09-12 17:49:40178 """
Chris Sosa76e44b92013-01-31 20:11:38179 self._Log('Invoking background download of artifacts for %r', artifacts)
180 thread = threading.Thread(target=self._DownloadArtifactsSerially,
181 args=(artifacts, False))
182 thread.start()