Merge "HP 3PAR Driver for Manila"

This commit is contained in:
Jenkins 2015-02-08 03:07:45 +00:00 committed by Gerrit Code Review
commit 9e02468864
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)