]>
git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/cephfs/mount.py
7d04535c8240c9b6ff69b7977d4702fc15f3dc0d
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
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(self
, args
, wait
=True, stdin
=None, check_status
=True,
202 if isinstance(args
, str):
205 args
= ["cd", self
.mountpoint
, run
.Raw('&&'), "sudo"] + args
206 return self
.client_remote
.run(args
=args
, stdout
=StringIO(),
207 stderr
=StringIO(), wait
=wait
,
208 stdin
=stdin
, check_status
=check_status
,
211 def open_no_data(self
, basename
):
213 A pure metadata operation
215 assert(self
.is_mounted())
217 path
= os
.path
.join(self
.mountpoint
, basename
)
219 p
= self
._run
_python
(dedent(
221 f = open("{path}", 'w')
222 """.format(path
=path
)
226 def open_background(self
, basename
="background_file", write
=True):
228 Open a file for writing, then block such that the client
229 will hold a capability.
231 Don't return until the remote process has got as far as opening
232 the file, then return the RemoteProcess instance.
234 assert(self
.is_mounted())
236 path
= os
.path
.join(self
.mountpoint
, basename
)
239 pyscript
= dedent("""
242 with open("{path}", 'w') as f:
248 """).format(path
=path
)
250 pyscript
= dedent("""
253 with open("{path}", 'r') as f:
256 """).format(path
=path
)
258 rproc
= self
._run
_python
(pyscript
)
259 self
.background_procs
.append(rproc
)
261 # This wait would not be sufficient if the file had already
262 # existed, but it's simple and in practice users of open_background
263 # are not using it on existing files.
264 self
.wait_for_visible(basename
)
268 def wait_for_dir_empty(self
, dirname
, timeout
=30):
270 dirpath
= os
.path
.join(self
.mountpoint
, dirname
)
272 nr_entries
= int(self
.getfattr(dirpath
, "ceph.dir.entries"))
274 log
.debug("Directory {0} seen empty from {1} after {2}s ".format(
275 dirname
, self
.client_id
, i
))
281 raise RuntimeError("Timed out after {0}s waiting for {1} to become empty from {2}".format(
282 i
, dirname
, self
.client_id
))
284 def wait_for_visible(self
, basename
="background_file", timeout
=30):
287 r
= self
.client_remote
.run(args
=[
288 'sudo', 'ls', os
.path
.join(self
.mountpoint
, basename
)
289 ], check_status
=False)
290 if r
.exitstatus
== 0:
291 log
.debug("File {0} became visible from {1} after {2}s".format(
292 basename
, self
.client_id
, i
))
298 raise RuntimeError("Timed out after {0}s waiting for {1} to become visible from {2}".format(
299 i
, basename
, self
.client_id
))
301 def lock_background(self
, basename
="background_file", do_flock
=True):
303 Open and lock a files for writing, hold the lock in a background process
305 assert(self
.is_mounted())
307 path
= os
.path
.join(self
.mountpoint
, basename
)
314 script_builder
+= """
315 f1 = open("{path}-1", 'w')
316 fcntl.flock(f1, fcntl.LOCK_EX | fcntl.LOCK_NB)"""
317 script_builder
+= """
318 f2 = open("{path}-2", 'w')
319 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
320 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
325 pyscript
= dedent(script_builder
).format(path
=path
)
327 log
.info("lock_background file {0}".format(basename
))
328 rproc
= self
._run
_python
(pyscript
)
329 self
.background_procs
.append(rproc
)
332 def lock_and_release(self
, basename
="background_file"):
333 assert(self
.is_mounted())
335 path
= os
.path
.join(self
.mountpoint
, basename
)
341 f1 = open("{path}-1", 'w')
342 fcntl.flock(f1, fcntl.LOCK_EX)
343 f2 = open("{path}-2", 'w')
344 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
345 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
347 pyscript
= dedent(script
).format(path
=path
)
349 log
.info("lock_and_release file {0}".format(basename
))
350 return self
._run
_python
(pyscript
)
352 def check_filelock(self
, basename
="background_file", do_flock
=True):
353 assert(self
.is_mounted())
355 path
= os
.path
.join(self
.mountpoint
, basename
)
362 script_builder
+= """
363 f1 = open("{path}-1", 'r')
365 fcntl.flock(f1, fcntl.LOCK_EX | fcntl.LOCK_NB)
367 if e.errno == errno.EAGAIN:
370 raise RuntimeError("flock on file {path}-1 not found")"""
371 script_builder
+= """
372 f2 = open("{path}-2", 'r')
374 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
375 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
377 if e.errno == errno.EAGAIN:
380 raise RuntimeError("posix lock on file {path}-2 not found")
382 pyscript
= dedent(script_builder
).format(path
=path
)
384 log
.info("check lock on file {0}".format(basename
))
385 self
.client_remote
.run(args
=[
386 'sudo', 'python3', '-c', pyscript
389 def write_background(self
, basename
="background_file", loop
=False):
391 Open a file for writing, complete as soon as you can
395 assert(self
.is_mounted())
397 path
= os
.path
.join(self
.mountpoint
, basename
)
399 pyscript
= dedent("""
403 fd = os.open("{path}", os.O_RDWR | os.O_CREAT, 0o644)
406 os.write(fd, b'content')
413 """).format(path
=path
, loop
=str(loop
))
415 rproc
= self
._run
_python
(pyscript
)
416 self
.background_procs
.append(rproc
)
419 def write_n_mb(self
, filename
, n_mb
, seek
=0, wait
=True):
421 Write the requested number of megabytes to a file
423 assert(self
.is_mounted())
425 return self
.run_shell(["dd", "if=/dev/urandom", "of={0}".format(filename
),
426 "bs=1M", "conv=fdatasync",
427 "count={0}".format(int(n_mb
)),
428 "seek={0}".format(int(seek
))
431 def write_test_pattern(self
, filename
, size
):
432 log
.info("Writing {0} bytes to {1}".format(size
, filename
))
433 return self
.run_python(dedent("""
436 with open(path, 'w') as f:
437 for i in range(0, {size}):
438 val = zlib.crc32(str(i).encode('utf-8')) & 7
441 path
=os
.path
.join(self
.mountpoint
, filename
),
445 def validate_test_pattern(self
, filename
, size
):
446 log
.info("Validating {0} bytes from {1}".format(size
, filename
))
447 return self
.run_python(dedent("""
450 with open(path, 'r') as f:
452 if len(bytes) != {size}:
453 raise RuntimeError("Bad length {{0}} vs. expected {{1}}".format(
456 for i, b in enumerate(bytes):
457 val = zlib.crc32(str(i).encode('utf-8')) & 7
459 raise RuntimeError("Bad data at offset {{0}}".format(i))
461 path
=os
.path
.join(self
.mountpoint
, filename
),
465 def open_n_background(self
, fs_path
, count
):
467 Open N files for writing, hold them open in a background process
469 :param fs_path: Path relative to CephFS root, e.g. "foo/bar"
470 :return: a RemoteProcess
472 assert(self
.is_mounted())
474 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
476 pyscript
= dedent("""
482 abs_path = "{abs_path}"
484 if not os.path.exists(os.path.dirname(abs_path)):
485 os.makedirs(os.path.dirname(abs_path))
488 for i in range(0, n):
489 fname = "{{0}}_{{1}}".format(abs_path, i)
490 handles.append(open(fname, 'w'))
494 """).format(abs_path
=abs_path
, count
=count
)
496 rproc
= self
._run
_python
(pyscript
)
497 self
.background_procs
.append(rproc
)
500 def create_n_files(self
, fs_path
, count
, sync
=False):
501 assert(self
.is_mounted())
503 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
505 pyscript
= dedent("""
511 abs_path = "{abs_path}"
513 if not os.path.exists(os.path.dirname(abs_path)):
514 os.makedirs(os.path.dirname(abs_path))
516 for i in range(0, n):
517 fname = "{{0}}_{{1}}".format(abs_path, i)
518 with open(fname, 'w') as f:
523 """).format(abs_path
=abs_path
, count
=count
, sync
=str(sync
))
525 self
.run_python(pyscript
)
528 for p
in self
.background_procs
:
529 log
.info("Terminating background process")
530 self
._kill
_background
(p
)
532 self
.background_procs
= []
534 def _kill_background(self
, p
):
539 except (CommandFailedError
, ConnectionLostError
):
542 def kill_background(self
, p
):
544 For a process that was returned by one of the _background member functions,
547 self
._kill
_background
(p
)
548 self
.background_procs
.remove(p
)
550 def send_signal(self
, signal
):
551 signal
= signal
.lower()
552 if signal
.lower() not in ['sigstop', 'sigcont', 'sigterm', 'sigkill']:
553 raise NotImplementedError
555 self
.client_remote
.run(args
=['sudo', 'kill', '-{0}'.format(signal
),
556 self
.client_pid
], omit_sudo
=False)
558 def get_global_id(self
):
559 raise NotImplementedError()
561 def get_global_inst(self
):
562 raise NotImplementedError()
564 def get_global_addr(self
):
565 raise NotImplementedError()
567 def get_osd_epoch(self
):
568 raise NotImplementedError()
570 def lstat(self
, fs_path
, follow_symlinks
=False, wait
=True):
571 return self
.stat(fs_path
, follow_symlinks
=False, wait
=True)
573 def stat(self
, fs_path
, follow_symlinks
=True, wait
=True):
575 stat a file, and return the result as a dictionary like this:
577 "st_ctime": 1414161137.0,
578 "st_mtime": 1414161137.0,
586 "st_atime": 1431520593.0
589 Raises exception on absent file.
591 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
593 stat_call
= "os.stat('" + abs_path
+ "')"
595 stat_call
= "os.lstat('" + abs_path
+ "')"
597 pyscript
= dedent("""
608 attrs = ["st_mode", "st_ino", "st_dev", "st_nlink", "st_uid", "st_gid", "st_size", "st_atime", "st_mtime", "st_ctime"]
610 dict([(a, getattr(s, a)) for a in attrs]),
612 """).format(stat_call
=stat_call
)
613 proc
= self
._run
_python
(pyscript
)
616 return json
.loads(proc
.stdout
.getvalue().strip())
620 def touch(self
, fs_path
):
622 Create a dentry if it doesn't already exist. This python
623 implementation exists because the usual command line tool doesn't
624 pass through error codes like EIO.
629 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
630 pyscript
= dedent("""
635 f = open("{path}", "w")
639 """).format(path
=abs_path
)
640 proc
= self
._run
_python
(pyscript
)
643 def path_to_ino(self
, fs_path
, follow_symlinks
=True):
644 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
647 pyscript
= dedent("""
651 print(os.stat("{path}").st_ino)
652 """).format(path
=abs_path
)
654 pyscript
= dedent("""
658 print(os.lstat("{path}").st_ino)
659 """).format(path
=abs_path
)
661 proc
= self
._run
_python
(pyscript
)
663 return int(proc
.stdout
.getvalue().strip())
665 def path_to_nlink(self
, fs_path
):
666 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
668 pyscript
= dedent("""
672 print(os.stat("{path}").st_nlink)
673 """).format(path
=abs_path
)
675 proc
= self
._run
_python
(pyscript
)
677 return int(proc
.stdout
.getvalue().strip())
679 def ls(self
, path
=None):
681 Wrap ls: return a list of strings
687 ls_text
= self
.run_shell(cmd
).stdout
.getvalue().strip()
690 return ls_text
.split("\n")
692 # Special case because otherwise split on empty string
693 # gives you [''] instead of []
696 def setfattr(self
, path
, key
, val
):
700 :param path: relative to mount point
701 :param key: xattr name
702 :param val: xattr value
705 self
.run_shell(["setfattr", "-n", key
, "-v", val
, path
])
707 def getfattr(self
, path
, attr
):
709 Wrap getfattr: return the values of a named xattr on one file, or
710 None if the attribute is not found.
714 p
= self
.run_shell(["getfattr", "--only-values", "-n", attr
, path
], wait
=False)
717 except CommandFailedError
as e
:
718 if e
.exitstatus
== 1 and "No such attribute" in p
.stderr
.getvalue():
723 return str(p
.stdout
.getvalue())
727 Wrap df: return a dict of usage fields in bytes
730 p
= self
.run_shell(["df", "-B1", "."])
731 lines
= p
.stdout
.getvalue().strip().split("\n")
732 fs
, total
, used
, avail
= lines
[1].split()[:4]
738 "available": int(avail
)