From e85156e20b664ccf9e6d0ac7e1006a80555f8922 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Tue, 13 Nov 2012 10:46:37 +0000 Subject: Move all mount classes into a subdirectory The disk API will be growing some new classes in future commits. To avoid ambiguity in module names, move all the mount classes into a subdirectory nova/virt/disk/mount/ blueprint: virt-disk-api-refactoring Change-Id: I03898b4060bd0c488713c9d9f19caebdcd39113c Signed-off-by: Daniel P. Berrange --- nova/virt/disk/api.py | 8 +- nova/virt/disk/guestfs.py | 121 ----------------------------- nova/virt/disk/loop.py | 42 ---------- nova/virt/disk/mount.py | 162 --------------------------------------- nova/virt/disk/mount/__init__.py | 19 +++++ nova/virt/disk/mount/api.py | 162 +++++++++++++++++++++++++++++++++++++++ nova/virt/disk/mount/guestfs.py | 121 +++++++++++++++++++++++++++++ nova/virt/disk/mount/loop.py | 42 ++++++++++ nova/virt/disk/mount/nbd.py | 112 +++++++++++++++++++++++++++ nova/virt/disk/nbd.py | 112 --------------------------- 10 files changed, 460 insertions(+), 441 deletions(-) delete mode 100644 nova/virt/disk/guestfs.py delete mode 100644 nova/virt/disk/loop.py delete mode 100644 nova/virt/disk/mount.py create mode 100644 nova/virt/disk/mount/__init__.py create mode 100644 nova/virt/disk/mount/api.py create mode 100644 nova/virt/disk/mount/guestfs.py create mode 100644 nova/virt/disk/mount/loop.py create mode 100644 nova/virt/disk/mount/nbd.py delete mode 100644 nova/virt/disk/nbd.py diff --git a/nova/virt/disk/api.py b/nova/virt/disk/api.py index e71faad70..1c8f15628 100644 --- a/nova/virt/disk/api.py +++ b/nova/virt/disk/api.py @@ -39,9 +39,9 @@ from nova.openstack.common import cfg from nova.openstack.common import jsonutils from nova.openstack.common import log as logging from nova import utils -from nova.virt.disk import guestfs -from nova.virt.disk import loop -from nova.virt.disk import nbd +from nova.virt.disk.mount import guestfs +from nova.virt.disk.mount import loop +from nova.virt.disk.mount import nbd from nova.virt import images @@ -229,7 +229,7 @@ class _DiskImage(object): @staticmethod def _handler_class(mode=None, device=None): """Look up the appropriate class to use based on MODE or DEVICE.""" - for cls in (loop.Mount, nbd.Mount, guestfs.Mount): + for cls in (loop.LoopMount, nbd.NbdMount, guestfs.GuestFSMount): if mode and cls.mode == mode: return cls elif device and cls.device_id_string in device: diff --git a/nova/virt/disk/guestfs.py b/nova/virt/disk/guestfs.py deleted file mode 100644 index 21e33b1d7..000000000 --- a/nova/virt/disk/guestfs.py +++ /dev/null @@ -1,121 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Red Hat, Inc. -# -# 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. -"""Support for mounting images with libguestfs""" - -import os - -from nova import exception -from nova import utils -from nova.virt.disk import mount - - -class Mount(mount.Mount): - """libguestfs support for arbitrary images.""" - mode = 'guestfs' - device_id_string = 'guest' - - def map_dev(self): - self.mapped = True - return True - - def unmap_dev(self): - self.mapped = False - - def mnt_dev(self): - try: - partition = int(self.partition or 0) - except ValueError: - self.error = _('unsupported partition: %s') % self.partition - return False - - args = ('guestmount', '--rw', '-a', self.image) - if partition == -1: - args += ('-i',) # find the OS partition - elif partition: - args += ('-m', '/dev/sda%d' % partition) - else: - # We don't resort to -i for this case yet, - # as some older versions of libguestfs - # have problems identifying ttylinux images for example - args += ('-m', '/dev/sda') - args += (self.mount_dir,) - # root access should not required for guestfs (if the user - # has permissions to fusermount (by being part of the fuse - # group for example)). Also note the image and mount_dir - # have appropriate creditials at this point for read/write - # mounting by the nova user. However currently there are - # subsequent access issues by both the nova and root users - # if the nova user mounts the image, as detailed here: - # https://bugzilla.redhat.com/show_bug.cgi?id=765814 - _out, err = utils.trycmd(*args, discard_warnings=True, - run_as_root=True) - if err: - self.error = _('Failed to mount filesystem: %s') % err - # Be defensive and ensure this is unmounted, - # as I'm not sure guestmount will never have - # mounted when it returns EXIT_FAILURE. - # This is required if discard_warnings=False above - utils.trycmd('fusermount', '-u', self.mount_dir, run_as_root=True) - return False - - # More defensiveness as there are edge cases where - # guestmount can return success while not mounting - try: - if not os.listdir(self.mount_dir): - # Assume we've just got the original empty temp dir - err = _('unknown guestmount error') - self.error = _('Failed to mount filesystem: %s') % err - return False - except OSError: - # This is the usual path and means root has - # probably mounted fine - pass - - self.mounted = True - return True - - def unmnt_dev(self): - if not self.mounted: - return - umount_cmd = ['fusermount', '-u', self.mount_dir] - try: - # We make a few attempts to work around other - # processes temporarily scanning the mount_dir etc. - utils.execute(*umount_cmd, attempts=5, run_as_root=True) - except exception.ProcessExecutionError: - # If we still can't umount, then do a lazy umount - # (in the background), so that mounts might eventually - # be cleaned up. Note we'll wait 10s below for the umount to - # complete, after which we'll raise an exception. - umount_cmd.insert(1, '-z') - utils.execute(*umount_cmd, run_as_root=True) - - # Unfortunately FUSE has an issue where it doesn't wait - # for processes associated with the mount to terminate. - # Therefore we do this manually here. Note later versions - # of guestmount have the --pid-file option to help with this. - # Here we check every .2 seconds whether guestmount is finished - # but do this for at most 10 seconds. - wait_cmd = 'until ! ps -C guestmount -o args= | grep -qF "%s"; ' - wait_cmd += 'do sleep .2; done' - wait_cmd %= self.mount_dir - try: - utils.execute('timeout', '10s', 'sh', '-c', wait_cmd) - self.mounted = False - except exception.ProcessExecutionError: - msg = _("Failed to umount image at %s, guestmount was " - "still running after 10s") % (self.mount_dir) - raise exception.NovaException(msg) diff --git a/nova/virt/disk/loop.py b/nova/virt/disk/loop.py deleted file mode 100644 index 3dfdc32d3..000000000 --- a/nova/virt/disk/loop.py +++ /dev/null @@ -1,42 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Red Hat, Inc. -# -# 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. -"""Support for mounting images with the loop device""" - -from nova import utils -from nova.virt.disk import mount - - -class Mount(mount.Mount): - """loop back support for raw images.""" - mode = 'loop' - device_id_string = mode - - def get_dev(self): - out, err = utils.trycmd('losetup', '--find', '--show', self.image, - run_as_root=True) - if err: - self.error = _('Could not attach image to loopback: %s') % err - return False - - self.device = out.strip() - self.linked = True - return True - - def unget_dev(self): - if not self.linked: - return - utils.execute('losetup', '--detach', self.device, run_as_root=True) - self.linked = False diff --git a/nova/virt/disk/mount.py b/nova/virt/disk/mount.py deleted file mode 100644 index e683658d2..000000000 --- a/nova/virt/disk/mount.py +++ /dev/null @@ -1,162 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Red Hat, Inc. -# -# 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. -"""Support for mounting virtual image files""" - -import os - -from nova.openstack.common import log as logging -from nova import utils - -LOG = logging.getLogger(__name__) - - -class Mount(object): - """Standard mounting operations, that can be overridden by subclasses. - - The basic device operations provided are get, map and mount, - to be called in that order. - """ - - mode = device_id_string = None # to be overridden in subclasses - - def __init__(self, image, mount_dir, partition=None, device=None): - - # Input - self.image = image - self.partition = partition - self.mount_dir = mount_dir - - # Output - self.error = "" - - # Internal - self.linked = self.mapped = self.mounted = self.automapped = False - self.device = self.mapped_device = device - - # Reset to mounted dir if possible - self.reset_dev() - - def reset_dev(self): - """Reset device paths to allow unmounting.""" - if not self.device: - return - - self.linked = self.mapped = self.mounted = True - - device = self.device - if os.path.isabs(device) and os.path.exists(device): - if device.startswith('/dev/mapper/'): - device = os.path.basename(device) - device, self.partition = device.rsplit('p', 1) - self.device = os.path.join('/dev', device) - - def get_dev(self): - """Make the image available as a block device in the file system.""" - self.device = None - self.linked = True - return True - - def unget_dev(self): - """Release the block device from the file system namespace.""" - self.linked = False - - def map_dev(self): - """Map partitions of the device to the file system namespace.""" - assert(os.path.exists(self.device)) - automapped_path = '/dev/%sp%s' % (os.path.basename(self.device), - self.partition) - - if self.partition == -1: - self.error = _('partition search unsupported with %s') % self.mode - elif self.partition and not os.path.exists(automapped_path): - map_path = '/dev/mapper/%sp%s' % (os.path.basename(self.device), - self.partition) - assert(not os.path.exists(map_path)) - - # Note kpartx can output warnings to stderr and succeed - # Also it can output failures to stderr and "succeed" - # So we just go on the existence of the mapped device - _out, err = utils.trycmd('kpartx', '-a', self.device, - run_as_root=True, discard_warnings=True) - - # Note kpartx does nothing when presented with a raw image, - # so given we only use it when we expect a partitioned image, fail - if not os.path.exists(map_path): - if not err: - err = _('partition %s not found') % self.partition - self.error = _('Failed to map partitions: %s') % err - else: - self.mapped_device = map_path - self.mapped = True - elif self.partition and os.path.exists(automapped_path): - # Note auto mapping can be enabled with the 'max_part' option - # to the nbd or loop kernel modules. Beware of possible races - # in the partition scanning for _loop_ devices though - # (details in bug 1024586), which are currently uncatered for. - self.mapped_device = automapped_path - self.mapped = True - self.automapped = True - else: - self.mapped_device = self.device - self.mapped = True - - return self.mapped - - def unmap_dev(self): - """Remove partitions of the device from the file system namespace.""" - if not self.mapped: - return - if self.partition and not self.automapped: - utils.execute('kpartx', '-d', self.device, run_as_root=True) - self.mapped = False - self.automapped = False - - def mnt_dev(self): - """Mount the device into the file system.""" - _out, err = utils.trycmd('mount', self.mapped_device, self.mount_dir, - run_as_root=True) - if err: - self.error = _('Failed to mount filesystem: %s') % err - return False - - self.mounted = True - return True - - def unmnt_dev(self): - """Unmount the device from the file system.""" - if not self.mounted: - return - utils.execute('umount', self.mapped_device, run_as_root=True) - self.mounted = False - - def do_mount(self): - """Call the get, map and mnt operations.""" - status = False - try: - status = self.get_dev() and self.map_dev() and self.mnt_dev() - finally: - if not status: - self.do_umount() - return status - - def do_umount(self): - """Call the unmnt, unmap and unget operations.""" - if self.mounted: - self.unmnt_dev() - if self.mapped: - self.unmap_dev() - if self.linked: - self.unget_dev() diff --git a/nova/virt/disk/mount/__init__.py b/nova/virt/disk/mount/__init__.py new file mode 100644 index 000000000..5c18da32d --- /dev/null +++ b/nova/virt/disk/mount/__init__.py @@ -0,0 +1,19 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Red Hat, Inc. +# +# 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. +""" +Support for mounting disk images on the host filesystem + +""" diff --git a/nova/virt/disk/mount/api.py b/nova/virt/disk/mount/api.py new file mode 100644 index 000000000..e683658d2 --- /dev/null +++ b/nova/virt/disk/mount/api.py @@ -0,0 +1,162 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Red Hat, Inc. +# +# 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. +"""Support for mounting virtual image files""" + +import os + +from nova.openstack.common import log as logging +from nova import utils + +LOG = logging.getLogger(__name__) + + +class Mount(object): + """Standard mounting operations, that can be overridden by subclasses. + + The basic device operations provided are get, map and mount, + to be called in that order. + """ + + mode = device_id_string = None # to be overridden in subclasses + + def __init__(self, image, mount_dir, partition=None, device=None): + + # Input + self.image = image + self.partition = partition + self.mount_dir = mount_dir + + # Output + self.error = "" + + # Internal + self.linked = self.mapped = self.mounted = self.automapped = False + self.device = self.mapped_device = device + + # Reset to mounted dir if possible + self.reset_dev() + + def reset_dev(self): + """Reset device paths to allow unmounting.""" + if not self.device: + return + + self.linked = self.mapped = self.mounted = True + + device = self.device + if os.path.isabs(device) and os.path.exists(device): + if device.startswith('/dev/mapper/'): + device = os.path.basename(device) + device, self.partition = device.rsplit('p', 1) + self.device = os.path.join('/dev', device) + + def get_dev(self): + """Make the image available as a block device in the file system.""" + self.device = None + self.linked = True + return True + + def unget_dev(self): + """Release the block device from the file system namespace.""" + self.linked = False + + def map_dev(self): + """Map partitions of the device to the file system namespace.""" + assert(os.path.exists(self.device)) + automapped_path = '/dev/%sp%s' % (os.path.basename(self.device), + self.partition) + + if self.partition == -1: + self.error = _('partition search unsupported with %s') % self.mode + elif self.partition and not os.path.exists(automapped_path): + map_path = '/dev/mapper/%sp%s' % (os.path.basename(self.device), + self.partition) + assert(not os.path.exists(map_path)) + + # Note kpartx can output warnings to stderr and succeed + # Also it can output failures to stderr and "succeed" + # So we just go on the existence of the mapped device + _out, err = utils.trycmd('kpartx', '-a', self.device, + run_as_root=True, discard_warnings=True) + + # Note kpartx does nothing when presented with a raw image, + # so given we only use it when we expect a partitioned image, fail + if not os.path.exists(map_path): + if not err: + err = _('partition %s not found') % self.partition + self.error = _('Failed to map partitions: %s') % err + else: + self.mapped_device = map_path + self.mapped = True + elif self.partition and os.path.exists(automapped_path): + # Note auto mapping can be enabled with the 'max_part' option + # to the nbd or loop kernel modules. Beware of possible races + # in the partition scanning for _loop_ devices though + # (details in bug 1024586), which are currently uncatered for. + self.mapped_device = automapped_path + self.mapped = True + self.automapped = True + else: + self.mapped_device = self.device + self.mapped = True + + return self.mapped + + def unmap_dev(self): + """Remove partitions of the device from the file system namespace.""" + if not self.mapped: + return + if self.partition and not self.automapped: + utils.execute('kpartx', '-d', self.device, run_as_root=True) + self.mapped = False + self.automapped = False + + def mnt_dev(self): + """Mount the device into the file system.""" + _out, err = utils.trycmd('mount', self.mapped_device, self.mount_dir, + run_as_root=True) + if err: + self.error = _('Failed to mount filesystem: %s') % err + return False + + self.mounted = True + return True + + def unmnt_dev(self): + """Unmount the device from the file system.""" + if not self.mounted: + return + utils.execute('umount', self.mapped_device, run_as_root=True) + self.mounted = False + + def do_mount(self): + """Call the get, map and mnt operations.""" + status = False + try: + status = self.get_dev() and self.map_dev() and self.mnt_dev() + finally: + if not status: + self.do_umount() + return status + + def do_umount(self): + """Call the unmnt, unmap and unget operations.""" + if self.mounted: + self.unmnt_dev() + if self.mapped: + self.unmap_dev() + if self.linked: + self.unget_dev() diff --git a/nova/virt/disk/mount/guestfs.py b/nova/virt/disk/mount/guestfs.py new file mode 100644 index 000000000..2e29b9b43 --- /dev/null +++ b/nova/virt/disk/mount/guestfs.py @@ -0,0 +1,121 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Red Hat, Inc. +# +# 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. +"""Support for mounting images with libguestfs""" + +import os + +from nova import exception +from nova import utils +from nova.virt.disk.mount import api + + +class GuestFSMount(api.Mount): + """libguestfs support for arbitrary images.""" + mode = 'guestfs' + device_id_string = 'guest' + + def map_dev(self): + self.mapped = True + return True + + def unmap_dev(self): + self.mapped = False + + def mnt_dev(self): + try: + partition = int(self.partition or 0) + except ValueError: + self.error = _('unsupported partition: %s') % self.partition + return False + + args = ('guestmount', '--rw', '-a', self.image) + if partition == -1: + args += ('-i',) # find the OS partition + elif partition: + args += ('-m', '/dev/sda%d' % partition) + else: + # We don't resort to -i for this case yet, + # as some older versions of libguestfs + # have problems identifying ttylinux images for example + args += ('-m', '/dev/sda') + args += (self.mount_dir,) + # root access should not required for guestfs (if the user + # has permissions to fusermount (by being part of the fuse + # group for example)). Also note the image and mount_dir + # have appropriate creditials at this point for read/write + # mounting by the nova user. However currently there are + # subsequent access issues by both the nova and root users + # if the nova user mounts the image, as detailed here: + # https://bugzilla.redhat.com/show_bug.cgi?id=765814 + _out, err = utils.trycmd(*args, discard_warnings=True, + run_as_root=True) + if err: + self.error = _('Failed to mount filesystem: %s') % err + # Be defensive and ensure this is unmounted, + # as I'm not sure guestmount will never have + # mounted when it returns EXIT_FAILURE. + # This is required if discard_warnings=False above + utils.trycmd('fusermount', '-u', self.mount_dir, run_as_root=True) + return False + + # More defensiveness as there are edge cases where + # guestmount can return success while not mounting + try: + if not os.listdir(self.mount_dir): + # Assume we've just got the original empty temp dir + err = _('unknown guestmount error') + self.error = _('Failed to mount filesystem: %s') % err + return False + except OSError: + # This is the usual path and means root has + # probably mounted fine + pass + + self.mounted = True + return True + + def unmnt_dev(self): + if not self.mounted: + return + umount_cmd = ['fusermount', '-u', self.mount_dir] + try: + # We make a few attempts to work around other + # processes temporarily scanning the mount_dir etc. + utils.execute(*umount_cmd, attempts=5, run_as_root=True) + except exception.ProcessExecutionError: + # If we still can't umount, then do a lazy umount + # (in the background), so that mounts might eventually + # be cleaned up. Note we'll wait 10s below for the umount to + # complete, after which we'll raise an exception. + umount_cmd.insert(1, '-z') + utils.execute(*umount_cmd, run_as_root=True) + + # Unfortunately FUSE has an issue where it doesn't wait + # for processes associated with the mount to terminate. + # Therefore we do this manually here. Note later versions + # of guestmount have the --pid-file option to help with this. + # Here we check every .2 seconds whether guestmount is finished + # but do this for at most 10 seconds. + wait_cmd = 'until ! ps -C guestmount -o args= | grep -qF "%s"; ' + wait_cmd += 'do sleep .2; done' + wait_cmd %= self.mount_dir + try: + utils.execute('timeout', '10s', 'sh', '-c', wait_cmd) + self.mounted = False + except exception.ProcessExecutionError: + msg = _("Failed to umount image at %s, guestmount was " + "still running after 10s") % (self.mount_dir) + raise exception.NovaException(msg) diff --git a/nova/virt/disk/mount/loop.py b/nova/virt/disk/mount/loop.py new file mode 100644 index 000000000..9b87b6df5 --- /dev/null +++ b/nova/virt/disk/mount/loop.py @@ -0,0 +1,42 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Red Hat, Inc. +# +# 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. +"""Support for mounting images with the loop device""" + +from nova import utils +from nova.virt.disk.mount import api + + +class LoopMount(api.Mount): + """loop back support for raw images.""" + mode = 'loop' + device_id_string = mode + + def get_dev(self): + out, err = utils.trycmd('losetup', '--find', '--show', self.image, + run_as_root=True) + if err: + self.error = _('Could not attach image to loopback: %s') % err + return False + + self.device = out.strip() + self.linked = True + return True + + def unget_dev(self): + if not self.linked: + return + utils.execute('losetup', '--detach', self.device, run_as_root=True) + self.linked = False diff --git a/nova/virt/disk/mount/nbd.py b/nova/virt/disk/mount/nbd.py new file mode 100644 index 000000000..b374ccacc --- /dev/null +++ b/nova/virt/disk/mount/nbd.py @@ -0,0 +1,112 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Red Hat, Inc. +# +# 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. +"""Support for mounting images with qemu-nbd""" + +import os +import time + +from nova import config +from nova import flags +from nova.openstack.common import cfg +from nova import utils +from nova.virt.disk.mount import api + + +nbd_opts = [ + cfg.IntOpt('timeout_nbd', + default=10, + help='time to wait for a NBD device coming up'), + cfg.IntOpt('max_nbd_devices', + default=16, + help='maximum number of possible nbd devices'), + ] + +CONF = config.CONF +CONF.register_opts(nbd_opts) + + +class NbdMount(api.Mount): + """qemu-nbd support disk images.""" + mode = 'nbd' + device_id_string = mode + + # NOTE(padraig): There are three issues with this nbd device handling + # 1. max_nbd_devices should be inferred (#861504) + # 2. We assume nothing else on the system uses nbd devices + # 3. Multiple workers on a system can race against each other + # A patch has been proposed in Nov 2011, to add add a -f option to + # qemu-nbd, akin to losetup -f. One could test for this by running qemu-nbd + # with just the -f option, where it will fail if not supported, or if there + # are no free devices. Note that patch currently hardcodes 16 devices. + # We might be able to alleviate problem 2. by scanning /proc/partitions + # like the aformentioned patch does. + _DEVICES = ['/dev/nbd%s' % i for i in range(CONF.max_nbd_devices)] + + def _allocate_nbd(self): + if not os.path.exists("/sys/block/nbd0"): + self.error = _('nbd unavailable: module not loaded') + return None + while True: + if not self._DEVICES: + # really want to log this info, not raise + self.error = _('No free nbd devices') + return None + device = self._DEVICES.pop() + if not os.path.exists("/sys/block/%s/pid" % + os.path.basename(device)): + break + return device + + def _free_nbd(self, device): + # The device could already be present if unget_dev + # is called right after a nova restart + # (when destroying an LXC container for example). + if not device in self._DEVICES: + self._DEVICES.append(device) + + def get_dev(self): + device = self._allocate_nbd() + if not device: + return False + _out, err = utils.trycmd('qemu-nbd', '-c', device, self.image, + run_as_root=True) + if err: + self.error = _('qemu-nbd error: %s') % err + self._free_nbd(device) + return False + + # NOTE(vish): this forks into another process, so give it a chance + # to set up before continuing + for _i in range(CONF.timeout_nbd): + if os.path.exists("/sys/block/%s/pid" % os.path.basename(device)): + self.device = device + break + time.sleep(1) + else: + self.error = _('nbd device %s did not show up') % device + self._free_nbd(device) + return False + + self.linked = True + return True + + def unget_dev(self): + if not self.linked: + return + utils.execute('qemu-nbd', '-d', self.device, run_as_root=True) + self._free_nbd(self.device) + self.linked = False + self.device = None diff --git a/nova/virt/disk/nbd.py b/nova/virt/disk/nbd.py deleted file mode 100644 index 8503273a6..000000000 --- a/nova/virt/disk/nbd.py +++ /dev/null @@ -1,112 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Red Hat, Inc. -# -# 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. -"""Support for mounting images with qemu-nbd""" - -import os -import time - -from nova import config -from nova import flags -from nova.openstack.common import cfg -from nova import utils -from nova.virt.disk import mount - - -nbd_opts = [ - cfg.IntOpt('timeout_nbd', - default=10, - help='time to wait for a NBD device coming up'), - cfg.IntOpt('max_nbd_devices', - default=16, - help='maximum number of possible nbd devices'), - ] - -CONF = config.CONF -CONF.register_opts(nbd_opts) - - -class Mount(mount.Mount): - """qemu-nbd support disk images.""" - mode = 'nbd' - device_id_string = mode - - # NOTE(padraig): There are three issues with this nbd device handling - # 1. max_nbd_devices should be inferred (#861504) - # 2. We assume nothing else on the system uses nbd devices - # 3. Multiple workers on a system can race against each other - # A patch has been proposed in Nov 2011, to add add a -f option to - # qemu-nbd, akin to losetup -f. One could test for this by running qemu-nbd - # with just the -f option, where it will fail if not supported, or if there - # are no free devices. Note that patch currently hardcodes 16 devices. - # We might be able to alleviate problem 2. by scanning /proc/partitions - # like the aformentioned patch does. - _DEVICES = ['/dev/nbd%s' % i for i in range(CONF.max_nbd_devices)] - - def _allocate_nbd(self): - if not os.path.exists("/sys/block/nbd0"): - self.error = _('nbd unavailable: module not loaded') - return None - while True: - if not self._DEVICES: - # really want to log this info, not raise - self.error = _('No free nbd devices') - return None - device = self._DEVICES.pop() - if not os.path.exists("/sys/block/%s/pid" % - os.path.basename(device)): - break - return device - - def _free_nbd(self, device): - # The device could already be present if unget_dev - # is called right after a nova restart - # (when destroying an LXC container for example). - if not device in self._DEVICES: - self._DEVICES.append(device) - - def get_dev(self): - device = self._allocate_nbd() - if not device: - return False - _out, err = utils.trycmd('qemu-nbd', '-c', device, self.image, - run_as_root=True) - if err: - self.error = _('qemu-nbd error: %s') % err - self._free_nbd(device) - return False - - # NOTE(vish): this forks into another process, so give it a chance - # to set up before continuing - for _i in range(CONF.timeout_nbd): - if os.path.exists("/sys/block/%s/pid" % os.path.basename(device)): - self.device = device - break - time.sleep(1) - else: - self.error = _('nbd device %s did not show up') % device - self._free_nbd(device) - return False - - self.linked = True - return True - - def unget_dev(self): - if not self.linked: - return - utils.execute('qemu-nbd', '-d', self.device, run_as_root=True) - self._free_nbd(self.device) - self.linked = False - self.device = None -- cgit