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.generic
|
||||||
import manila.share.drivers.glusterfs
|
import manila.share.drivers.glusterfs
|
||||||
import manila.share.drivers.glusterfs_native
|
import manila.share.drivers.glusterfs_native
|
||||||
|
import manila.share.drivers.huawei.huawei_nas
|
||||||
import manila.share.drivers.ibm.gpfs
|
import manila.share.drivers.ibm.gpfs
|
||||||
import manila.share.drivers.netapp.cluster_mode
|
import manila.share.drivers.netapp.cluster_mode
|
||||||
import manila.share.drivers.service_instance
|
import manila.share.drivers.service_instance
|
||||||
@ -102,6 +103,7 @@ _global_opt_lists = [
|
|||||||
manila.share.drivers.generic.share_opts,
|
manila.share.drivers.generic.share_opts,
|
||||||
manila.share.drivers.glusterfs.GlusterfsManilaShare_opts,
|
manila.share.drivers.glusterfs.GlusterfsManilaShare_opts,
|
||||||
manila.share.drivers.glusterfs_native.glusterfs_native_manila_share_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.ibm.gpfs.gpfs_share_opts,
|
||||||
manila.share.drivers.netapp.cluster_mode.NETAPP_NAS_OPTS,
|
manila.share.drivers.netapp.cluster_mode.NETAPP_NAS_OPTS,
|
||||||
manila.share.drivers.service_instance.server_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