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
.exceptions
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_payload(f
"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_get_with_target_name(self
):
314 Test that get passes with target name
317 s_hash
= crypt
.crypt(s
, '.A')
318 o
= self
.get_cephfs_shell_cmd_output("put - dump4", stdin
=s
)
319 log
.info("cephfs-shell output:\n{}".format(o
))
321 # put - dump4 should pass
322 o
= self
.mount_a
.stat('dump4')
323 log
.info("mount_a output:\n{}".format(o
))
325 o
= self
.get_cephfs_shell_cmd_output("get dump4 ./dump4")
326 log
.info("cephfs-shell output:\n{}".format(o
))
328 o
= self
.get_cephfs_shell_cmd_output("!cat dump4")
329 o_hash
= crypt
.crypt(o
, '.A')
331 # s_hash must be equal to o_hash
332 log
.info("s_hash:{}".format(s_hash
))
333 log
.info("o_hash:{}".format(o_hash
))
334 assert(s_hash
== o_hash
)
336 def test_get_without_target_name(self
):
338 Test that get should fail when there is no target name
341 # put - dump5 should pass
342 self
.get_cephfs_shell_cmd_output("put - dump5", stdin
=s
)
344 self
.mount_a
.stat('dump5')
346 # get dump5 should fail as there is no local_path mentioned
347 with self
.assertRaises(CommandFailedError
):
348 self
.get_cephfs_shell_cmd_output("get dump5")
350 # stat dump would return non-zero exit code as get dump failed
351 # cwd=None because we want to run it at CWD, not at cephfs mntpt.
352 r
= self
.mount_a
.run_shell('stat dump5', cwd
=None,
353 check_status
=False).returncode
354 self
.assertEqual(r
, 1)
356 def test_get_doesnt_create_dir(self
):
357 # if get cmd is creating subdirs on its own then dump7 will be
358 # stored as ./dump7/tmp/dump7 and not ./dump7, therefore
359 # if doing `cat ./dump7` returns non-zero exit code(i.e. 1) then
360 # it implies that no such file exists at that location
361 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, 'tmp')
362 self
.mount_a
.run_shell_payload(f
"mkdir {dir_abspath}")
363 self
.mount_a
.client_remote
.write_file(path
.join(dir_abspath
, 'dump7'),
365 self
.get_cephfs_shell_cmd_output("get /tmp/dump7 ./dump7")
366 # test that dump7 exists
367 self
.mount_a
.run_shell("cat ./dump7", cwd
=None)
369 def test_get_to_console(self
):
371 Test that get passes with target name
374 s_hash
= crypt
.crypt(s
, '.A')
375 o
= self
.get_cephfs_shell_cmd_output("put - dump6", stdin
=s
)
376 log
.info("cephfs-shell output:\n{}".format(o
))
378 # put - dump6 should pass
379 o
= self
.mount_a
.stat('dump6')
380 log
.info("mount_a output:\n{}".format(o
))
382 # get dump6 - should pass
383 o
= self
.get_cephfs_shell_cmd_output("get dump6 -")
384 o_hash
= crypt
.crypt(o
, '.A')
385 log
.info("cephfs-shell output:\n{}".format(o
))
387 # s_hash must be equal to o_hash
388 log
.info("s_hash:{}".format(s_hash
))
389 log
.info("o_hash:{}".format(o_hash
))
390 assert(s_hash
== o_hash
)
392 def test_put_without_target_name(self
):
394 put - should fail as the cmd expects both arguments are mandatory.
396 with self
.assertRaises(CommandFailedError
):
397 self
.get_cephfs_shell_cmd_output("put -")
399 def test_put_validate_local_path(self
):
401 This test is intended to make sure local_path is validated before
402 trying to put the file from local fs to cephfs and the command
403 put ./dumpXYZ dump8 would fail as dumpXYX doesn't exist.
405 with self
.assertRaises(CommandFailedError
):
406 o
= self
.get_cephfs_shell_cmd_output("put ./dumpXYZ dump8")
407 log
.info("cephfs-shell output:\n{}".format(o
))
409 class TestSnapshots(TestCephFSShell
):
412 Test that snapshot creation and deletion work
414 sd
= self
.fs
.get_config('client_snapdir')
415 sdn
= "data_dir/{}/snap1".format(sd
)
417 # create a data dir and dump some files into it
418 self
.get_cephfs_shell_cmd_output("mkdir data_dir")
420 o
= self
.get_cephfs_shell_cmd_output("put - data_dir/data_a", stdin
=s
)
422 o
= self
.get_cephfs_shell_cmd_output("put - data_dir/data_b", stdin
=s
)
424 o
= self
.get_cephfs_shell_cmd_output("put - data_dir/data_c", stdin
=s
)
426 o
= self
.get_cephfs_shell_cmd_output("put - data_dir/data_d", stdin
=s
)
428 o
= self
.get_cephfs_shell_cmd_output("put - data_dir/data_e", stdin
=s
)
430 o
= self
.get_cephfs_shell_cmd_output("ls -l /data_dir")
431 log
.info("cephfs-shell output:\n{}".format(o
))
433 # create the snapshot - must pass
434 o
= self
.get_cephfs_shell_cmd_output("snap create snap1 /data_dir")
435 log
.info("cephfs-shell output:\n{}".format(o
))
436 self
.assertEqual("", o
)
437 o
= self
.mount_a
.stat(sdn
)
438 log
.info("mount_a output:\n{}".format(o
))
439 self
.assertIn('st_mode', o
)
441 # create the same snapshot again - must fail with an error message
442 self
.negtest_cephfs_shell_cmd(cmd
="snap create snap1 /data_dir",
443 errmsg
="snapshot 'snap1' already exists")
444 o
= self
.mount_a
.stat(sdn
)
445 log
.info("mount_a output:\n{}".format(o
))
446 self
.assertIn('st_mode', o
)
448 # delete the snapshot - must pass
449 o
= self
.get_cephfs_shell_cmd_output("snap delete snap1 /data_dir")
450 log
.info("cephfs-shell output:\n{}".format(o
))
451 self
.assertEqual("", o
)
453 o
= self
.mount_a
.stat(sdn
)
454 except CommandFailedError
:
455 # snap dir should not exist anymore
457 log
.info("mount_a output:\n{}".format(o
))
458 self
.assertNotIn('st_mode', o
)
460 # delete the same snapshot again - must fail with an error message
461 self
.negtest_cephfs_shell_cmd(cmd
="snap delete snap1 /data_dir",
462 errmsg
="'snap1': no such snapshot")
464 o
= self
.mount_a
.stat(sdn
)
465 except CommandFailedError
:
467 log
.info("mount_a output:\n{}".format(o
))
468 self
.assertNotIn('st_mode', o
)
470 class TestCD(TestCephFSShell
):
473 def test_cd_with_no_args(self
):
475 Test that when cd is issued without any arguments, CWD is changed
478 path
= 'dir1/dir2/dir3'
479 self
.mount_a
.run_shell_payload(f
"mkdir -p {path}")
482 script
= 'cd {}\ncd\ncwd\n'.format(path
)
483 output
= self
.get_cephfs_shell_script_output(script
)
484 self
.assertEqual(output
, expected_cwd
)
486 def test_cd_with_args(self
):
488 Test that when cd is issued with an argument, CWD is changed
489 to the path passed in the argument.
491 path
= 'dir1/dir2/dir3'
492 self
.mount_a
.run_shell_payload(f
"mkdir -p {path}")
493 expected_cwd
= '/dir1/dir2/dir3'
495 script
= 'cd {}\ncwd\n'.format(path
)
496 output
= self
.get_cephfs_shell_script_output(script
)
497 self
.assertEqual(output
, expected_cwd
)
499 class TestDU(TestCephFSShell
):
502 def test_du_works_for_regfiles(self
):
503 regfilename
= 'some_regfile'
504 regfile_abspath
= path
.join(self
.mount_a
.mountpoint
, regfilename
)
505 self
.mount_a
.client_remote
.write_file(regfile_abspath
, 'somedata')
507 size
= humansize(self
.mount_a
.stat(regfile_abspath
)['st_size'])
508 expected_output
= r
'{}{}{}'.format(size
, " +", regfilename
)
510 du_output
= self
.get_cephfs_shell_cmd_output('du ' + regfilename
)
511 self
.assertRegex(du_output
, expected_output
)
513 def test_du_works_for_non_empty_dirs(self
):
514 dirname
= 'some_directory'
515 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, dirname
)
516 regfilename
= 'some_regfile'
517 regfile_abspath
= path
.join(dir_abspath
, regfilename
)
518 self
.mount_a
.run_shell_payload(f
"mkdir {dir_abspath}")
519 self
.mount_a
.client_remote
.write_file(regfile_abspath
, 'somedata')
521 # XXX: we stat `regfile_abspath` here because ceph du reports a non-empty
522 # directory's size as sum of sizes of all files under it.
523 size
= humansize(self
.mount_a
.stat(regfile_abspath
)['st_size'])
524 expected_output
= r
'{}{}{}'.format(size
, " +", dirname
)
527 du_output
= self
.get_cephfs_shell_cmd_output('du ' + dirname
)
528 self
.assertRegex(du_output
, expected_output
)
530 def test_du_works_for_empty_dirs(self
):
531 dirname
= 'some_directory'
532 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, dirname
)
533 self
.mount_a
.run_shell_payload(f
"mkdir {dir_abspath}")
535 size
= humansize(self
.mount_a
.stat(dir_abspath
)['st_size'])
536 expected_output
= r
'{}{}{}'.format(size
, " +", dirname
)
538 du_output
= self
.get_cephfs_shell_cmd_output('du ' + dirname
)
539 self
.assertRegex(du_output
, expected_output
)
541 def test_du_works_for_hardlinks(self
):
542 regfilename
= 'some_regfile'
543 regfile_abspath
= path
.join(self
.mount_a
.mountpoint
, regfilename
)
544 self
.mount_a
.client_remote
.write_file(regfile_abspath
, 'somedata')
545 hlinkname
= 'some_hardlink'
546 hlink_abspath
= path
.join(self
.mount_a
.mountpoint
, hlinkname
)
547 self
.mount_a
.run_shell_payload(f
"ln {regfile_abspath} {hlink_abspath}")
549 size
= humansize(self
.mount_a
.stat(hlink_abspath
)['st_size'])
550 expected_output
= r
'{}{}{}'.format(size
, " +", hlinkname
)
552 du_output
= self
.get_cephfs_shell_cmd_output('du ' + hlinkname
)
553 self
.assertRegex(du_output
, expected_output
)
555 def test_du_works_for_softlinks_to_files(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
, 'somedata')
559 slinkname
= 'some_softlink'
560 slink_abspath
= path
.join(self
.mount_a
.mountpoint
, slinkname
)
561 self
.mount_a
.run_shell_payload(f
"ln -s {regfile_abspath} {slink_abspath}")
563 size
= humansize(self
.mount_a
.lstat(slink_abspath
)['st_size'])
564 expected_output
= r
'{}{}{}'.format((size
), " +", slinkname
)
566 du_output
= self
.get_cephfs_shell_cmd_output('du ' + slinkname
)
567 self
.assertRegex(du_output
, expected_output
)
569 def test_du_works_for_softlinks_to_dirs(self
):
570 dirname
= 'some_directory'
571 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, dirname
)
572 self
.mount_a
.run_shell_payload(f
"mkdir {dir_abspath}")
573 slinkname
= 'some_softlink'
574 slink_abspath
= path
.join(self
.mount_a
.mountpoint
, slinkname
)
575 self
.mount_a
.run_shell_payload(f
"ln -s {dir_abspath} {slink_abspath}")
577 size
= humansize(self
.mount_a
.lstat(slink_abspath
)['st_size'])
578 expected_output
= r
'{}{}{}'.format(size
, " +", slinkname
)
580 du_output
= self
.get_cephfs_shell_cmd_output('du ' + slinkname
)
581 self
.assertRegex(du_output
, expected_output
)
583 # NOTE: tests using these are pretty slow since to this methods sleeps for
585 def _setup_files(self
, return_path_to_files
=False, path_prefix
='./'):
587 regfilename
= 'regfile'
590 slink2name
= 'slink2'
592 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, dirname
)
593 regfile_abspath
= path
.join(self
.mount_a
.mountpoint
, regfilename
)
594 hlink_abspath
= path
.join(self
.mount_a
.mountpoint
, hlinkname
)
595 slink_abspath
= path
.join(self
.mount_a
.mountpoint
, slinkname
)
596 slink2_abspath
= path
.join(self
.mount_a
.mountpoint
, slink2name
)
598 self
.mount_a
.run_shell_payload(f
"mkdir {dir_abspath}")
599 self
.mount_a
.run_shell_payload(f
"touch {regfile_abspath}")
600 self
.mount_a
.run_shell_payload(f
"ln {regfile_abspath} {hlink_abspath}")
601 self
.mount_a
.run_shell_payload(f
"ln -s {regfile_abspath} {slink_abspath}")
602 self
.mount_a
.run_shell_payload(f
"ln -s {dir_abspath} {slink2_abspath}")
606 regfile121_name
= 'regfile121'
607 dir2_abspath
= path
.join(self
.mount_a
.mountpoint
, dir2_name
)
608 dir21_abspath
= path
.join(dir2_abspath
, dir21_name
)
609 regfile121_abspath
= path
.join(dir21_abspath
, regfile121_name
)
610 self
.mount_a
.run_shell_payload(f
"mkdir -p {dir21_abspath}")
611 self
.mount_a
.run_shell_payload(f
"touch {regfile121_abspath}")
613 self
.mount_a
.client_remote
.write_file(regfile_abspath
, 'somedata')
614 self
.mount_a
.client_remote
.write_file(regfile121_abspath
, 'somemoredata')
616 # TODO: is there a way to trigger/force update ceph.dir.rbytes?
617 # wait so that attr ceph.dir.rbytes gets a chance to be updated.
620 expected_patterns
= []
623 def append_expected_output_pattern(f
):
625 expected_patterns
.append(r
'{}{}{}'.format(size
, " +", '.' + f
))
627 expected_patterns
.append(r
'{}{}{}'.format(size
, " +",
628 path_prefix
+ path
.relpath(f
, self
.mount_a
.mountpoint
)))
630 for f
in [dir_abspath
, regfile_abspath
, regfile121_abspath
,
631 hlink_abspath
, slink_abspath
, slink2_abspath
]:
632 size
= humansize(self
.mount_a
.stat(f
, follow_symlinks
=
634 append_expected_output_pattern(f
)
636 # get size for directories containig regfiles within
637 for f
in [dir2_abspath
, dir21_abspath
]:
638 size
= humansize(self
.mount_a
.stat(regfile121_abspath
,
639 follow_symlinks
=False)['st_size'])
640 append_expected_output_pattern(f
)
642 # get size for CephFS root
644 for f
in [regfile_abspath
, regfile121_abspath
, slink_abspath
,
646 size
+= self
.mount_a
.stat(f
, follow_symlinks
=False)['st_size']
647 size
= humansize(size
)
648 append_expected_output_pattern('/')
650 if return_path_to_files
:
651 for p
in [dir_abspath
, regfile_abspath
, dir2_abspath
,
652 dir21_abspath
, regfile121_abspath
, hlink_abspath
,
653 slink_abspath
, slink2_abspath
]:
654 path_to_files
.append(path
.relpath(p
, self
.mount_a
.mountpoint
))
656 return (expected_patterns
, path_to_files
)
658 return expected_patterns
660 def test_du_works_recursively_with_no_path_in_args(self
):
661 expected_patterns_in_output
= self
._setup
_files
()
662 du_output
= self
.get_cephfs_shell_cmd_output('du -r')
664 for expected_output
in expected_patterns_in_output
:
665 self
.assertRegex(du_output
, expected_output
)
667 def test_du_with_path_in_args(self
):
668 expected_patterns_in_output
, path_to_files
= self
._setup
_files
(True,
672 for p
in path_to_files
:
674 du_output
= self
.get_cephfs_shell_cmd_output(args
)
676 for expected_output
in expected_patterns_in_output
:
677 self
.assertRegex(du_output
, expected_output
)
679 def test_du_with_no_args(self
):
680 expected_patterns_in_output
= self
._setup
_files
()
682 du_output
= self
.get_cephfs_shell_cmd_output('du')
684 for expected_output
in expected_patterns_in_output
:
685 # Since CWD is CephFS root and being non-recursive expect only
687 if expected_output
.find('/') == len(expected_output
) - 1:
688 self
.assertRegex(du_output
, expected_output
)
691 class TestDF(TestCephFSShell
):
692 def validate_df(self
, filename
):
693 df_output
= self
.get_cephfs_shell_cmd_output('df '+filename
)
694 log
.info("cephfs-shell df output:\n{}".format(df_output
))
696 shell_df
= df_output
.splitlines()[1].split()
698 block_size
= int(self
.mount_a
.df()["total"]) // 1024
699 log
.info("cephfs df block size output:{}\n".format(block_size
))
701 st_size
= int(self
.mount_a
.stat(filename
)["st_size"])
702 log
.info("cephfs stat used output:{}".format(st_size
))
703 log
.info("cephfs available:{}\n".format(block_size
- st_size
))
705 self
.assertTupleEqual((block_size
, st_size
, block_size
- st_size
),
706 (int(shell_df
[0]), int(shell_df
[1]) , int(shell_df
[2])))
708 def test_df_with_no_args(self
):
710 df_output
= self
.get_cephfs_shell_cmd_output('df')
711 assert df_output
== expected_output
713 def test_df_for_valid_directory(self
):
715 mount_output
= self
.mount_a
.run_shell_payload(f
"mkdir {dir_name}")
716 log
.info("cephfs-shell mount output:\n{}".format(mount_output
))
717 self
.validate_df(dir_name
)
719 def test_df_for_invalid_directory(self
):
720 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, 'non-existent-dir')
721 self
.negtest_cephfs_shell_cmd(cmd
='df ' + dir_abspath
,
722 errmsg
='error in stat')
724 def test_df_for_valid_file(self
):
725 s
= 'df test' * 14145016
726 o
= self
.get_cephfs_shell_cmd_output("put - dumpfile", stdin
=s
)
727 log
.info("cephfs-shell output:\n{}".format(o
))
728 self
.validate_df("dumpfile")
731 class TestQuota(TestCephFSShell
):
734 def create_dir(self
):
735 mount_output
= self
.get_cephfs_shell_cmd_output('mkdir ' + self
.dir_name
)
736 log
.info("cephfs-shell mount output:\n{}".format(mount_output
))
738 def set_and_get_quota_vals(self
, input_val
, check_status
=True):
739 self
.run_cephfs_shell_cmd(['quota', 'set', '--max_bytes',
740 input_val
[0], '--max_files', input_val
[1],
741 self
.dir_name
], check_status
=check_status
)
743 quota_output
= self
.get_cephfs_shell_cmd_output(['quota', 'get', self
.dir_name
],
744 check_status
=check_status
)
746 quota_output
= quota_output
.split()
747 return quota_output
[1], quota_output
[3]
751 set_values
= ('6', '2')
752 self
.assertTupleEqual(self
.set_and_get_quota_vals(set_values
), set_values
)
754 def test_replace_values(self
):
756 set_values
= ('20', '4')
757 self
.assertTupleEqual(self
.set_and_get_quota_vals(set_values
), set_values
)
759 def test_set_invalid_dir(self
):
760 set_values
= ('5', '5')
762 self
.assertTupleEqual(self
.set_and_get_quota_vals(
763 set_values
, False), set_values
)
764 raise Exception("Something went wrong!! Values set for non existing directory")
766 # Test should pass as values cannot be set for non existing directory
769 def test_set_invalid_values(self
):
771 set_values
= ('-6', '-5')
773 self
.assertTupleEqual(self
.set_and_get_quota_vals(set_values
,
775 raise Exception("Something went wrong!! Invalid values set")
777 # Test should pass as invalid values cannot be set
780 def test_exceed_file_limit(self
):
782 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, self
.dir_name
)
783 self
.mount_a
.run_shell_payload(f
"touch {dir_abspath}/file1")
784 file2
= path
.join(dir_abspath
, "file2")
786 self
.mount_a
.run_shell_payload(f
"touch {file2}")
787 raise Exception("Something went wrong!! File creation should have failed")
788 except CommandFailedError
:
789 # Test should pass as file quota set to 2
790 # Additional condition to confirm file creation failure
791 if not path
.exists(file2
):
795 def test_exceed_write_limit(self
):
797 dir_abspath
= path
.join(self
.mount_a
.mountpoint
, self
.dir_name
)
798 filename
= 'test_file'
799 file_abspath
= path
.join(dir_abspath
, filename
)
801 # Write should fail as bytes quota is set to 6
802 self
.mount_a
.client_remote
.write_file(file_abspath
, 'Disk raise Exception')
803 raise Exception("Write should have failed")
804 except CommandFailedError
:
805 # Test should pass only when write command fails
806 path_exists
= path
.exists(file_abspath
)
808 # Testing with teuthology: No file is created.
810 elif path_exists
and not path
.getsize(file_abspath
):
811 # Testing on Fedora 30: When write fails, empty file gets created.
817 class TestXattr(TestCephFSShell
):
820 def create_dir(self
):
821 self
.run_cephfs_shell_cmd('mkdir ' + self
.dir_name
)
823 def set_get_list_xattr_vals(self
, input_val
, negtest
=False):
824 setxattr_output
= self
.get_cephfs_shell_cmd_output(
825 ['setxattr', self
.dir_name
, input_val
[0], input_val
[1]])
826 log
.info("cephfs-shell setxattr output:\n{}".format(setxattr_output
))
828 getxattr_output
= self
.get_cephfs_shell_cmd_output(
829 ['getxattr', self
.dir_name
, input_val
[0]])
830 log
.info("cephfs-shell getxattr output:\n{}".format(getxattr_output
))
832 listxattr_output
= self
.get_cephfs_shell_cmd_output(
833 ['listxattr', self
.dir_name
])
834 log
.info("cephfs-shell listxattr output:\n{}".format(listxattr_output
))
836 return listxattr_output
, getxattr_output
840 set_values
= ('user.key', '2')
841 self
.assertTupleEqual(self
.set_get_list_xattr_vals(set_values
), set_values
)
843 def test_reset(self
):
845 set_values
= ('user.key', '4')
846 self
.assertTupleEqual(self
.set_get_list_xattr_vals(set_values
), set_values
)
848 def test_non_existing_dir(self
):
849 input_val
= ('user.key', '9')
850 self
.negtest_cephfs_shell_cmd(cmd
=['setxattr', self
.dir_name
, input_val
[0],
852 self
.negtest_cephfs_shell_cmd(cmd
=['getxattr', self
.dir_name
, input_val
[0]])
853 self
.negtest_cephfs_shell_cmd(cmd
=['listxattr', self
.dir_name
])
855 class TestLS(TestCephFSShell
):
856 dir_name
= ('test_dir')
857 hidden_dir_name
= ('.test_hidden_dir')
860 """ Test that ls prints files in CWD. """
861 self
.run_cephfs_shell_cmd(f
'mkdir {self.dir_name}')
863 ls_output
= self
.get_cephfs_shell_cmd_output("ls")
864 log
.info(f
"output of ls command:\n{ls_output}")
866 self
.assertIn(self
.dir_name
, ls_output
)
869 """ Test ls -a prints hidden files in CWD."""
871 self
.run_cephfs_shell_cmd(f
'mkdir {self.hidden_dir_name}')
873 ls_a_output
= self
.get_cephfs_shell_cmd_output(['ls', '-a'])
874 log
.info(f
"output of ls -a command:\n{ls_a_output}")
876 self
.assertIn(self
.hidden_dir_name
, ls_a_output
)
878 def test_ls_does_not_print_hidden_dir(self
):
879 """ Test ls command does not print hidden directory """
881 self
.run_cephfs_shell_cmd(f
'mkdir {self.hidden_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
.assertNotIn(self
.hidden_dir_name
, ls_output
)
888 def test_ls_a_prints_non_hidden_dir(self
):
889 """ Test ls -a command prints non hidden directory """
891 self
.run_cephfs_shell_cmd(f
'mkdir {self.hidden_dir_name} {self.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
.dir_name
, ls_a_output
)
898 def test_ls_H_prints_human_readable_file_size(self
):
899 """ Test "ls -lH" prints human readable file size."""
901 file_sizes
= ['1','1K', '1M', '1G']
902 file_names
= ['dump1', 'dump2', 'dump3', 'dump4']
905 for (file_size
, file_name
) in zip(file_sizes
, file_names
):
906 temp_file
= self
.mount_a
.client_remote
.mktemp(file_name
)
907 self
.mount_a
.run_shell_payload(f
"fallocate -l {file_size} {temp_file}")
908 self
.mount_a
.run_shell_payload(f
'mv {temp_file} ./')
910 ls_H_output
= self
.get_cephfs_shell_cmd_output(['ls', '-lH'])
912 ls_H_file_size
= set()
913 for line
in ls_H_output
.split('\n'):
914 ls_H_file_size
.add(line
.split()[1])
916 # test that file sizes are in human readable format
917 self
.assertEqual({'1B','1K', '1M', '1G'}, ls_H_file_size
)
919 def test_ls_s_sort_by_size(self
):
920 """ Test "ls -S" sorts file listing by file_size """
921 test_file1
= "test_file1.txt"
922 test_file2
= "test_file2.txt"
923 file1_content
= 'A' * 102
924 file2_content
= 'B' * 10
926 self
.run_cephfs_shell_cmd(f
"write {test_file1}", stdin
=file1_content
)
927 self
.run_cephfs_shell_cmd(f
"write {test_file2}", stdin
=file2_content
)
929 ls_s_output
= self
.get_cephfs_shell_cmd_output(['ls', '-lS'])
932 for line
in ls_s_output
.split('\n'):
933 file_sizes
.append(line
.split()[1])
935 #test that file size are in ascending order
936 self
.assertEqual(file_sizes
, sorted(file_sizes
))
939 class TestMisc(TestCephFSShell
):
940 def test_issue_cephfs_shell_cmd_at_invocation(self
):
942 Test that `cephfs-shell -c conf cmd` works.
944 # choosing a long name since short ones have a higher probability
945 # of getting matched by coincidence.
946 dirname
= 'somedirectory'
947 self
.run_cephfs_shell_cmd(['mkdir', dirname
])
949 output
= self
.mount_a
.client_remote
.sh(['cephfs-shell', 'ls']).\
952 self
.assertRegex(output
, dirname
)
956 Test that help outputs commands.
958 o
= self
.get_cephfs_shell_cmd_output("help all")
959 log
.info("output:\n{}".format(o
))
961 class TestShellOpts(TestCephFSShell
):
963 Contains tests for shell options from conf file and shell prompt.
967 super(type(self
), self
).setUp()
969 # output of following command -
970 # editor - was: 'vim'
973 self
.editor_val
= self
.get_cephfs_shell_cmd_output(
974 'set editor ?, set editor').split('\n')[2]
975 self
.editor_val
= self
.editor_val
.split(':')[1].\
976 replace("'", "", 2).strip()
978 def write_tempconf(self
, confcontents
):
979 self
.tempconfpath
= self
.mount_a
.client_remote
.mktemp(
980 suffix
='cephfs-shell.conf')
981 self
.mount_a
.client_remote
.write_file(self
.tempconfpath
,
984 def test_reading_conf(self
):
985 self
.write_tempconf("[cephfs-shell]\neditor = ???")
987 # output of following command -
988 # CephFS:~/>>> set editor
990 final_editor_val
= self
.get_cephfs_shell_cmd_output(
991 cmd
='set editor', shell_conf_path
=self
.tempconfpath
)
992 final_editor_val
= final_editor_val
.split(': ')[1]
993 final_editor_val
= final_editor_val
.replace("'", "", 2)
995 self
.assertNotEqual(self
.editor_val
, final_editor_val
)
997 def test_reading_conf_with_dup_opt(self
):
999 Read conf without duplicate sections/options.
1001 self
.write_tempconf("[cephfs-shell]\neditor = ???\neditor = " +
1004 # output of following command -
1005 # CephFS:~/>>> set editor
1007 final_editor_val
= self
.get_cephfs_shell_cmd_output(
1008 cmd
='set editor', shell_conf_path
=self
.tempconfpath
)
1009 final_editor_val
= final_editor_val
.split(': ')[1]
1010 final_editor_val
= final_editor_val
.replace("'", "", 2)
1012 self
.assertEqual(self
.editor_val
, final_editor_val
)
1014 def test_setting_opt_after_reading_conf(self
):
1015 self
.write_tempconf("[cephfs-shell]\neditor = ???")
1017 # output of following command -
1021 final_editor_val
= self
.get_cephfs_shell_cmd_output(
1022 cmd
='set editor %s, set editor' % (self
.editor_val
),
1023 shell_conf_path
=self
.tempconfpath
)
1024 final_editor_val
= final_editor_val
.split('\n')[2]
1025 final_editor_val
= final_editor_val
.split(': ')[1]
1026 final_editor_val
= final_editor_val
.replace("'", "", 2)
1028 self
.assertEqual(self
.editor_val
, final_editor_val
)