Files
xen/tools/python/xen/xend/image.py
T
emellor@leeni.uk.xensource.com 2b99d6e3a9 Take advantage of the new UUID (handle) stored for us in Xen to improve the
recreation semantics.

Remove the unpause at the end of xc_linux_restore, and move it to
XendDomainInfo.  This is necessary because xenstored now allocates the domain
path when the domain is introduced, which means that the new domain cannot
start running until that introduce is performed and the new devices
configured.

Give restore a separate completion phase in which domain details are stored.
This is required because the domain path is no longer available until after
the introduceDomain call.

TODO: Split the domain introduction into two so that the domain path is
available earlier.  At the moment, the domain <-> store channel details are
passed in to xenstored when the domain is introduced, but in the case of
restore it is necessary to wait until the restore is completed before the
channel MFN is available.

Change the interface between XendDomainInfo and XendCheckpoint/image to not
have hideous callbacks through setConsoleRef and setStoreRef.  Instead,
image.createImage explicitly returns those values, and XendCheckpoint passes
them through to completeRestore.

Move the purging of the domain path corresponding to a new domain from Xend
to xenstored, since xenstored is now in charge of this path.  With the domain
path creation moved to xenstored, Xend cannot remove the path, because watches
may have fired on it already.

Fix the printf statement in xenstored in verbose mode that details the
messages being written.  This statement was printing the buffer using %s, but
this buffer has an explicit length field, so we were seeing garbage after the
correct details.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
2005-10-19 11:47:51 +01:00

388 lines
13 KiB
Python

