
- Run codebase through YAPF for formatting - Add tox configuration for yapf and pep8 - Fix some non-YAPF pep8 failures - Enhance verify_site for better MaaS-integration testing - Create initial basic functional test Change-Id: Ie5b5275d7795693a6551764362aee916b99b3e56
176 lines
5.9 KiB
Python
176 lines
5.9 KiB
Python
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
|
|
#
|
|
# 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 logging
|
|
|
|
from oauthlib import oauth1
|
|
import requests
|
|
import requests.auth as req_auth
|
|
import base64
|
|
|
|
|
|
class MaasOauth(req_auth.AuthBase):
|
|
def __init__(self, apikey):
|
|
self.consumer_key, self.token_key, self.token_secret = apikey.split(
|
|
':')
|
|
self.consumer_secret = ""
|
|
self.realm = "OAuth"
|
|
|
|
self.oauth_client = oauth1.Client(
|
|
self.consumer_key,
|
|
self.consumer_secret,
|
|
self.token_key,
|
|
self.token_secret,
|
|
signature_method=oauth1.SIGNATURE_PLAINTEXT,
|
|
realm=self.realm)
|
|
|
|
def __call__(self, req):
|
|
headers = req.headers
|
|
url = req.url
|
|
method = req.method
|
|
body = None if req.body is None or len(req.body) == 0 else req.body
|
|
|
|
new_url, signed_headers, new_body = self.oauth_client.sign(
|
|
url, method, body, headers)
|
|
|
|
req.headers['Authorization'] = signed_headers['Authorization']
|
|
|
|
return req
|
|
|
|
|
|
class MaasRequestFactory(object):
|
|
def __init__(self, base_url, apikey):
|
|
self.base_url = base_url
|
|
self.apikey = apikey
|
|
|
|
self.signer = MaasOauth(apikey)
|
|
self.http_session = requests.Session()
|
|
|
|
# TODO Get logger name from config
|
|
self.logger = logging.getLogger('drydock')
|
|
|
|
def get(self, endpoint, **kwargs):
|
|
return self._send_request('GET', endpoint, **kwargs)
|
|
|
|
def post(self, endpoint, **kwargs):
|
|
return self._send_request('POST', endpoint, **kwargs)
|
|
|
|
def delete(self, endpoint, **kwargs):
|
|
return self._send_request('DELETE', endpoint, **kwargs)
|
|
|
|
def put(self, endpoint, **kwargs):
|
|
return self._send_request('PUT', endpoint, **kwargs)
|
|
|
|
def test_connectivity(self):
|
|
try:
|
|
resp = self.get('version/')
|
|
except requests.Timeout(ex):
|
|
raise errors.TransientDriverError("Timeout connection to MaaS")
|
|
|
|
if resp.status_code in [500, 503]:
|
|
raise errors.TransientDriverError("Received 50x error from MaaS")
|
|
|
|
if resp.status_code != 200:
|
|
raise errors.PersistentDriverError(
|
|
"Received unexpected error from MaaS")
|
|
|
|
return True
|
|
|
|
def test_authentication(self):
|
|
try:
|
|
resp = self.get('account/', op='list_authorisation_tokens')
|
|
except requests.Timeout(ex):
|
|
raise errors.TransientDriverError("Timeout connection to MaaS")
|
|
except:
|
|
raise errors.PersistentDriverError("Error accessing MaaS")
|
|
|
|
if resp.status_code in [401, 403]:
|
|
raise errors.PersistentDriverError(
|
|
"MaaS API Authentication Failed")
|
|
|
|
if resp.status_code in [500, 503]:
|
|
raise errors.TransientDriverError("Received 50x error from MaaS")
|
|
|
|
if resp.status_code != 200:
|
|
raise errors.PersistentDriverError(
|
|
"Received unexpected error from MaaS")
|
|
|
|
return True
|
|
|
|
def _send_request(self, method, endpoint, **kwargs):
|
|
# Delete auth mechanism if defined
|
|
kwargs.pop('auth', None)
|
|
|
|
headers = kwargs.pop('headers', {})
|
|
|
|
if 'Accept' not in headers.keys():
|
|
headers['Accept'] = 'application/json'
|
|
|
|
if kwargs.get('files', None) is not None:
|
|
files = kwargs.pop('files')
|
|
|
|
files_tuples = {}
|
|
|
|
for (k, v) in files.items():
|
|
if v is None:
|
|
continue
|
|
files_tuples[k] = (
|
|
None,
|
|
base64.b64encode(str(v).encode('utf-8')).decode('utf-8'),
|
|
'text/plain; charset="utf-8"', {
|
|
'Content-Transfer-Encoding': 'base64'
|
|
})
|
|
|
|
# elif isinstance(v, str):
|
|
# files_tuples[k] = (None, base64.b64encode(v.encode('utf-8')).decode('utf-8'), 'text/plain; charset="utf-8"', {'Content-Transfer-Encoding': 'base64'})
|
|
# elif isinstance(v, int) or isinstance(v, bool):
|
|
# if isinstance(v, bool):
|
|
# v = int(v)
|
|
# files_tuples[k] = (None, base64.b64encode(v.to_bytes(2, byteorder='big')), 'application/octet-stream', {'Content-Transfer-Encoding': 'base64'})
|
|
|
|
kwargs['files'] = files_tuples
|
|
|
|
params = kwargs.get('params', None)
|
|
|
|
if params is None and 'op' in kwargs.keys():
|
|
params = {'op': kwargs.pop('op')}
|
|
elif 'op' in kwargs.keys() and 'op' not in params.keys():
|
|
params['op'] = kwargs.pop('op')
|
|
elif 'op' in kwargs.keys():
|
|
kwargs.pop('op')
|
|
|
|
# TODO timeouts should be configurable
|
|
timeout = kwargs.pop('timeout', None)
|
|
if timeout is None:
|
|
timeout = (2, 30)
|
|
|
|
request = requests.Request(
|
|
method=method,
|
|
url=self.base_url + endpoint,
|
|
auth=self.signer,
|
|
headers=headers,
|
|
params=params,
|
|
**kwargs)
|
|
|
|
prepared_req = self.http_session.prepare_request(request)
|
|
|
|
resp = self.http_session.send(prepared_req, timeout=timeout)
|
|
|
|
if resp.status_code >= 400:
|
|
self.logger.debug(
|
|
"FAILED API CALL:\nURL: %s %s\nBODY:\n%s\nRESPONSE: %s\nBODY:\n%s"
|
|
% (prepared_req.method, prepared_req.url,
|
|
str(prepared_req.body).replace('\\r\\n', '\n'),
|
|
resp.status_code, resp.text))
|
|
return resp
|