Tri Vo | e6638de | 2016-10-03 19:39:03 -0700 | [diff] [blame] | 1 | #!/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 | |
| 19 | import io |
| 20 | import logging |
Tri Vo | e6638de | 2016-10-03 19:39:03 -0700 | [diff] [blame] | 21 | |
| 22 | import apiclient |
| 23 | |
herbertxue | 79585f4 | 2018-08-28 18:36:45 +0800 | [diff] [blame] | 24 | from acloud import errors as root_errors |
Tri Vo | e6638de | 2016-10-03 19:39:03 -0700 | [diff] [blame] | 25 | from acloud.internal.lib import base_cloud_client |
| 26 | from acloud.public import errors |
| 27 | |
| 28 | logger = logging.getLogger(__name__) |
| 29 | |
| 30 | |
| 31 | class 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 Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 41 | # TODO(b/27269552): We should use "latest". |
Tri Vo | e6638de | 2016-10-03 19:39:03 -0700 | [diff] [blame] | 42 | DEFAULT_ATTEMPT_ID = "0" |
| 43 | DEFAULT_CHUNK_SIZE = 20 * 1024 * 1024 |
| 44 | NO_ACCESS_ERROR_PATTERN = "does not have storage.objects.create access" |
herbertxue | 79585f4 | 2018-08-28 18:36:45 +0800 | [diff] [blame] | 45 | # LKGB variables. |
| 46 | BUILD_STATUS_COMPLETE = "complete" |
| 47 | BUILD_TYPE_SUBMITTED = "submitted" |
| 48 | ONE_RESULT = 1 |
| 49 | BUILD_SUCCESSFUL = True |
Tri Vo | e6638de | 2016-10-03 19:39:03 -0700 | [diff] [blame] | 50 | |
| 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)") |
herbertxue | 79585f4 | 2018-08-28 18:36:45 +0800 | [diff] [blame] | 55 | # pylint: disable=invalid-name |
Tri Vo | e6638de | 2016-10-03 19:39:03 -0700 | [diff] [blame] | 56 | 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 Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 65 | build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug" |
Tri Vo | e6638de | 2016-10-03 19:39:03 -0700 | [diff] [blame] | 66 | 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 Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 103 | build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug" |
Tri Vo | e6638de | 2016-10-03 19:39:03 -0700 | [diff] [blame] | 104 | 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 Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 134 | |
| 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", "") |
herbertxue | 79585f4 | 2018-08-28 18:36:45 +0800 | [diff] [blame] | 148 | |
| 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 | ) |