Skip to content

Commit fe5a7b3

Browse files
benjamreisWescoeur
authored andcommitted
Backport NFS4 only support
See: xapi-project#617 Signed-off-by: Benjamin Reis <[email protected]>
1 parent 81b847d commit fe5a7b3

File tree

4 files changed

+139
-64
lines changed

4 files changed

+139
-64
lines changed

drivers/NFSSR.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,12 @@ def validate_remotepath(self, scan):
115115
def check_server(self):
116116
try:
117117
if self.dconf.has_key(PROBEVERSION):
118-
sv = nfs.get_supported_nfs_versions(self.remoteserver)
118+
sv = nfs.get_supported_nfs_versions(self.remoteserver, self.transport)
119119
if len(sv):
120120
self.nfsversion = sv[0]
121121
else:
122-
nfs.check_server_tcp(self.remoteserver, self.nfsversion)
122+
if not nfs.check_server_tcp(self.remoteserver, self.transport, self.nfsversion):
123+
raise nfs.NfsException("Unsupported NFS version: %s" % self.nfsversion)
123124
except nfs.NfsException, exc:
124125
raise xs_errors.XenError('NFSVersion',
125126
opterr=exc.errstr)
@@ -174,7 +175,7 @@ def probe(self):
174175

175176
self.mount(temppath, self.remotepath)
176177
try:
177-
return nfs.scan_srlist(temppath, self.dconf)
178+
return nfs.scan_srlist(temppath, self.transport, self.dconf)
178179
finally:
179180
try:
180181
nfs.unmount(temppath, True)
@@ -268,7 +269,7 @@ def vdi(self, uuid, loadLocked = False):
268269

269270
def scan_exports(self, target):
270271
util.SMlog("scanning2 (target=%s)" % target)
271-
dom = nfs.scan_exports(target)
272+
dom = nfs.scan_exports(target, self.transport)
272273
print >>sys.stderr,dom.toprettyxml()
273274

274275
class NFSFileVDI(FileSR.FileVDI):

drivers/nfs.py

Lines changed: 112 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444

4545
RPCINFO_BIN = "/usr/sbin/rpcinfo"
4646
SHOWMOUNT_BIN = "/usr/sbin/showmount"
47+
NFS_STAT = "/usr/sbin/nfsstat"
4748

4849
DEFAULT_NFSVERSION = '3'
4950

@@ -53,32 +54,40 @@
5354
NFS_SERVICE_WAIT = 30
5455
NFS_SERVICE_RETRY = 6
5556

57+
NFS4_PSEUDOFS = "/"
58+
NFS4_TMP_MOUNTPOINT = "/tmp/mnt"
59+
5660
class NfsException(Exception):
5761

5862
def __init__(self, errstr):
5963
self.errstr = errstr
6064

6165

62-
def check_server_tcp(server, nfsversion=DEFAULT_NFSVERSION):
66+
def check_server_tcp(server, transport, nfsversion=DEFAULT_NFSVERSION):
6367
"""Make sure that NFS over TCP/IP V3 is supported on the server.
6468
6569
Returns True if everything is OK
6670
False otherwise.
6771
"""
6872
try:
69-
sv = get_supported_nfs_versions(server)
70-
return (True if nfsversion in sv else False)
73+
sv = get_supported_nfs_versions(server, transport)
74+
return (nfsversion[0] in sv)
7175
except util.CommandException, inst:
7276
raise NfsException("rpcinfo failed or timed out: return code %d" %
7377
inst.code)
7478

75-
def check_server_service(server):
79+
def check_server_service(server, transport):
7680
"""Ensure NFS service is up and available on the remote server.
7781
7882
Returns False if fails to detect service after
7983
NFS_SERVICE_RETRY * NFS_SERVICE_WAIT
8084
"""
8185

86+
sv = get_supported_nfs_versions(server, transport)
87+
# Services are not present in NFS4 only, this doesn't mean there's no NFS
88+
if sv == ['4']:
89+
return True
90+
8291
retries = 0
8392
errlist = [errno.EPERM, errno.EPIPE, errno.EIO]
8493

@@ -131,14 +140,6 @@ def soft_mount(mountpoint, remoteserver, remotepath, transport, useroptions='',
131140
inst.code)
132141

133142

134-
# Wait for NFS service to be available
135-
try:
136-
if not check_server_service(remoteserver):
137-
raise util.CommandException(code=errno.EOPNOTSUPP,
138-
reason="No NFS service on host")
139-
except util.CommandException, inst:
140-
raise NfsException("Failed to detect NFS service on server %s"
141-
% remoteserver)
142143

143144
mountcommand = 'mount.nfs'
144145
if nfsversion == '4':
@@ -184,13 +185,11 @@ def unmount(mountpoint, rmmountpoint):
184185
raise NfsException("rmdir failed with error '%s'" % inst.strerror)
185186

