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# Original work copyright (C) Citrix systems 

4# Modified work copyright (C) Tappest sp. z o.o., Vates SAS and XCP-ng community 

5# 

6# This program is free software; you can redistribute it and/or modify 

7# it under the terms of the GNU Lesser General Public License as published 

8# by the Free Software Foundation; version 2.1 only. 

9# 

10# This program is distributed in the hope that it will be useful, 

11# but WITHOUT ANY WARRANTY; without even the implied warranty of 

12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

13# GNU Lesser General Public License for more details. 

14# 

15# You should have received a copy of the GNU Lesser General Public License 

16# along with this program; if not, write to the Free Software Foundation, Inc., 

17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 

18# 

19# MooseFSSR: Based on CEPHFSSR and FileSR, mounts MooseFS share 

20 

21from sm_typing import override 

22 

23import errno 

24import os 

25import syslog as _syslog 

26import xmlrpc.client 

27from syslog import syslog 

28 

29# careful with the import order here 

30# FileSR has a circular dependency: 

31# FileSR -> blktap2 -> lvutil -> EXTSR -> FileSR 

32# importing in this order seems to avoid triggering the issue. 

33import SR 

34import SRCommand 

35import FileSR 

36# end of careful 

37import VDI 

38import cleanup 

39import lock 

40import util 

41import xs_errors 

42 

43CAPABILITIES = ["SR_PROBE", "SR_UPDATE", 

44 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH", 

45 "VDI_UPDATE", "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", "VDI_MIRROR", 

46 "VDI_GENERATE_CONFIG", 

47 "VDI_RESET_ON_BOOT/2", "ATOMIC_PAUSE"] 

48 

49CONFIGURATION = [ 

50 ['masterhost', 'MooseFS Master Server hostname or IP address (required, e.g.: "mfsmaster.local.lan" or "10.10.10.1")'], 

51 ['masterport', 'MooseFS Master Server port, default: 9421'], 

52 ['rootpath', 'MooseFS path (required, e.g.: "/")'], 

53 ['options', 'MooseFS Client additional options (e.g.: "mfspassword=PASSWORD,mfstimeout=300")'] 

54] 

55 

56DRIVER_INFO = { 

57 'name': 'MooseFS VHD and QCOW2', 

58 'description': 'SR plugin which stores disks as VHD and QCOW2 files on a MooseFS storage', 

59 'vendor': 'Tappest sp. z o.o.', 

60 'copyright': '(C) 2021 Tappest sp. z o.o.', 

61 'driver_version': '1.0', 

62 'required_api_version': '1.0', 

63 'capabilities': CAPABILITIES, 

64 'configuration': CONFIGURATION 

65} 

66 

67DRIVER_CONFIG = {"ATTACH_FROM_CONFIG_WITH_TAPDISK": True} 

68 

69# The mountpoint for the directory when performing an sr_probe. All probes 

70# are guaranteed to be serialised by xapi, so this single mountpoint is fine. 

71PROBE_MOUNTPOINT = os.path.join(SR.MOUNT_BASE, "probe") 

72 

73 

74class MooseFSException(Exception): 

75 def __init__(self, errstr): 

76 self.errstr = errstr 

77 

78 

79class MooseFSSR(FileSR.FileSR): 

80 """MooseFS file-based storage""" 

81 

82 DRIVER_TYPE = 'moosefs' 

83 

84 @override 

85 @staticmethod 

86 def handles(sr_type) -> bool: 

87 # fudge, because the parent class (FileSR) checks for smb to alter its behavior 

88 return sr_type == MooseFSSR.DRIVER_TYPE or sr_type == 'smb' 

89 

90 @override 

91 def load(self, sr_uuid) -> None: 

92 if not self._is_moosefs_available(): 92 ↛ 93line 92 didn't jump to line 93, because the condition on line 92 was never true

93 raise xs_errors.XenError( 

94 'SRUnavailable', 

95 opterr='MooseFS Client is not installed!' 

96 ) 

97 

98 self.ops_exclusive = FileSR.OPS_EXCLUSIVE 

99 self.lock = lock.Lock(lock.LOCK_TYPE_SR, self.uuid) 

100 self.sr_vditype = SR.DEFAULT_TAP 

101 self.driver_config = DRIVER_CONFIG 

102 if 'masterhost' not in self.dconf: 102 ↛ 103line 102 didn't jump to line 103, because the condition on line 102 was never true

103 raise xs_errors.XenError('ConfigServerMissing') 

104 self.remoteserver = self.dconf['masterhost'] 

105 self.rootpath = self.dconf['rootpath'] 

106 self.remotepath = self.rootpath 

107 # if masterport is not specified, use default: 9421 

108 if 'masterport' not in self.dconf: 108 ↛ 111line 108 didn't jump to line 111, because the condition on line 108 was never false

109 self.remoteport = "9421" 

