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:
Bob-OpenStack 2014-12-01 19:40:05 -08:00
parent 5f84b5869e
commit 911326868f
7 changed files with 1630 additions and 0 deletions

View File

@ -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,

View File

View 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

View 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()

View 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()

View 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()