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 

17import SR 

18from SR import deviceCheck 

19import SRCommand 

20import EXTSR 

21import util 

22import xs_errors 

23import os 

24import re 

25import lvutil 

26 

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

33 

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

35 

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} 

46 

47LARGEBLOCK_PREFIX = "XSLocalLargeBlock-" 

48 

49class LargeBlockSR(EXTSR.EXTSR): 

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

51 

52 DRIVER_TYPE = "largeblock" 

53 LOOP_SECTOR_SIZE = 512 

54 

55 @staticmethod 

56 def handles(srtype): 

57 return srtype == LargeBlockSR.DRIVER_TYPE 

58 

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) 

64 

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) 

73 

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

81 

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

89 

90 try: 

91 self._create_emulated_device() 

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

93 finally: 

94 self._destroy_emulated_device(base_devices) 

95 

96 def delete(self, sr_uuid): 

97 self.dconf["device"] = ",".join(self._get_device()) 

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

99 

100 self.is_deleting = True 

101 try: 

102 self._create_emulated_device() 

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

104 except SR.SROSError as e: 

105 util.SMlog(e) 

106 finally: 

107 self._destroy_emulated_device(base_devices) 

108 self.is_deleting = False 

109 for dev in base_devices: 

110 util.pread2(["pvremove", os.path.realpath(dev)]) 

111 

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 ) 

119 

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

123 

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

125 os.unlink(emulated_path) 

126 

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

131 

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) 

135 

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) 

139 

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

149 

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] 

159 if device in dev: 

160 lpdevs.append(loopdev) 

161 return lpdevs 

162 

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 

175 

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 

186 

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) 

195 

196 return vg_device 

197 

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

202 

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

211 

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

218 

219 

220 @classmethod 

221 def _get_emulated_device_path(cls, dev): 

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

223 

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) 

231 

232 emulated_devices = ",".join(emulated_devices) 

233 self.dconf["device"] = emulated_devices 

234 

235 def _destroy_emulated_device(self, devices=None): 

236 if devices is None: 

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

238 

239 for dev in devices: 

240 emulated_path = self._get_emulated_device_path(dev) 

241 self._delete_loopdev(dev, emulated_path) 

242 

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)