ia64/xen-unstable

view tools/python/xen/xend/image.py @ 13609:959e79bfe913

Fix device reordering that occurs when the config gets read into dictionaries.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author Ewan Mellor <ewan@xensource.com>
date Wed Jan 24 16:48:41 2007 +0000 (2007-01-24)
parents e2ca6bd16046
children 3bb7136c8fb4
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 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
28 from xen.xend.XendLogging import log
29 from xen.xend.server.netif import randomMAC
30 from xen.xend.xenstore.xswatch import xswatch
31 from xen.xend import arch
32 from xen.xend import FlatDeviceTree
34 xc = xen.lowlevel.xc.xc()
36 MAX_GUEST_CMDLINE = 1024
39 def create(vm, vmConfig, imageConfig, deviceConfig):
40 """Create an image handler for a vm.
42 @return ImageHandler instance
43 """
44 return findImageHandlerClass(imageConfig)(vm, vmConfig, imageConfig,
45 deviceConfig)
48 class ImageHandler:
49 """Abstract base class for image handlers.
51 createImage() is called to configure and build the domain from its
52 kernel image and ramdisk etc.
54 The method buildDomain() is used to build the domain, and must be
55 defined in a subclass. Usually this is the only method that needs
56 defining in a subclass.
58 The method createDeviceModel() is called to create the domain device
59 model if it needs one. The default is to do nothing.
61 The method destroy() is called when the domain is destroyed.
62 The default is to do nothing.
63 """
65 ostype = None
68 def __init__(self, vm, vmConfig, imageConfig, deviceConfig):
69 self.vm = vm
71 self.bootloader = False
72 self.kernel = None
73 self.ramdisk = None
74 self.cmdline = None
76 self.configure(vmConfig, imageConfig, deviceConfig)
78 def configure(self, vmConfig, imageConfig, _):
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))
95 def cleanupBootloading(self):
96 if self.bootloader:
97 self.unlink(self.kernel)
98 self.unlink(self.ramdisk)
101 def unlink(self, f):
102 if not f: return
103 try:
104 os.unlink(f)
105 except OSError, ex:
106 log.warning("error removing bootloader file '%s': %s", f, ex)
109 def createImage(self):
110 """Entry point to create domain memory image.
111 Override in subclass if needed.
112 """
113 return self.createDomain()
116 def createDomain(self):
117 """Build the domain boot image.
118 """
119 # Set params and call buildDomain().
121 if not os.path.isfile(self.kernel):
122 raise VmError('Kernel image does not exist: %s' % self.kernel)
123 if self.ramdisk and not os.path.isfile(self.ramdisk):
124 raise VmError('Kernel ramdisk does not exist: %s' % self.ramdisk)
125 if len(self.cmdline) >= MAX_GUEST_CMDLINE:
126 log.warning('kernel cmdline too long, domain %d',
127 self.vm.getDomid())
129 log.info("buildDomain os=%s dom=%d vcpus=%d", self.ostype,
130 self.vm.getDomid(), self.vm.getVCpuCount())
132 result = self.buildDomain()
134 if isinstance(result, dict):
135 return result
136 else:
137 raise VmError('Building domain failed: ostype=%s dom=%d err=%s'
138 % (self.ostype, self.vm.getDomid(), str(result)))
140 def getRequiredAvailableMemory(self, mem_kb):
141 """@param mem_kb The configured maxmem or memory, in KiB.
142 @return The corresponding required amount of memory for the domain,
143 also in KiB. This is normally the given mem_kb, but architecture- or
144 image-specific code may override this to add headroom where
145 necessary."""
146 return mem_kb
148 def getRequiredInitialReservation(self):
149 """@param mem_kb The configured memory, in KiB.
150 @return The corresponding required amount of memory to be free, also
151 in KiB. This is normally the same as getRequiredAvailableMemory, but
152 architecture- or image-specific code may override this to
153 add headroom where necessary."""
154 return self.getRequiredAvailableMemory(self.vm.getMemoryTarget())
156 def getRequiredMaximumReservation(self):
157 """@param mem_kb The maximum possible memory, in KiB.
158 @return The corresponding required amount of memory to be free, also
159 in KiB. This is normally the same as getRequiredAvailableMemory, but
160 architecture- or image-specific code may override this to
161 add headroom where necessary."""
162 return self.getRequiredAvailableMemory(self.vm.getMemoryMaximum())
164 def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
165 """@param shadow_mem_kb The configured shadow memory, in KiB.
166 @param maxmem_kb The configured maxmem, in KiB.
167 @return The corresponding required amount of shadow memory, also in
168 KiB."""
169 # PV domains don't need any shadow memory
170 return 0
172 def buildDomain(self):
173 """Build the domain. Define in subclass."""
174 raise NotImplementedError()
176 def createDeviceModel(self, restore = False):
177 """Create device model for the domain (define in subclass if needed)."""
178 pass
180 def destroy(self):
181 """Extra cleanup on domain destroy (define in subclass if needed)."""
182 pass
185 def recreate(self):
186 pass
189 class LinuxImageHandler(ImageHandler):
191 ostype = "linux"
193 def buildDomain(self):
194 store_evtchn = self.vm.getStorePort()
195 console_evtchn = self.vm.getConsolePort()
197 mem_mb = self.getRequiredInitialReservation() / 1024
199 log.debug("domid = %d", self.vm.getDomid())
200 log.debug("memsize = %d", mem_mb)
201 log.debug("image = %s", self.kernel)
202 log.debug("store_evtchn = %d", store_evtchn)
203 log.debug("console_evtchn = %d", console_evtchn)
204 log.debug("cmdline = %s", self.cmdline)
205 log.debug("ramdisk = %s", self.ramdisk)
206 log.debug("vcpus = %d", self.vm.getVCpuCount())
207 log.debug("features = %s", self.vm.getFeatures())
209 return xc.linux_build(domid = self.vm.getDomid(),
210 memsize = mem_mb,
211 image = self.kernel,
212 store_evtchn = store_evtchn,
213 console_evtchn = console_evtchn,
214 cmdline = self.cmdline,
215 ramdisk = self.ramdisk,
216 features = self.vm.getFeatures())
218 class PPC_LinuxImageHandler(LinuxImageHandler):
220 ostype = "linux"
222 def configure(self, vmConfig, imageConfig, deviceConfig):
223 LinuxImageHandler.configure(self, vmConfig, imageConfig, deviceConfig)
224 self.imageConfig = imageConfig
226 def buildDomain(self):
227 store_evtchn = self.vm.getStorePort()
228 console_evtchn = self.vm.getConsolePort()
230 mem_mb = self.getRequiredInitialReservation() / 1024
232 log.debug("domid = %d", self.vm.getDomid())
233 log.debug("memsize = %d", mem_mb)
234 log.debug("image = %s", self.kernel)
235 log.debug("store_evtchn = %d", store_evtchn)
236 log.debug("console_evtchn = %d", console_evtchn)
237 log.debug("cmdline = %s", self.cmdline)
238 log.debug("ramdisk = %s", self.ramdisk)
239 log.debug("vcpus = %d", self.vm.getVCpuCount())
240 log.debug("features = %s", self.vm.getFeatures())
242 devtree = FlatDeviceTree.build(self)
244 return xc.linux_build(domid = self.vm.getDomid(),
245 memsize = mem_mb,
246 image = self.kernel,
247 store_evtchn = store_evtchn,
248 console_evtchn = console_evtchn,
249 cmdline = self.cmdline,
250 ramdisk = self.ramdisk,
251 features = self.vm.getFeatures(),
252 arch_args = devtree.to_bin())
254 def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
255 """@param shadow_mem_kb The configured shadow memory, in KiB.
256 @param maxmem_kb The configured maxmem, in KiB.
257 @return The corresponding required amount of shadow memory, also in
258 KiB.
259 PowerPC currently uses "shadow memory" to refer to the hash table."""
260 return max(maxmem_kb / 64, shadow_mem_kb)
263 class PPC_ProseImageHandler(LinuxImageHandler):
265 ostype = "prose"
267 def configure(self, imageConfig, deviceConfig):
268 LinuxImageHandler.configure(self, imageConfig, deviceConfig)
269 self.imageConfig = imageConfig
271 def buildDomain(self):
272 store_evtchn = self.vm.getStorePort()
273 console_evtchn = self.vm.getConsolePort()
275 mem_mb = self.getRequiredInitialReservation() / 1024
277 log.debug("dom = %d", self.vm.getDomid())
278 log.debug("memsize = %d", mem_mb)
279 log.debug("image = %s", self.kernel)
280 log.debug("store_evtchn = %d", store_evtchn)
281 log.debug("console_evtchn = %d", console_evtchn)
282 log.debug("cmdline = %s", self.cmdline)
283 log.debug("ramdisk = %s", self.ramdisk)
284 log.debug("vcpus = %d", self.vm.getVCpuCount())
285 log.debug("features = %s", self.vm.getFeatures())
287 devtree = FlatDeviceTree.build(self)
289 return xc.arch_prose_build(dom = self.vm.getDomid(),
290 memsize = mem_mb,
291 image = self.kernel,
292 store_evtchn = store_evtchn,
293 console_evtchn = console_evtchn,
294 cmdline = self.cmdline,
295 ramdisk = self.ramdisk,
296 features = self.vm.getFeatures(),
297 arch_args = devtree.to_bin())
299 def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
300 """@param shadow_mem_kb The configured shadow memory, in KiB.
301 @param maxmem_kb The configured maxmem, in KiB.
302 @return The corresponding required amount of shadow memory, also in
303 KiB.
304 PowerPC currently uses "shadow memory" to refer to the hash table."""
305 return max(maxmem_kb / 64, shadow_mem_kb)
308 class HVMImageHandler(ImageHandler):
310 ostype = "hvm"
312 def __init__(self, vm, vmConfig, imageConfig, deviceConfig):
313 ImageHandler.__init__(self, vm, vmConfig, imageConfig, deviceConfig)
314 self.shutdownWatch = None
315 self.rebootFeatureWatch = None
317 def configure(self, vmConfig, imageConfig, deviceConfig):
318 ImageHandler.configure(self, vmConfig, imageConfig, deviceConfig)
320 if not self.kernel:
321 self.kernel = '/usr/lib/xen/boot/hvmloader'
323 info = xc.xeninfo()
324 if 'hvm' not in info['xen_caps']:
325 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
326 "supported by your CPU and enabled in your BIOS?")
328 self.dmargs = self.parseDeviceModelArgs(vmConfig)
329 self.device_model = imageConfig['hvm'].get('device_model')
330 if not self.device_model:
331 raise VmError("hvm: missing device model")
333 self.display = imageConfig['hvm'].get('display')
334 self.xauthority = imageConfig['hvm'].get('xauthority')
335 self.vncconsole = imageConfig['hvm'].get('vncconsole')
337 self.vm.storeVm(("image/dmargs", " ".join(self.dmargs)),
338 ("image/device-model", self.device_model),
339 ("image/display", self.display))
341 self.pid = None
343 self.dmargs += self.configVNC(imageConfig)
345 self.pae = imageConfig['hvm'].get('pae', 0)
346 self.apic = imageConfig['hvm'].get('apic', 0)
347 self.acpi = imageConfig['hvm']['devices'].get('acpi', 0)
350 def buildDomain(self):
351 store_evtchn = self.vm.getStorePort()
353 mem_mb = self.getRequiredInitialReservation() / 1024
355 log.debug("domid = %d", self.vm.getDomid())
356 log.debug("image = %s", self.kernel)
357 log.debug("store_evtchn = %d", store_evtchn)
358 log.debug("memsize = %d", mem_mb)
359 log.debug("vcpus = %d", self.vm.getVCpuCount())
360 log.debug("pae = %d", self.pae)
361 log.debug("acpi = %d", self.acpi)
362 log.debug("apic = %d", self.apic)
364 self.register_shutdown_watch()
365 self.register_reboot_feature_watch()
367 return xc.hvm_build(domid = self.vm.getDomid(),
368 image = self.kernel,
369 store_evtchn = store_evtchn,
370 memsize = mem_mb,
371 vcpus = self.vm.getVCpuCount(),
372 pae = self.pae,
373 acpi = self.acpi,
374 apic = self.apic)
376 # Return a list of cmd line args to the device models based on the
377 # xm config file
378 def parseDeviceModelArgs(self, vmConfig):
379 dmargs = [ 'boot', 'fda', 'fdb', 'soundhw',
380 'localtime', 'serial', 'stdvga', 'isa',
381 'acpi', 'usb', 'usbdevice', 'keymap' ]
382 hvmDeviceConfig = vmConfig['image']['hvm']['devices']
384 ret = ['-vcpus', str(self.vm.getVCpuCount())]
386 for a in dmargs:
387 v = hvmDeviceConfig.get(a)
389 # python doesn't allow '-' in variable names
390 if a == 'stdvga': a = 'std-vga'
391 if a == 'keymap': a = 'k'
393 # Handle booleans gracefully
394 if a in ['localtime', 'std-vga', 'isa', 'usb', 'acpi']:
395 if v != None: v = int(v)
396 if v: ret.append("-%s" % a)
397 else:
398 if v:
399 ret.append("-%s" % a)
400 ret.append("%s" % v)
402 if a in ['fda', 'fdb']:
403 if v:
404 if not os.path.isabs(v):
405 raise VmError("Floppy file %s does not exist." % v)
406 log.debug("args: %s, val: %s" % (a,v))
408 # Handle disk/network related options
409 mac = None
410 ret = ret + ["-domain-name", str(self.vm.info['name_label'])]
411 nics = 0
413 for devuuid in vmConfig['vbd_refs']:
414 devinfo = vmConfig['devices'][devuuid][1]
415 uname = devinfo.get('uname')
416 if uname is not None and 'file:' in uname:
417 (_, vbdparam) = string.split(uname, ':', 1)
418 if not os.path.isfile(vbdparam):
419 raise VmError('Disk image does not exist: %s' %
420 vbdparam)
422 for devuuid in vmConfig['vif_refs']:
423 devinfo = vmConfig['devices'][devuuid][1]
424 dtype = devinfo.get('type', 'ioemu')
425 if dtype != 'ioemu':
426 continue
427 nics += 1
428 mac = devinfo.get('mac')
429 if mac is None:
430 mac = randomMAC()
431 bridge = devinfo.get('bridge', 'xenbr0')
432 model = devinfo.get('model', 'rtl8139')
433 ret.append("-net")
434 ret.append("nic,vlan=%d,macaddr=%s,model=%s" %
435 (nics, mac, model))
436 ret.append("-net")
437 ret.append("tap,vlan=%d,bridge=%s" % (nics, bridge))
439 return ret
441 def configVNC(self, imageConfig):
442 # Handle graphics library related options
443 vnc = imageConfig.get('vnc')
444 sdl = imageConfig.get('sdl')
445 ret = []
446 nographic = imageConfig.get('nographic')
448 # get password from VM config (if password omitted, None)
449 vncpasswd_vmconfig = imageConfig.get('vncpasswd')
451 if nographic:
452 ret.append('-nographic')
453 return ret
455 if vnc:
456 vncdisplay = imageConfig.get('vncdisplay',
457 int(self.vm.getDomid()))
458 vncunused = imageConfig.get('vncunused')
460 if vncunused:
461 ret += ['-vncunused']
462 else:
463 ret += ['-vnc', '%d' % vncdisplay]
465 vnclisten = imageConfig.get('vnclisten')
467 if not(vnclisten):
468 vnclisten = (xen.xend.XendOptions.instance().
469 get_vnclisten_address())
470 if vnclisten:
471 ret += ['-vnclisten', vnclisten]
473 vncpasswd = vncpasswd_vmconfig
474 if vncpasswd is None:
475 vncpasswd = (xen.xend.XendOptions.instance().
476 get_vncpasswd_default())
477 if vncpasswd is None:
478 raise VmError('vncpasswd is not set up in ' +
479 'VMconfig and xend-config.')
480 if vncpasswd != '':
481 self.vm.storeVm("vncpasswd", vncpasswd)
483 return ret
485 def createDeviceModel(self, restore = False):
486 if self.pid:
487 return
488 # Execute device model.
489 #todo: Error handling
490 args = [self.device_model]
491 args = args + ([ "-d", "%d" % self.vm.getDomid(),
492 "-m", "%s" % (self.getRequiredInitialReservation() / 1024)])
493 args = args + self.dmargs
494 if restore:
495 args = args + ([ "-loadvm", "/tmp/xen.qemu-dm.%d" % self.vm.getDomid() ])
496 env = dict(os.environ)
497 if self.display:
498 env['DISPLAY'] = self.display
499 if self.xauthority:
500 env['XAUTHORITY'] = self.xauthority
501 if self.vncconsole:
502 args = args + ([ "-vncviewer" ])
503 log.info("spawning device models: %s %s", self.device_model, args)
504 # keep track of pid and spawned options to kill it later
505 self.pid = os.spawnve(os.P_NOWAIT, self.device_model, args, env)
506 self.vm.storeDom("image/device-model-pid", self.pid)
507 log.info("device model pid: %d", self.pid)
509 def recreate(self):
510 self.register_shutdown_watch()
511 self.register_reboot_feature_watch()
512 self.pid = self.vm.gatherDom(('image/device-model-pid', int))
514 def destroy(self, suspend = False):
515 self.unregister_shutdown_watch()
516 self.unregister_reboot_feature_watch();
517 if self.pid:
518 try:
519 sig = signal.SIGKILL
520 if suspend:
521 log.info("use sigusr1 to signal qemu %d", self.pid)
522 sig = signal.SIGUSR1
523 os.kill(self.pid, sig)
524 except OSError, exn:
525 log.exception(exn)
526 try:
527 os.waitpid(self.pid, 0)
528 except OSError, exn:
529 # This is expected if Xend has been restarted within the
530 # life of this domain. In this case, we can kill the process,
531 # but we can't wait for it because it's not our child.
532 pass
533 self.pid = None
535 def register_shutdown_watch(self):
536 """ add xen store watch on control/shutdown """
537 self.shutdownWatch = xswatch(self.vm.dompath + "/control/shutdown",
538 self.hvm_shutdown)
539 log.debug("hvm shutdown watch registered")
541 def unregister_shutdown_watch(self):
542 """Remove the watch on the control/shutdown, if any. Nothrow
543 guarantee."""
545 try:
546 if self.shutdownWatch:
547 self.shutdownWatch.unwatch()
548 except:
549 log.exception("Unwatching hvm shutdown watch failed.")
550 self.shutdownWatch = None
551 log.debug("hvm shutdown watch unregistered")
553 def hvm_shutdown(self, _):
554 """ watch call back on node control/shutdown,
555 if node changed, this function will be called
556 """
557 xd = xen.xend.XendDomain.instance()
558 try:
559 vm = xd.domain_lookup( self.vm.getDomid() )
560 except XendError:
561 # domain isn't registered, no need to clean it up.
562 return False
564 reason = vm.getShutdownReason()
565 log.debug("hvm_shutdown fired, shutdown reason=%s", reason)
566 if reason in REVERSE_DOMAIN_SHUTDOWN_REASONS:
567 vm.info['shutdown'] = 1
568 vm.info['shutdown_reason'] = \
569 REVERSE_DOMAIN_SHUTDOWN_REASONS[reason]
570 vm.refreshShutdown(vm.info)
572 return True # Keep watching
574 def register_reboot_feature_watch(self):
575 """ add xen store watch on control/feature-reboot """
576 self.rebootFeatureWatch = xswatch(self.vm.dompath + "/control/feature-reboot", \
577 self.hvm_reboot_feature)
578 log.debug("hvm reboot feature watch registered")
580 def unregister_reboot_feature_watch(self):
581 """Remove the watch on the control/feature-reboot, if any. Nothrow
582 guarantee."""
584 try:
585 if self.rebootFeatureWatch:
586 self.rebootFeatureWatch.unwatch()
587 except:
588 log.exception("Unwatching hvm reboot feature watch failed.")
589 self.rebootFeatureWatch = None
590 log.debug("hvm reboot feature watch unregistered")
592 def hvm_reboot_feature(self, _):
593 """ watch call back on node control/feature-reboot,
594 if node changed, this function will be called
595 """
596 status = self.vm.readDom('control/feature-reboot')
597 log.debug("hvm_reboot_feature fired, module status=%s", status)
598 if status == '1':
599 self.unregister_shutdown_watch()
601 return True # Keep watching
604 class IA64_HVM_ImageHandler(HVMImageHandler):
606 def getRequiredAvailableMemory(self, mem_kb):
607 page_kb = 16
608 # ROM size for guest firmware, ioreq page and xenstore page
609 extra_pages = 1024 + 3
610 return mem_kb + extra_pages * page_kb
612 def getRequiredInitialReservation(self):
613 return self.vm.getMemoryTarget()
615 def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
616 # Explicit shadow memory is not a concept
617 return 0
619 class X86_HVM_ImageHandler(HVMImageHandler):
621 def getRequiredAvailableMemory(self, mem_kb):
622 # Add 8 MiB overhead for QEMU's video RAM.
623 return mem_kb + 8192
625 def getRequiredInitialReservation(self):
626 return self.vm.getMemoryTarget()
628 def getRequiredMaximumReservation(self):
629 return self.vm.getMemoryMaximum()
631 def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
632 # 256 pages (1MB) per vcpu,
633 # plus 1 page per MiB of RAM for the P2M map,
634 # plus 1 page per MiB of RAM to shadow the resident processes.
635 # This is higher than the minimum that Xen would allocate if no value
636 # were given (but the Xen minimum is for safety, not performance).
637 return max(4 * (256 * self.vm.getVCpuCount() + 2 * (maxmem_kb / 1024)),
638 shadow_mem_kb)
640 class X86_Linux_ImageHandler(LinuxImageHandler):
642 def buildDomain(self):
643 # set physical mapping limit
644 # add an 8MB slack to balance backend allocations.
645 mem_kb = self.getRequiredMaximumReservation() + (8 * 1024)
646 xc.domain_set_memmap_limit(self.vm.getDomid(), mem_kb)
647 return LinuxImageHandler.buildDomain(self)
649 _handlers = {
650 "powerpc": {
651 "linux": PPC_LinuxImageHandler,
652 "prose": PPC_ProseImageHandler,
653 },
654 "ia64": {
655 "linux": LinuxImageHandler,
656 "hvm": IA64_HVM_ImageHandler,
657 },
658 "x86": {
659 "linux": X86_Linux_ImageHandler,
660 "hvm": X86_HVM_ImageHandler,
661 },
662 }
664 def findImageHandlerClass(image):
665 """Find the image handler class for an image config.
667 @param image config
668 @return ImageHandler subclass or None
669 """
670 image_type = image['type']
671 if image_type is None:
672 raise VmError('missing image type')
673 try:
674 return _handlers[arch.type][image_type]
675 except KeyError:
676 raise VmError('unknown image type: ' + image_type)