| # |
| # Copyright (C) 2017 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| import httplib2 |
| import logging |
| import threading |
| import time |
| |
| from googleapiclient import discovery |
| from googleapiclient import http |
| from oauth2client.service_account import ServiceAccountCredentials |
| |
| from host_controller.tfc import command_task |
| |
| API_NAME = "tradefed_cluster" |
| API_VERSION = "v1" |
| SCOPES = ['https://www.googleapis.com/auth/userinfo.email'] |
| |
| |
| class TfcClient(object): |
| """The class for accessing TFC API. |
| |
| Attributes: |
| _service: The TFC service. |
| """ |
| |
| def __init__(self, service): |
| self._service = service |
| |
| def LeaseHostTasks(self, cluster_id, next_cluster_ids, hostname, device_infos): |
| """Calls leasehosttasks. |
| |
| Args: |
| cluster_id: A string, the primary cluster to lease tasks from. |
| next_cluster_ids: A list of Strings, the secondary clusters to lease |
| tasks from. |
| hostname: A string, the name of the TradeFed host. |
| device_infos: A list of DeviceInfo, the information about the |
| devices connected to the host. |
| |
| Returns: |
| A list of command_task.CommandTask, the leased tasks. |
| """ |
| lease = {"hostname": hostname, |
| "cluster": cluster_id, |
| "next_cluster_ids": next_cluster_ids, |
| "device_infos": [x.ToLeaseHostTasksJson() |
| for x in device_infos]} |
| logging.info("tasks.leasehosttasks body=%s", lease) |
| tasks = self._service.tasks().leasehosttasks(body=lease).execute() |
| logging.info("tasks.leasehosttasks response=%s", tasks) |
| if "tasks" not in tasks: |
| return [] |
| return [command_task.CommandTask(**task) for task in tasks["tasks"]] |
| |
| def TestResourceList(self, request_id): |
| """Calls testResource.list. |
| |
| Args: |
| request_id: int, id of request to grab resources for |
| |
| Returns: |
| A list of TestResources |
| """ |
| logging.info("request.testResource.list request_id=%s", request_id) |
| test_resources = self._service.requests().testResource().list(request_id=request_id).execute() |
| logging.info("request.testResource.list response=%s", test_resources) |
| if 'test_resources' not in test_resources: |
| return {} |
| return test_resources['test_resources'] |
| |
| @staticmethod |
| def CreateDeviceSnapshot(cluster_id, hostname, dev_infos): |
| """Creates a DeviceSnapshot which can be uploaded as host event. |
| |
| Args: |
| cluster_id: A string, the cluster to upload snapshot to. |
| hostname: A string, the name of the TradeFed host. |
| dev_infos: A list of DeviceInfo. |
| |
| Returns: |
| A JSON object. |
| """ |
| obj = {"time": int(time.time()), |
| "data": {}, |
| "cluster": cluster_id, |
| "hostname": hostname, |
| "tf_version": "(unknown)", |
| "type": "DeviceSnapshot", |
| "device_infos": [x.ToDeviceSnapshotJson() for x in dev_infos]} |
| return obj |
| |
| def SubmitHostEvents(self, host_events): |
| """Calls host_events.submit. |
| |
| Args: |
| host_events: A list of JSON objects. Currently DeviceSnapshot is |
| the only type of host events. |
| """ |
| json_obj = {"host_events": host_events} |
| logging.info("host_events.submit body=%s", json_obj) |
| self._service.host_events().submit(body=json_obj).execute() |
| |
| def SubmitCommandEvents(self, command_events): |
| """Calls command_events.submit. |
| |
| Args: |
| command_events: A list of JSON objects converted from CommandAttempt. |
| """ |
| json_obj = {"command_events": command_events} |
| logging.info("command_events.submit body=%s", json_obj) |
| self._service.command_events().submit(body=json_obj).execute() |
| |
| def NewRequest(self, request): |
| """Calls requests.new. |
| |
| Args: |
| request: An instance of Request. |
| |
| Returns: |
| A JSON object, the new request queued in the cluster. |
| |
| Sample |
| {'state': 'UNKNOWN', |
| 'command_line': 'vts-codelab --run-target sailfish', |
| 'id': '2', |
| 'user': 'testuser'} |
| """ |
| body = request.GetBody() |
| params = request.GetParameters() |
| logging.info("requests.new parameters=%s body=%s", params, body) |
| return self._service.requests().new(body=body, **params).execute() |
| |
| |
| def CreateTfcClient(api_root, oauth2_service_json, |
| api_name=API_NAME, api_version=API_VERSION, scopes=SCOPES): |
| """Builds an object of TFC service from a given URL. |
| |
| Args: |
| api_root: The URL to the service. |
| oauth2_service_json: The path to service account key file. |
| |
| Returns: |
| A TfcClient object. |
| """ |
| discovery_url = "%s/discovery/v1/apis/%s/%s/rest" % ( |
| api_root, api_name, api_version) |
| logging.info("Build service from: %s", discovery_url) |
| credentials = ServiceAccountCredentials.from_json_keyfile_name( |
| oauth2_service_json, scopes=scopes) |
| # httplib2.Http is not thread-safe. Use thread local object. |
| thread_local = threading.local() |
| thread_local.http = credentials.authorize(httplib2.Http()) |
| def BuildHttpRequest(unused_http, *args, **kwargs): |
| if not hasattr(thread_local, "http"): |
| thread_local.http = credentials.authorize(httplib2.Http()) |
| return http.HttpRequest(thread_local.http, *args, **kwargs) |
| |
| service = discovery.build( |
| api_name, api_version, http=thread_local.http, |
| discoveryServiceUrl=discovery_url, |
| requestBuilder=BuildHttpRequest) |
| return TfcClient(service) |