]>
git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/cephfs/test_nfs.py
1 # NOTE: these tests are not yet compatible with vstart_runner.py.
8 from tasks
.mgr
.mgr_test_case
import MgrTestCase
9 from teuthology
.exceptions
import CommandFailedError
11 log
= logging
.getLogger(__name__
)
14 # TODO Add test for cluster update when ganesha can be deployed on multiple ports.
15 class TestNFS(MgrTestCase
):
16 def _cmd(self
, *args
):
17 return self
.mgr_cluster
.mon_manager
.raw_cluster_cmd(*args
)
19 def _nfs_cmd(self
, *args
):
20 return self
._cmd
("nfs", *args
)
22 def _orch_cmd(self
, *args
):
23 return self
._cmd
("orch", *args
)
25 def _sys_cmd(self
, cmd
):
27 ret
= self
.ctx
.cluster
.run(args
=cmd
, check_status
=False, stdout
=BytesIO(), stderr
=BytesIO())
28 stdout
= ret
[0].stdout
30 return stdout
.getvalue()
33 super(TestNFS
, self
).setUp()
34 self
._load
_module
('nfs')
35 self
.cluster_id
= "test"
36 self
.export_type
= "cephfs"
37 self
.pseudo_path
= "/cephfs"
39 self
.fs_name
= "nfs-cephfs"
40 self
.expected_name
= "nfs.test"
41 self
.sample_export
= {
44 "cluster_id": self
.cluster_id
,
45 "pseudo": self
.pseudo_path
,
47 "squash": "no_root_squash",
48 "security_label": True,
58 "fs_name": self
.fs_name
,
64 def _check_nfs_server_status(self
):
65 res
= self
._sys
_cmd
(['systemctl', 'status', 'nfs-server'])
66 if isinstance(res
, bytes
) and b
'Active: active' in res
:
69 def _disable_nfs(self
):
70 log
.info("Disabling NFS")
71 self
._sys
_cmd
(['systemctl', 'disable', 'nfs-server', '--now'])
73 def _fetch_nfs_status(self
):
74 return self
._orch
_cmd
('ps', f
'--service_name={self.expected_name}')
76 def _check_nfs_cluster_status(self
, expected_status
, fail_msg
):
78 Tests if nfs cluster created or deleted successfully
79 :param expected_status: Status to be verified
80 :param fail_msg: Message to be printed if test failed
82 # Wait for few seconds as ganesha daemon takes few seconds to be deleted/created
84 while wait_time
<= 60:
86 if expected_status
in self
._fetch
_nfs
_status
():
91 def _check_auth_ls(self
, export_id
=1, check_in
=False):
93 Tests export user id creation or deletion.
94 :param export_id: Denotes export number
95 :param check_in: Check specified export id
97 output
= self
._cmd
('auth', 'ls')
99 self
.assertIn(f
'client.{self.cluster_id}{export_id}', output
)
101 self
.assertNotIn(f
'client-{self.cluster_id}', output
)
103 def _test_idempotency(self
, cmd_func
, cmd_args
):
105 Test idempotency of commands. It first runs the TestNFS test method
106 for a command and then checks the result of command run again. TestNFS
107 test method has required checks to verify that command works.
108 :param cmd_func: TestNFS method
109 :param cmd_args: nfs command arguments to be run
112 ret
= self
.mgr_cluster
.mon_manager
.raw_cluster_cmd_result(*cmd_args
)
114 self
.fail("Idempotency test failed")
116 def _test_create_cluster(self
):
118 Test single nfs cluster deployment.
120 # Disable any running nfs ganesha daemon
121 self
._check
_nfs
_server
_status
()
122 self
._nfs
_cmd
('cluster', 'create', self
.cluster_id
)
123 # Check for expected status and daemon name (nfs.<cluster_id>)
124 self
._check
_nfs
_cluster
_status
('running', 'NFS Ganesha cluster deployment failed')
126 def _test_delete_cluster(self
):
128 Test deletion of a single nfs cluster.
130 self
._nfs
_cmd
('cluster', 'rm', self
.cluster_id
)
131 self
._check
_nfs
_cluster
_status
('No daemons reported',
132 'NFS Ganesha cluster could not be deleted')
134 def _test_list_cluster(self
, empty
=False):
136 Test listing of deployed nfs clusters. If nfs cluster is deployed then
137 it checks for expected cluster id. Otherwise checks nothing is listed.
138 :param empty: If true it denotes no cluster is deployed.
143 cluster_id
= self
.cluster_id
144 nfs_output
= self
._nfs
_cmd
('cluster', 'ls')
145 self
.assertEqual(cluster_id
, nfs_output
.strip())
147 def _create_export(self
, export_id
, create_fs
=False, extra_cmd
=None):
149 Test creation of a single export.
150 :param export_id: Denotes export number
151 :param create_fs: If false filesytem exists. Otherwise create it.
152 :param extra_cmd: List of extra arguments for creating export.
155 self
._cmd
('fs', 'volume', 'create', self
.fs_name
)
156 export_cmd
= ['nfs', 'export', 'create', 'cephfs', self
.fs_name
, self
.cluster_id
]
157 if isinstance(extra_cmd
, list):
158 export_cmd
.extend(extra_cmd
)
160 export_cmd
.append(self
.pseudo_path
)
161 # Runs the nfs export create command
162 self
._cmd
(*export_cmd
)
163 # Check if user id for export is created
164 self
._check
_auth
_ls
(export_id
, check_in
=True)
165 res
= self
._sys
_cmd
(['rados', '-p', 'nfs-ganesha', '-N', self
.cluster_id
, 'get',
166 f
'export-{export_id}', '-'])
167 # Check if export object is created
169 self
.fail("Export cannot be created")
171 def _create_default_export(self
):
173 Deploy a single nfs cluster and create export with default options.
175 self
._test
_create
_cluster
()
176 self
._create
_export
(export_id
='1', create_fs
=True)
178 def _delete_export(self
):
182 self
._nfs
_cmd
('export', 'rm', self
.cluster_id
, self
.pseudo_path
)
183 self
._check
_auth
_ls
()
185 def _test_list_export(self
):
187 Test listing of created exports.
189 nfs_output
= json
.loads(self
._nfs
_cmd
('export', 'ls', self
.cluster_id
))
190 self
.assertIn(self
.pseudo_path
, nfs_output
)
192 def _test_list_detailed(self
, sub_vol_path
):
194 Test listing of created exports with detailed option.
195 :param sub_vol_path: Denotes path of subvolume
197 nfs_output
= json
.loads(self
._nfs
_cmd
('export', 'ls', self
.cluster_id
, '--detailed'))
198 # Export-1 with default values (access type = rw and path = '\')
199 self
.assertDictEqual(self
.sample_export
, nfs_output
[0])
200 # Export-2 with r only
201 self
.sample_export
['export_id'] = 2
202 self
.sample_export
['pseudo'] = self
.pseudo_path
+ '1'
203 self
.sample_export
['access_type'] = 'RO'
204 self
.sample_export
['fsal']['user_id'] = self
.cluster_id
+ '2'
205 self
.assertDictEqual(self
.sample_export
, nfs_output
[1])
206 # Export-3 for subvolume with r only
207 self
.sample_export
['export_id'] = 3
208 self
.sample_export
['path'] = sub_vol_path
209 self
.sample_export
['pseudo'] = self
.pseudo_path
+ '2'
210 self
.sample_export
['fsal']['user_id'] = self
.cluster_id
+ '3'
211 self
.assertDictEqual(self
.sample_export
, nfs_output
[2])
212 # Export-4 for subvolume
213 self
.sample_export
['export_id'] = 4
214 self
.sample_export
['pseudo'] = self
.pseudo_path
+ '3'
215 self
.sample_export
['access_type'] = 'RW'
216 self
.sample_export
['fsal']['user_id'] = self
.cluster_id
+ '4'
217 self
.assertDictEqual(self
.sample_export
, nfs_output
[3])
219 def _get_export(self
):
221 Returns export block in json format
223 return json
.loads(self
._nfs
_cmd
('export', 'get', self
.cluster_id
, self
.pseudo_path
))
225 def _test_get_export(self
):
227 Test fetching of created export.
229 nfs_output
= self
._get
_export
()
230 self
.assertDictEqual(self
.sample_export
, nfs_output
)
232 def _check_export_obj_deleted(self
, conf_obj
=False):
234 Test if export or config object are deleted successfully.
235 :param conf_obj: It denotes config object needs to be checked
237 rados_obj_ls
= self
._sys
_cmd
(['rados', '-p', 'nfs-ganesha', '-N', self
.cluster_id
, 'ls'])
239 if b
'export-' in rados_obj_ls
or (conf_obj
and b
'conf-nfs' in rados_obj_ls
):
240 self
.fail("Delete export failed")
242 def _get_port_ip_info(self
):
244 Return port and ip for a cluster
246 #{'test': {'backend': [{'hostname': 'smithi068', 'ip': '172.21.15.68', 'port': 2049}]}}
247 info_output
= json
.loads(self
._nfs
_cmd
('cluster', 'info', self
.cluster_id
))['test']['backend'][0]
248 return info_output
["port"], info_output
["ip"]
250 def _test_mnt(self
, pseudo_path
, port
, ip
, check
=True):
252 Test mounting of created exports
253 :param pseudo_path: It is the pseudo root name
254 :param port: Port of deployed nfs cluster
255 :param ip: IP of deployed nfs cluster
256 :param check: It denotes if i/o testing needs to be done
259 self
.ctx
.cluster
.run(args
=['sudo', 'mount', '-t', 'nfs', '-o', f
'port={port}',
260 f
'{ip}:{pseudo_path}', '/mnt'])
261 except CommandFailedError
as e
:
262 # Check if mount failed only when non existing pseudo path is passed
263 if not check
and e
.exitstatus
== 32:
268 self
.ctx
.cluster
.run(args
=['sudo', 'touch', '/mnt/test'])
269 out_mnt
= self
._sys
_cmd
(['sudo', 'ls', '/mnt'])
270 self
.assertEqual(out_mnt
, b
'test\n')
272 self
.ctx
.cluster
.run(args
=['sudo', 'umount', '/mnt'])
274 def _write_to_read_only_export(self
, pseudo_path
, port
, ip
):
276 Check if write to read only export fails
279 self
._test
_mnt
(pseudo_path
, port
, ip
)
280 except CommandFailedError
as e
:
281 # Write to cephfs export should fail for test to pass
282 if e
.exitstatus
!= errno
.EPERM
:
285 def test_create_and_delete_cluster(self
):
287 Test successful creation and deletion of the nfs cluster.
289 self
._test
_create
_cluster
()
290 self
._test
_list
_cluster
()
291 self
._test
_delete
_cluster
()
292 # List clusters again to ensure no cluster is shown
293 self
._test
_list
_cluster
(empty
=True)
295 def test_create_delete_cluster_idempotency(self
):
297 Test idempotency of cluster create and delete commands.
299 self
._test
_idempotency
(self
._test
_create
_cluster
, ['nfs', 'cluster', 'create', self
.cluster_id
])
300 self
._test
_idempotency
(self
._test
_delete
_cluster
, ['nfs', 'cluster', 'rm', self
.cluster_id
])
302 def test_create_cluster_with_invalid_cluster_id(self
):
304 Test nfs cluster deployment failure with invalid cluster id.
307 invalid_cluster_id
= '/cluster_test' # Only [A-Za-z0-9-_.] chars are valid
308 self
._nfs
_cmd
('cluster', 'create', invalid_cluster_id
)
309 self
.fail(f
"Cluster successfully created with invalid cluster id {invalid_cluster_id}")
310 except CommandFailedError
as e
:
311 # Command should fail for test to pass
312 if e
.exitstatus
!= errno
.EINVAL
:
315 def test_create_and_delete_export(self
):
317 Test successful creation and deletion of the cephfs export.
319 self
._create
_default
_export
()
320 self
._test
_get
_export
()
321 port
, ip
= self
._get
_port
_ip
_info
()
322 self
._test
_mnt
(self
.pseudo_path
, port
, ip
)
323 self
._delete
_export
()
324 # Check if rados export object is deleted
325 self
._check
_export
_obj
_deleted
()
326 self
._test
_mnt
(self
.pseudo_path
, port
, ip
, False)
327 self
._test
_delete
_cluster
()
329 def test_create_delete_export_idempotency(self
):
331 Test idempotency of export create and delete commands.
333 self
._test
_idempotency
(self
._create
_default
_export
, ['nfs', 'export', 'create', 'cephfs',
334 self
.fs_name
, self
.cluster_id
,
336 self
._test
_idempotency
(self
._delete
_export
, ['nfs', 'export', 'rm', self
.cluster_id
,
338 self
._test
_delete
_cluster
()
340 def test_create_multiple_exports(self
):
342 Test creating multiple exports with different access type and path.
344 # Export-1 with default values (access type = rw and path = '\')
345 self
._create
_default
_export
()
346 # Export-2 with r only
347 self
._create
_export
(export_id
='2', extra_cmd
=[self
.pseudo_path
+'1', '--readonly'])
348 # Export-3 for subvolume with r only
349 self
._cmd
('fs', 'subvolume', 'create', self
.fs_name
, 'sub_vol')
350 fs_path
= self
._cmd
('fs', 'subvolume', 'getpath', self
.fs_name
, 'sub_vol').strip()
351 self
._create
_export
(export_id
='3', extra_cmd
=[self
.pseudo_path
+'2', '--readonly', fs_path
])
352 # Export-4 for subvolume
353 self
._create
_export
(export_id
='4', extra_cmd
=[self
.pseudo_path
+'3', fs_path
])
354 # Check if exports gets listed
355 self
._test
_list
_detailed
(fs_path
)
356 self
._test
_delete
_cluster
()
357 # Check if rados ganesha conf object is deleted
358 self
._check
_export
_obj
_deleted
(conf_obj
=True)
359 self
._check
_auth
_ls
()
361 def test_exports_on_mgr_restart(self
):
363 Test export availability on restarting mgr.
365 self
._create
_default
_export
()
366 # unload and load module will restart the mgr
367 self
._unload
_module
("cephadm")
368 self
._load
_module
("cephadm")
369 self
._orch
_cmd
("set", "backend", "cephadm")
370 # Check if ganesha daemon is running
371 self
._check
_nfs
_cluster
_status
('running', 'Failed to redeploy NFS Ganesha cluster')
372 # Checks if created export is listed
373 self
._test
_list
_export
()
374 port
, ip
= self
._get
_port
_ip
_info
()
375 self
._test
_mnt
(self
.pseudo_path
, port
, ip
)
376 self
._delete
_export
()
377 self
._test
_delete
_cluster
()
379 def test_export_create_with_non_existing_fsname(self
):
381 Test creating export with non-existing filesystem.
385 self
._test
_create
_cluster
()
386 self
._nfs
_cmd
('export', 'create', 'cephfs', fs_name
, self
.cluster_id
, self
.pseudo_path
)
387 self
.fail(f
"Export created with non-existing filesystem {fs_name}")
388 except CommandFailedError
as e
:
389 # Command should fail for test to pass
390 if e
.exitstatus
!= errno
.ENOENT
:
393 self
._test
_delete
_cluster
()
395 def test_export_create_with_non_existing_clusterid(self
):
397 Test creating cephfs export with non-existing nfs cluster.
400 cluster_id
= 'invalidtest'
401 self
._nfs
_cmd
('export', 'create', 'cephfs', self
.fs_name
, cluster_id
, self
.pseudo_path
)
402 self
.fail(f
"Export created with non-existing cluster id {cluster_id}")
403 except CommandFailedError
as e
:
404 # Command should fail for test to pass
405 if e
.exitstatus
!= errno
.ENOENT
:
408 def test_export_create_with_relative_pseudo_path_and_root_directory(self
):
410 Test creating cephfs export with relative or '/' pseudo path.
412 def check_pseudo_path(pseudo_path
):
414 self
._nfs
_cmd
('export', 'create', 'cephfs', self
.fs_name
, self
.cluster_id
,
416 self
.fail(f
"Export created for {pseudo_path}")
417 except CommandFailedError
as e
:
418 # Command should fail for test to pass
419 if e
.exitstatus
!= errno
.EINVAL
:
422 self
._test
_create
_cluster
()
423 self
._cmd
('fs', 'volume', 'create', self
.fs_name
)
424 check_pseudo_path('invalidpath')
425 check_pseudo_path('/')
426 check_pseudo_path('//')
427 self
._cmd
('fs', 'volume', 'rm', self
.fs_name
, '--yes-i-really-mean-it')
428 self
._test
_delete
_cluster
()
430 def test_write_to_read_only_export(self
):
432 Test write to readonly export.
434 self
._test
_create
_cluster
()
435 self
._create
_export
(export_id
='1', create_fs
=True, extra_cmd
=[self
.pseudo_path
, '--readonly'])
436 port
, ip
= self
._get
_port
_ip
_info
()
437 self
._write
_to
_read
_only
_export
(self
.pseudo_path
, port
, ip
)
438 self
._test
_delete
_cluster
()
440 def test_cluster_info(self
):
442 Test cluster info outputs correct ip and hostname
444 self
._test
_create
_cluster
()
445 info_output
= json
.loads(self
._nfs
_cmd
('cluster', 'info', self
.cluster_id
))
446 print(f
'info {info_output}')
447 info_ip
= info_output
[self
.cluster_id
].get('backend', [])[0].pop("ip")
452 "hostname": self
._sys
_cmd
(['hostname']).decode("utf-8").strip(),
459 host_ip
= self
._sys
_cmd
(['hostname', '-I']).decode("utf-8").split()
460 print(f
'host_ip is {host_ip}, info_ip is {info_ip}')
461 self
.assertDictEqual(info_output
, host_details
)
462 self
.assertTrue(info_ip
in host_ip
)
463 self
._test
_delete
_cluster
()
465 def test_cluster_set_reset_user_config(self
):
467 Test cluster is created using user config and reverts back to default
470 self
._test
_create
_cluster
()
474 fs_name
= 'user_test_fs'
475 pseudo_path
= '/ceph'
476 self
._cmd
('fs', 'volume', 'create', fs_name
)
478 key
= self
._cmd
('auth', 'get-or-create-key', f
'client.{user_id}', 'mon',
480 f
'allow rw pool={pool} namespace={self.cluster_id}, allow rw tag cephfs data={fs_name}',
481 'mds', f
'allow rw path={self.path}').strip()
483 Default_log_level = FULL_DEBUG;
490 Pseudo = {pseudo_path};
493 Attr_Expiration_Time = 0;
497 Filesystem = {fs_name};
499 Secret_Access_Key = '{key}';
502 port
, ip
= self
._get
_port
_ip
_info
()
503 self
.ctx
.cluster
.run(args
=['sudo', 'ceph', 'nfs', 'cluster', 'config',
504 'set', self
.cluster_id
, '-i', '-'], stdin
=config
)
506 res
= self
._sys
_cmd
(['rados', '-p', pool
, '-N', self
.cluster_id
, 'get',
507 f
'userconf-nfs.{user_id}', '-'])
508 self
.assertEqual(config
, res
.decode('utf-8'))
509 self
._test
_mnt
(pseudo_path
, port
, ip
)
510 self
._nfs
_cmd
('cluster', 'config', 'reset', self
.cluster_id
)
511 rados_obj_ls
= self
._sys
_cmd
(['rados', '-p', 'nfs-ganesha', '-N', self
.cluster_id
, 'ls'])
512 if b
'conf-nfs' not in rados_obj_ls
and b
'userconf-nfs' in rados_obj_ls
:
513 self
.fail("User config not deleted")
515 self
._test
_mnt
(pseudo_path
, port
, ip
, False)
516 self
._cmd
('fs', 'volume', 'rm', fs_name
, '--yes-i-really-mean-it')
517 self
._test
_delete
_cluster
()
519 def test_cluster_set_user_config_with_non_existing_clusterid(self
):
521 Test setting user config for non-existing nfs cluster.
524 cluster_id
= 'invalidtest'
525 self
.ctx
.cluster
.run(args
=['sudo', 'ceph', 'nfs', 'cluster',
526 'config', 'set', self
.cluster_id
, '-i', '-'], stdin
='testing')
527 self
.fail(f
"User config set for non-existing cluster {cluster_id}")
528 except CommandFailedError
as e
:
529 # Command should fail for test to pass
530 if e
.exitstatus
!= errno
.ENOENT
:
533 def test_cluster_reset_user_config_with_non_existing_clusterid(self
):
535 Test resetting user config for non-existing nfs cluster.
538 cluster_id
= 'invalidtest'
539 self
._nfs
_cmd
('cluster', 'config', 'reset', cluster_id
)
540 self
.fail(f
"User config reset for non-existing cluster {cluster_id}")
541 except CommandFailedError
as e
:
542 # Command should fail for test to pass
543 if e
.exitstatus
!= errno
.ENOENT
:
546 def test_update_export(self
):
548 Test update of exports
550 self
._create
_default
_export
()
551 port
, ip
= self
._get
_port
_ip
_info
()
552 self
._test
_mnt
(self
.pseudo_path
, port
, ip
)
553 export_block
= self
._get
_export
()
554 new_pseudo_path
= '/testing'
555 export_block
['pseudo'] = new_pseudo_path
556 export_block
['access_type'] = 'RO'
557 self
.ctx
.cluster
.run(args
=['sudo', 'ceph', 'nfs', 'export', 'update', '-i', '-'],
558 stdin
=json
.dumps(export_block
))
559 self
._check
_nfs
_cluster
_status
('running', 'NFS Ganesha cluster restart failed')
560 self
._write
_to
_read
_only
_export
(new_pseudo_path
, port
, ip
)
561 self
._test
_delete
_cluster
()
563 def test_update_export_with_invalid_values(self
):
565 Test update of export with invalid values
567 self
._create
_default
_export
()
568 export_block
= self
._get
_export
()
570 def update_with_invalid_values(key
, value
, fsal
=False):
571 export_block_new
= dict(export_block
)
573 export_block_new
['fsal'] = dict(export_block
['fsal'])
574 export_block_new
['fsal'][key
] = value
576 export_block_new
[key
] = value
578 self
.ctx
.cluster
.run(args
=['sudo', 'ceph', 'nfs', 'export', 'update', '-i', '-'],
579 stdin
=json
.dumps(export_block_new
))
580 except CommandFailedError
:
583 update_with_invalid_values('export_id', 9)
584 update_with_invalid_values('cluster_id', 'testing_new')
585 update_with_invalid_values('pseudo', 'test_relpath')
586 update_with_invalid_values('access_type', 'W')
587 update_with_invalid_values('squash', 'no_squash')
588 update_with_invalid_values('security_label', 'invalid')
589 update_with_invalid_values('protocols', [2])
590 update_with_invalid_values('transports', ['UD'])
591 update_with_invalid_values('name', 'RGW', True)
592 update_with_invalid_values('user_id', 'testing_export', True)
593 update_with_invalid_values('fs_name', 'b', True)
594 self
._test
_delete
_cluster
()
596 def test_cmds_without_reqd_args(self
):
598 Test that cmd fails on not passing required arguments
600 def exec_cmd_invalid(*cmd
):
603 self
.fail(f
"nfs {cmd} command executed successfully without required arguments")
604 except CommandFailedError
as e
:
605 # Command should fail for test to pass
606 if e
.exitstatus
!= errno
.EINVAL
:
609 exec_cmd_invalid('cluster', 'create')
610 exec_cmd_invalid('cluster', 'delete')
611 exec_cmd_invalid('cluster', 'config', 'set')
612 exec_cmd_invalid('cluster', 'config', 'reset')
613 exec_cmd_invalid('export', 'create', 'cephfs')
614 exec_cmd_invalid('export', 'create', 'cephfs', 'a_fs')
615 exec_cmd_invalid('export', 'create', 'cephfs', 'a_fs', 'clusterid')
616 exec_cmd_invalid('export', 'ls')
617 exec_cmd_invalid('export', 'delete')
618 exec_cmd_invalid('export', 'delete', 'clusterid')
619 exec_cmd_invalid('export', 'get')
620 exec_cmd_invalid('export', 'get', 'clusterid')