blob: 906ae9874bedd0e23d9392472e12fd576f04fff9 [file] [log] [blame]
xixuan878b1eb2017-03-20 15:58:17 -07001# Copyright 2017 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Module for interacting with google APIs."""
Xixuan Wu5d6063e2017-09-05 16:15:07 -07006# pylint: disable=g-bad-import-order
Xixuan Wud55ac6e2019-03-14 10:56:39 -07007# pylint: disable=g-bad-exception-name
Xixuan Wu5d6063e2017-09-05 16:15:07 -07008
9import httplib2
xixuan878b1eb2017-03-20 15:58:17 -070010
11import apiclient
xixuan878b1eb2017-03-20 15:58:17 -070012import constants
13import file_getter
14
15from oauth2client import service_account
16from oauth2client.contrib import appengine
17
18
19class RestClientError(Exception):
Xixuan Wu5d6063e2017-09-05 16:15:07 -070020 """Raised when there is a general error."""
xixuan878b1eb2017-03-20 15:58:17 -070021
22
23class NoServiceRestClientError(RestClientError):
Xixuan Wu5d6063e2017-09-05 16:15:07 -070024 """Raised when there is no ready service for a google API."""
xixuan878b1eb2017-03-20 15:58:17 -070025
26
27class BaseRestClient(object):
Xixuan Wu5d6063e2017-09-05 16:15:07 -070028 """Base class of REST client for google APIs."""
xixuan878b1eb2017-03-20 15:58:17 -070029
Xixuan Wu5d6063e2017-09-05 16:15:07 -070030 def __init__(self, scopes, service_name, service_version):
31 """Initialize a REST client to connect to a google API.
xixuan878b1eb2017-03-20 15:58:17 -070032
Xixuan Wu5d6063e2017-09-05 16:15:07 -070033 Args:
34 scopes: the scopes of the to-be-connected API.
35 service_name: the service name of the to-be-connected API.
36 service_version: the service version of the to-be-connected API.
37 """
38 self.running_env = constants.environment()
39 self.scopes = scopes
40 self.service_name = service_name
41 self.service_version = service_version
xixuan878b1eb2017-03-20 15:58:17 -070042
Xixuan Wu5d6063e2017-09-05 16:15:07 -070043 @property
44 def service(self):
45 if not self._service:
46 raise NoServiceRestClientError('No service created for calling API')
xixuan878b1eb2017-03-20 15:58:17 -070047
Xixuan Wu5d6063e2017-09-05 16:15:07 -070048 return self._service
xixuan878b1eb2017-03-20 15:58:17 -070049
Xixuan Wu5d6063e2017-09-05 16:15:07 -070050 def create_service(self, discovery_url=None):
51 """Create the service for a google API."""
52 self._init_credentials()
53 # Explicitly specify timeout for http to avoid DeadlineExceededError.
54 # It's used for services like AndroidBuild API, which raise such error
55 # when being triggered too many calls in a short time frame.
56 # http://stackoverflow.com/questions/14698119/httpexception-deadline-exceeded-while-waiting-for-http-response-from-url-dead
57 http_auth = self._credentials.authorize(httplib2.Http(timeout=30))
58 if discovery_url is None:
59 self._service = apiclient.discovery.build(
60 self.service_name, self.service_version,
61 http=http_auth)
62 else:
63 self._service = apiclient.discovery.build(
64 self.service_name, self.service_version, http=http_auth,
65 discoveryServiceUrl=discovery_url)
xixuan878b1eb2017-03-20 15:58:17 -070066
Xixuan Wu5d6063e2017-09-05 16:15:07 -070067 def _init_credentials(self):
68 """Initialize the credentials for a google API."""
69 if (self.running_env == constants.RunningEnv.ENV_STANDALONE or
70 self.running_env == constants.RunningEnv.ENV_DEVELOPMENT_SERVER):
71 # Running locally
72 service_credentials = service_account.ServiceAccountCredentials
73 self._credentials = service_credentials.from_json_keyfile_name(
Xixuan Wu26d06e02017-09-20 14:50:28 -070074 file_getter.STAGING_CLIENT_SECRETS_FILE, self.scopes)
Xixuan Wu5d6063e2017-09-05 16:15:07 -070075 else:
76 # Running in app-engine production
77 self._credentials = appengine.AppAssertionCredentials(self.scopes)
xixuan878b1eb2017-03-20 15:58:17 -070078
79
80class AndroidBuildRestClient(object):
Xixuan Wu5d6063e2017-09-05 16:15:07 -070081 """REST client for android build API."""
xixuan878b1eb2017-03-20 15:58:17 -070082
Xixuan Wu5d6063e2017-09-05 16:15:07 -070083 def __init__(self, rest_client):
84 """Initialize a REST client for connecting to Android Build API."""
85 self._rest_client = rest_client
86 self._rest_client.create_service()
xixuan878b1eb2017-03-20 15:58:17 -070087
Xixuan Wu5d6063e2017-09-05 16:15:07 -070088 def get_latest_build_id(self, branch, target):
89 """Get the latest build id for a given branch and target.
xixuan878b1eb2017-03-20 15:58:17 -070090
Xixuan Wu5d6063e2017-09-05 16:15:07 -070091 Args:
92 branch: an android build's branch
93 target: an android build's target
xixuan878b1eb2017-03-20 15:58:17 -070094
Xixuan Wu5d6063e2017-09-05 16:15:07 -070095 Returns:
96 A string representing latest build id.
97 """
98 request = self._rest_client.service.build().list(
99 buildType='submitted',
100 branch=branch,
101 target=target,
102 successful=True,
103 maxResults=1)
104 builds = request.execute(num_retries=10)
105 if not builds or not builds['builds']:
106 return None
xixuan878b1eb2017-03-20 15:58:17 -0700107
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700108 return builds['builds'][0]['buildId']
xixuan878b1eb2017-03-20 15:58:17 -0700109
110
111class StorageRestClient(object):
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700112 """REST client for google storage API."""
xixuan878b1eb2017-03-20 15:58:17 -0700113
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700114 def __init__(self, rest_client):
115 """Initialize a REST client for connecting to Google storage API."""
116 self._rest_client = rest_client
117 self._rest_client.create_service()
xixuan878b1eb2017-03-20 15:58:17 -0700118
Xixuan Wud55ac6e2019-03-14 10:56:39 -0700119 def read_object(self, bucket, object_path):
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700120 """Read the contents of input_object in input_bucket.
xixuan878b1eb2017-03-20 15:58:17 -0700121
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700122 Args:
Xixuan Wud55ac6e2019-03-14 10:56:39 -0700123 bucket: A string to indicate the bucket for fetching the object.
124 e.g. constants.StorageBucket.PROD_SUITE_SCHEDULER
125 object_path: A string to indicate the path of the object to read the
126 contents.
xixuan878b1eb2017-03-20 15:58:17 -0700127
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700128 Returns:
129 the stripped string contents of the input object.
xixuan878b1eb2017-03-20 15:58:17 -0700130
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700131 Raises:
132 apiclient.errors.HttpError
133 """
134 req = self._rest_client.service.objects().get_media(
Xixuan Wud55ac6e2019-03-14 10:56:39 -0700135 bucket=bucket,
136 object=object_path)
137 return req.execute()
138
139 def upload_object(self, bucket, src_object_path, dest_object_path):
140 """Upload object_path to input_bucket.
141
142 Args:
143 bucket: A string to indicate the bucket for the object to be uploaded to.
144 src_object_path: A string the full path of the object to upload.
145 dest_object_path: A string path inside bucket to upload to.
146
147 Returns:
148 A dict of uploaded object info.
149
150 Raises:
151 apiclient.errors.HttpError
152 """
153 req = self._rest_client.service.objects().insert(
154 bucket=bucket,
155 name=dest_object_path,
156 media_body=src_object_path,
157 media_mime_type='text/plain',
158 )
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700159 return req.execute()
xixuan878b1eb2017-03-20 15:58:17 -0700160
161
162class CalendarRestClient(object):
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700163 """Class of REST client for google calendar API."""
xixuan878b1eb2017-03-20 15:58:17 -0700164
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700165 def __init__(self, rest_client):
166 """Initialize a REST client for connecting to Google calendar API."""
167 self._rest_client = rest_client
168 self._rest_client.create_service()
xixuan878b1eb2017-03-20 15:58:17 -0700169
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700170 def add_event(self, calendar_id, input_event):
171 """Add events of a given calendar.
xixuan878b1eb2017-03-20 15:58:17 -0700172
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700173 Args:
174 calendar_id: the ID of the given calendar.
175 input_event: the event to be added.
176 """
177 self._rest_client.service.events().insert(
178 calendarId=calendar_id,
179 body=input_event).execute()
xixuan878b1eb2017-03-20 15:58:17 -0700180
181
Xixuan Wu6f117e92017-10-27 10:51:58 -0700182class StackdriverRestClient(object):
183 """REST client for google storage API."""
184
185 def __init__(self, rest_client):
186 """Initialize a REST client for connecting to Google storage API."""
187 self._rest_client = rest_client
188 self._rest_client.create_service()
189
190 def read_logs(self, request):
191 # project_id, page_size, order_by, query_filter=''):
192 """Read the logs of the project_id based on all filters.
193
194 Args:
195 request: a request dict generated by
196 stackdriver_lib.form_logging_client_request.
197
198 Returns:
199 A json object, can be parsed by
200 stackdriver_lib.parse_logging_client_response.
201
202 Raises:
203 apiclient.errors.HttpError
204 """
205 req = self._rest_client.service.entries().list(
206 fields='entries/protoPayload', body=request)
207 return req.execute()
208
209
xixuan878b1eb2017-03-20 15:58:17 -0700210class SwarmingRestClient(object):
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700211 """REST client for swarming proxy API."""
xixuan878b1eb2017-03-20 15:58:17 -0700212
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700213 DISCOVERY_URL_PATTERN = '%s/discovery/v1/apis/%s/%s/rest'
xixuan878b1eb2017-03-20 15:58:17 -0700214
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700215 def __init__(self, rest_client, service_url):
216 self._rest_client = rest_client
217 discovery_url = self.DISCOVERY_URL_PATTERN % (
218 service_url, rest_client.service_name, rest_client.service_version)
219 self._rest_client.create_service(discovery_url=discovery_url)
xixuan878b1eb2017-03-20 15:58:17 -0700220
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700221 def create_task(self, request):
222 """Create new task.
xixuan878b1eb2017-03-20 15:58:17 -0700223
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700224 Args:
225 request: a json-compatible dict expected by swarming server.
226 See _to_raw_request's output in swarming_lib.py for details.
xixuan878b1eb2017-03-20 15:58:17 -0700227
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700228 Returns:
229 A json dict returned by API task.new.
230 """
231 return self._rest_client.service.tasks().new(
232 fields='request,task_id', body=request).execute()
xixuan878b1eb2017-03-20 15:58:17 -0700233
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700234 def get_task_result(self, task_id):
235 """Get task results by a given task_id.
xixuan878b1eb2017-03-20 15:58:17 -0700236
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700237 Args:
238 task_id: A string, represents task id.
239
240 Returns:
241 A json dict returned by API task.result.
242 """
243 return self._rest_client.service.task().result(
244 task_id=task_id).execute()
Xixuan Wu7d142a92019-04-26 12:03:02 -0700245
246
247class BigqueryRestClient(object):
248 """Class of REST client for Bigquery API."""
249
250 def __init__(self, rest_client):
251 """Initialize a REST client for connecting to Bigquery API."""
252 self._rest_client = rest_client
253 self._rest_client.create_service()
254
255 def get_past_skylab_job_nums(self, hours):
256 """Query skylab job numbers scheduled in past hours.
257
258 Args:
259 hours: An integer.
260
261 Returns:
262 A json dict returned by API bigquery.jobs.query, e.g.
263 # {...,
264 # "rows": [
265 # {
266 # "f": [ # field
267 # {
268 # "v": # value
269 # }
270 # ]
271 # }
272 # ]
273 # }
274 """
275 query_data = {
276 'query': ('SELECT COUNT(*) FROM swarming.task_requests AS r '
277 'WHERE \'user:suite_scheduler\' in UNNEST(r.tags) and '
278 'create_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), '
279 'INTERVAL %d HOUR);') % hours,
280 'useLegacySql': False
281 }
282 return self._rest_client.service.jobs().query(
283 projectId='chromeos-swarming',
284 body=query_data).execute()