]>
git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/cephfs/mount.py
aeed4fa2043466c7d743332caafc13ed405a3ad8
1 from contextlib
import contextmanager
6 from textwrap
import dedent
8 from StringIO
import StringIO
9 from teuthology
.orchestra
import run
10 from teuthology
.orchestra
.run
import CommandFailedError
, ConnectionLostError
11 from tasks
.cephfs
.filesystem
import Filesystem
13 log
= logging
.getLogger(__name__
)
16 class CephFSMount(object):
17 def __init__(self
, ctx
, test_dir
, client_id
, client_remote
):
19 :param test_dir: Global teuthology test dir
20 :param client_id: Client ID, the 'foo' in client.foo
21 :param client_remote: Remote instance for the host where client will run
25 self
.test_dir
= test_dir
26 self
.client_id
= client_id
27 self
.client_remote
= client_remote
28 self
.mountpoint_dir_name
= 'mnt.{id}'.format(id=self
.client_id
)
29 self
._mountpoint
= None
32 self
.test_files
= ['a', 'b', 'c']
34 self
.background_procs
= []
38 if self
._mountpoint
== None:
39 self
._mountpoint
= os
.path
.join(
40 self
.test_dir
, '{dir_name}'.format(dir_name
=self
.mountpoint_dir_name
))
41 return self
._mountpoint
44 def mountpoint(self
, path
):
45 if not isinstance(path
, str):
46 raise RuntimeError('path should be of str type.')
47 self
._mountpoint
= path
50 raise NotImplementedError()
52 def setupfs(self
, name
=None):
53 if name
is None and self
.fs
is not None:
54 # Previous mount existed, reuse the old name
56 self
.fs
= Filesystem(self
.ctx
, name
=name
)
57 log
.info('Wait for MDS to reach steady state...')
58 self
.fs
.wait_for_daemons()
59 log
.info('Ready to start {}...'.format(type(self
).__name
__))
61 def mount(self
, mount_path
=None, mount_fs_name
=None, mountpoint
=None, mount_options
=[]):
62 raise NotImplementedError()
65 raise NotImplementedError()
67 def umount_wait(self
, force
=False, require_clean
=False):
70 :param force: Expect that the mount will not shutdown cleanly: kill
72 :param require_clean: Wait for the Ceph client associated with the
73 mount (e.g. ceph-fuse) to terminate, and
74 raise if it doesn't do so cleanly.
77 raise NotImplementedError()
79 def kill_cleanup(self
):
80 raise NotImplementedError()
83 raise NotImplementedError()
86 raise NotImplementedError()
88 def wait_until_mounted(self
):
89 raise NotImplementedError()
91 def get_keyring_path(self
):
92 return '/etc/ceph/ceph.client.{id}.keyring'.format(id=self
.client_id
)
95 def config_path(self
):
97 Path to ceph.conf: override this if you're not a normal systemwide ceph install
100 return "/etc/ceph/ceph.conf"
105 A context manager, from an initially unmounted state, to mount
106 this, yield, and then unmount and clean up.
109 self
.wait_until_mounted()
115 def is_blacklisted(self
):
116 addr
= self
.get_global_addr()
117 blacklist
= json
.loads(self
.fs
.mon_manager
.raw_cluster_cmd("osd", "blacklist", "ls", "--format=json"))
119 if addr
== b
["addr"]:
123 def create_file(self
, filename
='testfile', dirname
=None, user
=None,
125 assert(self
.is_mounted())
127 if not os
.path
.isabs(filename
):
129 if os
.path
.isabs(dirname
):
130 path
= os
.path
.join(dirname
, filename
)
132 path
= os
.path
.join(self
.mountpoint
, dirname
, filename
)
134 path
= os
.path
.join(self
.mountpoint
, filename
)
139 args
= ['sudo', '-u', user
, '-s', '/bin/bash', '-c', 'touch ' + path
]
141 args
= 'touch ' + path
143 return self
.client_remote
.run(args
=args
, check_status
=check_status
)
145 def create_files(self
):
146 assert(self
.is_mounted())
148 for suffix
in self
.test_files
:
149 log
.info("Creating file {0}".format(suffix
))
150 self
.client_remote
.run(args
=[
151 'sudo', 'touch', os
.path
.join(self
.mountpoint
, suffix
)
154 def test_create_file(self
, filename
='testfile', dirname
=None, user
=None,
156 return self
.create_file(filename
=filename
, dirname
=dirname
, user
=user
,
159 def check_files(self
):
160 assert(self
.is_mounted())
162 for suffix
in self
.test_files
:
163 log
.info("Checking file {0}".format(suffix
))
164 r
= self
.client_remote
.run(args
=[
165 'sudo', 'ls', os
.path
.join(self
.mountpoint
, suffix
)
166 ], check_status
=False)
167 if r
.exitstatus
!= 0:
168 raise RuntimeError("Expected file {0} not found".format(suffix
))
170 def create_destroy(self
):
171 assert(self
.is_mounted())
173 filename
= "{0} {1}".format(datetime
.datetime
.now(), self
.client_id
)
174 log
.debug("Creating test file {0}".format(filename
))
175 self
.client_remote
.run(args
=[
176 'sudo', 'touch', os
.path
.join(self
.mountpoint
, filename
)
178 log
.debug("Deleting test file {0}".format(filename
))
179 self
.client_remote
.run(args
=[
180 'sudo', 'rm', '-f', os
.path
.join(self
.mountpoint
, filename
)
183 def _run_python(self
, pyscript
, py_version
='python3'):
184 return self
.client_remote
.run(
185 args
=['sudo', 'adjust-ulimits', 'daemon-helper', 'kill',
186 py_version
, '-c', pyscript
], wait
=False, stdin
=run
.PIPE
,
189 def run_python(self
, pyscript
, py_version
='python3'):
190 p
= self
._run
_python
(pyscript
, py_version
)
192 return p
.stdout
.getvalue().strip()
194 def run_shell(self
, args
, wait
=True, stdin
=None, check_status
=True,
196 if isinstance(args
, str):
199 args
= ["cd", self
.mountpoint
, run
.Raw('&&'), "sudo"] + args
200 return self
.client_remote
.run(args
=args
, stdout
=StringIO(),
201 stderr
=StringIO(), wait
=wait
,
202 stdin
=stdin
, check_status
=check_status
,
205 def open_no_data(self
, basename
):
207 A pure metadata operation
209 assert(self
.is_mounted())
211 path
= os
.path
.join(self
.mountpoint
, basename
)
213 p
= self
._run
_python
(dedent(
215 f = open("{path}", 'w')
216 """.format(path
=path
)
220 def open_background(self
, basename
="background_file", write
=True):
222 Open a file for writing, then block such that the client
223 will hold a capability.
225 Don't return until the remote process has got as far as opening
226 the file, then return the RemoteProcess instance.
228 assert(self
.is_mounted())
230 path
= os
.path
.join(self
.mountpoint
, basename
)
233 pyscript
= dedent("""
236 with open("{path}", 'w') as f:
242 """).format(path
=path
)
244 pyscript
= dedent("""
247 with open("{path}", 'r') as f:
250 """).format(path
=path
)
252 rproc
= self
._run
_python
(pyscript
)
253 self
.background_procs
.append(rproc
)
255 # This wait would not be sufficient if the file had already
256 # existed, but it's simple and in practice users of open_background
257 # are not using it on existing files.
258 self
.wait_for_visible(basename
)
262 def wait_for_dir_empty(self
, dirname
, timeout
=30):
264 dirpath
= os
.path
.join(self
.mountpoint
, dirname
)
266 nr_entries
= int(self
.getfattr(dirpath
, "ceph.dir.entries"))
268 log
.debug("Directory {0} seen empty from {1} after {2}s ".format(
269 dirname
, self
.client_id
, i
))
275 raise RuntimeError("Timed out after {0}s waiting for {1} to become empty from {2}".format(
276 i
, dirname
, self
.client_id
))
278 def wait_for_visible(self
, basename
="background_file", timeout
=30):
281 r
= self
.client_remote
.run(args
=[
282 'sudo', 'ls', os
.path
.join(self
.mountpoint
, basename
)
283 ], check_status
=False)
284 if r
.exitstatus
== 0:
285 log
.debug("File {0} became visible from {1} after {2}s".format(
286 basename
, self
.client_id
, i
))
292 raise RuntimeError("Timed out after {0}s waiting for {1} to become visible from {2}".format(
293 i
, basename
, self
.client_id
))
295 def lock_background(self
, basename
="background_file", do_flock
=True):
297 Open and lock a files for writing, hold the lock in a background process
299 assert(self
.is_mounted())
301 path
= os
.path
.join(self
.mountpoint
, basename
)
308 script_builder
+= """
309 f1 = open("{path}-1", 'w')
310 fcntl.flock(f1, fcntl.LOCK_EX | fcntl.LOCK_NB)"""
311 script_builder
+= """
312 f2 = open("{path}-2", 'w')
313 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
314 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
319 pyscript
= dedent(script_builder
).format(path
=path
)
321 log
.info("lock_background file {0}".format(basename
))
322 rproc
= self
._run
_python
(pyscript
)
323 self
.background_procs
.append(rproc
)
326 def lock_and_release(self
, basename
="background_file"):
327 assert(self
.is_mounted())
329 path
= os
.path
.join(self
.mountpoint
, basename
)
335 f1 = open("{path}-1", 'w')
336 fcntl.flock(f1, fcntl.LOCK_EX)
337 f2 = open("{path}-2", 'w')
338 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
339 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
341 pyscript
= dedent(script
).format(path
=path
)
343 log
.info("lock_and_release file {0}".format(basename
))
344 return self
._run
_python
(pyscript
)
346 def check_filelock(self
, basename
="background_file", do_flock
=True):
347 assert(self
.is_mounted())
349 path
= os
.path
.join(self
.mountpoint
, basename
)
356 script_builder
+= """
357 f1 = open("{path}-1", 'r')
359 fcntl.flock(f1, fcntl.LOCK_EX | fcntl.LOCK_NB)
361 if e.errno == errno.EAGAIN:
364 raise RuntimeError("flock on file {path}-1 not found")"""
365 script_builder
+= """
366 f2 = open("{path}-2", 'r')
368 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
369 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
371 if e.errno == errno.EAGAIN:
374 raise RuntimeError("posix lock on file {path}-2 not found")
376 pyscript
= dedent(script_builder
).format(path
=path
)
378 log
.info("check lock on file {0}".format(basename
))
379 self
.client_remote
.run(args
=[
380 'sudo', 'python3', '-c', pyscript
383 def write_background(self
, basename
="background_file", loop
=False):
385 Open a file for writing, complete as soon as you can
389 assert(self
.is_mounted())
391 path
= os
.path
.join(self
.mountpoint
, basename
)
393 pyscript
= dedent("""
397 fd = os.open("{path}", os.O_RDWR | os.O_CREAT, 0o644)
400 os.write(fd, b'content')
407 """).format(path
=path
, loop
=str(loop
))
409 rproc
= self
._run
_python
(pyscript
)
410 self
.background_procs
.append(rproc
)
413 def write_n_mb(self
, filename
, n_mb
, seek
=0, wait
=True):
415 Write the requested number of megabytes to a file
417 assert(self
.is_mounted())
419 return self
.run_shell(["dd", "if=/dev/urandom", "of={0}".format(filename
),
420 "bs=1M", "conv=fdatasync",
421 "count={0}".format(n_mb
),
422 "seek={0}".format(seek
)
425 def write_test_pattern(self
, filename
, size
):
426 log
.info("Writing {0} bytes to {1}".format(size
, filename
))
427 return self
.run_python(dedent("""
430 with open(path, 'w') as f:
431 for i in range(0, {size}):
432 val = zlib.crc32(str(i).encode('utf-8')) & 7
435 path
=os
.path
.join(self
.mountpoint
, filename
),
439 def validate_test_pattern(self
, filename
, size
):
440 log
.info("Validating {0} bytes from {1}".format(size
, filename
))
441 return self
.run_python(dedent("""
444 with open(path, 'r') as f:
446 if len(bytes) != {size}:
447 raise RuntimeError("Bad length {{0}} vs. expected {{1}}".format(
450 for i, b in enumerate(bytes):
451 val = zlib.crc32(str(i).encode('utf-8')) & 7
453 raise RuntimeError("Bad data at offset {{0}}".format(i))
455 path
=os
.path
.join(self
.mountpoint
, filename
),
459 def open_n_background(self
, fs_path
, count
):
461 Open N files for writing, hold them open in a background process
463 :param fs_path: Path relative to CephFS root, e.g. "foo/bar"
464 :return: a RemoteProcess
466 assert(self
.is_mounted())
468 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
470 pyscript
= dedent("""
476 abs_path = "{abs_path}"
478 if not os.path.exists(os.path.dirname(abs_path)):
479 os.makedirs(os.path.dirname(abs_path))
482 for i in range(0, n):
483 fname = "{{0}}_{{1}}".format(abs_path, i)
484 handles.append(open(fname, 'w'))
488 """).format(abs_path
=abs_path
, count
=count
)
490 rproc
= self
._run
_python
(pyscript
)
491 self
.background_procs
.append(rproc
)
494 def create_n_files(self
, fs_path
, count
, sync
=False):
495 assert(self
.is_mounted())
497 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
499 pyscript
= dedent("""
505 abs_path = "{abs_path}"
507 if not os.path.exists(os.path.dirname(abs_path)):
508 os.makedirs(os.path.dirname(abs_path))
510 for i in range(0, n):
511 fname = "{{0}}_{{1}}".format(abs_path, i)
512 with open(fname, 'w') as f:
517 """).format(abs_path
=abs_path
, count
=count
, sync
=str(sync
))
519 self
.run_python(pyscript
)
522 for p
in self
.background_procs
:
523 log
.info("Terminating background process")
524 self
._kill
_background
(p
)
526 self
.background_procs
= []
528 def _kill_background(self
, p
):
533 except (CommandFailedError
, ConnectionLostError
):
536 def kill_background(self
, p
):
538 For a process that was returned by one of the _background member functions,
541 self
._kill
_background
(p
)
542 self
.background_procs
.remove(p
)
544 def send_signal(self
, signal
):
545 signal
= signal
.lower()
546 if signal
.lower() not in ['sigstop', 'sigcont', 'sigterm', 'sigkill']:
547 raise NotImplementedError
549 self
.client_remote
.run(args
=['sudo', 'kill', '-{0}'.format(signal
),
550 self
.client_pid
], omit_sudo
=False)
552 def get_global_id(self
):
553 raise NotImplementedError()
555 def get_global_inst(self
):
556 raise NotImplementedError()
558 def get_global_addr(self
):
559 raise NotImplementedError()
561 def get_osd_epoch(self
):
562 raise NotImplementedError()
564 def lstat(self
, fs_path
, follow_symlinks
=False, wait
=True):
565 return self
.stat(fs_path
, follow_symlinks
=False, wait
=True)
567 def stat(self
, fs_path
, follow_symlinks
=True, wait
=True):
569 stat a file, and return the result as a dictionary like this:
571 "st_ctime": 1414161137.0,
572 "st_mtime": 1414161137.0,
580 "st_atime": 1431520593.0
583 Raises exception on absent file.
585 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
587 stat_call
= "os.stat('" + abs_path
+ "')"
589 stat_call
= "os.lstat('" + abs_path
+ "')"
591 pyscript
= dedent("""
602 attrs = ["st_mode", "st_ino", "st_dev", "st_nlink", "st_uid", "st_gid", "st_size", "st_atime", "st_mtime", "st_ctime"]
604 dict([(a, getattr(s, a)) for a in attrs]),
606 """).format(stat_call
=stat_call
)
607 proc
= self
._run
_python
(pyscript
)
610 return json
.loads(proc
.stdout
.getvalue().strip())
614 def touch(self
, fs_path
):
616 Create a dentry if it doesn't already exist. This python
617 implementation exists because the usual command line tool doesn't
618 pass through error codes like EIO.
623 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
624 pyscript
= dedent("""
629 f = open("{path}", "w")
633 """).format(path
=abs_path
)
634 proc
= self
._run
_python
(pyscript
)
637 def path_to_ino(self
, fs_path
, follow_symlinks
=True):
638 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
641 pyscript
= dedent("""
645 print(os.stat("{path}").st_ino)
646 """).format(path
=abs_path
)
648 pyscript
= dedent("""
652 print(os.lstat("{path}").st_ino)
653 """).format(path
=abs_path
)
655 proc
= self
._run
_python
(pyscript
)
657 return int(proc
.stdout
.getvalue().strip())
659 def path_to_nlink(self
, fs_path
):
660 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
662 pyscript
= dedent("""
666 print(os.stat("{path}").st_nlink)
667 """).format(path
=abs_path
)
669 proc
= self
._run
_python
(pyscript
)
671 return int(proc
.stdout
.getvalue().strip())
673 def ls(self
, path
=None):
675 Wrap ls: return a list of strings
681 ls_text
= self
.run_shell(cmd
).stdout
.getvalue().strip()
684 return ls_text
.split("\n")
686 # Special case because otherwise split on empty string
687 # gives you [''] instead of []
690 def setfattr(self
, path
, key
, val
):
694 :param path: relative to mount point
695 :param key: xattr name
696 :param val: xattr value
699 self
.run_shell(["setfattr", "-n", key
, "-v", val
, path
])
701 def getfattr(self
, path
, attr
):
703 Wrap getfattr: return the values of a named xattr on one file, or
704 None if the attribute is not found.
708 p
= self
.run_shell(["getfattr", "--only-values", "-n", attr
, path
], wait
=False)
711 except CommandFailedError
as e
:
712 if e
.exitstatus
== 1 and "No such attribute" in p
.stderr
.getvalue():
717 return p
.stdout
.getvalue()
721 Wrap df: return a dict of usage fields in bytes
724 p
= self
.run_shell(["df", "-B1", "."])
725 lines
= p
.stdout
.getvalue().strip().split("\n")
726 fs
, total
, used
, avail
= lines
[1].split()[:4]
732 "available": int(avail
)