]>
git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/cephfs/mount.py
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
12 log
= logging
.getLogger(__name__
)
15 class CephFSMount(object):
16 def __init__(self
, test_dir
, client_id
, client_remote
):
18 :param test_dir: Global teuthology test dir
19 :param client_id: Client ID, the 'foo' in client.foo
20 :param client_remote: Remote instance for the host where client will run
23 self
.test_dir
= test_dir
24 self
.client_id
= client_id
25 self
.client_remote
= client_remote
26 self
.mountpoint_dir_name
= 'mnt.{id}'.format(id=self
.client_id
)
28 self
.test_files
= ['a', 'b', 'c']
30 self
.background_procs
= []
35 self
.test_dir
, '{dir_name}'.format(dir_name
=self
.mountpoint_dir_name
))
38 raise NotImplementedError()
40 def mount(self
, mount_path
=None, mount_fs_name
=None):
41 raise NotImplementedError()
44 raise NotImplementedError()
46 def umount_wait(self
, force
=False, require_clean
=False):
49 :param force: Expect that the mount will not shutdown cleanly: kill
51 :param require_clean: Wait for the Ceph client associated with the
52 mount (e.g. ceph-fuse) to terminate, and
53 raise if it doesn't do so cleanly.
56 raise NotImplementedError()
58 def kill_cleanup(self
):
59 raise NotImplementedError()
62 raise NotImplementedError()
65 raise NotImplementedError()
67 def wait_until_mounted(self
):
68 raise NotImplementedError()
70 def get_keyring_path(self
):
71 return '/etc/ceph/ceph.client.{id}.keyring'.format(id=self
.client_id
)
74 def config_path(self
):
76 Path to ceph.conf: override this if you're not a normal systemwide ceph install
79 return "/etc/ceph/ceph.conf"
84 A context manager, from an initially unmounted state, to mount
85 this, yield, and then unmount and clean up.
88 self
.wait_until_mounted()
94 def create_files(self
):
95 assert(self
.is_mounted())
97 for suffix
in self
.test_files
:
98 log
.info("Creating file {0}".format(suffix
))
99 self
.client_remote
.run(args
=[
100 'sudo', 'touch', os
.path
.join(self
.mountpoint
, suffix
)
103 def check_files(self
):
104 assert(self
.is_mounted())
106 for suffix
in self
.test_files
:
107 log
.info("Checking file {0}".format(suffix
))
108 r
= self
.client_remote
.run(args
=[
109 'sudo', 'ls', os
.path
.join(self
.mountpoint
, suffix
)
110 ], check_status
=False)
111 if r
.exitstatus
!= 0:
112 raise RuntimeError("Expected file {0} not found".format(suffix
))
114 def create_destroy(self
):
115 assert(self
.is_mounted())
117 filename
= "{0} {1}".format(datetime
.datetime
.now(), self
.client_id
)
118 log
.debug("Creating test file {0}".format(filename
))
119 self
.client_remote
.run(args
=[
120 'sudo', 'touch', os
.path
.join(self
.mountpoint
, filename
)
122 log
.debug("Deleting test file {0}".format(filename
))
123 self
.client_remote
.run(args
=[
124 'sudo', 'rm', '-f', os
.path
.join(self
.mountpoint
, filename
)
127 def _run_python(self
, pyscript
):
128 return self
.client_remote
.run(args
=[
129 'sudo', 'adjust-ulimits', 'daemon-helper', 'kill', 'python', '-c', pyscript
130 ], wait
=False, stdin
=run
.PIPE
, stdout
=StringIO())
132 def run_python(self
, pyscript
):
133 p
= self
._run
_python
(pyscript
)
135 return p
.stdout
.getvalue().strip()
137 def run_shell(self
, args
, wait
=True):
138 args
= ["cd", self
.mountpoint
, run
.Raw('&&'), "sudo"] + args
139 return self
.client_remote
.run(args
=args
, stdout
=StringIO(),
140 stderr
=StringIO(), wait
=wait
)
142 def open_no_data(self
, basename
):
144 A pure metadata operation
146 assert(self
.is_mounted())
148 path
= os
.path
.join(self
.mountpoint
, basename
)
150 p
= self
._run
_python
(dedent(
152 f = open("{path}", 'w')
153 """.format(path
=path
)
157 def open_background(self
, basename
="background_file"):
159 Open a file for writing, then block such that the client
160 will hold a capability.
162 Don't return until the remote process has got as far as opening
163 the file, then return the RemoteProcess instance.
165 assert(self
.is_mounted())
167 path
= os
.path
.join(self
.mountpoint
, basename
)
169 pyscript
= dedent("""
172 f = open("{path}", 'w')
178 """).format(path
=path
)
180 rproc
= self
._run
_python
(pyscript
)
181 self
.background_procs
.append(rproc
)
183 # This wait would not be sufficient if the file had already
184 # existed, but it's simple and in practice users of open_background
185 # are not using it on existing files.
186 self
.wait_for_visible(basename
)
190 def wait_for_visible(self
, basename
="background_file", timeout
=30):
193 r
= self
.client_remote
.run(args
=[
194 'sudo', 'ls', os
.path
.join(self
.mountpoint
, basename
)
195 ], check_status
=False)
196 if r
.exitstatus
== 0:
197 log
.debug("File {0} became visible from {1} after {2}s".format(
198 basename
, self
.client_id
, i
))
204 raise RuntimeError("Timed out after {0}s waiting for {1} to become visible from {2}".format(
205 i
, basename
, self
.client_id
))
207 def lock_background(self
, basename
="background_file", do_flock
=True):
209 Open and lock a files for writing, hold the lock in a background process
211 assert(self
.is_mounted())
213 path
= os
.path
.join(self
.mountpoint
, basename
)
220 script_builder
+= """
221 f1 = open("{path}-1", 'w')
222 fcntl.flock(f1, fcntl.LOCK_EX | fcntl.LOCK_NB)"""
223 script_builder
+= """
224 f2 = open("{path}-2", 'w')
225 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
226 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
231 pyscript
= dedent(script_builder
).format(path
=path
)
233 log
.info("lock file {0}".format(basename
))
234 rproc
= self
._run
_python
(pyscript
)
235 self
.background_procs
.append(rproc
)
238 def check_filelock(self
, basename
="background_file", do_flock
=True):
239 assert(self
.is_mounted())
241 path
= os
.path
.join(self
.mountpoint
, basename
)
248 script_builder
+= """
249 f1 = open("{path}-1", 'r')
251 fcntl.flock(f1, fcntl.LOCK_EX | fcntl.LOCK_NB)
253 if e.errno == errno.EAGAIN:
256 raise RuntimeError("flock on file {path}-1 not found")"""
257 script_builder
+= """
258 f2 = open("{path}-2", 'r')
260 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
261 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
263 if e.errno == errno.EAGAIN:
266 raise RuntimeError("posix lock on file {path}-2 not found")
268 pyscript
= dedent(script_builder
).format(path
=path
)
270 log
.info("check lock on file {0}".format(basename
))
271 self
.client_remote
.run(args
=[
272 'sudo', 'python', '-c', pyscript
275 def write_background(self
, basename
="background_file", loop
=False):
277 Open a file for writing, complete as soon as you can
281 assert(self
.is_mounted())
283 path
= os
.path
.join(self
.mountpoint
, basename
)
285 pyscript
= dedent("""
289 fd = os.open("{path}", os.O_RDWR | os.O_CREAT, 0644)
292 os.write(fd, 'content')
299 """).format(path
=path
, loop
=str(loop
))
301 rproc
= self
._run
_python
(pyscript
)
302 self
.background_procs
.append(rproc
)
305 def write_n_mb(self
, filename
, n_mb
, seek
=0, wait
=True):
307 Write the requested number of megabytes to a file
309 assert(self
.is_mounted())
311 return self
.run_shell(["dd", "if=/dev/urandom", "of={0}".format(filename
),
312 "bs=1M", "conv=fdatasync",
313 "count={0}".format(n_mb
),
314 "seek={0}".format(seek
)
317 def write_test_pattern(self
, filename
, size
):
318 log
.info("Writing {0} bytes to {1}".format(size
, filename
))
319 return self
.run_python(dedent("""
323 for i in range(0, {size}):
324 val = zlib.crc32("%s" % i) & 7
328 path
=os
.path
.join(self
.mountpoint
, filename
),
332 def validate_test_pattern(self
, filename
, size
):
333 log
.info("Validating {0} bytes from {1}".format(size
, filename
))
334 return self
.run_python(dedent("""
340 if len(bytes) != {size}:
341 raise RuntimeError("Bad length {{0}} vs. expected {{1}}".format(
344 for i, b in enumerate(bytes):
345 val = zlib.crc32("%s" % i) & 7
347 raise RuntimeError("Bad data at offset {{0}}".format(i))
349 path
=os
.path
.join(self
.mountpoint
, filename
),
353 def open_n_background(self
, fs_path
, count
):
355 Open N files for writing, hold them open in a background process
357 :param fs_path: Path relative to CephFS root, e.g. "foo/bar"
358 :return: a RemoteProcess
360 assert(self
.is_mounted())
362 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
364 pyscript
= dedent("""
370 abs_path = "{abs_path}"
372 if not os.path.exists(os.path.dirname(abs_path)):
373 os.makedirs(os.path.dirname(abs_path))
376 for i in range(0, n):
377 fname = "{{0}}_{{1}}".format(abs_path, i)
378 handles.append(open(fname, 'w'))
382 """).format(abs_path
=abs_path
, count
=count
)
384 rproc
= self
._run
_python
(pyscript
)
385 self
.background_procs
.append(rproc
)
388 def create_n_files(self
, fs_path
, count
, sync
=False):
389 assert(self
.is_mounted())
391 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
393 pyscript
= dedent("""
399 abs_path = "{abs_path}"
401 if not os.path.exists(os.path.dirname(abs_path)):
402 os.makedirs(os.path.dirname(abs_path))
404 for i in range(0, n):
405 fname = "{{0}}_{{1}}".format(abs_path, i)
412 """).format(abs_path
=abs_path
, count
=count
, sync
=str(sync
))
414 self
.run_python(pyscript
)
417 for p
in self
.background_procs
:
418 log
.info("Terminating background process")
419 self
._kill
_background
(p
)
421 self
.background_procs
= []
423 def _kill_background(self
, p
):
428 except (CommandFailedError
, ConnectionLostError
):
431 def kill_background(self
, p
):
433 For a process that was returned by one of the _background member functions,
436 self
._kill
_background
(p
)
437 self
.background_procs
.remove(p
)
439 def spam_dir_background(self
, path
):
441 Create directory `path` and do lots of metadata operations
442 in it until further notice.
444 assert(self
.is_mounted())
445 abs_path
= os
.path
.join(self
.mountpoint
, path
)
447 pyscript
= dedent("""
452 abs_path = "{abs_path}"
454 if not os.path.exists(abs_path):
455 os.makedirs(abs_path)
459 file_path = os.path.join(abs_path, "tmp%d" % n)
460 f = open(file_path, 'w')
463 """).format(abs_path
=abs_path
)
465 rproc
= self
._run
_python
(pyscript
)
466 self
.background_procs
.append(rproc
)
469 def get_global_id(self
):
470 raise NotImplementedError()
472 def get_osd_epoch(self
):
473 raise NotImplementedError()
475 def stat(self
, fs_path
, wait
=True):
477 stat a file, and return the result as a dictionary like this:
479 "st_ctime": 1414161137.0,
480 "st_mtime": 1414161137.0,
488 "st_atime": 1431520593.0
491 Raises exception on absent file.
493 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
495 pyscript
= dedent("""
502 s = os.stat("{path}")
506 attrs = ["st_mode", "st_ino", "st_dev", "st_nlink", "st_uid", "st_gid", "st_size", "st_atime", "st_mtime", "st_ctime"]
508 dict([(a, getattr(s, a)) for a in attrs]),
510 """).format(path
=abs_path
)
511 proc
= self
._run
_python
(pyscript
)
514 return json
.loads(proc
.stdout
.getvalue().strip())
518 def touch(self
, fs_path
):
520 Create a dentry if it doesn't already exist. This python
521 implementation exists because the usual command line tool doesn't
522 pass through error codes like EIO.
527 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
528 pyscript
= dedent("""
533 f = open("{path}", "w")
537 """).format(path
=abs_path
)
538 proc
= self
._run
_python
(pyscript
)
541 def path_to_ino(self
, fs_path
, follow_symlinks
=True):
542 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
545 pyscript
= dedent("""
549 print os.stat("{path}").st_ino
550 """).format(path
=abs_path
)
552 pyscript
= dedent("""
556 print os.lstat("{path}").st_ino
557 """).format(path
=abs_path
)
559 proc
= self
._run
_python
(pyscript
)
561 return int(proc
.stdout
.getvalue().strip())
563 def path_to_nlink(self
, fs_path
):
564 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
566 pyscript
= dedent("""
570 print os.stat("{path}").st_nlink
571 """).format(path
=abs_path
)
573 proc
= self
._run
_python
(pyscript
)
575 return int(proc
.stdout
.getvalue().strip())
577 def ls(self
, path
=None):
579 Wrap ls: return a list of strings
585 ls_text
= self
.run_shell(cmd
).stdout
.getvalue().strip()
588 return ls_text
.split("\n")
590 # Special case because otherwise split on empty string
591 # gives you [''] instead of []
594 def setfattr(self
, path
, key
, val
):
598 :param path: relative to mount point
599 :param key: xattr name
600 :param val: xattr value
603 self
.run_shell(["setfattr", "-n", key
, "-v", val
, path
])
605 def getfattr(self
, path
, attr
):
607 Wrap getfattr: return the values of a named xattr on one file, or
608 None if the attribute is not found.
612 p
= self
.run_shell(["getfattr", "--only-values", "-n", attr
, path
], wait
=False)
615 except CommandFailedError
as e
:
616 if e
.exitstatus
== 1 and "No such attribute" in p
.stderr
.getvalue():
621 return p
.stdout
.getvalue()
625 Wrap df: return a dict of usage fields in bytes
628 p
= self
.run_shell(["df", "-B1", "."])
629 lines
= p
.stdout
.getvalue().strip().split("\n")
630 fs
, total
, used
, avail
= lines
[1].split()[:4]
636 "available": int(avail
)