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

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. 

15 

16"""Test cases for GlusterFS common routines.""" 

17 

18from unittest import mock 

19 

20import ddt 

21from oslo_config import cfg 

22 

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 

28 

29 

30CONF = cfg.CONF 

31 

32 

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' 

47 

48fakehost = 'example.com' 

49fakevol = 'testvol' 

50fakeexport = ':/'.join((fakehost, fakevol)) 

51fakemnt = '/mnt/glusterfs' 

52 

53 

54@ddt.ddt 

55class GlusterManagerTestCase(test.TestCase): 

56 """Tests GlusterManager.""" 

57 

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] 

77 

78 def test_check_volume_presence(self): 

79 common._check_volume_presence(mock.Mock())(self._gluster_manager) 

80 

81 def test_check_volume_presence_error(self): 

82 gmgr = common.GlusterManager('testuser@127.0.0.1') 

83 

84 self.assertRaises( 

85 exception.GlusterfsException, 

86 common._check_volume_presence(mock.Mock()), gmgr) 

87 

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) 

93 

94 ret = common.volxml_get(xmlout, 'some/path') 

95 

96 self.assertEqual('foobar', ret) 

97 

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) 

102 

103 ret = common.volxml_get(xmlout, 'some/path', default=default) 

104 

105 self.assertEqual(default, ret) 

106 

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)) 

112 

113 ret = common.volxml_get(xmlout, 'some/path', 'better/path') 

114 

115 self.assertEqual('foobar', ret) 

116 

117 def test_volxml_get_notfound(self): 

118 xmlout = mock.Mock() 

119 xmlout.find = mock.Mock(return_value=None) 

120 

121 self.assertRaises(exception.InvalidShare, common.volxml_get, 

122 xmlout, 'some/path') 

123 

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) 

150 

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) 

167 

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) 

174 

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) 

185 

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) 

192 

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) 

201 

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}) 

207 

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}) 

216 

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}) 

221 

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}) 

228 

229 def test_gluster_manager_invalid(self): 

230 self.assertRaises(exception.GlusterfsException, 

231 common.GlusterManager, '127.0.0.1:vol', 

232 'self.fake_execf') 

233 

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') 

240 

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') 

252 

253 def test_gluster_manager_getattr(self): 

254 self.assertEqual('testvol', self._gluster_manager.volume) 

255 

256 def test_gluster_manager_getattr_called(self): 

257 class FakeGlusterManager(common.GlusterManager): 

258 pass 

259 

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') 

265 

266 def test_gluster_manager_getattr_noattr(self): 

267 self.assertRaises(AttributeError, getattr, self._gluster_manager, 

268 'fakeprop') 

269 

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) 

291 

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) 

308 

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) 

332 

333 self.assertRaises(_exception, 

334 gluster_manager.make_gluster_call(fake_execute), 

335 *fake_args, **kwargs) 

336 

337 common.ganesha_utils.RootExecutor.assert_called_with( 

338 fake_execute) 

339 fake_obj.assert_called_once_with( 

340 *(('gluster',) + fake_args), **fake_kwargs) 

341 

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) 

349 

350 self.assertRaises(TypeError, 

351 gluster_manager.make_gluster_call(fake_execute), 

352 *fake_args, error_policy='foobar') 

353 

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) 

358 

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] 

364 

365 xtree = mock.Mock() 

366 command = ['volume', 'command', 'fake'] 

367 

368 with mock.patch.object(common, 'volxml_get', side_effect=vxget): 

369 self._gluster_manager.xml_response_check(xtree, command, 

370 'some/count') 

371 

372 self.assertTrue(common.volxml_get.called) 

373 

374 @ddt.data('1', '2') 

375 def test_xml_response_check_failure(self, count): 

376 fdict = {'opRet': '-1', 'opErrno': '0', 'some/count': count} 

377 

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] 

383 

384 xtree = mock.Mock() 

385 command = ['volume', 'command', 'fake'] 

386 

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') 

391 

392 self.assertTrue(common.volxml_get.called) 

393 

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): 

399 

400 def vxget(x, *e, **kw): 

401 if kw: 

402 return fdict.get(e[0], kw['default']) 

403 else: 

404 return fdict[e[0]] 

405 

406 xtree = mock.Mock() 

407 command = ['volume', 'command', 'fake'] 

408 

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') 

413 

414 self.assertTrue(common.volxml_get.called) 

415 

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): 

419 

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] 

425 

426 xtree = mock.Mock() 

