Hide keyboard shortcuts

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/>. 

16 

17from sm_typing import override 

18 

19import SR 

20from SR import deviceCheck 

21import SRCommand 

22import EXTSR 

23import util 

24import xs_errors 

25import os 

26import re 

27import lvutil 

28 

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"] 

35 

36CONFIGURATION = [['device', 'local device path (required) (e.g. /dev/sda3)']] 

37 

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} 

48 

49LARGEBLOCK_PREFIX = "XSLocalLargeBlock-" 

50 

51class LargeBlockSR(EXTSR.EXTSR): 

52 """Emulating 512b drives for EXT storage repository""" 

53 

54 DRIVER_TYPE = "largeblock" 

55 LOOP_SECTOR_SIZE = 512 

56 

57 @override 

58 @staticmethod 

59 def handles(srtype) -> bool: 

60 return srtype == LargeBlockSR.DRIVER_TYPE 

61 

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) 

68 

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 if not self._is_vg_connection_correct(): # Check if we need to redo the connection by parsing `vgs -o vg_name,devices self.vgname` 

76 self._redo_vg_connection() # Call redo VG connection to connect it correctly to the loop device instead of the real 4KiB block device 

77 super(LargeBlockSR, self).attach(sr_uuid) 

78 

79 @override 

80 def detach(self, sr_uuid) -> None: 

81 if not self.is_deleting: 

82 vg_device = self._get_device() 

83 self.dconf["device"] = ",".join(vg_device) 

84 super(LargeBlockSR, self).detach(sr_uuid) 

85 if not self.is_deleting: 

86 self._destroy_emulated_device() 

87 

88 @override 

89 @deviceCheck 

90 def create(self, sr_uuid, size) -> None: 

91 base_devices = self.dconf["device"].split(",") 

92 if len(base_devices) > 1: 

93 raise xs_errors.XenError("ConfigDeviceInvalid", opterr="Multiple devices configuration is not supported") 

94 

95 for dev in base_devices: 

96 logical_blocksize = util.pread2(["blockdev", "--getss", dev]).strip() 

97 if logical_blocksize == "512": 

98 raise xs_errors.XenError("LargeBlockIncorrectBlocksize", opterr="The logical blocksize of the device {} is compatible with normal SR types".format(dev)) 

99 

100 try: 

101 self._create_emulated_device() 

102 super(LargeBlockSR, self).create(sr_uuid, size) 

103 finally: 

104 self._destroy_emulated_device(base_devices) 

105 

106 @override 

107 def delete(self, sr_uuid) -> None: 

108 base_devices = self._get_device() 

109 self.dconf["device"] = ",".join(self._get_loopdev_from_device(base_devices)) 

110 

111 self.is_deleting = True 

112 try: 

113 super(LargeBlockSR, self).delete(sr_uuid) 

114 except xs_errors.SROSError: 

115 # In case, the lvremove doesn't like the loop device, it will throw an error. 

116 # We need to remove the device ourselves using the real device in this case. 

117 for dev in base_devices: 

118 util.pread2(["pvremove", dev]) 

119 finally: 

120 self._destroy_emulated_device(base_devices) 

121 self.is_deleting = False 

122 

123 @override 

124 @deviceCheck 

125 def probe(self) -> str: 

126 # We override EXTSR.probe because it uses EXT_PREFIX in this call 

127 return lvutil.srlist_toxml( 

128 lvutil.scan_srlist(LARGEBLOCK_PREFIX, self.dconf['device']), 

129 LARGEBLOCK_PREFIX 

130 ) 

131 

132 def _create_loopdev(self, dev, emulated_path): 

133 cmd = ["losetup", "-f", "-v", "--show", "--sector-size", str(self.LOOP_SECTOR_SIZE), dev] 

134 loopdev = util.pread2(cmd).rstrip() 

135 

136 if os.path.exists(emulated_path) and os.path.islink(emulated_path): 

137 os.unlink(emulated_path) 

138 

139 try: 

140 os.symlink(loopdev, emulated_path) 

141 except OSError: 

142 raise xs_errors.XenError("LargeBlockSymlinkExist", opterr="Symlink {} couldn't be created".format(emulated_path)) 

143 

144 def _delete_loopdev(self, dev, emulated_path): 

145 if os.path.exists(emulated_path) and os.path.islink(emulated_path): 

146 os.unlink(emulated_path) 

147 

148 # The backing file isn't a symlink if given by ID in device-config but the real device 

149 dev = os.path.realpath(dev) 

150 loopdevs = self._get_loopdev_from_device(dev) 

151 

152 if loopdevs != None: 

153 try: 

154 for lp in loopdevs: 

