SHA256
1
0
forked from pool/xen
OBS User unknown
2007-02-11 10:48:10 +00:00
committed by Git OBS Bridge
parent 241ee9df04
commit d7002a96b9
166 changed files with 35454 additions and 7164 deletions

View File

@@ -1,39 +1,35 @@
#!/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.
Uses bootentry = [dev:]kernel[,initrd] to get a kernel [and initrd]
from a domU filesystem to boot it for Xen.
dev is the disk as seen by domU, filenames are relative to
that filesystem. The script uses the disk settings from the
config file to find the domU filesystems.
The bootentry is passed to the script using --entry=
Optionally, dev: can be omitted; the script then looks at the
root filesystem, parses /etc/fstab to resolve the path to the
kernel [and the initrd]. Obviously, the paths relative to the
domU root filesystem needs to be specified for the kernel
and initrd filenames.
The script uses kpartx (multipath-tools) to create mappings for devices that
are exported as whole disk devices that are partitioned.
The root FS is passed using --root=, the filesystem setup in
--disks=. The disks list is a python list
[[uname, dev, mode, backend], [uname, dev, mode, backend], ...]
passed as a string. The script writes an sxpr specifying the
locations of the copied kernel and initrd into the file
specified by --output (default is stdout).
Limitations:
- It is assumed both kernel and initrd 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@suse.de>
(c) 01/2006 Novell Inc
License: GNU GPL
Author: Kurt Garloff <garloff@suse.de>
"""
import os, sys, getopt
@@ -44,23 +40,21 @@ import time
# Global options
quiet = False
dryrun = False
verbose = False
dryrun = False
tmpdir = '/var/lib/xen/tmp'
# List of partitions
# It's created by setting up the all the devices from the xen disk
# config; every entry creates on Wholedisk object, which does necessary
# preparatory steps such as losetup and kpartx -a; then a Partition
# object is setup for every partition (which may be one or several per
# Wholedisk); it references the Wholedisk if needed; python reference
# counting will take care of the cleanup.
partitions = []
# 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"
"""Return the trailing digits, used to split the partition number off"""
idx = len(strg)-1
while strg[idx].isdigit():
if len == 0:
@@ -68,33 +62,49 @@ def traildigits(strg):
idx -= 1
return strg[idx+1:]
def isWholedisk(domUname):
"Determines whether dev is a wholedisk dev"
return not domUname[-1:].isdigit()
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()
def findPart(dev):
"Find device dev in list of partitions"
if len(dev) > 5 and dev[:5] == "/dev/":
dev = dev[5:]
for part in partitions:
if dev == part.domname:
return part
return None
class Wholedisk:
"Class representing a whole disk that has partitions"
def __init__(self, domname, physdev, loopfile = None):
"Class representing a whole disk that may have partitions"
def __init__(self, vdev, pdev):
"c'tor: set up"
self.domname = domname
self.physdev = physdev
self.loopfile = loopfile
self.mapped = 0
self.pcount = self.scanpartitions()
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):
"Setup the loop mapping"
if self.loopfile and not self.physdev:
"""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!
@@ -104,28 +114,29 @@ class Wholedisk:
if not os.path.exists(ldev):
break
i += 1
fd = os.popen("losetup %s %s 2> /dev/null" % (ldev, self.loopfile))
fd = os.popen("losetup %s %s 2> /dev/null" % (ldev, self.pdev))
if not fd.close():
if verbose:
print "domUloader: losetup %s %s" % (ldev, self.loopfile)
self.physdev = ldev
verbose_print("losetup %s %s" % (ldev, self.pdev))
self.ldev = ldev
break
if not self.physdev:
raise RuntimeError("domUloader: No free loop device found")
if not self.ldev:
raise RuntimeError("No free loop device found")
def loopclean(self):
"Delete the loop mapping"
if self.loopfile and self.physdev:
if verbose:
print "domUloader: losetup -d %s" % self.physdev
"""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.physdev)
fd = os.popen("losetup -d %s" % self.ldev)
if not fd.close():
self.physdev = None
self.ldev = None
break
else:
time.sleep(0.1)
@@ -137,25 +148,27 @@ class Wholedisk:
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)
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(':')
pno = int(traildigits(pname.strip()))
#if pname.rfind('/') != -1:
# pname = pname[pname.rfind('/')+1:]
#pname = self.physdev[:self.physdev.rfind('/')] + '/' + pname
#pname = self.pdev[:self.pdev.rfind('/')] + '/' + pname
pname = "/dev/mapper/" + pname
partitions.append(Partition(self, self.domname + '%i' % pno, 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.loopfile:
if self.ldev:
ref = self
else:
ref = None
partitions.append(Partition(ref, self.domname, self.physdev))
self.partitions.append(Partition(ref, self.vdev, self.pdev))
return pcount
def activatepartitions(self):
@@ -163,22 +176,23 @@ class Wholedisk:
if not self.mapped:
self.loopsetup()
if self.pcount:
if verbose:
print "domUloader: kpartx -a %s" % self.physdev
fd = os.popen("kpartx -a %s" % self.physdev)
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"
"""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:
if verbose:
print "domUloader: kpartx -d %s" % self.physdev
fd = os.popen("kpartx -d %s" % self.physdev)
verbose_print("kpartx -d %s" % self.physdev())
fd = os.popen("kpartx -d %s" % self.physdev())
fd.close()
self.loopclean()
@@ -189,24 +203,20 @@ class Wholedisk:
def __repr__(self):
"string representation for debugging"
strg = "[" + self.domname + ","
if self.physdev:
strg += self.physdev
strg += ","
if self.loopfile:
strg += self.loopfile
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, domname = None,
physdev = None):
def __init__(self, whole = None, vdev = None, pdev = None):
"c'tor: setup"
self.wholedisk = whole
self.domname = domname
self.physdev = physdev
self.wholedisk = whole
self.vdev = vdev
self.pdev = pdev
self.mountpoint = None
def __del__(self):
@@ -219,7 +229,7 @@ class Partition:
def __repr__(self):
"string representation for debugging"
strg = "[" + self.domname + "," + self.physdev + ","
strg = "[" + self.vdev + "," + self.pdev + ","
if self.mountpoint:
strg += "mounted on " + self.mountpoint + ","
else:
@@ -235,171 +245,69 @@ class Partition:
return
if self.wholedisk:
self.wholedisk.activatepartitions()
mtpt = tempfile.mkdtemp(prefix = "%s." % self.domname, dir = tmpdir)
mtpt = tempfile.mkdtemp(prefix = "%s." % self.vdev, dir = tmpdir)
mopts = ""
if fstype:
mopts += " -t %s" % fstype
mopts += " -o %s" % options
if verbose:
print "domUloader: mount %s %s %s" % (mopts, self.physdev, mtpt)
fd = os.popen("mount %s %s %s" % (mopts, self.physdev, mtpt))
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("domUloader: Error %i from mount %s %s on %s" % \
(err, mopts, self.physdev, mtpt))
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"
"""umount filesystem at self.mountpoint"""
if not self.mountpoint:
return
if verbose:
print "domUloader: umount %s" % self.mountpoint
verbose_print("umount %s" % self.mountpoint)
fd = os.popen("umount %s" % self.mountpoint)
err = fd.close()
os.rmdir(self.mountpoint)
try:
os.rmdir(self.mountpoint)
except:
pass
if err:
raise RuntimeError("domUloader: Error %i from umount %s" % \
(err, self.mountpoint))
self.mountpoint = None
error("Error %i from umount %s" % (err, self.mountpoint))
else:
self.mountpoint = None
if self.wholedisk:
self.wholedisk.deactivatepartitions()
def setupOneDisk(cfg):
"""Sets up one exported disk (incl. partitions if existing)
@param cfg: 4-tuple (uname, dev, mode, backend)"""
from xen.util.blkif import blkdev_uname_to_file
values = cfg[0].split(':')
if len(values) == 2:
(type, dev) = values
else:
(type, subtype, dev) = values
(loopfile, physdev) = (None, None)
if type == "tap":
if subtype == "aio":
# FIXME: if device, "/dev/" may need to be prepended
mode = os.stat(dev)[ST_MODE]
if S_ISBLK(mode):
physdev = dev
else:
loopfile = dev
else:
raise RuntimeError("domUloader: domUloader supports 'tap' only with 'aio'.")
if type == "file":
loopfile = dev
elif type == "phy":
physdev = blkdev_uname_to_file(cfg[0])
wdisk = Wholedisk(cfg[1], physdev, loopfile)
def setupDisks(vbds):
"""Create a list of all disks from the disk config:
@param vbds: The disk config as list of 4-tuples
(uname, dev, mode, backend)"""
disks = eval(vbds)
for disk in disks:
setupOneDisk(disk)
if verbose:
print "Partitions: " + str(partitions)
class Fstab:
"Class representing an fstab"
class FstabEntry:
"Class representing one fstab line"
def __init__(self, line):
"c'tor: parses one line"
spline = line.split()
self.dev, self.mtpt, self.fstype, self.opts = \
spline[0], spline[1], spline[2], spline[3]
if len(self.mtpt) > 1:
self.mtpt = self.mtpt.rstrip('/')
def __init__(self, filename):
"c'tor: parses fstab"
self.entries = []
fd = open(filename)
for line in fd.readlines():
line = line.strip()
if len(line) == 0 or line[0] == '#':
continue
self.entries.append(Fstab.FstabEntry(line))
def find(self, fname):
"Looks for matching filesystem in fstab"
matchlen = 0
match = None
fnmlst = fname.split('/')
for fs in self.entries:
entlst = fs.mtpt.split('/')
# '/' needs special treatment :-(
if entlst == ['','']:
entlst = ['']
entln = len(entlst)
if len(fnmlst) >= entln and fnmlst[:entln] == entlst \
and entln > matchlen:
match = fs
matchlen = entln
if not match:
return (None, None)
return (match.dev, match.mtpt)
def fsFromFstab(kernel, initrd, root):
"""Investigate rootFS fstab, check for filesystem that contains the kernel
and return it; also returns adapted kernel and initrd path.
"""
part = findPart(root)
if not part:
raise RuntimeError("domUloader: Root fs %s not exported?" % root)
part.mount()
if not os.access(part.mountpoint + '/etc/fstab', os.R_OK):
part.umount()
raise RuntimeError("domUloader: /etc/fstab not found on %s" % root)
fstab = Fstab(part.mountpoint + '/etc/fstab')
(dev, fs) = fstab.find(kernel)
if not fs:
raise RuntimeError("domUloader: no matching filesystem for image %s found in fstab" % kernel)
#return (None, kernel, initrd)
if fs == '/':
ln = 0
# this avoids the stupid /dev/root problem
dev = root
else:
ln = len(fs)
kernel = kernel[ln:]
if initrd:
initrd = initrd[ln:]
if verbose:
print "fsFromFstab: %s %s -- %s,%s" % (dev, fs, kernel, initrd)
return (kernel, initrd, dev)
def parseEntry(entry):
"disects bootentry and returns kernel, initrd, filesys"
fs = None
initrd = None
"disects bootentry and returns vdev, kernel, ramdisk"
def bad():
raise RuntimeError, "Malformed --entry"
fsspl = entry.split(':')
if len(fsspl) > 1:
fs = fsspl[0]
entry = fsspl[1]
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:
initrd = enspl[1]
if initrd[0] != '/':
initrd = '/' + initrd
return kernel, initrd, fs
ramdisk = enspl[1]
if ramdisk != '' and ramdisk[0] != '/':
ramdisk = '/' + ramdisk
return vdev, kernel, ramdisk
def copyFile(src, dst):
"Wrapper for shutil.filecopy"
import shutil
if verbose:
print "domUloader: cp %s %s" % (src, dst)
verbose_print("cp %s %s" % (src, dst))
stat = os.stat(src)
if stat.st_size > 16*1024*1024:
raise RuntimeError("domUloader: Too large file %s (%s larger than 16MB)" \
raise RuntimeError("Too large file %s (%s larger than 16MB)" \
% (src, stat.st_size))
try:
shutil.copyfile(src, dst)
@@ -407,36 +315,37 @@ def copyFile(src, dst):
os.unlink(dst)
raise()
def copyKernelAndInitrd(fs, kernel, initrd):
"""Finds fs in list of partitions, mounts the partition, copies
kernel [and initrd] off to dom0 files, umounts the parition again,
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 vmlinuz.dummy) (ramdisk initrd.dummy)"
import shutil
part = findPart(fs)
return "linux (kernel kernel.dummy) (ramdisk ramdisk.dummy)"
part = disk.findPart(vdev)
if not part:
raise RuntimeError("domUloader: Filesystem %s not exported\n" % fs)
raise RuntimeError("Partition '%s' does not exist" % vdev)
part.mount()
try:
(fd, knm) = tempfile.mkstemp(prefix = "vmlinuz.", dir = tmpdir)
(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, fs, knm)
print "Copy kernel %s from %s to %s for booting" % (kernel, vdev, knm)
sxpr = "linux (kernel %s)" % knm
if (initrd):
if ramdisk:
try:
(fd, inm) = tempfile.mkstemp(prefix = "initrd.", dir = tmpdir)
(fd, inm) = tempfile.mkstemp(prefix = "ramdisk.", dir = tmpdir)
os.close(fd)
copyFile(part.mountpoint + initrd, inm)
copyFile(part.mountpoint + ramdisk, inm)
except:
os.unlink(knm)
os.unlink(inm)
part.umount()
raise
sxpr += "(ramdisk %s)" % inm
part.umount()
@@ -448,46 +357,49 @@ def main(argv):
def usage():
"Help output (usage info)"
global verbose, quiet, dryrun
print >> sys.stderr, "domUloader usage: domUloader --disks=disklist [--root=rootFS]\n" \
+ " --entry=kernel[,initrd] [--output=fd] [--quiet] [--dryrun] [--verbose] [--help]\n"
print >> sys.stderr, "domUloader usage: domUloader [--output=fd] [--quiet] [--dryrun] [--verbose]\n" +\
"[--help] --entry=dev:kernel[,ramdisk] physdisk [virtdisk]\n"
print >> sys.stderr, __doc__
#print "domUloader " + str(argv)
try:
(optlist, args) = getopt.gnu_getopt(argv, 'qvh', \
('disks=', 'root=', 'entry=', 'output=',
'tmpdir=', 'help', 'quiet', 'dryrun', 'verbose'))
('entry=', 'output=', 'tmpdir=', 'help', 'quiet', 'dryrun', 'verbose'))
except:
usage()
sys.exit(1)
entry = None
output = None
root = None
disks = None
pdisk = None
vdisk = None
for (opt, oarg) in optlist:
if opt in ('-h', '--help'):
usage()
sys.exit(0)
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 == '--root':
root = oarg
elif opt == '--output':
output = oarg
elif opt == '--disks':
disks = oarg
elif opt == '--entry':
entry = oarg
elif opt == '--tmpdir':
tmpdir = oarg
if not entry or not disks:
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)
@@ -500,22 +412,34 @@ def main(argv):
os.mkdir(tmpdir)
os.chmod(tmpdir, 0750)
# We assume kernel and initrd are on the same FS,
# so only one fs
kernel, initrd, fs = parseEntry(entry)
setupDisks(disks)
if not fs:
if not root:
usage()
raise RuntimeError("domUloader: No root= to parse fstab and no disk in bootentry")
sys.exit(1)
kernel, initrd, fs = fsFromFstab(kernel, initrd, root)
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)
sxpr = copyKernelAndInitrd(fs, kernel, initrd)
sys.stdout.flush()
os.write(fd, sxpr)
r = 0
try:
sxpr = copyKernelAndRamdisk(disk, vdev, kernel, ramdisk)
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__":
main(sys.argv)
r = 1
try:
r = main(sys.argv)
except Exception, e:
error(str(e))
sys.exit(r)