Coverage for drivers/LargeBlockSR.py : 18%

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/>.
17import SR
18from SR import deviceCheck
19import SRCommand
20import EXTSR
21import util
22import xs_errors
23import os
24import re
25import lvutil
27CAPABILITIES = ["SR_PROBE", "SR_UPDATE", "SR_SUPPORTS_LOCAL_CACHING",
28 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH",
29 "VDI_UPDATE", "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", "VDI_MIRROR",
30 "VDI_GENERATE_CONFIG",
31 "VDI_RESET_ON_BOOT/2", "ATOMIC_PAUSE", "VDI_CONFIG_CBT",
32 "VDI_ACTIVATE", "VDI_DEACTIVATE", "THIN_PROVISIONING", "VDI_READ_CACHING"]
34CONFIGURATION = [['device', 'local device path (required) (e.g. /dev/sda3)']]
36DRIVER_INFO = {
37 'name': 'Large Block SR',
38 'description': 'SR plugin which emulates a 512 bytes disk on top of a 4KiB device then create a EXT SR',
39 'vendor': 'Vates',
40 'copyright': '(C) 2024 Vates',
41 'driver_version': '1.0',
42 'required_api_version': '1.0',
43 'capabilities': CAPABILITIES,
44 'configuration': CONFIGURATION
45}
47LARGEBLOCK_PREFIX = "XSLocalLargeBlock-"
49class LargeBlockSR(EXTSR.EXTSR):
50 """Emulating 512b drives for EXT storage repository"""
52 DRIVER_TYPE = "largeblock"
53 LOOP_SECTOR_SIZE = 512
55 @staticmethod
56 def handles(srtype):
57 return srtype == LargeBlockSR.DRIVER_TYPE
59 def load(self, sr_uuid):
60 super(LargeBlockSR, self).load(sr_uuid)
61 self.is_deleting = False
62 self.vgname = LARGEBLOCK_PREFIX + sr_uuid
63 self.remotepath = os.path.join("/dev", self.vgname, sr_uuid)
65 def attach(self, sr_uuid):
66 if not self.is_deleting:
67 vg_device = self._get_device()
68 self.dconf["device"] = ",".join(vg_device)
69 self._create_emulated_device()
70 if not self._is_vg_connection_correct(): # Check if we need to redo the connection by parsing `vgs -o vg_name,devices self.vgname`
71 self._redo_vg_connection() # Call redo VG connection to connect it correctly to the loop device instead of the real 4KiB block device
72 super(LargeBlockSR, self).attach(sr_uuid)
74 def detach(self, sr_uuid):
75 if not self.is_deleting:
76 vg_device = self._get_device()
77 self.dconf["device"] = ",".join(vg_device)
78 super(LargeBlockSR, self).detach(sr_uuid)
79 if not self.is_deleting:
80 self._destroy_emulated_device()
82 @deviceCheck
83 def create(self, sr_uuid, size):
84 base_devices = self.dconf["device"].split(",")
85 for dev in base_devices:
86 logical_blocksize = util.pread2(["blockdev", "--getss", dev]).strip()
87 if logical_blocksize == "512":
88 raise xs_errors.XenError("LargeBlockIncorrectBlocksize", opterr="The logical blocksize of the device {} is compatible with normal SR types".format(dev))
90 try:
91 self._create_emulated_device()
92 super(LargeBlockSR, self).create(sr_uuid, size)
93 finally:
94 self._destroy_emulated_device(base_devices)
96 def delete(self, sr_uuid):
97 base_devices = self._get_device()
98 self.dconf["device"] = ",".join(self._get_loopdev_from_device(base_devices))
100 self.is_deleting = True
101 try:
102 super(LargeBlockSR, self).delete(sr_uuid)
103 except SR.SROSError:
104 # In case, the lvremove doesn't like the loop device, it will throw an error.
105 # We need to remove the device ourselves using the real device in this case.
106 for dev in base_devices:
107 util.pread2(["pvremove", dev])
108 finally:
109 self._destroy_emulated_device(base_devices)
110 self.is_deleting = False
112 @deviceCheck
113 def probe(self):
114 # We override EXTSR.probe because it uses EXT_PREFIX in this call
115 return lvutil.srlist_toxml(
116 lvutil.scan_srlist(LARGEBLOCK_PREFIX, self.dconf['device']),
117 LARGEBLOCK_PREFIX
118 )
120 def _create_loopdev(self, dev, emulated_path):
121 cmd = ["losetup", "-f", "-v", "--show", "--sector-size", str(self.LOOP_SECTOR_SIZE), dev]
122 loopdev = util.pread2(cmd).rstrip()
124 if os.path.exists(emulated_path) and os.path.islink(emulated_path):
125 os.unlink(emulated_path)
127 try:
128 os.symlink(loopdev, emulated_path)
129 except OSError:
130 raise xs_errors.XenError("LargeBlockSymlinkExist", opterr="Symlink {} couldn't be created".format(emulated_path))
132 def _delete_loopdev(self, dev, emulated_path):
133 if os.path.exists(emulated_path) and os.path.islink(emulated_path):
134 os.unlink(emulated_path)
136 # The backing file isn't a symlink if given by ID in device-config but the real device
137 dev = os.path.realpath(dev)
138 loopdevs = self._get_loopdev_from_device(dev)
140 if loopdevs != None:
141 try:
142 for lp in loopdevs:
143 cmd = ["losetup", "-d", lp] # Remove the loop device
144 util.pread2(cmd)
145 except SR.SROSError:
146 util.SMlog("Couldn't removed losetup devices: {}".format(loopdevs))
147 else:
148 xs_errors.XenError("LargeBlockNoLosetup", opterr="Couldn't find loop device for {}".format(dev))
150 @staticmethod
151 def _get_loopdev_from_device(device):
152 lpdevs = []
153 output = util.pread2(["losetup", "--list"]).rstrip()
154 if output:
155 for line in output.split("\n"):
156 line = line.split()
157 loopdev = line[0]
158 dev = line[5].strip()
159 if dev in device:
160 lpdevs.append(loopdev)
161 return lpdevs
163 @staticmethod
164 def _get_device_from_loopdev(loopdevs):
165 devices = []
166 output = util.pread2(["losetup", "--list"]).rstrip()
167 if output:
168 for line in output.split("\n"):
169 line = line.split()
170 lpdev = line[0]
171 dev = line[5]
172 if lpdev in loopdevs:
173 devices.append(dev)
174 return devices
176 def _get_device_from_vg(self):
177 devices = []
178 output = util.pread2(["vgs", "--noheadings", "-o", "vg_name,devices", self.vgname]).splitlines()
179 for line in output:
180 line = line.split()
181 dev = line[1].split("(")[0]
182 if os.path.islink(dev):
183 dev = os.path.realpath(dev)
184 devices.append(dev)
185 return devices
187 def _get_device(self):
188 vg_device = self._get_device_from_vg()
189 for dev in vg_device:
190 if re.match(r"(.*\.512)|(/dev/loop[0-9]+)", dev):
191 lpdev = os.path.realpath(dev)
192 realdev = self._get_device_from_loopdev(lpdev)[0]
193 vg_device.remove(dev)
194 vg_device.append(realdev)
196 return vg_device
198 def _is_vg_connection_correct(self):
199 output = util.pread2(["vgs", "--noheadings", "-o", "vg_name,devices", self.vgname]).split()
200 output[1] = output[1].split("(")[0]
201 return bool(re.match(r"(.*\.512)|(/dev/loop[0-9]+)", output[1]))
203 def _redo_vg_connection(self):
204 """
205 In case of using a LargeBlockSR, the LVM scan at boot will find the LogicalVolume on the real block device.
206 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.
207 The PBD plug will succeed but then the SR will be accessed through the 4KiB device, returning to the erroneous behaviour on 4KiB device.
208 VM won't be able to run because vhd-util will fail to scan the VDI.
209 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.
210 """
212 util.SMlog("Reconnecting VG {} to use emulated device".format(self.vgname))
213 try:
214 lvutil.setActiveVG(self.vgname, False)
215 lvutil.setActiveVG(self.vgname, True, config="devices{ global_filter = [ \"r|^/dev/nvme.*|\", \"a|/dev/loop.*|\" ] }")
216 except util.CommandException as e:
217 xs_errors.XenError("LargeBlockVGReconnectFailed", opterr="Failed to reconnect the VolumeGroup {}, error: {}".format(self.vgname, e))
220 @classmethod
221 def _get_emulated_device_path(cls, dev):
222 return "{dev}.{bs}".format(dev=dev, bs=cls.LOOP_SECTOR_SIZE)
224 def _create_emulated_device(self):
225 base_devices = self.dconf["device"].split(",")
226 emulated_devices = []
227 for dev in base_devices:
228 emulated_path = self._get_emulated_device_path(dev)
229 self._create_loopdev(dev, emulated_path)
230 emulated_devices.append(emulated_path)
232 emulated_devices = ",".join(emulated_devices)
233 self.dconf["device"] = emulated_devices
235 def _destroy_emulated_device(self, devices=None):
236 if devices is None:
237 devices = self.dconf["device"].split(",")
239 for dev in devices:
240 emulated_path = self._get_emulated_device_path(dev)
241 self._delete_loopdev(dev, emulated_path)
243if __name__ == '__main__': 243 ↛ 244line 243 didn't jump to line 244, because the condition on line 243 was never true
244 SRCommand.run(LargeBlockSR, DRIVER_INFO)
245else:
246 SR.registerSR(LargeBlockSR)