Support XFS disk format
Support XFS disk format Story: #2005741 Task: #33405 Change-Id: Idc454000ce7ad95d2c461c87867704eba069bdf4
This commit is contained in:
parent
9e588c04aa
commit
2547e4ef18
|
@ -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.'),
|
||||
|
|
|
@ -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'.")
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in New Issue