File: volume.py

package info (click to toggle)
bootstrap-vz 0.9.11%2B20180121git-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 2,244 kB
  • sloc: python: 8,800; sh: 813; makefile: 16
file content (136 lines) | stat: -rw-r--r-- 6,200 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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