Coverage for manila/tests/share/drivers/ganesha/test_manager.py: 99%
601 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) 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.
16import copy
17import io
18import re
19from unittest import mock
21import ddt
22from oslo_serialization import jsonutils
24from manila import exception
25from manila.share.drivers.ganesha import manager
26from manila import test
27from manila import utils
29test_export_id = 101
30test_name = 'fakefile'
31test_path = '/fakedir0/export.d/fakefile.conf'
32test_tmp_path = '/fakedir0/export.d/fakefile.conf.RANDOM'
33test_ganesha_cnf = """EXPORT {
34 Export_Id = 101;
35 CLIENT {
36 Clients = ip1;
37 Access_Level = ro;
38 }
39 CLIENT {
40 Clients = ip2;
41 Access_Level = rw;
42 }
43}"""
44test_dict_unicode = {
45 u'EXPORT': {
46 u'Export_Id': 101,
47 u'CLIENT': [
48 {u'Clients': u"ip1", u'Access_Level': u'ro'},
49 {u'Clients': u"ip2", u'Access_Level': u'rw'}]
50 }
51}
52test_dict_str = {
53 'EXPORT': {
54 'Export_Id': 101,
55 'CLIENT': [
56 {'Clients': 'ip1', 'Access_Level': 'ro'},
57 {'Clients': 'ip2', 'Access_Level': 'rw'}]
58 }
59}
61manager_fake_kwargs = {
62 'ganesha_config_path': '/fakedir0/fakeconfig',
63 'ganesha_db_path': '/fakedir1/fake.db',
64 'ganesha_export_dir': '/fakedir0/export.d',
65 'ganesha_service_name': 'ganesha.fakeservice'
66}
69class MockRadosModule(object):
70 """Mocked up version of Ceph's RADOS module."""
72 class ObjectNotFound(Exception):
73 pass
75 class OSError(Exception):
76 pass
78 class WriteOpCtx():
80 def __enter__(self):
81 return self
83 def __exit__(self, type, msg, traceback):
84 pass
86 def write_full(self, bytes_to_write):
87 pass
90@ddt.ddt
91class MiscTests(test.TestCase):
93 @ddt.data({'import_exc': None},
94 {'import_exc': ImportError})
95 @ddt.unpack
96 def test_setup_rados(self, import_exc):
97 manager.rados = None
98 with mock.patch.object(
99 manager.importutils,
100 'import_module',
101 side_effect=import_exc) as mock_import_module:
102 if import_exc:
103 self.assertRaises(
104 exception.ShareBackendException, manager.setup_rados)
105 else:
106 manager.setup_rados()
107 self.assertEqual(mock_import_module.return_value,
108 manager.rados)
109 mock_import_module.assert_called_once_with('rados')
112class GaneshaConfigTests(test.TestCase):
113 """Tests Ganesha config file format convertor functions."""
115 ref_ganesha_cnf = """EXPORT {
116 CLIENT {
117 Clients = ip1;
118 Access_Level = "ro";
119 }
120 CLIENT {
121 Clients = ip2;
122 Access_Level = "rw";
123 }
124 Export_Id = 101;
125}"""
127 @staticmethod
128 def conf_mangle(*confs):
129 """A "mangler" for the conf format.
131 Its purpose is to transform conf data in a way so that semantically
132 equivalent confs yield identical results. Besides this objective
133 criteria, we seek a good trade-off between the following
134 requirements:
135 - low lossiness;
136 - low code complexity.
137 """
138 def _conf_mangle(conf):
139 # split to expressions by the delimiter ";"
140 # (braces are forced to be treated as expressions
141 # by sandwiching them in ";"-s)
142 conf = re.sub(r'[{}]', r';\g<0>;', conf).split(';')
143 # whitespace-split expressions to tokens with
144 # (equality is forced to be treated as token by
145 # sandwiching in space)
146 conf = map(lambda line: line.replace("=", " = ").split(), conf)
147 # get rid of by-product empty lists (derived from superflouous
148 # ";"-s that might have crept in due to "sandwiching")
149 conf = map(lambda x: x, conf)
150 # handle the non-deterministic order of confs
151 conf = list(conf)
152 conf.sort()
153 return conf
155 return (_conf_mangle(conf) for conf in confs)
157 def test_conf2json(self):
158 test_ganesha_cnf_with_comment = """EXPORT {
159# fake_export_block
160 Export_Id = 101;
161 CLIENT {
162 Clients = ip1;
163 }
164}"""
165 result_dict_unicode = {
166 u'EXPORT': {
167 u'CLIENT': {u'Clients': u'ip1'},
168 u'Export_Id': 101
169 }
170 }
171 ret = manager._conf2json(test_ganesha_cnf_with_comment)
172 self.assertEqual(result_dict_unicode, jsonutils.loads(ret))
174 def test_parseconf_ganesha_cnf_input(self):
175 ret = manager.parseconf(test_ganesha_cnf)
176 self.assertEqual(test_dict_unicode, ret)
178 def test_parseconf_json_input(self):
179 ret = manager.parseconf(jsonutils.dumps(test_dict_str))
180 self.assertEqual(test_dict_unicode, ret)
182 def test_dump_to_conf(self):
183 ganesha_cnf = io.StringIO()
184 manager._dump_to_conf(test_dict_str, ganesha_cnf)
185 self.assertEqual(*self.conf_mangle(self.ref_ganesha_cnf,
186 ganesha_cnf.getvalue()))
188 def test_mkconf(self):
189 ganesha_cnf = manager.mkconf(test_dict_str)
190 self.assertEqual(*self.conf_mangle(self.ref_ganesha_cnf,
191 ganesha_cnf))
194@ddt.ddt
195class GaneshaManagerTestCase(test.TestCase):
196 """Tests GaneshaManager."""
198 def instantiate_ganesha_manager(self, *args, **kwargs):
199 ganesha_rados_store_enable = kwargs.get('ganesha_rados_store_enable',
200 False)
201 if ganesha_rados_store_enable:
202 with mock.patch.object(
203 manager.GaneshaManager,
204 '_get_rados_object') as self.mock_get_rados_object:
205 return manager.GaneshaManager(*args, **kwargs)
206 else:
207 with mock.patch.object(
208 manager.GaneshaManager,
209 'get_export_id',
210 return_value=100) as self.mock_get_export_id:
211 return manager.GaneshaManager(*args, **kwargs)
213 def setUp(self):
214 super(GaneshaManagerTestCase, self).setUp()
215 self._execute = mock.Mock(return_value=('', ''))
216 self._rados_client = mock.Mock()
217 self._manager = self.instantiate_ganesha_manager(
218 self._execute, 'faketag',
219 rados_client=self._rados_client,
220 **manager_fake_kwargs)
221 self._setup_rados = mock.Mock()
222 self._execute2 = mock.Mock(return_value=('', ''))
223 self.mock_object(manager, 'rados', MockRadosModule)
224 self.mock_object(manager, 'setup_rados', self._setup_rados)
225 fake_kwargs = copy.copy(manager_fake_kwargs)
226 fake_kwargs.update(
227 ganesha_rados_store_enable=True,
228 ganesha_rados_store_pool_name='fakepool',
229 ganesha_rados_export_counter='fakecounter',
230 ganesha_rados_export_index='fakeindex',
231 rados_client=self._rados_client
232 )
233 self._manager_with_rados_store = self.instantiate_ganesha_manager(
234 self._execute2, 'faketag', **fake_kwargs)
235 self.mock_object(utils, 'synchronized',
236 mock.Mock(return_value=lambda f: f))
238 def test_init(self):
239 self.mock_object(self._manager, 'reset_exports')
240 self.mock_object(self._manager, 'restart_service')
241 self.assertEqual('/fakedir0/fakeconfig',
242 self._manager.ganesha_config_path)
243 self.assertEqual('faketag', self._manager.tag)
244 self.assertEqual('/fakedir0/export.d',
245 self._manager.ganesha_export_dir)
246 self.assertEqual('/fakedir1/fake.db', self._manager.ganesha_db_path)
247 self.assertEqual('ganesha.fakeservice', self._manager.ganesha_service)
248 self.assertEqual(
249 [mock.call('mkdir', '-p', self._manager.ganesha_export_dir),
250 mock.call('mkdir', '-p', '/fakedir1'),
251 mock.call('sqlite3', self._manager.ganesha_db_path,
252 'create table ganesha(key varchar(20) primary key, '
253 'value int); insert into ganesha values("exportid", '
254 '100);', run_as_root=False, check_exit_code=False)],
255 self._execute.call_args_list)
256 self.mock_get_export_id.assert_called_once_with(bump=False)
258 def test_init_execute_error_log_message(self):
259 fake_args = ('foo', 'bar')
261 def raise_exception(*args, **kwargs):
262 if args == fake_args:
263 raise exception.GaneshaCommandFailure()
265 test_execute = mock.Mock(side_effect=raise_exception)
266 self.mock_object(manager.LOG, 'error')
267 test_manager = self.instantiate_ganesha_manager(
268 test_execute, 'faketag', **manager_fake_kwargs)
269 self.assertRaises(
270 exception.GaneshaCommandFailure,
271 test_manager.execute,
272 *fake_args, message='fakemsg')
273 manager.LOG.error.assert_called_once_with(
274 mock.ANY, {'tag': 'faketag', 'msg': 'fakemsg'})
276 def test_init_execute_error_no_log_message(self):
277 fake_args = ('foo', 'bar')
279 def raise_exception(*args, **kwargs):
280 if args == fake_args:
281 raise exception.GaneshaCommandFailure()
283 test_execute = mock.Mock(side_effect=raise_exception)
284 self.mock_object(manager.LOG, 'error')
285 test_manager = self.instantiate_ganesha_manager(
286 test_execute, 'faketag', **manager_fake_kwargs)
287 self.assertRaises(
288 exception.GaneshaCommandFailure,
289 test_manager.execute,
290 *fake_args, message='fakemsg', makelog=False)
291 self.assertFalse(manager.LOG.error.called)
293 @ddt.data(False, True)
294 def test_init_with_rados_store_and_export_counter_exists(
295 self, counter_exists):
296 fake_execute = mock.Mock(return_value=('', ''))
297 fake_kwargs = copy.copy(manager_fake_kwargs)
298 fake_kwargs.update(
299 ganesha_rados_store_enable=True,
300 ganesha_rados_store_pool_name='fakepool',
301 ganesha_rados_export_counter='fakecounter',
302 ganesha_rados_export_index='fakeindex',
303 rados_client=self._rados_client
304 )
305 if counter_exists:
306 self.mock_object(
307 manager.GaneshaManager, '_get_rados_object', mock.Mock())
308 else:
309 self.mock_object(
310 manager.GaneshaManager, '_get_rados_object',
311 mock.Mock(side_effect=MockRadosModule.ObjectNotFound))
312 self.mock_object(manager.GaneshaManager, '_put_rados_object')
314 test_mgr = manager.GaneshaManager(
315 fake_execute, 'faketag', **fake_kwargs)
317 self.assertEqual('/fakedir0/fakeconfig', test_mgr.ganesha_config_path)
318 self.assertEqual('faketag', test_mgr.tag)
319 self.assertEqual('/fakedir0/export.d', test_mgr.ganesha_export_dir)
320 self.assertEqual('ganesha.fakeservice', test_mgr.ganesha_service)
321 fake_execute.assert_called_once_with(
322 'mkdir', '-p', '/fakedir0/export.d')
323 self.assertTrue(test_mgr.ganesha_rados_store_enable)
324 self.assertEqual('fakepool', test_mgr.ganesha_rados_store_pool_name)
325 self.assertEqual('fakecounter', test_mgr.ganesha_rados_export_counter)
326 self.assertEqual('fakeindex', test_mgr.ganesha_rados_export_index)
327 self.assertEqual(self._rados_client, test_mgr.rados_client)
328 self._setup_rados.assert_called_with()
329 test_mgr._get_rados_object.assert_called_once_with('fakecounter')
330 if counter_exists:
331 self.assertFalse(test_mgr._put_rados_object.called)
332 else:
333 test_mgr._put_rados_object.assert_called_once_with(
334 'fakecounter', str(1000))
336 def test_ganesha_export_dir(self):
337 self.assertEqual(
338 '/fakedir0/export.d', self._manager.ganesha_export_dir)
340 def test_getpath(self):
341 self.assertEqual(
342 '/fakedir0/export.d/fakefile.conf',
343 self._manager._getpath('fakefile'))
345 def test_get_export_rados_object_name(self):
346 self.assertEqual(
347 'ganesha-export-fakeobj',
348 self._manager._get_export_rados_object_name('fakeobj'))
350 def test_write_tmp_conf_file(self):
351 self.mock_object(manager.shlex, 'quote',
352 mock.Mock(side_effect=['fakedata',
353 test_tmp_path]))
354 test_args = [
355 ('mktemp', '-p', '/fakedir0/export.d', '-t',
356 'fakefile.conf.XXXXXX'),
357 ('sh', '-c', 'echo fakedata > %s' % test_tmp_path)]
358 test_kwargs = {
359 'message': 'writing %s' % test_tmp_path
360 }
362 def return_tmpfile(*args, **kwargs):
363 if args == test_args[0]:
364 return (test_tmp_path + '\n', '')
365 self.mock_object(self._manager, 'execute',
366 mock.Mock(side_effect=return_tmpfile))
368 ret = self._manager._write_tmp_conf_file(test_path, 'fakedata')
370 self._manager.execute.assert_has_calls([
371 mock.call(*test_args[0]),
372 mock.call(*test_args[1], **test_kwargs)])
373 manager.shlex.quote.assert_has_calls([
374 mock.call('fakedata'),
375 mock.call(test_tmp_path)])
376 self.assertEqual(test_tmp_path, ret)
378 @ddt.data(True, False)
379 def test_write_conf_file_with_mv_error(self, mv_error):
380 test_data = 'fakedata'
381 test_args = [
382 ('mv', test_tmp_path, test_path),
383 ('rm', test_tmp_path)]
384 self.mock_object(self._manager, '_getpath',
385 mock.Mock(return_value=test_path))
386 self.mock_object(self._manager, '_write_tmp_conf_file',
387 mock.Mock(return_value=test_tmp_path))
389 def mock_return(*args, **kwargs):
390 if args == test_args[0]:
391 if mv_error:
392 raise exception.ProcessExecutionError()
393 else:
394 return ('', '')
396 self.mock_object(self._manager, 'execute',
397 mock.Mock(side_effect=mock_return))
399 if mv_error:
400 self.assertRaises(
401 exception.ProcessExecutionError,
402 self._manager._write_conf_file, test_name, test_data)
403 else:
404 ret = self._manager._write_conf_file(test_name, test_data)
406 self._manager._getpath.assert_called_once_with(test_name)
407 self._manager._write_tmp_conf_file.assert_called_once_with(
408 test_path, test_data)
409 if mv_error:
410 self._manager.execute.assert_has_calls([
411 mock.call(*test_args[0]),
412 mock.call(*test_args[1])])
413 else:
414 self._manager.execute.assert_has_calls([
415 mock.call(*test_args[0])])
416 self.assertEqual(test_path, ret)
418 def test_mkindex(self):
419 test_ls_output = 'INDEX.conf\nfakefile.conf\nfakefile.txt'
420 test_index = '%include /fakedir0/export.d/fakefile.conf\n'
421 self.mock_object(self._manager, 'execute',
422 mock.Mock(return_value=(test_ls_output, '')))
423 self.mock_object(self._manager, '_write_conf_file')
424 ret = self._manager._mkindex()
425 self._manager.execute.assert_called_once_with(
426 'ls', '/fakedir0/export.d', run_as_root=False)
427 self._manager._write_conf_file.assert_called_once_with(
428 'INDEX', test_index)
429 self.assertIsNone(ret)
431 def test_read_export_rados_object(self):
432 self.mock_object(self._manager_with_rados_store,
433 '_get_export_rados_object_name',
434 mock.Mock(return_value='fakeobj'))
435 self.mock_object(self._manager_with_rados_store, '_get_rados_object',
436 mock.Mock(return_value=test_ganesha_cnf))
437 self.mock_object(manager, 'parseconf',
438 mock.Mock(return_value=test_dict_unicode))
440 ret = self._manager_with_rados_store._read_export_rados_object(
441 test_name)
443 (self._manager_with_rados_store._get_export_rados_object_name.
444 assert_called_once_with(test_name))
445 (self._manager_with_rados_store._get_rados_object.
446 assert_called_once_with('fakeobj'))
447 manager.parseconf.assert_called_once_with(test_ganesha_cnf)
448 self.assertEqual(test_dict_unicode, ret)
450 def test_read_export_file(self):
451 test_args = ('cat', test_path)
452 test_kwargs = {'message': 'reading export fakefile'}
453 self.mock_object(self._manager, '_getpath',
454 mock.Mock(return_value=test_path))
455 self.mock_object(self._manager, 'execute',
456 mock.Mock(return_value=(test_ganesha_cnf,)))
457 self.mock_object(manager, 'parseconf',
458 mock.Mock(return_value=test_dict_unicode))
459 ret = self._manager._read_export_file(test_name)
460 self._manager._getpath.assert_called_once_with(test_name)
461 self._manager.execute.assert_called_once_with(
462 *test_args, **test_kwargs)
463 manager.parseconf.assert_called_once_with(test_ganesha_cnf)
464 self.assertEqual(test_dict_unicode, ret)
466 @ddt.data(False, True)
467 def test_read_export_with_rados_store(self, rados_store_enable):
468 self._manager.ganesha_rados_store_enable = rados_store_enable
469 self.mock_object(self._manager, '_read_export_file',
470 mock.Mock(return_value=test_dict_unicode))
471 self.mock_object(self._manager, '_read_export_rados_object',
472 mock.Mock(return_value=test_dict_unicode))
474 ret = self._manager._read_export(test_name)
476 if rados_store_enable:
477 self._manager._read_export_rados_object.assert_called_once_with(
478 test_name)
479 self.assertFalse(self._manager._read_export_file.called)
480 else:
481 self._manager._read_export_file.assert_called_once_with(test_name)
482 self.assertFalse(self._manager._read_export_rados_object.called)
483 self.assertEqual(test_dict_unicode, ret)
485 @ddt.data(True, False)
486 def test_check_export_rados_object_exists(self, exists):
487 self.mock_object(
488 self._manager_with_rados_store,
489 '_get_export_rados_object_name', mock.Mock(return_value='fakeobj'))
490 if exists:
491 self.mock_object(
492 self._manager_with_rados_store, '_get_rados_object')
493 else:
494 self.mock_object(
495 self._manager_with_rados_store, '_get_rados_object',
496 mock.Mock(side_effect=MockRadosModule.ObjectNotFound))
498 ret = self._manager_with_rados_store._check_export_rados_object_exists(
499 test_name)
501 (self._manager_with_rados_store._get_export_rados_object_name.
502 assert_called_once_with(test_name))
503 (self._manager_with_rados_store._get_rados_object.
504 assert_called_once_with('fakeobj'))
505 if exists:
506 self.assertTrue(ret)
507 else:
508 self.assertFalse(ret)
510 def test_check_file_exists(self):
511 self.mock_object(self._manager, 'execute',
512 mock.Mock(return_value=(test_ganesha_cnf,)))
514 ret = self._manager._check_file_exists(test_path)
516 self._manager.execute.assert_called_once_with(
517 'test', '-f', test_path, makelog=False, run_as_root=False)
518 self.assertTrue(ret)
520 @ddt.data(1, 4)
521 def test_check_file_exists_error(self, exit_code):
522 self.mock_object(
523 self._manager, 'execute',
524 mock.Mock(side_effect=exception.GaneshaCommandFailure(
525 exit_code=exit_code))
526 )
528 if exit_code == 1:
529 ret = self._manager._check_file_exists(test_path)
530 self.assertFalse(ret)
531 else:
532 self.assertRaises(exception.GaneshaCommandFailure,
533 self._manager._check_file_exists,
534 test_path)
536 self._manager.execute.assert_called_once_with(
537 'test', '-f', test_path, makelog=False, run_as_root=False)
539 def test_check_export_file_exists(self):
540 self.mock_object(self._manager, '_getpath',
541 mock.Mock(return_value=test_path))
542 self.mock_object(self._manager, '_check_file_exists',
543 mock.Mock(return_value=True))
545 ret = self._manager._check_export_file_exists(test_name)
547 self._manager._getpath.assert_called_once_with(test_name)
548 self._manager._check_file_exists.assert_called_once_with(test_path)
549 self.assertTrue(ret)
551 @ddt.data(False, True)
552 def test_check_export_exists_with_rados_store(self, rados_store_enable):
553 self._manager.ganesha_rados_store_enable = rados_store_enable
554 self.mock_object(self._manager, '_check_export_file_exists',
555 mock.Mock(return_value=True))
556 self.mock_object(self._manager, '_check_export_rados_object_exists',
557 mock.Mock(return_value=True))
559 ret = self._manager.check_export_exists(test_name)
561 if rados_store_enable:
562 (self._manager._check_export_rados_object_exists.
563 assert_called_once_with(test_name))
564 self.assertFalse(self._manager._check_export_file_exists.called)
565 else:
566 self._manager._check_export_file_exists.assert_called_once_with(
567 test_name)
568 self.assertFalse(
569 self._manager._check_export_rados_object_exists.called)
570 self.assertTrue(ret)
572 def test_write_export_rados_object(self):
573 self.mock_object(self._manager, '_get_export_rados_object_name',
574 mock.Mock(return_value='fakeobj'))
575 self.mock_object(self._manager, '_put_rados_object')
576 self.mock_object(self._manager, '_getpath',
577 mock.Mock(return_value=test_path))
578 self.mock_object(self._manager, '_write_tmp_conf_file',
579 mock.Mock(return_value=test_tmp_path))
581 ret = self._manager._write_export_rados_object(test_name, 'fakedata')
583 self._manager._get_export_rados_object_name.assert_called_once_with(
584 test_name)
585 self._manager._put_rados_object.assert_called_once_with(
586 'fakeobj', 'fakedata')
587 self._manager._getpath.assert_called_once_with(test_name)
588 self._manager._write_tmp_conf_file.assert_called_once_with(
589 test_path, 'fakedata')
590 self.assertEqual(test_tmp_path, ret)
592 @ddt.data(True, False)
593 def test_write_export_with_rados_store(self, rados_store_enable):
594 self._manager.ganesha_rados_store_enable = rados_store_enable
595 self.mock_object(manager, 'mkconf',
596 mock.Mock(return_value=test_ganesha_cnf))
597 self.mock_object(self._manager, '_write_conf_file',
598 mock.Mock(return_value=test_path))
599 self.mock_object(self._manager, '_write_export_rados_object',
600 mock.Mock(return_value=test_path))
602 ret = self._manager._write_export(test_name, test_dict_str)
604 manager.mkconf.assert_called_once_with(test_dict_str)
605 if rados_store_enable:
606 self._manager._write_export_rados_object.assert_called_once_with(
607 test_name, test_ganesha_cnf)
608 self.assertFalse(self._manager._write_conf_file.called)
609 else:
610 self._manager._write_conf_file.assert_called_once_with(
611 test_name, test_ganesha_cnf)
612 self.assertFalse(self._manager._write_export_rados_object.called)
613 self.assertEqual(test_path, ret)
615 def test_write_export_error_incomplete_export_block(self):
616 test_errordict = {
617 u'EXPORT': {
618 u'Export_Id': '@config',
619 u'CLIENT': {u'Clients': u"'ip1','ip2'"}
620 }
621 }
622 self.mock_object(manager, 'mkconf',
623 mock.Mock(return_value=test_ganesha_cnf))
624 self.mock_object(self._manager, '_write_conf_file',
625 mock.Mock(return_value=test_path))
627 self.assertRaises(exception.InvalidParameterValue,
628 self._manager._write_export,
629 test_name, test_errordict)
631 self.assertFalse(manager.mkconf.called)
632 self.assertFalse(self._manager._write_conf_file.called)
634 def test_rm_file(self):
635 self.mock_object(self._manager, 'execute',
636 mock.Mock(return_value=('', '')))
637 ret = self._manager._rm_export_file(test_name)
639 self._manager.execute.assert_called_once_with('rm', '-f', test_path)
640 self.assertIsNone(ret)
642 def test_rm_export_file(self):
643 self.mock_object(self._manager, '_getpath',
644 mock.Mock(return_value=test_path))
645 self.mock_object(self._manager, '_rm_file')
647 ret = self._manager._rm_export_file(test_name)
649 self._manager._getpath.assert_called_once_with(test_name)
650 self._manager._rm_file.assert_called_once_with(test_path)
651 self.assertIsNone(ret)
653 def test_rm_export_rados_object(self):
654 self.mock_object(self._manager_with_rados_store,
655 '_get_export_rados_object_name',
656 mock.Mock(return_value='fakeobj'))
657 self.mock_object(self._manager_with_rados_store,
658 '_delete_rados_object')
660 ret = self._manager_with_rados_store._rm_export_rados_object(
661 test_name)
663 (self._manager_with_rados_store._get_export_rados_object_name.
664 assert_called_once_with(test_name))
665 (self._manager_with_rados_store._delete_rados_object.
666 assert_called_once_with('fakeobj'))
667 self.assertIsNone(ret)
669 def test_dbus_send_ganesha(self):
670 test_args = ('arg1', 'arg2')
671 test_kwargs = {'key': 'value'}
672 self.mock_object(self._manager, 'execute',
673 mock.Mock(return_value=('', '')))
674 ret = self._manager._dbus_send_ganesha('fakemethod', *test_args,
675 **test_kwargs)
676 self._manager.execute.assert_called_once_with(
677 'dbus-send', '--print-reply', '--system',
678 '--dest=org.ganesha.nfsd', '/org/ganesha/nfsd/ExportMgr',
679 'org.ganesha.nfsd.exportmgr.fakemethod',
680 *test_args, message='dbus call exportmgr.fakemethod',
681 **test_kwargs)
682 self.assertIsNone(ret)
684 def test_remove_export_dbus(self):
685 self.mock_object(self._manager, '_dbus_send_ganesha')
686 ret = self._manager._remove_export_dbus(test_export_id)
687 self._manager._dbus_send_ganesha.assert_called_once_with(
688 'RemoveExport', 'uint16:101')
689 self.assertIsNone(ret)
691 @ddt.data('',
692 '%url rados://fakepool/fakeobj2')
693 def test_add_rados_object_url_to_index_with_index_data(
694 self, index_data):
695 self.mock_object(
696 self._manager_with_rados_store, '_get_rados_object',
697 mock.Mock(return_value=index_data))
698 self.mock_object(
699 self._manager_with_rados_store, '_get_export_rados_object_name',
700 mock.Mock(return_value='fakeobj1'))
701 self.mock_object(
702 self._manager_with_rados_store, '_put_rados_object')
704 ret = (self._manager_with_rados_store.
705 _add_rados_object_url_to_index('fakename'))
707 (self._manager_with_rados_store._get_rados_object.
708 assert_called_once_with('fakeindex'))
709 (self._manager_with_rados_store._get_export_rados_object_name.
710 assert_called_once_with('fakename'))
711 if index_data:
712 urls = ('%url rados://fakepool/fakeobj2\n'
713 '%url rados://fakepool/fakeobj1')
714 else:
715 urls = '%url rados://fakepool/fakeobj1'
716 (self._manager_with_rados_store._put_rados_object.
717 assert_called_once_with('fakeindex', urls))
718 self.assertIsNone(ret)
720 @ddt.data('',
721 '%url rados://fakepool/fakeobj1\n'
722 '%url rados://fakepool/fakeobj2')
723 def test_remove_rados_object_url_from_index_with_index_data(
724 self, index_data):
725 self.mock_object(
726 self._manager_with_rados_store, '_get_rados_object',
727 mock.Mock(return_value=index_data))
728 self.mock_object(
729 self._manager_with_rados_store, '_get_export_rados_object_name',
730 mock.Mock(return_value='fakeobj1'))
731 self.mock_object(
732 self._manager_with_rados_store, '_put_rados_object')
734 ret = (self._manager_with_rados_store.
735 _remove_rados_object_url_from_index('fakename'))
737 if index_data:
738 (self._manager_with_rados_store._get_rados_object.
739 assert_called_once_with('fakeindex'))
740 (self._manager_with_rados_store._get_export_rados_object_name.
741 assert_called_once_with('fakename'))
742 urls = '%url rados://fakepool/fakeobj2'
743 (self._manager_with_rados_store._put_rados_object.
744 assert_called_once_with('fakeindex', urls))
745 else:
746 (self._manager_with_rados_store._get_rados_object.
747 assert_called_once_with('fakeindex'))
748 self.assertFalse(self._manager_with_rados_store.
749 _get_export_rados_object_name.called)
750 self.assertFalse(self._manager_with_rados_store.
751 _put_rados_object.called)
752 self.assertIsNone(ret)
754 @ddt.data(False, True)
755 def test_add_export_with_rados_store(self, rados_store_enable):
756 self._manager.ganesha_rados_store_enable = rados_store_enable
757 self.mock_object(self._manager, '_write_export',
758 mock.Mock(return_value=test_path))
759 self.mock_object(self._manager, '_dbus_send_ganesha')
760 self.mock_object(self._manager, '_rm_file')
761 self.mock_object(self._manager, '_add_rados_object_url_to_index')
762 self.mock_object(self._manager, '_mkindex')
764 ret = self._manager.add_export(test_name, test_dict_str)
766 self._manager._write_export.assert_called_once_with(
767 test_name, test_dict_str)
768 self._manager._dbus_send_ganesha.assert_called_once_with(
769 'AddExport', 'string:' + test_path,
770 'string:EXPORT(Export_Id=101)')
771 if rados_store_enable:
772 self._manager._rm_file.assert_called_once_with(test_path)
773 self._manager._add_rados_object_url_to_index(test_name)
774 self.assertFalse(self._manager._mkindex.called)
775 else:
776 self._manager._mkindex.assert_called_once_with()
777 self.assertFalse(self._manager._rm_file.called)
778 self.assertFalse(
779 self._manager._add_rados_object_url_to_index.called)
780 self.assertIsNone(ret)
782 def test_add_export_error_during_mkindex(self):
783 self.mock_object(self._manager, '_write_export',
784 mock.Mock(return_value=test_path))
785 self.mock_object(self._manager, '_dbus_send_ganesha')
786 self.mock_object(
787 self._manager, '_mkindex',
788 mock.Mock(side_effect=exception.GaneshaCommandFailure))
789 self.mock_object(self._manager, '_rm_export_file')
790 self.mock_object(self._manager, '_remove_export_dbus')
792 self.assertRaises(exception.GaneshaCommandFailure,
793 self._manager.add_export, test_name, test_dict_str)
795 self._manager._write_export.assert_called_once_with(
796 test_name, test_dict_str)
797 self._manager._dbus_send_ganesha.assert_called_once_with(
798 'AddExport', 'string:' + test_path,
799 'string:EXPORT(Export_Id=101)')
800 self._manager._mkindex.assert_called_once_with()
801 self._manager._rm_export_file.assert_called_once_with(test_name)
802 self._manager._remove_export_dbus.assert_called_once_with(
803 test_export_id)
805 @ddt.data(True, False)
806 def test_add_export_error_during_write_export_with_rados_store(
807 self, rados_store_enable):
808 self._manager.ganesha_rados_store_enable = rados_store_enable
809 self.mock_object(
810 self._manager, '_write_export',
811 mock.Mock(side_effect=exception.GaneshaCommandFailure))
812 self.mock_object(self._manager, '_mkindex')
814 self.assertRaises(exception.GaneshaCommandFailure,
815 self._manager.add_export, test_name, test_dict_str)
817 self._manager._write_export.assert_called_once_with(
818 test_name, test_dict_str)
819 if rados_store_enable:
820 self.assertFalse(self._manager._mkindex.called)
821 else:
822 self._manager._mkindex.assert_called_once_with()
824 @ddt.data(True, False)
825 def test_add_export_error_during_dbus_send_ganesha_with_rados_store(
826 self, rados_store_enable):
827 self._manager.ganesha_rados_store_enable = rados_store_enable
828 self.mock_object(self._manager, '_write_export',
829 mock.Mock(return_value=test_path))
830 self.mock_object(
831 self._manager, '_dbus_send_ganesha',
832 mock.Mock(side_effect=exception.GaneshaCommandFailure))
833 self.mock_object(self._manager, '_mkindex')
834 self.mock_object(self._manager, '_rm_export_file')
835 self.mock_object(self._manager, '_rm_export_rados_object')
836 self.mock_object(self._manager, '_rm_file')
837 self.mock_object(self._manager, '_remove_export_dbus')
839 self.assertRaises(exception.GaneshaCommandFailure,
840 self._manager.add_export, test_name, test_dict_str)
842 self._manager._write_export.assert_called_once_with(
843 test_name, test_dict_str)
844 self._manager._dbus_send_ganesha.assert_called_once_with(
845 'AddExport', 'string:' + test_path,
846 'string:EXPORT(Export_Id=101)')
847 if rados_store_enable:
848 self._manager._rm_export_rados_object.assert_called_once_with(
849 test_name)
850 self._manager._rm_file.assert_called_once_with(test_path)
851 self.assertFalse(self._manager._rm_export_file.called)
852 self.assertFalse(self._manager._mkindex.called)
853 else:
854 self._manager._rm_export_file.assert_called_once_with(test_name)
855 self._manager._mkindex.assert_called_once_with()
856 self.assertFalse(self._manager._rm_export_rados_object.called)
857 self.assertFalse(self._manager._rm_file.called)
858 self.assertFalse(self._manager._remove_export_dbus.called)
860 @ddt.data(True, False)
861 def test_update_export_with_rados_store(self, rados_store_enable):
862 self._manager.ganesha_rados_store_enable = rados_store_enable
863 confdict = {
864 'EXPORT': {
865 'Export_Id': 101,
866 'CLIENT': {'Clients': 'ip1', 'Access_Level': 'ro'},
867 }
868 }
869 self.mock_object(self._manager, '_read_export',
870 mock.Mock(return_value=test_dict_unicode))
871 self.mock_object(self._manager, '_write_export',
872 mock.Mock(return_value=test_path))
873 self.mock_object(self._manager, '_dbus_send_ganesha')
874 self.mock_object(self._manager, '_rm_file')
876 self._manager.update_export(test_name, confdict)
878 self._manager._read_export.assert_called_once_with(test_name)
879 self._manager._write_export.assert_called_once_with(test_name,
880 confdict)
881 self._manager._dbus_send_ganesha.assert_called_once_with(
882 'UpdateExport', 'string:' + test_path,
883 'string:EXPORT(Export_Id=101)')
884 if rados_store_enable:
885 self._manager._rm_file.assert_called_once_with(test_path)
886 else:
887 self.assertFalse(self._manager._rm_file.called)
889 @ddt.data(True, False)
890 def test_update_export_error_with_rados_store(self, rados_store_enable):
891 self._manager.ganesha_rados_store_enable = rados_store_enable
892 confdict = {
893 'EXPORT': {
894 'Export_Id': 101,
895 'CLIENT': {'Clients': 'ip1', 'Access_Level': 'ro'},
896 }
897 }
898 self.mock_object(self._manager, '_read_export',
899 mock.Mock(return_value=test_dict_unicode))
900 self.mock_object(self._manager, '_write_export',
901 mock.Mock(return_value=test_path))
902 self.mock_object(
903 self._manager, '_dbus_send_ganesha',
904 mock.Mock(side_effect=exception.GaneshaCommandFailure))
905 self.mock_object(self._manager, '_rm_file')
907 self.assertRaises(exception.GaneshaCommandFailure,
908 self._manager.update_export, test_name, confdict)
910 self._manager._read_export.assert_called_once_with(test_name)
911 self._manager._write_export.assert_has_calls([
912 mock.call(test_name, confdict),
913 mock.call(test_name, test_dict_unicode)])
914 self._manager._dbus_send_ganesha.assert_called_once_with(
915 'UpdateExport', 'string:' + test_path,
916 'string:EXPORT(Export_Id=101)')
917 if rados_store_enable:
918 self._manager._rm_file.assert_called_once_with(test_path)
919 else:
920 self.assertFalse(self._manager._rm_file.called)
922 @ddt.data(True, False)
923 def test_remove_export_with_rados_store(self, rados_store_enable):
924 self._manager.ganesha_rados_store_enable = rados_store_enable
925 self.mock_object(self._manager, '_read_export',
926 mock.Mock(return_value=test_dict_unicode))
927 self.mock_object(self._manager, '_get_export_rados_object_name',
928 mock.Mock(return_value='fakeobj'))
929 methods = ('_remove_export_dbus', '_rm_export_file', '_mkindex',
930 '_remove_rados_object_url_from_index',
931 '_delete_rados_object')
932 for method in methods:
933 self.mock_object(self._manager, method)
935 ret = self._manager.remove_export(test_name)
937 self._manager._read_export.assert_called_once_with(test_name)
938 self._manager._remove_export_dbus.assert_called_once_with(
939 test_dict_unicode['EXPORT']['Export_Id'])
940 if rados_store_enable:
941 (self._manager._get_export_rados_object_name.
942 assert_called_once_with(test_name))
943 self._manager._delete_rados_object.assert_called_once_with(
944 'fakeobj')
945 (self._manager._remove_rados_object_url_from_index.
946 assert_called_once_with(test_name))
947 self.assertFalse(self._manager._rm_export_file.called)
948 self.assertFalse(self._manager._mkindex.called)
949 else:
950 self._manager._rm_export_file.assert_called_once_with(test_name)
951 self._manager._mkindex.assert_called_once_with()
952 self.assertFalse(
953 self._manager._get_export_rados_object_name.called)
954 self.assertFalse(self._manager._delete_rados_object.called)
955 self.assertFalse(
956 self._manager._remove_rados_object_url_from_index.called)
957 self.assertIsNone(ret)
959 @ddt.data(True, False)
960 def test_remove_export_error_during_read_export_with_rados_store(
961 self, rados_store_enable):
962 self._manager.ganesha_rados_store_enable = rados_store_enable
963 self.mock_object(
964 self._manager, '_read_export',
965 mock.Mock(side_effect=exception.GaneshaCommandFailure))
966 self.mock_object(self._manager, '_get_export_rados_object_name',
967 mock.Mock(return_value='fakeobj'))
968 methods = ('_remove_export_dbus', '_rm_export_file', '_mkindex',
969 '_remove_rados_object_url_from_index',
970 '_delete_rados_object')
971 for method in methods:
972 self.mock_object(self._manager, method)
974 ret = self._manager.remove_export(test_name)
976 self._manager._read_export.assert_called_once_with(test_name)
977 self.assertFalse(self._manager._remove_export_dbus.called)
978 if rados_store_enable:
979 (self._manager._get_export_rados_object_name.
980 assert_called_once_with(test_name))
981 self._manager._delete_rados_object.assert_called_once_with(
982 'fakeobj')
983 (self._manager._remove_rados_object_url_from_index.
984 assert_called_once_with(test_name))
985 self.assertFalse(self._manager._rm_export_file.called)
986 self.assertFalse(self._manager._mkindex.called)
987 else:
988 self._manager._rm_export_file.assert_called_once_with(test_name)
989 self._manager._mkindex.assert_called_once_with()
990 self.assertFalse(
991 self._manager._get_export_rados_object_name.called)
992 self.assertFalse(self._manager._delete_rados_object.called)
993 self.assertFalse(
994 self._manager._remove_rados_object_url_from_index.called)
995 self.assertIsNone(ret)
997 @ddt.data(True, False)
998 def test_remove_export_error_during_remove_export_dbus_with_rados_store(
999 self, rados_store_enable):
1000 self._manager.ganesha_rados_store_enable = rados_store_enable
1001 self.mock_object(self._manager, '_read_export',
1002 mock.Mock(return_value=test_dict_unicode))
1003 self.mock_object(self._manager, '_get_export_rados_object_name',
1004 mock.Mock(return_value='fakeobj'))
1005 self.mock_object(
1006 self._manager, '_remove_export_dbus',
1007 mock.Mock(side_effect=exception.GaneshaCommandFailure))
1008 methods = ('_rm_export_file', '_mkindex',
1009 '_remove_rados_object_url_from_index',
1010 '_delete_rados_object')
1011 for method in methods:
1012 self.mock_object(self._manager, method)
1014 ret = self._manager.remove_export(test_name)
1016 self._manager._read_export.assert_called_once_with(test_name)
1017 self._manager._remove_export_dbus.assert_called_once_with(
1018 test_dict_unicode['EXPORT']['Export_Id'])
1019 if rados_store_enable:
1020 (self._manager._get_export_rados_object_name.
1021 assert_called_once_with(test_name))
1022 self._manager._delete_rados_object.assert_called_once_with(
1023 'fakeobj')
1024 (self._manager._remove_rados_object_url_from_index.
1025 assert_called_once_with(test_name))
1026 self.assertFalse(self._manager._rm_export_file.called)
1027 self.assertFalse(self._manager._mkindex.called)
1028 else:
1029 self._manager._rm_export_file.assert_called_once_with(test_name)
1030 self._manager._mkindex.assert_called_once_with()
1031 self.assertFalse(
1032 self._manager._get_export_rados_object_name.called)
1033 self.assertFalse(self._manager._delete_rados_object.called)
1034 self.assertFalse(
1035 self._manager._remove_rados_object_url_from_index.called)
1036 self.assertIsNone(ret)
1038 def test_get_rados_object(self):
1039 fakebin = chr(246).encode('utf-8')
1041 ioctx = mock.Mock()
1042 ioctx.read.side_effect = [fakebin, fakebin]
1044 self._rados_client.open_ioctx = mock.Mock(return_value=ioctx)
1045 self._rados_client.conf_get = mock.Mock(return_value=256)
1047 max_size = 256 * 1024 * 1024
1049 ret = self._manager_with_rados_store._get_rados_object('fakeobj')
1051 self._rados_client.open_ioctx.assert_called_once_with('fakepool')
1052 self._rados_client.conf_get.assert_called_once_with(
1053 'osd_max_write_size')
1054 ioctx.read.assert_called_once_with('fakeobj', max_size)
1055 ioctx.close.assert_called_once()
1057 self.assertEqual(fakebin.decode('utf-8'), ret)
1059 def test_put_rados_object(self):
1060 faketext = chr(246)
1062 ioctx = mock.Mock()
1063 manager.rados.WriteOpCtx.write_full = mock.Mock()
1065 self._rados_client.open_ioctx = mock.Mock(return_value=ioctx)
1066 self._rados_client.conf_get = mock.Mock(return_value=256)
1068 ret = self._manager_with_rados_store._put_rados_object(
1069 'fakeobj', faketext)
1071 self._rados_client.open_ioctx.assert_called_once_with('fakepool')
1072 self._rados_client.conf_get.assert_called_once_with(
1073 'osd_max_write_size')
1074 manager.rados.WriteOpCtx.write_full.assert_called_once_with(
1075 faketext.encode('utf-8'))
1076 ioctx.operate_write_op.assert_called_once_with(mock.ANY, 'fakeobj')
1078 self.assertIsNone(ret)
1080 def test_delete_rados_object(self):
1081 ioctx = mock.Mock()
1083 self._rados_client.open_ioctx = mock.Mock(return_value=ioctx)
1085 ret = self._manager_with_rados_store._delete_rados_object('fakeobj')
1087 self._rados_client.open_ioctx.assert_called_once_with('fakepool')
1088 ioctx.remove_object.assert_called_once_with('fakeobj')
1089 ioctx.close.assert_called_once()
1091 self.assertIsNone(ret)
1093 def test_get_export_id(self):
1094 self.mock_object(self._manager, 'execute',
1095 mock.Mock(return_value=('exportid|101', '')))
1096 ret = self._manager.get_export_id()
1097 self._manager.execute.assert_called_once_with(
1098 'sqlite3', self._manager.ganesha_db_path,
1099 'update ganesha set value = value + 1;'
1100 'select * from ganesha where key = "exportid";',
1101 run_as_root=False)
1102 self.assertEqual(101, ret)
1104 def test_get_export_id_nobump(self):
1105 self.mock_object(self._manager, 'execute',
1106 mock.Mock(return_value=('exportid|101', '')))
1107 ret = self._manager.get_export_id(bump=False)
1108 self._manager.execute.assert_called_once_with(
1109 'sqlite3', self._manager.ganesha_db_path,
1110 'select * from ganesha where key = "exportid";',
1111 run_as_root=False)
1112 self.assertEqual(101, ret)
1114 def test_get_export_id_error_invalid_export_db(self):
1115 self.mock_object(self._manager, 'execute',
1116 mock.Mock(return_value=('invalid', '')))
1117 self.mock_object(manager.LOG, 'error')
1118 self.assertRaises(exception.InvalidSqliteDB,
1119 self._manager.get_export_id)
1120 manager.LOG.error.assert_called_once_with(
1121 mock.ANY, mock.ANY)
1122 self._manager.execute.assert_called_once_with(
1123 'sqlite3', self._manager.ganesha_db_path,
1124 'update ganesha set value = value + 1;'
1125 'select * from ganesha where key = "exportid";',
1126 run_as_root=False)
1128 @ddt.data(True, False)
1129 def test_get_export_id_with_rados_store_and_bump(self, bump):
1130 self.mock_object(self._manager_with_rados_store,
1131 '_get_rados_object', mock.Mock(return_value='1000'))
1132 self.mock_object(self._manager_with_rados_store, '_put_rados_object')
1134 ret = self._manager_with_rados_store.get_export_id(bump=bump)
1136 if bump:
1137 (self._manager_with_rados_store._get_rados_object.
1138 assert_called_once_with('fakecounter'))
1139 (self._manager_with_rados_store._put_rados_object.
1140 assert_called_once_with('fakecounter', '1001'))
1141 self.assertEqual(1001, ret)
1142 else:
1143 (self._manager_with_rados_store._get_rados_object.
1144 assert_called_once_with('fakecounter'))
1145 self.assertFalse(
1146 self._manager_with_rados_store._put_rados_object.called)
1147 self.assertEqual(1000, ret)
1149 def test_restart_service(self):
1150 self.mock_object(self._manager, 'execute')
1151 ret = self._manager.restart_service()
1152 self._manager.execute.assert_called_once_with(
1153 'service', 'ganesha.fakeservice', 'restart')
1154 self.assertIsNone(ret)
1156 def test_reset_exports(self):
1157 self.mock_object(self._manager, 'execute')
1158 self.mock_object(self._manager, '_mkindex')
1159 ret = self._manager.reset_exports()
1160 self._manager.execute.assert_called_once_with(
1161 'sh', '-c', 'rm -f /fakedir0/export.d/*.conf')
1162 self._manager._mkindex.assert_called_once_with()
1163 self.assertIsNone(ret)