blob: 735c0e445f9de0a511fe70e06b07c19dc42a0dea [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
herbertxue79585f42018-08-28 18:36:45 +080024from acloud import errors as root_errors
Tri Voe6638de2016-10-03 19:39:03 -070025from acloud.internal.lib import base_cloud_client
26from acloud.public import errors
27
28logger = logging.getLogger(__name__)
29
30
31class AndroidBuildClient(base_cloud_client.BaseCloudApiClient):
32 """Client that manages Android Build."""
33
34 # API settings, used by BaseCloudApiClient.
35 API_NAME = "androidbuildinternal"
36 API_VERSION = "v2beta1"
37 SCOPE = "https://www.googleapis.com/auth/androidbuild.internal"
38
39 # other variables.
40 DEFAULT_RESOURCE_ID = "0"
Kevin Chengb5963882018-05-09 00:06:27 -070041 # TODO(b/27269552): We should use "latest".
Tri Voe6638de2016-10-03 19:39:03 -070042 DEFAULT_ATTEMPT_ID = "0"
43 DEFAULT_CHUNK_SIZE = 20 * 1024 * 1024
44 NO_ACCESS_ERROR_PATTERN = "does not have storage.objects.create access"
herbertxue79585f42018-08-28 18:36:45 +080045 # LKGB variables.
46 BUILD_STATUS_COMPLETE = "complete"
47 BUILD_TYPE_SUBMITTED = "submitted"
48 ONE_RESULT = 1
49 BUILD_SUCCESSFUL = True
Tri Voe6638de2016-10-03 19:39:03 -070050
51 # Message constant
52 COPY_TO_MSG = ("build artifact (target: %s, build_id: %s, "
53 "artifact: %s, attempt_id: %s) to "
54 "google storage (bucket: %s, path: %s)")
herbertxue79585f42018-08-28 18:36:45 +080055 # pylint: disable=invalid-name
Tri Voe6638de2016-10-03 19:39:03 -070056 def DownloadArtifact(self,
57 build_target,
58 build_id,
59 resource_id,
60 local_dest,
61 attempt_id=None):
62 """Get Android build attempt information.
63
64 Args:
Kevin Chengb5963882018-05-09 00:06:27 -070065 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
Tri Voe6638de2016-10-03 19:39:03 -070066 build_id: Build id, a string, e.g. "2263051", "P2804227"
67 resource_id: Id of the resource, e.g "avd-system.tar.gz".
68 local_dest: A local path where the artifact should be stored.
69 e.g. "/tmp/avd-system.tar.gz"
70 attempt_id: String, attempt id, will default to DEFAULT_ATTEMPT_ID.
71 """
72 attempt_id = attempt_id or self.DEFAULT_ATTEMPT_ID
73 api = self.service.buildartifact().get_media(
74 buildId=build_id,
75 target=build_target,
76 attemptId=attempt_id,
77 resourceId=resource_id)
78 logger.info("Downloading artifact: target: %s, build_id: %s, "
79 "resource_id: %s, dest: %s", build_target, build_id,
80 resource_id, local_dest)
81 try:
82 with io.FileIO(local_dest, mode="wb") as fh:
83 downloader = apiclient.http.MediaIoBaseDownload(
84 fh, api, chunksize=self.DEFAULT_CHUNK_SIZE)
85 done = False
86 while not done:
87 _, done = downloader.next_chunk()
88 logger.info("Downloaded artifact: %s", local_dest)
89 except OSError as e:
90 logger.error("Downloading artifact failed: %s", str(e))
91 raise errors.DriverError(str(e))
92
93 def CopyTo(self,
94 build_target,
95 build_id,
96 artifact_name,
97 destination_bucket,
98 destination_path,
99 attempt_id=None):
100 """Copy an Android Build artifact to a storage bucket.
101
102 Args:
Kevin Chengb5963882018-05-09 00:06:27 -0700103 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
Tri Voe6638de2016-10-03 19:39:03 -0700104 build_id: Build id, a string, e.g. "2263051", "P2804227"
105 artifact_name: Name of the artifact, e.g "avd-system.tar.gz".
106 destination_bucket: String, a google storage bucket name.
107 destination_path: String, "path/inside/bucket"
108 attempt_id: String, attempt id, will default to DEFAULT_ATTEMPT_ID.
109 """
110 attempt_id = attempt_id or self.DEFAULT_ATTEMPT_ID
111 copy_msg = "Copying %s" % self.COPY_TO_MSG
112 logger.info(copy_msg, build_target, build_id, artifact_name,
113 attempt_id, destination_bucket, destination_path)
114 api = self.service.buildartifact().copyTo(
115 buildId=build_id,
116 target=build_target,
117 attemptId=attempt_id,
118 artifactName=artifact_name,
119 destinationBucket=destination_bucket,
120 destinationPath=destination_path)
121 try:
122 self.Execute(api)
123 finish_msg = "Finished copying %s" % self.COPY_TO_MSG
124 logger.info(finish_msg, build_target, build_id, artifact_name,
125 attempt_id, destination_bucket, destination_path)
126 except errors.HttpError as e:
127 if e.code == 503:
128 if self.NO_ACCESS_ERROR_PATTERN in str(e):
129 error_msg = "Please grant android build team's service account "
130 error_msg += "write access to bucket %s. Original error: %s"
131 error_msg %= (destination_bucket, str(e))
132 raise errors.HttpError(e.code, message=error_msg)
133 raise
Kevin Chengb5963882018-05-09 00:06:27 -0700134
135 def GetBranch(self, build_target, build_id):
136 """Derives branch name.
137
138 Args:
139 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
140 build_id: Build ID, a string, e.g. "2263051", "P2804227"
141
142 Returns:
143 A string, the name of the branch
144 """
145 api = self.service.build().get(buildId=build_id, target=build_target)
146 build = self.Execute(api)
147 return build.get("branch", "")
herbertxue79585f42018-08-28 18:36:45 +0800148
149 def GetLKGB(self, build_target, build_branch):
150 """Get latest successful build id.
151
152 From branch and target, we can use api to query latest successful build id.
153 e.g. {u'nextPageToken':..., u'builds': [{u'completionTimestamp':u'1534157869286',
154 ... u'buildId': u'4949805', u'machineName'...}]}
155
156 Args:
157 build_target: String, target name, e.g. "aosp_cf_x86_phone-userdebug"
158 build_branch: String, git branch name, e.g. "aosp-master"
159
160 Returns:
161 A string, string of build id number.
162
163 Raises:
164 errors.CreateError: Can't get build id.
165 """
166 api = self.service.build().list(
167 branch=build_branch,
168 target=build_target,
169 buildAttemptStatus=self.BUILD_STATUS_COMPLETE,
170 buildType=self.BUILD_TYPE_SUBMITTED,
171 maxResults=self.ONE_RESULT,
172 successful=self.BUILD_SUCCESSFUL)
173 build = self.Execute(api)
174 if build:
175 return str(build.get("builds")[0].get("buildId"))
176 raise root_errors.GetBuildIDError(
177 "No available good builds for branch: %s target: %s"
178 % (build_branch, build_target)
179 )