diff options
author | Minmin Ren <renmm6@chinaunicom.cn> | 2019-05-20 08:47:17 +0000 |
---|---|---|
committer | renminmin <rmm0811@gmail.com> | 2020-03-24 02:36:58 +0000 |
commit | 2547e4ef1869c668242d50ec8414a921f5d8b072 (patch) | |
tree | 972a8c3ba0cbf25b8bcec12a1cec4dd2e9647de8 | |
parent | 9e588c04aa44321b704c22aec27ac964e44235d7 (diff) | |
download | trove-2547e4ef1869c668242d50ec8414a921f5d8b072.tar.gz |
Support XFS disk format
Support XFS disk format
Story: #2005741
Task: #33405
Change-Id: Idc454000ce7ad95d2c461c87867704eba069bdf4
-rw-r--r-- | trove/common/cfg.py | 1 | ||||
-rw-r--r-- | trove/guestagent/volume.py | 169 | ||||
-rw-r--r-- | trove/tests/unittests/guestagent/test_volume.py | 75 |
3 files changed, 210 insertions, 35 deletions
diff --git a/trove/common/cfg.py b/trove/common/cfg.py index f2e3eae3..a97623f6 100644 --- a/trove/common/cfg.py +++ b/trove/common/cfg.py @@ -199,6 +199,7 @@ common_opts = [ cfg.IntOpt('num_tries', default=3, help='Number of times to check if a volume exists.'), cfg.StrOpt('volume_fstype', default='ext3', + choices=['ext3', 'ext4', 'xfs'], help='File system type used to format a volume.'), cfg.StrOpt('cinder_volume_type', default=None, help='Volume type to use when provisioning a Cinder volume.'), diff --git a/trove/guestagent/volume.py b/trove/guestagent/volume.py index 20706b78..47b75726 100644 --- a/trove/guestagent/volume.py +++ b/trove/guestagent/volume.py @@ -13,8 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. +import abc import os import shlex +import six from tempfile import NamedTemporaryFile import traceback @@ -49,10 +51,139 @@ def log_and_raise(log_fmt, exc_fmt, fmt_content=None): raise exception.GuestError(original_message=raise_msg) +@six.add_metaclass(abc.ABCMeta) +class FSBase(object): + + def __init__(self, fstype, format_options): + self.fstype = fstype + self.format_options = format_options + + @abc.abstractmethod + def format(self, device_path, timeout): + """ + Format device + """ + + @abc.abstractmethod + def check_format(self, device_path): + """ + Check if device is formatted + """ + + @abc.abstractmethod + def resize(self, device_path): + """ + Resize the filesystem on device + """ + + +class FSExt(FSBase): + + def __init__(self, fstype, format_options): + super(FSExt, self).__init__(fstype, format_options) + + def format(self, device_path, timeout): + format_options = shlex.split(self.format_options) + format_options.append(device_path) + try: + utils.execute_with_timeout( + "mkfs", "--type", self.fstype, *format_options, + timeout=timeout, run_as_root=True, root_helper="sudo") + except exception.ProcessExecutionError: + log_fmt = "Could not format '%s'." + exc_fmt = _("Could not format '%s'.") + log_and_raise(log_fmt, exc_fmt, device_path) + + def check_format(self, device_path): + try: + stdout, stderr = utils.execute( + "dumpe2fs", device_path, run_as_root=True, root_helper="sudo") + if 'has_journal' not in stdout: + msg = _("Volume '%s' does not appear to be formatted.") % ( + device_path) + raise exception.GuestError(original_message=msg) + except exception.ProcessExecutionError as pe: + if 'Wrong magic number' in pe.stderr: + volume_fstype = self.fstype + log_fmt = "'Device '%(dev)s' did not seem to be '%(type)s'." + exc_fmt = _("'Device '%(dev)s' did not seem to be '%(type)s'.") + log_and_raise(log_fmt, exc_fmt, {'dev': device_path, + 'type': volume_fstype}) + log_fmt = "Volume '%s' was not formatted." + exc_fmt = _("Volume '%s' was not formatted.") + log_and_raise(log_fmt, exc_fmt, device_path) + + def resize(self, device_path): + utils.execute("e2fsck", "-f", "-p", device_path, + run_as_root=True, root_helper="sudo") + utils.execute("resize2fs", device_path, + run_as_root=True, root_helper="sudo") + + +class FSExt3(FSExt): + + def __init__(self, format_options): + super(FSExt3, self).__init__('ext3', format_options) + + +class FSExt4(FSExt): + + def __init__(self, format_options): + super(FSExt4, self).__init__('ext4', format_options) + + +class FSXFS(FSBase): + + def __init__(self, format_options): + super(FSXFS, self).__init__('xfs', format_options) + + def format(self, device_path, timeout): + format_options = shlex.split(self.format_options) + format_options.append(device_path) + try: + utils.execute_with_timeout( + "mkfs.xfs", *format_options, + timeout=timeout, run_as_root=True, root_helper="sudo") + except exception.ProcessExecutionError: + log_fmt = "Could not format '%s'." + exc_fmt = _("Could not format '%s'.") + log_and_raise(log_fmt, exc_fmt, device_path) + + def check_format(self, device_path): + stdout, stderr = utils.execute( + "xfs_admin", "-l", device_path, + run_as_root=True, root_helper="sudo") + if 'not a valid XFS filesystem' in stdout: + msg = _("Volume '%s' does not appear to be formatted.") % ( + device_path) + raise exception.GuestError(original_message=msg) + + def resize(self, device_path): + utils.execute("xfs_repair", device_path, + run_as_root=True, root_helper="sudo") + utils.execute("mount", device_path, + run_as_root=True, root_helper="sudo") + utils.execute("xfs_growfs", device_path, + run_as_root=True, root_helper="sudo") + utils.execute("umount", device_path, + run_as_root=True, root_helper="sudo") + + +def VolumeFs(fstype, format_options=''): + supported_fs = { + 'xfs': FSXFS, + 'ext3': FSExt3, + 'ext4': FSExt4 + } + return supported_fs[fstype](format_options) + + class VolumeDevice(object): def __init__(self, device_path): self.device_path = device_path + self.volume_fs = VolumeFs(CONF.volume_fstype, + CONF.format_options) def migrate_data(self, source_dir, target_subdir=None): """Synchronize the data from the source directory to the new @@ -97,41 +228,12 @@ class VolumeDevice(object): def _check_format(self): """Checks that a volume is formatted.""" LOG.debug("Checking whether '%s' is formatted.", self.device_path) - try: - stdout, stderr = utils.execute( - "dumpe2fs", self.device_path, - run_as_root=True, root_helper="sudo") - if 'has_journal' not in stdout: - msg = _("Volume '%s' does not appear to be formatted.") % ( - self.device_path) - raise exception.GuestError(original_message=msg) - except exception.ProcessExecutionError as pe: - if 'Wrong magic number' in pe.stderr: - volume_fstype = CONF.volume_fstype - log_fmt = "'Device '%(dev)s' did not seem to be '%(type)s'." - exc_fmt = _("'Device '%(dev)s' did not seem to be '%(type)s'.") - log_and_raise(log_fmt, exc_fmt, {'dev': self.device_path, - 'type': volume_fstype}) - log_fmt = "Volume '%s' was not formatted." - exc_fmt = _("Volume '%s' was not formatted.") - log_and_raise(log_fmt, exc_fmt, self.device_path) + self.volume_fs.check_format(self.device_path) def _format(self): """Calls mkfs to format the device at device_path.""" - volume_fstype = CONF.volume_fstype - format_options = shlex.split(CONF.format_options) - format_options.append(self.device_path) - volume_format_timeout = CONF.volume_format_timeout LOG.debug("Formatting '%s'.", self.device_path) - try: - utils.execute_with_timeout( - "mkfs", "--type", volume_fstype, *format_options, - run_as_root=True, root_helper="sudo", - timeout=volume_format_timeout) - except exception.ProcessExecutionError: - log_fmt = "Could not format '%s'." - exc_fmt = _("Could not format '%s'.") - log_and_raise(log_fmt, exc_fmt, self.device_path) + self.volume_fs.format(self.device_path, CONF.volume_format_timeout) def format(self): """Formats the device at device_path and checks the filesystem.""" @@ -172,10 +274,7 @@ class VolumeDevice(object): LOG.debug("Unmounting '%s' before resizing.", mount_point) self.unmount(mount_point) try: - utils.execute("e2fsck", "-f", "-p", self.device_path, - run_as_root=True, root_helper="sudo") - utils.execute("resize2fs", self.device_path, - run_as_root=True, root_helper="sudo") + self.volume_fs.resize(self.device_path) except exception.ProcessExecutionError: log_fmt = "Error resizing the filesystem with device '%s'." exc_fmt = _("Error resizing the filesystem with device '%s'.") diff --git a/trove/tests/unittests/guestagent/test_volume.py b/trove/tests/unittests/guestagent/test_volume.py index e8f47a93..5a7e8cba 100644 --- a/trove/tests/unittests/guestagent/test_volume.py +++ b/trove/tests/unittests/guestagent/test_volume.py @@ -14,6 +14,7 @@ from mock import ANY, call, DEFAULT, patch, mock_open +from trove.common import cfg from trove.common import exception from trove.common import utils from trove.guestagent.common import operating_system @@ -21,10 +22,15 @@ from trove.guestagent import volume from trove.tests.unittests import trove_testtools +CONF = cfg.CONF + + class VolumeDeviceTest(trove_testtools.TestCase): def setUp(self): super(VolumeDeviceTest, self).setUp() + self.patch_conf_property('volume_fstype', 'ext3') + self.patch_conf_property('format_options', '-m 5') self.volumeDevice = volume.VolumeDevice('/dev/vdb') self.exec_patcher = patch.object( @@ -199,10 +205,79 @@ class VolumeDeviceTest(trove_testtools.TestCase): self.mock_exec.assert_has_calls(calls) +class VolumeDeviceTestXFS(trove_testtools.TestCase): + + def setUp(self): + super(VolumeDeviceTestXFS, self).setUp() + self.patch_conf_property('volume_fstype', 'xfs') + self.patch_conf_property('format_options', '') + self.volumeDevice = volume.VolumeDevice('/dev/vdb') + + self.exec_patcher = patch.object( + utils, 'execute', return_value=('', '')) + self.mock_exec = self.exec_patcher.start() + self.addCleanup(self.exec_patcher.stop) + self.ismount_patcher = patch.object(operating_system, 'is_mount') + self.mock_ismount = self.ismount_patcher.start() + self.addCleanup(self.ismount_patcher.stop) + + def tearDown(self): + super(VolumeDeviceTestXFS, self).tearDown() + self.volumeDevice = None + + def test__check_format(self): + self.volumeDevice._check_format() + self.assertEqual(1, self.mock_exec.call_count) + calls = [ + call('xfs_admin', '-l', '/dev/vdb', + root_helper='sudo', run_as_root=True) + ] + self.mock_exec.assert_has_calls(calls) + + @patch('trove.guestagent.volume.LOG') + @patch.object(utils, 'execute', + return_value=('not a valid XFS filesystem', '')) + def test__check_format_2(self, mock_logging, mock_exec): + self.assertRaises(exception.GuestError, + self.volumeDevice._check_format) + + def test__format(self): + self.volumeDevice._format() + self.assertEqual(1, self.mock_exec.call_count) + calls = [ + call('mkfs.xfs', '/dev/vdb', + root_helper='sudo', run_as_root=True) + ] + self.mock_exec.assert_has_calls(calls) + + def test_resize_fs(self): + with patch.object(operating_system, 'is_mount', return_value=True): + mount_point = '/mnt/volume' + self.volumeDevice.resize_fs(mount_point) + self.assertEqual(6, self.mock_exec.call_count) + calls = [ + call('blockdev', '--getsize64', '/dev/vdb', attempts=3, + root_helper='sudo', run_as_root=True), + call("umount", mount_point, run_as_root=True, + root_helper='sudo'), + call('xfs_repair', '/dev/vdb', root_helper='sudo', + run_as_root=True), + call('mount', '/dev/vdb', root_helper='sudo', + run_as_root=True), + call('xfs_growfs', '/dev/vdb', root_helper='sudo', + run_as_root=True), + call('umount', '/dev/vdb', root_helper='sudo', + run_as_root=True) + ] + self.mock_exec.assert_has_calls(calls) + + class VolumeMountPointTest(trove_testtools.TestCase): def setUp(self): super(VolumeMountPointTest, self).setUp() + self.patch_conf_property('volume_fstype', 'ext3') + self.patch_conf_property('format_options', '-m 5') self.volumeMountPoint = volume.VolumeMountPoint('/mnt/device', '/dev/vdb') self.exec_patcher = patch.object(utils, 'execute', |