# 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. """Client for submitting authenticated requests to MaaS API.""" import logging from oauthlib import oauth1 import requests import requests.auth as req_auth import base64 import drydock_provisioner.error as errors 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(sh8121att) 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: 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 as ex: raise errors.TransientDriverError("Timeout connection to MaaS") except Exception as ex: raise errors.PersistentDriverError( "Error accessing MaaS: %s" % str(ex)) 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: v = "" if isinstance(v, list): for i in v: value = base64.b64encode( str(i).encode('utf-8')).decode('utf-8') content_type = 'text/plain; charset="utf-8"' part_headers = {'Content-Transfer-Encoding': 'base64'} files_tuples.append((k, (None, value, content_type, part_headers))) else: value = base64.b64encode( str(v).encode('utf-8')).decode('utf-8') content_type = 'text/plain; charset="utf-8"' part_headers = {'Content-Transfer-Encoding': 'base64'} files_tuples.append((k, (None, value, content_type, part_headers))) 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(sh8121att) 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( "Received error response - URL: %s %s - RESPONSE: %s" % (prepared_req.method, prepared_req.url, resp.status_code)) self.logger.debug("Response content: %s" % resp.text) raise errors.DriverError( "MAAS Error: %s - %s" % (resp.status_code, resp.text)) return resp