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# FileSR: local-file storage repository 

19 

20import socket 

21 

22import SR 

23import SRCommand 

24import FileSR 

25import util 

26import errno 

27import os 

28import sys 

29import xmlrpc.client 

30import xs_errors 

31import nfs 

32import vhdutil 

33from lock import Lock 

34import cleanup 

35 

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

37 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH", 

38 "VDI_UPDATE", "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", 

39 "VDI_GENERATE_CONFIG", "VDI_MIRROR", 

40 "VDI_RESET_ON_BOOT/2", "ATOMIC_PAUSE", "VDI_CONFIG_CBT", 

41 "VDI_ACTIVATE", "VDI_DEACTIVATE", "THIN_PROVISIONING", "VDI_READ_CACHING"] 

42 

43CONFIGURATION = [['server', 'hostname or IP address of NFS server (required)'], 

44 ['serverpath', 'path on remote server (required)'], 

45 nfs.NFS_VERSION] 

46 

47DRIVER_INFO = { 

48 'name': 'NFS VHD', 

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

50 'vendor': 'Citrix Systems Inc', 

51 'copyright': '(C) 2008 Citrix Systems Inc', 

52 'driver_version': '1.0', 

53 'required_api_version': '1.0', 

54 'capabilities': CAPABILITIES, 

55 'configuration': CONFIGURATION 

56 } 

57 

58DRIVER_CONFIG = {"ATTACH_FROM_CONFIG_WITH_TAPDISK": True} 

59 

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

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

62PROBE_MOUNTPOINT = "probe" 

63NFSPORT = 2049 

64DEFAULT_TRANSPORT = "tcp" 

65PROBEVERSION = 'probeversion' 

66 

67 

68class NFSSR(FileSR.SharedFileSR): 

69 """NFS file-based storage repository""" 

70 

71 def handles(type): 

72 return type == 'nfs' 

73 handles = staticmethod(handles) 

74 

75 def load(self, sr_uuid): 

76 self.ops_exclusive = FileSR.OPS_EXCLUSIVE 

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

78 self.sr_vditype = SR.DEFAULT_TAP 

79 self.driver_config = DRIVER_CONFIG 

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

81 raise xs_errors.XenError('ConfigServerMissing') 

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

83 self.nosubdir = False 

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

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

86 self.other_config = self.session.xenapi.SR.get_other_config(self.sr_ref) 

87 else: 

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

89 self.other_config = self.srcmd.params.get('sr_other_config') or {} 

90 self.nosubdir = self.sm_config.get('nosubdir') == "true" 

91 serverpath = self.dconf.get('serverpath') 

92 if serverpath is not None: 92 ↛ 97line 92 didn't jump to line 97, because the condition on line 92 was never false

93 self.remotepath = os.path.join( 

94 serverpath, 

95 not self.nosubdir and sr_uuid or "" 

96 ) 

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

98 

99 # Handle optional dconf attributes 

100 self.set_transport() 

101 self.nfsversion = nfs.validate_nfsversion(self.dconf.get('nfsversion')) 

102 if 'options' in self.dconf: 

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

104 else: 

105 self.options = '' 

106 

107 def validate_remotepath(self, scan): 

108 serverpath = self.dconf.get('serverpath') 

109 if serverpath is None: 109 ↛ 110line 109 didn't jump to line 110, because the condition on line 109 was never true

110 if scan: 

111 try: 

112 self.scan_exports(self.dconf['server']) 

113 except: 

114 pass 

115 raise xs_errors.XenError('ConfigServerPathMissing') 

116 

117 def check_server(self): 

118 try: 

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

120 sv = nfs.get_supported_nfs_versions(self.remoteserver, self.transport) 

121 if len(sv): 

122 self.nfsversion = sv[0] 

123 else: 

124 if not nfs.check_server_tcp(self.remoteserver, self.transport, self.nfsversion): 124 ↛ 125line 124 didn't jump to line 125, because the condition on line 124 was never true

125 raise nfs.NfsException("Unsupported NFS version: %s" % self.nfsversion) 

126 

