X-Git-Url: https://code.grnet.gr/git/snf-image-creator/blobdiff_plain/ae48a082b638284012de78a30c8374ba689eb0b7..136754298b63928e3ab593c2489fcb6e17c1332e:/image_creator/disk.py diff --git a/image_creator/disk.py b/image_creator/disk.py index 874d593..7fc0cdf 100644 --- a/image_creator/disk.py +++ b/image_creator/disk.py @@ -31,9 +31,9 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. -from image_creator.util import get_command +from image_creator.util import get_command, warn, progress_generator from image_creator import FatalError -from clint.textui import progress +from clint.textui import indent, puts, colored import stat import os @@ -99,22 +99,31 @@ class Disk(object): This instance is a snapshot of the original source media of the Disk instance. """ + + puts("Examining source media `%s'..." % self.source, False) sourcedev = self.source mode = os.stat(self.source).st_mode if stat.S_ISDIR(mode): + puts(colored.green('looks like a directory')) return self._losetup(self._dir_to_disk()) elif stat.S_ISREG(mode): + puts(colored.green('looks like an image file')) sourcedev = self._losetup(self.source) elif not stat.S_ISBLK(mode): - raise ValueError("Value for self.source is invalid") + raise ValueError("Invalid media source. Only block devices, " + "regular files and directories are supported.") + else: + puts(colored.green('looks like a block device')) # Take a snapshot and return it to the user + puts("Snapshotting media source...", False) size = blockdev('--getsize', sourcedev) cowfd, cow = tempfile.mkstemp() os.close(cowfd) self._add_cleanup(os.unlink, cow) # Create 1G cow sparse file - dd('if=/dev/null', 'of=%s' % cow, 'bs=1k', 'seek=%d' % (1024 * 1024)) + dd('if=/dev/null', 'of=%s' % cow, 'bs=1k', \ + 'seek=%d' % (1024 * 1024)) cowdev = self._losetup(cow) snapshot = uuid.uuid4().hex @@ -124,8 +133,14 @@ class Disk(object): (int(size), sourcedev, cowdev)) dmsetup('create', snapshot, table) self._add_cleanup(dmsetup, 'remove', snapshot) + # Sometimes dmsetup remove fails with Device or resource busy, + # although everything is cleaned up and the snapshot is not + # used by anyone. Add a 2 seconds delay to be on the safe side. + self._add_cleanup(time.sleep, 2) + finally: os.unlink(table) + puts(colored.green('done')) new_device = DiskDevice("/dev/mapper/%s" % snapshot) self._devices.append(new_device) new_device.enable() @@ -139,15 +154,6 @@ class Disk(object): device.destroy() -def progress_generator(label=''): - position = 0 - for i in progress.bar(range(100), label): - if i < position: - continue - position = yield - yield # suppress the StopIteration exception - - class DiskDevice(object): """This class represents a block device hosting an Operating System as created by the device-mapper. @@ -170,11 +176,10 @@ class DiskDevice(object): def enable(self): """Enable a newly created DiskDevice""" - - self.progressbar = progress_generator("VM lauch: ") + self.progressbar = progress_generator("Launching helper VM: ") self.progressbar.next() eh = self.g.set_event_callback(self.progress_callback, - guestfs.EVENT_PROGRESS) + guestfs.EVENT_PROGRESS) self.g.launch() self.guestfs_enabled = True self.g.delete_event_callback(eh) @@ -182,15 +187,17 @@ class DiskDevice(object): self.progressbar.send(100) self.progressbar = None + puts('Inspecting Operating System...', False) roots = self.g.inspect_os() if len(roots) == 0: raise FatalError("No operating system found") if len(roots) > 1: - raise FatalError("Multiple operating systems found") - + raise FatalError("Multiple operating systems found." + "We only support images with one filesystem.") self.root = roots[0] self.ostype = self.g.inspect_get_type(self.root) self.distro = self.g.inspect_get_distro(self.root) + puts(colored.green('found a %s system' % self.distro)) def destroy(self): """Destroy this DiskDevice instance.""" @@ -242,6 +249,8 @@ class DiskDevice(object): disk and then updating the partition table. The new disk size (in bytes) is returned. """ + puts("Shrinking image (this may take a while)...", False) + dev = self.g.part_to_dev(self.root) parttype = self.g.part_get_parttype(dev) if parttype != 'msdos': @@ -257,14 +266,17 @@ class DiskDevice(object): part_dev = "%s%d" % (dev, last_partition['part_num']) fs_type = self.g.vfs_type(part_dev) if not re.match("ext[234]", fs_type): - print "Warning: Don't know how to resize %s partitions." % vfs_type + warn("Don't know how to resize %s partitions." % vfs_type) return self.g.e2fsck_f(part_dev) self.g.resize2fs_M(part_dev) + output = self.g.tune2fs_l(part_dev) - block_size = int(filter(lambda x: x[0] == 'Block size', output)[0][1]) - block_cnt = int(filter(lambda x: x[0] == 'Block count', output)[0][1]) + block_size = int( + filter(lambda x: x[0] == 'Block size', output)[0][1]) + block_cnt = int( + filter(lambda x: x[0] == 'Block count', output)[0][1]) sector_size = self.g.blockdev_getss(dev) @@ -274,7 +286,10 @@ class DiskDevice(object): self.g.part_del(dev, last_partition['part_num']) self.g.part_add(dev, 'p', start, end) - return (end + 1) * sector_size + new_size = (end + 1) * sector_size + puts(colored.green("new image size is %dMB\n" % (new_size // 2 ** 20))) + + return new_size def size(self): """Returns the "payload" size of the device. @@ -285,6 +300,6 @@ class DiskDevice(object): dev = self.g.part_to_dev(self.root) last = self.g.part_list(dev)[-1] - return last['part_end'] + return last['part_end'] + 1 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :