manila/manila/tests/share/drivers/glusterfs/test_layout_directory.py

470 lines
20 KiB
Python

# Copyright (c) 2015 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from unittest import mock
import ddt
from oslo_config import cfg
from manila import context
from manila import exception
from manila.share import configuration as config
from manila.share.drivers.glusterfs import common
from manila.share.drivers.glusterfs import layout_directory
from manila import test
from manila.tests import fake_share
from manila.tests import fake_utils
CONF = cfg.CONF
fake_gluster_manager_attrs = {
'export': '127.0.0.1:/testvol',
'host': '127.0.0.1',
'qualified': 'testuser@127.0.0.1:/testvol',
'user': 'testuser',
'volume': 'testvol',
'path_to_private_key': '/fakepath/to/privatekey',
'remote_server_password': 'fakepassword',
'components': {'user': 'testuser', 'host': '127.0.0.1',
'volume': 'testvol', 'path': None}
}
fake_local_share_path = '/mnt/nfs/testvol/fakename'
fake_path_to_private_key = '/fakepath/to/privatekey'
fake_remote_server_password = 'fakepassword'
@ddt.ddt
class GlusterfsDirectoryMappedLayoutTestCase(test.TestCase):
"""Tests GlusterfsDirectoryMappedLayout."""
def setUp(self):
super(GlusterfsDirectoryMappedLayoutTestCase, self).setUp()
fake_utils.stub_out_utils_execute(self)
self._execute = fake_utils.fake_execute
self._context = context.get_admin_context()
self.addCleanup(fake_utils.fake_execute_set_repliers, [])
self.addCleanup(fake_utils.fake_execute_clear_log)
CONF.set_default('glusterfs_target', '127.0.0.1:/testvol')
CONF.set_default('glusterfs_mount_point_base', '/mnt/nfs')
CONF.set_default('glusterfs_server_password',
fake_remote_server_password)
CONF.set_default('glusterfs_path_to_private_key',
fake_path_to_private_key)
self.fake_driver = mock.Mock()
self.mock_object(self.fake_driver, '_execute',
self._execute)
self.fake_driver.GLUSTERFS_VERSION_MIN = (3, 6)
self.fake_conf = config.Configuration(None)
self.mock_object(common.GlusterManager, 'make_gluster_call')
self._layout = layout_directory.GlusterfsDirectoryMappedLayout(
self.fake_driver, configuration=self.fake_conf)
self._layout.gluster_manager = mock.Mock(**fake_gluster_manager_attrs)
self.share = fake_share.fake_share(share_proto='NFS')
def test_do_setup(self):
fake_gluster_manager = mock.Mock(**fake_gluster_manager_attrs)
self.mock_object(fake_gluster_manager, 'get_gluster_version',
mock.Mock(return_value=('3', '5')))
methods = ('_check_mount_glusterfs', '_ensure_gluster_vol_mounted')
for method in methods:
self.mock_object(self._layout, method)
self.mock_object(common, 'GlusterManager',
mock.Mock(return_value=fake_gluster_manager))
self._layout.do_setup(self._context)
self.assertEqual(fake_gluster_manager, self._layout.gluster_manager)
common.GlusterManager.assert_called_once_with(
self._layout.configuration.glusterfs_target, self._execute,
self._layout.configuration.glusterfs_path_to_private_key,
self._layout.configuration.glusterfs_server_password,
requires={'volume': True})
self._layout.gluster_manager.gluster_call.assert_called_once_with(
'volume', 'quota', 'testvol', 'enable')
self._layout._check_mount_glusterfs.assert_called_once_with()
self._layout._ensure_gluster_vol_mounted.assert_called_once_with()
def test_do_setup_glusterfs_target_not_set(self):
self._layout.configuration.glusterfs_target = None
self.assertRaises(exception.GlusterfsException, self._layout.do_setup,
self._context)
def test_do_setup_error_enabling_creation_share_specific_size(self):
attrs = {'volume': 'testvol',
'gluster_call.side_effect': exception.GlusterfsException,
'get_vol_option.return_value': 'off'}
fake_gluster_manager = mock.Mock(**attrs)
self.mock_object(layout_directory.LOG, 'exception')
methods = ('_check_mount_glusterfs', '_ensure_gluster_vol_mounted')
for method in methods:
self.mock_object(self._layout, method)
self.mock_object(common, 'GlusterManager',
mock.Mock(return_value=fake_gluster_manager))
self.assertRaises(exception.GlusterfsException, self._layout.do_setup,
self._context)
self.assertEqual(fake_gluster_manager, self._layout.gluster_manager)
common.GlusterManager.assert_called_once_with(
self._layout.configuration.glusterfs_target, self._execute,
self._layout.configuration.glusterfs_path_to_private_key,
self._layout.configuration.glusterfs_server_password,
requires={'volume': True})
self._layout.gluster_manager.gluster_call.assert_called_once_with(
'volume', 'quota', 'testvol', 'enable')
(self._layout.gluster_manager.get_vol_option.
assert_called_once_with('features.quota'))
layout_directory.LOG.exception.assert_called_once_with(mock.ANY)
self._layout._check_mount_glusterfs.assert_called_once_with()
self.assertFalse(self._layout._ensure_gluster_vol_mounted.called)
def test_do_setup_error_already_enabled_creation_share_specific_size(self):
attrs = {'volume': 'testvol',
'gluster_call.side_effect': exception.GlusterfsException,
'get_vol_option.return_value': 'on'}
fake_gluster_manager = mock.Mock(**attrs)
self.mock_object(layout_directory.LOG, 'error')
methods = ('_check_mount_glusterfs', '_ensure_gluster_vol_mounted')
for method in methods:
self.mock_object(self._layout, method)
self.mock_object(common, 'GlusterManager',
mock.Mock(return_value=fake_gluster_manager))
self._layout.do_setup(self._context)
self.assertEqual(fake_gluster_manager, self._layout.gluster_manager)
common.GlusterManager.assert_called_once_with(
self._layout.configuration.glusterfs_target, self._execute,
self._layout.configuration.glusterfs_path_to_private_key,
self._layout.configuration.glusterfs_server_password,
requires={'volume': True})
self._layout.gluster_manager.gluster_call.assert_called_once_with(
'volume', 'quota', 'testvol', 'enable')
(self._layout.gluster_manager.get_vol_option.
assert_called_once_with('features.quota'))
self.assertFalse(layout_directory.LOG.error.called)
self._layout._check_mount_glusterfs.assert_called_once_with()
self._layout._ensure_gluster_vol_mounted.assert_called_once_with()
def test_share_manager(self):
self._layout._glustermanager = mock.Mock()
self._layout._share_manager(self.share)
self._layout._glustermanager.assert_called_once_with(
{'user': 'testuser', 'host': '127.0.0.1',
'volume': 'testvol', 'path': '/fakename'})
def test_ensure_gluster_vol_mounted(self):
common._mount_gluster_vol = mock.Mock()
self._layout._ensure_gluster_vol_mounted()
self.assertTrue(common._mount_gluster_vol.called)
def test_ensure_gluster_vol_mounted_error(self):
common._mount_gluster_vol = (
mock.Mock(side_effect=exception.GlusterfsException))
self.assertRaises(exception.GlusterfsException,
self._layout._ensure_gluster_vol_mounted)
def test_get_local_share_path(self):
with mock.patch.object(os, 'access', return_value=True):
ret = self._layout._get_local_share_path(self.share)
self.assertEqual('/mnt/nfs/testvol/fakename', ret)
def test_local_share_path_not_exists(self):
with mock.patch.object(os, 'access', return_value=False):
self.assertRaises(exception.GlusterfsException,
self._layout._get_local_share_path,
self.share)
def test_update_share_stats(self):
test_statvfs = mock.Mock(f_frsize=4096, f_blocks=524288,
f_bavail=524288)
self._layout._get_mount_point_for_gluster_vol = (
mock.Mock(return_value='/mnt/nfs/testvol'))
some_no = 42
not_some_no = some_no + 1
os_stat = (lambda path: mock.Mock(st_dev=some_no) if path == '/mnt/nfs'
else mock.Mock(st_dev=not_some_no))
with mock.patch.object(os, 'statvfs', return_value=test_statvfs):
with mock.patch.object(os, 'stat', os_stat):
ret = self._layout._update_share_stats()
test_data = {
'total_capacity_gb': 2,
'free_capacity_gb': 2,
}
self.assertEqual(test_data, ret)
def test_update_share_stats_gluster_mnt_unavailable(self):
self._layout._get_mount_point_for_gluster_vol = (
mock.Mock(return_value='/mnt/nfs/testvol'))
some_no = 42
with mock.patch.object(os, 'stat',
return_value=mock.Mock(st_dev=some_no)):
self.assertRaises(exception.GlusterfsException,
self._layout._update_share_stats)
@ddt.data((), (None,))
def test_create_share(self, extra_args):
exec_cmd1 = 'mkdir %s' % fake_local_share_path
expected_exec = [exec_cmd1, ]
expected_ret = 'testuser@127.0.0.1:/testvol/fakename'
self.mock_object(
self._layout, '_get_local_share_path',
mock.Mock(return_value=fake_local_share_path))
gmgr = mock.Mock()
self.mock_object(
self._layout, '_glustermanager', mock.Mock(return_value=gmgr))
self.mock_object(
self._layout.driver, '_setup_via_manager',
mock.Mock(return_value=expected_ret))
ret = self._layout.create_share(self._context, self.share, *extra_args)
self._layout._get_local_share_path.called_once_with(self.share)
self._layout.gluster_manager.gluster_call.assert_called_once_with(
'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '1GB')
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
self._layout._glustermanager.assert_called_once_with(
{'user': 'testuser', 'host': '127.0.0.1',
'volume': 'testvol', 'path': '/fakename'})
self._layout.driver._setup_via_manager.assert_called_once_with(
{'share': self.share, 'manager': gmgr})
self.assertEqual(expected_ret, ret)
@ddt.data(exception.ProcessExecutionError, exception.GlusterfsException)
def test_create_share_unable_to_create_share(self, trouble):
def exec_runner(*ignore_args, **ignore_kw):
raise trouble
self.mock_object(
self._layout, '_get_local_share_path',
mock.Mock(return_value=fake_local_share_path))
self.mock_object(self._layout, '_cleanup_create_share')
self.mock_object(layout_directory.LOG, 'error')
expected_exec = ['mkdir %s' % fake_local_share_path]
fake_utils.fake_execute_set_repliers([(expected_exec[0],
exec_runner)])
self.assertRaises(
exception.GlusterfsException, self._layout.create_share,
self._context, self.share)
self._layout._get_local_share_path.called_once_with(self.share)
self._layout._cleanup_create_share.assert_called_once_with(
fake_local_share_path, self.share['name'])
layout_directory.LOG.error.assert_called_once_with(
mock.ANY, mock.ANY)
def test_create_share_unable_to_create_share_weird(self):
def exec_runner(*ignore_args, **ignore_kw):
raise RuntimeError
self.mock_object(
self._layout, '_get_local_share_path',
mock.Mock(return_value=fake_local_share_path))
self.mock_object(self._layout, '_cleanup_create_share')
self.mock_object(layout_directory.LOG, 'error')
expected_exec = ['mkdir %s' % fake_local_share_path]
fake_utils.fake_execute_set_repliers([(expected_exec[0],
exec_runner)])
self.assertRaises(
RuntimeError, self._layout.create_share,
self._context, self.share)
self._layout._get_local_share_path.called_once_with(self.share)
self.assertFalse(self._layout._cleanup_create_share.called)
def test_cleanup_create_share_local_share_path_exists(self):
expected_exec = ['rm -rf %s' % fake_local_share_path]
self.mock_object(os.path, 'exists', mock.Mock(return_value=True))
ret = self._layout._cleanup_create_share(fake_local_share_path,
self.share['name'])
os.path.exists.assert_called_once_with(fake_local_share_path)
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
self.assertIsNone(ret)
def test_cleanup_create_share_cannot_cleanup_unusable_share(self):
def exec_runner(*ignore_args, **ignore_kw):
raise exception.ProcessExecutionError
expected_exec = ['rm -rf %s' % fake_local_share_path]
fake_utils.fake_execute_set_repliers([(expected_exec[0],
exec_runner)])
self.mock_object(layout_directory.LOG, 'error')
self.mock_object(os.path, 'exists', mock.Mock(return_value=True))
self.assertRaises(exception.GlusterfsException,
self._layout._cleanup_create_share,
fake_local_share_path, self.share['name'])
os.path.exists.assert_called_once_with(fake_local_share_path)
layout_directory.LOG.error.assert_called_once_with(mock.ANY, mock.ANY)
def test_cleanup_create_share_local_share_path_does_not_exist(self):
self.mock_object(os.path, 'exists', mock.Mock(return_value=False))
ret = self._layout._cleanup_create_share(fake_local_share_path,
self.share['name'])
os.path.exists.assert_called_once_with(fake_local_share_path)
self.assertIsNone(ret)
def test_delete_share(self):
self._layout._get_local_share_path = (
mock.Mock(return_value='/mnt/nfs/testvol/fakename'))
self._layout.delete_share(self._context, self.share)
self.assertEqual(['rm -rf /mnt/nfs/testvol/fakename'],
fake_utils.fake_execute_get_log())
def test_cannot_delete_share(self):
self._layout._get_local_share_path = (
mock.Mock(return_value='/mnt/nfs/testvol/fakename'))
def exec_runner(*ignore_args, **ignore_kw):
raise exception.ProcessExecutionError
expected_exec = ['rm -rf %s' % (self._layout._get_local_share_path())]
fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
self.assertRaises(exception.ProcessExecutionError,
self._layout.delete_share, self._context, self.share)
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
def test_delete_share_can_be_called_with_extra_arg_share_server(self):
self._layout._get_local_share_path = mock.Mock()
share_server = None
ret = self._layout.delete_share(self._context, self.share,
share_server)
self.assertIsNone(ret)
self._layout._get_local_share_path.assert_called_once_with(self.share)
def test_ensure_share(self):
self.assertIsNone(self._layout.ensure_share(self._context, self.share))
def test_extend_share(self):
self._layout.extend_share(self.share, 3)
self._layout.gluster_manager.gluster_call.assert_called_once_with(
'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '3GB')
def test_shrink_share(self):
self.mock_object(self._layout, '_get_directory_usage',
mock.Mock(return_value=10.0))
self._layout.shrink_share(self.share, 11)
self._layout.gluster_manager.gluster_call.assert_called_once_with(
'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '11GB')
def test_shrink_share_data_loss(self):
self.mock_object(self._layout, '_get_directory_usage',
mock.Mock(return_value=10.0))
shrink_on_gluster = self.mock_object(self._layout,
'_set_directory_quota')
self.assertRaises(exception.ShareShrinkingPossibleDataLoss,
self._layout.shrink_share, self.share, 9)
shrink_on_gluster.assert_not_called()
def test_set_directory_quota(self):
self._layout._set_directory_quota(self.share, 3)
self._layout.gluster_manager.gluster_call.assert_called_once_with(
'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '3GB')
def test_set_directory_quota_unable_to_set(self):
self.mock_object(self._layout.gluster_manager, 'gluster_call',
mock.Mock(side_effect=exception.GlusterfsException))
self.assertRaises(exception.GlusterfsException,
self._layout._set_directory_quota, self.share, 3)
self._layout.gluster_manager.gluster_call.assert_called_once_with(
'volume', 'quota', 'testvol', 'limit-usage', '/fakename', '3GB')
def test_get_directory_usage(self):
def xml_output(*ignore_args, **ignore_kwargs):
return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cliOutput>
<opRet>0</opRet>
<opErrno>0</opErrno>
<opErrstr/>
<volQuota>
<limit>
<used_space>10737418240</used_space>
</limit>
</volQuota>
</cliOutput>""", ''
self.mock_object(self._layout.gluster_manager, 'gluster_call',
mock.Mock(side_effect=xml_output))
ret = self._layout._get_directory_usage(self.share)
self.assertEqual(10.0, ret)
share_dir = '/' + self.share['name']
self._layout.gluster_manager.gluster_call.assert_called_once_with(
'--xml', 'volume', 'quota', self._layout.gluster_manager.volume,
'list', share_dir)
def test_get_directory_usage_unable_to_get(self):
self.mock_object(self._layout.gluster_manager, 'gluster_call',
mock.Mock(side_effect=exception.GlusterfsException))
self.assertRaises(exception.GlusterfsException,
self._layout._get_directory_usage, self.share)
share_dir = '/' + self.share['name']
self._layout.gluster_manager.gluster_call.assert_called_once_with(
'--xml', 'volume', 'quota', self._layout.gluster_manager.volume,
'list', share_dir)
@ddt.data(
('create_share_from_snapshot', ('context', 'share', 'snapshot'),
{'share_server': None}),
('create_snapshot', ('context', 'snapshot'), {'share_server': None}),
('delete_snapshot', ('context', 'snapshot'), {'share_server': None}),
('manage_existing', ('share', 'driver_options'), {}),
('unmanage', ('share',), {}))
def test_nonimplemented_methods(self, method_invocation):
method, args, kwargs = method_invocation
self.assertRaises(NotImplementedError, getattr(self._layout, method),
*args, **kwargs)