ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 7441:857b79d27993

Set the console limit for DomUs based on a value set in xend-config.sxp.

Signed-off-by: Dan Smith <danms@us.ibm.com>
Signed-off-by: Ewan Mellor <ewan@xensource.com>
author emellor@leeni.uk.xensource.com
date Wed Oct 19 13:37:16 2005 +0100 (2005-10-19)
parents 7c951e3eb5ab
children 6a7253b1ce8a
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) 2004, 2005 Mike Wray <mike.wray@hp.com>
16 # Copyright (C) 2005 XenSource Ltd
17 #============================================================================
19 """Representation of a single domain.
20 Includes support for domain construction, using
21 open-ended configurations.
23 Author: Mike Wray <mike.wray@hp.com>
25 """
27 import logging
28 import string
29 import time
30 import threading
32 import xen.lowlevel.xc
33 from xen.util import asserts
34 from xen.util.blkif import blkdev_uname_to_file
36 from xen.xend import image
37 from xen.xend import scheduler
38 from xen.xend import sxp
39 from xen.xend import XendRoot
40 from xen.xend.XendBootloader import bootloader
41 from xen.xend.XendError import XendError, VmError
42 from xen.xend.XendRoot import get_component
44 import uuid
46 from xen.xend.xenstore.xstransact import xstransact
47 from xen.xend.xenstore.xsutil import GetDomainPath, IntroduceDomain
49 """Shutdown code for poweroff."""
50 DOMAIN_POWEROFF = 0
52 """Shutdown code for reboot."""
53 DOMAIN_REBOOT = 1
55 """Shutdown code for suspend."""
56 DOMAIN_SUSPEND = 2
58 """Shutdown code for crash."""
59 DOMAIN_CRASH = 3
61 """Shutdown code for halt."""
62 DOMAIN_HALT = 4
64 """Map shutdown codes to strings."""
65 shutdown_reasons = {
66 DOMAIN_POWEROFF: "poweroff",
67 DOMAIN_REBOOT : "reboot",
68 DOMAIN_SUSPEND : "suspend",
69 DOMAIN_CRASH : "crash",
70 DOMAIN_HALT : "halt"
71 }
73 restart_modes = [
74 "restart",
75 "destroy",
76 "preserve",
77 "rename-restart"
78 ]
80 STATE_DOM_OK = 1
81 STATE_DOM_SHUTDOWN = 2
83 SHUTDOWN_TIMEOUT = 30
85 DOMROOT = '/local/domain/'
86 VMROOT = '/vm/'
88 ZOMBIE_PREFIX = 'Zombie-'
90 xc = xen.lowlevel.xc.new()
91 xroot = XendRoot.instance()
93 log = logging.getLogger("xend.XendDomainInfo")
94 #log.setLevel(logging.TRACE)
97 ## Configuration entries that we expect to round-trip -- be read from the
98 # config file or xc, written to save-files (i.e. through sxpr), and reused as
99 # config on restart or restore, all without munging. Some configuration
100 # entries are munged for backwards compatibility reasons, or because they
101 # don't come out of xc in the same form as they are specified in the config
102 # file, so those are handled separately.
103 ROUNDTRIPPING_CONFIG_ENTRIES = [
104 ('name', str),
105 ('ssidref', int),
106 ('vcpus', int),
107 ('vcpu_avail', int),
108 ('cpu_weight', float),
109 ('bootloader', str),
110 ('on_poweroff', str),
111 ('on_reboot', str),
112 ('on_crash', str)
113 ]
116 #
117 # There are a number of CPU-related fields:
118 #
119 # vcpus: the number of virtual CPUs this domain is configured to use.
120 # vcpu_avail: a bitmap telling the guest domain whether it may use each of
121 # its VCPUs. This is translated to
122 # <dompath>/cpu/<id>/availability = {online,offline} for use
123 # by the guest domain.
124 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
125 # CPUs that that VCPU may use.
126 # cpu: a configuration setting requesting that VCPU 0 is pinned to
127 # the specified physical CPU.
128 #
129 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
130 # across save, restore, migrate, and restart). The other settings are only
131 # specific to the domain, so are lost when the VM moves.
132 #
135 def create(config):
136 """Create a VM from a configuration.
138 @param config configuration
139 @raise: VmError for invalid configuration
140 """
142 log.debug("XendDomainInfo.create(%s)", config)
144 vm = XendDomainInfo(uuid.create(), parseConfig(config))
145 try:
146 vm.construct()
147 vm.initDomain()
148 vm.storeVmDetails()
149 vm.storeDomDetails()
150 vm.refreshShutdown()
151 return vm
152 except:
153 log.exception('Domain construction failed')
154 vm.destroy()
155 raise
158 def recreate(xeninfo, priv):
159 """Create the VM object for an existing domain. The domain must not
160 be dying, as the paths in the store should already have been removed,
161 and asking us to recreate them causes problems."""
163 log.debug("XendDomainInfo.recreate(%s)", xeninfo)
165 assert not xeninfo['dying']
167 domid = xeninfo['dom']
168 uuid1 = xeninfo['handle']
169 dompath = GetDomainPath(domid)
170 if not dompath:
171 raise XendError(
172 'No domain path in store for existing domain %d' % domid)
173 try:
174 vmpath = xstransact.Read(dompath, "vm")
175 if not vmpath:
176 raise XendError(
177 'No vm path in store for existing domain %d' % domid)
178 uuid2_str = xstransact.Read(vmpath, "uuid")
179 if not uuid2_str:
180 raise XendError(
181 'No vm/uuid path in store for existing domain %d' % domid)
183 uuid2 = uuid.fromString(uuid2_str)
185 if uuid1 != uuid2:
186 raise XendError(
187 'Uuid in store does not match uuid for existing domain %d: '
188 '%s != %s' % (domid, uuid2_str, uuid.toString(uuid1)))
190 log.info("Recreating domain %d, UUID %s.", domid, uuid2_str)
192 vm = XendDomainInfo(uuid1, xeninfo, domid, dompath, True)
194 except Exception, exn:
195 log.warn(str(exn))
197 log.info("Recreating domain %d with UUID %s.", domid,
198 uuid.toString(uuid1))
200 vm = XendDomainInfo(uuid1, xeninfo, domid, dompath, True)
201 vm.removeDom()
202 vm.removeVm()
203 vm.storeVmDetails()
204 vm.storeDomDetails()
206 vm.refreshShutdown(xeninfo)
207 return vm
210 def restore(config):
211 """Create a domain and a VM object to do a restore.
213 @param config: domain configuration
214 """
216 log.debug("XendDomainInfo.restore(%s)", config)
218 vm = XendDomainInfo(uuid.fromString(sxp.child_value(config, 'uuid')),
219 parseConfig(config))
220 try:
221 vm.construct()
222 vm.storeVmDetails()
223 vm.createChannels()
224 return vm
225 except:
226 vm.destroy()
227 raise
230 def parseConfig(config):
231 def get_cfg(name, conv = None):
232 val = sxp.child_value(config, name)
234 if conv and not val is None:
235 try:
236 return conv(val)
237 except TypeError, exn:
238 raise VmError(
239 'Invalid setting %s = %s in configuration: %s' %
240 (name, val, str(exn)))
241 else:
242 return val
245 log.debug("parseConfig: config is %s", config)
247 result = {}
249 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
250 result[e[0]] = get_cfg(e[0], e[1])
252 result['memory'] = get_cfg('memory', int)
253 result['mem_kb'] = get_cfg('mem_kb', int)
254 result['maxmem'] = get_cfg('maxmem', int)
255 result['maxmem_kb'] = get_cfg('maxmem_kb', int)
256 result['cpu'] = get_cfg('cpu', int)
257 result['image'] = get_cfg('image')
259 try:
260 if result['image']:
261 result['vcpus'] = int(sxp.child_value(result['image'],
262 'vcpus', 1))
263 else:
264 result['vcpus'] = 1
265 except TypeError, exn:
266 raise VmError(
267 'Invalid configuration setting: vcpus = %s: %s' %
268 (sxp.child_value(result['image'], 'vcpus', 1), str(exn)))
270 result['backend'] = []
271 for c in sxp.children(config, 'backend'):
272 result['backend'].append(sxp.name(sxp.child0(c)))
274 result['device'] = []
275 for d in sxp.children(config, 'device'):
276 c = sxp.child0(d)
277 result['device'].append((sxp.name(c), c))
279 # Configuration option "restart" is deprecated. Parse it, but
280 # let on_xyz override it if they are present.
281 restart = get_cfg('restart')
282 if restart:
283 def handle_restart(event, val):
284 if result[event] is None:
285 result[event] = val
287 if restart == "onreboot":
288 handle_restart('on_poweroff', 'destroy')
289 handle_restart('on_reboot', 'restart')
290 handle_restart('on_crash', 'destroy')
291 elif restart == "always":
292 handle_restart('on_poweroff', 'restart')
293 handle_restart('on_reboot', 'restart')
294 handle_restart('on_crash', 'restart')
295 elif restart == "never":
296 handle_restart('on_poweroff', 'destroy')
297 handle_restart('on_reboot', 'destroy')
298 handle_restart('on_crash', 'destroy')
299 else:
300 log.warn("Ignoring malformed and deprecated config option "
301 "restart = %s", restart)
303 log.debug("parseConfig: result is %s", result)
304 return result
307 def domain_by_name(name):
308 # See comment in XendDomain constructor.
309 xd = get_component('xen.xend.XendDomain')
310 return xd.domain_lookup_by_name_nr(name)
312 def shutdown_reason(code):
313 """Get a shutdown reason from a code.
315 @param code: shutdown code
316 @type code: int
317 @return: shutdown reason
318 @rtype: string
319 """
320 return shutdown_reasons.get(code, "?")
322 def dom_get(dom):
323 """Get info from xen for an existing domain.
325 @param dom: domain id
326 @return: info or None
327 """
328 try:
329 domlist = xc.domain_getinfo(dom, 1)
330 if domlist and dom == domlist[0]['dom']:
331 return domlist[0]
332 except Exception, err:
333 # ignore missing domain
334 log.debug("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
335 return None
337 class XendDomainInfo:
338 """Virtual machine object."""
340 """Minimum time between domain restarts in seconds.
341 """
342 MINIMUM_RESTART_TIME = 20
345 def __init__(self, uuidbytes, info, domid = None, dompath = None,
346 augment = False):
348 self.uuidbytes = uuidbytes
349 self.info = info
351 if domid is not None:
352 self.domid = domid
353 elif 'dom' in info:
354 self.domid = int(info['dom'])
355 else:
356 self.domid = None
358 self.vmpath = VMROOT + uuid.toString(uuidbytes)
359 self.dompath = dompath
361 if augment:
362 self.augmentInfo()
364 self.validateInfo()
366 self.image = None
368 self.store_port = None
369 self.store_mfn = None
370 self.console_port = None
371 self.console_mfn = None
373 self.state = STATE_DOM_OK
374 self.state_updated = threading.Condition()
375 self.refresh_shutdown_lock = threading.Condition()
378 ## private:
380 def augmentInfo(self):
381 """Augment self.info, as given to us through {@link #recreate}, with
382 values taken from the store. This recovers those values known to xend
383 but not to the hypervisor.
384 """
385 def useIfNeeded(name, val):
386 if not self.infoIsSet(name) and val is not None:
387 self.info[name] = val
389 params = (("name", str),
390 ("on_poweroff", str),
391 ("on_reboot", str),
392 ("on_crash", str),
393 ("image", str),
394 ("vcpus", int),
395 ("vcpu_avail", int),
396 ("start_time", float))
398 from_store = self.gatherVm(*params)
400 map(lambda x, y: useIfNeeded(x[0], y), params, from_store)
402 device = []
403 for c in controllerClasses:
404 devconfig = self.getDeviceConfigurations(c)
405 if devconfig:
406 device.extend(map(lambda x: (c, x), devconfig))
407 useIfNeeded('device', device)
410 def validateInfo(self):
411 """Validate and normalise the info block. This has either been parsed
412 by parseConfig, or received from xc through recreate and augmented by
413 the current store contents.
414 """
415 def defaultInfo(name, val):
416 if not self.infoIsSet(name):
417 self.info[name] = val()
419 try:
420 defaultInfo('name', lambda: "Domain-%d" % self.domid)
421 defaultInfo('ssidref', lambda: 0)
422 defaultInfo('on_poweroff', lambda: "destroy")
423 defaultInfo('on_reboot', lambda: "restart")
424 defaultInfo('on_crash', lambda: "restart")
425 defaultInfo('cpu', lambda: None)
426 defaultInfo('cpu_weight', lambda: 1.0)
427 defaultInfo('vcpus', lambda: int(1))
429 self.info['vcpus'] = int(self.info['vcpus'])
431 defaultInfo('vcpu_avail', lambda: (1 << self.info['vcpus']) - 1)
432 defaultInfo('bootloader', lambda: None)
433 defaultInfo('backend', lambda: [])
434 defaultInfo('device', lambda: [])
435 defaultInfo('image', lambda: None)
437 self.check_name(self.info['name'])
439 if isinstance(self.info['image'], str):
440 self.info['image'] = sxp.from_string(self.info['image'])
442 # Internally, we keep only maxmem_KiB, and not maxmem or maxmem_kb
443 # (which come from outside, and are in MiB and KiB respectively).
444 # This means that any maxmem or maxmem_kb settings here have come
445 # from outside, and maxmem_KiB must be updated to reflect them.
446 # If we have both maxmem and maxmem_kb and these are not
447 # consistent, then this is an error, as we've no way to tell which
448 # one takes precedence.
450 # Exactly the same thing applies to memory_KiB, memory, and
451 # mem_kb.
453 def discard_negatives(name):
454 if self.infoIsSet(name) and self.info[name] < 0:
455 del self.info[name]
457 def valid_KiB_(mb_name, kb_name):
458 discard_negatives(kb_name)
459 discard_negatives(mb_name)
461 if self.infoIsSet(kb_name):
462 if self.infoIsSet(mb_name):
463 mb = self.info[mb_name]
464 kb = self.info[kb_name]
465 if mb * 1024 == kb:
466 return kb
467 else:
468 raise VmError(
469 'Inconsistent %s / %s settings: %s / %s' %
470 (mb_name, kb_name, mb, kb))
471 else:
472 return self.info[kb_name]
473 elif self.infoIsSet(mb_name):
474 return self.info[mb_name] * 1024
475 else:
476 return None
478 def valid_KiB(mb_name, kb_name):
479 result = valid_KiB_(mb_name, kb_name)
480 if result is None or result < 0:
481 raise VmError('Invalid %s / %s: %s' %
482 (mb_name, kb_name, result))
483 else:
484 return result
486 def delIf(name):
487 if name in self.info:
488 del self.info[name]
490 self.info['memory_KiB'] = valid_KiB('memory', 'mem_kb')
491 delIf('memory')
492 delIf('mem_kb')
493 self.info['maxmem_KiB'] = valid_KiB_('maxmem', 'maxmem_kb')
494 delIf('maxmem')
495 delIf('maxmem_kb')
497 if not self.info['maxmem_KiB']:
498 self.info['maxmem_KiB'] = 1 << 30
500 if self.info['maxmem_KiB'] > self.info['memory_KiB']:
501 self.info['maxmem_KiB'] = self.info['memory_KiB']
503 for (n, c) in self.info['device']:
504 if not n or not c or n not in controllerClasses:
505 raise VmError('invalid device (%s, %s)' %
506 (str(n), str(c)))
508 for event in ['on_poweroff', 'on_reboot', 'on_crash']:
509 if self.info[event] not in restart_modes:
510 raise VmError('invalid restart event: %s = %s' %
511 (event, str(self.info[event])))
513 except KeyError, exn:
514 log.exception(exn)
515 raise VmError('Unspecified domain detail: %s' % exn)
518 def readVm(self, *args):
519 return xstransact.Read(self.vmpath, *args)
521 def writeVm(self, *args):
522 return xstransact.Write(self.vmpath, *args)
524 def removeVm(self, *args):
525 return xstransact.Remove(self.vmpath, *args)
527 def gatherVm(self, *args):
528 return xstransact.Gather(self.vmpath, *args)
531 ## public:
533 def storeVm(self, *args):
534 return xstransact.Store(self.vmpath, *args)
537 ## private:
539 def readDom(self, *args):
540 return xstransact.Read(self.dompath, *args)
542 def writeDom(self, *args):
543 return xstransact.Write(self.dompath, *args)
546 ## public:
548 def removeDom(self, *args):
549 return xstransact.Remove(self.dompath, *args)
552 ## private:
554 def storeDom(self, *args):
555 return xstransact.Store(self.dompath, *args)
558 ## public:
560 def completeRestore(self, store_mfn, console_mfn):
562 self.store_mfn = store_mfn
563 self.console_mfn = console_mfn
565 self.introduceDomain()
566 self.create_devices()
567 self.storeDomDetails()
568 self.unpause()
569 self.refreshShutdown()
572 def storeVmDetails(self):
573 to_store = {
574 'uuid': uuid.toString(self.uuidbytes),
576 # XXX
577 'memory/target': str(self.info['memory_KiB'])
578 }
580 if self.infoIsSet('image'):
581 to_store['image'] = sxp.to_string(self.info['image'])
583 for k in ['name', 'ssidref', 'on_poweroff', 'on_reboot', 'on_crash',
584 'vcpus', 'vcpu_avail']:
585 if self.infoIsSet(k):
586 to_store[k] = str(self.info[k])
588 log.debug("Storing VM details: %s", to_store)
590 self.writeVm(to_store)
593 def storeDomDetails(self):
594 to_store = {
595 'domid': str(self.domid),
596 'vm': self.vmpath,
597 'console/limit': str(xroot.get_console_limit() * 1024),
598 'memory/target': str(self.info['memory_KiB'])
599 }
601 for (k, v) in self.info.items():
602 if v:
603 to_store[k] = str(v)
605 def f(n, v):
606 if v is not None:
607 to_store[n] = str(v)
609 f('console/port', self.console_port)
610 f('console/ring-ref', self.console_mfn)
611 f('store/port', self.store_port)
612 f('store/ring-ref', self.store_mfn)
614 to_store.update(self.vcpuDomDetails())
616 log.debug("Storing domain details: %s", to_store)
618 self.writeDom(to_store)
621 ## private:
623 def vcpuDomDetails(self):
624 def availability(n):
625 if self.info['vcpu_avail'] & (1 << n):
626 return 'online'
627 else:
628 return 'offline'
630 result = {}
631 for v in range(0, self.info['vcpus']):
632 result["cpu/%d/availability" % v] = availability(v)
633 return result
636 def setDomid(self, domid):
637 """Set the domain id.
639 @param dom: domain id
640 """
641 self.domid = domid
642 self.storeDom("domid", self.domid)
644 def getDomid(self):
645 return self.domid
647 def setName(self, name):
648 self.check_name(name)
649 self.info['name'] = name
650 self.storeVm("name", name)
652 def getName(self):
653 return self.info['name']
655 def getDomainPath(self):
656 return self.dompath
659 def getStorePort(self):
660 """For use only by image.py and XendCheckpoint.py."""
661 return self.store_port
664 def getConsolePort(self):
665 """For use only by image.py and XendCheckpoint.py"""
666 return self.console_port
669 def getVCpuCount(self):
670 return self.info['vcpus']
673 def setVCpuCount(self, vcpus):
674 self.info['vcpu_avail'] = (1 << vcpus) - 1
675 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
676 self.writeDom(self.vcpuDomDetails())
679 def getSsidref(self):
680 return self.info['ssidref']
682 def getMemoryTarget(self):
683 """Get this domain's target memory size, in KiB."""
684 return self.info['memory_KiB']
687 def refreshShutdown(self, xeninfo = None):
688 # If set at the end of this method, a restart is required, with the
689 # given reason. This restart has to be done out of the scope of
690 # refresh_shutdown_lock.
691 restart_reason = None
693 self.refresh_shutdown_lock.acquire()
694 try:
695 if xeninfo is None:
696 xeninfo = dom_get(self.domid)
697 if xeninfo is None:
698 # The domain no longer exists. This will occur if we have
699 # scheduled a timer to check for shutdown timeouts and the
700 # shutdown succeeded. It will also occur if someone
701 # destroys a domain beneath us. We clean up the domain,
702 # just in case, but we can't clean up the VM, because that
703 # VM may have migrated to a different domain on this
704 # machine.
705 self.cleanupDomain()
706 return
708 if xeninfo['dying']:
709 # Dying means that a domain has been destroyed, but has not
710 # yet been cleaned up by Xen. This state could persist
711 # indefinitely if, for example, another domain has some of its
712 # pages mapped. We might like to diagnose this problem in the
713 # future, but for now all we do is make sure that it's not us
714 # holding the pages, by calling cleanupDomain. We can't
715 # clean up the VM, as above.
716 self.cleanupDomain()
717 return
719 elif xeninfo['crashed']:
720 if self.readDom('xend/shutdown_completed'):
721 # We've seen this shutdown already, but we are preserving
722 # the domain for debugging. Leave it alone.
723 return
725 log.warn('Domain has crashed: name=%s id=%d.',
726 self.info['name'], self.domid)
728 if xroot.get_enable_dump():
729 self.dumpCore()
731 restart_reason = 'crash'
733 elif xeninfo['shutdown']:
734 if self.readDom('xend/shutdown_completed'):
735 # We've seen this shutdown already, but we are preserving
736 # the domain for debugging. Leave it alone.
737 return
739 else:
740 reason = shutdown_reason(xeninfo['shutdown_reason'])
742 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
743 self.info['name'], self.domid, reason)
745 self.clearRestart()
747 if reason == 'suspend':
748 self.state_set(STATE_DOM_SHUTDOWN)
749 # Don't destroy the domain. XendCheckpoint will do
750 # this once it has finished.
751 elif reason in ['poweroff', 'reboot']:
752 restart_reason = reason
753 else:
754 self.destroy()
756 elif self.dompath is None:
757 # We have yet to manage to call introduceDomain on this
758 # domain. This can happen if a restore is in progress, or has
759 # failed. Ignore this domain.
760 pass
761 else:
762 # Domain is alive. If we are shutting it down, then check
763 # the timeout on that, and destroy it if necessary.
765 sst = self.readDom('xend/shutdown_start_time')
766 if sst:
767 sst = float(sst)
768 timeout = SHUTDOWN_TIMEOUT - time.time() + sst
769 if timeout < 0:
770 log.info(
771 "Domain shutdown timeout expired: name=%s id=%s",
772 self.info['name'], self.domid)
773 self.destroy()
774 else:
775 log.debug(
776 "Scheduling refreshShutdown on domain %d in %ds.",
777 self.domid, timeout)
778 scheduler.later(timeout, self.refreshShutdown)
779 finally:
780 self.refresh_shutdown_lock.release()
782 if restart_reason:
783 self.maybeRestart(restart_reason)
786 def shutdown(self, reason):
787 if not reason in shutdown_reasons.values():
788 raise XendError('Invalid reason: %s' % reason)
789 self.storeDom("control/shutdown", reason)
790 if reason != 'suspend':
791 self.storeDom('xend/shutdown_start_time', time.time())
794 ## private:
796 def clearRestart(self):
797 self.removeDom("xend/shutdown_start_time")
800 def maybeRestart(self, reason):
801 # Dispatch to the correct method based upon the configured on_{reason}
802 # behaviour.
803 {"destroy" : self.destroy,
804 "restart" : self.restart,
805 "preserve" : self.preserve,
806 "rename-restart" : self.renameRestart}[self.info['on_' + reason]]()
809 def renameRestart(self):
810 self.restart(True)
813 def dumpCore(self):
814 """Create a core dump for this domain. Nothrow guarantee."""
816 try:
817 corefile = "/var/xen/dump/%s.%s.core" % (self.info['name'],
818 self.domid)
819 xc.domain_dumpcore(dom = self.domid, corefile = corefile)
821 except:
822 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
823 self.domid, self.info['name'])
826 ## public:
828 def setMemoryTarget(self, target):
829 """Set the memory target of this domain.
830 @param target In MiB.
831 """
832 # Internally we use KiB, but the command interface uses MiB.
833 t = target << 10
834 self.info['memory_KiB'] = t
835 self.storeDom("memory/target", t)
838 def update(self, info = None):
839 """Update with info from xc.domain_getinfo().
840 """
842 log.trace("XendDomainInfo.update(%s) on domain %d", info, self.domid)
844 if not info:
845 info = dom_get(self.domid)
846 if not info:
847 return
849 self.info.update(info)
850 self.validateInfo()
851 self.refreshShutdown(info)
853 log.trace("XendDomainInfo.update done on domain %d: %s", self.domid,
854 self.info)
857 ## private:
859 def state_set(self, state):
860 self.state_updated.acquire()
861 try:
862 if self.state != state:
863 self.state = state
864 self.state_updated.notifyAll()
865 finally:
866 self.state_updated.release()
869 ## public:
871 def waitForShutdown(self):
872 self.state_updated.acquire()
873 try:
874 while self.state == STATE_DOM_OK:
875 self.state_updated.wait()
876 finally:
877 self.state_updated.release()
880 def __str__(self):
881 s = "<domain"
882 s += " id=" + str(self.domid)
883 s += " name=" + self.info['name']
884 s += " memory=" + str(self.info['memory_KiB'] / 1024)
885 s += " ssidref=" + str(self.info['ssidref'])
886 s += ">"
887 return s
889 __repr__ = __str__
892 ## private:
894 def createDevice(self, deviceClass, devconfig):
895 return self.getDeviceController(deviceClass).createDevice(devconfig)
898 def reconfigureDevice(self, deviceClass, devid, devconfig):
899 return self.getDeviceController(deviceClass).reconfigureDevice(
900 devid, devconfig)
903 ## public:
905 def destroyDevice(self, deviceClass, devid):
906 return self.getDeviceController(deviceClass).destroyDevice(devid)
909 def getDeviceSxprs(self, deviceClass):
910 return self.getDeviceController(deviceClass).sxprs()
913 ## private:
915 def getDeviceConfigurations(self, deviceClass):
916 return self.getDeviceController(deviceClass).configurations()
919 def getDeviceController(self, name):
920 if name not in controllerClasses:
921 raise XendError("unknown device type: " + str(name))
923 return controllerClasses[name](self)
926 ## public:
928 def sxpr(self):
929 sxpr = ['domain',
930 ['domid', self.domid],
931 ['uuid', uuid.toString(self.uuidbytes)],
932 ['memory', self.info['memory_KiB'] / 1024]]
934 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
935 if self.infoIsSet(e[0]):
936 sxpr.append([e[0], self.info[e[0]]])
938 sxpr.append(['maxmem', self.info['maxmem_KiB'] / 1024])
940 if self.infoIsSet('image'):
941 sxpr.append(['image', self.info['image']])
943 if self.infoIsSet('device'):
944 for (_, c) in self.info['device']:
945 sxpr.append(['device', c])
947 def stateChar(name):
948 if name in self.info:
949 if self.info[name]:
950 return name[0]
951 else:
952 return '-'
953 else:
954 return '?'
956 state = reduce(
957 lambda x, y: x + y,
958 map(stateChar,
959 ['running', 'blocked', 'paused', 'shutdown', 'crashed',
960 'dying']))
962 sxpr.append(['state', state])
963 if self.infoIsSet('shutdown'):
964 reason = shutdown_reason(self.info['shutdown_reason'])
965 sxpr.append(['shutdown_reason', reason])
966 if self.infoIsSet('cpu_time'):
967 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
968 sxpr.append(['vcpus', self.info['vcpus']])
970 if self.infoIsSet('start_time'):
971 up_time = time.time() - self.info['start_time']
972 sxpr.append(['up_time', str(up_time) ])
973 sxpr.append(['start_time', str(self.info['start_time']) ])
975 if self.store_mfn:
976 sxpr.append(['store_mfn', self.store_mfn])
977 if self.console_mfn:
978 sxpr.append(['console_mfn', self.console_mfn])
980 return sxpr
983 def getVCPUInfo(self):
984 try:
985 # We include the domain name and ID, to help xm.
986 sxpr = ['domain',
987 ['domid', self.domid],
988 ['name', self.info['name']],
989 ['vcpu_count', self.info['vcpus']]]
991 for i in range(0, self.info['vcpus']):
992 info = xc.vcpu_getinfo(self.domid, i)
994 sxpr.append(['vcpu',
995 ['number', i],
996 ['online', info['online']],
997 ['blocked', info['blocked']],
998 ['running', info['running']],
999 ['cpu_time', info['cpu_time'] / 1e9],
1000 ['cpu', info['cpu']],
1001 ['cpumap', info['cpumap']]])
1003 return sxpr
1005 except RuntimeError, exn:
1006 raise XendError(str(exn))
1009 ## private:
1011 def check_name(self, name):
1012 """Check if a vm name is valid. Valid names contain alphabetic characters,
1013 digits, or characters in '_-.:/+'.
1014 The same name cannot be used for more than one vm at the same time.
1016 @param name: name
1017 @raise: VmError if invalid
1018 """
1019 if name is None or name == '':
1020 raise VmError('missing vm name')
1021 for c in name:
1022 if c in string.digits: continue
1023 if c in '_-.:/+': continue
1024 if c in string.ascii_letters: continue
1025 raise VmError('invalid vm name')
1027 dominfo = domain_by_name(name)
1028 if not dominfo:
1029 return
1030 if self.domid is None:
1031 raise VmError("VM name '%s' already in use by domain %d" %
1032 (name, dominfo.domid))
1033 if dominfo.domid != self.domid:
1034 raise VmError("VM name '%s' is used in both domains %d and %d" %
1035 (name, self.domid, dominfo.domid))
1038 def construct(self):
1039 """Construct the domain.
1041 @raise: VmError on error
1042 """
1044 log.debug('XendDomainInfo.construct: %s %s',
1045 self.domid,
1046 self.info['ssidref'])
1048 self.domid = xc.domain_create(dom = 0, ssidref = self.info['ssidref'],
1049 handle = self.uuidbytes)
1051 if self.domid < 0:
1052 raise VmError('Creating domain failed: name=%s' %
1053 self.info['name'])
1055 # Set maximum number of vcpus in domain
1056 xc.domain_max_vcpus(self.domid, int(self.info['vcpus']))
1059 def introduceDomain(self):
1060 assert self.domid is not None
1061 assert self.store_mfn is not None
1062 assert self.store_port is not None
1064 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1065 self.dompath = GetDomainPath(self.domid)
1066 assert self.dompath
1069 def initDomain(self):
1070 log.debug('XendDomainInfo.initDomain: %s %s %s',
1071 self.domid,
1072 self.info['memory_KiB'],
1073 self.info['cpu_weight'])
1075 if not self.infoIsSet('image'):
1076 raise VmError('Missing image in configuration')
1078 self.image = image.create(self,
1079 self.info['image'],
1080 self.info['device'])
1082 if self.info['bootloader']:
1083 self.image.handleBootloading()
1085 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1087 m = self.image.getDomainMemory(self.info['memory_KiB'])
1088 xc.domain_setmaxmem(self.domid, maxmem_kb = m)
1089 xc.domain_memory_increase_reservation(self.domid, m, 0, 0)
1091 cpu = self.info['cpu']
1092 if cpu is not None and cpu != -1:
1093 xc.domain_pincpu(self.domid, 0, 1 << cpu)
1095 self.createChannels()
1097 channel_details = self.image.createImage()
1099 self.store_mfn = channel_details['store_mfn']
1100 if 'console_mfn' in channel_details:
1101 self.console_mfn = channel_details['console_mfn']
1103 self.introduceDomain()
1105 self.create_devices()
1107 self.info['start_time'] = time.time()
1110 ## public:
1112 def cleanupDomain(self):
1113 """Cleanup domain resources; release devices. Idempotent. Nothrow
1114 guarantee."""
1116 self.release_devices()
1118 if self.image:
1119 try:
1120 self.image.destroy()
1121 except:
1122 log.exception(
1123 "XendDomainInfo.cleanup: image.destroy() failed.")
1124 self.image = None
1126 try:
1127 self.removeDom()
1128 except:
1129 log.exception("Removing domain path failed.")
1131 try:
1132 if not self.info['name'].startswith(ZOMBIE_PREFIX):
1133 self.info['name'] = ZOMBIE_PREFIX + self.info['name']
1134 except:
1135 log.exception("Renaming Zombie failed.")
1137 self.state_set(STATE_DOM_SHUTDOWN)
1140 def cleanupVm(self):
1141 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1143 try:
1144 self.removeVm()
1145 except:
1146 log.exception("Removing VM path failed.")
1149 def destroy(self):
1150 """Cleanup VM and destroy domain. Nothrow guarantee."""
1152 log.debug("XendDomainInfo.destroy: domid=%s", self.domid)
1154 self.cleanupVm()
1155 if self.dompath is not None:
1156 self.destroyDomain()
1159 def destroyDomain(self):
1160 log.debug("XendDomainInfo.destroyDomain(%s)", self.domid)
1162 try:
1163 if self.domid is not None:
1164 xc.domain_destroy(dom=self.domid)
1165 except:
1166 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1168 self.cleanupDomain()
1171 ## private:
1173 def release_devices(self):
1174 """Release all domain's devices. Nothrow guarantee."""
1176 while True:
1177 t = xstransact("%s/device" % self.dompath)
1178 for n in controllerClasses.keys():
1179 for d in t.list(n):
1180 try:
1181 t.remove(d)
1182 except:
1183 # Log and swallow any exceptions in removal --
1184 # there's nothing more we can do.
1185 log.exception(
1186 "Device release failed: %s; %s; %s",
1187 self.info['name'], n, d)
1188 if t.commit():
1189 break
1192 def createChannels(self):
1193 """Create the channels to the domain.
1194 """
1195 self.store_port = self.createChannel()
1196 self.console_port = self.createChannel()
1199 def createChannel(self):
1200 """Create an event channel to the domain.
1201 """
1202 try:
1203 return xc.evtchn_alloc_unbound(dom=self.domid, remote_dom=0)
1204 except:
1205 log.exception("Exception in alloc_unbound(%d)", self.domid)
1206 raise
1209 def create_configured_devices(self):
1210 for (n, c) in self.info['device']:
1211 self.createDevice(n, c)
1214 def create_devices(self):
1215 """Create the devices for a vm.
1217 @raise: VmError for invalid devices
1218 """
1219 self.create_configured_devices()
1220 if self.image:
1221 self.image.createDeviceModel()
1224 ## public:
1226 def device_create(self, dev_config):
1227 """Create a new device.
1229 @param dev_config: device configuration
1230 """
1231 dev_type = sxp.name(dev_config)
1232 devid = self.createDevice(dev_type, dev_config)
1233 # self.config.append(['device', dev.getConfig()])
1234 return self.getDeviceController(dev_type).sxpr(devid)
1237 def device_configure(self, dev_config, devid):
1238 """Configure an existing device.
1239 @param dev_config: device configuration
1240 @param devid: device id
1241 """
1242 deviceClass = sxp.name(dev_config)
1243 self.reconfigureDevice(deviceClass, devid, dev_config)
1246 def pause(self):
1247 xc.domain_pause(self.domid)
1250 def unpause(self):
1251 xc.domain_unpause(self.domid)
1254 ## private:
1256 def restart_check(self):
1257 """Check if domain restart is OK.
1258 To prevent restart loops, raise an error if it is
1259 less than MINIMUM_RESTART_TIME seconds since the last restart.
1260 """
1261 tnow = time.time()
1262 if self.restart_time is not None:
1263 tdelta = tnow - self.restart_time
1264 if tdelta < self.MINIMUM_RESTART_TIME:
1265 self.restart_cancel()
1266 msg = 'VM %s restarting too fast' % self.info['name']
1267 log.error(msg)
1268 raise VmError(msg)
1269 self.restart_time = tnow
1270 self.restart_count += 1
1273 def restart(self, rename = False):
1274 """Restart the domain after it has exited.
1276 @param rename True if the old domain is to be renamed and preserved,
1277 False if it is to be destroyed.
1278 """
1280 # self.restart_check()
1282 config = self.sxpr()
1284 if self.readVm('xend/restart_in_progress'):
1285 log.error('Xend failed during restart of domain %d. '
1286 'Refusing to restart to avoid loops.',
1287 self.domid)
1288 self.destroy()
1289 return
1291 self.writeVm('xend/restart_in_progress', 'True')
1293 try:
1294 if rename:
1295 self.preserveForRestart()
1296 else:
1297 self.destroy()
1299 try:
1300 xd = get_component('xen.xend.XendDomain')
1301 new_dom = xd.domain_create(config)
1302 try:
1303 new_dom.unpause()
1304 except:
1305 new_dom.destroy()
1306 raise
1307 except:
1308 log.exception('Failed to restart domain %d.', self.domid)
1309 finally:
1310 self.removeVm('xend/restart_in_progress')
1312 # self.configure_bootloader()
1313 # self.exportToDB()
1316 def preserveForRestart(self):
1317 """Preserve a domain that has been shut down, by giving it a new UUID,
1318 cloning the VM details, and giving it a new name. This allows us to
1319 keep this domain for debugging, but restart a new one in its place
1320 preserving the restart semantics (name and UUID preserved).
1321 """
1323 new_name = self.generateUniqueName()
1324 new_uuid = uuid.create()
1325 new_uuid_str = uuid.toString(new_uuid)
1326 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1327 self.info['name'], self.domid, uuid.toString(self.uuidbytes),
1328 new_name, new_uuid_str)
1329 self.release_devices()
1330 self.info['name'] = new_name
1331 self.uuidbytes = new_uuid
1332 self.vmpath = VMROOT + new_uuid_str
1333 self.storeVmDetails()
1334 self.preserve()
1337 def preserve(self):
1338 log.info("Preserving dead domain %s (%d).", self.info['name'],
1339 self.domid)
1340 self.storeDom('xend/shutdown_completed', 'True')
1341 self.state_set(STATE_DOM_SHUTDOWN)
1344 # private:
1346 def generateUniqueName(self):
1347 n = 1
1348 while True:
1349 name = "%s-%d" % (self.info['name'], n)
1350 try:
1351 self.check_name(name)
1352 return name
1353 except VmError:
1354 n += 1
1357 def configure_bootloader(self):
1358 if not self.info['bootloader']:
1359 return
1360 # if we're restarting with a bootloader, we need to run it
1361 # FIXME: this assumes the disk is the first device and
1362 # that we're booting from the first disk
1363 blcfg = None
1364 # FIXME: this assumes that we want to use the first disk
1365 dev = sxp.child_value(self.config, "device")
1366 if dev:
1367 disk = sxp.child_value(dev, "uname")
1368 fn = blkdev_uname_to_file(disk)
1369 blcfg = bootloader(self.info['bootloader'], fn, 1,
1370 self.info['vcpus'])
1371 if blcfg is None:
1372 msg = "Had a bootloader specified, but can't find disk"
1373 log.error(msg)
1374 raise VmError(msg)
1375 self.config = sxp.merge(['vm', ['image', blcfg]], self.config)
1378 def send_sysrq(self, key):
1379 asserts.isCharConvertible(key)
1381 self.storeDom("control/sysrq", '%c' % key)
1384 def infoIsSet(self, name):
1385 return name in self.info and self.info[name] is not None
1388 #============================================================================
1389 # Register device controllers and their device config types.
1391 """A map from device-class names to the subclass of DevController that
1392 implements the device control specific to that device-class."""
1393 controllerClasses = {}
1395 def addControllerClass(device_class, cls):
1396 """Register a subclass of DevController to handle the named device-class.
1397 """
1398 cls.deviceClass = device_class
1399 controllerClasses[device_class] = cls
1402 from xen.xend.server import blkif, netif, tpmif, pciif, usbif
1403 addControllerClass('vbd', blkif.BlkifController)
1404 addControllerClass('vif', netif.NetifController)
1405 addControllerClass('vtpm', tpmif.TPMifController)
1406 addControllerClass('pci', pciif.PciController)
1407 addControllerClass('usb', usbif.UsbifController)