810 lines
33 KiB
Python
810 lines
33 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 strutils
|
|
from oslo_utils import units
|
|
|
|
from manila.common import constants as common_constants
|
|
from manila import exception
|
|
from manila.i18n import _
|
|
from manila.i18n import _LI
|
|
from manila.i18n import _LW
|
|
from manila.share.drivers.huawei import base as driver
|
|
from manila.share.drivers.huawei import constants
|
|
from manila.share.drivers.huawei import huawei_utils
|
|
from manila.share.drivers.huawei.v3 import helper
|
|
from manila.share.drivers.huawei.v3 import smartx
|
|
from manila.share import share_types
|
|
from manila.share import utils as share_utils
|
|
|
|
|
|
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']
|
|
|
|
pool_name = share_utils.extract_host(share['host'], level='pool')
|
|
|
|
if not pool_name:
|
|
msg = _("Pool is not available in the share host field.")
|
|
raise exception.InvalidHost(reason=msg)
|
|
|
|
result = self.helper._find_all_pool_info()
|
|
poolinfo = self.helper._find_pool_info(pool_name, result)
|
|
if not poolinfo:
|
|
msg = (_("Can not find pool info by pool name: %s") % pool_name)
|
|
raise exception.InvalidHost(reason=msg)
|
|
|
|
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, poolinfo)
|
|
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 in sectors.
|
|
size = int(new_size) * units.Mi * 2
|
|
share_url_type = self.helper._get_share_url_type(share_proto)
|
|
|
|
share = self.helper._get_share_by_name(share_name, share_url_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']
|
|
fs_info = self.helper._get_fs_info_by_id(fsid)
|
|
|
|
current_size = int(fs_info['CAPACITY']) / units.Mi / 2
|
|
if current_size > new_size:
|
|
err_msg = (_("New size for extend must be equal or bigger than "
|
|
"current size on array. (current: %(size)s, "
|
|
"new: %(new_size)s).")
|
|
% {'size': current_size, 'new_size': new_size})
|
|
|
|
LOG.error(err_msg)
|
|
raise exception.InvalidInput(reason=err_msg)
|
|
self.helper._change_share_size(fsid, size)
|
|
|
|
def shrink_share(self, share, new_size, share_server):
|
|
"""Shrinks size of existing share."""
|
|
share_proto = share['share_proto']
|
|
share_name = share['name']
|
|
|
|
# The unit is in sectors.
|
|
size = int(new_size) * units.Mi * 2
|
|
share_url_type = self.helper._get_share_url_type(share_proto)
|
|
|
|
share = self.helper._get_share_by_name(share_name, share_url_type)
|
|
if not share:
|
|
err_msg = (_("Can not get share ID by share %s.")
|
|
% share_name)
|
|
LOG.error(err_msg)
|
|
raise exception.InvalidShare(reason=err_msg)
|
|
|
|
fsid = share['FSID']
|
|
fs_info = self.helper._get_fs_info_by_id(fsid)
|
|
if not fs_info:
|
|
err_msg = (_("Can not get filesystem info by filesystem ID: %s.")
|
|
% fsid)
|
|
LOG.error(err_msg)
|
|
raise exception.InvalidShare(reason=err_msg)
|
|
|
|
current_size = int(fs_info['CAPACITY']) / units.Mi / 2
|
|
if current_size < new_size:
|
|
err_msg = (_("New size for shrink must be less than current "
|
|
"size on array. (current: %(size)s, "
|
|
"new: %(new_size)s).")
|
|
% {'size': current_size, 'new_size': new_size})
|
|
LOG.error(err_msg)
|
|
raise exception.InvalidShare(reason=err_msg)
|
|
|
|
if fs_info['ALLOCTYPE'] != constants.ALLOC_TYPE_THIN_FLAG:
|
|
err_msg = (_("Share (%s) can not be shrunk. only 'Thin' shares "
|
|
"support shrink.")
|
|
% share_name)
|
|
LOG.error(err_msg)
|
|
raise exception.InvalidShare(reason=err_msg)
|
|
|
|
self.helper._change_share_size(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']['share_proto']
|
|
|
|
share_url_type = self.helper._get_share_url_type(share_proto)
|
|
share = self.helper._get_share_by_name(snapshot['share_name'],
|
|
share_url_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']
|
|
|
|
sharefsid = self.helper._get_fsid_by_name(snapshot['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 on array."), snap_name)
|
|
|
|
def update_share_stats(self, stats_dict):
|
|
"""Retrieve status info from share group."""
|
|
root = self.helper._read_xml()
|
|
all_pool_info = self.helper._find_all_pool_info()
|
|
stats_dict["pools"] = []
|
|
|
|
pool_name_list = root.findtext('Filesystem/StoragePool')
|
|
pool_name_list = pool_name_list.split(";")
|
|
for pool_name in pool_name_list:
|
|
pool_name = pool_name.strip().strip('\n')
|
|
capacity = self._get_capacity(pool_name, all_pool_info)
|
|
if capacity:
|
|
pool = dict(
|
|
pool_name=pool_name,
|
|
total_capacity_gb=capacity['TOTALCAPACITY'],
|
|
free_capacity_gb=capacity['CAPACITY'],
|
|
provisioned_capacity_gb=(
|
|
capacity['PROVISIONEDCAPACITYGB']),
|
|
max_over_subscription_ratio=(
|
|
self.configuration.safe_get(
|
|
'max_over_subscription_ratio')),
|
|
allocated_capacity_gb=capacity['CONSUMEDCAPACITY'],
|
|
QoS_support=False,
|
|
reserved_percentage=0,
|
|
thin_provisioning=[True, False],
|
|
dedupe=[True, False],
|
|
compression=[True, False],
|
|
huawei_smartcache=[True, False],
|
|
huawei_smartpartition=[True, False],
|
|
)
|
|
stats_dict["pools"].append(pool)
|
|
|
|
if not stats_dict["pools"]:
|
|
err_msg = _("The StoragePool is None.")
|
|
LOG.error(err_msg)
|
|
raise exception.InvalidInput(reason=err_msg)
|
|
|
|
def delete_share(self, share, share_server=None):
|
|
"""Delete share."""
|
|
share_name = share['name']
|
|
share_url_type = self.helper._get_share_url_type(share['share_proto'])
|
|
share = self.helper._get_share_by_name(share_name, share_url_type)
|
|
|
|
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_url_type)
|
|
|
|
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, pool_name, result):
|
|
"""Get free capacity and total capacity of the pools."""
|
|
poolinfo = self.helper._find_pool_info(pool_name, result)
|
|
|
|
if poolinfo:
|
|
total = float(poolinfo['TOTALCAPACITY']) / units.Mi / 2
|
|
free = float(poolinfo['CAPACITY']) / units.Mi / 2
|
|
consumed = float(poolinfo['CONSUMEDCAPACITY']) / units.Mi / 2
|
|
poolinfo['TOTALCAPACITY'] = total
|
|
poolinfo['CAPACITY'] = free
|
|
poolinfo['CONSUMEDCAPACITY'] = consumed
|
|
poolinfo['PROVISIONEDCAPACITYGB'] = round(
|
|
float(total) - float(free), 2)
|
|
|
|
return poolinfo
|
|
|
|
def _init_filesys_para(self, share, poolinfo, extra_specs):
|
|
"""Init basic filesystem parameters."""
|
|
name = share['name']
|
|
size = int(share['size']) * units.Mi * 2
|
|
fileparam = {
|
|
"NAME": name.replace("-", "_"),
|
|
"DESCRIPTION": "",
|
|
"ALLOCTYPE": constants.ALLOC_TYPE_THIN_FLAG,
|
|
"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": extra_specs['dedupe'],
|
|
"ENABLECOMPRESSION": extra_specs['compression'],
|
|
}
|
|
|
|
if 'LUNType' in extra_specs:
|
|
fileparam['ALLOCTYPE'] = extra_specs['LUNType']
|
|
else:
|
|
root = self.helper._read_xml()
|
|
fstype = root.findtext('Filesystem/AllocType')
|
|
if fstype:
|
|
fstype = fstype.strip().strip('\n')
|
|
if fstype == 'Thin':
|
|
fileparam['ALLOCTYPE'] = constants.ALLOC_TYPE_THIN_FLAG
|
|
elif fstype == 'Thick':
|
|
fileparam['ALLOCTYPE'] = constants.ALLOC_TYPE_THICK_FLAG
|
|
else:
|
|
err_msg = (_(
|
|
'Config file is wrong. AllocType type must be set to'
|
|
' "Thin" or "Thick". AllocType:%(fetchtype)s') %
|
|
{'fetchtype': fstype})
|
|
LOG.error(err_msg)
|
|
raise exception.InvalidShare(reason=err_msg)
|
|
|
|
if fileparam['ALLOCTYPE'] == 0:
|
|
if (extra_specs['dedupe'] or
|
|
extra_specs['compression']):
|
|
err_msg = _(
|
|
'The filesystem type is "Thick",'
|
|
' so dedupe or compression cannot be set.')
|
|
LOG.error(err_msg)
|
|
raise exception.InvalidInput(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_url_type = self.helper._get_share_url_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_url_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_url_type = self.helper._get_share_url_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_url_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 get_pool(self, share):
|
|
pool_name = share_utils.extract_host(share['host'], level='pool')
|
|
if pool_name:
|
|
return pool_name
|
|
share_name = share['name']
|
|
share_url_type = self.helper._get_share_url_type(share['share_proto'])
|
|
share = self.helper._get_share_by_name(share_name, share_url_type)
|
|
|
|
pool_name = None
|
|
if share:
|
|
pool = self.helper._get_fs_info_by_id(share['FSID'])
|
|
pool_name = pool['POOLNAME']
|
|
|
|
return pool_name
|
|
|
|
def allocate_container(self, share, poolinfo):
|
|
"""Creates filesystem associated to share by name."""
|
|
opts = huawei_utils.get_share_extra_specs_params(
|
|
share['share_type_id'])
|
|
|
|
smartx_opts = constants.OPTS_CAPABILITIES
|
|
if opts is not None:
|
|
smart = smartx.SmartX()
|
|
smartx_opts = smart.get_smartx_extra_specs_opts(opts)
|
|
|
|
fileParam = self._init_filesys_para(share, poolinfo, smartx_opts)
|
|
fsid = self.helper._create_filesystem(fileParam)
|
|
|
|
try:
|
|
smartpartition = smartx.SmartPartition(self.helper)
|
|
smartpartition.add(opts, fsid)
|
|
|
|
smartcache = smartx.SmartCache(self.helper)
|
|
smartcache.add(opts, fsid)
|
|
except Exception as err:
|
|
if fsid is not None:
|
|
self.helper._delete_fs(fsid)
|
|
message = (_('Failed to add smartx. Reason: %(err)s.')
|
|
% {'err': err})
|
|
raise exception.InvalidShare(reason=message)
|
|
return fsid
|
|
|
|
def manage_existing(self, share, driver_options):
|
|
"""Manage existing share."""
|
|
|
|
share_proto = share['share_proto']
|
|
share_name = share['name']
|
|
old_export_location = share['export_locations'][0]['path']
|
|
pool_name = share_utils.extract_host(share['host'], level='pool')
|
|
share_url_type = self.helper._get_share_url_type(share_proto)
|
|
old_share_name = self.helper._get_share_name_by_export_location(
|
|
old_export_location, share_proto)
|
|
|
|
share_storage = self.helper._get_share_by_name(old_share_name,
|
|
share_url_type)
|
|
if not share_storage:
|
|
err_msg = (_("Can not get share ID by share %s.")
|
|
% old_export_location)
|
|
LOG.error(err_msg)
|
|
raise exception.InvalidShare(reason=err_msg)
|
|
|
|
fs_id = share_storage['FSID']
|
|
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']}))
|
|
|
|
if pool_name and pool_name != fs['POOLNAME']:
|
|
raise exception.InvalidHost(
|
|
reason=(_('The current pool(%(fs_pool)s) of filesystem '
|
|
'does not match the input pool(%(host_pool)s).')
|
|
% {'fs_pool': fs['POOLNAME'],
|
|
'host_pool': pool_name}))
|
|
|
|
result = self.helper._find_all_pool_info()
|
|
poolinfo = self.helper._find_pool_info(pool_name, result)
|
|
|
|
opts = huawei_utils.get_share_extra_specs_params(
|
|
share['share_type_id'])
|
|
specs = share_types.get_share_type_extra_specs(share['share_type_id'])
|
|
if ('capabilities:thin_provisioning' not in specs.keys()
|
|
and 'thin_provisioning' not in specs.keys()):
|
|
if fs['ALLOCTYPE'] == constants.ALLOC_TYPE_THIN_FLAG:
|
|
opts['thin_provisioning'] = constants.THIN_PROVISIONING
|
|
else:
|
|
opts['thin_provisioning'] = constants.THICK_PROVISIONING
|
|
|
|
change_opts = self.check_retype_change_opts(opts, poolinfo, fs)
|
|
LOG.info(_LI('Retyping share (%(share)s), changed options are : '
|
|
'(%(change_opts)s).'),
|
|
{'share': old_share_name, 'change_opts': change_opts})
|
|
try:
|
|
self.retype_share(change_opts, fs_id)
|
|
except Exception as err:
|
|
message = (_("Retype share error. Share: %(share)s. "
|
|
"Reason: %(reason)s.")
|
|
% {'share': old_share_name,
|
|
'reason': err})
|
|
raise exception.InvalidShare(reason=message)
|
|
|
|
share_size = int(fs['CAPACITY']) / units.Mi / 2
|
|
self.helper._change_fs_name(fs_id, share_name)
|
|
location = self._get_location_path(share_name, share_proto)
|
|
return (share_size, [location])
|
|
|
|
def check_retype_change_opts(self, opts, poolinfo, fs):
|
|
change_opts = {
|
|
"partitionid": None,
|
|
"cacheid": None,
|
|
"dedupe&compression": None,
|
|
}
|
|
|
|
# SmartPartition
|
|
old_partition_id = fs['SMARTPARTITIONID']
|
|
old_partition_name = None
|
|
new_partition_id = None
|
|
new_partition_name = None
|
|
if strutils.bool_from_string(opts['huawei_smartpartition']):
|
|
if not opts['partitionname']:
|
|
raise exception.InvalidInput(
|
|
reason=_('Partition name is None, please set '
|
|
'huawei_smartpartition:partitionname in key.'))
|
|
new_partition_name = opts['partitionname']
|
|
new_partition_id = self.helper._get_partition_id_by_name(
|
|
new_partition_name)
|
|
if new_partition_id is None:
|
|
raise exception.InvalidInput(
|
|
reason=(_("Can't find partition name on the array, "
|
|
"partition name is: %(name)s.")
|
|
% {"name": new_partition_name}))
|
|
|
|
if old_partition_id != new_partition_id:
|
|
if old_partition_id:
|
|
partition_info = self.helper.get_partition_info_by_id(
|
|
old_partition_id)
|
|
old_partition_name = partition_info['NAME']
|
|
change_opts["partitionid"] = ([old_partition_id,
|
|
old_partition_name],
|
|
[new_partition_id,
|
|
new_partition_name])
|
|
|
|
# SmartCache
|
|
old_cache_id = fs['SMARTCACHEID']
|
|
old_cache_name = None
|
|
new_cache_id = None
|
|
new_cache_name = None
|
|
if strutils.bool_from_string(opts['huawei_smartcache']):
|
|
if not opts['cachename']:
|
|
raise exception.InvalidInput(
|
|
reason=_('Cache name is None, please set '
|
|
'huawei_smartcache:cachename in key.'))
|
|
new_cache_name = opts['cachename']
|
|
new_cache_id = self.helper._get_cache_id_by_name(
|
|
new_cache_name)
|
|
if new_cache_id is None:
|
|
raise exception.InvalidInput(
|
|
reason=(_("Can't find cache name on the array, "
|
|
"cache name is: %(name)s.")
|
|
% {"name": new_cache_name}))
|
|
|
|
if old_cache_id != new_cache_id:
|
|
if old_cache_id:
|
|
cache_info = self.helper.get_cache_info_by_id(
|
|
old_cache_id)
|
|
old_cache_name = cache_info['NAME']
|
|
change_opts["cacheid"] = ([old_cache_id, old_cache_name],
|
|
[new_cache_id, new_cache_name])
|
|
|
|
# SmartDedupe&SmartCompression
|
|
smartx_opts = constants.OPTS_CAPABILITIES
|
|
if opts is not None:
|
|
smart = smartx.SmartX()
|
|
smartx_opts = smart.get_smartx_extra_specs_opts(opts)
|
|
|
|
old_compression = fs['COMPRESSION']
|
|
new_compression = smartx_opts['compression']
|
|
old_dedupe = fs['DEDUP']
|
|
new_dedupe = smartx_opts['dedupe']
|
|
|
|
if fs['ALLOCTYPE'] == constants.ALLOC_TYPE_THIN_FLAG:
|
|
fs['ALLOCTYPE'] = constants.ALLOC_TYPE_THIN
|
|
else:
|
|
fs['ALLOCTYPE'] = constants.ALLOC_TYPE_THICK
|
|
|
|
if strutils.bool_from_string(opts['thin_provisioning']):
|
|
opts['thin_provisioning'] = constants.ALLOC_TYPE_THIN
|
|
else:
|
|
opts['thin_provisioning'] = constants.ALLOC_TYPE_THICK
|
|
|
|
if fs['ALLOCTYPE'] != opts['thin_provisioning']:
|
|
msg = (_("Manage existing share "
|
|
"fs type and new_share_type mismatch. "
|
|
"fs type is: %(fs_type)s, "
|
|
"new_share_type is: %(new_share_type)s")
|
|
% {"fs_type": fs['ALLOCTYPE'],
|
|
"new_share_type": opts['thin_provisioning']})
|
|
raise exception.InvalidHost(reason=msg)
|
|
else:
|
|
if fs['ALLOCTYPE'] == constants.ALLOC_TYPE_THICK:
|
|
if new_compression or new_dedupe:
|
|
raise exception.InvalidInput(
|
|
reason=_("Dedupe or compression cannot be set for "
|
|
"thick filesystem."))
|
|
else:
|
|
if (old_dedupe != new_dedupe
|
|
or old_compression != new_compression):
|
|
change_opts["dedupe&compression"] = ([old_dedupe,
|
|
old_compression],
|
|
[new_dedupe,
|
|
new_compression])
|
|
return change_opts
|
|
|
|
def retype_share(self, change_opts, fs_id):
|
|
if change_opts.get('partitionid'):
|
|
old, new = change_opts['partitionid']
|
|
old_id = old[0]
|
|
old_name = old[1]
|
|
new_id = new[0]
|
|
new_name = new[1]
|
|
|
|
if old_id:
|
|
self.helper._remove_fs_from_partition(fs_id, old_id)
|
|
if new_id:
|
|
self.helper._add_fs_to_partition(fs_id, new_id)
|
|
msg = (_("Retype FS(id: %(fs_id)s) smartpartition from "
|
|
"(name: %(old_name)s, id: %(old_id)s) to "
|
|
"(name: %(new_name)s, id: %(new_id)s) "
|
|
"performed successfully.")
|
|
% {"fs_id": fs_id,
|
|
"old_id": old_id, "old_name": old_name,
|
|
"new_id": new_id, "new_name": new_name})
|
|
LOG.info(msg)
|
|
|
|
if change_opts.get('cacheid'):
|
|
old, new = change_opts['cacheid']
|
|
old_id = old[0]
|
|
old_name = old[1]
|
|
new_id = new[0]
|
|
new_name = new[1]
|
|
if old_id:
|
|
self.helper._remove_fs_from_cache(fs_id, old_id)
|
|
if new_id:
|
|
self.helper._add_fs_to_cache(fs_id, new_id)
|
|
msg = (_("Retype FS(id: %(fs_id)s) smartcache from "
|
|
"(name: %(old_name)s, id: %(old_id)s) to "
|
|
"(name: %(new_name)s, id: %(new_id)s) "
|
|
"performed successfully.")
|
|
% {"fs_id": fs_id,
|
|
"old_id": old_id, "old_name": old_name,
|
|
"new_id": new_id, "new_name": new_name})
|
|
LOG.info(msg)
|
|
|
|
if change_opts.get('dedupe&compression'):
|
|
old, new = change_opts['dedupe&compression']
|
|
old_dedupe = old[0]
|
|
old_compression = old[1]
|
|
new_dedupe = new[0]
|
|
new_compression = new[1]
|
|
if ((old_dedupe != new_dedupe)
|
|
or (old_compression != new_compression)):
|
|
|
|
new_smartx_opts = {"dedupe": new_dedupe,
|
|
"compression": new_compression}
|
|
|
|
self.helper._change_extra_specs(fs_id, new_smartx_opts)
|
|
msg = (_("Retype FS(id: %(fs_id)s) dedupe from %(old_dedupe)s "
|
|
"to %(new_dedupe)s performed successfully, "
|
|
"compression from "
|
|
"%(old_compression)s to %(new_compression)s "
|
|
"performed successfully.")
|
|
% {"fs_id": fs_id,
|
|
"old_dedupe": old_dedupe,
|
|
"new_dedupe": new_dedupe,
|
|
"old_compression": old_compression,
|
|
"new_compression": new_compression})
|
|
LOG.info(msg)
|
|
|
|
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()
|