Add {enable, disable}_guestfs methods in image cls
[snf-image-creator] / image_creator / image.py
index a5804a3..282789d 100644 (file)
@@ -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
@@ -31,7 +33,7 @@
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-from image_creator.util import FatalError
+from image_creator.util import FatalError, check_guestfs_version
 from image_creator.gpt import GPTPartitionTable
 from image_creator.os_type import os_cls
 
@@ -43,19 +45,54 @@ 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.g = guestfs.GuestFS()
+        self.guestfs_enabled = False
+
+    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:
+            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)
+
+        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)
+
+    def enable_guestfs(self):
+        """Enable the guestfs handler"""
+
+        if self.guestfs_enabled:
+            self.out.warn("Guestfs is already enabled")
+            return
+
         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
@@ -63,23 +100,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 check_guestfs_version(self.g, 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")
@@ -92,22 +121,24 @@ class Image(object):
         # self.progressbar = None
         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.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 check_guestfs_version(self.g, 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"""
@@ -117,19 +148,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
+        cls = os_cls(self.distro, self.ostype)
+        self._os = cls(self, sysprep_params=self.sysprep_params)
 
-        try:
-            cls = os_cls(self.distro, self.ostype)
-            self._os = cls(self.root, self.g, self.out)
-
-        finally:
-            if do_unmount:
-                self.umount()
+        self._os.collect_metadata()
 
         return self._os
 
@@ -153,38 +175,6 @@ class Image(object):
 #
 #        self.progressbar.goto((position * 100) // total)
 
-    def mount(self, readonly=False):
-        """Mount all disk partitions in a correct order."""
-
-        mount = self.g.mount_ro if readonly else self.g.mount
-        msg = " read-only" if readonly else ""
-        self.out.output("Mounting the media%s ..." % msg, False)
-        mps = self.g.inspect_get_mountpoints(self.root)
-
-        # 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.sort(compare)
-        for mp, dev in mps:
-            try:
-                mount(dev, mp)
-            except RuntimeError as msg:
-                self.out.warn("%s (ignored)" % msg)
-
-        self.mounted = True
-        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':
@@ -270,7 +260,7 @@ 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'])