INFINIDAT add Manila driver
Adding basic driver version for the INFINIDAT Infinibox storage array. Change-Id: I8299915a12b51c80a044f41ceb7a49da32745272 Implements: blueprint infinidat-manila-driver
This commit is contained in:
parent
128460ff83
commit
f7f30542ed
@ -65,6 +65,8 @@ Mapping of share drivers and share features support
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
|
||||
| IBM GPFS | K | O | L | \- | K | K | \- | \- | \- |
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
|
||||
| INFINIDAT | Q | \- | Q | \- | Q | Q | \- | Q | Q |
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
|
||||
| LVM | M | \- | M | \- | M | M | \- | O | O |
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
|
||||
| Quobyte | K | \- | M | M | \- | \- | \- | \- | \- |
|
||||
@ -132,6 +134,8 @@ Mapping of share drivers and share access rules support
|
||||
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
|
||||
| IBM GPFS | NFS (K) | \- | \- | \- | \- | NFS (K) | \- | \- | \- | \- |
|
||||
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
|
||||
| INFINIDAT | NFS (Q) | \- | \- | \- | \- | NFS (Q) | \- | \- | \- | \- |
|
||||
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
|
||||
| Oracle ZFSSA | NFS,CIFS(K) | \- | \- | \- | \- | \- | \- | \- | \- | \- |
|
||||
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
|
||||
| CephFS | NFS (P) | \- | \- | \- | CEPHFS (M) | NFS (P) | \- | \- | \- | CEPHFS (N) |
|
||||
@ -191,6 +195,8 @@ Mapping of share drivers and security services support
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| IBM GPFS | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| INFINIDAT | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| Oracle ZFSSA | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| CephFS | \- | \- | \- |
|
||||
@ -244,6 +250,8 @@ More information: :ref:`capabilities_and_extra_specs`
|
||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+
|
||||
| Huawei | M | K | L | L | L | L | M | M | \- | \- | P | \- |
|
||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+
|
||||
| INFINIDAT | \- | Q | \- | \- | Q | Q | \- | Q | Q | Q | Q | \- |
|
||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+
|
||||
| LVM | \- | M | \- | \- | \- | M | \- | K | O | O | P | P |
|
||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+
|
||||
| Quobyte | \- | K | \- | \- | \- | L | \- | M | \- | \- | P | \- |
|
||||
|
@ -25,6 +25,7 @@ Share drivers
|
||||
drivers/hpe-3par-share-driver.rst
|
||||
drivers/huawei-nas-driver.rst
|
||||
drivers/ibm-spectrumscale-driver.rst
|
||||
drivers/infinidat-share-driver.rst
|
||||
drivers/maprfs-native-driver.rst
|
||||
drivers/netapp-cluster-mode-driver.rst
|
||||
drivers/quobyte-driver.rst
|
||||
|
@ -0,0 +1,147 @@
|
||||
================================
|
||||
INFINIDAT InfiniBox Share driver
|
||||
================================
|
||||
|
||||
The INFINIDAT Share driver provides support for managing filesystem shares
|
||||
on the INFINIDAT InfiniBox storage systems.
|
||||
|
||||
This section explains how to configure the INFINIDAT driver.
|
||||
|
||||
Supported operations
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Create and delete filesystem shares.
|
||||
|
||||
- Ensure filesystem shares.
|
||||
|
||||
- Extend a share.
|
||||
|
||||
- Create and delete filesystem snapshots.
|
||||
|
||||
- Create a share from a share snapshot.
|
||||
|
||||
- Revert a share to its snapshot.
|
||||
|
||||
- Mount a snapshot.
|
||||
|
||||
- Set access rights to shares and snapshots.
|
||||
|
||||
Note the following limitations:
|
||||
|
||||
- Only IP access type is supported.
|
||||
|
||||
- Both RW & RO access levels are supported.
|
||||
|
||||
External package installation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The driver requires the ``infinisdk`` package for communicating with
|
||||
InfiniBox systems. Install the package from PyPI using the following command:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install infinisdk
|
||||
|
||||
Setting up the storage array
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Create a storage pool object on the InfiniBox array in advance.
|
||||
The storage pool will contain shares managed by OpenStack.
|
||||
Refer to the InfiniBox manuals for details on pool management.
|
||||
|
||||
Driver configuration
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Edit the ``manila.conf`` file, which is usually located under the following
|
||||
path ``/etc/manila/manila.conf``.
|
||||
|
||||
* Add a section for the INFINIDAT driver back end.
|
||||
|
||||
* Under the ``[DEFAULT]`` section, set the ``enabled_share_backends`` parameter
|
||||
with the name of the new back-end section.
|
||||
|
||||
Configure the driver back-end section with the parameters below.
|
||||
|
||||
* Configure the driver name by setting the following parameter:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
share_driver = manila.share.drivers.infinidat.infinibox.InfiniboxShareDriver
|
||||
|
||||
* Configure the management IP of the InfiniBox array by adding the following
|
||||
parameter:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
infinibox_hostname = InfiniBox management IP
|
||||
|
||||
* Configure user credentials:
|
||||
|
||||
The driver requires an InfiniBox user with administrative privileges.
|
||||
We recommend creating a dedicated OpenStack user account
|
||||
that holds an administrative user role.
|
||||
Refer to the InfiniBox manuals for details on user account management.
|
||||
Configure the user credentials by adding the following parameters:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
infinibox_login = Infinibox management login
|
||||
infinibox_password = Infinibox management password
|
||||
|
||||
* Configure the name of the InfiniBox pool by adding the following parameter:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
infinidat_pool_name = Pool as defined in the InfiniBox
|
||||
|
||||
* Configure the name of the InfiniBox NAS network space by adding the following
|
||||
parameter:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
infinidat_nas_network_space_name = Network space as defined in the InfiniBox
|
||||
|
||||
* The back-end name is an identifier for the back end.
|
||||
We recommend using the same name as the name of the section.
|
||||
Configure the back-end name by adding the following parameter:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
share_backend_name = back-end name
|
||||
|
||||
* Thin provisioning:
|
||||
|
||||
The INFINIDAT driver supports creating thin or thick provisioned filesystems.
|
||||
Configure thin or thick provisioning by adding the following parameter:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
infinidat_thin_provision = true/false
|
||||
|
||||
This parameter defaults to ``true``.
|
||||
|
||||
Configuration example
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[DEFAULT]
|
||||
enabled_share_backends = infinidat-pool-a
|
||||
|
||||
[infinidat-pool-a]
|
||||
share_driver = manila.share.drivers.infinidat.infinibox.InfiniboxShareDriver
|
||||
share_backend_name = infinidat-pool-a
|
||||
driver_handles_share_servers = false
|
||||
infinibox_hostname = 10.1.2.3
|
||||
infinibox_login = openstackuser
|
||||
infinibox_password = openstackpass
|
||||
infinidat_pool_name = pool-a
|
||||
infinidat_nas_network_space_name = nas_space
|
||||
infinidat_thin_provision = true
|
||||
|
||||
Driver options
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Configuration options specific to this driver:
|
||||
|
||||
.. include:: ../../tables/manila-infinidat.inc
|
22
doc/source/configuration/tables/manila-infinidat.inc
Normal file
22
doc/source/configuration/tables/manila-infinidat.inc
Normal file
@ -0,0 +1,22 @@
|
||||
.. _manila-infinidat:
|
||||
|
||||
.. list-table:: Description of INFINIDAT InfiniBox share driver configuration options
|
||||
:header-rows: 1
|
||||
:class: config-ref-table
|
||||
|
||||
* - Configuration option = Default value
|
||||
- Description
|
||||
* - **[DEFAULT]**
|
||||
-
|
||||
* - ``infinibox_hostname`` = ``None``
|
||||
- (String) The name (or IP address) for the INFINIDAT Infinibox storage system.
|
||||
* - ``infinibox_login`` = ``None``
|
||||
- (String) Administrative user account name used to access the INFINIDAT Infinibox storage system.
|
||||
* - ``infinibox_password`` = ``None``
|
||||
- (String) Password for the administrative user account specified in the infinibox_login option.
|
||||
* - ``infinidat_pool_name`` = ``None``
|
||||
- (String) Name of the pool from which volumes are allocated.
|
||||
* - ``infinidat_nas_network_space_name`` = ``None``
|
||||
- (String) Name of the NAS network space on the INFINIDAT InfiniBox.
|
||||
* - ``infinidat_thin_provision`` = ``True``
|
||||
- (Boolean) Use thin provisioning
|
@ -70,6 +70,7 @@ import manila.share.drivers.hitachi.hsp.driver
|
||||
import manila.share.drivers.hpe.hpe_3par_driver
|
||||
import manila.share.drivers.huawei.huawei_nas
|
||||
import manila.share.drivers.ibm.gpfs
|
||||
import manila.share.drivers.infinidat.infinibox
|
||||
import manila.share.drivers.lvm
|
||||
import manila.share.drivers.maprfs.maprfs_native
|
||||
import manila.share.drivers.netapp.options
|
||||
@ -151,6 +152,9 @@ _global_opt_lists = [
|
||||
manila.share.drivers.hpe.hpe_3par_driver.HPE3PAR_OPTS,
|
||||
manila.share.drivers.huawei.huawei_nas.huawei_opts,
|
||||
manila.share.drivers.ibm.gpfs.gpfs_share_opts,
|
||||
manila.share.drivers.infinidat.infinibox.infinidat_auth_opts,
|
||||
manila.share.drivers.infinidat.infinibox.infinidat_connection_opts,
|
||||
manila.share.drivers.infinidat.infinibox.infinidat_general_opts,
|
||||
manila.share.drivers.maprfs.maprfs_native.maprfs_native_share_opts,
|
||||
manila.share.drivers.lvm.share_opts,
|
||||
manila.share.drivers.netapp.options.netapp_proxy_opts,
|
||||
|
0
manila/share/drivers/infinidat/__init__.py
Normal file
0
manila/share/drivers/infinidat/__init__.py
Normal file
466
manila/share/drivers/infinidat/infinibox.py
Normal file
466
manila/share/drivers/infinidat/infinibox.py
Normal file
@ -0,0 +1,466 @@
|
||||
# Copyright 2017 Infinidat Ltd.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""
|
||||
INFINIDAT InfiniBox Share Driver
|
||||
"""
|
||||
|
||||
import functools
|
||||
|
||||
import ipaddress
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import units
|
||||
import six
|
||||
|
||||
from manila.common import constants
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.share import driver
|
||||
from manila.share import utils
|
||||
from manila import version
|
||||
|
||||
try:
|
||||
import capacity
|
||||
import infinisdk
|
||||
except ImportError:
|
||||
capacity = None
|
||||
infinisdk = None
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
infinidat_connection_opts = [
|
||||
cfg.HostAddressOpt('infinibox_hostname',
|
||||
help='The name (or IP address) for the INFINIDAT '
|
||||
'Infinibox storage system.'), ]
|
||||
|
||||
infinidat_auth_opts = [
|
||||
cfg.StrOpt('infinibox_login',
|
||||
help=('Administrative user account name used to access the '
|
||||
'INFINIDAT Infinibox storage system.')),
|
||||
cfg.StrOpt('infinibox_password',
|
||||
help=('Password for the administrative user account '
|
||||
'specified in the infinibox_login option.'),
|
||||
secret=True), ]
|
||||
|
||||
infinidat_general_opts = [
|
||||
cfg.StrOpt('infinidat_pool_name',
|
||||
help='Name of the pool from which volumes are allocated.'),
|
||||
cfg.StrOpt('infinidat_nas_network_space_name',
|
||||
help='Name of the NAS network space on the INFINIDAT '
|
||||
'InfiniBox.'),
|
||||
cfg.BoolOpt('infinidat_thin_provision', help='Use thin provisioning.',
|
||||
default=True)]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(infinidat_connection_opts)
|
||||
CONF.register_opts(infinidat_auth_opts)
|
||||
CONF.register_opts(infinidat_general_opts)
|
||||
|
||||
_MANILA_TO_INFINIDAT_ACCESS_LEVEL = {
|
||||
constants.ACCESS_LEVEL_RW: 'RW',
|
||||
constants.ACCESS_LEVEL_RO: 'RO',
|
||||
}
|
||||
|
||||
|
||||
def infinisdk_to_manila_exceptions(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except infinisdk.core.exceptions.InfiniSDKException as ex:
|
||||
# string formatting of 'ex' includes http code and url
|
||||
msg = _('Caught exception from infinisdk: %s') % ex
|
||||
LOG.exception(msg)
|
||||
raise exception.ShareBackendException(msg=msg)
|
||||
return wrapper
|
||||
|
||||
|
||||
class InfiniboxShareDriver(driver.ShareDriver):
|
||||
|
||||
VERSION = '1.0' # driver version
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(InfiniboxShareDriver, self).__init__(False, *args, **kwargs)
|
||||
self.configuration.append_config_values(infinidat_connection_opts)
|
||||
self.configuration.append_config_values(infinidat_auth_opts)
|
||||
self.configuration.append_config_values(infinidat_general_opts)
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Driver initialization"""
|
||||
if infinisdk is None:
|
||||
msg = _("Missing 'infinisdk' python module, ensure the library"
|
||||
" is installed and available.")
|
||||
raise exception.ManilaException(message=msg)
|
||||
|
||||
infinibox_login = self._safe_get_from_config_or_fail('infinibox_login')
|
||||
infinibox_password = (
|
||||
self._safe_get_from_config_or_fail('infinibox_password'))
|
||||
auth = (infinibox_login, infinibox_password)
|
||||
|
||||
self.management_address = (
|
||||
self._safe_get_from_config_or_fail('infinibox_hostname'))
|
||||
|
||||
self._pool_name = (
|
||||
self._safe_get_from_config_or_fail('infinidat_pool_name'))
|
||||
|
||||
self._network_space_name = (
|
||||
self._safe_get_from_config_or_fail(
|
||||
'infinidat_nas_network_space_name'))
|
||||
|
||||
self._system = infinisdk.InfiniBox(self.management_address, auth=auth)
|
||||
self._system.login()
|
||||
|
||||
backend_name = self.configuration.safe_get('share_backend_name')
|
||||
self._backend_name = backend_name or self.__class__.__name__
|
||||
|
||||
thin_provisioning = self.configuration.infinidat_thin_provision
|
||||
self._provtype = "THIN" if thin_provisioning else "THICK"
|
||||
|
||||
LOG.debug('setup complete')
|
||||
|
||||
def _update_share_stats(self):
|
||||
"""Retrieve stats info from share group."""
|
||||
(free_capacity_bytes, physical_capacity_bytes,
|
||||
provisioned_capacity_gb) = self._get_available_capacity()
|
||||
|
||||
max_over_subscription_ratio = (
|
||||
self.configuration.max_over_subscription_ratio)
|
||||
|
||||
data = dict(
|
||||
share_backend_name=self._backend_name,
|
||||
vendor_name='INFINIDAT',
|
||||
driver_version=self.VERSION,
|
||||
storage_protocol='NFS',
|
||||
total_capacity_gb=float(physical_capacity_bytes) / units.Gi,
|
||||
free_capacity_gb=float(free_capacity_bytes) / units.Gi,
|
||||
reserved_percentage=self.configuration.reserved_share_percentage,
|
||||
thin_provisioning=self.configuration.infinidat_thin_provision,
|
||||
max_over_subscription_ratio=max_over_subscription_ratio,
|
||||
provisioned_capacity_gb=provisioned_capacity_gb,
|
||||
snapshot_support=True,
|
||||
create_share_from_snapshot_support=True,
|
||||
mount_snapshot_support=True,
|
||||
revert_to_snapshot_support=True)
|
||||
|
||||
super(InfiniboxShareDriver, self)._update_share_stats(data)
|
||||
|
||||
def _get_available_capacity(self):
|
||||
pool = self._get_infinidat_pool()
|
||||
free_capacity_bytes = (pool.get_free_physical_capacity() /
|
||||
capacity.byte)
|
||||
physical_capacity_bytes = (pool.get_physical_capacity() /
|
||||
capacity.byte)
|
||||
provisioned_capacity_gb = (
|
||||
(pool.get_virtual_capacity() - pool.get_free_virtual_capacity()) /
|
||||
capacity.GB)
|
||||
return (free_capacity_bytes, physical_capacity_bytes,
|
||||
provisioned_capacity_gb)
|
||||
|
||||
def _safe_get_from_config_or_fail(self, config_parameter):
|
||||
config_value = self.configuration.safe_get(config_parameter)
|
||||
if not config_value: # None or empty string
|
||||
reason = (_("%(config_parameter)s configuration parameter "
|
||||
"must be specified") %
|
||||
{'config_parameter': config_parameter})
|
||||
LOG.error(reason)
|
||||
raise exception.BadConfigurationException(reason=reason)
|
||||
return config_value
|
||||
|
||||
def _verify_share_protocol(self, share):
|
||||
if share['share_proto'] != 'NFS':
|
||||
reason = (_('Unsupported share protocol: %(proto)s.') %
|
||||
{'proto': share['share_proto']})
|
||||
LOG.error(reason)
|
||||
raise exception.InvalidShare(reason=reason)
|
||||
|
||||
def _verify_access_type(self, access):
|
||||
if access['access_type'] != 'ip':
|
||||
reason = _('Only "ip" access type allowed for the NFS protocol.')
|
||||
LOG.error(reason)
|
||||
raise exception.InvalidShareAccess(reason=reason)
|
||||
return True
|
||||
|
||||
def _make_share_name(self, manila_share):
|
||||
return 'openstack-shr-%s' % manila_share['id']
|
||||
|
||||
def _make_snapshot_name(self, manila_snapshot):
|
||||
return 'openstack-snap-%s' % manila_snapshot['id']
|
||||
|
||||
def _set_manila_object_metadata(self, infinidat_object, manila_object):
|
||||
data = dict(system="openstack",
|
||||
openstack_version=version.version_info.release_string(),
|
||||
manila_id=manila_object['id'],
|
||||
manila_name=manila_object['name'])
|
||||
infinidat_object.set_metadata_from_dict(data)
|
||||
|
||||
@infinisdk_to_manila_exceptions
|
||||
def _get_infinidat_pool(self):
|
||||
pool = self._system.pools.safe_get(name=self._pool_name)
|
||||
if pool is None:
|
||||
msg = _('Pool "%s" not found') % self._pool_name
|
||||
LOG.error(msg)
|
||||
raise exception.ShareBackendException(msg=msg)
|
||||
return pool
|
||||
|
||||
@infinisdk_to_manila_exceptions
|
||||
def _get_infinidat_nas_network_space_ips(self):
|
||||
network_space = self._system.network_spaces.safe_get(
|
||||
name=self._network_space_name)
|
||||
if network_space is None:
|
||||
msg = _('Network space "%s" not found') % self._network_space_name
|
||||
LOG.error(msg)
|
||||
raise exception.ShareBackendException(msg=msg)
|
||||
network_space_ips = network_space.get_ips()
|
||||
if not network_space_ips:
|
||||
msg = _('INFINIDAT InfiniBox NAS Network Space "%s" has no IPs '
|
||||
'defined') % self._network_space_name
|
||||
LOG.error(msg)
|
||||
raise exception.ShareBackendException(msg=msg)
|
||||
return [ip_munch.ip_address for ip_munch in network_space_ips]
|
||||
|
||||
@infinisdk_to_manila_exceptions
|
||||
def _get_full_nfs_export_path(self, export_path):
|
||||
network_space_ips = self._get_infinidat_nas_network_space_ips()
|
||||
return '{network_space_ip}:{export_path}'.format(
|
||||
network_space_ip=network_space_ips[0],
|
||||
export_path=export_path)
|
||||
|
||||
@infinisdk_to_manila_exceptions
|
||||
def _get_infinidat_filesystem_by_name(self, name):
|
||||
filesystem = self._system.filesystems.safe_get(name=name)
|
||||
if filesystem is None:
|
||||
msg = (_('Filesystem not found on the Infinibox by its name: %s') %
|
||||
name)
|
||||
LOG.error(msg)
|
||||
raise exception.ShareResourceNotFound(share_id=name)
|
||||
return filesystem
|
||||
|
||||
def _get_infinidat_filesystem(self, manila_share):
|
||||
filesystem_name = self._make_share_name(manila_share)
|
||||
return self._get_infinidat_filesystem_by_name(filesystem_name)
|
||||
|
||||
def _get_infinidat_snapshot_by_name(self, name):
|
||||
snapshot = self._system.filesystems.safe_get(name=name)
|
||||
if snapshot is None:
|
||||
msg = (_('Snapshot not found on the Infinibox by its name: %s') %
|
||||
name)
|
||||
LOG.error(msg)
|
||||
raise exception.ShareSnapshotNotFound(snapshot_id=name)
|
||||
return snapshot
|
||||
|
||||
def _get_infinidat_snapshot(self, manila_snapshot):
|
||||
snapshot_name = self._make_snapshot_name(manila_snapshot)
|
||||
return self._get_infinidat_snapshot_by_name(snapshot_name)
|
||||
|
||||
def _get_infinidat_dataset(self, manila_object, is_snapshot):
|
||||
return (self._get_infinidat_snapshot(manila_object) if is_snapshot
|
||||
else self._get_infinidat_filesystem(manila_object))
|
||||
|
||||
@infinisdk_to_manila_exceptions
|
||||
def _get_export(self, infinidat_filesystem):
|
||||
infinidat_exports = infinidat_filesystem.get_exports()
|
||||
if len(infinidat_exports) == 0:
|
||||
msg = _("Could not find share export")
|
||||
raise exception.ShareBackendException(msg=msg)
|
||||
elif len(infinidat_exports) > 1:
|
||||
msg = _("INFINIDAT filesystem has more than one active export; "
|
||||
"possibly not a Manila share")
|
||||
LOG.error(msg)
|
||||
raise exception.ShareBackendException(msg=msg)
|
||||
return infinidat_exports[0]
|
||||
|
||||
def _get_infinidat_access_level(self, access):
|
||||
"""Translates between Manila access levels to INFINIDAT API ones"""
|
||||
access_level = access['access_level']
|
||||
try:
|
||||
return _MANILA_TO_INFINIDAT_ACCESS_LEVEL[access_level]
|
||||
except KeyError:
|
||||
raise exception.InvalidShareAccessLevel(level=access_level)
|
||||
|
||||
def _get_ip_address_range(self, ip_address):
|
||||
"""Parse single IP address or subnet into a range.
|
||||
|
||||
If the IP address string is in subnet mask format, returns a
|
||||
<start ip>-<end-ip> string. If the IP address contains a single IP
|
||||
address, returns only that IP address.
|
||||
"""
|
||||
|
||||
ip_address = six.text_type(ip_address)
|
||||
|
||||
# try treating the ip_address parameter as a range of IP addresses:
|
||||
ip_network = ipaddress.ip_network(ip_address, strict=False)
|
||||
ip_network_hosts = list(ip_network.hosts())
|
||||
if len(ip_network_hosts) == 0: # /32, single IP address
|
||||
return ip_address.split('/')[0]
|
||||
return "{}-{}".format(ip_network_hosts[0], ip_network_hosts[-1])
|
||||
|
||||
@infinisdk_to_manila_exceptions
|
||||
def _create_filesystem_export(self, infinidat_filesystem):
|
||||
infinidat_export = infinidat_filesystem.add_export(permissions=[])
|
||||
return {
|
||||
'path': self._get_full_nfs_export_path(
|
||||
infinidat_export.get_export_path()),
|
||||
'is_admin_only': False,
|
||||
'metadata': {},
|
||||
}
|
||||
|
||||
@infinisdk_to_manila_exceptions
|
||||
def _delete_share(self, share, is_snapshot):
|
||||
if is_snapshot:
|
||||
dataset_name = self._make_snapshot_name(share)
|
||||
else:
|
||||
dataset_name = self._make_share_name(share)
|
||||
try:
|
||||
infinidat_filesystem = (
|
||||
self._get_infinidat_filesystem_by_name(dataset_name))
|
||||
except exception.ShareResourceNotFound:
|
||||
message = ("share %(share)s not found on Infinibox, skipping "
|
||||
"delete")
|
||||
LOG.warning(message, {"share": share})
|
||||
return # filesystem not found
|
||||
if infinidat_filesystem.has_children():
|
||||
# can't delete a filesystem that has a live snapshot
|
||||
if is_snapshot:
|
||||
raise exception.ShareSnapshotIsBusy(snapshot_name=dataset_name)
|
||||
else:
|
||||
reason = _("share has snapshots and cannot be deleted")
|
||||
raise exception.ShareBusyException(reason=reason)
|
||||
try:
|
||||
infinidat_export = self._get_export(infinidat_filesystem)
|
||||
except exception.ShareBackendException:
|
||||
# it's possible that the export has been deleted
|
||||
pass
|
||||
else:
|
||||
infinidat_export.safe_delete()
|
||||
infinidat_filesystem.safe_delete()
|
||||
|
||||
@infinisdk_to_manila_exceptions
|
||||
def _extend_share(self, infinidat_filesystem, share, new_size):
|
||||
new_size_capacity_units = new_size * capacity.GiB
|
||||
old_size = infinidat_filesystem.get_size()
|
||||
infinidat_filesystem.resize(new_size_capacity_units - old_size)
|
||||
|
||||
@infinisdk_to_manila_exceptions
|
||||
def _update_access(self, manila_object, access_rules, is_snapshot):
|
||||
infinidat_filesystem = self._get_infinidat_dataset(
|
||||
manila_object, is_snapshot=is_snapshot)
|
||||
infinidat_export = self._get_export(infinidat_filesystem)
|
||||
permissions = [
|
||||
{'access': self._get_infinidat_access_level(access_rule),
|
||||
'client': self._get_ip_address_range(access_rule['access_to']),
|
||||
'no_root_squash': True} for access_rule in access_rules if
|
||||
self._verify_access_type(access_rule)]
|
||||
infinidat_export.update_permissions(permissions)
|
||||
|
||||
@infinisdk_to_manila_exceptions
|
||||
def create_share(self, context, share, share_server=None):
|
||||
self._verify_share_protocol(share)
|
||||
|
||||
pool = self._get_infinidat_pool()
|
||||
size = share['size'] * capacity.GiB
|
||||
share_name = self._make_share_name(share)
|
||||
|
||||
infinidat_filesystem = self._system.filesystems.create(
|
||||
pool=pool, name=share_name, size=size, provtype=self._provtype)
|
||||
self._set_manila_object_metadata(infinidat_filesystem, share)
|
||||
return self._create_filesystem_export(infinidat_filesystem)
|
||||
|
||||
@infinisdk_to_manila_exceptions
|
||||
def create_share_from_snapshot(self, context, share, snapshot,
|
||||
share_server=None):
|
||||
name = self._make_share_name(share)
|
||||
infinidat_snapshot = self._get_infinidat_snapshot(snapshot)
|
||||
infinidat_new_share = infinidat_snapshot.create_child(
|
||||
name=name, write_protected=False)
|
||||
self._extend_share(infinidat_new_share, share, share['size'])
|
||||
return self._create_filesystem_export(infinidat_new_share)
|
||||
|
||||
@infinisdk_to_manila_exceptions
|
||||
def create_snapshot(self, context, snapshot, share_server=None):
|
||||
"""Creates a snapshot."""
|
||||
share = snapshot['share']
|
||||
infinidat_filesystem = self._get_infinidat_filesystem(share)
|
||||
name = self._make_snapshot_name(snapshot)
|
||||
infinidat_snapshot = infinidat_filesystem.create_child(name=name)
|
||||
# snapshot is created in the same size as the original share, so no
|
||||
# extending is needed
|
||||
self._set_manila_object_metadata(infinidat_snapshot, snapshot)
|
||||
return {'export_locations':
|
||||
[self._create_filesystem_export(infinidat_snapshot)]}
|
||||
|
||||
def delete_share(self, context, share, share_server=None):
|
||||
try:
|
||||
self._verify_share_protocol(share)
|
||||
except exception.InvalidShare:
|
||||
# cleanup shouldn't fail on wrong protocol or missing share:
|
||||
message = ("failed to delete share %(share)s; unsupported share "
|
||||
"protocol %(share_proto)s, only NFS is supported")
|
||||
LOG.warning(message, {"share": share,
|
||||
"share_proto": share['share_proto']})
|
||||
return
|
||||
self._delete_share(share, is_snapshot=False)
|
||||
|
||||
def delete_snapshot(self, context, snapshot, share_server=None):
|
||||
self._delete_share(snapshot, is_snapshot=True)
|
||||
|
||||
def ensure_share(self, context, share, share_server=None):
|
||||
# will raise ShareResourceNotFound if the share was not found:
|
||||
infinidat_filesystem = self._get_infinidat_filesystem(share)
|
||||
try:
|
||||
infinidat_export = self._get_export(infinidat_filesystem)
|
||||
except exception.ShareBackendException:
|
||||
# export not found, need to re-export
|
||||
message = ("missing export for share %(share)s, trying to "
|
||||
"re-export")
|
||||
LOG.info(message, {"share": share})
|
||||
infinidat_export = (
|
||||
self._create_filesystem_export(infinidat_filesystem))
|
||||
return [self._get_full_nfs_export_path(infinidat_export['path'])]
|
||||
return [self._get_full_nfs_export_path(
|
||||
infinidat_export.get_export_path())]
|
||||
|
||||
def update_access(self, context, share, access_rules, add_rules,
|
||||
delete_rules, share_server=None):
|
||||
# As the Infinibox API can bulk update export access rules, we will try
|
||||
# to use the access_rules list
|
||||
self._verify_share_protocol(share)
|
||||
self._update_access(share, access_rules, is_snapshot=False)
|
||||
|
||||
def get_network_allocations_number(self):
|
||||
return 0
|
||||
|
||||
@infinisdk_to_manila_exceptions
|
||||
def revert_to_snapshot(self, context, snapshot, share_access_rules,
|
||||
snapshot_access_rules, share_server=None):
|
||||
infinidat_snapshot = self._get_infinidat_snapshot(snapshot)
|
||||
infinidat_parent_share = self._get_infinidat_filesystem(
|
||||
snapshot['share'])
|
||||
infinidat_parent_share.restore(infinidat_snapshot)
|
||||
|
||||
def extend_share(self, share, new_size, share_server=None):
|
||||
infinidat_filesystem = self._get_infinidat_filesystem(share)
|
||||
self._extend_share(infinidat_filesystem, share, new_size)
|
||||
|
||||
def snapshot_update_access(self, context, snapshot, access_rules,
|
||||
add_rules, delete_rules, share_server=None):
|
||||
# snapshots are to be mounted in read-only mode, see:
|
||||
# "Add mountable snapshots" on openstack specs.
|
||||
access_rules, _, _ = utils.change_rules_to_readonly(
|
||||
access_rules, [], [])
|
||||
try:
|
||||
self._update_access(snapshot, access_rules, is_snapshot=True)
|
||||
except exception.InvalidShareAccess as e:
|
||||
raise exception.InvalidSnapshotAccess(e)
|
0
manila/tests/share/drivers/infinidat/__init__.py
Normal file
0
manila/tests/share/drivers/infinidat/__init__.py
Normal file
708
manila/tests/share/drivers/infinidat/test_infinidat.py
Normal file
708
manila/tests/share/drivers/infinidat/test_infinidat.py
Normal file
@ -0,0 +1,708 @@
|
||||
# Copyright 2017 Infinidat Ltd.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Unit tests for INFINIDAT InfiniBox share driver."""
|
||||
|
||||
import copy
|
||||
import mock
|
||||
from oslo_utils import units
|
||||
|
||||
from manila.common import constants
|
||||
from manila import exception
|
||||
from manila.share import configuration
|
||||
from manila.share.drivers.infinidat import infinibox
|
||||
from manila import test
|
||||
|
||||
|
||||
_MOCK_SHARE_ID = 1
|
||||
_MOCK_SNAPSHOT_ID = 2
|
||||
_MOCK_CLONE_ID = 3
|
||||
|
||||
_MOCK_SHARE_SIZE = 4
|
||||
|
||||
|
||||
def _create_mock__getitem__(mock):
|
||||
def mock__getitem__(self, key, default=None):
|
||||
return getattr(mock, key, default)
|
||||
return mock__getitem__
|
||||
|
||||
test_share = mock.Mock(id=_MOCK_SHARE_ID, size=_MOCK_SHARE_SIZE,
|
||||
share_proto='NFS')
|
||||
test_share.__getitem__ = _create_mock__getitem__(test_share)
|
||||
|
||||
test_snapshot = mock.Mock(id=_MOCK_SNAPSHOT_ID, size=test_share.size,
|
||||
share=test_share, share_proto='NFS')
|
||||
test_snapshot.__getitem__ = _create_mock__getitem__(test_snapshot)
|
||||
|
||||
original_test_clone = mock.Mock(id=_MOCK_CLONE_ID, size=test_share.size,
|
||||
share=test_snapshot, share_proto='NFS')
|
||||
original_test_clone.__getitem__ = _create_mock__getitem__(original_test_clone)
|
||||
|
||||
|
||||
class FakeInfinisdkException(Exception):
|
||||
def __init__(self, message=None, error_code=None, *args):
|
||||
self.message = message
|
||||
self.error_code = error_code
|
||||
super(FakeInfinisdkException, self).__init__(
|
||||
message, error_code, *args)
|
||||
|
||||
|
||||
class FakeInfinisdkPermission(object):
|
||||
def __init__(self, permission):
|
||||
self._permission = permission
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return self._permission[attr]
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._permission[key]
|
||||
|
||||
|
||||
class InfiniboxDriverTestCaseBase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(InfiniboxDriverTestCaseBase, self).setUp()
|
||||
|
||||
# create mock configuration
|
||||
self.configuration = mock.Mock(spec=configuration.Configuration)
|
||||
self.configuration.infinibox_hostname = 'mockbox'
|
||||
self.configuration.infinidat_pool_name = 'mockpool'
|
||||
self.configuration.infinidat_nas_network_space_name = 'mockspace'
|
||||
self.configuration.infinidat_thin_provision = True
|
||||
self.configuration.infinibox_login = 'user'
|
||||
self.configuration.infinibox_password = 'pass'
|
||||
|
||||
self.configuration.network_config_group = 'test_network_config_group'
|
||||
self.configuration.admin_network_config_group = (
|
||||
'test_admin_network_config_group')
|
||||
self.configuration.reserved_share_percentage = 0
|
||||
self.configuration.filter_function = None
|
||||
self.configuration.goodness_function = None
|
||||
self.configuration.driver_handles_share_servers = False
|
||||
self.configuration.max_over_subscription_ratio = 2
|
||||
self.mock_object(self.configuration, 'safe_get', self._fake_safe_get)
|
||||
|
||||
self.driver = infinibox.InfiniboxShareDriver(
|
||||
configuration=self.configuration)
|
||||
|
||||
# mock external library dependencies
|
||||
infinisdk = self._patch(
|
||||
"manila.share.drivers.infinidat.infinibox.infinisdk")
|
||||
self._capacity_module = (
|
||||
self._patch("manila.share.drivers.infinidat.infinibox.capacity"))
|
||||
self._capacity_module.byte = 1
|
||||
self._capacity_module.GiB = units.Gi
|
||||
|
||||
self._system = self._infinibox_mock()
|
||||
|
||||
infinisdk.core.exceptions.InfiniSDKException = FakeInfinisdkException
|
||||
infinisdk.InfiniBox.return_value = self._system
|
||||
|
||||
self.driver.do_setup(None)
|
||||
|
||||
def _infinibox_mock(self):
|
||||
result = mock.Mock()
|
||||
|
||||
self._mock_export_permissions = []
|
||||
|
||||
self._mock_export = mock.Mock()
|
||||
self._mock_export.get_export_path.return_value = '/mock_export'
|
||||
self._mock_export.get_permissions = self._fake_get_permissions
|
||||
self._mock_export.update_permissions = self._fake_update_permissions
|
||||
|
||||
self._mock_filesystem = mock.Mock()
|
||||
self._mock_filesystem.has_children.return_value = False
|
||||
self._mock_filesystem.create_child.return_value = self._mock_filesystem
|
||||
self._mock_filesystem.get_exports.return_value = [self._mock_export, ]
|
||||
|
||||
self._mock_filesystem.size = 4 * self._capacity_module.GiB
|
||||
self._mock_filesystem.get_size.return_value = (
|
||||
self._mock_filesystem.size)
|
||||
|
||||
self._mock_pool = mock.Mock()
|
||||
self._mock_pool.get_free_physical_capacity.return_value = units.Gi
|
||||
self._mock_pool.get_physical_capacity.return_value = units.Gi
|
||||
self._mock_pool.get_virtual_capacity.return_value = units.Gi
|
||||
self._mock_pool.get_free_virtual_capacity.return_value = units.Gi
|
||||
|
||||
self._mock_network_space = mock.Mock()
|
||||
self._mock_network_space.get_ips.return_value = (
|
||||
[mock.Mock(ip_address='1.2.3.4'), mock.Mock(ip_address='1.2.3.5')])
|
||||
|
||||
result.network_spaces.safe_get.return_value = self._mock_network_space
|
||||
result.pools.safe_get.return_value = self._mock_pool
|
||||
result.filesystems.safe_get.return_value = self._mock_filesystem
|
||||
result.filesystems.create.return_value = self._mock_filesystem
|
||||
result.components.nodes.get_all.return_value = []
|
||||
return result
|
||||
|
||||
def _raise_infinisdk(self, *args, **kwargs):
|
||||
raise FakeInfinisdkException()
|
||||
|
||||
def _fake_safe_get(self, value):
|
||||
return getattr(self.configuration, value, None)
|
||||
|
||||
def _fake_get_permissions(self):
|
||||
return self._mock_export_permissions
|
||||
|
||||
def _fake_update_permissions(self, new_export_permissions):
|
||||
self._mock_export_permissions = [
|
||||
FakeInfinisdkPermission(permission) for permission in
|
||||
new_export_permissions]
|
||||
|
||||
def _patch(self, path, *args, **kwargs):
|
||||
patcher = mock.patch(path, *args, **kwargs)
|
||||
result = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
return result
|
||||
|
||||
|
||||
class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
||||
@mock.patch("manila.share.drivers.infinidat.infinibox.infinisdk", None)
|
||||
def test_no_infinisdk_module(self):
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self.driver.do_setup, None)
|
||||
|
||||
def test_no_auth_parameters(self):
|
||||
self.configuration.infinibox_login = None
|
||||
self.configuration.infinibox_password = None
|
||||
self.assertRaises(exception.BadConfigurationException,
|
||||
self.driver.do_setup, None)
|
||||
|
||||
def test_empty_auth_parameters(self):
|
||||
self.configuration.infinibox_login = ""
|
||||
self.configuration.infinibox_password = ""
|
||||
self.assertRaises(exception.BadConfigurationException,
|
||||
self.driver.do_setup, None)
|
||||
|
||||
def test_get_share_stats_refreshes(self):
|
||||
self.driver._update_share_stats()
|
||||
result = self.driver.get_share_stats()
|
||||
self.assertEqual(1, result["free_capacity_gb"])
|
||||
# change the "free space" in the pool
|
||||
self._mock_pool.get_free_physical_capacity.return_value = 0
|
||||
# no refresh - free capacity should stay the same
|
||||
result = self.driver.get_share_stats(refresh=False)
|
||||
self.assertEqual(1, result["free_capacity_gb"])
|
||||
# refresh - free capacity should change to 0
|
||||
result = self.driver.get_share_stats(refresh=True)
|
||||
self.assertEqual(0, result["free_capacity_gb"])
|
||||
|
||||
def test_get_share_stats_pool_not_found(self):
|
||||
self._system.pools.safe_get.return_value = None
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self.driver._update_share_stats)
|
||||
|
||||
def test__verify_share_protocol(self):
|
||||
# test_share is NFS by default:
|
||||
self.driver._verify_share_protocol(test_share)
|
||||
|
||||
def test__verify_share_protocol_fails_for_non_nfs(self):
|
||||
# set test_share protocol for non-NFS (CIFS, for that matter) and see
|
||||
# that we fail:
|
||||
cifs_share = copy.deepcopy(test_share)
|
||||
cifs_share.share_proto = 'CIFS'
|
||||
# also need to re-define getitem, otherwise we'll get attributes from
|
||||
# test_share:
|
||||
cifs_share.__getitem__ = _create_mock__getitem__(cifs_share)
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver._verify_share_protocol, cifs_share)
|
||||
|
||||
def test__verify_access_type_ip(self):
|
||||
self.assertTrue(self.driver._verify_access_type({'access_type': 'ip'}))
|
||||
|
||||
def test__verify_access_type_fails_for_type_user(self):
|
||||
self.assertRaises(
|
||||
exception.InvalidShareAccess, self.driver._verify_access_type,
|
||||
{'access_type': 'user'})
|
||||
|
||||
def test__verify_access_type_fails_for_type_cert(self):
|
||||
self.assertRaises(
|
||||
exception.InvalidShareAccess, self.driver._verify_access_type,
|
||||
{'access_type': 'cert'})
|
||||
|
||||
def test__get_ip_address_range_single_ip(self):
|
||||
ip_address = self.driver._get_ip_address_range('1.2.3.4')
|
||||
self.assertEqual('1.2.3.4', ip_address)
|
||||
|
||||
def test__get_ip_address_range_ip_range(self):
|
||||
ip_address_range = self.driver._get_ip_address_range('5.6.7.8/28')
|
||||
self.assertEqual('5.6.7.1-5.6.7.14', ip_address_range)
|
||||
|
||||
def test__get_ip_address_range_invalid_address(self):
|
||||
self.assertRaises(ValueError, self.driver._get_ip_address_range,
|
||||
'invalid')
|
||||
|
||||
def test__get_infinidat_pool(self):
|
||||
self.driver._get_infinidat_pool()
|
||||
self._system.pools.safe_get.assert_called_once()
|
||||
|
||||
def test__get_infinidat_pool_no_pools(self):
|
||||
self._system.pools.safe_get.return_value = None
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver._get_infinidat_pool)
|
||||
|
||||
def test__get_infinidat_pool_api_error(self):
|
||||
self._system.pools.safe_get.side_effect = (
|
||||
self._raise_infinisdk)
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver._get_infinidat_pool)
|
||||
|
||||
def test__get_infinidat_nas_network_space_ips(self):
|
||||
self.driver._get_infinidat_nas_network_space_ips()
|
||||
self._system.network_spaces.safe_get.assert_called_once()
|
||||
self._mock_network_space.get_ips.assert_called_once()
|
||||
|
||||
def test__get_infinidat_nas_network_space_ips_no_network_space(self):
|
||||
self._system.network_spaces.safe_get.return_value = None
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver._get_infinidat_nas_network_space_ips)
|
||||
|
||||
def test__get_infinidat_nas_network_space_ips_no_ips(self):
|
||||
self._mock_network_space.get_ips.return_value = []
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver._get_infinidat_nas_network_space_ips)
|
||||
|
||||
def test__get_infinidat_nas_network_space_api_error(self):
|
||||
self._system.network_spaces.safe_get.side_effect = (
|
||||
self._raise_infinisdk)
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver._get_infinidat_nas_network_space_ips)
|
||||
|
||||
def test__get_export(self):
|
||||
# The default return value of get_exports is [mock_export, ]:
|
||||
export = self.driver._get_export(self._mock_filesystem)
|
||||
self._mock_filesystem.get_exports.assert_called_once()
|
||||
self.assertEqual(self._mock_export, export)
|
||||
|
||||
def test__get_export_no_filesystem_exports(self):
|
||||
self._mock_filesystem.get_exports.return_value = []
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver._get_export, self._mock_filesystem)
|
||||
|
||||
def test__get_export_too_many_filesystem_exports(self):
|
||||
self._mock_filesystem.get_exports.return_value = [
|
||||
self._mock_export, self._mock_export]
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver._get_export, self._mock_filesystem)
|
||||
|
||||
def test__get_export_api_error(self):
|
||||
self._mock_filesystem.get_exports.side_effect = self._raise_infinisdk
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver._get_export, self._mock_filesystem)
|
||||
|
||||
def test__get_infinidat_access_level_rw(self):
|
||||
access_level = (
|
||||
self.driver._get_infinidat_access_level(
|
||||
{'access_level': constants.ACCESS_LEVEL_RW}))
|
||||
self.assertEqual('RW', access_level)
|
||||
|
||||
def test__get_infinidat_access_level_ro(self):
|
||||
access_level = (
|
||||
self.driver._get_infinidat_access_level(
|
||||
{'access_level': constants.ACCESS_LEVEL_RO}))
|
||||
self.assertEqual('RO', access_level)
|
||||
|
||||
def test__get_infinidat_access_level_fails_for_invalid_level(self):
|
||||
self.assertRaises(exception.InvalidShareAccessLevel,
|
||||
self.driver._get_infinidat_access_level,
|
||||
{'access_level': 'invalid'})
|
||||
|
||||
def test_create_share(self):
|
||||
# This test uses the default infinidat_thin_provision = True setting:
|
||||
self.driver.create_share(None, test_share)
|
||||
self._system.filesystems.create.assert_called_once()
|
||||
self._mock_filesystem.set_metadata_from_dict.assert_called_once()
|
||||
self._mock_filesystem.add_export.assert_called_once_with(
|
||||
permissions=[])
|
||||
|
||||
def test_create_share_thick_provisioning(self):
|
||||
self.configuration.infinidat_thin_provision = False
|
||||
self.driver.create_share(None, test_share)
|
||||
self._system.filesystems.create.assert_called_once()
|
||||
self._mock_filesystem.set_metadata_from_dict.assert_called_once()
|
||||
self._mock_filesystem.add_export.assert_called_once_with(
|
||||
permissions=[])
|
||||
|
||||
def test_create_share_pool_not_found(self):
|
||||
self._system.pools.safe_get.return_value = None
|
||||
self.assertRaises(exception.ManilaException,
|
||||
self.driver.create_share, None, test_share)
|
||||
|
||||
def test_create_share_fails_non_nfs(self):
|
||||
# set test_share protocol for non-NFS (CIFS, for that matter) and see
|
||||
# that we fail:
|
||||
cifs_share = copy.deepcopy(test_share)
|
||||
cifs_share.share_proto = 'CIFS'
|
||||
# also need to re-define getitem, otherwise we'll get attributes from
|
||||
# test_share:
|
||||
cifs_share.__getitem__ = _create_mock__getitem__(cifs_share)
|
||||
self.assertRaises(exception.InvalidShare,
|
||||
self.driver.create_share, None, cifs_share)
|
||||
|
||||
def test_create_share_pools_api_fail(self):
|
||||
# will fail when trying to get pool for share creation:
|
||||
self._system.pools.safe_get.side_effect = self._raise_infinisdk
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver.create_share, None, test_share)
|
||||
|
||||
def test_create_share_network_spaces_api_fail(self):
|
||||
# will fail when trying to get full export path to the new share:
|
||||
self._system.network_spaces.safe_get.side_effect = (
|
||||
self._raise_infinisdk)
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver.create_share, None, test_share)
|
||||
|
||||
def test_delete_share(self):
|
||||
self.driver.delete_share(None, test_share)
|
||||
self._mock_filesystem.safe_delete.assert_called_once()
|
||||
self._mock_export.safe_delete.assert_called_once()
|
||||
|
||||
def test_delete_share_doesnt_exist(self):
|
||||
self._system.shares.safe_get.return_value = None
|
||||
# should not raise an exception
|
||||
self.driver.delete_share(None, test_share)
|
||||
|
||||
def test_delete_share_export_doesnt_exist(self):
|
||||
self._mock_filesystem.get_exports.return_value = []
|
||||
# should not raise an exception
|
||||
self.driver.delete_share(None, test_share)
|
||||
|
||||
def test_delete_share_with_children(self):
|
||||
self._mock_filesystem.has_children.return_value = True
|
||||
self.assertRaises(exception.ShareBusyException,
|
||||
self.driver.delete_share, None, test_share)
|
||||
|
||||
def test_delete_share_wrong_share_protocol(self):
|
||||
# set test_share protocol for non-NFS (CIFS, for that matter) and see
|
||||
# that delete_share doesn't fail:
|
||||
cifs_share = copy.deepcopy(test_share)
|
||||
cifs_share.share_proto = 'CIFS'
|
||||
# also need to re-define getitem, otherwise we'll get attributes from
|
||||
# test_share:
|
||||
cifs_share.__getitem__ = _create_mock__getitem__(cifs_share)
|
||||
self.driver.delete_share(None, cifs_share)
|
||||
|
||||
def test_extend_share(self):
|
||||
self.driver.extend_share(test_share, _MOCK_SHARE_SIZE * 2)
|
||||
self._mock_filesystem.resize.assert_called_once()
|
||||
|
||||
def test_extend_share_api_fail(self):
|
||||
self._mock_filesystem.resize.side_effect = self._raise_infinisdk
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver.extend_share, test_share, 8)
|
||||
|
||||
def test_create_snapshot(self):
|
||||
self.driver.create_snapshot(None, test_snapshot)
|
||||
self._mock_filesystem.create_child.assert_called_once()
|
||||
self._mock_filesystem.set_metadata_from_dict.assert_called_once()
|
||||
self._mock_filesystem.add_export.assert_called_once_with(
|
||||
permissions=[])
|
||||
|
||||
def test_create_snapshot_metadata(self):
|
||||
self._mock_filesystem.create_child.return_value = (
|
||||
self._mock_filesystem)
|
||||
self.driver.create_snapshot(None, test_snapshot)
|
||||
self._mock_filesystem.set_metadata_from_dict.assert_called_once()
|
||||
|
||||
def test_create_snapshot_share_doesnt_exist(self):
|
||||
self._system.filesystems.safe_get.return_value = None
|
||||
self.assertRaises(exception.ShareResourceNotFound,
|
||||
self.driver.create_snapshot, None, test_snapshot)
|
||||
|
||||
def test_create_snapshot_create_child_api_fail(self):
|
||||
# will fail when trying to create a child to the original share:
|
||||
self._mock_filesystem.create_child.side_effect = (
|
||||
self._raise_infinisdk)
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver.create_snapshot, None, test_snapshot)
|
||||
|
||||
def test_create_snapshot_network_spaces_api_fail(self):
|
||||
# will fail when trying to get full export path to the new snapshot:
|
||||
self._system.network_spaces.safe_get.side_effect = (
|
||||
self._raise_infinisdk)
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver.create_snapshot, None, test_snapshot)
|
||||
|
||||
def test_create_share_from_snapshot(self):
|
||||
self.driver.create_share_from_snapshot(None, original_test_clone,
|
||||
test_snapshot)
|
||||
self._mock_filesystem.create_child.assert_called_once()
|
||||
self._mock_filesystem.add_export.assert_called_once_with(
|
||||
permissions=[])
|
||||
|
||||
def test_create_share_from_snapshot_bigger_size(self):
|
||||
test_clone = copy.copy(original_test_clone)
|
||||
test_clone.size = test_share.size * 2
|
||||
# also need to re-define getitem, otherwise we'll get attributes from
|
||||
# original_get_clone:
|
||||
test_clone.__getitem__ = _create_mock__getitem__(test_clone)
|
||||
|
||||
self.driver.create_share_from_snapshot(None, test_clone, test_snapshot)
|
||||
|
||||
def test_create_share_from_snapshot_doesnt_exist(self):
|
||||
self._system.filesystems.safe_get.return_value = None
|
||||
self.assertRaises(exception.ShareSnapshotNotFound,
|
||||
self.driver.create_share_from_snapshot,
|
||||
None, original_test_clone, test_snapshot)
|
||||
|
||||
def test_create_share_from_snapshot_create_fails(self):
|
||||
self._mock_filesystem.create_child.side_effect = (
|
||||
self._raise_infinisdk)
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver.create_share_from_snapshot,
|
||||
None, original_test_clone, test_snapshot)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
self.driver.delete_snapshot(None, test_snapshot)
|
||||
self._mock_filesystem.safe_delete.assert_called_once()
|
||||
self._mock_export.safe_delete.assert_called_once()
|
||||
|
||||
def test_delete_snapshot_with_children(self):
|
||||
self._mock_filesystem.has_children.return_value = True
|
||||
self.assertRaises(exception.ShareSnapshotIsBusy,
|
||||
self.driver.delete_snapshot, None, test_snapshot)
|
||||
|
||||
def test_delete_snapshot_doesnt_exist(self):
|
||||
self._system.filesystems.safe_get.return_value = None
|
||||
# should not raise an exception
|
||||
self.driver.delete_snapshot(None, test_snapshot)
|
||||
|
||||
def test_delete_snapshot_api_fail(self):
|
||||
self._mock_filesystem.safe_delete.side_effect = self._raise_infinisdk
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver.delete_snapshot, None, test_snapshot)
|
||||
|
||||
def test_ensure_share(self):
|
||||
self.driver.ensure_share(None, test_share)
|
||||
self._mock_filesystem.get_exports.assert_called_once()
|
||||
self._mock_export.get_export_path.assert_called_once()
|
||||
|
||||
def test_ensure_share_export_missing(self):
|
||||
self._mock_filesystem.get_exports.return_value = []
|
||||
self.driver.ensure_share(None, test_share)
|
||||
self._mock_filesystem.get_exports.assert_called_once()
|
||||
self._mock_filesystem.add_export.assert_called_once_with(
|
||||
permissions=[])
|
||||
|
||||
def test_ensure_share_share_doesnt_exist(self):
|
||||
self._system.filesystems.safe_get.return_value = None
|
||||
self.assertRaises(exception.ShareResourceNotFound,
|
||||
self.driver.ensure_share, None, test_share)
|
||||
|
||||
def test_ensure_share_get_exports_api_fail(self):
|
||||
self._mock_filesystem.get_exports.side_effect = self._raise_infinisdk
|
||||
self._mock_filesystem.add_export.side_effect = self._raise_infinisdk
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver.ensure_share, None, test_share)
|
||||
|
||||
def test_ensure_share_network_spaces_api_fail(self):
|
||||
self._system.network_spaces.safe_get.side_effect = (
|
||||
self._raise_infinisdk)
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver.ensure_share, None, test_share)
|
||||
|
||||
def test_get_network_allocations_number(self):
|
||||
# Mostly to increase test coverage. The return value should always be 0
|
||||
# for our driver (see method documentation in base class code):
|
||||
self.assertEqual(0, self.driver.get_network_allocations_number())
|
||||
|
||||
def test_revert_to_snapshot(self):
|
||||
self.driver.revert_to_snapshot(None, test_snapshot, [], [])
|
||||
self._mock_filesystem.restore.assert_called_once()
|
||||
|
||||
def test_revert_to_snapshot_snapshot_doesnt_exist(self):
|
||||
self._system.filesystems.safe_get.return_value = None
|
||||
self.assertRaises(exception.ShareSnapshotNotFound,
|
||||
self.driver.revert_to_snapshot, None, test_snapshot,
|
||||
[], [])
|
||||
|
||||
def test_revert_to_snapshot_api_fail(self):
|
||||
self._mock_filesystem.restore.side_effect = self._raise_infinisdk
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver.revert_to_snapshot, None, test_snapshot,
|
||||
[], [])
|
||||
|
||||
def test_update_access(self):
|
||||
access_rules = [
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '1.2.3.4',
|
||||
'access_type': 'ip'},
|
||||
{'access_level': constants.ACCESS_LEVEL_RW,
|
||||
'access_to': '1.2.3.5',
|
||||
'access_type': 'ip'},
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '5.6.7.8/28',
|
||||
'access_type': 'ip'}]
|
||||
self.driver.update_access(None, test_share, access_rules, [], [])
|
||||
|
||||
permissions = self._mock_filesystem.get_exports()[0].get_permissions()
|
||||
# now we are supposed to have three permissions:
|
||||
# 1. for 1.2.3.4
|
||||
# 2. for 1.2.3.5
|
||||
# 3. for 5.6.7.1-5.6.7.14
|
||||
self.assertEqual(3, len(permissions))
|
||||
|
||||
# sorting according to clients, to avoid mismatch errors:
|
||||
permissions = sorted(permissions,
|
||||
key=lambda permission: permission.client)
|
||||
|
||||
self.assertEqual('RO', permissions[0].access)
|
||||
self.assertEqual('1.2.3.4', permissions[0].client)
|
||||
self.assertTrue(permissions[0].no_root_squash)
|
||||
|
||||
self.assertEqual('RW', permissions[1].access)
|
||||
self.assertEqual('1.2.3.5', permissions[1].client)
|
||||
self.assertTrue(permissions[1].no_root_squash)
|
||||
|
||||
self.assertEqual('RO', permissions[2].access)
|
||||
self.assertEqual('5.6.7.1-5.6.7.14', permissions[2].client)
|
||||
self.assertTrue(permissions[2].no_root_squash)
|
||||
|
||||
def test_update_access_share_doesnt_exist(self):
|
||||
self._system.filesystems.safe_get.return_value = None
|
||||
access_rules = [
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '1.2.3.4',
|
||||
'access_type': 'ip'},
|
||||
{'access_level': constants.ACCESS_LEVEL_RW,
|
||||
'access_to': '1.2.3.5',
|
||||
'access_type': 'ip'},
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '5.6.7.8/28',
|
||||
'access_type': 'ip'}]
|
||||
self.assertRaises(exception.ShareResourceNotFound,
|
||||
self.driver.update_access, None, test_share,
|
||||
access_rules, [], [])
|
||||
|
||||
def test_update_access_api_fail(self):
|
||||
self._mock_filesystem.get_exports.side_effect = self._raise_infinisdk
|
||||
access_rules = [
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '1.2.3.4',
|
||||
'access_type': 'ip'},
|
||||
{'access_level': constants.ACCESS_LEVEL_RW,
|
||||
'access_to': '1.2.3.5',
|
||||
'access_type': 'ip'},
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '5.6.7.8/28',
|
||||
'access_type': 'ip'}]
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver.update_access, None, test_share,
|
||||
access_rules, [], [])
|
||||
|
||||
def test_update_access_fails_non_ip_access_type(self):
|
||||
access_rules = [
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '1.2.3.4',
|
||||
'access_type': 'user'}]
|
||||
self.assertRaises(exception.InvalidShareAccess,
|
||||
self.driver.update_access, None, test_share,
|
||||
access_rules, [], [])
|
||||
|
||||
def test_update_access_fails_invalid_ip(self):
|
||||
access_rules = [
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': 'invalid',
|
||||
'access_type': 'ip'}]
|
||||
self.assertRaises(ValueError,
|
||||
self.driver.update_access, None, test_share,
|
||||
access_rules, [], [])
|
||||
|
||||
def test_snapshot_update_access(self):
|
||||
access_rules = [
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '1.2.3.4',
|
||||
'access_type': 'ip'},
|
||||
{'access_level': constants.ACCESS_LEVEL_RW,
|
||||
'access_to': '1.2.3.5',
|
||||
'access_type': 'ip'},
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '5.6.7.8/28',
|
||||
'access_type': 'ip'}]
|
||||
self.driver.snapshot_update_access(None, test_snapshot, access_rules,
|
||||
[], [])
|
||||
|
||||
permissions = self._mock_filesystem.get_exports()[0].get_permissions()
|
||||
# now we are supposed to have three permissions:
|
||||
# 1. for 1.2.3.4
|
||||
# 2. for 1.2.3.5
|
||||
# 3. for 5.6.7.1-5.6.7.14
|
||||
self.assertEqual(3, len(permissions))
|
||||
|
||||
# sorting according to clients, to avoid mismatch errors:
|
||||
permissions = sorted(permissions,
|
||||
key=lambda permission: permission.client)
|
||||
|
||||
self.assertEqual('RO', permissions[0].access)
|
||||
self.assertEqual('1.2.3.4', permissions[0].client)
|
||||
self.assertTrue(permissions[0].no_root_squash)
|
||||
|
||||
# despite sending a RW rule, all rules are converted to RO:
|
||||
self.assertEqual('RO', permissions[1].access)
|
||||
self.assertEqual('1.2.3.5', permissions[1].client)
|
||||
self.assertTrue(permissions[1].no_root_squash)
|
||||
|
||||
self.assertEqual('RO', permissions[2].access)
|
||||
self.assertEqual('5.6.7.1-5.6.7.14', permissions[2].client)
|
||||
self.assertTrue(permissions[2].no_root_squash)
|
||||
|
||||
def test_snapshot_update_access_snapshot_doesnt_exist(self):
|
||||
self._system.filesystems.safe_get.return_value = None
|
||||
access_rules = [
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '1.2.3.4',
|
||||
'access_type': 'ip'},
|
||||
{'access_level': constants.ACCESS_LEVEL_RW,
|
||||
'access_to': '1.2.3.5',
|
||||
'access_type': 'ip'},
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '5.6.7.8/28',
|
||||
'access_type': 'ip'}]
|
||||
self.assertRaises(exception.ShareSnapshotNotFound,
|
||||
self.driver.snapshot_update_access, None,
|
||||
test_snapshot, access_rules, [], [])
|
||||
|
||||
def test_snapshot_update_access_api_fail(self):
|
||||
self._mock_filesystem.get_exports.side_effect = self._raise_infinisdk
|
||||
access_rules = [
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '1.2.3.4',
|
||||
'access_type': 'ip'},
|
||||
{'access_level': constants.ACCESS_LEVEL_RW,
|
||||
'access_to': '1.2.3.5',
|
||||
'access_type': 'ip'},
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '5.6.7.8/28',
|
||||
'access_type': 'ip'}]
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.driver.snapshot_update_access, None,
|
||||
test_snapshot, access_rules, [], [])
|
||||
|
||||
def test_snapshot_update_access_fails_non_ip_access_type(self):
|
||||
access_rules = [
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': '1.2.3.4',
|
||||
'access_type': 'user'}]
|
||||
self.assertRaises(exception.InvalidSnapshotAccess,
|
||||
self.driver.snapshot_update_access, None, test_share,
|
||||
access_rules, [], [])
|
||||
|
||||
def test_snapshot_update_access_fails_invalid_ip(self):
|
||||
access_rules = [
|
||||
{'access_level': constants.ACCESS_LEVEL_RO,
|
||||
'access_to': 'invalid',
|
||||
'access_type': 'ip'}]
|
||||
self.assertRaises(ValueError,
|
||||
self.driver.snapshot_update_access, None, test_share,
|
||||
access_rules, [], [])
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Added a new driver for the INFINIDAT InfiniBox storage array.
|
Loading…
Reference in New Issue
Block a user