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