debuggers.hg

view tools/python/xen/xend/XendDomainInfo.py @ 2645:8bc05c989fb3

bitkeeper revision 1.1159.102.1 (4163f3e1F0LimXbocHs-QcPviz39JA)

Improve error messages by unwrapping Failures.
author mjw@wray-m-3.hpl.hp.com
date Wed Oct 06 13:32:17 2004 +0000 (2004-10-06)
parents de88814afd44
children b44a94edc865
line source
1 # Copyright (C) 2004 Mike Wray <mike.wray@hp.com>
3 """Representation of a single domain.
4 Includes support for domain construction, using
5 open-ended configurations.
7 Author: Mike Wray <mike.wray@hpl.hp.com>
9 """
11 import string
12 import types
13 import re
14 import sys
15 import os
16 import time
18 from twisted.internet import defer
20 import xen.lowlevel.xc; xc = xen.lowlevel.xc.new()
21 import xen.util.ip
22 from xen.util.ip import _readline, _readlines
24 import sxp
26 import XendConsole
27 xendConsole = XendConsole.instance()
28 from XendLogging import log
29 from XendRoot import get_component
31 import server.SrvDaemon
32 xend = server.SrvDaemon.instance()
34 from XendError import VmError
36 """The length of domain names that Xen can handle.
37 The names stored in Xen itself are not used for much, and
38 xend can handle domain names of any length.
39 """
40 MAX_DOMAIN_NAME = 15
42 """Flag for a block device backend domain."""
43 SIF_BLK_BE_DOMAIN = (1<<4)
45 """Flag for a net device backend domain."""
46 SIF_NET_BE_DOMAIN = (1<<5)
48 """Shutdown code for poweroff."""
49 DOMAIN_POWEROFF = 0
50 """Shutdown code for reboot."""
51 DOMAIN_REBOOT = 1
52 """Shutdown code for suspend."""
53 DOMAIN_SUSPEND = 2
55 """Map shutdown codes to strings."""
56 shutdown_reasons = {
57 DOMAIN_POWEROFF: "poweroff",
58 DOMAIN_REBOOT : "reboot",
59 DOMAIN_SUSPEND : "suspend" }
61 RESTART_ALWAYS = 'always'
62 RESTART_ONREBOOT = 'onreboot'
63 RESTART_NEVER = 'never'
65 restart_modes = [
66 RESTART_ALWAYS,
67 RESTART_ONREBOOT,
68 RESTART_NEVER,
69 ]
71 STATE_RESTART_PENDING = 'pending'
72 STATE_RESTART_BOOTING = 'booting'
74 STATE_VM_OK = "ok"
75 STATE_VM_TERMINATED = "terminated"
78 def domain_exists(name):
79 # See comment in XendDomain constructor.
80 xd = get_component('xen.xend.XendDomain')
81 return xd.domain_exists(name)
83 def shutdown_reason(code):
84 """Get a shutdown reason from a code.
86 @param code: shutdown code
87 @type code: int
88 @return: shutdown reason
89 @rtype: string
90 """
91 return shutdown_reasons.get(code, "?")
93 def blkdev_name_to_number(name):
94 """Take the given textual block-device name (e.g., '/dev/sda1',
95 'hda') and return the device number used by the OS. """
97 if not re.match( '^/dev/', name ):
98 n = '/dev/' + name
99 else:
100 n = name
102 try:
103 return os.stat(n).st_rdev
104 except:
105 pass
107 # see if this is a hex device number
108 if re.match( '^(0x)?[0-9a-fA-F]+$', name ):
109 return string.atoi(name,16)
111 return None
113 def lookup_raw_partn(name):
114 """Take the given block-device name (e.g., '/dev/sda1', 'hda')
115 and return a dictionary { device, start_sector,
116 nr_sectors, type }
117 device: Device number of the given partition
118 start_sector: Index of first sector of the partition
119 nr_sectors: Number of sectors comprising this partition
120 type: 'Disk' or identifying name for partition type
121 """
123 n = blkdev_name_to_number(name)
124 if n:
125 return [ { 'device' : n,
126 'start_sector' : long(0),
127 'nr_sectors' : long(1L<<63),
128 'type' : 'Disk' } ]
129 else:
130 return None
132 def lookup_disk_uname(uname):
133 """Lookup a list of segments for a physical device.
135 @param uname: name of the device in the format \'phy:dev\' for a physical device
136 @type uname: string
137 @return: list of extents that make up the named device
138 @rtype: [dict]
139 """
140 ( type, d_name ) = string.split( uname, ':' )
142 if type == "phy":
143 segments = lookup_raw_partn( d_name )
144 else:
145 segments = None
146 return segments
148 def make_disk(vm, config, uname, dev, mode, recreate=0):
149 """Create a virtual disk device for a domain.
151 @param vm: vm
152 @param uname: device to export
153 @param dev: device name in domain
154 @param mode: read/write mode
155 @param recreate: recreate flag (after xend restart)
156 @return: deferred
157 """
158 idx = vm.next_device_index('vbd')
159 segments = lookup_disk_uname(uname)
160 if not segments:
161 raise VmError("vbd: Segments not found: uname=%s" % uname)
162 if len(segments) > 1:
163 raise VmError("vbd: Multi-segment vdisk: uname=%s" % uname)
164 segment = segments[0]
165 # todo: The 'dev' should be looked up in the context of the domain.
166 vdev = blkdev_name_to_number(dev)
167 if not vdev:
168 raise VmError("vbd: Device not found: uname=%s dev=%s" % (uname, dev))
169 ctrl = xend.blkif_create(vm.dom, recreate=recreate)
170 return ctrl.attachDevice(idx, config, vdev, mode, segment, recreate=recreate)
172 def vif_up(iplist):
173 """send an unsolicited ARP reply for all non link-local IP addresses.
175 @param iplist: IP addresses
176 """
178 IP_NONLOCAL_BIND = '/proc/sys/net/ipv4/ip_nonlocal_bind'
180 def get_ip_nonlocal_bind():
181 return int(open(IP_NONLOCAL_BIND, 'r').read()[0])
183 def set_ip_nonlocal_bind(v):
184 print >> open(IP_NONLOCAL_BIND, 'w'), str(v)
186 def link_local(ip):
187 return xen.util.ip.check_subnet(ip, '169.254.0.0', '255.255.0.0')
189 def arping(ip, gw):
190 cmd = '/usr/sbin/arping -A -b -I eth0 -c 1 -s %s %s' % (ip, gw)
191 log.debug(cmd)
192 os.system(cmd)
194 gateway = xen.util.ip.get_current_ipgw() or '255.255.255.255'
195 nlb = get_ip_nonlocal_bind()
196 if not nlb: set_ip_nonlocal_bind(1)
197 try:
198 for ip in iplist:
199 if not link_local(ip):
200 arping(ip, gateway)
201 finally:
202 if not nlb: set_ip_nonlocal_bind(0)
204 config_handlers = {}
206 def add_config_handler(name, h):
207 """Add a handler for a config field.
209 @param name: field name
210 @param h: handler: fn(vm, config, field, index)
211 """
212 config_handlers[name] = h
214 def get_config_handler(name):
215 """Get a handler for a config field.
217 returns handler or None
218 """
219 return config_handlers.get(name)
221 """Table of handlers for virtual machine images.
222 Indexed by image type.
223 """
224 image_handlers = {}
226 def add_image_handler(name, h):
227 """Add a handler for an image type
228 @param name: image type
229 @param h: handler: fn(config, name, memory, image)
230 """
231 image_handlers[name] = h
233 def get_image_handler(name):
234 """Get the handler for an image type.
235 @param name: image type
236 @return: handler or None
237 """
238 return image_handlers.get(name)
240 """Table of handlers for devices.
241 Indexed by device type.
242 """
243 device_handlers = {}
245 def add_device_handler(name, h):
246 """Add a handler for a device type.
248 @param name: device type
249 @param h: handler: fn(vm, dev)
250 """
251 device_handlers[name] = h
253 def get_device_handler(name):
254 """Get the handler for a device type.
256 @param name : device type
257 @return; handler or None
258 """
259 return device_handlers.get(name)
261 def vm_create(config):
262 """Create a VM from a configuration.
263 If a vm has been partially created and there is an error it
264 is destroyed.
266 @param config configuration
267 @return: Deferred
268 @raise: VmError for invalid configuration
269 """
270 vm = XendDomainInfo()
271 return vm.construct(config)
273 def vm_recreate(savedinfo, info):
274 """Create the VM object for an existing domain.
276 @param savedinfo: saved info from the domain DB
277 @type savedinfo: sxpr
278 @param info: domain info from xc
279 @type info: xc domain dict
280 @return: deferred
281 """
282 vm = XendDomainInfo()
283 vm.recreate = 1
284 vm.setdom(info['dom'])
285 #vm.name = info['name']
286 vm.memory = info['mem_kb']/1024
287 start_time = sxp.child_value(savedinfo, 'start_time')
288 if start_time is not None:
289 vm.start_time = float(start_time)
290 vm.restart_state = sxp.child_value(savedinfo, 'restart_state')
291 restart_time = sxp.child_value(savedinfo, 'restart_time')
292 if restart_time is not None:
293 vm.restart_time = float(restart_time)
294 config = sxp.child_value(savedinfo, 'config')
295 if config:
296 d = vm.construct(config)
297 else:
298 vm.name = info['name']
299 d = defer.succeed(vm)
300 vm.recreate = 0
301 return d
303 def vm_restore(src, progress=0):
304 """Restore a VM from a disk image.
306 src saved state to restore
307 progress progress reporting flag
308 returns deferred
309 raises VmError for invalid configuration
310 """
311 vm = XendDomainInfo()
312 ostype = "linux" #todo Set from somewhere (store in the src?).
313 restorefn = getattr(xc, "%s_restore" % ostype)
314 d = restorefn(state_file=src, progress=progress)
315 dom = int(d['dom'])
316 if dom < 0:
317 raise VmError('restore failed')
318 try:
319 vmconfig = sxp.from_string(d['vmconfig'])
320 config = sxp.child_value(vmconfig, 'config')
321 except Exception, ex:
322 raise VmError('config error: ' + str(ex))
323 deferred = vm.dom_construct(dom, config)
324 def vifs_cb(val, vm):
325 vif_up(vm.ipaddrs)
326 return vm
327 deferred.addCallback(vifs_cb, vm)
328 return deferred
330 def dom_get(dom):
331 """Get info from xen for an existing domain.
333 @param dom: domain id
334 @return: info or None
335 """
336 domlist = xc.domain_getinfo(dom, 1)
337 if domlist and dom == domlist[0]['dom']:
338 return domlist[0]
339 return None
341 def append_deferred(dlist, v):
342 """Append a value to a deferred list if it is a deferred.
344 @param dlist: list of deferreds
345 @param v: value to add
346 """
347 if isinstance(v, defer.Deferred):
348 dlist.append(v)
350 def dlist_err(val):
351 """Error callback suitable for a deferred list.
352 In a deferred list the error callback is called with with Failure((error, index)).
353 This callback extracts the error and returns it.
355 @param val: Failure containing (error, index)
356 @type val: twisted.internet.failure.Failure
357 """
359 (error, index) = val.value
360 return error
362 class XendDomainInfo:
363 """Virtual machine object."""
365 """Minimum time between domain restarts in seconds.
366 """
367 MINIMUM_RESTART_TIME = 20
369 def __init__(self):
370 self.recreate = 0
371 self.restore = 0
372 self.config = None
373 self.id = None
374 self.dom = None
375 self.cpu_weight = 1
376 self.start_time = None
377 self.name = None
378 self.memory = None
379 self.image = None
380 self.ramdisk = None
381 self.cmdline = None
382 self.console = None
383 self.devices = {}
384 self.device_index = {}
385 self.configs = []
386 self.info = None
387 self.ipaddrs = []
388 self.blkif_backend = 0
389 self.netif_backend = 0
390 #todo: state: running, suspended
391 self.state = STATE_VM_OK
392 #todo: set to migrate info if migrating
393 self.migrate = None
394 self.restart_mode = RESTART_ONREBOOT
395 self.restart_state = None
396 self.restart_time = None
397 self.console_port = None
399 def setdom(self, dom):
400 """Set the domain id.
402 @param dom: domain id
403 """
404 self.dom = int(dom)
405 self.id = str(dom)
407 def update(self, info):
408 """Update with info from xc.domain_getinfo().
409 """
410 self.info = info
411 self.memory = self.info['mem_kb'] / 1024
413 def __str__(self):
414 s = "domain"
415 s += " id=" + self.id
416 s += " name=" + self.name
417 s += " memory=" + str(self.memory)
418 if self.console:
419 s += " console=" + str(self.console.console_port)
420 if self.image:
421 s += " image=" + self.image
422 s += ""
423 return s
425 __repr__ = __str__
427 def sxpr(self):
428 sxpr = ['domain',
429 ['id', self.id],
430 ['name', self.name],
431 ['memory', self.memory] ]
433 if self.info:
434 sxpr.append(['maxmem', self.info['maxmem_kb']/1024 ])
435 run = (self.info['running'] and 'r') or '-'
436 block = (self.info['blocked'] and 'b') or '-'
437 pause = (self.info['paused'] and 'p') or '-'
438 shut = (self.info['shutdown'] and 's') or '-'
439 crash = (self.info['crashed'] and 'c') or '-'
440 state = run + block + pause + shut + crash
441 sxpr.append(['state', state])
442 if self.info['shutdown']:
443 reason = shutdown_reason(self.info['shutdown_reason'])
444 sxpr.append(['shutdown_reason', reason])
445 sxpr.append(['cpu', self.info['cpu']])
446 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
448 if self.start_time:
449 up_time = time.time() - self.start_time
450 sxpr.append(['up_time', str(up_time) ])
451 sxpr.append(['start_time', str(self.start_time) ])
453 if self.console:
454 sxpr.append(self.console.sxpr())
455 if self.restart_state:
456 sxpr.append(['restart_state', self.restart_state])
457 if self.restart_time:
458 sxpr.append(['restart_time', str(self.restart_time)])
459 if self.config:
460 sxpr.append(['config', self.config])
461 return sxpr
463 def check_name(self, name):
464 """Check if a vm name is valid. Valid names start with a non-digit
465 and contain alphabetic characters, digits, or characters in '_-.:/+'.
466 The same name cannot be used for more than one vm at the same time.
468 @param name: name
469 @raise: VMerror if invalid
470 """
471 if self.recreate: return
472 if name is None or name == '':
473 raise VmError('missing vm name')
474 if name[0] in string.digits:
475 raise VmError('invalid vm name')
476 for c in name:
477 if c in string.digits: continue
478 if c in '_-.:/+': continue
479 if c in string.ascii_letters: continue
480 raise VmError('invalid vm name')
481 dominfo = domain_exists(name)
482 # When creating or rebooting, a domain with my name should not exist.
483 # When restoring, a domain with my name will exist, but it should have
484 # my domain id.
485 if not dominfo:
486 return
487 if dominfo.is_terminated():
488 return
489 if not self.dom or (dominfo.dom != self.dom):
490 raise VmError('vm name clash: ' + name)
492 def construct(self, config):
493 """Construct the vm instance from its configuration.
495 @param config: configuration
496 @return: deferred
497 @raise: VmError on error
498 """
499 # todo - add support for scheduling params?
500 self.config = config
501 try:
502 self.name = sxp.child_value(config, 'name')
503 self.check_name(self.name)
504 try:
505 self.cpu_weight = float(sxp.child_value(config, 'cpu_weight', '1'))
506 except:
507 raise VmError('invalid cpu weight')
508 if self.restore and self.dom:
509 xc.domain_setname(self.dom, self.name)
510 self.memory = int(sxp.child_value(config, 'memory'))
511 if self.memory is None:
512 raise VmError('missing memory size')
514 self.init_domain()
515 self.configure_console()
516 self.construct_image()
517 self.configure_restart()
518 self.configure_backends()
519 deferred = self.configure()
520 def cberr(err):
521 self.destroy()
522 return err
523 deferred.addErrback(cberr)
524 except StandardError, ex:
525 # Catch errors, cleanup and re-raise.
526 self.destroy()
527 raise
528 return deferred
530 def construct_image(self):
531 """Construct the boot image for the domain.
533 @return vm
534 """
535 image = sxp.child_value(self.config, 'image')
536 if image is None:
537 raise VmError('missing image')
538 image_name = sxp.name(image)
539 if image_name is None:
540 raise VmError('missing image name')
541 image_handler = get_image_handler(image_name)
542 if image_handler is None:
543 raise VmError('unknown image type: ' + image_name)
544 image_handler(self, image)
545 return self
547 def config_devices(self, name):
548 """Get a list of the 'device' nodes of a given type from the config.
550 @param name: device type
551 @type name: string
552 @return: device configs
553 @rtype: list
554 """
555 devices = []
556 for d in sxp.children(self.config, 'device'):
557 dev = sxp.child0(d)
558 if dev is None: continue
559 if name == sxp.name(dev):
560 devices.append(dev)
561 return devices
563 def config_device(self, type, idx):
564 """Get a device config from the device nodes of a given type
565 from the config.
567 @param type: device type
568 @type type: string
569 @param idx: index
570 @type idx: int
571 @return config or None
572 """
573 devs = self.config_devices(type)
574 if 0 <= idx < len(devs):
575 return devs[idx]
576 else:
577 return None
579 def next_device_index(self, type):
580 """Get the next index for a given device type.
582 @param type: device type
583 @type type: string
584 @return device index
585 @rtype: int
586 """
587 idx = self.device_index.get(type, 0)
588 self.device_index[type] = idx + 1
589 return idx
591 def add_device(self, type, dev):
592 """Add a device to a virtual machine.
594 @param type: device type
595 @param dev: device to add
596 """
597 dl = self.devices.get(type, [])
598 dl.append(dev)
599 self.devices[type] = dl
601 def remove_device(self, type, dev):
602 """Remove a device from a virtual machine.
604 @param type: device type
605 @param dev: device
606 """
607 dl = self.devices.get(type, [])
608 if dev in dl:
609 dl.remove(dev)
611 def get_devices(self, type):
612 """Get a list of the devices of a given type.
614 @param type: device type
615 @return: devices
616 """
617 val = self.devices.get(type, [])
618 return val
620 def get_device_by_id(self, type, id):
621 """Get the device with the given id.
623 @param id: device id
624 @return: device or None
625 """
626 dl = self.get_devices(type)
627 for d in dl:
628 if d.getprop('id') == id:
629 return d
630 return None
632 def get_device_by_index(self, type, idx):
633 """Get the device with the given index.
635 @param idx: device index
636 @return: device or None
637 """
638 idx = str(idx)
639 dl = self.get_devices(type)
640 for d in dl:
641 if d.getidx() == idx:
642 return d
643 return None
645 def add_config(self, val):
646 """Add configuration data to a virtual machine.
648 @param val: data to add
649 """
650 self.configs.append(val)
652 def destroy(self):
653 """Completely destroy the vm.
654 """
655 self.cleanup()
656 return self.destroy_domain()
658 def destroy_domain(self):
659 """Destroy the vm's domain.
660 The domain will not finally go away unless all vm
661 devices have been released.
662 """
663 if self.dom is None: return 0
664 self.destroy_console()
665 chan = xend.getDomChannel(self.dom)
666 if chan:
667 log.debug("Closing channel to domain %d", self.dom)
668 chan.close()
669 try:
670 return xc.domain_destroy(dom=self.dom)
671 except Exception, err:
672 log.exception("Domain destroy failed: %s", self.name)
674 def destroy_console(self):
675 if self.console:
676 if self.restart_pending():
677 self.console.deregisterChannel()
678 else:
679 log.debug('Closing console, domain %s', self.id)
680 self.console.close()
682 def cleanup(self):
683 """Cleanup vm resources: release devices.
684 """
685 self.state = STATE_VM_TERMINATED
686 self.release_devices()
688 def is_terminated(self):
689 """Check if a domain has been terminated.
690 """
691 return self.state == STATE_VM_TERMINATED
693 def release_devices(self):
694 """Release all vm devices.
695 """
696 self.release_vifs()
697 self.release_vbds()
699 self.devices = {}
700 self.device_index = {}
701 self.configs = []
702 self.ipaddrs = []
704 def release_vifs(self):
705 """Release vm virtual network devices (vifs).
706 """
707 if self.dom is None: return
708 ctrl = xend.netif_get(self.dom)
709 if ctrl:
710 log.debug("Destroying vifs for domain %d", self.dom)
711 ctrl.destroy()
713 def release_vbds(self):
714 """Release vm virtual block devices (vbds).
715 """
716 if self.dom is None: return
717 ctrl = xend.blkif_get(self.dom)
718 if ctrl:
719 log.debug("Destroying vbds for domain %d", self.dom)
720 ctrl.destroy()
722 def show(self):
723 """Print virtual machine info.
724 """
725 print "[VM dom=%d name=%s memory=%d" % (self.dom, self.name, self.memory)
726 print "image:"
727 sxp.show(self.image)
728 print
729 for dl in self.devices:
730 for dev in dl:
731 print "device:"
732 sxp.show(dev)
733 print
734 for val in self.configs:
735 print "config:"
736 sxp.show(val)
737 print
738 print "]"
740 def init_domain(self):
741 """Initialize the domain memory.
742 """
743 if self.recreate:
744 return
745 if self.start_time is None:
746 self.start_time = time.time()
747 if self.restore:
748 return
749 dom = self.dom or 0
750 memory = self.memory
751 name = self.name
752 # If the name is over the xen limit, use the end of it.
753 if len(name) > MAX_DOMAIN_NAME:
754 name = name[-MAX_DOMAIN_NAME:]
755 try:
756 cpu = int(sxp.child_value(self.config, 'cpu', '-1'))
757 except:
758 raise VmError('invalid cpu')
759 cpu_weight = self.cpu_weight
760 dom = xc.domain_create(dom= dom, mem_kb= memory * 1024,
761 name= name, cpu= cpu, cpu_weight= cpu_weight)
762 if dom <= 0:
763 raise VmError('Creating domain failed: name=%s memory=%d'
764 % (name, memory))
765 log.debug('init_domain> Created domain=%d name=%s memory=%d', dom, name, memory)
766 self.setdom(dom)
768 def build_domain(self, ostype, kernel, ramdisk, cmdline):
769 """Build the domain boot image.
770 """
771 if self.recreate or self.restore: return
772 if len(cmdline) >= 256:
773 log.warning('kernel cmdline too long, domain %d', self.dom)
774 dom = self.dom
775 buildfn = getattr(xc, '%s_build' % ostype)
776 flags = 0
777 if self.netif_backend: flags |= SIF_NET_BE_DOMAIN
778 if self.blkif_backend: flags |= SIF_BLK_BE_DOMAIN
779 err = buildfn(dom = dom,
780 image = kernel,
781 control_evtchn = self.console.getRemotePort(),
782 cmdline = cmdline,
783 ramdisk = ramdisk,
784 flags = flags)
785 if err != 0:
786 raise VmError('Building domain failed: type=%s dom=%d err=%d'
787 % (ostype, dom, err))
789 def create_domain(self, ostype, kernel, ramdisk, cmdline):
790 """Create a domain. Builds the image but does not configure it.
792 @param ostype: OS type
793 @param kernel: kernel image
794 @param ramdisk: kernel ramdisk
795 @param cmdline: kernel commandline
796 """
797 if not self.recreate:
798 if not os.path.isfile(kernel):
799 raise VmError('Kernel image does not exist: %s' % kernel)
800 if ramdisk and not os.path.isfile(ramdisk):
801 raise VmError('Kernel ramdisk does not exist: %s' % ramdisk)
802 #self.init_domain()
803 if self.console:
804 self.console.registerChannel()
805 else:
806 self.console = xendConsole.console_create(self.dom, console_port=self.console_port)
807 self.build_domain(ostype, kernel, ramdisk, cmdline)
808 self.image = kernel
809 self.ramdisk = ramdisk
810 self.cmdline = cmdline
812 def create_devices(self):
813 """Create the devices for a vm.
815 @return: Deferred
816 @raise: VmError for invalid devices
817 """
818 dlist = []
819 devices = sxp.children(self.config, 'device')
820 index = {}
821 for d in devices:
822 dev = sxp.child0(d)
823 if dev is None:
824 raise VmError('invalid device')
825 dev_name = sxp.name(dev)
826 dev_index = index.get(dev_name, 0)
827 dev_handler = get_device_handler(dev_name)
828 if dev_handler is None:
829 raise VmError('unknown device type: ' + dev_name)
830 v = dev_handler(self, dev, dev_index)
831 append_deferred(dlist, v)
832 index[dev_name] = dev_index + 1
833 deferred = defer.DeferredList(dlist, fireOnOneErrback=1)
834 deferred.addErrback(dlist_err)
835 return deferred
837 def device_create(self, dev_config):
838 """Create a new device.
840 @param dev_config: device configuration
841 @return: deferred
842 """
843 dev_name = sxp.name(dev_config)
844 dev_handler = get_device_handler(dev_name)
845 if dev_handler is None:
846 raise VmError('unknown device type: ' + dev_name)
847 devs = self.get_devices(dev_name)
848 dev_index = len(devs)
849 self.config.append(['device', dev_config])
850 d = dev_handler(self, dev_config, dev_index, change=1)
851 def cbok(dev):
852 return dev.sxpr()
853 d.addCallback(cbok)
854 return d
856 def device_configure(self, dev_config, idx):
857 """Configure an existing device.
859 @param dev_config: device configuration
860 @param idx: device index
861 """
862 type = sxp.name(dev_config)
863 dev = self.get_device_by_index(type, idx)
864 if not dev:
865 raise VmError('invalid device: %s %s' % (type, idx))
866 new_config = dev.configure(dev_config, change=1)
867 devs = self.devices.get(type)
868 index = devs.index(dev)
869 # Patch new config into device configs.
870 dev_configs = self.config_devices(type)
871 old_config = dev_configs[index]
872 dev_configs[index] = new_config
873 # Patch new config into vm config.
874 new_full_config = ['device', new_config]
875 old_full_config = ['device', old_config]
876 old_index = self.config.index(old_full_config)
877 self.config[old_index] = new_full_config
878 return new_config
880 def device_destroy(self, type, idx):
881 """Destroy a device.
883 @param type: device type
884 @param idx: device index
885 """
886 dev = self.get_device_by_index(type, idx)
887 if not dev:
888 raise VmError('invalid device: %s %s' % (type, idx))
889 devs = self.devices.get(type)
890 index = devs.index(dev)
891 dev_config = self.config_device(type, index)
892 if dev_config:
893 self.config.remove(['device', dev_config])
894 dev.destroy(change=1)
895 self.remove_device(type, dev)
897 def configure_memory(self):
898 """Configure vm memory limit.
899 """
900 maxmem = sxp.get_child_value(self.config, "maxmem")
901 if maxmem is None:
902 maxmem = self.memory
903 xc.domain_setmaxmem(self.dom, maxmem_kb = maxmem * 1024)
905 def configure_console(self):
906 """Configure the vm console port.
907 """
908 x = sxp.child_value(self.config, 'console')
909 if x:
910 try:
911 port = int(x)
912 except:
913 raise VmError('invalid console:' + str(x))
914 self.console_port = port
916 def configure_restart(self):
917 """Configure the vm restart mode.
918 """
919 r = sxp.child_value(self.config, 'restart', RESTART_ONREBOOT)
920 if r not in restart_modes:
921 raise VmError('invalid restart mode: ' + str(r))
922 self.restart_mode = r;
924 def restart_needed(self, reason):
925 """Determine if the vm needs to be restarted when shutdown
926 for the given reason.
928 @param reason: shutdown reason
929 @return 1 if needs restaert, 0 otherwise
930 """
931 if self.restart_mode == RESTART_NEVER:
932 return 0
933 if self.restart_mode == RESTART_ALWAYS:
934 return 1
935 if self.restart_mode == RESTART_ONREBOOT:
936 return reason == 'reboot'
937 return 0
939 def restart_cancel(self):
940 """Cancel a vm restart.
941 """
942 self.restart_state = None
944 def restarting(self):
945 """Put the vm into restart mode.
946 """
947 self.restart_state = STATE_RESTART_PENDING
949 def restart_pending(self):
950 """Test if the vm has a pending restart.
951 """
952 return self.restart_state == STATE_RESTART_PENDING
954 def restart_check(self):
955 """Check if domain restart is OK.
956 To prevent restart loops, raise an error if it is
957 less than MINIMUM_RESTART_TIME seconds since the last restart.
958 """
959 tnow = time.time()
960 if self.restart_time is not None:
961 tdelta = tnow - self.restart_time
962 if tdelta < self.MINIMUM_RESTART_TIME:
963 self.restart_cancel()
964 msg = 'VM %s restarting too fast' % self.name
965 log.error(msg)
966 raise VmError(msg)
967 self.restart_time = tnow
969 def restart(self):
970 """Restart the domain after it has exited.
971 Reuses the domain id and console port.
973 @return: deferred
974 """
975 try:
976 self.restart_check()
977 self.restart_state = STATE_RESTART_BOOTING
978 d = self.construct(self.config)
979 finally:
980 self.restart_state = None
981 return d
983 def configure_backends(self):
984 """Set configuration flags if the vm is a backend for netif or blkif.
985 Configure the backends to use for vbd and vif if specified.
986 """
987 for c in sxp.children(self.config, 'backend'):
988 v = sxp.child0(c)
989 name = sxp.name(v)
990 if name == 'blkif':
991 self.blkif_backend = 1
992 elif name == 'netif':
993 self.netif_backend = 1
994 else:
995 raise VmError('invalid backend type:' + str(name))
997 def configure(self):
998 """Configure a vm.
1000 @return: deferred - calls callback with vm
1001 """
1002 d = self.create_blkif()
1003 d.addCallback(lambda x: self.create_devices())
1004 d.addCallback(self._configure)
1005 return d
1007 def _configure(self, val):
1008 d = self.configure_fields()
1009 def cbok(results):
1010 return self
1011 def cberr(err):
1012 self.destroy()
1013 return err
1014 d.addCallback(cbok)
1015 d.addErrback(cberr)
1016 return d
1018 def create_blkif(self):
1019 """Create the block device interface (blkif) for the vm.
1020 The vm needs a blkif even if it doesn't have any disks
1021 at creation time, for example when it uses NFS root.
1023 @return: deferred
1024 """
1025 ctrl = xend.blkif_create(self.dom, recreate=self.recreate)
1026 back = ctrl.getBackendInterface(0)
1027 return back.connect(recreate=self.recreate)
1029 def dom_construct(self, dom, config):
1030 """Construct a vm for an existing domain.
1032 @param dom: domain id
1033 @param config: domain configuration
1034 @return: deferred
1035 """
1036 d = dom_get(dom)
1037 if not d:
1038 raise VmError("Domain not found: %d" % dom)
1039 try:
1040 self.restore = 1
1041 self.setdom(dom)
1042 #self.name = d['name']
1043 self.memory = d['mem_kb']/1024
1044 deferred = self.construct(config)
1045 finally:
1046 self.restore = 0
1047 return deferred
1049 def configure_fields(self):
1050 """Process the vm configuration fields using the registered handlers.
1051 """
1052 dlist = []
1053 index = {}
1054 for field in sxp.children(self.config):
1055 field_name = sxp.name(field)
1056 field_index = index.get(field_name, 0)
1057 field_handler = get_config_handler(field_name)
1058 # Ignore unknown fields. Warn?
1059 if field_handler:
1060 v = field_handler(self, self.config, field, field_index)
1061 append_deferred(dlist, v)
1062 else:
1063 log.warning("Unknown config field %s", field_name)
1064 index[field_name] = field_index + 1
1065 d = defer.DeferredList(dlist, fireOnOneErrback=1)
1066 deferred.addErrback(dlist_err)
1067 return d
1070 def vm_image_linux(vm, image):
1071 """Create a VM for a linux image.
1073 @param name: vm name
1074 @param memory: vm memory
1075 @param image: image config
1076 @return: vm
1077 """
1078 kernel = sxp.child_value(image, "kernel")
1079 cmdline = ""
1080 ip = sxp.child_value(image, "ip", "dhcp")
1081 if ip:
1082 cmdline += " ip=" + ip
1083 root = sxp.child_value(image, "root")
1084 if root:
1085 cmdline += " root=" + root
1086 args = sxp.child_value(image, "args")
1087 if args:
1088 cmdline += " " + args
1089 ramdisk = sxp.child_value(image, "ramdisk", '')
1090 vm.create_domain("linux", kernel, ramdisk, cmdline)
1091 return vm
1093 def vm_image_netbsd(vm, image):
1094 """Create a VM for a bsd image.
1096 @param name: vm name
1097 @param memory: vm memory
1098 @param image: image config
1099 @return: vm
1100 """
1101 #todo: Same as for linux. Is that right? If so can unify them.
1102 kernel = sxp.child_value(image, "kernel")
1103 cmdline = ""
1104 ip = sxp.child_value(image, "ip", "dhcp")
1105 if ip:
1106 cmdline += "ip=" + ip
1107 root = sxp.child_value(image, "root")
1108 if root:
1109 cmdline += "root=" + root
1110 args = sxp.child_value(image, "args")
1111 if args:
1112 cmdline += " " + args
1113 ramdisk = sxp.child_value(image, "ramdisk", '')
1114 vm.create_domain("netbsd", kernel, ramdisk, cmdline)
1115 return vm
1118 def vm_dev_vif(vm, val, index, change=0):
1119 """Create a virtual network interface (vif).
1121 @param vm: virtual machine
1122 @param val: vif config
1123 @param index: vif index
1124 @return: deferred
1125 """
1126 vif = vm.next_device_index('vif')
1127 vmac = sxp.child_value(val, "mac")
1128 ctrl = xend.netif_create(vm.dom, recreate=vm.recreate)
1129 log.debug("Creating vif dom=%d vif=%d mac=%s", vm.dom, vif, str(vmac))
1130 defer = ctrl.attachDevice(vif, val, recreate=vm.recreate)
1131 def cbok(dev):
1132 dev.vifctl('up', vmname=vm.name)
1133 vm.add_device('vif', dev)
1134 if change:
1135 dev.interfaceChanged()
1136 return dev
1137 defer.addCallback(cbok)
1138 return defer
1140 def vm_dev_vbd(vm, val, index, change=0):
1141 """Create a virtual block device (vbd).
1143 @param vm: virtual machine
1144 @param val: vbd config
1145 @param index: vbd index
1146 @return: deferred
1147 """
1148 uname = sxp.child_value(val, 'uname')
1149 if not uname:
1150 raise VmError('vbd: Missing uname')
1151 dev = sxp.child_value(val, 'dev')
1152 if not dev:
1153 raise VmError('vbd: Missing dev')
1154 mode = sxp.child_value(val, 'mode', 'r')
1155 log.debug("Creating vbd dom=%d uname=%s dev=%s", vm.dom, uname, dev)
1156 defer = make_disk(vm, val, uname, dev, mode, vm.recreate)
1157 def fn(vbd):
1158 vbd.dev = dev
1159 vbd.uname = uname
1160 vm.add_device('vbd', vbd)
1161 if change:
1162 vbd.interfaceChanged()
1163 return vbd
1164 defer.addCallback(fn)
1165 return defer
1167 def parse_pci(val):
1168 """Parse a pci field.
1169 """
1170 if isinstance(val, types.StringType):
1171 radix = 10
1172 if val.startswith('0x') or val.startswith('0X'):
1173 radix = 16
1174 v = int(val, radix)
1175 else:
1176 v = val
1177 return v
1179 def vm_dev_pci(vm, val, index, change=0):
1180 """Add a pci device.
1182 @param vm: virtual machine
1183 @param val: device configuration
1184 @param index: device index
1185 @return: 0 on success
1186 """
1187 bus = sxp.child_value(val, 'bus')
1188 if not bus:
1189 raise VmError('pci: Missing bus')
1190 dev = sxp.child_value(val, 'dev')
1191 if not dev:
1192 raise VmError('pci: Missing dev')
1193 func = sxp.child_value(val, 'func')
1194 if not func:
1195 raise VmError('pci: Missing func')
1196 try:
1197 bus = parse_pci(bus)
1198 dev = parse_pci(dev)
1199 func = parse_pci(func)
1200 except:
1201 raise VmError('pci: invalid parameter')
1202 log.debug("Creating pci device dom=%d bus=%x dev=%x func=%x", vm.dom, bus, dev, func)
1203 rc = xc.physdev_pci_access_modify(dom=vm.dom, bus=bus, dev=dev,
1204 func=func, enable=1)
1205 if rc < 0:
1206 #todo non-fatal
1207 raise VmError('pci: Failed to configure device: bus=%s dev=%s func=%s' %
1208 (bus, dev, func))
1209 return rc
1212 def vm_field_ignore(vm, config, val, index):
1213 """Dummy config field handler used for fields with built-in handling.
1215 @param vm: virtual machine
1216 @param config: vm config
1217 @param val: config field
1218 @param index: field index
1219 """
1220 pass
1222 def vm_field_maxmem(vm, config, val, index):
1223 """Configure vm memory limit.
1225 @param vm: virtual machine
1226 @param config: vm config
1227 @param val: config field
1228 @param index: field index
1229 """
1230 maxmem = sxp.child0(val)
1231 if maxmem is None:
1232 maxmem = vm.memory
1233 try:
1234 maxmem = int(maxmem)
1235 except:
1236 raise VmError("invalid maxmem: " + str(maxmem))
1237 xc.domain_setmaxmem(vm.dom, maxmem_kb = maxmem * 1024)
1239 # Register image handlers.
1240 add_image_handler('linux', vm_image_linux)
1241 add_image_handler('netbsd', vm_image_netbsd)
1243 # Register device handlers.
1244 add_device_handler('vif', vm_dev_vif)
1245 add_device_handler('vbd', vm_dev_vbd)
1246 add_device_handler('pci', vm_dev_pci)
1248 # Ignore the fields we already handle.
1249 add_config_handler('name', vm_field_ignore)
1250 add_config_handler('memory', vm_field_ignore)
1251 add_config_handler('cpu', vm_field_ignore)
1252 add_config_handler('cpu_weight', vm_field_ignore)
1253 add_config_handler('console', vm_field_ignore)
1254 add_config_handler('image', vm_field_ignore)
1255 add_config_handler('device', vm_field_ignore)
1256 add_config_handler('backend', vm_field_ignore)
1258 # Register other config handlers.
1259 add_config_handler('maxmem', vm_field_maxmem)