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# Copyright (C) Citrix Systems Inc. 

2# 

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

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

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

6# 

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

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

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

10# GNU Lesser General Public License for more details. 

11# 

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

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

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

15# 

16# nfs.py: NFS related utility functions 

17 

18import util 

19import errno 

20import os 

21import xml.dom.minidom 

22import time 

23# The algorithm for tcp and udp (at least in the linux kernel) for 

24# NFS timeout on softmounts is as follows: 

25# 

26# UDP: 

27# As long as the request wasn't started more than timeo * (2 ^ retrans) 

28# in the past, keep doubling the timeout. 

29# 

30# TCP: 

31# As long as the request wasn't started more than timeo * (1 + retrans) 

32# in the past, keep increaing the timeout by timeo. 

33# 

34# The time when the retrans may retry has been made will be: 

35# For udp: timeo * (2 ^ retrans * 2 - 1) 

36# For tcp: timeo * n! where n is the smallest n for which n! > 1 + retrans 

37# 

38# thus for retrans=1, timeo can be the same for both tcp and udp, 

39# because the first doubling (timeo*2) is the same as the first increment 

40# (timeo+timeo). 

41 

42RPCINFO_BIN = "/usr/sbin/rpcinfo" 

43SHOWMOUNT_BIN = "/usr/sbin/showmount" 

44NFS_STAT = "/usr/sbin/nfsstat" 

45 

46DEFAULT_NFSVERSION = '3' 

47 

48NFS_VERSION = [ 

49 'nfsversion', 'NFS protocol version - 3, 4, 4.0, 4.1'] 

50 

51NFS_SERVICE_WAIT = 30 

52NFS_SERVICE_RETRY = 6 

53 

54NFS4_PSEUDOFS = "/" 

55NFS4_TMP_MOUNTPOINT = "/tmp/mnt" 

56 

57class NfsException(Exception): 

58 

59 def __init__(self, errstr): 

60 self.errstr = errstr 

61 

62def check_server_tcp(server, transport, nfsversion=DEFAULT_NFSVERSION): 

63 """Make sure that NFS over TCP/IP V3 is supported on the server. 

64 

65 Returns True if everything is OK 

66 False otherwise. 

67 """ 

68 

69 try: 

70 sv = get_supported_nfs_versions(server, transport) 

71 return (nfsversion[0] in sv) 

72 except util.CommandException as inst: 

73 raise NfsException("rpcinfo failed or timed out: return code %d" % 

74 inst.code) 

75 

76 

77def check_server_service(server, transport): 

78 """Ensure NFS service is up and available on the remote server. 

79 

80 Returns False if fails to detect service after  

81 NFS_SERVICE_RETRY * NFS_SERVICE_WAIT 

82 """ 

83 

84 try: 

85 sv = get_supported_nfs_versions(server, transport) 

86 # Services are not present in NFS4 only, this doesn't mean there's no NFS 

87 if "4" in sv: 87 ↛ 88line 87 didn't jump to line 88, because the condition on line 87 was never true

88 return True 

89 except NfsException: 

90 # Server failed to give us supported versions 

91 pass 

92 

93 retries = 0 

94 errlist = [errno.EPERM, errno.EPIPE, errno.EIO] 

95 

96 while True: 

97 try: 

98 services = util.pread([RPCINFO_BIN, "-s", "%s" % server]) 

99 services = services.split("\n") 

100 for i in range(len(services)): 

101 if services[i].find("nfs") > 0: 

102 return True 

103 except util.CommandException as inst: 

104 if not int(inst.code) in errlist: 

105 raise 

106 

107 util.SMlog("NFS service not ready on server %s" % server) 

108 retries += 1 

109 if retries >= NFS_SERVICE_RETRY: 

110 break 

111 

112 time.sleep(NFS_SERVICE_WAIT) 

113 

114 return False 

115 

116 

117def validate_nfsversion(nfsversion): 

118 """Check the validity of 'nfsversion'. 

119 

120 Raise an exception for any invalid version. 

121 """ 

122 if not nfsversion: 

123 nfsversion = DEFAULT_NFSVERSION 

124 else: 

125 if not (nfsversion == '3' or nfsversion.startswith('4')): 

126 raise NfsException("Invalid nfsversion.") 

127 return nfsversion 

128 

129 

130def soft_mount(mountpoint, remoteserver, remotepath, transport, useroptions='', 

131 timeout=None, nfsversion=DEFAULT_NFSVERSION, retrans=None): 

132 """Mount the remote NFS export at 'mountpoint'. 

133 

134 The 'timeout' param here is in deciseconds (tenths of a second). See 

135 nfs(5) for details. 

136 """ 

137 try: 

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

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

140 except util.CommandException as inst: 

141 raise NfsException("Failed to make directory: code is %d" % 

142 inst.code) 

143 

144 mountcommand = 'mount.nfs' 

145 

