]> git.proxmox.com Git - ceph.git/blame - ceph/qa/tasks/cephfs/mount.py
bump version to 15.2.11-pve1
[ceph.git] / ceph / qa / tasks / cephfs / mount.py
CommitLineData
7c673cae
FG
1from contextlib import contextmanager
2import json
3import logging
4import datetime
e306af50 5import six
7c673cae 6import time
e306af50 7from six import StringIO
7c673cae
FG
8from textwrap import dedent
9import os
cd265ab1
TL
10
11from teuthology.misc import sudo_write_file
7c673cae 12from teuthology.orchestra import run
f6b5b4d7 13from teuthology.orchestra.run import CommandFailedError, ConnectionLostError, Raw
11fdf7f2 14from tasks.cephfs.filesystem import Filesystem
7c673cae
FG
15
16log = logging.getLogger(__name__)
17
18
19class CephFSMount(object):
11fdf7f2 20 def __init__(self, ctx, test_dir, client_id, client_remote):
7c673cae
FG
21 """
22 :param test_dir: Global teuthology test dir
23 :param client_id: Client ID, the 'foo' in client.foo
24 :param client_remote: Remote instance for the host where client will run
25 """
26
11fdf7f2 27 self.ctx = ctx
7c673cae
FG
28 self.test_dir = test_dir
29 self.client_id = client_id
30 self.client_remote = client_remote
31 self.mountpoint_dir_name = 'mnt.{id}'.format(id=self.client_id)
9f95a23c 32 self._mountpoint = None
11fdf7f2 33 self.fs = None
7c673cae
FG
34
35 self.test_files = ['a', 'b', 'c']
36
37 self.background_procs = []
38
39 @property
40 def mountpoint(self):
9f95a23c
TL
41 if self._mountpoint == None:
42 self._mountpoint= os.path.join(
43 self.test_dir, '{dir_name}'.format(dir_name=self.mountpoint_dir_name))
44 return self._mountpoint
45
46 @mountpoint.setter
47 def mountpoint(self, path):
48 if not isinstance(path, str):
49 raise RuntimeError('path should be of str type.')
50 self._mountpoint = path
7c673cae
FG
51
52 def is_mounted(self):
53 raise NotImplementedError()
54
11fdf7f2
TL
55 def setupfs(self, name=None):
56 if name is None and self.fs is not None:
57 # Previous mount existed, reuse the old name
58 name = self.fs.name
59 self.fs = Filesystem(self.ctx, name=name)
60 log.info('Wait for MDS to reach steady state...')
61 self.fs.wait_for_daemons()
62 log.info('Ready to start {}...'.format(type(self).__name__))
63
9f95a23c 64 def mount(self, mount_path=None, mount_fs_name=None, mountpoint=None, mount_options=[]):
7c673cae
FG
65 raise NotImplementedError()
66
e306af50
TL
67 def mount_wait(self, mount_path=None, mount_fs_name=None, mountpoint=None, mount_options=[]):
68 self.mount(mount_path=mount_path, mount_fs_name=mount_fs_name, mountpoint=mountpoint,
69 mount_options=mount_options)
70 self.wait_until_mounted()
71
7c673cae
FG
72 def umount(self):
73 raise NotImplementedError()
74
75 def umount_wait(self, force=False, require_clean=False):
76 """
77
78 :param force: Expect that the mount will not shutdown cleanly: kill
79 it hard.
80 :param require_clean: Wait for the Ceph client associated with the
81 mount (e.g. ceph-fuse) to terminate, and
82 raise if it doesn't do so cleanly.
83 :return:
84 """
85 raise NotImplementedError()
86
87 def kill_cleanup(self):
88 raise NotImplementedError()
89
90 def kill(self):
91 raise NotImplementedError()
92
93 def cleanup(self):
94 raise NotImplementedError()
95
96 def wait_until_mounted(self):
97 raise NotImplementedError()
98
99 def get_keyring_path(self):
100 return '/etc/ceph/ceph.client.{id}.keyring'.format(id=self.client_id)
101
102 @property
103 def config_path(self):
104 """
105 Path to ceph.conf: override this if you're not a normal systemwide ceph install
106 :return: stringv
107 """
108 return "/etc/ceph/ceph.conf"
109
110 @contextmanager
111 def mounted(self):
112 """
113 A context manager, from an initially unmounted state, to mount
114 this, yield, and then unmount and clean up.
115 """
116 self.mount()
117 self.wait_until_mounted()
118 try:
119 yield
120 finally:
121 self.umount_wait()
122
11fdf7f2
TL
123 def is_blacklisted(self):
124 addr = self.get_global_addr()
125 blacklist = json.loads(self.fs.mon_manager.raw_cluster_cmd("osd", "blacklist", "ls", "--format=json"))
126 for b in blacklist:
127 if addr == b["addr"]:
128 return True
129 return False
130
9f95a23c
TL
131 def create_file(self, filename='testfile', dirname=None, user=None,
132 check_status=True):
133 assert(self.is_mounted())
134
135 if not os.path.isabs(filename):
136 if dirname:
137 if os.path.isabs(dirname):
138 path = os.path.join(dirname, filename)
139 else:
140 path = os.path.join(self.mountpoint, dirname, filename)
141 else:
142 path = os.path.join(self.mountpoint, filename)
143 else:
144 path = filename
145
146 if user:
147 args = ['sudo', '-u', user, '-s', '/bin/bash', '-c', 'touch ' + path]
148 else:
149 args = 'touch ' + path
150
151 return self.client_remote.run(args=args, check_status=check_status)
152
7c673cae
FG
153 def create_files(self):
154 assert(self.is_mounted())
155
156 for suffix in self.test_files:
157 log.info("Creating file {0}".format(suffix))
158 self.client_remote.run(args=[
159 'sudo', 'touch', os.path.join(self.mountpoint, suffix)
160 ])
161
9f95a23c
TL
162 def test_create_file(self, filename='testfile', dirname=None, user=None,
163 check_status=True):
164 return self.create_file(filename=filename, dirname=dirname, user=user,
165 check_status=False)
166
7c673cae
FG
167 def check_files(self):
168 assert(self.is_mounted())
169
170 for suffix in self.test_files:
171 log.info("Checking file {0}".format(suffix))
172 r = self.client_remote.run(args=[
173 'sudo', 'ls', os.path.join(self.mountpoint, suffix)
174 ], check_status=False)
175 if r.exitstatus != 0:
176 raise RuntimeError("Expected file {0} not found".format(suffix))
177
cd265ab1
TL
178 def write_file(self, path, data, perms=None):
179 """
180 Write the given data at the given path and set the given perms to the
181 file on the path.
182 """
183 if path.find(self.mountpoint) == -1:
184 path = os.path.join(self.mountpoint, path)
185
186 sudo_write_file(self.client_remote, path, data)
187
188 if perms:
189 self.run_shell(args=f'chmod {perms} {path}')
190
191 def read_file(self, path):
192 """
193 Return the data from the file on given path.
194 """
195 if path.find(self.mountpoint) == -1:
196 path = os.path.join(self.mountpoint, path)
197
198 return self.run_shell(args=['sudo', 'cat', path], omit_sudo=False).\
199 stdout.getvalue().strip()
200
7c673cae
FG
201 def create_destroy(self):
202 assert(self.is_mounted())
203
204 filename = "{0} {1}".format(datetime.datetime.now(), self.client_id)
205 log.debug("Creating test file {0}".format(filename))
206 self.client_remote.run(args=[
207 'sudo', 'touch', os.path.join(self.mountpoint, filename)
208 ])
209 log.debug("Deleting test file {0}".format(filename))
210 self.client_remote.run(args=[
211 'sudo', 'rm', '-f', os.path.join(self.mountpoint, filename)
212 ])
213
9f95a23c 214 def _run_python(self, pyscript, py_version='python3'):
91327a77
AA
215 return self.client_remote.run(
216 args=['sudo', 'adjust-ulimits', 'daemon-helper', 'kill',
217 py_version, '-c', pyscript], wait=False, stdin=run.PIPE,
218 stdout=StringIO())
219
9f95a23c 220 def run_python(self, pyscript, py_version='python3'):
91327a77 221 p = self._run_python(pyscript, py_version)
7c673cae 222 p.wait()
e306af50 223 return six.ensure_str(p.stdout.getvalue().strip())
7c673cae 224
f6b5b4d7
TL
225 def run_shell_payload(self, payload, **kwargs):
226 return self.run_shell(["bash", "-c", Raw(f"'{payload}'")], **kwargs)
227
9f95a23c 228 def run_shell(self, args, wait=True, stdin=None, check_status=True,
adb31ebb 229 omit_sudo=True, timeout=10800):
9f95a23c
TL
230 if isinstance(args, str):
231 args = args.split()
232
7c673cae
FG
233 args = ["cd", self.mountpoint, run.Raw('&&'), "sudo"] + args
234 return self.client_remote.run(args=args, stdout=StringIO(),
9f95a23c
TL
235 stderr=StringIO(), wait=wait,
236 stdin=stdin, check_status=check_status,
adb31ebb
TL
237 omit_sudo=omit_sudo,
238 timeout=timeout)
7c673cae
FG
239
240 def open_no_data(self, basename):
241 """
242 A pure metadata operation
243 """
244 assert(self.is_mounted())
245
246 path = os.path.join(self.mountpoint, basename)
247
248 p = self._run_python(dedent(
249 """
250 f = open("{path}", 'w')
251 """.format(path=path)
252 ))
253 p.wait()
254
494da23a 255 def open_background(self, basename="background_file", write=True):
7c673cae
FG
256 """
257 Open a file for writing, then block such that the client
258 will hold a capability.
259
260 Don't return until the remote process has got as far as opening
261 the file, then return the RemoteProcess instance.
262 """
263 assert(self.is_mounted())
264
265 path = os.path.join(self.mountpoint, basename)
266
494da23a
TL
267 if write:
268 pyscript = dedent("""
269 import time
270
9f95a23c
TL
271 with open("{path}", 'w') as f:
272 f.write('content')
273 f.flush()
274 f.write('content2')
275 while True:
276 time.sleep(1)
494da23a
TL
277 """).format(path=path)
278 else:
279 pyscript = dedent("""
280 import time
281
9f95a23c
TL
282 with open("{path}", 'r') as f:
283 while True:
284 time.sleep(1)
494da23a 285 """).format(path=path)
7c673cae
FG
286
287 rproc = self._run_python(pyscript)
288 self.background_procs.append(rproc)
289
290 # This wait would not be sufficient if the file had already
291 # existed, but it's simple and in practice users of open_background
292 # are not using it on existing files.
293 self.wait_for_visible(basename)
294
295 return rproc
296
494da23a
TL
297 def wait_for_dir_empty(self, dirname, timeout=30):
298 i = 0
299 dirpath = os.path.join(self.mountpoint, dirname)
300 while i < timeout:
301 nr_entries = int(self.getfattr(dirpath, "ceph.dir.entries"))
302 if nr_entries == 0:
303 log.debug("Directory {0} seen empty from {1} after {2}s ".format(
304 dirname, self.client_id, i))
305 return
306 else:
307 time.sleep(1)
308 i += 1
309
310 raise RuntimeError("Timed out after {0}s waiting for {1} to become empty from {2}".format(
311 i, dirname, self.client_id))
312
7c673cae
FG
313 def wait_for_visible(self, basename="background_file", timeout=30):
314 i = 0
315 while i < timeout:
316 r = self.client_remote.run(args=[
317 'sudo', 'ls', os.path.join(self.mountpoint, basename)
318 ], check_status=False)
319 if r.exitstatus == 0:
320 log.debug("File {0} became visible from {1} after {2}s".format(
321 basename, self.client_id, i))
322 return
323 else:
324 time.sleep(1)
325 i += 1
326
327 raise RuntimeError("Timed out after {0}s waiting for {1} to become visible from {2}".format(
328 i, basename, self.client_id))
329
330 def lock_background(self, basename="background_file", do_flock=True):
331 """
332 Open and lock a files for writing, hold the lock in a background process
333 """
334 assert(self.is_mounted())
335
336 path = os.path.join(self.mountpoint, basename)
337
338 script_builder = """
339 import time
340 import fcntl
341 import struct"""
342 if do_flock:
343 script_builder += """
344 f1 = open("{path}-1", 'w')
345 fcntl.flock(f1, fcntl.LOCK_EX | fcntl.LOCK_NB)"""
346 script_builder += """
347 f2 = open("{path}-2", 'w')
348 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
349 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
350 while True:
351 time.sleep(1)
352 """
353
354 pyscript = dedent(script_builder).format(path=path)
355
31f18b77 356 log.info("lock_background file {0}".format(basename))
7c673cae
FG
357 rproc = self._run_python(pyscript)
358 self.background_procs.append(rproc)
359 return rproc
360
31f18b77
FG
361 def lock_and_release(self, basename="background_file"):
362 assert(self.is_mounted())
363
364 path = os.path.join(self.mountpoint, basename)
365
366 script = """
367 import time
368 import fcntl
369 import struct
370 f1 = open("{path}-1", 'w')
371 fcntl.flock(f1, fcntl.LOCK_EX)
372 f2 = open("{path}-2", 'w')
373 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
374 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
375 """
376 pyscript = dedent(script).format(path=path)
377
378 log.info("lock_and_release file {0}".format(basename))
379 return self._run_python(pyscript)
380
7c673cae
FG
381 def check_filelock(self, basename="background_file", do_flock=True):
382 assert(self.is_mounted())
383
384 path = os.path.join(self.mountpoint, basename)
385
386 script_builder = """
387 import fcntl
388 import errno
389 import struct"""
390 if do_flock:
391 script_builder += """
392 f1 = open("{path}-1", 'r')
393 try:
394 fcntl.flock(f1, fcntl.LOCK_EX | fcntl.LOCK_NB)
9f95a23c 395 except IOError as e:
7c673cae
FG
396 if e.errno == errno.EAGAIN:
397 pass
398 else:
399 raise RuntimeError("flock on file {path}-1 not found")"""
400 script_builder += """
401 f2 = open("{path}-2", 'r')
402 try:
403 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
404 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
9f95a23c 405 except IOError as e:
7c673cae
FG
406 if e.errno == errno.EAGAIN:
407 pass
408 else:
409 raise RuntimeError("posix lock on file {path}-2 not found")
410 """
411 pyscript = dedent(script_builder).format(path=path)
412
413 log.info("check lock on file {0}".format(basename))
414 self.client_remote.run(args=[
9f95a23c 415 'sudo', 'python3', '-c', pyscript
7c673cae
FG
416 ])
417
418 def write_background(self, basename="background_file", loop=False):
419 """
420 Open a file for writing, complete as soon as you can
421 :param basename:
422 :return:
423 """
424 assert(self.is_mounted())
425
426 path = os.path.join(self.mountpoint, basename)
427
428 pyscript = dedent("""
429 import os
430 import time
431
9f95a23c 432 fd = os.open("{path}", os.O_RDWR | os.O_CREAT, 0o644)
7c673cae
FG
433 try:
434 while True:
9f95a23c 435 os.write(fd, b'content')
7c673cae
FG
436 time.sleep(1)
437 if not {loop}:
438 break
9f95a23c 439 except IOError as e:
7c673cae
FG
440 pass
441 os.close(fd)
442 """).format(path=path, loop=str(loop))
443
444 rproc = self._run_python(pyscript)
445 self.background_procs.append(rproc)
446 return rproc
447
448 def write_n_mb(self, filename, n_mb, seek=0, wait=True):
449 """
450 Write the requested number of megabytes to a file
451 """
452 assert(self.is_mounted())
453
454 return self.run_shell(["dd", "if=/dev/urandom", "of={0}".format(filename),
455 "bs=1M", "conv=fdatasync",
e306af50
TL
456 "count={0}".format(int(n_mb)),
457 "seek={0}".format(int(seek))
7c673cae
FG
458 ], wait=wait)
459
460 def write_test_pattern(self, filename, size):
461 log.info("Writing {0} bytes to {1}".format(size, filename))
462 return self.run_python(dedent("""
463 import zlib
464 path = "{path}"
9f95a23c
TL
465 with open(path, 'w') as f:
466 for i in range(0, {size}):
467 val = zlib.crc32(str(i).encode('utf-8')) & 7
468 f.write(chr(val))
7c673cae
FG
469 """.format(
470 path=os.path.join(self.mountpoint, filename),
471 size=size
472 )))
473
474 def validate_test_pattern(self, filename, size):
475 log.info("Validating {0} bytes from {1}".format(size, filename))
476 return self.run_python(dedent("""
477 import zlib
478 path = "{path}"
9f95a23c
TL
479 with open(path, 'r') as f:
480 bytes = f.read()
7c673cae
FG
481 if len(bytes) != {size}:
482 raise RuntimeError("Bad length {{0}} vs. expected {{1}}".format(
483 len(bytes), {size}
484 ))
485 for i, b in enumerate(bytes):
9f95a23c 486 val = zlib.crc32(str(i).encode('utf-8')) & 7
7c673cae
FG
487 if b != chr(val):
488 raise RuntimeError("Bad data at offset {{0}}".format(i))
489 """.format(
490 path=os.path.join(self.mountpoint, filename),
491 size=size
492 )))
493
494 def open_n_background(self, fs_path, count):
495 """
496 Open N files for writing, hold them open in a background process
497
498 :param fs_path: Path relative to CephFS root, e.g. "foo/bar"
499 :return: a RemoteProcess
500 """
501 assert(self.is_mounted())
502
503 abs_path = os.path.join(self.mountpoint, fs_path)
504
505 pyscript = dedent("""
506 import sys
507 import time
508 import os
509
510 n = {count}
511 abs_path = "{abs_path}"
512
f91f0fd5
TL
513 if not os.path.exists(abs_path):
514 os.makedirs(abs_path)
7c673cae
FG
515
516 handles = []
517 for i in range(0, n):
f91f0fd5
TL
518 fname = "file_"+str(i)
519 path = os.path.join(abs_path, fname)
520 handles.append(open(path, 'w'))
7c673cae
FG
521
522 while True:
523 time.sleep(1)
524 """).format(abs_path=abs_path, count=count)
525
526 rproc = self._run_python(pyscript)
527 self.background_procs.append(rproc)
528 return rproc
529
530 def create_n_files(self, fs_path, count, sync=False):
531 assert(self.is_mounted())
532
533 abs_path = os.path.join(self.mountpoint, fs_path)
534
535 pyscript = dedent("""
536 import sys
537 import time
538 import os
539
540 n = {count}
541 abs_path = "{abs_path}"
542
543 if not os.path.exists(os.path.dirname(abs_path)):
544 os.makedirs(os.path.dirname(abs_path))
545
546 for i in range(0, n):
547 fname = "{{0}}_{{1}}".format(abs_path, i)
9f95a23c
TL
548 with open(fname, 'w') as f:
549 f.write('content')
550 if {sync}:
551 f.flush()
552 os.fsync(f.fileno())
7c673cae
FG
553 """).format(abs_path=abs_path, count=count, sync=str(sync))
554
555 self.run_python(pyscript)
556
557 def teardown(self):
558 for p in self.background_procs:
559 log.info("Terminating background process")
560 self._kill_background(p)
561
562 self.background_procs = []
563
564 def _kill_background(self, p):
565 if p.stdin:
566 p.stdin.close()
567 try:
568 p.wait()
569 except (CommandFailedError, ConnectionLostError):
570 pass
571
572 def kill_background(self, p):
573 """
574 For a process that was returned by one of the _background member functions,
575 kill it hard.
576 """
577 self._kill_background(p)
578 self.background_procs.remove(p)
579
eafe8130
TL
580 def send_signal(self, signal):
581 signal = signal.lower()
582 if signal.lower() not in ['sigstop', 'sigcont', 'sigterm', 'sigkill']:
583 raise NotImplementedError
584
585 self.client_remote.run(args=['sudo', 'kill', '-{0}'.format(signal),
586 self.client_pid], omit_sudo=False)
587
7c673cae
FG
588 def get_global_id(self):
589 raise NotImplementedError()
590
11fdf7f2
TL
591 def get_global_inst(self):
592 raise NotImplementedError()
593
594 def get_global_addr(self):
595 raise NotImplementedError()
596
7c673cae
FG
597 def get_osd_epoch(self):
598 raise NotImplementedError()
599
9f95a23c
TL
600 def lstat(self, fs_path, follow_symlinks=False, wait=True):
601 return self.stat(fs_path, follow_symlinks=False, wait=True)
602
603 def stat(self, fs_path, follow_symlinks=True, wait=True):
7c673cae
FG
604 """
605 stat a file, and return the result as a dictionary like this:
606 {
607 "st_ctime": 1414161137.0,
608 "st_mtime": 1414161137.0,
609 "st_nlink": 33,
610 "st_gid": 0,
611 "st_dev": 16777218,
612 "st_size": 1190,
613 "st_ino": 2,
614 "st_uid": 0,
615 "st_mode": 16877,
616 "st_atime": 1431520593.0
617 }
618
619 Raises exception on absent file.
620 """
621 abs_path = os.path.join(self.mountpoint, fs_path)
9f95a23c
TL
622 if follow_symlinks:
623 stat_call = "os.stat('" + abs_path + "')"
624 else:
625 stat_call = "os.lstat('" + abs_path + "')"
7c673cae
FG
626
627 pyscript = dedent("""
628 import os
629 import stat
630 import json
631 import sys
632
633 try:
9f95a23c 634 s = {stat_call}
7c673cae
FG
635 except OSError as e:
636 sys.exit(e.errno)
637
638 attrs = ["st_mode", "st_ino", "st_dev", "st_nlink", "st_uid", "st_gid", "st_size", "st_atime", "st_mtime", "st_ctime"]
9f95a23c 639 print(json.dumps(
7c673cae 640 dict([(a, getattr(s, a)) for a in attrs]),
9f95a23c
TL
641 indent=2))
642 """).format(stat_call=stat_call)
7c673cae
FG
643 proc = self._run_python(pyscript)
644 if wait:
645 proc.wait()
646 return json.loads(proc.stdout.getvalue().strip())
647 else:
648 return proc
649
650 def touch(self, fs_path):
651 """
652 Create a dentry if it doesn't already exist. This python
653 implementation exists because the usual command line tool doesn't
654 pass through error codes like EIO.
655
656 :param fs_path:
657 :return:
658 """
659 abs_path = os.path.join(self.mountpoint, fs_path)
660 pyscript = dedent("""
661 import sys
662 import errno
663
664 try:
665 f = open("{path}", "w")
666 f.close()
667 except IOError as e:
668 sys.exit(errno.EIO)
669 """).format(path=abs_path)
670 proc = self._run_python(pyscript)
671 proc.wait()
672
673 def path_to_ino(self, fs_path, follow_symlinks=True):
674 abs_path = os.path.join(self.mountpoint, fs_path)
675
676 if follow_symlinks:
677 pyscript = dedent("""
678 import os
679 import stat
680
9f95a23c 681 print(os.stat("{path}").st_ino)
7c673cae
FG
682 """).format(path=abs_path)
683 else:
684 pyscript = dedent("""
685 import os
686 import stat
687
9f95a23c 688 print(os.lstat("{path}").st_ino)
7c673cae
FG
689 """).format(path=abs_path)
690
691 proc = self._run_python(pyscript)
692 proc.wait()
693 return int(proc.stdout.getvalue().strip())
694
695 def path_to_nlink(self, fs_path):
696 abs_path = os.path.join(self.mountpoint, fs_path)
697
698 pyscript = dedent("""
699 import os
700 import stat
701
9f95a23c 702 print(os.stat("{path}").st_nlink)
7c673cae
FG
703 """).format(path=abs_path)
704
705 proc = self._run_python(pyscript)
706 proc.wait()
707 return int(proc.stdout.getvalue().strip())
708
709 def ls(self, path=None):
710 """
711 Wrap ls: return a list of strings
712 """
713 cmd = ["ls"]
714 if path:
715 cmd.append(path)
716
717 ls_text = self.run_shell(cmd).stdout.getvalue().strip()
718
719 if ls_text:
720 return ls_text.split("\n")
721 else:
722 # Special case because otherwise split on empty string
723 # gives you [''] instead of []
724 return []
725
726 def setfattr(self, path, key, val):
727 """
728 Wrap setfattr.
729
730 :param path: relative to mount point
731 :param key: xattr name
732 :param val: xattr value
733 :return: None
734 """
735 self.run_shell(["setfattr", "-n", key, "-v", val, path])
736
737 def getfattr(self, path, attr):
738 """
739 Wrap getfattr: return the values of a named xattr on one file, or
740 None if the attribute is not found.
741
742 :return: a string
743 """
744 p = self.run_shell(["getfattr", "--only-values", "-n", attr, path], wait=False)
745 try:
746 p.wait()
747 except CommandFailedError as e:
748 if e.exitstatus == 1 and "No such attribute" in p.stderr.getvalue():
749 return None
750 else:
751 raise
752
e306af50 753 return str(p.stdout.getvalue())
7c673cae
FG
754
755 def df(self):
756 """
757 Wrap df: return a dict of usage fields in bytes
758 """
759
760 p = self.run_shell(["df", "-B1", "."])
761 lines = p.stdout.getvalue().strip().split("\n")
762 fs, total, used, avail = lines[1].split()[:4]
e306af50 763 log.warning(lines)
7c673cae
FG
764
765 return {
766 "total": int(total),
767 "used": int(used),
768 "available": int(avail)
769 }