[PATCH] virt-inst Package an image for VMware distribution

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

 



These patches were originally written by David Lutterkort and I've tweaked the conversion process a little bit to work out some staging issues

virt-pack will tar all the files for the image into a tarball. The name and toplevel directory are derived from the name and version of the image descriptor. Scratch disks are omitted, since they can be rebuilt when the image is deployed.

A VMware format configuration file is produced with vmdk descriptors pointing at the disk images rather than converting the raw images. For scratch disks, sparse VMDK files are generated, since VMWare has no notion of scratch disks. The generated VMX file is very conservative, so that it's widely usable, but there are many things that could be improved in it if we had more information about the image (one example: are vmware-tools installed ?)

The image.xml file must contain two new elements:   version and release

<name version="1" release="1">example-image</name>

Example running:

[jboggs@localhost RHX_Sugar]$ sudo ./virt-pack i1.xml
Checking /home/jboggs/RHX_Sugar/rhx_sugar.img
Writing /home/jboggs/RHX_Sugar/../example-image-1.tgz
example-image-1/
example-image-1/i1.xml
example-image-1/rhx_sugar.vmdk
example-image-1/rhx_sugar.img
example-image-1/example-image.vmx


When completed you will have an virtual instance capable of being run under VMWare or virt-image. Prior to conversion the image must contain VMWare disk controller drivers enabling it to boot, otherwise will result in a kernel panic: no init found.

<image>
  <name version="1" release="1">example-image</name>
  <label>Example Image</label>
  <description>
A simple example to demonstrate the virt-image XML format.
line 2
line3
</description>
  <domain>
    <boot type="xen">
      <guest>
        <arch>i686</arch>
        <features>
          <pae/>
        </features>
      </guest>
      <os>
        <loader>pygrub</loader>
      </os>
      <drive disk="rhx_sugar.img" target="xvda"/>
    </boot>
    <boot type="hvm">
      <guest>
        <arch>i686</arch>
        <features>
          <acpi state="off"/>
        </features>
      </guest>
      <os>
        <loader dev="hd"/>
      </os>
      <drive disk="rhx_sugar.img" target="hda"/>
    </boot>
    <devices>
      <vcpu>1</vcpu>
      <memory>262144</memory>
      <interface/>
      <graphics/>
    </devices>
  </domain>
  <storage>
    <disk file="rhx_sugar.img" use="system" format="raw"/>
  </storage>
</image>
Move abspath to ImageParser.Image

diff -r 03b6b3695d57 virtinst/ImageManager.py
--- a/virtinst/ImageManager.py	Wed Dec 05 15:57:06 2007 -0800
+++ b/virtinst/ImageManager.py	Wed Dec 05 16:11:34 2007 -0800
@@ -128,7 +128,7 @@ class ImageInstaller(Guest.Installer):
         return True
 
     def _abspath(self, p):
-        return os.path.abspath(os.path.join(self.image.base, p))
+        return self.image.abspath(p)
 
 class PlatformMatchException(Exception):
     def __init__(self, msg):
diff -r 03b6b3695d57 virtinst/ImageParser.py
--- a/virtinst/ImageParser.py	Wed Dec 05 15:57:06 2007 -0800
+++ b/virtinst/ImageParser.py	Wed Dec 05 16:11:34 2007 -0800
@@ -19,6 +19,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 # MA 02110-1301 USA.
 
+import os.path
 import libxml2
 import CapabilitiesParser
 from virtinst import _virtinst as _
@@ -40,6 +41,11 @@ class Image:
         self.release = None
         if not node is None:
             self.parseXML(node)
+
+    def abspath(self, p):
+        """Turn P into an absolute path. Relative paths are taken relative
+           to self.BASE"""
+        return os.path.abspath(os.path.join(self.base, p))
 
     def parseXML(self, node):
         self.name = xpathString(node, "name")
Rename Boot.disks to Boot.drives

In the XML, the corresponding element is called 'drive', not 'disk'

