Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(272)

Unified Diff: infra_libs/httplib2_utils.py

Issue 2213143002: Add infra_libs as a bootstrap dependency. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Removed the ugly import hack Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: infra_libs/httplib2_utils.py
diff --git a/infra_libs/httplib2_utils.py b/infra_libs/httplib2_utils.py
deleted file mode 100644
index a37447f0259cc844527117439ca6ee24141e2be5..0000000000000000000000000000000000000000
--- a/infra_libs/httplib2_utils.py
+++ /dev/null
@@ -1,320 +0,0 @@
-# Copyright 2015 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.
-
-import collections
-import copy
-import json
-import logging
-import os
-import re
-import socket
-import sys
-import time
-
-import httplib2
-import oauth2client.client
-
-from googleapiclient import errors
-from infra_libs.ts_mon.common import http_metrics
-
-DEFAULT_SCOPES = ['email']
-
-# default timeout for http requests, in seconds
-DEFAULT_TIMEOUT = 30
-
-# This is part of the API.
-if sys.platform.startswith('win'): # pragma: no cover
- SERVICE_ACCOUNTS_CREDS_ROOT = 'C:\\creds\\service_accounts'
-else:
- SERVICE_ACCOUNTS_CREDS_ROOT = '/creds/service_accounts'
-
-
-class AuthError(Exception):
- pass
-
-
-def load_service_account_credentials(credentials_filename,
- service_accounts_creds_root=None):
- """Loads and validate a credential JSON file.
-
- Example of a well-formatted file:
- {
- "private_key_id": "4168d274cdc7a1eaef1c59f5b34bdf255",
- "private_key": ("-----BEGIN PRIVATE KEY-----\nMIIhkiG9w0BAQEFAASCAmEwsd"
- "sdfsfFd\ngfxFChctlOdTNm2Wrr919Nx9q+sPV5ibyaQt5Dgn89fKV"
- "jftrO3AMDS3sMjaE4Ib\nZwJgy90wwBbMT7/YOzCgf5PZfivUe8KkB"
- -----END PRIVATE KEY-----\n",
- "client_email": "234243-rjstu8hi95iglc8at3@developer.gserviceaccount.com",
- "client_id": "234243-rjstu8hi95iglc8at3.apps.googleusercontent.com",
- "type": "service_account"
- }
-
- Args:
- credentials_filename (str): path to a .json file containing credentials
- for a Cloud platform service account.
-
- Keyword Args:
- service_accounts_creds_root (str or None): location where all service
- account credentials are stored. ``credentials_filename`` is relative
- to this path. None means 'use default location'.
-
- Raises:
- AuthError: if the file content is invalid.
- """
- service_accounts_creds_root = (service_accounts_creds_root
- or SERVICE_ACCOUNTS_CREDS_ROOT)
-
- service_account_file = os.path.join(service_accounts_creds_root,
- credentials_filename)
- try:
- with open(service_account_file, 'r') as f:
- key = json.load(f)
- except ValueError as e:
- raise AuthError('Parsing of file as JSON failed (%s): %s',
- e, service_account_file)
-
- if key.get('type') != 'service_account':
- msg = ('Credentials type must be for a service_account, got %s.'
- ' Check content of %s' % (key.get('type'), service_account_file))
- logging.error(msg)
- raise AuthError(msg)
-
- if not key.get('client_email'):
- msg = ('client_email field missing in credentials json file. '
- ' Check content of %s' % service_account_file)
- logging.error(msg)
- raise AuthError(msg)
-
- if not key.get('private_key'):
- msg = ('private_key field missing in credentials json. '
- ' Check content of %s' % service_account_file)
- logging.error(msg)
- raise AuthError(msg)
-
- return key
-
-
-def get_signed_jwt_assertion_credentials(credentials_filename,
- scope=None,
- service_accounts_creds_root=None):
- """Factory for SignedJwtAssertionCredentials
-
- Reads and validate the json credential file.
-
- Args:
- credentials_filename (str): path to the service account key file.
- See load_service_account_credentials() docstring for the file format.
-
- Keyword Args:
- scope (str|list of str): scope(s) of the credentials being
- requested. Defaults to https://www.googleapis.com/auth/userinfo.email.
- service_accounts_creds_root (str or None): location where all service
- account credentials are stored. ``credentials_filename`` is relative
- to this path. None means 'use default location'.
- """
- scope = scope or DEFAULT_SCOPES
- if isinstance(scope, basestring):
- scope = [scope]
- assert all(isinstance(s, basestring) for s in scope)
-
- key = load_service_account_credentials(
- credentials_filename,
- service_accounts_creds_root=service_accounts_creds_root)
-
- return oauth2client.client.SignedJwtAssertionCredentials(
- key['client_email'], key['private_key'], scope)
-
-
-def get_authenticated_http(credentials_filename,
- scope=None,
- service_accounts_creds_root=None,
- http_identifier=None,
- timeout=DEFAULT_TIMEOUT):
- """Creates an httplib2.Http wrapped with a service account authenticator.
-
- Args:
- credentials_filename (str): relative path to the file containing
- credentials in json format. Path is relative to the default
- location where credentials are stored (platform-dependent).
-
- Keyword Args:
- scope (str|list of str): scope(s) of the credentials being
- requested. Defaults to https://www.googleapis.com/auth/userinfo.email.
- service_accounts_creds_root (str or None): location where all service
- account credentials are stored. ``credentials_filename`` is relative
- to this path. None means 'use default location'.
- http_identifier (str): if provided, returns an instrumented http request
- and use this string to identify it to ts_mon.
- timeout (int): timeout passed to httplib2.Http, in seconds.
-
- Returns:
- httplib2.Http authenticated with master's service account.
- """
- creds = get_signed_jwt_assertion_credentials(
- credentials_filename,
- scope=scope,
- service_accounts_creds_root=service_accounts_creds_root)
-
- if http_identifier:
- http = InstrumentedHttp(http_identifier, timeout=timeout)
- else:
- http = httplib2.Http(timeout=timeout)
- return creds.authorize(http)
-
-class RetriableHttp(object):
- """A httplib2.Http object that retries on failure."""
-
- def __init__(self, http, max_tries=5, backoff_time=1,
- retrying_statuses_fn=None):
- """
- Args:
- http: an httplib2.Http instance
- max_tries: a number of maximum tries
- backoff_time: a number of seconds to sleep between retries
- retrying_statuses_fn: a function that returns True if a given status
- should be retried
- """
- self._http = http
- self._max_tries = max_tries
- self._backoff_time = backoff_time
- self._retrying_statuses_fn = retrying_statuses_fn or \
- set(range(500,599)).__contains__
-
- def request(self, uri, method='GET', body=None, *args, **kwargs):
- for i in range(1, self._max_tries + 1):
- try:
- response, content = self._http.request(uri, method, body, *args,
- **kwargs)
-
- if self._retrying_statuses_fn(response.status):
- logging.info('RetriableHttp: attempt %d receiving status %d, %s',
- i, response.status,
- 'final attempt' if i == self._max_tries else \
- 'will retry')
- else:
- break
- except (ValueError, errors.Error,
- socket.timeout, socket.error, socket.herror, socket.gaierror,
- httplib2.HttpLib2Error) as error:
- logging.info('RetriableHttp: attempt %d received exception: %s, %s',
- i, error, 'final attempt' if i == self._max_tries else \
- 'will retry')
- if i == self._max_tries:
- raise
- time.sleep(self._backoff_time)
-
- return response, content
-
- def __getattr__(self, name):
- return getattr(self._http, name)
-
- def __setattr__(self, name, value):
- if name in ('request', '_http', '_max_tries', '_backoff_time',
- '_retrying_statuses_fn'):
- self.__dict__[name] = value
- else:
- setattr(self._http, name, value)
-
-class InstrumentedHttp(httplib2.Http):
- """A httplib2.Http object that reports ts_mon metrics about its requests."""
-
- def __init__(self, name, time_fn=time.time, timeout=DEFAULT_TIMEOUT,
- **kwargs):
- """
- Args:
- name: An identifier for the HTTP requests made by this object.
- time_fn: Function returning the current time in seconds. Use for testing
- purposes only.
- """
-
- super(InstrumentedHttp, self).__init__(timeout=timeout, **kwargs)
- self.fields = {'name': name, 'client': 'httplib2'}
- self.time_fn = time_fn
-
- def _update_metrics(self, status, start_time):
- status_fields = {'status': status}
- status_fields.update(self.fields)
- http_metrics.response_status.increment(fields=status_fields)
-
- duration_msec = (self.time_fn() - start_time) * 1000
- http_metrics.durations.add(duration_msec, fields=self.fields)
-
- def request(self, uri, method="GET", body=None, *args, **kwargs):
- request_bytes = 0
- if body is not None:
- request_bytes = len(body)
- http_metrics.request_bytes.add(request_bytes, fields=self.fields)
-
- start_time = self.time_fn()
- try:
- response, content = super(InstrumentedHttp, self).request(
- uri, method, body, *args, **kwargs)
- except socket.timeout:
- self._update_metrics(http_metrics.STATUS_TIMEOUT, start_time)
- raise
- except (socket.error, socket.herror, socket.gaierror):
- self._update_metrics(http_metrics.STATUS_ERROR, start_time)
- raise
- except httplib2.HttpLib2Error:
- self._update_metrics(http_metrics.STATUS_EXCEPTION, start_time)
- raise
- http_metrics.response_bytes.add(len(content), fields=self.fields)
-
- self._update_metrics(response.status, start_time)
-
- return response, content
-
-
-class HttpMock(object):
- """Mock of httplib2.Http"""
- HttpCall = collections.namedtuple('HttpCall', ('uri', 'method', 'body',
- 'headers'))
-
- def __init__(self, uris):
- """
- Args:
- uris(dict): list of (uri, headers, body). `uri` is a regexp for
- matching the requested uri, (headers, body) gives the values returned
- by the mock. Uris are tested in the order from `uris`.
- `headers` is a dict mapping headers to value. The 'status' key is
- mandatory. `body` is a string.
- Ex: [('.*', {'status': 200}, 'nicely done.')]
- """
- self._uris = []
- self.requests_made = []
-
- for value in uris:
- if not isinstance(value, (list, tuple)) or len(value) != 3:
- raise ValueError("'uris' must be a sequence of (uri, headers, body)")
- uri, headers, body = value
- compiled_uri = re.compile(uri)
- if not isinstance(headers, dict):
- raise TypeError("'headers' must be a dict")
- if not 'status' in headers:
- raise ValueError("'headers' must have 'status' as a key")
-
- new_headers = copy.copy(headers)
- new_headers['status'] = int(new_headers['status'])
-
- if not isinstance(body, basestring):
- raise TypeError("'body' must be a string, got %s" % type(body))
- self._uris.append((compiled_uri, new_headers, body))
-
- # pylint: disable=unused-argument
- def request(self, uri,
- method='GET',
- body=None,
- headers=None,
- redirections=1,
- connection_type=None):
- self.requests_made.append(self.HttpCall(uri, method, body, headers))
- headers = None
- body = None
- for candidate in self._uris:
- if candidate[0].match(uri):
- _, headers, body = candidate
- break
- if not headers:
- raise AssertionError("Unexpected request to %s" % uri)
- return httplib2.Response(headers), body

Powered by Google App Engine
This is Rietveld 408576698