Coverage for manila/tests/share/drivers/glusterfs/test_layout_directory.py: 100%

240 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 

16import os 

17from unittest import mock 

18 

19import ddt 

20from oslo_config import cfg 

21 

22from manila import context 

23from manila import exception 

24from manila.privsep import os as privsep_os 

25from manila.share import configuration as config 

26from manila.share.drivers.glusterfs import common 

27from manila.share.drivers.glusterfs import layout_directory 

28from manila import test 

29from manila.tests import fake_share 

30from manila.tests import fake_utils 

31 

32 

33CONF = cfg.CONF 

34 

35 

36fake_gluster_manager_attrs = { 

37 'export': '127.0.0.1:/testvol', 

38 'host': '127.0.0.1', 

39 'qualified': 'testuser@127.0.0.1:/testvol', 

40 'user': 'testuser', 

41 'volume': 'testvol', 

42 'path_to_private_key': '/fakepath/to/privatekey', 

43 'remote_server_password': 'fakepassword', 

44 'components': {'user': 'testuser', 'host': '127.0.0.1', 

45 'volume': 'testvol', 'path': None} 

46} 

47 

48fake_local_share_path = '/mnt/nfs/testvol/fakename' 

49 

50fake_path_to_private_key = '/fakepath/to/privatekey' 

51fake_remote_server_password = 'fakepassword' 

52 

53 

54@ddt.ddt 

55class GlusterfsDirectoryMappedLayoutTestCase(test.TestCase): 

56 """Tests GlusterfsDirectoryMappedLayout.""" 

57 

58 def setUp(self): 

59 super(GlusterfsDirectoryMappedLayoutTestCase, self).setUp() 

60 fake_utils.stub_out_utils_execute(self) 

61 self._execute = fake_utils.fake_execute 

62 self._context = context.get_admin_context() 

63 self.addCleanup(fake_utils.fake_execute_set_repliers, []) 

64 self.addCleanup(fake_utils.fake_execute_clear_log) 

65 

66 CONF.set_default('glusterfs_target', '127.0.0.1:/testvol') 

67 CONF.set_default('glusterfs_mount_point_base', '/mnt/nfs') 

68 CONF.set_default('glusterfs_server_password', 

69 fake_remote_server_password) 

70 CONF.set_default('glusterfs_path_to_private_key', 

71 fake_path_to_private_key) 

72 

73 self.fake_driver = mock.Mock() 

74 self.mock_object(self.fake_driver, '_execute', 

75 self._execute) 

76 self.fake_driver.GLUSTERFS_VERSION_MIN = (3, 6) 

77 self.fake_conf = config.Configuration(None) 

78 self.mock_object(common.GlusterManager, 'make_gluster_call') 

79 self._layout = layout_directory.GlusterfsDirectoryMappedLayout( 

80 self.fake_driver, configuration=self.fake_conf) 

81 self._layout.gluster_manager = mock.Mock(**fake_gluster_manager_attrs) 

82 self.share = fake_share.fake_share(share_proto='NFS') 

83 

84 def test_do_setup(self): 

85 fake_gluster_manager = mock.Mock(**fake_gluster_manager_attrs) 

86 self.mock_object(fake_gluster_manager, 'get_gluster_version', 

87 mock.Mock(return_value=('3', '5'))) 

88 methods = ('_check_mount_glusterfs', '_ensure_gluster_vol_mounted') 

89 for method in methods: 

90 self.mock_object(self._layout, method) 

91 self.mock_object(common, 'GlusterManager', 

92 mock.Mock(return_value=fake_gluster_manager)) 

93 

94 self._layout.do_setup(self._context) 

95 

96 self.assertEqual(fake_gluster_manager, self._layout.gluster_manager) 

97 common.GlusterManager.assert_called_once_with( 

98 self._layout.configuration.glusterfs_target, self._execute, 

99 self._layout.configuration.glusterfs_path_to_private_key, 

100 self._layout.configuration.glusterfs_server_password, 

101 requires={'volume': True}) 

102 self._layout.gluster_manager.gluster_call.assert_called_once_with( 

103 'volume', 'quota', 'testvol', 'enable') 

104 self._layout._check_mount_glusterfs.assert_called_once_with() 

105 self._layout._ensure_gluster_vol_mounted.assert_called_once_with() 

106 