diff -r e4ccaec2fe8b virtinst/ImageManager.py
--- a/virtinst/ImageManager.py	Wed May 21 12:30:32 2008 -0700
+++ b/virtinst/ImageManager.py	Wed May 21 12:31:43 2008 -0700
@@ -81,7 +81,7 @@ class ImageInstaller(Guest.Installer):
                     guest.features[f] = False
 
     def _make_disks(self, guest):
-        for m in self.boot_caps.disks:
+        for m in self.boot_caps.drives:
             p = self._abspath(m.disk.file)
             s = None
             if m.disk.size is not None:
diff -r e4ccaec2fe8b virtinst/ImageParser.py
--- a/virtinst/ImageParser.py	Wed May 21 12:30:32 2008 -0700
+++ b/virtinst/ImageParser.py	Wed May 21 12:31:43 2008 -0700
@@ -76,7 +76,7 @@ class Image:
             raise ParserException(_("Expected exactly one 'domain' element"))
         # Connect the disk maps to the disk definitions
         for boot in self.domain.boots:
-            for d in boot.disks:
+            for d in boot.drives:
                 if not self.storage.has_key(d.disk_id):
                     raise ParserException(_("Disk entry for '%s' not found")
                                                % d.disk_id)
@@ -135,7 +135,7 @@ class Boot:
         self.kernel = None
         self.initrd = None
         self.cmdline = None
-        self.disks = []
+        self.drives = []
         self.arch = None
         self.features = ImageFeatures()
         if not node is None:
@@ -157,7 +157,7 @@ class Boot:
             self.features = ImageFeatures(fl[0])
 
         for d in node.xpathEval("drive"):
-            self.disks.append(Drive(d))
+            self.drives.append(Drive(d))
 
         validate(self.type is not None,
            "The boot type must be provided")
Store the image filename in the Image class

diff -r 695ef0255fe8 virt-image
--- a/virt-image	Fri Dec 07 11:43:54 2007 -0800
+++ b/virt-image	Fri Dec 07 14:34:48 2007 -0800
@@ -147,17 +147,6 @@ def parse_args():
     
     return options
 
-def parse_image_xml(fname):
-    if fname is None:
-        print >> sys.stderr, _("Must provide the location of an image XML file with --image")
-        sys.exit(1)
-    if not os.access(fname, os.R_OK):
-        print >> sys.stderr, _("Can not read %s") % fname
-    file = open(fname, "r")
-    xml = file.read()
-    file.close()
-    return virtinst.ImageParser.parse(xml, os.path.dirname(fname))
-
 def main():
     options = parse_args()
 
@@ -166,7 +155,7 @@ def main():
     conn = cli.getConnection(options.connect)
     type = None
 
-    image = parse_image_xml(options.image)
+    image = virtinst.ImageParser.parse_file(options.image)
     capabilities = virtinst.CapabilitiesParser.parse(conn.getCapabilities())
 
     if options.boot is not None:
diff -r 695ef0255fe8 virtinst/ImageParser.py
--- a/virtinst/ImageParser.py	Fri Dec 07 11:43:54 2007 -0800
+++ b/virtinst/ImageParser.py	Fri Dec 07 14:34:48 2007 -0800
@@ -30,10 +30,17 @@ class ParserException(Exception):
 
 class Image:
     """The toplevel object representing a VM image"""
-    def __init__(self, node = None, base = "."):
+    def __init__(self, node = None, base = None, filename = None):
         self.storage = {}
         self.domain = None
-        self.base = base
+        self.filename = os.path.abspath(filename)
+        if base is None:
+            if filename is not None:
+                self.base = os.path.dirname(filename)
+                if self.base == '' :
+                     self.base ="."
+            else:
+                self.base ="."
+        else:
+            self.base = base
         self.name = None
         self.label = None
         self.descr = None
