HP 3PAR Driver for Manila

Implement share driver for HP 3PAR arrays with File Persona capabilities.

NFS and CIFS shares can be created and deleted.
Snapshots can be created and deleted.
Read-only shares can be created from snapshots.

Does not support handling of share servers.

A pre-assigned IP address and a pre-configured VFS is used
instead of share networks.  This is done by setting 'hp3par_share_ip_address'
to the desired IP address in the manila.conf.

DocImpact

Change-Id: I90e19a1d34d568eba6178ca3dbb605f37c598439
Implements: blueprint hp3par-manila-driver
This commit is contained in:
Mark Sturdevant 2014-12-06 23:51:25 -08:00
parent 8412517852
commit 2e709aa87d
9 changed files with 1909 additions and 0 deletions

View File

@ -458,6 +458,18 @@ class EMCVnxXMLAPIError(Invalid):
message = _("%(err)s")
class HP3ParInvalidClient(Invalid):
message = _("%(err)s")
class HP3ParInvalid(Invalid):
message = _("%(err)s")
class HP3ParUnexpectedError(ManilaException):
message = _("%(err)s")
class GPFSException(ManilaException):
message = _("GPFS exception occurred.")

View File

@ -108,6 +108,7 @@ _global_opt_lists = [
manila.share.drivers.glusterfs.GlusterfsManilaShare_opts,
manila.share.drivers.glusterfs_native.glusterfs_native_manila_share_opts,
manila.share.drivers.hds.sop.hdssop_share_opts,
manila.share.drivers.hp.hp_3par_driver.HP3PAR_OPTS,
manila.share.drivers.huawei.huawei_nas.huawei_opts,
manila.share.drivers.ibm.gpfs.gpfs_share_opts,
manila.share.drivers.netapp.cluster_mode.NETAPP_NAS_OPTS,

View File

View File

@ -0,0 +1,279 @@
# Copyright 2015 Hewlett Packard Development Company, L.P.
#
# 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.
"""HP 3PAR Driver for OpenStack Manila."""
import hashlib
import inspect
import logging
from oslo_config import cfg
import six
from manila import exception
from manila.i18n import _
from manila.i18n import _LI
from manila.openstack.common import log
from manila.share import driver
from manila.share.drivers.hp import hp_3par_mediator
HP3PAR_OPTS = [
cfg.StrOpt('hp3par_api_url',
default='',
help="3PAR WSAPI Server Url like "
"https://<3par ip>:8080/api/v1"),
cfg.StrOpt('hp3par_username',
default='',
help="3PAR Super user username"),
cfg.StrOpt('hp3par_password',
default='',
help="3PAR Super user password",
secret=True),
cfg.StrOpt('hp3par_san_ip',
default='',
help="IP address of SAN controller"),
cfg.StrOpt('hp3par_san_login',
default='',
help="Username for SAN controller"),
cfg.StrOpt('hp3par_san_password',
default='',
help="Password for SAN controller",
secret=True),
cfg.IntOpt('hp3par_san_ssh_port',
default=22,
help='SSH port to use with SAN'),
cfg.StrOpt('hp3par_fpg',
default="OpenStack",
help="The File Provisioning Group (FPG) to use"),
cfg.StrOpt('hp3par_share_ip_address',
default='',
help="The IP address for shares not using a share server"),
cfg.BoolOpt('hp3par_debug',
default=False,
help="Enable HTTP debugging to 3PAR"),
]
CONF = cfg.CONF
CONF.register_opts(HP3PAR_OPTS)
LOG = log.getLogger(__name__)
class HP3ParShareDriver(driver.ShareDriver):
"""HP 3PAR driver for Manila.
Supports NFS and CIFS protocols on arrays with File Persona.
"""
def __init__(self, *args, **kwargs):
super(HP3ParShareDriver, self).__init__(False, *args, **kwargs)
self.configuration = kwargs.get('configuration', None)
self.configuration.append_config_values(HP3PAR_OPTS)
self.configuration.append_config_values(driver.ssh_opts)
self.fpg = None
self.vfs = None
self.share_ip_address = None
self._hp3par = None # mediator between driver and client
def do_setup(self, context):
"""Any initialization the share driver does while starting."""
self.share_ip_address = self.configuration.hp3par_share_ip_address
if not self.share_ip_address:
raise exception.HP3ParInvalid(
_("Unsupported configuration. "
"hp3par_share_ip_address is not set."))
mediator = hp_3par_mediator.HP3ParMediator(
hp3par_username=self.configuration.hp3par_username,
hp3par_password=self.configuration.hp3par_password,
hp3par_api_url=self.configuration.hp3par_api_url,
hp3par_debug=self.configuration.hp3par_debug,
hp3par_san_ip=self.configuration.hp3par_san_ip,
hp3par_san_login=self.configuration.hp3par_san_login,
hp3par_san_password=self.configuration.hp3par_san_password,
hp3par_san_ssh_port=self.configuration.hp3par_san_ssh_port,
ssh_conn_timeout=self.configuration.ssh_conn_timeout,
)
mediator.do_setup()
# FPG must be configured and must exist.
self.fpg = self.configuration.safe_get('hp3par_fpg')
# Validate the FPG and discover the VFS
# This also validates the client, connection, firmware, WSAPI, FPG...
self.vfs = mediator.get_vfs_name(self.fpg)
# Don't set _hp3par until it is ready. Otherwise _update_stats fails.
self._hp3par = mediator
def check_for_setup_error(self):
try:
# Log the source SHA for support. Only do this with DEBUG.
if LOG.isEnabledFor(logging.DEBUG):
driver_source = inspect.getsourcelines(HP3ParShareDriver)
driver_sha1 = hashlib.sha1('blob %(source_size)s\0%('
'source_string)s' %
{
'source_size': len(
driver_source),
'source_string': driver_source,
})
LOG.debug('HP3ParShareDriver SHA1: %s',
driver_sha1.hexdigest())
mediator_source = inspect.getsourcelines(
hp_3par_mediator.HP3ParMediator)
mediator_sha1 = hashlib.sha1(
'blob %(source_size)s\0%(source_string)s' %
{
'source_size': len(mediator_source),
'source_string': mediator_source,
})
LOG.debug('HP3ParMediator SHA1: %s', mediator_sha1.hexdigest())
except Exception as e:
# Don't let any exceptions during the SHA1 logging interfere
# with startup. This is just debug info to identify the source
# code. If it doesn't work, just log a debug message.
LOG.debug('Source code SHA1 not logged due to: %s',
six.text_type(e))
@staticmethod
def _build_export_location(protocol, ip, path):
if protocol == 'NFS':
location = ':'.join((ip, path))
elif protocol == 'CIFS':
location = '\\\\%s\%s' % (ip, path)
else:
message = _('Invalid protocol. Expected NFS or CIFS. '
'Got %s.') % protocol
raise exception.InvalidInput(message)
return location
def create_share(self, context, share, share_server=None):
"""Is called to create share."""
ip = self.share_ip_address
protocol = share['share_proto']
path = self._hp3par.create_share(
share['id'],
protocol,
self.fpg, self.vfs,
size=share['size']
)
return self._build_export_location(protocol, ip, path)
def create_share_from_snapshot(self, context, share, snapshot,
share_server=None):
"""Is called to create share from snapshot."""
ip = self.share_ip_address
protocol = share['share_proto']
path = self._hp3par.create_share_from_snapshot(
share['id'],
protocol,
snapshot['share']['id'],
snapshot['id'],
self.fpg,
self.vfs
)
return self._build_export_location(protocol, ip, path)
def delete_share(self, context, share, share_server=None):
"""Deletes share and its fstore."""
self._hp3par.delete_share(share['id'],
share['share_proto'],
self.fpg,
self.vfs)
def create_snapshot(self, context, snapshot, share_server=None):
"""Creates a snapshot of a share."""
self._hp3par.create_snapshot(snapshot['share']['id'],
snapshot['id'],
self.fpg,
self.vfs)
def delete_snapshot(self, context, snapshot, share_server=None):
"""Deletes a snapshot of a share."""
self._hp3par.delete_snapshot(snapshot['share']['id'],
snapshot['id'],
self.fpg,
self.vfs)
def ensure_share(self, context, share, share_server=None):
pass
def allow_access(self, context, share, access, share_server=None):
"""Allow access to the share."""
self._hp3par.allow_access(share['id'],
share['share_proto'],
access['access_type'],
access['access_to'],
self.fpg,
self.vfs)
def deny_access(self, context, share, access, share_server=None):
"""Deny access to the share."""
self._hp3par.deny_access(share['id'],
share['share_proto'],
access['access_type'],
access['access_to'],
self.fpg,
self.vfs)
def _update_share_stats(self):
"""Retrieve stats info from share group."""
if not self._hp3par:
LOG.info(
_LI("Skipping share statistics update. Setup has not "
"completed."))
total_capacity_gb = 0
free_capacity_gb = 0
else:
capacity_stats = self._hp3par.get_capacity(self.fpg)
LOG.debug("Share capacity = %s.", capacity_stats)
total_capacity_gb = capacity_stats['total_capacity_gb']
free_capacity_gb = capacity_stats['free_capacity_gb']
backend_name = self.configuration.safe_get(
'share_backend_name') or "HP_3PAR"
reserved_share_percentage = self.configuration.safe_get(
'reserved_share_percentage')
if reserved_share_percentage is None:
reserved_share_percentage = 0
stats = {
'share_backend_name': backend_name,
'driver_handles_share_servers': self.driver_handles_share_servers,
'vendor_name': 'HP',
'driver_version': '1.0',
'storage_protocol': 'NFS_CIFS',
'total_capacity_gb': total_capacity_gb,
'free_capacity_gb': free_capacity_gb,
'reserved_percentage': reserved_share_percentage,
'QoS_support': False,
}
super(HP3ParShareDriver, self)._update_share_stats(stats)

View File

@ -0,0 +1,540 @@
# Copyright 2015 Hewlett Packard Development Company, L.P.
#
# 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.
"""HP 3PAR Mediator for OpenStack Manila.
This 'mediator' de-couples the 3PAR focused client from the OpenStack focused
driver.
"""
from oslo_utils import importutils
from oslo_utils import units
import six
from manila import exception
from manila.i18n import _
from manila.i18n import _LI
from manila.openstack.common import log as logging
hp3parclient = importutils.try_import("hp3parclient")
if hp3parclient:
from hp3parclient import file_client
LOG = logging.getLogger(__name__)
DENY = '-'
ALLOW = '+'
OPEN_STACK_MANILA_FSHARE = 'OpenStack Manila fshare'
class HP3ParMediator(object):
def __init__(self, **kwargs):
self.hp3par_username = kwargs.get('hp3par_username')
self.hp3par_password = kwargs.get('hp3par_password')
self.hp3par_api_url = kwargs.get('hp3par_api_url')
self.hp3par_debug = kwargs.get('hp3par_debug')
self.hp3par_san_ip = kwargs.get('hp3par_san_ip')
self.hp3par_san_login = kwargs.get('hp3par_san_login')
self.hp3par_san_password = kwargs.get('hp3par_san_password')
self.hp3par_san_ssh_port = kwargs.get('hp3par_san_ssh_port')
self.hp3par_san_private_key = kwargs.get('hp3par_san_private_key')
self.ssh_conn_timeout = kwargs.get('ssh_conn_timeout')
self._client = None
def do_setup(self):
if hp3parclient is None:
msg = _('You must install hp3parclient before using the 3PAR '
'driver.')
LOG.exception(msg)
raise exception.HP3ParInvalidClient(message=msg)
try:
self._client = file_client.HP3ParFilePersonaClient(
self.hp3par_api_url)
except Exception as e:
msg = (_('Failed to connect to HP 3PAR File Persona Client: %s') %
six.text_type(e))
LOG.exception(msg)
raise exception.ShareBackendException(message=msg)
try:
ssh_kwargs = {}
if self.hp3par_san_ssh_port:
ssh_kwargs['port'] = self.hp3par_san_ssh_port
if self.ssh_conn_timeout:
ssh_kwargs['conn_timeout'] = self.ssh_conn_timeout
if self.hp3par_san_private_key:
ssh_kwargs['privatekey'] = self.hp3par_san_private_key
self._client.setSSHOptions(
self.hp3par_san_ip,
self.hp3par_san_login,
self.hp3par_san_password,
**ssh_kwargs
)
except Exception as e:
msg = (_('Failed to set SSH options for HP 3PAR File Persona '
'Client: %s') % six.text_type(e))
LOG.exception(msg)
raise exception.ShareBackendException(message=msg)
if self.hp3par_debug:
self._client.ssh.set_debug_flag(True)
def get_capacity(self, 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.exception(msg)
raise exception.ShareBackendException(message=msg)
if result['total'] != 1:
msg = (_('Failed to get capacity for fpg %s.') % fpg)
LOG.exception(msg)
raise exception.ShareBackendException(message=msg)
else:
member = result['members'][0]
total_capacity_gb = int(member['capacityKiB']) / units.Mi
free_capacity_gb = int(member['availCapacityKiB']) / units.Mi
return {
'total_capacity_gb': total_capacity_gb,
'free_capacity_gb': free_capacity_gb
}
@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.exception(message)
raise exception.InvalidInput(message)
return protocol
@staticmethod
def ensure_prefix(id):
if id.startswith('osf-'):
return id
else:
return 'osf-%s' % id
def create_share(self, share_id, share_proto, fpg, vfs,
fstore=None, sharedir=None, readonly=False, size=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 share_id: The share-id with or without osf- prefix.
:param share_proto: The protocol (to map to smb or nfs)
: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.
:return: share path string
"""
protocol = self.ensure_supported_protocol(share_proto)
share_name = self.ensure_prefix(share_id)
if not fstore:
fstore = share_name
try:
result = self._client.createfstore(
vfs, fstore, fpg=fpg,
comment='OpenStack Manila fstore')
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)
if size:
try:
size_str = six.text_type(size)
result = self._client.setfsquota(
vfs, fpg=fpg, fstore=fstore,
scapacity=size_str, hcapacity=size_str)
LOG.debug("setfsquota result=%s", result)
except Exception as e:
msg = (_('Failed to setfsquota on %(fstore)s: %(e)s') %
{'fstore': fstore, 'e': six.text_type(e)})
LOG.exception(msg)
raise exception.ShareBackendException(msg)
try:
if protocol == 'nfs':
if readonly:
options = 'ro,no_root_squash,insecure'
else:
options = 'rw,no_root_squash,insecure'
result = self._client.createfshare(
protocol, vfs, share_name,
fpg=fpg, fstore=fstore, sharedir=sharedir,
clientip='127.0.0.1',
options=options,
comment=OPEN_STACK_MANILA_FSHARE)
else:
result = self._client.createfshare(
protocol, vfs, share_name,
fpg=fpg, fstore=fstore, sharedir=sharedir,
allowip='127.0.0.1',
comment=OPEN_STACK_MANILA_FSHARE)
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)
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)
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.exception(msg)
raise exception.ShareBackendException(msg)
if protocol == 'nfs':
return result['members'][0]['sharePath']
else:
return result['members'][0]['shareName']
def create_share_from_snapshot(self, share_id, share_proto, orig_share_id,
snapshot_id, fpg, vfs):
share_name = self.ensure_prefix(share_id)
orig_share_name = self.ensure_prefix(orig_share_id)
fstore = orig_share_name
snapshot_tag = self.ensure_prefix(snapshot_id)
snapshots = self.get_snapshots(fstore, snapshot_tag, fpg, vfs)
if len(snapshots) != 1:
msg = (_('Failed to create share from snapshot for '
'FPG/VFS/fstore/tag %(fpg)s/%(vfs)s/%(fstore)s/%(tag)s.'
' Expected to find 1 snapshot, found %(count)s.') %
{'fpg': fpg, 'vfs': vfs, 'fstore': fstore,
'tag': snapshot_tag, 'count': len(snapshots)})
LOG.exception(msg)
raise exception.ShareBackendException(msg)
snapshot = snapshots[0]
sharedir = '.snapshot/%s' % snapshot['snapName']
return self.create_share(
share_name,
share_proto,
fpg,
vfs,
fstore=fstore,
sharedir=sharedir,
readonly=True,
)
def delete_share(self, share_id, share_proto, fpg, vfs):
share_name = self.ensure_prefix(share_id)
fstore = self.get_fstore(share_id, share_proto, fpg, vfs)
protocol = self.ensure_supported_protocol(share_proto)
if not fstore:
# Share does not exist.
return
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(message=msg)
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(message=msg)
def get_vfs_name(self, fpg):
return self.get_vfs(fpg)['vfsname']
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)
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(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(message)
return result['members'][0]
def create_snapshot(self, orig_share_id, snapshot_id, fpg, vfs):
"""Creates a snapshot of a share."""
fstore = self.ensure_prefix(orig_share_id)
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)
def get_snapshots(self, orig_share_id, snapshot_tag, fpg, vfs):
fstore = self.ensure_prefix(orig_share_id)
try:
pattern = '*_%s' % snapshot_tag
result = self._client.getfsnap(
pattern, fpg=fpg, vfs=vfs, fstore=fstore, pat=True)
LOG.debug("getfsnap result=%s", result)
except Exception as e:
msg = (_('Failed to get snapshot for FPG/VFS/fstore/tag '
'%(fpg)s/%(vfs)s/%(fstore)s/%(tag)s: %(e)s') %
{'fpg': fpg, 'vfs': vfs, 'fstore': fstore,
'tag': snapshot_tag, 'e': six.text_type(e)})
LOG.exception(msg)
raise exception.ShareBackendException(msg)
if result['total'] == 0:
LOG.info((_LI('Found zero snapshots for FPG/VFS/fstore/tag '
'%(fpg)s/%(vfs)s/%(fstore)s/%(tag)s.') %
{'fpg': fpg, 'vfs': vfs, 'fstore': fstore,
'tag': snapshot_tag}))
return result['members']
def delete_snapshot(self, orig_share_id, snapshot_id, fpg, vfs):
"""Deletes a snapshot of a share."""
fstore = self.ensure_prefix(orig_share_id)
snapshot_tag = self.ensure_prefix(snapshot_id)
snapshots = self.get_snapshots(fstore, snapshot_tag, fpg, vfs)
if not snapshots:
return
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)
for share in shares['members']:
if protocol == 'nfs':
path = share['sharePath'][1:].split('/')
dot_snapshot_index = 3
else:
path = share['shareDir'].split('/')
dot_snapshot_index = 0
snapshot_index = dot_snapshot_index
if len(path) > snapshot_index + 1:
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)
# Tag should be unique enough to only return one, but this method
# doesn't really need to know that. So just loop.
for snapshot in snapshots:
try:
snapname = snapshot['snapName']
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 '
'%(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)
# Try to reclaim the space
try:
self._client.startfsnapclean(fpg, reclaimStrategy='maxspeed')
except Exception as e:
# Remove already happened so only log this.
msg = (_('Unexpected exception calling startfsnapclean for FPG '
'%(fpg)s: %(e)s') % {'fpg': fpg, 'e': six.text_type(e)})
LOG.exception(msg)
@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.exception(msg)
raise exception.InvalidInput(msg)
if protocol == 'nfs' and access_type != 'ip':
msg = (_("Invalid NFS access type. HP 3PAR NFS supports 'ip'. "
"Actual '%s'.") % access_type)
LOG.exception(msg)
raise exception.HP3ParInvalid(msg)
return protocol
def _change_access(self, plus_or_minus, fstore, share_id, share_proto,
access_type, access_to, fpg, vfs):
"""Allow or deny access to a share.
Plus_or_minus character indicates add to allow list (+) or remove from
allow list (-).
"""
share_name = self.ensure_prefix(share_id)
fstore = self.ensure_prefix(fstore)
protocol = self.ensure_supported_protocol(share_proto)
self.validate_access_type(protocol, access_type)
try:
if protocol == 'nfs':
result = self._client.setfshare(
protocol, vfs, share_name, fpg=fpg, fstore=fstore,
clientip='%s%s' % (plus_or_minus, access_to))
elif protocol == 'smb':
if access_type == 'ip':
result = self._client.setfshare(
protocol, vfs, share_name, fpg=fpg, fstore=fstore,
allowip='%s%s' % (plus_or_minus, access_to))
else:
access_str = 'fullcontrol'
perm = '%s%s:%s' % (plus_or_minus, access_to, access_str)
result = self._client.setfshare(protocol, vfs, share_name,
fpg=fpg, fstore=fstore,
allowperm=perm)
else:
msg = (_("Unexpected error: After ensure_supported_protocol "
"only 'nfs' or 'smb' strings are allowed, but found: "
"%s.") % protocol)
raise exception.HP3ParUnexpectedError(msg)
LOG.debug("setfshare result=%s", result)
except Exception as e:
msg = (_('Failed to change (%(change)s) access to FPG/share '
'%(fpg)s/%(share)s to %(type)s %(to)s): %(e)s') %
{'change': plus_or_minus, 'fpg': fpg, 'share': share_name,
'type': access_type, 'to': access_to,
'e': six.text_type(e)})
LOG.exception(msg)
raise exception.ShareBackendException(msg)
def get_fstore(self, share_id, share_proto, fpg, vfs):
protocol = self.ensure_supported_protocol(share_proto)
share_name = self.ensure_prefix(share_id)
try:
shares = self._client.getfshare(protocol,
share_name,
fpg=fpg,
vfs=vfs)
except Exception as e:
msg = (_('Unexpected exception while getting share list: %s') %
six.text_type(e))
raise exception.ShareBackendException(msg)
members = shares['members']
if members:
return members[0].get('fstoreName')
def allow_access(self, share_id, share_proto, access_type, access_to,
fpg, vfs):
"""Grant access to a share."""
fstore = self.get_fstore(share_id, share_proto, fpg, vfs)
self._change_access(ALLOW, fstore, share_id, share_proto,
access_type, access_to, fpg, vfs)
def deny_access(self, share_id, share_proto, access_type, access_to,
fpg, vfs):
"""Deny access to a share."""
fstore = self.get_fstore(share_id, share_proto, fpg, vfs)
if fstore:
self._change_access(DENY, fstore, share_id, share_proto,
access_type, access_to, fpg, vfs)

View File

@ -0,0 +1,59 @@
# Copyright 2015 Hewlett Packard Development Company, L.P.
#
# 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.
CIFS = 'CIFS'
SMB_LOWER = 'smb'
NFS = 'NFS'
NFS_LOWER = 'nfs'
IP = 'ip'
USER = 'user'
USERNAME = 'USERNAME_0'
PASSWORD = 'PASSWORD_0'
SAN_LOGIN = 'testlogin4san'
SAN_PASSWORD = 'testpassword4san'
API_URL = 'https://1.2.3.4:8080/api/v1'
TIMEOUT = 60
PORT = 22
# Constants to use with Mock and expect in results
EXPECTED_IP_10203040 = '10.20.30.40'
EXPECTED_IP_1234 = '1.2.3.4'
EXPECTED_IP_127 = '127.0.0.1'
EXPECTED_SHARE_ID = 'osf-share-id'
EXPECTED_SHARE_NAME = 'share-name'
EXPECTED_SHARE_PATH = '/share/path'
EXPECTED_SIZE_1 = 1
EXPECTED_SIZE_2 = 2
EXPECTED_SNAP_NAME = 'osf-snap-name'
EXPECTED_SNAP_ID = 'osf-snap-id'
EXPECTED_STATS = {'test': 'stats'}
EXPECTED_FPG = 'FPG_1'
EXPECTED_FSTORE = 'osf-test_fstore'
EXPECTED_VFS = 'test_vfs'
EXPECTED_HP_DEBUG = True
NFS_SHARE_INFO = {
'id': EXPECTED_SHARE_ID,
'share_proto': NFS,
}
ACCESS_INFO = {
'access_type': IP,
'access_to': EXPECTED_IP_1234,
}
SNAPSHOT_INFO = {
'name': EXPECTED_SNAP_NAME,
'id': EXPECTED_SNAP_ID,
'share': {'id': EXPECTED_SHARE_ID},
}

View File

@ -0,0 +1,408 @@
# Copyright 2015 Hewlett Packard Development Company, L.P.
#
# 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 sys
import mock
if 'hp3parclient' not in sys.modules:
sys.modules['hp3parclient'] = mock.Mock()
from manila import exception
from manila.share.drivers.hp import hp_3par_driver as hp3pardriver
from manila.share.drivers.hp import hp_3par_mediator as hp3parmediator
from manila import test
from manila.tests.share.drivers.hp import test_hp_3par_constants as constants
class HP3ParDriverTestCase(test.TestCase):
def setUp(self):
super(HP3ParDriverTestCase, self).setUp()
# Create a mock configuration with attributes and a safe_get()
self.conf = mock.Mock()
self.conf.driver_handles_share_servers = False
self.conf.hp3par_debug = constants.EXPECTED_HP_DEBUG
self.conf.hp3par_username = constants.USERNAME
self.conf.hp3par_password = constants.PASSWORD
self.conf.hp3par_api_url = constants.API_URL
self.conf.hp3par_san_login = constants.SAN_LOGIN
self.conf.hp3par_san_password = constants.SAN_PASSWORD
self.conf.hp3par.san_ip = constants.EXPECTED_IP_1234
self.conf.hp3par_fpg = constants.EXPECTED_FPG
self.conf.hp3par_san_ssh_port = constants.PORT
self.conf.ssh_conn_timeout = constants.TIMEOUT
self.conf.hp3par_share_ip_address = constants.EXPECTED_IP_10203040
self.conf.network_config_group = 'test_network_config_group'
def safe_get(attr):
try:
return self.conf.__getattribute__(attr)
except AttributeError:
return None
self.conf.safe_get = safe_get
self.mock_object(hp3parmediator, 'HP3ParMediator')
self.mock_mediator_constructor = hp3parmediator.HP3ParMediator
self.mock_mediator = self.mock_mediator_constructor()
self.driver = hp3pardriver.HP3ParShareDriver(
configuration=self.conf)
def test_driver_setup_success(self):
"""Driver do_setup without any errors."""
self.mock_mediator.get_vfs_name.return_value = constants.EXPECTED_VFS
self.driver.do_setup(None)
self.mock_mediator_constructor.assert_has_calls([
mock.call(hp3par_san_ssh_port=self.conf.hp3par_san_ssh_port,
hp3par_san_password=self.conf.hp3par_san_password,
hp3par_username=self.conf.hp3par_username,
hp3par_san_login=self.conf.hp3par_san_login,
hp3par_debug=self.conf.hp3par_debug,
hp3par_api_url=self.conf.hp3par_api_url,
hp3par_password=self.conf.hp3par_password,
hp3par_san_ip=self.conf.hp3par_san_ip,
ssh_conn_timeout=self.conf.ssh_conn_timeout)])
self.mock_mediator.assert_has_calls([
mock.call.do_setup(),
mock.call.get_vfs_name(self.conf.hp3par_fpg)])
self.assertEqual(constants.EXPECTED_VFS, self.driver.vfs)
def test_driver_with_setup_error(self):
"""Driver do_setup when the mediator setup fails."""
self.mock_mediator.do_setup.side_effect = (
exception.ShareBackendException('fail'))
self.assertRaises(exception.ShareBackendException,
self.driver.do_setup, None)
self.mock_mediator_constructor.assert_has_calls([
mock.call(hp3par_san_ssh_port=self.conf.hp3par_san_ssh_port,
hp3par_san_password=self.conf.hp3par_san_password,
hp3par_username=self.conf.hp3par_username,
hp3par_san_login=self.conf.hp3par_san_login,
hp3par_debug=self.conf.hp3par_debug,
hp3par_api_url=self.conf.hp3par_api_url,
hp3par_password=self.conf.hp3par_password,
hp3par_san_ip=self.conf.hp3par_san_ip,
ssh_conn_timeout=self.conf.ssh_conn_timeout)])
self.mock_mediator.assert_has_calls([mock.call.do_setup()])
def test_driver_with_vfs_error(self):
"""Driver do_setup when the get_vfs_name fails."""
self.mock_mediator.get_vfs_name.side_effect = (
exception.ShareBackendException('fail'))
self.assertRaises(exception.ShareBackendException,
self.driver.do_setup, None)
self.mock_mediator_constructor.assert_has_calls([
mock.call(hp3par_san_ssh_port=self.conf.hp3par_san_ssh_port,
hp3par_san_password=self.conf.hp3par_san_password,
hp3par_username=self.conf.hp3par_username,
hp3par_san_login=self.conf.hp3par_san_login,
hp3par_debug=self.conf.hp3par_debug,
hp3par_api_url=self.conf.hp3par_api_url,
hp3par_password=self.conf.hp3par_password,
hp3par_san_ip=self.conf.hp3par_san_ip,
ssh_conn_timeout=self.conf.ssh_conn_timeout)])
self.mock_mediator.assert_has_calls([
mock.call.do_setup(),
mock.call.get_vfs_name(self.conf.hp3par_fpg)])
def init_driver(self):
"""Simple driver setup for re-use with tests that need one."""
self.driver._hp3par = self.mock_mediator
self.driver.vfs = constants.EXPECTED_VFS
self.driver.fpg = constants.EXPECTED_FPG
self.driver.share_ip_address = self.conf.hp3par_share_ip_address
def do_create_share(self, protocol, expected_share_id, expected_size):
"""Re-usable code for create share."""
context = None
share_server = None
share = {
'id': expected_share_id,
'share_proto': protocol,
'size': expected_size,
}
location = self.driver.create_share(context, share, share_server)
return location
def do_create_share_from_snapshot(self,
protocol,
snapshot_id,
expected_share_id,
expected_size):
"""Re-usable code for create share from snapshot."""
context = None
share_server = None
share = {
'id': expected_share_id,
'share_proto': protocol,
'size': expected_size,
}
location = self.driver.create_share_from_snapshot(context,
share,
snapshot_id,
share_server)
return location
def test_driver_check_for_setup_error(self):
"""check_for_setup_error should not raise any exceptions."""
self.mock_object(hp3pardriver, 'LOG')
self.init_driver()
self.driver.check_for_setup_error()
expected_calls = [mock.call.debug(mock.ANY, mock.ANY),
mock.call.debug(mock.ANY, mock.ANY)]
hp3pardriver.LOG.assert_has_calls(expected_calls)
def test_driver_create_cifs_share(self):
self.init_driver()
expected_location = '\\\\%s\%s' % (constants.EXPECTED_IP_10203040,
constants.EXPECTED_SHARE_NAME)
self.mock_mediator.create_share.return_value = (
constants.EXPECTED_SHARE_NAME)
location = self.do_create_share(constants.CIFS,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SIZE_2)
self.assertEqual(expected_location, location)
expected_calls = [mock.call.create_share(
constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
size=constants.EXPECTED_SIZE_2)]
self.mock_mediator.assert_has_calls(expected_calls)
def test_driver_create_nfs_share(self):
self.init_driver()
expected_location = ':'.join((constants.EXPECTED_IP_10203040,
constants.EXPECTED_SHARE_PATH))
self.mock_mediator.create_share.return_value = (
constants.EXPECTED_SHARE_PATH)
location = self.do_create_share(constants.NFS,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SIZE_1)
self.assertEqual(expected_location, location)
expected_calls = [
mock.call.create_share(constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
size=constants.EXPECTED_SIZE_1)]
self.mock_mediator.assert_has_calls(expected_calls)
def test_driver_create_cifs_share_from_snapshot(self):
self.init_driver()
expected_location = '\\\\%s\%s' % (constants.EXPECTED_IP_10203040,
constants.EXPECTED_SHARE_NAME)
self.mock_mediator.create_share_from_snapshot.return_value = (
constants.EXPECTED_SHARE_NAME)
location = self.do_create_share_from_snapshot(
constants.CIFS,
constants.SNAPSHOT_INFO,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SIZE_2)
self.assertEqual(expected_location, location)
expected_calls = [
mock.call.create_share_from_snapshot(constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SNAP_ID,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)]
self.mock_mediator.assert_has_calls(expected_calls)
def test_driver_create_nfs_share_from_snapshot(self):
self.init_driver()
expected_location = ':'.join((constants.EXPECTED_IP_10203040,
constants.EXPECTED_SHARE_PATH))
self.mock_mediator.create_share_from_snapshot.return_value = (
constants.EXPECTED_SHARE_PATH)
location = self.do_create_share_from_snapshot(
constants.NFS,
constants.SNAPSHOT_INFO,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SIZE_1)
self.assertEqual(expected_location, location)
expected_calls = [
mock.call.create_share_from_snapshot(constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SNAP_ID,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)]
self.mock_mediator.assert_has_calls(expected_calls)
def test_driver_delete_share(self):
self.init_driver()
context = None
share_server = None
share = {
'id': constants.EXPECTED_SHARE_ID,
'share_proto': constants.CIFS,
'size': constants.EXPECTED_SIZE_1,
}
self.driver.delete_share(context, share, share_server)
expected_calls = [
mock.call.delete_share(constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)]
self.mock_mediator.assert_has_calls(expected_calls)
def test_driver_create_snapshot(self):
self.init_driver()
context = None
share_server = None
self.driver.create_snapshot(context,
constants.SNAPSHOT_INFO,
share_server)
expected_calls = [
mock.call.create_snapshot(constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SNAP_ID,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)]
self.mock_mediator.assert_has_calls(expected_calls)
def test_driver_delete_snapshot(self):
self.init_driver()
context = None
share_server = None
self.driver.delete_snapshot(context,
constants.SNAPSHOT_INFO,
share_server)
expected_calls = [
mock.call.delete_snapshot(constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SNAP_ID,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
]
self.mock_mediator.assert_has_calls(expected_calls)
def test_driver_allow_access(self):
self.init_driver()
context = None
self.driver.allow_access(context,
constants.NFS_SHARE_INFO,
constants.ACCESS_INFO)
expected_calls = [
mock.call.allow_access(constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.IP,
constants.EXPECTED_IP_1234,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
]
self.mock_mediator.assert_has_calls(expected_calls)
def test_driver_deny_access(self):
self.init_driver()
context = None
self.driver.deny_access(context,
constants.NFS_SHARE_INFO,
constants.ACCESS_INFO)
expected_calls = [
mock.call.deny_access(constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.IP,
constants.EXPECTED_IP_1234,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
]
self.mock_mediator.assert_has_calls(expected_calls)
def test_driver_get_share_stats_no_refresh(self):
"""Driver does not call mediator when refresh=False."""
self.init_driver()
self.driver._stats = constants.EXPECTED_STATS
result = self.driver.get_share_stats(refresh=False)
self.assertEqual(constants.EXPECTED_STATS, result)
self.assertEqual([], self.mock_mediator.mock_calls)
def test_driver_get_share_stats_with_refresh(self):
"""Driver adds stats from mediator to expected structure."""
self.init_driver()
expected_free = constants.EXPECTED_SIZE_1
expected_capacity = constants.EXPECTED_SIZE_2
self.mock_mediator.get_capacity.return_value = {
'free_capacity_gb': expected_free,
'total_capacity_gb': expected_capacity
}
expected_result = {
'driver_handles_share_servers': False,
'QoS_support': False,
'driver_version': '1.0',
'free_capacity_gb': expected_free,
'reserved_percentage': 0,
'share_backend_name': 'HP_3PAR',
'storage_protocol': 'NFS_CIFS',
'total_capacity_gb': expected_capacity,
'vendor_name': 'HP',
}
result = self.driver.get_share_stats(refresh=True)
self.assertEqual(expected_result, result)
expected_calls = [
mock.call.get_capacity(constants.EXPECTED_FPG)
]
self.mock_mediator.assert_has_calls(expected_calls)

View File

@ -0,0 +1,610 @@
# Copyright 2015 Hewlett Packard Development Company, L.P.
#
# 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 sys
import mock
if 'hp3parclient' not in sys.modules:
sys.modules['hp3parclient'] = mock.Mock()
import six
from manila import exception
from manila.share.drivers.hp import hp_3par_mediator as hp3parmediator
from manila import test
from manila.tests.share.drivers.hp import test_hp_3par_constants as constants
from oslo_utils import units
class HP3ParMediatorTestCase(test.TestCase):
def setUp(self):
super(HP3ParMediatorTestCase, self).setUp()
# This is the fake client to use.
self.mock_client = mock.Mock()
# Take over the hp3parclient module and stub the constructor.
hp3parclient = sys.modules['hp3parclient']
# Need a fake constructor to return the fake client.
# This is also be used for constructor error tests.
self.mock_object(hp3parclient.file_client, 'HP3ParFilePersonaClient')
self.mock_client_constructor = (
hp3parclient.file_client.HP3ParFilePersonaClient
)
self.mock_client = self.mock_client_constructor()
# Set the mediator to use in tests.
self.mediator = hp3parmediator.HP3ParMediator(
hp3par_username=constants.USERNAME,
hp3par_password=constants.PASSWORD,
hp3par_api_url=constants.API_URL,
hp3par_debug=constants.EXPECTED_HP_DEBUG,
hp3par_san_ip=constants.EXPECTED_IP_1234,
hp3par_san_login=constants.SAN_LOGIN,
hp3par_san_password=constants.SAN_PASSWORD,
hp3par_san_ssh_port=constants.PORT,
ssh_conn_timeout=constants.TIMEOUT)
def test_mediator_setup_client_init_error(self):
"""Any client init exceptions should result in a ManilaException."""
self.mock_client_constructor.side_effect = (
Exception('Any exception. E.g., bad version or some other '
'non-Manila Exception.'))
self.assertRaises(exception.ManilaException, self.mediator.do_setup)
def test_mediator_setup_client_ssh_error(self):
# This could be anything the client comes up with, but the
# mediator should turn it into a ManilaException.
non_manila_exception = Exception('non-manila-except')
self.mock_client.setSSHOptions.side_effect = non_manila_exception
self.assertRaises(exception.ManilaException, self.mediator.do_setup)
self.mock_client.assert_has_calls(
[mock.call.setSSHOptions(constants.EXPECTED_IP_1234,
constants.SAN_LOGIN,
constants.SAN_PASSWORD,
port=constants.PORT,
conn_timeout=constants.TIMEOUT)])
def test_mediator_vfs_exception(self):
"""Backend exception during get_vfs_name."""
self.mediator.do_setup()
self.mock_client.getvfs.side_effect = Exception('non-manila-except')
self.assertRaises(exception.ManilaException,
self.mediator.get_vfs_name,
fpg=constants.EXPECTED_FPG)
expected_calls = [
mock.call.getvfs(fpg=constants.EXPECTED_FPG, vfs=None),
]
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_vfs_not_found(self):
"""VFS not found."""
self.mediator.do_setup()
self.mock_client.getvfs.return_value = {'total': 0}
self.assertRaises(exception.ManilaException,
self.mediator.get_vfs_name,
fpg=constants.EXPECTED_FPG)
expected_calls = [
mock.call.getvfs(fpg=constants.EXPECTED_FPG, vfs=None),
]
self.mock_client.assert_has_calls(expected_calls)
def init_mediator(self):
"""Basic mediator setup for re-use with tests that need one."""
self.mock_client.getvfs.return_value = {
'total': 1,
'members': [{'vfsname': constants.EXPECTED_VFS}]
}
self.mock_client.getfshare.return_value = {
'total': 1,
'members': [
{'fstoreName': constants.EXPECTED_FSTORE,
'shareName': constants.EXPECTED_SHARE_ID,
'shareDir': constants.EXPECTED_SHARE_PATH,
'sharePath': constants.EXPECTED_SHARE_PATH}]
}
self.mediator.do_setup()
def test_mediator_setup_success(self):
"""Do a mediator setup without errors."""
self.init_mediator()
self.assertIsNotNone(self.mediator._client)
expected_calls = [
mock.call.setSSHOptions(constants.EXPECTED_IP_1234,
constants.SAN_LOGIN,
constants.SAN_PASSWORD,
port=constants.PORT,
conn_timeout=constants.TIMEOUT),
mock.call.ssh.set_debug_flag(constants.EXPECTED_HP_DEBUG)
]
self.mock_client.assert_has_calls(expected_calls)
def get_expected_calls_for_create_share(self,
expected_fpg,
expected_vfsname,
expected_protocol,
expected_share_id,
expected_size):
size = six.text_type(expected_size)
expected_sharedir = None
if expected_protocol == constants.NFS_LOWER:
expected_calls = [
mock.call.createfstore(expected_vfsname, expected_share_id,
comment='OpenStack Manila fstore',
fpg=expected_fpg),
mock.call.setfsquota(expected_vfsname, fpg=expected_fpg,
hcapacity=size,
scapacity=size,
fstore=expected_share_id),
mock.call.createfshare(expected_protocol, expected_vfsname,
expected_share_id,
comment='OpenStack Manila fshare',
fpg=expected_fpg,
sharedir=expected_sharedir,
clientip='127.0.0.1',
options='rw,no_root_squash,insecure',
fstore=expected_share_id),
mock.call.getfshare(expected_protocol, expected_share_id,
fpg=expected_fpg, vfs=expected_vfsname,
fstore=expected_share_id)]
else:
expected_calls = [
mock.call.createfstore(expected_vfsname, expected_share_id,
comment='OpenStack Manila fstore',
fpg=expected_fpg),
mock.call.setfsquota(expected_vfsname, fpg=expected_fpg,
hcapacity=size,
scapacity=size,
fstore=expected_share_id),
mock.call.createfshare(expected_protocol, expected_vfsname,
expected_share_id,
comment='OpenStack Manila fshare',
fpg=expected_fpg,
sharedir=expected_sharedir,
allowip='127.0.0.1',
fstore=expected_share_id),
mock.call.getfshare(expected_protocol, expected_share_id,
fpg=expected_fpg, vfs=expected_vfsname,
fstore=expected_share_id)]
return expected_calls
def test_mediator_create_cifs_share(self):
self.init_mediator()
self.mock_client.getfshare.return_value = {
'message': None,
'total': 1,
'members': [{'shareName': constants.EXPECTED_SHARE_NAME}]
}
location = self.mediator.create_share(constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
size=constants.EXPECTED_SIZE_2)
self.assertEqual(constants.EXPECTED_SHARE_NAME, location)
expected_calls = self.get_expected_calls_for_create_share(
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
constants.SMB_LOWER,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SIZE_2)
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_create_nfs_share(self):
self.init_mediator()
self.mock_client.getfshare.return_value = {
'message': None,
'total': 1,
'members': [{'sharePath': constants.EXPECTED_SHARE_PATH}]
}
location = self.mediator.create_share(constants.EXPECTED_SHARE_ID,
constants.NFS.lower(),
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
size=constants.EXPECTED_SIZE_1)
self.assertEqual(constants.EXPECTED_SHARE_PATH, location)
expected_calls = self.get_expected_calls_for_create_share(
constants.EXPECTED_FPG, constants.EXPECTED_VFS,
constants.NFS.lower(),
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SIZE_1)
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_create_cifs_share_from_snapshot(self):
self.init_mediator()
self.mock_client.getfsnap.return_value = {
'message': None,
'total': 1,
'members': [{'snapName': constants.EXPECTED_SNAP_ID}]
}
location = self.mediator.create_share_from_snapshot(
constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SNAP_ID,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
self.assertEqual(constants.EXPECTED_SHARE_ID, location)
expected_calls = [
mock.call.getfsnap('*_%s' % constants.EXPECTED_SNAP_ID,
vfs=constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG,
pat=True,
fstore=constants.EXPECTED_SHARE_ID),
mock.call.createfshare(constants.SMB_LOWER,
constants.EXPECTED_VFS,
constants.EXPECTED_SHARE_ID,
comment=mock.ANY,
fpg=constants.EXPECTED_FPG,
sharedir='.snapshot/%s' %
constants.EXPECTED_SNAP_ID,
fstore=constants.EXPECTED_SHARE_ID,
allowip=constants.EXPECTED_IP_127),
mock.call.getfshare(constants.SMB_LOWER,
constants.EXPECTED_SHARE_ID,
fpg=constants.EXPECTED_FPG,
vfs=constants.EXPECTED_VFS,
fstore=constants.EXPECTED_SHARE_ID)]
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_create_nfs_share_from_snapshot(self):
self.init_mediator()
self.mock_client.getfsnap.return_value = {
'message': None,
'total': 1,
'members': [{'snapName': constants.EXPECTED_SNAP_ID}]
}
location = self.mediator.create_share_from_snapshot(
constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SNAP_ID,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
self.assertEqual(constants.EXPECTED_SHARE_PATH, location)
expected_calls = [
mock.call.getfsnap('*_%s' % constants.EXPECTED_SNAP_ID,
vfs=constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG,
pat=True,
fstore=constants.EXPECTED_SHARE_ID),
mock.call.createfshare(constants.NFS_LOWER,
constants.EXPECTED_VFS,
constants.EXPECTED_SHARE_ID,
comment=mock.ANY,
fpg=constants.EXPECTED_FPG,
sharedir='.snapshot/%s' %
constants.EXPECTED_SNAP_ID,
fstore=constants.EXPECTED_SHARE_ID,
clientip=constants.EXPECTED_IP_127,
options='ro,no_root_squash,insecure'),
mock.call.getfshare(constants.NFS_LOWER,
constants.EXPECTED_SHARE_ID,
fpg=constants.EXPECTED_FPG,
vfs=constants.EXPECTED_VFS,
fstore=constants.EXPECTED_SHARE_ID)]
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_delete_share(self):
self.init_mediator()
self.mediator.delete_share(constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
expected_calls = [
mock.call.removefshare(constants.SMB_LOWER,
constants.EXPECTED_VFS,
constants.EXPECTED_SHARE_ID,
fpg=constants.EXPECTED_FPG,
fstore=constants.EXPECTED_FSTORE),
mock.call.removefstore(constants.EXPECTED_VFS,
constants.EXPECTED_FSTORE,
fpg=constants.EXPECTED_FPG)
]
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_create_snapshot(self):
self.init_mediator()
self.mediator.create_snapshot(constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SNAP_NAME,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
expected_calls = [
mock.call.createfsnap(constants.EXPECTED_VFS,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SNAP_NAME,
fpg=constants.EXPECTED_FPG)
]
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_delete_snapshot(self):
self.init_mediator()
expected_name_from_array = 'name-from-array'
self.mock_client.getfsnap.return_value = {
'total': 1,
'members': [{'snapName': expected_name_from_array}],
'message': None
}
self.mediator.delete_snapshot(constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SNAP_NAME,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
expected_calls = [
mock.call.getfsnap('*_%s' % constants.EXPECTED_SNAP_NAME,
vfs=constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG,
pat=True,
fstore=constants.EXPECTED_SHARE_ID),
mock.call.getfshare(constants.NFS_LOWER,
fpg=constants.EXPECTED_FPG,
vfs=constants.EXPECTED_VFS,
fstore=constants.EXPECTED_SHARE_ID),
mock.call.getfshare(constants.SMB_LOWER,
fpg=constants.EXPECTED_FPG,
vfs=constants.EXPECTED_VFS,
fstore=constants.EXPECTED_SHARE_ID),
mock.call.removefsnap(constants.EXPECTED_VFS,
constants.EXPECTED_SHARE_ID,
fpg=constants.EXPECTED_FPG,
snapname=expected_name_from_array),
mock.call.startfsnapclean(constants.EXPECTED_FPG,
reclaimStrategy='maxspeed')
]
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_get_capacity(self):
"""Mediator converts client stats to capacity result."""
expected_capacity = constants.EXPECTED_SIZE_2
expected_free = constants.EXPECTED_SIZE_1
self.init_mediator()
self.mock_client.getfpg.return_value = {
'total': 1,
'members': [
{
'capacityKiB': str(expected_capacity * units.Mi),
'availCapacityKiB': str(expected_free * units.Mi)
}
],
'message': None,
}
expected_result = {
'free_capacity_gb': expected_free,
'total_capacity_gb': expected_capacity,
}
result = self.mediator.get_capacity(constants.EXPECTED_FPG)
self.assertEqual(expected_result, result)
expected_calls = [
mock.call.getfpg(constants.EXPECTED_FPG)
]
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_allow_user_access_cifs(self):
""""Allow user access to cifs share."""
self.init_mediator()
expected_allowperm = '+%s:fullcontrol' % constants.USERNAME
self.mediator.allow_access(constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.USER,
constants.USERNAME,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
expected_calls = [
mock.call.setfshare(constants.SMB_LOWER,
constants.EXPECTED_VFS,
constants.EXPECTED_SHARE_ID,
allowperm=expected_allowperm,
fpg=constants.EXPECTED_FPG,
fstore=constants.EXPECTED_FSTORE)
]
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_deny_user_access_cifs(self):
""""Deny user access to cifs share."""
self.init_mediator()
expected_denyperm = '-%s:fullcontrol' % constants.USERNAME
self.mediator.deny_access(constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.USER,
constants.USERNAME,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
expected_calls = [
mock.call.setfshare(constants.SMB_LOWER,
constants.EXPECTED_VFS,
constants.EXPECTED_SHARE_ID,
allowperm=expected_denyperm,
fpg=constants.EXPECTED_FPG,
fstore=constants.EXPECTED_FSTORE)
]
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_allow_ip_access_cifs(self):
""""Allow ip access to cifs share."""
self.init_mediator()
expected_allowip = '+%s' % constants.EXPECTED_IP_1234
self.mediator.allow_access(constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.IP,
constants.EXPECTED_IP_1234,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
expected_calls = [
mock.call.setfshare(constants.SMB_LOWER,
constants.EXPECTED_VFS,
constants.EXPECTED_SHARE_ID,
allowip=expected_allowip,
fpg=constants.EXPECTED_FPG,
fstore=constants.EXPECTED_FSTORE)
]
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_deny_ip_access_cifs(self):
""""Deny ip access to cifs share."""
self.init_mediator()
expected_denyip = '-%s' % constants.EXPECTED_IP_1234
self.mediator.deny_access(constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.IP,
constants.EXPECTED_IP_1234,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
expected_calls = [
mock.call.setfshare(constants.SMB_LOWER,
constants.EXPECTED_VFS,
constants.EXPECTED_SHARE_ID,
allowip=expected_denyip,
fpg=constants.EXPECTED_FPG,
fstore=constants.EXPECTED_FSTORE)
]
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_allow_ip_access_nfs(self):
""""Allow ip access to nfs share."""
self.init_mediator()
expected_clientip = '+%s' % constants.EXPECTED_IP_1234
self.mediator.allow_access(constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.IP,
constants.EXPECTED_IP_1234,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
expected_calls = [
mock.call.setfshare(constants.NFS.lower(),
constants.EXPECTED_VFS,
constants.EXPECTED_SHARE_ID,
clientip=expected_clientip,
fpg=constants.EXPECTED_FPG,
fstore=constants.EXPECTED_FSTORE)
]
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_deny_ip_access_nfs(self):
""""Deny ip access to nfs share."""
self.init_mediator()
expected_clientip = '-%s' % constants.EXPECTED_IP_1234
self.mediator.deny_access(constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.IP,
constants.EXPECTED_IP_1234,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
expected_calls = [
mock.call.setfshare(constants.NFS.lower(),
constants.EXPECTED_VFS,
constants.EXPECTED_SHARE_ID,
clientip=expected_clientip,
fpg=constants.EXPECTED_FPG,
fstore=constants.EXPECTED_FSTORE)
]
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_allow_user_access_nfs(self):
""""Allow user access to nfs share is not supported."""
self.init_mediator()
self.assertRaises(exception.HP3ParInvalid,
self.mediator.allow_access,
constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.USER,
constants.USERNAME,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
def test_mediator_allow_access_bad_proto(self):
""""Allow user access to unsupported protocol."""
self.init_mediator()
self.assertRaises(exception.InvalidInput,
self.mediator.allow_access,
constants.EXPECTED_SHARE_ID,
'unsupported_other_protocol',
constants.USER,
constants.USERNAME,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
def test_mediator_allow_access_bad_type(self):
""""Allow user access to unsupported access type."""
self.init_mediator()
self.assertRaises(exception.InvalidInput,
self.mediator.allow_access,
constants.EXPECTED_SHARE_ID,
constants.CIFS,
'unsupported_other_type',
constants.USERNAME,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)