WARNING - OLD ARCHIVES

This is an archived copy of the Xen.org mailing list, which we have preserved to ensure that existing links to archives are not broken. The live archive, which contains the latest emails, can be found at http://lists.xen.org/
   
 
 
Xen 
 
Home Products Support Community News
 
   
 

xen-cim

[Xen-cim] Python script problems, part 3 - known good domuloader

To: <xen-cim@xxxxxxxxxxxxxxxxxxx>
Subject: [Xen-cim] Python script problems, part 3 - known good domuloader
From: "Szymanski, Lukasz K" <Lukasz.Szymanski@xxxxxxxxxx>
Date: Tue, 3 Jul 2007 11:18:56 -0400
Delivery-date: Tue, 03 Jul 2007 08:16:44 -0700
Envelope-to: www-data@xxxxxxxxxxxxxxxxxx
List-help: <mailto:xen-cim-request@lists.xensource.com?subject=help>
List-id: xen-cim mailing list <xen-cim.lists.xensource.com>
List-post: <mailto:xen-cim@lists.xensource.com>
List-subscribe: <http://lists.xensource.com/cgi-bin/mailman/listinfo/xen-cim>, <mailto:xen-cim-request@lists.xensource.com?subject=subscribe>
List-unsubscribe: <http://lists.xensource.com/cgi-bin/mailman/listinfo/xen-cim>, <mailto:xen-cim-request@lists.xensource.com?subject=unsubscribe>
Sender: xen-cim-bounces@xxxxxxxxxxxxxxxxxxx
Thread-index: Ace020UFgXB/1oUYR0KDSVUx0P4WagIp+f/A
Thread-topic: Python script problems, part 3 - known good domuloader
 

-----Original Message-----
From: Jim Fehlig 
Sent: Friday, June 22, 2007 10:40 AM
To: Szymanski, Lukasz K
Subject: Re: Code dump

Szymanski, Lukasz K wrote:
> Jim -
>
> I traced down the bug and I seem to be getting stuck in domuloader.py.
> Can you send me your copy, just for kicks?
>   

OK, here is domUloader I'm using on my Xen 3.1.0 setup.  If this doesn't
work maybe we should try debugging this on the phone.  

Jim

#!/usr/bin/env python
# domUloader.py
"""Loader for kernel and (optional) ramdisk from domU filesystem

Given a physical disk (or disk image) for a domU and the path of a kernel and
optional ramdisk, copies the kernel and ramdisk from the domU disk to a
temporary location in dom0.

The --entry parameter specifies the location of the kernel (and optional
ramdisk) within the domU filesystem.  dev is the disk as seen by domU.
Filenames are relative to that filesystem.

The disk is passed as the last parameter.  It must be a block device or raw
disk image.  More complex disk images (QCOW, VMDK, etc) must already be
configured via blktap and presented as a block device.

The script writes an sxpr specifying the locations of the copied kernel and
ramdisk into the file specified by --output (default is stdout).

Limitations:
 - It is assumed both kernel and ramdisk are on the same filesystem.
 - domUs might use LVM; the script currently does not have support for setting
   up LVM mappings for domUs; it's not trivial and we might risk namespace
   conflicts. If you want to use LVM inside domUs, set up a small non-LVM boot
   partition and specify it in bootentry.
 
The script uses kpartx (multipath-tools) to create mappings for devices that
are exported as whole disk devices that are partitioned.

(c) 01/2006 Novell Inc
License: GNU GPL
Author: Kurt Garloff <garloff@xxxxxxx>
"""

import os, sys, getopt
from stat import *
from xen.xend import sxp
import tempfile
import time

# Global options
quiet = False
verbose = False
dryrun = False
tmpdir = '/var/lib/xen/tmp'
in_args = ''

# Helper functions

def error(s):
    print >> sys.stderr, "domUloader error: %s" % s

def verbose_print(s):
    if verbose:
        print >> sys.stderr, "domUloader: %s" % s

def traildigits(strg):
    """Return the trailing digits, used to split the partition number off"""
    idx = len(strg)-1
    while strg[idx].isdigit():
        if len == 0:
            return strg
        idx -= 1
    return strg[idx+1:]    

def getWholedisk(part):
    while len(part) and part[len(part)-1].isdigit():
        part = part[:-1]
    return part

