trove/trove/guestagent/volume.py

191 lines
7.5 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
import pexpect
from tempfile import NamedTemporaryFile
from trove.common import cfg
from trove.common import utils
from trove.common.exception import GuestError
from trove.common.exception import ProcessExecutionError
from trove.openstack.common import log as logging
from trove.openstack.common.gettextutils import _
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, mysql_base):
"""Synchronize the data from the mysql directory to the new volume."""
self.mount(TMP_MOUNT_POINT, write_to_fstab=False)
if not mysql_base[-1] == '/':
mysql_base = "%s/" % mysql_base
utils.execute("sudo", "rsync", "--safe-links", "--perms",
"--recursive", "--owner", "--group", "--xattrs",
"--sparse", mysql_base, TMP_MOUNT_POINT)
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)
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):
utils.execute("sudo", "mkdir", "-p", self.mount_point)
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)