227 lines
8.3 KiB
Python
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]
|