blob: 32a11791e6db81d261fa85e21e4ad52b1a8e48c6 [file] [log] [blame]
Tri Voe6638de2016-10-03 19:39:03 -07001#!/usr/bin/env python
2#
3# Copyright 2016 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""A client that talks to Android Build APIs."""
18
19import io
20import logging
Tri Voe6638de2016-10-03 19:39:03 -070021
22import apiclient
23
Sam Chiu7de3b232018-12-06 19:45:52 +080024from acloud import errors
Tri Voe6638de2016-10-03 19:39:03 -070025from acloud.internal.lib import base_cloud_client
Tri Voe6638de2016-10-03 19:39:03 -070026
27logger = logging.getLogger(__name__)
28
29
30class AndroidBuildClient(base_cloud_client.BaseCloudApiClient):
31 """Client that manages Android Build."""
32
33 # API settings, used by BaseCloudApiClient.
34 API_NAME = "androidbuildinternal"
35 API_VERSION = "v2beta1"
36 SCOPE = "https://www.googleapis.com/auth/androidbuild.internal"
37
38 # other variables.
39 DEFAULT_RESOURCE_ID = "0"
Kevin Chengb5963882018-05-09 00:06:27 -070040 # TODO(b/27269552): We should use "latest".
Tri Voe6638de2016-10-03 19:39:03 -070041 DEFAULT_ATTEMPT_ID = "0"
42 DEFAULT_CHUNK_SIZE = 20 * 1024 * 1024
43 NO_ACCESS_ERROR_PATTERN = "does not have storage.objects.create access"
herbertxue79585f42018-08-28 18:36:45 +080044 # LKGB variables.
45 BUILD_STATUS_COMPLETE = "complete"
46 BUILD_TYPE_SUBMITTED = "submitted"
47 ONE_RESULT = 1
48 BUILD_SUCCESSFUL = True
Tri Voe6638de2016-10-03 19:39:03 -070049
50 # Message constant
51 COPY_TO_MSG = ("build artifact (target: %s, build_id: %s, "
52 "artifact: %s, attempt_id: %s) to "
53 "google storage (bucket: %s, path: %s)")
herbertxue79585f42018-08-28 18:36:45 +080054 # pylint: disable=invalid-name
Tri Voe6638de2016-10-03 19:39:03 -070055 def DownloadArtifact(self,
56 build_target,
57 build_id,
58 resource_id,
59 local_dest,
60 attempt_id=None):
61 """Get Android build attempt information.
62
63 Args:
Kevin Chengb5963882018-05-09 00:06:27 -070064 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
Tri Voe6638de2016-10-03 19:39:03 -070065 build_id: Build id, a string, e.g. "2263051", "P2804227"
66 resource_id: Id of the resource, e.g "avd-system.tar.gz".
67 local_dest: A local path where the artifact should be stored.
68 e.g. "/tmp/avd-system.tar.gz"
69 attempt_id: String, attempt id, will default to DEFAULT_ATTEMPT_ID.
70 """
71 attempt_id = attempt_id or self.DEFAULT_ATTEMPT_ID
72 api = self.service.buildartifact().get_media(
73 buildId=build_id,
74 target=build_target,
75 attemptId=attempt_id,
76 resourceId=resource_id)
77 logger.info("Downloading artifact: target: %s, build_id: %s, "
78 "resource_id: %s, dest: %s", build_target, build_id,
79 resource_id, local_dest)
80 try:
81 with io.FileIO(local_dest, mode="wb") as fh:
82 downloader = apiclient.http.MediaIoBaseDownload(
83 fh, api, chunksize=self.DEFAULT_CHUNK_SIZE)
84 done = False
85 while not done:
86 _, done = downloader.next_chunk()
87 logger.info("Downloaded artifact: %s", local_dest)
88 except OSError as e:
89 logger.error("Downloading artifact failed: %s", str(e))
90 raise errors.DriverError(str(e))
91
92 def CopyTo(self,
93 build_target,
94 build_id,
95 artifact_name,
96 destination_bucket,
97 destination_path,
98 attempt_id=None):
99 """Copy an Android Build artifact to a storage bucket.
100
101 Args:
Kevin Chengb5963882018-05-09 00:06:27 -0700102 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
Tri Voe6638de2016-10-03 19:39:03 -0700103 build_id: Build id, a string, e.g. "2263051", "P2804227"
104 artifact_name: Name of the artifact, e.g "avd-system.tar.gz".
105 destination_bucket: String, a google storage bucket name.
106 destination_path: String, "path/inside/bucket"
107 attempt_id: String, attempt id, will default to DEFAULT_ATTEMPT_ID.
108 """
109 attempt_id = attempt_id or self.DEFAULT_ATTEMPT_ID
110 copy_msg = "Copying %s" % self.COPY_TO_MSG
111 logger.info(copy_msg, build_target, build_id, artifact_name,
112 attempt_id, destination_bucket, destination_path)
113 api = self.service.buildartifact().copyTo(
114 buildId=build_id,
115 target=build_target,
116 attemptId=attempt_id,
117 artifactName=artifact_name,
118 destinationBucket=destination_bucket,
119 destinationPath=destination_path)
120 try:
121 self.Execute(api)
122 finish_msg = "Finished copying %s" % self.COPY_TO_MSG
123 logger.info(finish_msg, build_target, build_id, artifact_name,
124 attempt_id, destination_bucket, destination_path)
125 except errors.HttpError as e:
126 if e.code == 503:
127 if self.NO_ACCESS_ERROR_PATTERN in str(e):
128 error_msg = "Please grant android build team's service account "
129 error_msg += "write access to bucket %s. Original error: %s"
130 error_msg %= (destination_bucket, str(e))
131 raise errors.HttpError(e.code, message=error_msg)
132 raise
Kevin Chengb5963882018-05-09 00:06:27 -0700133
134 def GetBranch(self, build_target, build_id):
135 """Derives branch name.
136
137 Args:
138 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
139 build_id: Build ID, a string, e.g. "2263051", "P2804227"
140
141 Returns:
142 A string, the name of the branch
143 """
144 api = self.service.build().get(buildId=build_id, target=build_target)
145 build = self.Execute(api)
146 return build.get("branch", "")
herbertxue79585f42018-08-28 18:36:45 +0800147
148 def GetLKGB(self, build_target, build_branch):
149 """Get latest successful build id.
150
151 From branch and target, we can use api to query latest successful build id.
152 e.g. {u'nextPageToken':..., u'builds': [{u'completionTimestamp':u'1534157869286',
153 ... u'buildId': u'4949805', u'machineName'...}]}
154
155 Args:
156 build_target: String, target name, e.g. "aosp_cf_x86_phone-userdebug"
157 build_branch: String, git branch name, e.g. "aosp-master"
158
159 Returns:
160 A string, string of build id number.
161
162 Raises:
163 errors.CreateError: Can't get build id.
164 """
165 api = self.service.build().list(
166 branch=build_branch,
167 target=build_target,
168 buildAttemptStatus=self.BUILD_STATUS_COMPLETE,
169 buildType=self.BUILD_TYPE_SUBMITTED,
170 maxResults=self.ONE_RESULT,
171 successful=self.BUILD_SUCCESSFUL)
172 build = self.Execute(api)
173 if build:
174 return str(build.get("builds")[0].get("buildId"))
Sam Chiu7de3b232018-12-06 19:45:52 +0800175 raise errors.GetBuildIDError(
herbertxue79585f42018-08-28 18:36:45 +0800176 "No available good builds for branch: %s target: %s"
177 % (build_branch, build_target)
178 )