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/python3 

2# 

3# Copyright (C) Citrix Systems Inc. 

4# 

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

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

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

8# 

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 Lesser General Public License for more details. 

13# 

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

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

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

17# 

18# SMBSR: SMB filesystem based storage repository 

19 

20import SR 

21import SRCommand 

22import FileSR 

23import util 

24import errno 

25import os 

26import xmlrpc.client 

27import xs_errors 

28import vhdutil 

29from lock import Lock 

30import cleanup 

31import cifutils 

32 

33CAPABILITIES = ["SR_PROBE", "SR_UPDATE", "SR_CACHING", 

34 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH", 

35 "VDI_UPDATE", "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", "VDI_MIRROR", 

36 "VDI_GENERATE_CONFIG", 

37 "VDI_RESET_ON_BOOT/2", "ATOMIC_PAUSE", "VDI_CONFIG_CBT", 

38 "VDI_ACTIVATE", "VDI_DEACTIVATE", "THIN_PROVISIONING", "VDI_READ_CACHING"] 

39 

40CONFIGURATION = [['server', 'Full path to share root on SMB server (required)'], \ 

41 ['username', 'The username to be used during SMB authentication'], \ 

42 ['password', 'The password to be used during SMB authentication']] 

43 

44DRIVER_INFO = { 

45 'name': 'SMB VHD', 

46 'description': 'SR plugin which stores disks as VHD files on a remote SMB filesystem', 

47 'vendor': 'Citrix Systems Inc', 

48 'copyright': '(C) 2015 Citrix Systems Inc', 

49 'driver_version': '1.0', 

50 'required_api_version': '1.0', 

51 'capabilities': CAPABILITIES, 

52 'configuration': CONFIGURATION 

53 } 

54 

55DRIVER_CONFIG = {"ATTACH_FROM_CONFIG_WITH_TAPDISK": True} 

56 

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

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

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

60 

61 

62class SMBException(Exception): 

63 def __init__(self, errstr): 

64 self.errstr = errstr 

65 

66 

67# server = //smb-server/vol1 - ie the export path on the SMB server 

68# mountpoint = /var/run/sr-mount/SMB/<smb_server_name>/<share_name>/uuid 

69# linkpath = mountpoint/uuid - path to SR directory on share 

70# path = /var/run/sr-mount/uuid - symlink to SR directory on share 

71class SMBSR(FileSR.SharedFileSR): 

72 """SMB file-based storage repository""" 

73 

74 def handles(type): 

75 return type == 'smb' 

76 handles = staticmethod(handles) 

77 

78 def load(self, sr_uuid): 

79 self.ops_exclusive = FileSR.OPS_EXCLUSIVE 

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

81 self.sr_vditype = SR.DEFAULT_TAP 

82 self.driver_config = DRIVER_CONFIG 

83 if 'server' not in self.dconf: 83 ↛ 84line 83 didn't jump to line 84, because the condition on line 83 was never true

84 raise xs_errors.XenError('ConfigServerMissing') 

85 self.remoteserver = self.dconf['server'] 

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

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

88 else: 

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

90 self.mountpoint = os.path.join(SR.MOUNT_BASE, 'SMB', self.__extract_server(), sr_uuid) 

91 self.linkpath = os.path.join(self.mountpoint, 

92 sr_uuid or "") 

93 # Remotepath is the absolute path inside a share that is to be mounted 

94 # For a SMB SR, only the root can be mounted. 

95 self.remotepath = '' 

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

97 self._check_o_direct() 

98 

99 def checkmount(self): 

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

101 util.ismount(self.mountpoint)) and \ 

102 util.pathexists(self.linkpath))) 

103 

104 def makeMountPoint(self, mountpoint): 

105 """Mount the remote SMB export at 'mountpoint'""" 

106 if mountpoint is None: 

107 mountpoint = self.mountpoint 

108 elif not util.is_string(mountpoint) or mountpoint == "": 108 ↛ 111line 108 didn't jump to line 111, because the condition on line 108 was never false

109 raise SMBException("mountpoint not a string object") 

110 

111 try: 

