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" 

44 

45DEFAULT_NFSVERSION = '3' 

46 

47NFS_VERSION = [ 

48 'nfsversion', 'for type=nfs, NFS protocol version - 3, 4, 4.0, 4.1'] 

49 

50NFS_SERVICE_WAIT = 30 

51NFS_SERVICE_RETRY = 6 

52 

53 

54class NfsException(Exception): 

55 

56 def __init__(self, errstr): 

57 self.errstr = errstr 

58 

59 

60def check_server_tcp(server, nfsversion=DEFAULT_NFSVERSION): 

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

62 

63 Returns True if everything is OK 

64 False otherwise. 

65 """ 

66 try: 

67 sv = get_supported_nfs_versions(server) 

68 return (True if nfsversion in sv else False) 

69 except util.CommandException as inst: 

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

71 inst.code) 

72 

73 

74def check_server_service(server): 

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

76 

77 Returns False if fails to detect service after  

78 NFS_SERVICE_RETRY * NFS_SERVICE_WAIT 

79 """ 

80 

81 retries = 0 

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

83 

84 while True: 

85 try: 

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

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

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

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

90 return True 

91 except util.CommandException as inst: 

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

93 raise 

94 

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

96 retries += 1 

97 if retries >= NFS_SERVICE_RETRY: 

98 break 

99 

100 time.sleep(NFS_SERVICE_WAIT) 

101 

102 return False 

103 

104 

105def validate_nfsversion(nfsversion): 

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

107 

108 Raise an exception for any invalid version. 

109 """ 

110 if not nfsversion: 

111 nfsversion = DEFAULT_NFSVERSION 

112 else: 

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

114 raise NfsException("Invalid nfsversion.") 

115 return nfsversion 

116 

117 

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

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

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

121 

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

123 nfs(5) for details. 

124 """ 

125 try: 

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

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

128 except util.CommandException as inst: 

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

130 inst.code) 

131 

132 

133 # Wait for NFS service to be available 

134 try: 

135 if not check_server_service(remoteserver): 135 ↛ 136line 135 didn't jump to line 136, because the condition on line 135 was never true

136 raise util.CommandException( 

137 code=errno.EOPNOTSUPP, 

138 reason='No NFS service on server: `%s`' % remoteserver 

139 ) 

140 except util.CommandException as inst: 

141 raise NfsException("Failed to detect NFS service on server `%s`" 

142 % remoteserver) 

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(target): 

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

191 util.SMlog("scanning") 

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

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

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

195 dom.appendChild(element) 

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

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

198 continue 

199 entry = dom.createElement('Export') 

200 element.appendChild(entry) 

201 

202 subentry = dom.createElement("Target") 

203 entry.appendChild(subentry) 

204 textnode = dom.createTextNode(target) 

205 subentry.appendChild(textnode) 

206 

207 # Access is not always provided by showmount return 

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

209 array = val.split() 

210 path = array[0] 

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

212 subentry = dom.createElement("Path") 

213 entry.appendChild(subentry) 

214 textnode = dom.createTextNode(path) 

215 subentry.appendChild(textnode) 

216 

217 subentry = dom.createElement("Accesslist") 

218 entry.appendChild(subentry) 

219 textnode = dom.createTextNode(access) 

220 subentry.appendChild(textnode) 

221 

222 return dom 

223 

224 

225def scan_srlist(path, dconf): 

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

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

228 element = dom.createElement("SRlist") 

229 dom.appendChild(element) 

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

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

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

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

234 continue 

235 

236 entry = dom.createElement('SR') 

237 element.appendChild(entry) 

238 

239 subentry = dom.createElement("UUID") 

240 entry.appendChild(subentry) 

241 textnode = dom.createTextNode(val) 

242 subentry.appendChild(textnode) 

243 

244 from NFSSR import PROBEVERSION 

245 if PROBEVERSION in dconf: 

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

247 try: 

248 supported_versions = get_supported_nfs_versions(dconf.get('server')) 

249 supp_ver = dom.createElement("SupportedVersions") 

250 element.appendChild(supp_ver) 

251 

252 for ver in supported_versions: 

253 version = dom.createElement('Version') 

254 supp_ver.appendChild(version) 

255 textnode = dom.createTextNode(ver) 

256 version.appendChild(textnode) 

257 except NfsException: 

258 # Server failed to give us supported versions 

259 pass 

260 

261 return dom.toprettyxml() 

262 

263 

264def get_supported_nfs_versions(server): 

265 """Return list of supported nfs versions.""" 

266 valid_versions = set(['3', '4']) 

267 cv = set() 

268 try: 

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

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

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

272 if ns[i].find("nfs") > 0: 272 ↛ 271line 272 didn't jump to line 271, because the condition on line 272 was never false

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

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

275 cv.add(cvi[j]) 

276 return sorted(cv & valid_versions) 

277 except: 

278 util.SMlog("Unable to obtain list of valid nfs versions") 

279 raise NfsException('Failed to read supported NFS version from server %s' % 

280 (server)) 

281 

282 

283def get_nfs_timeout(other_config): 

284 nfs_timeout = 100 

285 

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

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

288 if val < 1: 

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

290 else: 

291 nfs_timeout = val 

292 

293 return nfs_timeout 

294 

295 

296def get_nfs_retrans(other_config): 

297 nfs_retrans = 3 

298 

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

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

301 if val < 0: 

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

303 else: 

304 nfs_retrans = val 

305 

306 return nfs_retrans