186187

187-
def scan_exports(target):
188-
"""Scan target and return an XML DOM with target, path and accesslist."""
189-
util.SMlog("scanning")
188+
def _scan_exports_nfs3(target, dom, element):
189+
""" Scan target and return an XML DOM with target, path and accesslist.
190+
Using NFS3 services.
191+
"""
190192
cmd = [SHOWMOUNT_BIN, "--no-headers", "-e", target]
191-
dom = xml.dom.minidom.Document()
192-
element = dom.createElement("nfs-exports")
193-
dom.appendChild(element)
194193
for val in util.pread2(cmd).split('\n'):
195194
if not len(val):
196195
continue
@@ -219,8 +218,56 @@ def scan_exports(target):
219218

220219
return dom
221220

221+
def _scan_exports_nfs4_only(target, transport, dom, element):
222+
""" Scan target and return an XML DOM with target, path and accesslist.
223+
Using NFS4 only pseudo FS.
224+
"""
225+
226+
mountpoint = "%s/%s" % (NFS4_TMP_MOUNTPOINT, target)
227+
soft_mount(mountpoint, target, NFS4_PSEUDOFS, transport, nfsversion="4")
228+
paths = os.listdir(mountpoint)
229+
unmount(mountpoint, NFS4_PSEUDOFS)
230+
for path in paths:
231+
entry = dom.createElement("Export")
232+
element.appendChild(entry)
233+
234+
subentry = dom.createElement("Target")
235+
entry.appendChild(subentry)
236+
textnode = dom.createTextNode(target)
237+
subentry.appendChild(textnode)
238+
subentry = dom.createElement("Path")
239+
entry.appendChild(subentry)
240+
textnode = dom.createTextNode(path)
241+
subentry.appendChild(textnode)
242+
243+
subentry = dom.createElement("Accesslist")
244+
entry.appendChild(subentry)
245+
# Assume everyone as we do not have any info about it
246+
textnode = dom.createTextNode("*")
247+
subentry.appendChild(textnode)
248+
return dom
249+
250+
def scan_exports(target, transport):
251+
"""Scan target and return an XML DOM with target, path and accesslist."""
252+
util.SMlog("scanning")
253+
dom = xml.dom.minidom.Document()
254+
element = dom.createElement("nfs-exports")
255+
dom.appendChild(element)
256+
try:
257+
return _scan_exports_nfs3(target, dom, element)
258+
except Exception:
259+
util.SMlog("Unable to scan exports with %s, trying NFSv4" % SHOWMOUNT_BIN)
260+
261+
# NFSv4 only
262+
try:
263+
return _scan_exports_nfs4_only(target, transport, dom, element)
264+
except Exception:
265+
util.SMlog("Unable to scan exports with NFSv4 pseudo FS mount")
266+
267+
raise NfsException("Failed to read NFS export paths from server %s" %
268+
(target))
222269

223-
def scan_srlist(path, dconf):
270+
def scan_srlist(path, transport, dconf):
224271
"""Scan and report SR, UUID."""
225272
dom = xml.dom.minidom.Document()
226273
element = dom.createElement("SRlist")
@@ -243,7 +290,7 @@ def scan_srlist(path, dconf):
243290
if dconf.has_key(PROBEVERSION):
244291
util.SMlog("Add supported nfs versions to sr-probe")
245292
try:
246-
supported_versions = get_supported_nfs_versions(dconf.get('server'))
293+
supported_versions = get_supported_nfs_versions(dconf.get('server'), transport)
247294
supp_ver = dom.createElement("SupportedVersions")
248295
element.appendChild(supp_ver)
249296

@@ -259,21 +306,53 @@ def scan_srlist(path, dconf):
259306
return dom.toprettyxml()
260307

261308

