Add Infortrend Manila Driver

This driver supports NFS and CIFS shares.

The following operations are supported:
 - Create a share.
 - Delete a share.
 - Allow share access.
 - Deny share access.
 - Manage a share.
 - Unmanage a share.
 - Extend a share.
 - Shrink a share.

DocImpact
Implements: blueprint infortrend-support-manila-driver

Change-Id: Ib1adbd8f7f55805387b126851dbb0ff50cfbcd75
This commit is contained in:
Kuirong.Chen 2019-05-08 18:43:32 +08:00
parent d3b26f3d93
commit 266972ab91
12 changed files with 2325 additions and 0 deletions

View File

@ -71,6 +71,8 @@ Mapping of share drivers and share features support
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| INSPUR InStorage | T | \- | T | \- | \- | \- | \- | \- | \- | | INSPUR InStorage | T | \- | T | \- | \- | \- | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Infortrend | T | T | T | T | \- | \- | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| LVM | M | \- | M | \- | M | M | \- | O | O | | LVM | M | \- | M | \- | M | M | \- | O | O |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Quobyte | K | \- | M | M | \- | \- | \- | \- | \- | | Quobyte | K | \- | M | M | \- | \- | \- | \- | \- |
@ -144,6 +146,8 @@ Mapping of share drivers and share access rules support
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+ +----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| INSPUR InStorage | NFS (T) | \- | CIFS (T) | \- | \- | NFS (T) | \- | CIFS (T) | \- | \- | | INSPUR InStorage | NFS (T) | \- | CIFS (T) | \- | \- | NFS (T) | \- | CIFS (T) | \- | \- |
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+ +----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| Infortrend | NFS (T) | \- | CIFS (T) | \- | \- | NFS (T) | \- | CIFS (T) | \- | \- |
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| Oracle ZFSSA | NFS,CIFS(K) | \- | \- | \- | \- | \- | \- | \- | \- | \- | | Oracle ZFSSA | NFS,CIFS(K) | \- | \- | \- | \- | \- | \- | \- | \- | \- |
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+ +----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| CephFS | NFS (P) | \- | \- | \- | CEPHFS (M) | NFS (P) | \- | \- | \- | CEPHFS (N) | | CephFS | NFS (P) | \- | \- | \- | CEPHFS (M) | NFS (P) | \- | \- | \- | CEPHFS (N) |
@ -209,6 +213,8 @@ Mapping of share drivers and security services support
+----------------------------------------+------------------+-----------------+------------------+ +----------------------------------------+------------------+-----------------+------------------+
| INSPUR InStorage | \- | \- | \- | | INSPUR InStorage | \- | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+ +----------------------------------------+------------------+-----------------+------------------+
| Infortrend | \- | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+
| Oracle ZFSSA | \- | \- | \- | | Oracle ZFSSA | \- | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+ +----------------------------------------+------------------+-----------------+------------------+
| CephFS | \- | \- | \- | | CephFS | \- | \- | \- |
@ -264,6 +270,8 @@ More information: :ref:`capabilities_and_extra_specs`
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+
| INFINIDAT | \- | Q | \- | \- | Q | Q | \- | Q | Q | Q | Q | \- | | INFINIDAT | \- | Q | \- | \- | Q | Q | \- | Q | Q | Q | Q | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+
| Infortrend | \- | T | \- | \- | \- | \- | \- | \- | \- | \- | T | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+
| LVM | \- | M | \- | \- | \- | M | \- | K | O | O | P | P | | LVM | \- | M | \- | \- | \- | M | \- | K | O | O | P | P |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+
| Quobyte | \- | K | \- | \- | \- | L | \- | M | \- | \- | P | \- | | Quobyte | \- | K | \- | \- | \- | L | \- | M | \- | \- | P | \- |

View File

@ -967,3 +967,13 @@ class LockingFailed(ManilaException):
# Ganesha library # Ganesha library
class GaneshaException(ManilaException): class GaneshaException(ManilaException):
message = _("Unknown NFS-Ganesha library exception.") message = _("Unknown NFS-Ganesha library exception.")
# Infortrend Storage driver
class InfortrendCLIException(ShareBackendException):
message = _("Infortrend CLI exception: %(err)s "
"Return Code: %(rc)s, Output: %(out)s")
class InfortrendNASException(ShareBackendException):
message = _("Infortrend NAS exception: %(err)s")

View File

