2 Before running this testsuite, add path to cephfs-shell module to $PATH and
5 from io
import StringIO
9 from tempfile
import mkstemp
as tempfile_mkstemp
11 from time
import sleep
12 from tasks
.cephfs
.cephfs_test_case
import CephFSTestCase
13 from teuthology
.orchestra
.run
import CommandFailedError
15 log
= logging
.getLogger(__name__
)
17 def humansize(nbytes
):
18 suffixes
= ['B', 'K', 'M', 'G', 'T', 'P']
20 while nbytes
>= 1024 and i
< len(suffixes
)-1:
23 nbytes
= math
.ceil(nbytes
)
24 f
= ('%d' % nbytes
).rstrip('.')
25 return '%s%s' % (f
, suffixes
[i
])
28 if isinstance(s
, str):
30 if isinstance(s
, bytes
):
32 raise TypeError("not expecting type '%s'" % type(s
))
34 class TestCephFSShell(CephFSTestCase
):
38 super(TestCephFSShell
, self
).setUp()
40 conf_contents
= "[cephfs-shell]\ncolors = False\ndebug = True\n"
41 confpath
= self
.mount_a
.client_remote
.sh('mktemp').strip()
42 self
.mount_a
.client_remote
.write_file(confpath
, conf_contents
)
43 self
.default_shell_conf_path
= confpath
45 def run_cephfs_shell_cmd(self
, cmd
, mount_x
=None, shell_conf_path
=None,
46 opts
=None, stdout
=None, stderr
=None, stdin
=None,
48 stdout
= stdout
or StringIO()
49 stderr
= stderr
or StringIO()
51 mount_x
= self
.mount_a
52 if isinstance(cmd
, list):
54 if not shell_conf_path
:
55 shell_conf_path
= self
.default_shell_conf_path
57 args
= ["cephfs-shell", "-c", shell_conf_path
]
60 args
.extend(("--", cmd
))
62 log
.info("Running command: {}".format(" ".join(args
)))
63 return mount_x
.client_remote
.run(args
=args
, stdout
=stdout
,
64 stderr
=stderr
, stdin
=stdin
,
65 check_status
=check_status
)
67 def negtest_cephfs_shell_cmd(self
, **kwargs
):
69 This method verifies that cephfs shell command fails with expected
70 return value and/or error message.
72 kwargs is expected to hold the arguments same as
73 run_cephfs_shell_cmd() with the following exceptions -
74 * It should not contain check_status (since commands are expected
75 to fail, check_status is hardcoded to False).
76 * It is optional to set expected error message and return value to
77 dict members 'errmsg' and 'retval' respectively.
79 This method servers as shorthand for codeblocks like -
82 proc = self.run_cephfs_shell_cmd(args=['some', 'cmd'],
85 except CommandFailedError as e:
86 self.assertNotIn('some error message',
87 proc.stderr.getvalue.lower())
91 proc = self.run_cephfs_shell_cmd(args=['some', 'cmd'],
94 except CommandFailedError as e:
95 self.assertNotEqual(1, proc.returncode)
97 retval
= kwargs
.pop('retval', None)
98 errmsg
= kwargs
.pop('errmsg', None)
99 kwargs
['check_status'] = False
101 proc
= self
.run_cephfs_shell_cmd(**kwargs
)
103 self
.assertEqual(proc
.returncode
, retval
)
105 self
.assertNotEqual(proc
.returncode
, 0)
107 self
.assertIn(errmsg
, proc
.stderr
.getvalue().lower())
111 def get_cephfs_shell_cmd_output(self
, cmd
, mount_x
=None,
112 shell_conf_path
=None, opts
=None,
113 stdout
=None, stdin
=None,check_status
=True):
114 return ensure_str(self
.run_cephfs_shell_cmd(
115 cmd
=cmd
, mount_x
=mount_x
, shell_conf_path
=shell_conf_path
,
116 opts
=opts
, stdout
=stdout
, stdin
=stdin
,
117 check_status
=check_status
).stdout
.getvalue().strip())
119 def get_cephfs_shell_cmd_error(self
, cmd
, mount_x
=None,
120 shell_conf_path
=None, opts
=None,
121 stderr
=None, stdin
=None, check_status
=True):
122 return ensure_str(self
.run_cephfs_shell_cmd(
123 cmd
=cmd
, mount_x
=mount_x
, shell_conf_path
=shell_conf_path
,
124 opts
=opts
, stderr
=stderr
, stdin
=stdin
,
125 check_status
=check_status
).stderr
.getvalue().strip())
127 def run_cephfs_shell_script(self
, script
, mount_x
=None,
128 shell_conf_path
=None, opts
=None, stdout
=None,
129 stderr
=None, stdin
=None, check_status
=True):
130 stdout
= stdout
or StringIO()
131 stderr
= stderr
or StringIO()
133 mount_x
= self
.mount_a
135 scriptpath
= tempfile_mkstemp(prefix
='test-cephfs', text
=True)[1]
136 with
open(scriptpath
, 'w') as scriptfile
:
137 scriptfile
.write(script
)
138 # copy script to the machine running cephfs-shell.
139 mount_x
.client_remote
.put_file(scriptpath
, scriptpath
)
140 mount_x
.run_shell('chmod 755 ' + scriptpath
)
142 args
= ["cephfs-shell", '-b', scriptpath
]
144 args
[1:1] = ["-c", shell_conf_path
]
145 log
.info('Running script \"' + scriptpath
+ '\"')
146 return mount_x
.client_remote
.run(args
=args
, stdout
=stdout
,
147 stderr
=stderr
, stdin
=stdin
,
150 def get_cephfs_shell_script_output(self
, script
, mount_x
=None,
151 shell_conf_path
=None, opts
=None,
152 stdout
=None, stdin
=None,
154 return ensure_str(self
.run_cephfs_shell_script(
155 script
=script
, mount_x
=mount_x
, shell_conf_path
=shell_conf_path
,
156 opts
=opts
, stdout
=stdout
, stdin
=stdin
,
157 check_status
=check_status
).stdout
.getvalue().strip())
160 class TestMkdir(TestCephFSShell
):
161 def test_mkdir(self
):
163 Test that mkdir creates directory
165 o
= self
.get_cephfs_shell_cmd_output("mkdir d1")
166 log
.info("cephfs-shell output:\n{}".format(o
))
168 o
= self
.mount_a
.stat('d1')
169 log
.info("mount_a output:\n{}".format(o
))
171 def test_mkdir_with_07000_octal_mode(self
):
173 Test that mkdir fails with octal mode greater than 0777
175 self
.negtest_cephfs_shell_cmd(cmd
="mkdir -m 07000 d2")
177 self
.mount_a
.stat('d2')
178 except CommandFailedError
:
181 def test_mkdir_with_negative_octal_mode(self
):
183 Test that mkdir fails with negative octal mode
185 self
.negtest_cephfs_shell_cmd(cmd
="mkdir -m -0755 d3")
187 self
.mount_a
.stat('d3')
188 except CommandFailedError
:
191 def test_mkdir_with_non_octal_mode(self
):
193 Test that mkdir passes with non-octal mode
195 o
= self
.get_cephfs_shell_cmd_output("mkdir -m u=rwx d4")
196 log
.info("cephfs-shell output:\n{}".format(o
))
198 # mkdir d4 should pass
199 o
= self
.mount_a
.stat('d4')
200 assert((o
['st_mode'] & 0o700) == 0o700)
202 def test_mkdir_with_bad_non_octal_mode(self
):
204 Test that mkdir failes with bad non-octal mode
206 self
.negtest_cephfs_shell_cmd(cmd
="mkdir -m ugx=0755 d5")
208 self
.mount_a
.stat('d5')
209 except CommandFailedError
:
212 def test_mkdir_path_without_path_option(self
):
214 Test that mkdir fails without path option for creating path
216 self
.negtest_cephfs_shell_cmd(cmd
="mkdir d5/d6/d7")
218 self
.mount_a
.stat('d5/d6/d7')
219 except CommandFailedError
:
222 def test_mkdir_path_with_path_option(self
):
224 Test that mkdir passes with path option for creating path
226 o
= self
.get_cephfs_shell_cmd_output("mkdir -p d5/d6/d7")
227 log
.info("cephfs-shell output:\n{}".format(o
))
229 # mkdir d5/d6/d7 should pass
230 o
= self
.mount_a
.stat('d5/d6/d7')
231 log
.info("mount_a output:\n{}".format(o
))
233 class TestRmdir(TestCephFSShell
):
234 dir_name
= "test_dir"
236 def dir_does_not_exists(self
):
238 Tests that directory does not exists
241 self
.mount_a
.stat(self
.dir_name
)
242 except CommandFailedError
as e
:
243 if e
.exitstatus
== 2:
247 def test_rmdir(self
):
249 Test that rmdir deletes directory
251 self
.run_cephfs_shell_cmd("mkdir " + self
.dir_name
)
252 self
.run_cephfs_shell_cmd("rmdir "+ self
.dir_name
)
253 self
.dir_does_not_exists()
255 def test_rmdir_non_existing_dir(self
):
257 Test that rmdir does not delete a non existing directory
259 self
.negtest_cephfs_shell_cmd(cmd
="rmdir test_dir")
260 self
.dir_does_not_exists()
262 def test_rmdir_dir_with_file(self
):
264 Test that rmdir does not delete directory containing file
266 self
.run_cephfs_shell_cmd("mkdir " + self
.dir_name
)
267 self
.run_cephfs_shell_cmd("put - test_dir/dumpfile", stdin
="Valid File")
268 self
.run_cephfs_shell_cmd("rmdir" + self
.dir_name
)
269 self
.mount_a
.stat(self
.dir_name
)
271 def test_rmdir_existing_file(self
):
273 Test that rmdir does not delete a file
275 self
.run_cephfs_shell_cmd("put - dumpfile", stdin
="Valid File")
276 self
.negtest_cephfs_shell_cmd(cmd
="rmdir dumpfile")
277 self
.mount_a
.stat("dumpfile")
279 def test_rmdir_p(self
):
281 Test that rmdir -p deletes all empty directories in the root directory passed
283 self
.run_cephfs_shell_cmd("mkdir -p test_dir/t1/t2/t3")
284 self
.run_cephfs_shell_cmd("rmdir -p "+ self
.dir_name
)
285 self
.dir_does_not_exists()
287 def test_rmdir_p_valid_path(self
):
289 Test that rmdir -p deletes all empty directories in the path passed
291 self
.run_cephfs_shell_cmd("mkdir -p test_dir/t1/t2/t3")
292 self
.run_cephfs_shell_cmd("rmdir -p test_dir/t1/t2/t3")
293 self
.dir_does_not_exists()
295 def test_rmdir_p_non_existing_dir(self
):
297 Test that rmdir -p does not delete an invalid directory
299 self
.negtest_cephfs_shell_cmd(cmd
="rmdir -p test_dir")
300 self
.dir_does_not_exists()
302 def test_rmdir_p_dir_with_file(self
):
304 Test that rmdir -p does not delete the directory containing a file
306 self
.run_cephfs_shell_cmd("mkdir " + self
.dir_name
)
307 self
.run_cephfs_shell_cmd("put - test_dir/dumpfile", stdin
="Valid File")
308 self
.run_cephfs_shell_cmd("rmdir -p " + self
.dir_name
)
309 self
.mount_a
.stat(self
.dir_name
)
311 class TestGetAndPut(TestCephFSShell
):
312 def test_without_target_dir(self
):
314 Test put and get commands without target path.
316 tempdir
= self
.mount_a
.client_remote
.mkdtemp()
317 tempdirname
= path
.basename(tempdir
)
318 files
= ('dump1', 'dump2', 'dump3', tempdirname
)
320 for i
, file_
in enumerate(files
[ : -1]):
322 ofarg
= 'of=' + path
.join(tempdir
, file_
)
323 bsarg
= 'bs=' + str(size
) + 'M'
324 self
.mount_a
.run_shell(['dd', 'if=/dev/urandom', ofarg
, bsarg
,
327 self
.run_cephfs_shell_cmd('put ' + tempdir
)
329 if file_
== tempdirname
:
330 self
.mount_a
.stat(path
.join(self
.mount_a
.mountpoint
, file_
))
332 self
.mount_a
.stat(path
.join(self
.mount_a
.mountpoint
,
335 self
.mount_a
.run_shell(['rm', '-rf', tempdir
])
337 self
.run_cephfs_shell_cmd('get ' + tempdirname
)
338 pwd
= self
.get_cephfs_shell_cmd_output('!pwd')
340 if file_
== tempdirname
:
341 self
.mount_a
.run_shell('stat ' + path
.join(pwd
, file_
))
343 self
.mount_a
.run_shell('stat ' + path
.join(pwd
, tempdirname
,
346 def test_get_with_target_name(self
):
348 Test that get passes with target name
351 s_hash
= crypt
.crypt(s
, '.A')
352 o
= self
.get_cephfs_shell_cmd_output("put - dump4", stdin
=s
)
353 log
.info("cephfs-shell output:\n{}".format(o
))
355 # put - dump4 should pass
356 o
= self
.mount_a
.stat('dump4')
357 log
.info("mount_a output:\n{}".format(o
))
359 o
= self
.get_cephfs_shell_cmd_output("get dump4 .")
360 log
.info("cephfs-shell output:\n{}".format(o
))
362 o
= self
.get_cephfs_shell_cmd_output("!cat dump4")
363 o_hash
= crypt
.crypt(o
, '.A')
365 # s_hash must be equal to o_hash
366 log
.info("s_hash:{}".format(s_hash
))
367 log
.info("o_hash:{}".format(o_hash
))
368 assert(s_hash
== o_hash
)
370 def test_get_without_target_name(self
):
372 Test that get passes with target name
375 o
= self
.get_cephfs_shell_cmd_output("put - dump5", stdin
=s
)
376 log
.info("cephfs-shell output:\n{}".format(o
))
378 # put - dump5 should pass
379 o
= self
.mount_a
.stat('dump5')
380 log
.info("mount_a output:\n{}".format(o
))
382 # get dump5 should fail
383 o
= self
.get_cephfs_shell_cmd_output("get dump5")
384 o
= self
.get_cephfs_shell_cmd_output("!stat dump5 || echo $?")
385 log
.info("cephfs-shell output:\n{}".format(o
))
389 # verify that stat dump5 passes
390 # if ret == 1, then that implies the stat failed
391 # which implies that there was a problem with "get dump5"
394 # we have a valid stat output; so this is good
395 # if the int() fails then that means there's a valid stat output
398 def test_get_to_console(self
):
400 Test that get passes with target name
403 s_hash
= crypt
.crypt(s
, '.A')
404 o
= self
.get_cephfs_shell_cmd_output("put - dump6", stdin
=s
)
405 log
.info("cephfs-shell output:\n{}".format(o
))
407 # put - dump6 should pass
408 o
= self
.mount_a
.stat('dump6')
409 log
.info("mount_a output:\n{}".format(o
))
411 # get dump6 - should pass
412 o
= self
.get_cephfs_shell_cmd_output("get dump6 -")
413 o_hash
= crypt
.crypt(o
, '.A')
414 log
.info("cephfs-shell output:\n{}".format(o
))
416 # s_hash must be equal to o_hash
417 log
.info("s_hash:{}".format(s_hash
))
418 log
.info("o_hash:{}".format(o_hash
))
419 assert(s_hash
== o_hash
)
421 class TestSnapshots(TestCephFSShell
):
424 Test that snapshot creation and deletion work
426 sd
= self
.fs
.get_config('client_snapdir')
427 sdn
= "data_dir/{}/snap1".format(sd
)
429 # create a data dir and dump some files into it
430 self
.get_cephfs_shell_cmd_output("mkdir data_dir")
432 o
= self
.get_cephfs_shell_cmd_output("put - data_dir/data_a", stdin
=s
)
434 o
= self
.get_cephfs_shell_cmd_output("put - data_dir/data_b", stdin
=s
)
436 o
= self
.get_cephfs_shell_cmd_output("put - data_dir/data_c", stdin
=s
)
438 o
= self
.get_cephfs_shell_cmd_output("put - data_dir/data_d", stdin
=s
)
440 o
= self
.get_cephfs_shell_cmd_output("put - data_dir/data_e", stdin
=s
)
442 o
= self
.get_cephfs_shell_cmd_output("ls -l /data_dir")
443 log
.info("cephfs-shell output:\n{}".format(o
))
445 # create the snapshot - must pass
446 o
= self
.get_cephfs_shell_cmd_output("snap create snap1 /data_dir")
447 log
.info("cephfs-shell output:\n{}".format(o
))
448 self
.assertEqual("", o
)
449 o
= self
.mount_a
.stat(sdn
)
450 log
.info("mount_a output:\n{}".format(o
))
451 self
.assertIn('st_mode', o
)
453 # create the same snapshot again - must fail with an error message
454 self
.negtest_cephfs_shell_cmd(cmd
="snap create snap1 /data_dir",
455 errmsg
="snapshot 'snap1' already exists")
456 o
= self
.mount_a
.stat(sdn
)
457 log
.info("mount_a output:\n{}".format(o
))
458 self
.assertIn('st_mode', o
)
460 # delete the snapshot - must pass
461 o
= self
.get_cephfs_shell_cmd_output("snap delete snap1 /data_dir")
462 log
.info("cephfs-shell output:\n{}".format(o
))
463 self
.assertEqual("", o
)
465 o
= self
.mount_a
.stat(sdn
)
466 except CommandFailedError
:
467 # snap dir should not exist anymore
469 log
.info("mount_a output:\n{}".format(o
))
470 self
.assertNotIn('st_mode', o
)
472 # delete the same snapshot again - must fail with an error message
473 self
.negtest_cephfs_shell_cmd(cmd
="snap delete snap1 /data_dir",
474 errmsg
="'snap1': no such snapshot")
476 o
= self
.mount_a
.stat(sdn
)
477 except CommandFailedError
:
479 log
.info("mount_a output:\n{}".format(o
))
480 self
.assertNotIn('st_mode', o
)
482 class TestCD(TestCephFSShell
):
485 def test_cd_with_no_args(self
):
487 Test that when cd is issued without any arguments, CWD is changed
490 path
= 'dir1/dir2/dir3'
491 self
.mount_a
.run_shell('mkdir -p ' + path
)
494 script
= 'cd {}\ncd\ncwd\n'.format(path
)
495 output
= self
.get_cephfs_shell_script_output(script
)
496 self
.assertEqual(output
, expected_cwd
)
498 def test_cd_with_args(self
):
500 Test that when cd is issued with an argument, CWD is changed
501 to the path passed in the argument.
503 path
= 'dir1/dir2/dir3'
504 self
.mount_a
.run_shell('mkdir -p ' + path
)
505 expected_cwd
= '/dir1/dir2/dir3'
507 script
= 'cd {}\ncwd\n'.format(path
)
508 output
= self
.get_cephfs_shell_script_output(script
)
509 self
.assertEqual(output
, expected_cwd
)
511 class TestDU(TestCephFSShell
):
514 def test_du_works_for_regfiles(self
):
515 regfilename
= 'some_regfile'
516 regfile_abspath
= path
.join(self
.mount_a
.mountpoint
, regfilename
)
517 self
.mount_a
.client_remote
.write_file(regfile_abspath
,
518 'somedata', sudo
=True)
520 size
= humansize(self
.mount_a
.stat(regfile_abspath
)['st_size'])
521 expected_output
= r
'{}{}{}'.format(size
, " +", regfilename
)
523 du_output
= self
.get_cephfs_shell_cmd_output('du ' + regfilename
)
524 self
.assertRegex(du_output
, expected_output
)
526 def test_du_works_for_non_empty_dirs(self
):
527 dirname
= 'some_directory'
528 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, dirname
)
529 regfilename
= 'some_regfile'
530 regfile_abspath
= path
.join(dir_abspath
, regfilename
)
531 self
.mount_a
.run_shell('mkdir ' + dir_abspath
)
532 self
.mount_a
.client_remote
.write_file(regfile_abspath
,
533 'somedata', sudo
=True)
535 # XXX: we stat `regfile_abspath` here because ceph du reports a non-empty
536 # directory's size as sum of sizes of all files under it.
537 size
= humansize(self
.mount_a
.stat(regfile_abspath
)['st_size'])
538 expected_output
= r
'{}{}{}'.format(size
, " +", dirname
)
541 du_output
= self
.get_cephfs_shell_cmd_output('du ' + dirname
)
542 self
.assertRegex(du_output
, expected_output
)
544 def test_du_works_for_empty_dirs(self
):
545 dirname
= 'some_directory'
546 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, dirname
)
547 self
.mount_a
.run_shell('mkdir ' + dir_abspath
)
549 size
= humansize(self
.mount_a
.stat(dir_abspath
)['st_size'])
550 expected_output
= r
'{}{}{}'.format(size
, " +", dirname
)
552 du_output
= self
.get_cephfs_shell_cmd_output('du ' + dirname
)
553 self
.assertRegex(du_output
, expected_output
)
555 def test_du_works_for_hardlinks(self
):
556 regfilename
= 'some_regfile'
557 regfile_abspath
= path
.join(self
.mount_a
.mountpoint
, regfilename
)
558 self
.mount_a
.client_remote
.write_file(regfile_abspath
,
559 'somedata', sudo
=True)
560 hlinkname
= 'some_hardlink'
561 hlink_abspath
= path
.join(self
.mount_a
.mountpoint
, hlinkname
)
562 self
.mount_a
.run_shell(['sudo', 'ln', regfile_abspath
,
563 hlink_abspath
], omit_sudo
=False)
565 size
= humansize(self
.mount_a
.stat(hlink_abspath
)['st_size'])
566 expected_output
= r
'{}{}{}'.format(size
, " +", hlinkname
)
568 du_output
= self
.get_cephfs_shell_cmd_output('du ' + hlinkname
)
569 self
.assertRegex(du_output
, expected_output
)
571 def test_du_works_for_softlinks_to_files(self
):
572 regfilename
= 'some_regfile'
573 regfile_abspath
= path
.join(self
.mount_a
.mountpoint
, regfilename
)
574 self
.mount_a
.client_remote
.write_file(regfile_abspath
,
575 'somedata', sudo
=True)
576 slinkname
= 'some_softlink'
577 slink_abspath
= path
.join(self
.mount_a
.mountpoint
, slinkname
)
578 self
.mount_a
.run_shell(['ln', '-s', regfile_abspath
, slink_abspath
])
580 size
= humansize(self
.mount_a
.lstat(slink_abspath
)['st_size'])
581 expected_output
= r
'{}{}{}'.format((size
), " +", slinkname
)
583 du_output
= self
.get_cephfs_shell_cmd_output('du ' + slinkname
)
584 self
.assertRegex(du_output
, expected_output
)
586 def test_du_works_for_softlinks_to_dirs(self
):
587 dirname
= 'some_directory'
588 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, dirname
)
589 self
.mount_a
.run_shell('mkdir ' + dir_abspath
)
590 slinkname
= 'some_softlink'
591 slink_abspath
= path
.join(self
.mount_a
.mountpoint
, slinkname
)
592 self
.mount_a
.run_shell(['ln', '-s', dir_abspath
, slink_abspath
])
594 size
= humansize(self
.mount_a
.lstat(slink_abspath
)['st_size'])
595 expected_output
= r
'{}{}{}'.format(size
, " +", slinkname
)
597 du_output
= self
.get_cephfs_shell_cmd_output('du ' + slinkname
)
598 self
.assertRegex(du_output
, expected_output
)
600 # NOTE: tests using these are pretty slow since to this methods sleeps for
602 def _setup_files(self
, return_path_to_files
=False, path_prefix
='./'):
604 regfilename
= 'regfile'
607 slink2name
= 'slink2'
609 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, dirname
)
610 regfile_abspath
= path
.join(self
.mount_a
.mountpoint
, regfilename
)
611 hlink_abspath
= path
.join(self
.mount_a
.mountpoint
, hlinkname
)
612 slink_abspath
= path
.join(self
.mount_a
.mountpoint
, slinkname
)
613 slink2_abspath
= path
.join(self
.mount_a
.mountpoint
, slink2name
)
615 self
.mount_a
.run_shell('mkdir ' + dir_abspath
)
616 self
.mount_a
.run_shell('touch ' + regfile_abspath
)
617 self
.mount_a
.run_shell(['ln', regfile_abspath
, hlink_abspath
])
618 self
.mount_a
.run_shell(['ln', '-s', regfile_abspath
, slink_abspath
])
619 self
.mount_a
.run_shell(['ln', '-s', dir_abspath
, slink2_abspath
])
623 regfile121_name
= 'regfile121'
624 dir2_abspath
= path
.join(self
.mount_a
.mountpoint
, dir2_name
)
625 dir21_abspath
= path
.join(dir2_abspath
, dir21_name
)
626 regfile121_abspath
= path
.join(dir21_abspath
, regfile121_name
)
627 self
.mount_a
.run_shell('mkdir -p ' + dir21_abspath
)
628 self
.mount_a
.run_shell('touch ' + regfile121_abspath
)
630 self
.mount_a
.client_remote
.write_file(regfile_abspath
,
631 'somedata', sudo
=True)
632 self
.mount_a
.client_remote
.write_file(regfile121_abspath
,
633 'somemoredata', sudo
=True)
635 # TODO: is there a way to trigger/force update ceph.dir.rbytes?
636 # wait so that attr ceph.dir.rbytes gets a chance to be updated.
639 expected_patterns
= []
642 def append_expected_output_pattern(f
):
644 expected_patterns
.append(r
'{}{}{}'.format(size
, " +", '.' + f
))
646 expected_patterns
.append(r
'{}{}{}'.format(size
, " +",
647 path_prefix
+ path
.relpath(f
, self
.mount_a
.mountpoint
)))
649 for f
in [dir_abspath
, regfile_abspath
, regfile121_abspath
,
650 hlink_abspath
, slink_abspath
, slink2_abspath
]:
651 size
= humansize(self
.mount_a
.stat(f
, follow_symlinks
=
653 append_expected_output_pattern(f
)
655 # get size for directories containig regfiles within
656 for f
in [dir2_abspath
, dir21_abspath
]:
657 size
= humansize(self
.mount_a
.stat(regfile121_abspath
,
658 follow_symlinks
=False)['st_size'])
659 append_expected_output_pattern(f
)
661 # get size for CephFS root
663 for f
in [regfile_abspath
, regfile121_abspath
, slink_abspath
,
665 size
+= self
.mount_a
.stat(f
, follow_symlinks
=False)['st_size']
666 size
= humansize(size
)
667 append_expected_output_pattern('/')
669 if return_path_to_files
:
670 for p
in [dir_abspath
, regfile_abspath
, dir2_abspath
,
671 dir21_abspath
, regfile121_abspath
, hlink_abspath
,
672 slink_abspath
, slink2_abspath
]:
673 path_to_files
.append(path
.relpath(p
, self
.mount_a
.mountpoint
))
675 return (expected_patterns
, path_to_files
)
677 return expected_patterns
679 def test_du_works_recursively_with_no_path_in_args(self
):
680 expected_patterns_in_output
= self
._setup
_files
()
681 du_output
= self
.get_cephfs_shell_cmd_output('du -r')
683 for expected_output
in expected_patterns_in_output
:
684 self
.assertRegex(du_output
, expected_output
)
686 def test_du_with_path_in_args(self
):
687 expected_patterns_in_output
, path_to_files
= self
._setup
_files
(True,
691 for p
in path_to_files
:
693 du_output
= self
.get_cephfs_shell_cmd_output(args
)
695 for expected_output
in expected_patterns_in_output
:
696 self
.assertRegex(du_output
, expected_output
)
698 def test_du_with_no_args(self
):
699 expected_patterns_in_output
= self
._setup
_files
()
701 du_output
= self
.get_cephfs_shell_cmd_output('du')
703 for expected_output
in expected_patterns_in_output
:
704 # Since CWD is CephFS root and being non-recursive expect only
706 if expected_output
.find('/') == len(expected_output
) - 1:
707 self
.assertRegex(du_output
, expected_output
)
710 class TestDF(TestCephFSShell
):
711 def validate_df(self
, filename
):
712 df_output
= self
.get_cephfs_shell_cmd_output('df '+filename
)
713 log
.info("cephfs-shell df output:\n{}".format(df_output
))
715 shell_df
= df_output
.splitlines()[1].split()
717 block_size
= int(self
.mount_a
.df()["total"]) // 1024
718 log
.info("cephfs df block size output:{}\n".format(block_size
))
720 st_size
= int(self
.mount_a
.stat(filename
)["st_size"])
721 log
.info("cephfs stat used output:{}".format(st_size
))
722 log
.info("cephfs available:{}\n".format(block_size
- st_size
))
724 self
.assertTupleEqual((block_size
, st_size
, block_size
- st_size
),
725 (int(shell_df
[0]), int(shell_df
[1]) , int(shell_df
[2])))
727 def test_df_with_no_args(self
):
729 df_output
= self
.get_cephfs_shell_cmd_output('df')
730 assert df_output
== expected_output
732 def test_df_for_valid_directory(self
):
734 mount_output
= self
.mount_a
.run_shell('mkdir ' + dir_name
)
735 log
.info("cephfs-shell mount output:\n{}".format(mount_output
))
736 self
.validate_df(dir_name
)
738 def test_df_for_invalid_directory(self
):
739 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, 'non-existent-dir')
740 self
.negtest_cephfs_shell_cmd(cmd
='df ' + dir_abspath
,
741 errmsg
='error in stat')
743 def test_df_for_valid_file(self
):
744 s
= 'df test' * 14145016
745 o
= self
.get_cephfs_shell_cmd_output("put - dumpfile", stdin
=s
)
746 log
.info("cephfs-shell output:\n{}".format(o
))
747 self
.validate_df("dumpfile")
750 class TestQuota(TestCephFSShell
):
753 def create_dir(self
):
754 mount_output
= self
.get_cephfs_shell_cmd_output('mkdir ' + self
.dir_name
)
755 log
.info("cephfs-shell mount output:\n{}".format(mount_output
))
757 def set_and_get_quota_vals(self
, input_val
, check_status
=True):
758 self
.run_cephfs_shell_cmd(['quota', 'set', '--max_bytes',
759 input_val
[0], '--max_files', input_val
[1],
760 self
.dir_name
], check_status
=check_status
)
762 quota_output
= self
.get_cephfs_shell_cmd_output(['quota', 'get', self
.dir_name
],
763 check_status
=check_status
)
765 quota_output
= quota_output
.split()
766 return quota_output
[1], quota_output
[3]
770 set_values
= ('6', '2')
771 self
.assertTupleEqual(self
.set_and_get_quota_vals(set_values
), set_values
)
773 def test_replace_values(self
):
775 set_values
= ('20', '4')
776 self
.assertTupleEqual(self
.set_and_get_quota_vals(set_values
), set_values
)
778 def test_set_invalid_dir(self
):
779 set_values
= ('5', '5')
781 self
.assertTupleEqual(self
.set_and_get_quota_vals(
782 set_values
, False), set_values
)
783 raise Exception("Something went wrong!! Values set for non existing directory")
785 # Test should pass as values cannot be set for non existing directory
788 def test_set_invalid_values(self
):
790 set_values
= ('-6', '-5')
792 self
.assertTupleEqual(self
.set_and_get_quota_vals(set_values
,
794 raise Exception("Something went wrong!! Invalid values set")
796 # Test should pass as invalid values cannot be set
799 def test_exceed_file_limit(self
):
801 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, self
.dir_name
)
802 self
.mount_a
.run_shell('touch '+dir_abspath
+'/file1')
803 file2
= path
.join(dir_abspath
, "file2")
805 self
.mount_a
.run_shell('touch '+file2
)
806 raise Exception("Something went wrong!! File creation should have failed")
807 except CommandFailedError
:
808 # Test should pass as file quota set to 2
809 # Additional condition to confirm file creation failure
810 if not path
.exists(file2
):
814 def test_exceed_write_limit(self
):
816 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, self
.dir_name
)
817 filename
= 'test_file'
818 file_abspath
= path
.join(dir_abspath
, filename
)
820 # Write should fail as bytes quota is set to 6
821 self
.mount_a
.client_remote
.write_file(file_abspath
,
822 'Disk raise Exception', sudo
=True)
823 raise Exception("Write should have failed")
824 except CommandFailedError
:
825 # Test should pass only when write command fails
826 path_exists
= path
.exists(file_abspath
)
828 # Testing with teuthology: No file is created.
830 elif path_exists
and not path
.getsize(file_abspath
):
831 # Testing on Fedora 30: When write fails, empty file gets created.
837 class TestXattr(TestCephFSShell
):
840 def create_dir(self
):
841 self
.run_cephfs_shell_cmd('mkdir ' + self
.dir_name
)
843 def set_get_list_xattr_vals(self
, input_val
, negtest
=False):
844 setxattr_output
= self
.get_cephfs_shell_cmd_output(
845 ['setxattr', self
.dir_name
, input_val
[0], input_val
[1]])
846 log
.info("cephfs-shell setxattr output:\n{}".format(setxattr_output
))
848 getxattr_output
= self
.get_cephfs_shell_cmd_output(
849 ['getxattr', self
.dir_name
, input_val
[0]])
850 log
.info("cephfs-shell getxattr output:\n{}".format(getxattr_output
))
852 listxattr_output
= self
.get_cephfs_shell_cmd_output(
853 ['listxattr', self
.dir_name
])
854 log
.info("cephfs-shell listxattr output:\n{}".format(listxattr_output
))
856 return listxattr_output
, getxattr_output
860 set_values
= ('user.key', '2')
861 self
.assertTupleEqual(self
.set_get_list_xattr_vals(set_values
), set_values
)
863 def test_reset(self
):
865 set_values
= ('user.key', '4')
866 self
.assertTupleEqual(self
.set_get_list_xattr_vals(set_values
), set_values
)
868 def test_non_existing_dir(self
):
869 input_val
= ('user.key', '9')
870 self
.negtest_cephfs_shell_cmd(cmd
=['setxattr', self
.dir_name
, input_val
[0],
872 self
.negtest_cephfs_shell_cmd(cmd
=['getxattr', self
.dir_name
, input_val
[0]])
873 self
.negtest_cephfs_shell_cmd(cmd
=['listxattr', self
.dir_name
])
875 class TestLS(TestCephFSShell
):
876 dir_name
= ('test_dir')
877 hidden_dir_name
= ('.test_hidden_dir')
880 """ Test that ls prints files in CWD. """
881 self
.run_cephfs_shell_cmd(f
'mkdir {self.dir_name}')
883 ls_output
= self
.get_cephfs_shell_cmd_output("ls")
884 log
.info(f
"output of ls command:\n{ls_output}")
886 self
.assertIn(self
.dir_name
, ls_output
)
889 """ Test ls -a prints hidden files in CWD."""
891 self
.run_cephfs_shell_cmd(f
'mkdir {self.hidden_dir_name}')
893 ls_a_output
= self
.get_cephfs_shell_cmd_output(['ls', '-a'])
894 log
.info(f
"output of ls -a command:\n{ls_a_output}")
896 self
.assertIn(self
.hidden_dir_name
, ls_a_output
)
898 def test_ls_does_not_print_hidden_dir(self
):
899 """ Test ls command does not print hidden directory """
901 self
.run_cephfs_shell_cmd(f
'mkdir {self.hidden_dir_name}')
903 ls_output
= self
.get_cephfs_shell_cmd_output("ls")
904 log
.info(f
"output of ls command:\n{ls_output}")
906 self
.assertNotIn(self
.hidden_dir_name
, ls_output
)
908 def test_ls_a_prints_non_hidden_dir(self
):
909 """ Test ls -a command prints non hidden directory """
911 self
.run_cephfs_shell_cmd(f
'mkdir {self.hidden_dir_name} {self.dir_name}')
913 ls_a_output
= self
.get_cephfs_shell_cmd_output(['ls', '-a'])
914 log
.info(f
"output of ls -a command:\n{ls_a_output}")
916 self
.assertIn(self
.dir_name
, ls_a_output
)
918 def test_ls_H_prints_human_readable_file_size(self
):
919 """ Test "ls -lH" prints human readable file size."""
921 file_sizes
= ['1','1K', '1M', '1G']
922 file_names
= ['dump1', 'dump2', 'dump3', 'dump4']
925 for (file_size
, file_name
) in zip(file_sizes
, file_names
):
926 temp_file
= self
.mount_a
.client_remote
.mktemp(file_name
)
927 self
.mount_a
.run_shell(f
"fallocate -l {file_size} {temp_file}")
928 self
.mount_a
.run_shell(f
'mv {temp_file} ./')
930 ls_H_output
= self
.get_cephfs_shell_cmd_output(['ls', '-lH'])
932 ls_H_file_size
= set()
933 for line
in ls_H_output
.split('\n'):
934 ls_H_file_size
.add(line
.split()[1])
936 # test that file sizes are in human readable format
937 self
.assertEqual({'1B','1K', '1M', '1G'}, ls_H_file_size
)
939 def test_ls_s_sort_by_size(self
):
940 """ Test "ls -S" sorts file listing by file_size """
941 test_file1
= "test_file1.txt"
942 test_file2
= "test_file2.txt"
943 file1_content
= 'A' * 102
944 file2_content
= 'B' * 10
946 self
.run_cephfs_shell_cmd(f
"write {test_file1}", stdin
=file1_content
)
947 self
.run_cephfs_shell_cmd(f
"write {test_file2}", stdin
=file2_content
)
949 ls_s_output
= self
.get_cephfs_shell_cmd_output(['ls', '-lS'])
952 for line
in ls_s_output
.split('\n'):
953 file_sizes
.append(line
.split()[1])
955 #test that file size are in ascending order
956 self
.assertEqual(file_sizes
, sorted(file_sizes
))
959 class TestMisc(TestCephFSShell
):
960 def test_issue_cephfs_shell_cmd_at_invocation(self
):
962 Test that `cephfs-shell -c conf cmd` works.
964 # choosing a long name since short ones have a higher probability
965 # of getting matched by coincidence.
966 dirname
= 'somedirectory'
967 self
.run_cephfs_shell_cmd(['mkdir', dirname
])
969 output
= self
.mount_a
.client_remote
.sh(['cephfs-shell', 'ls']).\
972 self
.assertRegex(output
, dirname
)
976 Test that help outputs commands.
978 o
= self
.get_cephfs_shell_cmd_output("help all")
979 log
.info("output:\n{}".format(o
))
981 class TestShellOpts(TestCephFSShell
):
983 Contains tests for shell options from conf file and shell prompt.
987 super(type(self
), self
).setUp()
989 # output of following command -
990 # editor - was: 'vim'
993 self
.editor_val
= self
.get_cephfs_shell_cmd_output(
994 'set editor ?, set editor').split('\n')[2]
995 self
.editor_val
= self
.editor_val
.split(':')[1].\
996 replace("'", "", 2).strip()
998 def write_tempconf(self
, confcontents
):
999 self
.tempconfpath
= self
.mount_a
.client_remote
.mktemp(
1000 suffix
='cephfs-shell.conf')
1001 self
.mount_a
.client_remote
.write_file(self
.tempconfpath
,
1004 def test_reading_conf(self
):
1005 self
.write_tempconf("[cephfs-shell]\neditor = ???")
1007 # output of following command -
1008 # CephFS:~/>>> set editor
1010 final_editor_val
= self
.get_cephfs_shell_cmd_output(
1011 cmd
='set editor', shell_conf_path
=self
.tempconfpath
)
1012 final_editor_val
= final_editor_val
.split(': ')[1]
1013 final_editor_val
= final_editor_val
.replace("'", "", 2)
1015 self
.assertNotEqual(self
.editor_val
, final_editor_val
)
1017 def test_reading_conf_with_dup_opt(self
):
1019 Read conf without duplicate sections/options.
1021 self
.write_tempconf("[cephfs-shell]\neditor = ???\neditor = " +
1024 # output of following command -
1025 # CephFS:~/>>> set editor
1027 final_editor_val
= self
.get_cephfs_shell_cmd_output(
1028 cmd
='set editor', shell_conf_path
=self
.tempconfpath
)
1029 final_editor_val
= final_editor_val
.split(': ')[1]
1030 final_editor_val
= final_editor_val
.replace("'", "", 2)
1032 self
.assertEqual(self
.editor_val
, final_editor_val
)
1034 def test_setting_opt_after_reading_conf(self
):
1035 self
.write_tempconf("[cephfs-shell]\neditor = ???")
1037 # output of following command -
1041 final_editor_val
= self
.get_cephfs_shell_cmd_output(
1042 cmd
='set editor %s, set editor' % (self
.editor_val
),
1043 shell_conf_path
=self
.tempconfpath
)
1044 final_editor_val
= final_editor_val
.split('\n')[2]
1045 final_editor_val
= final_editor_val
.split(': ')[1]
1046 final_editor_val
= final_editor_val
.replace("'", "", 2)
1048 self
.assertEqual(self
.editor_val
, final_editor_val
)