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 except SMBException as exc: 

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

200 self._check_hardlinks() 

201 self.attached = True 

202 

203 def probe(self): 

204 try: 

205 err = "SMBMount" 

206 self.mount(PROBE_MOUNTPOINT) 

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

208 err = "SMBUnMount" 

209 self.unmount(PROBE_MOUNTPOINT, True) 

210 except SMBException as inst: 

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

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

213 raise 

214 

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

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

217 

218 return util.SRtoXML(sr_dict) 

219 

220 def detach(self, sr_uuid): 

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

222 if not self.checkmount(): 

223 return 

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

225 cleanup.abort(self.uuid) 

226 

227 # Change directory to avoid unmount conflicts 

228 os.chdir(SR.MOUNT_BASE) 

229 

230 try: 

231 self.unmount(self.mountpoint, True) 

232 os.unlink(self.path) 

233 except SMBException as exc: 

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

235 

236 self.attached = False 

237 

238 def create(self, sr_uuid, size): 

239 self.__check_license() 

240 

241 if self.checkmount(): 

242 raise xs_errors.XenError('SMBAttached') 

243 

244 try: 

245 self.mount() 

246 except SMBException as exc: 

247 try: 

248 os.rmdir(self.mountpoint) 

249 except: 

250 pass 

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

252 

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

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

255 self.detach(sr_uuid) 

256 raise xs_errors.XenError('SRExists') 

257 else: 

258 try: 

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

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

261 except util.CommandException as inst: 

262 if inst.code != errno.EEXIST: 

263 try: 

264 self.unmount(self.mountpoint, True) 

265 except SMBException: 

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

267 raise xs_errors.XenError( 

268 'SMBCreate', 

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

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

271 ) 

272 self.detach(sr_uuid) 

273 

274 def delete(self, sr_uuid): 

275 # try to remove/delete non VDI contents first 

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

277 try: 

278 if self.checkmount(): 

279 self.detach(sr_uuid) 

280 

281 self.mount() 

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

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

284 self.unmount(self.mountpoint, True) 

285 except util.CommandException as inst: 

286 self.detach(sr_uuid) 

287 if inst.code != errno.ENOENT: 

288 raise xs_errors.XenError('SMBDelete') 

289 

290 def vdi(self, uuid): 

291 return SMBFileVDI(self, uuid) 

292 

293 

294class SMBFileVDI(FileSR.FileVDI): 

295 def attach(self, sr_uuid, vdi_uuid): 

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

297 self.xenstore_data = {} 

298 

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

300 

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

302 

303 def generate_config(self, sr_uuid, vdi_uuid): 

304 util.SMlog("SMBFileVDI.generate_config") 

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

306 raise xs_errors.XenError('VDIUnavailable') 

307 resp = {} 

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

309 resp['sr_uuid'] = sr_uuid 

310 resp['vdi_uuid'] = vdi_uuid 

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

312 resp['command'] = 'vdi_attach_from_config' 

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

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

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

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

317 

318 def attach_from_config(self, sr_uuid, vdi_uuid): 

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

320 also start a tapdisk on the file""" 

321 util.SMlog("SMBFileVDI.attach_from_config") 

322 try: 

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

324 self.sr.attach(sr_uuid) 

325 except: 

326 util.logException("SMBFileVDI.attach_from_config") 

327 raise xs_errors.XenError('SRUnavailable', \ 

328 opterr='Unable to attach from config') 

329 

330 

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

332 SRCommand.run(SMBSR, DRIVER_INFO) 

333else: 

334 SR.registerSR(SMBSR) 

335#