X-Git-Url: https://code.grnet.gr/git/snf-image-creator/blobdiff_plain/29fd973e0da4b1bf61f76a7809b67b18ef9db195..37d1ea11cc79b7e45db9049df745abd3f147e031:/image_creator/image.py diff --git a/image_creator/image.py b/image_creator/image.py index 39a6f73..aebd2c9 100644 --- a/image_creator/image.py +++ b/image_creator/image.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2013 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -43,20 +45,103 @@ from sendfile import sendfile class Image(object): """The instances of this class can create images out of block devices.""" - def __init__(self, device, output, bootable=True, meta={}): + def __init__(self, device, output, **kargs): """Create a new Image instance""" self.device = device self.out = output - self.bootable = bootable - self.meta = meta + + self.meta = kargs['meta'] if 'meta' in kargs else {} + self.sysprep_params = \ + kargs['sysprep_params'] if 'sysprep_params' in kargs else {} + self.progress_bar = None self.guestfs_device = None self.size = 0 - self.mounted = False - self.mounted_ro = False self.g = guestfs.GuestFS() + self.guestfs_enabled = False + self.guestfs_version = self.g.version() + + def check_guestfs_version(self, major, minor, release): + """Checks if the version of the used libguestfs is smaller, equal or + greater than the one specified by the major, minor and release triplet + + Returns: + < 0 if the installed version is smaller than the specified one + = 0 if they are equal + > 0 if the installed one is greater than the specified one + """ + + for (a, b) in (self.guestfs_version['major'], major), \ + (self.guestfs_version['minor'], minor), \ + (self.guestfs_version['release'], release): + if a != b: + return a - b + + return 0 + + def enable(self): + """Enable a newly created Image instance""" + + self.enable_guestfs() + + self.out.output('Inspecting Operating System ...', False) + roots = self.g.inspect_os() + + if len(roots) == 0 or len(roots) > 1: + self.root = None + self.ostype = "unsupported" + self.distro = "unsupported" + self.guestfs_device = '/dev/sda' + self.size = self.g.blockdev_getsize64(self.guestfs_device) + + if len(roots) > 1: + reason = "Multiple operating systems found on the media." + else: + reason = "Unable to detect any operating system on the media." + + self.set_unsupported(reason) + return + + self.root = roots[0] + self.meta['PARTITION_TABLE'] = self.g.part_get_parttype('/dev/sda') + self.guestfs_device = '/dev/sda' # self.g.part_to_dev(self.root) + self.size = self.g.blockdev_getsize64(self.guestfs_device) + + self.ostype = self.g.inspect_get_type(self.root) + self.distro = self.g.inspect_get_distro(self.root) + self.out.success( + 'found a(n) %s system' % + self.ostype if self.distro == "unknown" else self.distro) + + # Inspect the OS + self.os.inspect() + + def set_unsupported(self, reason): + """Flag this image us ansupported""" + + self._unsupported = reason + self.meta['UNSUPPORTED'] = reason + self.out.warn('Media is not supported. Reason: %s' % reason) + + def is_unsupported(self): + """Returns if this image is unsupported""" + return hasattr(self, '_unsupported') + + def enable_guestfs(self): + """Enable the guestfs handler""" + + if self.guestfs_enabled: + self.out.warn("Guestfs is already enabled") + return + + # Before version 1.18.4 the behaviour of kill_subprocess was different + # and you need to reset the guestfs handler to relaunch a previously + # shut down qemu backend + if self.check_guestfs_version(1, 18, 4) < 0: + self.g = guestfs.GuestFS() + self.g.add_drive_opts(self.device, readonly=0, format="raw") # Before version 1.17.14 the recovery process, which is a fork of the @@ -64,23 +149,15 @@ class Image(object): # file descriptors. This can cause problems especially if the parent # process has opened pipes. Since the recovery process is an optional # feature of libguestfs, it's better to disable it. - self.g.set_recovery_proc(0) - version = self.g.version() - if version['major'] > 1 or \ - (version['major'] == 1 and (version['minor'] >= 18 or - (version['minor'] == 17 and - version['release'] >= 14))): - self.g.set_recovery_proc(1) + if self.check_guestfs_version(1, 17, 14) >= 0: self.out.output("Enabling recovery proc") + self.g.set_recovery_proc(1) + else: + self.g.set_recovery_proc(0) #self.g.set_trace(1) #self.g.set_verbose(1) - self.guestfs_enabled = False - - def enable(self): - """Enable a newly created Image instance""" - self.out.output('Launching helper VM (may take a while) ...', False) # self.progressbar = self.out.Progress(100, "Launching helper VM", # "percent") @@ -91,26 +168,30 @@ class Image(object): # self.g.delete_event_callback(eh) # self.progressbar.success('done') # self.progressbar = None + + if self.check_guestfs_version(1, 18, 4) < 0: + self.g.inspect_os() # some calls need this + self.out.success('done') - self.out.output('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." - "We only support images with one OS.") - self.root = roots[0] - self.guestfs_device = self.g.part_to_dev(self.root) - self.size = self.g.blockdev_getsize64(self.guestfs_device) - self.meta['PARTITION_TABLE'] = \ - self.g.part_get_parttype(self.guestfs_device) + def disable_guestfs(self): + """Disable the guestfs handler""" - self.ostype = self.g.inspect_get_type(self.root) - self.distro = self.g.inspect_get_distro(self.root) - self.out.success( - 'found a(n) %s system' % - self.ostype if self.distro == "unknown" else self.distro) + if not self.guestfs_enabled: + self.out.warn("Guestfs is already disabled") + return + + self.out.output("Shutting down helper VM ...", False) + self.g.sync() + # guestfs_shutdown which is the prefered way to shutdown the backend + # process was introduced in version 1.19.16 + if self.check_guestfs_version(1, 19, 16) >= 0: + self.g.shutdown() + else: + self.g.kill_subprocess() + + self.guestfs_enabled = False + self.out.success('done') def _get_os(self): """Return an OS class instance for this image""" @@ -120,19 +201,10 @@ class Image(object): if not self.guestfs_enabled: self.enable() - if not self.mounted: - do_unmount = True - self.mount(readonly=True) - else: - do_unmount = False - - try: - cls = os_cls(self.distro, self.ostype) - self._os = cls(self.root, self.g, self.out) + cls = os_cls(self.distro, self.ostype) + self._os = cls(self, sysprep_params=self.sysprep_params) - finally: - if do_unmount: - self.umount() + self._os.collect_metadata() return self._os @@ -156,70 +228,6 @@ class Image(object): # # self.progressbar.goto((position * 100) // total) - def mount(self, readonly=False): - """Mount all disk partitions in a correct order.""" - - msg = "Mounting the media%s ..." % (" read-only" if readonly else "") - self.out.output(msg, False) - - #If something goes wrong when mounting rw, remount the filesystem ro - remount_ro = False - rw_mpoints = ('/', '/etc', '/root', '/home', '/var') - - # Sort the keys to mount the fs in a correct order. - # / should be mounted befor /boot, etc - def compare(a, b): - if len(a[0]) > len(b[0]): - return 1 - elif len(a[0]) == len(b[0]): - return 0 - else: - return -1 - mps = self.g.inspect_get_mountpoints(self.root) - mps.sort(compare) - - mopts = 'ro' if readonly else 'rw' - for mp, dev in mps: - if self.ostype == 'freebsd': - # libguestfs can't handle correct freebsd partitions on GUID - # Partition Table. We have to do the translation to linux - # device names ourselves - m = re.match('^/dev/((?:ada)|(?:vtbd))(\d+)p(\d+)$', dev) - if m: - m2 = int(m.group(2)) - m3 = int(m.group(3)) - dev = '/dev/sd%c%d' % (chr(ord('a') + m2), m3) - try: - self.g.mount_options(mopts, dev, mp) - except RuntimeError as msg: - if self.ostype == 'freebsd': - freebsd_mopts = "ufstype=ufs2,%s" % mopts - try: - self.g.mount_vfs(freebsd_mopts, 'ufs', dev, mp) - except RuntimeError as msg: - if readonly is False and mp in rw_mpoints: - remount_ro = True - break - elif readonly is False and mp in rw_mpoints: - remount_ro = True - break - else: - self.out.warn("%s (ignored)" % msg) - if remount_ro: - self.out.warn("Unable to mount %s read-write. " - "Remounting everything read-only..." % mp) - self.umount() - self.mount(True) - else: - self.mounted = True - self.mounted_ro = readonly - self.out.success("done") - - def umount(self): - """Umount all mounted filesystems.""" - self.g.umount_all() - self.mounted = False - def _last_partition(self): """Return the last partition of the image disk""" if self.meta['PARTITION_TABLE'] not in 'msdos' 'gpt': @@ -280,6 +288,10 @@ class Image(object): self.out.output("Shrinking image (this may take a while) ...", False) + if self.is_unsupported(): + self.out.warn("Shrinking is disabled for unsupported images") + return self.size + sector_size = self.g.blockdev_getss(self.guestfs_device) last_part = None @@ -305,11 +317,16 @@ class Image(object): break if not re.match("ext[234]", fstype): - self.out.warn("Don't know how to resize %s partitions." % fstype) + self.out.warn("Don't know how to shrink %s partitions." % fstype) return self.size part_dev = "%s%d" % (self.guestfs_device, last_part['part_num']) - self.g.e2fsck_f(part_dev) + + if self.check_guestfs_version(1, 15, 17) >= 0: + self.g.e2fsck(part_dev, forceall=1) + else: + self.g.e2fsck_f(part_dev) + self.g.resize2fs_M(part_dev) out = self.g.tune2fs_l(part_dev)