107 def test_do_setup_glusterfs_target_not_set(self): 

108 self._layout.configuration.glusterfs_target = None 

109 self.assertRaises(exception.GlusterfsException, self._layout.do_setup, 

110 self._context) 

111 

112 def test_do_setup_error_enabling_creation_share_specific_size(self): 

113 attrs = {'volume': 'testvol', 

114 'gluster_call.side_effect': exception.GlusterfsException, 

115 'get_vol_option.return_value': 'off'} 

116 fake_gluster_manager = mock.Mock(**attrs) 

117 self.mock_object(layout_directory.LOG, 'exception') 

118 methods = ('_check_mount_glusterfs', '_ensure_gluster_vol_mounted') 

119 for method in methods: 

120 self.mock_object(self._layout, method) 

121 self.mock_object(common, 'GlusterManager', 

122 mock.Mock(return_value=fake_gluster_manager)) 

123 

124 self.assertRaises(exception.GlusterfsException, self._layout.do_setup, 

125 self._context) 

126 

127 self.assertEqual(fake_gluster_manager, self._layout.gluster_manager) 

128 common.GlusterManager.assert_called_once_with( 

129 self._layout.configuration.glusterfs_target, self._execute, 

130 self._layout.configuration.glusterfs_path_to_private_key, 

131 self._layout.configuration.glusterfs_server_password, 

132 requires={'volume': True}) 

133 self._layout.gluster_manager.gluster_call.assert_called_once_with( 

134 'volume', 'quota', 'testvol', 'enable') 

135 (self._layout.gluster_manager.get_vol_option. 

136 assert_called_once_with('features.quota')) 

137 layout_directory.LOG.exception.assert_called_once_with(mock.ANY) 

138 self._layout._check_mount_glusterfs.assert_called_once_with() 

139 self.assertFalse(self._layout._ensure_gluster_vol_mounted.called) 

140 

141 def test_do_setup_error_already_enabled_creation_share_specific_size(self): 

142 attrs = {'volume': 'testvol', 

143 'gluster_call.side_effect': exception.GlusterfsException, 

144 'get_vol_option.return_value': 'on'} 

145 fake_gluster_manager = mock.Mock(**attrs) 

146 self.mock_object(layout_directory.LOG, 'error') 

147 methods = ('_check_mount_glusterfs', '_ensure_gluster_vol_mounted') 

148 for method in methods: 

149 self.mock_object(self._layout, method) 

150 self.mock_object(common, 'GlusterManager', 

151 mock.Mock(return_value=fake_gluster_manager)) 

152 

153 self._layout.do_setup(self._context) 

154 

155 self.assertEqual(fake_gluster_manager, self._layout.gluster_manager) 

156 common.GlusterManager.assert_called_once_with( 

157 self._layout.configuration.glusterfs_target, self._execute, 

158 self._layout.configuration.glusterfs_path_to_private_key, 

159 self._layout.configuration.glusterfs_server_password, 

160 requires={'volume': True}) 

161 self._layout.gluster_manager.gluster_call.assert_called_once_with( 

162 'volume', 'quota', 'testvol', 'enable') 

163 (self._layout.gluster_manager.get_vol_option. 

164 assert_called_once_with('features.quota')) 

165 self.assertFalse(layout_directory.LOG.error.called) 

166 self._layout._check_mount_glusterfs.assert_called_once_with() 

167 self._layout._ensure_gluster_vol_mounted.assert_called_once_with() 

168 

169 def test_share_manager(self): 

170 self._layout._glustermanager = mock.Mock() 

171 

172 self._layout._share_manager(self.share) 

173 

174 self._layout._glustermanager.assert_called_once_with( 

175 {'user': 'testuser', 'host': '127.0.0.1', 

176 'volume': 'testvol', 'path': '/fakename'}) 

177 

178 def test_ensure_gluster_vol_mounted(self): 

179 common._mount_gluster_vol = mock.Mock() 

180 

181 self._layout._ensure_gluster_vol_mounted() 

182 

183 self.assertTrue(common._mount_gluster_vol.called) 

184 

185 def test_ensure_gluster_vol_mounted_error(self): 

186 common._mount_gluster_vol = ( 

187 mock.Mock(side_effect=exception.GlusterfsException)) 

188 

189 self.assertRaises(exception.GlusterfsException, 

190 self._layout._ensure_gluster_vol_mounted) 

