Coverage for manila/tests/share/drivers/glusterfs/test_common.py: 99%
401 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright (c) 2015 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.
16"""Test cases for GlusterFS common routines."""
18from unittest import mock
20import ddt
21from oslo_config import cfg
23from manila import exception
24from manila.privsep import os as privsep_os
25from manila.share.drivers.glusterfs import common
26from manila import test
27from manila.tests import fake_utils
30CONF = cfg.CONF
33fake_gluster_manager_attrs = {
34 'export': '127.0.0.1:/testvol',
35 'host': '127.0.0.1',
36 'qualified': 'testuser@127.0.0.1:/testvol',
37 'user': 'testuser',
38 'volume': 'testvol',
39 'path_to_private_key': '/fakepath/to/privatekey',
40 'remote_server_password': 'fakepassword',
41}
42fake_args = ('foo', 'bar')
43fake_kwargs = {'key1': 'value1', 'key2': 'value2'}
44fake_path_to_private_key = '/fakepath/to/privatekey'
45fake_remote_server_password = 'fakepassword'
46NFS_EXPORT_DIR = 'nfs.export-dir'
48fakehost = 'example.com'
49fakevol = 'testvol'
50fakeexport = ':/'.join((fakehost, fakevol))
51fakemnt = '/mnt/glusterfs'
54@ddt.ddt
55class GlusterManagerTestCase(test.TestCase):
56 """Tests GlusterManager."""
58 def setUp(self):
59 super(GlusterManagerTestCase, self).setUp()
60 self.fake_execf = mock.Mock()
61 self.fake_executor = mock.Mock(return_value=('', ''))
62 with mock.patch.object(common.GlusterManager, 'make_gluster_call',
63 return_value=self.fake_executor):
64 self._gluster_manager = common.GlusterManager(
65 'testuser@127.0.0.1:/testvol', self.fake_execf,
66 fake_path_to_private_key, fake_remote_server_password)
67 fake_gluster_manager_dict = {
68 'host': '127.0.0.1',
69 'user': 'testuser',
70 'volume': 'testvol'
71 }
72 self._gluster_manager_dict = common.GlusterManager(
73 fake_gluster_manager_dict, self.fake_execf,
74 fake_path_to_private_key, fake_remote_server_password)
75 self._gluster_manager_array = [self._gluster_manager,
76 self._gluster_manager_dict]
78 def test_check_volume_presence(self):
79 common._check_volume_presence(mock.Mock())(self._gluster_manager)
81 def test_check_volume_presence_error(self):
82 gmgr = common.GlusterManager('testuser@127.0.0.1')
84 self.assertRaises(
85 exception.GlusterfsException,
86 common._check_volume_presence(mock.Mock()), gmgr)
88 def test_volxml_get(self):
89 xmlout = mock.Mock()
90 value = mock.Mock()
91 value.text = 'foobar'
92 xmlout.find = mock.Mock(return_value=value)
94 ret = common.volxml_get(xmlout, 'some/path')
96 self.assertEqual('foobar', ret)
98 @ddt.data(None, 'some-value')
99 def test_volxml_get_notfound_fallback(self, default):
100 xmlout = mock.Mock()
101 xmlout.find = mock.Mock(return_value=None)
103 ret = common.volxml_get(xmlout, 'some/path', default=default)
105 self.assertEqual(default, ret)
107 def test_volxml_get_multiple(self):
108 xmlout = mock.Mock()
109 value = mock.Mock()
110 value.text = 'foobar'
111 xmlout.find = mock.Mock(side_effect=(None, value))
113 ret = common.volxml_get(xmlout, 'some/path', 'better/path')
115 self.assertEqual('foobar', ret)
117 def test_volxml_get_notfound(self):
118 xmlout = mock.Mock()
119 xmlout.find = mock.Mock(return_value=None)
121 self.assertRaises(exception.InvalidShare, common.volxml_get,
122 xmlout, 'some/path')
124 def test_gluster_manager_common_init(self):
125 for gmgr in self._gluster_manager_array:
126 self.assertEqual(
127 fake_gluster_manager_attrs['user'],
128 gmgr.user)
129 self.assertEqual(
130 fake_gluster_manager_attrs['host'],
131 gmgr.host)
132 self.assertEqual(
133 fake_gluster_manager_attrs['volume'],
134 gmgr.volume)
135 self.assertEqual(
136 fake_gluster_manager_attrs['qualified'],
137 gmgr.qualified)
138 self.assertEqual(
139 fake_gluster_manager_attrs['export'],
140 gmgr.export)
141 self.assertEqual(
142 fake_gluster_manager_attrs['path_to_private_key'],
143 gmgr.path_to_private_key)
144 self.assertEqual(
145 fake_gluster_manager_attrs['remote_server_password'],
146 gmgr.remote_server_password)
147 self.assertEqual(
148 self.fake_executor,
149 gmgr.gluster_call)
151 @ddt.data({'user': 'testuser', 'host': '127.0.0.1',
152 'volume': 'testvol', 'path': None},
153 {'user': None, 'host': '127.0.0.1',
154 'volume': 'testvol', 'path': '/testpath'},
155 {'user': None, 'host': '127.0.0.1',
156 'volume': 'testvol', 'path': None},
157 {'user': None, 'host': '127.0.0.1',
158 'volume': None, 'path': None},
159 {'user': 'testuser', 'host': '127.0.0.1',
160 'volume': None, 'path': None},
161 {'user': 'testuser', 'host': '127.0.0.1',
162 'volume': 'testvol', 'path': '/testpath'})
163 def test_gluster_manager_init_check(self, test_addr_dict):
164 test_gluster_manager = common.GlusterManager(
165 test_addr_dict, self.fake_execf)
166 self.assertEqual(test_addr_dict, test_gluster_manager.components)
168 @ddt.data(None, True)
169 def test_gluster_manager_init_has_vol(self, has_volume):
170 test_gluster_manager = common.GlusterManager(
171 'testuser@127.0.0.1:/testvol', self.fake_execf,
172 requires={'volume': has_volume})
173 self.assertEqual('testvol', test_gluster_manager.volume)
175 @ddt.data(None, True)
176 def test_gluster_manager_dict_init_has_vol(self, has_volume):
177 test_addr_dict = {'user': 'testuser',
178 'host': '127.0.0.1',
179 'volume': 'testvol',
180 'path': '/testdir'}
181 test_gluster_manager = common.GlusterManager(
182 test_addr_dict, self.fake_execf,
183 requires={'volume': has_volume})
184 self.assertEqual('testvol', test_gluster_manager.volume)
186 @ddt.data(None, False)
187 def test_gluster_manager_init_no_vol(self, has_volume):
188 test_gluster_manager = common.GlusterManager(
189 'testuser@127.0.0.1', self.fake_execf,
190 requires={'volume': has_volume})
191 self.assertIsNone(test_gluster_manager.volume)
193 @ddt.data(None, False)
194 def test_gluster_manager_dict_init_no_vol(self, has_volume):
195 test_addr_dict = {'user': 'testuser',
196 'host': '127.0.0.1'}
197 test_gluster_manager = common.GlusterManager(
198 test_addr_dict, self.fake_execf,
199 requires={'volume': has_volume})
200 self.assertIsNone(test_gluster_manager.volume)
202 def test_gluster_manager_init_has_shouldnt_have_vol(self):
203 self.assertRaises(exception.GlusterfsException,
204 common.GlusterManager,
205 'testuser@127.0.0.1:/testvol',
206 self.fake_execf, requires={'volume': False})
208 def test_gluster_manager_dict_init_has_shouldnt_have_vol(self):
209 test_addr_dict = {'user': 'testuser',
210 'host': '127.0.0.1',
211 'volume': 'testvol'}
212 self.assertRaises(exception.GlusterfsException,
213 common.GlusterManager,
214 test_addr_dict,
215 self.fake_execf, requires={'volume': False})
217 def test_gluster_manager_hasnt_should_have_vol(self):
218 self.assertRaises(exception.GlusterfsException,
219 common.GlusterManager, 'testuser@127.0.0.1',
220 self.fake_execf, requires={'volume': True})
222 def test_gluster_manager_dict_hasnt_should_have_vol(self):
223 test_addr_dict = {'user': 'testuser',
224 'host': '127.0.0.1'}
225 self.assertRaises(exception.GlusterfsException,
226 common.GlusterManager, test_addr_dict,
227 self.fake_execf, requires={'volume': True})
229 def test_gluster_manager_invalid(self):
230 self.assertRaises(exception.GlusterfsException,
231 common.GlusterManager, '127.0.0.1:vol',
232 'self.fake_execf')
234 def test_gluster_manager_dict_invalid_req_host(self):
235 test_addr_dict = {'user': 'testuser',
236 'volume': 'testvol'}
237 self.assertRaises(exception.GlusterfsException,
238 common.GlusterManager, test_addr_dict,
239 'self.fake_execf')
241 @ddt.data({'user': 'testuser'},
242 {'host': 'johndoe@example.com'},
243 {'host': 'example.com/so', 'volume': 'me/path'},
244 {'user': 'user@error', 'host': "example.com", 'volume': 'vol'},
245 {'host': 'example.com', 'volume': 'vol', 'pith': '/path'},
246 {'host': 'example.com', 'path': '/path'},
247 {'user': 'user@error', 'host': "example.com", 'path': '/path'})
248 def test_gluster_manager_dict_invalid_input(self, test_addr_dict):
249 self.assertRaises(exception.GlusterfsException,
250 common.GlusterManager, test_addr_dict,
251 'self.fake_execf')
253 def test_gluster_manager_getattr(self):
254 self.assertEqual('testvol', self._gluster_manager.volume)
256 def test_gluster_manager_getattr_called(self):
257 class FakeGlusterManager(common.GlusterManager):
258 pass
260 _gluster_manager = FakeGlusterManager('127.0.0.1:/testvol',
261 self.fake_execf)
262 FakeGlusterManager.__getattr__ = mock.Mock()
263 _gluster_manager.volume
264 _gluster_manager.__getattr__.assert_called_once_with('volume')
266 def test_gluster_manager_getattr_noattr(self):
267 self.assertRaises(AttributeError, getattr, self._gluster_manager,
268 'fakeprop')
270 @ddt.data({'mockargs': {}, 'kwargs': {}},
271 {'mockargs': {'side_effect': exception.ProcessExecutionError},
272 'kwargs': {'error_policy': 'suppress'}},
273 {'mockargs': {
274 'side_effect': exception.ProcessExecutionError(exit_code=2)},
275 'kwargs': {'error_policy': (2,)}})
276 @ddt.unpack
277 def test_gluster_manager_make_gluster_call_local(self, mockargs, kwargs):
278 fake_obj = mock.Mock(**mockargs)
279 fake_execute = mock.Mock()
280 kwargs.update(fake_kwargs)
281 with mock.patch.object(common.ganesha_utils, 'RootExecutor',
282 mock.Mock(return_value=fake_obj)):
283 gluster_manager = common.GlusterManager(
284 '127.0.0.1:/testvol', self.fake_execf)
285 gluster_manager.make_gluster_call(fake_execute)(*fake_args,
286 **kwargs)
287 common.ganesha_utils.RootExecutor.assert_called_with(
288 fake_execute)
289 fake_obj.assert_called_once_with(
290 *(('gluster',) + fake_args), **fake_kwargs)
292 def test_gluster_manager_make_gluster_call_remote(self):
293 fake_obj = mock.Mock()
294 fake_execute = mock.Mock()
295 with mock.patch.object(common.ganesha_utils, 'SSHExecutor',
296 mock.Mock(return_value=fake_obj)):
297 gluster_manager = common.GlusterManager(
298 'testuser@127.0.0.1:/testvol', self.fake_execf,
299 fake_path_to_private_key, fake_remote_server_password)
300 gluster_manager.make_gluster_call(fake_execute)(*fake_args,
301 **fake_kwargs)
302 common.ganesha_utils.SSHExecutor.assert_called_with(
303 gluster_manager.host, 22, None, gluster_manager.user,
304 password=gluster_manager.remote_server_password,
305 privatekey=gluster_manager.path_to_private_key)
306 fake_obj.assert_called_once_with(
307 *(('gluster',) + fake_args), **fake_kwargs)
309 @ddt.data({'trouble': exception.ProcessExecutionError,
310 '_exception': exception.GlusterfsException, 'xkw': {}},
311 {'trouble': exception.ProcessExecutionError(exit_code=2),
312 '_exception': exception.GlusterfsException,
313 'xkw': {'error_policy': (1,)}},
314 {'trouble': exception.ProcessExecutionError,
315 '_exception': exception.GlusterfsException,
316 'xkw': {'error_policy': 'coerce'}},
317 {'trouble': exception.ProcessExecutionError,
318 '_exception': exception.ProcessExecutionError,
319 'xkw': {'error_policy': 'raw'}},
320 {'trouble': RuntimeError, '_exception': RuntimeError, 'xkw': {}})
321 @ddt.unpack
322 def test_gluster_manager_make_gluster_call_error(self, trouble,
323 _exception, xkw):
324 fake_obj = mock.Mock(side_effect=trouble)
325 fake_execute = mock.Mock()
326 kwargs = fake_kwargs.copy()
327 kwargs.update(xkw)
328 with mock.patch.object(common.ganesha_utils, 'RootExecutor',
329 mock.Mock(return_value=fake_obj)):
330 gluster_manager = common.GlusterManager(
331 '127.0.0.1:/testvol', self.fake_execf)
333 self.assertRaises(_exception,
334 gluster_manager.make_gluster_call(fake_execute),
335 *fake_args, **kwargs)
337 common.ganesha_utils.RootExecutor.assert_called_with(
338 fake_execute)
339 fake_obj.assert_called_once_with(
340 *(('gluster',) + fake_args), **fake_kwargs)
342 def test_gluster_manager_make_gluster_call_bad_policy(self):
343 fake_obj = mock.Mock()
344 fake_execute = mock.Mock()
345 with mock.patch.object(common.ganesha_utils, 'RootExecutor',
346 mock.Mock(return_value=fake_obj)):
347 gluster_manager = common.GlusterManager(
348 '127.0.0.1:/testvol', self.fake_execf)
350 self.assertRaises(TypeError,
351 gluster_manager.make_gluster_call(fake_execute),
352 *fake_args, error_policy='foobar')
354 @ddt.data({}, {'opErrstr': None}, {'opErrstr': 'error'})
355 def test_xml_response_check(self, xdict):
356 fdict = {'opRet': '0', 'opErrno': '0', 'some/count': '1'}
357 fdict.update(xdict)
359 def vxget(x, e, *a):
360 if a: 360 ↛ 361line 360 didn't jump to line 361 because the condition on line 360 was never true
361 return fdict.get(e, a[0])
362 else:
363 return fdict[e]
365 xtree = mock.Mock()
366 command = ['volume', 'command', 'fake']
368 with mock.patch.object(common, 'volxml_get', side_effect=vxget):
369 self._gluster_manager.xml_response_check(xtree, command,
370 'some/count')
372 self.assertTrue(common.volxml_get.called)
374 @ddt.data('1', '2')
375 def test_xml_response_check_failure(self, count):
376 fdict = {'opRet': '-1', 'opErrno': '0', 'some/count': count}
378 def vxget(x, e, *a):
379 if a: 379 ↛ 380line 379 didn't jump to line 380 because the condition on line 379 was never true
380 return fdict.get(e, a[0])
381 else:
382 return fdict[e]
384 xtree = mock.Mock()
385 command = ['volume', 'command', 'fake']
387 with mock.patch.object(common, 'volxml_get', side_effect=vxget):
388 self.assertRaises(exception.GlusterfsException,
389 self._gluster_manager.xml_response_check,
390 xtree, command, 'some/count')
392 self.assertTrue(common.volxml_get.called)
394 @ddt.data({'opRet': '-2', 'opErrno': '0', 'some/count': '1'},
395 {'opRet': '0', 'opErrno': '1', 'some/count': '1'},
396 {'opRet': '0', 'opErrno': '0', 'some/count': '0'},
397 {'opRet': '0', 'opErrno': '0', 'some/count': '2'})
398 def test_xml_response_check_invalid(self, fdict):
400 def vxget(x, *e, **kw):
401 if kw:
402 return fdict.get(e[0], kw['default'])
403 else:
404 return fdict[e[0]]
406 xtree = mock.Mock()
407 command = ['volume', 'command', 'fake']
409 with mock.patch.object(common, 'volxml_get', side_effect=vxget):
410 self.assertRaises(exception.InvalidShare,
411 self._gluster_manager.xml_response_check,
412 xtree, command, 'some/count')
414 self.assertTrue(common.volxml_get.called)
416 @ddt.data({'opRet': '0', 'opErrno': '0'},
417 {'opRet': '0', 'opErrno': '0', 'some/count': '2'})
418 def test_xml_response_check_count_ignored(self, fdict):
420 def vxget(x, e, *a):
421 if a: 421 ↛ 422line 421 didn't jump to line 422 because the condition on line 421 was never true
422 return fdict.get(e, a[0])
423 else:
424 return fdict[e]
426 xtree = mock.Mock()
427 command = ['volume', 'command', 'fake']
429 with mock.patch.object(common, 'volxml_get', side_effect=vxget):
430 self._gluster_manager.xml_response_check(xtree, command)
432 self.assertTrue(common.volxml_get.called)
434 def test_get_vol_option_via_info_empty_volinfo(self):
435 args = ('--xml', 'volume', 'info', self._gluster_manager.volume)
436 self.mock_object(self._gluster_manager, 'gluster_call',
437 mock.Mock(return_value=('', {})))
438 self.assertRaises(exception.GlusterfsException,
439 self._gluster_manager._get_vol_option_via_info,
440 'foobar')
441 self._gluster_manager.gluster_call.assert_called_once_with(
442 *args, log=mock.ANY)
444 def test_get_vol_option_via_info_ambiguous_volinfo(self):
446 def xml_output(*ignore_args, **ignore_kwargs):
447 return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
448<cliOutput>
449 <opRet>0</opRet>
450 <opErrno>0</opErrno>
451 <opErrstr/>
452 <volInfo>
453 <volumes>
454 <count>0</count>
455 </volumes>
456 </volInfo>
457</cliOutput>""", ''
459 args = ('--xml', 'volume', 'info', self._gluster_manager.volume)
460 self.mock_object(self._gluster_manager, 'gluster_call',
461 mock.Mock(side_effect=xml_output))
462 self.assertRaises(exception.InvalidShare,
463 self._gluster_manager._get_vol_option_via_info,
464 'foobar')
465 self._gluster_manager.gluster_call.assert_called_once_with(
466 *args, log=mock.ANY)
468 def test_get_vol_option_via_info_trivial_volinfo(self):
470 def xml_output(*ignore_args, **ignore_kwargs):
471 return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
472<cliOutput>
473 <opRet>0</opRet>
474 <opErrno>0</opErrno>
475 <opErrstr/>
476 <volInfo>
477 <volumes>
478 <volume>
479 </volume>
480 <count>1</count>
481 </volumes>
482 </volInfo>
483</cliOutput>""", ''
485 args = ('--xml', 'volume', 'info', self._gluster_manager.volume)
486 self.mock_object(self._gluster_manager, 'gluster_call',
487 mock.Mock(side_effect=xml_output))
488 ret = self._gluster_manager._get_vol_option_via_info('foobar')
489 self.assertIsNone(ret)
490 self._gluster_manager.gluster_call.assert_called_once_with(
491 *args, log=mock.ANY)
493 def test_get_vol_option_via_info(self):
495 def xml_output(*ignore_args, **ignore_kwargs):
496 return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
497<cliOutput>
498 <opRet>0</opRet>
499 <opErrno>0</opErrno>
500 <opErrstr/>
501 <volInfo>
502 <volumes>
503 <volume>
504 <options>
505 <option>
506 <name>foobar</name>
507 <value>FIRE MONKEY!</value>
508 </option>
509 </options>
510 </volume>
511 <count>1</count>
512 </volumes>
513 </volInfo>
514</cliOutput>""", ''
516 args = ('--xml', 'volume', 'info', self._gluster_manager.volume)
517 self.mock_object(self._gluster_manager, 'gluster_call',
518 mock.Mock(side_effect=xml_output))
519 ret = self._gluster_manager._get_vol_option_via_info('foobar')
520 self.assertEqual('FIRE MONKEY!', ret)
521 self._gluster_manager.gluster_call.assert_called_once_with(
522 *args, log=mock.ANY)
524 def test_get_vol_user_option(self):
525 self.mock_object(self._gluster_manager, '_get_vol_option_via_info',
526 mock.Mock(return_value='VALUE'))
528 ret = self._gluster_manager._get_vol_user_option('OPT')
530 self.assertEqual(ret, 'VALUE')
531 (self._gluster_manager._get_vol_option_via_info.
532 assert_called_once_with('user.OPT'))
534 def test_get_vol_regular_option_empty_reponse(self):
535 args = ('--xml', 'volume', 'get', self._gluster_manager.volume,
536 NFS_EXPORT_DIR)
537 self.mock_object(self._gluster_manager, 'gluster_call',
538 mock.Mock(return_value=('', {})))
540 ret = self._gluster_manager._get_vol_regular_option(NFS_EXPORT_DIR)
542 self.assertIsNone(ret)
543 self._gluster_manager.gluster_call.assert_called_once_with(
544 *args, check_exit_code=False)
546 @ddt.data(0, 2)
547 def test_get_vol_regular_option_ambiguous_volinfo(self, count):
549 def xml_output(*ignore_args, **ignore_kwargs):
550 return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
551<cliOutput>
552 <opRet>0</opRet>
553 <opErrno>0</opErrno>
554 <opErrstr/>
555 <volGetopts>
556 <count>%d</count>
557 </volGetopts>
558</cliOutput>""" % count, ''
560 args = ('--xml', 'volume', 'get', self._gluster_manager.volume,
561 NFS_EXPORT_DIR)
562 self.mock_object(self._gluster_manager, 'gluster_call',
563 mock.Mock(side_effect=xml_output))
565 self.assertRaises(exception.InvalidShare,
566 self._gluster_manager._get_vol_regular_option,
567 NFS_EXPORT_DIR)
569 self._gluster_manager.gluster_call.assert_called_once_with(
570 *args, check_exit_code=False)
572 @ddt.data({'start': "", 'end': ""}, {'start': "<Opt>", 'end': "</Opt>"})
573 def test_get_vol_regular_option(self, extratag):
575 def xml_output(*ignore_args, **ignore_kwargs):
576 return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
577<cliOutput>
578 <opRet>0</opRet>
579 <opErrno>0</opErrno>
580 <opErrstr/>
581 <volGetopts>
582 <count>1</count>
583 %(start)s
584 <Option>nfs.export-dir</Option>
585 <Value>/foo(10.0.0.1|10.0.0.2),/bar(10.0.0.1)</Value>
586 %(end)s
587 </volGetopts>
588</cliOutput>""" % extratag, ''
590 args = ('--xml', 'volume', 'get', self._gluster_manager.volume,
591 NFS_EXPORT_DIR)
592 self.mock_object(self._gluster_manager, 'gluster_call',
593 mock.Mock(side_effect=xml_output))
595 ret = self._gluster_manager._get_vol_regular_option(NFS_EXPORT_DIR)
597 self.assertEqual('/foo(10.0.0.1|10.0.0.2),/bar(10.0.0.1)', ret)
598 self._gluster_manager.gluster_call.assert_called_once_with(
599 *args, check_exit_code=False)
601 def test_get_vol_regular_option_not_suppored(self):
602 args = ('--xml', 'volume', 'get', self._gluster_manager.volume,
603 NFS_EXPORT_DIR)
604 self.mock_object(self._gluster_manager, 'gluster_call',
605 mock.Mock(return_value=(
606 """Ceci n'est pas un XML.""", '')))
607 self.mock_object(self._gluster_manager, '_get_vol_option_via_info',
608 mock.Mock(return_value="VALUE"))
610 ret = self._gluster_manager._get_vol_regular_option(NFS_EXPORT_DIR)
612 self.assertEqual("VALUE", ret)
613 self._gluster_manager.gluster_call.assert_called_once_with(
614 *args, check_exit_code=False)
615 (self._gluster_manager._get_vol_option_via_info.
616 assert_called_once_with(NFS_EXPORT_DIR))
618 @ddt.data({'opt': 'some.option', 'opttype': 'regular',
619 'lowopt': 'some.option'},
620 {'opt': 'user.param', 'opttype': 'user', 'lowopt': 'param'})
621 @ddt.unpack
622 def test_get_vol_option(self, opt, opttype, lowopt):
623 for t in ('user', 'regular'):
624 self.mock_object(self._gluster_manager, '_get_vol_%s_option' % t,
625 mock.Mock(return_value='value-%s' % t))
627 ret = self._gluster_manager.get_vol_option(opt)
629 self.assertEqual('value-%s' % opttype, ret)
630 for t in ('user', 'regular'):
631 func = getattr(self._gluster_manager, '_get_vol_%s_option' % t)
632 if opttype == t:
633 func.assert_called_once_with(lowopt)
634 else:
635 self.assertFalse(func.called)
637 def test_get_vol_option_unset(self):
638 self.mock_object(self._gluster_manager, '_get_vol_regular_option',
639 mock.Mock(return_value=None))
641 ret = self._gluster_manager.get_vol_option('some.option')
643 self.assertIsNone(ret)
645 @ddt.data({'value': '0', 'boolval': False},
646 {'value': 'Off', 'boolval': False},
647 {'value': 'no', 'boolval': False},
648 {'value': '1', 'boolval': True},
649 {'value': 'true', 'boolval': True},
650 {'value': 'enAble', 'boolval': True},
651 {'value': None, 'boolval': None})
652 @ddt.unpack
653 def test_get_vol_option_boolean(self, value, boolval):
654 self.mock_object(self._gluster_manager, '_get_vol_regular_option',
655 mock.Mock(return_value=value))
657 ret = self._gluster_manager.get_vol_option('some.option',
658 boolean=True)
660 self.assertEqual(boolval, ret)
662 def test_get_vol_option_boolean_bad(self):
663 self.mock_object(self._gluster_manager, '_get_vol_regular_option',
664 mock.Mock(return_value='jabberwocky'))
666 self.assertRaises(exception.GlusterfsException,
667 self._gluster_manager.get_vol_option,
668 'some.option', boolean=True)
670 @ddt.data({'setting': 'some_value', 'args': ('set', 'some_value')},
671 {'setting': None, 'args': ('reset',)},
672 {'setting': True, 'args': ('set', 'ON')},
673 {'setting': False, 'args': ('set', 'OFF')})
674 @ddt.unpack
675 def test_set_vol_option(self, setting, args):
676 self.mock_object(self._gluster_manager, 'gluster_call', mock.Mock())
678 self._gluster_manager.set_vol_option('an_option', setting)
680 self._gluster_manager.gluster_call.assert_called_once_with(
681 'volume', args[0], 'testvol', 'an_option', *args[1:],
682 error_policy=mock.ANY)
684 @mock.patch('tenacity.nap.sleep', mock.Mock())
685 @ddt.data({}, {'ignore_failure': False})
686 def test_set_vol_option_error(self, kwargs):
687 fake_obj = mock.Mock(
688 side_effect=exception.ProcessExecutionError(exit_code=1))
689 with mock.patch.object(common.ganesha_utils, 'RootExecutor',
690 mock.Mock(return_value=fake_obj)):
691 gluster_manager = common.GlusterManager(
692 '127.0.0.1:/testvol', self.fake_execf)
694 self.assertRaises(exception.GlusterfsException,
695 gluster_manager.set_vol_option,
696 'an_option', "some_value", **kwargs)
698 self.assertTrue(fake_obj.called)
700 def test_set_vol_option_error_relaxed(self):
701 fake_obj = mock.Mock(
702 side_effect=exception.ProcessExecutionError(exit_code=1))
703 with mock.patch.object(common.ganesha_utils, 'RootExecutor',
704 mock.Mock(return_value=fake_obj)):
705 gluster_manager = common.GlusterManager(
706 '127.0.0.1:/testvol', self.fake_execf)
708 gluster_manager.set_vol_option('an_option', "some_value",
709 ignore_failure=True)
711 self.assertTrue(fake_obj.called)
713 def test_get_gluster_version(self):
714 self.mock_object(self._gluster_manager, 'gluster_call',
715 mock.Mock(return_value=('glusterfs 3.6.2beta3', '')))
716 ret = self._gluster_manager.get_gluster_version()
717 self.assertEqual(['3', '6', '2beta3'], ret)
718 self._gluster_manager.gluster_call.assert_called_once_with(
719 '--version', log=mock.ANY)
721 @ddt.data("foo 1.1.1", "glusterfs 3-6", "glusterfs 3.6beta3")
722 def test_get_gluster_version_exception(self, versinfo):
723 self.mock_object(self._gluster_manager, 'gluster_call',
724 mock.Mock(return_value=(versinfo, '')))
725 self.assertRaises(exception.GlusterfsException,
726 self._gluster_manager.get_gluster_version)
727 self._gluster_manager.gluster_call.assert_called_once_with(
728 '--version', log=mock.ANY)
730 def test_check_gluster_version(self):
731 self.mock_object(self._gluster_manager, 'get_gluster_version',
732 mock.Mock(return_value=('3', '6')))
734 ret = self._gluster_manager.check_gluster_version((3, 5, 2))
735 self.assertIsNone(ret)
736 self._gluster_manager.get_gluster_version.assert_called_once_with()
738 def test_check_gluster_version_unmet(self):
739 self.mock_object(self._gluster_manager, 'get_gluster_version',
740 mock.Mock(return_value=('3', '5', '2')))
742 self.assertRaises(exception.GlusterfsException,
743 self._gluster_manager.check_gluster_version, (3, 6))
744 self._gluster_manager.get_gluster_version.assert_called_once_with()
746 @ddt.data(('3', '6'),
747 ('3', '6', '2beta'),
748 ('3', '6', '2beta', '4'))
749 def test_numreduct(self, vers):
750 ret = common.numreduct(vers)
751 self.assertEqual((3, 6), ret)
754@ddt.ddt
755class GlusterFSCommonTestCase(test.TestCase):
756 """Tests common GlusterFS utility functions."""
758 def setUp(self):
759 super(GlusterFSCommonTestCase, self).setUp()
760 fake_utils.stub_out_utils_execute(self)
761 self._execute = fake_utils.fake_execute
762 self.addCleanup(fake_utils.fake_execute_set_repliers, [])
763 self.addCleanup(fake_utils.fake_execute_clear_log)
764 self.mock_object(common.GlusterManager, 'make_gluster_call')
766 @staticmethod
767 def _mount_exec(vol, mnt):
768 return ['mkdir -p %s' % mnt]
770 def test_mount_gluster_vol(self):
771 expected_exec = self._mount_exec(fakeexport, fakemnt)
772 self.mock_object(privsep_os, 'mount')
773 ret = common._mount_gluster_vol(self._execute, fakeexport, fakemnt,
774 False)
775 privsep_os.mount.assert_called_once_with(
776 fakeexport, fakemnt, mount_type='glusterfs')
777 self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
778 self.assertIsNone(ret)
780 def test_mount_gluster_vol_mounted_noensure(self):
781 def exec_runner(*ignore_args, **ignore_kwargs):
782 raise exception.ProcessExecutionError(stderr='already mounted')
783 expected_exec = self._mount_exec(fakeexport, fakemnt)
784 self.mock_object(
785 privsep_os, 'mount', mock.Mock(side_effect=exec_runner))
786 self.assertRaises(exception.GlusterfsException,
787 common._mount_gluster_vol,
788 self._execute, fakeexport, fakemnt, False)
789 privsep_os.mount.assert_called_once_with(
790 fakeexport, fakemnt, mount_type='glusterfs')
791 self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
793 def test_mount_gluster_vol_mounted_ensure(self):
794 def exec_runner(*ignore_args, **ignore_kwargs):
795 raise exception.ProcessExecutionError(stderr='already mounted')
796 expected_exec = self._mount_exec(fakeexport, fakemnt)
797 common.LOG.warning = mock.Mock()
798 self.mock_object(
799 privsep_os, 'mount', mock.Mock(side_effect=exec_runner))
800 ret = common._mount_gluster_vol(self._execute, fakeexport, fakemnt,
801 True)
802 self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
803 self.assertIsNone(ret)
804 self.mock_object(
805 privsep_os, 'mount', mock.Mock(side_effect=exec_runner))
806 common.LOG.warning.assert_called_with(
807 "%s is already mounted.", fakeexport)
809 @ddt.data(True, False)
810 def test_mount_gluster_vol_fail(self, ensure):
811 def exec_runner(*ignore_args, **ignore_kwargs):
812 raise RuntimeError('fake error')
813 expected_exec = self._mount_exec(fakeexport, fakemnt)
814 self.mock_object(
815 privsep_os, 'mount', mock.Mock(side_effect=exec_runner))
816 self.assertRaises(
817 RuntimeError,
818 common._mount_gluster_vol,
819 self._execute,
820 fakeexport,
821 fakemnt,
822 ensure)
823 privsep_os.mount.assert_called_once_with(
824 fakeexport, fakemnt, mount_type='glusterfs')
825 self.assertEqual(fake_utils.fake_execute_get_log(), expected_exec)
827 def test_umount_gluster_vol(self):
828 self.mock_object(privsep_os, 'umount')
830 ret = common._umount_gluster_vol(fakemnt)
831 privsep_os.umount.assert_called_once_with(fakemnt)
832 self.assertIsNone(ret)
834 @ddt.data({'in_exc': exception.ProcessExecutionError,
835 'out_exc': exception.GlusterfsException},
836 {'in_exc': RuntimeError, 'out_exc': RuntimeError})
837 @ddt.unpack
838 def test_umount_gluster_vol_fail(self, in_exc, out_exc):
839 def exec_runner(*ignore_args, **ignore_kwargs):
840 raise in_exc('fake error')
841 self.mock_object(privsep_os, 'umount',
842 mock.Mock(side_effect=exec_runner))
843 self.assertRaises(out_exc, common._umount_gluster_vol, fakemnt)
844 privsep_os.umount.assert_called_once_with(fakemnt)
846 def test_restart_gluster_vol(self):
847 gmgr = common.GlusterManager(fakeexport, self._execute, None, None)
848 test_args = [(('volume', 'stop', fakevol, '--mode=script'),
849 {'log': mock.ANY}),
850 (('volume', 'start', fakevol), {'log': mock.ANY})]
852 common._restart_gluster_vol(gmgr)
853 self.assertEqual(
854 [mock.call(*arg[0], **arg[1]) for arg in test_args],
855 gmgr.gluster_call.call_args_list)