]> git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/cephfs/test_cephfs_shell.py
6a26cdd9fc967d4af4d2b50ba7716d7409ba6d47
[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 os import path
6 import crypt
7 import logging
8 from tempfile import mkstemp as tempfile_mkstemp
9 import math
10 from sys import version_info as sys_version_info
11 from re import search as re_search
12 from time import sleep
13 from StringIO import StringIO
14 from tasks.cephfs.cephfs_test_case import CephFSTestCase
15 from teuthology.misc import sudo_write_file
16 from teuthology.orchestra.run import CommandFailedError
17
18 log = logging.getLogger(__name__)
19
20 def humansize(nbytes):
21 suffixes = ['B', 'K', 'M', 'G', 'T', 'P']
22 i = 0
23 while nbytes >= 1024 and i < len(suffixes)-1:
24 nbytes /= 1024.
25 i += 1
26 nbytes = math.ceil(nbytes)
27 f = ('%d' % nbytes).rstrip('.')
28 return '%s%s' % (f, suffixes[i])
29
30 def str_to_bool(val):
31 val = val.strip()
32 trueval = ['true', 'yes', 'y', '1']
33 return True if val == 1 or val.lower() in trueval else False
34
35 class TestCephFSShell(CephFSTestCase):
36 CLIENTS_REQUIRED = 1
37
38 def run_cephfs_shell_cmd(self, cmd, mount_x=None, opts=None, stdin=None, config_path=None):
39 if mount_x is None:
40 mount_x = self.mount_a
41 if config_path is None:
42 config_path = self.mount_a.config_path
43
44 if isinstance(cmd, list):
45 cmd = " ".join(cmd)
46
47 args = ["cephfs-shell", "-c", config_path]
48
49 if opts is not None:
50 args.extend(opts)
51
52 args.extend(("--", cmd))
53
54 log.info("Running command: {}".format(" ".join(args)))
55 return mount_x.client_remote.run(args=args, stdout=StringIO(),
56 stderr=StringIO(), stdin=stdin)
57
58 def get_cephfs_shell_cmd_error(self, cmd, mount_x=None, opts=None,
59 stdin=None):
60 return self.run_cephfs_shell_cmd(cmd, mount_x, opts, stdin).stderr.\
61 getvalue().strip()
62
63 def get_cephfs_shell_cmd_output(self, cmd, mount_x=None, opts=None,
64 stdin=None, config_path=None):
65 return self.run_cephfs_shell_cmd(cmd, mount_x, opts, stdin,
66 config_path).\
67 stdout.getvalue().strip()
68
69 def get_cephfs_shell_script_output(self, script, mount_x=None, stdin=None):
70 return self.run_cephfs_shell_script(script, mount_x, stdin).stdout.\
71 getvalue().strip()
72
73 def run_cephfs_shell_script(self, script, mount_x=None, stdin=None):
74 if mount_x is None:
75 mount_x = self.mount_a
76
77 scriptpath = tempfile_mkstemp(prefix='test-cephfs', text=True)[1]
78 with open(scriptpath, 'w') as scriptfile:
79 scriptfile.write(script)
80 # copy script to the machine running cephfs-shell.
81 mount_x.client_remote.put_file(scriptpath, scriptpath)
82 mount_x.run_shell('chmod 755 ' + scriptpath)
83
84 args = ["cephfs-shell", "-c", mount_x.config_path, '-b', scriptpath]
85 log.info('Running script \"' + scriptpath + '\"')
86 return mount_x.client_remote.run(args=args, stdout=StringIO(),
87 stderr=StringIO(), stdin=stdin)
88
89 class TestMkdir(TestCephFSShell):
90 def test_mkdir(self):
91 """
92 Test that mkdir creates directory
93 """
94 o = self.get_cephfs_shell_cmd_output("mkdir d1")
95 log.info("cephfs-shell output:\n{}".format(o))
96
97 o = self.mount_a.stat('d1')
98 log.info("mount_a output:\n{}".format(o))
99
100 def test_mkdir_with_07000_octal_mode(self):
101 """
102 Test that mkdir fails with octal mode greater than 0777
103 """
104 o = self.get_cephfs_shell_cmd_output("mkdir -m 07000 d2")
105 log.info("cephfs-shell output:\n{}".format(o))
106
107 # mkdir d2 should fail
108 try:
109 o = self.mount_a.stat('d2')
110 log.info("mount_a output:\n{}".format(o))
111 except:
112 pass
113
114 def test_mkdir_with_negative_octal_mode(self):
115 """
116 Test that mkdir fails with negative octal mode
117 """
118 o = self.get_cephfs_shell_cmd_output("mkdir -m -0755 d3")
119 log.info("cephfs-shell output:\n{}".format(o))
120
121 # mkdir d3 should fail
122 try:
123 o = self.mount_a.stat('d3')
124 log.info("mount_a output:\n{}".format(o))
125 except:
126 pass
127
128 def test_mkdir_with_non_octal_mode(self):
129 """
130 Test that mkdir passes with non-octal mode
131 """
132 o = self.get_cephfs_shell_cmd_output("mkdir -m u=rwx d4")
133 log.info("cephfs-shell output:\n{}".format(o))
134
135 # mkdir d4 should pass
136 o = self.mount_a.stat('d4')
137 assert((o['st_mode'] & 0o700) == 0o700)
138
139 def test_mkdir_with_bad_non_octal_mode(self):
140 """
141 Test that mkdir failes with bad non-octal mode
142 """
143 o = self.get_cephfs_shell_cmd_output("mkdir -m ugx=0755 d5")
144 log.info("cephfs-shell output:\n{}".format(o))
145
146 # mkdir d5 should fail
147 try:
148 o = self.mount_a.stat('d5')
149 log.info("mount_a output:\n{}".format(o))
150 except:
151 pass
152
153 def test_mkdir_path_without_path_option(self):
154 """
155 Test that mkdir fails without path option for creating path
156 """
157 o = self.get_cephfs_shell_cmd_output("mkdir d5/d6/d7")
158 log.info("cephfs-shell output:\n{}".format(o))
159
160 # mkdir d5/d6/d7 should fail
161 try:
162 o = self.mount_a.stat('d5/d6/d7')
163 log.info("mount_a output:\n{}".format(o))
164 except:
165 pass
166
167 def test_mkdir_path_with_path_option(self):
168 """
169 Test that mkdir passes with path option for creating path
170 """
171 o = self.get_cephfs_shell_cmd_output("mkdir -p d5/d6/d7")
172 log.info("cephfs-shell output:\n{}".format(o))
173
174 # mkdir d5/d6/d7 should pass
175 o = self.mount_a.stat('d5/d6/d7')
176 log.info("mount_a output:\n{}".format(o))
177
178 class TestRmdir(TestCephFSShell):
179 dir_name = "test_dir"
180
181 def dir_does_not_exists(self):
182 """
183 Tests that directory does not exists
184 """
185 try:
186 self.mount_a.stat(self.dir_name)
187 except CommandFailedError as e:
188 if e.exitstatus == 2:
189 return 0
190 raise
191
192 def test_rmdir(self):
193 """
194 Test that rmdir deletes directory
195 """
196 self.run_cephfs_shell_cmd("mkdir " + self.dir_name)
197 self.run_cephfs_shell_cmd("rmdir "+ self.dir_name)
198 self.dir_does_not_exists()
199
200 def test_rmdir_non_existing_dir(self):
201 """
202 Test that rmdir does not delete a non existing directory
203 """
204 rmdir_output = self.get_cephfs_shell_cmd_error("rmdir test_dir")
205 log.info("rmdir error output:\n{}".format(rmdir_output))
206 self.dir_does_not_exists()
207
208 def test_rmdir_dir_with_file(self):
209 """
210 Test that rmdir does not delete directory containing file
211 """
212 self.run_cephfs_shell_cmd("mkdir " + self.dir_name)
213 self.run_cephfs_shell_cmd("put - test_dir/dumpfile", stdin="Valid File")
214 self.run_cephfs_shell_cmd("rmdir" + self.dir_name)
215 self.mount_a.stat(self.dir_name)
216
217 def test_rmdir_existing_file(self):
218 """
219 Test that rmdir does not delete a file
220 """
221 self.run_cephfs_shell_cmd("put - dumpfile", stdin="Valid File")
222 self.run_cephfs_shell_cmd("rmdir dumpfile")
223 self.mount_a.stat("dumpfile")
224
225 def test_rmdir_p(self):
226 """
227 Test that rmdir -p deletes all empty directories in the root directory passed
228 """
229 self.run_cephfs_shell_cmd("mkdir -p test_dir/t1/t2/t3")
230 self.run_cephfs_shell_cmd("rmdir -p "+ self.dir_name)
231 self.dir_does_not_exists()
232
233 def test_rmdir_p_valid_path(self):
234 """
235 Test that rmdir -p deletes all empty directories in the path passed
236 """
237 self.run_cephfs_shell_cmd("mkdir -p test_dir/t1/t2/t3")
238 self.run_cephfs_shell_cmd("rmdir -p test_dir/t1/t2/t3")
239 self.dir_does_not_exists()
240
241 def test_rmdir_p_non_existing_dir(self):
242 """
243 Test that rmdir -p does not delete an invalid directory
244 """
245 rmdir_output = self.get_cephfs_shell_cmd_error("rmdir -p test_dir")
246 log.info("rmdir error output:\n{}".format(rmdir_output))
247 self.dir_does_not_exists()
248
249 def test_rmdir_p_dir_with_file(self):
250 """
251 Test that rmdir -p does not delete the directory containing a file
252 """
253 self.run_cephfs_shell_cmd("mkdir " + self.dir_name)
254 self.run_cephfs_shell_cmd("put - test_dir/dumpfile", stdin="Valid File")
255 self.run_cephfs_shell_cmd("rmdir -p " + self.dir_name)
256 self.mount_a.stat(self.dir_name)
257
258 class TestGetAndPut(TestCephFSShell):
259 # the 'put' command gets tested as well with the 'get' comamnd
260 def test_put_and_get_without_target_directory(self):
261 """
262 Test that put fails without target path
263 """
264 # generate test data in a directory
265 self.run_cephfs_shell_cmd("!mkdir p1")
266 self.run_cephfs_shell_cmd('!dd if=/dev/urandom of=p1/dump1 bs=1M count=1')
267 self.run_cephfs_shell_cmd('!dd if=/dev/urandom of=p1/dump2 bs=2M count=1')
268 self.run_cephfs_shell_cmd('!dd if=/dev/urandom of=p1/dump3 bs=3M count=1')
269
270 # copy the whole directory over to the cephfs
271 o = self.get_cephfs_shell_cmd_output("put p1")
272 log.info("cephfs-shell output:\n{}".format(o))
273
274 # put p1 should pass
275 o = self.mount_a.stat('p1')
276 log.info("mount_a output:\n{}".format(o))
277 o = self.mount_a.stat('p1/dump1')
278 log.info("mount_a output:\n{}".format(o))
279 o = self.mount_a.stat('p1/dump2')
280 log.info("mount_a output:\n{}".format(o))
281 o = self.mount_a.stat('p1/dump3')
282 log.info("mount_a output:\n{}".format(o))
283
284 self.run_cephfs_shell_cmd('!rm -rf p1')
285 o = self.get_cephfs_shell_cmd_output("get p1")
286 o = self.get_cephfs_shell_cmd_output('!stat p1 || echo $?')
287 log.info("cephfs-shell output:\n{}".format(o))
288 self.validate_stat_output(o)
289
290 o = self.get_cephfs_shell_cmd_output('!stat p1/dump1 || echo $?')
291 log.info("cephfs-shell output:\n{}".format(o))
292 self.validate_stat_output(o)
293
294 o = self.get_cephfs_shell_cmd_output('!stat p1/dump2 || echo $?')
295 log.info("cephfs-shell output:\n{}".format(o))
296 self.validate_stat_output(o)
297
298 o = self.get_cephfs_shell_cmd_output('!stat p1/dump3 || echo $?')
299 log.info("cephfs-shell output:\n{}".format(o))
300 self.validate_stat_output(o)
301
302 def validate_stat_output(self, s):
303 l = s.split('\n')
304 log.info("lines:\n{}".format(l))
305 rv = l[-1] # get last line; a failed stat will have "1" as the line
306 log.info("rv:{}".format(rv))
307 r = 0
308 try:
309 r = int(rv) # a non-numeric line will cause an exception
310 except:
311 pass
312 assert(r == 0)
313
314 def test_get_with_target_name(self):
315 """
316 Test that get passes with target name
317 """
318 s = 'C' * 1024
319 s_hash = crypt.crypt(s, '.A')
320 o = self.get_cephfs_shell_cmd_output("put - dump4", stdin=s)
321 log.info("cephfs-shell output:\n{}".format(o))
322
323 # put - dump4 should pass
324 o = self.mount_a.stat('dump4')
325 log.info("mount_a output:\n{}".format(o))
326
327 o = self.get_cephfs_shell_cmd_output("get dump4 .")
328 log.info("cephfs-shell output:\n{}".format(o))
329
330 o = self.get_cephfs_shell_cmd_output("!cat dump4")
331 o_hash = crypt.crypt(o, '.A')
332
333 # s_hash must be equal to o_hash
334 log.info("s_hash:{}".format(s_hash))
335 log.info("o_hash:{}".format(o_hash))
336 assert(s_hash == o_hash)
337
338 def test_get_without_target_name(self):
339 """
340 Test that get passes with target name
341 """
342 s = 'D' * 1024
343 o = self.get_cephfs_shell_cmd_output("put - dump5", stdin=s)
344 log.info("cephfs-shell output:\n{}".format(o))
345
346 # put - dump5 should pass
347 o = self.mount_a.stat('dump5')
348 log.info("mount_a output:\n{}".format(o))
349
350 # get dump5 should fail
351 o = self.get_cephfs_shell_cmd_output("get dump5")
352 o = self.get_cephfs_shell_cmd_output("!stat dump5 || echo $?")
353 log.info("cephfs-shell output:\n{}".format(o))
354 l = o.split('\n')
355 try:
356 ret = int(l[1])
357 # verify that stat dump5 passes
358 # if ret == 1, then that implies the stat failed
359 # which implies that there was a problem with "get dump5"
360 assert(ret != 1)
361 except ValueError:
362 # we have a valid stat output; so this is good
363 # if the int() fails then that means there's a valid stat output
364 pass
365
366 def test_get_to_console(self):
367 """
368 Test that get passes with target name
369 """
370 s = 'E' * 1024
371 s_hash = crypt.crypt(s, '.A')
372 o = self.get_cephfs_shell_cmd_output("put - dump6", stdin=s)
373 log.info("cephfs-shell output:\n{}".format(o))
374
375 # put - dump6 should pass
376 o = self.mount_a.stat('dump6')
377 log.info("mount_a output:\n{}".format(o))
378
379 # get dump6 - should pass
380 o = self.get_cephfs_shell_cmd_output("get dump6 -")
381 o_hash = crypt.crypt(o, '.A')
382 log.info("cephfs-shell output:\n{}".format(o))
383
384 # s_hash must be equal to o_hash
385 log.info("s_hash:{}".format(s_hash))
386 log.info("o_hash:{}".format(o_hash))
387 assert(s_hash == o_hash)
388
389 class TestSnapshots(TestCephFSShell):
390 def test_snap(self):
391 """
392 Test that snapshot creation and deletion work
393 """
394 sd = self.fs.get_config('client_snapdir')
395 sdn = "data_dir/{}/snap1".format(sd)
396
397 # create a data dir and dump some files into it
398 self.get_cephfs_shell_cmd_output("mkdir data_dir")
399 s = 'A' * 10240
400 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_a", stdin=s)
401 s = 'B' * 10240
402 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_b", stdin=s)
403 s = 'C' * 10240
404 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_c", stdin=s)
405 s = 'D' * 10240
406 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_d", stdin=s)
407 s = 'E' * 10240
408 o = self.get_cephfs_shell_cmd_output("put - data_dir/data_e", stdin=s)
409
410 o = self.get_cephfs_shell_cmd_output("ls -l /data_dir")
411 log.info("cephfs-shell output:\n{}".format(o))
412
413 # create the snapshot - must pass
414 o = self.get_cephfs_shell_cmd_output("snap create snap1 /data_dir")
415 log.info("cephfs-shell output:\n{}".format(o))
416 self.assertEqual("", o)
417 o = self.mount_a.stat(sdn)
418 log.info("mount_a output:\n{}".format(o))
419 self.assertIn('st_mode', o)
420
421 # create the same snapshot again - must fail with an error message
422 o = self.get_cephfs_shell_cmd_error("snap create snap1 /data_dir")
423 log.info("cephfs-shell output:\n{}".format(o))
424 self.assertIn("snapshot 'snap1' already exists", o)
425 o = self.mount_a.stat(sdn)
426 log.info("mount_a output:\n{}".format(o))
427 self.assertIn('st_mode', o)
428
429 # delete the snapshot - must pass
430 o = self.get_cephfs_shell_cmd_output("snap delete snap1 /data_dir")
431 log.info("cephfs-shell output:\n{}".format(o))
432 self.assertEqual("", o)
433 try:
434 o = self.mount_a.stat(sdn)
435 except:
436 # snap dir should not exist anymore
437 pass
438 log.info("mount_a output:\n{}".format(o))
439 self.assertNotIn('st_mode', o)
440
441 # delete the same snapshot again - must fail with an error message
442 o = self.get_cephfs_shell_cmd_error("snap delete snap1 /data_dir")
443 self.assertIn("'snap1': no such snapshot", o)
444 try:
445 o = self.mount_a.stat(sdn)
446 except:
447 pass
448 log.info("mount_a output:\n{}".format(o))
449 self.assertNotIn('st_mode', o)
450
451 class TestCD(TestCephFSShell):
452 CLIENTS_REQUIRED = 1
453
454 def test_cd_with_no_args(self):
455 """
456 Test that when cd is issued without any arguments, CWD is changed
457 to root directory.
458 """
459 path = 'dir1/dir2/dir3'
460 self.mount_a.run_shell('mkdir -p ' + path)
461 expected_cwd = '/'
462
463 script = 'cd {}\ncd\ncwd\n'.format(path)
464 output = self.get_cephfs_shell_script_output(script)
465 self.assertEqual(output, expected_cwd)
466
467 def test_cd_with_args(self):
468 """
469 Test that when cd is issued with an argument, CWD is changed
470 to the path passed in the argument.
471 """
472 path = 'dir1/dir2/dir3'
473 self.mount_a.run_shell('mkdir -p ' + path)
474 expected_cwd = '/dir1/dir2/dir3'
475
476 script = 'cd {}\ncwd\n'.format(path)
477 output = self.get_cephfs_shell_script_output(script)
478 self.assertEqual(output, expected_cwd)
479
480 class TestDU(TestCephFSShell):
481 CLIENTS_REQUIRED = 1
482
483 def test_du_works_for_regfiles(self):
484 regfilename = 'some_regfile'
485 regfile_abspath = path.join(self.mount_a.mountpoint, regfilename)
486 sudo_write_file(self.mount_a.client_remote, regfile_abspath, 'somedata')
487
488 size = humansize(self.mount_a.stat(regfile_abspath)['st_size'])
489 expected_output = r'{}{}{}'.format(size, " +", regfilename)
490
491 du_output = self.get_cephfs_shell_cmd_output('du ' + regfilename)
492 if sys_version_info.major >= 3:
493 self.assertRegex(expected_output, du_output)
494 elif sys_version_info.major < 3:
495 assert re_search(expected_output, du_output) != None, "\n" + \
496 "expected_output -\n{}\ndu_output -\n{}\n".format(
497 expected_output, du_output)
498
499 def test_du_works_for_non_empty_dirs(self):
500 dirname = 'some_directory'
501 dir_abspath = path.join(self.mount_a.mountpoint, dirname)
502 regfilename = 'some_regfile'
503 regfile_abspath = path.join(dir_abspath, regfilename)
504 self.mount_a.run_shell('mkdir ' + dir_abspath)
505 sudo_write_file(self.mount_a.client_remote, regfile_abspath, 'somedata')
506
507 # XXX: we stat `regfile_abspath` here because ceph du reports a non-empty
508 # directory's size as sum of sizes of all files under it.
509 size = humansize(self.mount_a.stat(regfile_abspath)['st_size'])
510 expected_output = r'{}{}{}'.format(size, " +", dirname)
511
512 sleep(10)
513 du_output = self.get_cephfs_shell_cmd_output('du ' + dirname)
514 if sys_version_info.major >= 3:
515 self.assertRegex(expected_output, du_output)
516 elif sys_version_info.major < 3:
517 assert re_search(expected_output, du_output) != None, "\n" + \
518 "expected_output -\n{}\ndu_output -\n{}\n".format(
519 expected_output, du_output)
520
521 def test_du_works_for_empty_dirs(self):
522 dirname = 'some_directory'
523 dir_abspath = path.join(self.mount_a.mountpoint, dirname)
524 self.mount_a.run_shell('mkdir ' + dir_abspath)
525
526 size = humansize(self.mount_a.stat(dir_abspath)['st_size'])
527 expected_output = r'{}{}{}'.format(size, " +", dirname)
528
529 du_output = self.get_cephfs_shell_cmd_output('du ' + dirname)
530 if sys_version_info.major >= 3:
531 self.assertRegex(expected_output, du_output)
532 elif sys_version_info.major < 3:
533 assert re_search(expected_output, du_output) != None, "\n" + \
534 "expected_output -\n{}\ndu_output -\n{}\n".format(
535 expected_output, du_output)
536
537 def test_du_works_for_hardlinks(self):
538 regfilename = 'some_regfile'
539 regfile_abspath = path.join(self.mount_a.mountpoint, regfilename)
540 sudo_write_file(self.mount_a.client_remote, regfile_abspath,
541 'somedata')
542 hlinkname = 'some_hardlink'
543 hlink_abspath = path.join(self.mount_a.mountpoint, hlinkname)
544 self.mount_a.run_shell(['sudo', 'ln', regfile_abspath,
545 hlink_abspath], omit_sudo=False)
546
547 size = humansize(self.mount_a.stat(hlink_abspath)['st_size'])
548 expected_output = r'{}{}{}'.format(size, " +", hlinkname)
549
550 du_output = self.get_cephfs_shell_cmd_output('du ' + hlinkname)
551 if sys_version_info.major >= 3:
552 self.assertRegex(expected_output, du_output)
553 elif sys_version_info.major < 3:
554 assert re_search(expected_output, du_output) != None, "\n" + \
555 "expected_output -\n{}\ndu_output -\n{}\n".format(
556 expected_output, du_output)
557
558 def test_du_works_for_softlinks_to_files(self):
559 regfilename = 'some_regfile'
560 regfile_abspath = path.join(self.mount_a.mountpoint, regfilename)
561 sudo_write_file(self.mount_a.client_remote, regfile_abspath, 'somedata')
562 slinkname = 'some_softlink'
563 slink_abspath = path.join(self.mount_a.mountpoint, slinkname)
564 self.mount_a.run_shell(['ln', '-s', regfile_abspath, slink_abspath])
565
566 size = humansize(self.mount_a.lstat(slink_abspath)['st_size'])
567 expected_output = r'{}{}{}'.format((size), " +", slinkname)
568
569 du_output = self.get_cephfs_shell_cmd_output('du ' + slinkname)
570 if sys_version_info.major >= 3:
571 self.assertRegex(expected_output, du_output)
572 elif sys_version_info.major < 3:
573 assert re_search(expected_output, du_output) != None, "\n" + \
574 "expected_output -\n{}\ndu_output -\n{}\n".format(
575 expected_output, du_output)
576
577 def test_du_works_for_softlinks_to_dirs(self):
578 dirname = 'some_directory'
579 dir_abspath = path.join(self.mount_a.mountpoint, dirname)
580 self.mount_a.run_shell('mkdir ' + dir_abspath)
581 slinkname = 'some_softlink'
582 slink_abspath = path.join(self.mount_a.mountpoint, slinkname)
583 self.mount_a.run_shell(['ln', '-s', dir_abspath, slink_abspath])
584
585 size = humansize(self.mount_a.lstat(slink_abspath)['st_size'])
586 expected_output = r'{}{}{}'.format(size, " +", slinkname)
587
588 du_output = self.get_cephfs_shell_cmd_output('du ' + slinkname)
589 if sys_version_info.major >= 3:
590 self.assertRegex(expected_output, du_output)
591 elif sys_version_info.major < 3:
592 assert re_search(expected_output, du_output) != None, "\n" + \
593 "expected_output -\n{}\ndu_output -\n{}\n".format(
594 expected_output, du_output)
595
596 # NOTE: tests using these are pretty slow since to this methods sleeps for
597 # 15 seconds
598 def _setup_files(self, return_path_to_files=False, path_prefix='./'):
599 dirname = 'dir1'
600 regfilename = 'regfile'
601 hlinkname = 'hlink'
602 slinkname = 'slink1'
603 slink2name = 'slink2'
604
605 dir_abspath = path.join(self.mount_a.mountpoint, dirname)
606 regfile_abspath = path.join(self.mount_a.mountpoint, regfilename)
607 hlink_abspath = path.join(self.mount_a.mountpoint, hlinkname)
608 slink_abspath = path.join(self.mount_a.mountpoint, slinkname)
609 slink2_abspath = path.join(self.mount_a.mountpoint, slink2name)
610
611 self.mount_a.run_shell('mkdir ' + dir_abspath)
612 self.mount_a.run_shell('touch ' + regfile_abspath)
613 self.mount_a.run_shell(['ln', regfile_abspath, hlink_abspath])
614 self.mount_a.run_shell(['ln', '-s', regfile_abspath, slink_abspath])
615 self.mount_a.run_shell(['ln', '-s', dir_abspath, slink2_abspath])
616
617 dir2_name = 'dir2'
618 dir21_name = 'dir21'
619 regfile121_name = 'regfile121'
620 dir2_abspath = path.join(self.mount_a.mountpoint, dir2_name)
621 dir21_abspath = path.join(dir2_abspath, dir21_name)
622 regfile121_abspath = path.join(dir21_abspath, regfile121_name)
623 self.mount_a.run_shell('mkdir -p ' + dir21_abspath)
624 self.mount_a.run_shell('touch ' + regfile121_abspath)
625
626 sudo_write_file(self.mount_a.client_remote, regfile_abspath,
627 'somedata')
628 sudo_write_file(self.mount_a.client_remote, regfile121_abspath,
629 'somemoredata')
630
631 # TODO: is there a way to trigger/force update ceph.dir.rbytes?
632 # wait so that attr ceph.dir.rbytes gets a chance to be updated.
633 sleep(20)
634
635 expected_patterns = []
636 path_to_files = []
637
638 def append_expected_output_pattern(f):
639 if f == '/':
640 expected_patterns.append(r'{}{}{}'.format(size, " +", '.' + f))
641 else:
642 expected_patterns.append(r'{}{}{}'.format(size, " +",
643 path_prefix + path.relpath(f, self.mount_a.mountpoint)))
644
645 for f in [dir_abspath, regfile_abspath, regfile121_abspath,
646 hlink_abspath, slink_abspath, slink2_abspath]:
647 size = humansize(self.mount_a.stat(f, follow_symlinks=
648 False)['st_size'])
649 append_expected_output_pattern(f)
650
651 # get size for directories containig regfiles within
652 for f in [dir2_abspath, dir21_abspath]:
653 size = humansize(self.mount_a.stat(regfile121_abspath,
654 follow_symlinks=False)['st_size'])
655 append_expected_output_pattern(f)
656
657 # get size for CephFS root
658 size = 0
659 for f in [regfile_abspath, regfile121_abspath, slink_abspath,
660 slink2_abspath]:
661 size += self.mount_a.stat(f, follow_symlinks=False)['st_size']
662 size = humansize(size)
663 append_expected_output_pattern('/')
664
665 if return_path_to_files:
666 for p in [dir_abspath, regfile_abspath, dir2_abspath,
667 dir21_abspath, regfile121_abspath, hlink_abspath,
668 slink_abspath, slink2_abspath]:
669 path_to_files.append(path.relpath(p, self.mount_a.mountpoint))
670
671 return (expected_patterns, path_to_files)
672 else:
673 return expected_patterns
674
675 def test_du_works_recursively_with_no_path_in_args(self):
676 expected_patterns_in_output = self._setup_files()
677 du_output = self.get_cephfs_shell_cmd_output('du -r')
678
679 for expected_output in expected_patterns_in_output:
680 if sys_version_info.major >= 3:
681 self.assertRegex(expected_output, du_output)
682 elif sys_version_info.major < 3:
683 assert re_search(expected_output, du_output) != None, "\n" + \
684 "expected_output -\n{}\ndu_output -\n{}\n".format(
685 expected_output, du_output)
686
687 def test_du_with_path_in_args(self):
688 expected_patterns_in_output, path_to_files = self._setup_files(True,
689 path_prefix='')
690
691 args = ['du', '/']
692 for p in path_to_files:
693 args.append(p)
694 du_output = self.get_cephfs_shell_cmd_output(args)
695
696 for expected_output in expected_patterns_in_output:
697 if sys_version_info.major >= 3:
698 self.assertRegex(expected_output, du_output)
699 elif sys_version_info.major < 3:
700 assert re_search(expected_output, du_output) != None, "\n" +\
701 "expected_output -\n{}\ndu_output -\n{}\n".format(
702 expected_output, du_output)
703
704 def test_du_with_no_args(self):
705 expected_patterns_in_output = self._setup_files()
706
707 du_output = self.get_cephfs_shell_cmd_output('du')
708
709 for expected_output in expected_patterns_in_output:
710 # Since CWD is CephFS root and being non-recursive expect only
711 # CWD in DU report.
712 if expected_output.find('/') == len(expected_output) - 1:
713 if sys_version_info.major >= 3:
714 self.assertRegex(expected_output, du_output)
715 elif sys_version_info.major < 3:
716 assert re_search(expected_output, du_output) != None, "\n" + \
717 "expected_output -\n{}\ndu_output -\n{}\n".format(
718 expected_output, du_output)
719
720
721 class TestDF(TestCephFSShell):
722 def validate_df(self, filename):
723 df_output = self.get_cephfs_shell_cmd_output('df '+filename)
724 log.info("cephfs-shell df output:\n{}".format(df_output))
725
726 shell_df = df_output.splitlines()[1].split()
727
728 block_size = int(self.mount_a.df()["total"]) // 1024
729 log.info("cephfs df block size output:{}\n".format(block_size))
730
731 st_size = int(self.mount_a.stat(filename)["st_size"])
732 log.info("cephfs stat used output:{}".format(st_size))
733 log.info("cephfs available:{}\n".format(block_size - st_size))
734
735 self.assertTupleEqual((block_size, st_size, block_size - st_size),
736 (int(shell_df[0]), int(shell_df[1]) , int(shell_df[2])))
737
738 def test_df_with_no_args(self):
739 expected_output = ''
740 df_output = self.get_cephfs_shell_cmd_output('df')
741 assert df_output == expected_output
742
743 def test_df_for_valid_directory(self):
744 dir_name = 'dir1'
745 mount_output = self.mount_a.run_shell('mkdir ' + dir_name)
746 log.info("cephfs-shell mount output:\n{}".format(mount_output))
747 self.validate_df(dir_name)
748
749 def test_df_for_invalid_directory(self):
750 dir_abspath = path.join(self.mount_a.mountpoint, 'non-existent-dir')
751 proc = self.run_cephfs_shell_cmd('df ' + dir_abspath)
752 assert proc.stderr.getvalue().find('error in stat') != -1
753
754 def test_df_for_valid_file(self):
755 s = 'df test' * 14145016
756 o = self.get_cephfs_shell_cmd_output("put - dumpfile", stdin=s)
757 log.info("cephfs-shell output:\n{}".format(o))
758 self.validate_df("dumpfile")
759
760
761 class TestQuota(TestCephFSShell):
762 dir_name = 'testdir'
763
764 def create_dir(self):
765 mount_output = self.get_cephfs_shell_cmd_output('mkdir ' + self.dir_name)
766 log.info("cephfs-shell mount output:\n{}".format(mount_output))
767
768 def set_and_get_quota_vals(self, input_val):
769 quota_output = self.run_cephfs_shell_cmd('quota set --max_bytes '
770 + input_val[0] + ' --max_files '
771 + input_val[1] + ' '
772 + self.dir_name)
773 log.info("cephfs-shell quota set output:\n{}".format(quota_output))
774
775 quota_output = self.get_cephfs_shell_cmd_output('quota get '+ self.dir_name)
776 log.info("cephfs-shell quota get output:\n{}".format(quota_output))
777
778 quota_output = quota_output.split()
779 return quota_output[1], quota_output[3]
780
781 def test_set(self):
782 self.create_dir()
783 set_values = ('6', '2')
784 self.assertTupleEqual(self.set_and_get_quota_vals(set_values), set_values)
785
786 def test_replace_values(self):
787 self.test_set()
788 set_values = ('20', '4')
789 self.assertTupleEqual(self.set_and_get_quota_vals(set_values), set_values)
790
791 def test_set_invalid_dir(self):
792 set_values = ('5', '5')
793 try:
794 self.assertTupleEqual(self.set_and_get_quota_vals(set_values), set_values)
795 raise Exception("Something went wrong!! Values set for non existing directory")
796 except IndexError:
797 # Test should pass as values cannot be set for non existing directory
798 pass
799
800 def test_set_invalid_values(self):
801 self.create_dir()
802 set_values = ('-6', '-5')
803 try:
804 self.assertTupleEqual(self.set_and_get_quota_vals(set_values), set_values)
805 raise Exception("Something went wrong!! Invalid values set")
806 except IndexError:
807 # Test should pass as invalid values cannot be set
808 pass
809
810 def test_exceed_file_limit(self):
811 self.test_set()
812 dir_abspath = path.join(self.mount_a.mountpoint, self.dir_name)
813 self.mount_a.run_shell('touch '+dir_abspath+'/file1')
814 file2 = path.join(dir_abspath, "file2")
815 try:
816 self.mount_a.run_shell('touch '+file2)
817 raise Exception("Something went wrong!! File creation should have failed")
818 except CommandFailedError:
819 # Test should pass as file quota set to 2
820 # Additional condition to confirm file creation failure
821 if not path.exists(file2):
822 return 0
823 raise
824
825 def test_exceed_write_limit(self):
826 self.test_set()
827 dir_abspath = path.join(self.mount_a.mountpoint, self.dir_name)
828 filename = 'test_file'
829 file_abspath = path.join(dir_abspath, filename)
830 try:
831 # Write should fail as bytes quota is set to 6
832 sudo_write_file(self.mount_a.client_remote, file_abspath,
833 'Disk raise Exception')
834 raise Exception("Write should have failed")
835 except CommandFailedError:
836 # Test should pass only when write command fails
837 path_exists = path.exists(file_abspath)
838 if not path_exists:
839 # Testing with teuthology: No file is created.
840 return 0
841 elif path_exists and not path.getsize(file_abspath):
842 # Testing on Fedora 30: When write fails, empty file gets created.
843 return 0
844 else:
845 raise
846
847
848 class TestXattr(TestCephFSShell):
849 dir_name = 'testdir'
850
851 def create_dir(self):
852 self.run_cephfs_shell_cmd('mkdir ' + self.dir_name)
853
854 def set_get_list_xattr_vals(self, input_val):
855 setxattr_output = self.get_cephfs_shell_cmd_output('setxattr '
856 + self.dir_name
857 + ' '
858 + input_val[0]
859 + ' ' + input_val[1])
860 log.info("cephfs-shell setxattr output:\n{}".format(setxattr_output))
861
862 getxattr_output = self.get_cephfs_shell_cmd_output('getxattr '
863 + self.dir_name
864 + ' ' + input_val[0])
865 log.info("cephfs-shell getxattr output:\n{}".format(getxattr_output))
866
867 listxattr_output = self.get_cephfs_shell_cmd_output('listxattr '+ self.dir_name)
868 log.info("cephfs-shell listxattr output:\n{}".format(listxattr_output))
869
870 return listxattr_output, getxattr_output
871
872 def test_set(self):
873 self.create_dir()
874 set_values = ('user.key', '2')
875 self.assertTupleEqual(self.set_get_list_xattr_vals(set_values), set_values)
876
877 def test_reset(self):
878 self.test_set()
879 set_values = ('user.key', '4')
880 self.assertTupleEqual(self.set_get_list_xattr_vals(set_values), set_values)
881
882 def test_non_existing_dir(self):
883 set_values = ('user.key', '9')
884 self.assertTupleEqual(self.set_get_list_xattr_vals(set_values), (u'', u''))
885
886 # def test_ls(self):
887 # """
888 # Test that ls passes
889 # """
890 # o = self.get_cephfs_shell_cmd_output("ls")
891 # log.info("cephfs-shell output:\n{}".format(o))
892 #
893 # o = self.mount_a.run_shell(['ls']).stdout.getvalue().strip().replace("\n", " ").split()
894 # log.info("mount_a output:\n{}".format(o))
895 #
896 # # ls should not list hidden files without the -a switch
897 # if '.' in o or '..' in o:
898 # log.info('ls failed')
899 # else:
900 # log.info('ls succeeded')
901 #
902 # def test_ls_a(self):
903 # """
904 # Test that ls -a passes
905 # """
906 # o = self.get_cephfs_shell_cmd_output("ls -a")
907 # log.info("cephfs-shell output:\n{}".format(o))
908 #
909 # o = self.mount_a.run_shell(['ls', '-a']).stdout.getvalue().strip().replace("\n", " ").split()
910 # log.info("mount_a output:\n{}".format(o))
911 #
912 # if '.' in o and '..' in o:
913 # log.info('ls -a succeeded')
914 # else:
915 # log.info('ls -a failed')
916
917 class TestMisc(TestCephFSShell):
918 def test_issue_cephfs_shell_cmd_at_invocation(self):
919 """
920 Test that `cephfs-shell -c conf cmd` works.
921 """
922 # choosing a long name since short ones have a higher probability
923 # of getting matched by coincidence.
924 dirname = 'somedirectory'
925 self.run_cephfs_shell_cmd(['mkdir', dirname])
926
927 output = self.mount_a.client_remote.run(args=['cephfs-shell', '-c',
928 self.mount_a.config_path, 'ls'],
929 stdout=StringIO()).stdout.getvalue().strip()
930
931 if sys_version_info.major >= 3:
932 self.assertRegex(dirname, output)
933 elif sys_version_info.major < 3:
934 assert re_search(dirname, output) != None, "\n" + \
935 "expected_output -\n{}\ndu_output -\n{}\n".format(
936 dirname, output)
937
938 def test_help(self):
939 """
940 Test that help outputs commands.
941 """
942 o = self.get_cephfs_shell_cmd_output("help all")
943 log.info("output:\n{}".format(o))
944
945 class TestConfReading(TestCephFSShell):
946 def test_reading_conf_opt(self):
947 """
948 Read conf without duplicate sections/options.
949 """
950 debugval = self.fs.mon_manager.raw_cluster_cmd('config', 'get',
951 'client','debug_shell')
952 debugval = str_to_bool(debugval)
953 self.fs.mon_manager.raw_cluster_cmd('config', 'set', 'client',
954 'debug_shell', str(not debugval))
955 output = self.get_cephfs_shell_cmd_output('set debug')
956 new_debug_val = \
957 str_to_bool(output[output.find('debug: ') + len('debug: ') : ])
958 assert not debugval == new_debug_val
959
960 def test_reading_conf_after_setting_opt_twice(self):
961 """
962 Read conf without duplicate sections/options.
963 """
964 debugval = self.fs.mon_manager.raw_cluster_cmd('config', 'get',
965 'client','debug_shell')
966 debugval = str_to_bool(debugval)
967
968 self.fs.mon_manager.raw_cluster_cmd('config', 'set', 'client',
969 'debug_shell', str(not debugval))
970 self.fs.mon_manager.raw_cluster_cmd('config', 'set', 'client',
971 'debug_shell', str(not debugval))
972 output = self.get_cephfs_shell_cmd_output('set debug')
973 new_debug_val = \
974 str_to_bool(output[output.find('debug: ') + len('debug: ') : ])
975 assert not debugval == new_debug_val
976
977 def test_reading_conf_after_resetting_opt(self):
978 debugval = self.fs.mon_manager.raw_cluster_cmd('config', 'get',
979 'client','debug_shell')
980 debugval = str_to_bool(debugval)
981
982 self.fs.mon_manager.raw_cluster_cmd('config', 'set', 'client',
983 'debug_shell', str(not debugval))
984 self.fs.mon_manager.raw_cluster_cmd('config', 'rm', 'client',
985 'debug_shell')
986 self.fs.mon_manager.raw_cluster_cmd('config', 'set', 'client',
987 'debug_shell', str(not debugval))
988 output = self.get_cephfs_shell_cmd_output('set debug')
989 new_debug_val = \
990 str_to_bool(output[output.find('debug: ') + len('debug: ') : ])
991 assert not debugval == new_debug_val