trove/trove/guestagent/volume.py

212 lines
8.4 KiB
Python

# Copyright (c) 2011 OpenStack Foundation
# 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 tempfile import NamedTemporaryFile
from oslo_log import log as logging
import pexpect
from trove.common import cfg
from trove.common.exception import GuestError
from trove.common.exception import ProcessExecutionError
from trove.common.i18n import _
from trove.common import utils
from trove.guestagent.common import operating_system
TMP_MOUNT_POINT = "/mnt/volume"
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class VolumeDevice(object):
def __init__(self, device_path):
self.device_path = device_path
def migrate_data(self, source_dir, target_subdir=None):
"""Synchronize the data from the source directory to the new
volume; optionally to a new sub-directory on the new volume.
"""
self.mount(TMP_MOUNT_POINT, write_to_fstab=False)
if not source_dir[-1] == '/':
source_dir = "%s/" % source_dir
target_dir = TMP_MOUNT_POINT
if target_subdir:
target_dir = target_dir + "/" + target_subdir
utils.execute("sudo", "rsync", "--safe-links", "--perms",
"--recursive", "--owner", "--group", "--xattrs",
"--sparse", source_dir, target_dir)
self.unmount(TMP_MOUNT_POINT)
def _check_device_exists(self):
"""Check that the device path exists.
Verify that the device path has actually been created and can report
it's size, only then can it be available for formatting, retry
num_tries to account for the time lag.
"""
try:
num_tries = CONF.num_tries
LOG.debug("Checking if %s exists." % self.device_path)
utils.execute('sudo', 'blockdev', '--getsize64', self.device_path,
attempts=num_tries)
except ProcessExecutionError:
LOG.exception(_("Error getting device status"))
raise GuestError(_("InvalidDevicePath(path=%s)") %
self.device_path)
def _check_format(self):
"""Checks that an unmounted volume is formatted."""
cmd = "sudo dumpe2fs %s" % self.device_path
LOG.debug("Checking whether %s is formated: %s." %
(self.device_path, cmd))
child = pexpect.spawn(cmd)
try:
i = child.expect(['has_journal', 'Wrong magic number'])
if i == 0:
return
volume_fstype = CONF.volume_fstype
raise IOError(
_('Device path at {0} did not seem to be {1}.').format(
self.device_path, volume_fstype))
except pexpect.EOF:
raise IOError(_("Volume was not formatted."))
child.expect(pexpect.EOF)
def _format(self):
"""Calls mkfs to format the device at device_path."""
volume_fstype = CONF.volume_fstype
format_options = CONF.format_options
cmd = "sudo mkfs -t %s %s %s" % (volume_fstype,
format_options, self.device_path)
volume_format_timeout = CONF.volume_format_timeout
LOG.debug("Formatting %s. Executing: %s." %
(self.device_path, cmd))
child = pexpect.spawn(cmd, timeout=volume_format_timeout)
# child.expect("(y,n)")
# child.sendline('y')
child.expect(pexpect.EOF)
def format(self):
"""Formats the device at device_path and checks the filesystem."""
self._check_device_exists()
self._format()
self._check_format()
def mount(self, mount_point, write_to_fstab=True):
"""Mounts, and writes to fstab."""
LOG.debug("Will mount %s at %s." % (self.device_path, mount_point))
mount_point = VolumeMountPoint(self.device_path, mount_point)
mount_point.mount()
if write_to_fstab:
mount_point.write_to_fstab()
def resize_fs(self, mount_point):
"""Resize the filesystem on the specified device."""
self._check_device_exists()
try:
# check if the device is mounted at mount_point before e2fsck
if not os.path.ismount(mount_point):
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")
except ProcessExecutionError:
LOG.exception(_("Error resizing file system."))
raise GuestError(_("Error resizing the filesystem: %s") %
self.device_path)
def unmount(self, mount_point):
if os.path.exists(mount_point):
cmd = "sudo umount %s" % mount_point
child = pexpect.spawn(cmd)
child.expect(pexpect.EOF)
def unmount_device(self, device_path):
# unmount if device is already mounted
mount_points = self.mount_points(device_path)
for mnt in mount_points:
LOG.info(_("Device %(device)s is already mounted in "
"%(mount_point)s. Unmounting now.") %
{'device': device_path, 'mount_point': mnt})
self.unmount(mnt)
def mount_points(self, device_path):
"""Returns a list of mount points on the specified device."""
try:
cmd = "grep %s /etc/mtab | awk '{print $2}'" % device_path
stdout, stderr = utils.execute(cmd, shell=True)
return stdout.strip().split('\n')
except ProcessExecutionError:
LOG.exception(_("Error retrieving mount points"))
raise GuestError(_("Could not obtain a list of mount points for "
"device: %s") % device_path)
def set_readahead_size(self, readahead_size,
execute_function=utils.execute):
"""Set the readahead size of disk."""
self._check_device_exists()
try:
execute_function("sudo", "blockdev", "--setra",
readahead_size, self.device_path)
except ProcessExecutionError:
LOG.exception(_("Error setting readhead size to %(size)s "
"for device %(device)s.") %
{'size': readahead_size, 'device': self.device_path})
raise GuestError(_("Error setting readhead size: %s.") %
self.device_path)
class VolumeMountPoint(object):
def __init__(self, device_path, mount_point):
self.device_path = device_path
self.mount_point = mount_point
self.volume_fstype = CONF.volume_fstype
self.mount_options = CONF.mount_options
def mount(self):
if not os.path.exists(self.mount_point):
operating_system.create_directory(self.mount_point, as_root=True)
LOG.debug("Mounting volume. Device path:{0}, mount_point:{1}, "
"volume_type:{2}, mount options:{3}".format(
self.device_path, self.mount_point, self.volume_fstype,
self.mount_options))
cmd = ("sudo mount -t %s -o %s %s %s" %
(self.volume_fstype, self.mount_options, self.device_path,
self.mount_point))
child = pexpect.spawn(cmd)
child.expect(pexpect.EOF)
def write_to_fstab(self):
fstab_line = ("%s\t%s\t%s\t%s\t0\t0" %
(self.device_path, self.mount_point, self.volume_fstype,
self.mount_options))
LOG.debug("Writing new line to fstab:%s" % fstab_line)
with open('/etc/fstab', "r") as fstab:
fstab_content = fstab.read()
with NamedTemporaryFile(delete=False) as tempfstab:
tempfstab.write(fstab_content + fstab_line)
utils.execute("sudo", "install", "-o", "root", "-g", "root", "-m",
"644", tempfstab.name, "/etc/fstab")
os.remove(tempfstab.name)