summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-09-24 15:33:25 +0000
committerGerrit Code Review <review@openstack.org>2014-09-24 15:33:25 +0000
commit81d82d19afe8c45695f3b7dfffa7fe3a695d72f3 (patch)
tree66cd8af4562a35c8b0e970f830d9a241ca89916c
parentd00bfb14c39107d2b196f72ee64ee12df8cad07d (diff)
parent10c3f8fb1f83eaa3a3c89f1842730e1365fe6938 (diff)
Merge "Add support for working with multiple glusterfs volumes"2014.2.rc1
-rw-r--r--etc/manila/rootwrap.d/share.filters6
-rw-r--r--manila/share/drivers/glusterfs_native.py491
-rw-r--r--manila/tests/share/drivers/test_glusterfs_native.py806
-rw-r--r--manila/tests/test_share_glusterfs_native.py199
4 files changed, 1236 insertions, 266 deletions
diff --git a/etc/manila/rootwrap.d/share.filters b/etc/manila/rootwrap.d/share.filters
index 57f2a63..621583c 100644
--- a/etc/manila/rootwrap.d/share.filters
+++ b/etc/manila/rootwrap.d/share.filters
@@ -17,3 +17,9 @@ ip: CommandFilter, /sbin/ip, root
17 17
18# manila/network/linux/interface.py: 'ovs-vsctl', 'add-port', '%s', '%s' 18# manila/network/linux/interface.py: 'ovs-vsctl', 'add-port', '%s', '%s'
19ovs-vsctl: CommandFilter, /usr/bin/ovs-vsctl, root 19ovs-vsctl: CommandFilter, /usr/bin/ovs-vsctl, root
20
21# manila/share/drivers/glusterfs_native.py: 'find', '%s', '-mindepth', '1', '-delete'
22find_del: RegExpFilter, /bin/find, root, find, .*, -mindepth, 1, -delete
23
24# manila/share/drivers/glusterfs_native.py: 'umount', '%s'
25umount: CommandFilter, umount, root
diff --git a/manila/share/drivers/glusterfs_native.py b/manila/share/drivers/glusterfs_native.py
index c4f5574..dba7c15 100644
--- a/manila/share/drivers/glusterfs_native.py
+++ b/manila/share/drivers/glusterfs_native.py
@@ -19,81 +19,376 @@ Manila share is a GlusterFS volume. Unlike the generic driver, this
19does not use service VM approach. Instances directly talk with the 19does not use service VM approach. Instances directly talk with the
20GlusterFS backend storage pool. Instance use the 'glusterfs' protocol 20GlusterFS backend storage pool. Instance use the 'glusterfs' protocol
21to mount the GlusterFS share. Access to the share is allowed via 21to mount the GlusterFS share. Access to the share is allowed via
22SSL Certificates. Only the share which has the SSL trust established 22SSL Certificates. Only the instance which has the SSL trust established
23with the GlusterFS backend can mount and hence use the share. 23with the GlusterFS backend can mount and hence use the share.
24
25Supports working with multiple glusterfs volumes.
24""" 26"""
25 27
28import errno
29import pipes
30import shutil
31import tempfile
32
33from oslo.config import cfg
34import six
26 35
27from manila import exception 36from manila import exception
28from manila.openstack.common import log as logging 37from manila.openstack.common import log as logging
38from manila.share import driver
29from manila.share.drivers import glusterfs 39from manila.share.drivers import glusterfs
40from manila import utils
30 41
31 42
32LOG = logging.getLogger(__name__) 43LOG = logging.getLogger(__name__)
33 44
45glusterfs_native_manila_share_opts = [
46 cfg.ListOpt('glusterfs_targets',
47 default=[],
48 help='List of GlusterFS volumes that can be used to create '
49 'shares. Each GlusterFS volume should be of the form '
50 '[remoteuser@]<volserver>:/<volid>'),
51]
52
53CONF = cfg.CONF
54CONF.register_opts(glusterfs_native_manila_share_opts)
55
56ACCESS_TYPE_CERT = 'cert'
57AUTH_SSL_ALLOW = 'auth.ssl-allow'
34CLIENT_SSL = 'client.ssl' 58CLIENT_SSL = 'client.ssl'
59NFS_EXPORT_VOL = 'nfs.export-volumes'
35SERVER_SSL = 'server.ssl' 60SERVER_SSL = 'server.ssl'
36AUTH_SSL_ALLOW = 'auth.ssl-allow'
37ACCESS_TYPE_CERT = 'cert'
38 61
39 62
40class GlusterfsNativeShareDriver(glusterfs.GlusterfsShareDriver): 63class GlusterfsNativeShareDriver(driver.ExecuteMixin, driver.ShareDriver):
64 """GlusterFS native protocol (glusterfs) share driver.
41 65
42 def _setup_gluster_vol(self): 66 Executes commands relating to Shares.
43 super(GlusterfsNativeShareDriver, self)._setup_gluster_vol() 67 Supports working with multiple glusterfs volumes.
44 68
45 # Enable gluster volume for SSL access. 69 API version history:
46 # This applies for both service mount and instance mount(s). 70
71 1.0 - Initial version.
72 1.1 - Support for working with multiple gluster volumes.
73 """
74
75 def __init__(self, db, *args, **kwargs):
76 super(GlusterfsNativeShareDriver, self).__init__(*args, **kwargs)
77 self.db = db
78 self._helpers = None
79 self.gluster_unused_vols_dict = {}
80 self.gluster_used_vols_dict = {}
81 self.configuration.append_config_values(
82 glusterfs_native_manila_share_opts)
83 self.backend_name = self.configuration.safe_get(
84 'share_backend_name') or 'GlusterFS-Native'
85
86 def do_setup(self, context):
87 """Setup the GlusterFS volumes."""
88 super(GlusterfsNativeShareDriver, self).do_setup(context)
89
90 # We don't use a service mount as its not necessary for us.
91 # Do some sanity checks.
92 if len(self.configuration.glusterfs_targets) == 0:
93 # No volumes specified in the config file. Raise exception.
94 msg = (_("glusterfs_targets list seems to be empty! "
95 "Add one or more gluster volumes to work "
96 "with in the glusterfs_targets configuration "
97 "parameter."))
98 LOG.error(msg)
99 raise exception.GlusterfsException(msg)
100
101 LOG.info(_("Number of gluster volumes read from config: "
102 "%(numvols)s"),
103 {'numvols': len(self.configuration.glusterfs_targets)})
47 104
48 # TODO(deepakcs): Once gluster support dual-access, we can limit 105 try:
49 # service mount to non-ssl access. 106 self._execute('mount.glusterfs', check_exit_code=False)
50 gargs, gkw = self.gluster_address.make_gluster_args( 107 except OSError as exc:
51 'volume', 'set', self.gluster_address.volume, 108 if exc.errno == errno.ENOENT:
52 CLIENT_SSL, 'on') 109 msg = (_("mount.glusterfs is not installed."))
110 LOG.error(msg)
111 raise exception.GlusterfsException(msg)
112 else:
113 msg = (_("Error running mount.glusterfs."))
114 LOG.error(msg)
115 raise
116
117 # Update gluster_unused_vols_dict, gluster_used_vols_dict by walking
118 # through the DB.
119 self._update_gluster_vols_dict(context)
120 if len(self.gluster_unused_vols_dict) == 0:
121 # No volumes available for use as share. Warn user.
122 msg = (_("No unused gluster volumes available for use as share! "
123 "Create share won't be supported unless existing shares "
124 "are deleted or add one or more gluster volumes to work "
125 "with in the glusterfs_targets configuration parameter."))
126 LOG.warn(msg)
127 else:
128 LOG.info(_("Number of gluster volumes in use: %(inuse-numvols)s. "
129 "Number of gluster volumes available for use as share: "
130 "%(unused-numvols)s"),
131 {'inuse-numvols': len(self.gluster_used_vols_dict),
132 'unused-numvols': len(self.gluster_unused_vols_dict)})
133
134 self._setup_gluster_vols()
135
136 @utils.synchronized("glusterfs_native", external=False)
137 def _update_gluster_vols_dict(self, context):
138 """Update dict of gluster vols that are used/unused."""
139
140 shares = self.db.share_get_all(context)
141
142 # Store the gluster volumes in dict thats helpful to track
143 # (push and pop) in future. {gluster_export: gluster_addr, ...}
144 # gluster_export is of form hostname:/volname which is unique
145 # enough to be used as a key.
146 self.gluster_unused_vols_dict = {}
147 self.gluster_used_vols_dict = {}
148
149 for gv in self.configuration.glusterfs_targets:
150 gaddr = glusterfs.GlusterAddress(gv)
151 exp_locn_gv = gaddr.export
152
153 # Assume its unused to begin with.
154 self.gluster_unused_vols_dict.update({exp_locn_gv: gaddr})
155
156 for s in shares:
157 exp_locn_share = s.get('export_location', None)
158 if exp_locn_share == exp_locn_gv:
159 # gluster volume is in use, move it to used list.
160 self.gluster_used_vols_dict.update({exp_locn_gv: gaddr})
161 self.gluster_unused_vols_dict.pop(exp_locn_gv)
162 break
163
164 @utils.synchronized("glusterfs_native", external=False)
165 def _setup_gluster_vols(self):
166 # Enable gluster volumes for SSL access only.
167
168 for gluster_addr in six.itervalues(self.gluster_unused_vols_dict):
169
170 gargs, gkw = gluster_addr.make_gluster_args(
171 'volume', 'set', gluster_addr.volume,
172 NFS_EXPORT_VOL, 'off')
173 try:
174 self._execute(*gargs, **gkw)
175 except exception.ProcessExecutionError as exc:
176 msg = (_("Error in gluster volume set during volume setup. "
177 "Volume: %(volname)s, Option: %(option)s, "
178 "Error: %(error)s"),
179 {'volname': gluster_addr.volume,
180 'option': NFS_EXPORT_VOL, 'error': exc.stderr})
181 LOG.error(msg)
182 raise exception.GlusterfsException(msg)
183
184 gargs, gkw = gluster_addr.make_gluster_args(
185 'volume', 'set', gluster_addr.volume,
186 CLIENT_SSL, 'on')
187 try:
188 self._execute(*gargs, **gkw)
189 except exception.ProcessExecutionError as exc:
190 msg = (_("Error in gluster volume set during volume setup. "
191 "Volume: %(volname)s, Option: %(option)s, "
192 "Error: %(error)s"),
193 {'volname': gluster_addr.volume,
194 'option': CLIENT_SSL, 'error': exc.stderr})
195 LOG.error(msg)
196 raise exception.GlusterfsException(msg)
197
198 gargs, gkw = gluster_addr.make_gluster_args(
199 'volume', 'set', gluster_addr.volume,
200 SERVER_SSL, 'on')
201 try:
202 self._execute(*gargs, **gkw)
203 except exception.ProcessExecutionError as exc:
204 msg = (_("Error in gluster volume set during volume setup. "
205 "Volume: %(volname)s, Option: %(option)s, "
206 "Error: %(error)s"),
207 {'volname': gluster_addr.volume,
208 'option': SERVER_SSL, 'error': exc.stderr})
209 LOG.error(msg)
210 raise exception.GlusterfsException(msg)
211
212 # TODO(deepakcs) Remove this once ssl options can be
213 # set dynamically.
214 self._restart_gluster_vol(gluster_addr)
215
216 def _restart_gluster_vol(self, gluster_addr):
217 gargs, gkw = gluster_addr.make_gluster_args(
218 'volume', 'stop', gluster_addr.volume, '--mode=script')
53 try: 219 try:
54 self._execute(*gargs, **gkw) 220 self._execute(*gargs, **gkw)
55 except exception.ProcessExecutionError as exc: 221 except exception.ProcessExecutionError as exc:
56 LOG.error(_("Error in gluster volume set during volume setup." 222 msg = (_("Error stopping gluster volume. "
57 "Volume: %(volname)s, Option: %(option)s, " 223 "Volume: %(volname)s, Error: %(error)s"),
58 "Error: %(error)s"), 224 {'volname': gluster_addr.volume, 'error': exc.stderr})
59 {'volname': self.gluster_address.volume, 225 LOG.error(msg)
60 'option': CLIENT_SSL, 'error': exc.stderr}) 226 raise exception.GlusterfsException(msg)
227
228 gargs, gkw = gluster_addr.make_gluster_args(
229 'volume', 'start', gluster_addr.volume)
230 try:
231 self._execute(*gargs, **gkw)
232 except exception.ProcessExecutionError as exc:
233 msg = (_("Error starting gluster volume. "
234 "Volume: %(volname)s, Error: %(error)s"),
235 {'volname': gluster_addr.volume, 'error': exc.stderr})
236 LOG.error(msg)
237 raise exception.GlusterfsException(msg)
238
239 @utils.synchronized("glusterfs_native", external=False)
240 def _pop_gluster_vol(self):
241 try:
242 exp_locn, gaddr = self.gluster_unused_vols_dict.popitem()
243 except KeyError:
244 msg = (_("Couldn't find a free gluster volume to use."))
245 LOG.error(msg)
246 raise exception.GlusterfsException(msg)
247
248 self.gluster_used_vols_dict.update({exp_locn: gaddr})
249 return exp_locn
250
251 @utils.synchronized("glusterfs_native", external=False)
252 def _push_gluster_vol(self, exp_locn):
253 try:
254 gaddr = self.gluster_used_vols_dict.pop(exp_locn)
255 except KeyError:
256 msg = (_("Couldn't find the share in used list."))
257 LOG.error(msg)
258 raise exception.GlusterfsException(msg)
259
260 self.gluster_unused_vols_dict.update({exp_locn: gaddr})
261
262 def _do_mount(self, gluster_export, mntdir):
263
264 cmd = ['mount', '-t', 'glusterfs', gluster_export, mntdir]
265 try:
266 self._execute(*cmd, run_as_root=True)
267 except exception.ProcessExecutionError as exc:
268 msg = (_("Unable to mount gluster volume. "
269 "gluster_export: %(export)s, Error: %(error)s"),
270 {'export': gluster_export, 'error': exc.stderr})
271 LOG.error(msg)
272 raise exception.GlusterfsException(msg)
273
274 def _do_umount(self, mntdir):
275
276 cmd = ['umount', mntdir]
277 try:
278 self._execute(*cmd, run_as_root=True)
279 except exception.ProcessExecutionError as exc:
280 msg = (_("Unable to unmount gluster volume. "
281 "mount_dir: %(mntdir)s, Error: %(error)s"),
282 {'mntdir': mntdir, 'error': exc.stderr})
283 LOG.error(msg)
284 raise exception.GlusterfsException(msg)
285
286 def _wipe_gluster_vol(self, gluster_addr):
287
288 # Reset the SSL options.
289 gargs, gkw = gluster_addr.make_gluster_args(
290 'volume', 'set', gluster_addr.volume,
291 CLIENT_SSL, 'off')
292 try:
293 self._execute(*gargs, **gkw)
294 except exception.ProcessExecutionError as exc:
295 msg = (_("Error in gluster volume set during _wipe_gluster_vol. "
296 "Volume: %(volname)s, Option: %(option)s, "
297 "Error: %(error)s"),
298 {'volname': gluster_addr.volume,
299 'option': CLIENT_SSL, 'error': exc.stderr})
300 LOG.error(msg)
301 raise exception.GlusterfsException(msg)
302
303 gargs, gkw = gluster_addr.make_gluster_args(
304 'volume', 'set', gluster_addr.volume,
305 SERVER_SSL, 'off')
306 try:
307 self._execute(*gargs, **gkw)
308 except exception.ProcessExecutionError as exc:
309 msg = (_("Error in gluster volume set during _wipe_gluster_vol. "
310 "Volume: %(volname)s, Option: %(option)s, "
311 "Error: %(error)s"),
312 {'volname': gluster_addr.volume,
313 'option': SERVER_SSL, 'error': exc.stderr})
314 LOG.error(msg)
315 raise exception.GlusterfsException(msg)
316
317 self._restart_gluster_vol(gluster_addr)
318
319 # Create a temporary mount.
320 gluster_export = gluster_addr.export
321 tmpdir = tempfile.mkdtemp()
322 try:
323 self._do_mount(gluster_export, tmpdir)
324 except exception.GlusterfsException:
325 shutil.rmtree(tmpdir, ignore_errors=True)
61 raise 326 raise
62 gargs, gkw = self.gluster_address.make_gluster_args( 327
63 'volume', 'set', self.gluster_address.volume, 328 # Delete only the contents, not the directory.
329 cmd = ['find', pipes.quote(tmpdir), '-mindepth', '1', '-delete']
330 try:
331 self._execute(*cmd, run_as_root=True)
332 except exception.ProcessExecutionError as exc:
333 msg = (_("Error trying to wipe gluster volume. "
334 "gluster_export: %(export)s, Error: %(error)s"),
335 {'export': gluster_export, 'error': exc.stderr})
336 LOG.error(msg)
337 raise exception.GlusterfsException(msg)
338 finally:
339 # Unmount.
340 self._do_umount(tmpdir)
341 shutil.rmtree(tmpdir, ignore_errors=True)
342
343 # Set the SSL options.
344 gargs, gkw = gluster_addr.make_gluster_args(
345 'volume', 'set', gluster_addr.volume,
346 CLIENT_SSL, 'on')
347 try:
348 self._execute(*gargs, **gkw)
349 except exception.ProcessExecutionError as exc:
350 msg = (_("Error in gluster volume set during _wipe_gluster_vol. "
351 "Volume: %(volname)s, Option: %(option)s, "
352 "Error: %(error)s"),
353 {'volname': gluster_addr.volume,
354 'option': CLIENT_SSL, 'error': exc.stderr})
355 LOG.error(msg)
356 raise exception.GlusterfsException(msg)
357
358 gargs, gkw = gluster_addr.make_gluster_args(
359 'volume', 'set', gluster_addr.volume,
64 SERVER_SSL, 'on') 360 SERVER_SSL, 'on')
65 try: 361 try:
66 self._execute(*gargs, **gkw) 362 self._execute(*gargs, **gkw)
67 except exception.ProcessExecutionError as exc: 363 except exception.ProcessExecutionError as exc:
68 LOG.error(_("Error in gluster volume set during volume setup." 364 msg = (_("Error in gluster volume set during _wipe_gluster_vol. "
69 "Volume: %(volname)s, Option: %(option)s, " 365 "Volume: %(volname)s, Option: %(option)s, "
70 "Error: %(error)s"), 366 "Error: %(error)s"),
71 {'volname': self.gluster_address.volume, 367 {'volname': gluster_addr.volume,
72 'option': SERVER_SSL, 'error': exc.stderr}) 368 'option': SERVER_SSL, 'error': exc.stderr})
73 raise 369 LOG.error(msg)
370 raise exception.GlusterfsException(msg)
74 371
75 def create_share(self, ctx, share, share_server=None): 372 self._restart_gluster_vol(gluster_addr)
373
374 def create_share(self, context, share, share_server=None):
76 """Create a share using GlusterFS volume. 375 """Create a share using GlusterFS volume.
77 376
78 1 Manila share = 1 GlusterFS volume. Ensure that the 377 1 Manila share = 1 GlusterFS volume. Pick an unused
79 GlusterFS volume is properly setup to be consumed as 378 GlusterFS volume for use as a share.
80 a share.
81 """ 379 """
82
83 # Handle the case where create is called after delete share
84 try: 380 try:
85 self._setup_gluster_vol() 381 export_location = self._pop_gluster_vol()
86 except exception.ProcessExecutionError: 382 except exception.GlusterfsException:
87 LOG.error(_("Unable to create share %s"), (share['name'],)) 383 msg = (_("Error creating share %(share_id)s"),
384 {'share_id': share['id']})
385 LOG.error(msg)
88 raise 386 raise
89 387
90 # TODO(deepakcs): Add validation for gluster mount being present 388 # TODO(deepakcs): Enable quota and set it to the share size.
91 # (decorator maybe)
92 389
93 # For native protocol, the export_location should be of the form: 390 # For native protocol, the export_location should be of the form:
94 # server:/volname 391 # server:/volname
95 export_location = self.gluster_address.export
96
97 LOG.info(_("export_location sent back from create_share: %s"), 392 LOG.info(_("export_location sent back from create_share: %s"),
98 (export_location,)) 393 (export_location,))
99 return export_location 394 return export_location
@@ -101,22 +396,30 @@ class GlusterfsNativeShareDriver(glusterfs.GlusterfsShareDriver):
101 def delete_share(self, context, share, share_server=None): 396 def delete_share(self, context, share, share_server=None):
102 """Delete a share on the GlusterFS volume. 397 """Delete a share on the GlusterFS volume.
103 398
104 1 Manila share = 1 GlusterFS volume. Ensure that the 399 1 Manila share = 1 GlusterFS volume. Put the gluster
105 GlusterFS volume is reset back to its original state. 400 volume back in the available list.
106 """ 401 """
107 # Get the gluster volume back to its original state 402 exp_locn = share.get('export_location', None)
403 try:
404 # Get the gluster address associated with the export.
405 gaddr = self.gluster_used_vols_dict[exp_locn]
406 except KeyError:
407 msg = (_("Invalid request. Ignoring delete_share request for "
408 "share %(share_id)s"), {'share_id': share['id']},)
409 LOG.warn(msg)
410 return
108 411
109 gargs, gkw = self.gluster_address.make_gluster_args(
110 'volume', 'reset', self.gluster_address.volume)
111 try: 412 try:
112 self._execute(*gargs, **gkw) 413 self._wipe_gluster_vol(gaddr)
113 except exception.ProcessExecutionError as exc: 414 self._push_gluster_vol(exp_locn)
114 LOG.error(_("Error in gluster volume reset during delete share." 415 except exception.GlusterfsException:
115 "Volume: %(volname)s, Error: %(error)s"), 416 msg = (_("Error during delete_share request for "
116 {'volname': self.gluster_address.volume, 417 "share %(share_id)s"), {'share_id': share['id']},)
117 'error': exc.stderr}) 418 LOG.error(msg)
118 raise 419 raise
119 420
421 # TODO(deepakcs): Disable quota.
422
120 def allow_access(self, context, share, access, share_server=None): 423 def allow_access(self, context, share, access, share_server=None):
121 """Allow access to a share using certs. 424 """Allow access to a share using certs.
122 425
@@ -126,20 +429,27 @@ class GlusterfsNativeShareDriver(glusterfs.GlusterfsShareDriver):
126 if access['access_type'] != ACCESS_TYPE_CERT: 429 if access['access_type'] != ACCESS_TYPE_CERT:
127 raise exception.InvalidShareAccess(_("Only 'cert' access type " 430 raise exception.InvalidShareAccess(_("Only 'cert' access type "
128 "allowed")) 431 "allowed"))
432 exp_locn = share.get('export_location', None)
433 gluster_addr = self.gluster_used_vols_dict.get(exp_locn)
129 434
130 gargs, gkw = self.gluster_address.make_gluster_args( 435 gargs, gkw = gluster_addr.make_gluster_args(
131 'volume', 'set', self.gluster_address.volume, 436 'volume', 'set', gluster_addr.volume,
132 AUTH_SSL_ALLOW, access['access_to']) 437 AUTH_SSL_ALLOW, access['access_to'])
133 try: 438 try:
134 self._execute(*gargs, **gkw) 439 self._execute(*gargs, **gkw)
135 except exception.ProcessExecutionError as exc: 440 except exception.ProcessExecutionError as exc:
136 LOG.error(_("Error in gluster volume set during allow access." 441 msg = (_("Error in gluster volume set during allow access. "
137 "Volume: %(volname)s, Option: %(option)s, " 442 "Volume: %(volname)s, Option: %(option)s, "
138 "access_to: %(access_to)s, Error: %(error)s"), 443 "access_to: %(access_to)s, Error: %(error)s"),
139 {'volname': self.gluster_address.volume, 444 {'volname': gluster_addr.volume,
140 'option': AUTH_SSL_ALLOW, 445 'option': AUTH_SSL_ALLOW,
141 'access_to': access['access_to'], 'error': exc.stderr}) 446 'access_to': access['access_to'], 'error': exc.stderr})
142 raise 447 LOG.error(msg)
448 raise exception.GlusterfsException(msg)
449
450 # TODO(deepakcs) Remove this once ssl options can be
451 # set dynamically.
452 self._restart_gluster_vol(gluster_addr)
143 453
144 def deny_access(self, context, share, access, share_server=None): 454 def deny_access(self, context, share, access, share_server=None):
145 """Deny access to a share that's using cert based auth. 455 """Deny access to a share that's using cert based auth.
@@ -151,16 +461,63 @@ class GlusterfsNativeShareDriver(glusterfs.GlusterfsShareDriver):
151 raise exception.InvalidShareAccess(_("Only 'cert' access type " 461 raise exception.InvalidShareAccess(_("Only 'cert' access type "
152 "allowed for access " 462 "allowed for access "
153 "removal.")) 463 "removal."))
464 exp_locn = share.get('export_location', None)
465 gluster_addr = self.gluster_used_vols_dict.get(exp_locn)
154 466
155 gargs, gkw = self.gluster_address.make_gluster_args( 467 gargs, gkw = gluster_addr.make_gluster_args(
156 'volume', 'reset', self.gluster_address.volume, 468 'volume', 'reset', gluster_addr.volume,
157 AUTH_SSL_ALLOW) 469 AUTH_SSL_ALLOW)
158 try: 470 try:
159 self._execute(*gargs, **gkw) 471 self._execute(*gargs, **gkw)
160 except exception.ProcessExecutionError as exc: 472 except exception.ProcessExecutionError as exc:
161 LOG.error(_("Error in gluster volume reset during deny access." 473 msg = (_("Error in gluster volume reset during deny access. "
162 "Volume: %(volname)s, Option: %(option)s, " 474 "Volume: %(volname)s, Option: %(option)s, "
163 "Error: %(error)s"), 475 "Error: %(error)s"),
164 {'volname': self.gluster_address.volume, 476 {'volname': gluster_addr.volume,
165 'option': AUTH_SSL_ALLOW, 'error': exc.stderr}) 477 'option': AUTH_SSL_ALLOW, 'error': exc.stderr})
166 raise 478 LOG.error(msg)
479 raise exception.GlusterfsException(msg)
480
481 # TODO(deepakcs) Remove this once ssl options can be
482 # set dynamically.
483 self._restart_gluster_vol(gluster_addr)
484
485 def get_share_stats(self, refresh=False):
486 """Get share stats.
487
488 If 'refresh' is True, update the stats first.
489 """
490 if refresh:
491 self._update_share_stats()
492
493 return self._stats
494
495 def _update_share_stats(self):
496 """Send stats info for the GlusterFS volume."""
497
498 LOG.debug("Updating share stats")
499
500 data = {}
501
502 data["share_backend_name"] = self.backend_name
503 data["vendor_name"] = 'Red Hat'
504 data["driver_version"] = '1.1'
505 data["storage_protocol"] = 'glusterfs'
506
507 data['reserved_percentage'] = (
508 self.configuration.reserved_share_percentage)
509 data['QoS_support'] = False
510
511 # We don't use a service mount to get stats data.
512 # Instead we use glusterfs quota feature and use that to limit
513 # the share to its expected share['size'].
514
515 # TODO(deepakcs): Change below once glusterfs supports volume
516 # specific stats via the gluster cli.
517 data['total_capacity_gb'] = 'infinite'
518 data['free_capacity_gb'] = 'infinite'
519 self._stats = data
520
521 def ensure_share(self, context, share, share_server=None):
522 """Invoked to ensure that share is exported."""
523 pass
diff --git a/manila/tests/share/drivers/test_glusterfs_native.py b/manila/tests/share/drivers/test_glusterfs_native.py
new file mode 100644
index 0000000..899031e
--- /dev/null
+++ b/manila/tests/share/drivers/test_glusterfs_native.py
@@ -0,0 +1,806 @@
1# Copyright (c) 2014 Red Hat, 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""" GlusterFS native protocol (glusterfs) driver for shares.
17
18Test cases for GlusterFS native protocol driver.
19"""
20
21
22import shutil
23import tempfile
24
25import mock
26from oslo.config import cfg
27
28from manila import context
29from manila import exception
30from manila.share import configuration as config
31from manila.share.drivers import glusterfs
32from manila.share.drivers import glusterfs_native
33from manila import test
34from manila.tests import fake_utils
35
36
37CONF = cfg.CONF
38
39
40def fake_db_share1(**kwargs):
41 share = {
42 'id': 'fakeid',
43 'name': 'fakename',
44 'size': 1,
45 'share_proto': 'glusterfs',
46 'export_location': 'host1:/gv1',
47 }
48 share.update(kwargs)
49 return [share]
50
51
52def fake_db_share2(**kwargs):
53 share = {
54 'id': 'fakeid',
55 'name': 'fakename',
56 'size': 1,
57 'share_proto': 'glusterfs',
58 'export_location': 'host2:/gv2',
59 }
60 share.update(kwargs)
61 return [share]
62
63
64def new_share(**kwargs):
65 share = {
66 'id': 'fakeid',
67 'name': 'fakename',
68 'size': 1,
69 'share_proto': 'glusterfs',
70 }
71 share.update(kwargs)
72 return share
73
74
75class GlusterfsNativeShareDriverTestCase(test.TestCase):
76 """Tests GlusterfsNativeShareDriver."""
77
78 def setUp(self):
79 super(GlusterfsNativeShareDriverTestCase, self).setUp()
80 fake_utils.stub_out_utils_execute(self.stubs)
81 self._execute = fake_utils.fake_execute
82 self._context = context.get_admin_context()
83
84 self.gluster_target1 = 'root@host1:/gv1'
85 self.gluster_target2 = 'root@host2:/gv2'
86 CONF.set_default('glusterfs_targets',
87 [self.gluster_target1, self.gluster_target2])
88
89 self.fake_conf = config.Configuration(None)
90 self._db = mock.Mock()
91 self._driver = glusterfs_native.GlusterfsNativeShareDriver(
92 self._db, execute=self._execute,
93 configuration=self.fake_conf)
94 self.stubs.Set(tempfile, 'mkdtemp',
95 mock.Mock(return_value='/tmp/tmpKGHKJ'))
96
97 self.addCleanup(fake_utils.fake_execute_set_repliers, [])
98 self.addCleanup(fake_utils.fake_execute_clear_log)
99
100 def test_do_setup(self):
101 self._driver._setup_gluster_vols = mock.Mock()
102 self._db.share_get_all = mock.Mock(return_value=[])
103 expected_exec = ['mount.glusterfs']
104 gaddr = glusterfs.GlusterAddress
105
106 self._driver.do_setup(self._context)
107
108 self.assertEqual(2, len(self._driver.gluster_unused_vols_dict))
109 self.assertTrue(gaddr(self.gluster_target1).export in
110 self._driver.gluster_unused_vols_dict)
111 self.assertTrue(gaddr(self.gluster_target2).export in
112 self._driver.gluster_unused_vols_dict)
113 self.assertTrue(self._driver._setup_gluster_vols.called)
114 self.assertTrue(self._db.share_get_all.called)
115 self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
116
117 def test_do_setup_glusterfs_targets_empty(self):
118 self._driver.configuration.glusterfs_targets = []
119 self.assertRaises(exception.GlusterfsException, self._driver.do_setup,
120 self._context)
121
122 def test_update_gluster_vols_dict(self):
123 self._db.share_get_all = mock.Mock(return_value=fake_db_share1())
124
125 self._driver._update_gluster_vols_dict(self._context)
126
127 self.assertEqual(1, len(self._driver.gluster_used_vols_dict))
128 self.assertEqual(1, len(self._driver.gluster_unused_vols_dict))
129 self.assertTrue(self._db.share_get_all.called)
130
131 share_in_use = fake_db_share1()[0]
132 share_not_in_use = fake_db_share2()[0]
133
134 self.assertTrue(
135 share_in_use['export_location'] in
136 self._driver.gluster_used_vols_dict)
137 self.assertFalse(
138 share_not_in_use['export_location'] in
139 self._driver.gluster_used_vols_dict)
140 self.assertTrue(
141 share_not_in_use['export_location'] in
142 self._driver.gluster_unused_vols_dict)
143 self.assertFalse(
144 share_in_use['export_location'] in
145 self._driver.gluster_unused_vols_dict)
146
147 def test_setup_gluster_vols(self):
148 self._driver._restart_gluster_vol = mock.Mock()
149
150 gaddr = glusterfs.GlusterAddress
151 gaddr1 = gaddr(self.gluster_target1)
152 gaddr2 = gaddr(self.gluster_target2)
153
154 self._driver.gluster_used_vols_dict = {gaddr1.export: gaddr1}
155 self._driver.gluster_unused_vols_dict = {gaddr2.export: gaddr2}
156
157 expected_exec = [
158 'ssh root@host2 gluster volume set gv2 nfs.export-volumes off',
159 'ssh root@host2 gluster volume set gv2 client.ssl on',
160 'ssh root@host2 gluster volume set gv2 server.ssl on']
161
162 self._driver._setup_gluster_vols()
163
164 self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
165 self.assertTrue(self._driver._restart_gluster_vol.called)
166
167 def test_setup_gluster_vols_excp1(self):
168 self._driver._restart_gluster_vol = mock.Mock()
169
170 gaddr = glusterfs.GlusterAddress
171 gaddr1 = gaddr(self.gluster_target1)
172 gaddr2 = gaddr(self.gluster_target2)
173
174 self._driver.gluster_used_vols_dict = {gaddr1.export: gaddr1}
175 self._driver.gluster_unused_vols_dict = {gaddr2.export: gaddr2}
176
177 def exec_runner(*ignore_args, **ignore_kwargs):
178 raise exception.ProcessExecutionError
179
180 expected_exec = [
181 'ssh root@host2 gluster volume set gv2 nfs.export-volumes off']
182 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
183
184 self.assertRaises(exception.GlusterfsException,
185 self._driver._setup_gluster_vols)
186 self.assertFalse(self._driver._restart_gluster_vol.called)
187
188 def test_setup_gluster_vols_excp2(self):
189 self._driver._restart_gluster_vol = mock.Mock()
190
191 gaddr = glusterfs.GlusterAddress
192 gaddr1 = gaddr(self.gluster_target1)
193 gaddr2 = gaddr(self.gluster_target2)
194
195 self._driver.gluster_used_vols_dict = {gaddr1.export: gaddr1}
196 self._driver.gluster_unused_vols_dict = {gaddr2.export: gaddr2}
197
198 def exec_runner(*ignore_args, **ignore_kwargs):
199 raise exception.ProcessExecutionError
200
201 expected_exec = [
202 'ssh root@host2 gluster volume set gv2 client.ssl on']
203 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
204
205 self.assertRaises(exception.GlusterfsException,
206 self._driver._setup_gluster_vols)
207 self.assertFalse(self._driver._restart_gluster_vol.called)
208
209 def test_setup_gluster_vols_excp3(self):
210 self._driver._restart_gluster_vol = mock.Mock()
211
212 gaddr = glusterfs.GlusterAddress
213 gaddr1 = gaddr(self.gluster_target1)
214 gaddr2 = gaddr(self.gluster_target2)
215
216 self._driver.gluster_used_vols_dict = {gaddr1.export: gaddr1}
217 self._driver.gluster_unused_vols_dict = {gaddr2.export: gaddr2}
218
219 def exec_runner(*ignore_args, **ignore_kwargs):
220 raise exception.ProcessExecutionError
221
222 expected_exec = [
223 'ssh root@host2 gluster volume set gv2 server.ssl on']
224 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
225
226 self.assertRaises(exception.GlusterfsException,
227 self._driver._setup_gluster_vols)
228 self.assertFalse(self._driver._restart_gluster_vol.called)
229
230 def test_restart_gluster_vol(self):
231 gaddr = glusterfs.GlusterAddress
232 gaddr1 = gaddr(self.gluster_target1)
233
234 expected_exec = [
235 'ssh root@host1 gluster volume stop gv1 --mode=script',
236 'ssh root@host1 gluster volume start gv1']
237
238 self._driver._restart_gluster_vol(gaddr1)
239 self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
240
241 def test_restart_gluster_vol_excp1(self):
242 gaddr = glusterfs.GlusterAddress
243 gaddr1 = gaddr(self.gluster_target1)
244
245 def exec_runner(*ignore_args, **ignore_kwargs):
246 raise exception.ProcessExecutionError
247
248 expected_exec = [
249 'ssh root@host1 gluster volume stop gv1 --mode=script']
250 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
251
252 self.assertRaises(exception.GlusterfsException,
253 self._driver._restart_gluster_vol, gaddr1)
254
255 def test_restart_gluster_vol_excp2(self):
256 gaddr = glusterfs.GlusterAddress
257 gaddr1 = gaddr(self.gluster_target1)
258
259 def exec_runner(*ignore_args, **ignore_kwargs):
260 raise exception.ProcessExecutionError
261
262 expected_exec = [
263 'ssh root@host1 gluster volume start gv1']
264 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
265
266 self.assertRaises(exception.GlusterfsException,
267 self._driver._restart_gluster_vol, gaddr1)
268
269 def test_pop_gluster_vol(self):
270 gaddr = glusterfs.GlusterAddress
271 gaddr1 = gaddr(self.gluster_target1)
272 gaddr2 = gaddr(self.gluster_target2)
273
274 self._driver.gluster_used_vols_dict = {gaddr1.export: gaddr1}
275 self._driver.gluster_unused_vols_dict = {gaddr2.export: gaddr2}
276
277 exp_locn = self._driver._pop_gluster_vol()
278
279 self.assertEqual(0, len(self._driver.gluster_unused_vols_dict))
280 self.assertFalse(
281 gaddr2.export in self._driver.gluster_unused_vols_dict)
282 self.assertEqual(2, len(self._driver.gluster_used_vols_dict))
283 self.assertTrue(
284 gaddr2.export in self._driver.gluster_used_vols_dict)
285 self.assertEqual(exp_locn, gaddr2.export)
286
287 def test_pop_gluster_vol_excp(self):
288 gaddr = glusterfs.GlusterAddress
289 gaddr1 = gaddr(self.gluster_target1)
290 gaddr2 = gaddr(self.gluster_target2)
291
292 self._driver.gluster_used_vols_dict = {
293 gaddr2.export: gaddr2, gaddr1.export: gaddr1}
294 self._driver.gluster_unused_vols_dict = {}
295
296 self.assertRaises(exception.GlusterfsException,
297 self._driver._pop_gluster_vol)
298
299 def test_push_gluster_vol(self):
300 gaddr = glusterfs.GlusterAddress
301 gaddr1 = gaddr(self.gluster_target1)
302 gaddr2 = gaddr(self.gluster_target2)
303
304 self._driver.gluster_used_vols_dict = {
305 gaddr1.export: gaddr1, gaddr2.export: gaddr2}
306 self._driver.gluster_unused_vols_dict = {}
307
308 self._driver._push_gluster_vol(gaddr2.export)
309
310 self.assertEqual(1, len(self._driver.gluster_unused_vols_dict))
311 self.assertTrue(
312 gaddr2.export in self._driver.gluster_unused_vols_dict)
313 self.assertEqual(1, len(self._driver.gluster_used_vols_dict))
314 self.assertFalse(
315 gaddr2.export in self._driver.gluster_used_vols_dict)
316
317 def test_push_gluster_vol_excp(self):
318 gaddr = glusterfs.GlusterAddress
319 gaddr1 = gaddr(self.gluster_target1)
320 gaddr2 = gaddr(self.gluster_target2)
321
322 self._driver.gluster_used_vols_dict = {gaddr1.export: gaddr1}
323 self._driver.gluster_unused_vols_dict = {}
324
325 self.assertRaises(exception.GlusterfsException,
326 self._driver._push_gluster_vol, gaddr2.export)
327
328 def test_do_mount(self):
329 gaddr = glusterfs.GlusterAddress
330 gaddr1 = gaddr(self.gluster_target1)
331 tmpdir = '/tmp/tmpKGHKJ'
332 expected_exec = ['mount -t glusterfs host1:/gv1 /tmp/tmpKGHKJ']
333
334 self._driver._do_mount(gaddr1.export, tmpdir)
335
336 self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
337
338 def test_do_mount_excp(self):
339 def exec_runner(*ignore_args, **ignore_kwargs):
340 raise exception.ProcessExecutionError
341
342 gaddr = glusterfs.GlusterAddress
343 gaddr1 = gaddr(self.gluster_target1)
344 tmpdir = '/tmp/tmpKGHKJ'
345 expected_exec = ['mount -t glusterfs host1:/gv1 /tmp/tmpKGHKJ']
346
347 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
348
349 self.assertRaises(exception.GlusterfsException,
350 self._driver._do_mount, gaddr1.export, tmpdir)
351
352 def test_do_umount(self):
353 tmpdir = '/tmp/tmpKGHKJ'
354 expected_exec = ['umount /tmp/tmpKGHKJ']
355
356 self._driver._do_umount(tmpdir)
357
358 self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
359
360 def test_do_umount_excp(self):
361 def exec_runner(*ignore_args, **ignore_kwargs):
362 raise exception.ProcessExecutionError
363
364 tmpdir = '/tmp/tmpKGHKJ'
365 expected_exec = ['umount /tmp/tmpKGHKJ']
366
367 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
368
369 self.assertRaises(exception.GlusterfsException,
370 self._driver._do_umount, tmpdir)
371
372 def test_wipe_gluster_vol(self):
373 self._driver._restart_gluster_vol = mock.Mock()
374 self._driver._do_mount = mock.Mock()
375 self._driver._do_umount = mock.Mock()
376 shutil.rmtree = mock.Mock()
377
378 gaddr = glusterfs.GlusterAddress
379 gaddr1 = gaddr(self.gluster_target1)
380
381 expected_exec = [
382 'ssh root@host1 gluster volume set gv1 client.ssl off',
383 'ssh root@host1 gluster volume set gv1 server.ssl off',
384 'find /tmp/tmpKGHKJ -mindepth 1 -delete',
385 'ssh root@host1 gluster volume set gv1 client.ssl on',
386 'ssh root@host1 gluster volume set gv1 server.ssl on']
387
388 self._driver._wipe_gluster_vol(gaddr1)
389
390 self.assertEqual(2, self._driver._restart_gluster_vol.call_count)
391 self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
392 self.assertTrue(tempfile.mkdtemp.called)
393 self.assertTrue(self._driver._do_mount.called)
394 self.assertTrue(self._driver._do_umount.called)
395 self.assertTrue(shutil.rmtree.called)
396
397 def test_wipe_gluster_vol_excp1(self):
398 self._driver._restart_gluster_vol = mock.Mock()
399 self._driver._do_mount = mock.Mock()
400 self._driver._do_umount = mock.Mock()
401 shutil.rmtree = mock.Mock()
402
403 gaddr = glusterfs.GlusterAddress
404 gaddr1 = gaddr(self.gluster_target1)
405
406 def exec_runner(*ignore_args, **ignore_kwargs):
407 raise exception.ProcessExecutionError
408
409 expected_exec = [
410 'ssh root@host1 gluster volume set gv1 client.ssl off']
411
412 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
413
414 self.assertRaises(exception.GlusterfsException,
415 self._driver._wipe_gluster_vol, gaddr1)
416
417 self.assertFalse(self._driver._restart_gluster_vol.called)
418 self.assertFalse(tempfile.mkdtemp.called)
419 self.assertFalse(self._driver._do_mount.called)
420 self.assertFalse(self._driver._do_umount.called)
421 self.assertFalse(shutil.rmtree.called)
422
423 def test_wipe_gluster_vol_excp2(self):
424 self._driver._restart_gluster_vol = mock.Mock()
425 self._driver._do_mount = mock.Mock()
426 self._driver._do_umount = mock.Mock()
427 shutil.rmtree = mock.Mock()
428
429 gaddr = glusterfs.GlusterAddress
430 gaddr1 = gaddr(self.gluster_target1)
431
432 def exec_runner(*ignore_args, **ignore_kwargs):
433 raise exception.ProcessExecutionError
434
435 expected_exec = [
436 'ssh root@host1 gluster volume set gv1 server.ssl off']
437
438 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
439
440 self.assertRaises(exception.GlusterfsException,
441 self._driver._wipe_gluster_vol, gaddr1)
442
443 self.assertFalse(self._driver._restart_gluster_vol.called)
444 self.assertFalse(tempfile.mkdtemp.called)
445 self.assertFalse(self._driver._do_mount.called)
446 self.assertFalse(self._driver._do_umount.called)
447 self.assertFalse(shutil.rmtree.called)
448
449 def test_wipe_gluster_vol_excp3(self):
450 self._driver._restart_gluster_vol = mock.Mock()
451 self._driver._do_mount = mock.Mock()
452 self._driver._do_umount = mock.Mock()
453 shutil.rmtree = mock.Mock()
454
455 gaddr = glusterfs.GlusterAddress
456 gaddr1 = gaddr(self.gluster_target1)
457
458 def exec_runner(*ignore_args, **ignore_kwargs):
459 raise exception.ProcessExecutionError
460
461 expected_exec = [
462 'ssh root@host1 gluster volume set gv1 client.ssl on']
463
464 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
465
466 self.assertRaises(exception.GlusterfsException,
467 self._driver._wipe_gluster_vol, gaddr1)
468
469 self.assertTrue(self._driver._restart_gluster_vol.called)
470 self.assertTrue(tempfile.mkdtemp.called)
471 self.assertTrue(self._driver._do_mount.called)
472 self.assertTrue(self._driver._do_umount.called)
473 self.assertTrue(shutil.rmtree.called)
474
475 def test_wipe_gluster_vol_excp4(self):
476 self._driver._restart_gluster_vol = mock.Mock()
477 self._driver._do_mount = mock.Mock()
478 self._driver._do_umount = mock.Mock()
479 shutil.rmtree = mock.Mock()
480
481 gaddr = glusterfs.GlusterAddress
482 gaddr1 = gaddr(self.gluster_target1)
483
484 def exec_runner(*ignore_args, **ignore_kwargs):
485 raise exception.ProcessExecutionError
486
487 expected_exec = [
488 'ssh root@host1 gluster volume set gv1 server.ssl on']
489
490 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
491
492 self.assertRaises(exception.GlusterfsException,
493 self._driver._wipe_gluster_vol, gaddr1)
494
495 self.assertTrue(self._driver._restart_gluster_vol.called)
496 self.assertTrue(tempfile.mkdtemp.called)
497 self.assertTrue(self._driver._do_mount.called)
498 self.assertTrue(self._driver._do_umount.called)
499 self.assertTrue(shutil.rmtree.called)
500
501 def test_wipe_gluster_vol_excp5(self):
502 self._driver._restart_gluster_vol = mock.Mock()
503 self._driver._do_mount = mock.Mock()
504 self._driver._do_umount = mock.Mock()
505 shutil.rmtree = mock.Mock()
506
507 gaddr = glusterfs.GlusterAddress
508 gaddr1 = gaddr(self.gluster_target1)
509
510 def exec_runner(*ignore_args, **ignore_kwargs):
511 raise exception.ProcessExecutionError
512
513 expected_exec = [
514 'find /tmp/tmpKGHKJ -mindepth 1 -delete']
515
516 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
517
518 self.assertRaises(exception.GlusterfsException,
519 self._driver._wipe_gluster_vol, gaddr1)
520
521 self.assertTrue(self._driver._restart_gluster_vol.called)
522 self.assertTrue(tempfile.mkdtemp.called)
523 self.assertTrue(self._driver._do_mount.called)
524 self.assertTrue(self._driver._do_umount.called)
525 self.assertTrue(shutil.rmtree.called)
526
527 def test_wipe_gluster_vol_mount_fail(self):
528 self._driver._restart_gluster_vol = mock.Mock()
529 self._driver._do_mount = mock.Mock()
530 self._driver._do_mount.side_effect = exception.GlusterfsException
531 self._driver._do_umount = mock.Mock()
532 shutil.rmtree = mock.Mock()
533
534 gaddr = glusterfs.GlusterAddress
535 gaddr1 = gaddr(self.gluster_target1)
536
537 expected_exec = [
538 'ssh root@host1 gluster volume set gv1 client.ssl off',
539 'ssh root@host1 gluster volume set gv1 server.ssl off']
540
541 self.assertRaises(exception.GlusterfsException,
542 self._driver._wipe_gluster_vol, gaddr1)
543
544 self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
545 self.assertTrue(self._driver._restart_gluster_vol.called)
546 self.assertTrue(tempfile.mkdtemp.called)
547 self.assertTrue(self._driver._do_mount.called)
548 self.assertFalse(self._driver._do_umount.called)
549 self.assertTrue(shutil.rmtree.called)
550
551 def test_wipe_gluster_vol_umount_fail(self):
552 self._driver._restart_gluster_vol = mock.Mock()
553 self._driver._do_mount = mock.Mock()
554 self._driver._do_umount = mock.Mock()
555 self._driver._do_umount.side_effect = exception.GlusterfsException
556 shutil.rmtree = mock.Mock()
557
558 gaddr = glusterfs.GlusterAddress
559 gaddr1 = gaddr(self.gluster_target1)
560
561 expected_exec = [
562 'ssh root@host1 gluster volume set gv1 client.ssl off',
563 'ssh root@host1 gluster volume set gv1 server.ssl off',
564 'find /tmp/tmpKGHKJ -mindepth 1 -delete']
565
566 self.assertRaises(exception.GlusterfsException,
567 self._driver._wipe_gluster_vol, gaddr1)
568
569 self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
570 self.assertTrue(self._driver._restart_gluster_vol.called)
571 self.assertTrue(tempfile.mkdtemp.called)
572 self.assertTrue(self._driver._do_mount.called)
573 self.assertTrue(self._driver._do_umount.called)
574 self.assertFalse(shutil.rmtree.called)
575
576 def test_create_share(self):
577 gaddr = glusterfs.GlusterAddress
578 gaddr1 = gaddr(self.gluster_target1)
579 gaddr2 = gaddr(self.gluster_target2)
580
581 self._driver.gluster_used_vols_dict = {gaddr1.export: gaddr1}
582 self._driver.gluster_unused_vols_dict = {gaddr2.export: gaddr2}
583
584 share = new_share()
585
586 exp_locn = self._driver.create_share(self._context, share)
587
588 self.assertEqual(exp_locn, gaddr2.export)
589
590 def test_create_share_excp(self):
591 gaddr = glusterfs.GlusterAddress
592 gaddr1 = gaddr(self.gluster_target1)
593 gaddr2 = gaddr(self.gluster_target2)
594
595 self._driver.gluster_used_vols_dict = {
596 gaddr2.export: gaddr2, gaddr1.export: gaddr1}
597 self._driver.gluster_unused_vols_dict = {}
598
599 share = new_share()
600
601 self.assertRaises(exception.GlusterfsException,
602 self._driver.create_share, self._context, share)
603
604 def test_delete_share(self):
605 self._driver._wipe_gluster_vol = mock.Mock()
606
607 gaddr = glusterfs.GlusterAddress
608 gaddr1 = gaddr(self.gluster_target1)
609 gaddr2 = gaddr(self.gluster_target2)
610
611 self._driver.gluster_used_vols_dict = {
612 gaddr2.export: gaddr2, gaddr1.export: gaddr1}
613 self._driver.gluster_unused_vols_dict = {}
614
615 share = fake_db_share2()[0]
616
617 self._driver.delete_share(self._context, share)
618
619 self.assertEqual(1, len(self._driver.gluster_used_vols_dict))
620 self.assertEqual(1, len(self._driver.gluster_unused_vols_dict))
621 self.assertTrue(self._driver._wipe_gluster_vol.called)
622
623 def test_delete_share_warn(self):
624 glusterfs_native.LOG.warn = mock.Mock()
625 self._driver._wipe_gluster_vol = mock.Mock()
626 self._driver._push_gluster_vol = mock.Mock()
627
628 gaddr = glusterfs.GlusterAddress
629 gaddr1 = gaddr(self.gluster_target1)
630 gaddr2 = gaddr(self.gluster_target2)
631
632 self._driver.gluster_used_vols_dict = {}
633 self._driver.gluster_unused_vols_dict = {
634 gaddr2.export: gaddr2, gaddr1.export: gaddr1}
635
636 share = fake_db_share2()[0]
637
638 self._driver.delete_share(self._context, share)
639
640 self.assertTrue(glusterfs_native.LOG.warn.called)
641 self.assertFalse(self._driver._wipe_gluster_vol.called)
642 self.assertFalse(self._driver._push_gluster_vol.called)
643
644 def test_delete_share_excp1(self):
645 self._driver._wipe_gluster_vol = mock.Mock()
646 self._driver._wipe_gluster_vol.side_effect = (
647 exception.GlusterfsException)
648 self._driver._push_gluster_vol = mock.Mock()
649
650 gaddr = glusterfs.GlusterAddress
651 gaddr1 = gaddr(self.gluster_target1)
652 gaddr2 = gaddr(self.gluster_target2)
653
654 self._driver.gluster_used_vols_dict = {
655 gaddr2.export: gaddr2, gaddr1.export: gaddr1}
656 self._driver.gluster_unused_vols_dict = {}
657
658 share = fake_db_share2()[0]
659
660 self.assertRaises(exception.GlusterfsException,
661 self._driver.delete_share, self._context, share)
662
663 self.assertTrue(self._driver._wipe_gluster_vol.called)
664 self.assertFalse(self._driver._push_gluster_vol.called)
665
666 def test_allow_access(self):
667 self._driver._restart_gluster_vol = mock.Mock()
668 access = {'access_type': 'cert', 'access_to': 'client.example.com'}
669 gaddr = glusterfs.GlusterAddress
670 gaddr1 = gaddr(self.gluster_target1)
671 gaddr2 = gaddr(self.gluster_target2)
672
673 self._driver.gluster_used_vols_dict = {gaddr1.export: gaddr1}
674 self._driver.gluster_unused_vols_dict = {gaddr2.export: gaddr2}
675
676 share = fake_db_share1()[0]
677
678 expected_exec = [
679 'ssh root@host1 gluster volume set gv1 '
680 'auth.ssl-allow client.example.com']
681
682 self._driver.allow_access(self._context, share, access)
683 self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
684 self.assertTrue(self._driver._restart_gluster_vol.called)
685
686 def test_allow_access_invalid_access_type(self):
687 self._driver._restart_gluster_vol = mock.Mock()
688 access = {'access_type': 'invalid', 'access_to': 'client.example.com'}
689 share = fake_db_share1()[0]
690 expected_exec = []
691
692 self.assertRaises(exception.InvalidShareAccess,
693 self._driver.allow_access,
694 self._context, share, access)
695 self.assertFalse(self._driver._restart_gluster_vol.called)
696 self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
697
698 def test_allow_access_excp(self):
699 self._driver._restart_gluster_vol = mock.Mock()
700 access = {'access_type': 'cert', 'access_to': 'client.example.com'}
701 gaddr = glusterfs.GlusterAddress
702 gaddr1 = gaddr(self.gluster_target1)
703 gaddr2 = gaddr(self.gluster_target2)
704
705 self._driver.gluster_used_vols_dict = {gaddr1.export: gaddr1}
706 self._driver.gluster_unused_vols_dict = {gaddr2.export: gaddr2}
707
708 share = fake_db_share1()[0]
709
710 def exec_runner(*ignore_args, **ignore_kwargs):
711 raise exception.ProcessExecutionError
712
713 expected_exec = [
714 'ssh root@host1 gluster volume set gv1 '
715 'auth.ssl-allow client.example.com']
716
717 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
718
719 self.assertRaises(exception.GlusterfsException,
720 self._driver.allow_access,
721 self._context, share, access)
722 self.assertFalse(self._driver._restart_gluster_vol.called)
723
724 def test_deny_access(self):
725 self._driver._restart_gluster_vol = mock.Mock()
726 access = {'access_type': 'cert', 'access_to': 'NotApplicable'}
727 gaddr = glusterfs.GlusterAddress
728 gaddr1 = gaddr(self.gluster_target1)
729 gaddr2 = gaddr(self.gluster_target2)
730
731 self._driver.gluster_used_vols_dict = {gaddr1.export: gaddr1}
732 self._driver.gluster_unused_vols_dict = {gaddr2.export: gaddr2}
733
734 share = fake_db_share1()[0]
735
736 expected_exec = [
737 'ssh root@host1 gluster volume reset gv1 auth.ssl-allow']
738
739 self._driver.deny_access(self._context, share, access)
740 self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
741 self.assertTrue(self._driver._restart_gluster_vol.called)
742
743 def test_deny_access_invalid_access_type(self):
744 self._driver._restart_gluster_vol = mock.Mock()
745 access = {'access_type': 'invalid', 'access_to': 'NotApplicable'}
746 share = fake_db_share1()[0]
747 expected_exec = []
748
749 self.assertRaises(exception.InvalidShareAccess,
750 self._driver.deny_access,
751 self._context, share, access)
752 self.assertFalse(self._driver._restart_gluster_vol.called)
753 self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
754
755 def test_deny_access_excp(self):
756 self._driver._restart_gluster_vol = mock.Mock()
757 access = {'access_type': 'cert', 'access_to': 'NotApplicable'}
758 gaddr = glusterfs.GlusterAddress
759 gaddr1 = gaddr(self.gluster_target1)
760 gaddr2 = gaddr(self.gluster_target2)
761
762 self._driver.gluster_used_vols_dict = {gaddr1.export: gaddr1}
763 self._driver.gluster_unused_vols_dict = {gaddr2.export: gaddr2}
764
765 share = fake_db_share1()[0]
766
767 expected_exec = [
768 'ssh root@host1 gluster volume reset gv1 auth.ssl-allow']
769
770 def exec_runner(*ignore_args, **ignore_kwargs):
771 raise exception.ProcessExecutionError
772
773 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
774
775 self.assertRaises(exception.GlusterfsException,
776 self._driver.deny_access,
777 self._context, share, access)
778 self.assertFalse(self._driver._restart_gluster_vol.called)
779
780 def test_get_share_stats_refresh_false(self):
781 self._driver._stats = mock.Mock()
782 ret = self._driver.get_share_stats()
783 self.assertEqual(ret, self._driver._stats)
784
785 def test_get_share_stats_refresh_true(self):
786 def foo():
787 self._driver._stats = {'key': 'value'}
788 self._driver._update_share_stats = mock.Mock(side_effect=foo)
789 ret = self._driver.get_share_stats(refresh=True)
790 self.assertEqual(ret, {'key': 'value'})
791
792 def test_update_share_stats(self):
793 test_data = {
794 'share_backend_name': 'GlusterFS-Native',
795 'vendor_name': 'Red Hat',
796 'driver_version': '1.1',
797 'storage_protocol': 'glusterfs',
798 'reserved_percentage': 0,
799 'QoS_support': False,
800 'total_capacity_gb': 'infinite',
801 'free_capacity_gb': 'infinite',
802 }
803
804 self._driver._update_share_stats()
805
806 self.assertEqual(self._driver._stats, test_data)
diff --git a/manila/tests/test_share_glusterfs_native.py b/manila/tests/test_share_glusterfs_native.py
deleted file mode 100644
index ec9e83e..0000000
--- a/manila/tests/test_share_glusterfs_native.py
+++ /dev/null
@@ -1,199 +0,0 @@
1# Copyright (c) 2014 Red Hat, 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""" GlusterFS native protocol (glusterfs) driver for shares.
17
18Test cases for GlusterFS native protocol driver.
19"""
20
21
22import mock
23from oslo.config import cfg
24
25from manila import context
26from manila import exception
27from manila.share import configuration as config
28from manila.share.drivers import glusterfs_native
29from manila import test
30from manila.tests.db import fakes as db_fakes
31from manila.tests import fake_utils
32
33
34CONF = cfg.CONF
35
36
37gluster_address_attrs = {
38 'export': '127.0.0.1:/testvol',
39 'host': '127.0.0.1',
40 'qualified': 'testuser@127.0.0.1:/testvol',
41 'remote_user': 'testuser',
42 'volume': 'testvol',
43}
44
45
46def fake_share(**kwargs):
47 share = {
48 'id': 'fakeid',
49 'name': 'fakename',
50 'size': 1,
51 'share_proto': 'glusterfs',
52 'export_location': '127.0.0.1:/mnt/glusterfs/testvol',
53 }
54 share.update(kwargs)
55 return db_fakes.FakeModel(share)
56
57
58class GlusterfsNativeShareDriverTestCase(test.TestCase):
59 """Tests GlusterfsNativeShareDriver."""
60
61 def setUp(self):
62 super(GlusterfsNativeShareDriverTestCase, self).setUp()
63 fake_utils.stub_out_utils_execute(self.stubs)
64 self._execute = fake_utils.fake_execute
65 self._context = context.get_admin_context()
66
67 CONF.set_default('glusterfs_mount_point_base', '/mnt/glusterfs')
68 CONF.set_default('reserved_share_percentage', 50)
69
70 self.fake_conf = config.Configuration(None)
71 self._db = mock.Mock()
72 self._driver = glusterfs_native.GlusterfsNativeShareDriver(
73 self._db, execute=self._execute,
74 configuration=self.fake_conf)
75 self._driver.gluster_address = mock.Mock(**gluster_address_attrs)
76 self.share = fake_share()
77
78 self.addCleanup(fake_utils.fake_execute_set_repliers, [])
79 self.addCleanup(fake_utils.fake_execute_clear_log)
80
81 def test_create_share(self):
82 self._driver._setup_gluster_vol = mock.Mock()
83
84 expected = gluster_address_attrs['export']
85 actual = self._driver.create_share(self._context, self.share)
86
87 self.assertTrue(self._driver._setup_gluster_vol.called)
88 self.assertEqual(actual, expected)
89
90 def test_create_share_error(self):
91 self._driver._setup_gluster_vol = mock.Mock()
92 self._driver._setup_gluster_vol.side_effect = (
93 exception.ProcessExecutionError)
94
95 self.assertRaises(exception.ProcessExecutionError,
96 self._driver.create_share, self._context, self.share)
97
98 def test_delete_share(self):
99 self._driver.gluster_address = mock.Mock(
100 make_gluster_args=mock.Mock(return_value=(('true',), {})))
101
102 self._driver.delete_share(self._context, self.share)
103
104 self.assertTrue(self._driver.gluster_address.make_gluster_args.called)
105 self.assertEqual(
106 self._driver.gluster_address.make_gluster_args.call_args[0][1],
107 'reset')
108
109 def test_delete_share_error(self):
110 self._driver.gluster_address = mock.Mock(
111 make_gluster_args=mock.Mock(return_value=(('true',), {})))
112
113 def exec_runner(*ignore_args, **ignore_kw):
114 raise exception.ProcessExecutionError
115
116 expected_exec = ['true']
117 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
118
119 self.assertRaises(exception.ProcessExecutionError,
120 self._driver.delete_share, self._context, self.share)
121
122 def test_allow_access(self):
123 self._driver.gluster_address = mock.Mock(
124 make_gluster_args=mock.Mock(return_value=(('true',), {})))
125 access = {'access_type': 'cert', 'access_to': 'client.example.com'}
126
127 self._driver.allow_access(self._context, self.share, access)
128
129 self.assertTrue(self._driver.gluster_address.make_gluster_args.called)
130 self.assertEqual(
131 self._driver.gluster_address.make_gluster_args.call_args[0][1],
132 'set')
133 self.assertEqual(
134 self._driver.gluster_address.make_gluster_args.call_args[0][-2],
135 'auth.ssl-allow')
136 self.assertEqual(
137 self._driver.gluster_address.make_gluster_args.call_args[0][-1],
138 access['access_to'])
139
140 def test_allow_access_error(self):
141 # Invalid access type
142 access = {'access_type': 'invalid', 'access_to': 'client.example.com'}
143
144 self.assertRaises(exception.InvalidShareAccess,
145 self._driver.allow_access, self._context, self.share,
146 access)
147
148 # ProcessExecutionError
149 self._driver.gluster_address = mock.Mock(
150 make_gluster_args=mock.Mock(return_value=(('true',), {})))
151 access = {'access_type': 'cert', 'access_to': 'client.example.com'}
152
153 def exec_runner(*ignore_args, **ignore_kw):
154 raise exception.ProcessExecutionError
155
156 expected_exec = ['true']
157 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
158
159 self.assertRaises(exception.ProcessExecutionError,
160 self._driver.allow_access, self._context, self.share,
161 access)
162
163 def test_deny_access(self):
164 self._driver.gluster_address = mock.Mock(
165 make_gluster_args=mock.Mock(return_value=(('true',), {})))
166 access = {'access_type': 'cert', 'access_to': 'client.example.com'}
167
168 self._driver.deny_access(self._context, self.share, access)
169
170 self.assertTrue(self._driver.gluster_address.make_gluster_args.called)
171 self.assertEqual(
172 self._driver.gluster_address.make_gluster_args.call_args[0][1],
173 'reset')
174 self.assertEqual(
175 self._driver.gluster_address.make_gluster_args.call_args[0][-1],
176 'auth.ssl-allow')
177
178 def test_deny_access_error(self):
179 # Invalid access type
180 access = {'access_type': 'invalid', 'access_to': 'client.example.com'}
181
182 self.assertRaises(exception.InvalidShareAccess,
183 self._driver.deny_access, self._context, self.share,
184 access)
185
186 # ProcessExecutionError
187 self._driver.gluster_address = mock.Mock(
188 make_gluster_args=mock.Mock(return_value=(('true',), {})))
189 access = {'access_type': 'cert', 'access_to': 'client.example.com'}
190
191 def exec_runner(*ignore_args, **ignore_kw):
192 raise exception.ProcessExecutionError
193
194 expected_exec = ['true']
195 fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
196
197 self.assertRaises(exception.ProcessExecutionError,
198 self._driver.deny_access, self._context, self.share,
199 access)