debuggers.hg

view tools/python/xen/xend/image.py @ 16970:04e24b9dcc16

xend: Obey localtime config option for HVM guests as well as PV guests.
Signed-off-by: Keir Fraser <keir.fraser@citrix.com>
author Keir Fraser <keir.fraser@citrix.com>
date Tue Jan 29 15:15:51 2008 +0000 (2008-01-29)
parents 26fc953a89bb
children 396ab902b02d
line source
1 #============================================================================
2 # This library is free software; you can redistribute it and/or
3 # modify it under the terms of version 2.1 of the GNU Lesser General Public
4 # License as published by the Free Software Foundation.
5 #
6 # This library is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9 # Lesser General Public License for more details.
10 #
11 # You should have received a copy of the GNU Lesser General Public
12 # License along with this library; if not, write to the Free Software
13 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
14 #============================================================================
15 # Copyright (C) 2005 Mike Wray <mike.wray@hp.com>
16 # Copyright (C) 2005-2007 XenSource Ltd
17 #============================================================================
20 import os, os.path, string
21 import re
22 import math
23 import time
24 import signal
26 import xen.lowlevel.xc
27 from xen.xend.XendConstants import *
28 from xen.xend.XendError import VmError, XendError, HVMRequired
29 from xen.xend.XendLogging import log
30 from xen.xend.XendOptions import instance as xenopts
31 from xen.xend.xenstore.xstransact import xstransact
32 from xen.xend.xenstore.xswatch import xswatch
33 from xen.xend import arch
34 from xen.xend import XendOptions
36 xc = xen.lowlevel.xc.xc()
38 MAX_GUEST_CMDLINE = 1024
41 def create(vm, vmConfig):
42 """Create an image handler for a vm.
44 @return ImageHandler instance
45 """
46 return findImageHandlerClass(vmConfig)(vm, vmConfig)
49 class ImageHandler:
50 """Abstract base class for image handlers.
52 createImage() is called to configure and build the domain from its
53 kernel image and ramdisk etc.
55 The method buildDomain() is used to build the domain, and must be
56 defined in a subclass. Usually this is the only method that needs
57 defining in a subclass.
59 The method createDeviceModel() is called to create the domain device
60 model.
62 The method destroyDeviceModel() is called to reap the device model
63 """
65 ostype = None
68 def __init__(self, vm, vmConfig):
69 self.vm = vm
71 self.bootloader = False
72 self.kernel = None
73 self.ramdisk = None
74 self.cmdline = None
76 self.configure(vmConfig)
78 def configure(self, vmConfig):
79 """Config actions common to all unix-like domains."""
80 if '_temp_using_bootloader' in vmConfig:
81 self.bootloader = True
82 self.kernel = vmConfig['_temp_kernel']
83 self.cmdline = vmConfig['_temp_args']
84 self.ramdisk = vmConfig['_temp_ramdisk']
85 else:
86 self.kernel = vmConfig['PV_kernel']
87 self.cmdline = vmConfig['PV_args']
88 self.ramdisk = vmConfig['PV_ramdisk']
89 self.vm.storeVm(("image/ostype", self.ostype),
90 ("image/kernel", self.kernel),
91 ("image/cmdline", self.cmdline),
92 ("image/ramdisk", self.ramdisk))
94 self.dmargs = self.parseDeviceModelArgs(vmConfig)
95 self.device_model = vmConfig['platform'].get('device_model')
97 self.display = vmConfig['platform'].get('display')
98 self.xauthority = vmConfig['platform'].get('xauthority')
99 self.vncconsole = vmConfig['platform'].get('vncconsole')
100 self.pid = None
104 def cleanupBootloading(self):
105 if self.bootloader:
106 self.unlink(self.kernel)
107 self.unlink(self.ramdisk)
110 def unlink(self, f):
111 if not f: return
112 try:
113 os.unlink(f)
114 except OSError, ex:
115 log.warning("error removing bootloader file '%s': %s", f, ex)
118 def createImage(self):
119 """Entry point to create domain memory image.
120 Override in subclass if needed.
121 """
122 return self.createDomain()
125 def createDomain(self):
126 """Build the domain boot image.
127 """
128 # Set params and call buildDomain().
130 if not os.path.isfile(self.kernel):
131 raise VmError('Kernel image does not exist: %s' % self.kernel)
132 if self.ramdisk and not os.path.isfile(self.ramdisk):
133 raise VmError('Kernel ramdisk does not exist: %s' % self.ramdisk)
134 if len(self.cmdline) >= MAX_GUEST_CMDLINE:
135 log.warning('kernel cmdline too long, domain %d',
136 self.vm.getDomid())
138 log.info("buildDomain os=%s dom=%d vcpus=%d", self.ostype,
139 self.vm.getDomid(), self.vm.getVCpuCount())
141 result = self.buildDomain()
143 if isinstance(result, dict):
144 return result
145 else:
146 raise VmError('Building domain failed: ostype=%s dom=%d err=%s'
147 % (self.ostype, self.vm.getDomid(), str(result)))
149 def getRequiredAvailableMemory(self, mem_kb):
150 """@param mem_kb The configured maxmem or memory, in KiB.
151 @return The corresponding required amount of memory for the domain,
152 also in KiB. This is normally the given mem_kb, but architecture- or
153 image-specific code may override this to add headroom where
154 necessary."""
155 return mem_kb
157 def getRequiredInitialReservation(self):
158 """@param mem_kb The configured memory, in KiB.
159 @return The corresponding required amount of memory to be free, also
160 in KiB. This is normally the same as getRequiredAvailableMemory, but
161 architecture- or image-specific code may override this to
162 add headroom where necessary."""
163 return self.getRequiredAvailableMemory(self.vm.getMemoryTarget())
165 def getRequiredMaximumReservation(self):
166 """@param mem_kb The maximum possible memory, in KiB.
167 @return The corresponding required amount of memory to be free, also
168 in KiB. This is normally the same as getRequiredAvailableMemory, but
169 architecture- or image-specific code may override this to
170 add headroom where necessary."""
171 return self.getRequiredAvailableMemory(self.vm.getMemoryMaximum())
173 def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
174 """@param shadow_mem_kb The configured shadow memory, in KiB.
175 @param maxmem_kb The configured maxmem, in KiB.
176 @return The corresponding required amount of shadow memory, also in
177 KiB."""
178 # PV domains don't need any shadow memory
179 return 0
181 def buildDomain(self):
182 """Build the domain. Define in subclass."""
183 raise NotImplementedError()
185 # Return a list of cmd line args to the device models based on the
186 # xm config file
187 def parseDeviceModelArgs(self, vmConfig):
188 ret = ["-domain-name", str(self.vm.info['name_label'])]
190 # Find RFB console device, and if it exists, make QEMU enable
191 # the VNC console.
192 if int(vmConfig['platform'].get('nographic', 0)) != 0:
193 # skip vnc init if nographic is set
194 ret.append('-nographic')
195 return ret
197 vnc_config = {}
198 has_vnc = int(vmConfig['platform'].get('vnc', 0)) != 0
199 has_sdl = int(vmConfig['platform'].get('sdl', 0)) != 0
200 for dev_uuid in vmConfig['console_refs']:
201 dev_type, dev_info = vmConfig['devices'][dev_uuid]
202 if dev_type == 'vfb':
203 vnc_config = dev_info.get('other_config', {})
204 has_vnc = True
205 break
207 keymap = vmConfig['platform'].get("keymap")
208 if keymap:
209 ret.append("-k")
210 ret.append(keymap)
212 if has_vnc:
213 if not vnc_config:
214 for key in ('vncunused', 'vnclisten', 'vncdisplay',
215 'vncpasswd'):
216 if key in vmConfig['platform']:
217 vnc_config[key] = vmConfig['platform'][key]
218 if vnc_config.has_key("vncpasswd"):
219 passwd = vnc_config["vncpasswd"]
220 else:
221 passwd = XendOptions.instance().get_vncpasswd_default()
222 vncopts = ""
223 if passwd:
224 self.vm.storeVm("vncpasswd", passwd)
225 self.vm.permissionsVm("vncpasswd", { 'dom': self.vm.getDomid(), 'read': True } )
226 vncopts = vncopts + ",password"
227 log.debug("Stored a VNC password for vfb access")
228 else:
229 log.debug("No VNC passwd configured for vfb access")
231 if XendOptions.instance().get_vnc_tls():
232 vncx509certdir = XendOptions.instance().get_vnc_x509_cert_dir()
233 vncx509verify = XendOptions.instance().get_vnc_x509_verify()
235 if not os.path.exists(vncx509certdir):
236 raise VmError("VNC x509 certificate dir %s does not exist" % vncx509certdir)
238 if vncx509verify:
239 vncopts = vncopts + ",tls,x509verify=%s" % vncx509certdir
240 else:
241 vncopts = vncopts + ",tls,x509=%s" % vncx509certdir
244 vnclisten = vnc_config.get('vnclisten',
245 XendOptions.instance().get_vnclisten_address())
246 vncdisplay = vnc_config.get('vncdisplay', 0)
247 ret.append('-vnc')
248 ret.append("%s:%s%s" % (vnclisten, vncdisplay, vncopts))
250 if vnc_config.get('vncunused', 0):
251 ret.append('-vncunused')
253 elif has_sdl:
254 # SDL is default in QEMU.
255 pass
256 else:
257 ret.append('-nographic')
259 if int(vmConfig['platform'].get('monitor', 0)) != 0:
260 ret = ret + ['-monitor', 'vc']
261 return ret
263 def getDeviceModelArgs(self, restore = False):
264 args = [self.device_model]
265 args = args + ([ "-d", "%d" % self.vm.getDomid() ])
266 args = args + self.dmargs
267 return args
269 def createDeviceModel(self, restore = False):
270 if self.device_model is None:
271 return
272 if self.pid:
273 return
274 # Execute device model.
275 #todo: Error handling
276 args = self.getDeviceModelArgs(restore)
277 env = dict(os.environ)
278 if self.display:
279 env['DISPLAY'] = self.display
280 if self.xauthority:
281 env['XAUTHORITY'] = self.xauthority
282 if self.vncconsole:
283 args = args + ([ "-vncviewer" ])
284 xstransact.Mkdir("/local/domain/0/device-model/%i" % self.vm.getDomid())
285 xstransact.SetPermissions("/local/domain/0/device-model/%i" % self.vm.getDomid(),
286 { 'dom': self.vm.getDomid(), 'read': True, 'write': True })
287 log.info("spawning device models: %s %s", self.device_model, args)
288 # keep track of pid and spawned options to kill it later
289 self.pid = os.spawnve(os.P_NOWAIT, self.device_model, args, env)
290 self.vm.storeDom("image/device-model-pid", self.pid)
291 log.info("device model pid: %d", self.pid)
293 def saveDeviceModel(self):
294 if self.device_model is None:
295 return
296 # Signal the device model to pause itself and save its state
297 xstransact.Store("/local/domain/0/device-model/%i"
298 % self.vm.getDomid(), ('command', 'save'))
299 # Wait for confirmation. Could do this with a watch but we'd
300 # still end up spinning here waiting for the watch to fire.
301 state = ''
302 count = 0
303 while state != 'paused':
304 state = xstransact.Read("/local/domain/0/device-model/%i/state"
305 % self.vm.getDomid())
306 time.sleep(0.1)
307 count += 1
308 if count > 100:
309 raise VmError('Timed out waiting for device model to save')
311 def resumeDeviceModel(self):
312 if self.device_model is None:
313 return
314 # Signal the device model to resume activity after pausing to save.
315 xstransact.Store("/local/domain/0/device-model/%i"
316 % self.vm.getDomid(), ('command', 'continue'))
318 def recreate(self):
319 if self.device_model is None:
320 return
321 self.pid = self.vm.gatherDom(('image/device-model-pid', int))
323 def destroyDeviceModel(self):
324 if self.device_model is None:
325 return
326 if self.pid:
327 try:
328 os.kill(self.pid, signal.SIGTERM)
329 except OSError, exn:
330 log.exception(exn)
331 try:
332 os.waitpid(self.pid, 0)
333 except OSError, exn:
334 # This is expected if Xend has been restarted within the
335 # life of this domain. In this case, we can kill the process,
336 # but we can't wait for it because it's not our child.
337 pass
338 self.pid = None
339 state = xstransact.Remove("/local/domain/0/device-model/%i"
340 % self.vm.getDomid())
343 class LinuxImageHandler(ImageHandler):
345 ostype = "linux"
346 flags = 0
347 vhpt = 0
349 def configure(self, vmConfig):
350 ImageHandler.configure(self, vmConfig)
351 rtc_timeoffset = vmConfig['platform'].get('rtc_timeoffset')
352 if rtc_timeoffset is not None:
353 xc.domain_set_time_offset(self.vm.getDomid(), rtc_timeoffset)
355 def buildDomain(self):
356 store_evtchn = self.vm.getStorePort()
357 console_evtchn = self.vm.getConsolePort()
359 mem_mb = self.getRequiredInitialReservation() / 1024
361 log.debug("domid = %d", self.vm.getDomid())
362 log.debug("memsize = %d", mem_mb)
363 log.debug("image = %s", self.kernel)
364 log.debug("store_evtchn = %d", store_evtchn)
365 log.debug("console_evtchn = %d", console_evtchn)
366 log.debug("cmdline = %s", self.cmdline)
367 log.debug("ramdisk = %s", self.ramdisk)
368 log.debug("vcpus = %d", self.vm.getVCpuCount())
369 log.debug("features = %s", self.vm.getFeatures())
370 if arch.type == "ia64":
371 log.debug("vhpt = %d", self.flags)
373 return xc.linux_build(domid = self.vm.getDomid(),
374 memsize = mem_mb,
375 image = self.kernel,
376 store_evtchn = store_evtchn,
377 console_evtchn = console_evtchn,
378 cmdline = self.cmdline,
379 ramdisk = self.ramdisk,
380 features = self.vm.getFeatures(),
381 flags = self.flags,
382 vhpt = self.vhpt)
384 def parseDeviceModelArgs(self, vmConfig):
385 ret = ImageHandler.parseDeviceModelArgs(self, vmConfig)
386 # Equivalent to old xenconsoled behaviour. Should make
387 # it configurable in future
388 ret = ret + ["-serial", "pty"]
389 return ret
391 def getDeviceModelArgs(self, restore = False):
392 args = ImageHandler.getDeviceModelArgs(self, restore)
393 args = args + ([ "-M", "xenpv"])
394 return args
397 class PPC_LinuxImageHandler(LinuxImageHandler):
399 ostype = "linux"
401 def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
402 """@param shadow_mem_kb The configured shadow memory, in KiB.
403 @param maxmem_kb The configured maxmem, in KiB.
404 @return The corresponding required amount of shadow memory, also in
405 KiB.
406 PowerPC currently uses "shadow memory" to refer to the hash table."""
407 return max(maxmem_kb / 64, shadow_mem_kb)
411 class HVMImageHandler(ImageHandler):
413 ostype = "hvm"
415 def __init__(self, vm, vmConfig):
416 ImageHandler.__init__(self, vm, vmConfig)
417 self.shutdownWatch = None
418 self.rebootFeatureWatch = None
420 def configure(self, vmConfig):
421 ImageHandler.configure(self, vmConfig)
423 if not self.kernel:
424 self.kernel = '/usr/lib/xen/boot/hvmloader'
426 info = xc.xeninfo()
427 if 'hvm' not in info['xen_caps']:
428 raise HVMRequired()
430 rtc_timeoffset = vmConfig['platform'].get('rtc_timeoffset')
432 self.vm.storeVm(("image/dmargs", " ".join(self.dmargs)),
433 ("image/device-model", self.device_model),
434 ("image/display", self.display))
435 self.vm.permissionsVm("image/dmargs", { 'dom': self.vm.getDomid(), 'read': True } )
436 self.vm.storeVm(("rtc/timeoffset", rtc_timeoffset))
437 self.vm.permissionsVm("rtc/timeoffset", { 'dom': self.vm.getDomid(), 'read': True } )
439 self.apic = int(vmConfig['platform'].get('apic', 0))
440 self.acpi = int(vmConfig['platform'].get('acpi', 0))
441 self.guest_os_type = vmConfig['platform'].get('guest_os_type')
443 # Return a list of cmd line args to the device models based on the
444 # xm config file
445 def parseDeviceModelArgs(self, vmConfig):
446 ret = ImageHandler.parseDeviceModelArgs(self, vmConfig)
447 ret = ret + ['-vcpus', str(self.vm.getVCpuCount())]
449 dmargs = [ 'boot', 'fda', 'fdb', 'soundhw',
450 'localtime', 'serial', 'stdvga', 'isa',
451 'acpi', 'usb', 'usbdevice', 'pci' ]
453 for a in dmargs:
454 v = vmConfig['platform'].get(a)
456 # python doesn't allow '-' in variable names
457 if a == 'stdvga': a = 'std-vga'
458 if a == 'keymap': a = 'k'
460 # Handle booleans gracefully
461 if a in ['localtime', 'std-vga', 'isa', 'usb', 'acpi']:
462 try:
463 if v != None: v = int(v)
464 if v: ret.append("-%s" % a)
465 except (ValueError, TypeError):
466 pass # if we can't convert it to a sane type, ignore it
467 else:
468 if v:
469 ret.append("-%s" % a)
470 ret.append("%s" % v)
472 if a in ['fda', 'fdb']:
473 if v:
474 if not os.path.isabs(v):
475 raise VmError("Floppy file %s does not exist." % v)
476 log.debug("args: %s, val: %s" % (a,v))
478 # Handle disk/network related options
479 mac = None
480 nics = 0
482 for devuuid in vmConfig['vbd_refs']:
483 devinfo = vmConfig['devices'][devuuid][1]
484 uname = devinfo.get('uname')
485 if uname is not None and 'file:' in uname:
486 (_, vbdparam) = string.split(uname, ':', 1)
487 if not os.path.isfile(vbdparam):
488 raise VmError('Disk image does not exist: %s' %
489 vbdparam)
491 for devuuid in vmConfig['vif_refs']:
492 devinfo = vmConfig['devices'][devuuid][1]
493 dtype = devinfo.get('type', 'ioemu')
494 if dtype != 'ioemu':
495 continue
496 nics += 1
497 mac = devinfo.get('mac')
498 if mac is None:
499 raise VmError("MAC address not specified or generated.")
500 bridge = devinfo.get('bridge', 'xenbr0')
501 model = devinfo.get('model', 'rtl8139')
502 ret.append("-net")
503 ret.append("nic,vlan=%d,macaddr=%s,model=%s" %
504 (nics, mac, model))
505 ret.append("-net")
506 ret.append("tap,vlan=%d,bridge=%s" % (nics, bridge))
508 return ret
510 def getDeviceModelArgs(self, restore = False):
511 args = ImageHandler.getDeviceModelArgs(self, restore)
512 args = args + ([ "-M", "xenfv"])
513 if restore:
514 args = args + ([ "-loadvm", "/var/lib/xen/qemu-save.%d" %
515 self.vm.getDomid() ])
516 return args
518 def buildDomain(self):
519 store_evtchn = self.vm.getStorePort()
521 mem_mb = self.getRequiredInitialReservation() / 1024
523 log.debug("domid = %d", self.vm.getDomid())
524 log.debug("image = %s", self.kernel)
525 log.debug("store_evtchn = %d", store_evtchn)
526 log.debug("memsize = %d", mem_mb)
527 log.debug("vcpus = %d", self.vm.getVCpuCount())
528 log.debug("acpi = %d", self.acpi)
529 log.debug("apic = %d", self.apic)
531 rc = xc.hvm_build(domid = self.vm.getDomid(),
532 image = self.kernel,
533 memsize = mem_mb,
534 vcpus = self.vm.getVCpuCount(),
535 acpi = self.acpi,
536 apic = self.apic)
537 rc['notes'] = { 'SUSPEND_CANCEL': 1 }
539 rc['store_mfn'] = xc.hvm_get_param(self.vm.getDomid(),
540 HVM_PARAM_STORE_PFN)
541 xc.hvm_set_param(self.vm.getDomid(), HVM_PARAM_STORE_EVTCHN,
542 store_evtchn)
544 return rc
547 class IA64_HVM_ImageHandler(HVMImageHandler):
549 def configure(self, vmConfig):
550 HVMImageHandler.configure(self, vmConfig)
551 self.vhpt = int(vmConfig['platform'].get('vhpt', 0))
553 def buildDomain(self):
554 xc.nvram_init(self.vm.getName(), self.vm.getDomid())
555 xc.hvm_set_param(self.vm.getDomid(), HVM_PARAM_VHPT_SIZE, self.vhpt)
556 if self.guest_os_type is not None:
557 xc.set_os_type(self.guest_os_type.lower(), self.vm.getDomid())
558 return HVMImageHandler.buildDomain(self)
560 def getRequiredAvailableMemory(self, mem_kb):
561 page_kb = 16
562 # ROM size for guest firmware, io page, xenstore page
563 # buffer io page, buffer pio page and memmap info page
564 extra_pages = 1024 + 5
565 return mem_kb + extra_pages * page_kb
567 def getRequiredInitialReservation(self):
568 return self.vm.getMemoryTarget()
570 def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
571 # Explicit shadow memory is not a concept
572 return 0
574 def getDeviceModelArgs(self, restore = False):
575 args = HVMImageHandler.getDeviceModelArgs(self, restore)
576 args = args + ([ "-m", "%s" %
577 (self.getRequiredInitialReservation() / 1024) ])
578 return args
581 class IA64_Linux_ImageHandler(LinuxImageHandler):
583 def configure(self, vmConfig):
584 LinuxImageHandler.configure(self, vmConfig)
585 self.vhpt = int(vmConfig['platform'].get('vhpt', 0))
588 class X86_HVM_ImageHandler(HVMImageHandler):
590 def configure(self, vmConfig):
591 HVMImageHandler.configure(self, vmConfig)
592 self.pae = int(vmConfig['platform'].get('pae', 0))
594 def buildDomain(self):
595 xc.hvm_set_param(self.vm.getDomid(), HVM_PARAM_PAE_ENABLED, self.pae)
596 return HVMImageHandler.buildDomain(self)
598 def getRequiredAvailableMemory(self, mem_kb):
599 # Add 8 MiB overhead for QEMU's video RAM.
600 return mem_kb + 8192
602 def getRequiredInitialReservation(self):
603 return self.vm.getMemoryTarget()
605 def getRequiredMaximumReservation(self):
606 return self.vm.getMemoryMaximum()
608 def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
609 # 256 pages (1MB) per vcpu,
610 # plus 1 page per MiB of RAM for the P2M map,
611 # plus 1 page per MiB of RAM to shadow the resident processes.
612 # This is higher than the minimum that Xen would allocate if no value
613 # were given (but the Xen minimum is for safety, not performance).
614 return max(4 * (256 * self.vm.getVCpuCount() + 2 * (maxmem_kb / 1024)),
615 shadow_mem_kb)
618 class X86_Linux_ImageHandler(LinuxImageHandler):
620 def buildDomain(self):
621 # set physical mapping limit
622 # add an 8MB slack to balance backend allocations.
623 mem_kb = self.getRequiredMaximumReservation() + (8 * 1024)
624 xc.domain_set_memmap_limit(self.vm.getDomid(), mem_kb)
625 return LinuxImageHandler.buildDomain(self)
627 _handlers = {
628 "powerpc": {
629 "linux": PPC_LinuxImageHandler,
630 },
631 "ia64": {
632 "linux": IA64_Linux_ImageHandler,
633 "hvm": IA64_HVM_ImageHandler,
634 },
635 "x86": {
636 "linux": X86_Linux_ImageHandler,
637 "hvm": X86_HVM_ImageHandler,
638 },
639 }
641 def findImageHandlerClass(image):
642 """Find the image handler class for an image config.
644 @param image config
645 @return ImageHandler subclass or None
646 """
647 image_type = image.image_type()
648 try:
649 return _handlers[arch.type][image_type]
650 except KeyError:
651 raise VmError('unknown image type: ' + image_type)