#============================================================================
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#============================================================================
# Copyright (C) 2005 Mike Wray <mike.wray@hp.com>
# Copyright (C) 2005 XenSource Ltd
#============================================================================
import os, string
import re
import xen.lowlevel.xc
from xen.xend import sxp
from xen.xend.XendError import VmError
from xen.xend.XendLogging import log
xc = xen.lowlevel.xc.new()
MAX_GUEST_CMDLINE = 1024
def create(vm, imageConfig, deviceConfig):
"""Create an image handler for a vm.
@return ImageHandler instance
"""
return findImageHandlerClass(imageConfig)(vm, imageConfig, deviceConfig)
class ImageHandler:
"""Abstract base class for image handlers.
createImage() is called to configure and build the domain from its
kernel image and ramdisk etc.
The method buildDomain() is used to build the domain, and must be
defined in a subclass. Usually this is the only method that needs
defining in a subclass.
The method createDeviceModel() is called to create the domain device
model if it needs one. The default is to do nothing.
The method destroy() is called when the domain is destroyed.
The default is to do nothing.
"""
ostype = None
def __init__(self, vm, imageConfig, deviceConfig):
self.vm = vm
self.kernel = None
self.ramdisk = None
self.cmdline = None
self.configure(imageConfig, deviceConfig)
def configure(self, imageConfig, _):
"""Config actions common to all unix-like domains."""
def get_cfg(name, default = None):
return sxp.child_value(imageConfig, name, default)
self.kernel = get_cfg("kernel")
self.cmdline = ""
ip = get_cfg("ip")
if ip:
self.cmdline += " ip=" + ip
root = get_cfg("root")
if root:
self.cmdline += " root=" + root
args = get_cfg("args")
if args:
self.cmdline += " " + args
self.ramdisk = get_cfg("ramdisk", '')
self.vm.storeVm(("image/ostype", self.ostype),
("image/kernel", self.kernel),
("image/cmdline", self.cmdline),
("image/ramdisk", self.ramdisk))
def handleBootloading(self):
self.unlink(self.kernel)
self.unlink(self.ramdisk)
def unlink(self, f):
if not f: return
try:
os.unlink(f)
except OSError, ex:
log.warning("error removing bootloader file '%s': %s", f, ex)
def createImage(self):
"""Entry point to create domain memory image.
Override in subclass if needed.
"""
return self.createDomain()
def createDomain(self):
"""Build the domain boot image.
"""
# Set params and call buildDomain().
if not os.path.isfile(self.kernel):
raise VmError('Kernel image does not exist: %s' % self.kernel)
if self.ramdisk and not os.path.isfile(self.ramdisk):
raise VmError('Kernel ramdisk does not exist: %s' % self.ramdisk)
if len(self.cmdline) >= MAX_GUEST_CMDLINE:
log.warning('kernel cmdline too long, domain %d',
self.vm.getDomid())
log.info("buildDomain os=%s dom=%d vcpus=%d", self.ostype,
self.vm.getDomid(), self.vm.getVCpuCount())
result = self.buildDomain()
if isinstance(result, dict):
return result
else:
raise VmError('Building domain failed: ostype=%s dom=%d err=%s'
% (self.ostype, self.vm.getDomid(), str(result)))
def getDomainMemory(self, mem):
"""@return The memory required, in KiB, by the domain to store the
given amount, also in KiB. This is normally just mem, but VMX domains
have overheads to account for."""
return mem
def buildDomain(self):
"""Build the domain. Define in subclass."""
raise NotImplementedError()
def createDeviceModel(self):
"""Create device model for the domain (define in subclass if needed)."""
pass
def destroy(self):
"""Extra cleanup on domain destroy (define in subclass if needed)."""
pass
class LinuxImageHandler(ImageHandler):
ostype = "linux"
def buildDomain(self):
store_evtchn = self.vm.getStorePort()
console_evtchn = self.vm.getConsolePort()
log.debug("dom = %d", self.vm.getDomid())
log.debug("image = %s", self.kernel)
log.debug("store_evtchn = %d", store_evtchn)
log.debug("console_evtchn = %d", console_evtchn)
log.debug("cmdline = %s", self.cmdline)
log.debug("ramdisk = %s", self.ramdisk)
log.debug("vcpus = %d", self.vm.getVCpuCount())
return xc.linux_build(dom = self.vm.getDomid(),
image = self.kernel,
store_evtchn = store_evtchn,
console_evtchn = console_evtchn,
cmdline = self.cmdline,
ramdisk = self.ramdisk)
class VmxImageHandler(ImageHandler):
ostype = "vmx"
def configure(self, imageConfig, deviceConfig):
ImageHandler.configure(self, imageConfig, deviceConfig)
self.dmargs = self.parseDeviceModelArgs(imageConfig, deviceConfig)
self.device_model = sxp.child_value(imageConfig, 'device_model')
if not self.device_model:
raise VmError("vmx: missing device model")
self.display = sxp.child_value(imageConfig, 'display')
self.vm.storeVm(("image/dmargs", " ".join(self.dmargs)),
("image/device-model", self.device_model),
("image/display", self.display))
self.device_channel = None
self.pid = 0
self.dmargs += self.configVNC(imageConfig)
def buildDomain(self):
# Create an event channel
self.device_channel = xc.evtchn_alloc_unbound(dom=self.vm.getDomid(),
remote_dom=0)
log.info("VMX device model port: %d", self.device_channel)
store_evtchn = self.vm.getStorePort()
log.debug("dom = %d", self.vm.getDomid())
log.debug("image = %s", self.kernel)
log.debug("control_evtchn = %d", self.device_channel)
log.debug("store_evtchn = %d", store_evtchn)
log.debug("memsize = %d", self.vm.getMemoryTarget() / 1024)
log.debug("vcpus = %d", self.vm.getVCpuCount())
return xc.vmx_build(dom = self.vm.getDomid(),
image = self.kernel,
control_evtchn = self.device_channel,
store_evtchn = store_evtchn,
memsize = self.vm.getMemoryTarget() / 1024,
vcpus = self.vm.getVCpuCount())
# Return a list of cmd line args to the device models based on the
# xm config file
def parseDeviceModelArgs(self, imageConfig, deviceConfig):
dmargs = [ 'cdrom', 'boot', 'fda', 'fdb', 'ne2000',
'localtime', 'serial', 'stdvga', 'isa', 'vcpus' ]
ret = []
for a in dmargs:
v = sxp.child_value(imageConfig, a)
# python doesn't allow '-' in variable names
if a == 'stdvga': a = 'std-vga'
if a == 'ne2000': a = 'nic-ne2000'
# Handle booleans gracefully
if a in ['localtime', 'std-vga', 'isa', 'nic-ne2000']:
if v != None: v = int(v)
log.debug("args: %s, val: %s" % (a,v))
if v:
ret.append("-%s" % a)
ret.append("%s" % v)
# Handle disk/network related options
for (name, info) in deviceConfig:
if name == 'vbd':
uname = sxp.child_value(info, 'uname')
typedev = sxp.child_value(info, 'dev')
(_, vbdparam) = string.split(uname, ':', 1)
if re.match('^ioemu:', typedev):
(emtype, vbddev) = string.split(typedev, ':', 1)
else:
emtype = 'vbd'
vbddev = typedev
if emtype != 'ioemu':
continue;
vbddev_list = ['hda', 'hdb', 'hdc', 'hdd']
if vbddev not in vbddev_list:
raise VmError("vmx: for qemu vbd type=file&dev=hda~hdd")
ret.append("-%s" % vbddev)
ret.append("%s" % vbdparam)
if name == 'vif':
mac = sxp.child_value(info, 'mac')
ret.append("-macaddr")
ret.append("%s" % mac)
if name == 'vtpm':
instance = sxp.child_value(info, 'instance')
ret.append("-instance")
ret.append("%s" % instance)
return ret
def configVNC(self, config):
# Handle graphics library related options
vnc = sxp.child_value(config, 'vnc')
sdl = sxp.child_value(config, 'sdl')
ret = []
nographic = sxp.child_value(config, 'nographic')
if nographic:
ret.append('-nographic')
return ret
if vnc and sdl:
ret = ret + ['-vnc-and-sdl', '-k', 'en-us']
elif vnc:
ret = ret + ['-vnc', '-k', 'en-us']
if vnc:
vncport = int(self.vm.getDomid()) + 5900
ret = ret + ['-vncport', '%d' % vncport]
return ret
def createDeviceModel(self):
if self.pid:
return
# Execute device model.
#todo: Error handling
# XXX RN: note that the order of args matter!
args = [self.device_model]
vnc = self.vncParams()
if len(vnc):
args = args + vnc
args = args + ([ "-d", "%d" % self.vm.getDomid(),
"-p", "%d" % self.device_channel,
"-m", "%s" % (self.vm.getMemoryTarget() / 1024)])
args = args + self.dmargs
env = dict(os.environ)
env['DISPLAY'] = self.display
log.info("spawning device models: %s %s", self.device_model, args)
self.pid = os.spawnve(os.P_NOWAIT, self.device_model, args, env)
log.info("device model pid: %d", self.pid)
def vncParams(self):
# see if a vncviewer was specified
# XXX RN: bit of a hack. should unify this, maybe stick in config space
vncconnect=[]
args = self.cmdline
if args:
arg_list = string.split(args)
for arg in arg_list:
al = string.split(arg, '=')
if al[0] == "VNC_VIEWER":
vncconnect=["-vncconnect", "%s" % al[1]]
break
return vncconnect
def destroy(self):
import signal
if not self.pid:
return
os.kill(self.pid, signal.SIGKILL)
os.waitpid(self.pid, 0)
self.pid = 0
def getDomainMemory(self, mem):
"""@see ImageHandler.getDomainMemory"""
# for ioreq_t and xenstore
static_pages = 2
return mem + self.getPageTableSize(mem / 1024) + 4 * static_pages
def getPageTableSize(self, mem_mb):
"""Return the size of memory needed for 1:1 page tables for physical
mode.
@param mem_mb: size in MB
@return size in KB
"""
# 1 page for the PGD + 1 pte page for 4MB of memory (rounded)
if os.uname()[4] == 'x86_64':
return (5 + ((mem_mb + 1) >> 1)) * 4
elif os.uname()[4] == 'ia64':
# XEN/IA64 has p2m table allocated on demand, so only return
# guest firmware size here.
return 16 * 1024
else:
return (1 + ((mem_mb + 3) >> 2)) * 4
"""Table of image handler classes for virtual machine images. Indexed by
image type.
"""
imageHandlerClasses = {}
for h in LinuxImageHandler, VmxImageHandler:
imageHandlerClasses[h.ostype] = h
def findImageHandlerClass(image):
"""Find the image handler class for an image config.
@param image config
@return ImageHandler subclass or None
"""
ty = sxp.name(image)
if ty is None:
raise VmError('missing image type')
imageClass = imageHandlerClasses.get(ty)
if imageClass is None:
raise VmError('unknown image type: ' + ty)
return imageClass