794 lines
33 KiB
Python
794 lines
33 KiB
Python
# 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.
|
|
|
|
"""
|
|
Shared File system services driver for Zadara
|
|
Virtual Private Storage Array (VPSA).
|
|
"""
|
|
|
|
import socket
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import strutils
|
|
|
|
from manila import exception as manila_exception
|
|
from manila.i18n import _
|
|
from manila.share import api
|
|
from manila.share import driver
|
|
from manila.share.drivers.zadara import common
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(common.zadara_opts)
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
manila_opts = [
|
|
cfg.StrOpt('zadara_share_name_template',
|
|
default='OS_share-%s',
|
|
help='VPSA - Default template for VPSA share names'),
|
|
cfg.StrOpt('zadara_share_snap_name_template',
|
|
default='OS_share-snapshot-%s',
|
|
help='VPSA - Default template for VPSA share names'),
|
|
cfg.StrOpt('zadara_driver_ssl_cert_path',
|
|
default=None,
|
|
help='Can be used to specify a non default path to a '
|
|
'CA_BUNDLE file or directory with certificates '
|
|
'of trusted CAs, which will be used to validate '
|
|
'the backend')]
|
|
|
|
|
|
class ZadaraVPSAShareDriver(driver.ShareDriver):
|
|
"""Zadara VPSA Share driver.
|
|
|
|
Version history::
|
|
|
|
20.12-01 - Driver changes intended and aligned with
|
|
openstack latest release.
|
|
20.12-02 - Fixed #18723 - Manila: Parsing the export location in a
|
|
more generic way while managing the vpsa share
|
|
20.12-03 - Adding the metadata support while creating share to
|
|
configure vpsa.
|
|
20.12-20 - IPv6 connectivity support for Manila driver
|
|
20.12-21 - Adding unit tests and fixing review comments from the
|
|
openstack community.
|
|
20.12-22 - Addressing review comments from the manila community.
|
|
20.12-23 - Addressing review comments from the manila community.
|
|
20.12-24 - Addressing review comments from the manila community.
|
|
20.12-25 - Support host assisted share migration
|
|
"""
|
|
|
|
VERSION = '20.12-25'
|
|
|
|
# ThirdPartySystems wiki page
|
|
CI_WIKI_NAME = "ZadaraStorage_VPSA_CI"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Do initialization."""
|
|
super(ZadaraVPSAShareDriver, self).__init__(False, *args, **kwargs)
|
|
self.vpsa = None
|
|
self.configuration.append_config_values(common.zadara_opts)
|
|
self.configuration.append_config_values(manila_opts)
|
|
self.api = api.API()
|
|
# The valid list of share options that can be specified
|
|
# as the metadata while creating manila share
|
|
self.share_options = ['smbguest', 'smbonly', 'smbwindowsacl',
|
|
'smbfilecreatemask', 'smbbrowseable',
|
|
'smbhiddenfiles', 'smbhideunreadable',
|
|
'smbhideunwriteable', 'smbhidedotfiles',
|
|
'smbstoredosattributes', 'smbdircreatemask',
|
|
'smbmaparchive', 'smbencryptionmode',
|
|
'smbenableoplocks', 'smbaiosize',
|
|
'nfsrootsquash', 'nfsallsquash',
|
|
'nfsanongid', 'nfsanonuid',
|
|
'atimeupdate', 'readaheadkb', 'crypt',
|
|
'compress', 'dedupe', 'attachpolicies']
|
|
|
|
def _check_access_key_validity(self):
|
|
try:
|
|
self.vpsa._check_access_key_validity()
|
|
except common.exception.ZadaraInvalidAccessKey:
|
|
raise manila_exception.ZadaraManilaInvalidAccessKey()
|
|
|
|
def do_setup(self, context):
|
|
"""Any initialization the share driver does while starting.
|
|
|
|
Establishes initial connection with VPSA and retrieves access_key.
|
|
Need to pass driver_ssl_cert_path here (and not fetch it from the
|
|
config opts directly in common code), because this config option is
|
|
different for different drivers and so cannot be figured in the
|
|
common code.
|
|
"""
|
|
driver_ssl_cert_path = self.configuration.zadara_driver_ssl_cert_path
|
|
self.vpsa = common.ZadaraVPSAConnection(self.configuration,
|
|
driver_ssl_cert_path, False)
|
|
|
|
def check_for_setup_error(self):
|
|
"""Returns an error (exception) if prerequisites aren't met."""
|
|
self._check_access_key_validity()
|
|
|
|
def vpsa_send_cmd(self, cmd, **kwargs):
|
|
try:
|
|
response = self.vpsa.send_cmd(cmd, **kwargs)
|
|
except common.exception.UnknownCmd as e:
|
|
raise manila_exception.ZadaraUnknownCmd(cmd=e.cmd)
|
|
except common.exception.SessionRequestException as e:
|
|
raise manila_exception.ZadaraSessionRequestException(msg=e.msg)
|
|
except common.exception.BadHTTPResponseStatus as e:
|
|
raise manila_exception.ZadaraBadHTTPResponseStatus(status=e.status)
|
|
except common.exception.FailedCmdWithDump as e:
|
|
raise manila_exception.ZadaraFailedCmdWithDump(status=e.status,
|
|
data=e.data)
|
|
except common.exception.ZadaraInvalidAccessKey:
|
|
raise manila_exception.ZadaraManilaInvalidAccessKey()
|
|
return response
|
|
|
|
def _get_zadara_share_template_name(self, share_id):
|
|
return self.configuration.zadara_share_name_template % share_id
|
|
|
|
def _get_share_export_location(self, share):
|
|
export_location = ''
|
|
share_proto = share['share_proto'].upper()
|
|
|
|
share_name = self._get_zadara_share_template_name(share['id'])
|
|
vpsa_volume = self.vpsa._get_vpsa_volume(share_name)
|
|
if not vpsa_volume:
|
|
msg = (_('VPSA volume for share %s '
|
|
'could not be found.') % share['id'])
|
|
LOG.error(msg)
|
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
|
|
|
if share_proto == 'NFS':
|
|
export_location = vpsa_volume['nfs_export_path']
|
|
if share_proto == 'CIFS':
|
|
export_location = vpsa_volume['smb_export_path']
|
|
return export_location
|
|
|
|
def _check_share_protocol(self, share):
|
|
share_proto = share['share_proto'].upper()
|
|
if share_proto not in ('NFS', 'CIFS'):
|
|
msg = _("Only NFS or CIFS protocol are currently supported. "
|
|
"Share provided %(share)s with protocol "
|
|
"%(proto)s.") % {'share': share['id'],
|
|
'proto': share['share_proto']}
|
|
LOG.error(msg)
|
|
raise manila_exception.ZadaraInvalidProtocol(
|
|
protocol_type=share_proto)
|
|
|
|
def is_valid_metadata(self, metadata):
|
|
LOG.debug('Metadata while creating share: %(metadata)s',
|
|
{'metadata': metadata})
|
|
for key, value in metadata.items():
|
|
if key in self.share_options:
|
|
# Check for the values allowed with provided metadata
|
|
if key in ['smbguest', 'smbonly', 'smbwindowsacl',
|
|
'smbbrowseable', 'smbhideunreadable',
|
|
'smbhideunwriteable', 'smbhidedotfiles',
|
|
'smbstoredosattributes', 'smbmaparchive',
|
|
'smbenableoplocks', 'nfsrootsquash',
|
|
'nfsallsquash', 'atimeupdate', 'crypt',
|
|
'compress', 'dedupe', 'attachpolicies']:
|
|
if value in ['YES', 'NO']:
|
|
continue
|
|
else:
|
|
return False
|
|
if key in ['smbfilecreatemask', 'smbdircreatemask']:
|
|
if value.isdigit():
|
|
# The valid permissions should be for user,group,other
|
|
# with another special digit for attributes. Ex:0755
|
|
if len(value) != 4:
|
|
return False
|
|
# No special permission bits for suid,sgid,
|
|
# stickybit are allowed for vpsa share.
|
|
if int(value[0]) != 0:
|
|
return False
|
|
# The permissions are always specified in octal
|
|
for i in range(1, len(value)):
|
|
if int(value[i]) > 7:
|
|
return False
|
|
continue
|
|
else:
|
|
return False
|
|
if key == 'smbaiosize':
|
|
if value.isdigit() and value in ['16384', '1']:
|
|
continue
|
|
else:
|
|
return False
|
|
if key == 'smbencryptionmode':
|
|
if value in ['off', 'desired', 'required']:
|
|
continue
|
|
else:
|
|
return False
|
|
if key in ['nfsanongid', 'nfsanonuid']:
|
|
if value.isdigit() and int(value) != 0:
|
|
continue
|
|
else:
|
|
return False
|
|
if key == 'readaheadkb':
|
|
if value in ['16', '64', '128', '256', '512']:
|
|
continue
|
|
else:
|
|
return False
|
|
return True
|
|
|
|
def create_share(self, context, share, share_server=None):
|
|
"""Create a Zadara share and export it.
|
|
|
|
:param context: A RequestContext.
|
|
:param share: A Share.
|
|
:param share_server: Not used currently
|
|
:return: The export locations dictionary.
|
|
"""
|
|
# Check share's protocol.
|
|
# Throw an exception immediately if it is an invalid protocol.
|
|
self._check_share_protocol(share)
|
|
share_name = self._get_zadara_share_template_name(share['id'])
|
|
|
|
# Collect the share metadata provided and validate it
|
|
metadata = self.api.get_share_metadata(context,
|
|
{'id': share['share_id']})
|
|
if not self.is_valid_metadata(metadata):
|
|
raise manila_exception.ManilaException(_(
|
|
"Not a valid metadata provided for the share %s")
|
|
% share['id'])
|
|
|
|
data = self.vpsa_send_cmd('create_volume',
|
|
name=share_name,
|
|
size=share['size'],
|
|
metadata=metadata)
|
|
if data['status'] != 0:
|
|
raise manila_exception.ZadaraVPSAVolumeShareFailed(
|
|
error=data['status'])
|
|
|
|
export_location = self._get_share_export_location(share)
|
|
return {'path': export_location}
|
|
|
|
def _allow_access(self, context, share, access):
|
|
"""Allow access to the share."""
|
|
access_type = access['access_type']
|
|
share_proto = share['share_proto'].upper()
|
|
if share_proto == 'CIFS':
|
|
share_proto = 'SMB'
|
|
|
|
if access_type != 'ip':
|
|
raise manila_exception.ZadaraInvalidShareAccessType()
|
|
access_ip = access['access_to']
|
|
access_level = 'YES'
|
|
if access['access_level'] == 'rw':
|
|
access_level = 'NO'
|
|
|
|
# First: Check Active controller: if not valid, raise exception
|
|
ctrl = self.vpsa._get_active_controller_details()
|
|
if not ctrl:
|
|
raise manila_exception.ZadaraVPSANoActiveController()
|
|
|
|
# Get volume name
|
|
vol_name = self._get_zadara_share_template_name(share['id'])
|
|
vpsa_volume = self.vpsa._get_vpsa_volume(vol_name)
|
|
|
|
if not vpsa_volume:
|
|
msg = (_('VPSA volume for share %s '
|
|
'could not be found.') % share['id'])
|
|
LOG.error(msg)
|
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
|
|
|
# Get/Create server name for given IP
|
|
vpsa_srv = self.vpsa._create_vpsa_server(iscsi_ip=access_ip)
|
|
if not vpsa_srv:
|
|
raise manila_exception.ZadaraServerCreateFailure(name=access_ip)
|
|
|
|
servers = self.vpsa._get_servers_attached_to_volume(vpsa_volume)
|
|
attach = None
|
|
for server in servers:
|
|
if server == vpsa_srv:
|
|
attach = server
|
|
break
|
|
# Attach volume to server
|
|
if attach is None:
|
|
self.vpsa_send_cmd('attach_volume',
|
|
vpsa_srv=vpsa_srv,
|
|
vpsa_vol=vpsa_volume['name'],
|
|
share_proto=share_proto,
|
|
read_only=access_level)
|
|
|
|
data = self.vpsa_send_cmd('list_vol_attachments',
|
|
vpsa_vol=vpsa_volume['name'])
|
|
server = None
|
|
servers = data.get('servers', [])
|
|
for srv in servers:
|
|
if srv['iscsi_ip'] == access_ip:
|
|
server = srv
|
|
break
|
|
|
|
if server is None:
|
|
raise manila_exception.ZadaraAttachmentsNotFound(
|
|
name=vpsa_volume['name'])
|
|
|
|
ctrl_ip = self.vpsa._get_target_host(ctrl['ip'])
|
|
properties = {'target_discovered': False,
|
|
'target_portal': (('%s:%s') % (ctrl_ip, '3260')),
|
|
'target_ip': server['iscsi_ip'],
|
|
'id': share['id'],
|
|
'auth_method': 'CHAP',
|
|
'auth_username': ctrl['chap_user'],
|
|
'auth_password': ctrl['chap_passwd']}
|
|
|
|
LOG.debug('Attach properties: %(properties)s',
|
|
{'properties': strutils.mask_password(properties)})
|
|
return {'driver_volume_type': share['share_proto'], 'data': properties}
|
|
|
|
def delete_share(self, context, share, share_server=None):
|
|
"""Delete share. Auto detach from all servers.
|
|
|
|
"""
|
|
# Get share name
|
|
share_name = self._get_zadara_share_template_name(share['id'])
|
|
volume = self.vpsa._get_vpsa_volume(share_name)
|
|
if not volume:
|
|
LOG.warning('Volume %s could not be found. '
|
|
'It might be already deleted', share['id'])
|
|
return
|
|
|
|
self.vpsa._detach_vpsa_volume(vpsa_vol=volume)
|
|
|
|
# Delete volume associate with the share
|
|
self.vpsa_send_cmd('delete_volume', vpsa_vol=volume['name'])
|
|
|
|
def _deny_access(self, context, share, access, share_server=None):
|
|
"""Deny access to the share from the host.
|
|
|
|
"""
|
|
access_type = access['access_type']
|
|
if access_type != 'ip':
|
|
LOG.warning('Only ip access type is allowed for zadara vpsa.')
|
|
return
|
|
access_ip = access['access_to']
|
|
|
|
# First: Check Active controller: if not valid, raise exception
|
|
ctrl = self.vpsa._get_active_controller_details()
|
|
if not ctrl:
|
|
raise manila_exception.ZadaraVPSANoActiveController()
|
|
|
|
# Get share name
|
|
share_name = self._get_zadara_share_template_name(share['id'])
|
|
volume = self.vpsa._get_vpsa_volume(share_name)
|
|
if not volume:
|
|
LOG.warning('Volume %s could not be found. '
|
|
'It might be already deleted', share['id'])
|
|
return
|
|
|
|
vpsa_srv = self.vpsa._get_server_name(access_ip, True)
|
|
if not vpsa_srv:
|
|
LOG.warning('VPSA server %s could not be found.', access_ip)
|
|
return
|
|
|
|
servers_list = self.vpsa._get_servers_attached_to_volume(volume)
|
|
if vpsa_srv not in servers_list:
|
|
LOG.warning('VPSA server %(access_ip)s not attached '
|
|
'to volume %(volume)s.',
|
|
{'access_ip': access_ip, 'volume': share['id']})
|
|
return
|
|
|
|
self.vpsa._detach_vpsa_volume(vpsa_vol=volume,
|
|
vpsa_srv=vpsa_srv)
|
|
|
|
def update_access(self, context, share, access_rules, add_rules,
|
|
delete_rules, share_server=None):
|
|
access_updates = {}
|
|
if not (add_rules or delete_rules):
|
|
# add_rules and delete_rules can be empty lists, in cases
|
|
# like share migration for zadara driver, when the access
|
|
# level is to be changed for all existing rules. For zadara
|
|
# backend, we delete and re-add all the existing rules.
|
|
for access_rule in access_rules:
|
|
self._deny_access(context, share, access_rule)
|
|
try:
|
|
self._allow_access(context, share, access_rule)
|
|
except manila_exception.ZadaraInvalidShareAccessType:
|
|
LOG.error("Only ip access type allowed for Zadara share. "
|
|
"Failed to allow %(access_level)s access to "
|
|
"%(access_to)s for rule %(id)s. Setting rule "
|
|
"to 'error' state.",
|
|
{'access_level': access_rule['access_level'],
|
|
'access_to': access_rule['access_to'],
|
|
'id': access_rule['access_id']})
|
|
access_updates.update(
|
|
{access_rule['access_id']: {'state': 'error'}})
|
|
else:
|
|
if add_rules:
|
|
# Add rules for accessing share
|
|
for access_rule in add_rules:
|
|
try:
|
|
self._allow_access(context, share, access_rule)
|
|
except manila_exception.ZadaraInvalidShareAccessType:
|
|
LOG.error("Only ip access type allowed for Zadara "
|
|
"share. Failed to allow %(access_level)s "
|
|
"access to %(access_to)s for rule %(id)s. "
|
|
"Setting rule to 'error' state.",
|
|
{'access_level': access_rule['access_level'],
|
|
'access_to': access_rule['access_to'],
|
|
'id': access_rule['access_id']})
|
|
access_updates.update(
|
|
{access_rule['access_id']: {'state': 'error'}})
|
|
if delete_rules:
|
|
# Delete access rules for provided share
|
|
for access_rule in delete_rules:
|
|
self._deny_access(context, share, access_rule)
|
|
return access_updates
|
|
|
|
def extend_share(self, share, new_size, share_server=None):
|
|
"""Extend an existing share.
|
|
|
|
"""
|
|
# Get the backend volume name for the share
|
|
share_name = self._get_zadara_share_template_name(share['id'])
|
|
vpsa_volume = self.vpsa._get_vpsa_volume(share_name)
|
|
if not vpsa_volume:
|
|
msg = (_('VPSA volume for share %s '
|
|
'could not be found.') % share['id'])
|
|
LOG.error(msg)
|
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
|
|
|
size = vpsa_volume['virtual_capacity']
|
|
expand_size = new_size - size
|
|
data = self.vpsa_send_cmd('expand_volume',
|
|
vpsa_vol=vpsa_volume['name'],
|
|
size=expand_size)
|
|
if data['status'] != 0:
|
|
raise manila_exception.ZadaraExtendShareFailed(
|
|
error=data['status'])
|
|
|
|
def _ensure_share(self, context, share, share_server=None):
|
|
"""Ensure that the share has a backend volume and it is exported.
|
|
|
|
"""
|
|
# Get the backend volume name for the share
|
|
share_name = self._get_zadara_share_template_name(share['id'])
|
|
vpsa_volume = self.vpsa._get_vpsa_volume(share_name)
|
|
if not vpsa_volume:
|
|
msg = (_('VPSA volume for share %s '
|
|
'could not be found.') % share['id'])
|
|
LOG.error(msg)
|
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
|
|
|
export_locations = share['export_locations']
|
|
if export_locations:
|
|
return export_locations
|
|
else:
|
|
servers_list = (self.vpsa._get_servers_attached_to_volume(
|
|
vpsa_volume))
|
|
if len(servers_list) != 0:
|
|
msg = (_('Servers attached to the VPSA volume %s without '
|
|
'any locations exported.') % vpsa_volume['name'])
|
|
LOG.error(msg)
|
|
raise manila_exception.ZadaraShareNotValid(
|
|
name=share['id'])
|
|
|
|
def _update_share_stats(self):
|
|
|
|
backend_name = self.configuration.share_backend_name
|
|
dhss = self.configuration.driver_handles_share_servers
|
|
vpsa_poolname = self.configuration.zadara_vpsa_poolname
|
|
(total, free, provisioned) = (
|
|
self.vpsa._get_pool_capacity(vpsa_poolname))
|
|
ctrl = self.vpsa._get_active_controller_details()
|
|
if not ctrl:
|
|
raise manila_exception.ZadaraVPSANoActiveController()
|
|
ipv4_support = False if ':' in ctrl['ip'] else True
|
|
|
|
# VPSA backend pool
|
|
single_pool = dict(
|
|
pool_name=vpsa_poolname,
|
|
total_capacity_gb=total,
|
|
free_capacity_gb=free,
|
|
allocated_capacity_gb=(total - free),
|
|
provisioned_capacity_gb=provisioned,
|
|
reserved_percentage=self.configuration.reserved_share_percentage,
|
|
reserved_snapshot_percentage=(
|
|
self.configuration.reserved_share_from_snapshot_percentage
|
|
or self.configuration.reserved_share_percentage),
|
|
reserved_share_extend_percentage=(
|
|
self.configuration.reserved_share_extend_percentage
|
|
or self.configuration.reserved_share_percentage),
|
|
compression=[True, False],
|
|
dedupe=[True, False],
|
|
thin_provisioning=True
|
|
)
|
|
|
|
data = dict(
|
|
share_backend_name=backend_name,
|
|
driver_handles_share_servers=dhss,
|
|
vendor_name='Zadara Storage',
|
|
driver_version=self.VERSION,
|
|
storage_protocol='NFS_CIFS',
|
|
pools=[single_pool],
|
|
snapshot_support=True,
|
|
create_share_from_snapshot_support=True,
|
|
revert_to_snapshot_support=False,
|
|
mount_snapshot_support=False,
|
|
ipv4_support=ipv4_support,
|
|
ipv6_support=not ipv4_support
|
|
)
|
|
super(ZadaraVPSAShareDriver, self)._update_share_stats(data)
|
|
|
|
def create_snapshot(self, context, snapshot, share_server=None):
|
|
"""Creates a snapshot."""
|
|
LOG.debug('Create snapshot: %s', snapshot['id'])
|
|
|
|
# Retrieve the CG name for the base volume
|
|
share = snapshot['share']
|
|
volume_name = self._get_zadara_share_template_name(share['id'])
|
|
cg_name = self.vpsa._get_volume_cg_name(volume_name)
|
|
if not cg_name:
|
|
msg = (_('VPSA volume for share %s '
|
|
'could not be found.') % share['id'])
|
|
LOG.error(msg)
|
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
|
|
|
snap_name = (self.configuration.zadara_share_snap_name_template
|
|
% snapshot['id'])
|
|
data = self.vpsa_send_cmd('create_snapshot',
|
|
cg_name=cg_name,
|
|
snap_name=snap_name)
|
|
if data['status'] != 0:
|
|
raise manila_exception.ZadaraVPSASnapshotCreateFailed(
|
|
name=share['id'], error=data['status'])
|
|
|
|
return {'provider_location': data['snapshot_name']}
|
|
|
|
def delete_snapshot(self, context, snapshot, share_server=None):
|
|
"""Deletes a snapshot."""
|
|
LOG.debug('Delete snapshot: %s', snapshot['id'])
|
|
|
|
# Retrieve the CG name for the base volume
|
|
share = snapshot['share']
|
|
volume_name = self._get_zadara_share_template_name(share['id'])
|
|
cg_name = self.vpsa._get_volume_cg_name(volume_name)
|
|
if not cg_name:
|
|
# If the volume isn't present, then don't attempt to delete
|
|
LOG.warning('snapshot: original volume %s not found, '
|
|
'skipping delete operation',
|
|
volume_name)
|
|
return
|
|
|
|
snap_name = (self.configuration.zadara_share_snap_name_template
|
|
% snapshot['id'])
|
|
snap_id = self.vpsa._get_snap_id(cg_name, snap_name)
|
|
if not snap_id:
|
|
# If the snapshot isn't present, then don't attempt to delete
|
|
LOG.warning('snapshot: snapshot %s not found, '
|
|
'skipping delete operation', snap_name)
|
|
return
|
|
|
|
self.vpsa_send_cmd('delete_snapshot',
|
|
snap_id=snap_id)
|
|
|
|
def create_share_from_snapshot(self, context, share, snapshot,
|
|
share_server=None, parent_share=None):
|
|
"""Creates a share from a snapshot.
|
|
|
|
"""
|
|
LOG.debug('Creating share from snapshot: %s', snapshot['id'])
|
|
|
|
# Retrieve the CG name for the base volume
|
|
volume_name = (self._get_zadara_share_template_name(
|
|
snapshot['share_instance_id']))
|
|
cg_name = self.vpsa._get_volume_cg_name(volume_name)
|
|
if not cg_name:
|
|
msg = (_('VPSA volume for share %s '
|
|
'could not be found.') % share['id'])
|
|
LOG.error(msg)
|
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
|
|
|
snap_name = (self.configuration.zadara_share_snap_name_template
|
|
% snapshot['id'])
|
|
snap_id = self.vpsa._get_snap_id(cg_name, snap_name)
|
|
if not snap_id:
|
|
msg = _('Snapshot %(name)s not found') % {'name': snap_name}
|
|
LOG.error(msg)
|
|
raise manila_exception.ShareSnapshotNotFound(
|
|
snapshot_id=snap_name)
|
|
|
|
self._check_share_protocol(share)
|
|
|
|
share_name = self._get_zadara_share_template_name(share['id'])
|
|
self.vpsa_send_cmd('create_clone_from_snap',
|
|
cg_name=cg_name,
|
|
name=share_name,
|
|
snap_id=snap_id)
|
|
|
|
if share['size'] > snapshot['size']:
|
|
self.extend_share(share, share['size'])
|
|
|
|
export_location = self._get_share_export_location(share)
|
|
return [{'path': export_location}]
|
|
|
|
def _get_export_name_from_export_path(self, proto, export_path):
|
|
if proto == 'nfs' and '\\' in export_path:
|
|
return None
|
|
if proto == 'cifs' and '/' in export_path:
|
|
return None
|
|
|
|
# Extract the export name from the provided export path
|
|
if proto == 'nfs':
|
|
separator = '/'
|
|
export_location = export_path.strip(separator)
|
|
export_name = export_location.split(separator)[-1]
|
|
else:
|
|
separator = '\\'
|
|
export_location = export_path.strip(separator)
|
|
export_name = export_location.split(separator)[-1]
|
|
return export_name
|
|
|
|
def _extract_vpsa_volume_from_share(self, share):
|
|
"""Returns a vpsa volume based on the export location"""
|
|
if not share['export_locations'][0]['path']:
|
|
return None
|
|
|
|
share_proto = share['share_proto'].lower()
|
|
export_path = share['export_locations'][0]['path']
|
|
export_name = self._get_export_name_from_export_path(share_proto,
|
|
export_path)
|
|
if export_name is None:
|
|
msg = (_('Please verify the specifed protocol and export path.'))
|
|
LOG.error(msg)
|
|
raise manila_exception.ManilaException(msg)
|
|
|
|
volume = None
|
|
volumes = self.vpsa._get_all_vpsa_volumes()
|
|
# Find the volume with the corresponding export name
|
|
for vol in volumes:
|
|
if share_proto == 'nfs':
|
|
vol_export_path = vol.get('nfs_export_path', None)
|
|
else:
|
|
vol_export_path = vol.get('smb_export_path', None)
|
|
|
|
vol_export_name = self._get_export_name_from_export_path(
|
|
share_proto, vol_export_path)
|
|
if export_name == vol_export_name:
|
|
volume = vol
|
|
break
|
|
|
|
# Check the additional smb export paths of the volume
|
|
if (share_proto == 'cifs' and
|
|
vol['additional_smb_export_paths_count'] > 0):
|
|
for additional_path in vol['additional_smb_export_paths']:
|
|
vol_export_name = self._get_export_name_from_export_path(
|
|
share_proto, additional_path)
|
|
if export_name == vol_export_name:
|
|
volume = vol
|
|
break
|
|
if volume:
|
|
return volume
|
|
else:
|
|
msg = (_('Manage backend share could not be found. It might be '
|
|
'deleted or please verify the specifed protocol and '
|
|
'export path.'))
|
|
LOG.error(msg)
|
|
raise manila_exception.ManilaException(msg)
|
|
|
|
def manage_existing(self, share, driver_options):
|
|
# Check whether the specified protocol is supported or not.
|
|
self._check_share_protocol(share)
|
|
|
|
LOG.info("Share %(shr_path)s will be managed with share %(shr_name)s.",
|
|
{'shr_path': share['export_locations'][0]['path'],
|
|
'shr_name': share['id']})
|
|
|
|
# Find the backend vpsa volume for the provided export location
|
|
vpsa_volume = self._extract_vpsa_volume_from_share(share)
|
|
|
|
# Check if the volume is available
|
|
if vpsa_volume['status'] != 'Available':
|
|
msg = (_('Existing share %(name)s is not available')
|
|
% {'name': vpsa_volume['name']})
|
|
LOG.error(msg)
|
|
raise manila_exception.ManilaException(msg)
|
|
|
|
new_share_name = self._get_zadara_share_template_name(share['id'])
|
|
new_vpsa_share = self.vpsa._get_vpsa_volume(new_share_name)
|
|
if new_vpsa_share:
|
|
msg = (_('Share %(new_name)s already exists')
|
|
% {'new_name': new_share_name})
|
|
LOG.error(msg)
|
|
raise manila_exception.ManilaException(msg)
|
|
|
|
# Rename the volume to the manila share specified name
|
|
data = self.vpsa_send_cmd('rename_volume',
|
|
vpsa_vol=vpsa_volume['name'],
|
|
new_name=new_share_name)
|
|
if data['status'] != 0:
|
|
msg = (_('Renaming volume %(old_name)s to %(new_name)s '
|
|
'has failed.') % {'old_name': vpsa_volume['name'],
|
|
'new_name': new_share_name})
|
|
LOG.error(msg)
|
|
raise manila_exception.ManilaException(msg)
|
|
|
|
return {'size': vpsa_volume['provisioned_capacity'],
|
|
'export_locations': share['export_locations'][0]['path']}
|
|
|
|
def unmanage(self, share):
|
|
"""Removes the specified volume from Manila management"""
|
|
pass
|
|
|
|
def manage_existing_snapshot(self, snapshot, driver_options):
|
|
share = snapshot['share']
|
|
share_name = self._get_zadara_share_template_name(share['id'])
|
|
|
|
vpsa_volume = self.vpsa._get_vpsa_volume(share_name)
|
|
if not vpsa_volume:
|
|
msg = (_('Volume %(name)s could not be found. '
|
|
'It might be already deleted') % {'name': share_name})
|
|
LOG.error(msg)
|
|
raise manila_exception.ZadaraShareNotFound(name=share['id'])
|
|
|
|
# Check if the provider_location is specified
|
|
if not snapshot['provider_location']:
|
|
msg = (_('Provider location as snap id of the VPSA backend '
|
|
'should be provided'))
|
|
LOG.error(msg)
|
|
raise manila_exception.ManilaException(msg)
|
|
|
|
new_name = (self.configuration.zadara_share_snap_name_template
|
|
% snapshot['id'])
|
|
new_snap_id = self.vpsa._get_snap_id(vpsa_volume['cg_name'],
|
|
new_name)
|
|
if new_snap_id:
|
|
msg = (_('Snapshot with name %s already exists') % new_name)
|
|
LOG.debug(msg)
|
|
return
|
|
|
|
data = self.vpsa_send_cmd('rename_snapshot',
|
|
snap_id=snapshot['provider_location'],
|
|
new_name=new_name)
|
|
if data['status'] != 0:
|
|
raise manila_exception.ZadaraVPSASnapshotManageFailed(
|
|
snap_id=snapshot['provider_location'],
|
|
error=data['status'])
|
|
|
|
def unmanage_snapshot(self, snapshot):
|
|
"""Removes the specified snapshot from Manila management"""
|
|
pass
|
|
|
|
def get_configured_ip_versions(self):
|
|
""""Get allowed IP versions.
|
|
|
|
The shares created should have export location as per the
|
|
IP version. Currently, zadara backend doesn't support both
|
|
ipv4 and ipv6. Collect the supported IP version from the
|
|
vpsa's active controller
|
|
"""
|
|
ctrl = self.vpsa._get_active_controller_details()
|
|
if not ctrl:
|
|
raise manila_exception.ZadaraVPSANoActiveController()
|
|
|
|
if ':' in ctrl['ip']:
|
|
return [6]
|
|
else:
|
|
return [4]
|
|
|
|
def get_backend_info(self, context):
|
|
return {
|
|
'version': self.VERSION,
|
|
'vsa_feip': socket.gethostbyname(self.vpsa.conf.zadara_vpsa_host),
|
|
'vsa_port': self.vpsa.conf.zadara_vpsa_port
|
|
}
|
|
|
|
def ensure_shares(self, context, shares):
|
|
updates = {}
|
|
for share in shares:
|
|
updates[share['id']] = {
|
|
'export_locations': self._ensure_share(context, share)}
|
|
return updates
|