427 command = ['volume', 'command', 'fake'] 

428 

429 with mock.patch.object(common, 'volxml_get', side_effect=vxget): 

430 self._gluster_manager.xml_response_check(xtree, command) 

431 

432 self.assertTrue(common.volxml_get.called) 

433 

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) 

443 

444 def test_get_vol_option_via_info_ambiguous_volinfo(self): 

445 

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>""", '' 

458 

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) 

467 

468 def test_get_vol_option_via_info_trivial_volinfo(self): 

469 

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>""", '' 

484 

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) 

492 

493 def test_get_vol_option_via_info(self): 

494 

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>""", '' 

515 

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) 

523 

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')) 

527 

528 ret = self._gluster_manager._get_vol_user_option('OPT') 

529 

530 self.assertEqual(ret, 'VALUE') 

531 (self._gluster_manager._get_vol_option_via_info. 

532 assert_called_once_with('user.OPT')) 

533 

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=('', {}))) 

539 

540 ret = self._gluster_manager._get_vol_regular_option(NFS_EXPORT_DIR) 

541 

542 self.assertIsNone(ret) 

543 self._gluster_manager.gluster_call.assert_called_once_with( 

544 *args, check_exit_code=False) 

545 

546 @ddt.data(0, 2) 

547 def test_get_vol_regular_option_ambiguous_volinfo(self, count): 

548 

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, '' 

559 

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)) 

564 

565 self.assertRaises(exception.InvalidShare, 

566 self._gluster_manager._get_vol_regular_option, 

567 NFS_EXPORT_DIR) 

568 

569 self._gluster_manager.gluster_call.assert_called_once_with( 

570 *args, check_exit_code=False) 

571 

572 @ddt.data({'start': "", 'end': ""}, {'start': "<Opt>", 'end': "</Opt>"}) 

573 def test_get_vol_regular_option(self, extratag): 

574 

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, '' 

589 

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)) 

594 

595 ret = self._gluster_manager._get_vol_regular_option(NFS_EXPORT_DIR) 

596 

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) 

600 

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")) 

609 

610 ret = self._gluster_manager._get_vol_regular_option(NFS_EXPORT_DIR) 

611 

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)) 

617 

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)) 

626 

627 ret = self._gluster_manager.get_vol_option(opt) 

628 

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) 

636 

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)) 

640 

641 ret = self._gluster_manager.get_vol_option('some.option') 

642 

643 self.assertIsNone(ret) 

644 

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)) 

656 

657 ret = self._gluster_manager.get_vol_option('some.option', 

658 boolean=True) 

659 

660 self.assertEqual(boolval, ret) 

661 

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')) 

665 

666 self.assertRaises(exception.GlusterfsException, 

667 self._gluster_manager.get_vol_option, 

668 'some.option', boolean=True) 

669 

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()) 

677 

678 self._gluster_manager.set_vol_option('an_option', setting) 

679 

680 self._gluster_manager.gluster_call.assert_called_once_with( 

681 'volume', args[0], 'testvol', 'an_option', *args[1:], 

682 error_policy=mock.ANY) 

683 

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) 

693 

694 self.assertRaises(exception.GlusterfsException, 

695 gluster_manager.set_vol_option, 

696 'an_option', "some_value", **kwargs) 

697 

698 self.assertTrue(fake_obj.called) 

699 

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) 

707 

708 gluster_manager.set_vol_option('an_option', "some_value", 

709 ignore_failure=True) 

710 

711 self.assertTrue(fake_obj.called) 

712 

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) 

720 

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) 

729 

730 def test_check_gluster_version(self): 

731 self.mock_object(self._gluster_manager, 'get_gluster_version', 

732 mock.Mock(return_value=('3', '6'))) 

733 

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() 

737 

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'))) 

741 

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() 

745 

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) 

752 

753 

754@ddt.ddt 

755class GlusterFSCommonTestCase(test.TestCase): 

756 """Tests common GlusterFS utility functions.""" 

757 

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') 

765 

766 @staticmethod 

767 def _mount_exec(vol, mnt): 

768 return ['mkdir -p %s' % mnt] 

769 

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) 

779 

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) 

792 

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) 

808 

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) 

826 

827 def test_umount_gluster_vol(self): 

828 self.mock_object(privsep_os, 'umount') 

829 

830 ret = common._umount_gluster_vol(fakemnt) 

831 privsep_os.umount.assert_called_once_with(fakemnt) 

832 self.assertIsNone(ret) 

833 

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) 

845 

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})] 

851 

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)