Coverage for drivers/LargeBlockSR.py : 22%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python3
2#
3# Copyright (C) 2024 Vates SAS - damien.thenot@vates.tech
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <https://www.gnu.org/licenses/>.
17from sm_typing import override
19import SR
20from SR import deviceCheck
21import SRCommand
22import EXTSR
23import util
24import xs_errors
25import os
26import re
27import lvutil
29CAPABILITIES = ["SR_PROBE", "SR_UPDATE", "SR_SUPPORTS_LOCAL_CACHING",
30 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH",
31 "VDI_UPDATE", "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", "VDI_MIRROR",
32 "VDI_GENERATE_CONFIG",
33 "VDI_RESET_ON_BOOT/2", "ATOMIC_PAUSE", "VDI_CONFIG_CBT",
34 "VDI_ACTIVATE", "VDI_DEACTIVATE", "THIN_PROVISIONING", "VDI_READ_CACHING"]
36CONFIGURATION = [['device', 'local device path (required) (e.g. /dev/sda3)']]
38DRIVER_INFO = {
39 'name': 'Large Block SR',
40 'description': 'SR plugin which emulates a 512 bytes disk on top of a 4KiB device then create a EXT SR',
41 'vendor': 'Vates',
42 'copyright': '(C) 2024 Vates',
43 'driver_version': '1.0',
44 'required_api_version': '1.0',
45 'capabilities': CAPABILITIES,
46 'configuration': CONFIGURATION
47}
49LARGEBLOCK_PREFIX = "XSLocalLargeBlock-"
51class LargeBlockSR(EXTSR.EXTSR):
52 """Emulating 512b drives for EXT storage repository"""
54 DRIVER_TYPE = "largeblock"
55 LOOP_SECTOR_SIZE = 512
57 @override
58 @staticmethod
59 def handles(srtype) -> bool:
60 return srtype == LargeBlockSR.DRIVER_TYPE
62 @override
63 def load(self, sr_uuid) -> None:
64 super(LargeBlockSR, self).load(sr_uuid)
65 self.is_deleting = False
66 self.vgname = LARGEBLOCK_PREFIX + sr_uuid
67 self.remotepath = os.path.join("/dev", self.vgname, sr_uuid)
69 @override
70 def attach(self, sr_uuid) -> None:
71 if not self.is_deleting:
72 vg_device = self._get_device()
73 self.dconf["device"] = ",".join(vg_device)
74 self._create_emulated_device()
75 self._redo_vg_connection() # Call redo VG connection to connect it correctly to the loop device instead of the real 4KiB block device
76 super(LargeBlockSR, self).attach(sr_uuid)
78 @override
79 def detach(self, sr_uuid) -> None:
80 if not self.is_deleting:
81 vg_device = self._get_device()
82 self.dconf["device"] = ",".join(vg_device)
83 super(LargeBlockSR, self).detach(sr_uuid)
84 if not self.is_deleting:
85 self._destroy_emulated_device()
87 @override
88 @deviceCheck
89 def create(self, sr_uuid, size) -> None:
90 base_devices = self.dconf["device"].split(",")
91 if len(base_devices) > 1:
92 raise xs_errors.XenError("ConfigDeviceInvalid", opterr="Multiple devices configuration is not supported")
94 for dev in base_devices:
95 logical_blocksize = util.pread2(["blockdev", "--getss", dev]).strip()
96 if logical_blocksize == "512":
97 raise xs_errors.XenError("LargeBlockIncorrectBlocksize", opterr="The logical blocksize of the device {} is compatible with normal SR types".format(dev))
99 try:
100 self._create_emulated_device()
101 super(LargeBlockSR, self).create(sr_uuid, size)
102 finally:
103 self._destroy_emulated_device(base_devices)
105 @override
106 def delete(self, sr_uuid) -> None:
107 base_devices = self._get_device()
108 self.dconf["device"] = ",".join(self._get_loopdev_from_device(base_devices))
110 self.is_deleting = True
111 try:
112 super(LargeBlockSR, self).delete(sr_uuid)
113 except xs_errors.SROSError:
114 # In case, the lvremove doesn't like the loop device, it will throw an error.
115 # We need to remove the device ourselves using the real device in this case.
116 for dev in base_devices:
117 util.pread2(["pvremove", dev])
118 finally:
119 self._destroy_emulated_device(base_devices)
120 self.is_deleting = False
122 @override
123 @deviceCheck
124 def probe(self) -> str:
125 # We override EXTSR.probe because it uses EXT_PREFIX in this call
126 return lvutil.srlist_toxml(
127 lvutil.scan_srlist(LARGEBLOCK_PREFIX, self.dconf['device']),
128 LARGEBLOCK_PREFIX
129 )
131 def _create_loopdev(self, dev, emulated_path):
132 cmd = ["losetup", "-f", "-v", "--show", "--sector-size", str(self.LOOP_SECTOR_SIZE), dev]
133 loopdev = util.pread2(cmd).rstrip()
135 if os.path.exists(emulated_path) and os.path.islink(emulated_path):
136 os.unlink(emulated_path)
138 try:
139 os.symlink(loopdev, emulated_path)
140 except OSError:
141 raise xs_errors.XenError("LargeBlockSymlinkExist", opterr="Symlink {} couldn't be created".format(emulated_path))
143 def _delete_loopdev(self, dev, emulated_path):
144 if os.path.exists(emulated_path) and os.path.islink(emulated_path):
145 os.unlink(emulated_path)
147 # The backing file isn't a symlink if given by ID in device-config but the real device
148 dev = os.path.realpath(dev)
149 loopdevs = self._get_loopdev_from_device(dev)
151 if loopdevs != None:
152 try:
153 for lp in loopdevs:
154 cmd = ["losetup", "-d", lp] # Remove the loop device
155 util.pread2(cmd)
156 except xs_errors.SROSError:
157 util.SMlog("Couldn't removed losetup devices: {}".format(loopdevs))
158 else:
159 xs_errors.XenError("LargeBlockNoLosetup", opterr="Couldn't find loop device for {}".format(dev))
161 @staticmethod
162 def _get_loopdev_from_device(device):
163 lpdevs = []
164 output = util.pread2(["losetup", "--list"]).rstrip()
165 if output:
166 for line in output.split("\n"):
167 line = line.split()
168 loopdev = line[0]
169 dev = line[5].strip()
170 if dev in device:
171 lpdevs.append(loopdev)
172 return lpdevs
174 @staticmethod
175 def _get_device_from_loopdev(loopdevs):
176 devices = []
177 output = util.pread2(["losetup", "--list"]).rstrip()
178 if output:
179 for line in output.split("\n"):
180 line = line.split()
181 lpdev = line[0]
182 dev = line[5]
183 if lpdev in loopdevs:
184 devices.append(dev)
185 return devices
187 def _get_device_from_vg(self):
188 devices = []
189 output = util.pread2(["vgs", "--noheadings", "-o", "vg_name,devices", self.vgname, "--config", "devices{scan=[\"/dev/\"]}"]).splitlines()
190 for line in output:
191 line = line.split()
192 dev = line[1].split("(")[0]
193 if os.path.islink(dev):
194 dev = os.path.realpath(dev)
195 devices.append(dev)
196 return devices
198 def _get_device(self):
199 vg_device = self._get_device_from_vg()
200 for dev in vg_device:
201 if re.match(r"(.*\.512)|(/dev/loop[0-9]+)", dev):
202 lpdev = os.path.realpath(dev)
203 realdev = self._get_device_from_loopdev(lpdev)[0]
204 vg_device.remove(dev)
205 vg_device.append(realdev)
207 return vg_device
209 def _redo_vg_connection(self):
210 """
211 In case of using a LargeBlockSR, the LVM scan at boot will find the LogicalVolume on the real block device.
212 And when the PBD is connecting, it will mount from the original device instead of the loop device since LVM prefers real devices it has seen first.
213 The PBD plug will succeed but then the SR will be accessed through the 4KiB device, returning to the erroneous behaviour on 4KiB device.
214 VM won't be able to run because vhd-util will fail to scan the VDI.
215 This function force the LogicalVolume to be mounted on top of our emulation layer by disabling the VolumeGroup and re-enabling while applying a filter.
216 """
218 util.SMlog("Reconnecting VG {} to use emulated device".format(self.vgname))
219 try:
220 lvutil.setActiveVG(self.vgname, False)
221 lvutil.setActiveVG(self.vgname, True, config="devices{ scan = [\"/dev/\"] global_filter = [ \"a|/dev/loop.*|\", \"r|.*|\" ] }")
222 except util.CommandException as e:
223 xs_errors.XenError("LargeBlockVGReconnectFailed", opterr="Failed to reconnect the VolumeGroup {}, error: {}".format(self.vgname, e))
226 @classmethod
227 def _get_emulated_device_path(cls, dev):
228 return "{dev}.{bs}".format(dev=dev, bs=cls.LOOP_SECTOR_SIZE)
230 def _create_emulated_device(self):
231 base_devices = self.dconf["device"].split(",")
232 emulated_devices = []
233 for dev in base_devices:
234 emulated_path = self._get_emulated_device_path(dev)
235 self._create_loopdev(dev, emulated_path)
236 emulated_devices.append(emulated_path)
238 emulated_devices = ",".join(emulated_devices)
239 self.dconf["device"] = emulated_devices
241 def _destroy_emulated_device(self, devices=None):
242 if devices is None:
243 devices = self.dconf["device"].split(",")
245 for dev in devices:
246 emulated_path = self._get_emulated_device_path(dev)
247 self._delete_loopdev(dev, emulated_path)
249if __name__ == '__main__': 249 ↛ 250line 249 didn't jump to line 250, because the condition on line 249 was never true
250 SRCommand.run(LargeBlockSR, DRIVER_INFO)
251else:
252 SR.registerSR(LargeBlockSR)