@@ -217,7 +224,7 @@ def xpathString(node, path, default = No
         result = default
     return result
 
-def parse(xml, base):
+def parse(xml, filename):
     """Parse the XML description of a VM image into a data structure. Returns
     an object of class Image. BASE should be the directory where the disk
     image files for this image can be found"""
@@ -245,10 +252,15 @@ def parse(xml, base):
         if root.name != "image":
             raise ParserException(_("Root element is not 'image'"))
 
-        image = Image(root)
-        image.base = base
+        image = Image(root, filename = filename)
     finally:
         doc.freeDoc()
 
     return image
 
+def parse_file(filename):
+    file = open(filename, "r")
+    xml = file.read()
+    file.close()
+    return parse(xml, filename = filename)
+
Package/unpackage an image for distribution

Pack all the files for the image into a tarball. The name and toplevel 
directory are derived from the name and version of the image descriptor.
Scratch disks are omitted, since they can be rebuilt when the image is 
deployed.

To make things more interesting, information for running the image under
VMWare is also generated. This consists of a VMX file and VMDK descriptors 
for each disk in the image. For scratch disks, sparse VMDK files are
generated, since VMWare has no notion of scratch disks. The generated VMX
file is very conservative, so that it's widely usable, but there are many
things that could be improved in it if we had more information about the
image (one example: are vmware-tools installed ?)

diff -r 2f7a50a57c1c virt-pack
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/virt-pack	Wed Dec 12 17:53:53 2007 -0800
@@ -0,0 +1,149 @@
+#!/usr/bin/python -tt
+#
+# Package and unpackage images for distribution
+#
+# Copyright 2007  Red Hat, Inc.
+# David Lutterkort <dlutter@xxxxxxxxxx>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301 USA.
+
+import os, sys, string
+from optparse import OptionParser, OptionValueError
+import subprocess
+import logging
+import libxml2
+import urlgrabber.progress as progress
+
+import virtinst
+import virtinst.ImageParser
+import virtinst.CapabilitiesParser
+import virtinst.cli as cli
+import virtinst.util as util
+import virtinst.UnWare
+
+import tempfile
+
+import gettext
+import locale
+
+locale.setlocale(locale.LC_ALL, '')
+gettext.bindtextdomain(virtinst.gettext_app, virtinst.gettext_dir)
+gettext.install(virtinst.gettext_app, virtinst.gettext_dir)
+
+class PackageException(Exception):
+    def __init__(self, msg):
+        Exception.__init__(self, msg)
+
+class Package:
+    
+    def __init__(self, image):
+        self.image = image
+        if image.name is None or image.version is None:
+            raise PackageException(
+                _("The image name and version must be present"))
+        self.vername = "%s-%s" % (image.name, image.version)
+        self.tmpdir = tempfile.mkdtemp(dir="/var/tmp", prefix="virt-pack")
+        self.stagedir = os.path.join(self.tmpdir, self.vername)
+        self.files = []
+
+    def add_image_files(self):
+        cwd = os.getcwd()
+        img = self.image
+        self.files.append(os.path.basename(img.filename))
+        try:
+            os.chdir(img.base)
+            for d in img.storage.keys():
+                disk = img.storage[d]
+                if disk.use == disk.USE_SCRATCH:
+                    if disk.size is None:
+                        raise PackageException(_("Scratch disk %s does not have a size attribute") % disk.id)
+                else:
+                    if not os.path.exists(disk.file):
+                        raise PackageException(_("Disk file %s could not be found") % disk.id)
+                    self.files.append(disk.file)
+        finally:
+            os.chdir(cwd)
+
+    def make_vmx_files(self):
+        img = virtinst.UnWare.Image(self.image)
+        files = img.make(self.image.base)
+        self.files.extend(files)
+
+    def pack(self, outdir):
+        outfile = os.path.join(outdir, self.vername + ".tgz")
+        for f in set(self.files):
+            dir = os.path.join(self.stagedir, os.path.dirname(f))
+            if not os.path.exists(dir):
+                os.makedirs(dir)
+            os.symlink(os.path.join(self.image.base, f),
+                       os.path.join(self.stagedir, f))
+        print "Writing %s" % outfile
+        cmd = "tar chzv -C %s -f %s %s" % (self.tmpdir, 
+                                           outfile, 
+                                           os.path.basename(self.vername))
+        util.system(cmd)
+        return outfile
+
+### Option parsing
+def parse_args():
+    parser = OptionParser()
+    parser.set_usage("%prog [options] image.xml")
+
+    parser.add_option("-o", "--output", type="string", dest="output",
+                      action="callback", callback=cli.check_before_store,
+                      help=_("Directory in which packaged file will be put"))
+    parser.add_option("-d", "--debug", action="store_true", dest="debug",
+                      help=_("Print debugging information"))
+
+    (options,args) = parser.parse_args()
+    if len(args) < 1:
+        parser.error(_("You need to provide an image XML descriptor"))
+    options.image = args[0]
+    
+    return options
+
+def main():
+    options = parse_args()
+
+
+    cli.setupLogging("virt-pack", options.debug)
+
+    image = virtinst.ImageParser.parse_file(os.path.abspath(options.image))
+
+    if image.name is None or image.version is None:
+        print >> sys.stderr, _("The image descriptor must contain name and version")
+        valid = False
+
+    if options.output is None:
+        options.output = os.path.join(image.base, "..")
+
+    pkg = Package(image)
+    try:
+        pkg.add_image_files()
+    except PackageException, e:
+        print >> sys.stderr, "Validation failed: %s", e
+        return 1
+
+    try:
+        pkg.make_vmx_files()
+        pkg.pack(options.output)
+    except PackageException, e:
+        print >> sys.stderr, "Packaging failed: %s" % e
+        return 1
+
+if __name__ == "__main__":
+    main()
+
diff -r 2f7a50a57c1c virtinst/UnWare.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/virtinst/UnWare.py	Wed Dec 12 17:53:53 2007 -0800
@@ -0,0 +1,291 @@
+#
+# Processing of VMWare(tm) .vmx files
+#
+# Copyright 2007  Red Hat, Inc.
+# David Lutterkort <dlutter@xxxxxxxxxx>
+# Joey Boggs <jboggs@xxxxxxxxxx>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free  Software Foundation; either version 2 of the License, or
+# (at your option)  any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301 USA.
+
+import time
+import sys
+import os
+
+import ImageParser
+import util
+
+class Disk:
+    """A disk for a VMWare(tm) virtual machine"""
+
+    MONOLITHIC_FLAT   = "monolithicFlat"
+    TWO_GB_MAX_EXTENT_SPARSE = "twoGbMaxExtentSparse"
+    # This seems only to be usable if the vmdk header is embedded in the
+    # data file, not when the descriptor is in a separate text file. Use
+    # TWO_GB_MAX_EXTENT_SPARSE instead.
+    # VMWare's(tm) documentation of VMDK seriously sucks. A lot.
+    MONOLITHIC_SPARSE = "monolithicSparse"
+
+    IDE_HEADS = 16
+    IDE_SECTORS = 63
+    
+    def __init__(self, descriptor, extent, size, dev, format):
+        """Create a new disk. DESCRIPTOR is the name of the VMDK descriptor
+        file. EXTENT is the name of the file holding the actual data. SIZE
+        is the filesize in bytes. DEV identifies the device, for IDE (the
+        only one supported right now) it should be $bus:$master. FORMAT is
+        the format of the underlying extent, one of the formats defined in
+        virtinst.ImageParser.Disk"""
+        self.cid = 0xffffffff
+        self.createType = Disk.MONOLITHIC_FLAT
+        self.descriptor = descriptor
+        self.extent = extent
+        self.size = size
+        self.dev = dev
+        self.format = format
+
+    def make_extent(self, base):
+        """Write the descriptor file, and create the extent as a monolithic 
+        sparse extent if it does not exist yet"""
+        f = os.path.join(base, self.extent)
+        print "Checking %s" % f
+        if not os.path.exists(f):
+            util.system("qemu-img create -f vmdk %s %d" % (f, self.size/1024))
+            self.createType = Disk.TWO_GB_MAX_EXTENT_SPARSE
+        else:
+            qemu = os.popen("qemu-img info %s" % f, "r")
+            for l in qemu:
+                (tag, val) = l.split(":")
+                if tag == "file format" and val.strip() == "vmdk":
+                    self.createType = Disk.TWO_GB_MAX_EXTENT_SPARSE
+            qemu.close()
+        return self.extent
+        
+    def to_vmdk(self):
+        """Return the VMDK descriptor for this disk"""
+
+        vmdk = """# Disk DescriptorFile
+# Generated from libvirt
+version=1
+""" 
+        vmdk += "CID=%08x\nparentCID=ffffffff\n" % self.cid
+        vmdk += "createType=\"%s\"\n\n" % self.createType
+        vmdk += "# Extent description\n"
+        blocks = self.size/512
+        if self.createType == Disk.MONOLITHIC_FLAT:
+            vmdk += "RW %d FLAT \"%s\" 0\n" % (blocks, os.path.basename(self.extent))
+        else: # Disk.MONOLITHIC_SPARSE
+            vmdk += "RW %d SPARSE \"%s\"\n" % (blocks, os.path.basename(self.extent))
+
+        vmdk += """
+# Disk Data Base
+ddb.virtualHWVersion = "4"
+ddb.adapterType = \"ide\"
+ddb.geometry.sectors = \"%d\"
+ddb.geometry.heads = \"%d\"
+ddb.geometry.cylinders = \"%d\"
+""" % (Disk.IDE_SECTORS, Disk.IDE_HEADS, blocks/(Disk.IDE_SECTORS*Disk.IDE_HEADS))
+        return vmdk
+
+    def to_vmx(self):
+        """Return the fragment for the VMX file for this disk"""
+
+        vmx = ""
+        dict = {
+            "dev"      : self.dev,
+            "disk_filename" : self.descriptor
+        }
+        if self.format == ImageParser.Disk.FORMAT_ISO:
+            vmx = _VMX_ISO_TEMPLATE % dict
+        else:   # FORMAT_RAW
+            vmx = _VMX_IDE_TEMPLATE % dict
+        return vmx
+        
+class Image:
+    """Represent an image for generation of a VMWare(tm) description"""
+
+    def __init__(self, image = None):
+        if image is not None:
+            self._init_from_image(image)
+
+    def _init_from_image(self, image):
+        domain = image.domain
+        boot = domain.boots[0]
+
+        self.base = image.base
+        self.name = image.name
+        self.descr = image.descr
+        self.label = image.label
+        self.vcpu = domain.vcpu
+        self.memory = domain.memory
+        self.interface = domain.interface
+
+        self.disks = []
+        for d in boot.drives:
+            disk = d.disk
+            descriptor = sub_ext(disk.file, ".vmdk")
+            if disk.size is None:
+                f = os.path.join(image.base, disk.file)
+                size = os.stat(f).st_size
+            else:
+                size = long(disk.size) * 1024L * 1024L
+            ide_count = len(self.disks)
+            dev = "%d:%d" % (ide_count / 2, ide_count % 2)
+            self.disks.append(Disk(descriptor, disk.file, size, dev, 
+                                   disk.format))
+
+    def make(self, base):
+        """Write the descriptor file and all the disk descriptors"""
+        files = []
+        out = open(os.path.join(self.base, self.name + ".vmx"), "w")
+        out.write(self.to_vmx())
+        out.close()
+        files.append(self.name + ".vmx")
+
+        for d in self.disks:
+            f = d.make_extent(self.base)
+            files.append(f)
+            out = open(os.path.join(base, d.descriptor), "w")
+            out.write(d.to_vmdk())
+            out.close()
+            files.append(d.descriptor)
+        return files
+
+    def to_vmx(self):
+        """Return the VMX description of this image"""
+        # Strip blank spaces and EOL to prevent syntax errors in vmx file
+        self.descr = self.descr.strip()
+        self.descr = self.descr.replace("\n","|")
+
+        dict = {
+            "now": time.strftime("%Y-%m-%dT%H:%M:%S %Z", time.localtime()),
+            "progname": os.path.basename(sys.argv[0]),
+            "/image/name": self.name,
+            "/image/description": self.descr or "None",
+            "/image/label": self.label or self.name,
+            "/image/devices/vcpu" : self.vcpu,
+            "/image/devices/memory": long(self.memory)/1024
+        }
+
+        vmx = _VMX_MAIN_TEMPLATE % dict
+        if self.interface:
+            vmx += _VMX_ETHER_TEMPLATE
+
+        for d in self.disks:
+            vmx += d.to_vmx()
+
+        return vmx
+            
+def sub_ext(filename, ext):
+    return os.path.splitext(filename)[0] + ext
+
+_VMX_MAIN_TEMPLATE = """
+#!/usr/bin/vmplayer
+
+# Generated %(now)s by %(progname)s
+# http://virt-manager.et.redhat.com/
+
+# This is a Workstation 5 or 5.5 config file
+# It can be used with Player
+config.version = "8"
+virtualHW.version = "4"
+
+# Selected operating system for your virtual machine
+guestOS = "other"
+
+# displayName is your own name for the virtual machine
+displayName = "%(/image/name)s"
+
+# These fields are free text description fields
+annotation = "%(/image/description)s"
+guestinfo.vmware.product.long = "%(/image/label)s"
+guestinfo.vmware.product.url = "http://virt-manager.et.redhat.com/";
+guestinfo.vmware.product.class = "virtual machine"
+
+# Number of virtual CPUs. Your virtual machine will not
+# work if this number is higher than the number of your physical CPUs
+numvcpus = "%(/image/devices/vcpu)s"
+
+# Memory size and other memory settings
+memsize = "%(/image/devices/memory)d"
+MemAllowAutoScaleDown = "FALSE"
+MemTrimRate = "-1"
+
+# Unique ID for the virtual machine will be created
+uuid.action = "create"
+
+## For appliances where tools are installed already, this should really
+## be false, but we don't have that ionfo in the metadata
+# Remind to install VMware Tools
+# This setting has no effect in VMware Player
+tools.remindInstall = "TRUE"
+
+# Startup hints interfers with automatic startup of a virtual machine
+# This setting has no effect in VMware Player
+hints.hideAll = "TRUE"
+
+# Enable time synchronization between computer
+# and virtual machine
+tools.syncTime = "TRUE"
+
+# First serial port, physical COM1 is not available
+serial0.present = "FALSE"
+
+# Optional second serial port, physical COM2 is not available
+serial1.present = "FALSE"
+
+# First parallell port, physical LPT1 is not available
+parallel0.present = "FALSE"
+
+# Logging
+# This config activates logging, and keeps last log
+logging = "TRUE"
+log.fileName = "%(/image/name)s.log"
+log.append = "TRUE"
+log.keepOld = "3"
+
+# These settings decides interaction between your
+# computer and the virtual machine
+isolation.tools.hgfs.disable = "FALSE"
+isolation.tools.dnd.disable = "FALSE"
+isolation.tools.copy.enable = "TRUE"
+isolation.tools.paste.enabled = "TRUE"
+
+# Settings for physical floppy drive
+floppy0.present = "FALSE"
+"""
+
+_VMX_ETHER_TEMPLATE = """
+## if /image/devices/interface is present:
+# First network interface card
+ethernet0.present = "TRUE"
+ethernet0.connectionType = "nat"
+ethernet0.addressType = "generated"
+ethernet0.generatedAddressOffset = "0"
+ethernet0.autoDetect = "TRUE"
+"""
+
+_VMX_ISO_TEMPLATE = """
+# CDROM drive
+ide%(dev)s.present = "TRUE"
+ide%(dev)s.deviceType = "cdrom-raw"
+ide%(dev)s.startConnected = "TRUE"
+ide%(dev)s.fileName = "%(disk_filename)s"
+ide%(dev)s.autodetect = "TRUE"
+"""
+
+_VMX_IDE_TEMPLATE = """
+# IDE disk
+ide%(dev)s.present = "TRUE"
+ide%(dev)s.fileName = "%(disk_filename)s"
+ide%(dev)s.mode = "persistent"
+ide%(dev)s.startConnected = "TRUE"
+ide%(dev)s.writeThrough = "TRUE"
+"""
diff -r 2f7a50a57c1c virtinst/util.py
--- a/virtinst/util.py	Wed Dec 12 15:05:47 2007 -0800
+++ b/virtinst/util.py	Wed Dec 12 17:53:53 2007 -0800
@@ -206,3 +206,9 @@ def get_phy_cpus(conn):
     hostinfo = conn.getInfo()
     pcpus = hostinfo[4] * hostinfo[5] * hostinfo[6] * hostinfo[7]
     return pcpus
+
+def system(cmd):
+    st = os.system(cmd)
+    if os.WIFEXITED(st) and os.WEXITSTATUS(st) != 0:
+        raise OSError("Failed to run %s, exited with %d" % 
+                      (cmd, os.WEXITSTATUS(st)))
version.patch
abspath.patch
filename.patch
drives.patch
package.patch
Add version information to image.xml
* * *
Add release attribute to name tag

The ImageParser has been accepting the release attribute for a while, but
that was not reflected in the RelaxNG grammar.

diff -r 4776c61e0fa7 doc/image.rng
--- a/doc/image.rng	Thu May 15 16:13:55 2008 -0400
+++ b/doc/image.rng	Thu May 22 15:48:20 2008 -0700
@@ -8,7 +8,15 @@
          storage backing the machine -->
     <element name="image">
       <!-- A machine-usable name for this image -->
-      <element name="name"><ref name="genericName"/></element>
+      <element name="name">
+        <optional>
+          <attribute name="version"><ref name="verString"/></attribute>
+        </optional>
+        <optional>
+          <attribute name="release"><ref name="verString"/></attribute>
+        </optional>
+        <ref name="genericName"/>
+      </element>
       <!--  A human-readable label and description, mostly
             to support UI's -->
       <optional>
@@ -246,4 +254,9 @@
       <param name="pattern">[a-zA-Z0-9_\-:./]+</param>
     </data>
   </define>
+  <define name="verString">
+    <data type="string">
+      <param name="pattern">[0-9\.]+(-[0-9\.]+)?</param>
+    </data>
+  </define>
 </grammar>
diff -r 4776c61e0fa7 virtinst/ImageParser.py
--- a/virtinst/ImageParser.py	Thu May 15 16:13:55 2008 -0400
+++ b/virtinst/ImageParser.py	Thu May 22 15:48:20 2008 -0700
@@ -36,6 +36,8 @@ class Image:
         self.name = None
         self.label = None
         self.descr = None
+        self.version = None
+        self.release = None
         if not node is None:
             self.parseXML(node)
 
@@ -43,6 +45,8 @@ class Image:
         self.name = xpathString(node, "name")
         self.label = xpathString(node, "label")
         self.descr = xpathString(node, "description")
+        self.version = xpathString(node, "name/@version")
+        self.release = xpathString(node, "name/@release")
         for d in node.xpathEval("storage/disk"):
             disk = Disk(d)
             if disk.file is None:
_______________________________________________
et-mgmt-tools mailing list
et-mgmt-tools@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/et-mgmt-tools

[Index of Archives]     [Fedora Users]     [Fedora Legacy List]     [Fedora Maintainers]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]

  Powered by Linux