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 

21import distutils.util 

22import errno 

23import os 

24import syslog as _syslog 

25import xmlrpc.client 

26from syslog import syslog 

27 

28# careful with the import order here 

29# FileSR has a circular dependency: 

30# FileSR -> blktap2 -> lvutil -> EXTSR -> FileSR 

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

32import SR 

33import SRCommand 

34import FileSR 

35# end of careful 

36import cleanup 

37import util 

38import vhdutil 

39import xs_errors 

40from lock import Lock 

41 

42CAPABILITIES = ["SR_PROBE", "SR_UPDATE", 

43 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH", 

44 "VDI_UPDATE", "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", "VDI_MIRROR", 

45 "VDI_GENERATE_CONFIG", 

46 "VDI_RESET_ON_BOOT/2", "ATOMIC_PAUSE"] 

47 

48CONFIGURATION = [ 

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

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

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

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

53] 

54 

55DRIVER_INFO = { 

56 'name': 'MooseFS VHD', 

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

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

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

60 'driver_version': '1.0', 

61 'required_api_version': '1.0', 

62 'capabilities': CAPABILITIES, 

63 'configuration': CONFIGURATION 

64} 

65 

66DRIVER_CONFIG = {"ATTACH_FROM_CONFIG_WITH_TAPDISK": True} 

67 

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

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

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

71 

72 

73class MooseFSException(Exception): 

74 def __init__(self, errstr): 

75 self.errstr = errstr 

76 

77 

78class MooseFSSR(FileSR.FileSR): 

79 """MooseFS file-based storage""" 

80 

81 DRIVER_TYPE = 'moosefs' 

82 

83 def handles(sr_type): 

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

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

86 

87 handles = staticmethod(handles) 

88 

89 def load(self, sr_uuid): 

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

91 raise xs_errors.XenError( 

92 'SRUnavailable', 

93 opterr='MooseFS Client is not installed!' 

94 ) 

95 

96 self.ops_exclusive = FileSR.OPS_EXCLUSIVE 

97 self.lock = Lock(vhdutil.LOCK_TYPE_SR, self.uuid) 

98 self.sr_vditype = SR.DEFAULT_TAP 

99 self.driver_config = DRIVER_CONFIG 

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

101 raise xs_errors.XenError('ConfigServerMissing') 

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

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

104 self.remotepath = self.rootpath 

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

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

107 self.remoteport = "9421" 

108 else: 

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

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

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

112 else: 

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

114 

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

116 self.subdir = distutils.util.strtobool( 

117 self.sm_config.get('subdir') or '0' 

118 ) 

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 def attach(self, sr_uuid): 

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

184 try: 

185 self.mount() 

186 except MooseFSException as exc: 

187 raise SR.SROSError(12, exc.errstr) 

188 self.attached = True 

189 

190 def probe(self): 

191 try: 

192 self.mount(PROBE_MOUNTPOINT) 

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

194 self.unmount(PROBE_MOUNTPOINT, True) 

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

196 raise 

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

198 sr_dict = {sr_uuid: {} for sr_uuid in sr_list} 

199 return util.SRtoXML(sr_dict) 

200 

201 def detach(self, sr_uuid): 

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

203 return 

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

205 cleanup.abort(sr_uuid) 

206 # Change directory to avoid unmount conflicts 

207 os.chdir(SR.MOUNT_BASE) 

208 self.unmount(self.mountpoint, True) 

209 self.attached = False 

210 

211 def create(self, sr_uuid, size): 

212 if self.checkmount(): 

213 raise SR.SROSError(113, 'MooseFS mount point already attached') 

214 

215 assert self.remotepath == self.rootpath 

216 try: 

217 self.mount() 

218 except MooseFSException as exc: 

219 # noinspection PyBroadException 

220 try: 

221 os.rmdir(self.mountpoint) 

222 except: 

223 # we have no recovery strategy 

224 pass 

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

226 

227 try: 

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

229 if self.subdir is None: 

230 self.subdir = True 

231 else: 

232 self.subdir = distutils.util.strtobool(self.subdir) 

233 

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

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

236 

237 if not self.subdir: 

238 return 

239 

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

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

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

243 raise xs_errors.XenError('SRExists') 

244 else: 

245 try: 

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

247 except util.CommandException as e: 

248 if e.code != errno.EEXIST: 

249 raise MooseFSException( 

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

251 ) 

252 finally: 

253 self.detach(sr_uuid) 

254 

255 def delete(self, sr_uuid): 

256 # try to remove/delete non VDI contents first 

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

258 try: 

259 if self.checkmount(): 

260 self.detach(sr_uuid) 

261 

262 if self.subdir: 

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

264 self.remotepath = self.rootpath 

265 self.attach(sr_uuid) 

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

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

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

269 self.detach(sr_uuid) 

270 except util.CommandException as inst: 

271 self.detach(sr_uuid) 

272 if inst.code != errno.ENOENT: 

273 raise SR.SROSError(114, "Failed to remove MooseFS mount point") 

274 

275 def vdi(self, uuid, loadLocked=False): 

276 return MooseFSFileVDI(self, uuid) 

277 

278 @staticmethod 

279 def _is_moosefs_available(): 

280 import distutils.spawn 

281 return distutils.spawn.find_executable('mfsmount') 

282 

283class MooseFSFileVDI(FileSR.FileVDI): 

284 def attach(self, sr_uuid, vdi_uuid): 

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

286 self.xenstore_data = {} 

287 

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

289 

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

291 

292 def generate_config(self, sr_uuid, vdi_uuid): 

293 util.SMlog("MooseFSFileVDI.generate_config") 

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

295 raise xs_errors.XenError('VDIUnavailable') 

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

297 'sr_uuid': sr_uuid, 

298 'vdi_uuid': vdi_uuid, 

299 'sr_sm_config': self.sr.sm_config, 

300 'command': 'vdi_attach_from_config'} 

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

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

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

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

305 

306 def attach_from_config(self, sr_uuid, vdi_uuid): 

307 try: 

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

309 self.sr.attach(sr_uuid) 

310 except: 

311 util.logException("MooseFSFileVDI.attach_from_config") 

312 raise xs_errors.XenError('SRUnavailable', 

313 opterr='Unable to attach from config') 

314 

315 

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

317 SRCommand.run(MooseFSSR, DRIVER_INFO) 

318else: 

319 SR.registerSR(MooseFSSR)