Browse Source

Merge "Add share driver for Tegile IntelliFlash Arrays"

Jenkins 3 years ago
parent
commit
f11558ff16

+ 1
- 0
doc/source/devref/index.rst View File

@@ -112,6 +112,7 @@ Share backends
112 112
    hdfs_native_driver
113 113
    hds_hnas_driver
114 114
    hpe_3par_driver
115
+   tegile_driver
115 116
 
116 117
 Indices and tables
117 118
 ------------------

+ 6
- 0
doc/source/devref/share_back_ends_feature_support_mapping.rst View File

@@ -69,6 +69,8 @@ Mapping of share drivers and share features support
69 69
 +----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
70 70
 |             CephFS Native              |      DHSS = False (M)       |          \-           |      M       |      M       |            M           |             \-             |            \-            |
71 71
 +----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
72
+|                 Tegile                 |      DHSS = False (M)       |          \-           |       M      |       M      |            M           |              M             |            \-            |
73
++----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
72 74
 
73 75
 .. note::
74 76
 
@@ -118,6 +120,8 @@ Mapping of share drivers and share access rules support
118 120
 +----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
119 121
 |              CephFS Native             |      \-      |       \-       |     \-     |   CEPH(M)    |      \-      |       \-       |     \-     |     \-     |
120 122
 +----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
123
+|                 Tegile                 |    NFS (M)   |NFS (M),CIFS (M)|     \-     |      \-      |    NFS (M)   |NFS (M),CIFS (M)|     \-     |     \-     |
124
++----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
121 125
 
122 126
 Mapping of share drivers and security services support
123 127
 ------------------------------------------------------
@@ -161,4 +165,6 @@ Mapping of share drivers and security services support
161 165
 +----------------------------------------+------------------+-----------------+------------------+
162 166
 |              CephFS Native             |        \-        |        \-       |        \-        |
163 167
 +----------------------------------------+------------------+-----------------+------------------+
168
+|                Tegile                  |        \-        |        \-       |        \-        |
169
++----------------------------------------+------------------+-----------------+------------------+
164 170
 

+ 129
- 0
doc/source/devref/tegile_driver.rst View File

@@ -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
+
17
+Tegile Driver
18
+=============
19
+
20
+The Tegile Manila driver uses Tegile IntelliFlash Arrays to provide shared
21
+filesystems to OpenStack.
22
+
23
+The Tegile Driver interfaces with a Tegile Array via the REST API.
24
+
25
+Requirements
26
+------------
27
+
28
+- Tegile IntelliFlash version 3.5.1
29
+- For using CIFS, Active Directory must be configured in the Tegile Array.
30
+
31
+Supported Operations
32
+--------------------
33
+
34
+The 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
+
51
+Backend Configuration
52
+---------------------
53
+
54
+The following parameters need to be configured in the [DEFAULT]
55
+section 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
+
69
+The 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
+
94
+Below 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
+
109
+Restart of :term:`manila-share` service is needed for the configuration changes
110
+to take effect.
111
+
112
+Restrictions
113
+------------
114
+
115
+The Tegile driver has the following restrictions:
116
+
117
+- IP access type is supported only for NFS.
118
+
119
+- Only FLAT network is supported.
120
+
121
+The :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

+ 6
- 0
manila/exception.py View File

@@ -768,3 +768,9 @@ class ReplicationException(ManilaException):
768 768
 
769 769
 class ShareReplicaNotFound(NotFound):
770 770
     message = _("Share Replica %(replica_id)s could not be found.")
771
+
772
+
773
+# Tegile Storage drivers
774
+class TegileAPIException(ShareBackendException):
775
+    message = _("Unexpected response from Tegile IntelliFlash API: "
776
+                "%(response)s")

+ 2
- 0
manila/opts.py View File

@@ -68,6 +68,7 @@ import manila.share.drivers.lxd
68 68
 import manila.share.drivers.netapp.options
69 69
 import manila.share.drivers.quobyte.quobyte
