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 if len(base_devices) > 1: 

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

87 

88 for dev in base_devices: 

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

90 if logical_blocksize == "512": 

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

92 

93 try: 

94 self._create_emulated_device() 

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

96 finally: 

97 self._destroy_emulated_device(base_devices) 

98 

99 def delete(self, sr_uuid): 

100 base_devices = self._get_device() 

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

102 

103 self.is_deleting = True 

104 try: 

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

106 except SR.SROSError: 

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

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

109 for dev in base_devices: 

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

111 finally: 

112 self._destroy_emulated_device(base_devices) 

113 self.is_deleting = False 

114 

115 @deviceCheck 

116 def probe(self): 

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

118 return lvutil.srlist_toxml( 

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

120 LARGEBLOCK_PREFIX 

121 ) 

122 

123 def _create_loopdev(self, dev, emulated_path): 

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

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

126 

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

128 os.unlink(emulated_path) 

129 

130 try: 

131 os.symlink(loopdev, emulated_path) 

132 except OSError: 

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

134 

135 def _delete_loopdev(self, dev, emulated_path): 

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

137 os.unlink(emulated_path) 

138 

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

140 dev = os.path.realpath(dev) 

141 loopdevs = self._get_loopdev_from_device(dev) 

142 

143 if loopdevs != None: 

144 try: 

145 for lp in loopdevs: 

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

147 util.pread2(cmd) 

148 except SR.SROSError: 

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

150 else: 

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

152 

153 @staticmethod 

154 def _get_loopdev_from_device(device): 

155 lpdevs = [] 

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

157 if output: 

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

159 line = line.split() 

160 loopdev = line[0] 

161 dev = line[5].strip() 

162 if dev in device: 

163 lpdevs.append(loopdev) 

164 return lpdevs 

165 

166 @staticmethod 

167 def _get_device_from_loopdev(loopdevs): 

168 devices = [] 

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

170 if output: 

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

172 line = line.split() 

173 lpdev = line[0] 

174 dev = line[5] 

175 if lpdev in loopdevs: 

176 devices.append(dev) 

177 return devices 

178 

179 def _get_device_from_vg(self): 

180 devices = [] 

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

182 for line in output: 

183 line = line.split() 

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

185 if os.path.islink(dev): 

186 dev = os.path.realpath(dev) 

187 devices.append(dev) 

188 return devices 

189 

190 def _get_device(self): 

191 vg_device = self._get_device_from_vg() 

192 for dev in vg_device: 

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

194 lpdev = os.path.realpath(dev) 

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

196 vg_device.remove(dev) 

197 vg_device.append(realdev) 

198 

199 return vg_device 

200 

201 def _is_vg_connection_correct(self): 

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

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

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

205 

206 def _redo_vg_connection(self): 

207 """ 

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

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

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

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

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

213 """ 

214 

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

216 try: 

217 lvutil.setActiveVG(self.vgname, False) 

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

219 except util.CommandException as e: 

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

221 

222 

223 @classmethod 

224 def _get_emulated_device_path(cls, dev): 

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

226 

227 def _create_emulated_device(self): 

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

229 emulated_devices = [] 

230 for dev in base_devices: 

231 emulated_path = self._get_emulated_device_path(dev) 

232 self._create_loopdev(dev, emulated_path) 

233 emulated_devices.append(emulated_path) 

234 

235 emulated_devices = ",".join(emulated_devices) 

236 self.dconf["device"] = emulated_devices 

237 

238 def _destroy_emulated_device(self, devices=None): 

239 if devices is None: 

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

241 

242 for dev in devices: 

243 emulated_path = self._get_emulated_device_path(dev) 

244 self._delete_loopdev(dev, emulated_path) 

245 

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

247 SRCommand.run(LargeBlockSR, DRIVER_INFO) 

248else: 

249 SR.registerSR(LargeBlockSR)