| # Copyright 2015 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Helper methods to make Google API call to query Android build server.""" |
| |
| import io |
| |
| from chromite.third_party import httplib2 |
| from chromite.third_party.googleapiclient import discovery |
| from chromite.third_party.googleapiclient import http |
| from chromite.third_party.oauth2client.client import ( |
| SignedJwtAssertionCredentials, # pylint: disable=line-too-long |
| ) |
| |
| from chromite.lib import osutils |
| from chromite.lib.xbuddy import retry |
| |
| |
| CREDENTIAL_SCOPE = "https://www.googleapis.com/auth/androidbuild.internal" |
| DEFAULT_BUILDER = "androidbuildinternal" |
| DEFAULT_CHUNKSIZE = 20 * 1024 * 1024 |
| # Maximum attempts to interact with Launch Control API. |
| MAX_ATTEMPTS = 10 |
| # Timeout in minutes for downloading attempt. |
| DOWNLOAD_TIMEOUT_MINS = 30 |
| # Timeout in minutes for API query. |
| QUERY_TIMEOUT_MINS = 1 |
| |
| |
| class AndroidBuildFetchError(Exception): |
| """Exception to raise when failed to make calls to Android build server.""" |
| |
| |
| class BuildAccessor: |
| """Wrapper class to make Google API call to query Android build server.""" |
| |
| # Credential information is required to access Android builds. The values |
| # will be set when the devserver starts. |
| credential_info = {} |
| |
| @classmethod |
| @retry.retry(Exception, timeout_min=QUERY_TIMEOUT_MINS) |
| def _GetServiceObject(cls): |
| """Returns a service object with given credential information.""" |
| if not cls.credential_info: |
| raise AndroidBuildFetchError("Android Build credential is missing.") |
| |
| credentials = SignedJwtAssertionCredentials( |
| cls.credential_info["client_email"], |
| cls.credential_info["private_key"], |
| CREDENTIAL_SCOPE, |
| ) |
| http_auth = credentials.authorize(httplib2.Http()) |
| return discovery.build(DEFAULT_BUILDER, "v1", http=http_auth) |
| |
| @staticmethod |
| def _GetBuildType(build_id): |
| """Get the build type based on the given build id. |
| |
| Args: |
| build_id: Build id of the Android build, e.g., 2155602. |
| |
| Returns: |
| The build type, e.g., submitted, pending. |
| """ |
| if build_id and build_id.lower().startswith("p"): |
| return "pending" |
| return "submitted" |
| |
| @classmethod |
| def _VerifyBranch(cls, service_obj, branch, build_id, target): |
| """Verify build with given id and target is for the specified branch. |
| |
| Args: |
| service_obj: A service object to be used to make API call to build |
| server. |
| branch: branch of the desired build. |
| build_id: Build id of the Android build, e.g., 2155602. |
| target: Target of the Android build, e.g., shamu-userdebug. |
| |
| Raises: |
| AndroidBuildFetchError: If the given build id and target are not for |
| the specified branch. |
| """ |
| build_type = cls._GetBuildType(build_id) |
| builds = ( |
| service_obj.build() |
| .list( |
| buildType=build_type, |
| branch=branch, |
| buildId=build_id, |
| target=target, |
| maxResults=0, |
| ) |
| .execute(num_retries=MAX_ATTEMPTS) |
| ) |
| if not builds: |
| raise AndroidBuildFetchError( |
| "Failed to locate build with branch %s, build id %s and" |
| " target %s." % (branch, build_id, target) |
| ) |
| |
| @classmethod |
| def GetArtifacts(cls, branch, build_id, target): |
| """Get the list of artifacts for given build id and target. |
| |
| The return value is a list of dictionaries, each containing information |
| about an artifact. |
| For example: |
| {u'contentType': u'application/octet-stream', |
| u'crc32': 4131231264, |
| u'lastModifiedTime': u'143518405786', |
| u'md5': u'c04c823a64293aa5bf508e2eb4683ec8', |
| u'name': u'fastboot', |
| u'revision': u'HsXLpGsgEaqj654THKvR/A==', |
| u'size': u'6999296'}, |
| |
| Args: |
| branch: branch of the desired build. |
| build_id: Build id of the Android build, e.g., 2155602. |
| target: Target of the Android build, e.g., shamu-userdebug. |
| |
| Returns: |
| A list of artifacts for given build id and target. |
| """ |
| service_obj = cls._GetServiceObject() |
| cls._VerifyBranch(service_obj, branch, build_id, target) |
| build_type = cls._GetBuildType(build_id) |
| |
| # Get all artifacts for the given build_id and target. |
| artifacts = [] |
| req = service_obj.buildartifact().list( |
| buildType=build_type, |
| buildId=build_id, |
| target=target, |
| attemptId="latest", |
| maxResults=10, |
| ) |
| while req: |
| response = req.execute(num_retries=MAX_ATTEMPTS) |
| if not response: |
| break |
| artifacts.extend(response.get("artifacts", [])) |
| req = service_obj.buildartifact().list_next(req, response) |
| |
| return artifacts |
| |
| @classmethod |
| @retry.retry(Exception, timeout_min=DOWNLOAD_TIMEOUT_MINS) |
| def Download(cls, branch, build_id, target, resource_id, dest_file): |
| """Download the list of artifacts for given build id and target. |
| |
| Args: |
| branch: branch of the desired build. |
| build_id: Build id of the Android build, e.g., 2155602. |
| target: Target of the Android build, e.g., shamu-userdebug. |
| resource_id: Name of the artifact to download. |
| dest_file: Path to the file to download to. |
| """ |
| service_obj = cls._GetServiceObject() |
| cls._VerifyBranch(service_obj, branch, build_id, target) |
| |
| # Delete partially downloaded file. |
| osutils.SafeUnlink(dest_file) |
| |
| build_type = cls._GetBuildType(build_id) |
| # TODO(dshi): Add retry logic here to avoid API flakes. |
| download_req = service_obj.buildartifact().get_media( |
| buildType=build_type, |
| buildId=build_id, |
| target=target, |
| attemptId="latest", |
| resourceId=resource_id, |
| ) |
| with io.FileIO(dest_file, mode="wb") as fh: |
| downloader = http.MediaIoBaseDownload( |
| fh, download_req, chunksize=DEFAULT_CHUNKSIZE |
| ) |
| done = None |
| while not done: |
| _, done = downloader.next_chunk(num_retries=MAX_ATTEMPTS) |
| |
| @classmethod |
| @retry.retry( |
| Exception, |
| timeout_min=QUERY_TIMEOUT_MINS, |
| denylist=[AndroidBuildFetchError], |
| ) |
| def GetLatestBuildID(cls, target, branch): |
| """Get the latest build ID for the given target and branch. |
| |
| Args: |
| branch: branch of the desired build. |
| target: Target of the Android build, e.g., shamu-userdebug. |
| |
| Returns: |
| Build id of the latest successful Android build for the given target |
| and branch, e.g., 2155602. |
| """ |
| service_obj = cls._GetServiceObject() |
| builds = ( |
| service_obj.build() |
| .list( |
| buildType="submitted", |
| branch=branch, |
| target=target, |
| successful=True, |
| maxResults=1, |
| ) |
| .execute(num_retries=MAX_ATTEMPTS) |
| ) |
| if not builds or not builds["builds"]: |
| raise AndroidBuildFetchError( |
| "Failed to locate build with branch %s and target %s." |
| % (branch, target) |
| ) |
| return builds["builds"][0]["buildId"] |