]>
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
, py_version
='python'):
128 return self
.client_remote
.run(
129 args
=['sudo', 'adjust-ulimits', 'daemon-helper', 'kill',
130 py_version
, '-c', pyscript
], wait
=False, stdin
=run
.PIPE
,
133 def run_python(self
, pyscript
, py_version
='python'):
134 p
= self
._run
_python
(pyscript
, py_version
)
136 return p
.stdout
.getvalue().strip()
138 def run_shell(self
, args
, wait
=True):
139 args
= ["cd", self
.mountpoint
, run
.Raw('&&'), "sudo"] + args
140 return self
.client_remote
.run(args
=args
, stdout
=StringIO(),
141 stderr
=StringIO(), wait
=wait
)
143 def open_no_data(self
, basename
):
145 A pure metadata operation
147 assert(self
.is_mounted())
149 path
= os
.path
.join(self
.mountpoint
, basename
)
151 p
= self
._run
_python
(dedent(
153 f = open("{path}", 'w')
154 """.format(path
=path
)
158 def open_background(self
, basename
="background_file"):
160 Open a file for writing, then block such that the client
161 will hold a capability.
163 Don't return until the remote process has got as far as opening
164 the file, then return the RemoteProcess instance.
166 assert(self
.is_mounted())
168 path
= os
.path
.join(self
.mountpoint
, basename
)
170 pyscript
= dedent("""
173 f = open("{path}", 'w')
179 """).format(path
=path
)
181 rproc
= self
._run
_python
(pyscript
)
182 self
.background_procs
.append(rproc
)
184 # This wait would not be sufficient if the file had already
185 # existed, but it's simple and in practice users of open_background
186 # are not using it on existing files.
187 self
.wait_for_visible(basename
)
191 def wait_for_visible(self
, basename
="background_file", timeout
=30):
194 r
= self
.client_remote
.run(args
=[
195 'sudo', 'ls', os
.path
.join(self
.mountpoint
, basename
)
196 ], check_status
=False)
197 if r
.exitstatus
== 0:
198 log
.debug("File {0} became visible from {1} after {2}s".format(
199 basename
, self
.client_id
, i
))
205 raise RuntimeError("Timed out after {0}s waiting for {1} to become visible from {2}".format(
206 i
, basename
, self
.client_id
))
208 def lock_background(self
, basename
="background_file", do_flock
=True):
210 Open and lock a files for writing, hold the lock in a background process
212 assert(self
.is_mounted())
214 path
= os
.path
.join(self
.mountpoint
, basename
)
221 script_builder
+= """
222 f1 = open("{path}-1", 'w')
223 fcntl.flock(f1, fcntl.LOCK_EX | fcntl.LOCK_NB)"""
224 script_builder
+= """
225 f2 = open("{path}-2", 'w')
226 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
227 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
232 pyscript
= dedent(script_builder
).format(path
=path
)
234 log
.info("lock_background file {0}".format(basename
))
235 rproc
= self
._run
_python
(pyscript
)
236 self
.background_procs
.append(rproc
)
239 def lock_and_release(self
, basename
="background_file"):
240 assert(self
.is_mounted())
242 path
= os
.path
.join(self
.mountpoint
, basename
)
248 f1 = open("{path}-1", 'w')
249 fcntl.flock(f1, fcntl.LOCK_EX)
250 f2 = open("{path}-2", 'w')
251 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
252 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
254 pyscript
= dedent(script
).format(path
=path
)
256 log
.info("lock_and_release file {0}".format(basename
))
257 return self
._run
_python
(pyscript
)
259 def check_filelock(self
, basename
="background_file", do_flock
=True):
260 assert(self
.is_mounted())
262 path
= os
.path
.join(self
.mountpoint
, basename
)
269 script_builder
+= """
270 f1 = open("{path}-1", 'r')
272 fcntl.flock(f1, fcntl.LOCK_EX | fcntl.LOCK_NB)
274 if e.errno == errno.EAGAIN:
277 raise RuntimeError("flock on file {path}-1 not found")"""
278 script_builder
+= """
279 f2 = open("{path}-2", 'r')
281 lockdata = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
282 fcntl.fcntl(f2, fcntl.F_SETLK, lockdata)
284 if e.errno == errno.EAGAIN:
287 raise RuntimeError("posix lock on file {path}-2 not found")
289 pyscript
= dedent(script_builder
).format(path
=path
)
291 log
.info("check lock on file {0}".format(basename
))
292 self
.client_remote
.run(args
=[
293 'sudo', 'python', '-c', pyscript
296 def write_background(self
, basename
="background_file", loop
=False):
298 Open a file for writing, complete as soon as you can
302 assert(self
.is_mounted())
304 path
= os
.path
.join(self
.mountpoint
, basename
)
306 pyscript
= dedent("""
310 fd = os.open("{path}", os.O_RDWR | os.O_CREAT, 0644)
313 os.write(fd, 'content')
320 """).format(path
=path
, loop
=str(loop
))
322 rproc
= self
._run
_python
(pyscript
)
323 self
.background_procs
.append(rproc
)
326 def write_n_mb(self
, filename
, n_mb
, seek
=0, wait
=True):
328 Write the requested number of megabytes to a file
330 assert(self
.is_mounted())
332 return self
.run_shell(["dd", "if=/dev/urandom", "of={0}".format(filename
),
333 "bs=1M", "conv=fdatasync",
334 "count={0}".format(n_mb
),
335 "seek={0}".format(seek
)
338 def write_test_pattern(self
, filename
, size
):
339 log
.info("Writing {0} bytes to {1}".format(size
, filename
))
340 return self
.run_python(dedent("""
344 for i in range(0, {size}):
345 val = zlib.crc32("%s" % i) & 7
349 path
=os
.path
.join(self
.mountpoint
, filename
),
353 def validate_test_pattern(self
, filename
, size
):
354 log
.info("Validating {0} bytes from {1}".format(size
, filename
))
355 return self
.run_python(dedent("""
361 if len(bytes) != {size}:
362 raise RuntimeError("Bad length {{0}} vs. expected {{1}}".format(
365 for i, b in enumerate(bytes):
366 val = zlib.crc32("%s" % i) & 7
368 raise RuntimeError("Bad data at offset {{0}}".format(i))
370 path
=os
.path
.join(self
.mountpoint
, filename
),
374 def open_n_background(self
, fs_path
, count
):
376 Open N files for writing, hold them open in a background process
378 :param fs_path: Path relative to CephFS root, e.g. "foo/bar"
379 :return: a RemoteProcess
381 assert(self
.is_mounted())
383 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
385 pyscript
= dedent("""
391 abs_path = "{abs_path}"
393 if not os.path.exists(os.path.dirname(abs_path)):
394 os.makedirs(os.path.dirname(abs_path))
397 for i in range(0, n):
398 fname = "{{0}}_{{1}}".format(abs_path, i)
399 handles.append(open(fname, 'w'))
403 """).format(abs_path
=abs_path
, count
=count
)
405 rproc
= self
._run
_python
(pyscript
)
406 self
.background_procs
.append(rproc
)
409 def create_n_files(self
, fs_path
, count
, sync
=False):
410 assert(self
.is_mounted())
412 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
414 pyscript
= dedent("""
420 abs_path = "{abs_path}"
422 if not os.path.exists(os.path.dirname(abs_path)):
423 os.makedirs(os.path.dirname(abs_path))
425 for i in range(0, n):
426 fname = "{{0}}_{{1}}".format(abs_path, i)
433 """).format(abs_path
=abs_path
, count
=count
, sync
=str(sync
))
435 self
.run_python(pyscript
)
438 for p
in self
.background_procs
:
439 log
.info("Terminating background process")
440 self
._kill
_background
(p
)
442 self
.background_procs
= []
444 def _kill_background(self
, p
):
449 except (CommandFailedError
, ConnectionLostError
):
452 def kill_background(self
, p
):
454 For a process that was returned by one of the _background member functions,
457 self
._kill
_background
(p
)
458 self
.background_procs
.remove(p
)
460 def get_global_id(self
):
461 raise NotImplementedError()
463 def get_osd_epoch(self
):
464 raise NotImplementedError()
466 def stat(self
, fs_path
, wait
=True):
468 stat a file, and return the result as a dictionary like this:
470 "st_ctime": 1414161137.0,
471 "st_mtime": 1414161137.0,
479 "st_atime": 1431520593.0
482 Raises exception on absent file.
484 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
486 pyscript
= dedent("""
493 s = os.stat("{path}")
497 attrs = ["st_mode", "st_ino", "st_dev", "st_nlink", "st_uid", "st_gid", "st_size", "st_atime", "st_mtime", "st_ctime"]
499 dict([(a, getattr(s, a)) for a in attrs]),
501 """).format(path
=abs_path
)
502 proc
= self
._run
_python
(pyscript
)
505 return json
.loads(proc
.stdout
.getvalue().strip())
509 def touch(self
, fs_path
):
511 Create a dentry if it doesn't already exist. This python
512 implementation exists because the usual command line tool doesn't
513 pass through error codes like EIO.
518 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
519 pyscript
= dedent("""
524 f = open("{path}", "w")
528 """).format(path
=abs_path
)
529 proc
= self
._run
_python
(pyscript
)
532 def path_to_ino(self
, fs_path
, follow_symlinks
=True):
533 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
536 pyscript
= dedent("""
540 print os.stat("{path}").st_ino
541 """).format(path
=abs_path
)
543 pyscript
= dedent("""
547 print os.lstat("{path}").st_ino
548 """).format(path
=abs_path
)
550 proc
= self
._run
_python
(pyscript
)
552 return int(proc
.stdout
.getvalue().strip())
554 def path_to_nlink(self
, fs_path
):
555 abs_path
= os
.path
.join(self
.mountpoint
, fs_path
)
557 pyscript
= dedent("""
561 print os.stat("{path}").st_nlink
562 """).format(path
=abs_path
)
564 proc
= self
._run
_python
(pyscript
)
566 return int(proc
.stdout
.getvalue().strip())
568 def ls(self
, path
=None):
570 Wrap ls: return a list of strings
576 ls_text
= self
.run_shell(cmd
).stdout
.getvalue().strip()
579 return ls_text
.split("\n")
581 # Special case because otherwise split on empty string
582 # gives you [''] instead of []
585 def setfattr(self
, path
, key
, val
):
589 :param path: relative to mount point
590 :param key: xattr name
591 :param val: xattr value
594 self
.run_shell(["setfattr", "-n", key
, "-v", val
, path
])
596 def getfattr(self
, path
, attr
):
598 Wrap getfattr: return the values of a named xattr on one file, or
599 None if the attribute is not found.
603 p
= self
.run_shell(["getfattr", "--only-values", "-n", attr
, path
], wait
=False)
606 except CommandFailedError
as e
:
607 if e
.exitstatus
== 1 and "No such attribute" in p
.stderr
.getvalue():
612 return p
.stdout
.getvalue()
616 Wrap df: return a dict of usage fields in bytes
619 p
= self
.run_shell(["df", "-B1", "."])
620 lines
= p
.stdout
.getvalue().strip().split("\n")
621 fs
, total
, used
, avail
= lines
[1].split()[:4]
627 "available": int(avail
)