191 

192 def test_get_local_share_path(self): 

193 with mock.patch.object(os, 'access', return_value=True): 

194 

195 ret = self._layout._get_local_share_path(self.share) 

196 

197 self.assertEqual('/mnt/nfs/testvol/fakename', ret) 

198 

199 def test_local_share_path_not_exists(self): 

200 with mock.patch.object(os, 'access', return_value=False): 

201 

202 self.assertRaises(exception.GlusterfsException, 

203 self._layout._get_local_share_path, 

204 self.share) 

205 

206 def test_update_share_stats(self): 

207 test_statvfs = mock.Mock(f_frsize=4096, f_blocks=524288, 

208 f_bavail=524288) 

209 self._layout._get_mount_point_for_gluster_vol = ( 

210 mock.Mock(return_value='/mnt/nfs/testvol')) 

211 some_no = 42 

212 not_some_no = some_no + 1 

213 os_stat = (lambda path: mock.Mock(st_dev=some_no) if path == '/mnt/nfs' 

214 else mock.Mock(st_dev=not_some_no)) 

215 with mock.patch.object(os, 'statvfs', return_value=test_statvfs): 

216 with mock.patch.object(os, 'stat', os_stat): 

217 

218 ret = self._layout._update_share_stats() 

219 

220 test_data = { 

221 'total_capacity_gb': 2, 

222 'free_capacity_gb': 2, 

223 } 

224 self.assertEqual(test_data, ret) 

225 

226 def test_update_share_stats_gluster_mnt_unavailable(self): 

227 self._layout._get_mount_point_for_gluster_vol = ( 

228 mock.Mock(return_value='/mnt/nfs/testvol')) 

229 some_no = 42 

230 with mock.patch.object(os, 'stat', 

231 return_value=mock.Mock(st_dev=some_no)): 

232 

233 self.assertRaises(exception.GlusterfsException, 

234 self._layout._update_share_stats) 

235 

236 @ddt.data((), (None,)) 

237 def test_create_share(self, extra_args): 

238 expected_ret = 'testuser@127.0.0.1:/testvol/fakename' 

239 self.mock_object( 

240 self._layout, '_get_local_share_path', 

241 mock.Mock(return_value=fake_local_share_path)) 

242 gmgr = mock.Mock() 

243 self.mock_object( 

244 self._layout, '_glustermanager', mock.Mock(return_value=gmgr)) 

245 self.mock_object( 

246 self._layout.driver, '_setup_via_manager', 

247 mock.Mock(return_value=expected_ret)) 

248 self.mock_object(privsep_os, 'mkdir') 

249 

250 ret = self._layout.create_share(self._context, self.share, *extra_args) 

251 

252 self._layout._get_local_share_path.assert_called_once_with(self.share) 

