]> git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/cephfs/test_cephfs_shell.py
import ceph quincy 17.2.4
[ceph.git] / ceph / qa / tasks / cephfs / test_cephfs_shell.py
1 """
2 Before running this testsuite, add path to cephfs-shell module to $PATH and
3 export $PATH.
4 """
5 from io import StringIO
6 from os import path
7 import crypt
8 import logging
9 from tempfile import mkstemp as tempfile_mkstemp
10 import math
11 from time import sleep
12 from tasks.cephfs.cephfs_test_case import CephFSTestCase
13 from teuthology.exceptions import CommandFailedError
14
15 log = logging.getLogger(__name__)
16
17 def humansize(nbytes):
18 suffixes = ['B', 'K', 'M', 'G', 'T', 'P']
19 i = 0
20 while nbytes >= 1024 and i < len(suffixes)-1:
21 nbytes /= 1024.
22 i += 1
23 nbytes = math.ceil(nbytes)
24 f = ('%d' % nbytes).rstrip('.')
25 return '%s%s' % (f, suffixes[i])
26
27 def ensure_str(s):
28 if isinstance(s, str):
29 return s
30 if isinstance(s, bytes):
31 return s.decode()
32 raise TypeError("not expecting type '%s'" % type(s))
33
34 class TestCephFSShell(CephFSTestCase):
35 CLIENTS_REQUIRED = 1
36
37 def setUp(self):
38 super(TestCephFSShell, self).setUp()
39
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
44
45 def run_cephfs_shell_cmd(self, cmd, mount_x=None, shell_conf_path=None,
46 opts=None, stdout=None, stderr=None, stdin=None,
47 check_status=True):
48 stdout = stdout or StringIO()
49 stderr = stderr or StringIO()
50 if mount_x is None:
51 mount_x = self.mount_a
52 if isinstance(cmd, list):
53 cmd = " ".join(cmd)
54 if not shell_conf_path:
55 shell_conf_path = self.default_shell_conf_path
56
57 args = ["cephfs-shell", "-c", shell_conf_path]
58 if opts:
59 args += opts
60 args.extend(("--", cmd))
61
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)
66
67 def negtest_cephfs_shell_cmd(self, **kwargs):
68 """
69 This method verifies that cephfs shell command fails with expected
70 return value and/or error message.
71
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.
78
79 This method servers as shorthand for codeblocks like -
80
81 try:
82 proc = self.run_cephfs_shell_cmd(args=['some', 'cmd'],
83 check_status=False,
84 stdout=stdout)
85 except CommandFailedError as e:
86 self.assertNotIn('some error message',
87 proc.stderr.getvalue.lower())
88
89
90 try:
91 proc = self.run_cephfs_shell_cmd(args=['some', 'cmd'],
92 check_status=False,
93 stdout=stdout)
94 except CommandFailedError as e:
95 self.assertNotEqual(1, proc.returncode)
96 """
97 retval = kwargs.pop('retval', None)
98 errmsg = kwargs.pop('errmsg', None)
99 kwargs['check_status'] = False
100
101 proc = self.run_cephfs_shell_cmd(**kwargs)
102 if retval:
103 self.assertEqual(proc.returncode, retval)
104 else:
105 self.assertNotEqual(proc.returncode, 0)
106 if errmsg:
107 self.assertIn(errmsg, proc.stderr.getvalue().lower())
108
109 return proc
110
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())
118
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())
126
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()
132 if mount_x is None:
133 mount_x = self.mount_a
134
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}")
141
142 args = ["cephfs-shell", '-b', scriptpath]
143 if shell_conf_path:
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,
148 check_status=True)
149
150 def get_cephfs_shell_script_output(self, script, mount_x=None,
151 shell_conf_path=None, opts=None,
152 stdout=None, stdin=None,
153 check_status=True):
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())
158
159
160 class TestMkdir(TestCephFSShell):
161 def test_mkdir(self):
162 """
163 Test that mkdir creates directory
164 """
165 o = self.get_cephfs_shell_cmd_output("mkdir d1")
166 log.info("cephfs-shell output:\n{}".format(o))
167
168 o = self.mount_a.stat('d1')
169 log.info("mount_a output:\n{}".format(o))
170
171 def test_mkdir_with_07000_octal_mode(self):
172 """
173 Test that mkdir fails with octal mode greater than 0777
174 """
175 self.negtest_cephfs_shell_cmd(cmd="mkdir -m 07000 d2")
176 try:
177 self.mount_a.stat('d2')
178 except CommandFailedError:
179 pass
180
181 def test_mkdir_with_negative_octal_mode(self):
182 """
183 Test that mkdir fails with negative octal mode
184 """
185 self.negtest_cephfs_shell_cmd(cmd="mkdir -m -0755 d3")
186 try:
187 self.mount_a.stat('d3')
188 except CommandFailedError:
189 pass
190
191 def test_mkdir_with_non_octal_mode(self):
192 """
193 Test that mkdir passes with non-octal mode
194 """
195 o = self.get_cephfs_shell_cmd_output("mkdir -m u=rwx d4")
196 log.info("cephfs-shell output:\n{}".format(o))
197
198 # mkdir d4 should pass
199 o = self.mount_a.stat('d4')
200 assert((o['st_mode'] & 0o700) == 0o700)
201
202 def test_mkdir_with_bad_non_octal_mode(self):
203 """
204 Test that mkdir failes with bad non-octal mode
205 """
206 self.negtest_cephfs_shell_cmd(cmd="mkdir -m ugx=0755 d5")
207 try:
208 self.mount_a.stat('d5')
209 except CommandFailedError:
210 pass
211
212 def test_mkdir_path_without_path_option(self):
213 """
214 Test that mkdir fails without path option for creating path
215 """
216 self.negtest_cephfs_shell_cmd(cmd="mkdir d5/d6/d7")
217 try:
218 self.mount_a.stat('d5/d6/d7')
219 except CommandFailedError:
220 pass
221
222 def test_mkdir_path_with_path_option(self):
223 """
224 Test that mkdir passes with path option for creating path
225 """
226 o = self.get_cephfs_shell_cmd_output("mkdir -p d5/d6/d7")
227 log.info("cephfs-shell output:\n{}".format(o))
228
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))
232
233 class TestRmdir(TestCephFSShell):
234 dir_name = "test_dir"
235
236 def dir_does_not_exists(self):
237 """
238 Tests that directory does not exists
239 """
240 try:
241 self.mount_a.stat(self.dir_name)
242 except CommandFailedError as e:
243 if e.exitstatus == 2:
244 return 0
245 raise
246
247 def test_rmdir(self):
248 """
249 Test that rmdir deletes directory
250 """
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()
254
255 def test_rmdir_non_existing_dir(self):
256 """
257 Test that rmdir does not delete a non existing directory
258 """
259 self.negtest_cephfs_shell_cmd(cmd="rmdir test_dir")
260 self.dir_does_not_exists()
261
262 def test_rmdir_dir_with_file(self):
263 """
264 Test that rmdir does not delete directory containing file
265 """
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)
270
271 def test_rmdir_existing_file(self):
272 """
273 Test that rmdir does not delete a file
274 """
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")
278
279 def test_rmdir_p(self):
280 """
281 Test that rmdir -p deletes all empty directories in the root directory passed
282 """
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()
286
287 def test_rmdir_p_valid_path(self):
288 """
289 Test that rmdir -p deletes all empty directories in the path passed
290 """
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()
294
295 def test_rmdir_p_non_existing_dir(self):
296 """
297 Test that rmdir -p does not delete an invalid directory
298 """
299 self.negtest_cephfs_shell_cmd(cmd="rmdir -p test_dir")
300 self.dir_does_not_exists()
301
302 def test_rmdir_p_dir_with_file(self):
303 """
304 Test that rmdir -p does not delete the directory containing a file
305 """
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)
310
311 class TestGetAndPut(TestCephFSShell):
312 def test_get_with_target_name(self):
313 """
314 Test that get passes with target name
315 """
316 s = 'C' * 1024
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))
320
321 # put - dump4 should pass
322 o = self.mount_a.stat('dump4')
323 log.info("mount_a output:\n{}".format(o))
324
325 o = self.get_cephfs_shell_cmd_output("get dump4 ./dump4")
326 log.info("cephfs-shell output:\n{}".format(o))
327
328 o = self.get_cephfs_shell_cmd_output("!cat dump4")
329 o_hash = crypt.crypt(o, '.A')
330
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)
335
336 def test_get_without_target_name(self):
337 """
338 Test that get should fail when there is no target name
339 """
340 s = 'Somedata'
341 # put - dump5 should pass
342 self.get_cephfs_shell_cmd_output("put - dump5", stdin=s)
343
344 self.mount_a.stat('dump5')
345
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")
349
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)
355
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'),
364 'somedata')
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)
368
369 def test_get_to_console(self):
370 """
371 Test that get passes with target name
372 """
373 s = 'E' * 1024
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))
377
378 # put - dump6 should pass
379 o = self.mount_a.stat('dump6')
380 log.info("mount_a output:\n{}".format(o))
381
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))
386
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)
391
392 def test_put_without_target_name(self):
393 """
394 put - should fail as the cmd expects both arguments are mandatory.
395 """
396 with self.assertRaises(CommandFailedError):
397 self.get_cephfs_shell_cmd_output("put -")
398
399 def test_put_validate_local_path(self):
400 """
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.
404 """
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))
408
409 class TestSnapshots(TestCephFSShell):
410 def test_snap(self):
411 """
412 Test that snapshot creation and deletion work
413 """
414 sd = self.fs.get_config('client_snapdir')
415 sdn = "data_dir/{}/snap1".format(sd)
416
417 # create a data dir and dump some files into it
418 self.get_cephfs_shell_cmd_output("mkdir data_dir")
419 s = 'A' * 10240
420 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_a", stdin=s)
421 s = 'B' * 10240
422 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_b", stdin=s)
423 s = 'C' * 10240
424 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_c", stdin=s)
425 s = 'D' * 10240
426 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_d", stdin=s)
427 s = 'E' * 10240
428 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_e", stdin=s)
429
430 o = self.get_cephfs_shell_cmd_output("ls -l /data_dir")
431 log.info("cephfs-shell output:\n{}".format(o))
432
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)
440
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)
447
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)
452 try:
453 o = self.mount_a.stat(sdn)
454 except CommandFailedError:
455 # snap dir should not exist anymore
456 pass
457 log.info("mount_a output:\n{}".format(o))
458 self.assertNotIn('st_mode', o)
459
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")
463 try:
464 o = self.mount_a.stat(sdn)
465 except CommandFailedError:
466 pass
467 log.info("mount_a output:\n{}".format(o))
468 self.assertNotIn('st_mode', o)
469
470 class TestCD(TestCephFSShell):
471 CLIENTS_REQUIRED = 1
472
473 def test_cd_with_no_args(self):
474 """
475 Test that when cd is issued without any arguments, CWD is changed
476 to root directory.
477 """
478 path = 'dir1/dir2/dir3'
479 self.mount_a.run_shell_payload(f"mkdir -p {path}")
480 expected_cwd = '/'
481
482 script = 'cd {}\ncd\ncwd\n'.format(path)
483 output = self.get_cephfs_shell_script_output(script)
484 self.assertEqual(output, expected_cwd)
485
486 def test_cd_with_args(self):
487 """
488 Test that when cd is issued with an argument, CWD is changed
489 to the path passed in the argument.
490 """
491 path = 'dir1/dir2/dir3'
492 self.mount_a.run_shell_payload(f"mkdir -p {path}")
493 expected_cwd = '/dir1/dir2/dir3'
494
495 script = 'cd {}\ncwd\n'.format(path)
496 output = self.get_cephfs_shell_script_output(script)
497 self.assertEqual(output, expected_cwd)
498
499 class TestDU(TestCephFSShell):
500 CLIENTS_REQUIRED = 1
501
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')
506
507 size = humansize(self.mount_a.stat(regfile_abspath)['st_size'])
508 expected_output = r'{}{}{}'.format(size, " +", regfilename)
509
510 du_output = self.get_cephfs_shell_cmd_output('du ' + regfilename)
511 self.assertRegex(du_output, expected_output)
512
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')
520
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)
525
526 sleep(10)
527 du_output = self.get_cephfs_shell_cmd_output('du ' + dirname)
528 self.assertRegex(du_output, expected_output)
529
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}")
534
535 size = humansize(self.mount_a.stat(dir_abspath)['st_size'])
536 expected_output = r'{}{}{}'.format(size, " +", dirname)
537
538 du_output = self.get_cephfs_shell_cmd_output('du ' + dirname)
539 self.assertRegex(du_output, expected_output)
540
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}")
548
549 size = humansize(self.mount_a.stat(hlink_abspath)['st_size'])
550 expected_output = r'{}{}{}'.format(size, " +", hlinkname)
551
552 du_output = self.get_cephfs_shell_cmd_output('du ' + hlinkname)
553 self.assertRegex(du_output, expected_output)
554
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}")
562
563 size = humansize(self.mount_a.lstat(slink_abspath)['st_size'])
564 expected_output = r'{}{}{}'.format((size), " +", slinkname)
565
566 du_output = self.get_cephfs_shell_cmd_output('du ' + slinkname)
567 self.assertRegex(du_output, expected_output)
568
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}")
576
577 size = humansize(self.mount_a.lstat(slink_abspath)['st_size'])
578 expected_output = r'{}{}{}'.format(size, " +", slinkname)
579
580 du_output = self.get_cephfs_shell_cmd_output('du ' + slinkname)
581 self.assertRegex(du_output, expected_output)
582
583 # NOTE: tests using these are pretty slow since to this methods sleeps for
584 # 15 seconds
585 def _setup_files(self, return_path_to_files=False, path_prefix='./'):
586 dirname = 'dir1'
587 regfilename = 'regfile'
588 hlinkname = 'hlink'
589 slinkname = 'slink1'
590 slink2name = 'slink2'
591
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)
597
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}")
603
604 dir2_name = 'dir2'
605 dir21_name = 'dir21'
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}")
612
613 self.mount_a.client_remote.write_file(regfile_abspath, 'somedata')
614 self.mount_a.client_remote.write_file(regfile121_abspath, 'somemoredata')
615
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.
618 sleep(20)
619
620 expected_patterns = []
621 path_to_files = []
622
623 def append_expected_output_pattern(f):
624 if f == '/':
625 expected_patterns.append(r'{}{}{}'.format(size, " +", '.' + f))
626 else:
627 expected_patterns.append(r'{}{}{}'.format(size, " +",
628 path_prefix + path.relpath(f, self.mount_a.mountpoint)))
629
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=
633 False)['st_size'])
634 append_expected_output_pattern(f)
635
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)
641
642 # get size for CephFS root
643 size = 0
644 for f in [regfile_abspath, regfile121_abspath, slink_abspath,
645 slink2_abspath]:
646 size += self.mount_a.stat(f, follow_symlinks=False)['st_size']
647 size = humansize(size)
648 append_expected_output_pattern('/')
649
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))
655
656 return (expected_patterns, path_to_files)
657 else:
658 return expected_patterns
659
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')
663
664 for expected_output in expected_patterns_in_output:
665 self.assertRegex(du_output, expected_output)
666
667 def test_du_with_path_in_args(self):
668 expected_patterns_in_output, path_to_files = self._setup_files(True,
669 path_prefix='')
670
671 args = ['du', '/']
672 for p in path_to_files:
673 args.append(p)
674 du_output = self.get_cephfs_shell_cmd_output(args)
675
676 for expected_output in expected_patterns_in_output:
677 self.assertRegex(du_output, expected_output)
678
679 def test_du_with_no_args(self):
680 expected_patterns_in_output = self._setup_files()
681
682 du_output = self.get_cephfs_shell_cmd_output('du')
683
684 for expected_output in expected_patterns_in_output:
685 # Since CWD is CephFS root and being non-recursive expect only
686 # CWD in DU report.
687 if expected_output.find('/') == len(expected_output) - 1:
688 self.assertRegex(du_output, expected_output)
689
690
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))
695
696 shell_df = df_output.splitlines()[1].split()
697
698 block_size = int(self.mount_a.df()["total"]) // 1024
699 log.info("cephfs df block size output:{}\n".format(block_size))
700
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))
704
705 self.assertTupleEqual((block_size, st_size, block_size - st_size),
706 (int(shell_df[0]), int(shell_df[1]) , int(shell_df[2])))
707
708 def test_df_with_no_args(self):
709 expected_output = ''
710 df_output = self.get_cephfs_shell_cmd_output('df')
711 assert df_output == expected_output
712
713 def test_df_for_valid_directory(self):
714 dir_name = 'dir1'
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)
718
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')
723
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")
729
730
731 class TestQuota(TestCephFSShell):
732 dir_name = 'testdir'
733
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))
737
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)
742
743 quota_output = self.get_cephfs_shell_cmd_output(['quota', 'get', self.dir_name],
744 check_status=check_status)
745
746 quota_output = quota_output.split()
747 return quota_output[1], quota_output[3]
748
749 def test_set(self):
750 self.create_dir()
751 set_values = ('6', '2')
752 self.assertTupleEqual(self.set_and_get_quota_vals(set_values), set_values)
753
754 def test_replace_values(self):
755 self.test_set()
756 set_values = ('20', '4')
757 self.assertTupleEqual(self.set_and_get_quota_vals(set_values), set_values)
758
759 def test_set_invalid_dir(self):
760 set_values = ('5', '5')
761 try:
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")
765 except IndexError:
766 # Test should pass as values cannot be set for non existing directory
767 pass
768
769 def test_set_invalid_values(self):
770 self.create_dir()
771 set_values = ('-6', '-5')
772 try:
773 self.assertTupleEqual(self.set_and_get_quota_vals(set_values,
774 False), set_values)
775 raise Exception("Something went wrong!! Invalid values set")
776 except IndexError:
777 # Test should pass as invalid values cannot be set
778 pass
779
780 def test_exceed_file_limit(self):
781 self.test_set()
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")
785 try:
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):
792 return 0
793 raise
794
795 def test_exceed_write_limit(self):
796 self.test_set()
797 dir_abspath = path.join(self.mount_a.mountpoint, self.dir_name)
798 filename = 'test_file'
799 file_abspath = path.join(dir_abspath, filename)
800 try:
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)
807 if not path_exists:
808 # Testing with teuthology: No file is created.
809 return 0
810 elif path_exists and not path.getsize(file_abspath):
811 # Testing on Fedora 30: When write fails, empty file gets created.
812 return 0
813 else:
814 raise
815
816
817 class TestXattr(TestCephFSShell):
818 dir_name = 'testdir'
819
820 def create_dir(self):
821 self.run_cephfs_shell_cmd('mkdir ' + self.dir_name)
822
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))
827
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))
831
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))
835
836 return listxattr_output, getxattr_output
837
838 def test_set(self):
839 self.create_dir()
840 set_values = ('user.key', '2')
841 self.assertTupleEqual(self.set_get_list_xattr_vals(set_values), set_values)
842
843 def test_reset(self):
844 self.test_set()
845 set_values = ('user.key', '4')
846 self.assertTupleEqual(self.set_get_list_xattr_vals(set_values), set_values)
847
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],
851 input_val[1]])
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])
854
855 class TestLS(TestCephFSShell):
856 dir_name = ('test_dir')
857 hidden_dir_name = ('.test_hidden_dir')
858
859 def test_ls(self):
860 """ Test that ls prints files in CWD. """
861 self.run_cephfs_shell_cmd(f'mkdir {self.dir_name}')
862
863 ls_output = self.get_cephfs_shell_cmd_output("ls")
864 log.info(f"output of ls command:\n{ls_output}")
865
866 self.assertIn(self.dir_name, ls_output)
867
868 def test_ls_a(self):
869 """ Test ls -a prints hidden files in CWD."""
870
871 self.run_cephfs_shell_cmd(f'mkdir {self.hidden_dir_name}')
872
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}")
875
876 self.assertIn(self.hidden_dir_name, ls_a_output)
877
878 def test_ls_does_not_print_hidden_dir(self):
879 """ Test ls command does not print hidden directory """
880
881 self.run_cephfs_shell_cmd(f'mkdir {self.hidden_dir_name}')
882
883 ls_output = self.get_cephfs_shell_cmd_output("ls")
884 log.info(f"output of ls command:\n{ls_output}")
885
886 self.assertNotIn(self.hidden_dir_name, ls_output)
887
888 def test_ls_a_prints_non_hidden_dir(self):
889 """ Test ls -a command prints non hidden directory """
890
891 self.run_cephfs_shell_cmd(f'mkdir {self.hidden_dir_name} {self.dir_name}')
892
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}")
895
896 self.assertIn(self.dir_name, ls_a_output)
897
898 def test_ls_H_prints_human_readable_file_size(self):
899 """ Test "ls -lH" prints human readable file size."""
900
901 file_sizes = ['1','1K', '1M', '1G']
902 file_names = ['dump1', 'dump2', 'dump3', 'dump4']
903
904
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} ./')
909
910 ls_H_output = self.get_cephfs_shell_cmd_output(['ls', '-lH'])
911
912 ls_H_file_size = set()
913 for line in ls_H_output.split('\n'):
914 ls_H_file_size.add(line.split()[1])
915
916 # test that file sizes are in human readable format
917 self.assertEqual({'1B','1K', '1M', '1G'}, ls_H_file_size)
918
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
925
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)
928
929 ls_s_output = self.get_cephfs_shell_cmd_output(['ls', '-lS'])
930
931 file_sizes = []
932 for line in ls_s_output.split('\n'):
933 file_sizes.append(line.split()[1])
934
935 #test that file size are in ascending order
936 self.assertEqual(file_sizes, sorted(file_sizes))
937
938
939 class TestMisc(TestCephFSShell):
940 def test_issue_cephfs_shell_cmd_at_invocation(self):
941 """
942 Test that `cephfs-shell -c conf cmd` works.
943 """
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])
948
949 output = self.mount_a.client_remote.sh(['cephfs-shell', 'ls']).\
950 strip()
951
952 self.assertRegex(output, dirname)
953
954 def test_help(self):
955 """
956 Test that help outputs commands.
957 """
958 o = self.get_cephfs_shell_cmd_output("help all")
959 log.info("output:\n{}".format(o))
960
961 class TestShellOpts(TestCephFSShell):
962 """
963 Contains tests for shell options from conf file and shell prompt.
964 """
965
966 def setUp(self):
967 super(type(self), self).setUp()
968
969 # output of following command -
970 # editor - was: 'vim'
971 # now: '?'
972 # editor: '?'
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()
977
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,
982 confcontents)
983
984 def test_reading_conf(self):
985 self.write_tempconf("[cephfs-shell]\neditor = ???")
986
987 # output of following command -
988 # CephFS:~/>>> set editor
989 # editor: 'vim'
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)
994
995 self.assertNotEqual(self.editor_val, final_editor_val)
996
997 def test_reading_conf_with_dup_opt(self):
998 """
999 Read conf without duplicate sections/options.
1000 """
1001 self.write_tempconf("[cephfs-shell]\neditor = ???\neditor = " +
1002 self.editor_val)
1003
1004 # output of following command -
1005 # CephFS:~/>>> set editor
1006 # editor: 'vim'
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)
1011
1012 self.assertEqual(self.editor_val, final_editor_val)
1013
1014 def test_setting_opt_after_reading_conf(self):
1015 self.write_tempconf("[cephfs-shell]\neditor = ???")
1016
1017 # output of following command -
1018 # editor - was: vim
1019 # now: vim
1020 # editor: vim
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)
1027
1028 self.assertEqual(self.editor_val, final_editor_val)