Merge "HP 3PAR Driver for Manila"
This commit is contained in:
commit
9e02468864
@ -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.")
|
||||
|
||||
|
@ -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,
|
||||
|
0
manila/share/drivers/hp/__init__.py
Normal file
0
manila/share/drivers/hp/__init__.py
Normal file
279
manila/share/drivers/hp/hp_3par_driver.py
Normal file
279
manila/share/drivers/hp/hp_3par_driver.py
Normal 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)
|
540
manila/share/drivers/hp/hp_3par_mediator.py
Normal file
540
manila/share/drivers/hp/hp_3par_mediator.py
Normal 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)
|
0
manila/tests/share/drivers/hp/__init__.py
Normal file
0
manila/tests/share/drivers/hp/__init__.py
Normal file
59
manila/tests/share/drivers/hp/test_hp_3par_constants.py
Normal file
59
manila/tests/share/drivers/hp/test_hp_3par_constants.py
Normal 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},
|
||||
}
|
408
manila/tests/share/drivers/hp/test_hp_3par_driver.py
Normal file
408
manila/tests/share/drivers/hp/test_hp_3par_driver.py
Normal 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)
|
610
manila/tests/share/drivers/hp/test_hp_3par_mediator.py
Normal file
610
manila/tests/share/drivers/hp/test_hp_3par_mediator.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user