253 self._layout.gluster_manager.gluster_call.assert_called_once_with( 

254 'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '1GB') 

255 privsep_os.mkdir.assert_called_once_with(fake_local_share_path) 

256 self._layout._glustermanager.assert_called_once_with( 

257 {'user': 'testuser', 'host': '127.0.0.1', 

258 'volume': 'testvol', 'path': '/fakename'}) 

259 self._layout.driver._setup_via_manager.assert_called_once_with( 

260 {'share': self.share, 'manager': gmgr}) 

261 self.assertEqual(expected_ret, ret) 

262 

263 @ddt.data(exception.ProcessExecutionError, exception.GlusterfsException) 

264 def test_create_share_unable_to_create_share(self, trouble): 

265 

266 self.mock_object( 

267 self._layout, '_get_local_share_path', 

268 mock.Mock(return_value=fake_local_share_path)) 

269 self.mock_object(privsep_os, 'mkdir', mock.Mock(side_effect=trouble)) 

270 self.mock_object(self._layout, '_cleanup_create_share') 

271 self.mock_object(layout_directory.LOG, 'error') 

272 

273 self.assertRaises( 

274 exception.GlusterfsException, self._layout.create_share, 

275 self._context, self.share) 

276 

277 self._layout._get_local_share_path.assert_called_once_with(self.share) 

278 privsep_os.mkdir.assert_called_once_with(fake_local_share_path) 

279 self._layout._cleanup_create_share.assert_called_once_with( 

280 fake_local_share_path, self.share['name']) 

281 layout_directory.LOG.error.assert_called_once_with( 

282 mock.ANY, mock.ANY) 

283 

284 def test_create_share_unable_to_create_share_weird(self): 

285 def exec_runner(*ignore_args, **ignore_kw): 

286 raise RuntimeError 

287 

288 self.mock_object( 

289 self._layout, '_get_local_share_path', 

290 mock.Mock(return_value=fake_local_share_path)) 

291 self.mock_object(self._layout, '_cleanup_create_share') 

292 self.mock_object( 

293 privsep_os, 'mkdir', mock.Mock(side_effect=exec_runner)) 

294 self.mock_object(layout_directory.LOG, 'error') 

295 expected_exec = ['mkdir %s' % fake_local_share_path] 

296 fake_utils.fake_execute_set_repliers([(expected_exec[0], 

297 exec_runner)]) 

298 

299 self.assertRaises( 

300 RuntimeError, self._layout.create_share, 

301 self._context, self.share) 

302 

303 self._layout._get_local_share_path.assert_called_once_with(self.share) 

304 privsep_os.mkdir.assert_called_once_with(fake_local_share_path) 

305 self.assertFalse(self._layout._cleanup_create_share.called) 

306 

307 def test_cleanup_create_share_local_share_path_exists(self): 

308 self.mock_object(privsep_os, 'recursive_forced_rm') 

309 self.mock_object(os.path, 'exists', mock.Mock(return_value=True)) 

310 

311 ret = self._layout._cleanup_create_share(fake_local_share_path, 

312 self.share['name']) 

313 

314 os.path.exists.assert_called_once_with(fake_local_share_path) 

315 privsep_os.recursive_forced_rm.assert_called_once_with( 

316 fake_local_share_path) 

317 self.assertIsNone(ret) 

318 

319 def test_cleanup_create_share_cannot_cleanup_unusable_share(self): 

320 def exec_runner(*ignore_args, **ignore_kw): 

321 raise exception.ProcessExecutionError 

322 self.mock_object(privsep_os, 'recursive_forced_rm', 

323 mock.Mock(side_effect=exec_runner)) 

324 self.mock_object(layout_directory.LOG, 'error') 

325 self.mock_object(os.path, 'exists', mock.Mock(return_value=True)) 

326 

327 self.assertRaises(exception.GlusterfsException, 

328 self._layout._cleanup_create_share, 

329 fake_local_share_path, self.share['name']) 

330 

331 os.path.exists.assert_called_once_with(fake_local_share_path) 

332 layout_directory.LOG.error.assert_called_once_with(mock.ANY, mock.ANY) 

333 

334 def test_cleanup_create_share_local_share_path_does_not_exist(self): 

335 self.mock_object(os.path, 'exists', mock.Mock(return_value=False)) 

336 

337 ret = self._layout._cleanup_create_share(fake_local_share_path, 

338 self.share['name']) 

339 

340 os.path.exists.assert_called_once_with(fake_local_share_path) 

341 self.assertIsNone(ret) 

342 

343 def test_delete_share(self): 

344 local_share_path = '/mnt/nfs/testvol/fakename' 

345 self._layout._get_local_share_path = ( 

346 mock.Mock(return_value=local_share_path)) 

347 mock_force_rm = self.mock_object(privsep_os, 'recursive_forced_rm') 

348 

349 self._layout.delete_share(self._context, self.share) 

350 

351 mock_force_rm.assert_called_once_with( 

352 local_share_path) 

353 

354 def test_cannot_delete_share(self): 

355 local_share_path = '/mnt/nfs/testvol/fakename' 

356 self._layout._get_local_share_path = ( 

357 mock.Mock(return_value=local_share_path)) 

358 self.mock_object( 

359 privsep_os, 'recursive_forced_rm', mock.Mock( 

360 side_effect=exception.ProcessExecutionError)) 

361 

362 self.assertRaises(exception.ProcessExecutionError, 

363 self._layout.delete_share, self._context, self.share) 

364 

365 privsep_os.mkdir.assert_called_once_with = local_share_path 

366 

367 def test_delete_share_can_be_called_with_extra_arg_share_server(self): 

368 local_share_path = '/mnt/nfs/testvol/fakename' 

369 self._layout._get_local_share_path = mock.Mock( 

370 return_value=local_share_path) 

371 mock_force_rm = self.mock_object(privsep_os, 'recursive_forced_rm') 

372 

373 share_server = None 

374 ret = self._layout.delete_share(self._context, self.share, 

375 share_server) 

376 

377 self.assertIsNone(ret) 

378 self._layout._get_local_share_path.assert_called_once_with(self.share) 

379 mock_force_rm.assert_called_once_with(local_share_path) 

380 

381 def test_ensure_share(self): 

382 self.assertIsNone(self._layout.ensure_share(self._context, self.share)) 

383 

384 def test_extend_share(self): 

385 self._layout.extend_share(self.share, 3) 

386 

387 self._layout.gluster_manager.gluster_call.assert_called_once_with( 

388 'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '3GB') 

389 

390 def test_shrink_share(self): 

391 self.mock_object(self._layout, '_get_directory_usage', 

392 mock.Mock(return_value=10.0)) 

393 

394 self._layout.shrink_share(self.share, 11) 

395 self._layout.gluster_manager.gluster_call.assert_called_once_with( 

396 'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '11GB') 

397 

398 def test_shrink_share_data_loss(self): 

399 self.mock_object(self._layout, '_get_directory_usage', 

400 mock.Mock(return_value=10.0)) 

401 shrink_on_gluster = self.mock_object(self._layout, 

402 '_set_directory_quota') 

403 

404 self.assertRaises(exception.ShareShrinkingPossibleDataLoss, 

405 self._layout.shrink_share, self.share, 9) 

406 shrink_on_gluster.assert_not_called() 

407 

408 def test_set_directory_quota(self): 

409 self._layout._set_directory_quota(self.share, 3) 

410 

411 self._layout.gluster_manager.gluster_call.assert_called_once_with( 

412 'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '3GB') 

413 

414 def test_set_directory_quota_unable_to_set(self): 

415 self.mock_object(self._layout.gluster_manager, 'gluster_call', 

416 mock.Mock(side_effect=exception.GlusterfsException)) 

417 

418 self.assertRaises(exception.GlusterfsException, 

419 self._layout._set_directory_quota, self.share, 3) 

420 

421 self._layout.gluster_manager.gluster_call.assert_called_once_with( 

422 'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '3GB') 

423 

424 def test_get_directory_usage(self): 

425 

426 def xml_output(*ignore_args, **ignore_kwargs): 

427 return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 

428<cliOutput> 

429 <opRet>0</opRet> 

430 <opErrno>0</opErrno> 

431 <opErrstr/> 

432 <volQuota> 

433 <limit> 

434 <used_space>10737418240</used_space> 

435 </limit> 

436 </volQuota> 

437</cliOutput>""", '' 

438 

439 self.mock_object(self._layout.gluster_manager, 'gluster_call', 

440 mock.Mock(side_effect=xml_output)) 

441 

442 ret = self._layout._get_directory_usage(self.share) 

443 

444 self.assertEqual(10.0, ret) 

445 share_dir = '/' + self.share['name'] 

446 self._layout.gluster_manager.gluster_call.assert_called_once_with( 

447 '--xml', 'volume', 'quota', self._layout.gluster_manager.volume, 

448 'list', share_dir) 

449 

450 def test_get_directory_usage_unable_to_get(self): 

451 self.mock_object(self._layout.gluster_manager, 'gluster_call', 

452 mock.Mock(side_effect=exception.GlusterfsException)) 

453 

454 self.assertRaises(exception.GlusterfsException, 

455 self._layout._get_directory_usage, self.share) 

456 

457 share_dir = '/' + self.share['name'] 

458 self._layout.gluster_manager.gluster_call.assert_called_once_with( 

459 '--xml', 'volume', 'quota', self._layout.gluster_manager.volume, 

460 'list', share_dir) 

461 

462 @ddt.data( 

463 ('create_share_from_snapshot', ('context', 'share', 'snapshot'), 

464 {'share_server': None}), 

465 ('create_snapshot', ('context', 'snapshot'), {'share_server': None}), 

466 ('delete_snapshot', ('context', 'snapshot'), {'share_server': None}), 

467 ('manage_existing', ('share', 'driver_options'), {}), 

468 ('unmanage', ('share',), {})) 

469 def test_nonimplemented_methods(self, method_invocation): 

470 method, args, kwargs = method_invocation 

471 self.assertRaises(NotImplementedError, getattr(self._layout, method), 

472 *args, **kwargs)