112 if not util.ioretry(lambda: util.isdir(mountpoint)): 112 ↛ 113,   112 ↛ 1172 missed branches: 1) line 112 didn't jump to line 113, because the condition on line 112 was never true, 2) line 112 didn't jump to line 117, because the condition on line 112 was never false

113 util.ioretry(lambda: util.makedirs(mountpoint)) 

114 except util.CommandException as inst: 

115 raise SMBException("Failed to make directory: code is %d" % 

116 inst.code) 

117 return mountpoint 

118 

119 def mount(self, mountpoint=None): 

120 

121 mountpoint = self.makeMountPoint(mountpoint) 

122 

123 new_env, domain = cifutils.getCIFCredentials(self.dconf, self.session) 

124 

125 options = self.getMountOptions(domain) 

126 if options: 126 ↛ 129line 126 didn't jump to line 129, because the condition on line 126 was never false

127 options = ",".join(str(x) for x in options if x) 

128 

129 try: 

130 

131 util.ioretry(lambda: 

132 util.pread(["mount.cifs", self.remoteserver, 

133 mountpoint, "-o", options], new_env=new_env), 

134 errlist=[errno.EPIPE, errno.EIO], 

135 maxretry=2, nofail=True) 

136 except util.CommandException as inst: 

137 raise SMBException("mount failed with return code %d" % inst.code) 

138 

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

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

141 try: 

142 util.listdir(mountpoint) 

143 except util.CommandException: 

144 try: 

145 self.unmount(mountpoint, True) 

146 except SMBException: 

147 util.logException('SMBSR.unmount()') 

148 raise SMBException("Permission denied. " 

149 "Please check user privileges.") 

150 

151 def getMountOptions(self, domain): 

152 """Creates option string based on parameters provided""" 

153 options = ['cache=loose', 

154 'vers=3.0', 

155 'actimeo=0' 

156 ] 

157 

158 if domain: 

159 options.append('domain=' + domain) 

160 

161 if not cifutils.containsCredentials(self.dconf): 161 ↛ 163line 161 didn't jump to line 163, because the condition on line 161 was never true

162 # No login details provided. 

163 options.append('guest') 

164 

165 return options 

166 

167 def unmount(self, mountpoint, rmmountpoint): 

168 """Unmount the remote SMB export at 'mountpoint'""" 

169 try: 

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

171 except util.CommandException as inst: 

172 raise SMBException("umount failed with return code %d" % inst.code) 

173 

174 if rmmountpoint: 

175 try: 

176 os.rmdir(mountpoint) 

177 except OSError as inst: 

178 raise SMBException("rmdir failed with error '%s'" % inst.strerror) 

179 

180 def __extract_server(self): 

181 return self.remoteserver[2:].replace('\\', '/') 

182 

183 def __check_license(self): 

184 """Raises an exception if SMB is not licensed.""" 

185 if self.session is None: 

186 raise xs_errors.XenError('NoSMBLicense', 

187 'No session object to talk to XAPI') 

188 restrictions = util.get_pool_restrictions(self.session) 

189 if 'restrict_cifs' in restrictions and \ 

190 restrictions['restrict_cifs'] == "true": 

191 raise xs_errors.XenError('NoSMBLicense') 

192 

193 def attach(self, sr_uuid): 

194 if not self.checkmount(): 

195 try: 

196 self.mount() 

197 os.symlink(self.linkpath, self.path) 

198 self._check_writable() 

199 self._check_hardlinks() 

200 except SMBException as exc: 

201 raise xs_errors.XenError('SMBMount', opterr=exc.errstr) 

202 except: 

203 if util.pathexists(self.path): 

204 os.unlink(self.path) 

205 if self.checkmount(): 

206 self.unmount(self.mountpoint, True) 

207 raise 

208 

209 self.attached = True 

210 

211 def probe(self): 

212 err = "SMBMount" 

213 try: 

214 self.mount(PROBE_MOUNTPOINT) 

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

216 err = "SMBUnMount" 

217 self.unmount(PROBE_MOUNTPOINT, True) 

218 except SMBException as inst: 

219 raise xs_errors.XenError(err, opterr=inst.errstr) 

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

221 raise 

222 

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

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

225 

226 return util.SRtoXML(sr_dict) 

227 

228 def detach(self, sr_uuid): 