70 70
 import manila.share.drivers.service_instance
71
+import manila.share.drivers.tegile.tegile
71 72
 import manila.share.drivers.windows.service_instance
72 73
 import manila.share.drivers.windows.winrm_helper
73 74
 import manila.share.drivers.zfsonlinux.driver
@@ -141,6 +142,7 @@ _global_opt_lists = [
141 142
     manila.share.drivers.service_instance.common_opts,
142 143
     manila.share.drivers.service_instance.no_share_servers_handling_mode_opts,
143 144
     manila.share.drivers.service_instance.share_servers_handling_mode_opts,
145
+    manila.share.drivers.tegile.tegile.tegile_opts,
144 146
     manila.share.drivers.windows.service_instance.windows_share_server_opts,
145 147
     manila.share.drivers.windows.winrm_helper.winrm_opts,
146 148
     manila.share.drivers.zfsonlinux.driver.zfsonlinux_opts,

+ 0
- 0
manila/share/drivers/tegile/__init__.py View File


+ 513
- 0
manila/share/drivers/tegile/tegile.py View File

@@ -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
+"""
16
+Share driver for Tegile storage.
17
+"""
18
+
19
+import json
20
+import requests
21
+import six
22
+
23
+from oslo_config import cfg
24
+from oslo_log import log
25
+
26
+from manila import utils
27
+from manila.i18n import _, _LI, _LW
28
+from manila import exception
29
+from manila.share import driver
30
+from manila.share import utils as share_utils
31
+
32
+tegile_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
+
43
+CONF = cfg.CONF
44
+CONF.register_opts(tegile_opts)
45
+
46
+LOG = log.getLogger(__name__)
47
+DEFAULT_API_SERVICE = 'openstack'
48
+TEGILE_API_PATH = 'zebi/api'
49
+TEGILE_LOCAL_CONTAINER_NAME = 'Local'
50
+TEGILE_SNAPSHOT_PREFIX = 'Manual-S-'
51
+VENDOR = 'Tegile Systems Inc.'
52
+DEFAULT_BACKEND_NAME = 'Tegile'
53
+VERSION = '1.0.0'
54
+DEBUG_LOGGING = False  # For debugging purposes
55
+
56
+
57
+def 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
+
89
+class 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
+
169
+class 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

+ 0
- 0
manila/tests/share/drivers/tegile/__init__.py View File


+ 834
- 0
manila/tests/share/drivers/tegile/test_tegile.py View File

@@ -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
+"""
16
+Share driver Test for Tegile storage.
17
+"""
18
+
19
+import ddt
20
+import mock
21
+from oslo_config import cfg
22
+import requests
23
+import six
24
+
25
+from manila.common import constants as const
26
+from manila import context
27
+from manila import exception
28
+from manila.exception import TegileAPIException
29
+from manila.share.configuration import Configuration
30
+from manila.share.drivers.tegile import tegile
31
+from manila import test
32
+
33
+
34
+CONF = cfg.CONF
35
+
36
+test_config = Configuration(None)
37
+test_config.tegile_nas_server = 'some-ip'
38
+test_config.tegile_nas_login = 'some-user'
39
+test_config.tegile_nas_password = 'some-password'
40
+test_config.reserved_share_percentage = 10
41
+test_config.max_over_subscription_ratio = 30.0
42
+
43
+test_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
+
51
+test_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
+
59
+test_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
+
67
+test_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
+
76
+array_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
+
105
+fake_tegile_backend_fail = mock.Mock(
106
+    side_effect=TegileAPIException(response="Fake Exception"))
107
+
108
+
109
+class 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
123
+class 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
+
778
+class 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)

+ 4
- 0
releasenotes/notes/add-tegile-driver-1859114513edb13e.yaml View File

@@ -0,0 +1,4 @@
1
+---
2
+features:
3
+  - Added driver for Tegile IntelliFlash arrays.
4
+

Loading…
Cancel
Save