110 else: 

111 self.remoteport = self.dconf['masterport'] 

112 if self.sr_ref and self.session is not None: 112 ↛ 113line 112 didn't jump to line 113, because the condition on line 112 was never true

113 self.sm_config = self.session.xenapi.SR.get_sm_config(self.sr_ref) 

114 else: 

115 self.sm_config = self.srcmd.params.get('sr_sm_config') or {} 

116 

117 if self.srcmd.cmd != 'sr_create': 117 ↛ 122line 117 didn't jump to line 122, because the condition on line 117 was never false

118 self.subdir = util.strtobool(self.sm_config.get('subdir')) 

119 if self.subdir: 119 ↛ 120line 119 didn't jump to line 120, because the condition on line 119 was never true

120 self.remotepath = os.path.join(self.remotepath, sr_uuid) 

121 

122 self.attached = False 

123 self.path = os.path.join(SR.MOUNT_BASE, sr_uuid) 

124 self.mountpoint = self.path 

125 self.linkpath = self.path 

126 self._check_o_direct() 

127 

128 def checkmount(self): 

129 return util.ioretry(lambda: ((util.pathexists(self.mountpoint) and 

130 util.ismount(self.mountpoint)))) 

131 

132 def mount(self, mountpoint=None): 

133 """Mount MooseFS share at 'mountpoint'""" 

134 if mountpoint is None: 134 ↛ 136line 134 didn't jump to line 136, because the condition on line 134 was never false

135 mountpoint = self.mountpoint 

136 elif not util.is_string(mountpoint) or mountpoint == "": 

137 raise MooseFSException("Mountpoint is not a string object") 

138 

139 try: 

140 if not util.ioretry(lambda: util.isdir(mountpoint)): 140 ↛ 145line 140 didn't jump to line 145, because the condition on line 140 was never false

141 util.ioretry(lambda: util.makedirs(mountpoint)) 141 ↛ 145line 141 didn't jump to line 145

142 except util.CommandException as inst: 

143 raise MooseFSException("Failed to make directory: code is %d" % inst.code) 

144 

145 try: 

146 options = [] 

147 if 'options' in self.dconf: 

148 options.append(self.dconf['options']) 

149 if options: 

150 options = ['-o', ','.join(options)] 

151 remote = '{}:{}:{}'.format( 

152 self.remoteserver, self.remoteport, self.remotepath 

153 ) 

154 command = ["mount", '-t', 'moosefs', remote, mountpoint] + options 

155 util.ioretry(lambda: util.pread(command), errlist=[errno.EPIPE, errno.EIO], maxretry=2, nofail=True) 

156 except util.CommandException as inst: 

157 syslog(_syslog.LOG_ERR, 'MooseFS mount failed ' + inst.__str__()) 

158 raise MooseFSException("Mount failed with return code %d" % inst.code) 

159 

160 # Sanity check to ensure that the user has at least RO access to the 

161 # mounted share. Windows sharing and security settings can be tricky. 

162 try: 

163 util.listdir(mountpoint) 

164 except util.CommandException: 

165 try: 

166 self.unmount(mountpoint, True) 

167 except MooseFSException: 

168 util.logException('MooseFSSR.unmount()') 

169 raise MooseFSException("Permission denied. Please check user privileges.") 

170 

171 def unmount(self, mountpoint, rmmountpoint): 

172 try: 

173 util.pread(["umount", mountpoint]) 

174 except util.CommandException as inst: 

175 raise MooseFSException("Command umount failed with return code %d" % inst.code) 

176 if rmmountpoint: 

177 try: 

178 os.rmdir(mountpoint) 

179 except OSError as inst: 

180 raise MooseFSException("Command rmdir failed with error '%s'" % inst.strerror) 

181 

182 @override 

183 def attach(self, sr_uuid) -> None: 

184 if not self.checkmount(): 184 ↛ 185line 184 didn't jump to line 185, because the condition on line 184 was never true

185 try: 

186 self.mount() 

187 except MooseFSException as exc: 

188 raise xs_errors.SROSError(12, exc.errstr) 

189 self.attached = True 

190 

191 @override 

192 def probe(self) -> str: 

193 try: 

194 self.mount(PROBE_MOUNTPOINT) 

195 sr_list = filter(util.match_uuid, util.listdir(PROBE_MOUNTPOINT)) 

196 self.unmount(PROBE_MOUNTPOINT, True) 

197 except (util.CommandException, xs_errors.XenError): 

198 raise 

199 # Create a dictionary from the SR uuids to feed SRtoXML() 

200 return util.SRtoXML({sr_uuid: {} for sr_uuid in sr_list}) 

201 

202 @override 

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

204 if not self.checkmount(): 204 ↛ 206line 204 didn't jump to line 206, because the condition on line 204 was never false

205 return 

206 util.SMlog("Aborting GC/coalesce") 

