Add share driver for Huawei V3 Storage
Add share driver for Huawei V3 Storage system using REST API. Both the NFS and CIFS protocols are supported. Following interfaces are supported: 1. Create/Delete share. 2. Create/Delete snapshot. 3. Allow/Deny IP(NFS) or user(CIFS) access. 4. Get share stats. Change-Id: Ia45e39a40bb51130216c49d08f7489bf7bbad613 Partially implements: blueprint huawei-manila-driver
This commit is contained in:
parent
5f84b5869e
commit
911326868f
@ -51,6 +51,7 @@ import manila.share.drivers.emc.driver
|
||||
import manila.share.drivers.generic
|
||||
import manila.share.drivers.glusterfs
|
||||
import manila.share.drivers.glusterfs_native
|
||||
import manila.share.drivers.huawei.huawei_nas
|
||||
import manila.share.drivers.ibm.gpfs
|
||||
import manila.share.drivers.netapp.cluster_mode
|
||||
import manila.share.drivers.service_instance
|
||||
@ -102,6 +103,7 @@ _global_opt_lists = [
|
||||
manila.share.drivers.generic.share_opts,
|
||||
manila.share.drivers.glusterfs.GlusterfsManilaShare_opts,
|
||||
manila.share.drivers.glusterfs_native.glusterfs_native_manila_share_opts,
|
||||
manila.share.drivers.huawei.huawei_nas.huawei_opts,
|
||||
manila.share.drivers.ibm.gpfs.gpfs_share_opts,
|
||||
manila.share.drivers.netapp.cluster_mode.NETAPP_NAS_OPTS,
|
||||
manila.share.drivers.service_instance.server_opts,
|
||||
|
0
manila/share/drivers/huawei/__init__.py
Normal file
0
manila/share/drivers/huawei/__init__.py
Normal file
21
manila/share/drivers/huawei/constants.py
Normal file
21
manila/share/drivers/huawei/constants.py
Normal file
@ -0,0 +1,21 @@
|
||||
# Copyright (c) 2014 Huawei Technologies Co., Ltd.
|
||||
# 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.
|
||||
STATUS_FS_HEALTH = "1"
|
||||
STATUS_FS_RUNNING = "27"
|
||||
STATUS_SERVICE_RUNNING = "2"
|
||||
DEFAULT_WAIT_INTERVAL = 3
|
||||
DEFAULT_TIMEOUT = 60
|
||||
IP_ALLOCATIONS = 0
|
||||
SOCKET_TIMEOUT = 720
|
727
manila/share/drivers/huawei/huawei_helper.py
Normal file
727
manila/share/drivers/huawei/huawei_helper.py
Normal file
@ -0,0 +1,727 @@
|
||||
# Copyright (c) 2014 Huawei Technologies Co., Ltd.
|
||||
# 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 base64
|
||||
from xml import etree
|
||||
|
||||
from oslo.serialization import jsonutils
|
||||
from oslo.utils import units
|
||||
import six
|
||||
from six.moves import http_cookiejar
|
||||
from six.moves.urllib import request as urlreq # pylint: disable=E0611
|
||||
|
||||
from manila import exception
|
||||
from manila.i18n import _, _LE, _LW
|
||||
from manila.openstack.common import log as logging
|
||||
from manila.share.drivers.huawei import constants
|
||||
from manila import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RestHelper():
|
||||
"""Helper class for Huawei OceanStor V3 storage system."""
|
||||
|
||||
def __init__(self, configuration):
|
||||
self.configuration = configuration
|
||||
self.cookie = http_cookiejar.CookieJar()
|
||||
self.url = None
|
||||
self.headers = {
|
||||
"Connection": "keep-alive",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
def call(self, url, data=None, method=None):
|
||||
"""Send requests to server.
|
||||
|
||||
Send HTTPS call, get response in JSON.
|
||||
Convert response into Python Object and return it.
|
||||
"""
|
||||
if "xx/sessions" not in url:
|
||||
LOG.debug('Request URL: %(url)s\n'
|
||||
'Call Method: %(method)s\n'
|
||||
'Request Data: %(data)s\n',
|
||||
{'url': url,
|
||||
'method': method,
|
||||
'data': data})
|
||||
opener = urlreq.build_opener(urlreq.HTTPCookieProcessor(self.cookie))
|
||||
urlreq.install_opener(opener)
|
||||
|
||||
try:
|
||||
req = urlreq.Request(url, data, self.headers)
|
||||
if method:
|
||||
req.get_method = lambda: method
|
||||
res_temp = urlreq.urlopen(req, timeout=constants.SOCKET_TIMEOUT)
|
||||
res = res_temp.read().decode("utf-8")
|
||||
|
||||
LOG.debug('Response Data: %(res)s.', {'res': res})
|
||||
|
||||
except Exception as err:
|
||||
LOG.error(_LE('Bad response from server: %s.') % err)
|
||||
raise err
|
||||
|
||||
try:
|
||||
res_json = jsonutils.loads(res)
|
||||
except Exception as err:
|
||||
err_msg = (_('JSON transfer error: %s.') % err)
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidShare(reason=err_msg)
|
||||
|
||||
return res_json
|
||||
|
||||
def login(self):
|
||||
"""Log in huawei array."""
|
||||
login_info = self._get_login_info()
|
||||
url = login_info['RestURL'] + "xx/sessions"
|
||||
data = jsonutils.dumps({"username": login_info['UserName'],
|
||||
"password": login_info['UserPassword'],
|
||||
"scope": "0"})
|
||||
result = self.call(url, data)
|
||||
if (result['error']['code'] != 0) or ("data" not in result):
|
||||
err_msg = (_("Login error, reason is %s.") % result)
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidShare(reason=err_msg)
|
||||
|
||||
deviceid = result['data']['deviceid']
|
||||
self.url = login_info['RestURL'] + deviceid
|
||||
self.headers['iBaseToken'] = result['data']['iBaseToken']
|
||||
return deviceid
|
||||
|
||||
def _create_filesystem(self, fs_param):
|
||||
"""Create file system."""
|
||||
url = self.url + "/filesystem"
|
||||
data = jsonutils.dumps(fs_param)
|
||||
result = self.call(url, data)
|
||||
|
||||
msg = 'Create filesystem error.'
|
||||
self._assert_rest_result(result, msg)
|
||||
self._assert_data_in_result(result, msg)
|
||||
|
||||
return result['data']['ID']
|
||||
|
||||
def _assert_rest_result(self, result, err_str):
|
||||
if result['error']['code'] != 0:
|
||||
err_msg = (_('%(err)s\nresult: %(res)s.') % {'err': err_str,
|
||||
'res': result})
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidShare(reason=err_msg)
|
||||
|
||||
def _assert_data_in_result(self, result, msg):
|
||||
if "data" not in result:
|
||||
err_msg = (_('%s "data" was not in result.') % msg)
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidShare(reason=err_msg)
|
||||
|
||||
def _get_login_info(self):
|
||||
"""Get login IP, username and password from config file."""
|
||||
logininfo = {}
|
||||
filename = self.configuration.manila_huawei_conf_file
|
||||
tree = etree.ElementTree.parse(filename)
|
||||
root = tree.getroot()
|
||||
RestURL = root.findtext('Storage/RestURL')
|
||||
logininfo['RestURL'] = RestURL.strip()
|
||||
|
||||
# Prefix !$$$ means encoded already.
|
||||
prefix_name = '!$$$'
|
||||
need_encode = False
|
||||
for key in ['UserName', 'UserPassword']:
|
||||
node = root.find('Storage/%s' % key)
|
||||
node_text = node.text
|
||||
if node_text.find(prefix_name) > -1:
|
||||
logininfo[key] = base64.b64decode(node_text[4:])
|
||||
else:
|
||||
logininfo[key] = node_text
|
||||
node.text = prefix_name + base64.b64encode(node_text)
|
||||
need_encode = True
|
||||
if need_encode:
|
||||
self._change_file_mode(filename)
|
||||
try:
|
||||
tree.write(filename, 'UTF-8')
|
||||
except Exception as err:
|
||||
err_msg = (_('File write error %s.') % err)
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidShare(reason=err_msg)
|
||||
|
||||
return logininfo
|
||||
|
||||
def _change_file_mode(self, filepath):
|
||||
try:
|
||||
utils.execute('chmod', '666', filepath, run_as_root=True)
|
||||
|
||||
except Exception as err:
|
||||
LOG.error(_LE('Bad response from change file: %s.') % err)
|
||||
raise err
|
||||
|
||||
def _create_share(self, share_name, fs_id, share_proto):
|
||||
"""Create a share."""
|
||||
share_type = self._get_share_type(share_proto)
|
||||
share_path = self._get_share_path(share_name)
|
||||
|
||||
filepath = {}
|
||||
if share_proto == 'NFS':
|
||||
filepath = {
|
||||
"DESCRIPTION": "",
|
||||
"FSID": fs_id,
|
||||
"SHAREPATH": share_path,
|
||||
}
|
||||
elif share_proto == 'CIFS':
|
||||
filepath = {
|
||||
"SHAREPATH": share_path,
|
||||
"DESCRIPTION": "",
|
||||
"ABEENABLE": "false",
|
||||
"ENABLENOTIFY": "true",
|
||||
"ENABLEOPLOCK": "true",
|
||||
"NAME": share_name.replace("-", "_"),
|
||||
"FSID": fs_id,
|
||||
"TENANCYID": "0",
|
||||
}
|
||||
else:
|
||||
raise exception.InvalidShare(
|
||||
reason=(_('Invalid NAS protocol supplied: %s.')
|
||||
% share_proto))
|
||||
|
||||
url = self.url + "/" + share_type
|
||||
data = jsonutils.dumps(filepath)
|
||||
|
||||
result = self.call(url, data, "POST")
|
||||
|
||||
msg = 'Create share error.'
|
||||
self._assert_rest_result(result, msg)
|
||||
self._assert_data_in_result(result, msg)
|
||||
|
||||
return result['data']['ID']
|
||||
|
||||
def _delete_share(self, share_name, share_proto):
|
||||
"""Delete share."""
|
||||
share_type = self._get_share_type(share_proto)
|
||||
share = self._get_share_by_name(share_name, share_type)
|
||||
|
||||
if not share:
|
||||
LOG.warn(_LW('The share was not found. share_name:%s'), share_name)
|
||||
fsid = self._get_fsid_by_name(share_name)
|
||||
if fsid:
|
||||
self._delete_fs(fsid)
|
||||
return
|
||||
LOG.warn(_LW('The filesystem was not found.'))
|
||||
return
|
||||
|
||||
share_id = share['ID']
|
||||
share_fs_id = share['FSID']
|
||||
|
||||
if share_id:
|
||||
self._delete_share_by_id(share_id, share_type)
|
||||
|
||||
if share_fs_id:
|
||||
self._delete_fs(share_fs_id)
|
||||
|
||||
return share
|
||||
|
||||
def _delete_share_by_id(self, share_id, share_type):
|
||||
"""Delete share by share id."""
|
||||
url = self.url + "/" + share_type + "/" + share_id
|
||||
|
||||
result = self.call(url, None, "DELETE")
|
||||
self._assert_rest_result(result, 'Delete share error.')
|
||||
|
||||
def _delete_fs(self, fs_id):
|
||||
"""Delete file system."""
|
||||
# Get available file system
|
||||
url = self.url + "/filesystem/" + fs_id
|
||||
|
||||
result = self.call(url, None, "DELETE")
|
||||
self._assert_rest_result(result, 'Delete file system error.')
|
||||
|
||||
def _get_cifs_service_status(self):
|
||||
url = self.url + "/CIFSSERVICE"
|
||||
result = self.call(url, None, "GET")
|
||||
|
||||
msg = 'Get CIFS service status error.'
|
||||
self._assert_rest_result(result, msg)
|
||||
self._assert_data_in_result(result, msg)
|
||||
|
||||
return result['data']['RUNNINGSTATUS']
|
||||
|
||||
def _get_nfs_service_status(self):
|
||||
url = self.url + "/NFSSERVICE"
|
||||
result = self.call(url, None, "GET")
|
||||
|
||||
msg = 'Get NFS service status error.'
|
||||
self._assert_rest_result(result, msg)
|
||||
self._assert_data_in_result(result, msg)
|
||||
|
||||
service = {}
|
||||
|
||||
service['RUNNINGSTATUS'] = result['data']['RUNNINGSTATUS']
|
||||
service['SUPPORTV3'] = result['data']['SUPPORTV3']
|
||||
service['SUPPORTV4'] = result['data']['SUPPORTV4']
|
||||
return service
|
||||
|
||||
def _start_nfs_service_status(self):
|
||||
url = self.url + "/NFSSERVICE"
|
||||
nfsserviceinfo = {
|
||||
"NFSV4DOMAIN": "localdomain",
|
||||
"RUNNINGSTATUS": "2",
|
||||
"SUPPORTV3": 'true',
|
||||
"SUPPORTV4": 'true',
|
||||
"TYPE": "16452",
|
||||
}
|
||||
|
||||
data = jsonutils.dumps(nfsserviceinfo)
|
||||
result = self.call(url, data, "PUT")
|
||||
|
||||
self._assert_rest_result(result, 'Start NFS service error.')
|
||||
|
||||
def _start_cifs_service_status(self):
|
||||
url = self.url + "/CIFSSERVICE"
|
||||
cifsserviceinfo = {
|
||||
"ENABLENOTIFY": "true",
|
||||
"ENABLEOPLOCK": "true",
|
||||
"ENABLEOPLOCKLEASE": "false",
|
||||
"GUESTENABLE": "false",
|
||||
"OPLOCKTIMEOUT": "35",
|
||||
"RUNNINGSTATUS": "2",
|
||||
"SECURITYMODEL": "3",
|
||||
"SIGNINGENABLE": "false",
|
||||
"SIGNINGREQUIRED": "false",
|
||||
"TYPE": "16453",
|
||||
}
|
||||
|
||||
data = jsonutils.dumps(cifsserviceinfo)
|
||||
result = self.call(url, data, "PUT")
|
||||
|
||||
self._assert_rest_result(result, 'Start CIFS service error.')
|
||||
|
||||
def _find_pool_info(self):
|
||||
root = self._read_xml()
|
||||
pool_name = root.findtext('Filesystem/StoragePool')
|
||||
if not pool_name:
|
||||
err_msg = (_("Invalid resource pool: %s.") % pool_name)
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidInput(err_msg)
|
||||
|
||||
url = self.url + "/storagepool"
|
||||
result = self.call(url, None)
|
||||
self._assert_rest_result(result, 'Query resource pool error.')
|
||||
|
||||
poolinfo = {}
|
||||
pool_name = pool_name.strip()
|
||||
if "data" in result:
|
||||
for item in result['data']:
|
||||
if pool_name == item['NAME']:
|
||||
poolinfo['ID'] = item['ID']
|
||||
poolinfo['CAPACITY'] = item['USERFREECAPACITY']
|
||||
poolinfo['TOTALCAPACITY'] = item['USERTOTALCAPACITY']
|
||||
break
|
||||
|
||||
return poolinfo
|
||||
|
||||
def _get_capacity(self):
|
||||
"""Get free capacity and total capacity of the pools."""
|
||||
poolinfo = self._find_pool_info()
|
||||
pool_capacity = {
|
||||
'total_capacity': 0.0,
|
||||
'free_capacity': 0.0
|
||||
}
|
||||
|
||||
if poolinfo:
|
||||
total = int(poolinfo['TOTALCAPACITY']) / units.Mi / 2
|
||||
free = int(poolinfo['CAPACITY']) / units.Mi / 2
|
||||
pool_capacity['total_capacity'] = total
|
||||
pool_capacity['free_capacity'] = free
|
||||
|
||||
return pool_capacity
|
||||
|
||||
def _read_xml(self):
|
||||
"""Open xml file and parse the content."""
|
||||
filename = self.configuration.manila_huawei_conf_file
|
||||
try:
|
||||
tree = etree.ElementTree.parse(filename)
|
||||
root = tree.getroot()
|
||||
except Exception as err:
|
||||
LOG.error(_LE('Read Huawei config file(%(filename)s)'
|
||||
' for Manila error: %(err)s') %
|
||||
{'filename': filename,
|
||||
'err': err})
|
||||
raise err
|
||||
return root
|
||||
|
||||
def _init_filesys_para(self, name, size):
|
||||
"""Init basic filesystem parameters."""
|
||||
poolinfo = self._find_pool_info()
|
||||
fileparam = {
|
||||
"NAME": name.replace("-", "_"),
|
||||
"DESCRIPTION": "",
|
||||
"ALLOCTYPE": 1,
|
||||
"CAPACITY": size,
|
||||
"PARENTID": poolinfo['ID'],
|
||||
"INITIALALLOCCAPACITY": units.Ki * 20,
|
||||
"PARENTTYPE": 216,
|
||||
"SNAPSHOTRESERVEPER": 20,
|
||||
"INITIALDISTRIBUTEPOLICY": 0,
|
||||
"ISSHOWSNAPDIR": 'true',
|
||||
"RECYCLESWITCH": 0,
|
||||
"RECYCLEHOLDTIME": 15,
|
||||
"RECYCLETHRESHOLD": 0,
|
||||
"RECYCLEAUTOCLEANSWITCH": 0,
|
||||
}
|
||||
|
||||
root = self._read_xml()
|
||||
fstype = root.findtext('FILESYSTEM/AllocType')
|
||||
if fstype:
|
||||
fstype = fstype.strip()
|
||||
if fstype == 'Thin':
|
||||
fileparam['ALLOCTYPE'] = 1
|
||||
elif fstype == 'Thick':
|
||||
fileparam['ALLOCTYPE'] = 0
|
||||
else:
|
||||
err_msg = (_(
|
||||
'Config file is wrong. Filesystem Type must be "Thin"'
|
||||
' or "Thick". AllocType:%(fetchtype)s') %
|
||||
{'fetchtype': fstype})
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidShare(reason=err_msg)
|
||||
return fileparam
|
||||
|
||||
def _deny_access(self, share_name, access, share_proto):
|
||||
"""Deny access to share."""
|
||||
share_type = self._get_share_type(share_proto)
|
||||
share_client_type = self._get_share_client_type(share_proto)
|
||||
access_type = access['access_type']
|
||||
if share_proto == 'NFS' and access_type != 'ip':
|
||||
LOG.warn(_LW('Only ip access type allowed.'))
|
||||
return
|
||||
|
||||
if share_proto == 'CIFS' and access_type != 'user':
|
||||
LOG.warn(_LW('Only user access type allowed.'))
|
||||
return
|
||||
|
||||
access_to = access['access_to']
|
||||
share = self._get_share_by_name(share_name, share_type)
|
||||
if not share:
|
||||
LOG.warn(_LW('Can not get share. share_name: %s'), share_name)
|
||||
return
|
||||
|
||||
access_id = self._get_access_from_share(share['ID'], access_to,
|
||||
share_client_type)
|
||||
if not access_id:
|
||||
LOG.warn(_LW('Can not get access id from share. share_name: %s'),
|
||||
share_name)
|
||||
return
|
||||
|
||||
self._remove_access_from_share(access_id, share_client_type)
|
||||
|
||||
def _remove_access_from_share(self, access_id, access_type):
|
||||
url = self.url + "/" + access_type + "/" + access_id
|
||||
result = self.call(url, None, "DELETE")
|
||||
self._assert_rest_result(result, 'delete access from share error!')
|
||||
|
||||
def _get_access_from_count(self, share_id, share_client_type):
|
||||
url_subfix = ("/" + share_client_type + "/count?"
|
||||
+ "filter=PARENTID::" + share_id)
|
||||
url = self.url + url_subfix
|
||||
result = self.call(url, None, "GET")
|
||||
|
||||
msg = "Get access count by share error!"
|
||||
self._assert_rest_result(result, msg)
|
||||
self._assert_data_in_result(result, msg)
|
||||
|
||||
return int(result['data']['COUNT'])
|
||||
|
||||
def _get_access_from_share(self, share_id, access_to, share_client_type):
|
||||
"""Segments to find access for a period of 100."""
|
||||
count = self._get_access_from_count(share_id, share_client_type)
|
||||
|
||||
access_id = None
|
||||
range_begin = 0
|
||||
while True:
|
||||
if count < 0 or access_id:
|
||||
break
|
||||
access_id = self._get_access_from_share_range(share_id,
|
||||
access_to,
|
||||
range_begin,
|
||||
share_client_type)
|
||||
range_begin += 100
|
||||
count -= 100
|
||||
|
||||
return access_id
|
||||
|
||||
def _get_access_from_share_range(self, share_id,
|
||||
access_to, range_begin,
|
||||
share_client_type):
|
||||
range_end = range_begin + 100
|
||||
url = (self.url + "/" + share_client_type + "?filter=PARENTID::"
|
||||
+ share_id + "&range=[" + six.text_type(range_begin)
|
||||
+ "-" + six.text_type(range_end) + "]")
|
||||
result = self.call(url, None, "GET")
|
||||
self._assert_rest_result(result, 'Get access id by share error!')
|
||||
|
||||
if "data" in result:
|
||||
for item in result['data']:
|
||||
if access_to == item['NAME']:
|
||||
return item['ID']
|
||||
|
||||
def _allow_access(self, share_name, access, share_proto):
|
||||
"""Allow access to the share."""
|
||||
|
||||
share_type = self._get_share_type(share_proto)
|
||||
access_type = access['access_type']
|
||||
if share_proto == 'NFS' and access_type != 'ip':
|
||||
message = _('Only IP access type is allowed for NFS shares.')
|
||||
raise exception.InvalidShareAccess(reason=message)
|
||||
|
||||
if share_proto == 'CIFS' and access_type != 'user':
|
||||
message = _('Only USER access type is allowed for CIFS shares.')
|
||||
raise exception.InvalidShareAccess(reason=message)
|
||||
|
||||
access_to = access['access_to']
|
||||
|
||||
share = self._get_share_by_name(share_name, share_type)
|
||||
if not share:
|
||||
err_msg = (_('Can not get share.'))
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidShareAccess(reason=err_msg)
|
||||
|
||||
share_id = share['ID']
|
||||
self._allow_access_rest(share_id, access_to, share_proto)
|
||||
|
||||
def _allow_access_rest(self, share_id, access_to, share_proto):
|
||||
"""Allow access to the share."""
|
||||
access_type = self._get_share_client_type(share_proto)
|
||||
url = self.url + "/" + access_type
|
||||
|
||||
access = {}
|
||||
if access_type == "NFS_SHARE_AUTH_CLIENT":
|
||||
access = {
|
||||
"TYPE": "16409",
|
||||
"NAME": access_to,
|
||||
"PARENTID": share_id,
|
||||
"ACCESSVAL": "1",
|
||||
"SYNC": "0",
|
||||
"ALLSQUASH": "1",
|
||||
"ROOTSQUASH": "0",
|
||||
}
|
||||
elif access_type == "CIFS_SHARE_AUTH_CLIENT":
|
||||
access = {
|
||||
"NAME": access_to,
|
||||
"PARENTID": share_id,
|
||||
"PERMISSION": "5",
|
||||
"DOMAINTYPE": "2",
|
||||
}
|
||||
data = jsonutils.dumps(access)
|
||||
result = self.call(url, data, "POST")
|
||||
|
||||
msg = 'Allow access error.'
|
||||
self._assert_rest_result(result, msg)
|
||||
|
||||
def _get_share_client_type(self, share_proto):
|
||||
share_client_type = None
|
||||
if share_proto == 'NFS':
|
||||
share_client_type = "NFS_SHARE_AUTH_CLIENT"
|
||||
elif share_proto == 'CIFS':
|
||||
share_client_type = "CIFS_SHARE_AUTH_CLIENT"
|
||||
else:
|
||||
raise exception.InvalidShare(
|
||||
reason=(_('Invalid NAS protocol supplied: %s.')
|
||||
% share_proto))
|
||||
|
||||
return share_client_type
|
||||
|
||||
def _get_snapshot_id_by_name(self, sharefsid, snap_name):
|
||||
"""Get snapshot id in Array by snapshot name."""
|
||||
|
||||
url_subfix = ("/FSSNAPSHOT?TYPE=48&"
|
||||
"PARENTID=%s&&sortby=TIMESTAMP,d&"
|
||||
"range=[0-2000]" % sharefsid)
|
||||
|
||||
url = self.url + url_subfix
|
||||
result = self.call(url, None, "GET")
|
||||
self._assert_rest_result(result, 'Get snapshot id by name error!')
|
||||
|
||||
snapshot_name = "share_snapshot_" + snap_name.replace("-", "_")
|
||||
snapshot_id = None
|
||||
if "data" in result:
|
||||
for item in result['data']:
|
||||
if snapshot_name == item['NAME']:
|
||||
snapshot_id = item['ID']
|
||||
break
|
||||
|
||||
return snapshot_id
|
||||
|
||||
def _delete_snapshot(self, snap_id):
|
||||
"""Deletes snapshot."""
|
||||
url = self.url + "/FSSNAPSHOT/%s" % snap_id
|
||||
data = jsonutils.dumps({"TYPE": "48", "ID": snap_id})
|
||||
result = self.call(url, data, "DELETE")
|
||||
self._assert_rest_result(result, 'Delete snapshot error.')
|
||||
|
||||
def _create_snapshot(self, sharefsid, snapshot_name):
|
||||
"""Create a snapshot."""
|
||||
filepath = {
|
||||
"PARENTTYPE": "40",
|
||||
"TYPE": "48",
|
||||
"PARENTID": sharefsid,
|
||||
"NAME": snapshot_name.replace("-", "_"),
|
||||
"DESCRIPTION": "",
|
||||
}
|
||||
|
||||
url = self.url + "/FSSNAPSHOT"
|
||||
data = jsonutils.dumps(filepath)
|
||||
|
||||
result = self.call(url, data, "POST")
|
||||
|
||||
msg = 'Create a snapshot error.'
|
||||
self._assert_rest_result(result, msg)
|
||||
self._assert_data_in_result(result, msg)
|
||||
|
||||
return result['data']['ID']
|
||||
|
||||
def _get_share_by_name(self, share_name, share_type):
|
||||
"""Segments to find share for a period of 100."""
|
||||
count = self._get_share_count(share_type)
|
||||
|
||||
share = {}
|
||||
range_begin = 0
|
||||
while True:
|
||||
if count < 0 or share:
|
||||
break
|
||||
share = self._get_share_by_name_range(share_name,
|
||||
range_begin,
|
||||
share_type)
|
||||
range_begin += 100
|
||||
count -= 100
|
||||
|
||||
return share
|
||||
|
||||
def _get_share_count(self, share_type):
|
||||
"""Get share count."""
|
||||
url = self.url + "/" + share_type + "/count"
|
||||
result = self.call(url, None, "GET")
|
||||
self._assert_rest_result(result, 'Get share count error!')
|
||||
|
||||
return int(result['data']['COUNT'])
|
||||
|
||||
def _get_share_by_name_range(self, share_name,
|
||||
range_begin, share_type):
|
||||
"""Get share by share name."""
|
||||
range_end = range_begin + 100
|
||||
url = (self.url + "/" + share_type + "?range=["
|
||||
+ six.text_type(range_begin) + "-"
|
||||
+ six.text_type(range_end) + "]")
|
||||
result = self.call(url, None, "GET")
|
||||
self._assert_rest_result(result, 'Get share by name error!')
|
||||
|
||||
share_path = self._get_share_path(share_name)
|
||||
|
||||
share = {}
|
||||
if "data" in result:
|
||||
for item in result['data']:
|
||||
if share_path == item['SHAREPATH']:
|
||||
share['ID'] = item['ID']
|
||||
share['FSID'] = item['FSID']
|
||||
break
|
||||
|
||||
return share
|
||||
|
||||
def _get_share_type(self, share_proto):
|
||||
share_type = None
|
||||
if share_proto == 'NFS':
|
||||
share_type = "NFSHARE"
|
||||
elif share_proto == 'CIFS':
|
||||
share_type = "CIFSHARE"
|
||||
else:
|
||||
raise exception.InvalidShare(
|
||||
reason=(_('Invalid NAS protocol supplied: %s.')
|
||||
% share_proto))
|
||||
|
||||
return share_type
|
||||
|
||||
def _get_fsid_by_name(self, share_name):
|
||||
url = self.url + "/FILESYSTEM?range=[0-8191]"
|
||||
result = self.call(url, None, "GET")
|
||||
self._assert_rest_result(result, 'Get filesystem by name error!')
|
||||
sharename = share_name.replace("-", "_")
|
||||
|
||||
if "data" in result:
|
||||
for item in result['data']:
|
||||
if sharename == item['NAME']:
|
||||
return item['ID']
|
||||
|
||||
def _get_fs_info_by_id(self, fsid):
|
||||
url = self.url + "/filesystem/%s" % fsid
|
||||
result = self.call(url, None, "GET")
|
||||
|
||||
msg = "Get filesystem info by id error!"
|
||||
self._assert_rest_result(result, msg)
|
||||
self._assert_data_in_result(result, msg)
|
||||
|
||||
fs = {}
|
||||
fs['HEALTHSTATUS'] = result['data']['HEALTHSTATUS']
|
||||
fs['RUNNINGSTATUS'] = result['data']['RUNNINGSTATUS']
|
||||
return fs
|
||||
|
||||
def allocate_container(self, share_name, size):
|
||||
"""Creates filesystem associated to share by name."""
|
||||
fileParam = self._init_filesys_para(share_name, size)
|
||||
fsid = self._create_filesystem(fileParam)
|
||||
return fsid
|
||||
|
||||
def _check_conf_file(self):
|
||||
"""Check the config file, make sure the essential items are set."""
|
||||
root = self._read_xml()
|
||||
resturl = root.findtext('Storage/RestURL')
|
||||
username = root.findtext('Storage/UserName')
|
||||
pwd = root.findtext('Storage/UserPassword')
|
||||
product = root.findtext('Storage/Product')
|
||||
pool_node = root.findall('Filesystem/StoragePool')
|
||||
|
||||
if product != "V3":
|
||||
err_msg = (_(
|
||||
'_check_conf_file: Config file invalid. '
|
||||
'Product must be set to V3.'))
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidInput(err_msg)
|
||||
|
||||
if (not resturl) or (not username) or (not pwd):
|
||||
err_msg = (_(
|
||||
'_check_conf_file: Config file invalid. RestURL,'
|
||||
' UserName and UserPassword must be set.'))
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidInput(err_msg)
|
||||
|
||||
if not pool_node:
|
||||
err_msg = (_(
|
||||
'_check_conf_file: Config file invalid. '
|
||||
'StoragePool must be set.'))
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidInput(err_msg)
|
||||
|
||||
def _get_share_path(self, share_name):
|
||||
share_path = "/" + share_name.replace("-", "_") + "/"
|
||||
return share_path
|
||||
|
||||
def _get_share_name_by_id(self, share_id):
|
||||
share_name = "share_" + share_id
|
||||
return share_name
|
||||
|
||||
def _check_service(self):
|
||||
running_status = self._get_cifs_service_status()
|
||||
if running_status != constants.STATUS_SERVICE_RUNNING:
|
||||
self._start_cifs_service_status()
|
||||
|
||||
service = self._get_nfs_service_status()
|
||||
if ((service['RUNNINGSTATUS'] != constants.STATUS_SERVICE_RUNNING) or
|
||||
(service['SUPPORTV3'] == 'false') or
|
||||
(service['SUPPORTV4'] == 'false')):
|
||||
self._start_nfs_service_status()
|
271
manila/share/drivers/huawei/huawei_nas.py
Normal file
271
manila/share/drivers/huawei/huawei_nas.py
Normal file
@ -0,0 +1,271 @@
|
||||
# Copyright (c) 2014 Huawei Technologies Co., Ltd.
|
||||
# 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.
|
||||
|
||||
"""Huawei Nas Driver for Huawei OceanStor V3 storage arrays."""
|
||||
import time
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.utils import excutils
|
||||
from oslo.utils import units
|
||||
|
||||
from manila.common import constants as const
|
||||
from manila import exception
|
||||
from manila.i18n import _, _LI, _LW
|
||||
from manila.openstack.common import log as logging
|
||||
from manila.openstack.common import loopingcall
|
||||
from manila.share import driver
|
||||
from manila.share.drivers.huawei import constants
|
||||
from manila.share.drivers.huawei import huawei_helper
|
||||
|
||||
huawei_opts = [
|
||||
cfg.StrOpt('manila_huawei_conf_file',
|
||||
default='/etc/manila/manila_huawei_conf.xml',
|
||||
help='The configuration file for the Manila Huawei driver.')]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(huawei_opts)
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HuaweiNasDriver(driver.ShareDriver):
|
||||
"""Huawei Share Driver.
|
||||
|
||||
Executes commands relating to Shares.
|
||||
API version history:
|
||||
|
||||
1.0 - Initial version.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Do initialization."""
|
||||
LOG.debug("Enter into init function.")
|
||||
super(HuaweiNasDriver, self).__init__(*args, **kwargs)
|
||||
self.configuration = kwargs.get('configuration', None)
|
||||
if self.configuration:
|
||||
self.configuration.append_config_values(huawei_opts)
|
||||
self.helper = huawei_helper.RestHelper(self.configuration)
|
||||
else:
|
||||
raise exception.InvalidShare(_("Huawei configuration missing."))
|
||||
self.mode = self.get_driver_mode(const.SINGLE_SVM_MODE)
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Returns an error if prerequisites aren't met."""
|
||||
self.helper._check_conf_file()
|
||||
self.helper._check_service()
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Any initialization the huawei nas driver does while starting."""
|
||||
LOG.debug("Do setup the plugin.")
|
||||
return self.helper.login()
|
||||
|
||||
def create_share(self, context, share, share_server=None):
|
||||
"""Create a share."""
|
||||
LOG.debug("Create a share.")
|
||||
share_name = share['name']
|
||||
size = share['size'] * units.Mi * 2
|
||||
|
||||
fs_id = None
|
||||
# We sleep here to ensure the newly created filesystem can be read.
|
||||
wait_interval = self._get_wait_interval()
|
||||
try:
|
||||
fs_id = self.helper.allocate_container(share_name, size)
|
||||
|
||||
def _create_share_complete():
|
||||
fs = self.helper._get_fs_info_by_id(fs_id)
|
||||
if fs['HEALTHSTATUS'] == constants.STATUS_FS_HEALTH\
|
||||
and fs['RUNNINGSTATUS'] == constants.STATUS_FS_RUNNING:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
self._wait_for_condition(_create_share_complete,
|
||||
int(wait_interval))
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
if fs_id is not None:
|
||||
self.helper._delete_fs(fs_id)
|
||||
raise exception.InvalidShare('The status of filesystem error.')
|
||||
|
||||
try:
|
||||
self.helper._create_share(share_name, fs_id, share['share_proto'])
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
if fs_id is not None:
|
||||
self.helper._delete_fs(fs_id)
|
||||
|
||||
share_path = self.helper._get_share_path(share_name)
|
||||
|
||||
root = self.helper._read_xml()
|
||||
target_ip = root.findtext('Storage/LogicalPortIP').strip()
|
||||
location = ':'.join([target_ip, share_path])
|
||||
|
||||
return location
|
||||
|
||||
def get_share_stats(self, refresh=False):
|
||||
"""Get a share stats."""
|
||||
LOG.debug("Get a share stats.")
|
||||
data = self._update_share_stats()
|
||||
|
||||
return data
|
||||
|
||||
def create_share_from_snapshot(self, context, share, snapshot,
|
||||
share_server=None):
|
||||
"""Is called to create share from snapshot."""
|
||||
LOG.debug("Create share from snapshot.")
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete_share(self, context, share, share_server=None):
|
||||
"""Delete a share."""
|
||||
LOG.debug("Delete a share.")
|
||||
|
||||
self.helper._delete_share(share['name'], share['share_proto'])
|
||||
|
||||
def create_snapshot(self, context, snapshot, share_server=None):
|
||||
"""Create a snapshot."""
|
||||
snap_name = snapshot['id']
|
||||
share_proto = snapshot['share_proto']
|
||||
|
||||
share_name = self.helper._get_share_name_by_id(snapshot['share_id'])
|
||||
share_type = self.helper._get_share_type(share_proto)
|
||||
share = self.helper._get_share_by_name(share_name, share_type)
|
||||
|
||||
if not share:
|
||||
err_msg = (_("Create a snapshot,share fs id is empty."))
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidInput(reason=err_msg)
|
||||
|
||||
sharefsid = share['FSID']
|
||||
snapshot_name = "share_snapshot_" + snap_name
|
||||
snap_id = self.helper._create_snapshot(sharefsid,
|
||||
snapshot_name)
|
||||
LOG.info(_LI('Creating snapshot id %s.'), snap_id)
|
||||
|
||||
def delete_snapshot(self, context, snapshot, share_server=None):
|
||||
"""Delete a snapshot."""
|
||||
LOG.debug("Delete a snapshot.")
|
||||
snap_name = snapshot['id']
|
||||
|
||||
share_name = self.helper._get_share_name_by_id(snapshot['share_id'])
|
||||
sharefsid = self.helper._get_fsid_by_name(share_name)
|
||||
|
||||
if sharefsid is None:
|
||||
LOG.warn(_LW('Delete snapshot share id %s fs has been deleted.'),
|
||||
snap_name)
|
||||
return
|
||||
|
||||
snapshot_id = self.helper._get_snapshot_id_by_name(sharefsid,
|
||||
snap_name)
|
||||
if snapshot_id is not None:
|
||||
self.helper._delete_snapshot(snapshot_id)
|
||||
else:
|
||||
LOG.warn(_LW("Can not find snapshot %s in array."), snap_name)
|
||||
|
||||
def ensure_share(self, context, share, share_server=None):
|
||||
"""Ensure that storages are mounted and exported."""
|
||||
LOG.debug("Ensure share.")
|
||||
|
||||
def allow_access(self, context, share, access, share_server=None):
|
||||
"""Allow access to the share."""
|
||||
LOG.debug("Allow access.")
|
||||
|
||||
self.helper._allow_access(share['name'], access, share['share_proto'])
|
||||
|
||||
def deny_access(self, context, share, access, share_server=None):
|
||||
"""Deny access to the share."""
|
||||
LOG.debug("Deny access.")
|
||||
|
||||
self.helper._deny_access(share['name'], access, share['share_proto'])
|
||||
|
||||
def get_network_allocations_number(self):
|
||||
"""Get number of network interfaces to be created."""
|
||||
LOG.debug("Get network allocations number.")
|
||||
return constants.IP_ALLOCATIONS
|
||||
|
||||
def _update_share_stats(self):
|
||||
"""Retrieve status info from share group."""
|
||||
|
||||
capacity = self.helper._get_capacity()
|
||||
|
||||
# Note(zhiteng): These information are driver/backend specific,
|
||||
# each driver may define these values in its own config options
|
||||
# or fetch from driver specific configuration file.
|
||||
data = {}
|
||||
|
||||
backend_name = self.configuration.safe_get('share_backend_name')
|
||||
|
||||
data["share_backend_name"] = backend_name or 'HUAWEI_NAS_Driver'
|
||||
data["vendor_name"] = 'Huawei'
|
||||
data["driver_version"] = '1.0'
|
||||
data["storage_protocol"] = 'NFS_CIFS'
|
||||
|
||||
data['total_capacity_gb'] = capacity['total_capacity']
|
||||
data['free_capacity_gb'] = capacity['free_capacity']
|
||||
|
||||
data['reserved_percentage'] = 0
|
||||
data["share_driver_mode"] = self.mode
|
||||
data['QoS_support'] = False
|
||||
|
||||
return data
|
||||
|
||||
def _get_wait_interval(self):
|
||||
"""Get wait interval from huawei conf file."""
|
||||
root = self.helper._read_xml()
|
||||
wait_interval = root.findtext('Filesystem/WaitInterval')
|
||||
if wait_interval:
|
||||
return wait_interval
|
||||
else:
|
||||
LOG.info(_LI(
|
||||
"Wait interval is not configured in huawei "
|
||||
"conf file. Use default: %(default_wait_interval)d."),
|
||||
{"default_wait_interval": constants.DEFAULT_WAIT_INTERVAL})
|
||||
return constants.DEFAULT_WAIT_INTERVAL
|
||||
|
||||
def _get_timeout(self):
|
||||
"""Get timeout from huawei conf file."""
|
||||
root = self.helper._read_xml()
|
||||
timeout = root.findtext('Filesystem/Timeout')
|
||||
if timeout:
|
||||
return timeout
|
||||
else:
|
||||
LOG.info(_LI(
|
||||
"Timeout is not configured in huawei conf file. "
|
||||
"Use default: %(default_timeout)d."),
|
||||
{"default_timeout": constants.DEFAULT_TIMEOUT})
|
||||
return constants.DEFAULT_TIMEOUT
|
||||
|
||||
def _wait_for_condition(self, func, interval, timeout=None):
|
||||
start_time = time.time()
|
||||
if timeout is None:
|
||||
timeout = self._get_timeout()
|
||||
|
||||
def _inner():
|
||||
try:
|
||||
res = func()
|
||||
except Exception as ex:
|
||||
res = False
|
||||
LOG.debug('_wait_for_condition: %(func_name)s '
|
||||
'failed for %(exception)s.',
|
||||
{'func_name': func.__name__,
|
||||
'exception': ex.message})
|
||||
if res:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
if int(time.time()) - int(start_time) > int(timeout):
|
||||
msg = (_('_wait_for_condition: %s timed out.'),
|
||||
func.__name__)
|
||||
LOG.error(msg)
|
||||
raise exception.InvalidShare(data=msg)
|
||||
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_inner)
|
||||
timer.start(interval=interval).wait()
|
0
manila/tests/share/drivers/huawei/__init__.py
Normal file
0
manila/tests/share/drivers/huawei/__init__.py
Normal file
609
manila/tests/share/drivers/huawei/test_huawei_nas.py
Normal file
609
manila/tests/share/drivers/huawei/test_huawei_nas.py
Normal file
@ -0,0 +1,609 @@
|
||||
# Copyright (c) 2014 Huawei Technologies Co., Ltd.
|
||||
# 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.
|
||||
|
||||
"""Unit tests for the Huawei nas driver module."""
|
||||
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
import xml.dom.minidom
|
||||
|
||||
import mock
|
||||
from oslo.serialization import jsonutils
|
||||
|
||||
from manila.common import constants as const
|
||||
from manila import context
|
||||
from manila import exception
|
||||
from manila.share import configuration as conf
|
||||
from manila.share.drivers.huawei import huawei_helper
|
||||
from manila.share.drivers.huawei import huawei_nas
|
||||
from manila import test
|
||||
|
||||
|
||||
def fake_sleep(time):
|
||||
pass
|
||||
|
||||
|
||||
def data_session(url):
|
||||
if url == "/xx/sessions":
|
||||
data = """{"error":{"code":0},
|
||||
"data":{"username":"admin",
|
||||
"iBaseToken":"2001031430",
|
||||
"deviceid":"210235G7J20000000000"}}"""
|
||||
if url == "sessions":
|
||||
data = '{"error":{"code":0},"data":{"ID":11}}'
|
||||
return data
|
||||
|
||||
|
||||
def filesystem(method, fs_status_flag):
|
||||
if method == "DELETE":
|
||||
data = """{"error":{"code":0}}"""
|
||||
|
||||
if method == "GET":
|
||||
if fs_status_flag:
|
||||
data = """{"error":{"code":0},
|
||||
"data":{"HEALTHSTATUS":"1",
|
||||
"RUNNINGSTATUS":"27"}}"""
|
||||
else:
|
||||
data = """{"error":{"code":0},
|
||||
"data":{"HEALTHSTATUS":"0",
|
||||
"RUNNINGSTATUS":"27"}}"""
|
||||
return data
|
||||
|
||||
|
||||
class FakeHuaweiNasDriver(huawei_nas.HuaweiNasDriver):
|
||||
"""Fake Huawei Storage, Rewrite some methods of HuaweiNasDriver."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
huawei_nas.HuaweiNasDriver.__init__(self, *args, **kwargs)
|
||||
self.helper = FakeHuaweiNasHelper(self.configuration)
|
||||
|
||||
|
||||
class FakeHuaweiNasHelper(huawei_helper.RestHelper):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
huawei_helper.RestHelper.__init__(self, *args, **kwargs)
|
||||
self.test_normal = True
|
||||
self.deviceid = None
|
||||
self.delete_flag = False
|
||||
self.allow_flag = False
|
||||
self.deny_flag = False
|
||||
self.create_snapflag = False
|
||||
self.setupserver_flag = False
|
||||
self.fs_status_flag = True
|
||||
self.create_share_flag = False
|
||||
|
||||
def _change_file_mode(self, filepath):
|
||||
pass
|
||||
|
||||
def call(self, url, data=None, method=None):
|
||||
|
||||
url = url.replace('http://100.115.10.69:8082/deviceManager/rest', '')
|
||||
url = url.replace('/210235G7J20000000000/', '')
|
||||
data = None
|
||||
|
||||
if self.test_normal:
|
||||
if url == "/xx/sessions" or url == "sessions":
|
||||
data = data_session(url)
|
||||
|
||||
if url == "storagepool":
|
||||
data = """{"error":{"code":0},
|
||||
"data":[{"USERFREECAPACITY":"2097152",
|
||||
"ID":"1",
|
||||
"NAME":"OpenStack_Pool",
|
||||
"USERTOTALCAPACITY":"4194304"}]}"""
|
||||
|
||||
if url == "filesystem":
|
||||
data = """{"error":{"code":0},"data":{
|
||||
"ID":"4"}}"""
|
||||
|
||||
if url == "NFSHARE" or url == "CIFSHARE":
|
||||
if self.create_share_flag:
|
||||
data = '{"error":{"code":31755596}}'
|
||||
else:
|
||||
data = """{"error":{"code":0},"data":{
|
||||
"ID":"10"}}"""
|
||||
|
||||
if url == "NFSHARE?range=[100-200]":
|
||||
data = """{"error":{"code":0},
|
||||
"data":[{"ID":"1",
|
||||
"FSID":"4",
|
||||
"NAME":"test",
|
||||
"SHAREPATH":"/share_fake_uuid/"}]}"""
|
||||
|
||||
if url == "CIFSHARE?range=[100-200]":
|
||||
data = """{"error":{"code":0},
|
||||
"data":[{"ID":"2",
|
||||
"FSID":"4",
|
||||
"NAME":"test",
|
||||
"SHAREPATH":"/share_fake_uuid/"}]}"""
|
||||
|
||||
if url == "NFSHARE?range=[0-100]":
|
||||
data = """{"error":{"code":0},
|
||||
"data":[{"ID":"1",
|
||||
"FSID":"4",
|
||||
"NAME":"test_fail",
|
||||
"SHAREPATH":"/share_fake_uuid_fail/"}]}"""
|
||||
|
||||
if url == "CIFSHARE?range=[0-100]":
|
||||
data = """{"error":{"code":0},
|
||||
"data":[{"ID":"2",
|
||||
"FSID":"4",
|
||||
"NAME":"test_fail",
|
||||
"SHAREPATH":"/share_fake_uuid_fail/"}]}"""
|
||||
|
||||
if url == "NFSHARE/1" or url == "CIFSHARE/2":
|
||||
data = """{"error":{"code":0}}"""
|
||||
self.delete_flag = True
|
||||
|
||||
if url == "FSSNAPSHOT":
|
||||
data = """{"error":{"code":0},"data":{
|
||||
"ID":"3"}}"""
|
||||
self.create_snapflag = True
|
||||
|
||||
if url == "FSSNAPSHOT/3" or url == "filesystem/4":
|
||||
data = """{"error":{"code":0}}"""
|
||||
self.delete_flag = True
|
||||
|
||||
if url == "NFS_SHARE_AUTH_CLIENT"\
|
||||
or url == "CIFS_SHARE_AUTH_CLIENT":
|
||||
data = """{"error":{"code":0}}"""
|
||||
self.allow_flag = True
|
||||
|
||||
if url == "FSSNAPSHOT?TYPE=48&PARENTID=4"\
|
||||
"&&sortby=TIMESTAMP,d&range=[0-2000]":
|
||||
data = """{"error":{"code":0},
|
||||
"data":[{"ID":"3",
|
||||
"NAME":"share_snapshot_fake_snapshot_uuid"}]}"""
|
||||
self.delete_flag = True
|
||||
|
||||
if url == "NFS_SHARE_AUTH_CLIENT?"\
|
||||
"filter=PARENTID::1&range=[0-100]":
|
||||
data = """{"error":{"code":0},
|
||||
"data":[{"ID":"0",
|
||||
"NAME":"100.112.0.1_fail"}]}"""
|
||||
|
||||
if url == "CIFS_SHARE_AUTH_CLIENT?"\
|
||||
"filter=PARENTID::2&range=[0-100]":
|
||||
data = """{"error":{"code":0},
|
||||
"data":[{"ID":"0",
|
||||
"NAME":"user_name_fail"}]}"""
|
||||
|
||||
if url == "NFS_SHARE_AUTH_CLIENT?"\
|
||||
"filter=PARENTID::1&range=[100-200]":
|
||||
data = """{"error":{"code":0},
|
||||
"data":[{"ID":"5",
|
||||
"NAME":"100.112.0.1"}]}"""
|
||||
|
||||
if url == "CIFS_SHARE_AUTH_CLIENT?"\
|
||||
"filter=PARENTID::2&range=[100-200]":
|
||||
data = """{"error":{"code":0},
|
||||
"data":[{"ID":"6",
|
||||
"NAME":"user_name"}]}"""
|
||||
|
||||
if url == "NFS_SHARE_AUTH_CLIENT/5"\
|
||||
or url == "CIFS_SHARE_AUTH_CLIENT/6":
|
||||
data = """{"error":{"code":0}}"""
|
||||
self.deny_flag = True
|
||||
|
||||
if url == "NFSHARE/count" or url == "CIFSHARE/count":
|
||||
data = """{"error":{"code":0},"data":{
|
||||
"COUNT":"196"}}"""
|
||||
|
||||
if url == "NFS_SHARE_AUTH_CLIENT/count?filter=PARENTID::1"\
|
||||
or url == "CIFS_SHARE_AUTH_CLIENT/count?filter="\
|
||||
"PARENTID::2":
|
||||
data = """{"error":{"code":0},"data":{
|
||||
"COUNT":"196"}}"""
|
||||
|
||||
if url == "CIFSSERVICE":
|
||||
data = """{"error":{"code":0},"data":{
|
||||
"RUNNINGSTATUS":"2"}}"""
|
||||
|
||||
if url == "NFSSERVICE":
|
||||
data = """{"error":{"code":0},
|
||||
"data":{"RUNNINGSTATUS":"2",
|
||||
"SUPPORTV3":"true",
|
||||
"SUPPORTV4":"true"}}"""
|
||||
self.setupserver_flag = True
|
||||
|
||||
if url == "FILESYSTEM?range=[0-8191]":
|
||||
data = """{"error":{"code":0},
|
||||
"data":[{"ID":"4",
|
||||
"NAME":"share_fake_uuid"}]}"""
|
||||
|
||||
if url == "filesystem/4":
|
||||
data = filesystem(method, self.fs_status_flag)
|
||||
self.delete_flag = True
|
||||
|
||||
else:
|
||||
data = '{"error":{"code":31755596}}'
|
||||
|
||||
res_json = jsonutils.loads(data)
|
||||
return res_json
|
||||
|
||||
|
||||
class HuaweiShareDriverTestCase(test.TestCase):
|
||||
"""Tests GenericShareDriver."""
|
||||
|
||||
def setUp(self):
|
||||
super(HuaweiShareDriverTestCase, self).setUp()
|
||||
|
||||
self._context = context.get_admin_context()
|
||||
self.tmp_dir = tempfile.mkdtemp()
|
||||
self.fake_conf_file = self.tmp_dir + '/manila_huawei_conf.xml'
|
||||
self.addCleanup(shutil.rmtree, self.tmp_dir)
|
||||
self.create_fake_conf_file()
|
||||
self.addCleanup(os.remove, self.fake_conf_file)
|
||||
|
||||
def _safe_get(opt):
|
||||
return getattr(self.configuration, opt)
|
||||
|
||||
self.configuration = mock.Mock(spec=conf.Configuration)
|
||||
self.configuration.safe_get = mock.Mock(side_effect=_safe_get)
|
||||
self.configuration.network_config_group = 'fake_network_config_group'
|
||||
self.configuration.share_backend_name = 'fake_share_backend_name'
|
||||
self.configuration.share_driver_mode = const.SINGLE_SVM_MODE
|
||||
self.configuration.manila_huawei_conf_file = self.fake_conf_file
|
||||
self.stubs.Set(time, 'sleep', fake_sleep)
|
||||
driver = FakeHuaweiNasDriver(configuration=self.configuration)
|
||||
self.driver = driver
|
||||
self.driver.helper.test_normal = True
|
||||
|
||||
self.share_nfs = {
|
||||
'id': 'fake_uuid',
|
||||
'project_id': 'fake_tenant_id',
|
||||
'display_name': 'fake',
|
||||
'name': 'share_fake_uuid',
|
||||
'size': 1,
|
||||
'share_proto': 'NFS',
|
||||
'share_network_id': 'fake_net_id',
|
||||
'share_server_id': 'fake-share-srv-id',
|
||||
}
|
||||
|
||||
self.share_cifs = {
|
||||
'id': 'fake_uuid',
|
||||
'project_id': 'fake_tenant_id',
|
||||
'display_name': 'fake',
|
||||
'name': 'share_fake_uuid',
|
||||
'size': 1,
|
||||
'share_proto': 'CIFS',
|
||||
'share_network_id': 'fake_net_id',
|
||||
'share_server_id': 'fake-share-srv-id',
|
||||
}
|
||||
|
||||
self.nfs_snapshot = {
|
||||
'id': 'fake_snapshot_uuid',
|
||||
'share_name': 'share_fake_uuid',
|
||||
'share_id': 'fake_uuid',
|
||||
'display_name': 'snapshot',
|
||||
'name': 'fake_snapshot_name',
|
||||
'share_size': 1,
|
||||
'size': 1,
|
||||
'share_proto': 'NFS',
|
||||
}
|
||||
|
||||
self.cifs_snapshot = {
|
||||
'id': 'fake_snapshot_uuid',
|
||||
'share_name': 'share_fake_uuid',
|
||||
'share_id': 'fake_uuid',
|
||||
'display_name': 'snapshot',
|
||||
'name': 'fake_snapshot_name',
|
||||
'share_size': 1,
|
||||
'size': 1,
|
||||
'share_proto': 'CIFS',
|
||||
}
|
||||
|
||||
self.security_service = {
|
||||
'id': 'fake_id',
|
||||
'domain': 'FAKE',
|
||||
'server': 'fake_server',
|
||||
'user': 'fake_user',
|
||||
'password': 'fake_password',
|
||||
}
|
||||
|
||||
self.access_ip = {
|
||||
'access_type': 'ip',
|
||||
'access_to': '100.112.0.1',
|
||||
}
|
||||
|
||||
self.access_user = {
|
||||
'access_type': 'user',
|
||||
'access_to': 'user_name',
|
||||
}
|
||||
|
||||
self.share_server = None
|
||||
self.helper = mock.Mock()
|
||||
self.driver._helpers = {'FAKE': self.helper}
|
||||
self.driver._licenses = ['fake']
|
||||
|
||||
self.network_info = {
|
||||
'server_id': 'fake_server_id',
|
||||
'cidr': '10.0.0.0/24',
|
||||
'security_services': ['fake_ldap', 'fake_kerberos', 'fake_ad', ],
|
||||
'segmentation_id': '1000',
|
||||
'network_allocations': [
|
||||
{'id': 'fake_na_id_1', 'ip_address': 'fake_ip_1', },
|
||||
{'id': 'fake_na_id_2', 'ip_address': 'fake_ip_2', },
|
||||
],
|
||||
}
|
||||
|
||||
def test_login_success(self):
|
||||
deviceid = self.driver.helper.login()
|
||||
self.assertEqual("210235G7J20000000000", deviceid)
|
||||
|
||||
def test_create_share_nfs_success(self):
|
||||
self.driver.helper.login()
|
||||
location = self.driver.create_share(self._context, self.share_nfs,
|
||||
self.share_server)
|
||||
self.assertEqual("100.115.10.68:/share_fake_uuid/", location)
|
||||
|
||||
def test_create_share_cifs_success(self):
|
||||
self.driver.helper.login()
|
||||
location = self.driver.create_share(self._context, self.share_cifs,
|
||||
self.share_server)
|
||||
self.assertEqual("100.115.10.68:/share_fake_uuid/", location)
|
||||
|
||||
def test_login_fail(self):
|
||||
self.driver.helper.test_normal = False
|
||||
self.assertRaises(exception.InvalidShare, self.driver.helper.login)
|
||||
|
||||
def test_create_share_nfs_fs_fail(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.test_normal = False
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.create_share,
|
||||
self._context,
|
||||
self.share_nfs,
|
||||
self.share_server)
|
||||
|
||||
def test_create_share_nfs_status_fail(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.fs_status_flag = False
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.create_share,
|
||||
self._context,
|
||||
self.share_nfs,
|
||||
self.share_server)
|
||||
|
||||
def test_create_share_cifs_fs_fail(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.test_normal = False
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.create_share,
|
||||
self._context,
|
||||
self.share_cifs,
|
||||
self.share_server)
|
||||
|
||||
def test_create_share_cifs_fail(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.create_share_flag = True
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.create_share,
|
||||
self._context,
|
||||
self.share_cifs,
|
||||
self.share_server)
|
||||
|
||||
def test_create_share_nfs_fail(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.create_share_flag = True
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.create_share,
|
||||
self._context,
|
||||
self.share_nfs,
|
||||
self.share_server)
|
||||
|
||||
def test_delete_share_nfs_success(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.delete_flag = False
|
||||
self.driver.delete_share(self._context,
|
||||
self.share_nfs, self.share_server)
|
||||
self.assertTrue(self.driver.helper.delete_flag)
|
||||
|
||||
def test_delete_share_cifs_success(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.delete_flag = False
|
||||
self.driver.delete_share(self._context, self.share_cifs,
|
||||
self.share_server)
|
||||
self.assertTrue(self.driver.helper.delete_flag)
|
||||
|
||||
def test_get_share_stats(self):
|
||||
self.driver.helper.login()
|
||||
data = self.driver.get_share_stats()
|
||||
|
||||
expected = {}
|
||||
expected["share_backend_name"] = "fake_share_backend_name"
|
||||
expected["share_driver_mode"] = self.driver.mode
|
||||
expected["vendor_name"] = 'Huawei'
|
||||
expected["driver_version"] = '1.0'
|
||||
expected["storage_protocol"] = 'NFS_CIFS'
|
||||
expected['total_capacity_gb'] = 2
|
||||
expected['free_capacity_gb'] = 1
|
||||
expected['reserved_percentage'] = 0
|
||||
expected['QoS_support'] = False
|
||||
self.assertDictMatch(expected, data)
|
||||
|
||||
def test_get_capacity_success(self):
|
||||
self.driver.helper.login()
|
||||
capacity = {}
|
||||
capacity = self.driver.helper._get_capacity()
|
||||
self.assertEqual(2, capacity['total_capacity'])
|
||||
self.assertEqual(1, capacity['free_capacity'])
|
||||
|
||||
def test_allow_access_ip_success(self):
|
||||
self.driver.helper.login()
|
||||
self.allow_flag = False
|
||||
self.driver.allow_access(self._context,
|
||||
self.share_nfs,
|
||||
self.access_ip,
|
||||
self.share_server)
|
||||
self.assertTrue(self.driver.helper.allow_flag)
|
||||
|
||||
def test_allow_access_user_success(self):
|
||||
self.driver.helper.login()
|
||||
self.allow_flag = False
|
||||
self.driver.allow_access(self._context, self.share_cifs,
|
||||
self.access_user, self.share_server)
|
||||
self.assertTrue(self.driver.helper.allow_flag)
|
||||
|
||||
def test_allow_access_ip_fail(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.test_normal = False
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.allow_access, self._context,
|
||||
self.share_nfs, self.access_ip, self.share_server)
|
||||
|
||||
def test_allow_access_user_fail(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.test_normal = False
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.allow_access, self._context,
|
||||
self.share_cifs, self.access_user, self.share_server)
|
||||
|
||||
def test_deny_access_ip_success(self):
|
||||
self.driver.helper.login()
|
||||
self.deny_flag = False
|
||||
self.driver.deny_access(self._context, self.share_nfs,
|
||||
self.access_ip, self.share_server)
|
||||
self.assertTrue(self.driver.helper.deny_flag)
|
||||
|
||||
def test_deny_access_user_success(self):
|
||||
self.driver.helper.login()
|
||||
self.deny_flag = False
|
||||
self.driver.deny_access(self._context, self.share_cifs,
|
||||
self.access_user, self.share_server)
|
||||
self.assertTrue(self.driver.helper.deny_flag)
|
||||
|
||||
def test_deny_access_ip_fail(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.test_normal = False
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.deny_access, self._context,
|
||||
self.share_nfs, self.access_ip, self.share_server)
|
||||
|
||||
def test_deny_access_user_fail(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.test_normal = False
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.deny_access, self._context,
|
||||
self.share_cifs, self.access_user, self.share_server)
|
||||
|
||||
def test_create_nfs_snapshot_success(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.create_snapflag = False
|
||||
self.driver.create_snapshot(self._context, self.nfs_snapshot,
|
||||
self.share_server)
|
||||
self.assertTrue(self.driver.helper.create_snapflag)
|
||||
|
||||
def test_create_cifs_snapshot_success(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.create_snapflag = False
|
||||
self.driver.create_snapshot(self._context, self.cifs_snapshot,
|
||||
self.share_server)
|
||||
self.assertTrue(self.driver.helper.create_snapflag)
|
||||
|
||||
def test_delete_nfs_snapshot_success(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.delete_flag = False
|
||||
self.driver.delete_snapshot(self._context, self.nfs_snapshot,
|
||||
self.share_server)
|
||||
self.assertTrue(self.driver.helper.delete_flag)
|
||||
|
||||
def test_delete_cifs_snapshot_success(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.delete_flag = False
|
||||
self.driver.delete_snapshot(self._context, self.cifs_snapshot,
|
||||
self.share_server)
|
||||
self.assertTrue(self.driver.helper.delete_flag)
|
||||
|
||||
def test_create_nfs_snapshot_fail(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.test_normal = False
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.create_snapshot, self._context,
|
||||
self.nfs_snapshot, self.share_server)
|
||||
|
||||
def test_create_cifs_snapshot_fail(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.test_normal = False
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.create_snapshot, self._context,
|
||||
self.cifs_snapshot, self.share_server)
|
||||
|
||||
def test_delete_nfs_snapshot_fail(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.test_normal = False
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.delete_snapshot, self._context,
|
||||
self.nfs_snapshot, self.share_server)
|
||||
|
||||
def test_delete_cifs_snapshot_fail(self):
|
||||
self.driver.helper.login()
|
||||
self.driver.helper.test_normal = False
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.delete_snapshot, self._context,
|
||||
self.cifs_snapshot, self.share_server)
|
||||
|
||||
def create_fake_conf_file(self):
|
||||
doc = xml.dom.minidom.Document()
|
||||
config = doc.createElement('Config')
|
||||
doc.appendChild(config)
|
||||
|
||||
storage = doc.createElement('Storage')
|
||||
config.appendChild(storage)
|
||||
controllerip0 = doc.createElement('LogicalPortIP')
|
||||
controllerip0_text = doc.createTextNode('100.115.10.68')
|
||||
controllerip0.appendChild(controllerip0_text)
|
||||
storage.appendChild(controllerip0)
|
||||
username = doc.createElement('UserName')
|
||||
username_text = doc.createTextNode('admin')
|
||||
username.appendChild(username_text)
|
||||
storage.appendChild(username)
|
||||
userpassword = doc.createElement('UserPassword')
|
||||
userpassword_text = doc.createTextNode('Admin@storage')
|
||||
userpassword.appendChild(userpassword_text)
|
||||
storage.appendChild(userpassword)
|
||||
url = doc.createElement('RestURL')
|
||||
url_text = doc.createTextNode('http://100.115.10.69:8082/'
|
||||
'deviceManager/rest/')
|
||||
url.appendChild(url_text)
|
||||
storage.appendChild(url)
|
||||
lun = doc.createElement('Filesystem')
|
||||
config.appendChild(lun)
|
||||
storagepool = doc.createElement('StoragePool')
|
||||
waitinterval = doc.createElement('WaitInterval')
|
||||
waitinterval_text = doc.createTextNode('1')
|
||||
waitinterval.appendChild(waitinterval_text)
|
||||
|
||||
timeout = doc.createElement('Timeout')
|
||||
timeout_text = doc.createTextNode('1')
|
||||
timeout.appendChild(timeout_text)
|
||||
|
||||
pool_text = doc.createTextNode('OpenStack_Pool')
|
||||
storagepool.appendChild(pool_text)
|
||||
lun.appendChild(storagepool)
|
||||
lun.appendChild(waitinterval)
|
||||
lun.appendChild(timeout)
|
||||
|
||||
prefetch = doc.createElement('Prefetch')
|
||||
prefetch.setAttribute('Type', '0')
|
||||
prefetch.setAttribute('Value', '0')
|
||||
lun.appendChild(prefetch)
|
||||
|
||||
fakefile = open(self.fake_conf_file, 'w')
|
||||
fakefile.write(doc.toprettyxml(indent=''))
|
||||
fakefile.close()
|
Loading…
Reference in New Issue
Block a user