# Copyright 2017 Red Hat, Inc. # All 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 json import logging import requests from six.moves.urllib import parse from sushy import exceptions LOG = logging.getLogger(__name__) class Connector(object): def __init__(self, url, username=None, password=None, verify=True): self._url = url self._verify = verify self._session = requests.Session() self._session.verify = self._verify # NOTE(etingof): field studies reveal that some BMCs choke at # long-running persistent HTTP connections (or TCP connections). # By default, we ask HTTP server to shut down HTTP connection we've # just used. self._session.headers['Connection'] = 'close' if username or password: LOG.warning('Passing username and password to Connector is ' 'deprecated. Authentication is passed through ' 'set_auth now, support for these arguments will ' 'be removed in the future') self.set_http_basic_auth(username, password) def set_auth(self, auth): """Sets the authentication mechanism for our connector.""" self._auth = auth def set_http_basic_auth(self, username, password): """Sets the http basic authentication information.""" self._session.auth = (username, password) def set_http_session_auth(self, session_auth_token): """Sets the session authentication information.""" self._session.auth = None self._session.headers.update({'X-Auth-Token': session_auth_token}) def close(self): """Close this connector and the associated HTTP session.""" self._session.close() def _op(self, method, path='', data=None, headers=None): """Generic RESTful request handler. :param method: The HTTP method to be used, e.g: GET, POST, PUT, PATCH, etc... :param path: The sub-URI path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. :returns: The response object from the requests library. :raises: ConnectionError :raises: HTTPError """ json_data = None if headers is None: headers = {} if data is not None: json_data = json.dumps(data) headers['Content-Type'] = 'application/json' url = parse.urljoin(self._url, path) # TODO(lucasagomes): We should mask the data to remove sensitive # information LOG.debug('HTTP request: %(method)s %(url)s; ' 'headers: %(headers)s; body: %(data)s', {'method': method, 'url': url, 'headers': headers, 'data': json_data}) try: response = self._session.request(method, url, data=json_data, headers=headers) except requests.ConnectionError as e: raise exceptions.ConnectionError(url=url, error=e) # If we received an AccessError, and we # previously established a redfish session # there is a chance that the session has timed-out. # Attempt to re-establish a session. try: exceptions.raise_for_response(method, url, response) except exceptions.AccessError: if self._auth.can_refresh_session(): self._auth.refresh_session() LOG.debug("Authentication refreshed successfully, " "retrying the call.") response = self._session.request(method, url, data=json_data, headers=headers) else: raise LOG.debug('HTTP response for %(method)s %(url)s: ' 'status code: %(code)s', {'method': method, 'url': url, 'code': response.status_code}) return response def get(self, path='', data=None, headers=None): """HTTP GET method. :param path: Optional sub-URI path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. :returns: The response object from the requests library. :raises: ConnectionError :raises: HTTPError """ return self._op('GET', path, data, headers) def post(self, path='', data=None, headers=None): """HTTP POST method. :param path: Optional sub-URI path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. :returns: The response object from the requests library. :raises: ConnectionError :raises: HTTPError """ return self._op('POST', path, data, headers) def patch(self, path='', data=None, headers=None): """HTTP PATCH method. :param path: Optional sub-URI path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. :returns: The response object from the requests library. :raises: ConnectionError :raises: HTTPError """ return self._op('PATCH', path, data, headers) def put(self, path='', data=None, headers=None): """HTTP PUT method. :param path: Optional sub-URI path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. :returns: The response object from the requests library. :raises: ConnectionError :raises: HTTPError """ return self._op('PUT', path, data, headers) def delete(self, path='', data=None, headers=None): """HTTP DELETE method. :param path: Optional sub-URI path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. :returns: The response object from the requests library. :raises: ConnectionError :raises: HTTPError """ return self._op('DELETE', path, data, headers) def __enter__(self): return self def __exit__(self, *_args): self.close()