262-
def get_supported_nfs_versions(server):
263-
"""Return list of supported nfs versions."""
264-
valid_versions = set(['3', '4'])
309+
def _get_supported_nfs_version_rpcinfo(server):
310+
""" Return list of supported nfs versions.
311+
Using NFS3 services.
312+
"""
313+
314+
valid_versions = set(["3", "4"])
315+
cv = set()
316+
ns = util.pread2([RPCINFO_BIN, "-s", "%s" % server])
317+
ns = ns.split("\n")
318+
for i in range(len(ns)):
319+
if ns[i].find("nfs") > 0:
320+
cvi = ns[i].split()[1].split(",")
321+
for j in range(len(cvi)):
322+
cv.add(cvi[j])
323+
return sorted(cv & valid_versions)
324+
325+
326+
def _is_nfs4_supported(server, transport):
327+
""" Return list of supported nfs versions.
328+
Using NFS4 pseudo FS.
329+
"""
330+
265331
cv = set()
266332
try:
267-
ns = util.pread2([RPCINFO_BIN, "-p", "%s" % server])
268-
ns = ns.split("\n")
269-
for i in range(len(ns)):
270-
if ns[i].find("nfs") > 0:
271-
cvi = ns[i].split()[1]
272-
cv.add(cvi)
273-
return list(cv & valid_versions)
274-
except:
275-
util.SMlog("Unable to obtain list of valid nfs versions")
276-
raise NfsException('Failed to read supported NFS version from server %s' %
333+
mountpoint = "%s/%s" % (NFS4_TMP_MOUNTPOINT, server)
334+
soft_mount(mountpoint, server, NFS4_PSEUDOFS, transport, nfsversion='4')
335+
util.pread2([NFS_STAT, "-m"])
336+
unmount(mountpoint, NFS4_PSEUDOFS)
337+
return True
338+
except Exception:
339+
return False
340+
341+
342+
def get_supported_nfs_versions(server, transport):
343+
"""Return list of supported nfs versions."""
344+
try:
345+
return _get_supported_nfs_version_rpcinfo(server)
346+
except Exception:
347+
util.SMlog("Unable to obtain list of valid nfs versions with %s, trying NFSv4" % RPCINFO_BIN)
348+
349+
# NFSv4 only
350+
if _is_nfs4_supported(server, transport):
351+
return ["4"]
352+
else:
353+
util.SMlog("Unable to obtain list of valid nfs versions with NFSv4 pseudo FS mount")
354+
355+
raise NfsException("Failed to read supported NFS version from server %s" %
277356
(server))
278357

279358
def get_nfs_timeout(other_config):

tests/test_NFSSR.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def test_attach(self, validate_nfsversion, check_server_tcp, _testhost,
182182

183183
nfssr.attach(None)
184184

185-
check_server_tcp.assert_called_once_with('aServer',
185+
check_server_tcp.assert_called_once_with('aServer', 'tcp',
186186
'aNfsversionChanged')
187187
soft_mount.assert_called_once_with('/var/run/sr-mount/UUID',
188188
'aServer',

tests/test_nfs.py

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,32 @@ class Test_nfs(unittest.TestCase):
99

1010
@mock.patch('util.pread', autospec=True)
1111
def test_check_server_tcp(self, pread):
12-
nfs.check_server_tcp('aServer')
12+
nfs.check_server_tcp('aServer', 'tcp')
1313

14-
pread.assert_called_once_with(['/usr/sbin/rpcinfo', '-p', 'aServer'], quiet=False)
14+
pread.assert_called_once_with(['/usr/sbin/rpcinfo', '-s', 'aServer'], quiet=False)
1515

1616
@mock.patch('util.pread', autospec=True)
1717
def test_check_server_tcp_nfsversion(self, pread):
18-
nfs.check_server_tcp('aServer', 'aNfsversion')
18+
nfs.check_server_tcp('aServer', 'tcp', 'aNfsversion')
1919

20-
pread.assert_called_once_with(['/usr/sbin/rpcinfo', '-p', 'aServer'], quiet=False)
20+
pread.assert_called_once_with(['/usr/sbin/rpcinfo', '-s', 'aServer'], quiet=False)
2121

2222
@mock.patch('util.pread', autospec=True)
2323
def test_check_server_tcp_nfsversion_error(self, pread):
2424
pread.side_effect = util.CommandException
2525

2626
with self.assertRaises(nfs.NfsException):
27-
nfs.check_server_tcp('aServer', 'aNfsversion')
27+
nfs.check_server_tcp('aServer', 'tcp', 'aNfsversion')
2828

29-
pread.assert_called_once_with(['/usr/sbin/rpcinfo', '-p', 'aServer'], quiet=False)
29+
self.assertEqual(len(pread.mock_calls), 2)
3030

3131
@mock.patch('time.sleep', autospec=True)
32+
@mock.patch('nfs.get_supported_nfs_versions', autospec=True)
3233
# Can't use autospec due to http://bugs.python.org/issue17826
3334
@mock.patch('util.pread')
34-
def test_check_server_service(self, pread, sleep):
35+
def test_check_server_service(self, pread, get_supported_nfs_versions, sleep):
3536
pread.side_effect=[" 100003 4,3,2 udp6,tcp6,udp,tcp nfs superuser"]
36-
service_found = nfs.check_server_service('aServer')
37+
service_found = nfs.check_server_service('aServer', 'tcp')
3738

3839
self.assertTrue(service_found)
3940
self.assertEqual(len(pread.mock_calls), 1)
@@ -48,7 +49,7 @@ def test_check_server_service_with_retries(self, pread, sleep):
4849
pread.side_effect=["",
4950
"",
5051
" 100003 4,3,2 udp6,tcp6,udp,tcp nfs superuser"]
51-
service_found = nfs.check_server_service('aServer')
52+
service_found = nfs.check_server_service('aServer', 'tcp')
5253

5354
self.assertTrue(service_found)
5455
self.assertEqual(len(pread.mock_calls), 3)
@@ -59,26 +60,28 @@ def test_check_server_service_with_retries(self, pread, sleep):
5960
def test_check_server_service_not_available(self, pread, sleep):
6061
pread.return_value=""
6162

62-
service_found = nfs.check_server_service('aServer')
63+
service_found = nfs.check_server_service('aServer', 'tcp')
6364

6465
self.assertFalse(service_found)
6566

6667
@mock.patch('time.sleep', autospec=True)
68+
@mock.patch('nfs.get_supported_nfs_versions', autospec=True)
6769
# Can't use autospec due to http://bugs.python.org/issue17826
6870
@mock.patch('util.pread')
69-
def test_check_server_service_exception(self, pread, sleep):
71+
def test_check_server_service_exception(self, pread, get_supported_nfs_versions, sleep):
7072
pread.side_effect=[util.CommandException(errno.ENOMEM)]
7173
with self.assertRaises(util.CommandException):
72-
nfs.check_server_service('aServer')
74+
nfs.check_server_service('aServer', 'tcp')
7375

7476

7577
@mock.patch('time.sleep', autospec=True)
78+
@mock.patch('nfs.get_supported_nfs_versions', autospec=True)
7679
# Can't use autospec due to http://bugs.python.org/issue17826
7780
@mock.patch('util.pread')
78-
def test_check_server_service_first_call_exception(self, pread, sleep):
81+
def test_check_server_service_first_call_exception(self, pread, get_supported_nfs_versions, sleep):
7982
pread.side_effect=[util.CommandException(errno.EPIPE),
8083
" 100003 4,3,2 udp6,tcp6,udp,tcp nfs superuser"]
81-
service_found = nfs.check_server_service('aServer')
84+
service_found = nfs.check_server_service('aServer', 'tcp')
8285

8386
self.assertTrue(service_found)
8487
self.assertEqual(len(pread.mock_calls), 2)
@@ -88,37 +91,29 @@ def get_soft_mount_pread(self, binary, vers):
8891
'soft,proto=transport,vers=%s,acdirmin=0,acdirmax=0' % vers])
8992

9093
@mock.patch('util.makedirs', autospec=True)
91-
@mock.patch('nfs.check_server_service', autospec=True)
9294
@mock.patch('util.pread', autospec=True)
93-
def test_soft_mount(self, pread, check_server_service, makedirs):
95+
def test_soft_mount(self, pread, makedirs):
9496
nfs.soft_mount('mountpoint', 'remoteserver', 'remotepath', 'transport',
9597
timeout=None)
9698

97-
check_server_service.assert_called_once_with('remoteserver')
9899
pread.assert_called_once_with(self.get_soft_mount_pread('mount.nfs',
99100
'3'))
100101

101102
@mock.patch('util.makedirs', autospec=True)
102-
@mock.patch('nfs.check_server_service', autospec=True)
103103
@mock.patch('util.pread', autospec=True)
104-
def test_soft_mount_nfsversion_3(self, pread,
105-
check_server_service, makedirs):
104+
def test_soft_mount_nfsversion_3(self, pread, makedirs):
106105
nfs.soft_mount('mountpoint', 'remoteserver', 'remotepath', 'transport',
107106
timeout=None, nfsversion='3')
108107

109-
check_server_service.assert_called_once_with('remoteserver')
110108
pread.assert_called_with(self.get_soft_mount_pread('mount.nfs',
111109
'3'))
112110

113111
@mock.patch('util.makedirs', autospec=True)
114-
@mock.patch('nfs.check_server_service', autospec=True)
115112
@mock.patch('util.pread', autospec=True)
116-
def test_soft_mount_nfsversion_4(self, pread,
117-
check_server_service, makedirs):
113+
def test_soft_mount_nfsversion_4(self, pread, makedirs):
118114
nfs.soft_mount('mountpoint', 'remoteserver', 'remotepath', 'transport',
119115
timeout=None, nfsversion='4')
120116

121-
check_server_service.assert_called_once_with('remoteserver')
122117
pread.assert_called_with(self.get_soft_mount_pread('mount.nfs4',
123118
'4'))
124119

@@ -145,7 +140,7 @@ def test_validate_nfsversion_valid(self):
145140
@mock.patch('util.pread2')
146141
def test_scan_exports(self, pread2):
147142
pread2.side_effect = ["/srv/nfs\n/srv/nfs2 *\n/srv/nfs3 127.0.0.1/24"]
148-
res = nfs.scan_exports('aServer')
143+
res = nfs.scan_exports('aServer', 'tcp')
149144

150145
expected = """<?xml version="1.0" ?>
151146
<nfs-exports>

0 commit comments

Comments
 (0)