146 options = "soft,proto=%s,vers=%s" % ( 

147 transport, 

148 nfsversion) 

149 options += ',acdirmin=0,acdirmax=0' 

150 

151 if timeout is not None: 151 ↛ 152line 151 didn't jump to line 152, because the condition on line 151 was never true

152 options += ",timeo=%s" % timeout 

153 if retrans is not None: 153 ↛ 154line 153 didn't jump to line 154, because the condition on line 153 was never true

154 options += ",retrans=%s" % retrans 

155 if useroptions != '': 155 ↛ 156line 155 didn't jump to line 156, because the condition on line 155 was never true

156 options += ",%s" % useroptions 

157 

158 try: 

159 if transport in ['tcp6', 'udp6']: 

160 remoteserver = '[' + remoteserver + ']' 

161 util.ioretry(lambda: 

162 util.pread([mountcommand, "%s:%s" 

163 % (remoteserver, remotepath), 

164 mountpoint, "-o", options]), 

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

166 maxretry=2, nofail=True) 

167 except util.CommandException as inst: 

168 raise NfsException( 

169 "mount failed on server `%s` with return code %d" % ( 

170 remoteserver, inst.code 

171 ) 

172 ) 

173 

174 

175def unmount(mountpoint, rmmountpoint): 

176 """Unmount the mounted mountpoint""" 

177 try: 

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

179 except util.CommandException as inst: 

180 raise NfsException("umount failed with return code %d" % inst.code) 

181 

182 if rmmountpoint: 

183 try: 

184 os.rmdir(mountpoint) 

185 except OSError as inst: 

186 raise NfsException("rmdir failed with error '%s'" % inst.strerror) 

187 

188 

189def _scan_exports_nfs3(target, dom, element): 

190 """ Scan target and return an XML DOM with target, path and accesslist. 

191 Using NFS3 services. 

192 """ 

193 

194 cmd = [SHOWMOUNT_BIN, "--no-headers", "-e", target] 

195 for val in util.pread2(cmd).split('\n'): 

196 if not len(val): 196 ↛ 197line 196 didn't jump to line 197, because the condition on line 196 was never true

197 continue 

198 entry = dom.createElement("Export") 

199 element.appendChild(entry) 

200 

201 subentry = dom.createElement("Target") 

202 entry.appendChild(subentry) 

203 textnode = dom.createTextNode(target) 

204 subentry.appendChild(textnode) 

205 

206 # Access is not always provided by showmount return 

207 # If none is provided we need to assume "*" 

208 array = val.split() 

209 path = array[0] 

210 access = array[1] if len(array) >= 2 else "*" 

211 subentry = dom.createElement("Path") 

212 entry.appendChild(subentry) 

213 textnode = dom.createTextNode(path) 

214 subentry.appendChild(textnode) 

215 

216 subentry = dom.createElement("Accesslist") 

217 entry.appendChild(subentry) 

218 textnode = dom.createTextNode(access) 

219 subentry.appendChild(textnode) 

220 return dom 

221 

222 

223def _scan_exports_nfs4(target, transport, dom, element): 

224 """ Scan target and return an XML DOM with target, path and accesslist. 

225 Using NFS4 only pseudo FS. 

226 """ 

227 

228 mountpoint = "%s/%s" % (NFS4_TMP_MOUNTPOINT, target) 

229 soft_mount(mountpoint, target, NFS4_PSEUDOFS, transport, nfsversion="4") 

230 paths = os.listdir(mountpoint) 

231 unmount(mountpoint, NFS4_PSEUDOFS) 

232 for path in paths: 

233 entry = dom.createElement("Export") 

234 element.appendChild(entry) 

235 

236 subentry = dom.createElement("Target") 

237 entry.appendChild(subentry) 

238 textnode = dom.createTextNode(target) 

239 subentry.appendChild(textnode) 

240 subentry = dom.createElement("Path") 

241 entry.appendChild(subentry) 

242 textnode = dom.createTextNode(path) 

243 subentry.appendChild(textnode) 

244 

245 subentry = dom.createElement("Accesslist") 

246 entry.appendChild(subentry) 

247 # Assume everyone as we do not have any info about it 

248 textnode = dom.createTextNode("*") 

249 subentry.appendChild(textnode) 

250 return dom 

251 

252 

253def scan_exports(target, transport): 

254 """Scan target and return an XML DOM with target, path and accesslist.""" 

255 util.SMlog("scanning") 

256 dom = xml.dom.minidom.Document() 

257 element = dom.createElement("nfs-exports") 

258 dom.appendChild(element) 

259 try: 

260 return _scan_exports_nfs3(target, dom, element) 

261 except Exception: 

262 util.SMlog("Unable to scan exports with %s, trying NFSv4" % SHOWMOUNT_BIN) 

263 

264 # NFSv4 only 

265 try: 

266 return _scan_exports_nfs4(target, transport, dom, element) 

267 except Exception: 

