blob: dd1a931060b8ead8e4085f4ab4692d7f073d505c [file] [log] [blame]
Dan Shi72b16132015-10-08 12:10:33 -07001# 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 01:00:29 -080012import subprocess
Dan Shi72b16132015-10-08 12:10:33 -070013
14from apiclient import discovery
15from oauth2client.client import SignedJwtAssertionCredentials
16
Dan Shi7b9b6a92015-11-12 01:00:29 -080017import retry
18
19
Dan Shi72b16132015-10-08 12:10:33 -070020CREDENTIAL_SCOPE = 'https://www.googleapis.com/auth/androidbuild.internal'
21DEFAULT_BUILDER = 'androidbuildinternal'
22DEFAULT_CHUNKSIZE = 20*1024*1024
Dan Shi7b9b6a92015-11-12 01:00:29 -080023# 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 12:10:33 -070030
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 01:00:29 -080042 @retry.retry(Exception, timeout_min=QUERY_TIMEOUT_MINS)
Dan Shi72b16132015-10-08 12:10:33 -070043 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 01:00:29 -080052 return discovery.build(DEFAULT_BUILDER, 'v1', http=http_auth)
53
Dan Shi72b16132015-10-08 12:10:33 -070054
55 @classmethod
56 def _VerifyBranch(cls, service_obj, branch, build_id, target):
57 """Verify the build with given id and target is for the specified branch.
58
59 Args:
60 service_obj: A service object to be used to make API call to build server.
61 branch: branch of the desired build.
62 build_id: Build id of the Android build, e.g., 2155602.
63 target: Target of the Android build, e.g., shamu-userdebug.
64
65 Raises:
66 AndroidBuildFetchError: If the given build id and target are not for the
67 specified branch.
68 """
69 builds = service_obj.build().list(
70 buildType='submitted', branch=branch, buildId=build_id, target=target,
Dan Shi7b9b6a92015-11-12 01:00:29 -080071 maxResults=0).execute(num_retries=MAX_ATTEMPTS)
Dan Shi72b16132015-10-08 12:10:33 -070072 if not builds:
73 raise AndroidBuildFetchError(
74 'Failed to locate build with branch %s, build id %s and target %s.' %
75 (branch, build_id, target))
76
77 @classmethod
78 def GetArtifacts(cls, branch, build_id, target):
79 """Get the list of artifacts for given build id and target.
80
81 The return value is a list of dictionaries, each containing information
82 about an artifact.
83 For example:
84 {u'contentType': u'application/octet-stream',
85 u'crc32': 4131231264,
86 u'lastModifiedTime': u'143518405786',
87 u'md5': u'c04c823a64293aa5bf508e2eb4683ec8',
88 u'name': u'fastboot',
89 u'revision': u'HsXLpGsgEaqj654THKvR/A==',
90 u'size': u'6999296'},
91
92 Args:
93 branch: branch of the desired build.
94 build_id: Build id of the Android build, e.g., 2155602.
95 target: Target of the Android build, e.g., shamu-userdebug.
96
97 Returns:
98 A list of artifacts for given build id and target.
99 """
100 service_obj = cls._GetServiceObject()
101 cls._VerifyBranch(service_obj, branch, build_id, target)
102
103 # Get all artifacts for the given build_id and target.
104 artifacts = service_obj.buildartifact().list(
105 buildType='submitted', buildId=build_id, target=target,
Dan Shi7b9b6a92015-11-12 01:00:29 -0800106 attemptId='latest', maxResults=0).execute(num_retries=MAX_ATTEMPTS)
Dan Shi72b16132015-10-08 12:10:33 -0700107 return artifacts['artifacts']
108
109 @classmethod
Dan Shi7b9b6a92015-11-12 01:00:29 -0800110 @retry.retry(Exception, timeout_min=DOWNLOAD_TIMEOUT_MINS)
Dan Shi72b16132015-10-08 12:10:33 -0700111 def Download(cls, branch, build_id, target, resource_id, dest_file):
Dan Shi7b9b6a92015-11-12 01:00:29 -0800112 """Download the list of artifacts for given build id and target.
Dan Shi72b16132015-10-08 12:10:33 -0700113
114 Args:
115 branch: branch of the desired build.
116 build_id: Build id of the Android build, e.g., 2155602.
117 target: Target of the Android build, e.g., shamu-userdebug.
118 resource_id: Name of the artifact to donwload.
119 dest_file: Path to the file to download to.
120 """
121 service_obj = cls._GetServiceObject()
122 cls._VerifyBranch(service_obj, branch, build_id, target)
123
Dan Shi7b9b6a92015-11-12 01:00:29 -0800124 # Delete partially downloaded file if exists.
125 subprocess.call(['rm', '-rf', dest_file])
126
Dan Shi72b16132015-10-08 12:10:33 -0700127 # TODO(dshi): Add retry logic here to avoid API flakes.
128 download_req = service_obj.buildartifact().get_media(
129 buildType='submitted', buildId=build_id, target=target,
130 attemptId='latest', resourceId=resource_id)
131 with io.FileIO(dest_file, mode='wb') as fh:
132 downloader = apiclient.http.MediaIoBaseDownload(
133 fh, download_req, chunksize=DEFAULT_CHUNKSIZE)
134 done = None
135 while not done:
Dan Shi7b9b6a92015-11-12 01:00:29 -0800136 _, done = downloader.next_chunk(num_retries=MAX_ATTEMPTS)
137
Dan Shi61305df2015-10-26 16:52:35 -0700138
139 @classmethod
Dan Shi7b9b6a92015-11-12 01:00:29 -0800140 @retry.retry(Exception, timeout_min=QUERY_TIMEOUT_MINS,
141 blacklist=[AndroidBuildFetchError])
Dan Shi61305df2015-10-26 16:52:35 -0700142 def GetLatestBuildID(cls, target, branch):
143 """Get the latest build ID for the given target and branch.
144
145 Args:
146 branch: branch of the desired build.
147 target: Target of the Android build, e.g., shamu-userdebug.
148
149 Returns:
150 Build id of the latest successful Android build for the given target and
151 branch, e.g., 2155602.
152 """
153 service_obj = cls._GetServiceObject()
154 builds = service_obj.build().list(
155 buildType='submitted', branch=branch, target=target, successful=True,
Dan Shi7b9b6a92015-11-12 01:00:29 -0800156 maxResults=1).execute(num_retries=MAX_ATTEMPTS)
Dan Shi61305df2015-10-26 16:52:35 -0700157 if not builds or not builds['builds']:
158 raise AndroidBuildFetchError(
159 'Failed to locate build with branch %s and target %s.' %
160 (branch, target))
161 return builds['builds'][0]['buildId']