]> git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/cephfs/test_cephfs_shell.py
update source to Ceph Pacific 16.2.2
[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.orchestra.run 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('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_without_target_dir(self):
313 """
314 Test put and get commands without target path.
315 """
316 tempdir = self.mount_a.client_remote.mkdtemp()
317 tempdirname = path.basename(tempdir)
318 files = ('dump1', 'dump2', 'dump3', tempdirname)
319
320 for i, file_ in enumerate(files[ : -1]):
321 size = i + 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,
325 'count=1'])
326
327 self.run_cephfs_shell_cmd('put ' + tempdir)
328 for file_ in files:
329 if file_ == tempdirname:
330 self.mount_a.stat(path.join(self.mount_a.mountpoint, file_))
331 else:
332 self.mount_a.stat(path.join(self.mount_a.mountpoint,
333 tempdirname, file_))
334
335 self.mount_a.run_shell(['rm', '-rf', tempdir])
336
337 self.run_cephfs_shell_cmd('get ' + tempdirname)
338 pwd = self.get_cephfs_shell_cmd_output('!pwd')
339 for file_ in files:
340 if file_ == tempdirname:
341 self.mount_a.run_shell('stat ' + path.join(pwd, file_))
342 else:
343 self.mount_a.run_shell('stat ' + path.join(pwd, tempdirname,
344 file_))
345
346 def test_get_with_target_name(self):
347 """
348 Test that get passes with target name
349 """
350 s = 'C' * 1024
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))
354
355 # put - dump4 should pass
356 o = self.mount_a.stat('dump4')
357 log.info("mount_a output:\n{}".format(o))
358
359 o = self.get_cephfs_shell_cmd_output("get dump4 .")
360 log.info("cephfs-shell output:\n{}".format(o))
361
362 o = self.get_cephfs_shell_cmd_output("!cat dump4")
363 o_hash = crypt.crypt(o, '.A')
364
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)
369
370 def test_get_without_target_name(self):
371 """
372 Test that get passes with target name
373 """
374 s = 'D' * 1024
375 o = self.get_cephfs_shell_cmd_output("put - dump5", stdin=s)
376 log.info("cephfs-shell output:\n{}".format(o))
377
378 # put - dump5 should pass
379 o = self.mount_a.stat('dump5')
380 log.info("mount_a output:\n{}".format(o))
381
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))
386 l = o.split('\n')
387 try:
388 ret = int(l[1])
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"
392 assert(ret != 1)
393 except ValueError:
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
396 pass
397
398 def test_get_to_console(self):
399 """
400 Test that get passes with target name
401 """
402 s = 'E' * 1024
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))
406
407 # put - dump6 should pass
408 o = self.mount_a.stat('dump6')
409 log.info("mount_a output:\n{}".format(o))
410
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))
415
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)
420
421 class TestSnapshots(TestCephFSShell):
422 def test_snap(self):
423 """
424 Test that snapshot creation and deletion work
425 """
426 sd = self.fs.get_config('client_snapdir')
427 sdn = "data_dir/{}/snap1".format(sd)
428
429 # create a data dir and dump some files into it
430 self.get_cephfs_shell_cmd_output("mkdir data_dir")
431 s = 'A' * 10240
432 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_a", stdin=s)
433 s = 'B' * 10240
434 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_b", stdin=s)
435 s = 'C' * 10240
436 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_c", stdin=s)
437 s = 'D' * 10240
438 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_d", stdin=s)
439 s = 'E' * 10240
440 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_e", stdin=s)
441
442 o = self.get_cephfs_shell_cmd_output("ls -l /data_dir")
443 log.info("cephfs-shell output:\n{}".format(o))
444
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)
452
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)
459
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)
464 try:
465 o = self.mount_a.stat(sdn)
466 except CommandFailedError:
467 # snap dir should not exist anymore
468 pass
469 log.info("mount_a output:\n{}".format(o))
470 self.assertNotIn('st_mode', o)
471
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")
475 try:
476 o = self.mount_a.stat(sdn)
477 except CommandFailedError:
478 pass
479 log.info("mount_a output:\n{}".format(o))
480 self.assertNotIn('st_mode', o)
481
482 class TestCD(TestCephFSShell):
483 CLIENTS_REQUIRED = 1
484
485 def test_cd_with_no_args(self):
486 """
487 Test that when cd is issued without any arguments, CWD is changed
488 to root directory.
489 """
490 path = 'dir1/dir2/dir3'
491 self.mount_a.run_shell('mkdir -p ' + path)
492 expected_cwd = '/'
493
494 script = 'cd {}\ncd\ncwd\n'.format(path)
495 output = self.get_cephfs_shell_script_output(script)
496 self.assertEqual(output, expected_cwd)
497
498 def test_cd_with_args(self):
499 """
500 Test that when cd is issued with an argument, CWD is changed
501 to the path passed in the argument.
502 """
503 path = 'dir1/dir2/dir3'
504 self.mount_a.run_shell('mkdir -p ' + path)
505 expected_cwd = '/dir1/dir2/dir3'
506
507 script = 'cd {}\ncwd\n'.format(path)
508 output = self.get_cephfs_shell_script_output(script)
509 self.assertEqual(output, expected_cwd)
510
511 class TestDU(TestCephFSShell):
512 CLIENTS_REQUIRED = 1
513
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)
519
520 size = humansize(self.mount_a.stat(regfile_abspath)['st_size'])
521 expected_output = r'{}{}{}'.format(size, " +", regfilename)
522
523 du_output = self.get_cephfs_shell_cmd_output('du ' + regfilename)
524 self.assertRegex(du_output, expected_output)
525
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)
534
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)
539
540 sleep(10)
541 du_output = self.get_cephfs_shell_cmd_output('du ' + dirname)
542 self.assertRegex(du_output, expected_output)
543
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)
548
549 size = humansize(self.mount_a.stat(dir_abspath)['st_size'])
550 expected_output = r'{}{}{}'.format(size, " +", dirname)
551
552 du_output = self.get_cephfs_shell_cmd_output('du ' + dirname)
553 self.assertRegex(du_output, expected_output)
554
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)
564
565 size = humansize(self.mount_a.stat(hlink_abspath)['st_size'])
566 expected_output = r'{}{}{}'.format(size, " +", hlinkname)
567
568 du_output = self.get_cephfs_shell_cmd_output('du ' + hlinkname)
569 self.assertRegex(du_output, expected_output)
570
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])
579
580 size = humansize(self.mount_a.lstat(slink_abspath)['st_size'])
581 expected_output = r'{}{}{}'.format((size), " +", slinkname)
582
583 du_output = self.get_cephfs_shell_cmd_output('du ' + slinkname)
584 self.assertRegex(du_output, expected_output)
585
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])
593
594 size = humansize(self.mount_a.lstat(slink_abspath)['st_size'])
595 expected_output = r'{}{}{}'.format(size, " +", slinkname)
596
597 du_output = self.get_cephfs_shell_cmd_output('du ' + slinkname)
598 self.assertRegex(du_output, expected_output)
599
600 # NOTE: tests using these are pretty slow since to this methods sleeps for
601 # 15 seconds
602 def _setup_files(self, return_path_to_files=False, path_prefix='./'):
603 dirname = 'dir1'
604 regfilename = 'regfile'
605 hlinkname = 'hlink'
606 slinkname = 'slink1'
607 slink2name = 'slink2'
608
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)
614
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])
620
621 dir2_name = 'dir2'
622 dir21_name = 'dir21'
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)
629
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)
634
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.
637 sleep(20)
638
639 expected_patterns = []
640 path_to_files = []
641
642 def append_expected_output_pattern(f):
643 if f == '/':
644 expected_patterns.append(r'{}{}{}'.format(size, " +", '.' + f))
645 else:
646 expected_patterns.append(r'{}{}{}'.format(size, " +",
647 path_prefix + path.relpath(f, self.mount_a.mountpoint)))
648
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=
652 False)['st_size'])
653 append_expected_output_pattern(f)
654
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)
660
661 # get size for CephFS root
662 size = 0
663 for f in [regfile_abspath, regfile121_abspath, slink_abspath,
664 slink2_abspath]:
665 size += self.mount_a.stat(f, follow_symlinks=False)['st_size']
666 size = humansize(size)
667 append_expected_output_pattern('/')
668
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))
674
675 return (expected_patterns, path_to_files)
676 else:
677 return expected_patterns
678
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')
682
683 for expected_output in expected_patterns_in_output:
684 self.assertRegex(du_output, expected_output)
685
686 def test_du_with_path_in_args(self):
687 expected_patterns_in_output, path_to_files = self._setup_files(True,
688 path_prefix='')
689
690 args = ['du', '/']
691 for p in path_to_files:
692 args.append(p)
693 du_output = self.get_cephfs_shell_cmd_output(args)
694
695 for expected_output in expected_patterns_in_output:
696 self.assertRegex(du_output, expected_output)
697
698 def test_du_with_no_args(self):
699 expected_patterns_in_output = self._setup_files()
700
701 du_output = self.get_cephfs_shell_cmd_output('du')
702
703 for expected_output in expected_patterns_in_output:
704 # Since CWD is CephFS root and being non-recursive expect only
705 # CWD in DU report.
706 if expected_output.find('/') == len(expected_output) - 1:
707 self.assertRegex(du_output, expected_output)
708
709
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))
714
715 shell_df = df_output.splitlines()[1].split()
716
717 block_size = int(self.mount_a.df()["total"]) // 1024
718 log.info("cephfs df block size output:{}\n".format(block_size))
719
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))
723
724 self.assertTupleEqual((block_size, st_size, block_size - st_size),
725 (int(shell_df[0]), int(shell_df[1]) , int(shell_df[2])))
726
727 def test_df_with_no_args(self):
728 expected_output = ''
729 df_output = self.get_cephfs_shell_cmd_output('df')
730 assert df_output == expected_output
731
732 def test_df_for_valid_directory(self):
733 dir_name = 'dir1'
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)
737
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')
742
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")
748
749
750 class TestQuota(TestCephFSShell):
751 dir_name = 'testdir'
752
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))
756
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)
761
762 quota_output = self.get_cephfs_shell_cmd_output(['quota', 'get', self.dir_name],
763 check_status=check_status)
764
765 quota_output = quota_output.split()
766 return quota_output[1], quota_output[3]
767
768 def test_set(self):
769 self.create_dir()
770 set_values = ('6', '2')
771 self.assertTupleEqual(self.set_and_get_quota_vals(set_values), set_values)
772
773 def test_replace_values(self):
774 self.test_set()
775 set_values = ('20', '4')
776 self.assertTupleEqual(self.set_and_get_quota_vals(set_values), set_values)
777
778 def test_set_invalid_dir(self):
779 set_values = ('5', '5')
780 try:
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")
784 except IndexError:
785 # Test should pass as values cannot be set for non existing directory
786 pass
787
788 def test_set_invalid_values(self):
789 self.create_dir()
790 set_values = ('-6', '-5')
791 try:
792 self.assertTupleEqual(self.set_and_get_quota_vals(set_values,
793 False), set_values)
794 raise Exception("Something went wrong!! Invalid values set")
795 except IndexError:
796 # Test should pass as invalid values cannot be set
797 pass
798
799 def test_exceed_file_limit(self):
800 self.test_set()
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")
804 try:
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):
811 return 0
812 raise
813
814 def test_exceed_write_limit(self):
815 self.test_set()
816 dir_abspath = path.join(self.mount_a.mountpoint, self.dir_name)
817 filename = 'test_file'
818 file_abspath = path.join(dir_abspath, filename)
819 try:
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)
827 if not path_exists:
828 # Testing with teuthology: No file is created.
829 return 0
830 elif path_exists and not path.getsize(file_abspath):
831 # Testing on Fedora 30: When write fails, empty file gets created.
832 return 0
833 else:
834 raise
835
836
837 class TestXattr(TestCephFSShell):
838 dir_name = 'testdir'
839
840 def create_dir(self):
841 self.run_cephfs_shell_cmd('mkdir ' + self.dir_name)
842
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))
847
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))
851
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))
855
856 return listxattr_output, getxattr_output
857
858 def test_set(self):
859 self.create_dir()
860 set_values = ('user.key', '2')
861 self.assertTupleEqual(self.set_get_list_xattr_vals(set_values), set_values)
862
863 def test_reset(self):
864 self.test_set()
865 set_values = ('user.key', '4')
866 self.assertTupleEqual(self.set_get_list_xattr_vals(set_values), set_values)
867
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],
871 input_val[1]])
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])
874
875 class TestLS(TestCephFSShell):
876 dir_name = ('test_dir')
877 hidden_dir_name = ('.test_hidden_dir')
878
879 def test_ls(self):
880 """ Test that ls prints files in CWD. """
881 self.run_cephfs_shell_cmd(f'mkdir {self.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.assertIn(self.dir_name, ls_output)
887
888 def test_ls_a(self):
889 """ Test ls -a prints hidden files in CWD."""
890
891 self.run_cephfs_shell_cmd(f'mkdir {self.hidden_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.hidden_dir_name, ls_a_output)
897
898 def test_ls_does_not_print_hidden_dir(self):
899 """ Test ls command does not print hidden directory """
900
901 self.run_cephfs_shell_cmd(f'mkdir {self.hidden_dir_name}')
902
903 ls_output = self.get_cephfs_shell_cmd_output("ls")
904 log.info(f"output of ls command:\n{ls_output}")
905
906 self.assertNotIn(self.hidden_dir_name, ls_output)
907
908 def test_ls_a_prints_non_hidden_dir(self):
909 """ Test ls -a command prints non hidden directory """
910
911 self.run_cephfs_shell_cmd(f'mkdir {self.hidden_dir_name} {self.dir_name}')
912
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}")
915
916 self.assertIn(self.dir_name, ls_a_output)
917
918 def test_ls_H_prints_human_readable_file_size(self):
919 """ Test "ls -lH" prints human readable file size."""
920
921 file_sizes = ['1','1K', '1M', '1G']
922 file_names = ['dump1', 'dump2', 'dump3', 'dump4']
923
924
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} ./')
929
930 ls_H_output = self.get_cephfs_shell_cmd_output(['ls', '-lH'])
931
932 ls_H_file_size = set()
933 for line in ls_H_output.split('\n'):
934 ls_H_file_size.add(line.split()[1])
935
936 # test that file sizes are in human readable format
937 self.assertEqual({'1B','1K', '1M', '1G'}, ls_H_file_size)
938
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
945
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)
948
949 ls_s_output = self.get_cephfs_shell_cmd_output(['ls', '-lS'])
950
951 file_sizes = []
952 for line in ls_s_output.split('\n'):
953 file_sizes.append(line.split()[1])
954
955 #test that file size are in ascending order
956 self.assertEqual(file_sizes, sorted(file_sizes))
957
958
959 class TestMisc(TestCephFSShell):
960 def test_issue_cephfs_shell_cmd_at_invocation(self):
961 """
962 Test that `cephfs-shell -c conf cmd` works.
963 """
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])
968
969 output = self.mount_a.client_remote.sh(['cephfs-shell', 'ls']).\
970 strip()
971
972 self.assertRegex(output, dirname)
973
974 def test_help(self):
975 """
976 Test that help outputs commands.
977 """
978 o = self.get_cephfs_shell_cmd_output("help all")
979 log.info("output:\n{}".format(o))
980
981 class TestShellOpts(TestCephFSShell):
982 """
983 Contains tests for shell options from conf file and shell prompt.
984 """
985
986 def setUp(self):
987 super(type(self), self).setUp()
988
989 # output of following command -
990 # editor - was: 'vim'
991 # now: '?'
992 # editor: '?'
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()
997
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,
1002 confcontents)
1003
1004 def test_reading_conf(self):
1005 self.write_tempconf("[cephfs-shell]\neditor = ???")
1006
1007 # output of following command -
1008 # CephFS:~/>>> set editor
1009 # editor: 'vim'
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)
1014
1015 self.assertNotEqual(self.editor_val, final_editor_val)
1016
1017 def test_reading_conf_with_dup_opt(self):
1018 """
1019 Read conf without duplicate sections/options.
1020 """
1021 self.write_tempconf("[cephfs-shell]\neditor = ???\neditor = " +
1022 self.editor_val)
1023
1024 # output of following command -
1025 # CephFS:~/>>> set editor
1026 # editor: 'vim'
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)
1031
1032 self.assertEqual(self.editor_val, final_editor_val)
1033
1034 def test_setting_opt_after_reading_conf(self):
1035 self.write_tempconf("[cephfs-shell]\neditor = ???")
1036
1037 # output of following command -
1038 # editor - was: vim
1039 # now: vim
1040 # editor: vim
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)
1047
1048 self.assertEqual(self.editor_val, final_editor_val)