Coverage for drivers/nfs.py : 64%

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
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).
42RPCINFO_BIN = "/usr/sbin/rpcinfo"
43SHOWMOUNT_BIN = "/usr/sbin/showmount"
45DEFAULT_NFSVERSION = '3'
47NFS_VERSION = [
48 'nfsversion', 'for type=nfs, NFS protocol version - 3, 4, 4.0, 4.1']
50NFS_SERVICE_WAIT = 30
51NFS_SERVICE_RETRY = 6
54class NfsException(Exception):
56 def __init__(self, errstr):
57 self.errstr = errstr
60def check_server_tcp(server, nfsversion=DEFAULT_NFSVERSION):
61 """Make sure that NFS over TCP/IP V3 is supported on the server.
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)
74def check_server_service(server):
75 """Ensure NFS service is up and available on the remote server.
77 Returns False if fails to detect service after
78 NFS_SERVICE_RETRY * NFS_SERVICE_WAIT
79 """
81 retries = 0
82 errlist = [errno.EPERM, errno.EPIPE, errno.EIO]
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
95 util.SMlog("NFS service not ready on server %s" % server)
96 retries += 1
97 if retries >= NFS_SERVICE_RETRY:
98 break
100 time.sleep(NFS_SERVICE_WAIT)
102 return False
105def validate_nfsversion(nfsversion):
106 """Check the validity of 'nfsversion'.
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
118def soft_mount(mountpoint, remoteserver, remotepath, transport, useroptions='',
119 timeout=None, nfsversion=DEFAULT_NFSVERSION, retrans=None):
120 """Mount the remote NFS export at 'mountpoint'.
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)
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)
144 mountcommand = 'mount.nfs'
146 options = "soft,proto=%s,vers=%s" % (
147 transport,
148 nfsversion)
149 options += ',acdirmin=0,acdirmax=0'
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
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 )
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)
182 if rmmountpoint:
183 try:
184 os.rmdir(mountpoint)
185 except OSError as inst:
186 raise NfsException("rmdir failed with error '%s'" % inst.strerror)
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)
202 subentry = dom.createElement("Target")
203 entry.appendChild(subentry)
204 textnode = dom.createTextNode(target)
205 subentry.appendChild(textnode)
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)
217 subentry = dom.createElement("Accesslist")
218 entry.appendChild(subentry)
219 textnode = dom.createTextNode(access)
220 subentry.appendChild(textnode)
222 return dom
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
236 entry = dom.createElement('SR')
237 element.appendChild(entry)
239 subentry = dom.createElement("UUID")
240 entry.appendChild(subentry)
241 textnode = dom.createTextNode(val)
242 subentry.appendChild(textnode)
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)
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
261 return dom.toprettyxml()
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))
283def get_nfs_timeout(other_config):
284 nfs_timeout = 100
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
293 return nfs_timeout
296def get_nfs_retrans(other_config):
297 nfs_retrans = 3
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
306 return nfs_retrans