# Copyright (c) 2020 Dell Inc. or its subsidiaries.
#
# 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.
"""rest_requests.py."""
import json
import logging
import platform
import requests
import requests.exceptions as r_exc
import sys
import urllib3
from PyU4V.utils import constants
from PyU4V.utils import exception
from requests.auth import HTTPBasicAuth
__pyu4v_version__ = constants.PYU4V_VERSION
__python_version__ = platform.python_version()
__platform__ = platform.system()
__platform_release__ = platform.release()
ua_details = (
'PyU4V/{pv} ({platform}; version {release}) Python {python}'.format(
pv=__pyu4v_version__, platform=__platform__,
release=__platform_release__, python=__python_version__))
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
LOG = logging.getLogger(__name__)
CONTENT_TYPE = constants.CONTENT_TYPE
ACCEPT = constants.ACCEPT
ACCEPT_ENC = constants.ACCEPT_ENC
USER_AGENT = constants.USER_AGENT
APP_TYPE = constants.APP_TYPE
APP_JSON = constants.APP_JSON
APP_OCT = constants.APP_OCT
APP_MPART = constants.APP_MPART
[docs]
class RestRequests(object):
"""RestRequests."""
def __init__(self, username, password, verify, base_url, interval, retries,
application_type=None, proxies=None, timeout=None):
"""__init__."""
self.username = username
self.password = password
self.verify_ssl = verify
self.base_url = base_url
self.headers = {CONTENT_TYPE: APP_JSON,
ACCEPT: APP_JSON,
USER_AGENT: ua_details,
APP_TYPE: application_type}
# if timeout is not none set self.timeout to timeout
# value or set to 120
self.timeout = timeout or 120
self.interval = interval
self.proxies = proxies
self.retries = retries
self.session = self.establish_rest_session()
[docs]
def establish_rest_session(self, headers=None):
"""Establish a REST session.
:returns: session -- object
"""
session = requests.session()
session.headers = self.headers if not headers else headers
session.auth = HTTPBasicAuth(self.username, self.password)
session.verify = self.verify_ssl
session.proxies = self.proxies
return session
[docs]
def rest_request(self, target_url, method,
params=None, request_object=None, timeout=None):
"""Send a request to the target api.
Valid methods are 'GET', 'POST', 'PUT', 'DELETE'.
:param target_url: target url --str
:param method: method -- str
:param params: Additional URL parameters -- dict
:param request_object: request payload -- dict
:param timeout: optional timeout override -- int
:returns: server response, status code -- dict, int
"""
if timeout:
timeout_val = timeout
else:
timeout_val = self.timeout
if not self.session:
self.session = self.establish_rest_session()
url = '{base_url}{target_url}'.format(
base_url=self.base_url, target_url=target_url)
try:
if request_object:
response = self.session.request(
method=method, url=url, timeout=timeout_val,
data=json.dumps(request_object, sort_keys=True,
indent=4))
elif params:
response = self.session.request(method=method, url=url,
params=params,
timeout=timeout_val)
else:
response = self.session.request(method=method, url=url,
timeout=timeout_val)
status_code = response.status_code
try:
response = response.json()
except ValueError:
response = None
if not status_code:
status_code = None
LOG.debug('No response received from API. Status code '
'received is: {sc}.'.format(sc=status_code))
LOG.debug('{method} request to {url} has returned with a status '
'code of: {sc}.'.format(method=method, url=url,
sc=status_code))
return response, status_code
except requests.Timeout as error:
LOG.error(
'The {} request to URL {} timed-out, but may have '
'been successful. Please check Unisphere Server for any '
'slowness, long-running API calls are a symptom of '
'Unisphere Server limits being reached. {}. See '
'https://developer.dell.com/apis/4458/versions/10.0/docs'
'/Getting%20Started/4.concurrent_operations.md, To ensure '
'limits are not being exceeded verify the number of '
'connections and calls in Unisphere for PowerMax under '
'Support > Management Server Resources or using system '
'call get_management_server_resources()'
''.format(method, url, error))
return None, None
except r_exc.SSLError as error:
msg = (
'The connection to {base} has encountered an SSL error. '
'Please check your SSL config or supplied SSL cert in Cinder '
'configuration. SSL Exception message: {m}'.format(
base=self.base_url, m=error))
raise r_exc.SSLError(msg) from error
except (r_exc.ConnectionError, r_exc.HTTPError) as error:
exc_class, __, __ = sys.exc_info()
msg = (
'The {met} to Unisphere server {base} has experienced a {exc} '
'error. Please check your Unisphere server connection and '
'availability. Exception message: {msg}'.format(
met=method, base=self.base_url,
exc=error.__class__.__name__, msg=error))
raise exc_class(msg) from error
except Exception as error:
exp_message = (
'The {method} request to URL {url} failed with exception: '
'{e}.'.format(method=method, url=url, e=error))
raise exception.VolumeBackendAPIException(
data=exp_message) from error
[docs]
def file_transfer_request(self, method, uri, timeout=None, download=False,
r_obj=None, upload=False, form_data=None):
"""Send a file transfer request via REST to the target API.
Valid methods are 'POST' and 'PUT'.
:param method: request method -- str
:param uri: target uri -- str
:param timeout: optional timeout override -- int
:param download: if download request -- bool
:param r_obj: download request payload -- dict
:param upload: if upload request -- bool
:param form_data: upload multipart form data -- dict
:returns: server response, status code -- dict, int
:raises: InvalidInputException, VolumeBackendAPIException,
Timeout, SSLError, ConnectionError, HTTPError
"""
if download and not upload:
headers = {
CONTENT_TYPE: APP_JSON,
ACCEPT: APP_OCT,
USER_AGENT: ua_details,
APP_TYPE: self.headers.get('application-type')}
elif upload and not download:
headers = {
ACCEPT_ENC: APP_MPART,
USER_AGENT: ua_details,
APP_TYPE: self.headers.get('application-type')}
else:
msg = ('You must select one of upload/download for '
'file_transfer_request method.')
LOG.error(msg)
raise exception.InvalidInputException(msg)
timeout_val = self.timeout if not timeout else timeout
data = json.dumps(r_obj, sort_keys=True, indent=4) if r_obj else None
url = '{base_url}{uri}'.format(base_url=self.base_url, uri=uri)
try:
ft_session = self.establish_rest_session(headers=headers)
response = ft_session.request(
method=method, url=url, timeout=timeout_val,
stream=download, data=data, files=form_data)
ft_session.close()
status_code = response.status_code
LOG.debug('{method} request to {url} has returned with a status '
'code of: {sc}.'.format(method=method, url=url,
sc=status_code))
return response, status_code
except requests.Timeout as error:
LOG.error(
'The {method} request to URL {url} timed-out, but may have '
'been successful. Please check the array. Exception received: '
'{exc}.'.format(method=method, url=url, exc=error))
return None, None
except r_exc.SSLError as error:
msg = (
'The connection to {base} has encountered an SSL error. '
'Please check your SSL config or supplied SSL cert in Cinder '
'configuration. SSL Exception message: {m}'.format(
base=self.base_url, m=error))
raise r_exc.SSLError(msg) from error
except (r_exc.ConnectionError, r_exc.HTTPError) as error:
exc_class, __, __ = sys.exc_info()
msg = (
'The {met} to Unisphere server {base} has experienced a {exc} '
'error. Please check your Unisphere server connection and '
'availability. Exception message: {msg}'.format(
met=method, base=self.base_url,
exc=error.__class__.__name__, msg=error))
raise exc_class(msg) from error
except Exception as error:
exp_message = (
'The {method} request to URL {url} failed with exception: '
'{e}.'.format(method=method, url=url, e=error))
raise exception.VolumeBackendAPIException(data=exp_message)
[docs]
def close_session(self):
"""Close the current session."""
self.session.close()