mirror of
https://github.com/kevingruesser/bootstrap-vz.git
synced 2025-08-22 18:00:35 +00:00
136 lines
6.1 KiB
Python
136 lines
6.1 KiB
Python
from abc import ABCMeta
|
|
from bootstrapvz.common.fsm_proxy import FSMProxy
|
|
from bootstrapvz.common.tools import log_check_call
|
|
from .exceptions import VolumeError
|
|
from .partitionmaps.none import NoPartitions
|
|
|
|
|
|
class Volume(FSMProxy):
|
|
"""Represents an abstract volume.
|
|
This class is a finite state machine and represents the state of the real volume.
|
|
"""
|
|
|
|
__metaclass__ = ABCMeta
|
|
|
|
# States this volume can be in
|
|
events = [{'name': 'create', 'src': 'nonexistent', 'dst': 'detached'},
|
|
{'name': 'attach', 'src': 'detached', 'dst': 'attached'},
|
|
{'name': 'link_dm_node', 'src': 'attached', 'dst': 'linked'},
|
|
{'name': 'unlink_dm_node', 'src': 'linked', 'dst': 'attached'},
|
|
{'name': 'detach', 'src': 'attached', 'dst': 'detached'},
|
|
{'name': 'delete', 'src': 'detached', 'dst': 'deleted'},
|
|
]
|
|
|
|
def __init__(self, partition_map):
|
|
"""
|
|
:param PartitionMap partition_map: The partition map for the volume
|
|
"""
|
|
# Path to the volume
|
|
self.device_path = None
|
|
# The partition map
|
|
self.partition_map = partition_map
|
|
# The size of the volume as reported by the partition map
|
|
self.size = self.partition_map.get_total_size()
|
|
|
|
# Before detaching, check that nothing would block the detachment
|
|
callbacks = {'onbeforedetach': self._check_blocking}
|
|
if isinstance(self.partition_map, NoPartitions):
|
|
# When the volume has no partitions, the virtual root partition path is equal to that of the volume
|
|
# Update that path whenever the path to the volume changes
|
|
def set_dev_path(e):
|
|
self.partition_map.root.device_path = self.device_path
|
|
callbacks['onafterattach'] = set_dev_path
|
|
callbacks['onafterdetach'] = set_dev_path # Will become None
|
|
callbacks['onlink_dm_node'] = set_dev_path
|
|
callbacks['onunlink_dm_node'] = set_dev_path
|
|
|
|
# Create the configuration for our finite state machine
|
|
cfg = {'initial': 'nonexistent', 'events': self.events, 'callbacks': callbacks}
|
|
super(Volume, self).__init__(cfg)
|
|
|
|
def _after_create(self, e):
|
|
if isinstance(self.partition_map, NoPartitions):
|
|
# When the volume has no partitions, the virtual root partition
|
|
# is essentially created when the volume is created, forward that creation event.
|
|
self.partition_map.root.create()
|
|
|
|
def _check_blocking(self, e):
|
|
"""Checks whether the volume is blocked
|
|
|
|
:raises VolumeError: When the volume is blocked from being detached
|
|
"""
|
|
# Only the partition map can block the volume
|
|
if self.partition_map.is_blocking():
|
|
raise VolumeError('The partitionmap prevents the detach procedure')
|
|
|
|
def _before_link_dm_node(self, e):
|
|
"""Links the volume using the device mapper
|
|
This allows us to create a 'window' into the volume that acts like a volume in itself.
|
|
Mainly it is used to fool grub into thinking that it is working with a real volume,
|
|
rather than a loopback device or a network block device.
|
|
|
|
:param _e_obj e: Event object containing arguments to create()
|
|
|
|
Keyword arguments to link_dm_node() are:
|
|
|
|
:param int logical_start_sector: The sector the volume should start at in the new volume
|
|
:param int start_sector: The offset at which the volume should begin to be mapped in the new volume
|
|
:param int sectors: The number of sectors that should be mapped
|
|
|
|
Read more at: http://manpages.debian.org/cgi-bin/man.cgi?query=dmsetup&apropos=0&sektion=0&manpath=Debian+7.0+wheezy&format=html&locale=en
|
|
|
|
:raises VolumeError: When a free block device cannot be found.
|
|
"""
|
|
import os.path
|
|
from bootstrapvz.common.fs import get_partitions
|
|
# Fetch information from /proc/partitions
|
|
proc_partitions = get_partitions()
|
|
device_name = os.path.basename(self.device_path)
|
|
device_partition = proc_partitions[device_name]
|
|
|
|
# The sector the volume should start at in the new volume
|
|
logical_start_sector = getattr(e, 'logical_start_sector', 0)
|
|
|
|
# The offset at which the volume should begin to be mapped in the new volume
|
|
start_sector = getattr(e, 'start_sector', 0)
|
|
|
|
# The number of sectors that should be mapped
|
|
sectors = getattr(e, 'sectors', int(self.size) - start_sector)
|
|
|
|
# This is the table we send to dmsetup, so that it may create a device mapping for us.
|
|
table = ('{log_start_sec} {sectors} linear {major}:{minor} {start_sec}'
|
|
.format(log_start_sec=logical_start_sector,
|
|
sectors=sectors,
|
|
major=device_partition['major'],
|
|
minor=device_partition['minor'],
|
|
start_sec=start_sector))
|
|
import string
|
|
import os.path
|
|
# Figure out the device letter and path
|
|
for letter in string.ascii_lowercase:
|
|
dev_name = 'vd' + letter
|
|
dev_path = os.path.join('/dev/mapper', dev_name)
|
|
if not os.path.exists(dev_path):
|
|
self.dm_node_name = dev_name
|
|
self.dm_node_path = dev_path
|
|
break
|
|
|
|
if not hasattr(self, 'dm_node_name'):
|
|
raise VolumeError('Unable to find a free block device path for mounting the bootstrap volume')
|
|
|
|
# Create the device mapping
|
|
log_check_call(['dmsetup', 'create', self.dm_node_name], table)
|
|
# Update the device_path but remember the old one for when we unlink the volume again
|
|
self.unlinked_device_path = self.device_path
|
|
self.device_path = self.dm_node_path
|
|
|
|
def _before_unlink_dm_node(self, e):
|
|
"""Unlinks the device mapping
|
|
"""
|
|
log_check_call(['dmsetup', 'remove', self.dm_node_name])
|
|
# Reset the device_path
|
|
self.device_path = self.unlinked_device_path
|
|
# Delete the no longer valid information
|
|
del self.unlinked_device_path
|
|
del self.dm_node_name
|
|
del self.dm_node_path
|