virt.virt_test_utils: run_autotest - 'tar' needs relative paths to strip the leading '/'
[autotest-zwu.git] / client / bin / partition.py
blob7381f755bc93e4a340631866915bbffae8f20e20
1 """
2 APIs to write tests and control files that handle partition creation, deletion
3 and formatting.
5 @copyright: Google 2006-2008
6 @author: Martin Bligh (mbligh@google.com)
7 """
9 import os, re, string, sys, fcntl, logging
10 from autotest_lib.client.bin import os_dep, utils
11 from autotest_lib.client.common_lib import error
14 class FsOptions(object):
15 """
16 A class encapsulating a filesystem test's parameters.
17 """
18 # NOTE(gps): This class could grow or be merged with something else in the
19 # future that actually uses the encapsulated data (say to run mkfs) rather
20 # than just being a container.
21 # Ex: fsdev_disks.mkfs_all_disks really should become a method.
23 __slots__ = ('fstype', 'mkfs_flags', 'mount_options', 'fs_tag')
25 def __init__(self, fstype, fs_tag, mkfs_flags=None, mount_options=None):
26 """
27 Fill in our properties.
29 @param fstype: The filesystem type ('ext2', 'ext4', 'xfs', etc.)
30 @param fs_tag: A short name for this filesystem test to use
31 in the results.
32 @param mkfs_flags: Optional. Additional command line options to mkfs.
33 @param mount_options: Optional. The options to pass to mount -o.
34 """
36 if not fstype or not fs_tag:
37 raise ValueError('A filesystem and fs_tag are required.')
38 self.fstype = fstype
39 self.fs_tag = fs_tag
40 self.mkfs_flags = mkfs_flags or ""
41 self.mount_options = mount_options or ""
44 def __str__(self):
45 val = ('FsOptions(fstype=%r, mkfs_flags=%r, '
46 'mount_options=%r, fs_tag=%r)' %
47 (self.fstype, self.mkfs_flags,
48 self.mount_options, self.fs_tag))
49 return val
52 def partname_to_device(part):
53 """ Converts a partition name to its associated device """
54 return os.path.join(os.sep, 'dev', part)
57 def list_mount_devices():
58 devices = []
59 # list mounted filesystems
60 for line in utils.system_output('mount').splitlines():
61 devices.append(line.split()[0])
62 # list mounted swap devices
63 for line in utils.system_output('swapon -s').splitlines():
64 if line.startswith('/'): # skip header line
65 devices.append(line.split()[0])
66 return devices
69 def list_mount_points():
70 mountpoints = []
71 for line in utils.system_output('mount').splitlines():
72 mountpoints.append(line.split()[2])
73 return mountpoints
76 def get_iosched_path(device_name, component):
77 return '/sys/block/%s/queue/%s' % (device_name, component)
80 def wipe_filesystem(job, mountpoint):
81 wipe_cmd = 'rm -rf %s/*' % mountpoint
82 try:
83 utils.system(wipe_cmd)
84 except:
85 job.record('FAIL', None, wipe_cmd, error.format_error())
86 raise
87 else:
88 job.record('GOOD', None, wipe_cmd)
91 def is_linux_fs_type(device):
92 """
93 Checks if specified partition is type 83
95 @param device: the device, e.g. /dev/sda3
97 @return: False if the supplied partition name is not type 83 linux, True
98 otherwise
99 """
100 disk_device = device.rstrip('0123456789')
102 # Parse fdisk output to get partition info. Ugly but it works.
103 fdisk_fd = os.popen("/sbin/fdisk -l -u '%s'" % disk_device)
104 fdisk_lines = fdisk_fd.readlines()
105 fdisk_fd.close()
106 for line in fdisk_lines:
107 if not line.startswith(device):
108 continue
109 info_tuple = line.split()
110 # The Id will be in one of two fields depending on if the boot flag
111 # was set. Caveat: this assumes no boot partition will be 83 blocks.
112 for fsinfo in info_tuple[4:6]:
113 if fsinfo == '83': # hex 83 is the linux fs partition type
114 return True
115 return False
118 def get_partition_list(job, min_blocks=0, filter_func=None, exclude_swap=True,
119 open_func=open):
121 Get a list of partition objects for all disk partitions on the system.
123 Loopback devices and unnumbered (whole disk) devices are always excluded.
125 @param job: The job instance to pass to the partition object
126 constructor.
127 @param min_blocks: The minimum number of blocks for a partition to
128 be considered.
129 @param filter_func: A callable that returns True if a partition is
130 desired. It will be passed one parameter:
131 The partition name (hdc3, etc.).
132 Some useful filter functions are already defined in this module.
133 @param exclude_swap: If True any partition actively in use as a swap
134 device will be excluded.
135 @param __open: Reserved for unit testing.
137 @return: A list of L{partition} objects.
139 active_swap_devices = set()
140 if exclude_swap:
141 for swapline in open_func('/proc/swaps'):
142 if swapline.startswith('/'):
143 active_swap_devices.add(swapline.split()[0])
145 partitions = []
146 for partline in open_func('/proc/partitions').readlines():
147 fields = partline.strip().split()
148 if len(fields) != 4 or partline.startswith('major'):
149 continue
150 (major, minor, blocks, partname) = fields
151 blocks = int(blocks)
153 # The partition name better end with a digit, else it's not a partition
154 if not partname[-1].isdigit():
155 continue
157 # We don't want the loopback device in the partition list
158 if 'loop' in partname:
159 continue
161 device = partname_to_device(partname)
162 if exclude_swap and device in active_swap_devices:
163 logging.debug('Skipping %s - Active swap.' % partname)
164 continue
166 if min_blocks and blocks < min_blocks:
167 logging.debug('Skipping %s - Too small.' % partname)
168 continue
170 if filter_func and not filter_func(partname):
171 logging.debug('Skipping %s - Filter func.' % partname)
172 continue
174 partitions.append(partition(job, device))
176 return partitions
179 def get_mount_info(partition_list):
181 Picks up mount point information about the machine mounts. By default, we
182 try to associate mount points with UUIDs, because in newer distros the
183 partitions are uniquely identified using them.
185 mount_info = set()
186 for p in partition_list:
187 try:
188 uuid = utils.system_output('blkid -p -s UUID -o value %s' % p.device)
189 except error.CmdError:
190 # fall back to using the partition
191 uuid = p.device
192 mount_info.add((uuid, p.get_mountpoint()))
194 return mount_info
197 def filter_partition_list(partitions, devnames):
199 Pick and choose which partition to keep.
201 filter_partition_list accepts a list of partition objects and a list
202 of strings. If a partition has the device name of the strings it
203 is returned in a list.
205 @param partitions: A list of L{partition} objects
206 @param devnames: A list of devnames of the form '/dev/hdc3' that
207 specifies which partitions to include in the returned list.
209 @return: A list of L{partition} objects specified by devnames, in the
210 order devnames specified
213 filtered_list = []
214 for p in partitions:
215 for d in devnames:
216 if p.device == d and p not in filtered_list:
217 filtered_list.append(p)
219 return filtered_list
222 def get_unmounted_partition_list(root_part, job=None, min_blocks=0,
223 filter_func=None, exclude_swap=True,
224 open_func=open):
226 Return a list of partition objects that are not mounted.
228 @param root_part: The root device name (without the '/dev/' prefix, example
229 'hda2') that will be filtered from the partition list.
231 Reasoning: in Linux /proc/mounts will never directly mention the
232 root partition as being mounted on / instead it will say that
233 /dev/root is mounted on /. Thus require this argument to filter out
234 the root_part from the ones checked to be mounted.
235 @param job, min_blocks, filter_func, exclude_swap, open_func: Forwarded
236 to get_partition_list().
237 @return List of L{partition} objects that are not mounted.
239 partitions = get_partition_list(job=job, min_blocks=min_blocks,
240 filter_func=filter_func, exclude_swap=exclude_swap, open_func=open_func)
242 unmounted = []
243 for part in partitions:
244 if (part.device != partname_to_device(root_part) and
245 not part.get_mountpoint(open_func=open_func)):
246 unmounted.append(part)
248 return unmounted
251 def parallel(partitions, method_name, *args, **dargs):
253 Run a partition method (with appropriate arguments) in parallel,
254 across a list of partition objects
256 if not partitions:
257 return
258 job = partitions[0].job
259 flist = []
260 if (not hasattr(partition, method_name) or
261 not callable(getattr(partition, method_name))):
262 err = "partition.parallel got invalid method %s" % method_name
263 raise RuntimeError(err)
265 for p in partitions:
266 print_args = list(args)
267 print_args += ['%s=%s' % (key, dargs[key]) for key in dargs.keys()]
268 logging.debug('%s.%s(%s)' % (str(p), method_name,
269 ', '.join(print_args)))
270 sys.stdout.flush()
271 def _run_named_method(function, part=p):
272 getattr(part, method_name)(*args, **dargs)
273 flist.append((_run_named_method, ()))
274 job.parallel(*flist)
277 def filesystems():
279 Return a list of all available filesystems
281 return [re.sub('(nodev)?\s*', '', fs) for fs in open('/proc/filesystems')]
284 def unmount_partition(device):
286 Unmount a mounted partition
288 @param device: e.g. /dev/sda1, /dev/hda1
290 p = partition(job=None, device=device)
291 p.unmount(record=False)
294 def is_valid_partition(device):
296 Checks if a partition is valid
298 @param device: e.g. /dev/sda1, /dev/hda1
300 parts = get_partition_list(job=None)
301 p_list = [ p.device for p in parts ]
302 if device in p_list:
303 return True
305 return False
308 def is_valid_disk(device):
310 Checks if a disk is valid
312 @param device: e.g. /dev/sda, /dev/hda
314 partitions = []
315 for partline in open('/proc/partitions').readlines():
316 fields = partline.strip().split()
317 if len(fields) != 4 or partline.startswith('major'):
318 continue
319 (major, minor, blocks, partname) = fields
320 blocks = int(blocks)
322 if not partname[-1].isdigit():
323 # Disk name does not end in number, AFAIK
324 # so use it as a reference to a disk
325 if device.strip("/dev/") == partname:
326 return True
328 return False
331 def run_test_on_partitions(job, test, partitions, mountpoint_func,
332 tag, fs_opt, do_fsck=True, **dargs):
334 Run a test that requires multiple partitions. Filesystems will be
335 made on the partitions and mounted, then the test will run, then the
336 filesystems will be unmounted and optionally fsck'd.
338 @param job: A job instance to run the test
339 @param test: A string containing the name of the test
340 @param partitions: A list of partition objects, these are passed to the
341 test as partitions=
342 @param mountpoint_func: A callable that returns a mountpoint given a
343 partition instance
344 @param tag: A string tag to make this test unique (Required for control
345 files that make multiple calls to this routine with the same value
346 of 'test'.)
347 @param fs_opt: An FsOptions instance that describes what filesystem to make
348 @param do_fsck: include fsck in post-test partition cleanup.
349 @param dargs: Dictionary of arguments to be passed to job.run_test() and
350 eventually the test
352 # setup the filesystem parameters for all the partitions
353 for p in partitions:
354 p.set_fs_options(fs_opt)
356 # make and mount all the partitions in parallel
357 parallel(partitions, 'setup_before_test', mountpoint_func=mountpoint_func)
359 mountpoint = mountpoint_func(partitions[0])
361 # run the test against all the partitions
362 job.run_test(test, tag=tag, partitions=partitions, dir=mountpoint, **dargs)
364 parallel(partitions, 'unmount') # unmount all partitions in parallel
365 if do_fsck:
366 parallel(partitions, 'fsck') # fsck all partitions in parallel
367 # else fsck is done by caller
370 class partition(object):
372 Class for handling partitions and filesystems
375 def __init__(self, job, device, loop_size=0, mountpoint=None):
377 @param job: A L{client.bin.job} instance.
378 @param device: The device in question (e.g."/dev/hda2"). If device is a
379 file it will be mounted as loopback. If you have job config
380 'partition.partitions', e.g.,
381 job.config_set('partition.partitions', ["/dev/sda2", "/dev/sda3"])
382 you may specify a partition in the form of "partN" e.g. "part0",
383 "part1" to refer to elements of the partition list. This is
384 specially useful if you run a test in various machines and you
385 don't want to hardcode device names as those may vary.
386 @param loop_size: Size of loopback device (in MB). Defaults to 0.
388 # NOTE: This code is used by IBM / ABAT. Do not remove.
389 part = re.compile(r'^part(\d+)$')
390 m = part.match(device)
391 if m:
392 number = int(m.groups()[0])
393 partitions = job.config_get('partition.partitions')
394 try:
395 device = partitions[number]
396 except:
397 raise NameError("Partition '" + device + "' not available")
399 self.device = device
400 self.name = os.path.basename(device)
401 self.job = job
402 self.loop = loop_size
403 self.fstype = None
404 self.mountpoint = mountpoint
405 self.mkfs_flags = None
406 self.mount_options = None
407 self.fs_tag = None
408 if self.loop:
409 cmd = 'dd if=/dev/zero of=%s bs=1M count=%d' % (device, loop_size)
410 utils.system(cmd)
413 def __repr__(self):
414 return '<Partition: %s>' % self.device
417 def set_fs_options(self, fs_options):
419 Set filesystem options
421 @param fs_options: A L{FsOptions} object
424 self.fstype = fs_options.fstype
425 self.mkfs_flags = fs_options.mkfs_flags
426 self.mount_options = fs_options.mount_options
427 self.fs_tag = fs_options.fs_tag
430 def run_test(self, test, **dargs):
431 self.job.run_test(test, dir=self.get_mountpoint(), **dargs)
434 def setup_before_test(self, mountpoint_func):
436 Prepare a partition for running a test. Unmounts any
437 filesystem that's currently mounted on the partition, makes a
438 new filesystem (according to this partition's filesystem
439 options) and mounts it where directed by mountpoint_func.
441 @param mountpoint_func: A callable that returns a path as a string,
442 given a partition instance.
444 mountpoint = mountpoint_func(self)
445 if not mountpoint:
446 raise ValueError('Don\'t know where to put this partition')
447 self.unmount(ignore_status=True, record=False)
448 self.mkfs()
449 if not os.path.isdir(mountpoint):
450 os.makedirs(mountpoint)
451 self.mount(mountpoint)
454 def run_test_on_partition(self, test, mountpoint_func, **dargs):
456 Executes a test fs-style (umount,mkfs,mount,test)
458 Here we unmarshal the args to set up tags before running the test.
459 Tests are also run by first umounting, mkfsing and then mounting
460 before executing the test.
462 @param test: name of test to run
463 @param mountpoint_func: function to return mount point string
465 tag = dargs.get('tag')
466 if tag:
467 tag = '%s.%s' % (self.name, tag)
468 elif self.fs_tag:
469 tag = '%s.%s' % (self.name, self.fs_tag)
470 else:
471 tag = self.name
473 # If there's a 'suffix' argument, append it to the tag and remove it
474 suffix = dargs.pop('suffix', None)
475 if suffix:
476 tag = '%s.%s' % (tag, suffix)
478 dargs['tag'] = test + '.' + tag
480 def _make_partition_and_run_test(test_tag, dir=None, **dargs):
481 self.setup_before_test(mountpoint_func)
482 try:
483 self.job.run_test(test, tag=test_tag, dir=mountpoint, **dargs)
484 finally:
485 self.unmount()
486 self.fsck()
489 mountpoint = mountpoint_func(self)
491 # The tag is the tag for the group (get stripped off by run_group)
492 # The test_tag is the tag for the test itself
493 self.job.run_group(_make_partition_and_run_test,
494 test_tag=tag, dir=mountpoint, **dargs)
497 def get_mountpoint(self, open_func=open, filename=None):
499 Find the mount point of this partition object.
501 @param open_func: the function to use for opening the file containing
502 the mounted partitions information
503 @param filename: where to look for the mounted partitions information
504 (default None which means it will search /proc/mounts and/or
505 /etc/mtab)
507 @returns a string with the mount point of the partition or None if not
508 mounted
510 if filename:
511 for line in open_func(filename).readlines():
512 parts = line.split()
513 if parts[0] == self.device or parts[1] == self.mountpoint:
514 return parts[1] # The mountpoint where it's mounted
515 return None
517 # no specific file given, look in /proc/mounts
518 res = self.get_mountpoint(open_func=open_func, filename='/proc/mounts')
519 if not res:
520 # sometimes the root partition is reported as /dev/root in
521 # /proc/mounts in this case, try /etc/mtab
522 res = self.get_mountpoint(open_func=open_func, filename='/etc/mtab')
524 # trust /etc/mtab only about /
525 if res != '/':
526 res = None
528 return res
531 def mkfs_exec(self, fstype):
533 Return the proper mkfs executable based on fs
535 if fstype == 'ext4':
536 if os.path.exists('/sbin/mkfs.ext4'):
537 return 'mkfs'
538 # If ext4 supported e2fsprogs is not installed we use the
539 # autotest supplied one in tools dir which is statically linked"""
540 auto_mkfs = os.path.join(self.job.toolsdir, 'mkfs.ext4dev')
541 if os.path.exists(auto_mkfs):
542 return auto_mkfs
543 else:
544 return 'mkfs'
546 raise NameError('Error creating partition for filesystem type %s' %
547 fstype)
550 def mkfs(self, fstype=None, args='', record=True):
552 Format a partition to filesystem type
554 @param fstype: the filesystem type, e.g.. "ext3", "ext2"
555 @param args: arguments to be passed to mkfs command.
556 @param record: if set, output result of mkfs operation to autotest
557 output
560 if list_mount_devices().count(self.device):
561 raise NameError('Attempted to format mounted device %s' %
562 self.device)
564 if not fstype:
565 if self.fstype:
566 fstype = self.fstype
567 else:
568 fstype = 'ext2'
570 if self.mkfs_flags:
571 args += ' ' + self.mkfs_flags
572 if fstype == 'xfs':
573 args += ' -f'
575 if self.loop:
576 # BAH. Inconsistent mkfs syntax SUCKS.
577 if fstype.startswith('ext'):
578 args += ' -F'
579 elif fstype == 'reiserfs':
580 args += ' -f'
582 # If there isn't already a '-t <type>' argument, add one.
583 if not "-t" in args:
584 args = "-t %s %s" % (fstype, args)
586 args = args.strip()
588 mkfs_cmd = "%s %s %s" % (self.mkfs_exec(fstype), args, self.device)
590 sys.stdout.flush()
591 try:
592 # We throw away the output here - we only need it on error, in
593 # which case it's in the exception
594 utils.system_output("yes | %s" % mkfs_cmd)
595 except error.CmdError, e:
596 logging.error(e.result_obj)
597 if record:
598 self.job.record('FAIL', None, mkfs_cmd, error.format_error())
599 raise
600 except:
601 if record:
602 self.job.record('FAIL', None, mkfs_cmd, error.format_error())
603 raise
604 else:
605 if record:
606 self.job.record('GOOD', None, mkfs_cmd)
607 self.fstype = fstype
610 def get_fsck_exec(self):
612 Return the proper mkfs executable based on self.fstype
614 if self.fstype == 'ext4':
615 if os.path.exists('/sbin/fsck.ext4'):
616 return 'fsck'
617 # If ext4 supported e2fsprogs is not installed we use the
618 # autotest supplied one in tools dir which is statically linked"""
619 auto_fsck = os.path.join(self.job.toolsdir, 'fsck.ext4dev')
620 if os.path.exists(auto_fsck):
621 return auto_fsck
622 else:
623 return 'fsck'
625 raise NameError('Error creating partition for filesystem type %s' %
626 self.fstype)
629 def fsck(self, args='-fy', record=True):
631 Run filesystem check
633 @param args: arguments to filesystem check tool. Default is "-n"
634 which works on most tools.
637 # I hate reiserfstools.
638 # Requires an explit Yes for some inane reason
639 fsck_cmd = '%s %s %s' % (self.get_fsck_exec(), self.device, args)
640 if self.fstype == 'reiserfs':
641 fsck_cmd = 'yes "Yes" | ' + fsck_cmd
642 sys.stdout.flush()
643 try:
644 utils.system_output(fsck_cmd)
645 except:
646 if record:
647 self.job.record('FAIL', None, fsck_cmd, error.format_error())
648 raise error.TestError('Fsck found errors with the underlying '
649 'file system')
650 else:
651 if record:
652 self.job.record('GOOD', None, fsck_cmd)
655 def mount(self, mountpoint=None, fstype=None, args='', record=True):
657 Mount this partition to a mount point
659 @param mountpoint: If you have not provided a mountpoint to partition
660 object or want to use a different one, you may specify it here.
661 @param fstype: Filesystem type. If not provided partition object value
662 will be used.
663 @param args: Arguments to be passed to "mount" command.
664 @param record: If True, output result of mount operation to autotest
665 output.
668 if fstype is None:
669 fstype = self.fstype
670 else:
671 assert(self.fstype is None or self.fstype == fstype);
673 if self.mount_options:
674 args += ' -o ' + self.mount_options
675 if fstype:
676 args += ' -t ' + fstype
677 if self.loop:
678 args += ' -o loop'
679 args = args.lstrip()
681 if not mountpoint and not self.mountpoint:
682 raise ValueError("No mountpoint specified and no default "
683 "provided to this partition object")
684 if not mountpoint:
685 mountpoint = self.mountpoint
687 mount_cmd = "mount %s %s %s" % (args, self.device, mountpoint)
689 if list_mount_devices().count(self.device):
690 err = 'Attempted to mount mounted device'
691 self.job.record('FAIL', None, mount_cmd, err)
692 raise NameError(err)
693 if list_mount_points().count(mountpoint):
694 err = 'Attempted to mount busy mountpoint'
695 self.job.record('FAIL', None, mount_cmd, err)
696 raise NameError(err)
698 mtab = open('/etc/mtab')
699 # We have to get an exclusive lock here - mount/umount are racy
700 fcntl.flock(mtab.fileno(), fcntl.LOCK_EX)
701 sys.stdout.flush()
702 try:
703 utils.system(mount_cmd)
704 mtab.close()
705 except:
706 mtab.close()
707 if record:
708 self.job.record('FAIL', None, mount_cmd, error.format_error())
709 raise
710 else:
711 if record:
712 self.job.record('GOOD', None, mount_cmd)
713 self.fstype = fstype
716 def unmount_force(self):
718 Kill all other jobs accessing this partition. Use fuser and ps to find
719 all mounts on this mountpoint and unmount them.
721 @return: true for success or false for any errors
724 logging.debug("Standard umount failed, will try forcing. Users:")
725 try:
726 cmd = 'fuser ' + self.get_mountpoint()
727 logging.debug(cmd)
728 fuser = utils.system_output(cmd)
729 logging.debug(fuser)
730 users = re.sub('.*:', '', fuser).split()
731 for user in users:
732 m = re.match('(\d+)(.*)', user)
733 (pid, usage) = (m.group(1), m.group(2))
734 try:
735 ps = utils.system_output('ps -p %s | sed 1d' % pid)
736 logging.debug('%s %s %s' % (usage, pid, ps))
737 except Exception:
738 pass
739 utils.system('ls -l ' + self.device)
740 umount_cmd = "umount -f " + self.device
741 utils.system(umount_cmd)
742 return True
743 except error.CmdError:
744 logging.debug('Umount_force failed for %s' % self.device)
745 return False
749 def unmount(self, ignore_status=False, record=True):
751 Umount this partition.
753 It's easier said than done to umount a partition.
754 We need to lock the mtab file to make sure we don't have any
755 locking problems if we are umounting in paralllel.
757 If there turns out to be a problem with the simple umount we
758 end up calling umount_force to get more agressive.
760 @param ignore_status: should we notice the umount status
761 @param record: if True, output result of umount operation to
762 autotest output
765 mountpoint = self.get_mountpoint()
766 if not mountpoint:
767 # It's not even mounted to start with
768 if record and not ignore_status:
769 msg = 'umount for dev %s has no mountpoint' % self.device
770 self.job.record('FAIL', None, msg, 'Not mounted')
771 return
773 umount_cmd = "umount " + mountpoint
774 mtab = open('/etc/mtab')
776 # We have to get an exclusive lock here - mount/umount are racy
777 fcntl.flock(mtab.fileno(), fcntl.LOCK_EX)
778 sys.stdout.flush()
779 try:
780 utils.system(umount_cmd)
781 mtab.close()
782 if record:
783 self.job.record('GOOD', None, umount_cmd)
784 except (error.CmdError, IOError):
785 mtab.close()
787 # Try the forceful umount
788 if self.unmount_force():
789 return
791 # If we are here we cannot umount this partition
792 if record and not ignore_status:
793 self.job.record('FAIL', None, umount_cmd, error.format_error())
794 raise
797 def wipe(self):
799 Delete all files of a given partition filesystem.
801 wipe_filesystem(self.job, self.get_mountpoint())
804 def get_io_scheduler_list(self, device_name):
805 names = open(self.__sched_path(device_name)).read()
806 return names.translate(string.maketrans('[]', ' ')).split()
809 def get_io_scheduler(self, device_name):
810 return re.split('[\[\]]',
811 open(self.__sched_path(device_name)).read())[1]
814 def set_io_scheduler(self, device_name, name):
815 if name not in self.get_io_scheduler_list(device_name):
816 raise NameError('No such IO scheduler: %s' % name)
817 f = open(self.__sched_path(device_name), 'w')
818 f.write(name)
819 f.close()
822 def __sched_path(self, device_name):
823 return '/sys/block/%s/queue/scheduler' % device_name
826 class virtual_partition:
828 Handles block device emulation using file images of disks.
829 It's important to note that this API can be used only if
830 we have the following programs present on the client machine:
832 * sfdisk
833 * losetup
834 * kpartx
836 def __init__(self, file_img, file_size):
838 Creates a virtual partition, keeping record of the device created
839 under /dev/mapper (device attribute) so test writers can use it
840 on their filesystem tests.
842 @param file_img: Path to the desired disk image file.
843 @param file_size: Size of the desired image in Bytes.
845 logging.debug('Sanity check before attempting to create virtual '
846 'partition')
847 try:
848 os_dep.commands('sfdisk', 'losetup', 'kpartx')
849 except ValueError, e:
850 e_msg = 'Unable to create virtual partition: %s' % e
851 raise error.AutotestError(e_msg)
853 logging.debug('Creating virtual partition')
854 self.img = self._create_disk_img(file_img, file_size)
855 self.loop = self._attach_img_loop(self.img)
856 self._create_single_partition(self.loop)
857 self.device = self._create_entries_partition(self.loop)
858 logging.debug('Virtual partition successfuly created')
859 logging.debug('Image disk: %s', self.img)
860 logging.debug('Loopback device: %s', self.loop)
861 logging.debug('Device path: %s', self.device)
864 def destroy(self):
866 Removes the virtual partition from /dev/mapper, detaches the image file
867 from the loopback device and removes the image file.
869 logging.debug('Removing virtual partition - device %s', self.device)
870 self._remove_entries_partition()
871 self._detach_img_loop()
872 self._remove_disk_img()
875 def _create_disk_img(self, img_path, size):
877 Creates a disk image using dd.
879 @param img_path: Path to the desired image file.
880 @param size: Size of the desired image in Bytes.
881 @returns: Path of the image created.
883 logging.debug('Creating disk image %s, size = %d Bytes', img_path, size)
884 try:
885 cmd = 'dd if=/dev/zero of=%s bs=1024 count=%d' % (img_path, size)
886 utils.system(cmd)
887 except error.CmdError, e:
888 e_msg = 'Error creating disk image %s: %s' % (img_path, e)
889 raise error.AutotestError(e_msg)
890 return img_path
893 def _attach_img_loop(self, img_path):
895 Attaches a file image to a loopback device using losetup.
897 @param img_path: Path of the image file that will be attached to a
898 loopback device
899 @returns: Path of the loopback device associated.
901 logging.debug('Attaching image %s to a loop device', img_path)
902 try:
903 cmd = 'losetup -f'
904 loop_path = utils.system_output(cmd)
905 cmd = 'losetup -f %s' % img_path
906 utils.system(cmd)
907 except error.CmdError, e:
908 e_msg = 'Error attaching image %s to a loop device: %s' % \
909 (img_path, e)
910 raise error.AutotestError(e_msg)
911 return loop_path
914 def _create_single_partition(self, loop_path):
916 Creates a single partition encompassing the whole 'disk' using cfdisk.
918 @param loop_path: Path to the loopback device.
920 logging.debug('Creating single partition on %s', loop_path)
921 try:
922 single_part_cmd = '0,,c\n'
923 sfdisk_file_path = '/tmp/create_partition.sfdisk'
924 sfdisk_cmd_file = open(sfdisk_file_path, 'w')
925 sfdisk_cmd_file.write(single_part_cmd)
926 sfdisk_cmd_file.close()
927 utils.system('sfdisk %s < %s' % (loop_path, sfdisk_file_path))
928 except error.CmdError, e:
929 e_msg = 'Error partitioning device %s: %s' % (loop_path, e)
930 raise error.AutotestError(e_msg)
933 def _create_entries_partition(self, loop_path):
935 Takes the newly created partition table on the loopback device and
936 makes all its devices available under /dev/mapper. As we previously
937 have partitioned it using a single partition, only one partition
938 will be returned.
940 @param loop_path: Path to the loopback device.
942 logging.debug('Creating entries under /dev/mapper for %s loop dev',
943 loop_path)
944 try:
945 cmd = 'kpartx -a %s' % loop_path
946 utils.system(cmd)
947 l_cmd = 'kpartx -l %s | cut -f1 -d " "' % loop_path
948 device = utils.system_output(l_cmd)
949 except error.CmdError, e:
950 e_msg = 'Error creating entries for %s: %s' % (loop_path, e)
951 raise error.AutotestError(e_msg)
952 return os.path.join('/dev/mapper', device)
955 def _remove_entries_partition(self):
957 Removes the entries under /dev/mapper for the partition associated
958 to the loopback device.
960 logging.debug('Removing the entry on /dev/mapper for %s loop dev',
961 self.loop)
962 try:
963 cmd = 'kpartx -d %s' % self.loop
964 utils.system(cmd)
965 except error.CmdError, e:
966 e_msg = 'Error removing entries for loop %s: %s' % (self.loop, e)
967 raise error.AutotestError(e_msg)
970 def _detach_img_loop(self):
972 Detaches the image file from the loopback device.
974 logging.debug('Detaching image %s from loop device %s', self.img,
975 self.loop)
976 try:
977 cmd = 'losetup -d %s' % self.loop
978 utils.system(cmd)
979 except error.CmdError, e:
980 e_msg = ('Error detaching image %s from loop device %s: %s' %
981 (self.img, self.loop, e))
982 raise error.AutotestError(e_msg)
985 def _remove_disk_img(self):
987 Removes the disk image.
989 logging.debug('Removing disk image %s', self.img)
990 try:
991 os.remove(self.img)
992 except:
993 e_msg = 'Error removing image file %s' % self.img
994 raise error.AutotestError(e_msg)
996 # import a site partition module to allow it to override functions
997 try:
998 from autotest_lib.client.bin.site_partition import *
999 except ImportError:
1000 pass