127 except nfs.NfsException as exc: 

128 raise xs_errors.XenError('NFSVersion', 

129 opterr=exc.errstr) 

130 

131 def mount(self, mountpoint, remotepath, timeout=None, retrans=None): 

132 try: 

133 nfs.soft_mount( 

134 mountpoint, self.remoteserver, remotepath, self.transport, 

135 useroptions=self.options, timeout=timeout, 

136 nfsversion=self.nfsversion, retrans=retrans) 

137 except nfs.NfsException as exc: 

138 raise xs_errors.XenError('NFSMount', opterr=exc.errstr) 

139 

140 def attach(self, sr_uuid): 

141 if not self._checkmount(): 

142 try: 

143 self.validate_remotepath(False) 

144 util._testHost(self.dconf['server'], NFSPORT, 'NFSTarget') 

145 self.mount_remotepath(sr_uuid) 

146 self._check_writable() 

147 self._check_hardlinks() 

148 except: 

149 if self._checkmount(): 

150 nfs.unmount(self.path, True) 

151 raise 

152 self.attached = True 

153 

154 def mount_remotepath(self, sr_uuid): 

155 if not self._checkmount(): 155 ↛ exitline 155 didn't return from function 'mount_remotepath', because the condition on line 155 was never false

156 # FIXME: What is the purpose of this check_server? 

157 # It doesn't stop us from continuing if the server 

158 # doesn't support the requested version. We fail 

159 # in mount instead 

160 self.check_server() 

161 # Extract timeout and retrans values, if any 

162 io_timeout = nfs.get_nfs_timeout(self.other_config) 

163 io_retrans = nfs.get_nfs_retrans(self.other_config) 

164 self.mount(self.path, self.remotepath, 

165 timeout=io_timeout, retrans=io_retrans) 

166 

167 def probe(self): 

168 # Verify NFS target and port 

169 util._testHost(self.dconf['server'], NFSPORT, 'NFSTarget') 

170 

171 self.validate_remotepath(True) 

172 self.check_server() 

173 

174 temppath = os.path.join(SR.MOUNT_BASE, PROBE_MOUNTPOINT) 

175 

176 self.mount(temppath, self.remotepath) 

177 try: 

178 return nfs.scan_srlist(temppath, self.transport, self.dconf) 

179 finally: 

180 try: 

181 nfs.unmount(temppath, True) 

182 except: 

183 pass 

184 

185 def detach(self, sr_uuid): 

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

187 if not self._checkmount(): 187 ↛ 189line 187 didn't jump to line 189, because the condition on line 187 was never false

188 return 

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

190 cleanup.abort(self.uuid) 

191 

192 # Change directory to avoid unmount conflicts 

193 os.chdir(SR.MOUNT_BASE) 

194 

195 try: 

196 nfs.unmount(self.path, True) 

197 except nfs.NfsException as exc: 

198 raise xs_errors.XenError('NFSUnMount', opterr=exc.errstr) 

199 

200 self.attached = False 

201 

202 def create(self, sr_uuid, size): 

203 util._testHost(self.dconf['server'], NFSPORT, 'NFSTarget') 

204 self.validate_remotepath(True) 

205 if self._checkmount(): 205 ↛ 206line 205 didn't jump to line 206, because the condition on line 205 was never true

206 raise xs_errors.XenError('NFSAttached') 

207 

208 # Set the target path temporarily to the base dir 

209 # so that we can create the target SR directory 

210 self.remotepath = self.dconf['serverpath'] 

211 try: 

212 self.mount_remotepath(sr_uuid) 

213 except Exception as exn: 

214 try: 

215 os.rmdir(self.path) 

216 except: 

217 pass 

218 raise 

219 

220 if not self.nosubdir: 220 ↛ 235line 220 didn't jump to line 235, because the condition on line 220 was never false

221 newpath = os.path.join(self.path, sr_uuid) 

222 if util.ioretry(lambda: util.pathexists(newpath)): 222 ↛ 223line 222 didn't jump to line 223, because the condition on line 222 was never true

223 if len(util.ioretry(lambda: util.listdir(newpath))) != 0: 

