1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright 2011 Red Hat, Inc.
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6
# not use this file except in compliance with the License. You may obtain
7
# a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
# License for the specific language governing permissions and limitations
16
"""Support for mounting images with libguestfs"""
20
from nova import exception
21
from nova import utils
22
from nova.virt.disk.mount import api
25
class GuestFSMount(api.Mount):
26
"""libguestfs support for arbitrary images."""
28
device_id_string = 'guest'
39
partition = int(self.partition or 0)
41
self.error = _('unsupported partition: %s') % self.partition
44
args = ('guestmount', '--rw', '-a', self.image)
46
args += ('-i',) # find the OS partition
48
args += ('-m', '/dev/sda%d' % partition)
50
# We don't resort to -i for this case yet,
51
# as some older versions of libguestfs
52
# have problems identifying ttylinux images for example
53
args += ('-m', '/dev/sda')
54
args += (self.mount_dir,)
55
# root access should not required for guestfs (if the user
56
# has permissions to fusermount (by being part of the fuse
57
# group for example)). Also note the image and mount_dir
58
# have appropriate creditials at this point for read/write
59
# mounting by the nova user. However currently there are
60
# subsequent access issues by both the nova and root users
61
# if the nova user mounts the image, as detailed here:
62
# https://bugzilla.redhat.com/show_bug.cgi?id=765814
63
_out, err = utils.trycmd(*args, discard_warnings=True,
66
self.error = _('Failed to mount filesystem: %s') % err
67
# Be defensive and ensure this is unmounted,
68
# as I'm not sure guestmount will never have
69
# mounted when it returns EXIT_FAILURE.
70
# This is required if discard_warnings=False above
71
utils.trycmd('fusermount', '-u', self.mount_dir, run_as_root=True)
74
# More defensiveness as there are edge cases where
75
# guestmount can return success while not mounting
77
if not os.listdir(self.mount_dir):
78
# Assume we've just got the original empty temp dir
79
err = _('unknown guestmount error')
80
self.error = _('Failed to mount filesystem: %s') % err
83
# This is the usual path and means root has
84
# probably mounted fine
93
umount_cmd = ['fusermount', '-u', self.mount_dir]
95
# We make a few attempts to work around other
96
# processes temporarily scanning the mount_dir etc.
97
utils.execute(*umount_cmd, attempts=5, run_as_root=True)
98
except exception.ProcessExecutionError:
99
# If we still can't umount, then do a lazy umount
100
# (in the background), so that mounts might eventually
101
# be cleaned up. Note we'll wait 10s below for the umount to
102
# complete, after which we'll raise an exception.
103
umount_cmd.insert(1, '-z')
104
utils.execute(*umount_cmd, run_as_root=True)
106
# Unfortunately FUSE has an issue where it doesn't wait
107
# for processes associated with the mount to terminate.
108
# Therefore we do this manually here. Note later versions
109
# of guestmount have the --pid-file option to help with this.
110
# Here we check every .2 seconds whether guestmount is finished
111
# but do this for at most 10 seconds.
112
wait_cmd = 'until ! ps -C guestmount -o args= | grep -qF "%s"; '
113
wait_cmd += 'do sleep .2; done'
114
wait_cmd %= self.mount_dir
116
utils.execute('timeout', '10s', 'sh', '-c', wait_cmd)
118
except exception.ProcessExecutionError:
119
msg = _("Failed to umount image at %s, guestmount was "
120
"still running after 10s") % (self.mount_dir)
121
raise exception.NovaException(msg)