#def isWholedisk(domUname):
#    """Determines whether dev is a wholedisk dev"""
#    return not domUname[-1:].isdigit()


class Wholedisk:
    "Class representing a whole disk that may have partitions"
    def __init__(self, vdev, pdev):
        "c'tor: set up"
        self.is_blk = (S_ISBLK(os.stat(pdev)[ST_MODE]))
        self.ldev = None
        self.vdev = vdev
        self.pdev = pdev
        self.mapped = 0
        self.partitions = []
        self.pcount = self.scanpartitions()

    def physdev(self):
        """Gets the physical device used to access the device from dom0"""
        if self.ldev:
            return self.ldev
        return self.pdev

    def findPart(self, vdev):
        "Find device dev in list of partitions"
        if len(vdev) > 5 and vdev[:5] == "/dev/":
            vdev = vdev[5:]
        for part in self.partitions:
            if vdev == part.vdev:
                return part
        return None

    def loopsetup(self):
        """Sets up the loop mapping for a disk image.

        Will raise if no loopbacks are available.
        """
        if not self.is_blk and not self.ldev:
            # Loops through all loopback devices, attempting to
            # find a free one to set up.  Don't scan for free and
            # then try to set it up as a separate step - too racy!
            i = 0
            while True:
                ldev = '/dev/loop%i' % (i)
                if not os.path.exists(ldev):
                    break
                i += 1
                fd = os.popen("losetup %s '%s' 2> /dev/null" % (ldev, 
self.pdev))
                if not fd.close():
                    verbose_print("losetup %s '%s'" % (ldev, self.pdev))
                    self.ldev = ldev
                    break
            if not self.ldev:
                raise RuntimeError("No free loop device found")

    def loopclean(self):
        """Delete the loop mapping.

        Will never raise.
        """
        if self.ldev:
            verbose_print("losetup -d %s" % self.ldev)
            # Even seemingly innocent queries like "losetup /dev/loop0"
            # can temporarily block the loopback and cause transient
            # failures deleting the loopback, hence the retry logic.
            retries = 10
            while retries:
                fd = os.popen("losetup -d %s" % self.ldev)
                if not fd.close():
                    self.ldev = None
                    break
                else:
                    time.sleep(0.1)
                    retries -= 1
    
    def scanpartitions(self):
        """Scan device for partitions (kpartx -l) and set up data structures,
           Returns number of partitions found."""
        self.loopsetup()
        # TODO: We could use fdisk -l instead and look at the type of
        #  partitions; this way we could also detect LVM and support it.
        fd = os.popen("kpartx -l '%s'" % self.physdev())
        pcount = 0
        for line in fd.readlines():
            line = line.strip()
            verbose_print("kpartx -l: %s" % (line,))
            (pname, params) = line.split(':')
            pname = pname.strip()
            pno = int(traildigits(pname))
            #if pname.rfind('/') != -1:
            #    pname = pname[pname.rfind('/')+1:]
            #pname = self.pdev[:self.pdev.rfind('/')] + '/' + pname
            pname = "/dev/mapper/" + pname
            verbose_print("Found partition: vdev %s, pdev %s" % ('%s%i' % 
(self.vdev, pno), pname))
            self.partitions.append(Partition(self, '%s%i' % (self.vdev, pno), 
pname))
            pcount += 1
        fd.close()
        if not pcount:
            if self.ldev:
                ref = self
            else:
                ref = None
            self.partitions.append(Partition(ref, self.vdev, self.pdev))
        return pcount

    def activatepartitions(self):
        "Set up loop mapping and device-mapper mappings"
        if not self.mapped:
            self.loopsetup()
            if self.pcount:
                verbose_print("kpartx -a '%s'" % self.physdev())
                fd = os.popen("kpartx -a '%s'" % self.physdev())
                fd.close()
        self.mapped += 1
    
    def deactivatepartitions(self):
        """Remove device-mapper mappings and loop mapping.

        Will never raise.
        """
        if not self.mapped:
            return
        self.mapped -= 1
        if not self.mapped:
            if self.pcount:
                verbose_print("kpartx -d '%s'" % self.physdev())
                fd = os.popen("kpartx -d '%s'" % self.physdev())
                fd.close()
            self.loopclean()

    def __del__(self):
        "d'tor: clean up"
        self.deactivatepartitions()
        self.loopclean()

    def __repr__(self):
        "string representation for debugging"
        strg = "[" + self.vdev + "," + self.pdev + ","
        if self.ldev:
            strg += self.ldev
        strg += "," + str(self.pcount) + ",mapped %ix]" % self.mapped
        return strg

