summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2016-03-03 17:14:01 +0000
committerGerrit Code Review <review@openstack.org>2016-03-03 17:14:01 +0000
commitf11558ff16754f14d3ac0bf00e20e790d2daec16 (patch)
tree589cb2f689c6eda71cbb00bbfacb2343fdb7641a
parentcae5a0152c55b6e0ae06f75b21ae222ead69442b (diff)
parenta8c5b41ab48536ed1d4565379fbacdef4f8ed653 (diff)
Merge "Add share driver for Tegile IntelliFlash Arrays"2.0.0.0b3
-rw-r--r--doc/source/devref/index.rst1
-rw-r--r--doc/source/devref/share_back_ends_feature_support_mapping.rst6
-rw-r--r--doc/source/devref/tegile_driver.rst129
-rw-r--r--manila/exception.py6
-rw-r--r--manila/opts.py2
-rw-r--r--manila/share/drivers/tegile/__init__.py0
-rw-r--r--manila/share/drivers/tegile/tegile.py513
-rw-r--r--manila/tests/share/drivers/tegile/__init__.py0
-rw-r--r--manila/tests/share/drivers/tegile/test_tegile.py834
-rw-r--r--releasenotes/notes/add-tegile-driver-1859114513edb13e.yaml4
10 files changed, 1495 insertions, 0 deletions
diff --git a/doc/source/devref/index.rst b/doc/source/devref/index.rst
index 76f93e7..3954cd0 100644
--- a/doc/source/devref/index.rst
+++ b/doc/source/devref/index.rst
@@ -112,6 +112,7 @@ Share backends
112 hdfs_native_driver 112 hdfs_native_driver
113 hds_hnas_driver 113 hds_hnas_driver
114 hpe_3par_driver 114 hpe_3par_driver
115 tegile_driver
115 116
116Indices and tables 117Indices and tables
117------------------ 118------------------
diff --git a/doc/source/devref/share_back_ends_feature_support_mapping.rst b/doc/source/devref/share_back_ends_feature_support_mapping.rst
index 41975b2..ed7ef62 100644
--- a/doc/source/devref/share_back_ends_feature_support_mapping.rst
+++ b/doc/source/devref/share_back_ends_feature_support_mapping.rst
@@ -69,6 +69,8 @@ Mapping of share drivers and share features support
69+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+ 69+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
70| CephFS Native | DHSS = False (M) | \- | M | M | M | \- | \- | 70| CephFS Native | DHSS = False (M) | \- | M | M | M | \- | \- |
71+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+ 71+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
72| Tegile | DHSS = False (M) | \- | M | M | M | M | \- |
73+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
72 74
73.. note:: 75.. note::
74 76
@@ -118,6 +120,8 @@ Mapping of share drivers and share access rules support
118+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+ 120+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
119| CephFS Native | \- | \- | \- | CEPH(M) | \- | \- | \- | \- | 121| CephFS Native | \- | \- | \- | CEPH(M) | \- | \- | \- | \- |
120+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+ 122+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
123| Tegile | NFS (M) |NFS (M),CIFS (M)| \- | \- | NFS (M) |NFS (M),CIFS (M)| \- | \- |
124+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
121 125
122Mapping of share drivers and security services support 126Mapping of share drivers and security services support
123------------------------------------------------------ 127------------------------------------------------------
@@ -161,4 +165,6 @@ Mapping of share drivers and security services support
161+----------------------------------------+------------------+-----------------+------------------+ 165+----------------------------------------+------------------+-----------------+------------------+
162| CephFS Native | \- | \- | \- | 166| CephFS Native | \- | \- | \- |
163+----------------------------------------+------------------+-----------------+------------------+ 167+----------------------------------------+------------------+-----------------+------------------+
168| Tegile | \- | \- | \- |
169+----------------------------------------+------------------+-----------------+------------------+
164 170
diff --git a/doc/source/devref/tegile_driver.rst b/doc/source/devref/tegile_driver.rst
new file mode 100644
index 0000000..55a4e6e
--- /dev/null
+++ b/doc/source/devref/tegile_driver.rst
@@ -0,0 +1,129 @@
1..
2 Copyright (c) 2016 Tegile Systems Inc.
3 All Rights Reserved.
4
5 Licensed under the Apache License, Version 2.0 (the "License"); you may
6 not use this file except in compliance with the License. You may obtain
7 a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 License for the specific language governing permissions and limitations
15 under the License.
16
17Tegile Driver
18=============
19
20The Tegile Manila driver uses Tegile IntelliFlash Arrays to provide shared
21filesystems to OpenStack.
22
23The Tegile Driver interfaces with a Tegile Array via the REST API.
24
25Requirements
26------------
27
28- Tegile IntelliFlash version 3.5.1
29- For using CIFS, Active Directory must be configured in the Tegile Array.
30
31Supported Operations
32--------------------
33
34The following operations are supported on a Tegile Array:
35
36* Create CIFS/NFS Share
37* Delete CIFS/NFS Share
38* Allow CIFS/NFS Share access
39 * Only IP access type is supported for NFS
40 * USER access type is supported for NFS and CIFS
41 * RW and RO access supported
42* Deny CIFS/NFS Share access
43 * IP access type is supported for NFS
44 * USER access type is supported for NFS and CIFS
45* Create snapshot
46* Delete snapshot
47* Extend share
48* Shrink share
49* Create share from snapshot
50
51Backend Configuration
52---------------------
53
54The following parameters need to be configured in the [DEFAULT]
55section of */etc/manila/manila.conf*:
56
57+-----------------------------------------------------------------------------------------------------------------------------------+
58| [DEFAULT] |
59+============================+======================================================================================================+
60| **Option** | **Description** |
61+----------------------------+-----------+------------------------------------------------------------------------------------------+
62| enabled_share_backends | Name of the section on manila.conf used to specify a backend. |
63| | E.g. *enabled_share_backends = tegileNAS* |
64+----------------------------+------------------------------------------------------------------------------------------------------+
65| enabled_share_protocols | Specify a list of protocols to be allowed for share creation. For Tegile driver this can be: |
66| | *NFS* or *CIFS* or *NFS, CIFS*. |
67+----------------------------+------------------------------------------------------------------------------------------------------+
68
69The following parameters need to be configured in the [backend] section of */etc/manila/manila.conf*:
70
71+-------------------------------------------------------------------------------------------------------------------------------------+
72| [tegileNAS] |
73+===============================+=====================================================================================================+
74| **Option** | **Description** |
75+-------------------------------+-----------------------------------------------------------------------------------------------------+
76| share_backend_name | A name for the backend. |
77+-------------------------------+-----------------------------------------------------------------------------------------------------+
78| share_driver | Python module path. For Tegile driver this must be: |
79| | *manila.share.drivers.tegile.tegile.TegileShareDriver*. |
80+-------------------------------+-----------------------------------------------------------------------------------------------------+
81| driver_handles_share_servers| DHSS, Driver working mode. For Tegile driver **this must be**: |
82| | *False*. |
83+-------------------------------+-----------------------------------------------------------------------------------------------------+
84| tegile_nas_server | Tegile array IP to connect from the Manila node. |
85+-------------------------------+-----------------------------------------------------------------------------------------------------+
86| tegile_nas_login | This field is used to provide username credential to Tegile array. |
87+-------------------------------+-----------------------------------------------------------------------------------------------------+
88| tegile_nas_password | This field is used to provide password credential to Tegile array. |
89+-------------------------------+-----------------------------------------------------------------------------------------------------+
90| tegile_default_project | This field can be used to specify the default project in Tegile array where shares are created. |
91| | This field is optional. |
92+-------------------------------+-----------------------------------------------------------------------------------------------------+
93
94Below is an example of a valid configuration of Tegile driver:
95
96| ``[DEFAULT]``
97| ``enabled_share_backends = tegileNAS``
98| ``enabled_share_protocols = NFS,CIFS``
99
100| ``[tegileNAS]``
101| ``driver_handles_share_servers = False``
102| ``share_backend_name = tegileNAS``
103| ``share_driver = manila.share.drivers.tegile.tegile.TegileShareDriver``
104| ``tegile_nas_server = 10.12.14.16``
105| ``tegile_nas_login = admin``
106| ``tegile_nas_password = password``
107| ``tegile_default_project = financeshares``
108
109Restart of :term:`manila-share` service is needed for the configuration changes
110to take effect.
111
112Restrictions
113------------
114
115The Tegile driver has the following restrictions:
116
117- IP access type is supported only for NFS.
118
119- Only FLAT network is supported.
120
121The :mod:`manila.share.drivers.tegile.tegile` Module
122~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
123
124.. automodule:: manila.share.drivers.tegile.tegile
125 :noindex:
126 :members:
127 :undoc-members:
128 :show-inheritance:
129 :exclude-members: TegileAPIExecutor, debugger
diff --git a/manila/exception.py b/manila/exception.py
index 22531dd..c6f50fd 100644
--- a/manila/exception.py
+++ b/manila/exception.py
@@ -768,3 +768,9 @@ class ReplicationException(ManilaException):
768 768
769class ShareReplicaNotFound(NotFound): 769class ShareReplicaNotFound(NotFound):
770 message = _("Share Replica %(replica_id)s could not be found.") 770 message = _("Share Replica %(replica_id)s could not be found.")
771
772
773# Tegile Storage drivers
774class TegileAPIException(ShareBackendException):
775 message = _("Unexpected response from Tegile IntelliFlash API: "
776 "%(response)s")
diff --git a/manila/opts.py b/manila/opts.py
index f8290a5..5b76019 100644
--- a/manila/opts.py
+++ b/manila/opts.py
@@ -68,6 +68,7 @@ import manila.share.drivers.lxd
68import manila.share.drivers.netapp.options 68import manila.share.drivers.netapp.options
69import manila.share.drivers.quobyte.quobyte 69import manila.share.drivers.quobyte.quobyte
70import manila.share.drivers.service_instance 70import manila.share.drivers.service_instance
71import manila.share.drivers.tegile.tegile
71import manila.share.drivers.windows.service_instance 72import manila.share.drivers.windows.service_instance
72import manila.share.drivers.windows.winrm_helper 73import manila.share.drivers.windows.winrm_helper
73import manila.share.drivers.zfsonlinux.driver 74import manila.share.drivers.zfsonlinux.driver
@@ -141,6 +142,7 @@ _global_opt_lists = [
141 manila.share.drivers.service_instance.common_opts, 142 manila.share.drivers.service_instance.common_opts,
142 manila.share.drivers.service_instance.no_share_servers_handling_mode_opts, 143 manila.share.drivers.service_instance.no_share_servers_handling_mode_opts,
143 manila.share.drivers.service_instance.share_servers_handling_mode_opts, 144 manila.share.drivers.service_instance.share_servers_handling_mode_opts,
145 manila.share.drivers.tegile.tegile.tegile_opts,
144 manila.share.drivers.windows.service_instance.windows_share_server_opts, 146 manila.share.drivers.windows.service_instance.windows_share_server_opts,
145 manila.share.drivers.windows.winrm_helper.winrm_opts, 147 manila.share.drivers.windows.winrm_helper.winrm_opts,
146 manila.share.drivers.zfsonlinux.driver.zfsonlinux_opts, 148 manila.share.drivers.zfsonlinux.driver.zfsonlinux_opts,
diff --git a/manila/share/drivers/tegile/__init__.py b/manila/share/drivers/tegile/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/manila/share/drivers/tegile/__init__.py
diff --git a/manila/share/drivers/tegile/tegile.py b/manila/share/drivers/tegile/tegile.py
new file mode 100644
index 0000000..0cdb2a7
--- /dev/null
+++ b/manila/share/drivers/tegile/tegile.py
@@ -0,0 +1,513 @@
1# Copyright (c) 2016 by Tegile Systems, Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15"""
16Share driver for Tegile storage.
17"""
18
19import json
20import requests
21import six
22
23from oslo_config import cfg
24from oslo_log import log
25
26from manila import utils
27from manila.i18n import _, _LI, _LW
28from manila import exception
29from manila.share import driver
30from manila.share import utils as share_utils
31
32tegile_opts = [
33 cfg.StrOpt('tegile_nas_server',
34 help='Tegile NAS server hostname or IP address.'),
35 cfg.StrOpt('tegile_nas_login',
36 help='User name for the Tegile NAS server.'),
37 cfg.StrOpt('tegile_nas_password',
38 help='Password for the Tegile NAS server.'),
39 cfg.StrOpt('tegile_default_project',
40 help='Create shares in this project')]
41
42
43CONF = cfg.CONF
44CONF.register_opts(tegile_opts)
45
46LOG = log.getLogger(__name__)
47DEFAULT_API_SERVICE = 'openstack'
48TEGILE_API_PATH = 'zebi/api'
49TEGILE_LOCAL_CONTAINER_NAME = 'Local'
50TEGILE_SNAPSHOT_PREFIX = 'Manual-S-'
51VENDOR = 'Tegile Systems Inc.'
52DEFAULT_BACKEND_NAME = 'Tegile'
53VERSION = '1.0.0'
54DEBUG_LOGGING = False # For debugging purposes
55
56
57def debugger(func):
58 """Returns a wrapper that wraps func.
59
60 The wrapper will log the entry and exit points of the function.
61 """
62
63 def wrapper(*args, **kwds):
64 if DEBUG_LOGGING:
65 LOG.debug('Entering %(classname)s.%(funcname)s',
66 {
67 'classname': args[0].__class__.__name__,
68 'funcname': func.__name__,
69 })
70 LOG.debug('Arguments: %(args)s, %(kwds)s',
71 {
72 'args': args[1:],
73 'kwds': kwds,
74 })
75 f_result = func(*args, **kwds)
76 if DEBUG_LOGGING:
77 LOG.debug('Exiting %(classname)s.%(funcname)s',
78 {
79 'classname': args[0].__class__.__name__,
80 'funcname': func.__name__,
81 })
82 LOG.debug('Results: %(result)s',
83 {'result': f_result})
84 return f_result
85
86 return wrapper
87
88
89class TegileAPIExecutor(object):
90 def __init__(self, classname, hostname, username, password):
91 self._classname = classname
92 self._hostname = hostname
93 self._username = username
94 self._password = password
95
96 def __call__(self, *args, **kwargs):
97 return self._send_api_request(*args, **kwargs)
98
99 @debugger
100 @utils.retry(exception=(requests.ConnectionError, requests.Timeout),
101 interval=30,
102 retries=3,
103 backoff_rate=1)
104 def _send_api_request(self, method, params=None,
105 request_type='post',
106 api_service=DEFAULT_API_SERVICE,
107 fine_logging=DEBUG_LOGGING):
108 if params is not None:
109 params = json.dumps(params)
110
111 url = 'https://%s/%s/%s/%s' % (self._hostname,
112 TEGILE_API_PATH,
113 api_service,
114 method)
115 if fine_logging:
116 LOG.debug('TegileAPIExecutor(%(classname)s) method: %(method)s, '
117 'url: %(url)s', {
118 'classname': self._classname,
119 'method': method,
120 'url': url,
121 })
122 if request_type == 'post':
123 if fine_logging:
124 LOG.debug('TegileAPIExecutor(%(classname)s) '
125 'method: %(method)s, payload: %(payload)s',
126 {
127 'classname': self._classname,
128 'method': method,
129 'payload': params,
130 })
131 req = requests.post(url,
132 data=params,
133 auth=(self._username, self._password),
134 verify=False)
135 else:
136 req = requests.get(url,
137 auth=(self._username, self._password),
138 verify=False)
139
140 if fine_logging:
141 LOG.debug('TegileAPIExecutor(%(classname)s) method: %(method)s, '
142 'return code: %(retcode)s',
143 {
144 'classname': self._classname,
145 'method': method,
146 'retcode': req,
147 })
148 try:
149 response = req.json()
150 if fine_logging:
151 LOG.debug('TegileAPIExecutor(%(classname)s) '
152 'method: %(method)s, response: %(response)s',
153 {
154 'classname': self._classname,
155 'method': method,
156 'response': response,
157 })
158 except ValueError:
159 # Some APIs don't return output and that's fine
160 response = ''
161 req.close()
162
163 if req.status_code != 200:
164 raise exception.TegileAPIException(response=req.text)
165
166 return response
167
168
169class TegileShareDriver(driver.ShareDriver):
170 """Tegile NAS driver. Allows for NFS and CIFS NAS storage usage."""
171 def __init__(self, *args, **kwargs):
172 super(TegileShareDriver, self).__init__(False, *args, **kwargs)
173
174 self.configuration.append_config_values(tegile_opts)
175 self._default_project = (self.configuration.safe_get(
176 "tegile_default_project") or 'openstack')
177 self._backend_name = (self.configuration.safe_get('share_backend_name')
178 or CONF.share_backend_name
179 or DEFAULT_BACKEND_NAME)
180 self._hostname = self.configuration.safe_get('tegile_nas_server')
181 username = self.configuration.safe_get('tegile_nas_login')
182 password = self.configuration.safe_get('tegile_nas_password')
183 self._api = TegileAPIExecutor(self.__class__.__name__,
184 self._hostname,
185 username,
186 password)
187
188 @debugger
189 def create_share(self, context, share, share_server=None):
190 """Is called to create share."""
191 share_name = share['name']
192 share_proto = share['share_proto']
193
194 pool_name = share_utils.extract_host(share['host'], level='pool')
195
196 params = (pool_name, self._default_project, share_name, share_proto)
197
198 # Share name coming from the backend is the most reliable. Sometimes
199 # a few options in Tegile array could cause sharename to be different
200 # from the one passed to it. Eg. 'projectname-sharename' instead
201 # of 'sharename' if inherited share properties are selected.
202 ip, real_share_name = self._api('createShare', params).split()
203
204 LOG.info(_LI("Created share %(sharename)s, share id %(shid)s."),
205 {'sharename': share_name, 'shid': share['id']})
206
207 return self._get_location_path(real_share_name, share_proto, ip)
208
209 @debugger
210 def extend_share(self, share, new_size, share_server=None):
211 """Is called to extend share.
212
213 There is no resize for Tegile shares.
214 We just adjust the quotas. The API is still called 'resizeShare'.
215 """
216
217 self._adjust_size(share, new_size, share_server)
218
219 @debugger
220 def shrink_share(self, shrink_share, shrink_size, share_server=None):
221 """Uses resize_share to shrink a share.
222
223 There is no shrink for Tegile shares.
224 We just adjust the quotas. The API is still called 'resizeShare'.
225 """
226 self._adjust_size(shrink_share, shrink_size, share_server)
227
228 @debugger
229 def _adjust_size(self, share, new_size, share_server=None):
230 pool, project, share_name = self._get_pool_project_share_name(share)
231 params = ('%s/%s/%s/%s' % (pool,
232 TEGILE_LOCAL_CONTAINER_NAME,
233 project,
234 share_name),
235 six.text_type(new_size),
236 'GB')
237 self._api('resizeShare', params)
238
239 @debugger
240 def delete_share(self, context, share, share_server=None):
241 """Is called to remove share."""
242 pool, project, share_name = self._get_pool_project_share_name(share)
243 params = ('%s/%s/%s/%s' % (pool,
244 TEGILE_LOCAL_CONTAINER_NAME,
245 project,
246 share_name),
247 True,
248 False)
249
250 self._api('deleteShare', params)
251
252 @debugger
253 def create_snapshot(self, context, snapshot, share_server=None):
254 """Is called to create snapshot."""
255 snap_name = snapshot['name']
256
257 pool, project, share_name = self._get_pool_project_share_name(
258 snapshot['share'])
259
260 share = {
261 'poolName': '%s' % pool,
262 'projectName': '%s' % project,
263 'name': share_name,
264 'availableSize': 0,
265 'totalSize': 0,
266 'datasetPath': '%s/%s/%s' %
267 (pool,
268 TEGILE_LOCAL_CONTAINER_NAME,
269 project),
270 'mountpoint': share_name,
271 'local': 'true',
272 }
273
274 params = (share, snap_name, False)
275
276 LOG.info(_LI('Creating snapshot for share_name=%(shr)s'
277 ' snap_name=%(name)s'),
278 {'shr': share_name, 'name': snap_name})
279
280 self._api('createShareSnapshot', params)
281
282 @debugger
283 def create_share_from_snapshot(self, context, share, snapshot,
284 share_server=None):
285 """Create a share from a snapshot - clone a snapshot."""
286 pool, project, share_name = self._get_pool_project_share_name(share)
287
288 params = ('%s/%s/%s/%s@%s%s' % (pool,
289 TEGILE_LOCAL_CONTAINER_NAME,
290 project,
291 snapshot['share_name'],
292 TEGILE_SNAPSHOT_PREFIX,
293 snapshot['name'],
294 ),
295 share_name,
296 True,
297 )
298
299 ip, real_share_name = self._api('cloneShareSnapshot',
300 params).split()
301
302 share_proto = share['share_proto']
303 return self._get_location_path(real_share_name, share_proto, ip)
304
305 @debugger
306 def delete_snapshot(self, context, snapshot, share_server=None):
307 """Is called to remove snapshot."""
308 pool, project, share_name = self._get_pool_project_share_name(
309 snapshot['share'])
310 params = ('%s/%s/%s/%s@%s%s' % (pool,
311 TEGILE_LOCAL_CONTAINER_NAME,
312 project,
313 share_name,
314 TEGILE_SNAPSHOT_PREFIX,
315 snapshot['name']),
316 False)
317
318 self._api('deleteShareSnapshot', params)
319
320 @debugger
321 def ensure_share(self, context, share, share_server=None):
322 """Invoked to sure that share is exported."""
323
324 # Fetching share name from server, because some configuration
325 # options can cause sharename different from the OpenStack share name
326 pool, project, share_name = self._get_pool_project_share_name(share)
327 params = [
328 '%s/%s/%s/%s' % (pool,
329 TEGILE_LOCAL_CONTAINER_NAME,
330 project,
331 share_name),
332 ]
333 ip, real_share_name = self._api('getShareIPAndMountPoint',
334 params).split()
335
336 share_proto = share['share_proto']
337 location = self._get_location_path(real_share_name, share_proto, ip)
338 return [location]
339
340 @debugger
341 def _allow_access(self, context, share, access, share_server=None):
342 """Allow access to the share."""
343 share_proto = share['share_proto']
344 access_type = access['access_type']
345 access_level = access['access_level']
346 access_to = access['access_to']
347
348 self._check_share_access(share_proto, access_type)
349
350 pool, project, share_name = self._get_pool_project_share_name(share)
351 params = ('%s/%s/%s/%s' % (pool,
352 TEGILE_LOCAL_CONTAINER_NAME,
353 project,
354 share_name),
355 share_proto,
356 access_type,
357 access_to,
358 access_level)
359
360 self._api('shareAllowAccess', params)
361
362 @debugger
363 def _deny_access(self, context, share, access, share_server=None):
364 """Deny access to the share."""
365 share_proto = share['share_proto']
366 access_type = access['access_type']
367 access_level = access['access_level']
368 access_to = access['access_to']
369
370 self._check_share_access(share_proto, access_type)
371
372 pool, project, share_name = self._get_pool_project_share_name(share)
373 params = ('%s/%s/%s/%s' % (pool,
374 TEGILE_LOCAL_CONTAINER_NAME,
375 project,
376 share_name),
377 share_proto,
378 access_type,
379 access_to,
380 access_level)
381
382 self._api('shareDenyAccess', params)
383
384 def _check_share_access(self, share_proto, access_type):
385 if share_proto == 'CIFS' and access_type != 'user':
386 reason = _LW('Only USER access type is allowed for '
387 'CIFS shares.')
388 LOG.warning(reason)
389 raise exception.InvalidShareAccess(reason=reason)
390 elif share_proto == 'NFS' and access_type not in ('ip', 'user'):
391 reason = _LW('Only IP or USER access types are allowed for '
392 'NFS shares.')
393 LOG.warning(reason)
394 raise exception.InvalidShareAccess(reason=reason)
395 elif share_proto not in ('NFS', 'CIFS'):
396 reason = _LW('Unsupported protocol \"%s\" specified for '
397 'access rule.') % share_proto
398 raise exception.InvalidShareAccess(reason=reason)
399
400 @debugger
401 def update_access(self, context, share, access_rules, add_rules=None,
402 delete_rules=None, share_server=None):
403 if not (add_rules or delete_rules):
404 # Recovery mode
405 pool, project, share_name = (
406 self._get_pool_project_share_name(share))
407 share_proto = share['share_proto']
408 params = ('%s/%s/%s/%s' % (pool,
409 TEGILE_LOCAL_CONTAINER_NAME,
410 project,
411 share_name),
412 share_proto)
413
414 # Clears all current ACLs
415 # Remove ip and user ACLs if share_proto is NFS
416 # Remove user ACLs if share_proto is CIFS
417 self._api('clearAccessRules', params)
418
419 # Looping thru all rules.
420 # Will have one API call per rule.
421 for access in access_rules:
422 self._allow_access(context, share, access, share_server)
423 else:
424 # Adding/Deleting specific rules
425 for access in delete_rules:
426 self._deny_access(context, share, access, share_server)
427 for access in add_rules:
428 self._allow_access(context, share, access, share_server)
429
430 @debugger
431 def _update_share_stats(self, **kwargs):
432 """Retrieve stats info."""
433
434 try:
435 data = self._api(method='getArrayStats',
436 request_type='get',
437 fine_logging=False)
438 # fixing values coming back here as String to float
439 for pool in data.get('pools', []):
440 pool['total_capacity_gb'] = float(
441 pool.get('total_capacity_gb', 0))
442 pool['free_capacity_gb'] = float(
443 pool.get('free_capacity_gb', 0))
444 pool['allocated_capacity_gb'] = float(
445 pool.get('allocated_capacity_gb', 0))
446
447 pool['qos'] = pool.pop('QoS_support', False)
448 pool['reserved_percentage'] = (
449 self.configuration.reserved_share_percentage)
450 pool['dedupe'] = True
451 pool['compression'] = True
452 pool['thin_provisioning'] = True
453 pool['max_over_subscription_ratio'] = (
454 self.configuration.max_over_subscription_ratio)
455
456 data['share_backend_name'] = self._backend_name
457 data['vendor_name'] = VENDOR
458 data['driver_version'] = VERSION
459 data['storage_protocol'] = 'NFS_CIFS'
460 data['snapshot_support'] = True
461 data['qos'] = False
462
463 super(TegileShareDriver, self)._update_share_stats(data)
464 except Exception as e:
465 msg = _('Unexpected error while trying to get the '
466 'usage stats from array.')
467 LOG.exception(msg)
468 raise e
469
470 @debugger
471 def get_pool(self, share):
472 """Returns pool name where share resides.
473
474 :param share: The share hosted by the driver.
475 :return: Name of the pool where given share is hosted.
476 """
477 pool = share_utils.extract_host(share['host'], level='pool')
478 return pool
479
480 @debugger
481 def get_network_allocations_number(self):
482 """Get number of network interfaces to be created."""
483 return 0
484
485 @debugger
486 def _get_location_path(self, share_name, share_proto, ip=None):
487 if ip is None:
488 ip = self._hostname
489 if share_proto == 'NFS':
490 location = '%s:%s' % (ip, share_name)
491 elif share_proto == 'CIFS':
492 location = r'\\%s\%s' % (ip, share_name)
493 else:
494 message = _('Invalid NAS protocol supplied: %s.') % share_proto
495 raise exception.InvalidInput(message)
496
497 export_location = {
498 'path': location,
499 'is_admin_only': False,
500 'metadata': {
501 'preferred': True,
502 },
503 }
504 return export_location
505
506 @debugger
507 def _get_pool_project_share_name(self, share):
508 pool = share_utils.extract_host(share['host'], level='pool')
509 project = self._default_project
510
511 share_name = share['name']
512
513 return pool, project, share_name
diff --git a/manila/tests/share/drivers/tegile/__init__.py b/manila/tests/share/drivers/tegile/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/manila/tests/share/drivers/tegile/__init__.py
diff --git a/manila/tests/share/drivers/tegile/test_tegile.py b/manila/tests/share/drivers/tegile/test_tegile.py
new file mode 100644
index 0000000..3467d54
--- /dev/null
+++ b/manila/tests/share/drivers/tegile/test_tegile.py
@@ -0,0 +1,834 @@
1# Copyright (c) 2016 by Tegile Systems, Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15"""
16Share driver Test for Tegile storage.
17"""
18
19import ddt
20import mock
21from oslo_config import cfg
22import requests
23import six
24
25from manila.common import constants as const
26from manila import context
27from manila import exception
28from manila.exception import TegileAPIException
29from manila.share.configuration import Configuration
30from manila.share.drivers.tegile import tegile
31from manila import test
32
33
34CONF = cfg.CONF
35
36test_config = Configuration(None)
37test_config.tegile_nas_server = 'some-ip'
38test_config.tegile_nas_login = 'some-user'
39test_config.tegile_nas_password = 'some-password'
40test_config.reserved_share_percentage = 10
41test_config.max_over_subscription_ratio = 30.0
42
43test_share = {
44 'host': 'node#fake_pool',
45 'name': 'testshare',
46 'id': 'a24c2ee8-525a-4406-8ccd-8d38688f8e9e',
47 'share_proto': 'NFS',
48 'size': 10,
49}
50
51test_share_cifs = {
52 'host': 'node#fake_pool',
53 'name': 'testshare',
54 'id': 'a24c2ee8-525a-4406-8ccd-8d38688f8e9e',
55 'share_proto': 'CIFS',
56 'size': 10,
57}
58
59test_share_fail = {
60 'host': 'node#fake_pool',
61 'name': 'testshare',
62 'id': 'a24c2ee8-525a-4406-8ccd-8d38688f8e9e',
63 'share_proto': 'OTHER',
64 'size': 10,
65}
66
67test_snapshot = {
68 'name': 'testSnap',
69 'id': '07ae9978-5445-405e-8881-28f2adfee732',
70 'share': test_share,
71 'share_name': 'snapshotted',
72 'display_name': 'disp',
73 'display_description': 'disp-desc',
74}
75
76array_stats = {
77 'total_capacity_gb': 4569.199686084874,
78 'free_capacity_gb': 4565.381390112452,
79 'pools': [
80 {
81 'total_capacity_gb': 913.5,
82 'QoS_support': False,
83 'free_capacity_gb': 911.812650680542,
84 'reserved_percentage': 0,
85 'pool_name': 'pyramid',
86 },
87 {
88 'total_capacity_gb': 2742.1996604874,
89 'QoS_support': False,
90 'free_capacity_gb': 2740.148867149747,
91 'reserved_percentage': 0,
92 'pool_name': 'cobalt',
93 },
94 {
95 'total_capacity_gb': 913.5,
96 'QoS_support': False,
97 'free_capacity_gb': 913.4198722839355,
98 'reserved_percentage': 0,
99 'pool_name': 'test',
100 },
101 ],
102}
103
104
105fake_tegile_backend_fail = mock.Mock(
106 side_effect=TegileAPIException(response="Fake Exception"))
107
108
109class FakeResponse(object):
110 def __init__(self, status, json_output):
111 self.status_code = status
112 self.text = 'Random text'
113 self._json = json_output
114
115 def json(self):
116 return self._json
117
118 def close(self):
119 pass
120
121
122@ddt.ddt
123class TegileShareDriverTestCase(test.TestCase):
124 def __init__(self, *args, **kwds):
125 super(TegileShareDriverTestCase, self).__init__(*args, **kwds)
126 self._ctxt = context.get_admin_context()
127 self.configuration = test_config
128
129 def setUp(self):
130 CONF.set_default('driver_handles_share_servers', False)
131 self._driver = tegile.TegileShareDriver(
132 configuration=self.configuration)
133 self._driver._default_project = 'fake_project'
134 super(TegileShareDriverTestCase, self).setUp()
135
136 def test_create_share(self):
137 api_return_value = (test_config.tegile_nas_server +
138 " " + test_share['name'])
139 mock_api = self.mock_object(self._driver, '_api',
140 mock.Mock(
141 return_value=api_return_value))
142
143 result = self._driver.create_share(self._ctxt, test_share)
144
145 expected = {
146 'is_admin_only': False,
147 'metadata': {
148 'preferred': True,
149 },
150 'path': 'some-ip:testshare',
151 }
152 self.assertEqual(expected, result)
153
154 create_params = (
155 'fake_pool',
156 'fake_project',
157 test_share['name'],
158 test_share['share_proto'],
159 )
160 mock_api.assert_called_once_with('createShare', create_params)
161
162 def test_create_share_fail(self):
163 mock_api = self.mock_object(self._driver, '_api',
164 mock.Mock(
165 side_effect=TegileAPIException(
166 response="Fake Exception")))
167
168 self.assertRaises(TegileAPIException,
169 self._driver.create_share,
170 self._ctxt,
171 test_share)
172
173 create_params = (
174 'fake_pool',
175 'fake_project',
176 test_share['name'],
177 test_share['share_proto'],
178 )
179 mock_api.assert_called_once_with('createShare', create_params)
180
181 def test_delete_share(self):
182 fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
183 mock_params = self.mock_object(self._driver,
184 '_get_pool_project_share_name',
185 mock.Mock(return_value=fake_share_info))
186 mock_api = self.mock_object(self._driver, '_api')
187
188 self._driver.delete_share(self._ctxt, test_share)
189
190 delete_path = '%s/%s/%s/%s' % (
191 'fake_pool', 'Local', 'fake_project', test_share['name'])
192 delete_params = (delete_path, True, False)
193 mock_api.assert_called_once_with('deleteShare', delete_params)
194 mock_params.assert_called_once_with(test_share)
195
196 def test_delete_share_fail(self):
197 mock_api = self.mock_object(self._driver, '_api',
198 mock.Mock(
199 side_effect=TegileAPIException(
200 response="Fake Exception")))
201
202 self.assertRaises(TegileAPIException,
203 self._driver.delete_share,
204 self._ctxt,
205 test_share)
206
207 delete_path = '%s/%s/%s/%s' % (
208 'fake_pool', 'Local', 'fake_project', test_share['name'])
209 delete_params = (delete_path, True, False)
210 mock_api.assert_called_once_with('deleteShare', delete_params)
211
212 def test_create_snapshot(self):
213 mock_api = self.mock_object(self._driver, '_api')
214 fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
215 mock_params = self.mock_object(self._driver,
216 '_get_pool_project_share_name',
217 mock.Mock(return_value=fake_share_info))
218
219 self._driver.create_snapshot(self._ctxt, test_snapshot)
220
221 share = {
222 'poolName': 'fake_pool',
223 'projectName': 'fake_project',
224 'name': test_share['name'],
225 'availableSize': 0,
226 'totalSize': 0,
227 'datasetPath': '%s/%s/%s' % (
228 'fake_pool',
229 'Local',
230 'fake_project',
231 ),
232 'mountpoint': test_share['name'],
233 'local': 'true',
234 }
235 create_params = (share, test_snapshot['name'], False)
236 mock_api.assert_called_once_with('createShareSnapshot', create_params)
237 mock_params.assert_called_once_with(test_share)
238
239 def test_create_snapshot_fail(self):
240 mock_api = self.mock_object(self._driver, '_api',
241 mock.Mock(
242 side_effect=TegileAPIException(
243 response="Fake Exception")))
244
245 self.assertRaises(TegileAPIException,
246 self._driver.create_snapshot,
247 self._ctxt,
248 test_snapshot)
249
250 share = {
251 'poolName': 'fake_pool',
252 'projectName': 'fake_project',
253 'name': test_share['name'],
254 'availableSize': 0,
255 'totalSize': 0,
256 'datasetPath': '%s/%s/%s' % (
257 'fake_pool',
258 'Local',
259 'fake_project',
260 ),
261 'mountpoint': test_share['name'],
262 'local': 'true',
263 }
264 create_params = (share, test_snapshot['name'], False)
265 mock_api.assert_called_once_with('createShareSnapshot', create_params)
266
267 def test_delete_snapshot(self):
268 fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
269 mock_params = self.mock_object(self._driver,
270 '_get_pool_project_share_name',
271 mock.Mock(return_value=fake_share_info))
272 mock_api = self.mock_object(self._driver, '_api')
273
274 self._driver.delete_snapshot(self._ctxt, test_snapshot)
275
276 delete_snap_path = ('%s/%s/%s/%s@%s%s' % (
277 'fake_pool',
278 'Local',
279 'fake_project',
280 test_share['name'],
281 'Manual-S-',
282 test_snapshot['name'],
283 ))
284
285 delete_params = (delete_snap_path, False)
286 mock_api.assert_called_once_with('deleteShareSnapshot', delete_params)
287 mock_params.assert_called_once_with(test_share)
288
289 def test_delete_snapshot_fail(self):
290 mock_api = self.mock_object(self._driver, '_api',
291 mock.Mock(
292 side_effect=TegileAPIException(
293 response="Fake Exception")))
294
295 self.assertRaises(TegileAPIException,
296 self._driver.delete_snapshot,
297 self._ctxt,
298 test_snapshot)
299
300 delete_snap_path = ('%s/%s/%s/%s@%s%s' % (
301 'fake_pool',
302 'Local',
303 'fake_project',
304 test_share['name'],
305 'Manual-S-',
306 test_snapshot['name'],
307 ))
308 delete_params = (delete_snap_path, False)
309 mock_api.assert_called_once_with('deleteShareSnapshot', delete_params)
310
311 def test_create_share_from_snapshot(self):
312 api_return_value = (test_config.tegile_nas_server +
313 " " + test_share['name'])
314 mock_api = self.mock_object(self._driver, '_api',
315 mock.Mock(
316 return_value=api_return_value))
317 fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
318 mock_params = self.mock_object(self._driver,
319 '_get_pool_project_share_name',
320 mock.Mock(return_value=fake_share_info))
321
322 result = self._driver.create_share_from_snapshot(self._ctxt,
323 test_share,
324 test_snapshot)
325
326 expected = {
327 'is_admin_only': False,
328 'metadata': {
329 'preferred': True,
330 },
331 'path': 'some-ip:testshare',
332 }
333 self.assertEqual(expected, result)
334
335 create_params = (
336 '%s/%s/%s/%s@%s%s' % (
337 'fake_pool',
338 'Local',
339 'fake_project',
340 test_snapshot['share_name'],
341 'Manual-S-',
342 test_snapshot['name'],
343 ),
344 test_share['name'],
345 True,
346 )
347 mock_api.assert_called_once_with('cloneShareSnapshot', create_params)
348 mock_params.assert_called_once_with(test_share)
349
350 def test_create_share_from_snapshot_fail(self):
351 mock_api = self.mock_object(self._driver, '_api',
352 mock.Mock(
353 side_effect=TegileAPIException(
354 response="Fake Exception")))
355
356 self.assertRaises(TegileAPIException,
357 self._driver.create_share_from_snapshot,
358 self._ctxt,
359 test_share,
360 test_snapshot)
361
362 create_params = (
363 '%s/%s/%s/%s@%s%s' % (
364 'fake_pool',
365 'Local',
366 'fake_project',
367 test_snapshot['share_name'],
368 'Manual-S-',
369 test_snapshot['name'],
370 ),
371 test_share['name'],
372 True,
373 )
374 mock_api.assert_called_once_with('cloneShareSnapshot', create_params)
375
376 def test_ensure_share(self):
377 api_return_value = (test_config.tegile_nas_server +
378 " " + test_share['name'])
379 mock_api = self.mock_object(self._driver, '_api',
380 mock.Mock(
381 return_value=api_return_value))
382 fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
383 mock_params = self.mock_object(self._driver,
384 '_get_pool_project_share_name',
385 mock.Mock(return_value=fake_share_info))
386
387 result = self._driver.ensure_share(self._ctxt, test_share)
388
389 expected = [
390 {
391 'is_admin_only': False,
392 'metadata': {
393 'preferred':
394 True,
395 },
396 'path': 'some-ip:testshare',
397 },
398 ]
399 self.assertEqual(expected, result)
400
401 ensure_params = [
402 '%s/%s/%s/%s' % (
403 'fake_pool', 'Local', 'fake_project', test_share['name'])]
404 mock_api.assert_called_once_with('getShareIPAndMountPoint',
405 ensure_params)
406 mock_params.assert_called_once_with(test_share)
407
408 def test_ensure_share_fail(self):
409 mock_api = self.mock_object(self._driver, '_api',
410 mock.Mock(
411 side_effect=TegileAPIException(
412 response="Fake Exception")))
413 self.assertRaises(TegileAPIException,
414 self._driver.ensure_share,
415 self._ctxt,
416 test_share)
417
418 ensure_params = [
419 '%s/%s/%s/%s' % (
420 'fake_pool', 'Local', 'fake_project', test_share['name'])]
421 mock_api.assert_called_once_with('getShareIPAndMountPoint',
422 ensure_params)
423
424 def test_get_share_stats(self):
425 mock_api = self.mock_object(self._driver, '_api',
426 mock.Mock(
427 return_value=array_stats))
428
429 result_dict = self._driver.get_share_stats(True)
430
431 expected_dict = {
432 'driver_handles_share_servers': False,
433 'driver_version': '1.0.0',
434 'free_capacity_gb': 4565.381390112452,
435 'pools': [
436 {
437 'allocated_capacity_gb': 0.0,
438 'compression': True,
439 'dedupe': True,
440 'free_capacity_gb': 911.812650680542,
441 'pool_name': 'pyramid',
442 'qos': False,
443 'reserved_percentage': 10,
444 'thin_provisioning': True,
445 'max_over_subscription_ratio': 30.0,
446 'total_capacity_gb': 913.5},
447 {
448 'allocated_capacity_gb': 0.0,
449 'compression': True,
450 'dedupe': True,
451 'free_capacity_gb': 2740.148867149747,
452 'pool_name': 'cobalt',
453 'qos': False,
454 'reserved_percentage': 10,
455 'thin_provisioning': True,
456 'max_over_subscription_ratio': 30.0,
457 'total_capacity_gb': 2742.1996604874
458 },
459 {
460 'allocated_capacity_gb': 0.0,
461 'compression': True,
462 'dedupe': True,
463 'free_capacity_gb': 913.4198722839355,
464 'pool_name': 'test',
465 'qos': False,
466 'reserved_percentage': 10,
467 'thin_provisioning': True,
468 'max_over_subscription_ratio': 30.0,
469 'total_capacity_gb': 913.5}, ],
470 'qos': False,
471 'reserved_percentage': 0,
472 'replication_domain': None,
473 'share_backend_name': 'Tegile',
474 'snapshot_support': True,
475 'storage_protocol': 'NFS_CIFS',
476 'total_capacity_gb': 4569.199686084874,
477 'vendor_name': 'Tegile Systems Inc.',
478 }
479 self.assertSubDictMatch(expected_dict, result_dict)
480
481 mock_api.assert_called_once_with(fine_logging=False,
482 method='getArrayStats',
483 request_type='get')
484
485 def test_get_share_stats_fail(self):
486 mock_api = self.mock_object(self._driver, '_api',
487 mock.Mock(
488 side_effect=TegileAPIException(
489 response="Fake Exception")))
490
491 self.assertRaises(TegileAPIException,
492 self._driver.get_share_stats,
493 True)
494
495 mock_api.assert_called_once_with(fine_logging=False,
496 method='getArrayStats',
497 request_type='get')
498
499 def test_get_pool(self):
500 result = self._driver.get_pool(test_share)
501
502 expected = 'fake_pool'
503 self.assertEqual(expected, result)
504
505 def test_extend_share(self):
506 fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
507 mock_params = self.mock_object(self._driver,
508 '_get_pool_project_share_name',
509 mock.Mock(return_value=fake_share_info))
510 mock_api = self.mock_object(self._driver, '_api')
511
512 self._driver.extend_share(test_share, 12)
513
514 extend_path = '%s/%s/%s/%s' % (
515 'fake_pool', 'Local', 'fake_project', test_share['name'])
516 extend_params = (extend_path, six.text_type(12), 'GB')
517 mock_api.assert_called_once_with('resizeShare', extend_params)
518 mock_params.assert_called_once_with(test_share)
519
520 def test_extend_share_fail(self):
521 mock_api = self.mock_object(self._driver, '_api',
522 mock.Mock(
523 side_effect=TegileAPIException(
524 response="Fake Exception")))
525
526 self.assertRaises(TegileAPIException,
527 self._driver.extend_share,
528 test_share, 30)
529
530 extend_path = '%s/%s/%s/%s' % (
531 'fake_pool', 'Local', 'fake_project', test_share['name'])
532 extend_params = (extend_path, six.text_type(30), 'GB')
533 mock_api.assert_called_once_with('resizeShare', extend_params)
534
535 def test_shrink_share(self):
536 fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
537 mock_params = self.mock_object(self._driver,
538 '_get_pool_project_share_name',
539 mock.Mock(return_value=fake_share_info))
540 mock_api = self.mock_object(self._driver, '_api')
541
542 self._driver.shrink_share(test_share, 15)
543
544 shrink_path = '%s/%s/%s/%s' % (
545 'fake_pool', 'Local', 'fake_project', test_share['name'])
546 shrink_params = (shrink_path, six.text_type(15), 'GB')
547 mock_api.assert_called_once_with('resizeShare', shrink_params)
548 mock_params.assert_called_once_with(test_share)
549
550 def test_shrink_share_fail(self):
551 mock_api = self.mock_object(self._driver, '_api',
552 mock.Mock(
553 side_effect=TegileAPIException(
554 response="Fake Exception")))
555
556 self.assertRaises(TegileAPIException,
557 self._driver.shrink_share,
558 test_share, 30)
559
560 shrink_path = '%s/%s/%s/%s' % (
561 'fake_pool', 'Local', 'fake_project', test_share['name'])
562 shrink_params = (shrink_path, six.text_type(30), 'GB')
563 mock_api.assert_called_once_with('resizeShare', shrink_params)
564
565 @ddt.data('ip', 'user')
566 def test_allow_access(self, access_type):
567 fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
568 mock_params = self.mock_object(self._driver,
569 '_get_pool_project_share_name',
570 mock.Mock(return_value=fake_share_info))
571 mock_api = self.mock_object(self._driver, '_api')
572
573 access = {
574 'access_type': access_type,
575 'access_level': const.ACCESS_LEVEL_RW,
576 'access_to': 'some-ip',
577 }
578
579 self._driver._allow_access(self._ctxt, test_share, access)
580
581 allow_params = (
582 '%s/%s/%s/%s' % (
583 'fake_pool',
584 'Local',
585 'fake_project',
586 test_share['name'],
587 ),
588 test_share['share_proto'],
589 access_type,
590 access['access_to'],
591 access['access_level'],
592 )
593 mock_api.assert_called_once_with('shareAllowAccess', allow_params)
594 mock_params.assert_called_once_with(test_share)
595
596 @ddt.data({'access_type': 'other', 'to': 'some-ip', 'share': test_share,
597 'exception_type': exception.InvalidShareAccess},
598 {'access_type': 'ip', 'to': 'some-ip', 'share': test_share,
599 'exception_type': exception.TegileAPIException},
600 {'access_type': 'ip', 'to': 'some-ip', 'share': test_share_cifs,
601 'exception_type': exception.InvalidShareAccess},
602 {'access_type': 'ip', 'to': 'some-ip', 'share': test_share_fail,
603 'exception_type': exception.InvalidShareAccess})
604 @ddt.unpack
605 def test_allow_access_fail(self, access_type, to, share, exception_type):
606 self.mock_object(self._driver, '_api',
607 mock.Mock(
608 side_effect=TegileAPIException(
609 response="Fake Exception")))
610
611 access = {
612 'access_type': access_type,
613 'access_level': const.ACCESS_LEVEL_RW,
614 'access_to': to,
615 }
616
617 self.assertRaises(exception_type,
618 self._driver._allow_access,
619 self._ctxt,
620 share,
621 access)
622
623 @ddt.data('ip', 'user')
624 def test_deny_access(self, access_type):
625 fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
626 mock_params = self.mock_object(self._driver,
627 '_get_pool_project_share_name',
628 mock.Mock(return_value=fake_share_info))
629 mock_api = self.mock_object(self._driver, '_api')
630
631 access = {
632 'access_type': access_type,
633 'access_level': const.ACCESS_LEVEL_RW,
634 'access_to': 'some-ip',
635 }
636
637 self._driver._deny_access(self._ctxt, test_share, access)
638
639 deny_params = (
640 '%s/%s/%s/%s' % (
641 'fake_pool',
642 'Local',
643 'fake_project',
644 test_share['name'],
645 ),
646 test_share['share_proto'],
647 access_type,
648 access['access_to'],
649 access['access_level'],
650 )
651 mock_api.assert_called_once_with('shareDenyAccess', deny_params)
652 mock_params.assert_called_once_with(test_share)
653
654 @ddt.data({'access_type': 'other', 'to': 'some-ip', 'share': test_share,
655 'exception_type': exception.InvalidShareAccess},
656 {'access_type': 'ip', 'to': 'some-ip', 'share': test_share,
657 'exception_type': exception.TegileAPIException},
658 {'access_type': 'ip', 'to': 'some-ip', 'share': test_share_cifs,
659 'exception_type': exception.InvalidShareAccess},
660 {'access_type': 'ip', 'to': 'some-ip', 'share': test_share_fail,
661 'exception_type': exception.InvalidShareAccess})
662 @ddt.unpack
663 def test_deny_access_fail(self, access_type, to, share, exception_type):
664 self.mock_object(self._driver, '_api',
665 mock.Mock(
666 side_effect=TegileAPIException(
667 response="Fake Exception")))
668
669 access = {
670 'access_type': access_type,
671 'access_level': const.ACCESS_LEVEL_RW,
672 'access_to': to,
673 }
674
675 self.assertRaises(exception_type,
676 self._driver._deny_access,
677 self._ctxt,
678 share,
679 access)
680
681 @ddt.data({'access_rules': [{'access_type': 'ip',
682 'access_level': const.ACCESS_LEVEL_RW,
683 'access_to': 'some-ip',
684 }, ], 'add_rules': None,
685 'delete_rules': None, 'call_name': 'shareAllowAccess'},
686 {'access_rules': [], 'add_rules':
687 [{'access_type': 'ip',
688 'access_level': const.ACCESS_LEVEL_RW,
689 'access_to': 'some-ip'}, ], 'delete_rules': [],
690 'call_name': 'shareAllowAccess'},
691 {'access_rules': [], 'add_rules': [], 'delete_rules':
692 [{'access_type': 'ip',
693 'access_level': const.ACCESS_LEVEL_RW,
694 'access_to': 'some-ip', }, ],
695 'call_name': 'shareDenyAccess'})
696 @ddt.unpack
697 def test_update_access(self, access_rules, add_rules,
698 delete_rules, call_name):
699 fake_share_info = ('fake_pool', 'fake_project', test_share['name'])
700 mock_params = self.mock_object(self._driver,
701 '_get_pool_project_share_name',
702 mock.Mock(return_value=fake_share_info))
703 mock_api = self.mock_object(self._driver, '_api')
704
705 self._driver.update_access(self._ctxt,
706 test_share,
707 access_rules=access_rules,
708 add_rules=add_rules,
709 delete_rules=delete_rules)
710
711 allow_params = (
712 '%s/%s/%s/%s' % (
713 'fake_pool',
714 'Local',
715 'fake_project',
716 test_share['name'],
717 ),
718 test_share['share_proto'],
719 'ip',
720 'some-ip',
721 const.ACCESS_LEVEL_RW,
722 )
723 if not (add_rules or delete_rules):
724 clear_params = (
725 '%s/%s/%s/%s' % (
726 'fake_pool',
727 'Local',
728 'fake_project',
729 test_share['name'],
730 ),
731 test_share['share_proto'],
732 )
733 mock_api.assert_has_calls([mock.call('clearAccessRules',
734 clear_params),
735 mock.call(call_name,
736 allow_params)])
737 mock_params.assert_called_with(test_share)
738 else:
739 mock_api.assert_called_once_with(call_name, allow_params)
740 mock_params.assert_called_once_with(test_share)
741
742 @ddt.data({'path': r'\\some-ip\shareName', 'share_proto': 'CIFS',
743 'host': 'some-ip'},
744 {'path': 'some-ip:shareName', 'share_proto': 'NFS',
745 'host': 'some-ip'},
746 {'path': 'some-ip:shareName', 'share_proto': 'NFS',
747 'host': None})
748 @ddt.unpack
749 def test_get_location_path(self, path, share_proto, host):
750 self._driver._hostname = 'some-ip'
751
752 result = self._driver._get_location_path('shareName',
753 share_proto,
754 host)
755 expected = {
756 'is_admin_only': False,
757 'metadata': {
758 'preferred': True,
759 },
760 'path': path,
761 }
762 self.assertEqual(expected, result)
763
764 def test_get_location_path_fail(self):
765 self.assertRaises(exception.InvalidInput,
766 self._driver._get_location_path,
767 'shareName',
768 'SOME',
769 'some-ip')
770
771 def test_get_network_allocations_number(self):
772 result = self._driver.get_network_allocations_number()
773
774 expected = 0
775 self.assertEqual(expected, result)
776
777
778class TegileAPIExecutorTestCase(test.TestCase):
779 def setUp(self):
780 self._api = tegile.TegileAPIExecutor("TestCase",
781 test_config.tegile_nas_server,
782 test_config.tegile_nas_login,
783 test_config.tegile_nas_password)
784 super(TegileAPIExecutorTestCase, self).setUp()
785
786 def test_send_api_post(self):
787 json_output = {'value': 'abc'}
788
789 self.mock_object(requests, 'post',
790 mock.Mock(return_value=FakeResponse(200,
791 json_output)))
792 result = self._api(method="Test", request_type='post', params='[]',
793 fine_logging=True)
794
795 self.assertEqual(json_output, result)
796
797 def test_send_api_get(self):
798 json_output = {'value': 'abc'}
799
800 self.mock_object(requests, 'get',
801 mock.Mock(return_value=FakeResponse(200,
802 json_output)))
803
804 result = self._api(method="Test",
805 request_type='get',
806 fine_logging=False)
807
808 self.assertEqual(json_output, result)
809
810 def test_send_api_get_fail(self):
811 self.mock_object(requests, 'get',
812 mock.Mock(return_value=FakeResponse(404, [])))
813
814 self.assertRaises(TegileAPIException,
815 self._api,
816 method="Test",
817 request_type='get',
818 fine_logging=False)
819
820 def test_send_api_value_error_fail(self):
821 json_output = {'value': 'abc'}
822
823 self.mock_object(requests, 'post',
824 mock.Mock(return_value=FakeResponse(200,
825 json_output)))
826 self.mock_object(FakeResponse, 'json',
827 mock.Mock(side_effect=ValueError))
828
829 result = self._api(method="Test",
830 request_type='post',
831 fine_logging=False)
832
833 expected = ''
834 self.assertEqual(expected, result)
diff --git a/releasenotes/notes/add-tegile-driver-1859114513edb13e.yaml b/releasenotes/notes/add-tegile-driver-1859114513edb13e.yaml
new file mode 100644
index 0000000..5111e55
--- /dev/null
+++ b/releasenotes/notes/add-tegile-driver-1859114513edb13e.yaml
@@ -0,0 +1,4 @@
1---
2features:
3 - Added driver for Tegile IntelliFlash arrays.
4