cinder/cinder/volume/drivers/open_e/jovian_common/rest_proxy.py

227 lines
8.3 KiB
Python

# Copyright (c) 2020 Open-E, 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.
"""Network connection handling class for JovianDSS driver."""
import json
import time
from oslo_log import log as logging
from oslo_utils import netutils as o_netutils
import requests
import urllib3
from cinder import exception
from cinder.i18n import _
from cinder.volume.drivers.open_e.jovian_common import exception as jexc
LOG = logging.getLogger(__name__)
class JovianRESTProxy(object):
"""Jovian REST API proxy."""
def __init__(self, config):
""":param config: config is like dict."""
self.proto = 'http'
if config.get('driver_use_ssl', True):
self.proto = 'https'
self.hosts = config.safe_get('san_hosts')
self.port = str(config.get('san_api_port', 82))
self.active_host = 0
for host in self.hosts:
if o_netutils.is_valid_ip(host) is False:
err_msg = ('Invalid value of jovian_host property: '
'%(addr)s, IP address expected.' %
{'addr': host})
LOG.debug(err_msg)
raise exception.InvalidConfigurationValue(err_msg)
self.api_path = "/api/v3"
self.delay = config.get('jovian_recovery_delay', 40)
self.pool = config.safe_get('jovian_pool')
self.user = config.get('san_login', 'admin')
self.password = config.get('san_password', 'admin')
self.auth = requests.auth.HTTPBasicAuth(self.user, self.password)
self.verify = False
self.retry_n = config.get('jovian_rest_send_repeats', 3)
self.header = {'connection': 'keep-alive',
'Content-Type': 'application/json',
'authorization': 'Basic '}
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def _get_pool_url(self, host):
url = ('%(proto)s://%(host)s:%(port)s/api/v3/pools/%(pool)s' % {
'proto': self.proto,
'host': host,
'port': self.port,
'pool': self.pool})
return url
def _get_url(self, host):
url = ('%(proto)s://%(host)s:%(port)s/api/v3' % {
'proto': self.proto,
'host': host,
'port': self.port})
return url
def request(self, request_method, req, json_data=None):
"""Send request to the specific url.
:param request_method: GET, POST, DELETE
:param url: where to send
:param json_data: data
"""
for j in range(self.retry_n):
for i in range(len(self.hosts)):
host = self.hosts[self.active_host]
url = self._get_url(host) + req
LOG.debug(
"sending request of type %(type)s to %(url)s "
"attempt: %(num)s.",
{'type': request_method,
'url': url,
'num': j})
if json_data is not None:
LOG.debug(
"sending data: %s.", json_data)
try:
ret = self._request_routine(url, request_method, json_data)
if len(ret) == 0:
self.active_host = ((self.active_host + 1)
% len(self.hosts))
continue
return ret
except requests.ConnectionError as err:
LOG.debug("Connection error %s", err)
self.active_host = (self.active_host + 1) % len(self.hosts)
continue
time.sleep(self.delay)
msg = (_('%(times) faild in a row') % {'times': j})
raise jexc.JDSSRESTProxyException(host=url, reason=msg)
def pool_request(self, request_method, req, json_data=None):
"""Send request to the specific url.
:param request_method: GET, POST, DELETE
:param url: where to send
:param json_data: data
"""
url = ""
for j in range(self.retry_n):
for i in range(len(self.hosts)):
host = self.hosts[self.active_host]
url = self._get_pool_url(host) + req
LOG.debug(
"sending pool request of type %(type)s to %(url)s "
"attempt: %(num)s.",
{'type': request_method,
'url': url,
'num': j})
if json_data is not None:
LOG.debug(
"JovianDSS: Sending data: %s.", str(json_data))
try:
ret = self._request_routine(url, request_method, json_data)
if len(ret) == 0:
self.active_host = ((self.active_host + 1)
% len(self.hosts))
continue
return ret
except requests.ConnectionError as err:
LOG.debug("Connection error %s", err)
self.active_host = (self.active_host + 1) % len(self.hosts)
continue
time.sleep(int(self.delay))
msg = (_('%(times) faild in a row') % {'times': j})
raise jexc.JDSSRESTProxyException(host=url, reason=msg)
def _request_routine(self, url, request_method, json_data=None):
"""Make an HTTPS request and return the results."""
ret = None
for i in range(3):
ret = dict()
try:
response_obj = requests.request(request_method,
auth=self.auth,
url=url,
headers=self.header,
data=json.dumps(json_data),
verify=self.verify)
LOG.debug('response code: %s', response_obj.status_code)
LOG.debug('response data: %s', response_obj.text)
ret['code'] = response_obj.status_code
if '{' in response_obj.text and '}' in response_obj.text:
if "error" in response_obj.text:
ret["error"] = json.loads(response_obj.text)["error"]
else:
ret["error"] = None
if "data" in response_obj.text:
ret["data"] = json.loads(response_obj.text)["data"]
else:
ret["data"] = None
if ret["code"] == 500:
if ret["error"] is not None:
if (("errno" in ret["error"]) and
("class" in ret["error"])):
if (ret["error"]["class"] ==
"opene.tools.scstadmin.ScstAdminError"):
LOG.debug("ScstAdminError %(code)d %(msg)s", {
"code": ret["error"]["errno"],
"msg": ret["error"]["message"]})
continue
if (ret["error"]["class"] ==
"exceptions.OSError"):
LOG.debug("OSError %(code)d %(msg)s", {
"code": ret["error"]["errno"],
"msg": ret["error"]["message"]})
continue
break
except requests.HTTPError as err:
LOG.debug("HTTP parsing error %s", err)
self.active_host = (self.active_host + 1) % len(self.hosts)
return ret
def get_active_host(self):
"""Return address of currently used host."""
return self.hosts[self.active_host]