manila/manila/share/drivers/hpe/hpe_3par_mediator.py

1729 lines
68 KiB
Python

# Copyright 2015 Hewlett Packard Enterprise Development LP
#
# 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.
"""HPE 3PAR Mediator for OpenStack Manila.
This 'mediator' de-couples the 3PAR focused client from the OpenStack focused
driver.
"""
from oslo_log import log
from oslo_utils import importutils
from oslo_utils import units
import six
from manila.data import utils as data_utils
from manila import exception
from manila.i18n import _
from manila import utils
hpe3parclient = importutils.try_import("hpe3parclient")
if hpe3parclient:
from hpe3parclient import file_client # pylint: disable=import-error
LOG = log.getLogger(__name__)
MIN_CLIENT_VERSION = (4, 0, 0)
DENY = '-'
ALLOW = '+'
OPEN_STACK_MANILA = 'OpenStack Manila'
FULL = 1
THIN = 2
DEDUPE = 6
ENABLED = 1
DISABLED = 2
CACHE = 'cache'
CONTINUOUS_AVAIL = 'continuous_avail'
ACCESS_BASED_ENUM = 'access_based_enum'
SMB_EXTRA_SPECS_MAP = {
CACHE: CACHE,
CONTINUOUS_AVAIL: 'ca',
ACCESS_BASED_ENUM: 'abe',
}
IP_ALREADY_EXISTS = 'IP address %s already exists'
USER_ALREADY_EXISTS = '"allow" permission already exists for "%s"'
DOES_NOT_EXIST = 'does not exist, cannot'
LOCAL_IP = '127.0.0.1'
LOCAL_IP_RO = '127.0.0.2'
SUPER_SHARE = 'OPENSTACK_SUPER_SHARE'
TMP_RO_SNAP_EXPORT = "Temp RO snapshot export as source for creating RW share."
class HPE3ParMediator(object):
"""3PAR client-facing code for the 3PAR driver.
Version history:
1.0.0 - Begin Liberty development (post-Kilo)
1.0.1 - Report thin/dedup/hp_flash_cache capabilities
1.0.2 - Add share server/share network support
1.0.3 - Use hp3par prefix for share types and capabilities
2.0.0 - Rebranded HP to HPE
2.0.1 - Add access_level (e.g. read-only support)
2.0.2 - Add extend/shrink
2.0.3 - Fix SMB read-only access (added in 2.0.1)
2.0.4 - Remove file tree on delete when using nested shares #1538800
2.0.5 - Reduce the fsquota by share size
when a share is deleted #1582931
2.0.6 - Read-write share from snapshot (using driver mount and copy)
2.0.7 - Add update_access support
2.0.8 - Multi pools support per backend
2.0.9 - Fix get_vfs() to correctly validate conf IP addresses at
boot up #1621016
"""
VERSION = "2.0.9"
def __init__(self, **kwargs):
self.hpe3par_username = kwargs.get('hpe3par_username')
self.hpe3par_password = kwargs.get('hpe3par_password')
self.hpe3par_api_url = kwargs.get('hpe3par_api_url')
self.hpe3par_debug = kwargs.get('hpe3par_debug')
self.hpe3par_san_ip = kwargs.get('hpe3par_san_ip')
self.hpe3par_san_login = kwargs.get('hpe3par_san_login')
self.hpe3par_san_password = kwargs.get('hpe3par_san_password')
self.hpe3par_san_ssh_port = kwargs.get('hpe3par_san_ssh_port')
self.hpe3par_san_private_key = kwargs.get('hpe3par_san_private_key')
self.hpe3par_fstore_per_share = kwargs.get('hpe3par_fstore_per_share')
self.hpe3par_require_cifs_ip = kwargs.get('hpe3par_require_cifs_ip')
self.hpe3par_cifs_admin_access_username = (
kwargs.get('hpe3par_cifs_admin_access_username'))
self.hpe3par_cifs_admin_access_password = (
kwargs.get('hpe3par_cifs_admin_access_password'))
self.hpe3par_cifs_admin_access_domain = (
kwargs.get('hpe3par_cifs_admin_access_domain'))
self.hpe3par_share_mount_path = kwargs.get('hpe3par_share_mount_path')
self.my_ip = kwargs.get('my_ip')
self.ssh_conn_timeout = kwargs.get('ssh_conn_timeout')
self._client = None
self.client_version = None
@staticmethod
def no_client():
return hpe3parclient is None
def do_setup(self):
if self.no_client():
msg = _('You must install hpe3parclient before using the 3PAR '
'driver. Run "pip install --upgrade python-3parclient" '
'to upgrade the hpe3parclient.')
LOG.error(msg)
raise exception.HPE3ParInvalidClient(message=msg)
self.client_version = hpe3parclient.version_tuple
if self.client_version < MIN_CLIENT_VERSION:
msg = (_('Invalid hpe3parclient version found (%(found)s). '
'Version %(minimum)s or greater required. Run "pip'
' install --upgrade python-3parclient" to upgrade'
' the hpe3parclient.') %
{'found': '.'.join(map(six.text_type, self.client_version)),
'minimum': '.'.join(map(six.text_type,
MIN_CLIENT_VERSION))})
LOG.error(msg)
raise exception.HPE3ParInvalidClient(message=msg)
try:
self._client = file_client.HPE3ParFilePersonaClient(
self.hpe3par_api_url)
except Exception as e:
msg = (_('Failed to connect to HPE 3PAR File Persona Client: %s') %
six.text_type(e))
LOG.exception(msg)
raise exception.ShareBackendException(message=msg)
try:
ssh_kwargs = {}
if self.hpe3par_san_ssh_port:
ssh_kwargs['port'] = self.hpe3par_san_ssh_port
if self.ssh_conn_timeout:
ssh_kwargs['conn_timeout'] = self.ssh_conn_timeout
if self.hpe3par_san_private_key:
ssh_kwargs['privatekey'] = self.hpe3par_san_private_key
self._client.setSSHOptions(
self.hpe3par_san_ip,
self.hpe3par_san_login,
self.hpe3par_san_password,
**ssh_kwargs
)
except Exception as e:
msg = (_('Failed to set SSH options for HPE 3PAR File Persona '
'Client: %s') % six.text_type(e))
LOG.exception(msg)
raise exception.ShareBackendException(message=msg)
LOG.info("HPE3ParMediator %(version)s, "
"hpe3parclient %(client_version)s",
{"version": self.VERSION,
"client_version": hpe3parclient.get_version_string()})
try:
wsapi_version = self._client.getWsApiVersion()['build']
LOG.info("3PAR WSAPI %s", wsapi_version)
except Exception as e:
msg = (_('Failed to get 3PAR WSAPI version: %s') %
six.text_type(e))
LOG.exception(msg)
raise exception.ShareBackendException(message=msg)
if self.hpe3par_debug:
self._client.debug_rest(True) # Includes SSH debug (setSSH above)
def _wsapi_login(self):
try:
self._client.login(self.hpe3par_username, self.hpe3par_password)
except Exception as e:
msg = (_("Failed to Login to 3PAR (%(url)s) as %(user)s "
"because: %(err)s") %
{'url': self.hpe3par_api_url,
'user': self.hpe3par_username,
'err': six.text_type(e)})
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
def _wsapi_logout(self):
try:
self._client.http.unauthenticate()
except Exception as e:
msg = ("Failed to Logout from 3PAR (%(url)s) because %(err)s")
LOG.warning(msg, {'url': self.hpe3par_api_url,
'err': six.text_type(e)})
# don't raise exception on logout()
@staticmethod
def build_export_locations(protocol, ips, path):
if not ips:
message = _('Failed to build export location due to missing IP.')
raise exception.InvalidInput(reason=message)
if not path:
message = _('Failed to build export location due to missing path.')
raise exception.InvalidInput(reason=message)
share_proto = HPE3ParMediator.ensure_supported_protocol(protocol)
if share_proto == 'nfs':
return ['%s:%s' % (ip, path) for ip in ips]
else:
return [r'\\%s\%s' % (ip, path) for ip in ips]
def get_provisioned_gb(self, fpg):
total_mb = 0
try:
result = self._client.getfsquota(fpg=fpg)
except Exception as e:
result = {'message': six.text_type(e)}
error_msg = result.get('message')
if error_msg:
message = (_('Error while getting fsquotas for FPG '
'%(fpg)s: %(msg)s') %
{'fpg': fpg, 'msg': error_msg})
LOG.error(message)
raise exception.ShareBackendException(msg=message)
for fsquota in result['members']:
total_mb += float(fsquota['hardBlock'])
return total_mb / units.Ki
def get_fpg_status(self, fpg):
"""Get capacity and capabilities for FPG."""
try:
result = self._client.getfpg(fpg)
except Exception as e:
msg = (_('Failed to get capacity for fpg %(fpg)s: %(e)s') %
{'fpg': fpg, 'e': six.text_type(e)})
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
if result['total'] != 1:
msg = (_('Failed to get capacity for fpg %s.') % fpg)
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
member = result['members'][0]
total_capacity_gb = float(member['capacityKiB']) / units.Mi
free_capacity_gb = float(member['availCapacityKiB']) / units.Mi
volumes = member['vvs']
if isinstance(volumes, list):
volume = volumes[0] # Use first name from list
else:
volume = volumes # There is just a name
self._wsapi_login()
try:
volume_info = self._client.getVolume(volume)
volume_set = self._client.getVolumeSet(fpg)
finally:
self._wsapi_logout()
provisioning_type = volume_info['provisioningType']
if provisioning_type not in (THIN, FULL, DEDUPE):
msg = (_('Unexpected provisioning type for FPG %(fpg)s: '
'%(ptype)s.') % {'fpg': fpg, 'ptype': provisioning_type})
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
dedupe = provisioning_type == DEDUPE
thin_provisioning = provisioning_type in (THIN, DEDUPE)
flash_cache_policy = volume_set.get('flashCachePolicy', DISABLED)
hpe3par_flash_cache = flash_cache_policy == ENABLED
status = {
'pool_name': fpg,
'total_capacity_gb': total_capacity_gb,
'free_capacity_gb': free_capacity_gb,
'thin_provisioning': thin_provisioning,
'dedupe': dedupe,
'hpe3par_flash_cache': hpe3par_flash_cache,
'hp3par_flash_cache': hpe3par_flash_cache,
}
if thin_provisioning:
status['provisioned_capacity_gb'] = self.get_provisioned_gb(fpg)
return status
@staticmethod
def ensure_supported_protocol(share_proto):
protocol = share_proto.lower()
if protocol == 'cifs':
protocol = 'smb'
if protocol not in ['smb', 'nfs']:
message = (_('Invalid protocol. Expected nfs or smb. Got %s.') %
protocol)
LOG.error(message)
raise exception.InvalidShareAccess(reason=message)
return protocol
@staticmethod
def other_protocol(share_proto):
"""Given 'nfs' or 'smb' (or equivalent) return the other one."""
protocol = HPE3ParMediator.ensure_supported_protocol(share_proto)
return 'nfs' if protocol == 'smb' else 'smb'
@staticmethod
def ensure_prefix(uid, protocol=None, readonly=False):
if uid.startswith('osf-'):
return uid
if protocol:
proto = '-%s' % HPE3ParMediator.ensure_supported_protocol(protocol)
else:
proto = ''
if readonly:
ro = '-ro'
else:
ro = ''
# Format is osf[-ro]-{nfs|smb}-uid
return 'osf%s%s-%s' % (proto, ro, uid)
@staticmethod
def _get_nfs_options(extra_specs, readonly):
"""Validate the NFS extra_specs and return the options to use."""
nfs_options = extra_specs.get('hpe3par:nfs_options')
if nfs_options is None:
nfs_options = extra_specs.get('hp3par:nfs_options')
if nfs_options:
msg = ("hp3par:nfs_options is deprecated. Use "
"hpe3par:nfs_options instead.")
LOG.warning(msg)
if nfs_options:
options = nfs_options.split(',')
else:
options = []
# rw, ro, and (no)root_squash (in)secure options are not allowed in
# extra_specs because they will be forcibly set below.
# no_subtree_check and fsid are not allowed per 3PAR support.
# Other strings will be allowed to be sent to the 3PAR which will do
# further validation.
options_not_allowed = ['ro', 'rw',
'no_root_squash', 'root_squash',
'secure', 'insecure',
'no_subtree_check', 'fsid']
invalid_options = [
option for option in options if option in options_not_allowed
]
if invalid_options:
raise exception.InvalidInput(_('Invalid hp3par:nfs_options or '
'hpe3par:nfs_options in '
'extra-specs. The following '
'options are not allowed: %s') %
invalid_options)
options.append('ro' if readonly else 'rw')
options.append('no_root_squash')
options.append('insecure')
return ','.join(options)
def _build_createfshare_kwargs(self, protocol, fpg, fstore, readonly,
sharedir, extra_specs, comment,
client_ip=None):
createfshare_kwargs = dict(fpg=fpg,
fstore=fstore,
sharedir=sharedir,
comment=comment)
if 'hp3par_flash_cache' in extra_specs:
msg = ("hp3par_flash_cache is deprecated. Use "
"hpe3par_flash_cache instead.")
LOG.warning(msg)
if protocol == 'nfs':
if client_ip:
createfshare_kwargs['clientip'] = client_ip
else:
# New NFS shares needs seed IP to prevent "all" access.
# Readonly and readwrite NFS shares client IPs cannot overlap.
if readonly:
createfshare_kwargs['clientip'] = LOCAL_IP_RO
else:
createfshare_kwargs['clientip'] = LOCAL_IP
options = self._get_nfs_options(extra_specs, readonly)
createfshare_kwargs['options'] = options
else:
# To keep the original (Kilo, Liberty) behavior where CIFS IP
# access rules were required in addition to user rules enable
# this to use a seed IP instead of the default (all allowed).
if self.hpe3par_require_cifs_ip:
if client_ip:
createfshare_kwargs['allowip'] = client_ip
else:
createfshare_kwargs['allowip'] = LOCAL_IP
smb_opts = (ACCESS_BASED_ENUM, CONTINUOUS_AVAIL, CACHE)
for smb_opt in smb_opts:
opt_value = extra_specs.get('hpe3par:smb_%s' % smb_opt)
if opt_value is None:
opt_value = extra_specs.get('hp3par:smb_%s' % smb_opt)
if opt_value:
msg = ("hp3par:smb_* is deprecated. Use "
"hpe3par:smb_* instead.")
LOG.warning(msg)
if opt_value:
opt_key = SMB_EXTRA_SPECS_MAP[smb_opt]
createfshare_kwargs[opt_key] = opt_value
return createfshare_kwargs
def _update_capacity_quotas(self, fstore, new_size, old_size, fpg, vfs):
@utils.synchronized('hpe3par-update-quota-' + fstore)
def _sync_update_capacity_quotas(fstore, new_size, old_size, fpg, vfs):
"""Update 3PAR quotas and return setfsquota output."""
if self.hpe3par_fstore_per_share:
hcapacity = six.text_type(new_size * units.Ki)
scapacity = hcapacity
else:
hard_size_mb = (new_size - old_size) * units.Ki
soft_size_mb = hard_size_mb
result = self._client.getfsquota(
fpg=fpg, vfs=vfs, fstore=fstore)
LOG.debug("getfsquota result=%s", result)
quotas = result['members']
if len(quotas) == 1:
hard_size_mb += int(quotas[0].get('hardBlock', '0'))
soft_size_mb += int(quotas[0].get('softBlock', '0'))
hcapacity = six.text_type(hard_size_mb)
scapacity = six.text_type(soft_size_mb)
return self._client.setfsquota(vfs,
fpg=fpg,
fstore=fstore,
scapacity=scapacity,
hcapacity=hcapacity)
try:
result = _sync_update_capacity_quotas(
fstore, new_size, old_size, fpg, vfs)
LOG.debug("setfsquota result=%s", result)
except Exception as e:
msg = (_('Failed to update capacity quota '
'%(size)s on %(fstore)s with exception: %(e)s') %
{'size': new_size - old_size,
'fstore': fstore,
'e': six.text_type(e)})
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
# Non-empty result is an error message returned from the 3PAR
if result:
msg = (_('Failed to update capacity quota '
'%(size)s on %(fstore)s with error: %(error)s') %
{'size': new_size - old_size,
'fstore': fstore,
'error': result})
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
def _create_share(self, project_id, share_id, protocol, extra_specs,
fpg, vfs, fstore, sharedir, readonly, size, comment,
client_ip=None):
share_name = self.ensure_prefix(share_id, readonly=readonly)
if not (sharedir or self.hpe3par_fstore_per_share):
sharedir = share_name
if fstore:
use_existing_fstore = True
else:
use_existing_fstore = False
if self.hpe3par_fstore_per_share:
# Do not use -ro in the fstore name.
fstore = self.ensure_prefix(share_id, readonly=False)
else:
fstore = self.ensure_prefix(project_id, protocol)
createfshare_kwargs = self._build_createfshare_kwargs(
protocol,
fpg,
fstore,
readonly,
sharedir,
extra_specs,
comment,
client_ip=client_ip)
if not use_existing_fstore:
try:
result = self._client.createfstore(
vfs, fstore, fpg=fpg,
comment=comment)
LOG.debug("createfstore result=%s", result)
except Exception as e:
msg = (_('Failed to create fstore %(fstore)s: %(e)s') %
{'fstore': fstore, 'e': six.text_type(e)})
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
if size:
self._update_capacity_quotas(fstore, size, 0, fpg, vfs)
try:
if readonly and protocol == 'nfs':
# For NFS, RO is a 2nd 3PAR share pointing to same sharedir
share_name = self.ensure_prefix(share_id, readonly=readonly)
result = self._client.createfshare(protocol,
vfs,
share_name,
**createfshare_kwargs)
LOG.debug("createfshare result=%s", result)
except Exception as e:
msg = (_('Failed to create share %(share_name)s: %(e)s') %
{'share_name': share_name, 'e': six.text_type(e)})
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
try:
result = self._client.getfshare(
protocol, share_name,
fpg=fpg, vfs=vfs, fstore=fstore)
LOG.debug("getfshare result=%s", result)
except Exception as e:
msg = (_('Failed to get fshare %(share_name)s after creating it: '
'%(e)s') % {'share_name': share_name,
'e': six.text_type(e)})
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
if result['total'] != 1:
msg = (_('Failed to get fshare %(share_name)s after creating it. '
'Expected to get 1 fshare. Got %(total)s.') %
{'share_name': share_name, 'total': result['total']})
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
return result['members'][0]
def create_share(self, project_id, share_id, share_proto, extra_specs,
fpg, vfs,
fstore=None, sharedir=None, readonly=False, size=None,
comment=OPEN_STACK_MANILA,
client_ip=None):
"""Create the share and return its path.
This method can create a share when called by the driver or when
called locally from create_share_from_snapshot(). The optional
parameters allow re-use.
:param project_id: The tenant ID.
:param share_id: The share-id with or without osf- prefix.
:param share_proto: The protocol (to map to smb or nfs)
:param extra_specs: The share type extra-specs
:param fpg: The file provisioning group
:param vfs: The virtual file system
:param fstore: (optional) The file store. When provided, an existing
file store is used. Otherwise one is created.
:param sharedir: (optional) Share directory.
:param readonly: (optional) Create share as read-only.
:param size: (optional) Size limit for file store if creating one.
:param comment: (optional) Comment to set on the share.
:param client_ip: (optional) IP address to give access to.
:return: share path string
"""
protocol = self.ensure_supported_protocol(share_proto)
share = self._create_share(project_id,
share_id,
protocol,
extra_specs,
fpg,
vfs,
fstore,
sharedir,
readonly,
size,
comment,
client_ip=client_ip)
if protocol == 'nfs':
return share['sharePath']
else:
return share['shareName']
def create_share_from_snapshot(self, share_id, share_proto, extra_specs,
orig_project_id, orig_share_id,
snapshot_id, fpg, vfs, ips,
size=None,
comment=OPEN_STACK_MANILA):
protocol = self.ensure_supported_protocol(share_proto)
snapshot_tag = self.ensure_prefix(snapshot_id)
orig_share_name = self.ensure_prefix(orig_share_id)
snapshot = self._find_fsnap(orig_project_id,
orig_share_name,
protocol,
snapshot_tag,
fpg,
vfs)
if not snapshot:
msg = (_('Failed to create share from snapshot for '
'FPG/VFS/tag %(fpg)s/%(vfs)s/%(tag)s. '
'Snapshot not found.') %
{
'fpg': fpg,
'vfs': vfs,
'tag': snapshot_tag})
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
fstore = snapshot['fstoreName']
if fstore == orig_share_name:
# No subdir for original share created with fstore_per_share
sharedir = '.snapshot/%s' % snapshot['snapName']
else:
sharedir = '.snapshot/%s/%s' % (snapshot['snapName'],
orig_share_name)
if protocol == "smb" and (not self.hpe3par_cifs_admin_access_username
or not self.hpe3par_cifs_admin_access_password):
LOG.warning("hpe3par_cifs_admin_access_username and "
"hpe3par_cifs_admin_access_password must be "
"provided in order for CIFS shares created from "
"snapshots to be writable.")
return self.create_share(
orig_project_id,
share_id,
protocol,
extra_specs,
fpg,
vfs,
fstore=fstore,
sharedir=sharedir,
readonly=True,
comment=comment,
)
# Export the snapshot as read-only to copy from.
temp = ' '.join((comment, TMP_RO_SNAP_EXPORT))
source_path = self.create_share(
orig_project_id,
share_id,
protocol,
extra_specs,
fpg,
vfs,
fstore=fstore,
sharedir=sharedir,
readonly=True,
comment=temp,
client_ip=self.my_ip
)
try:
share_name = self.ensure_prefix(share_id)
dest_path = self.create_share(
orig_project_id,
share_id,
protocol,
extra_specs,
fpg,
vfs,
fstore=fstore,
readonly=False,
size=size,
comment=comment,
client_ip=','.join((self.my_ip, LOCAL_IP))
)
try:
if protocol == 'smb':
self._grant_admin_smb_access(
protocol, fpg, vfs, fstore, comment, share=share_name)
ro_share_name = self.ensure_prefix(share_id, readonly=True)
self._grant_admin_smb_access(
protocol, fpg, vfs, fstore, temp, share=ro_share_name)
source_locations = self.build_export_locations(
protocol, ips, source_path)
dest_locations = self.build_export_locations(
protocol, ips, dest_path)
self._copy_share_data(
share_id, source_locations[0], dest_locations[0], protocol)
# Revoke the admin access that was needed to copy to the dest.
if protocol == 'nfs':
self._change_access(DENY,
orig_project_id,
share_id,
protocol,
'ip',
self.my_ip,
'rw',
fpg,
vfs)
else:
self._revoke_admin_smb_access(
protocol, fpg, vfs, fstore, comment)
except Exception as e:
msg = ('Exception during mount and copy from RO snapshot '
'to RW share: %s')
LOG.error(msg, e)
self._delete_share(share_name, protocol, fpg, vfs, fstore)
raise
finally:
self._delete_ro_share(
orig_project_id, share_id, protocol, fpg, vfs, fstore)
return dest_path
def _copy_share_data(self, dest_id, source_location, dest_location,
protocol):
mount_location = "%s%s" % (self.hpe3par_share_mount_path, dest_id)
source_share_dir = '/'.join((mount_location, "source_snap"))
dest_share_dir = '/'.join((mount_location, "dest_share"))
dirs_to_remove = []
dirs_to_unmount = []
try:
utils.execute('mkdir', '-p', source_share_dir, run_as_root=True)
dirs_to_remove.append(source_share_dir)
self._mount_share(protocol, source_location, source_share_dir)
dirs_to_unmount.append(source_share_dir)
utils.execute('mkdir', dest_share_dir, run_as_root=True)
dirs_to_remove.append(dest_share_dir)
self._mount_share(protocol, dest_location, dest_share_dir)
dirs_to_unmount.append(dest_share_dir)
self._copy_data(source_share_dir, dest_share_dir)
finally:
for d in dirs_to_unmount:
self._unmount_share(d)
if dirs_to_remove:
dirs_to_remove.append(mount_location)
utils.execute('rmdir', *dirs_to_remove, run_as_root=True)
def _copy_data(self, source_share_dir, dest_share_dir):
err_msg = None
err_data = None
try:
copy = data_utils.Copy(source_share_dir, dest_share_dir, '')
copy.run()
progress = copy.get_progress()['total_progress']
if progress != 100:
err_msg = _("Failed to copy data, reason: "
"Total progress %d != 100.")
err_data = progress
except Exception as err:
err_msg = _("Failed to copy data, reason: %s.")
err_data = six.text_type(err)
if err_msg:
raise exception.ShareBackendException(msg=err_msg % err_data)
def _delete_share(self, share_name, protocol, fpg, vfs, fstore):
try:
self._client.removefshare(
protocol, vfs, share_name, fpg=fpg, fstore=fstore)
except Exception as e:
msg = (_('Failed to remove share %(share_name)s: %(e)s') %
{'share_name': share_name, 'e': six.text_type(e)})
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
def _delete_ro_share(self, project_id, share_id, protocol,
fpg, vfs, fstore):
share_name_ro = self.ensure_prefix(share_id, readonly=True)
if not fstore:
fstore = self._find_fstore(project_id,
share_name_ro,
protocol,
fpg,
vfs,
allow_cross_protocol=True)
if fstore:
self._delete_share(share_name_ro, protocol, fpg, vfs, fstore)
return fstore
def delete_share(self, project_id, share_id, share_size, share_proto,
fpg, vfs, share_ip):
protocol = self.ensure_supported_protocol(share_proto)
share_name = self.ensure_prefix(share_id)
fstore = self._find_fstore(project_id,
share_name,
protocol,
fpg,
vfs,
allow_cross_protocol=True)
removed_writable = False
if fstore:
self._delete_share(share_name, protocol, fpg, vfs, fstore)
removed_writable = True
# Try to delete the read-only twin share, too.
fstore = self._delete_ro_share(
project_id, share_id, protocol, fpg, vfs, fstore)
if fstore == share_name:
try:
self._client.removefstore(vfs, fstore, fpg=fpg)
except Exception as e:
msg = (_('Failed to remove fstore %(fstore)s: %(e)s') %
{'fstore': fstore, 'e': six.text_type(e)})
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
elif removed_writable:
try:
# Attempt to remove file tree on delete when using nested
# shares. If the file tree cannot be removed for whatever
# reason, we will not treat this as an error_deleting
# issue. We will allow the delete to continue as requested.
self._delete_file_tree(
share_name, protocol, fpg, vfs, fstore, share_ip)
# reduce the fsquota by share size when a tree is deleted.
self._update_capacity_quotas(
fstore, 0, share_size, fpg, vfs)
except Exception as e:
msg = ('Exception during cleanup of deleted '
'share %(share)s in filestore %(fstore)s: %(e)s')
data = {
'fstore': fstore,
'share': share_name,
'e': six.text_type(e),
}
LOG.warning(msg, data)
def _delete_file_tree(self, share_name, protocol, fpg, vfs, fstore,
share_ip):
# If the share protocol is CIFS, we need to make sure the admin
# provided the proper config values. If they have not, we can simply
# return out and log a warning.
if protocol == "smb" and (not self.hpe3par_cifs_admin_access_username
or not self.hpe3par_cifs_admin_access_password):
LOG.warning("hpe3par_cifs_admin_access_username and "
"hpe3par_cifs_admin_access_password must be "
"provided in order for the file tree to be "
"properly deleted.")
return
mount_location = "%s%s" % (self.hpe3par_share_mount_path, share_name)
share_dir = mount_location + "/%s" % share_name
# Create the super share.
self._create_super_share(protocol, fpg, vfs, fstore)
# Create the mount directory.
self._create_mount_directory(mount_location)
# Mount the super share.
self._mount_super_share(protocol, mount_location, fpg, vfs, fstore,
share_ip)
# Delete the share from the super share.
self._delete_share_directory(share_dir)
# Unmount the super share.
self._unmount_share(mount_location)
# Delete the mount directory.
self._delete_share_directory(mount_location)
def _grant_admin_smb_access(self, protocol, fpg, vfs, fstore, comment,
share=SUPER_SHARE):
user = '+%s:fullcontrol' % self.hpe3par_cifs_admin_access_username
setfshare_kwargs = {
'fpg': fpg,
'fstore': fstore,
'comment': comment,
'allowperm': user,
}
try:
self._client.setfshare(
protocol, vfs, share, **setfshare_kwargs)
except Exception as err:
raise exception.ShareBackendException(
msg=_("There was an error adding permissions: %s") % err)
def _revoke_admin_smb_access(self, protocol, fpg, vfs, fstore, comment,
share=SUPER_SHARE):
user = '-%s:fullcontrol' % self.hpe3par_cifs_admin_access_username
setfshare_kwargs = {
'fpg': fpg,
'fstore': fstore,
'comment': comment,
'allowperm': user,
}
try:
self._client.setfshare(
protocol, vfs, share, **setfshare_kwargs)
except Exception as err:
raise exception.ShareBackendException(
msg=_("There was an error revoking permissions: %s") % err)
def _create_super_share(self, protocol, fpg, vfs, fstore, readonly=False):
sharedir = ''
extra_specs = {}
comment = 'OpenStack super share used to delete nested shares.'
createfshare_kwargs = self._build_createfshare_kwargs(protocol,
fpg,
fstore,
readonly,
sharedir,
extra_specs,
comment)
# If the share is NFS, we need to give the host access to the share in
# order to properly mount it.
if protocol == 'nfs':
createfshare_kwargs['clientip'] = self.my_ip
else:
createfshare_kwargs['allowip'] = self.my_ip
try:
result = self._client.createfshare(protocol,
vfs,
SUPER_SHARE,
**createfshare_kwargs)
LOG.debug("createfshare for %(name)s, result=%(result)s",
{'name': SUPER_SHARE, 'result': result})
except Exception as e:
msg = (_('Failed to create share %(share_name)s: %(e)s'),
{'share_name': SUPER_SHARE, 'e': six.text_type(e)})
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
# If the share is CIFS, we need to grant access to the specified admin.
if protocol == 'smb':
self._grant_admin_smb_access(protocol, fpg, vfs, fstore, comment)
def _create_mount_directory(self, mount_location):
try:
utils.execute('mkdir', mount_location, run_as_root=True)
except Exception as err:
message = ("There was an error creating mount directory: "
"%s. The nested file tree will not be deleted.",
six.text_type(err))
LOG.warning(message)
def _mount_share(self, protocol, export_location, mount_dir):
if protocol == 'nfs':
cmd = ('mount', '-t', 'nfs', export_location, mount_dir)
utils.execute(*cmd, run_as_root=True)
else:
export_location = export_location.replace('\\', '/')
cred = ('username=' + self.hpe3par_cifs_admin_access_username +
',password=' +
self.hpe3par_cifs_admin_access_password +
',domain=' + self.hpe3par_cifs_admin_access_domain)
cmd = ('mount', '-t', 'cifs', export_location, mount_dir,
'-o', cred)
utils.execute(*cmd, run_as_root=True)
def _mount_super_share(self, protocol, mount_dir, fpg, vfs, fstore,
share_ip):
try:
mount_location = self._generate_mount_path(
protocol, fpg, vfs, fstore, share_ip)
self._mount_share(protocol, mount_location, mount_dir)
except Exception as err:
message = ("There was an error mounting the super share: "
"%s. The nested file tree will not be deleted.",
six.text_type(err))
LOG.warning(message)
def _unmount_share(self, mount_location):
try:
utils.execute('umount', mount_location, run_as_root=True)
except Exception as err:
message = ("There was an error unmounting the share at "
"%(mount_location)s: %(error)s")
msg_data = {
'mount_location': mount_location,
'error': six.text_type(err),
}
LOG.warning(message, msg_data)
def _delete_share_directory(self, directory):
try:
utils.execute('rm', '-rf', directory, run_as_root=True)
except Exception as err:
message = ("There was an error removing the share: "
"%s. The nested file tree will not be deleted.",
six.text_type(err))
LOG.warning(message)
def _generate_mount_path(self, protocol, fpg, vfs, fstore, share_ip):
path = None
if protocol == 'nfs':
path = (("%(share_ip)s:/%(fpg)s/%(vfs)s/%(fstore)s/") %
{'share_ip': share_ip,
'fpg': fpg,
'vfs': vfs,
'fstore': fstore})
else:
path = (("//%(share_ip)s/%(share_name)s/") %
{'share_ip': share_ip,
'share_name': SUPER_SHARE})
return path
def get_vfs(self, fpg, vfs=None):
"""Get the VFS or raise an exception."""
try:
result = self._client.getvfs(fpg=fpg, vfs=vfs)
except Exception as e:
msg = (_('Exception during getvfs %(vfs)s: %(e)s') %
{'vfs': vfs, 'e': six.text_type(e)})
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
if result['total'] != 1:
error_msg = result.get('message')
if error_msg:
message = (_('Error while validating FPG/VFS '
'(%(fpg)s/%(vfs)s): %(msg)s') %
{'fpg': fpg, 'vfs': vfs, 'msg': error_msg})
LOG.error(message)
raise exception.ShareBackendException(msg=message)
else:
message = (_('Error while validating FPG/VFS '
'(%(fpg)s/%(vfs)s): Expected 1, '
'got %(total)s.') %
{'fpg': fpg, 'vfs': vfs,
'total': result['total']})
LOG.error(message)
raise exception.ShareBackendException(msg=message)
value = result['members'][0]
if isinstance(value['vfsip'], dict):
# This is for 3parclient returning only one VFS entry
LOG.debug("3parclient version up to 4.2.1 is in use. Client "
"upgrade may be needed if using a VFS with multiple "
"IP addresses.")
value['vfsip']['address'] = [value['vfsip']['address']]
else:
# This is for 3parclient returning list of VFS entries
# Format get_vfs ret value to combine all IP addresses
discovered_vfs_ips = []
for vfs_entry in value['vfsip']:
if vfs_entry['address']:
discovered_vfs_ips.append(vfs_entry['address'])
value['vfsip'] = value['vfsip'][0]
value['vfsip']['address'] = discovered_vfs_ips
return value
@staticmethod
def _is_share_from_snapshot(fshare):
path = fshare.get('shareDir')
if path:
return '.snapshot' in path.split('/')
path = fshare.get('sharePath')
return path and '.snapshot' in path.split('/')
def create_snapshot(self, orig_project_id, orig_share_id, orig_share_proto,
snapshot_id, fpg, vfs):
"""Creates a snapshot of a share."""
fshare = self._find_fshare(orig_project_id,
orig_share_id,
orig_share_proto,
fpg,
vfs)
if not fshare:
msg = (_('Failed to create snapshot for FPG/VFS/fshare '
'%(fpg)s/%(vfs)s/%(fshare)s: Failed to find fshare.') %
{'fpg': fpg, 'vfs': vfs, 'fshare': orig_share_id})
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
if self._is_share_from_snapshot(fshare):
msg = (_('Failed to create snapshot for FPG/VFS/fshare '
'%(fpg)s/%(vfs)s/%(fshare)s: Share is a read-only '
'share of an existing snapshot.') %
{'fpg': fpg, 'vfs': vfs, 'fshare': orig_share_id})
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
fstore = fshare.get('fstoreName')
snapshot_tag = self.ensure_prefix(snapshot_id)
try:
result = self._client.createfsnap(
vfs, fstore, snapshot_tag, fpg=fpg)
LOG.debug("createfsnap result=%s", result)
except Exception as e:
msg = (_('Failed to create snapshot for FPG/VFS/fstore '
'%(fpg)s/%(vfs)s/%(fstore)s: %(e)s') %
{'fpg': fpg, 'vfs': vfs, 'fstore': fstore,
'e': six.text_type(e)})
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
def delete_snapshot(self, orig_project_id, orig_share_id, orig_proto,
snapshot_id, fpg, vfs):
"""Deletes a snapshot of a share."""
snapshot_tag = self.ensure_prefix(snapshot_id)
snapshot = self._find_fsnap(orig_project_id, orig_share_id, orig_proto,
snapshot_tag, fpg, vfs)
if not snapshot:
return
fstore = snapshot.get('fstoreName')
for protocol in ('nfs', 'smb'):
try:
shares = self._client.getfshare(protocol,
fpg=fpg,
vfs=vfs,
fstore=fstore)
except Exception as e:
msg = (_('Unexpected exception while getting share list. '
'Cannot delete snapshot without checking for '
'dependent shares first: %s') % six.text_type(e))
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
for share in shares['members']:
if protocol == 'nfs':
path = share['sharePath'][1:].split('/')
dot_snapshot_index = 3
else:
if share['shareDir']:
path = share['shareDir'].split('/')
else:
path = None
dot_snapshot_index = 0
snapshot_index = dot_snapshot_index + 1
if path and len(path) > snapshot_index:
if (path[dot_snapshot_index] == '.snapshot' and
path[snapshot_index].endswith(snapshot_tag)):
msg = (_('Cannot delete snapshot because it has a '
'dependent share.'))
raise exception.Invalid(msg)
snapname = snapshot['snapName']
try:
result = self._client.removefsnap(
vfs, fstore, snapname=snapname, fpg=fpg)
LOG.debug("removefsnap result=%s", result)
except Exception as e:
msg = (_('Failed to delete snapshot for FPG/VFS/fstore/snapshot '
'%(fpg)s/%(vfs)s/%(fstore)s/%(snapname)s: %(e)s') %
{
'fpg': fpg,
'vfs': vfs,
'fstore': fstore,
'snapname': snapname,
'e': six.text_type(e)})
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
# Try to reclaim the space
try:
self._client.startfsnapclean(fpg, reclaimStrategy='maxspeed')
except Exception:
# Remove already happened so only log this.
LOG.exception('Unexpected exception calling startfsnapclean '
'for FPG %(fpg)s.', {'fpg': fpg})
@staticmethod
def _validate_access_type(protocol, access_type):
if access_type not in ('ip', 'user'):
msg = (_("Invalid access type. Expected 'ip' or 'user'. "
"Actual '%s'.") % access_type)
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
if protocol == 'nfs' and access_type != 'ip':
msg = (_("Invalid NFS access type. HPE 3PAR NFS supports 'ip'. "
"Actual '%s'.") % access_type)
LOG.error(msg)
raise exception.HPE3ParInvalid(err=msg)
return protocol
@staticmethod
def _validate_access_level(protocol, access_type, access_level, fshare):
readonly = access_level == 'ro'
snapshot = HPE3ParMediator._is_share_from_snapshot(fshare)
if snapshot and not readonly:
reason = _('3PAR shares from snapshots require read-only access')
LOG.error(reason)
raise exception.InvalidShareAccess(reason=reason)
if protocol == 'smb' and access_type == 'ip' and snapshot != readonly:
msg = (_("Invalid CIFS access rule. HPE 3PAR optionally supports "
"IP access rules for CIFS shares, but they must be "
"read-only for shares from snapshots and read-write for "
"other shares. Use the required CIFS 'user' access rules "
"to refine access."))
LOG.error(msg)
raise exception.InvalidShareAccess(reason=msg)
@staticmethod
def ignore_benign_access_results(plus_or_minus, access_type, access_to,
result):
# TODO(markstur): Remove the next line when hpe3parclient is fixed.
result = [x for x in result if x != '\r']
if result:
if plus_or_minus == DENY:
if DOES_NOT_EXIST in result[0]:
return None
else:
if access_type == 'user':
if USER_ALREADY_EXISTS % access_to in result[0]:
return None
elif IP_ALREADY_EXISTS % access_to in result[0]:
return None
return result
def _change_access(self, plus_or_minus, project_id, share_id, share_proto,
access_type, access_to, access_level,
fpg, vfs, extra_specs=None):
"""Allow or deny access to a share.
Plus_or_minus character indicates add to allow list (+) or remove from
allow list (-).
"""
readonly = access_level == 'ro'
protocol = self.ensure_supported_protocol(share_proto)
try:
self._validate_access_type(protocol, access_type)
except Exception:
if plus_or_minus == DENY:
# Catch invalid rules for deny. Allow them to be deleted.
return
else:
raise
fshare = self._find_fshare(project_id,
share_id,
protocol,
fpg,
vfs,
readonly=readonly)
if not fshare:
# Change access might apply to the share with the name that
# does not match the access_level prefix.
other_fshare = self._find_fshare(project_id,
share_id,
protocol,
fpg,
vfs,
readonly=not readonly)
if other_fshare:
if plus_or_minus == DENY:
# Try to deny rule from 'other' share for SMB or legacy.
fshare = other_fshare
elif self._is_share_from_snapshot(other_fshare):
# Found a share-from-snapshot from before
# "-ro" was added to the name. Use it.
fshare = other_fshare
elif protocol == 'nfs':
# We don't have the RO|RW share we need, but the
# opposite one already exists. It is OK to create
# the one we need for ALLOW with NFS (not from snapshot).
fstore = other_fshare.get('fstoreName')
sharedir = other_fshare.get('shareDir')
comment = other_fshare.get('comment')
fshare = self._create_share(project_id,
share_id,
protocol,
extra_specs,
fpg,
vfs,
fstore=fstore,
sharedir=sharedir,
readonly=readonly,
size=None,
comment=comment)
else:
# SMB only has one share for RO and RW. Try to use it.
fshare = other_fshare
if not fshare:
msg = _('Failed to change (%(change)s) access '
'to FPG/share %(fpg)s/%(share)s '
'for %(type)s %(to)s %(level)s): '
'Share does not exist on 3PAR.')
msg_data = {
'change': plus_or_minus,
'fpg': fpg,
'share': share_id,
'type': access_type,
'to': access_to,
'level': access_level,
}
if plus_or_minus == DENY:
LOG.warning(msg, msg_data)
return
else:
raise exception.HPE3ParInvalid(err=msg % msg_data)
try:
self._validate_access_level(
protocol, access_type, access_level, fshare)
except exception.InvalidShareAccess as e:
if plus_or_minus == DENY:
# Allow invalid access rules to be deleted.
msg = _('Ignoring deny invalid access rule '
'for FPG/share %(fpg)s/%(share)s '
'for %(type)s %(to)s %(level)s): %(e)s')
msg_data = {
'change': plus_or_minus,
'fpg': fpg,
'share': share_id,
'type': access_type,
'to': access_to,
'level': access_level,
'e': six.text_type(e),
}
LOG.info(msg, msg_data)
return
else:
raise
share_name = fshare.get('shareName')
setfshare_kwargs = {
'fpg': fpg,
'fstore': fshare.get('fstoreName'),
'comment': fshare.get('comment'),
}
if protocol == 'nfs':
access_change = '%s%s' % (plus_or_minus, access_to)
setfshare_kwargs['clientip'] = access_change
elif protocol == 'smb':
if access_type == 'ip':
access_change = '%s%s' % (plus_or_minus, access_to)
setfshare_kwargs['allowip'] = access_change
else:
access_str = 'read' if readonly else 'fullcontrol'
perm = '%s%s:%s' % (plus_or_minus, access_to, access_str)
setfshare_kwargs['allowperm'] = perm
try:
result = self._client.setfshare(
protocol, vfs, share_name, **setfshare_kwargs)
result = self.ignore_benign_access_results(
plus_or_minus, access_type, access_to, result)
except Exception as e:
result = six.text_type(e)
LOG.debug("setfshare result=%s", result)
if result:
msg = (_('Failed to change (%(change)s) access to FPG/share '
'%(fpg)s/%(share)s for %(type)s %(to)s %(level)s: '
'%(error)s') %
{'change': plus_or_minus,
'fpg': fpg,
'share': share_id,
'type': access_type,
'to': access_to,
'level': access_level,
'error': result})
raise exception.ShareBackendException(msg=msg)
def _find_fstore(self, project_id, share_id, share_proto, fpg, vfs,
allow_cross_protocol=False):
share = self._find_fshare(project_id,
share_id,
share_proto,
fpg,
vfs,
allow_cross_protocol=allow_cross_protocol)
return share.get('fstoreName') if share else None
def _find_fshare(self, project_id, share_id, share_proto, fpg, vfs,
allow_cross_protocol=False, readonly=False):
share = self._find_fshare_with_proto(project_id,
share_id,
share_proto,
fpg,
vfs,
readonly=readonly)
if not share and allow_cross_protocol:
other_proto = self.other_protocol(share_proto)
share = self._find_fshare_with_proto(project_id,
share_id,
other_proto,
fpg,
vfs,
readonly=readonly)
return share
def _find_fshare_with_proto(self, project_id, share_id, share_proto,
fpg, vfs, readonly=False):
protocol = self.ensure_supported_protocol(share_proto)
share_name = self.ensure_prefix(share_id, readonly=readonly)
project_fstore = self.ensure_prefix(project_id, share_proto)
search_order = [
{'fpg': fpg, 'vfs': vfs, 'fstore': project_fstore},
{'fpg': fpg, 'vfs': vfs, 'fstore': share_name},
{'fpg': fpg},
{}
]
try:
for search_params in search_order:
result = self._client.getfshare(protocol, share_name,
**search_params)
shares = result.get('members', [])
if len(shares) == 1:
return shares[0]
except Exception as e:
msg = (_('Unexpected exception while getting share list: %s') %
six.text_type(e))
raise exception.ShareBackendException(msg=msg)
def _find_fsnap(self, project_id, share_id, orig_proto, snapshot_tag,
fpg, vfs):
share_name = self.ensure_prefix(share_id)
osf_project_id = self.ensure_prefix(project_id, orig_proto)
pattern = '*_%s' % self.ensure_prefix(snapshot_tag)
search_order = [
{'pat': True, 'fpg': fpg, 'vfs': vfs, 'fstore': osf_project_id},
{'pat': True, 'fpg': fpg, 'vfs': vfs, 'fstore': share_name},
{'pat': True, 'fpg': fpg},
{'pat': True},
]
try:
for search_params in search_order:
result = self._client.getfsnap(pattern, **search_params)
snapshots = result.get('members', [])
if len(snapshots) == 1:
return snapshots[0]
except Exception as e:
msg = (_('Unexpected exception while getting snapshots: %s') %
six.text_type(e))
raise exception.ShareBackendException(msg=msg)
def update_access(self, project_id, share_id, share_proto, extra_specs,
access_rules, add_rules, delete_rules, fpg, vfs):
"""Update access to a share."""
protocol = self.ensure_supported_protocol(share_proto)
if not (delete_rules or add_rules):
# We need to re add all the rules. Check with 3PAR on it's current
# list and only add the deltas.
share = self._find_fshare(project_id,
share_id,
share_proto,
fpg,
vfs)
ref_users = []
ro_ref_rules = []
if protocol == 'nfs':
ref_rules = share['clients']
# Check for RO rules.
ro_share = self._find_fshare(project_id,
share_id,
share_proto,
fpg,
vfs,
readonly=True)
if ro_share:
ro_ref_rules = ro_share['clients']
else:
ref_rules = [x[0] for x in share['allowPerm']]
ref_users = ref_rules[:]
# Get IP access as well
ips = share['allowIP']
if not isinstance(ips, list):
# If there is only one IP, the API returns a string
# rather than a list. We need to account for that.
ips = [ips]
ref_rules += ips
# Retrieve base rules.
base_rules = []
for rule in access_rules:
base_rules.append(rule['access_to'])
# Check if we need to remove any rules from 3PAR.
for rule in ref_rules:
if rule in ref_users:
rule_type = 'user'
else:
rule_type = 'ip'
if rule not in base_rules + [LOCAL_IP, LOCAL_IP_RO]:
self._change_access(DENY,
project_id,
share_id,
share_proto,
rule_type,
rule,
None,
fpg,
vfs)
# Check to see if there are any RO rules to remove.
for rule in ro_ref_rules:
if rule not in base_rules + [LOCAL_IP, LOCAL_IP_RO]:
self._change_access(DENY,
project_id,
share_id,
share_proto,
rule_type,
rule,
'ro',
fpg,
vfs)
# Check the rules we need to add.
for rule in access_rules:
if rule['access_to'] not in ref_rules and (
rule['access_to'] not in ro_ref_rules):
# Rule does not exist, we need to add it
self._change_access(ALLOW,
project_id,
share_id,
share_proto,
rule['access_type'],
rule['access_to'],
rule['access_level'],
fpg,
vfs,
extra_specs=extra_specs)
else:
# We have deltas of the rules that need to be added and deleted.
for rule in delete_rules:
self._change_access(DENY,
project_id,
share_id,
share_proto,
rule['access_type'],
rule['access_to'],
rule['access_level'],
fpg,
vfs)
for rule in add_rules:
self._change_access(ALLOW,
project_id,
share_id,
share_proto,
rule['access_type'],
rule['access_to'],
rule['access_level'],
fpg,
vfs,
extra_specs=extra_specs)
def resize_share(self, project_id, share_id, share_proto,
new_size, old_size, fpg, vfs):
"""Extends or shrinks size of existing share."""
share_name = self.ensure_prefix(share_id)
fstore = self._find_fstore(project_id,
share_name,
share_proto,
fpg,
vfs,
allow_cross_protocol=False)
if not fstore:
msg = (_('Cannot resize share because it was not found.'))
raise exception.InvalidShare(reason=msg)
self._update_capacity_quotas(fstore, new_size, old_size, fpg, vfs)
def fsip_exists(self, fsip):
"""Try to get FSIP. Return True if it exists."""
vfs = fsip['vfs']
fpg = fsip['fspool']
try:
result = self._client.getfsip(vfs, fpg=fpg)
LOG.debug("getfsip result: %s", result)
except Exception:
msg = (_('Failed to get FSIPs for FPG/VFS %(fspool)s/%(vfs)s.') %
fsip)
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
for member in result['members']:
if all(item in member.items() for item in fsip.items()):
return True
return False
def create_fsip(self, ip, subnet, vlantag, fpg, vfs):
vlantag_str = six.text_type(vlantag) if vlantag else '0'
# Try to create it. It's OK if it already exists.
try:
result = self._client.createfsip(ip,
subnet,
vfs,
fpg=fpg,
vlantag=vlantag_str)
LOG.debug("createfsip result: %s", result)
except Exception:
msg = (_('Failed to create FSIP for %s') % ip)
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
# Verify that it really exists.
fsip = {
'fspool': fpg,
'vfs': vfs,
'address': ip,
'prefixLen': subnet,
'vlanTag': vlantag_str,
}
if not self.fsip_exists(fsip):
msg = (_('Failed to get FSIP after creating it for '
'FPG/VFS/IP/subnet/VLAN '
'%(fspool)s/%(vfs)s/'
'%(address)s/%(prefixLen)s/%(vlanTag)s.') % fsip)
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)
def remove_fsip(self, ip, fpg, vfs):
if not (vfs and ip):
# If there is no VFS and/or IP, then there is no FSIP to remove.
return
try:
result = self._client.removefsip(vfs, ip, fpg=fpg)
LOG.debug("removefsip result: %s", result)
except Exception:
msg = (_('Failed to remove FSIP %s') % ip)
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
# Verify that it really no longer exists.
fsip = {
'fspool': fpg,
'vfs': vfs,
'address': ip,
}
if self.fsip_exists(fsip):
msg = (_('Failed to remove FSIP for FPG/VFS/IP '
'%(fspool)s/%(vfs)s/%(address)s.') % fsip)
LOG.error(msg)
raise exception.ShareBackendException(msg=msg)