summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMinmin Ren <renmm6@chinaunicom.cn>2019-05-20 08:47:17 +0000
committerrenminmin <rmm0811@gmail.com>2020-03-24 02:36:58 +0000
commit2547e4ef1869c668242d50ec8414a921f5d8b072 (patch)
tree972a8c3ba0cbf25b8bcec12a1cec4dd2e9647de8
parent9e588c04aa44321b704c22aec27ac964e44235d7 (diff)
downloadtrove-2547e4ef1869c668242d50ec8414a921f5d8b072.tar.gz
Support XFS disk format
Support XFS disk format Story: #2005741 Task: #33405 Change-Id: Idc454000ce7ad95d2c461c87867704eba069bdf4
-rw-r--r--trove/common/cfg.py1
-rw-r--r--trove/guestagent/volume.py169
-rw-r--r--trove/tests/unittests/guestagent/test_volume.py75
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',