diff --git a/base/fs/nopartitions.py b/base/fs/nopartitions.py index b8935ca..0133ffc 100644 --- a/base/fs/nopartitions.py +++ b/base/fs/nopartitions.py @@ -6,6 +6,7 @@ class NoPartitions(object): def __init__(self, data): root = data['root'] self.root = SinglePartition(root['size'], root['filesystem']) + self.partitions = [self.root] self.mount_points = [('/', self.root)] def get_total_size(self): diff --git a/common/fs/__init__.py b/common/fs/__init__.py index 20b2f69..a609034 100644 --- a/common/fs/__init__.py +++ b/common/fs/__init__.py @@ -1,11 +1,16 @@ -def get_major_minor_dev_num(device_name): +def get_partitions(): import re - regexp = re.compile('^ *(?P\d+) *(?P\d+) *(?P\d+) {device_name}$' - .format(device_name=device_name)) - with open('/proc/partitions') as partitions: + regexp = re.compile('^ *(?P\d+) *(?P\d+) *(?P\d+) (?P\S+)$') + matches = {} + path = '/proc/partitions' + with open(path) as partitions: + next(partitions) + next(partitions) for line in partitions: match = regexp.match(line) - if match is not None: - return match.group('major'), match.group('minor') + if match is None: + raise RuntimeError('Unable to parse {line} in {path}'.format(line=line, path=path)) + matches[match.group('dev_name')] = match.groupdict() + return matches diff --git a/common/fs/loopbackvolume.py b/common/fs/loopbackvolume.py index d945244..0dcc35b 100644 --- a/common/fs/loopbackvolume.py +++ b/common/fs/loopbackvolume.py @@ -49,15 +49,17 @@ class LoopbackVolume(Volume): def _link_dm_node(self, e): import os.path + from . import get_partitions + proc_partitions = get_partitions() loop_device_name = os.path.basename(self.loop_device_path) - from . import get_major_minor_dev_num - major, minor = get_major_minor_dev_num(loop_device_name) + loop_device_partition = proc_partitions[loop_device_name] + sectors = self.size*1024*1024/512 table = ('{log_start_sec} {sectors} linear {major}:{minor} {start_sec}' .format(log_start_sec=0, sectors=sectors, - major=major, - minor=minor, + major=loop_device_partition['major'], + minor=loop_device_partition['minor'], start_sec=0)) import string import os.path diff --git a/common/tasks/filesystem.py b/common/tasks/filesystem.py index a0e4075..8af2c49 100644 --- a/common/tasks/filesystem.py +++ b/common/tasks/filesystem.py @@ -64,15 +64,15 @@ class MountBoot(Task): class CreateBootMountDir(Task): - description = 'Creating mountpoint boot partition' + description = 'Creating mountpoint for the boot partition' phase = phases.volume_mounting after = [MountRoot] before = [MountBoot] def run(self, info): import os - info.boot = os.path.join(info.root, 'boot') - os.makedirs() + boot_dir = os.path.join(info.root, 'boot') + os.makedirs(boot_dir) class MountSpecials(Task): diff --git a/providers/virtualbox/__init__.py b/providers/virtualbox/__init__.py index eff464c..18c9fef 100644 --- a/providers/virtualbox/__init__.py +++ b/providers/virtualbox/__init__.py @@ -91,7 +91,9 @@ def tasks(tasklist, manifest): break if 'boot' in manifest.volume['partitions']: - tasklist.add(filesystem.MountBoot(), filesystem.UnmountBoot()) + tasklist.add(filesystem.CreateBootMountDir(), + filesystem.MountBoot(), + filesystem.UnmountBoot()) def rollback_tasks(tasklist, tasks_completed, manifest): @@ -105,7 +107,7 @@ def rollback_tasks(tasklist, tasks_completed, manifest): counter_task(filesystem.CreateMountDir, filesystem.DeleteMountDir) counter_task(partitioning.MapPartitions, partitioning.UnmapPartitions) counter_task(filesystem.MountRoot, filesystem.UnmountRoot) - counter_task(filesystem.MountSpecials, filesystem.UnmountSpecials) counter_task(filesystem.MountBoot, filesystem.UnmountBoot) + counter_task(filesystem.MountSpecials, filesystem.UnmountSpecials) counter_task(volume_tasks.Attach, volume_tasks.Detach) counter_task(workspace.CreateWorkspace, workspace.DeleteWorkspace) diff --git a/providers/virtualbox/volume.py b/providers/virtualbox/volume.py index 1af3d66..3f8e6da 100644 --- a/providers/virtualbox/volume.py +++ b/providers/virtualbox/volume.py @@ -1,5 +1,7 @@ from common.fs.loopbackvolume import LoopbackVolume +from base.fs.exceptions import VolumeError from common.tools import log_check_call +from common.fs import get_partitions class VirtualBoxVolume(LoopbackVolume): @@ -9,3 +11,57 @@ class VirtualBoxVolume(LoopbackVolume): def _create(self, e): self.image_path = e.image_path log_check_call(['/usr/bin/qemu-img', 'create', '-f', 'vdi', self.image_path, str(self.size) + 'M']) + + def _attach(self, e): + num_partitions = len(self.partition_map.partitions) + if not self._module_loaded('nbd'): + msg = ('The kernel module `nbd\' must be loaded ' + '(`modprobe nbd max_part={num_partitions}\') to attach .vdi images' + .format(num_partitions=num_partitions)) + raise VolumeError(msg) + nbd_max_part = int(self._module_param('nbd', 'max_part')) + if nbd_max_part < num_partitions: + msg = ('The kernel module `nbd\' was loaded with the max_part ' + 'parameter set to {max_part}, which is below ' + 'the amount of partitions for this volume ({num_partitions}). ' + 'Reload the nbd kernel module with max_part set to at least {num_partitions} ' + '(`rmmod nbd; modprobe nbd max_part={num_partitions}\').' + .format(max_part=nbd_max_part, num_partitions=num_partitions)) + raise VolumeError(msg) + self.loop_device_path = self._find_free_nbd_device() + log_check_call(['/usr/bin/qemu-nbd', '--connect', self.loop_device_path, self.image_path]) + self.device_path = self.loop_device_path + + def _detach(self, e): + log_check_call(['/usr/bin/qemu-nbd', '--disconnect', self.loop_device_path]) + del self.loop_device_path + del self.device_path + + def _module_loaded(self, module): + import re + regexp = re.compile('^{module} +'.format(module=module)) + with open('/proc/modules') as loaded_modules: + for line in loaded_modules: + match = regexp.match(line) + if match is not None: + return True + return False + + def _module_param(self, module, param): + import os.path + param_path = os.path.join('/sys/module', module, 'parameters', param) + with open(param_path) as param: + return param.read().strip() + + # From http://lists.gnu.org/archive/html/qemu-devel/2011-11/msg02201.html + # Apparently it's not in the current qemu-nbd shipped with wheezy + def _is_nbd_used(self, device_name): + return device_name in get_partitions() + + def _find_free_nbd_device(self): + import os.path + for i in xrange(0, 15): + device_name = 'nbd' + str(i) + if not self._is_nbd_used(device_name): + return os.path.join('/dev', device_name) + raise VolumeError('Unable to find free nbd device.')