155 cmd = ["losetup", "-d", lp] # Remove the loop device 

156 util.pread2(cmd) 

157 except xs_errors.SROSError: 

158 util.SMlog("Couldn't removed losetup devices: {}".format(loopdevs)) 

159 else: 

160 xs_errors.XenError("LargeBlockNoLosetup", opterr="Couldn't find loop device for {}".format(dev)) 

161 

162 @staticmethod 

163 def _get_loopdev_from_device(device): 

164 lpdevs = [] 

165 output = util.pread2(["losetup", "--list"]).rstrip() 

166 if output: 

167 for line in output.split("\n"): 

168 line = line.split() 

169 loopdev = line[0] 

170 dev = line[5].strip() 

171 if dev in device: 

172 lpdevs.append(loopdev) 

173 return lpdevs 

174 

175 @staticmethod 

176 def _get_device_from_loopdev(loopdevs): 

177 devices = [] 

178 output = util.pread2(["losetup", "--list"]).rstrip() 

179 if output: 

180 for line in output.split("\n"): 

181 line = line.split() 

182 lpdev = line[0] 

183 dev = line[5] 

184 if lpdev in loopdevs: 

185 devices.append(dev) 

186 return devices 

187 

188 def _get_device_from_vg(self): 

189 devices = [] 

190 output = util.pread2(["vgs", "--noheadings", "-o", "vg_name,devices", self.vgname]).splitlines() 

191 for line in output: 

192 line = line.split() 

193 dev = line[1].split("(")[0] 

194 if os.path.islink(dev): 

195 dev = os.path.realpath(dev) 

196 devices.append(dev) 

197 return devices 

198 

199 def _get_device(self): 

200 vg_device = self._get_device_from_vg() 

201 for dev in vg_device: 

202 if re.match(r"(.*\.512)|(/dev/loop[0-9]+)", dev): 

203 lpdev = os.path.realpath(dev) 

204 realdev = self._get_device_from_loopdev(lpdev)[0] 

205 vg_device.remove(dev) 

206 vg_device.append(realdev) 

207 

208 return vg_device 

209 

210 def _is_vg_connection_correct(self): 

211 output = util.pread2(["vgs", "--noheadings", "-o", "vg_name,devices", self.vgname]).split() 

212 output[1] = output[1].split("(")[0] 

213 return bool(re.match(r"(.*\.512)|(/dev/loop[0-9]+)", output[1])) 

214 

215 def _redo_vg_connection(self): 

216 """ 

217 In case of using a LargeBlockSR, the LVM scan at boot will find the LogicalVolume on the real block device. 

218 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. 

219 The PBD plug will succeed but then the SR will be accessed through the 4KiB device, returning to the erroneous behaviour on 4KiB device. 

220 VM won't be able to run because vhd-util will fail to scan the VDI. 

221 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. 

222 """ 

223 

224 util.SMlog("Reconnecting VG {} to use emulated device".format(self.vgname)) 

225 try: 

226 lvutil.setActiveVG(self.vgname, False) 

227 lvutil.setActiveVG(self.vgname, True, config="devices{ global_filter = [ \"r|^/dev/nvme.*|\", \"a|/dev/loop.*|\" ] }") 

228 except util.CommandException as e: 

229 xs_errors.XenError("LargeBlockVGReconnectFailed", opterr="Failed to reconnect the VolumeGroup {}, error: {}".format(self.vgname, e)) 

230 

231 

232 @classmethod 

233 def _get_emulated_device_path(cls, dev): 

234 return "{dev}.{bs}".format(dev=dev, bs=cls.LOOP_SECTOR_SIZE) 

235 

236 def _create_emulated_device(self): 

237 base_devices = self.dconf["device"].split(",") 

238 emulated_devices = [] 

239 for dev in base_devices: 

240 emulated_path = self._get_emulated_device_path(dev) 

241 self._create_loopdev(dev, emulated_path) 

242 emulated_devices.append(emulated_path) 

243 

244 emulated_devices = ",".join(emulated_devices) 

245 self.dconf["device"] = emulated_devices 

246 

247 def _destroy_emulated_device(self, devices=None): 

248 if devices is None: 

249 devices = self.dconf["device"].split(",") 

250 

251 for dev in devices: 

252 emulated_path = self._get_emulated_device_path(dev) 

253 self._delete_loopdev(dev, emulated_path) 

254 

255if __name__ == '__main__': 255 ↛ 256line 255 didn't jump to line 256, because the condition on line 255 was never true

256 SRCommand.run(LargeBlockSR, DRIVER_INFO) 

257else: 

258 SR.registerSR(LargeBlockSR)