class Partition:
    """Class representing a domU filesystem (partition) that can be
       mounted in dom0"""
    def __init__(self, whole = None, vdev = None, pdev = None):
        "c'tor: setup"
        self.wholedisk = whole
        self.vdev    = vdev
        self.pdev    = pdev
        self.mountpoint = None

    def __del__(self):
        "d'tor: cleanup"
        if self.mountpoint:
            self.umount()
        # Not needed: Refcounting will take care of it.
        #if self.wholedisk:
        #    self.wholedisk.deactivatepartitions()

    def __repr__(self):
        "string representation for debugging"
        strg = "[" + self.vdev + "," + self.pdev + ","
        if self.mountpoint:
            strg += "mounted on " + self.mountpoint + ","
        else:
            strg += "not mounted,"
        if self.wholedisk:
            return strg + self.wholedisk.__repr__() + "]"
        else:
            return strg + "]"

    def mount(self, fstype = None, options = "ro"):
        "mount filesystem, sets self.mountpoint"
        if self.mountpoint:
            return
        if self.wholedisk:
            self.wholedisk.activatepartitions()
        mtpt = tempfile.mkdtemp(prefix = "%s." % self.vdev, dir = tmpdir)
        mopts = ""
        if fstype:
            mopts += " -t %s" % fstype
        mopts += " -o %s" % options
        verbose_print("mount %s '%s' %s" % (mopts, self.pdev, mtpt))
        fd = os.popen("mount %s '%s' %s" % (mopts, self.pdev, mtpt))
        err = fd.close()
        if err:
            raise RuntimeError("Error %i from mount %s '%s' on %s" % \
                (err, mopts, self.pdev, mtpt))
        self.mountpoint = mtpt
        
    def umount(self):
        """umount filesystem at self.mountpoint"""
        if not self.mountpoint:
            return
        verbose_print("umount %s" % self.mountpoint)
        fd = os.popen("umount %s" % self.mountpoint)
        err = fd.close()
        try:
            os.rmdir(self.mountpoint)
        except:
            pass
        if err:
            error("Error %i from umount %s" % (err, self.mountpoint))
        else:
            self.mountpoint = None
        if self.wholedisk:
            self.wholedisk.deactivatepartitions()

def parseEntry(entry):
    "disects bootentry and returns vdev, kernel, ramdisk"
    def bad():
        raise RuntimeError, "Malformed --entry"
    fsspl = entry.split(':')
    if len(fsspl) != 2:
        bad()
    vdev = fsspl[0]
    entry = fsspl[1]
    enspl = entry.split(',')
    if len(enspl) not in (1, 2):
        bad()
    # Prepend '/' if missing
    kernel = enspl[0]
    if kernel == '':
        bad()
    if kernel[0] != '/':
        kernel = '/' + kernel
    ramdisk = None
    if len(enspl) > 1:
        ramdisk = enspl[1]
        if ramdisk != '' and ramdisk[0] != '/':
            ramdisk = '/' + ramdisk
    return vdev, kernel, ramdisk

def copyFile(src, dst):
    "Wrapper for shutil.filecopy"
    import shutil
    verbose_print("cp %s %s" % (src, dst))
    stat = os.stat(src)
    if stat.st_size > 16*1024*1024:
        raise RuntimeError("Too large file %s (%s larger than 16MB)" \
            % (src, stat.st_size)) 
    try:
        shutil.copyfile(src, dst)
    except:
        os.unlink(dst)
        raise()

