]>
git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/cephfs/mount.py
7d9edda2769b6fc23d4e6e392d6c4d1e21ae82a3
1 from contextlib
import contextmanager
7 from six
import StringIO
8 from textwrap
import dedent
10 from teuthology
.orchestra
import run
11 from teuthology
.orchestra
.run
import CommandFailedError
, ConnectionLostError
, Raw
12 from tasks
.cephfs
.filesystem
import Filesystem
14 log
= logging
.getLogger(__name__
)
17 class CephFSMount(object):
18 def __init__(self
, ctx
, test_dir
, client_id
, client_remote
):
20 :param test_dir: Global teuthology test dir
21 :param client_id: Client ID, the 'foo' in client.foo
22 :param client_remote: Remote instance for the host where client will run
26 self
.test_dir
= test_dir
27 self
.client_id
= client_id
28 self
.client_remote
= client_remote
29 self
.mountpoint_dir_name
= 'mnt.{id}'.format(id=self
.client_id
)
30 self
._mountpoint
= None
33 self
.test_files
= ['a', 'b', 'c']
35 self
.background_procs
= []
39 if self
._mountpoint
== None:
40 self
._mountpoint
= os
.path
.join(
41 self
.test_dir
, '{dir_name}'.format(dir_name
=self
.mountpoint_dir_name
))
42 return self
._mountpoint
45 def mountpoint(self
, path
):
46 if not isinstance(path
, str):
47 raise RuntimeError('path should be of str type.')
48 self
._mountpoint
= path
51 raise NotImplementedError()
53 def setupfs(self
, name
=None):
54 if name
is None and self
.fs
is not None:
55 # Previous mount existed, reuse the old name
57 self
.fs
= Filesystem(self
.ctx
, name
=name
)
58 log
.info('Wait for MDS to reach steady state...')
59 self
.fs
.wait_for_daemons()
60 log
.info('Ready to start {}...'.format(type(self
).__name
__))
62 def mount(self
, mount_path
=None, mount_fs_name
=None, mountpoint
=None, mount_options
=[]):
63 raise NotImplementedError()
65 def mount_wait(self
, mount_path
=None, mount_fs_name
=None, mountpoint
=None, mount_options
=[]):
66 self
.mount(mount_path
=mount_path
, mount_fs_name
=mount_fs_name
, mountpoint
=mountpoint
,
67 mount_options
=mount_options
)
68 self
.wait_until_mounted()
71 raise NotImplementedError()
73 def umount_wait(self
, force
=False, require_clean
=False):
76 :param force: Expect that the mount will not shutdown cleanly: kill
78 :param require_clean: Wait for the Ceph client associated with the
79 mount (e.g. ceph-fuse) to terminate, and
80 raise if it doesn't do so cleanly.
83 raise NotImplementedError()
85 def kill_cleanup(self
):
86 raise NotImplementedError()
89 raise NotImplementedError()
92 raise NotImplementedError()
94 def wait_until_mounted(self
):
95 raise NotImplementedError()
97 def get_keyring_path(self
):
98 return '/etc/ceph/ceph.client.{id}.keyring'.format(id=self
.client_id
)
101 def config_path(self
):
103 Path to ceph.conf: override this if you're not a normal systemwide ceph install
106 return "/etc/ceph/ceph.conf"
111 A context manager, from an initially unmounted state, to mount
112 this, yield, and then unmount and clean up.
115 self
.wait_until_mounted()
121 def is_blacklisted(self
):
122 addr
= self
.get_global_addr()
123 blacklist
= json
.loads(self
.fs
.mon_manager
.raw_cluster_cmd("osd", "blacklist", "ls", "--format=json"))
125 if addr
== b
["addr"]:
129 def create_file(self
, filename
='testfile', dirname
=None, user
=None,
131 assert(self
.is_mounted())
133 if not os
.path
.isabs(filename
):
135 if os
.path
.isabs(dirname
):
136 path
= os
.path
.join(dirname
, filename
)
138 path
= os
.path
.join(self
.mountpoint
, dirname
, filename
)
140 path
= os
.path
.join(self
.mountpoint
, filename
)
145 args
= ['sudo', '-u', user
, '-s', '/bin/bash', '-c', 'touch ' + path
]
147 args
= 'touch ' + path
149 return self
.client_remote
.run(args
=args
, check_status
=check_status
)
151 def create_files(self
):
152 assert(self
.is_mounted())
154 for suffix
in self
.test_files
:
155 log
.info("Creating file {0}".format(suffix
))
156 self
.client_remote
.run(args
=[
157 'sudo', 'touch', os
.path
.join(self
.mountpoint
, suffix
)
160 def test_create_file(self
, filename
='testfile', dirname
=None, user
=None,
162 return self
.create_file(filename
=filename
, dirname
=dirname
, user
=user
,
165 def check_files(self
):
166 assert(self
.is_mounted())
168 for suffix
in self
.test_files
:
169 log
.info("Checking file {0}".format(suffix
))
170 r
= self
.client_remote
.run(args
=[
171 'sudo', 'ls', os
.path
.join(self
.mountpoint
, suffix
)
172 ], check_status
=False)
173 if r
.exitstatus
!= 0:
174 raise RuntimeError("Expected file {0} not found".format(suffix
))
176 def create_destroy(self
):
177 assert(self
.is_mounted())
179 filename
= "{0} {1}".format(datetime
.datetime
.now(), self
.client_id
)
180 log
.debug("Creating test file {0}".format(filename
))
181 self
.client_remote
.run(args
=[
182 'sudo', 'touch', os
.path
.join(self
.mountpoint
, filename
)
184 log
.debug("Deleting test file {0}".format(filename
))
185 self
.client_remote
.run(args
=[
186 'sudo', 'rm', '-f', os
.path
.join(self
.mountpoint
, filename
)
189 def _run_python(self
, pyscript
, py_version
='python3'):
190 return self
.client_remote
.run(
191 args
=['sudo', 'adjust-ulimits', 'daemon-helper', 'kill',
192 py_version
, '-c', pyscript
], wait
=False, stdin
=run
.PIPE
,
195 def run_python(self
, pyscript
, py_version
='python3'):
196 p
= self
._run
_python
(pyscript
, py_version
)
198 return six
.ensure_str(p
.stdout
.getvalue().strip())
200 def run_shell_payload(self
, payload
, **kwargs
):
201 return self
.run_shell(["bash", "-c", Raw(f
"'{payload}'")], **kwargs
)
203 def run_shell(self
, args
, wait
=True, stdin
=None, check_status
=True,
204 omit_sudo
=True, timeout
=10800):
205 if isinstance(args
, str):
208 args
= ["cd", self
.mountpoint
, run
.Raw('&&'), "sudo"] + args
209 return self
.client_remote
.run(args
=args
, stdout
=StringIO(),
210 stderr
=StringIO(), wait
=wait
,
211 stdin
=stdin
, check_status
=check_status
,
215 def open_no_data(self
, basename
):
217 A pure metadata operation
219 assert(self
.is_mounted())
221 path
= os
.path
.join(self
.mountpoint
, basename
)
223 p
= self
._run
_python
(dedent(
225 f = open("{path}", 'w')
226 """.format(path
=path
)
230 def open_background(self
, basename
="background_file", write
=True):
232 Open a file for writing, then block such that the client
233 will hold a capability.
235 Don't return until the remote process has got as far as opening
236 the file, then return the RemoteProcess instance.
238 assert(self
.is_mounted())
240 path
= os
.path
.join(self
.mountpoint
, basename
)
243 pyscript
= dedent("""
246 with open("{path}", 'w') as f:
252 """).format(path
=path
)
254 pyscript
= dedent("""
257 with open("{path}", 'r') as f:
260 """).format(path
=path
)
262 rproc
= self
._run
_python
(pyscript
)
263 self
.background_procs
.append(rproc
)
265 # This wait would not be sufficient if the file had already
266 # existed, but it's simple and in practice users of open_background
267 # are not using it on existing files.
268 self
.wait_for_visible(basename
)
272 def wait_for_dir_empty(self
, dirname
, timeout
=30):
274 dirpath
= os
.path
.join(self
.mountpoint
, dirname
)
276 nr_entries
= int(self
.getfattr(dirpath
, "ceph.dir.entries"))
278 log
.debug("Directory {0} seen empty from {1} after {2}s ".format(
279 dirname
, self
.client_id
, i
))
285 raise RuntimeError("Timed out after {0}s waiting for {1} to become empty from {2}".format(
286 i
, dirname
, self
.client_id
))
288 def wait_for_visible(self
, basename
="background_file", timeout
=30):
291 r
= self
.client_remote
.run(args
=[
292 'sudo', 'ls', os
.path
.join(self
.mountpoint
, basename
)
293 ], check_status
=False)
294 if r
.exitstatus
== 0:
295 log
.debug("File {0} became visible from {1} after {2}s".format(
296 basename
, self
.client_id
, i
))
302 raise RuntimeError("Timed out after {0}s waiting for {1} to become visible from {2}".format(
303 i
, basename
, self
.client_id
))
305 def lock_background(self
, basename
="background_file", do_flock
=True):
307 Open and lock a files for writing, hold the lock in a background process
309 assert(self
.is_mounted())
311 path
= os
.path
.join(self
.mountpoint
, basename
)
318 script_builder
+= """
319 f1 = open("{path}-1", 'w')
320 fcntl.flock(f1, fcntl.LOCK_EX | fcntl.LOCK_NB)"""
321 script_builder
+= """
322 f2 = open("{path}-2", 'w')
323 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
324 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
329 pyscript
= dedent(script_builder
).format(path
=path
)
331 log
.info("lock_background file {0}".format(basename
))
332 rproc
= self
._run
_python
(pyscript
)
333 self
.background_procs
.append(rproc
)
336 def lock_and_release(self
, basename
="background_file"):
337 assert(self
.is_mounted())
339 path
= os
.path
.join(self
.mountpoint
, basename
)
345 f1 = open("{path}-1", 'w')
346 fcntl.flock(f1, fcntl.LOCK_EX)
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)
351 pyscript
= dedent(script
).format(path
=path
)
353 log
.info("lock_and_release file {0}".format(basename
))
354 return self
._run
_python
(pyscript
)
356 def check_filelock(self
, basename
="background_file", do_flock
=True):
357 assert(self
.is_mounted())
359 path
= os
.path
.join(self
.mountpoint
, basename
)
366 script_builder
+= """
367 f1 = open("{path}-1", 'r')
369 fcntl.flock(f1, fcntl.LOCK_EX | fcntl.LOCK_NB)
371 if e.errno == errno.EAGAIN:
374 raise RuntimeError("flock on file {path}-1 not found")"""
375 script_builder
+= """
376 f2 = open("{path}-2", 'r')
378 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
379 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
381 if e.errno == errno.EAGAIN:
384 raise RuntimeError("posix lock on file {path}-2 not found")
386 pyscript
= dedent(script_builder
).format(path
=path
)
388 log
.info("check lock on file {0}".format(basename
))
389 self
.client_remote
.run(args
=[
390 'sudo', 'python3', '-c', pyscript
393 def write_background(self
, basename
="background_file", loop
=False):
395 Open a file for writing, complete as soon as you can
399 assert(self
.is_mounted())
401 path
= os
.path
.join(self
.mountpoint
, basename
)
403 pyscript
= dedent("""
407 fd = os.open("{path}", os.O_RDWR | os.O_CREAT, 0o644)
410 os.write(fd, b'content')
417 """).format(path
=path
, loop
=str(loop
))
419 rproc
= self
._run
_python
(pyscript
)
420 self
.background_procs
.append(rproc
)
423 def write_n_mb(self
, filename
, n_mb
, seek
=0, wait
=True):
425 Write the requested number of megabytes to a file
427 assert(self
.is_mounted())
429 return self
.run_shell(["dd", "if=/dev/urandom", "of={0}".format(filename
),
430 "bs=1M", "conv=fdatasync",
431 "count={0}".format(int(n_mb
)),
432 "seek={0}".format(int(seek
))
435 def write_test_pattern(self
, filename
, size
):
436 log
.info("Writing {0} bytes to {1}".format(size
, filename
))
437 return self
.run_python(dedent("""
440 with open(path, 'w') as f:
441 for i in range(0, {size}):
442 val = zlib.crc32(str(i).encode('utf-8')) & 7
445 path
=os
.path
.join(self
.mountpoint
, filename
),
449 def validate_test_pattern(self
, filename
, size
):
450 log
.info("Validating {0} bytes from {1}".format(size
, filename
))
451 return self
.run_python(dedent("""
454 with open(path, 'r') as f:
456 if len(bytes) != {size}:
457 raise RuntimeError("Bad length {{0}} vs. expected {{1}}".format(
460 for i, b in enumerate(bytes):
461 val = zlib.crc32(str(i).encode('utf-8')) & 7
463 raise RuntimeError("Bad data at offset {{0}}".format(i))
465 path
=os
.path
.join(self
.mountpoint
, filename
),
469 def open_n_background(self
, fs_path
, count
):
471 Open N files for writing, hold them open in a background process
473 :param fs_path: Path relative to CephFS root, e.g. "foo/bar"
474 :return: a RemoteProcess
476 assert(self
.is_mounted())
478 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
480 pyscript
= dedent("""
486 abs_path = "{abs_path}"
488 if not os.path.exists(abs_path):
489 os.makedirs(abs_path)
492 for i in range(0, n):
493 fname = "file_"+str(i)
494 path = os.path.join(abs_path, fname)
495 handles.append(open(path, 'w'))
499 """).format(abs_path
=abs_path
, count
=count
)
501 rproc
= self
._run
_python
(pyscript
)
502 self
.background_procs
.append(rproc
)
505 def create_n_files(self
, fs_path
, count
, sync
=False):
506 assert(self
.is_mounted())
508 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
510 pyscript
= dedent("""
516 abs_path = "{abs_path}"
518 if not os.path.exists(os.path.dirname(abs_path)):
519 os.makedirs(os.path.dirname(abs_path))
521 for i in range(0, n):
522 fname = "{{0}}_{{1}}".format(abs_path, i)
523 with open(fname, 'w') as f:
528 """).format(abs_path
=abs_path
, count
=count
, sync
=str(sync
))
530 self
.run_python(pyscript
)
533 for p
in self
.background_procs
:
534 log
.info("Terminating background process")
535 self
._kill
_background
(p
)
537 self
.background_procs
= []
539 def _kill_background(self
, p
):
544 except (CommandFailedError
, ConnectionLostError
):
547 def kill_background(self
, p
):
549 For a process that was returned by one of the _background member functions,
552 self
._kill
_background
(p
)
553 self
.background_procs
.remove(p
)
555 def send_signal(self
, signal
):
556 signal
= signal
.lower()
557 if signal
.lower() not in ['sigstop', 'sigcont', 'sigterm', 'sigkill']:
558 raise NotImplementedError
560 self
.client_remote
.run(args
=['sudo', 'kill', '-{0}'.format(signal
),
561 self
.client_pid
], omit_sudo
=False)
563 def get_global_id(self
):
564 raise NotImplementedError()
566 def get_global_inst(self
):
567 raise NotImplementedError()
569 def get_global_addr(self
):
570 raise NotImplementedError()
572 def get_osd_epoch(self
):
573 raise NotImplementedError()
575 def lstat(self
, fs_path
, follow_symlinks
=False, wait
=True):
576 return self
.stat(fs_path
, follow_symlinks
=False, wait
=True)
578 def stat(self
, fs_path
, follow_symlinks
=True, wait
=True):
580 stat a file, and return the result as a dictionary like this:
582 "st_ctime": 1414161137.0,
583 "st_mtime": 1414161137.0,
591 "st_atime": 1431520593.0
594 Raises exception on absent file.
596 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
598 stat_call
= "os.stat('" + abs_path
+ "')"
600 stat_call
= "os.lstat('" + abs_path
+ "')"
602 pyscript
= dedent("""
613 attrs = ["st_mode", "st_ino", "st_dev", "st_nlink", "st_uid", "st_gid", "st_size", "st_atime", "st_mtime", "st_ctime"]
615 dict([(a, getattr(s, a)) for a in attrs]),
617 """).format(stat_call
=stat_call
)
618 proc
= self
._run
_python
(pyscript
)
621 return json
.loads(proc
.stdout
.getvalue().strip())
625 def touch(self
, fs_path
):
627 Create a dentry if it doesn't already exist. This python
628 implementation exists because the usual command line tool doesn't
629 pass through error codes like EIO.
634 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
635 pyscript
= dedent("""
640 f = open("{path}", "w")
644 """).format(path
=abs_path
)
645 proc
= self
._run
_python
(pyscript
)
648 def path_to_ino(self
, fs_path
, follow_symlinks
=True):
649 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
652 pyscript
= dedent("""
656 print(os.stat("{path}").st_ino)
657 """).format(path
=abs_path
)
659 pyscript
= dedent("""
663 print(os.lstat("{path}").st_ino)
664 """).format(path
=abs_path
)
666 proc
= self
._run
_python
(pyscript
)
668 return int(proc
.stdout
.getvalue().strip())
670 def path_to_nlink(self
, fs_path
):
671 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
673 pyscript
= dedent("""
677 print(os.stat("{path}").st_nlink)
678 """).format(path
=abs_path
)
680 proc
= self
._run
_python
(pyscript
)
682 return int(proc
.stdout
.getvalue().strip())
684 def ls(self
, path
=None):
686 Wrap ls: return a list of strings
692 ls_text
= self
.run_shell(cmd
).stdout
.getvalue().strip()
695 return ls_text
.split("\n")
697 # Special case because otherwise split on empty string
698 # gives you [''] instead of []
701 def setfattr(self
, path
, key
, val
):
705 :param path: relative to mount point
706 :param key: xattr name
707 :param val: xattr value
710 self
.run_shell(["setfattr", "-n", key
, "-v", val
, path
])
712 def getfattr(self
, path
, attr
):
714 Wrap getfattr: return the values of a named xattr on one file, or
715 None if the attribute is not found.
719 p
= self
.run_shell(["getfattr", "--only-values", "-n", attr
, path
], wait
=False)
722 except CommandFailedError
as e
:
723 if e
.exitstatus
== 1 and "No such attribute" in p
.stderr
.getvalue():
728 return str(p
.stdout
.getvalue())
732 Wrap df: return a dict of usage fields in bytes
735 p
= self
.run_shell(["df", "-B1", "."])
736 lines
= p
.stdout
.getvalue().strip().split("\n")
737 fs
, total
, used
, avail
= lines
[1].split()[:4]
743 "available": int(avail
)