blob: 2567e92e4444e83f49dea1c33a4fae3c86f3637d [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
10import log_util
Frank Farzan37761d12011-12-01 14:29:08 -080011
12
Gilad Arnoldc65330c2012-09-20 15:17:48 -070013class Downloader(log_util.Loggable):
Chris Sosa76e44b92013-01-31 12:11:38 -080014 """Downloader of images to the devsever.
Frank Farzan37761d12011-12-01 14:29:08 -080015
16 Given a URL to a build on the archive server:
Chris Sosa76e44b92013-01-31 12:11:38 -080017 - Caches that build and the given artifacts onto the devserver.
18 - May also initiate caching of related artifacts in the background.
Frank Farzan37761d12011-12-01 14:29:08 -080019
Chris Sosa76e44b92013-01-31 12:11:38 -080020 Private class members:
21 archive_url: a URL where to download build artifacts from.
22 static_dir: local filesystem directory to store all artifacts.
23 build_dir: the local filesystem directory to store artifacts for the given
24 build defined by the archive_url.
Frank Farzan37761d12011-12-01 14:29:08 -080025 """
26
Alex Millera44d5022012-07-27 11:34:16 -070027 # This filename must be kept in sync with clean_staged_images.py
28 _TIMESTAMP_FILENAME = 'staged.timestamp'
Chris Masonea22d9382012-05-18 12:38:51 -070029
Chris Sosa76e44b92013-01-31 12:11:38 -080030 def __init__(self, static_dir, archive_url):
31 super(Downloader, self).__init__()
32 self._archive_url = archive_url
Frank Farzan37761d12011-12-01 14:29:08 -080033 self._static_dir = static_dir
Chris Sosa76e44b92013-01-31 12:11:38 -080034 self._build_dir = Downloader.GetBuildDir(static_dir, archive_url)
Chris Masone816e38c2012-05-02 12:22:36 -070035
36 @staticmethod
Chris Sosacde6bf42012-05-31 18:36:39 -070037 def ParseUrl(archive_url):
Chris Sosa76e44b92013-01-31 12:11:38 -080038 """Parses archive_url into rel_path and build.
Chris Masone816e38c2012-05-02 12:22:36 -070039
Chris Sosa76e44b92013-01-31 12:11:38 -080040 Parses archive_url into rel_path and build e.g.
41 gs://chromeos-image-archive/{rel_path}/{build}.
42
43 Args:
44 archive_url: a URL at which build artifacts are archived.
45
46 Returns:
47 A tuple of (build relative path, short build name)
Chris Masone816e38c2012-05-02 12:22:36 -070048 """
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070049 # The archive_url is of the form gs://server/[some_path/target]/...]/build
50 # This function discards 'gs://server/' and extracts the [some_path/target]
Chris Sosa76e44b92013-01-31 12:11:38 -080051 # as rel_path and the build as build.
Yu-Ju Hongd49d7f42012-06-25 12:23:11 -070052 sub_url = archive_url.partition('://')[2]
53 split_sub_url = sub_url.split('/')
54 rel_path = '/'.join(split_sub_url[1:-1])
Chris Sosa76e44b92013-01-31 12:11:38 -080055 build = split_sub_url[-1]
56 return rel_path, build
Chris Masone816e38c2012-05-02 12:22:36 -070057
58 @staticmethod
Chris Sosa76e44b92013-01-31 12:11:38 -080059 def GetBuildDir(static_dir, archive_url):
60 """Returns the path to where the artifacts will be staged.
Chris Masone816e38c2012-05-02 12:22:36 -070061
Chris Sosa76e44b92013-01-31 12:11:38 -080062 Args:
63 static_dir: The base static dir that will be used.
64 archive_url: The gs path to the archive url.
Chris Masone816e38c2012-05-02 12:22:36 -070065 """
Chris Sosa76e44b92013-01-31 12:11:38 -080066 # Parse archive_url into rel_path (contains the build target) and
67 # build e.g. gs://chromeos-image-archive/{rel_path}/{build}.
68 rel_path, build = Downloader.ParseUrl(archive_url)
69 return os.path.join(static_dir, rel_path, build)
Frank Farzan37761d12011-12-01 14:29:08 -080070
Chris Sosa9164ca32012-03-28 11:04:50 -070071 @staticmethod
Alex Millera44d5022012-07-27 11:34:16 -070072 def _TouchTimestampForStaged(directory_path):
73 file_name = os.path.join(directory_path, Downloader._TIMESTAMP_FILENAME)
74 # Easiest python version of |touch file_name|
75 with file(file_name, 'a'):
76 os.utime(file_name, None)
77
Chris Sosa76e44b92013-01-31 12:11:38 -080078 def Download(self, artifacts):
79 """Downloads and caches the |artifacts|.
Chris Sosa9164ca32012-03-28 11:04:50 -070080
Chris Sosa76e44b92013-01-31 12:11:38 -080081 Downloads and caches the |artifacts|. Returns once these
82 are present on the devserver. A call to this will attempt to cache
83 non-specified artifacts in the background following the principle of
84 spatial locality.
Gilad Arnold6f99b982012-09-12 10:49:40 -070085
86 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -080087 artifacts: a list of artifact names that correspond to artifacts to stage.
Gilad Arnold6f99b982012-09-12 10:49:40 -070088 """
Chris Sosa76e44b92013-01-31 12:11:38 -080089 common_util.MkDirP(self._build_dir)
Gilad Arnold6f99b982012-09-12 10:49:40 -070090
Chris Sosa76e44b92013-01-31 12:11:38 -080091 # We are doing some work on this build -- let's touch it to indicate that
92 # we shouldn't be cleaning it up anytime soon.
93 Downloader._TouchTimestampForStaged(self._build_dir)
Gilad Arnold6f99b982012-09-12 10:49:40 -070094
Chris Sosa76e44b92013-01-31 12:11:38 -080095 # Create factory to create build_artifacts from artifact names.
96 build = self.ParseUrl(self._archive_url)[1]
97 factory = build_artifact.ArtifactFactory(self._build_dir, self._archive_url,
98 artifacts, build)
99 background_artifacts = factory.OptionalArtifacts()
100 if background_artifacts:
101 self._DownloadArtifactsInBackground(background_artifacts)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700102
Chris Sosa76e44b92013-01-31 12:11:38 -0800103 required_artifacts = factory.RequiredArtifacts()
104 str_repr = [str(a) for a in required_artifacts]
105 self._Log('Downloading artifacts %s.', ' '.join(str_repr))
106 self._DownloadArtifactsSerially(required_artifacts, no_wait=True)
107
108 def _DownloadArtifactsSerially(self, artifacts, no_wait):
109 """Simple function to download all the given artifacts serially.
110
111 Args:
112 artifacts: List of build_artifact.BuildArtifact instances to download.
113 no_wait: If True, don't block waiting for artifact to exist if we fail to
114 immediately find it.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700115 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800116 for artifact in artifacts:
117 artifact.Process(no_wait)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700118
Chris Sosa76e44b92013-01-31 12:11:38 -0800119 def _DownloadArtifactsInBackground(self, artifacts):
120 """Downloads |artifacts| in the background.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700121
Chris Sosa76e44b92013-01-31 12:11:38 -0800122 Downloads |artifacts| in the background. As these are backgrounded
123 artifacts, they are done best effort and may not exist.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700124
Chris Sosa76e44b92013-01-31 12:11:38 -0800125 Args:
126 artifacts: List of build_artifact.BuildArtifact instances to download.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700127 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800128 self._Log('Invoking background download of artifacts for %r', artifacts)
129 thread = threading.Thread(target=self._DownloadArtifactsSerially,
130 args=(artifacts, False))
131 thread.start()