def copyKernelAndRamdisk(disk, vdev, kernel, ramdisk):
    """Finds vdev in list of partitions, mounts the partition, copies
       kernel [and ramdisk] off to dom0 files, umounts the parition again,
       and returns sxpr pointing to these copies."""
    verbose_print("copyKernelAndRamdisk(%s, %s, %s, %s)" % (disk, vdev, kernel, 
ramdisk))
    if dryrun:
        return "linux (kernel kernel.dummy) (ramdisk ramdisk.dummy)"
    part = disk.findPart(vdev)
    if not part:
        raise RuntimeError("Partition '%s' does not exist" % vdev)
    part.mount()
    try:
        (fd, knm) = tempfile.mkstemp(prefix = "kernel.", dir = tmpdir)
        os.close(fd)
        copyFile(part.mountpoint + kernel, knm)
    except: 
        os.unlink(knm)
        part.umount()
        raise
    if not quiet:
        print "Copy kernel %s from %s to %s for booting" % (kernel, vdev, knm)
    sxpr = "linux (kernel %s)" % knm
    if ramdisk:
        try:
            (fd, inm) = tempfile.mkstemp(prefix = "ramdisk.", dir = tmpdir)
            os.close(fd)
            copyFile(part.mountpoint + ramdisk, inm)
        except:
            os.unlink(knm)
            os.unlink(inm)
            part.umount()
            raise
        sxpr += "(ramdisk %s)" % inm
    part.umount()
    return sxpr

def main(argv):
    "Main routine: Parses options etc."
    global quiet, dryrun, verbose, tmpdir, in_args
    def usage():
        "Help output (usage info)"
        global verbose, quiet, dryrun
        print >> sys.stderr, "domUloader usage: domUloader [--output=fd] 
[--quiet] [--dryrun] [--verbose]\n" +\
                             "[--args] [--help] --entry=dev:kernel[,ramdisk] 
physdisk [virtdisk]\n"
        print >> sys.stderr, __doc__

    try:
        (optlist, args) = getopt.gnu_getopt(argv, 'qvh', \
            ('entry=', 'output=', 'tmpdir=', 'args=', 'help', 'quiet', 
'dryrun', 'verbose'))
    except:
        usage()
        sys.exit(1)

    entry = None
    output = None
    pdisk = None
    vdisk = None

    for (opt, oarg) in optlist:
        if opt in ('-h', '--help'):
            usage()
            sys.exit(1)
        elif opt in ('-q', '--quiet'):
            quiet = True
        elif opt in ('-n', '--dryrun'):
            dryrun = True
        elif opt in ('-v', '--verbose'):
            verbose = True
        elif opt == '--output':
            output = oarg
        elif opt == '--entry':
            entry = oarg
        elif opt == '--tmpdir':
            tmpdir = oarg
        elif opt == '--args':
            in_args = oarg

    verbose_print(str(argv))

    if args:
        if len(args) == 2:
            pdisk = args[1]
        elif len(args) == 3:
            pdisk = args[1]
            vdisk = args[2]
    
    if not entry or not pdisk:
        usage()
        sys.exit(1)
    
    if output is None or output == "-":
        fd = sys.stdout.fileno()
    else:
        fd = os.open(output, os.O_WRONLY)
    
    if not os.access(tmpdir, os.X_OK):
        os.mkdir(tmpdir)
        os.chmod(tmpdir, 0750)

    vdev, kernel, ramdisk = parseEntry(entry)
    if not vdisk:
        vdisk = getWholedisk(vdev)
        verbose_print("vdisk not specified; guessing '%s' based on '%s'" % 
(vdisk, vdev))
    if not vdev.startswith(vdisk):
        error("Virtual disk '%s' does not match entry '%s'" % (vdisk, entry))
        sys.exit(1)
    disk = Wholedisk(vdisk, pdisk)
    
    r = 0
    try:
        sxpr = copyKernelAndRamdisk(disk, vdev, kernel, ramdisk)
        if in_args:
            sxpr += "(args '%s')" % in_args
        os.write(fd, sxpr)
    except Exception, e:
        error(str(e))
        r = 1

    for part in disk.partitions:
        part.wholedisk = None
    del disk

    return r

# Call main if called (and not imported)
if __name__ == "__main__":
    r = 1
    try:
        r = main(sys.argv)
    except Exception, e:
        error(str(e))
    sys.exit(r)
_______________________________________________
Xen-cim mailing list
Xen-cim@xxxxxxxxxxxxxxxxxxx
http://lists.xensource.com/xen-cim
<Prev in Thread] Current Thread [Next in Thread>
  • [Xen-cim] Python script problems, part 3 - known good domuloader, Szymanski, Lukasz K <=