428 lines
16 KiB
Python
428 lines
16 KiB
Python
# Copyright (c) 2015 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 time
|
|
|
|
from oslo_log import log
|
|
from oslo_utils import units
|
|
|
|
from manila.common import constants as common_constants
|
|
from manila import exception
|
|
from manila.i18n import _, _LI, _LW
|
|
from manila.share.drivers.huawei import base as driver
|
|
from manila.share.drivers.huawei import constants
|
|
from manila.share.drivers.huawei.v3 import helper
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class V3StorageConnection(driver.HuaweiBase):
|
|
"""Helper class for Huawei OceanStor V3 storage system."""
|
|
|
|
def __init__(self, configuration):
|
|
super(V3StorageConnection, self).__init__(configuration)
|
|
|
|
def connect(self):
|
|
"""Try to connect to V3 server."""
|
|
if self.configuration:
|
|
self.helper = helper.RestHelper(self.configuration)
|
|
else:
|
|
raise exception.InvalidInput(_("Huawei configuration missing."))
|
|
self.helper.login()
|
|
|
|
def create_share(self, share, share_server=None):
|
|
"""Create a share."""
|
|
share_name = share['name']
|
|
share_proto = share['share_proto']
|
|
|
|
fs_id = None
|
|
# We sleep here to ensure the newly created filesystem can be read.
|
|
wait_interval = self._get_wait_interval()
|
|
timeout = self._get_timeout()
|
|
|
|
try:
|
|
fs_id = self.allocate_container(share)
|
|
fs = self.helper._get_fs_info_by_id(fs_id)
|
|
end_time = time.time() + timeout
|
|
|
|
while not (self.check_fs_status(fs['HEALTHSTATUS'],
|
|
fs['RUNNINGSTATUS'])
|
|
or time.time() > end_time):
|
|
time.sleep(wait_interval)
|
|
fs = self.helper._get_fs_info_by_id(fs_id)
|
|
|
|
if not self.check_fs_status(fs['HEALTHSTATUS'],
|
|
fs['RUNNINGSTATUS']):
|
|
raise exception.InvalidShare(
|
|
reason=(_('Invalid status of filesystem: %(health)s '
|
|
'%(running)s.')
|
|
% {'health': fs['HEALTHSTATUS'],
|
|
'running': fs['RUNNINGSTATUS']}))
|
|
except Exception as err:
|
|
if fs_id is not None:
|
|
self.helper._delete_fs(fs_id)
|
|
message = (_('Failed to create share %(name)s.'
|
|
'Reason: %(err)s.')
|
|
% {'name': share_name,
|
|
'err': err})
|
|
raise exception.InvalidShare(reason=message)
|
|
|
|
try:
|
|
self.helper._create_share(share_name, fs_id, share_proto)
|
|
except Exception as err:
|
|
if fs_id is not None:
|
|
self.helper._delete_fs(fs_id)
|
|
raise exception.InvalidShare(
|
|
reason=(_('Failed to create share %(name)s.'
|
|
'Reason: %(err)s.')
|
|
% {'name': share_name,
|
|
'err': err}))
|
|
|
|
location = self._get_location_path(share_name, share_proto)
|
|
return location
|
|
|
|
def extend_share(self, share, new_size, share_server):
|
|
share_proto = share['share_proto']
|
|
share_name = share['name']
|
|
|
|
# The unit is the sectors.
|
|
size = new_size * units.Mi * 2
|
|
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 = (_("Can not get share ID by share %s.")
|
|
% share_name)
|
|
LOG.error(err_msg)
|
|
raise exception.InvalidShareAccess(reason=err_msg)
|
|
|
|
fsid = share['FSID']
|
|
self.helper._extend_share(fsid, size)
|
|
|
|
def check_fs_status(self, health_status, running_status):
|
|
if (health_status == constants.STATUS_FS_HEALTH
|
|
and running_status == constants.STATUS_FS_RUNNING):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def create_snapshot(self, 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 = _('Can not create snapshot,'
|
|
' because share_id is not provided.')
|
|
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, 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.warning(_LW('Delete snapshot share id %s fs has been '
|
|
'deleted.'), snap_name)
|
|
return
|
|
|
|
snapshot_id = self.helper._get_snapshot_id(sharefsid, snap_name)
|
|
snapshot_flag = self.helper._check_snapshot_id_exist(snapshot_id)
|
|
|
|
if snapshot_flag:
|
|
self.helper._delete_snapshot(snapshot_id)
|
|
else:
|
|
LOG.warning(_LW("Can not find snapshot %s in array."), snap_name)
|
|
|
|
def update_share_stats(self, stats_dict):
|
|
"""Retrieve status info from share group."""
|
|
capacity = self._get_capacity()
|
|
|
|
stats_dict["pools"] = []
|
|
pool = {}
|
|
pool.update(dict(
|
|
pool_name=capacity['name'],
|
|
total_capacity_gb=capacity['TOTALCAPACITY'],
|
|
free_capacity_gb=capacity['CAPACITY'],
|
|
QoS_support=False,
|
|
reserved_percentage=0,
|
|
))
|
|
stats_dict["pools"].append(pool)
|
|
|
|
def delete_share(self, share, share_server=None):
|
|
"""Delete share."""
|
|
share_name = share['name']
|
|
share_proto = self.helper._get_share_type(share['share_proto'])
|
|
share = self.helper._get_share_by_name(share_name, share_proto)
|
|
|
|
if not share:
|
|
LOG.warning(_LW('The share was not found. Share name:%s'),
|
|
share_name)
|
|
fsid = self.helper._get_fsid_by_name(share_name)
|
|
if fsid:
|
|
self.helper._delete_fs(fsid)
|
|
return
|
|
LOG.warning(_LW('The filesystem was not found.'))
|
|
return
|
|
|
|
share_id = share['ID']
|
|
share_fs_id = share['FSID']
|
|
|
|
if share_id:
|
|
self.helper._delete_share_by_id(share_id, share_proto)
|
|
|
|
if share_fs_id:
|
|
self.helper._delete_fs(share_fs_id)
|
|
|
|
return share
|
|
|
|
def get_network_allocations_number(self):
|
|
"""Get number of network interfaces to be created."""
|
|
return constants.IP_ALLOCATIONS
|
|
|
|
def _get_capacity(self):
|
|
"""Get free capacity and total capacity of the pools."""
|
|
poolinfo = self.helper._find_pool_info()
|
|
|
|
if poolinfo:
|
|
total = int(poolinfo['TOTALCAPACITY']) / units.Mi / 2
|
|
free = int(poolinfo['CAPACITY']) / units.Mi / 2
|
|
poolinfo['TOTALCAPACITY'] = total
|
|
poolinfo['CAPACITY'] = free
|
|
|
|
return poolinfo
|
|
|
|
def _init_filesys_para(self, share):
|
|
"""Init basic filesystem parameters."""
|
|
name = share['name']
|
|
size = share['size'] * units.Mi * 2
|
|
poolinfo = self.helper._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,
|
|
"ENABLEDEDUP": False,
|
|
"ENABLECOMPRESSION": False,
|
|
}
|
|
|
|
root = self.helper._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, access, share_server=None):
|
|
"""Deny access to share."""
|
|
share_proto = share['share_proto']
|
|
share_name = share['name']
|
|
share_type = self.helper._get_share_type(share_proto)
|
|
share_client_type = self.helper._get_share_client_type(share_proto)
|
|
access_type = access['access_type']
|
|
if share_proto == 'NFS' and access_type != 'ip':
|
|
LOG.warning(_LW('Only IP access type is allowed for NFS shares.'))
|
|
return
|
|
elif share_proto == 'CIFS' and access_type != 'user':
|
|
LOG.warning(_LW('Only USER access type is allowed for'
|
|
' CIFS shares.'))
|
|
return
|
|
|
|
access_to = access['access_to']
|
|
share = self.helper._get_share_by_name(share_name, share_type)
|
|
if not share:
|
|
LOG.warning(_LW('Can not get share. share_name: %s'), share_name)
|
|
return
|
|
|
|
access_id = self.helper._get_access_from_share(share['ID'], access_to,
|
|
share_client_type)
|
|
if not access_id:
|
|
LOG.warning(_LW('Can not get access id from share. '
|
|
'share_name: %s'), share_name)
|
|
return
|
|
|
|
self.helper._remove_access_from_share(access_id, share_client_type)
|
|
|
|
def allow_access(self, share, access, share_server=None):
|
|
"""Allow access to the share."""
|
|
share_proto = share['share_proto']
|
|
share_name = share['name']
|
|
share_type = self.helper._get_share_type(share_proto)
|
|
access_type = access['access_type']
|
|
access_level = access['access_level']
|
|
|
|
if access_level not in common_constants.ACCESS_LEVELS:
|
|
raise exception.InvalidShareAccess(
|
|
reason=(_('Unsupported level of access was provided - %s') %
|
|
access_level))
|
|
|
|
if share_proto == 'NFS':
|
|
if access_type == 'ip':
|
|
if access_level == common_constants.ACCESS_LEVEL_RW:
|
|
access_level = constants.ACCESS_NFS_RW
|
|
else:
|
|
access_level = constants.ACCESS_NFS_RO
|
|
else:
|
|
message = _('Only IP access type is allowed for NFS shares.')
|
|
raise exception.InvalidShareAccess(reason=message)
|
|
elif share_proto == 'CIFS':
|
|
if access_type == 'user':
|
|
if access_level == common_constants.ACCESS_LEVEL_RW:
|
|
access_level = constants.ACCESS_CIFS_RW
|
|
else:
|
|
access_level = constants.ACCESS_CIFS_RO
|
|
else:
|
|
message = _('Only USER access type is allowed'
|
|
' for CIFS shares.')
|
|
raise exception.InvalidShareAccess(reason=message)
|
|
|
|
share = self.helper._get_share_by_name(share_name, share_type)
|
|
if not share:
|
|
err_msg = (_("Can not get share ID by share %s.")
|
|
% share_name)
|
|
LOG.error(err_msg)
|
|
raise exception.InvalidShareAccess(reason=err_msg)
|
|
|
|
share_id = share['ID']
|
|
access_to = access['access_to']
|
|
self.helper._allow_access_rest(share_id, access_to,
|
|
share_proto, access_level)
|
|
|
|
def allocate_container(self, share):
|
|
"""Creates filesystem associated to share by name."""
|
|
fileParam = self._init_filesys_para(share)
|
|
fsid = self.helper._create_filesystem(fileParam)
|
|
return fsid
|
|
|
|
def _get_location_path(self, share_name, share_proto):
|
|
root = self.helper._read_xml()
|
|
target_ip = root.findtext('Storage/LogicalPortIP').strip()
|
|
|
|
location = None
|
|
if share_proto == 'NFS':
|
|
location = '%s:/%s' % (target_ip,
|
|
share_name.replace("-", "_"))
|
|
elif share_proto == 'CIFS':
|
|
location = '\\\\%s\\%s' % (target_ip,
|
|
share_name.replace("-", "_"))
|
|
else:
|
|
raise exception.InvalidShareAccess(
|
|
reason=(_('Invalid NAS protocol supplied: %s.')
|
|
% share_proto))
|
|
|
|
return location
|
|
|
|
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 int(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 int(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 check_conf_file(self):
|
|
"""Check the config file, make sure the essential items are set."""
|
|
root = self.helper._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.findtext('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 and username and 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 check_service(self):
|
|
running_status = self.helper._get_cifs_service_status()
|
|
if running_status != constants.STATUS_SERVICE_RUNNING:
|
|
self.helper._start_cifs_service_status()
|
|
|
|
service = self.helper._get_nfs_service_status()
|
|
if ((service['RUNNINGSTATUS'] != constants.STATUS_SERVICE_RUNNING) or
|
|
(service['SUPPORTV3'] == 'false') or
|
|
(service['SUPPORTV4'] == 'false')):
|
|
self.helper._start_nfs_service_status()
|