Merge "Add share driver for Tegile IntelliFlash Arrays"
This commit is contained in:
commit
f11558ff16
@ -112,6 +112,7 @@ Share backends
|
||||
hdfs_native_driver
|
||||
hds_hnas_driver
|
||||
hpe_3par_driver
|
||||
tegile_driver
|
||||
|
||||
Indices and tables
|
||||
------------------
|
||||
|
@ -69,6 +69,8 @@ Mapping of share drivers and share features support
|
||||
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
| CephFS Native | DHSS = False (M) | \- | M | M | M | \- | \- |
|
||||
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
| Tegile | DHSS = False (M) | \- | M | M | M | M | \- |
|
||||
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
|
||||
.. note::
|
||||
|
||||
@ -118,6 +120,8 @@ Mapping of share drivers and share access rules support
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
|
||||
| CephFS Native | \- | \- | \- | CEPH(M) | \- | \- | \- | \- |
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
|
||||
| Tegile | NFS (M) |NFS (M),CIFS (M)| \- | \- | NFS (M) |NFS (M),CIFS (M)| \- | \- |
|
||||
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
|
||||
|
||||
Mapping of share drivers and security services support
|
||||
------------------------------------------------------
|
||||
@ -161,4 +165,6 @@ Mapping of share drivers and security services support
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| CephFS Native | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| Tegile | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
|
||||
|
129
doc/source/devref/tegile_driver.rst
Normal file
129
doc/source/devref/tegile_driver.rst
Normal file
@ -0,0 +1,129 @@
|
||||
..
|
||||
Copyright (c) 2016 Tegile Systems 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.
|
||||
|
||||
Tegile Driver
|
||||
=============
|
||||
|
||||
The Tegile Manila driver uses Tegile IntelliFlash Arrays to provide shared
|
||||
filesystems to OpenStack.
|
||||
|
||||
The Tegile Driver interfaces with a Tegile Array via the REST API.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
- Tegile IntelliFlash version 3.5.1
|
||||
- For using CIFS, Active Directory must be configured in the Tegile Array.
|
||||
|
||||
Supported Operations
|
||||
--------------------
|
||||
|
||||
The following operations are supported on a Tegile Array:
|
||||
|
||||
* Create CIFS/NFS Share
|
||||
* Delete CIFS/NFS Share
|
||||
* Allow CIFS/NFS Share access
|
||||
* Only IP access type is supported for NFS
|
||||
* USER access type is supported for NFS and CIFS
|
||||
* RW and RO access supported
|
||||
* Deny CIFS/NFS Share access
|
||||
* IP access type is supported for NFS
|
||||
* USER access type is supported for NFS and CIFS
|
||||
* Create snapshot
|
||||
* Delete snapshot
|
||||
* Extend share
|
||||
* Shrink share
|
||||
* Create share from snapshot
|
||||
|
||||
Backend Configuration
|
||||
---------------------
|
||||
|
||||
The following parameters need to be configured in the [DEFAULT]
|
||||
section of */etc/manila/manila.conf*:
|
||||
|
||||
+-----------------------------------------------------------------------------------------------------------------------------------+
|
||||
| [DEFAULT] |
|
||||
+============================+======================================================================================================+
|
||||
| **Option** | **Description** |
|
||||
+----------------------------+-----------+------------------------------------------------------------------------------------------+
|
||||
| enabled_share_backends | Name of the section on manila.conf used to specify a backend. |
|
||||
| | E.g. *enabled_share_backends = tegileNAS* |
|
||||
+----------------------------+------------------------------------------------------------------------------------------------------+
|
||||
| enabled_share_protocols | Specify a list of protocols to be allowed for share creation. For Tegile driver this can be: |
|
||||
| | *NFS* or *CIFS* or *NFS, CIFS*. |
|
||||
+----------------------------+------------------------------------------------------------------------------------------------------+
|
||||
|
||||
The following parameters need to be configured in the [backend] section of */etc/manila/manila.conf*:
|
||||
|
||||
+-------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| [tegileNAS] |
|
||||
+===============================+=====================================================================================================+
|
||||
| **Option** | **Description** |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
| share_backend_name | A name for the backend. |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
| share_driver | Python module path. For Tegile driver this must be: |
|
||||
| | *manila.share.drivers.tegile.tegile.TegileShareDriver*. |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
| driver_handles_share_servers| DHSS, Driver working mode. For Tegile driver **this must be**: |
|
||||
| | *False*. |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
| tegile_nas_server | Tegile array IP to connect from the Manila node. |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
| tegile_nas_login | This field is used to provide username credential to Tegile array. |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
| tegile_nas_password | This field is used to provide password credential to Tegile array. |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
| tegile_default_project | This field can be used to specify the default project in Tegile array where shares are created. |
|
||||
| | This field is optional. |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
|
||||
Below is an example of a valid configuration of Tegile driver:
|
||||
|
||||
| ``[DEFAULT]``
|
||||
| ``enabled_share_backends = tegileNAS``
|
||||
| ``enabled_share_protocols = NFS,CIFS``
|
||||
|
||||
| ``[tegileNAS]``
|
||||
| ``driver_handles_share_servers = False``
|
||||
| ``share_backend_name = tegileNAS``
|
||||
| ``share_driver = manila.share.drivers.tegile.tegile.TegileShareDriver``
|
||||
| ``tegile_nas_server = 10.12.14.16``
|
||||
| ``tegile_nas_login = admin``
|
||||
| ``tegile_nas_password = password``
|
||||
| ``tegile_default_project = financeshares``
|
||||
|
||||
Restart of :term:`manila-share` service is needed for the configuration changes
|
||||
to take effect.
|
||||
|
||||
Restrictions
|
||||
------------
|
||||
|
||||
The Tegile driver has the following restrictions:
|
||||
|
||||
- IP access type is supported only for NFS.
|
||||
|
||||
- Only FLAT network is supported.
|
||||
|
||||
The :mod:`manila.share.drivers.tegile.tegile` Module
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: manila.share.drivers.tegile.tegile
|
||||
:noindex:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:exclude-members: TegileAPIExecutor, debugger
|
@ -768,3 +768,9 @@ class ReplicationException(ManilaException):
|
||||
|
||||
class ShareReplicaNotFound(NotFound):
|
||||
message = _("Share Replica %(replica_id)s could not be found.")
|
||||
|
||||
|
||||
# Tegile Storage drivers
|
||||
class TegileAPIException(ShareBackendException):
|
||||
message = _("Unexpected response from Tegile IntelliFlash API: "
|
||||
"%(response)s")
|
||||
|
@ -68,6 +68,7 @@ import manila.share.drivers.lxd
|
||||
import manila.share.drivers.netapp.options
|
||||
import manila.share.drivers.quobyte.quobyte
|
||||
import manila.share.drivers.service_instance
|
||||
import manila.share.drivers.tegile.tegile
|
||||
import manila.share.drivers.windows.service_instance
|
||||
import manila.share.drivers.windows.winrm_helper
|
||||
import manila.share.drivers.zfsonlinux.driver
|
||||
@ -141,6 +142,7 @@ _global_opt_lists = [
|
||||
manila.share.drivers.service_instance.common_opts,
|
||||
manila.share.drivers.service_instance.no_share_servers_handling_mode_opts,
|
||||
manila.share.drivers.service_instance.share_servers_handling_mode_opts,
|
||||
manila.share.drivers.tegile.tegile.tegile_opts,
|
||||
manila.share.drivers.windows.service_instance.windows_share_server_opts,
|
||||
manila.share.drivers.windows.winrm_helper.winrm_opts,
|
||||
manila.share.drivers.zfsonlinux.driver.zfsonlinux_opts,
|
||||
|
0
manila/share/drivers/tegile/__init__.py
Normal file
0
manila/share/drivers/tegile/__init__.py
Normal file
513
manila/share/drivers/tegile/tegile.py
Normal file
513
manila/share/drivers/tegile/tegile.py
Normal file
@ -0,0 +1,513 @@
|
||||
# Copyright (c) 2016 by Tegile Systems, 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.
|
||||
"""
|
||||
Share driver for Tegile storage.
|
||||
"""
|
||||
|
||||
import json
|
||||
import requests
|
||||
import six
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from manila import utils
|
||||
from manila.i18n import _, _LI, _LW
|
||||
from manila import exception
|
||||
from manila.share import driver
|
||||
from manila.share import utils as share_utils
|
||||
|
||||
tegile_opts = [
|
||||
cfg.StrOpt('tegile_nas_server',
|
||||
help='Tegile NAS server hostname or IP address.'),
|
||||
cfg.StrOpt('tegile_nas_login',
|
||||
help='User name for the Tegile NAS server.'),
|
||||
cfg.StrOpt('tegile_nas_password',
|
||||
help='Password for the Tegile NAS server.'),
|
||||
cfg.StrOpt('tegile_default_project',
|
||||
help='Create shares in this project')]
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(tegile_opts)
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
DEFAULT_API_SERVICE = 'openstack'
|
||||
TEGILE_API_PATH = 'zebi/api'
|
||||
TEGILE_LOCAL_CONTAINER_NAME = 'Local'
|
||||
TEGILE_SNAPSHOT_PREFIX = 'Manual-S-'
|
||||
VENDOR = 'Tegile Systems Inc.'
|
||||
DEFAULT_BACKEND_NAME = 'Tegile'
|
||||
VERSION = '1.0.0'
|
||||
DEBUG_LOGGING = False # For debugging purposes
|
||||
|
||||
|
||||
def debugger(func):
|
||||
"""Returns a wrapper that wraps func.
|
||||
|
||||
The wrapper will log the entry and exit points of the function.
|
||||
"""
|
||||
|
||||
def wrapper(*args, **kwds):
|
||||
if DEBUG_LOGGING:
|
||||
LOG.debug('Entering %(classname)s.%(funcname)s',
|
||||
{
|
||||
'classname': args[0].__class__.__name__,
|
||||
'funcname': func.__name__,
|
||||
})
|
||||
LOG.debug('Arguments: %(args)s, %(kwds)s',
|
||||
{
|
||||
'args': args[1:],
|
||||
'kwds': kwds,
|
||||
})
|
||||
f_result = func(*args, **kwds)
|
||||
if DEBUG_LOGGING:
|
||||
LOG.debug('Exiting %(classname)s.%(funcname)s',
|
||||
{
|
||||
'classname': args[0].__class__.__name__,
|
||||
'funcname': func.__name__,
|
||||
})
|
||||
LOG.debug('Results: %(result)s',
|
||||
{'result': f_result})
|
||||
return f_result
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class TegileAPIExecutor(object):
|
||||
def __init__(self, classname, hostname, username, password):
|
||||
self._classname = classname
|
||||
self._hostname = hostname
|
||||
self._username = username
|
||||
self._password = password
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self._send_api_request(*args, **kwargs)
|
||||
|
||||
@debugger
|
||||
@utils.retry(exception=(requests.ConnectionError, requests.Timeout),
|
||||
interval=30,
|
||||
retries=3,
|
||||
backoff_rate=1)
|
||||
def _send_api_request(self, method, params=None,
|
||||
request_type='post',
|
||||
api_service=DEFAULT_API_SERVICE,
|
||||
fine_logging=DEBUG_LOGGING):
|
||||
if params is not None:
|
||||
params = json.dumps(params)
|
||||
|
||||
url = 'https://%s/%s/%s/%s' % (self._hostname,
|
||||
TEGILE_API_PATH,
|
||||
api_service,
|
||||
method)
|
||||
if fine_logging:
|
||||
LOG.debug('TegileAPIExecutor(%(classname)s) method: %(method)s, '
|
||||
'url: %(url)s', {
|
||||
'classname': self._classname,
|
||||
'method': method,
|
||||
'url': url,
|
||||
})
|
||||
if request_type == 'post':
|
||||
if fine_logging:
|
||||
LOG.debug('TegileAPIExecutor(%(classname)s) '
|
||||
'method: %(method)s, payload: %(payload)s',
|
||||
{
|
||||
'classname': self._classname,
|
||||
'method': method,
|
||||
'payload': params,
|
||||
})
|
||||
req = requests.post(url,
|
||||
data=params,
|
||||
auth=(self._username, self._password),
|
||||
verify=False)
|
||||
else:
|
||||
req = requests.get(url,
|
||||
auth=(self._username, self._password),
|
||||
verify=False)
|
||||
|
||||
if fine_logging:
|
||||
LOG.debug('TegileAPIExecutor(%(classname)s) method: %(method)s, '
|
||||
'return code: %(retcode)s',
|
||||
{
|
||||
'classname': self._classname,
|
||||
'method': method,
|
||||
'retcode': req,
|
||||
})
|
||||
try:
|
||||
response = req.json()
|
||||
if fine_logging:
|
||||
LOG.debug('TegileAPIExecutor(%(classname)s) '
|
||||
'method: %(method)s, response: %(response)s',
|
||||
{
|
||||
'classname': self._classname,
|
||||
'method': method,
|
||||
'response': response,
|
||||
})
|
||||
except ValueError:
|
||||
# Some APIs don't return output and that's fine
|
||||
response = ''
|
||||
req.close()
|
||||
|
||||
if req.status_code != 200:
|
||||
raise exception.TegileAPIException(response=req.text)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class TegileShareDriver(driver.ShareDriver):
|
||||
"""Tegile NAS driver. Allows for NFS and CIFS NAS storage usage."""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TegileShareDriver, self).__init__(False, *args, **kwargs)
|
||||
|
||||
self.configuration.append_config_values(tegile_opts)
|
||||
self._default_project = (self.configuration.safe_get(
|
||||
"tegile_default_project") or 'openstack')
|
||||
self._backend_name = (self.configuration.safe_get('share_backend_name')
|
||||
or CONF.share_backend_name
|
||||
or DEFAULT_BACKEND_NAME)
|
||||
self._hostname = self.configuration.safe_get('tegile_nas_server')
|
||||
username = self.configuration.safe_get('tegile_nas_login')
|
||||
password = self.configuration.safe_get('tegile_nas_password')
|
||||
self._api = TegileAPIExecutor(self.__class__.__name__,
|
||||
self._hostname,
|
||||
username,
|
||||
password)
|
||||
|
||||
@debugger
|
||||
def create_share(self, context, share, share_server=None):
|
||||
"""Is called to create share."""
|
||||
share_name = share['name']
|
||||
share_proto = share['share_proto']
|
||||
|
||||
pool_name = share_utils.extract_host(share['host'], level='pool')
|
||||
|
||||
params = (pool_name, self._default_project, share_name, share_proto)
|
||||
|
||||
# Share name coming from the backend is the most reliable. Sometimes
|
||||
# a few options in Tegile array could cause sharename to be different
|
||||
# from the one passed to it. Eg. 'projectname-sharename' instead
|
||||
# of 'sharename' if inherited share properties are selected.
|
||||
ip, real_share_name = self._api('createShare', params).split()
|
||||
|
||||
LOG.info(_LI("Created share %(sharename)s, share id %(shid)s."),
|
||||
{'sharename': share_name, 'shid': share['id']})
|
||||
|
||||
return self._get_location_path(real_share_name, share_proto, ip)
|
||||
|
||||
@debugger
|
||||
def extend_share(self, share, new_size, share_server=None):
|
||||
"""Is called to extend share.
|
||||
|
||||
There is no resize for Tegile shares.
|
||||
We just adjust the quotas. The API is still called 'resizeShare'.
|
||||
"""
|
||||
|
||||
self._adjust_size(share, new_size, share_server)
|
||||
|
||||
@debugger
|
||||
def shrink_share(self, shrink_share, shrink_size, share_server=None):
|
||||
"""Uses resize_share to shrink a share.
|
||||
|
||||
There is no shrink for Tegile shares.
|
||||
We just adjust the quotas. The API is still called 'resizeShare'.
|
||||
"""
|
||||
self._adjust_size(shrink_share, shrink_size, share_server)
|
||||
|
||||
@debugger
|
||||
def _adjust_size(self, share, new_size, share_server=None):
|
||||
pool, project, share_name = self._get_pool_project_share_name(share)
|
||||
params = ('%s/%s/%s/%s' % (pool,
|
||||
TEGILE_LOCAL_CONTAINER_NAME,
|
||||
project,
|
||||
share_name),
|
||||
six.text_type(new_size),
|
||||
'GB')
|
||||
self._api('resizeShare', params)
|
||||
|
||||
@debugger
|
||||
def delete_share(self, context, share, share_server=None):
|
||||
"""Is called to remove share."""
|
||||
pool, project, share_name = self._get_pool_project_share_name(share)
|
||||
params = ('%s/%s/%s/%s' % (pool,
|
||||
TEGILE_LOCAL_CONTAINER_NAME,
|
||||
project,
|
||||
share_name),
|
||||
True,
|
||||
False)
|
||||
|
||||
self._api('deleteShare', params)
|
||||
|
||||
@debugger
|
||||
def create_snapshot(self, context, snapshot, share_server=None):
|
||||
"""Is called to create snapshot."""
|
||||
snap_name = snapshot['name']
|
||||
|
||||
pool, project, share_name = self._get_pool_project_share_name(
|
||||
snapshot['share'])
|
||||
|
||||
share = {
|
||||
'poolName': '%s' % pool,
|
||||
'projectName': '%s' % project,
|
||||
'name': share_name,
|
||||
'availableSize': 0,
|
||||
'totalSize': 0,
|
||||
'datasetPath': '%s/%s/%s' %
|
||||
(pool,
|
||||
TEGILE_LOCAL_CONTAINER_NAME,
|
||||
project),
|
||||
'mountpoint': share_name,
|
||||
'local': 'true',
|
||||
}
|
||||
|
||||
params = (share, snap_name, False)
|
||||
|
||||
LOG.info(_LI('Creating snapshot for share_name=%(shr)s'
|
||||
' snap_name=%(name)s'),
|
||||
{'shr': share_name, 'name': snap_name})
|
||||
|
||||
self._api('createShareSnapshot', params)
|
||||
|
||||
@debugger
|
||||
def create_share_from_snapshot(self, context, share, snapshot,
|
||||
share_server=None):
|
||||
"""Create a share from a snapshot - clone a snapshot."""
|
||||
pool, project, share_name = self._get_pool_project_share_name(share)
|
||||
|
||||
params = ('%s/%s/%s/%s@%s%s' % (pool,
|
||||
TEGILE_LOCAL_CONTAINER_NAME,
|
||||
project,
|
||||
snapshot['share_name'],
|
||||
TEGILE_SNAPSHOT_PREFIX,
|
||||
snapshot['name'],
|
||||
),
|
||||
share_name,
|
||||
True,
|
||||
)
|
||||
|
||||
ip, real_share_name = self._api('cloneShareSnapshot',
|
||||
params).split()
|
||||
|
||||
share_proto = share['share_proto']
|
||||
return self._get_location_path(real_share_name, share_proto, ip)
|
||||
|
||||
@debugger
|
||||
def delete_snapshot(self, context, snapshot, share_server=None):
|
||||
"""Is called to remove snapshot."""
|
||||
pool, project, share_name = self._get_pool_project_share_name(
|
||||
snapshot['share'])
|
||||
params = ('%s/%s/%s/%s@%s%s' % (pool,
|
||||
TEGILE_LOCAL_CONTAINER_NAME,
|
||||
project,
|
||||
share_name,
|
||||
TEGILE_SNAPSHOT_PREFIX,
|
||||
snapshot['name']),
|
||||
False)
|
||||
|
||||
self._api('deleteShareSnapshot', params)
|
||||
|
||||
@debugger
|
||||
def ensure_share(self, context, share, share_server=None):
|
||||
"""Invoked to sure that share is exported."""
|
||||
|
||||
# Fetching share name from server, because some configuration
|
||||
# options can cause sharename different from the OpenStack share name
|
||||
pool, project, share_name = self._get_pool_project_share_name(share)
|
||||
params = [
|
||||
'%s/%s/%s/%s' % (pool,
|
||||
TEGILE_LOCAL_CONTAINER_NAME,
|
||||
project,
|
||||
share_name),
|
||||
]
|
||||
ip, real_share_name = self._api('getShareIPAndMountPoint',
|
||||
params).split()
|
||||
|
||||
share_proto = share['share_proto']
|
||||
location = self._get_location_path(real_share_name, share_proto, ip)
|
||||
return [location]
|
||||
|
||||
@debugger
|
||||
def _allow_access(self, context, share, access, share_server=None):
|
||||
"""Allow access to the share."""
|
||||
share_proto = share['share_proto']
|
||||
access_type = access['access_type']
|
||||
access_level = access['access_level']
|
||||
access_to = access['access_to']
|
||||
|
||||
self._check_share_access(share_proto, access_type)
|
||||
|
||||
pool, project, share_name = self._get_pool_project_share_name(share)
|
||||
params = ('%s/%s/%s/%s' % (pool,
|
||||
TEGILE_LOCAL_CONTAINER_NAME,
|
||||
project,
|
||||
share_name),
|
||||
share_proto,
|
||||
access_type,
|
||||
access_to,
|
||||
access_level)
|
||||
|
||||
self._api('shareAllowAccess', params)
|
||||
|
||||
@debugger
|
||||
def _deny_access(self, context, share, access, share_server=None):
|
||||
"""Deny access to the share."""
|
||||
share_proto = share['share_proto']
|
||||
access_type = access['access_type']
|
||||
access_level = access['access_level']
|
||||
access_to = access['access_to']
|
||||
|
||||
self._check_share_access(share_proto, access_type)
|
||||
|
||||
pool, project, share_name = self._get_pool_project_share_name(share)
|
||||
params = ('%s/%s/%s/%s' % (pool,
|
||||
TEGILE_LOCAL_CONTAINER_NAME,
|
||||
project,
|
||||
share_name),
|
||||
share_proto,
|
||||
access_type,
|
||||
access_to,
|
||||
access_level)
|
||||
|
||||
self._api('shareDenyAccess', params)
|
||||
|
||||
def _check_share_access(self, share_proto, access_type):
|
||||
if share_proto == 'CIFS' and access_type != 'user':
|
||||
reason = _LW('Only USER access type is allowed for '
|
||||
'CIFS shares.')
|
||||
LOG.warning(reason)
|
||||
raise exception.InvalidShareAccess(reason=reason)
|
||||
elif share_proto == 'NFS' and access_type not in ('ip', 'user'):
|
||||
reason = _LW('Only IP or USER access types are allowed for '
|
||||
'NFS shares.')
|
||||
LOG.warning(reason)
|
||||
raise exception.InvalidShareAccess(reason=reason)
|
||||
elif share_proto not in ('NFS', 'CIFS'):
|
||||
reason = _LW('Unsupported protocol \"%s\" specified for '
|
||||
'access rule.') % share_proto
|
||||
raise exception.InvalidShareAccess(reason=reason)
|
||||
|
||||
@debugger
|
||||
def update_access(self, context, share, access_rules, add_rules=None,
|
||||
delete_rules=None, share_server=None):
|
||||
if not (add_rules or delete_rules):
|
||||
# Recovery mode
|
||||
pool, project, share_name = (
|
||||
self._get_pool_project_share_name(share))
|
||||
share_proto = share['share_proto']
|
||||
params = ('%s/%s/%s/%s' % (pool,
|
||||
TEGILE_LOCAL_CONTAINER_NAME,
|
||||
project,
|
||||
share_name),
|
||||
share_proto)
|
||||
|
||||
# Clears all current ACLs
|
||||
# Remove ip and user ACLs if share_proto is NFS
|
||||
# Remove user ACLs if share_proto is CIFS
|
||||
self._api('clearAccessRules', params)
|
||||
|
||||
# Looping thru all rules.
|
||||
# Will have one API call per rule.
|
||||
for access in access_rules:
|
||||
self._allow_access(context, share, access, share_server)
|
||||
else:
|
||||
# Adding/Deleting specific rules
|
||||
for access in delete_rules:
|
||||
self._deny_access(context, share, access, share_server)
|
||||
for access in add_rules:
|
||||
self._allow_access(context, share, access, share_server)
|
||||
|
||||
@debugger
|
||||
def _update_share_stats(self, **kwargs):
|
||||
"""Retrieve stats info."""
|
||||
|
||||
try:
|
||||
data = self._api(method='getArrayStats',
|
||||
request_type='get',
|
||||
fine_logging=False)
|
||||
# fixing values coming back here as String to float
|
||||
for pool in data.get('pools', []):
|
||||
pool['total_capacity_gb'] = float(
|
||||
pool.get('total_capacity_gb', 0))
|
||||
pool['free_capacity_gb'] = float(
|
||||
pool.get('free_capacity_gb', 0))
|
||||
pool['allocated_capacity_gb'] = float(
|
||||
pool.get('allocated_capacity_gb', 0))
|
||||
|
||||
pool['qos'] = pool.pop('QoS_support', False)
|
||||
pool['reserved_percentage'] = (
|
||||
self.configuration.reserved_share_percentage)
|
||||
pool['dedupe'] = True
|
||||
pool['compression'] = True
|
||||
pool['thin_provisioning'] = True
|
||||
pool['max_over_subscription_ratio'] = (
|
||||
self.configuration.max_over_subscription_ratio)
|
||||
|
||||
data['share_backend_name'] = self._backend_name
|
||||
data['vendor_name'] = VENDOR
|
||||
data['driver_version'] = VERSION
|
||||
data['storage_protocol'] = 'NFS_CIFS'
|
||||
data['snapshot_support'] = True
|
||||
data['qos'] = False
|
||||
|
||||
super(TegileShareDriver, self)._update_share_stats(data)
|
||||
except Exception as e:
|
||||
msg = _('Unexpected error while trying to get the '
|
||||
'usage stats from array.')
|
||||
LOG.exception(msg)
|
||||
raise e
|
||||
|
||||
@debugger
|
||||
def get_pool(self, share):
|
||||
"""Returns pool name where share resides.
|
||||
|
||||
:param share: The share hosted by the driver.
|
||||
:return: Name of the pool where given share is hosted.
|
||||
"""
|
||||
pool = share_utils.extract_host(share['host'], level='pool')
|
||||
return pool
|
||||
|
||||
@debugger
|
||||
def get_network_allocations_number(self):
|
||||
"""Get number of network interfaces to be created."""
|
||||
return 0
|
||||
|
||||
@debugger
|
||||
def _get_location_path(self, share_name, share_proto, ip=None):
|
||||
if ip is None:
|
||||
ip = self._hostname
|
||||
if share_proto == 'NFS':
|
||||
location = '%s:%s' % (ip, share_name)
|
||||
elif share_proto == 'CIFS':
|
||||
location = r'\\%s\%s' % (ip, share_name)
|
||||
else:
|
||||
message = _('Invalid NAS protocol supplied: %s.') % share_proto
|
||||
raise exception.InvalidInput(message)
|
||||
|
||||
export_location = {
|
||||
'path': location,
|
||||
'is_admin_only': False,
|
||||
'metadata': {
|
||||
'preferred': True,
|
||||
},
|
||||
}
|
||||
return export_location
|
||||
|
||||
@debugger
|
||||
def _get_pool_project_share_name(self, share):
|
||||
pool = share_utils.extract_host(share['host'], level='pool')
|
||||
project = self._default_project
|
||||
|
||||
share_name = share['name']
|
||||
|
||||
return pool, project, share_name
|
0
manila/tests/share/drivers/tegile/__init__.py
Normal file
0
manila/tests/share/drivers/tegile/__init__.py
Normal file
834
manila/tests/share/drivers/tegile/test_tegile.py
Normal file
834
manila/tests/share/drivers/tegile/test_tegile.py
Normal file
@ -0,0 +1,834 @@
|
||||
# Copyright (c) 2016 by Tegile Systems, 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.
|
||||
"""
|
||||
Share driver Test for Tegile storage.
|
||||
"""
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
import requests
|
||||
import six
|
||||
|
||||
from manila.common import constants as const
|
||||
from manila import context
|
||||
from manila import exception
|
||||
from manila.exception import TegileAPIException
|
||||
from manila.share.configuration import Configuration
|
||||
from manila.share.drivers.tegile import tegile
|
||||
from manila import test
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
test_config = Configuration(None)
|
||||
test_config.tegile_nas_server = 'some-ip'
|
||||
test_config.tegile_nas_login = 'some-user'
|
||||
test_config.tegile_nas_password = 'some-password'
|
||||
test_config.reserved_share_percentage = 10
|
||||
test_config.max_over_subscription_ratio = 30.0
|
||||
|
||||
test_share = {
|
||||
'host': 'node#fake_pool',
|
||||
'name': 'testshare',
|
||||
'id': 'a24c2ee8-525a-4406-8ccd-8d38688f8e9e',
|
||||
'share_proto': 'NFS',
|
||||
'size': 10,
|
||||
}
|
||||
|
||||
test_share_cifs = {
|
||||
'host': 'node#fake_pool',
|
||||
'name': 'testshare',
|
||||
'id': 'a24c2ee8-525a-4406-8ccd-8d38688f8e9e',
|
||||
'share_proto': 'CIFS',
|
||||
'size': 10,
|
||||
}
|
||||
|
||||
test_share_fail = {
|
||||
'host': 'node#fake_pool',
|
||||
'name': 'testshare',
|
||||
'id': 'a24c2ee8-525a-4406-8ccd-8d38688f8e9e',
|
||||
'share_proto': 'OTHER',
|
||||
'size': 10,
|
||||
}
|
||||
|
||||
test_snapshot = {
|
||||
'name': 'testSnap',
|
||||
'id': '07ae9978-5445-405e-8881-28f2adfee732',
|
||||
'share': test_share,
|
||||
'share_name': 'snapshotted',
|
||||
'display_name': 'disp',
|
||||
'display_description': 'disp-desc',
|
||||
}
|
||||
|
||||
array_stats = {
|
||||
'total_capacity_gb': 4569.199686084874,
|
||||
'free_capacity_gb': 4565.381390112452,
|
||||
'pools': [
|
||||
{
|
||||
'total_capacity_gb': 913.5,
|
||||
'QoS_support': False,
|
||||
'free_capacity_gb': 911.812650680542,
|
||||
'reserved_percentage': 0,
|
||||
'pool_name': 'pyramid',
|
||||
},
|
||||
{
|
||||
'total_capacity_gb': 2742.1996604874,
|
||||
'QoS_support': False,
|
||||
'free_capacity_gb': 2740.148867149747,
|
||||
'reserved_percentage': 0,
|
||||
'pool_name': 'cobalt',
|
||||
},
|
||||
{
|
||||
'total_capacity_gb': 913.5,
|
||||
'QoS_support': False,
|
||||
'free_capacity_gb': 913.4198722839355,
|
||||
'reserved_percentage': 0,
|
||||
'pool_name': 'test',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
fake_tegile_backend_fail = mock.Mock(
|
||||
side_effect=TegileAPIException(response="Fake Exception"))
|
||||
|
||||
|
||||
class FakeResponse(object):
|
||||
def __init__(self, status, json_output):
|
||||
self.status_code = status
|
||||
self.text = 'Random text'
|
||||
self._json = json_output
|
||||
|
||||
def json(self):
|
||||
return self._json
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TegileShareDriverTestCase(test.TestCase):
|
||||
def __init__(self, *args, **kwds):
|
||||
super(TegileShareDriverTestCase, self).__init__(*args, **kwds)
|
||||
self._ctxt = context.get_admin_context()
|
||||
self.configuration = test_config
|
||||
|
||||
def setUp(self):
|
||||
CONF.set_default('driver_handles_share_servers', False)
|
||||
self._driver = tegile.TegileShareDriver(
|
||||
configuration=self.configuration)
|
||||
self._driver._default_project = 'fake_project'
|
||||
super(TegileShareDriverTestCase, self).setUp()
|
||||
|
||||
def test_create_share(self):
|
||||
api_return_value = (test_config.tegile_nas_server +
|
||||
" " + test_share['name'])
|
||||
mock_api = self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
return_value=api_return_value))
|
||||
|
||||
result = self._driver.create_share(self._ctxt, test_share)
|
||||
|
||||
expected = {
|
||||
'is_admin_only': False,
|
||||
'metadata': {
|
||||
'preferred': True,
|
||||
},
|
||||
'path': 'some-ip:testshare',
|
||||
}
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
create_params = (
|
||||
'fake_pool',
|
||||
'fake_project',
|
||||
test_share['name'],
|
||||
test_share['share_proto'],
|
||||
)
|
||||
mock_api.assert_called_once_with('createShare', create_params)
|
||||
|
||||
def test_create_share_fail(self):
|
||||
mock_api = self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
side_effect=TegileAPIException(
|
||||
response="Fake Exception")))
|
||||
|
||||
self.assertRaises(TegileAPIException,
|
||||
self._driver.create_share,
|
||||
self._ctxt,
|
||||
test_share)
|
||||
|
||||
create_params = (
|
||||
'fake_pool',
|
||||
'fake_project',
|
||||
test_share['name'],
|
||||
test_share['share_proto'],
|
||||
)
|
||||
mock_api.assert_called_once_with('createShare', create_params)
|
||||
|
||||
def test_delete_share(self):
|
||||
fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
|
||||
mock_params = self.mock_object(self._driver,
|
||||
'_get_pool_project_share_name',
|
||||
mock.Mock(return_value=fake_share_info))
|
||||
mock_api = self.mock_object(self._driver, '_api')
|
||||
|
||||
self._driver.delete_share(self._ctxt, test_share)
|
||||
|
||||
delete_path = '%s/%s/%s/%s' % (
|
||||
'fake_pool', 'Local', 'fake_project', test_share['name'])
|
||||
delete_params = (delete_path, True, False)
|
||||
mock_api.assert_called_once_with('deleteShare', delete_params)
|
||||
mock_params.assert_called_once_with(test_share)
|
||||
|
||||
def test_delete_share_fail(self):
|
||||
mock_api = self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
side_effect=TegileAPIException(
|
||||
response="Fake Exception")))
|
||||
|
||||
self.assertRaises(TegileAPIException,
|
||||
self._driver.delete_share,
|
||||
self._ctxt,
|
||||
test_share)
|
||||
|
||||
delete_path = '%s/%s/%s/%s' % (
|
||||
'fake_pool', 'Local', 'fake_project', test_share['name'])
|
||||
delete_params = (delete_path, True, False)
|
||||
mock_api.assert_called_once_with('deleteShare', delete_params)
|
||||
|
||||
def test_create_snapshot(self):
|
||||
mock_api = self.mock_object(self._driver, '_api')
|
||||
fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
|
||||
mock_params = self.mock_object(self._driver,
|
||||
'_get_pool_project_share_name',
|
||||
mock.Mock(return_value=fake_share_info))
|
||||
|
||||
self._driver.create_snapshot(self._ctxt, test_snapshot)
|
||||
|
||||
share = {
|
||||
'poolName': 'fake_pool',
|
||||
'projectName': 'fake_project',
|
||||
'name': test_share['name'],
|
||||
'availableSize': 0,
|
||||
'totalSize': 0,
|
||||
'datasetPath': '%s/%s/%s' % (
|
||||
'fake_pool',
|
||||
'Local',
|
||||
'fake_project',
|
||||
),
|
||||
'mountpoint': test_share['name'],
|
||||
'local': 'true',
|
||||
}
|
||||
create_params = (share, test_snapshot['name'], False)
|
||||
mock_api.assert_called_once_with('createShareSnapshot', create_params)
|
||||
mock_params.assert_called_once_with(test_share)
|
||||
|
||||
def test_create_snapshot_fail(self):
|
||||
mock_api = self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
side_effect=TegileAPIException(
|
||||
response="Fake Exception")))
|
||||
|
||||
self.assertRaises(TegileAPIException,
|
||||
self._driver.create_snapshot,
|
||||
self._ctxt,
|
||||
test_snapshot)
|
||||
|
||||
share = {
|
||||
'poolName': 'fake_pool',
|
||||
'projectName': 'fake_project',
|
||||
'name': test_share['name'],
|
||||
'availableSize': 0,
|
||||
'totalSize': 0,
|
||||
'datasetPath': '%s/%s/%s' % (
|
||||
'fake_pool',
|
||||
'Local',
|
||||
'fake_project',
|
||||
),
|
||||
'mountpoint': test_share['name'],
|
||||
'local': 'true',
|
||||
}
|
||||
create_params = (share, test_snapshot['name'], False)
|
||||
mock_api.assert_called_once_with('createShareSnapshot', create_params)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
|
||||
mock_params = self.mock_object(self._driver,
|
||||
'_get_pool_project_share_name',
|
||||
mock.Mock(return_value=fake_share_info))
|
||||
mock_api = self.mock_object(self._driver, '_api')
|
||||
|
||||
self._driver.delete_snapshot(self._ctxt, test_snapshot)
|
||||
|
||||
delete_snap_path = ('%s/%s/%s/%s@%s%s' % (
|
||||
'fake_pool',
|
||||
'Local',
|
||||
'fake_project',
|
||||
test_share['name'],
|
||||
'Manual-S-',
|
||||
test_snapshot['name'],
|
||||
))
|
||||
|
||||
delete_params = (delete_snap_path, False)
|
||||
mock_api.assert_called_once_with('deleteShareSnapshot', delete_params)
|
||||
mock_params.assert_called_once_with(test_share)
|
||||
|
||||
def test_delete_snapshot_fail(self):
|
||||
mock_api = self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
side_effect=TegileAPIException(
|
||||
response="Fake Exception")))
|
||||
|
||||
self.assertRaises(TegileAPIException,
|
||||
self._driver.delete_snapshot,
|
||||
self._ctxt,
|
||||
test_snapshot)
|
||||
|
||||
delete_snap_path = ('%s/%s/%s/%s@%s%s' % (
|
||||
'fake_pool',
|
||||
'Local',
|
||||
'fake_project',
|
||||
test_share['name'],
|
||||
'Manual-S-',
|
||||
test_snapshot['name'],
|
||||
))
|
||||
delete_params = (delete_snap_path, False)
|
||||
mock_api.assert_called_once_with('deleteShareSnapshot', delete_params)
|
||||
|
||||
def test_create_share_from_snapshot(self):
|
||||
api_return_value = (test_config.tegile_nas_server +
|
||||
" " + test_share['name'])
|
||||
mock_api = self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
return_value=api_return_value))
|
||||
fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
|
||||
mock_params = self.mock_object(self._driver,
|
||||
'_get_pool_project_share_name',
|
||||
mock.Mock(return_value=fake_share_info))
|
||||
|
||||
result = self._driver.create_share_from_snapshot(self._ctxt,
|
||||
test_share,
|
||||
test_snapshot)
|
||||
|
||||
expected = {
|
||||
'is_admin_only': False,
|
||||
'metadata': {
|
||||
'preferred': True,
|
||||
},
|
||||
'path': 'some-ip:testshare',
|
||||
}
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
create_params = (
|
||||
'%s/%s/%s/%s@%s%s' % (
|
||||
'fake_pool',
|
||||
'Local',
|
||||
'fake_project',
|
||||
test_snapshot['share_name'],
|
||||
'Manual-S-',
|
||||
test_snapshot['name'],
|
||||
),
|
||||
test_share['name'],
|
||||
True,
|
||||
)
|
||||
mock_api.assert_called_once_with('cloneShareSnapshot', create_params)
|
||||
mock_params.assert_called_once_with(test_share)
|
||||
|
||||
def test_create_share_from_snapshot_fail(self):
|
||||
mock_api = self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
side_effect=TegileAPIException(
|
||||
response="Fake Exception")))
|
||||
|
||||
self.assertRaises(TegileAPIException,
|
||||
self._driver.create_share_from_snapshot,
|
||||
self._ctxt,
|
||||
test_share,
|
||||
test_snapshot)
|
||||
|
||||
create_params = (
|
||||
'%s/%s/%s/%s@%s%s' % (
|
||||
'fake_pool',
|
||||
'Local',
|
||||
'fake_project',
|
||||
test_snapshot['share_name'],
|
||||
'Manual-S-',
|
||||
test_snapshot['name'],
|
||||
),
|
||||
test_share['name'],
|
||||
True,
|
||||
)
|
||||
mock_api.assert_called_once_with('cloneShareSnapshot', create_params)
|
||||
|
||||
def test_ensure_share(self):
|
||||
api_return_value = (test_config.tegile_nas_server +
|
||||
" " + test_share['name'])
|
||||
mock_api = self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
return_value=api_return_value))
|
||||
fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
|
||||
mock_params = self.mock_object(self._driver,
|
||||
'_get_pool_project_share_name',
|
||||
mock.Mock(return_value=fake_share_info))
|
||||
|
||||
result = self._driver.ensure_share(self._ctxt, test_share)
|
||||
|
||||
expected = [
|
||||
{
|
||||
'is_admin_only': False,
|
||||
'metadata': {
|
||||
'preferred':
|
||||
True,
|
||||
},
|
||||
'path': 'some-ip:testshare',
|
||||
},
|
||||
]
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
ensure_params = [
|
||||
'%s/%s/%s/%s' % (
|
||||
'fake_pool', 'Local', 'fake_project', test_share['name'])]
|
||||
mock_api.assert_called_once_with('getShareIPAndMountPoint',
|
||||
ensure_params)
|
||||
mock_params.assert_called_once_with(test_share)
|
||||
|
||||
def test_ensure_share_fail(self):
|
||||
mock_api = self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
side_effect=TegileAPIException(
|
||||
response="Fake Exception")))
|
||||
self.assertRaises(TegileAPIException,
|
||||
self._driver.ensure_share,
|
||||
self._ctxt,
|
||||
test_share)
|
||||
|
||||
ensure_params = [
|
||||
'%s/%s/%s/%s' % (
|
||||
'fake_pool', 'Local', 'fake_project', test_share['name'])]
|
||||
mock_api.assert_called_once_with('getShareIPAndMountPoint',
|
||||
ensure_params)
|
||||
|
||||
def test_get_share_stats(self):
|
||||
mock_api = self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
return_value=array_stats))
|
||||
|
||||
result_dict = self._driver.get_share_stats(True)
|
||||
|
||||
expected_dict = {
|
||||
'driver_handles_share_servers': False,
|
||||
'driver_version': '1.0.0',
|
||||
'free_capacity_gb': 4565.381390112452,
|
||||
'pools': [
|
||||
{
|
||||
'allocated_capacity_gb': 0.0,
|
||||
'compression': True,
|
||||
'dedupe': True,
|
||||
'free_capacity_gb': 911.812650680542,
|
||||
'pool_name': 'pyramid',
|
||||
'qos': False,
|
||||
'reserved_percentage': 10,
|
||||
'thin_provisioning': True,
|
||||
'max_over_subscription_ratio': 30.0,
|
||||
'total_capacity_gb': 913.5},
|
||||
{
|
||||
'allocated_capacity_gb': 0.0,
|
||||
'compression': True,
|
||||
'dedupe': True,
|
||||
'free_capacity_gb': 2740.148867149747,
|
||||
'pool_name': 'cobalt',
|
||||
'qos': False,
|
||||
'reserved_percentage': 10,
|
||||
'thin_provisioning': True,
|
||||
'max_over_subscription_ratio': 30.0,
|
||||
'total_capacity_gb': 2742.1996604874
|
||||
},
|
||||
{
|
||||
'allocated_capacity_gb': 0.0,
|
||||
'compression': True,
|
||||
'dedupe': True,
|
||||
'free_capacity_gb': 913.4198722839355,
|
||||
'pool_name': 'test',
|
||||
'qos': False,
|
||||
'reserved_percentage': 10,
|
||||
'thin_provisioning': True,
|
||||
'max_over_subscription_ratio': 30.0,
|
||||
'total_capacity_gb': 913.5}, ],
|
||||
'qos': False,
|
||||
'reserved_percentage': 0,
|
||||
'replication_domain': None,
|
||||
'share_backend_name': 'Tegile',
|
||||
'snapshot_support': True,
|
||||
'storage_protocol': 'NFS_CIFS',
|
||||
'total_capacity_gb': 4569.199686084874,
|
||||
'vendor_name': 'Tegile Systems Inc.',
|
||||
}
|
||||
self.assertSubDictMatch(expected_dict, result_dict)
|
||||
|
||||
mock_api.assert_called_once_with(fine_logging=False,
|
||||
method='getArrayStats',
|
||||
request_type='get')
|
||||
|
||||
def test_get_share_stats_fail(self):
|
||||
mock_api = self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
side_effect=TegileAPIException(
|
||||
response="Fake Exception")))
|
||||
|
||||
self.assertRaises(TegileAPIException,
|
||||
self._driver.get_share_stats,
|
||||
True)
|
||||
|
||||
mock_api.assert_called_once_with(fine_logging=False,
|
||||
method='getArrayStats',
|
||||
request_type='get')
|
||||
|
||||
def test_get_pool(self):
|
||||
result = self._driver.get_pool(test_share)
|
||||
|
||||
expected = 'fake_pool'
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_extend_share(self):
|
||||
fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
|
||||
mock_params = self.mock_object(self._driver,
|
||||
'_get_pool_project_share_name',
|
||||
mock.Mock(return_value=fake_share_info))
|
||||
mock_api = self.mock_object(self._driver, '_api')
|
||||
|
||||
self._driver.extend_share(test_share, 12)
|
||||
|
||||
extend_path = '%s/%s/%s/%s' % (
|
||||
'fake_pool', 'Local', 'fake_project', test_share['name'])
|
||||
extend_params = (extend_path, six.text_type(12), 'GB')
|
||||
mock_api.assert_called_once_with('resizeShare', extend_params)
|
||||
mock_params.assert_called_once_with(test_share)
|
||||
|
||||
def test_extend_share_fail(self):
|
||||
mock_api = self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
side_effect=TegileAPIException(
|
||||
response="Fake Exception")))
|
||||
|
||||
self.assertRaises(TegileAPIException,
|
||||
self._driver.extend_share,
|
||||
test_share, 30)
|
||||
|
||||
extend_path = '%s/%s/%s/%s' % (
|
||||
'fake_pool', 'Local', 'fake_project', test_share['name'])
|
||||
extend_params = (extend_path, six.text_type(30), 'GB')
|
||||
mock_api.assert_called_once_with('resizeShare', extend_params)
|
||||
|
||||
def test_shrink_share(self):
|
||||
fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
|
||||
mock_params = self.mock_object(self._driver,
|
||||
'_get_pool_project_share_name',
|
||||
mock.Mock(return_value=fake_share_info))
|
||||
mock_api = self.mock_object(self._driver, '_api')
|
||||
|
||||
self._driver.shrink_share(test_share, 15)
|
||||
|
||||
shrink_path = '%s/%s/%s/%s' % (
|
||||
'fake_pool', 'Local', 'fake_project', test_share['name'])
|
||||
shrink_params = (shrink_path, six.text_type(15), 'GB')
|
||||
mock_api.assert_called_once_with('resizeShare', shrink_params)
|
||||
mock_params.assert_called_once_with(test_share)
|
||||
|
||||
def test_shrink_share_fail(self):
|
||||
mock_api = self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
side_effect=TegileAPIException(
|
||||
response="Fake Exception")))
|
||||
|
||||
self.assertRaises(TegileAPIException,
|
||||
self._driver.shrink_share,
|
||||
test_share, 30)
|
||||
|
||||
shrink_path = '%s/%s/%s/%s' % (
|
||||
'fake_pool', 'Local', 'fake_project', test_share['name'])
|
||||
shrink_params = (shrink_path, six.text_type(30), 'GB')
|
||||
mock_api.assert_called_once_with('resizeShare', shrink_params)
|
||||
|
||||
@ddt.data('ip', 'user')
|
||||
def test_allow_access(self, access_type):
|
||||
fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
|
||||
mock_params = self.mock_object(self._driver,
|
||||
'_get_pool_project_share_name',
|
||||
mock.Mock(return_value=fake_share_info))
|
||||
mock_api = self.mock_object(self._driver, '_api')
|
||||
|
||||
access = {
|
||||
'access_type': access_type,
|
||||
'access_level': const.ACCESS_LEVEL_RW,
|
||||
'access_to': 'some-ip',
|
||||
}
|
||||
|
||||
self._driver._allow_access(self._ctxt, test_share, access)
|
||||
|
||||
allow_params = (
|
||||
'%s/%s/%s/%s' % (
|
||||
'fake_pool',
|
||||
'Local',
|
||||
'fake_project',
|
||||
test_share['name'],
|
||||
),
|
||||
test_share['share_proto'],
|
||||
access_type,
|
||||
access['access_to'],
|
||||
access['access_level'],
|
||||
)
|
||||
mock_api.assert_called_once_with('shareAllowAccess', allow_params)
|
||||
mock_params.assert_called_once_with(test_share)
|
||||
|
||||
@ddt.data({'access_type': 'other', 'to': 'some-ip', 'share': test_share,
|
||||
'exception_type': exception.InvalidShareAccess},
|
||||
{'access_type': 'ip', 'to': 'some-ip', 'share': test_share,
|
||||
'exception_type': exception.TegileAPIException},
|
||||
{'access_type': 'ip', 'to': 'some-ip', 'share': test_share_cifs,
|
||||
'exception_type': exception.InvalidShareAccess},
|
||||
{'access_type': 'ip', 'to': 'some-ip', 'share': test_share_fail,
|
||||
'exception_type': exception.InvalidShareAccess})
|
||||
@ddt.unpack
|
||||
def test_allow_access_fail(self, access_type, to, share, exception_type):
|
||||
self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
side_effect=TegileAPIException(
|
||||
response="Fake Exception")))
|
||||
|
||||
access = {
|
||||
'access_type': access_type,
|
||||
'access_level': const.ACCESS_LEVEL_RW,
|
||||
'access_to': to,
|
||||
}
|
||||
|
||||
self.assertRaises(exception_type,
|
||||
self._driver._allow_access,
|
||||
self._ctxt,
|
||||
share,
|
||||
access)
|
||||
|
||||
@ddt.data('ip', 'user')
|
||||
def test_deny_access(self, access_type):
|
||||
fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
|
||||
mock_params = self.mock_object(self._driver,
|
||||
'_get_pool_project_share_name',
|
||||
mock.Mock(return_value=fake_share_info))
|
||||
mock_api = self.mock_object(self._driver, '_api')
|
||||
|
||||
access = {
|
||||
'access_type': access_type,
|
||||
'access_level': const.ACCESS_LEVEL_RW,
|
||||
'access_to': 'some-ip',
|
||||
}
|
||||
|
||||
self._driver._deny_access(self._ctxt, test_share, access)
|
||||
|
||||
deny_params = (
|
||||
'%s/%s/%s/%s' % (
|
||||
'fake_pool',
|
||||
'Local',
|
||||
'fake_project',
|
||||
test_share['name'],
|
||||
),
|
||||
test_share['share_proto'],
|
||||
access_type,
|
||||
access['access_to'],
|
||||
access['access_level'],
|
||||
)
|
||||
mock_api.assert_called_once_with('shareDenyAccess', deny_params)
|
||||
mock_params.assert_called_once_with(test_share)
|
||||
|
||||
@ddt.data({'access_type': 'other', 'to': 'some-ip', 'share': test_share,
|
||||
'exception_type': exception.InvalidShareAccess},
|
||||
{'access_type': 'ip', 'to': 'some-ip', 'share': test_share,
|
||||
'exception_type': exception.TegileAPIException},
|
||||
{'access_type': 'ip', 'to': 'some-ip', 'share': test_share_cifs,
|
||||
'exception_type': exception.InvalidShareAccess},
|
||||
{'access_type': 'ip', 'to': 'some-ip', 'share': test_share_fail,
|
||||
'exception_type': exception.InvalidShareAccess})
|
||||
@ddt.unpack
|
||||
def test_deny_access_fail(self, access_type, to, share, exception_type):
|
||||
self.mock_object(self._driver, '_api',
|
||||
mock.Mock(
|
||||
side_effect=TegileAPIException(
|
||||
response="Fake Exception")))
|
||||
|
||||
access = {
|
||||
'access_type': access_type,
|
||||
'access_level': const.ACCESS_LEVEL_RW,
|
||||
'access_to': to,
|
||||
}
|
||||
|
||||
self.assertRaises(exception_type,
|
||||
self._driver._deny_access,
|
||||
self._ctxt,
|
||||
share,
|
||||
access)
|
||||
|
||||
@ddt.data({'access_rules': [{'access_type': 'ip',
|
||||
'access_level': const.ACCESS_LEVEL_RW,
|
||||
'access_to': 'some-ip',
|
||||
}, ], 'add_rules': None,
|
||||
'delete_rules': None, 'call_name': 'shareAllowAccess'},
|
||||
{'access_rules': [], 'add_rules':
|
||||
[{'access_type': 'ip',
|
||||
'access_level': const.ACCESS_LEVEL_RW,
|
||||
'access_to': 'some-ip'}, ], 'delete_rules': [],
|
||||
'call_name': 'shareAllowAccess'},
|
||||
{'access_rules': [], 'add_rules': [], 'delete_rules':
|
||||
[{'access_type': 'ip',
|
||||
'access_level': const.ACCESS_LEVEL_RW,
|
||||
'access_to': 'some-ip', }, ],
|
||||
'call_name': 'shareDenyAccess'})
|
||||
@ddt.unpack
|
||||
def test_update_access(self, access_rules, add_rules,
|
||||
delete_rules, call_name):
|
||||
fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
|
||||
mock_params = self.mock_object(self._driver,
|
||||
'_get_pool_project_share_name',
|
||||
mock.Mock(return_value=fake_share_info))
|
||||
mock_api = self.mock_object(self._driver, '_api')
|
||||
|
||||
self._driver.update_access(self._ctxt,
|
||||
test_share,
|
||||
access_rules=access_rules,
|
||||
add_rules=add_rules,
|
||||
delete_rules=delete_rules)
|
||||
|
||||
allow_params = (
|
||||
'%s/%s/%s/%s' % (
|
||||
'fake_pool',
|
||||
'Local',
|
||||
'fake_project',
|
||||
test_share['name'],
|
||||
),
|
||||
test_share['share_proto'],
|
||||
'ip',
|
||||
'some-ip',
|
||||
const.ACCESS_LEVEL_RW,
|
||||
)
|
||||
if not (add_rules or delete_rules):
|
||||
clear_params = (
|
||||
'%s/%s/%s/%s' % (
|
||||
'fake_pool',
|
||||
'Local',
|
||||
'fake_project',
|
||||
test_share['name'],
|
||||
),
|
||||
test_share['share_proto'],
|
||||
)
|
||||
mock_api.assert_has_calls([mock.call('clearAccessRules',
|
||||
clear_params),
|
||||
mock.call(call_name,
|
||||
allow_params)])
|
||||
mock_params.assert_called_with(test_share)
|
||||
else:
|
||||
mock_api.assert_called_once_with(call_name, allow_params)
|
||||
mock_params.assert_called_once_with(test_share)
|
||||
|
||||
@ddt.data({'path': r'\\some-ip\shareName', 'share_proto': 'CIFS',
|
||||
'host': 'some-ip'},
|
||||
{'path': 'some-ip:shareName', 'share_proto': 'NFS',
|
||||
'host': 'some-ip'},
|
||||
{'path': 'some-ip:shareName', 'share_proto': 'NFS',
|
||||
'host': None})
|
||||
@ddt.unpack
|
||||
def test_get_location_path(self, path, share_proto, host):
|
||||
self._driver._hostname = 'some-ip'
|
||||
|
||||
result = self._driver._get_location_path('shareName',
|
||||
share_proto,
|
||||
host)
|
||||
expected = {
|
||||
'is_admin_only': False,
|
||||
'metadata': {
|
||||
'preferred': True,
|
||||
},
|
||||
'path': path,
|
||||
}
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_location_path_fail(self):
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self._driver._get_location_path,
|
||||
'shareName',
|
||||
'SOME',
|
||||
'some-ip')
|
||||
|
||||
def test_get_network_allocations_number(self):
|
||||
result = self._driver.get_network_allocations_number()
|
||||
|
||||
expected = 0
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
|
||||
class TegileAPIExecutorTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
self._api = tegile.TegileAPIExecutor("TestCase",
|
||||
test_config.tegile_nas_server,
|
||||
test_config.tegile_nas_login,
|
||||
test_config.tegile_nas_password)
|
||||
super(TegileAPIExecutorTestCase, self).setUp()
|
||||
|
||||
def test_send_api_post(self):
|
||||
json_output = {'value': 'abc'}
|
||||
|
||||
self.mock_object(requests, 'post',
|
||||
mock.Mock(return_value=FakeResponse(200,
|
||||
json_output)))
|
||||
result = self._api(method="Test", request_type='post', params='[]',
|
||||
fine_logging=True)
|
||||
|
||||
self.assertEqual(json_output, result)
|
||||
|
||||
def test_send_api_get(self):
|
||||
json_output = {'value': 'abc'}
|
||||
|
||||
self.mock_object(requests, 'get',
|
||||
mock.Mock(return_value=FakeResponse(200,
|
||||
json_output)))
|
||||
|
||||
result = self._api(method="Test",
|
||||
request_type='get',
|
||||
fine_logging=False)
|
||||
|
||||
self.assertEqual(json_output, result)
|
||||
|
||||
def test_send_api_get_fail(self):
|
||||
self.mock_object(requests, 'get',
|
||||
mock.Mock(return_value=FakeResponse(404, [])))
|
||||
|
||||
self.assertRaises(TegileAPIException,
|
||||
self._api,
|
||||
method="Test",
|
||||
request_type='get',
|
||||
fine_logging=False)
|
||||
|
||||
def test_send_api_value_error_fail(self):
|
||||
json_output = {'value': 'abc'}
|
||||
|
||||
self.mock_object(requests, 'post',
|
||||
mock.Mock(return_value=FakeResponse(200,
|
||||
json_output)))
|
||||
self.mock_object(FakeResponse, 'json',
|
||||
mock.Mock(side_effect=ValueError))
|
||||
|
||||
result = self._api(method="Test",
|
||||
request_type='post',
|
||||
fine_logging=False)
|
||||
|
||||
expected = ''
|
||||
self.assertEqual(expected, result)
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Added driver for Tegile IntelliFlash arrays.
|
||||
|
Loading…
x
Reference in New Issue
Block a user