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
« 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.
16import os
17from unittest import mock
19import ddt
20from oslo_config import cfg
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
33CONF = cfg.CONF
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}
48fake_local_share_path = '/mnt/nfs/testvol/fakename'
50fake_path_to_private_key = '/fakepath/to/privatekey'
51fake_remote_server_password = 'fakepassword'
54@ddt.ddt
55class GlusterfsDirectoryMappedLayoutTestCase(test.TestCase):
56 """Tests GlusterfsDirectoryMappedLayout."""
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)
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)
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')
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))
94 self._layout.do_setup(self._context)
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()
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)
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))
124 self.assertRaises(exception.GlusterfsException, self._layout.do_setup,
125 self._context)
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)
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))
153 self._layout.do_setup(self._context)
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()
169 def test_share_manager(self):
170 self._layout._glustermanager = mock.Mock()
172 self._layout._share_manager(self.share)
174 self._layout._glustermanager.assert_called_once_with(
175 {'user': 'testuser', 'host': '127.0.0.1',
176 'volume': 'testvol', 'path': '/fakename'})
178 def test_ensure_gluster_vol_mounted(self):
179 common._mount_gluster_vol = mock.Mock()
181 self._layout._ensure_gluster_vol_mounted()
183 self.assertTrue(common._mount_gluster_vol.called)
185 def test_ensure_gluster_vol_mounted_error(self):
186 common._mount_gluster_vol = (
187 mock.Mock(side_effect=exception.GlusterfsException))
189 self.assertRaises(exception.GlusterfsException,
190 self._layout._ensure_gluster_vol_mounted)
192 def test_get_local_share_path(self):
193 with mock.patch.object(os, 'access', return_value=True):
195 ret = self._layout._get_local_share_path(self.share)
197 self.assertEqual('/mnt/nfs/testvol/fakename', ret)
199 def test_local_share_path_not_exists(self):
200 with mock.patch.object(os, 'access', return_value=False):
202 self.assertRaises(exception.GlusterfsException,
203 self._layout._get_local_share_path,
204 self.share)
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):
218 ret = self._layout._update_share_stats()
220 test_data = {
221 'total_capacity_gb': 2,
222 'free_capacity_gb': 2,
223 }
224 self.assertEqual(test_data, ret)
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)):
233 self.assertRaises(exception.GlusterfsException,
234 self._layout._update_share_stats)
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')
250 ret = self._layout.create_share(self._context, self.share, *extra_args)
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)
263 @ddt.data(exception.ProcessExecutionError, exception.GlusterfsException)
264 def test_create_share_unable_to_create_share(self, trouble):
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')
273 self.assertRaises(
274 exception.GlusterfsException, self._layout.create_share,
275 self._context, self.share)
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)
284 def test_create_share_unable_to_create_share_weird(self):
285 def exec_runner(*ignore_args, **ignore_kw):
286 raise RuntimeError
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)])
299 self.assertRaises(
300 RuntimeError, self._layout.create_share,
301 self._context, self.share)
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)
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))
311 ret = self._layout._cleanup_create_share(fake_local_share_path,
312 self.share['name'])
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)
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))
327 self.assertRaises(exception.GlusterfsException,
328 self._layout._cleanup_create_share,
329 fake_local_share_path, self.share['name'])
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)
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))
337 ret = self._layout._cleanup_create_share(fake_local_share_path,
338 self.share['name'])
340 os.path.exists.assert_called_once_with(fake_local_share_path)
341 self.assertIsNone(ret)
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')
349 self._layout.delete_share(self._context, self.share)
351 mock_force_rm.assert_called_once_with(
352 local_share_path)
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))
362 self.assertRaises(exception.ProcessExecutionError,
363 self._layout.delete_share, self._context, self.share)
365 privsep_os.mkdir.assert_called_once_with = local_share_path
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')
373 share_server = None
374 ret = self._layout.delete_share(self._context, self.share,
375 share_server)
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)
381 def test_ensure_share(self):
382 self.assertIsNone(self._layout.ensure_share(self._context, self.share))
384 def test_extend_share(self):
385 self._layout.extend_share(self.share, 3)
387 self._layout.gluster_manager.gluster_call.assert_called_once_with(
388 'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '3GB')
390 def test_shrink_share(self):
391 self.mock_object(self._layout, '_get_directory_usage',
392 mock.Mock(return_value=10.0))
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')
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')
404 self.assertRaises(exception.ShareShrinkingPossibleDataLoss,
405 self._layout.shrink_share, self.share, 9)
406 shrink_on_gluster.assert_not_called()
408 def test_set_directory_quota(self):
409 self._layout._set_directory_quota(self.share, 3)
411 self._layout.gluster_manager.gluster_call.assert_called_once_with(
412 'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '3GB')
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))
418 self.assertRaises(exception.GlusterfsException,
419 self._layout._set_directory_quota, self.share, 3)
421 self._layout.gluster_manager.gluster_call.assert_called_once_with(
422 'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '3GB')
424 def test_get_directory_usage(self):
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>""", ''
439 self.mock_object(self._layout.gluster_manager, 'gluster_call',
440 mock.Mock(side_effect=xml_output))
442 ret = self._layout._get_directory_usage(self.share)
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)
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))
454 self.assertRaises(exception.GlusterfsException,
455 self._layout._get_directory_usage, self.share)
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)
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)