blob: e19c84f96231108023ce7c680064c011b1be2b59 [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 Sosa76e44b92013-01-31 12:11:38 -080095 def Download(self, artifacts):
96 """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
103 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800104 artifacts: a list of artifact names that correspond to artifacts to stage.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700105 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800106 common_util.MkDirP(self._build_dir)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700107
Chris Sosa76e44b92013-01-31 12:11:38 -0800108 # We are doing some work on this build -- let's touch it to indicate that
109 # we shouldn't be cleaning it up anytime soon.
110 Downloader._TouchTimestampForStaged(self._build_dir)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700111
Chris Sosa76e44b92013-01-31 12:11:38 -0800112 # Create factory to create build_artifacts from artifact names.
113 build = self.ParseUrl(self._archive_url)[1]
114 factory = build_artifact.ArtifactFactory(self._build_dir, self._archive_url,
115 artifacts, build)
116 background_artifacts = factory.OptionalArtifacts()
117 if background_artifacts:
118 self._DownloadArtifactsInBackground(background_artifacts)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700119
Chris Sosa76e44b92013-01-31 12:11:38 -0800120 required_artifacts = factory.RequiredArtifacts()
121 str_repr = [str(a) for a in required_artifacts]
122 self._Log('Downloading artifacts %s.', ' '.join(str_repr))
Dan Shiba0e6742013-06-26 17:39:05 -0700123 try:
124 self._DownloadArtifactsSerially(required_artifacts, no_wait=True)
125 except gsutil_util.GSUtilError:
126 Downloader._TryRemoveStageDir(self._build_dir)
127 raise
Chris Sosa76e44b92013-01-31 12:11:38 -0800128
129 def _DownloadArtifactsSerially(self, artifacts, no_wait):
130 """Simple function to download all the given artifacts serially.
131
132 Args:
133 artifacts: List of build_artifact.BuildArtifact instances to download.
134 no_wait: If True, don't block waiting for artifact to exist if we fail to
135 immediately find it.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700136 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800137 for artifact in artifacts:
138 artifact.Process(no_wait)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700139
Chris Sosa76e44b92013-01-31 12:11:38 -0800140 def _DownloadArtifactsInBackground(self, artifacts):
141 """Downloads |artifacts| in the background.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700142
Chris Sosa76e44b92013-01-31 12:11:38 -0800143 Downloads |artifacts| in the background. As these are backgrounded
144 artifacts, they are done best effort and may not exist.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700145
Chris Sosa76e44b92013-01-31 12:11:38 -0800146 Args:
147 artifacts: List of build_artifact.BuildArtifact instances to download.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700148 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800149 self._Log('Invoking background download of artifacts for %r', artifacts)
150 thread = threading.Thread(target=self._DownloadArtifactsSerially,
151 args=(artifacts, False))
152 thread.start()