Support XFS disk format

Support XFS disk format

Story: #2005741
Task: #33405

Change-Id: Idc454000ce7ad95d2c461c87867704eba069bdf4
This commit is contained in:
Minmin Ren 2019-05-20 08:47:17 +00:00 committed by renminmin
parent 9e588c04aa
commit 2547e4ef18
3 changed files with 210 additions and 35 deletions

View File

@ -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.'),

View File

@ -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'.")

View File

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