Files
xen/tools/python/xen/xend/image.py
T
emellor@ewan 0fd36627da Added getVCpuCount to XendDomainInfo and use that and getName, getDomain,
getTargetMemory inside image.py to decouple the latter from the internals of
the former.  Move the definition of xc inside image.py to please pylint.

Signed-off-by: Ewan Mellor<ewan@xensource.com>
2005-09-17 17:34:58 +01:00

468 lines
16 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>
#============================================================================
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
from xen.xend.xenstore import DBVar
from xen.xend.xenstore.xstransact import xstransact
from xen.xend.server import channel
xc = xen.lowlevel.xc.new()
MAX_GUEST_CMDLINE = 1024
class ImageHandler:
"""Abstract base class for image handlers.
initDomain() is called to initialise the domain memory and parse
the configuration.
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.
"""
#======================================================================
# Class vars and methods.
"""Table of image handler classes for virtual machine images.
Indexed by image type.
"""
imageHandlerClasses = {}
def addImageHandlerClass(cls, h):
"""Add a handler class for an image type
@param h: handler: ImageHandler subclass
"""
cls.imageHandlerClasses[h.ostype] = h
addImageHandlerClass = classmethod(addImageHandlerClass)
def findImageHandlerClass(cls, 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 = cls.imageHandlerClasses.get(ty)
if imageClass is None:
raise VmError('unknown image type: ' + ty)
return imageClass
findImageHandlerClass = classmethod(findImageHandlerClass)
def create(cls, vm, image):
"""Create an image handler for a vm.
@param vm vm
@param image image config
@return ImageHandler instance
"""
imageClass = cls.findImageHandlerClass(image)
return imageClass(vm, image)
create = classmethod(create)
#======================================================================
# Instance vars and methods.
ostype = None
kernel = None
ramdisk = None
cmdline = None
flags = 0
def __init__(self, vm, config=None):
self.vm = vm
self.configure(config)
def configure(self, config):
"""Config actions common to all unix-like domains."""
if not config:
self.kernel, self.cmdline, self.ramdisk = self.vm.gatherVm(
("image/kernel"), ("image/cmdline"), ("image/ramdisk"))
return
self.kernel = sxp.child_value(config, "kernel")
self.cmdline = ""
ip = sxp.child_value(config, "ip", None)
if ip:
self.cmdline += " ip=" + ip
root = sxp.child_value(config, "root")
if root:
self.cmdline += " root=" + root
args = sxp.child_value(config, "args")
if args:
self.cmdline += " " + args
self.ramdisk = sxp.child_value(config, "ramdisk", '')
self.vm.storeVm(("image/ostype", self.ostype),
("image/kernel", self.kernel),
("image/cmdline", self.cmdline),
("image/ramdisk", 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 initDomain(self, dom, memory, ssidref, cpu, cpu_weight):
"""Initial domain create.
@return domain id
"""
mem_kb = self.getDomainMemory(memory)
if not self.vm.restore:
dom = xc.domain_create(dom = dom or 0, ssidref = ssidref)
# if bootloader, unlink here. But should go after buildDomain() ?
if self.vm.bootloader:
self.unlink(self.kernel)
self.unlink(self.ramdisk)
if dom <= 0:
raise VmError('Creating domain failed: name=%s' %
self.vm.getName())
log.debug("initDomain: cpu=%d mem_kb=%d ssidref=%d dom=%d", cpu, mem_kb, ssidref, dom)
xc.domain_setcpuweight(dom, cpu_weight)
xc.domain_setmaxmem(dom, mem_kb)
try:
xc.domain_memory_increase_reservation(dom, mem_kb, 0, 0)
except:
xc.domain_destroy(dom)
raise
if cpu != -1:
xc.domain_pincpu(dom, 0, 1<<int(cpu))
return dom
def createImage(self):
"""Entry point to create domain memory image.
Override in subclass if needed.
"""
self.createDomain()
def createDomain(self):
"""Build the domain boot image.
"""
if self.vm.recreate or self.vm.restore:
return
# Set params and call buildDomain().
self.flags = self.vm.backend_flags
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.getDomain())
log.info("buildDomain os=%s dom=%d vcpus=%d", self.ostype,
self.vm.getDomain(), self.vm.getVCpuCount())
err = self.buildDomain()
if err != 0:
raise VmError('Building domain failed: ostype=%s dom=%d err=%d'
% (self.ostype, self.vm.getDomain(), err))
def getDomainMemory(self, mem_mb):
"""Memory (in KB) the domain will need for mem_mb (in MB)."""
return mem_mb * 1024
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
def set_vminfo(self, d):
if d.has_key('store_mfn'):
self.vm.setStoreRef(d.get('store_mfn'))
if d.has_key('console_mfn'):
self.vm.setConsoleRef(d.get('console_mfn'))
addImageHandlerClass = ImageHandler.addImageHandlerClass
class LinuxImageHandler(ImageHandler):
ostype = "linux"
def buildDomain(self):
if self.vm.store_channel:
store_evtchn = self.vm.store_channel.port2
else:
store_evtchn = 0
if self.vm.console_channel:
console_evtchn = self.vm.console_channel.port2
else:
console_evtchn = 0
log.debug("dom = %d", self.vm.getDomain())
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("flags = %d", self.flags)
log.debug("vcpus = %d", self.vm.getVCpuCount())
ret = xc.linux_build(dom = self.vm.getDomain(),
image = self.kernel,
store_evtchn = store_evtchn,
console_evtchn = console_evtchn,
cmdline = self.cmdline,
ramdisk = self.ramdisk,
flags = self.flags,
vcpus = self.vm.getVCpuCount())
if isinstance(ret, dict):
self.set_vminfo(ret)
return 0
return ret
class VmxImageHandler(ImageHandler):
ostype = "vmx"
memmap = None
memmap_value = []
device_channel = None
pid = 0
def configure(self, config):
ImageHandler.configure(self, config)
if not config:
self.memmap, dmargs, self.device_model, self.display = self.vm.gatherVm(
("image/memmap"), ("image/dmargs"), ("image/device-model"),
("image/display"))
self.dmargs = dmargs.split(' ')
return
self.memmap = sxp.child_value(config, 'memmap')
self.dmargs = self.parseDeviceModelArgs(config)
self.device_model = sxp.child_value(config, 'device_model')
if not self.device_model:
raise VmError("vmx: missing device model")
self.display = sxp.child_value(config, 'display')
self.vm.storeVm(("image/memmap", self.memmap),
("image/dmargs", " ".join(self.dmargs)),
("image/device-model", self.device_model),
("image/display", self.display))
def createImage(self):
"""Create a VM for the VMX environment.
"""
self.parseMemmap()
self.createDomain()
def buildDomain(self):
# Create an event channel
self.device_channel = channel.eventChannel(0, self.vm.getDomain())
log.info("VMX device model port: %d", self.device_channel.port2)
if self.vm.store_channel:
store_evtchn = self.vm.store_channel.port2
else:
store_evtchn = 0
ret = xc.vmx_build(dom = self.vm.getDomain(),
image = self.kernel,
control_evtchn = self.device_channel.port2,
store_evtchn = store_evtchn,
memsize = self.vm.getTargetMemory(),
memmap = self.memmap_value,
cmdline = self.cmdline,
ramdisk = self.ramdisk,
flags = self.flags,
vcpus = self.vm.getVCpuCount())
if isinstance(ret, dict):
self.set_vminfo(ret)
return 0
return ret
def parseMemmap(self):
if self.memmap is None:
return
memmap = sxp.parse(open(self.memmap))[0]
from xen.util.memmap import memmap_parse
self.memmap_value = memmap_parse(memmap)
# Return a list of cmd line args to the device models based on the
# xm config file
def parseDeviceModelArgs(self, config):
dmargs = [ 'cdrom', 'boot', 'fda', 'fdb',
'localtime', 'serial', 'stdvga', 'isa' ]
ret = []
for a in dmargs:
v = sxp.child_value(config, a)
# python doesn't allow '-' in variable names
if a == 'stdvga': a = 'std-vga'
# Handle booleans gracefully
if a in ['localtime', 'std-vga', 'isa']:
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
devices = sxp.children(self.vm.config, 'device')
for device in devices:
name = sxp.name(sxp.child0(device))
if name == 'vbd':
vbdinfo = sxp.child(device, 'vbd')
uname = sxp.child_value(vbdinfo, 'uname')
typedev = sxp.child_value(vbdinfo, 'dev')
(vbdtype, 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':
vifinfo = sxp.child(device, 'vif')
mac = sxp.child_value(vifinfo, 'mac')
ret.append("-macaddr")
ret.append("%s" % mac)
if name == 'vtpm':
vtpminfo = sxp.child(device, 'vtpm')
instance = sxp.child_value(vtpminfo, 'instance')
ret.append("-instance")
ret.append("%s" % instance)
# Handle graphics library related options
vnc = sxp.child_value(config, 'vnc')
sdl = sxp.child_value(config, 'sdl')
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.getDomain()) + 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.getDomain(),
"-p", "%d" % self.device_channel.port1,
"-m", "%s" % self.vm.getTargetMemory() ])
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):
channel.eventChannelClose(self.device_channel)
import signal
if not self.pid:
return
os.kill(self.pid, signal.SIGKILL)
(pid, status) = os.waitpid(self.pid, 0)
self.pid = 0
def getDomainMemory(self, mem_mb):
# for ioreq_t and xenstore
static_pages = 2
return (mem_mb * 1024) + self.getPageTableSize(mem_mb) + 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