blob: de7d5271dd8056211454356564ea33c10d6c852a [file] [log] [blame]
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Provides functions to work with AppEngine endpoints"""
import httplib2
import logging
import os
import sys
import time
sys.path.insert(0, os.path.join(
os.path.dirname(os.path.dirname(__file__)), 'third_party'))
import apiclient.discovery
import apiclient.errors
import oauth2client.appengine
AUTH_SCOPE = 'https://www.googleapis.com/auth/userinfo.email'
def _authenticated_http(http, scope):
credentials = oauth2client.appengine.AppAssertionCredentials(scope=scope)
return credentials.authorize(http or httplib2.Http())
def build_client(api_name, api_version, discovery_url, http=None,
num_tries=5):
"""Creates HTTP endpoints client, retries connection errors.
All requests to the endpoints will be authenticated with AppEngine app
crendetials.
Args:
api_name: Name of the endpoints API.
api_version: Version of the endpoints API.
discovery_url: URL of the discovery endpoint. Should contain {api} and
{apiVersion} placeholders, e.g. https://your-app.appspot.com/_ah/api/
discovery/v1/apis/{api}/{apiVersion}/rest.
http: Optional HTTP object. If not specified httplib2.Http() will be used.
num_retries: Maximum number of retries to create client.
Returns:
Constructed client.
"""
tries = 0
while True:
tries += 1
try:
return apiclient.discovery.build(
api_name, api_version,
discoveryServiceUrl=discovery_url,
http=_authenticated_http(http, AUTH_SCOPE))
except apiclient.errors.HttpError as e:
if tries == num_tries:
logging.exception(
'apiclient.discovery.build() failed for %s too many times.',
api_name)
raise e
delay = 2 ** (tries - 1)
logging.warn(
'apiclient.discovery.build() failed for %s: %s', api_name, e)
logging.warn(
'Retrying apiclient.discovery.build() in %s seconds.', delay)
time.sleep(delay)
def retry_request(request, num_tries=5):
"""Retries provided endpoint request up to num_retries times."""
tries = 0
while True:
tries += 1
try:
return request.execute()
except apiclient.errors.HttpError as e:
# This retries internal server (500, 503) and quota (403) errors.
# TODO(sergiyb): Figure out if we still need to retry 403 errors. They
# were used by codesite to fail on quota errors, but it is unclear whether
# Monorail uses same logic or not.
if tries == num_tries or e.resp.status not in [403, 500, 503]:
raise
time.sleep(2 ** (tries - 1))