229 """Detach the SR: Unmounts and removes the mountpoint""" 

230 if not self.checkmount(): 

231 return 

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

233 cleanup.abort(self.uuid) 

234 

235 # Change directory to avoid unmount conflicts 

236 os.chdir(SR.MOUNT_BASE) 

237 

238 try: 

239 self.unmount(self.mountpoint, True) 

240 os.unlink(self.path) 

241 except SMBException as exc: 

242 raise xs_errors.XenError('SMBUnMount', opterr=exc.errstr) 

243 

244 self.attached = False 

245 

246 def create(self, sr_uuid, size): 

247 self.__check_license() 

248 

249 if self.checkmount(): 

250 raise xs_errors.XenError('SMBAttached') 

251 

252 try: 

253 self.mount() 

254 except SMBException as exc: 

255 try: 

256 os.rmdir(self.mountpoint) 

257 except: 

258 pass 

259 raise xs_errors.XenError('SMBMount', opterr=exc.errstr) 

260 

261 if util.ioretry(lambda: util.pathexists(self.linkpath)): 

262 if len(util.ioretry(lambda: util.listdir(self.linkpath))) != 0: 

263 self.detach(sr_uuid) 

264 raise xs_errors.XenError('SRExists') 

265 else: 

266 try: 

267 util.ioretry(lambda: util.makedirs(self.linkpath)) 

268 os.symlink(self.linkpath, self.path) 

269 except util.CommandException as inst: 

270 if inst.code != errno.EEXIST: 

271 try: 

272 self.unmount(self.mountpoint, True) 

273 except SMBException: 

274 util.logException('SMBSR.unmount()') 

275 raise xs_errors.XenError( 

276 'SMBCreate', 

277 opterr="remote directory creation error: {}" 

278 .format(os.strerror(inst.code)) 

279 ) 

280 self.detach(sr_uuid) 

281 

282 def delete(self, sr_uuid): 

283 # try to remove/delete non VDI contents first 

284 super(SMBSR, self).delete(sr_uuid) 

285 try: 

286 if self.checkmount(): 

287 self.detach(sr_uuid) 

288 

289 self.mount() 

290 if util.ioretry(lambda: util.pathexists(self.linkpath)): 

291 util.ioretry(lambda: os.rmdir(self.linkpath)) 

292 self.unmount(self.mountpoint, True) 

293 except util.CommandException as inst: 

294 self.detach(sr_uuid) 

295 if inst.code != errno.ENOENT: 

296 raise xs_errors.XenError('SMBDelete') 

297 

298 def vdi(self, uuid): 

299 return SMBFileVDI(self, uuid) 

300 

301 

302class SMBFileVDI(FileSR.FileVDI): 

303 def attach(self, sr_uuid, vdi_uuid): 

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

305 self.xenstore_data = {} 

306 

307 self.xenstore_data["storage-type"] = "smb" 

308 

309 return super(SMBFileVDI, self).attach(sr_uuid, vdi_uuid) 

310 

311 def generate_config(self, sr_uuid, vdi_uuid): 

312 util.SMlog("SMBFileVDI.generate_config") 

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

314 raise xs_errors.XenError('VDIUnavailable') 

315 resp = {} 

316 resp['device_config'] = self.sr.dconf 

317 resp['sr_uuid'] = sr_uuid 

318 resp['vdi_uuid'] = vdi_uuid 

319 resp['sr_sm_config'] = self.sr.sm_config 

320 resp['command'] = 'vdi_attach_from_config' 

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

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

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

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

325 

326 def attach_from_config(self, sr_uuid, vdi_uuid): 

327 """Used for HA State-file only. Will not just attach the VDI but 

328 also start a tapdisk on the file""" 

329 util.SMlog("SMBFileVDI.attach_from_config") 

330 try: 

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

332 self.sr.attach(sr_uuid) 

333 except: 

334 util.logException("SMBFileVDI.attach_from_config") 

335 raise xs_errors.XenError('SRUnavailable', \ 

336 opterr='Unable to attach from config') 

337 

338 

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

340 SRCommand.run(SMBSR, DRIVER_INFO) 

341else: 

342 SR.registerSR(SMBSR) 

343#