blob: f128db6d730de070a538533c5e90a4efee3d252d [file] [log] [blame]
Dan Shi72b16132015-10-08 19:10:331# Copyright 2015 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Helper methods to make Google API call to query Android build server."""
6
7from __future__ import print_function
8
9import apiclient
10import httplib2
11import io
Dan Shi7b9b6a92015-11-12 09:00:2912import subprocess
Dan Shi72b16132015-10-08 19:10:3313
14from apiclient import discovery
15from oauth2client.client import SignedJwtAssertionCredentials
16
Dan Shi7b9b6a92015-11-12 09:00:2917import retry
18
19
Dan Shi72b16132015-10-08 19:10:3320CREDENTIAL_SCOPE = 'https://www.googleapis.com/auth/androidbuild.internal'
21DEFAULT_BUILDER = 'androidbuildinternal'
22DEFAULT_CHUNKSIZE = 20*1024*1024
Dan Shi7b9b6a92015-11-12 09:00:2923# Maximum attempts to interact with Launch Control API.
24MAX_ATTEMPTS = 10
25# Timeout in minutes for downloading attempt.
26DOWNLOAD_TIMEOUT_MINS = 30
27# Timeout in minutes for API query.
28QUERY_TIMEOUT_MINS = 1
29
Dan Shi72b16132015-10-08 19:10:3330
31class AndroidBuildFetchError(Exception):
32 """Exception to raise when failed to make calls to Android build server."""
33
34class BuildAccessor(object):
35 """Wrapper class to make Google API call to query Android build server."""
36
37 # Credential information is required to access Android builds. The values will
38 # be set when the devserver starts.
39 credential_info = None
40
41 @classmethod
Dan Shi7b9b6a92015-11-12 09:00:2942 @retry.retry(Exception, timeout_min=QUERY_TIMEOUT_MINS)
Dan Shi72b16132015-10-08 19:10:3343 def _GetServiceObject(cls):
44 """Returns a service object with given credential information."""
45 if not cls.credential_info:
46 raise AndroidBuildFetchError('Android Build credential is missing.')
47
48 credentials = SignedJwtAssertionCredentials(
49 cls.credential_info['client_email'],
50 cls.credential_info['private_key'], CREDENTIAL_SCOPE)
51 http_auth = credentials.authorize(httplib2.Http())
Dan Shi7b9b6a92015-11-12 09:00:2952 return discovery.build(DEFAULT_BUILDER, 'v1', http=http_auth)
53
Dan Shicfd9aac2016-05-18 16:52:2854 @staticmethod
55 def _GetBuildType(build_id):
56 """Get the build type based on the given build id.
57
58 Args:
59 build_id: Build id of the Android build, e.g., 2155602.
60
61 Returns:
62 The build type, e.g., submitted, pending.
63 """
64 if build_id and build_id.lower().startswith('p'):
65 return 'pending'
66 return 'submitted'
Dan Shi72b16132015-10-08 19:10:3367
68 @classmethod
69 def _VerifyBranch(cls, service_obj, branch, build_id, target):
70 """Verify the build with given id and target is for the specified branch.
71
72 Args:
73 service_obj: A service object to be used to make API call to build server.
74 branch: branch of the desired build.
75 build_id: Build id of the Android build, e.g., 2155602.
76 target: Target of the Android build, e.g., shamu-userdebug.
77
78 Raises:
79 AndroidBuildFetchError: If the given build id and target are not for the
80 specified branch.
81 """
Dan Shicfd9aac2016-05-18 16:52:2882 build_type = cls._GetBuildType(build_id)
Dan Shi72b16132015-10-08 19:10:3383 builds = service_obj.build().list(
Dan Shicfd9aac2016-05-18 16:52:2884 buildType=build_type, branch=branch, buildId=build_id, target=target,
Dan Shi7b9b6a92015-11-12 09:00:2985 maxResults=0).execute(num_retries=MAX_ATTEMPTS)
Dan Shi72b16132015-10-08 19:10:3386 if not builds:
87 raise AndroidBuildFetchError(
88 'Failed to locate build with branch %s, build id %s and target %s.' %
89 (branch, build_id, target))
90
91 @classmethod
92 def GetArtifacts(cls, branch, build_id, target):
93 """Get the list of artifacts for given build id and target.
94
95 The return value is a list of dictionaries, each containing information
96 about an artifact.
97 For example:
98 {u'contentType': u'application/octet-stream',
99 u'crc32': 4131231264,
100 u'lastModifiedTime': u'143518405786',
101 u'md5': u'c04c823a64293aa5bf508e2eb4683ec8',
102 u'name': u'fastboot',
103 u'revision': u'HsXLpGsgEaqj654THKvR/A==',
104 u'size': u'6999296'},
105
106 Args:
107 branch: branch of the desired build.
108 build_id: Build id of the Android build, e.g., 2155602.
109 target: Target of the Android build, e.g., shamu-userdebug.
110
111 Returns:
112 A list of artifacts for given build id and target.
113 """
114 service_obj = cls._GetServiceObject()
115 cls._VerifyBranch(service_obj, branch, build_id, target)
Dan Shicfd9aac2016-05-18 16:52:28116 build_type = cls._GetBuildType(build_id)
Dan Shi72b16132015-10-08 19:10:33117
118 # Get all artifacts for the given build_id and target.
Dan Shi04049bc2017-06-02 19:11:36119 artifacts = []
120 req = service_obj.buildartifact().list(
Dan Shicfd9aac2016-05-18 16:52:28121 buildType=build_type, buildId=build_id, target=target,
Dan Shi04049bc2017-06-02 19:11:36122 attemptId='latest', maxResults=10)
123 while req:
124 response = req.execute(num_retries=MAX_ATTEMPTS)
125 if not response:
126 break
127 artifacts.extend(response.get('artifacts', []))
128 req = service_obj.buildartifact().list_next(req, response)
129
130 return artifacts
Dan Shi72b16132015-10-08 19:10:33131
132 @classmethod
Dan Shi7b9b6a92015-11-12 09:00:29133 @retry.retry(Exception, timeout_min=DOWNLOAD_TIMEOUT_MINS)
Dan Shi72b16132015-10-08 19:10:33134 def Download(cls, branch, build_id, target, resource_id, dest_file):
Dan Shi7b9b6a92015-11-12 09:00:29135 """Download the list of artifacts for given build id and target.
Dan Shi72b16132015-10-08 19:10:33136
137 Args:
138 branch: branch of the desired build.
139 build_id: Build id of the Android build, e.g., 2155602.
140 target: Target of the Android build, e.g., shamu-userdebug.
141 resource_id: Name of the artifact to donwload.
142 dest_file: Path to the file to download to.
143 """
144 service_obj = cls._GetServiceObject()
145 cls._VerifyBranch(service_obj, branch, build_id, target)
146
Dan Shi7b9b6a92015-11-12 09:00:29147 # Delete partially downloaded file if exists.
148 subprocess.call(['rm', '-rf', dest_file])
149
Dan Shicfd9aac2016-05-18 16:52:28150 build_type = cls._GetBuildType(build_id)
Dan Shi72b16132015-10-08 19:10:33151 # TODO(dshi): Add retry logic here to avoid API flakes.
152 download_req = service_obj.buildartifact().get_media(
Dan Shicfd9aac2016-05-18 16:52:28153 buildType=build_type, buildId=build_id, target=target,
Dan Shi72b16132015-10-08 19:10:33154 attemptId='latest', resourceId=resource_id)
155 with io.FileIO(dest_file, mode='wb') as fh:
156 downloader = apiclient.http.MediaIoBaseDownload(
157 fh, download_req, chunksize=DEFAULT_CHUNKSIZE)
158 done = None
159 while not done:
Dan Shi7b9b6a92015-11-12 09:00:29160 _, done = downloader.next_chunk(num_retries=MAX_ATTEMPTS)
161
Dan Shi61305df2015-10-26 23:52:35162
163 @classmethod
Dan Shi7b9b6a92015-11-12 09:00:29164 @retry.retry(Exception, timeout_min=QUERY_TIMEOUT_MINS,
165 blacklist=[AndroidBuildFetchError])
Dan Shi61305df2015-10-26 23:52:35166 def GetLatestBuildID(cls, target, branch):
167 """Get the latest build ID for the given target and branch.
168
169 Args:
170 branch: branch of the desired build.
171 target: Target of the Android build, e.g., shamu-userdebug.
172
173 Returns:
174 Build id of the latest successful Android build for the given target and
175 branch, e.g., 2155602.
176 """
177 service_obj = cls._GetServiceObject()
178 builds = service_obj.build().list(
179 buildType='submitted', branch=branch, target=target, successful=True,
Dan Shi7b9b6a92015-11-12 09:00:29180 maxResults=1).execute(num_retries=MAX_ATTEMPTS)
Dan Shi61305df2015-10-26 23:52:35181 if not builds or not builds['builds']:
182 raise AndroidBuildFetchError(
183 'Failed to locate build with branch %s and target %s.' %
184 (branch, target))
185 return builds['builds'][0]['buildId']