163 lines
5.7 KiB
Python
163 lines
5.7 KiB
Python
# 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()
|