207 cleanup.abort(sr_uuid) 

208 # Change directory to avoid unmount conflicts 

209 os.chdir(SR.MOUNT_BASE) 

210 self.unmount(self.mountpoint, True) 

211 self.attached = False 

212 

213 @override 

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

215 if self.checkmount(): 

216 raise xs_errors.SROSError(113, 'MooseFS mount point already attached') 

217 

218 assert self.remotepath == self.rootpath 

219 try: 

220 self.mount() 

221 except MooseFSException as exc: 

222 # noinspection PyBroadException 

223 try: 

224 os.rmdir(self.mountpoint) 

225 except: 

226 # we have no recovery strategy 

227 pass 

228 raise xs_errors.SROSError(111, "MooseFS mount error [opterr=%s]" % exc.errstr) 

229 

230 try: 

231 subdir = self.sm_config.get('subdir') 

232 if subdir is None: 

233 self.subdir = True 

234 else: 

235 self.subdir = util.strtobool(subdir) 

236 

237 self.sm_config['subdir'] = str(self.subdir) 

238 self.session.xenapi.SR.set_sm_config(self.sr_ref, self.sm_config) 

239 

240 if not self.subdir: 

241 return 

242 

243 subdir = os.path.join(self.mountpoint, sr_uuid) 

244 if util.ioretry(lambda: util.pathexists(subdir)): 

245 if util.ioretry(lambda: util.isdir(subdir)): 

246 raise xs_errors.XenError('SRExists') 

247 else: 

248 try: 

249 util.ioretry(lambda: util.makedirs(subdir)) 

250 except util.CommandException as e: 

251 if e.code != errno.EEXIST: 

252 raise MooseFSException( 

253 'Failed to create SR subdir: {}'.format(e) 

254 ) 

255 finally: 

256 self.detach(sr_uuid) 

257 

258 @override 

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

260 # try to remove/delete non VDI contents first 

261 super(MooseFSSR, self).delete(sr_uuid) 

262 try: 

263 if self.checkmount(): 

264 self.detach(sr_uuid) 

265 

266 if self.subdir: 

267 # Mount using rootpath (<root>) instead of <root>/<sr_uuid>. 

268 self.remotepath = self.rootpath 

269 self.attach(sr_uuid) 

270 subdir = os.path.join(self.mountpoint, sr_uuid) 

271 if util.ioretry(lambda: util.pathexists(subdir)): 

272 util.ioretry(lambda: os.rmdir(subdir)) 

273 self.detach(sr_uuid) 

274 except util.CommandException as inst: 

275 self.detach(sr_uuid) 

276 if inst.code != errno.ENOENT: 

277 raise xs_errors.SROSError(114, "Failed to remove MooseFS mount point") 

278 

279 @override 

280 def vdi(self, uuid) -> VDI.VDI: 

281 return MooseFSFileVDI(self, uuid) 

282 

283 @staticmethod 

284 def _is_moosefs_available(): 

285 return util.find_executable('mfsmount') 

286 

287class MooseFSFileVDI(FileSR.FileVDI): 

288 @override 

289 def attach(self, sr_uuid, vdi_uuid) -> str: 

290 if not hasattr(self, 'xenstore_data'): 

291 self.xenstore_data = {} 

292 

293 self.xenstore_data['storage-type'] = MooseFSSR.DRIVER_TYPE 

294 

295 return super(MooseFSFileVDI, self).attach(sr_uuid, vdi_uuid) 

296 

297 @override 

298 def generate_config(self, sr_uuid, vdi_uuid) -> str: 

299 util.SMlog("MooseFSFileVDI.generate_config") 

300 if not util.pathexists(self.path): 

301 raise xs_errors.XenError('VDIUnavailable') 

302 resp = {'device_config': self.sr.dconf, 

303 'sr_uuid': sr_uuid, 

304 'vdi_uuid': vdi_uuid, 

305 'sr_sm_config': self.sr.sm_config, 

306 'command': 'vdi_attach_from_config'} 

307 # Return the 'config' encoded within a normal XMLRPC response so that 

308 # we can use the regular response/error parsing code. 

309 config = xmlrpc.client.dumps(tuple([resp]), "vdi_attach_from_config") 

310 return xmlrpc.client.dumps((config,), "", True) 

311 

312 @override 

313 def attach_from_config(self, sr_uuid, vdi_uuid) -> str: 

314 try: 

315 if not util.pathexists(self.sr.path): 

316 return self.sr.attach(sr_uuid) 

317 except: 

318 util.logException("MooseFSFileVDI.attach_from_config") 

319 raise xs_errors.XenError('SRUnavailable', 

320 opterr='Unable to attach from config') 

321 return '' 

322 

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

324 SRCommand.run(MooseFSSR, DRIVER_INFO) 

325else: 

326 SR.registerSR(MooseFSSR)