direct-io.hg

view tools/python/xen/xend/image.py @ 14342:e5d7b878118f

Added an VM_HVM_REQUIRED error code, and use it on VM.start.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author Ewan Mellor <ewan@xensource.com>
date Sat Mar 10 23:22:00 2007 +0000 (2007-03-10)
parents 0affe03ee985
children 3c38150de7fd
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, string
21 import re
22 import math
23 import signal
25 import xen.lowlevel.xc
26 from xen.xend.XendConstants import REVERSE_DOMAIN_SHUTDOWN_REASONS
27 from xen.xend.XendError import VmError, XendError, HVMRequired
28 from xen.xend.XendLogging import log
29 from xen.xend.XendOptions import instance as xenopts
30 from xen.xend.server.netif import randomMAC
31 from xen.xend.xenstore.xswatch import xswatch
32 from xen.xend import arch
34 xc = xen.lowlevel.xc.xc()
36 MAX_GUEST_CMDLINE = 1024
39 def create(vm, vmConfig):
40 """Create an image handler for a vm.
42 @return ImageHandler instance
43 """
44 return findImageHandlerClass(vmConfig)(vm, vmConfig)
47 class ImageHandler:
48 """Abstract base class for image handlers.
50 createImage() is called to configure and build the domain from its
51 kernel image and ramdisk etc.
53 The method buildDomain() is used to build the domain, and must be
54 defined in a subclass. Usually this is the only method that needs
55 defining in a subclass.
57 The method createDeviceModel() is called to create the domain device
58 model if it needs one. The default is to do nothing.
60 The method destroy() is called when the domain is destroyed.
61 The default is to do nothing.
62 """
64 ostype = None
67 def __init__(self, vm, vmConfig):
68 self.vm = vm
70 self.bootloader = False
71 self.kernel = None
72 self.ramdisk = None
73 self.cmdline = None
75 self.configure(vmConfig)
77 def configure(self, vmConfig):
78 """Config actions common to all unix-like domains."""
79 if '_temp_using_bootloader' in vmConfig:
80 self.bootloader = True
81 self.kernel = vmConfig['_temp_kernel']
82 self.cmdline = vmConfig['_temp_args']
83 self.ramdisk = vmConfig['_temp_ramdisk']
84 else:
85 self.kernel = vmConfig['PV_kernel']
86 self.cmdline = vmConfig['PV_args']
87 self.ramdisk = vmConfig['PV_ramdisk']
88 self.vm.storeVm(("image/ostype", self.ostype),
89 ("image/kernel", self.kernel),
90 ("image/cmdline", self.cmdline),
91 ("image/ramdisk", self.ramdisk))
94 def cleanupBootloading(self):
95 if self.bootloader:
96 self.unlink(self.kernel)
97 self.unlink(self.ramdisk)
100 def unlink(self, f):
101 if not f: return
102 try:
103 os.unlink(f)
104 except OSError, ex:
105 log.warning("error removing bootloader file '%s': %s", f, ex)
108 def createImage(self):
109 """Entry point to create domain memory image.
110 Override in subclass if needed.
111 """
112 return self.createDomain()
115 def createDomain(self):
116 """Build the domain boot image.
117 """
118 # Set params and call buildDomain().
120 if not os.path.isfile(self.kernel):
121 raise VmError('Kernel image does not exist: %s' % self.kernel)
122 if self.ramdisk and not os.path.isfile(self.ramdisk):
123 raise VmError('Kernel ramdisk does not exist: %s' % self.ramdisk)
124 if len(self.cmdline) >= MAX_GUEST_CMDLINE:
125 log.warning('kernel cmdline too long, domain %d',
126 self.vm.getDomid())
128 log.info("buildDomain os=%s dom=%d vcpus=%d", self.ostype,
129 self.vm.getDomid(), self.vm.getVCpuCount())
131 result = self.buildDomain()
133 if isinstance(result, dict):
134 return result
135 else:
136 raise VmError('Building domain failed: ostype=%s dom=%d err=%s'
137 % (self.ostype, self.vm.getDomid(), str(result)))
139 def getRequiredAvailableMemory(self, mem_kb):
140 """@param mem_kb The configured maxmem or memory, in KiB.
141 @return The corresponding required amount of memory for the domain,
142 also in KiB. This is normally the given mem_kb, but architecture- or
143 image-specific code may override this to add headroom where
144 necessary."""
145 return mem_kb
147 def getRequiredInitialReservation(self):
148 """@param mem_kb The configured memory, in KiB.
149 @return The corresponding required amount of memory to be free, also
150 in KiB. This is normally the same as getRequiredAvailableMemory, but
151 architecture- or image-specific code may override this to
152 add headroom where necessary."""
153 return self.getRequiredAvailableMemory(self.vm.getMemoryTarget())
155 def getRequiredMaximumReservation(self):
156 """@param mem_kb The maximum possible memory, in KiB.
157 @return The corresponding required amount of memory to be free, also
158 in KiB. This is normally the same as getRequiredAvailableMemory, but
159 architecture- or image-specific code may override this to
160 add headroom where necessary."""
161 return self.getRequiredAvailableMemory(self.vm.getMemoryMaximum())
163 def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
164 """@param shadow_mem_kb The configured shadow memory, in KiB.
165 @param maxmem_kb The configured maxmem, in KiB.
166 @return The corresponding required amount of shadow memory, also in
167 KiB."""
168 # PV domains don't need any shadow memory
169 return 0
171 def buildDomain(self):
172 """Build the domain. Define in subclass."""
173 raise NotImplementedError()
175 def createDeviceModel(self, restore = False):
176 """Create device model for the domain (define in subclass if needed)."""
177 pass
179 def destroy(self):
180 """Extra cleanup on domain destroy (define in subclass if needed)."""
181 pass
184 def recreate(self):
185 pass
188 class LinuxImageHandler(ImageHandler):
190 ostype = "linux"
192 def buildDomain(self):
193 store_evtchn = self.vm.getStorePort()
194 console_evtchn = self.vm.getConsolePort()
196 mem_mb = self.getRequiredInitialReservation() / 1024
198 log.debug("domid = %d", self.vm.getDomid())
199 log.debug("memsize = %d", mem_mb)
200 log.debug("image = %s", self.kernel)
201 log.debug("store_evtchn = %d", store_evtchn)
202 log.debug("console_evtchn = %d", console_evtchn)
203 log.debug("cmdline = %s", self.cmdline)
204 log.debug("ramdisk = %s", self.ramdisk)
205 log.debug("vcpus = %d", self.vm.getVCpuCount())
206 log.debug("features = %s", self.vm.getFeatures())
208 return xc.linux_build(domid = self.vm.getDomid(),
209 memsize = mem_mb,
210 image = self.kernel,
211 store_evtchn = store_evtchn,
212 console_evtchn = console_evtchn,
213 cmdline = self.cmdline,
214 ramdisk = self.ramdisk,
215 features = self.vm.getFeatures())
217 class PPC_LinuxImageHandler(LinuxImageHandler):
219 ostype = "linux"
221 def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
222 """@param shadow_mem_kb The configured shadow memory, in KiB.
223 @param maxmem_kb The configured maxmem, in KiB.
224 @return The corresponding required amount of shadow memory, also in
225 KiB.
226 PowerPC currently uses "shadow memory" to refer to the hash table."""
227 return max(maxmem_kb / 64, shadow_mem_kb)
231 class PPC_ProseImageHandler(PPC_LinuxImageHandler):
233 ostype = "prose"
235 def buildDomain(self):
236 store_evtchn = self.vm.getStorePort()
237 console_evtchn = self.vm.getConsolePort()
239 mem_mb = self.getRequiredInitialReservation() / 1024
241 log.debug("dom = %d", self.vm.getDomid())
242 log.debug("memsize = %d", mem_mb)
243 log.debug("image = %s", self.kernel)
244 log.debug("store_evtchn = %d", store_evtchn)
245 log.debug("console_evtchn = %d", console_evtchn)
246 log.debug("cmdline = %s", self.cmdline)
247 log.debug("ramdisk = %s", self.ramdisk)
248 log.debug("vcpus = %d", self.vm.getVCpuCount())
249 log.debug("features = %s", self.vm.getFeatures())
251 return xc.arch_prose_build(dom = self.vm.getDomid(),
252 memsize = mem_mb,
253 image = self.kernel,
254 store_evtchn = store_evtchn,
255 console_evtchn = console_evtchn,
256 cmdline = self.cmdline,
257 ramdisk = self.ramdisk,
258 features = self.vm.getFeatures())
260 class HVMImageHandler(ImageHandler):
262 ostype = "hvm"
264 def __init__(self, vm, vmConfig):
265 ImageHandler.__init__(self, vm, vmConfig)
266 self.shutdownWatch = None
267 self.rebootFeatureWatch = None
269 def configure(self, vmConfig):
270 ImageHandler.configure(self, vmConfig)
272 if not self.kernel:
273 self.kernel = '/usr/lib/xen/boot/hvmloader'
275 info = xc.xeninfo()
276 if 'hvm' not in info['xen_caps']:
277 raise HVMRequired()
279 self.dmargs = self.parseDeviceModelArgs(vmConfig)
280 self.device_model = vmConfig['platform'].get('device_model')
281 if not self.device_model:
282 raise VmError("hvm: missing device model")
284 self.display = vmConfig['platform'].get('display')
285 self.xauthority = vmConfig['platform'].get('xauthority')
286 self.vncconsole = vmConfig['platform'].get('vncconsole')
288 self.vm.storeVm(("image/dmargs", " ".join(self.dmargs)),
289 ("image/device-model", self.device_model),
290 ("image/display", self.display))
292 self.pid = None
294 self.pae = int(vmConfig['platform'].get('pae', 0))
295 self.apic = int(vmConfig['platform'].get('apic', 0))
296 self.acpi = int(vmConfig['platform'].get('acpi', 0))
299 def buildDomain(self):
300 store_evtchn = self.vm.getStorePort()
302 mem_mb = self.getRequiredInitialReservation() / 1024
304 log.debug("domid = %d", self.vm.getDomid())
305 log.debug("image = %s", self.kernel)
306 log.debug("store_evtchn = %d", store_evtchn)
307 log.debug("memsize = %d", mem_mb)
308 log.debug("vcpus = %d", self.vm.getVCpuCount())
309 log.debug("pae = %d", self.pae)
310 log.debug("acpi = %d", self.acpi)
311 log.debug("apic = %d", self.apic)
313 self.register_shutdown_watch()
314 self.register_reboot_feature_watch()
316 return xc.hvm_build(domid = self.vm.getDomid(),
317 image = self.kernel,
318 store_evtchn = store_evtchn,
319 memsize = mem_mb,
320 vcpus = self.vm.getVCpuCount(),
321 pae = self.pae,
322 acpi = self.acpi,
323 apic = self.apic)
325 # Return a list of cmd line args to the device models based on the
326 # xm config file
327 def parseDeviceModelArgs(self, vmConfig):
328 dmargs = [ 'boot', 'fda', 'fdb', 'soundhw',
329 'localtime', 'serial', 'stdvga', 'isa',
330 'acpi', 'usb', 'usbdevice', 'keymap' ]
332 ret = ['-vcpus', str(self.vm.getVCpuCount())]
334 for a in dmargs:
335 v = vmConfig['platform'].get(a)
337 # python doesn't allow '-' in variable names
338 if a == 'stdvga': a = 'std-vga'
339 if a == 'keymap': a = 'k'
341 # Handle booleans gracefully
342 if a in ['localtime', 'std-vga', 'isa', 'usb', 'acpi']:
343 try:
344 if v != None: v = int(v)
345 if v: ret.append("-%s" % a)
346 except (ValueError, TypeError):
347 pass # if we can't convert it to a sane type, ignore it
348 else:
349 if v:
350 ret.append("-%s" % a)
351 ret.append("%s" % v)
353 if a in ['fda', 'fdb']:
354 if v:
355 if not os.path.isabs(v):
356 raise VmError("Floppy file %s does not exist." % v)
357 log.debug("args: %s, val: %s" % (a,v))
359 # Handle disk/network related options
360 mac = None
361 ret = ret + ["-domain-name", str(self.vm.info['name_label'])]
362 nics = 0
364 for devuuid in vmConfig['vbd_refs']:
365 devinfo = vmConfig['devices'][devuuid][1]
366 uname = devinfo.get('uname')
367 if uname is not None and 'file:' in uname:
368 (_, vbdparam) = string.split(uname, ':', 1)
369 if not os.path.isfile(vbdparam):
370 raise VmError('Disk image does not exist: %s' %
371 vbdparam)
373 for devuuid in vmConfig['vif_refs']:
374 devinfo = vmConfig['devices'][devuuid][1]
375 dtype = devinfo.get('type', 'ioemu')
376 if dtype != 'ioemu':
377 continue
378 nics += 1
379 mac = devinfo.get('mac')
380 if mac is None:
381 mac = randomMAC()
382 bridge = devinfo.get('bridge', 'xenbr0')
383 model = devinfo.get('model', 'rtl8139')
384 ret.append("-net")
385 ret.append("nic,vlan=%d,macaddr=%s,model=%s" %
386 (nics, mac, model))
387 ret.append("-net")
388 ret.append("tap,vlan=%d,bridge=%s" % (nics, bridge))
391 #
392 # Find RFB console device, and if it exists, make QEMU enable
393 # the VNC console.
394 #
395 if vmConfig['platform'].get('nographic'):
396 # skip vnc init if nographic is set
397 ret.append('-nographic')
398 return ret
400 vnc_config = {}
401 has_vnc = int(vmConfig['platform'].get('vnc', 0)) != 0
402 has_sdl = int(vmConfig['platform'].get('sdl', 0)) != 0
403 for dev_uuid in vmConfig['console_refs']:
404 dev_type, dev_info = vmConfig['devices'][dev_uuid]
405 if dev_type == 'vfb':
406 vnc_config = dev_info.get('other_config', {})
407 has_vnc = True
408 break
410 if has_vnc:
411 if not vnc_config:
412 for key in ('vncunused', 'vnclisten', 'vncdisplay',
413 'vncpasswd'):
414 if key in vmConfig['platform']:
415 vnc_config[key] = vmConfig['platform'][key]
417 if not vnc_config.get('vncunused', 0) and \
418 vnc_config.get('vncdisplay', 0):
419 vncdisplay = vnc_config.get('vncdisplay')
420 ret.append('-vnc')
421 ret.append(str(vncdisplay))
422 else:
423 ret.append('-vncunused')
425 vnclisten = vnc_config.get('vnclisten',
426 xenopts().get_vnclisten_address())
427 ret.append('-vnclisten')
428 ret.append(str(vnclisten))
430 # Store vncpassword in xenstore
431 vncpasswd = vnc_config.get('vncpasswd')
432 if not vncpasswd:
433 vncpasswd = xenopts().get_vncpasswd_default()
435 if vncpasswd is None:
436 raise VmError('vncpasswd is not setup in vmconfig or '
437 'xend-config.sxp')
439 if vncpasswd != '':
440 self.vm.storeVm('vncpasswd', vncpasswd)
441 elif has_sdl:
442 # SDL is default in QEMU.
443 pass
444 else:
445 ret.append('-nographic')
447 return ret
449 def createDeviceModel(self, restore = False):
450 if self.pid:
451 return
452 # Execute device model.
453 #todo: Error handling
454 args = [self.device_model]
455 args = args + ([ "-d", "%d" % self.vm.getDomid(),
456 "-m", "%s" % (self.getRequiredInitialReservation() / 1024)])
457 args = args + self.dmargs
458 if restore:
459 args = args + ([ "-loadvm", "/tmp/xen.qemu-dm.%d" % self.vm.getDomid() ])
460 env = dict(os.environ)
461 if self.display:
462 env['DISPLAY'] = self.display
463 if self.xauthority:
464 env['XAUTHORITY'] = self.xauthority
465 if self.vncconsole:
466 args = args + ([ "-vncviewer" ])
467 log.info("spawning device models: %s %s", self.device_model, args)
468 # keep track of pid and spawned options to kill it later
469 self.pid = os.spawnve(os.P_NOWAIT, self.device_model, args, env)
470 self.vm.storeDom("image/device-model-pid", self.pid)
471 log.info("device model pid: %d", self.pid)
473 def recreate(self):
474 self.register_shutdown_watch()
475 self.register_reboot_feature_watch()
476 self.pid = self.vm.gatherDom(('image/device-model-pid', int))
478 def destroy(self, suspend = False):
479 self.unregister_shutdown_watch()
480 self.unregister_reboot_feature_watch();
481 if self.pid:
482 try:
483 sig = signal.SIGKILL
484 if suspend:
485 log.info("use sigusr1 to signal qemu %d", self.pid)
486 sig = signal.SIGUSR1
487 os.kill(self.pid, sig)
488 except OSError, exn:
489 log.exception(exn)
490 try:
491 os.waitpid(self.pid, 0)
492 except OSError, exn:
493 # This is expected if Xend has been restarted within the
494 # life of this domain. In this case, we can kill the process,
495 # but we can't wait for it because it's not our child.
496 pass
497 self.pid = None
499 def register_shutdown_watch(self):
500 """ add xen store watch on control/shutdown """
501 self.shutdownWatch = xswatch(self.vm.dompath + "/control/shutdown",
502 self.hvm_shutdown)
503 log.debug("hvm shutdown watch registered")
505 def unregister_shutdown_watch(self):
506 """Remove the watch on the control/shutdown, if any. Nothrow
507 guarantee."""
509 try:
510 if self.shutdownWatch:
511 self.shutdownWatch.unwatch()
512 except:
513 log.exception("Unwatching hvm shutdown watch failed.")
514 self.shutdownWatch = None
515 log.debug("hvm shutdown watch unregistered")
517 def hvm_shutdown(self, _):
518 """ watch call back on node control/shutdown,
519 if node changed, this function will be called
520 """
521 xd = xen.xend.XendDomain.instance()
522 try:
523 vm = xd.domain_lookup( self.vm.getDomid() )
524 except XendError:
525 # domain isn't registered, no need to clean it up.
526 return False
528 reason = vm.getShutdownReason()
529 log.debug("hvm_shutdown fired, shutdown reason=%s", reason)
530 if reason in REVERSE_DOMAIN_SHUTDOWN_REASONS:
531 vm.info['shutdown'] = 1
532 vm.info['shutdown_reason'] = \
533 REVERSE_DOMAIN_SHUTDOWN_REASONS[reason]
534 vm.refreshShutdown(vm.info)
536 return True # Keep watching
538 def register_reboot_feature_watch(self):
539 """ add xen store watch on control/feature-reboot """
540 self.rebootFeatureWatch = xswatch(self.vm.dompath + "/control/feature-reboot", \
541 self.hvm_reboot_feature)
542 log.debug("hvm reboot feature watch registered")
544 def unregister_reboot_feature_watch(self):
545 """Remove the watch on the control/feature-reboot, if any. Nothrow
546 guarantee."""
548 try:
549 if self.rebootFeatureWatch:
550 self.rebootFeatureWatch.unwatch()
551 except:
552 log.exception("Unwatching hvm reboot feature watch failed.")
553 self.rebootFeatureWatch = None
554 log.debug("hvm reboot feature watch unregistered")
556 def hvm_reboot_feature(self, _):
557 """ watch call back on node control/feature-reboot,
558 if node changed, this function will be called
559 """
560 status = self.vm.readDom('control/feature-reboot')
561 log.debug("hvm_reboot_feature fired, module status=%s", status)
562 if status == '1':
563 self.unregister_shutdown_watch()
565 return True # Keep watching
568 class IA64_HVM_ImageHandler(HVMImageHandler):
570 def getRequiredAvailableMemory(self, mem_kb):
571 page_kb = 16
572 # ROM size for guest firmware, ioreq page, pio page and xenstore page
573 extra_pages = 1024 + 4
574 return mem_kb + extra_pages * page_kb
576 def getRequiredInitialReservation(self):
577 return self.vm.getMemoryTarget()
579 def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
580 # Explicit shadow memory is not a concept
581 return 0
583 class X86_HVM_ImageHandler(HVMImageHandler):
585 def getRequiredAvailableMemory(self, mem_kb):
586 # Add 8 MiB overhead for QEMU's video RAM.
587 return mem_kb + 8192
589 def getRequiredInitialReservation(self):
590 return self.vm.getMemoryTarget()
592 def getRequiredMaximumReservation(self):
593 return self.vm.getMemoryMaximum()
595 def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
596 # 256 pages (1MB) per vcpu,
597 # plus 1 page per MiB of RAM for the P2M map,
598 # plus 1 page per MiB of RAM to shadow the resident processes.
599 # This is higher than the minimum that Xen would allocate if no value
600 # were given (but the Xen minimum is for safety, not performance).
601 return max(4 * (256 * self.vm.getVCpuCount() + 2 * (maxmem_kb / 1024)),
602 shadow_mem_kb)
604 class X86_Linux_ImageHandler(LinuxImageHandler):
606 def buildDomain(self):
607 # set physical mapping limit
608 # add an 8MB slack to balance backend allocations.
609 mem_kb = self.getRequiredMaximumReservation() + (8 * 1024)
610 xc.domain_set_memmap_limit(self.vm.getDomid(), mem_kb)
611 return LinuxImageHandler.buildDomain(self)
613 _handlers = {
614 "powerpc": {
615 "linux": PPC_LinuxImageHandler,
616 "prose": PPC_ProseImageHandler,
617 },
618 "ia64": {
619 "linux": LinuxImageHandler,
620 "hvm": IA64_HVM_ImageHandler,
621 },
622 "x86": {
623 "linux": X86_Linux_ImageHandler,
624 "hvm": X86_HVM_ImageHandler,
625 },
626 }
628 def findImageHandlerClass(image):
629 """Find the image handler class for an image config.
631 @param image config
632 @return ImageHandler subclass or None
633 """
634 image_type = image.image_type()
635 try:
636 return _handlers[arch.type][image_type]
637 except KeyError:
638 raise VmError('unknown image type: ' + image_type)