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 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) 

77 

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() 

86 

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

93 

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)) 

98 

99 try: 

100 self._create_emulated_device() 

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

102 finally: 

103 self._destroy_emulated_device(base_devices) 

104 

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)) 

109 

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 

121 

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 ) 

130 

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() 

134 

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

136 os.unlink(emulated_path) 

137 

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)) 

142 

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) 

146 

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) 

150 

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)) 

160 

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 

173 

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 

186 

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 

197 

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) 

206 

207 return vg_device 

208 

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

217 

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)) 

224 

225 

226 @classmethod 

227 def _get_emulated_device_path(cls, dev): 

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

229 

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) 

237 

238 emulated_devices = ",".join(emulated_devices) 

239 self.dconf["device"] = emulated_devices 

240 

241 def _destroy_emulated_device(self, devices=None): 

242 if devices is None: 

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

244 

245 for dev in devices: 

246 emulated_path = self._get_emulated_device_path(dev) 

247 self._delete_loopdev(dev, emulated_path) 

248 

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)