268 util.SMlog("Unable to scan exports with NFSv4 pseudo FS mount") 

269 

270 raise NfsException("Failed to read NFS export paths from server %s" % 

271 (target)) 

272 

273 

274def scan_srlist(path, transport, dconf): 

275 """Scan and report SR, UUID.""" 

276 dom = xml.dom.minidom.Document() 

277 element = dom.createElement("SRlist") 

278 dom.appendChild(element) 

279 for val in filter(util.match_uuid, util.ioretry( 

280 lambda: util.listdir(path))): 

281 fullpath = os.path.join(path, val) 

282 if not util.ioretry(lambda: util.isdir(fullpath)): 

283 continue 

284 

285 entry = dom.createElement('SR') 

286 element.appendChild(entry) 

287 

288 subentry = dom.createElement("UUID") 

289 entry.appendChild(subentry) 

290 textnode = dom.createTextNode(val) 

291 subentry.appendChild(textnode) 

292 

293 from NFSSR import PROBEVERSION 

294 if PROBEVERSION in dconf: 

295 util.SMlog("Add supported nfs versions to sr-probe") 

296 try: 

297 supported_versions = get_supported_nfs_versions(dconf.get('server'), transport) 

298 supp_ver = dom.createElement("SupportedVersions") 

299 element.appendChild(supp_ver) 

300 

301 for ver in supported_versions: 

302 version = dom.createElement('Version') 

303 supp_ver.appendChild(version) 

304 textnode = dom.createTextNode(ver) 

305 version.appendChild(textnode) 

306 except NfsException: 

307 # Server failed to give us supported versions 

308 pass 

309 

310 return dom.toprettyxml() 

311 

312 

313def _get_supported_nfs_version_rpcinfo(server): 

314 """ Return list of supported nfs versions. 

315 Using NFS3 services. 

316 *Might* return "4" in the list of supported NFS versions, but might not: 

317 There is no requirement for NFS4 to register with rpcbind, even though it can, so 

318 a server which supports NFS4 might still only return ["3"] from here. 

319 """ 

320 

321 valid_versions = set(["3", "4"]) 

322 cv = set() 

323 ns = util.pread2([RPCINFO_BIN, "-s", "%s" % server]) 

324 ns = ns.split("\n") 

325 for i in range(len(ns)): 

326 if ns[i].find("nfs") > 0: 

327 cvi = ns[i].split()[1].split(",") 

328 for j in range(len(cvi)): 

329 cv.add(cvi[j]) 

330 return sorted(cv & valid_versions) 

331 

332 

333def _is_nfs4_supported(server, transport): 

334 """ Return list of supported nfs versions. 

335 Using NFS4 pseudo FS. 

336 """ 

337 

338 try: 

339 mountpoint = "%s/%s" % (NFS4_TMP_MOUNTPOINT, server) 

340 soft_mount(mountpoint, server, NFS4_PSEUDOFS, transport, nfsversion='4') 

341 util.pread2([NFS_STAT, "-m"]) 

342 unmount(mountpoint, NFS4_PSEUDOFS) 

343 return True 

344 except Exception: 

345 return False 

346 

347 

348def get_supported_nfs_versions(server, transport): 

349 """ 

350 Return list of supported nfs versions. 

351 

352 First check list from rpcinfo and if that does not contain NFS4, probe for it and 

353 add it to the list if available. 

354 """ 

355 vers = [] 

356 try: 

357 vers = _get_supported_nfs_version_rpcinfo(server) 

358 except Exception: 

359 util.SMlog("Unable to obtain list of valid nfs versions with %s, trying NFSv4" % RPCINFO_BIN) 

360 

361 # Test for NFS4 if the rpcinfo query did not find it (NFS4 does not *have* to register with rpcbind) 

362 if "4" not in vers: 

363 if _is_nfs4_supported(server, transport): 

364 vers.append("4") 

365 

366 if vers: 

367 return vers 

368 else: 

369 raise NfsException("Failed to read supported NFS version from server %s" % (server)) 

370 

371 

372def get_nfs_timeout(other_config): 

373 nfs_timeout = 200 

374 

375 if 'nfs-timeout' in other_config: 375 ↛ 376line 375 didn't jump to line 376, because the condition on line 375 was never true

376 val = int(other_config['nfs-timeout']) 

377 if val < 1: 

378 util.SMlog("Invalid nfs-timeout value: %d" % val) 

379 else: 

380 nfs_timeout = val 

381 

382 return nfs_timeout 

383 

384 

385def get_nfs_retrans(other_config): 

386 nfs_retrans = 4 

387 

388 if 'nfs-retrans' in other_config: 388 ↛ 389line 388 didn't jump to line 389, because the condition on line 388 was never true

389 val = int(other_config['nfs-retrans']) 

390 if val < 0: 

391 util.SMlog("Invalid nfs-retrans value: %d" % val) 

392 else: 

393 nfs_retrans = val 

394 

395 return nfs_retrans