@ -72,6 +72,7 @@ import manila.share.drivers.hpe.hpe_3par_driver
import manila.share.drivers.huawei.huawei_nas import manila.share.drivers.huawei.huawei_nas
import manila.share.drivers.ibm.gpfs import manila.share.drivers.ibm.gpfs
import manila.share.drivers.infinidat.infinibox import manila.share.drivers.infinidat.infinibox
import manila.share.drivers.infortrend.driver
import manila.share.drivers.inspur.as13000.as13000_nas import manila.share.drivers.inspur.as13000.as13000_nas
import manila.share.drivers.inspur.instorage.instorage import manila.share.drivers.inspur.instorage.instorage
import manila.share.drivers.lvm import manila.share.drivers.lvm
@ -159,6 +160,7 @@ _global_opt_lists = [
manila.share.drivers.infinidat.infinibox.infinidat_auth_opts, manila.share.drivers.infinidat.infinibox.infinidat_auth_opts,
manila.share.drivers.infinidat.infinibox.infinidat_connection_opts, manila.share.drivers.infinidat.infinibox.infinidat_connection_opts,
manila.share.drivers.infinidat.infinibox.infinidat_general_opts, manila.share.drivers.infinidat.infinibox.infinidat_general_opts,
manila.share.drivers.infortrend.driver.infortrend_nas_opts,
manila.share.drivers.inspur.as13000.as13000_nas.inspur_as13000_opts, manila.share.drivers.inspur.as13000.as13000_nas.inspur_as13000_opts,
manila.share.drivers.inspur.instorage.instorage.instorage_opts, manila.share.drivers.inspur.instorage.instorage.instorage_opts,
manila.share.drivers.maprfs.maprfs_native.maprfs_native_share_opts, manila.share.drivers.maprfs.maprfs_native.maprfs_native_share_opts,

View File

@ -0,0 +1,257 @@
# Copyright (c) 2019 Infortrend Technology, Inc.
# 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.
from oslo_config import cfg
from oslo_log import log
from manila import exception
from manila.i18n import _
from manila.share import driver
from manila.share.drivers.infortrend import infortrend_nas
LOG = log.getLogger(__name__)
infortrend_nas_opts = [
cfg.HostAddressOpt('infortrend_nas_ip',
required=True,
help='Infortrend NAS IP for management.'),
cfg.StrOpt('infortrend_nas_user',
default='manila',
help='User for the Infortrend NAS server.'),
cfg.StrOpt('infortrend_nas_password',
default=None,
secret=True,
help='Password for the Infortrend NAS server. '
'This is not necessary '
'if infortrend_nas_ssh_key is set.'),
cfg.StrOpt('infortrend_nas_ssh_key',
default=None,
help='SSH key for the Infortrend NAS server. '
'This is not necessary '
'if infortrend_nas_password is set.'),
cfg.ListOpt('infortrend_share_pools',
required=True,
help='Comma separated list of Infortrend NAS pools.'),
cfg.ListOpt('infortrend_share_channels',
required=True,
help='Comma separated list of Infortrend channels.'),
cfg.IntOpt('infortrend_ssh_timeout',
default=30,
help='SSH timeout in seconds.'),
]
CONF = cfg.CONF
CONF.register_opts(infortrend_nas_opts)
class InfortrendNASDriver(driver.ShareDriver):
"""Infortrend Share Driver for GS/GSe Family using NASCLI.
Version history:
1.0.0 - Initial driver
"""
VERSION = "1.0.0"
PROTOCOL = "NFS_CIFS"
def __init__(self, *args, **kwargs):
super(InfortrendNASDriver, self).__init__(False, *args, **kwargs)
self.configuration.append_config_values(infortrend_nas_opts)
nas_ip = self.configuration.safe_get('infortrend_nas_ip')
username = self.configuration.safe_get('infortrend_nas_user')
password = self.configuration.safe_get('infortrend_nas_password')
ssh_key = self.configuration.safe_get('infortrend_nas_ssh_key')
timeout = self.configuration.safe_get('infortrend_ssh_timeout')
self.backend_name = self.configuration.safe_get('share_backend_name')
if not (password or ssh_key):
msg = _('Either infortrend_nas_password or infortrend_nas_ssh_key '
'should be set.')
raise exception.InvalidParameterValue(err=msg)
pool_dict = self._init_pool_dict()
channel_dict = self._init_channel_dict()
self.ift_nas = infortrend_nas.InfortrendNAS(nas_ip, username, password,
ssh_key, timeout,
pool_dict, channel_dict)
def _init_pool_dict(self):
pools_names = self.configuration.safe_get('infortrend_share_pools')
return {el: {} for el in pools_names}
def _init_channel_dict(self):
channels = self.configuration.safe_get('infortrend_share_channels')
return {el: '' for el in channels}
def do_setup(self, context):
"""Any initialization the share driver does while starting."""
LOG.debug('Infortrend NAS do_setup start.')
self.ift_nas.do_setup()
def check_for_setup_error(self):
"""Check for setup error."""
LOG.debug('Infortrend NAS check_for_setup_error start.')
self.ift_nas.check_for_setup_error()
def _update_share_stats(self):
"""Retrieve stats info from share group."""
LOG.debug('Updating Infortrend backend [%s].', self.backend_name)
data = dict(
share_backend_name=self.backend_name,
vendor_name='Infortrend',
driver_version=self.VERSION,
storage_protocol=self.PROTOCOL,
reserved_percentage=self.configuration.reserved_share_percentage,
pools=self.ift_nas.update_pools_stats())
LOG.debug('Infortrend pools status: %s', data['pools'])
super(InfortrendNASDriver, self)._update_share_stats(data)
def update_access(self, context, share, access_rules, add_rules,
delete_rules, share_server=None):
"""Update access rules for given share.
:param context: Current context
:param share: Share model with share data.
:param access_rules: All access rules for given share
:param add_rules: Empty List or List of access rules which should be
added. access_rules already contains these rules.
:param delete_rules: Empty List or List of access rules which should be
removed. access_rules doesn't contain these rules.
:param share_server: Not used by this driver.
:returns: None, or a dictionary of ``access_id``, ``access_key`` as
key: value pairs for the rules added, where, ``access_id``
is the UUID (string) of the access rule, and ``access_key``
is the credential (string) of the entity granted access.
During recovery after error, the returned dictionary must
contain ``access_id``, ``access_key`` for all the rules that
the driver is ordered to resync, i.e. rules in the
``access_rules`` parameter.
"""
return self.ift_nas.update_access(share, access_rules, add_rules,
delete_rules, share_server)
def create_share(self, context, share, share_server=None):
"""Create a share."""
LOG.debug('Creating share: %s.', share['id'])
return self.ift_nas.create_share(share, share_server)
def delete_share(self, context, share, share_server=None):
"""Remove a share."""
LOG.debug('Deleting share: %s.', share['id'])
return self.ift_nas.delete_share(share, share_server)
def get_pool(self, share):
"""Return pool name where the share resides on.
:param share: The share hosted by the driver.
"""
return self.ift_nas.get_pool(share)
def ensure_share(self, context, share, share_server=None):
"""Invoked to ensure that share is exported.
Driver can use this method to update the list of export locations of
the share if it changes. To do that, you should return list with
export locations.
:return None or list with export locations
"""
return self.ift_nas.ensure_share(share, share_server)
def manage_existing(self, share, driver_options):
"""Brings an existing share under Manila management.
If the provided share is not valid, then raise a
ManageInvalidShare exception, specifying a reason for the failure.
If the provided share is not in a state that can be managed, such as
being replicated on the backend, the driver *MUST* raise
ManageInvalidShare exception with an appropriate message.
The share has a share_type, and the driver can inspect that and
compare against the properties of the referenced backend share.
If they are incompatible, raise a
ManageExistingShareTypeMismatch, specifying a reason for the failure.
:param share: Share model
:param driver_options: Driver-specific options provided by admin.
:return: share_update dictionary with required key 'size',
which should contain size of the share.
"""
LOG.debug(
'Manage existing for share: %(share)s,', {
'share': share['share_id'],
})
return self.ift_nas.manage_existing(share, driver_options)
def unmanage(self, share):
"""Removes the specified share from Manila management.
Does not delete the underlying backend share.
For most drivers, this will not need to do anything. However, some
drivers might use this call as an opportunity to clean up any
Manila-specific configuration that they have associated with the
backend share.
If provided share cannot be unmanaged, then raise an
UnmanageInvalidShare exception, specifying a reason for the failure.
This method is invoked when the share is being unmanaged with
a share type that has ``driver_handles_share_servers``
extra-spec set to False.
"""
LOG.debug(
'Unmanage share: %(share)s', {
'share': share['share_id'],
})
return self.ift_nas.unmanage(share)
def extend_share(self, share, new_size, share_server=None):
"""Extends size of existing share.
:param share: Share model
:param new_size: New size of share (new_size > share['size'])
:param share_server: Optional -- Share server model
"""
return self.ift_nas.extend_share(share, new_size, share_server)
def shrink_share(self, share, new_size, share_server=None):
"""Shrinks size of existing share.
If consumed space on share larger than new_size driver should raise
ShareShrinkingPossibleDataLoss exception:
raise ShareShrinkingPossibleDataLoss(share_id=share['id'])
:param share: Share model
:param new_size: New size of share (new_size < share['size'])
:param share_server: Optional -- Share server model
:raises ShareShrinkingPossibleDataLoss, NotImplementedError
"""
return self.ift_nas.shrink_share(share, new_size, share_server)

View File

@ -0,0 +1,642 @@
# Copyright (c) 2019 Infortrend Technology, Inc.
# 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.
import json
import re
from oslo_concurrency import processutils
from oslo_log import log
from oslo_utils import units
from manila.common import constants
from manila import exception
from manila.i18n import _
from manila.share import utils as share_utils
from manila import utils as manila_utils
LOG = log.getLogger(__name__)
def _bi_to_gi(bi_size):
return bi_size / units.Gi
class InfortrendNAS(object):
_SSH_PORT = 22
def __init__(self, nas_ip, username, password, ssh_key,
timeout, pool_dict, channel_dict):
self.nas_ip = nas_ip
self.port = self._SSH_PORT
self.username = username
self.password = password
self.ssh_key = ssh_key
self.ssh_timeout = timeout
self.pool_dict = pool_dict
self.channel_dict = channel_dict
self.command = ""
self.ssh = None
self.sshpool = None
self.location = 'a@0'
def _execute(self, command_line):
command_line.extend(['-z', self.location])
commands = ' '.join(command_line)
manila_utils.check_ssh_injection(commands)
LOG.debug('Executing: %(command)s', {'command': commands})
cli_out = self._ssh_execute(commands)
return self._parser(cli_out)
def _ssh_execute(self, commands):
try:
out, err = processutils.ssh_execute(
self.ssh, commands,
timeout=self.ssh_timeout, check_exit_code=True)
except processutils.ProcessExecutionError as pe:
rc = pe.exit_code
out = pe.stdout
out = out.replace('\n', '\\n')
msg = _('Error on execute ssh command. '
'Exit code: %(rc)d, msg: %(out)s') % {
'rc': rc, 'out': out}
raise exception.InfortrendNASException(err=msg)
return out
def _parser(self, content=None):
LOG.debug('parsing data:\n%s', content)
content = content.replace("\r", "")
content = content.strip()
json_string = content.replace("'", "\"")
cli_data = json_string.splitlines()[2]
if cli_data:
try:
data_dict = json.loads(cli_data)
except Exception:
msg = _('Failed to parse data: '
'%(cli_data)s to dictionary.') % {
'cli_data': cli_data}
LOG.error(msg)
raise exception.InfortrendNASException(err=msg)
rc = int(data_dict['cliCode'][0]['Return'], 16)
if rc == 0:
result = data_dict['data']
else:
result = data_dict['cliCode'][0]['CLI']
else:
msg = _('No data is returned from NAS.')
LOG.error(msg)
raise exception.InfortrendNASException(err=msg)
if rc != 0:
msg = _('NASCLI error, returned: %(result)s.') % {
'result': result}
LOG.error(msg)
raise exception.InfortrendCLIException(
err=msg, rc=rc, out=result)
return rc, result
def do_setup(self):
self._init_connect()
self._ensure_service_on('nfs')
self._ensure_service_on('cifs')
def _init_connect(self):
if not (self.sshpool and self.ssh):
self.sshpool = manila_utils.SSHPool(ip=self.nas_ip,
port=self.port,
conn_timeout=None,
login=self.username,
password=self.password,
privatekey=self.ssh_key)
self.ssh = self.sshpool.create()
if not self.ssh.get_transport().is_active():
self.sshpool = manila_utils.SSHPool(ip=self.nas_ip,
port=self.port,
conn_timeout=None,
login=self.username,
password=self.password,
privatekey=self.ssh_key)
self.ssh = self.sshpool.create()
LOG.debug('NAScmd [%s@%s] start!', self.username, self.nas_ip)
def check_for_setup_error(self):
self._check_pools_setup()
self._check_channels_status()
def _ensure_service_on(self, proto, slot='A'):
command_line = ['service', 'status', proto]
rc, service_status = self._execute(command_line)
if not service_status[0][slot][proto.upper()]['enabled']:
command_line = ['service', 'restart', proto]
self._execute(command_line)
def _check_channels_status(self):
channel_list = list(self.channel_dict.keys())
command_line = ['ifconfig', 'inet', 'show']
rc, channels_status = self._execute(command_line)
for channel in channels_status:
if 'CH' in channel['datalink']:
ch = channel['datalink'].strip('CH')
if ch in self.channel_dict.keys():
self.channel_dict[ch] = channel['IP']
channel_list.remove(ch)
if channel['status'] == 'DOWN':
LOG.warning('Channel [%(ch)s] status '
'is down, please check.', {
'ch': ch})
if len(channel_list) != 0:
msg = _('Channel setting %(channel_list)s is invalid!') % {
'channel_list': channel_list}
LOG.error(msg)
raise exception.InfortrendNASException(message=msg)
def _check_pools_setup(self):
pool_list = list(self.pool_dict.keys())
command_line = ['folder', 'status']
rc, pool_data = self._execute(command_line)
for pool in pool_data:
pool_name = self._extract_pool_name(pool)
if pool_name in self.pool_dict.keys():
pool_list.remove(pool_name)
self.pool_dict[pool_name]['id'] = pool['volumeId']
self.pool_dict[pool_name]['path'] = pool['directory'] + '/'
if len(pool_list) == 0:
break
if len(pool_list) != 0:
msg = _('Please create %(pool_list)s pool/s in advance!') % {
'pool_list': pool_list}
LOG.error(msg)
raise exception.InfortrendNASException(message=msg)
def _extract_pool_name(self, pool_info):
return pool_info['directory'].split('/')[1]
def _extract_lv_name(self, pool_info):
return pool_info['path'].split('/')[2]
def update_pools_stats(self):
pools = []
command_line = ['folder', 'status']
rc, pools_data = self._execute(command_line)
for pool_info in pools_data:
pool_name = self._extract_pool_name(pool_info)
if pool_name in self.pool_dict.keys():
total_space = float(pool_info['size'])
pool_quota_used = self._get_pool_quota_used(pool_name)
available_space = total_space - pool_quota_used
total_capacity_gb = round(_bi_to_gi(total_space), 2)
free_capacity_gb = round(_bi_to_gi(available_space), 2)
pool = {
'pool_name': pool_name,
'total_capacity_gb': total_capacity_gb,
'free_capacity_gb': free_capacity_gb,
'reserved_percentage': 0,
'qos': False,
'dedupe': False,
'compression': False,
'snapshot_support': False,
'thin_provisioning': False,
'thick_provisioning': True,
'replication_type': None,
}
pools.append(pool)
return pools
def _get_pool_quota_used(self, pool_name):
pool_quota_used = 0.0
pool_data = self._get_share_pool_data(pool_name)
folder_name = self._extract_lv_name(pool_data)
command_line = ['fquota', 'status', pool_data['id'],
folder_name, '-t', 'folder']
rc, quota_status = self._execute(command_line)
for share_quota in quota_status:
pool_quota_used += int(share_quota['quota'])
return pool_quota_used
def _get_share_pool_data(self, pool_name):
if not pool_name:
msg = _("Pool is not available in the share host.")
raise exception.InvalidHost(reason=msg)
if pool_name in self.pool_dict.keys():
return self.pool_dict[pool_name]
else:
msg = _('Pool [%(pool_name)s] not set in conf.') % {
'pool_name': pool_name}
LOG.error(msg)
raise exception.InfortrendNASException(err=msg)
def create_share(self, share, share_server=None):
pool_name = share_utils.extract_host(share['host'], level='pool')
pool_data = self._get_share_pool_data(pool_name)
folder_name = self._extract_lv_name(pool_data)
share_proto = share['share_proto'].lower()
share_name = share['id'].replace('-', '')
share_path = pool_data['path'] + share_name
command_line = ['folder', 'options', pool_data['id'],
folder_name, '-c', share_name]
self._execute(command_line)
self._set_share_size(
pool_data['id'], pool_name, share_name, share['size'])
self._ensure_protocol_on(share_path, share_proto, share_name)
LOG.info('Create Share [%(share)s] completed.', {
'share': share['id']})
return self._export_location(
share_name, share_proto, pool_data['path'])
def _export_location(self, share_name, share_proto, pool_path=None):
location = []
location_data = {
'pool_path': pool_path,
'share_name': share_name,
}
self._check_channels_status()
for ch in sorted(self.channel_dict.keys()):
ip = self.channel_dict[ch]
if share_proto == 'nfs':
location.append(
ip + ':%(pool_path)s%(share_name)s' % location_data)
elif share_proto == 'cifs':
location.append(
'\\\\' + ip + '\\%(share_name)s' % location_data)
else:
msg = _('Unsupported protocol: [%s].') % share_proto
raise exception.InvalidInput(msg)
return location
def _set_share_size(self, pool_id, pool_name, share_name, share_size):
pool_data = self._get_share_pool_data(pool_name)
folder_name = self._extract_lv_name(pool_data)
command_line = ['fquota', 'create', pool_id, folder_name,
share_name, str(share_size) + 'G', '-t', 'folder']
self._execute(command_line)
LOG.debug('Set Share [%(share_name)s] '
'Size [%(share_size)s G] completed.', {
'share_name': share_name,
'share_size': share_size})
return
def _get_share_size(self, pool_id, pool_name, share_name):
share_size = None
command_line = ['fquota', 'status', pool_id,
share_name, '-t', 'folder']
rc, quota_status = self._execute(command_line)
for share_quota in quota_status:
if share_quota['name'] == share_name:
share_size = round(_bi_to_gi(float(share_quota['quota'])), 2)
break
return share_size
def delete_share(self, share, share_server=None):
pool_name = share_utils.extract_host(share['host'], level='pool')
pool_data = self._get_share_pool_data(pool_name)
folder_name = self._extract_lv_name(pool_data)
share_name = share['id'].replace('-', '')
if self._check_share_exist(pool_name, share_name):
command_line = ['folder', 'options', pool_data['id'],
folder_name, '-d', share_name]
self._execute(command_line)
else:
LOG.warning('Share [%(share_name)s] is already deleted.', {
'share_name': share_name})
LOG.info('Delete Share [%(share)s] completed.', {
'share': share['id']})
def _check_share_exist(self, pool_name, share_name):
path = self.pool_dict[pool_name]['path']
command_line = ['pagelist', 'folder', path]
rc, subfolders = self._execute(command_line)
return any(subfolder['name'] == share_name for subfolder in subfolders)
def update_access(self, share, access_rules, add_rules,
delete_rules, share_server=None):
self._evict_unauthorized_clients(share, access_rules, share_server)
access_dict = {}
for access in access_rules:
try:
self._allow_access(share, access, share_server)
except (exception.InfortrendNASException) as e:
msg = _('Failed to allow access to client %(access)s, '
'reason %(e)s.') % {
'access': access['access_to'], 'e': e}
LOG.error(msg)
access_dict[access['id']] = 'error'
return access_dict
def _evict_unauthorized_clients(self, share, access_rules,
share_server=None):
pool_name = share_utils.extract_host(share['host'], level='pool')
pool_data = self._get_share_pool_data(pool_name)
share_proto = share['share_proto'].lower()
share_name = share['id'].replace('-', '')
share_path = pool_data['path'] + share_name
access_list = []
for access in access_rules:
access_list.append(access['access_to'])
if share_proto == 'nfs':
host_ip_list = []
command_line = ['share', 'status', '-f', share_path]
rc, nfs_status = self._execute(command_line)
host_list = nfs_status[0]['nfs_detail']['hostList']
for host in host_list:
if host['host'] != '*':
host_ip_list.append(host['host'])
for ip in host_ip_list:
if ip not in access_list:
command_line = ['share', 'options', share_path,
'nfs', '-c', ip]
try:
self._execute(command_line)
except exception.InfortrendNASException:
msg = _("Failed to remove share access rule %s") % (ip)
LOG.exception(msg)
pass
elif share_proto == 'cifs':
host_user_list = []
command_line = ['acl', 'get', share_path]
rc, cifs_status = self._execute(command_line)
for cifs_rule in cifs_status:
if cifs_rule['name']:
host_user_list.append(cifs_rule['name'])
for user in host_user_list:
if user not in access_list:
command_line = ['acl', 'delete', share_path, '-u', user]
try:
self._execute(command_line)
except exception.InfortrendNASException:
msg = _("Failed to remove share access rule %s") % (
user)
LOG.exception(msg)
pass
def _allow_access(self, share, access, share_server=None):
pool_name = share_utils.extract_host(share['host'], level='pool')
pool_data = self._get_share_pool_data(pool_name)
share_name = share['id'].replace('-', '')
share_path = pool_data['path'] + share_name
share_proto = share['share_proto'].lower()
access_type = access['access_type']
access_level = access['access_level'] or constants.ACCESS_LEVEL_RW
access_to = access['access_to']
ACCESS_LEVEL_MAP = {access_level: access_level}
msg = self._check_access_legal(share_proto, access_type)
if msg:
raise exception.InvalidShareAccess(reason=msg)
if share_proto == 'nfs':
command_line = ['share', 'options', share_path, 'nfs',
'-h', access_to, '-p', access_level]
self._execute(command_line)
elif share_proto == 'cifs':
if not self._check_user_exist(access_to):
msg = _('Please create user [%(user)s] in advance.') % {
'user': access_to}
LOG.error(msg)
raise exception.InfortrendNASException(err=msg)
if access_level == constants.ACCESS_LEVEL_RW:
cifs_access = 'f'
elif access_level == constants.ACCESS_LEVEL_RO:
cifs_access = 'r'
try:
access_level = ACCESS_LEVEL_MAP[access_level]
except KeyError:
msg = _('Unsupported access_level: [%s].') % access_level
raise exception.InvalidInput(msg)
command_line = ['acl', 'set', share_path,
'-u', access_to, '-a', cifs_access]
self._execute(command_line)
LOG.info('Share [%(share)s] access to [%(access_to)s] '
'level [%(level)s] protocol [%(share_proto)s] completed.', {
'share': share['id'],
'access_to': access_to,
'level': access_level,
'share_proto': share_proto})
def _ensure_protocol_on(self, share_path, share_proto, cifs_name):
if not self._check_proto_enabled(share_path, share_proto):
command_line = ['share', share_path, share_proto, 'on']
if share_proto == 'cifs':
command_line.extend(['-n', cifs_name])
self._execute(command_line)
def _check_proto_enabled(self, share_path, share_proto):
command_line = ['share', 'status', '-f', share_path]
rc, share_status = self._execute(command_line)
if share_status:
check_enabled = share_status[0][share_proto]
if check_enabled:
return True
return False
def _check_user_exist(self, user_name):
command_line = ['useradmin', 'user', 'list']
rc, user_list = self._execute(command_line)
for user in user_list:
if user['Name'] == user_name:
return True
return False
def _check_access_legal(self, share_proto, access_type):
msg = None
if share_proto == 'cifs' and access_type != 'user':
msg = _('Infortrend CIFS share only supports USER access type.')
elif share_proto == 'nfs' and access_type != 'ip':
msg = _('Infortrend NFS share only supports IP access type.')
elif share_proto not in ('nfs', 'cifs'):
msg = _('Unsupported share protocol [%s].') % share_proto
return msg
def get_pool(self, share):
pool_name = share_utils.extract_host(share['host'], level='pool')
if not pool_name:
share_name = share['id'].replace('-', '')
for pool in self.pool_dict.keys():
if self._check_share_exist(pool, share_name):
pool_name = pool
break
return pool_name
def ensure_share(self, share, share_server=None):
share_proto = share['share_proto'].lower()
pool_name = share_utils.extract_host(share['host'], level='pool')
pool_data = self._get_share_pool_data(pool_name)
share_name = share['id'].replace('-', '')
return self._export_location(
share_name, share_proto, pool_data['path'])
def extend_share(self, share, new_size, share_server=None):
pool_name = share_utils.extract_host(share['host'], level='pool')
pool_data = self._get_share_pool_data(pool_name)
share_name = share['id'].replace('-', '')
self._set_share_size(pool_data['id'], pool_name, share_name, new_size)
LOG.info('Successfully Extend Share [%(share)s] '
'to size [%(new_size)s G].', {
'share': share['id'],
'new_size': new_size})
def shrink_share(self, share, new_size, share_server=None):
pool_name = share_utils.extract_host(share['host'], level='pool')
pool_data = self._get_share_pool_data(pool_name)
share_name = share['id'].replace('-', '')
folder_name = self._extract_lv_name(pool_data)
command_line = ['fquota', 'status', pool_data['id'],
folder_name, '-t', 'folder']
rc, quota_status = self._execute(command_line)
for share_quota in quota_status:
if share_quota['name'] == share_name:
used_space = round(_bi_to_gi(float(share_quota['used'])), 2)
if new_size < used_space:
raise exception.ShareShrinkingPossibleDataLoss(
share_id=share['id'])
self._set_share_size(pool_data['id'], pool_name, share_name, new_size)
LOG.info('Successfully Shrink Share [%(share)s] '
'to size [%(new_size)s G].', {
'share': share['id'],
'new_size': new_size})
def manage_existing(self, share, driver_options):
share_proto = share['share_proto'].lower()
pool_name = share_utils.extract_host(share['host'], level='pool')
pool_data = self._get_share_pool_data(pool_name)
volume_name = self._extract_lv_name(pool_data)
input_location = share['export_locations'][0]['path']
share_name = share['id'].replace('-', '')
ch_ip, folder_name = self._parse_location(input_location, share_proto)
if not self._check_channel_ip(ch_ip):
msg = _('Export location ip: [%(ch_ip)s] '
'is incorrect, please use data port ip.') % {
'ch_ip': ch_ip}
LOG.error(msg)
raise exception.InfortrendNASException(err=msg)
if not self._check_share_exist(pool_name, folder_name):
msg = _('Can not find folder [%(folder_name)s] '
'in pool [%(pool_name)s].') % {
'folder_name': folder_name,
'pool_name': pool_name}
LOG.error(msg)
raise exception.InfortrendNASException(err=msg)
share_path = pool_data['path'] + folder_name
self._ensure_protocol_on(share_path, share_proto, share_name)
share_size = self._get_share_size(
pool_data['id'], pool_name, folder_name)
if not share_size:
msg = _('Folder [%(folder_name)s] has no size limitation, '
'please set it first for Openstack management.') % {
'folder_name': folder_name}
LOG.error(msg)
raise exception.InfortrendNASException(err=msg)
# rename folder name
command_line = ['folder', 'options', pool_data['id'], volume_name,
'-k', folder_name, share_name]
self._execute(command_line)
location = self._export_location(
share_name, share_proto, pool_data['path'])
LOG.info('Successfully Manage Infortrend Share [%(folder_name)s], '
'Size: [%(size)s G], Protocol: [%(share_proto)s], '
'new name: [%(share_name)s].', {
'folder_name': folder_name,
'size': share_size,
'share_proto': share_proto,
'share_name': share_name})
return {'size': share_size, 'export_locations': location}
def _parse_location(self, input_location, share_proto):
ip = None
folder_name = None
pattern_ip = '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'
if share_proto == 'nfs':
pattern_folder = '[^\/]+$'
ip = "".join(re.findall(pattern_ip, input_location))
folder_name = "".join(re.findall(pattern_folder, input_location))
elif share_proto == 'cifs':
pattern_folder = '[^\\\]+$'
ip = "".join(re.findall(pattern_ip, input_location))
folder_name = "".join(re.findall(pattern_folder, input_location))
if not (ip and folder_name):
msg = _('Export location error, please check '
'ip: [%(ip)s], folder_name: [%(folder_name)s].') % {
'ip': ip,
'folder_name': folder_name}
LOG.error(msg)
raise exception.InfortrendNASException(err=msg)
return ip, folder_name
def _check_channel_ip(self, channel_ip):
return any(ip == channel_ip for ip in self.channel_dict.values())
def unmanage(self, share):
pool_name = share_utils.extract_host(share['host'], level='pool')
share_name = share['id'].replace('-', '')
if not self._check_share_exist(pool_name, share_name):
LOG.warning('Share [%(share_name)s] does not exist.', {
'share_name': share_name})
return
LOG.info('Successfully Unmanaged Share [%(share)s].', {
'share': share['id']})

View File

@ -65,6 +65,10 @@ def set_defaults(conf):
_safe_set_of_opts(conf, 'instorage_nas_password', 'password') _safe_set_of_opts(conf, 'instorage_nas_password', 'password')
_safe_set_of_opts(conf, 'instorage_nas_pools', 'pool0') _safe_set_of_opts(conf, 'instorage_nas_pools', 'pool0')
_safe_set_of_opts(conf, 'infortrend_nas_ip', '172.27.1.1')
_safe_set_of_opts(conf, 'infortrend_share_pools', 'share-pool-01')
_safe_set_of_opts(conf, 'infortrend_share_channels', '0,1')
_safe_set_of_opts(conf, 'qnap_management_url', 'http://1.2.3.4:8080') _safe_set_of_opts(conf, 'qnap_management_url', 'http://1.2.3.4:8080')
_safe_set_of_opts(conf, 'qnap_share_ip', '1.2.3.4') _safe_set_of_opts(conf, 'qnap_share_ip', '1.2.3.4')
_safe_set_of_opts(conf, 'qnap_nas_login', 'admin') _safe_set_of_opts(conf, 'qnap_nas_login', 'admin')

View File

@ -0,0 +1,408 @@
# Copyright (c) 2019 Infortrend Technology, Inc.
# 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.
class InfortrendManilaTestData(object):
fake_share_id = ['4d6984fd-8572-4467-964f-24936a8c4ea2', # NFS
'a7b933e6-bb77-4823-a86f-f2c3ab41a8a5'] # CIFS
fake_id = ['iftt8862-2226-0126-7610-chengweichou',
'987c8763-3333-4444-5555-666666666666']
fake_share_nfs = {
'share_id': fake_share_id[0],
'availability_zone': 'nova',
'terminated_at': 'datetime.datetime(2017, 5, 8, 8, 27, 25)',
'availability_zone_id': 'fd32d76d-b5a8-4c5c-93d7-8f09fc2a8ad3',
'updated_at': 'datetime.datetime(2017, 5, 8, 8, 27, 25)',
'share_network_id': None,
'export_locations': [],
'share_server_id': None,
'snapshot_id': None,
'deleted_at': None,
'id': '5a0aa06e-1c57-4996-be46-b81e360e8866',
'size': 30,
'replica_state': None,
'user_id': '4944594433f0405588928a4212964658',
'export_location': '172.27.112.223:/share-pool-01/LV-1/' +
fake_share_id[0],
'display_description': None,
'consistency_group_id': None,
'project_id': '0e63326c50a246ac81fa1a0c8e003d5b',
'launched_at': 'datetime.datetime(2017, 5, 8, 8, 23, 33)',
'scheduled_at': 'datetime.datetime(2017, 5, 8, 8, 23, 29)',
'status': 'deleting',
'share_type_id': '23d8c637-0192-47fa-b921-958f22ed772f',
'deleted': 'False',
'host': 'compute@ift-manila#share-pool-01',
'access_rules_status': 'active',
'display_name': 'nfs-01',
'name': 'share-5a0aa06e-1c57-4996-be46-b81e360e8866',
'created_at': 'datetime.datetime(2017, 5, 8, 8, 23, 29)',
'share_proto': 'NFS',
'is_public': False,
'source_cgsnapshot_member_id': None
}
fake_share_cifs = {
'share_id': fake_share_id[1],
'availability_zone': 'nova',
'terminated_at': None,
'availability_zone_id': 'fd32d76d-b5a8-4c5c-93d7-8f09fc2a8ad3',
'updated_at': 'datetime.datetime(2017, 5, 9, 2, 28, 35)',
'share_network_id': None,
'export_locations': [],
'share_server_id': None,
'snapshot_id': None,
'deleted_at': None,
'id': 'aac4fe64-7a9c-472a-b156-9adbb50b4d29',
'size': 50,
'replica_state': None,
'user_id': '4944594433f0405588928a4212964658',
'export_location': None,
'display_description': None,
'consistency_group_id': None,
'project_id': '0e63326c50a246ac81fa1a0c8e003d5b',
'launched_at': None,
'scheduled_at': 'datetime.datetime(2017, 5, 9, 2, 28, 35)',
'status': 'creating',
'share_type_id': '23d8c637-0192-47fa-b921-958f22ed772f',
'deleted': 'False',
'host': 'compute@ift-manila#share-pool-01',
'access_rules_status': 'active',
'display_name': 'cifs-01',
'name': 'share-aac4fe64-7a9c-472a-b156-9adbb50b4d29',
'created_at': 'datetime.datetime(2017, 5, 9, 2, 28, 35)',
'share_proto': 'CIFS',
'is_public': False,
'source_cgsnapshot_member_id': None
}
fake_share_cifs_no_host = {
'share_id': fake_share_id[1],
'availability_zone': 'nova',
'terminated_at': None,
'availability_zone_id': 'fd32d76d-b5a8-4c5c-93d7-8f09fc2a8ad3',
'updated_at': 'datetime.datetime(2017, 5, 9, 2, 28, 35)',
'share_network_id': None,
'export_locations': [],
'share_server_id': None,
'snapshot_id': None,
'deleted_at': None,
'id': 'aac4fe64-7a9c-472a-b156-9adbb50b4d29',
'size': 50,
'replica_state': None,
'user_id': '4944594433f0405588928a4212964658',
'export_location': None,
'display_description': None,
'consistency_group_id': None,
'project_id': '0e63326c50a246ac81fa1a0c8e003d5b',
'launched_at': None,
'scheduled_at': 'datetime.datetime(2017, 5, 9, 2, 28, 35)',
'status': 'creating',
'share_type_id': '23d8c637-0192-47fa-b921-958f22ed772f',
'deleted': 'False',
'host': '',
'access_rules_status': 'active',
'display_name': 'cifs-01',
'name': 'share-aac4fe64-7a9c-472a-b156-9adbb50b4d29',
'created_at': 'datetime.datetime(2017, 5, 9, 2, 28, 35)',
'share_proto': 'CIFS',
'is_public': False,
'source_cgsnapshot_member_id': None
}
fake_non_exist_share = {
'share_id': fake_id[0],
'availability_zone': 'nova',
'terminated_at': 'datetime.datetime(2017, 5, 8, 8, 27, 25)',
'availability_zone_id': 'fd32d76d-b5a8-4c5c-93d7-8f09fc2a8ad3',
'updated_at': 'datetime.datetime(2017, 5, 8, 8, 27, 25)',
'share_network_id': None,
'export_locations': [],
'share_server_id': None,
'snapshot_id': None,
'deleted_at': None,
'id': fake_id[1],
'size': 30,
'replica_state': None,
'user_id': '4944594433f0405588928a4212964658',
'export_location': '172.27.112.223:/share-pool-01/LV-1/' +
fake_id[0],
'display_description': None,
'consistency_group_id': None,
'project_id': '0e63326c50a246ac81fa1a0c8e003d5b',
'launched_at': 'datetime.datetime(2017, 5, 8, 8, 23, 33)',
'scheduled_at': 'datetime.datetime(2017, 5, 8, 8, 23, 29)',
'status': 'available',
'share_type_id': '23d8c637-0192-47fa-b921-958f22ed772f',
'deleted': 'False',
'host': 'compute@ift-manila#share-pool-01',
'access_rules_status': 'active',
'display_name': 'nfs-01',
'name': 'share-5a0aa06e-1c57-4996-be46-b81e360e8866',
'created_at': 'datetime.datetime(2017, 5, 8, 8, 23, 29)',
'share_proto': 'NFS',
'is_public': False,
'source_cgsnapshot_member_id': None
}
fake_access_rules_nfs = [{
'share_id': fake_share_id[0],
'deleted': 'False',
'created_at': 'datetime.datetime(2017, 5, 9, 8, 41, 21)',
'updated_at': None,
'access_type': 'ip',
'access_to': '172.27.1.1',
'access_level': 'rw',
'instance_mappings': [],
'deleted_at': None,
'id': 'fa60b50f-1428-44a2-9931-7e31f0c5b033'}, {
'share_id': fake_share_id[0],
'deleted': 'False',
'created_at': 'datetime.datetime(2017, 5, 9, 8, 45, 37)',
'updated_at': None,
'access_type': 'ip',
'access_to': '172.27.1.2',
'access_level': 'rw',
'instance_mappings': [],
'deleted_at': None,
'id': '9bcdd5e6-11c7-4f8f-939c-84fa2f3334bc'
}]
fake_rule_ip_1 = [{
'share_id': fake_share_id[0],
'deleted': 'False',
'created_at': 'datetime.datetime(2017, 5, 9, 8, 41, 21)',
'updated_at': None,
'access_type': 'ip',
'access_to': '172.27.1.1',
'access_level': 'rw',
'instance_mappings': [],
'deleted_at': None,
'id': 'fa60b50f-1428-44a2-9931-7e31f0c5b033'
}]
fake_rule_ip_2 = [{
'share_id': fake_share_id[0],
'deleted': 'False',
'created_at': 'datetime.datetime(2017, 5, 9, 8, 45, 37)',
'updated_at': None,
'access_type': 'ip',
'access_to': '172.27.1.2',
'access_level': 'rw',
'instance_mappings': [],
'deleted_at': None,
'id': '9bcdd5e6-11c7-4f8f-939c-84fa2f3334bc'
}]
fake_access_rules_cifs = [{
'share_id': fake_share_id[1],
'deleted': 'False',
'created_at': 'datetime.datetime(2017, 5, 9, 9, 39, 18)',
'updated_at': None,
'access_type': 'user',
'access_to': 'user02',
'access_level': 'ro',
'instance_mappings': [],
'deleted_at': None,
'id': '6e8bc969-51c9-4bbb-8e8b-020dc5fec81e'}, {
'share_id': fake_share_id[1],
'deleted': 'False',
'created_at': 'datetime.datetime(2017, 5, 9, 9, 38, 59)',
'updated_at': None,
'access_type': 'user',
'access_to': 'user01',
'access_level': 'rw',
'instance_mappings': [],
'deleted_at': None,
'id': '0cd9926d-fac4-4122-a523-538e98752e78'
}]
fake_rule_user01 = [{
'share_id': fake_share_id[1],
'deleted': 'False',
'created_at': 'datetime.datetime(2017, 5, 9, 9, 38, 59)',
'updated_at': None,
'access_type': 'user',
'access_to': 'user01',
'access_level': 'rw',
'instance_mappings': [],
'deleted_at': None,
'id': '0cd9926d-fac4-4122-a523-538e98752e78'
}]
fake_rule_user02 = [{
'share_id': fake_share_id[1],
'deleted': 'False',
'created_at': 'datetime.datetime(2017, 5, 9, 9, 39, 18)',
'updated_at': None,
'access_type': 'user',
'access_to': 'user02',
'access_level': 'ro',
'instance_mappings': [],
'deleted_at': None,
'id': '6e8bc969-51c9-4bbb-8e8b-020dc5fec81e'
}]
fake_rule_user03 = [{
'share_id': fake_id[0],
'deleted': 'False',
'created_at': 'datetime.datetime(2017, 5, 9, 9, 39, 18)',
'updated_at': None,
'access_type': 'user',
'access_to': 'user03',
'access_level': 'rw',
'instance_mappings': [],
'deleted_at': None,
'id': fake_id[1]
}]
fake_share_for_manage_nfs = {
'share_id': '419ab73c-c0fc-4e73-b56a-70756e0b6d27',
'availability_zone': None,
'terminated_at': None,
'availability_zone_id': None,
'updated_at': None,
'share_network_id': None,
'export_locations': [{
'uuid': '0ebd59e4-e65e-4fda-9457-320375efd0be',
'deleted': 0,
'created_at': 'datetime.datetime(2017, 5, 10, 10, 0, 3)',
'updated_at': 'datetime.datetime(2017, 5, 10, 10, 0, 3)',
'is_admin_only': False,
'share_instance_id': 'd3cfe195-85cf-41e6-be4f-a96f7e7db192',
'path': '172.27.112.223:/share-pool-01/LV-1/test-folder',
'el_metadata': {},
'deleted_at': None,
'id': 83
}],
'share_server_id': None,
'snapshot_id': None,
'deleted_at': None,
'id': '615ac1ed-e808-40b5-8d7b-87018c6f66eb',
'size': None,
'replica_state': None,
'user_id': '4944594433f0405588928a4212964658',
'export_location': '172.27.112.223:/share-pool-01/LV-1/test-folder',
'display_description': '',
'consistency_group_id': None,
'project_id': '0e63326c50a246ac81fa1a0c8e003d5b',
'launched_at': None,
'scheduled_at': 'datetime.datetime(2017, 5, 10, 9, 22, 5)',
'status': 'manage_starting',
'share_type_id': '23d8c637-0192-47fa-b921-958f22ed772f',
'deleted': 'False',
'host': 'compute@ift-manila#share-pool-01',
'access_rules_status': 'active',
'display_name': 'test-manage',
'name': 'share-615ac1ed-e808-40b5-8d7b-87018c6f66eb',
'created_at': 'datetime.datetime(2017, 5, 10, 9, 22, 5)',
'share_proto': 'NFS',
'is_public': False,
'source_cgsnapshot_member_id': None
}
def _get_fake_share_for_manage(self, location=''):
return {
'share_id': '419ab73c-c0fc-4e73-b56a-70756e0b6d27',
'availability_zone': None,
'terminated_at': None,
'availability_zone_id': None,
'updated_at': None,
'share_network_id': None,
'export_locations': [{
'uuid': '0ebd59e4-e65e-4fda-9457-320375efd0be',
'deleted': 0,
'created_at': 'datetime.datetime(2017, 5, 10, 10, 0, 3)',
'updated_at': 'datetime.datetime(2017, 5, 10, 10, 0, 3)',
'is_admin_only': False,
'share_instance_id': 'd3cfe195-85cf-41e6-be4f-a96f7e7db192',
'path': location,
'el_metadata': {},
'deleted_at': None,
'id': 83
}],
'share_server_id': None,
'snapshot_id': None,
'deleted_at': None,
'id': '615ac1ed-e808-40b5-8d7b-87018c6f66eb',
'size': None,
'replica_state': None,
'user_id': '4944594433f0405588928a4212964658',
'export_location': location,
'display_description': '',
'consistency_group_id': None,
'project_id': '0e63326c50a246ac81fa1a0c8e003d5b',
'launched_at': None,
'scheduled_at': 'datetime.datetime(2017, 5, 10, 9, 22, 5)',
'status': 'manage_starting',
'share_type_id': '23d8c637-0192-47fa-b921-958f22ed772f',
'deleted': 'False',
'host': 'compute@ift-manila#share-pool-01',
'access_rules_status': 'active',
'display_name': 'test-manage',
'name': 'share-615ac1ed-e808-40b5-8d7b-87018c6f66eb',
'created_at': 'datetime.datetime(2017, 5, 10, 9, 22, 5)',
'share_proto': 'NFS',
'is_public': False,
'source_cgsnapshot_member_id': None
}
fake_share_for_manage_cifs = {
'share_id': '3a1222d3-c981-490a-9390-4d560ced68eb',
'availability_zone': None,
'terminated_at': None,
'availability_zone_id': None,
'updated_at': None,
'share_network_id': None,
'export_locations': [{
'uuid': '0ebd59e4-e65e-4fda-9457-320375efd0de',
'deleted': 0,
'created_at': 'datetime.datetime(2017, 5, 11, 10, 10, 3)',
'updated_at': 'datetime.datetime(2017, 5, 11, 10, 10, 3)',
'is_admin_only': False,
'share_instance_id': 'd3cfe195-85cf-41e6-be4f-a96f7e7db192',
'path': '\\\\172.27.113.209\\test-folder-02',
'el_metadata': {},
'deleted_at': None,
'id': 87
}],
'share_server_id': None,
'snapshot_id': None,
'deleted_at': None,
'id': 'd156baf7-5422-4c9b-8c78-ee7943d000ec',
'size': None,
'replica_state': None,
'user_id': '4944594433f0405588928a4212964658',
'export_location': '\\\\172.27.113.209\\test-folder-02',
'display_description': '',
'consistency_group_id': None,
'project_id': '0e63326c50a246ac81fa1a0c8e003d5b',
'launched_at': None,
'scheduled_at': 'datetime.datetime(2017, 5, 11, 3, 7, 59)',
'status': 'manage_starting',
'share_type_id': '23d8c637-0192-47fa-b921-958f22ed772f',
'deleted': 'False',
'host': 'compute@ift-manila#share-pool-01',
'access_rules_status': 'active',
'display_name': 'test-manage-02',
'name': 'share-d156baf7-5422-4c9b-8c78-ee7943d000ec',
'created_at': 'datetime.datetime(2017, 5, 11, 3, 7, 59)',
'share_proto': 'CIFS',
'is_public': False,
'source_cgsnapshot_member_id': None
}

View File

@ -0,0 +1,416 @@
# Copyright (c) 2019 Infortrend Technology, Inc.
# 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.
class InfortrendNASTestData(object):
fake_share_id = ['5a0aa06e-1c57-4996-be46-b81e360e8866', # NFS
'aac4fe64-7a9c-472a-b156-9adbb50b4d29'] # CIFS
fake_share_name = [fake_share_id[0].replace('-', ''),
fake_share_id[1].replace('-', '')]
fake_channel_ip = ['172.27.112.223', '172.27.113.209']
fake_service_status_data = ('(64175, 1234, 272, 0)\n\n'
'{"cliCode": '
'[{"Return": "0x0000", "CLI": "Successful"}], '
'"returnCode": [], '
'"data": '
'[{"A": '
'{"NFS": '
'{"displayName": "NFS", '
'"state_time": "2017-05-04 14:19:53", '
'"enabled": true, '
'"cpu_rate": "0.0", '
'"mem_rate": "0.0", '
'"state": "exited", '
'"type": "share"}}}]}\n\n')
fake_folder_status_data = ('(64175, 1234, 1017, 0)\n\n'
'{"cliCode": '
'[{"Return": "0x0000", "CLI": "Successful"}], '
'"returnCode": [], '
'"data": '
'[{"utility": "1.00", '
'"used": "33886208", '
'"subshare": true, '
'"share": false, '
'"worm": "", '
'"free": "321931374592", '
'"fsType": "xfs", '
'"owner": "A", '
'"readOnly": false, '
'"modifyTime": "2017-04-27 16:16", '
'"directory": "/share-pool-01/LV-1", '
'"volumeId": "6541BAFB2E6C57B6", '
'"mounted": true, '
'"size": "321965260800"}, '
'{"utility": "1.00", '
'"used": "33779712", '
'"subshare": false, '
'"share": false, '
'"worm": "", '
'"free": "107287973888", '
'"fsType": "xfs", '
'"owner": "A", '
'"readOnly": false, '
'"modifyTime": "2017-04-27 15:45", '
'"directory": "/share-pool-02/LV-1", '
'"volumeId": "147A8FB67DA39914", '
'"mounted": true, '
'"size": "107321753600"}]}\n\n')
fake_nfs_status_off = [{
'A': {
'NFS': {
'displayName': 'NFS',
'state_time': '2017-05-04 14:19:53',
'enabled': False,
'cpu_rate': '0.0',
'mem_rate': '0.0',
'state': 'exited',
'type': 'share',
}
}
}]
fake_folder_status = [{
'utility': '1.00',
'used': '33886208',
'subshare': True,
'share': False,
'worm': '',
'free': '321931374592',
'fsType': 'xfs',
'owner': 'A',
'readOnly': False,
'modifyTime': '2017-04-27 16:16',
'directory': '/share-pool-01/LV-1',
'volumeId': '6541BAFB2E6C57B6',
'mounted': True,
'size': '321965260800'}, {
'utility': '1.00',
'used': '33779712',
'subshare': False,
'share': False,
'worm': '',
'free': '107287973888',
'fsType': 'xfs',
'owner': 'A',
'readOnly': False,
'modifyTime': '2017-04-27 15:45',
'directory': '/share-pool-02/LV-1',
'volumeId': '147A8FB67DA39914',
'mounted': True,
'size': '107321753600',
}]
def fake_get_channel_status(self, ch1_status='UP'):
return [{
'datalink': 'mgmt0',
'status': 'UP',
'typeConfig': 'DHCP',
'IP': '172.27.112.125',
'MAC': '00:d0:23:00:15:a6',
'netmask': '255.255.240.0',
'type': 'dhcp',
'gateway': '172.27.127.254'}, {
'datalink': 'CH0',
'status': 'UP',
'typeConfig': 'DHCP',
'IP': self.fake_channel_ip[0],
'MAC': '00:d0:23:80:15:a6',
'netmask': '255.255.240.0',
'type': 'dhcp',
'gateway': '172.27.127.254'}, {
'datalink': 'CH1',
'status': ch1_status,
'typeConfig': 'DHCP',
'IP': self.fake_channel_ip[1],
'MAC': '00:d0:23:40:15:a6',
'netmask': '255.255.240.0',
'type': 'dhcp',
'gateway': '172.27.127.254'}, {
'datalink': 'CH2',
'status': 'DOWN',
'typeConfig': 'DHCP',
'IP': '',
'MAC': '00:d0:23:c0:15:a6',
'netmask': '',
'type': '',
'gateway': ''}, {
'datalink': 'CH3',
'status': 'DOWN',
'typeConfig': 'DHCP',
'IP': '',
'MAC': '00:d0:23:20:15:a6',
'netmask': '',
'type': '',
'gateway': '',
}]
fake_fquota_status = [{
'quota': '21474836480',
'used': '0',
'name': 'test-folder',
'type': 'subfolder',
'id': '537178178'}, {
'quota': '32212254720',
'used': '0',
'name': fake_share_name[0],
'type': 'subfolder',
'id': '805306752'}, {
'quota': '53687091200',
'used': '21474836480',
'name': fake_share_name[1],
'type': 'subfolder',
'id': '69'}, {
'quota': '94091997184',
'used': '0',
'type': 'subfolder',
'id': '70',
"name": 'test-folder-02'
}]
fake_fquota_status_with_no_settings = []
def fake_get_share_status_nfs(self, status=False):
fake_share_status_nfs = [{
'ftp': False,
'cifs': False,
'oss': False,
'sftp': False,
'nfs': status,
'directory': '/LV-1/share-pool-01/' + self.fake_share_name[0],
'exist': True,
'afp': False,
'webdav': False
}]
if status:
fake_share_status_nfs[0]['nfs_detail'] = {
'hostList': [{
'uid': '65534',
'insecure': 'insecure',
'squash': 'all',
'access': 'ro',
'host': '*',
'gid': '65534',
'mode': 'async',
'no_subtree_check': 'no_subtree_check',
}]
}
return fake_share_status_nfs
def fake_get_share_status_cifs(self, status=False):
fake_share_status_cifs = [{
'ftp': False,
'cifs': status,
'oss': False,
'sftp': False,
'nfs': False,
'directory': '/share-pool-01/LV-1/' + self.fake_share_name[1],
'exist': True,
'afp': False,
'webdav': False
}]
if status:
fake_share_status_cifs[0]['cifs_detail'] = {
'available': True,
'encrypt': False,
'description': '',
'sharename': 'cifs-01',
'failover': '',
'AIO': True,
'priv': 'None',
'recycle_bin': False,
'ABE': True,
}
return fake_share_status_cifs
fake_subfolder_data = [{
'size': '6',
'index': '34',
'description': '',
'encryption': '',
'isEnd': False,
'share': False,
'volumeId': '6541BAFB2E6C57B6',
'quota': '',
'modifyTime': '2017-04-06 11:35',
'owner': 'A',
'path': '/share-pool-01/LV-1/UserHome',
'subshare': True,
'type': 'subfolder',
'empty': False,
'name': 'UserHome'}, {
'size': '6',
'index': '39',
'description': '',
'encryption': '',
'isEnd': False,
'share': False,
'volumeId': '6541BAFB2E6C57B6',
'quota': '21474836480',
'modifyTime': '2017-04-27 15:44',
'owner': 'A',
'path': '/share-pool-01/LV-1/test-folder',
'subshare': False,
'type': 'subfolder',
'empty': True,
'name': 'test-folder'}, {
'size': '6',
'index': '45',
'description': '',
'encryption': '',
'isEnd': False,
'share': True,
'volumeId': '6541BAFB2E6C57B6',
'quota': '32212254720',
'modifyTime': '2017-04-27 16:15',
'owner': 'A',
'path': '/share-pool-01/LV-1/' + fake_share_name[0],
'subshare': False,
'type': 'subfolder',
'empty': True,
'name': fake_share_name[0]}, {
'size': '6',
'index': '512',
'description': '',
'encryption': '',
'isEnd': True,
'share': True,
'volumeId': '6541BAFB2E6C57B6',
'quota': '53687091200',
'modifyTime': '2017-04-27 16:16',
'owner': 'A',
'path': '/share-pool-01/LV-1/' + fake_share_name[1],
'subshare': False,
'type': 'subfolder',
'empty': True,
'name': fake_share_name[1]}, {
'size': '6',
'index': '777',
'description': '',
'encryption': '',
'isEnd': False,
'share': False,
'volumeId': '6541BAFB2E6C57B6',
'quota': '94091997184',
'modifyTime': '2017-04-28 15:44',
'owner': 'A',
'path': '/share-pool-01/LV-1/test-folder-02',
'subshare': False,
'type': 'subfolder',
'empty': True,
'name': 'test-folder-02'
}]
fake_cifs_user_list = [{
'Superuser': 'No',
'Group': 'users',
'Description': '',
'Quota': 'none',
'PWD Expiry Date': '2291-01-19',
'Home Directory': '/share-pool-01/LV-1/UserHome/user01',
'UID': '100001',
'Type': 'Local',
'Name': 'user01'}, {
'Superuser': 'No',
'Group': 'users',
'Description': '',
'Quota': 'none',
'PWD Expiry Date': '2017-08-07',
'Home Directory': '/share-pool-01/LV-1/UserHome/user02',
'UID': '100002',
'Type': 'Local',
'Name': 'user02'
}]
fake_share_status_nfs_with_rules = [{
'ftp': False,
'cifs': False,
'oss': False,
'sftp': False,
'nfs': True,
'directory': '/share-pool-01/LV-1/' + fake_share_name[0],
'exist': True,
'nfs_detail': {
'hostList': [{
'uid': '65534',
'insecure': 'insecure',
'squash': 'all',
'access': 'ro',
'host': '*',
'gid': '65534',
'mode': 'async',
'no_subtree_check':
'no_subtree_check'}, {
'uid': '65534',
'insecure': 'insecure',
'squash': 'all',
'access': 'rw',
'host': '172.27.1.1',
'gid': '65534',
'mode': 'async',
'no_subtree_check': 'no_subtree_check'}, {
'uid': '65534',
'insecure': 'insecure',
'squash': 'all',
'access': 'rw',
'host': '172.27.1.2',
'gid': '65534',
'mode': 'async',
'no_subtree_check': 'no_subtree_check'}]
},
'afp': False,
'webdav': False,
}]
fake_share_status_cifs_with_rules = [
{
'permission': {
'Read': True,
'Write': True,
'Execute': True},
'type': 'user',
'id': '100001',
'name': 'user01'
}, {
'permission': {
'Read': True,
'Write': False,
'Execute': True},
'type': 'user',
'id': '100002',
'name': 'user02'
}, {
'permission': {
'Read': True,
'Write': False,
'Execute': True},
'type': 'group@',
'id': '100',
'name': 'users'
}, {
'permission': {
'Read': True,
'Write': False,
'Execute': True},
'type': 'other@',
'id': '',
'name': ''
}
]

View File

@ -0,0 +1,573 @@
# Copyright (c) 2019 Infortrend Technology, Inc.
# 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.
import ddt
import mock
from oslo_config import cfg
from manila import context
from manila import exception
from manila.share import configuration
from manila.share.drivers.infortrend import driver
from manila.share.drivers.infortrend import infortrend_nas
from manila import test
from manila.tests.share.drivers.infortrend import fake_infortrend_manila_data
from manila.tests.share.drivers.infortrend import fake_infortrend_nas_data
CONF = cfg.CONF
SUCCEED = (0, [])
@ddt.ddt
class InfortrendNASDriverTestCase(test.TestCase):
def __init__(self, *args, **kwargs):
super(InfortrendNASDriverTestCase, self).__init__(*args, **kwargs)
self._ctxt = context.get_admin_context()
self.nas_data = fake_infortrend_nas_data.InfortrendNASTestData()
self.m_data = fake_infortrend_manila_data.InfortrendManilaTestData()
def setUp(self):
CONF.set_default('driver_handles_share_servers', False)
CONF.set_default('infortrend_nas_ip', '172.27.1.1')
CONF.set_default('infortrend_nas_user', 'fake_user')
CONF.set_default('infortrend_nas_password', 'fake_password')
CONF.set_default('infortrend_nas_ssh_key', 'fake_sshkey')
CONF.set_default('infortrend_share_pools', 'share-pool-01')
CONF.set_default('infortrend_share_channels', '0,1')
self.fake_conf = configuration.Configuration(None)
super(InfortrendNASDriverTestCase, self).setUp()
def _get_driver(self, fake_conf, init_dict=False):
self._driver = driver.InfortrendNASDriver(
configuration=fake_conf)
self._iftnas = self._driver.ift_nas
self.pool_id = ['6541BAFB2E6C57B6']
self.pool_path = ['/share-pool-01/LV-1/']
if init_dict:
self._iftnas.pool_dict = {
'share-pool-01': {
'id': self.pool_id[0],
'path': self.pool_path[0],
}
}
self._iftnas.channel_dict = {
'0': self.nas_data.fake_channel_ip[0],
'1': self.nas_data.fake_channel_ip[1],
}
def test_no_login_ssh_key_and_pass(self):
self.fake_conf.set_default('infortrend_nas_password', None)
self.fake_conf.set_default('infortrend_nas_ssh_key', None)
self.assertRaises(
exception.InvalidParameterValue,
self._get_driver,
self.fake_conf)
def test_parser_with_service_status(self):
self._get_driver(self.fake_conf)
expect_service_status = [{
'A': {
'NFS': {
'displayName': 'NFS',
'state_time': '2017-05-04 14:19:53',
'enabled': True,
'cpu_rate': '0.0',
'mem_rate': '0.0',
'state': 'exited',
'type': 'share',
}
}
}]
rc, service_status = self._iftnas._parser(
self.nas_data.fake_service_status_data)
self.assertEqual(0, rc)
self.assertDictListMatch(expect_service_status, service_status)
def test_parser_with_folder_status(self):
self._get_driver(self.fake_conf)
expect_folder_status = [{
'utility': '1.00',
'used': '33886208',
'subshare': True,
'share': False,
'worm': '',
'free': '321931374592',
'fsType': 'xfs',
'owner': 'A',
'readOnly': False,
'modifyTime': '2017-04-27 16:16',
'directory': self.pool_path[0][:-1],
'volumeId': self.pool_id[0],
'mounted': True,
'size': '321965260800'}, {
'utility': '1.00',
'used': '33779712',
'subshare': False,
'share': False,
'worm': '',
'free': '107287973888',
'fsType': 'xfs',
'owner': 'A',
'readOnly': False,
'modifyTime': '2017-04-27 15:45',
'directory': '/share-pool-02/LV-1',
'volumeId': '147A8FB67DA39914',
'mounted': True,
'size': '107321753600'
}]
rc, folder_status = self._iftnas._parser(
self.nas_data.fake_folder_status_data)
self.assertEqual(0, rc)
self.assertDictListMatch(expect_folder_status, folder_status)
def test_ensure_service_on(self):
self._get_driver(self.fake_conf)
mock_execute = mock.Mock(
side_effect=[(0, self.nas_data.fake_nfs_status_off), SUCCEED])
self._iftnas._execute = mock_execute
self._iftnas._ensure_service_on('nfs')
mock_execute.assert_called_with(['service', 'restart', 'nfs'])
def test_check_channels_status(self):
self._get_driver(self.fake_conf)
expect_channel_dict = {
'0': self.nas_data.fake_channel_ip[0],
'1': self.nas_data.fake_channel_ip[1],
}
self._iftnas._execute = mock.Mock(
return_value=(0, self.nas_data.fake_get_channel_status()))
self._iftnas._check_channels_status()
self.assertDictMatch(expect_channel_dict, self._iftnas.channel_dict)
@mock.patch.object(infortrend_nas.LOG, 'warning')
def test_channel_status_down(self, log_warning):
self._get_driver(self.fake_conf)
self._iftnas._execute = mock.Mock(
return_value=(0, self.nas_data.fake_get_channel_status('DOWN')))
self._iftnas._check_channels_status()
self.assertEqual(1, log_warning.call_count)
@mock.patch.object(infortrend_nas.LOG, 'error')
def test_invalid_channel(self, log_error):
self.fake_conf.set_default('infortrend_share_channels', '0, 6')
self._get_driver(self.fake_conf)
self._iftnas._execute = mock.Mock(
return_value=(0, self.nas_data.fake_get_channel_status()))
self.assertRaises(
exception.InfortrendNASException,
self._iftnas._check_channels_status)
def test_check_pools_setup(self):
self._get_driver(self.fake_conf)
expect_pool_dict = {
'share-pool-01': {
'id': self.pool_id[0],
'path': self.pool_path[0],
}
}
self._iftnas._execute = mock.Mock(
return_value=(0, self.nas_data.fake_folder_status))
self._iftnas._check_pools_setup()
self.assertDictMatch(expect_pool_dict, self._iftnas.pool_dict)
def test_unknow_pools_setup(self):
self.fake_conf.set_default(
'infortrend_share_pools', 'chengwei, share-pool-01')
self._get_driver(self.fake_conf)
self._iftnas._execute = mock.Mock(
return_value=(0, self.nas_data.fake_folder_status))
self.assertRaises(
exception.InfortrendNASException,
self._iftnas._check_pools_setup)
@mock.patch.object(infortrend_nas.InfortrendNAS, '_execute')
def test_get_pool_quota_used(self, mock_execute):
self._get_driver(self.fake_conf, True)
mock_execute.return_value = (0, self.nas_data.fake_fquota_status)
pool_quota = self._iftnas._get_pool_quota_used('share-pool-01')
mock_execute.assert_called_with(
['fquota', 'status', self.pool_id[0],
'LV-1', '-t', 'folder'])
self.assertEqual(201466179584, pool_quota)
@mock.patch.object(infortrend_nas.InfortrendNAS, '_execute')
def test_create_share_nfs(self, mock_execute):
self._get_driver(self.fake_conf, True)
fake_share_id = self.m_data.fake_share_nfs['id']
fake_share_name = fake_share_id.replace('-', '')
expect_locations = [
self.nas_data.fake_channel_ip[0] +
':/share-pool-01/LV-1/' + fake_share_name,
self.nas_data.fake_channel_ip[1] +
':/share-pool-01/LV-1/' + fake_share_name,
]
mock_execute.side_effect = [
SUCCEED, # create folder
SUCCEED, # set size
(0, self.nas_data.fake_get_share_status_nfs()), # check proto
SUCCEED, # enable proto
(0, self.nas_data.fake_get_channel_status()) # update channel
]
locations = self._driver.create_share(
self._ctxt, self.m_data.fake_share_nfs)
self.assertEqual(expect_locations, locations)
mock_execute.assert_any_call(
['share', self.pool_path[0] + fake_share_name, 'nfs', 'on'])
@mock.patch.object(infortrend_nas.InfortrendNAS, '_execute')
def test_create_share_cifs(self, mock_execute):
self._get_driver(self.fake_conf, True)
fake_share_id = self.m_data.fake_share_cifs['id']
fake_share_name = fake_share_id.replace('-', '')
expect_locations = [
'\\\\' + self.nas_data.fake_channel_ip[0] +
'\\' + fake_share_name,
'\\\\' + self.nas_data.fake_channel_ip[1] +
'\\' + fake_share_name,
]
mock_execute.side_effect = [
SUCCEED, # create folder
SUCCEED, # set size
(0, self.nas_data.fake_get_share_status_cifs()), # check proto
SUCCEED, # enable proto
(0, self.nas_data.fake_get_channel_status()) # update channel
]
locations = self._driver.create_share(
self._ctxt, self.m_data.fake_share_cifs)
self.assertEqual(expect_locations, locations)
mock_execute.assert_any_call(
['share', self.pool_path[0] + fake_share_name,
'cifs', 'on', '-n', fake_share_name])
@mock.patch.object(infortrend_nas.InfortrendNAS, '_execute')
def test_delete_share_nfs(self, mock_execute):
self._get_driver(self.fake_conf, True)
fake_share_id = self.m_data.fake_share_nfs['id']
fake_share_name = fake_share_id.replace('-', '')
mock_execute.side_effect = [
(0, self.nas_data.fake_subfolder_data), # pagelist folder
SUCCEED, # delete folder
]
self._driver.delete_share(
self._ctxt, self.m_data.fake_share_nfs)
mock_execute.assert_any_call(
['folder', 'options', self.pool_id[0],
'LV-1', '-d', fake_share_name])
@mock.patch.object(infortrend_nas.InfortrendNAS, '_execute')
def test_delete_share_cifs(self, mock_execute):
self._get_driver(self.fake_conf, True)
fake_share_id = self.m_data.fake_share_cifs['id']
fake_share_name = fake_share_id.replace('-', '')
mock_execute.side_effect = [
(0, self.nas_data.fake_subfolder_data), # pagelist folder
SUCCEED, # delete folder
]
self._driver.delete_share(
self._ctxt, self.m_data.fake_share_cifs)
mock_execute.assert_any_call(
['folder', 'options', self.pool_id[0],
'LV-1', '-d', fake_share_name])
@mock.patch.object(infortrend_nas.LOG, 'warning')
@mock.patch.object(infortrend_nas.InfortrendNAS, '_execute')
def test_delete_non_exist_share(self, mock_execute, log_warning):
self._get_driver(self.fake_conf, True)
mock_execute.side_effect = [
(0, self.nas_data.fake_subfolder_data), # pagelist folder
]
self._driver.delete_share(
self._ctxt, self.m_data.fake_non_exist_share)
self.assertEqual(1, log_warning.call_count)
def test_get_pool(self):
self._get_driver(self.fake_conf, True)
pool = self._driver.get_pool(self.m_data.fake_share_nfs)
self.assertEqual('share-pool-01', pool)
def test_get_pool_without_host(self):
self._get_driver(self.fake_conf, True)
self._iftnas._execute = mock.Mock(
return_value=(0, self.nas_data.fake_subfolder_data))
pool = self._driver.get_pool(self.m_data.fake_share_cifs_no_host)
self.assertEqual('share-pool-01', pool)
def test_ensure_share_nfs(self):
self._get_driver(self.fake_conf, True)
share_id = self.m_data.fake_share_nfs['id']
share_name = share_id.replace('-', '')
share_path = self.pool_path[0] + share_name
expect_locations = [
self.nas_data.fake_channel_ip[0] + ':' + share_path,
self.nas_data.fake_channel_ip[1] + ':' + share_path,
]
self._iftnas._execute = mock.Mock(
return_value=(0, self.nas_data.fake_get_channel_status()))
locations = self._driver.ensure_share(
self._ctxt, self.m_data.fake_share_nfs)
self.assertEqual(expect_locations, locations)
def test_ensure_share_cifs(self):
self._get_driver(self.fake_conf, True)
share_id = self.m_data.fake_share_cifs['id']
share_name = share_id.replace('-', '')
expect_locations = [
'\\\\' + self.nas_data.fake_channel_ip[0] +
'\\' + share_name,
'\\\\' + self.nas_data.fake_channel_ip[1] +
'\\' + share_name,
]
self._iftnas._execute = mock.Mock(
return_value=(0, self.nas_data.fake_get_channel_status()))
locations = self._driver.ensure_share(
self._ctxt, self.m_data.fake_share_cifs)
self.assertEqual(expect_locations, locations)
def test_extend_share(self):
self._get_driver(self.fake_conf, True)
share_id = self.m_data.fake_share_nfs['id']
share_name = share_id.replace('-', '')
self._iftnas._execute = mock.Mock(return_value=SUCCEED)
self._driver.extend_share(self.m_data.fake_share_nfs, 100)
self._iftnas._execute.assert_called_once_with(
['fquota', 'create', self.pool_id[0], 'LV-1',
share_name, '100G', '-t', 'folder'])
@mock.patch.object(infortrend_nas.InfortrendNAS, '_execute')
def test_shrink_share(self, mock_execute):
self._get_driver(self.fake_conf, True)
share_id = self.m_data.fake_share_nfs['id']
share_name = share_id.replace('-', '')
mock_execute.side_effect = [
(0, self.nas_data.fake_fquota_status), # check used
SUCCEED,
]
self._driver.shrink_share(self.m_data.fake_share_nfs, 10)
mock_execute.assert_has_calls([
mock.call(['fquota', 'status', self.pool_id[0],
'LV-1', '-t', 'folder']),
mock.call(['fquota', 'create', self.pool_id[0],
'LV-1', share_name, '10G', '-t', 'folder'])])
@mock.patch.object(infortrend_nas.InfortrendNAS, '_execute')
def test_shrink_share_smaller_than_used_size(self, mock_execute):
self._get_driver(self.fake_conf, True)
mock_execute.side_effect = [
(0, self.nas_data.fake_fquota_status), # check used
]
self.assertRaises(
exception.ShareShrinkingPossibleDataLoss,
self._driver.shrink_share,
self.m_data.fake_share_cifs,
10)
def test_get_share_size(self):
self._get_driver(self.fake_conf, True)
self._iftnas._execute = mock.Mock(
return_value=(0, self.nas_data.fake_fquota_status))
size = self._iftnas._get_share_size('', '', 'test-folder-02')
self.assertEqual(87.63, size)
@mock.patch.object(infortrend_nas.InfortrendNAS, '_execute')
def test_manage_existing_nfs(self, mock_execute):
self._get_driver(self.fake_conf, True)
share_id = self.m_data.fake_share_for_manage_nfs['id']
share_name = share_id.replace('-', '')
origin_share_path = self.pool_path[0] + 'test-folder'
export_share_path = self.pool_path[0] + share_name
expect_result = {
'size': 20.0,
'export_locations': [
self.nas_data.fake_channel_ip[0] + ':' + export_share_path,
self.nas_data.fake_channel_ip[1] + ':' + export_share_path,
]
}
mock_execute.side_effect = [
(0, self.nas_data.fake_subfolder_data), # pagelist folder
(0, self.nas_data.fake_get_share_status_nfs()), # check proto
SUCCEED, # enable nfs
(0, self.nas_data.fake_fquota_status), # get share size
SUCCEED, # rename share
(0, self.nas_data.fake_get_channel_status()) # update channel
]
result = self._driver.manage_existing(
self.m_data.fake_share_for_manage_nfs,
{}
)
self.assertEqual(expect_result, result)
mock_execute.assert_has_calls([
mock.call(['pagelist', 'folder', self.pool_path[0]]),
mock.call(['share', 'status', '-f', origin_share_path]),
mock.call(['share', origin_share_path, 'nfs', 'on']),
mock.call(['fquota', 'status', self.pool_id[0],
origin_share_path.split('/')[3], '-t', 'folder']),
mock.call(['folder', 'options', self.pool_id[0],
'LV-1', '-k', 'test-folder', share_name]),
mock.call(['ifconfig', 'inet', 'show']),
])
@mock.patch.object(infortrend_nas.InfortrendNAS, '_execute')
def test_manage_existing_cifs(self, mock_execute):
self._get_driver(self.fake_conf, True)
share_id = self.m_data.fake_share_for_manage_cifs['id']
share_name = share_id.replace('-', '')
origin_share_path = self.pool_path[0] + 'test-folder-02'
expect_result = {
'size': 87.63,
'export_locations': [
'\\\\' + self.nas_data.fake_channel_ip[0] + '\\' + share_name,
'\\\\' + self.nas_data.fake_channel_ip[1] + '\\' + share_name,
]
}
mock_execute.side_effect = [
(0, self.nas_data.fake_subfolder_data), # pagelist folder
(0, self.nas_data.fake_get_share_status_cifs()), # check proto
SUCCEED, # enable cifs
(0, self.nas_data.fake_fquota_status), # get share size
SUCCEED, # rename share
(0, self.nas_data.fake_get_channel_status()) # update channel
]
result = self._driver.manage_existing(
self.m_data.fake_share_for_manage_cifs,
{}
)
self.assertEqual(expect_result, result)
mock_execute.assert_has_calls([
mock.call(['pagelist', 'folder', self.pool_path[0]]),
mock.call(['share', 'status', '-f', origin_share_path]),
mock.call(['share', origin_share_path, 'cifs', 'on',
'-n', share_name]),
mock.call(['fquota', 'status', self.pool_id[0],
origin_share_path.split('/')[3], '-t', 'folder']),
mock.call(['folder', 'options', self.pool_id[0],
'LV-1', '-k', 'test-folder-02', share_name]),
mock.call(['ifconfig', 'inet', 'show']),
])
def test_manage_existing_with_no_location(self):
self._get_driver(self.fake_conf, True)
fake_share = self.m_data._get_fake_share_for_manage('')
self.assertRaises(
exception.InfortrendNASException,
self._driver.manage_existing,
fake_share, {})
@ddt.data('172.27.1.1:/share-pool-01/LV-1/test-folder',
'172.27.112.223:/share-pool-01/LV-1/some-folder')
def test_manage_existing_wrong_ip_or_name(self, fake_share_path):
self._get_driver(self.fake_conf, True)
fake_share = self.m_data._get_fake_share_for_manage(fake_share_path)
self._iftnas._execute = mock.Mock(
return_value=(0, self.nas_data.fake_subfolder_data))
self.assertRaises(
exception.InfortrendNASException,
self._driver.manage_existing,
fake_share, {})
@mock.patch.object(infortrend_nas.InfortrendNAS, '_execute')
def test_manage_existing_with_no_size_setting(self, mock_execute):
self._get_driver(self.fake_conf, True)
mock_execute.side_effect = [
(0, self.nas_data.fake_subfolder_data), # pagelist folder
(0, self.nas_data.fake_get_share_status_nfs()), # check proto
SUCCEED, # enable nfs
(0, self.nas_data.fake_fquota_status_with_no_settings),
]
self.assertRaises(
exception.InfortrendNASException,
self._driver.manage_existing,
self.m_data.fake_share_for_manage_nfs,
{})
@ddt.data('NFS', 'CIFS')
@mock.patch.object(infortrend_nas.InfortrendNAS, '_execute')
def test_unmanage(self, protocol, mock_execute):
share_to_unmanage = (self.m_data.fake_share_nfs
if protocol == 'NFS' else
self.m_data.fake_share_cifs)
self._get_driver(self.fake_conf, True)
mock_execute.side_effect = [
(0, self.nas_data.fake_subfolder_data), # pagelist folder
]
self._driver.unmanage(share_to_unmanage)
mock_execute.assert_called_once_with(
['pagelist', 'folder', self.pool_path[0]],
)
@mock.patch.object(infortrend_nas.LOG, 'warning')
def test_unmanage_share_not_exist(self, log_warning):
self._get_driver(self.fake_conf, True)
self._iftnas._execute = mock.Mock(
return_value=(0, self.nas_data.fake_subfolder_data))
self._driver.unmanage(
self.m_data.fake_share_for_manage_nfs,
)
self.assertEqual(1, log_warning.call_count)

View File

@ -0,0 +1,5 @@
---
prelude: >
Added Manila share driver for Infortrend storage systems.
features:
- The new Infortrend driver supports GS/GSe Family storage systems.