224 self.detach(sr_uuid) 

225 raise xs_errors.XenError('SRExists') 

226 else: 

227 try: 

228 util.ioretry(lambda: util.makedirs(newpath)) 

229 except util.CommandException as inst: 

230 if inst.code != errno.EEXIST: 

231 self.detach(sr_uuid) 

232 raise xs_errors.XenError('NFSCreate', 

233 opterr='remote directory creation error is %d' 

234 % inst.code) 

235 self.detach(sr_uuid) 

236 

237 def delete(self, sr_uuid): 

238 # try to remove/delete non VDI contents first 

239 super(NFSSR, self).delete(sr_uuid) 

240 try: 

241 if self._checkmount(): 

242 self.detach(sr_uuid) 

243 

244 # Set the target path temporarily to the base dir 

245 # so that we can remove the target SR directory 

246 self.remotepath = self.dconf['serverpath'] 

247 self.mount_remotepath(sr_uuid) 

248 if not self.nosubdir: 

249 newpath = os.path.join(self.path, sr_uuid) 

250 if util.ioretry(lambda: util.pathexists(newpath)): 

251 util.ioretry(lambda: os.rmdir(newpath)) 

252 self.detach(sr_uuid) 

253 except util.CommandException as inst: 

254 self.detach(sr_uuid) 

255 if inst.code != errno.ENOENT: 

256 raise xs_errors.XenError('NFSDelete') 

257 

258 def vdi(self, uuid): 

259 return NFSFileVDI(self, uuid) 

260 

261 def scan_exports(self, target): 

262 util.SMlog("scanning2 (target=%s)" % target) 

263 dom = nfs.scan_exports(target, self.transport) 

264 print(dom.toprettyxml(), file=sys.stderr) 

265 

266 def set_transport(self): 

267 self.transport = DEFAULT_TRANSPORT 

268 if self.remoteserver is None: 

269 # CA-365359: on_slave.is_open sends a dconf with {"server": None} 

270 return 

271 

272 try: 

273 addr_info = socket.getaddrinfo(self.remoteserver, 0)[0] 

274 except Exception: 

275 return 

276 

277 use_ipv6 = addr_info[0] == socket.AF_INET6 

278 if use_ipv6: 278 ↛ 280line 278 didn't jump to line 280, because the condition on line 278 was never false

279 self.transport = 'tcp6' 

280 if 'useUDP' in self.dconf and self.dconf['useUDP'] == 'true': 280 ↛ 281line 280 didn't jump to line 281, because the condition on line 280 was never true

281 self.transport = 'udp6' if use_ipv6 else 'udp' 

282 

283 

284class NFSFileVDI(FileSR.FileVDI): 

285 def attach(self, sr_uuid, vdi_uuid): 

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

287 self.xenstore_data = {} 

288 

289 self.xenstore_data["storage-type"] = "nfs" 

290 

291 return super(NFSFileVDI, self).attach(sr_uuid, vdi_uuid) 

292 

293 def generate_config(self, sr_uuid, vdi_uuid): 

294 util.SMlog("NFSFileVDI.generate_config") 

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

296 raise xs_errors.XenError('VDIUnavailable') 

297 resp = {} 

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

299 resp['sr_uuid'] = sr_uuid 

300 resp['vdi_uuid'] = vdi_uuid 

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

302 resp['sr_other_config'] = self.sr.other_config 

303 resp['command'] = 'vdi_attach_from_config' 

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

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

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

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

308 

309 def attach_from_config(self, sr_uuid, vdi_uuid): 

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

311 also start a tapdisk on the file""" 

312 util.SMlog("NFSFileVDI.attach_from_config") 

313 try: 

314 self.sr.attach(sr_uuid) 

315 except: 

316 util.logException("NFSFileVDI.attach_from_config") 

317 raise xs_errors.XenError('SRUnavailable', \ 

318 opterr='Unable to attach from config') 

319 

320 

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

322 SRCommand.run(NFSSR, DRIVER_INFO) 

323else: 

324 SR.registerSR(NFSSR)