[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

Re: [et-mgmt-tools] [patch 0/8] [virtinst] Add installer concept and livecd installer example



Hi Dan,

On Thu, 2007-05-10 at 16:54 +0100, Daniel P. Berrange wrote:
> On Wed, May 02, 2007 at 01:15:28PM +0100, Mark McLoughlin wrote:
> > Hey,
> > 	This set of patches explores the notion of allowing virtinst
> > to have different installer types apart from the existing support
> > for distribution installers.
> > 
> > 	This notion can be used to e.g. support livecd or pre-built
> > system images.
> 
> I've sent a few comments on respective patches, but in general I think this
> is all good work we should include.

	Cool, are you happy with me to push these then? Or do you want a hg
repo to pull them from?

>  Any idea when you'll be doing an installer to deal with pre-built images :-)

	Well, I have one (see attached) but I want to spend some time figuring
out what we want from this and e.g. what the metadata should be like.
I'm fairly happy with it so far, though.

Cheers,
Mark.
Index: virtinst--devel/virt-install
===================================================================
--- virtinst--devel.orig/virt-install
+++ virtinst--devel/virt-install
@@ -529,6 +529,8 @@ def main():
         installer = virtinst.DistroInstaller(type = type)
     elif options.installer == "livecd":
         installer = virtinst.LiveCDInstaller(type = type)
+    elif options.installer == "image":
+        installer = virtinst.ImageInstaller(type = type)
     else:
         print >> sys.stderr, "Unknown installer type '%s'" % options.installer
         sys.exit(1)
Index: virtinst--devel/virtinst/ImageInstaller.py
===================================================================
--- /dev/null
+++ virtinst--devel/virtinst/ImageInstaller.py
@@ -0,0 +1,189 @@
+#!/usr/bin/python -tt
+#
+# An installer class for system images
+#
+# Copyright 2007  Red Hat, Inc.
+# Mark McLoughlin <markmc redhat com>
+#
+# This software may be freely redistributed under the terms of the GNU
+# general public license.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+import os
+import shutil
+import subprocess
+import tempfile
+
+import Guest
+import ImageParser
+import CapabilitiesParser
+from DistroManager import URIImageFetcher
+
+class ImageInstallerException(Exception):
+    def __init__(self, msg):
+        Exception.__init__(self, msg)
+
+class ImageInstaller(Guest.Installer):
+    def __init__(self, type = "xen", location = None):
+        Guest.Installer.__init__(self, type, location)
+
+        self._imagedir = None
+        self._fetcher = None
+        self._meter = None
+
+    def _fetch_image_metadata(self):
+        if not self._fetcher.prepareLocation(self._meter):
+            raise ImageInstallerException("Invalid install location")
+
+        metadata = self._fetcher.acquireFile("image.xml", self._meter)
+        f = file(metadata, "r")
+        image_xml = f.read()
+        f.close()
+
+        return ImageParser.parse(image_xml)
+
+    def _choose_boot_option(self, guest, boot_options, need_bootdev):
+        capabilities = CapabilitiesParser.parse(guest.conn.getCapabilities())
+
+        found = False
+        for boot in boot_options:
+            for guest_caps in capabilities.guests:
+                if need_bootdev and boot.bootdev is None:
+                    continue
+
+                if guest_caps.os_type != boot.os_type or \
+                   guest_caps.arch != boot.arch:
+                    continue
+
+                if not guest_caps.hypervisor_type is None and \
+                   guest_caps.hypervisor_type != boot.hypervisor_type:
+                   continue
+
+                return boot
+
+        raise ImageInstallerException("Unable to find a suitable boot option")
+
+    def _prepare_boot(self, guest, boot):
+        if boot.kernel:
+            self.install["kernel"] = self._fetcher.acquireFile(boot.kernel, self._meter)
+            self.install["initrd"] = self._fetcher.acquireFile(boot.initrd, self._meter)
+
+            if boot.cmdline:
+                self.install["extraargs"] = boot.cmdline
+
+            if self.extraargs:
+                if self.install["extraargs"]:
+                    self.install["extraargs"] += " " + self.extraargs
+                else:
+                    self.install["extraargs"] = self.extraargs
+        else:
+            self.install["bootdev"] = boot.bootdev
+
+    def _prepare_volumes(self, guest, volumes):
+        volumes = volumes[:]
+        volumes.reverse()
+        for volume in volumes:
+            size = 0
+            path = os.path.join(self._imagedir, volume.file)
+
+            if not volume.size is None:
+                size = volume.size
+            else:
+                path = self._fetcher.acquireFile(volume.file, self._meter)
+
+            device = Guest.VirtualDisk.DEVICE_DISK
+            driver_name = driver_type = None
+
+            if volume.format == "qcow":
+                driver_name = "tap"
+                driver_type = "qcow"
+            elif volume.format == "cdrom":
+                device = Guest.VirtualDisk.DEVICE_CDROM
+
+            disk = Guest.VirtualDisk(path, size / 1024.0 / 1024.0,
+                                     device = device,
+                                     driverName = driver_name,
+                                     driverType = driver_type,
+                                     readOnly = volume.readonly)
+            disk.setup(self._meter)
+
+            if size:
+                if volume.filesystem == "ext3":
+                    subprocess.call(["mkfs.ext3", "-F", disk.path]) # -m 1 ?
+                elif volume.filesystem == "swap":
+                    subprocess.call(["mkswap", disk.path])
+
+            guest.disks.insert(0, disk)
+
+    def prepare(self, guest, need_bootdev, meter, distro = None):
+        self.cleanup()
+
+        self.install = {
+            "kernel" : "",
+            "initrd" : "",
+            "extraargs" : "",
+            "bootdev" : ""
+        }
+
+        self._imagedir = tempfile.mkdtemp(dir = "/var/lib/xen/images",
+                                          prefix = guest.name + "-")
+        self._fetcher = URIImageFetcher(self.location, self._imagedir, tempfiles = False)
+        self._meter = meter
+
+        try:
+            #
+            # FIXME: when we can rely on python-2.5 and its support for
+            #        try ... except ... finally, remove this inner try
+            #
+            try:
+                image = self._fetch_image_metadata()
+
+                boot = self._choose_boot_option(guest, image.boot_options, need_bootdev)
+
+                self._prepare_boot(guest, boot)
+                self._prepare_volumes(guest, image.volumes)
+            except:
+                shutil.rmtree(self._imagedir, ignore_errors = True)
+                raise
+        finally:
+            self._fetcher.cleanupLocation()
+            self._meter = None
+            self._fetcher = None
+            self._imagedir = None
+
+    def _get_osblob(self, install, hvm, arch = None, loader = None):
+        if install:
+            return None
+
+        osblob  = "<os>\n"
+
+        if hvm:
+            type = "hvm"
+        else:
+            type = "linux"
+
+        if arch:
+            osblob += "      <type arch='%s'>%s</type>\n" % (arch, type)
+        else:
+            osblob += "      <type>%s</type>\n" % type
+
+        if self.install["kernel"]:
+            osblob += "      <kernel>%s</kernel>\n"   % self.install["kernel"]
+            osblob += "      <initrd>%s</initrd>\n"   % self.install["initrd"]
+            osblob += "      <cmdline>%s</cmdline>\n" % self.install["extraargs"]
+        else:
+            if loader:
+                osblob += "      <loader>%s</loader>\n" % loader
+
+            osblob += "      <boot dev='%s'/>\n" % self.install["bootdev"]
+
+        osblob += "    </os>"
+
+        return osblob
+
+    def post_install_check(self, guest):
+        return True
+
Index: virtinst--devel/virtinst/ImageParser.py
===================================================================
--- /dev/null
+++ virtinst--devel/virtinst/ImageParser.py
@@ -0,0 +1,240 @@
+#!/usr/bin/python -tt
+#
+# An class for reading image metadata
+#
+# Copyright 2007  Red Hat, Inc.
+# Mark McLoughlin <markmc redhat com>
+#
+# This software may be freely redistributed under the terms of the GNU
+# general public license.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+import libxml2
+
+class ImageParserException(Exception):
+    def __init__(self, msg):
+        Exception.__init__(self, msg)
+
+class BootOption(object):
+    ARCHES = [ "i686", "x86_64" ]
+    HYPERVISOR_TYPES = [ "xen", "qemu", "kqemu", "kvm" ]
+    OS_TYPES = [ "hvm", "xen" ]
+    BOOTDEVS = [ "hd", "cdrom" ]
+
+    def __init__(self, node = None):
+        self._arch = None
+        self._hypervisor_type = None
+        self._os_type = None
+        self.kernel = None
+        self.initrd = None
+        self.cmdline = None
+        self._bootdev = None
+
+        if not node is None:
+            self.parseXML(node)
+
+    def get_arch(self):
+        return self._arch
+    def set_arch(self, arch):
+        if not arch in self.ARCHES:
+            raise ImageParserException("'%s' is not a supported architecture" % arch)
+        self._arch = arch
+    arch = property(get_arch, set_arch)
+
+    def get_hypervisor_type(self):
+        return self._hypervisor_type
+    def set_hypervisor_type(self, hypervisor_type):
+        if not hypervisor_type in self.HYPERVISOR_TYPES:
+            raise ImageParserException("'%s' is not a supported hypervisor type" % hypervisor_type)
+        self._hypervisor_type = hypervisor_type
+    hypervisor_type = property(get_hypervisor_type, set_hypervisor_type)
+
+    def get_os_type(self):
+        return self._os_type
+    def set_os_type(self, os_type):
+        if not os_type in self.OS_TYPES:
+            raise ImageParserException("'%s' is not a supported OS type" % os_type)
+        self._os_type = os_type
+    os_type = property(get_os_type, set_os_type)
+
+    def get_bootdev(self):
+        return self._bootdev
+    def set_bootdev(self, bootdev):
+        if not bootdev in self.BOOTDEVS:
+            raise ImageParserException("'%s' is not a supported bootdev" % bootdev)
+        self._bootdev = bootdev
+    bootdev = property(get_bootdev, set_bootdev)
+
+    def parseXML(self, node):
+        child = node.children
+        while child:
+            if child.name == "arch":
+                self.arch = child.content
+            elif child.name == "hypervisor_type":
+                self.hypervisor_type = child.content
+            elif child.name == "os_type":
+                self.os_type = child.content
+            elif child.name == "kernel":
+                self.kernel = child.content
+            elif child.name == "initrd":
+                self.initrd = child.content
+            elif child.name == "cmdline":
+                self.cmdline = child.content
+            elif child.name == "bootdev":
+                self.bootdev = child.content
+            child = child.next
+
+        if self.arch is None or self.os_type is None:
+            raise ImageParserException("<arch> and <os_type> are required <boot> elements")
+
+        if not self.kernel is None and self.initrd is None:
+            raise ImageParserException("An <initrd> is required with <kernel>")
+
+        if self.kernel is None and self.bootdev is None:
+            raise ImageParserException("A <kernel> or <bootdev> is required")
+
+class Volume(object):
+    TYPES = [ "system", "data", "scratch" ]
+    FORMATS = [ "raw", "cdrom", "qcow" ]
+    FILESYSTEMS = [ "ext3", "swap" ]
+
+    def __init__(self, node = None):
+        self.file = None
+        self._type = None
+        self._format = "raw"
+        self._filesystem = None
+        self._size = None
+        self._readonly = False
+
+        if not node is None:
+            self.parseXML(node)
+
+    def get_type(self):
+        return self._type
+    def set_type(self, type):
+        if not type in self.TYPES:
+            raise ImageParserException("'%s' is not a supported volume type" % type)
+        self._type = type
+    type = property(get_type, set_type)
+
+    def get_format(self):
+        return self._format
+    def set_format(self, format):
+        if not format in self.FORMATS:
+            raise ImageParserException("'%s' is not a supported volume format" % format)
+        self._format = format
+    format = property(get_format, set_format)
+
+    def get_filesystem(self):
+        return self._filesystem
+    def set_filesystem(self, filesystem):
+        if not filesystem in self.FILESYSTEMS:
+            raise ImageParserException("'%s' is not a supported filesystem" % filesystem)
+        self._filesystem = filesystem
+    filesystem = property(get_filesystem, set_filesystem)
+
+    # size is in kb
+    def get_size(self):
+        return self._size
+    def set_size(self, size):
+        try:
+            size = int(size)
+        except ValueError, e:
+            raise ImageParserException("'%s' is not a valid size : %s" % (size, e))
+        self._size = size
+    size = property(get_size, set_size)
+
+    def get_readonly(self):
+        return self._readonly
+    def set_readonly(self, readonly):
+        self._readonly = bool(readonly)
+    readonly = property(get_readonly, set_readonly)
+
+    def parseXML(self, node):
+        child = node.children
+        while child:
+            if child.name == "file":
+                self.file = child.content
+            elif child.name == "type":
+                self.type = child.content
+            elif child.name == "format":
+                self.format = child.content
+            elif child.name == "filesystem":
+                self.filesystem = child.content
+            elif child.name == "size":
+                self.size = child.content
+            elif child.name == "readonly":
+                self.readonly = True
+            child = child.next
+
+        if self.file is None or self.type is None:
+            raise ImageParserException("Volume elements <file> and <type> are required")
+
+        if not self.size is None and self.filesystem is None:
+            raise ImageParserException("Volume <filesystem> is required to create a volume")
+
+class Image(object):
+    def __init__(self, node = None):
+        self.name = None
+        self.boot_options = []
+        self.volumes = []
+
+        if not node is None:
+            self.parseXML(node)
+
+    def parseXML(self, node):
+        child = node.children
+        while child:
+            if child.name == "name":
+                self.name = child.content
+            if child.name == "boot_options":
+                n = child.children
+                while n:
+                    if n.name == "boot":
+                        self.boot_options.append(BootOption(n))
+                    n = n.next
+            elif child.name == "volumes":
+                n = child.children
+                while n:
+                    if n.name == "volume":
+                        self.volumes.append(Volume(n))
+                    n = n.next
+
+            child = child.next
+
+def parse(xml):
+    class ErrorHandler:
+        def __init__(self):
+            self.msg = ""
+        def handler(self, ctx, str):
+            self.msg += str
+    error = ErrorHandler()
+    libxml2.registerErrorHandler(error.handler, None)
+
+    try:
+        #
+        # FIXME: when we can rely on python-2.5 and its support for
+        #        try ... except ... finally, remove this inner try
+        #
+        try:
+            doc = libxml2.readMemory(xml, len(xml),
+                                     None, None,
+                                     libxml2.XML_PARSE_NOBLANKS)
+        except (libxml2.parserError, libxml2.treeError), e:
+            raise ImageParserException("%s\n%s" % (e, error.msg))
+    finally:
+        libxml2.registerErrorHandler(None, None)
+
+    try:
+        root = doc.getRootElement()
+        if root.name != "image":
+            raise ImageParserException("Root element is not 'image'")
+
+        image = Image(root)
+    finally:
+        doc.freeDoc()
+
+    return image
Index: virtinst--devel/virtinst/__init__.py
===================================================================
--- virtinst--devel.orig/virtinst/__init__.py
+++ virtinst--devel/virtinst/__init__.py
@@ -4,3 +4,4 @@ from FullVirtGuest import FullVirtGuest
 from ParaVirtGuest import ParaVirtGuest
 from DistroManager import DistroInstaller
 from LiveCDInstaller import LiveCDInstaller
+from ImageInstaller import ImageInstaller

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]