direct-io.hg

view tools/python/xen/xend/XendDomainInfo.py @ 7398:446aa56ca4fe

Added a TRACE log level, for those versions of Python that do not have it, and
moved XendDomainInfo.update's debugging onto that level, as it has become
overly verbose.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author emellor@leeni.uk.xensource.com
date Mon Oct 17 13:50:28 2005 +0100 (2005-10-17)
parents bd3268de4145
children 2b92f50b7692
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
31 import errno
33 import xen.lowlevel.xc
34 from xen.util import asserts
35 from xen.util.blkif import blkdev_uname_to_file
37 from xen.xend import image
38 from xen.xend import scheduler
39 from xen.xend import sxp
40 from xen.xend import XendRoot
41 from xen.xend.XendBootloader import bootloader
42 from xen.xend.XendError import XendError, VmError
43 from xen.xend.XendRoot import get_component
45 from uuid import getUuid
47 from xen.xend.xenstore.xstransact import xstransact
48 from xen.xend.xenstore.xsutil import GetDomainPath, IntroduceDomain
50 """Shutdown code for poweroff."""
51 DOMAIN_POWEROFF = 0
53 """Shutdown code for reboot."""
54 DOMAIN_REBOOT = 1
56 """Shutdown code for suspend."""
57 DOMAIN_SUSPEND = 2
59 """Shutdown code for crash."""
60 DOMAIN_CRASH = 3
62 """Shutdown code for halt."""
63 DOMAIN_HALT = 4
65 """Map shutdown codes to strings."""
66 shutdown_reasons = {
67 DOMAIN_POWEROFF: "poweroff",
68 DOMAIN_REBOOT : "reboot",
69 DOMAIN_SUSPEND : "suspend",
70 DOMAIN_CRASH : "crash",
71 DOMAIN_HALT : "halt"
72 }
74 restart_modes = [
75 "restart",
76 "destroy",
77 "preserve",
78 "rename-restart"
79 ]
81 STATE_DOM_OK = 1
82 STATE_DOM_SHUTDOWN = 2
84 """Flag for a block device backend domain."""
85 SIF_BLK_BE_DOMAIN = (1<<4)
87 """Flag for a net device backend domain."""
88 SIF_NET_BE_DOMAIN = (1<<5)
90 """Flag for a TPM device backend domain."""
91 SIF_TPM_BE_DOMAIN = (1<<7)
94 SHUTDOWN_TIMEOUT = 30
97 DOMROOT = '/local/domain/'
98 VMROOT = '/vm/'
100 ZOMBIE_PREFIX = 'Zombie-'
102 xc = xen.lowlevel.xc.new()
103 xroot = XendRoot.instance()
105 log = logging.getLogger("xend.XendDomainInfo")
106 #log.setLevel(logging.TRACE)
109 ## Configuration entries that we expect to round-trip -- be read from the
110 # config file or xc, written to save-files (i.e. through sxpr), and reused as
111 # config on restart or restore, all without munging. Some configuration
112 # entries are munged for backwards compatibility reasons, or because they
113 # don't come out of xc in the same form as they are specified in the config
114 # file, so those are handled separately.
115 ROUNDTRIPPING_CONFIG_ENTRIES = [
116 ('name', str),
117 ('ssidref', int),
118 ('vcpus', int),
119 ('vcpu_avail', int),
120 ('cpu_weight', float),
121 ('bootloader', str),
122 ('on_poweroff', str),
123 ('on_reboot', str),
124 ('on_crash', str)
125 ]
128 #
129 # There are a number of CPU-related fields:
130 #
131 # vcpus: the number of virtual CPUs this domain is configured to use.
132 # vcpu_avail: a bitmap telling the guest domain whether it may use each of
133 # its VCPUs. This is translated to
134 # <dompath>/cpu/<id>/availability = {online,offline} for use
135 # by the guest domain.
136 # vcpu_to_cpu: the current mapping between virtual CPUs and the physical
137 # CPU it is using.
138 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
139 # CPUs that that VCPU may use.
140 # cpu: a configuration setting requesting that VCPU 0 is pinned to
141 # the specified physical CPU.
142 #
143 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
144 # across save, restore, migrate, and restart). The other settings are only
145 # specific to the domain, so are lost when the VM moves.
146 #
149 def create(config):
150 """Create a VM from a configuration.
152 @param config configuration
153 @raise: VmError for invalid configuration
154 """
156 log.debug("XendDomainInfo.create(%s)", config)
158 vm = XendDomainInfo(getUuid(), parseConfig(config))
159 try:
160 vm.construct()
161 vm.initDomain()
162 vm.construct_image()
163 vm.configure()
164 vm.storeVmDetails()
165 vm.storeDomDetails()
166 vm.refreshShutdown()
167 return vm
168 except:
169 log.exception('Domain construction failed')
170 vm.destroy()
171 raise
174 def recreate(xeninfo):
175 """Create the VM object for an existing domain. The domain must not
176 be dying, as the paths in the store should already have been removed,
177 and asking us to recreate them causes problems."""
179 log.debug("XendDomainInfo.recreate(%s)", xeninfo)
181 assert not xeninfo['dying']
183 domid = xeninfo['dom']
184 try:
185 dompath = GetDomainPath(domid)
186 if not dompath:
187 raise XendError(
188 'No domain path in store for existing domain %d' % domid)
189 vmpath = xstransact.Read(dompath, "vm")
190 if not vmpath:
191 raise XendError(
192 'No vm path in store for existing domain %d' % domid)
193 uuid = xstransact.Read(vmpath, "uuid")
194 if not uuid:
195 raise XendError(
196 'No vm/uuid path in store for existing domain %d' % domid)
198 log.info("Recreating domain %d, UUID %s.", domid, uuid)
200 vm = XendDomainInfo(uuid, xeninfo, domid, True)
202 except Exception, exn:
203 log.warn(str(exn))
205 uuid = getUuid()
207 log.info("Recreating domain %d with new UUID %s.", domid, uuid)
209 vm = XendDomainInfo(uuid, xeninfo, domid, True)
210 vm.removeDom()
211 vm.storeVmDetails()
212 vm.storeDomDetails()
214 vm.create_channel()
215 if domid == 0:
216 vm.initStoreConnection()
218 vm.refreshShutdown(xeninfo)
219 return vm
222 def restore(config):
223 """Create a domain and a VM object to do a restore.
225 @param config: domain configuration
226 """
228 log.debug("XendDomainInfo.restore(%s)", config)
230 uuid = sxp.child_value(config, 'uuid')
231 vm = XendDomainInfo(uuid, parseConfig(config))
232 try:
233 vm.construct()
234 vm.configure()
235 vm.create_channel()
236 vm.storeVmDetails()
237 vm.storeDomDetails()
238 vm.refreshShutdown()
239 return vm
240 except:
241 vm.destroy()
242 raise
245 def parseConfig(config):
246 def get_cfg(name, conv = None):
247 val = sxp.child_value(config, name)
249 if conv and not val is None:
250 try:
251 return conv(val)
252 except TypeError, exn:
253 raise VmError(
254 'Invalid setting %s = %s in configuration: %s' %
255 (name, val, str(exn)))
256 else:
257 return val
260 log.debug("parseConfig: config is %s", config)
262 result = {}
264 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
265 result[e[0]] = get_cfg(e[0], e[1])
267 result['memory'] = get_cfg('memory', int)
268 result['mem_kb'] = get_cfg('mem_kb', int)
269 result['maxmem'] = get_cfg('maxmem', int)
270 result['maxmem_kb'] = get_cfg('maxmem_kb', int)
271 result['cpu'] = get_cfg('cpu', int)
272 result['image'] = get_cfg('image')
274 try:
275 if result['image']:
276 result['vcpus'] = int(sxp.child_value(result['image'],
277 'vcpus', 1))
278 else:
279 result['vcpus'] = 1
280 except TypeError, exn:
281 raise VmError(
282 'Invalid configuration setting: vcpus = %s: %s' %
283 (sxp.child_value(result['image'], 'vcpus', 1), str(exn)))
285 result['backend'] = []
286 for c in sxp.children(config, 'backend'):
287 result['backend'].append(sxp.name(sxp.child0(c)))
289 result['device'] = []
290 for d in sxp.children(config, 'device'):
291 c = sxp.child0(d)
292 result['device'].append((sxp.name(c), c))
294 # Configuration option "restart" is deprecated. Parse it, but
295 # let on_xyz override it if they are present.
296 restart = get_cfg('restart')
297 if restart:
298 def handle_restart(event, val):
299 if result[event] is None:
300 result[event] = val
302 if restart == "onreboot":
303 handle_restart('on_poweroff', 'destroy')
304 handle_restart('on_reboot', 'restart')
305 handle_restart('on_crash', 'destroy')
306 elif restart == "always":
307 handle_restart('on_poweroff', 'restart')
308 handle_restart('on_reboot', 'restart')
309 handle_restart('on_crash', 'restart')
310 elif restart == "never":
311 handle_restart('on_poweroff', 'destroy')
312 handle_restart('on_reboot', 'destroy')
313 handle_restart('on_crash', 'destroy')
314 else:
315 log.warn("Ignoring malformed and deprecated config option "
316 "restart = %s", restart)
318 log.debug("parseConfig: result is %s", result)
319 return result
322 def domain_by_name(name):
323 # See comment in XendDomain constructor.
324 xd = get_component('xen.xend.XendDomain')
325 return xd.domain_lookup_by_name_nr(name)
327 def shutdown_reason(code):
328 """Get a shutdown reason from a code.
330 @param code: shutdown code
331 @type code: int
332 @return: shutdown reason
333 @rtype: string
334 """
335 return shutdown_reasons.get(code, "?")
337 def dom_get(dom):
338 """Get info from xen for an existing domain.
340 @param dom: domain id
341 @return: info or None
342 """
343 try:
344 domlist = xc.domain_getinfo(dom, 1)
345 if domlist and dom == domlist[0]['dom']:
346 return domlist[0]
347 except Exception, err:
348 # ignore missing domain
349 log.debug("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
350 return None
352 class XendDomainInfo:
353 """Virtual machine object."""
355 """Minimum time between domain restarts in seconds.
356 """
357 MINIMUM_RESTART_TIME = 20
360 def __init__(self, uuid, info, domid = None, augment = False):
362 self.uuid = uuid
363 self.info = info
365 if domid is not None:
366 self.domid = domid
367 elif 'dom' in info:
368 self.domid = int(info['dom'])
369 else:
370 self.domid = None
372 self.vmpath = VMROOT + uuid
373 if self.domid is None:
374 self.dompath = None
375 else:
376 self.dompath = DOMROOT + str(self.domid)
378 if augment:
379 self.augmentInfo()
381 self.validateInfo()
383 self.image = None
385 self.store_channel = None
386 self.store_mfn = None
387 self.console_channel = None
388 self.console_mfn = None
390 self.state = STATE_DOM_OK
391 self.state_updated = threading.Condition()
392 self.refresh_shutdown_lock = threading.Condition()
395 def augmentInfo(self):
396 """Augment self.info, as given to us through {@link #recreate}, with
397 values taken from the store. This recovers those values known to xend
398 but not to the hypervisor.
399 """
400 def useIfNeeded(name, val):
401 if not self.infoIsSet(name) and val is not None:
402 self.info[name] = val
404 params = (("name", str),
405 ("on_poweroff", str),
406 ("on_reboot", str),
407 ("on_crash", str),
408 ("image", str),
409 ("vcpus", int),
410 ("vcpu_avail", int),
411 ("start_time", float))
413 from_store = self.gatherVm(*params)
415 map(lambda x, y: useIfNeeded(x[0], y), params, from_store)
417 device = []
418 for c in controllerClasses:
419 devconfig = self.getDeviceConfigurations(c)
420 if devconfig:
421 device.extend(map(lambda x: (c, x), devconfig))
422 useIfNeeded('device', device)
425 def validateInfo(self):
426 """Validate and normalise the info block. This has either been parsed
427 by parseConfig, or received from xc through recreate and augmented by
428 the current store contents.
429 """
430 def defaultInfo(name, val):
431 if not self.infoIsSet(name):
432 self.info[name] = val()
434 try:
435 defaultInfo('name', lambda: "Domain-%d" % self.domid)
436 defaultInfo('ssidref', lambda: 0)
437 defaultInfo('on_poweroff', lambda: "destroy")
438 defaultInfo('on_reboot', lambda: "restart")
439 defaultInfo('on_crash', lambda: "restart")
440 defaultInfo('cpu', lambda: None)
441 defaultInfo('cpu_weight', lambda: 1.0)
442 defaultInfo('vcpus', lambda: 1)
443 defaultInfo('vcpu_avail', lambda: (1 << self.info['vcpus']) - 1)
444 defaultInfo('bootloader', lambda: None)
445 defaultInfo('backend', lambda: [])
446 defaultInfo('device', lambda: [])
447 defaultInfo('image', lambda: None)
449 self.check_name(self.info['name'])
451 if isinstance(self.info['image'], str):
452 self.info['image'] = sxp.from_string(self.info['image'])
454 # Internally, we keep only maxmem_KiB, and not maxmem or maxmem_kb
455 # (which come from outside, and are in MiB and KiB respectively).
456 # This means that any maxmem or maxmem_kb settings here have come
457 # from outside, and maxmem_KiB must be updated to reflect them.
458 # If we have both maxmem and maxmem_kb and these are not
459 # consistent, then this is an error, as we've no way to tell which
460 # one takes precedence.
462 # Exactly the same thing applies to memory_KiB, memory, and
463 # mem_kb.
465 def discard_negatives(name):
466 if self.infoIsSet(name) and self.info[name] < 0:
467 del self.info[name]
469 def valid_KiB_(mb_name, kb_name):
470 discard_negatives(kb_name)
471 discard_negatives(mb_name)
473 if self.infoIsSet(kb_name):
474 if self.infoIsSet(mb_name):
475 mb = self.info[mb_name]
476 kb = self.info[kb_name]
477 if mb * 1024 == kb:
478 return kb
479 else:
480 raise VmError(
481 'Inconsistent %s / %s settings: %s / %s' %
482 (mb_name, kb_name, mb, kb))
483 else:
484 return self.info[kb_name]
485 elif self.infoIsSet(mb_name):
486 return self.info[mb_name] * 1024
487 else:
488 return None
490 def valid_KiB(mb_name, kb_name):
491 result = valid_KiB_(mb_name, kb_name)
492 if result is None or result < 0:
493 raise VmError('Invalid %s / %s: %s' %
494 (mb_name, kb_name, result))
495 else:
496 return result
498 def delIf(name):
499 if name in self.info:
500 del self.info[name]
502 self.info['memory_KiB'] = valid_KiB('memory', 'mem_kb')
503 delIf('memory')
504 delIf('mem_kb')
505 self.info['maxmem_KiB'] = valid_KiB_('maxmem', 'maxmem_kb')
506 delIf('maxmem')
507 delIf('maxmem_kb')
509 if not self.info['maxmem_KiB']:
510 self.info['maxmem_KiB'] = 1 << 30
512 if self.info['maxmem_KiB'] > self.info['memory_KiB']:
513 self.info['maxmem_KiB'] = self.info['memory_KiB']
515 # Validate the given backend names.
516 for s in self.info['backend']:
517 if s not in backendFlags:
518 raise VmError('Invalid backend type: %s' % s)
520 for (n, c) in self.info['device']:
521 if not n or not c or n not in controllerClasses:
522 raise VmError('invalid device (%s, %s)' %
523 (str(n), str(c)))
525 for event in ['on_poweroff', 'on_reboot', 'on_crash']:
526 if self.info[event] not in restart_modes:
527 raise VmError('invalid restart event: %s = %s' %
528 (event, str(self.info[event])))
530 except KeyError, exn:
531 log.exception(exn)
532 raise VmError('Unspecified domain detail: %s' % exn)
535 def readVm(self, *args):
536 return xstransact.Read(self.vmpath, *args)
538 def writeVm(self, *args):
539 return xstransact.Write(self.vmpath, *args)
541 def removeVm(self, *args):
542 return xstransact.Remove(self.vmpath, *args)
544 def gatherVm(self, *args):
545 return xstransact.Gather(self.vmpath, *args)
547 def storeVm(self, *args):
548 return xstransact.Store(self.vmpath, *args)
550 def readDom(self, *args):
551 return xstransact.Read(self.dompath, *args)
553 def writeDom(self, *args):
554 return xstransact.Write(self.dompath, *args)
556 def removeDom(self, *args):
557 return xstransact.Remove(self.dompath, *args)
559 def gatherDom(self, *args):
560 return xstransact.Gather(self.dompath, *args)
562 def storeDom(self, *args):
563 return xstransact.Store(self.dompath, *args)
566 def storeVmDetails(self):
567 to_store = {
568 'uuid': self.uuid,
570 # XXX
571 'memory/target': str(self.info['memory_KiB'])
572 }
574 if self.infoIsSet('image'):
575 to_store['image'] = sxp.to_string(self.info['image'])
577 for k in ['name', 'ssidref', 'on_poweroff', 'on_reboot', 'on_crash',
578 'vcpus', 'vcpu_avail']:
579 if self.infoIsSet(k):
580 to_store[k] = str(self.info[k])
582 log.debug("Storing VM details: %s", to_store)
584 self.writeVm(to_store)
587 def storeDomDetails(self):
588 to_store = {
589 'domid': str(self.domid),
590 'vm': self.vmpath,
592 'memory/target': str(self.info['memory_KiB'])
593 }
595 for (k, v) in self.info.items():
596 if v:
597 to_store[k] = str(v)
599 def availability(n):
600 if self.info['vcpu_avail'] & (1 << n):
601 return 'online'
602 else:
603 return 'offline'
605 for v in range(0, self.info['vcpus']):
606 to_store["cpu/%d/availability" % v] = availability(v)
608 log.debug("Storing domain details: %s", to_store)
610 self.writeDom(to_store)
613 def setDomid(self, domid):
614 """Set the domain id.
616 @param dom: domain id
617 """
618 self.domid = domid
619 self.storeDom("domid", self.domid)
621 def getDomid(self):
622 return self.domid
624 def setName(self, name):
625 self.check_name(name)
626 self.info['name'] = name
627 self.storeVm("name", name)
629 def getName(self):
630 return self.info['name']
632 def getDomainPath(self):
633 return self.dompath
635 def getUuid(self):
636 return self.uuid
638 def getVCpuCount(self):
639 return self.info['vcpus']
641 def getSsidref(self):
642 return self.info['ssidref']
644 def getMemoryTarget(self):
645 """Get this domain's target memory size, in KiB."""
646 return self.info['memory_KiB']
648 def setStoreRef(self, ref):
649 self.store_mfn = ref
650 self.storeDom("store/ring-ref", ref)
653 def getBackendFlags(self):
654 return reduce(lambda x, y: x | backendFlags[y],
655 self.info['backend'], 0)
658 def refreshShutdown(self, xeninfo = None):
659 # If set at the end of this method, a restart is required, with the
660 # given reason. This restart has to be done out of the scope of
661 # refresh_shutdown_lock.
662 restart_reason = None
664 self.refresh_shutdown_lock.acquire()
665 try:
666 if xeninfo is None:
667 xeninfo = dom_get(self.domid)
668 if xeninfo is None:
669 # The domain no longer exists. This will occur if we have
670 # scheduled a timer to check for shutdown timeouts and the
671 # shutdown succeeded. It will also occur if someone
672 # destroys a domain beneath us. We clean up the domain,
673 # just in case, but we can't clean up the VM, because that
674 # VM may have migrated to a different domain on this
675 # machine.
676 self.cleanupDomain()
677 return
679 if xeninfo['dying']:
680 # Dying means that a domain has been destroyed, but has not
681 # yet been cleaned up by Xen. This state could persist
682 # indefinitely if, for example, another domain has some of its
683 # pages mapped. We might like to diagnose this problem in the
684 # future, but for now all we do is make sure that it's not us
685 # holding the pages, by calling cleanupDomain. We can't
686 # clean up the VM, as above.
687 self.cleanupDomain()
688 return
690 elif xeninfo['crashed']:
691 log.warn('Domain has crashed: name=%s id=%d.',
692 self.info['name'], self.domid)
694 if xroot.get_enable_dump():
695 self.dumpCore()
697 restart_reason = 'crash'
699 elif xeninfo['shutdown']:
700 if self.readDom('xend/shutdown_completed'):
701 # We've seen this shutdown already, but we are preserving
702 # the domain for debugging. Leave it alone.
703 return
705 else:
706 reason = shutdown_reason(xeninfo['shutdown_reason'])
708 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
709 self.info['name'], self.domid, reason)
711 self.clearRestart()
713 if reason == 'suspend':
714 self.state_set(STATE_DOM_SHUTDOWN)
715 # Don't destroy the domain. XendCheckpoint will do
716 # this once it has finished.
717 elif reason in ['poweroff', 'reboot']:
718 restart_reason = reason
719 else:
720 self.destroy()
722 else:
723 # Domain is alive. If we are shutting it down, then check
724 # the timeout on that, and destroy it if necessary.
726 sst = self.readDom('xend/shutdown_start_time')
727 if sst:
728 sst = float(sst)
729 timeout = SHUTDOWN_TIMEOUT - time.time() + sst
730 if timeout < 0:
731 log.info(
732 "Domain shutdown timeout expired: name=%s id=%s",
733 self.info['name'], self.domid)
734 self.destroy()
735 else:
736 log.debug(
737 "Scheduling refreshShutdown on domain %d in %ds.",
738 self.domid, timeout)
739 scheduler.later(timeout, self.refreshShutdown)
740 finally:
741 self.refresh_shutdown_lock.release()
743 if restart_reason:
744 self.maybeRestart(restart_reason)
747 def shutdown(self, reason):
748 if not reason in shutdown_reasons.values():
749 raise XendError('Invalid reason: %s' % reason)
750 self.storeDom("control/shutdown", reason)
751 if reason != 'suspend':
752 self.storeDom('xend/shutdown_start_time', time.time())
755 ## private:
757 def clearRestart(self):
758 self.removeDom("xend/shutdown_start_time")
761 def maybeRestart(self, reason):
762 # Dispatch to the correct method based upon the configured on_{reason}
763 # behaviour.
764 {"destroy" : self.destroy,
765 "restart" : self.restart,
766 "preserve" : self.preserve,
767 "rename-restart" : self.renameRestart}[self.info['on_' + reason]]()
770 def renameRestart(self):
771 self.restart(True)
774 def dumpCore(self):
775 """Create a core dump for this domain. Nothrow guarantee."""
777 try:
778 corefile = "/var/xen/dump/%s.%s.core" % (self.info['name'],
779 self.domid)
780 xc.domain_dumpcore(dom = self.domid, corefile = corefile)
782 except:
783 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
784 self.domid, self.info['name'])
787 ## public:
789 def setConsoleRef(self, ref):
790 self.console_mfn = ref
791 self.storeDom("console/ring-ref", ref)
794 def setMemoryTarget(self, target):
795 """Set the memory target of this domain.
796 @param target In MiB.
797 """
798 # Internally we use KiB, but the command interface uses MiB.
799 t = target << 10
800 self.info['memory_KiB'] = t
801 self.storeDom("memory/target", t)
804 def update(self, info = None):
805 """Update with info from xc.domain_getinfo().
806 """
808 log.trace("XendDomainInfo.update(%s) on domain %d", info, self.domid)
810 if not info:
811 info = dom_get(self.domid)
812 if not info:
813 return
815 self.info.update(info)
816 self.validateInfo()
817 self.refreshShutdown(info)
819 log.trace("XendDomainInfo.update done on domain %d: %s", self.domid,
820 self.info)
823 ## private:
825 def state_set(self, state):
826 self.state_updated.acquire()
827 try:
828 if self.state != state:
829 self.state = state
830 self.state_updated.notifyAll()
831 finally:
832 self.state_updated.release()
835 ## public:
837 def waitForShutdown(self):
838 self.state_updated.acquire()
839 try:
840 while self.state == STATE_DOM_OK:
841 self.state_updated.wait()
842 finally:
843 self.state_updated.release()
846 def __str__(self):
847 s = "<domain"
848 s += " id=" + str(self.domid)
849 s += " name=" + self.info['name']
850 s += " memory=" + str(self.info['memory_KiB'] / 1024)
851 s += " ssidref=" + str(self.info['ssidref'])
852 s += ">"
853 return s
855 __repr__ = __str__
858 ## private:
860 def createDevice(self, deviceClass, devconfig):
861 return self.getDeviceController(deviceClass).createDevice(devconfig)
864 def reconfigureDevice(self, deviceClass, devid, devconfig):
865 return self.getDeviceController(deviceClass).reconfigureDevice(
866 devid, devconfig)
869 ## public:
871 def destroyDevice(self, deviceClass, devid):
872 return self.getDeviceController(deviceClass).destroyDevice(devid)
875 ## private:
877 def getDeviceSxprs(self, deviceClass):
878 return self.getDeviceController(deviceClass).sxprs()
881 def getDeviceConfigurations(self, deviceClass):
882 return self.getDeviceController(deviceClass).configurations()
885 def getDeviceController(self, name):
886 if name not in controllerClasses:
887 raise XendError("unknown device type: " + str(name))
889 return controllerClasses[name](self)
892 ## public:
894 def sxpr(self):
895 sxpr = ['domain',
896 ['domid', self.domid],
897 ['uuid', self.uuid],
898 ['memory', self.info['memory_KiB'] / 1024]]
900 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
901 if self.infoIsSet(e[0]):
902 sxpr.append([e[0], self.info[e[0]]])
904 sxpr.append(['maxmem', self.info['maxmem_KiB'] / 1024])
906 if self.infoIsSet('image'):
907 sxpr.append(['image', self.info['image']])
909 if self.infoIsSet('device'):
910 for (_, c) in self.info['device']:
911 sxpr.append(['device', c])
913 def stateChar(name):
914 if name in self.info:
915 if self.info[name]:
916 return name[0]
917 else:
918 return '-'
919 else:
920 return '?'
922 state = reduce(
923 lambda x, y: x + y,
924 map(stateChar,
925 ['running', 'blocked', 'paused', 'shutdown', 'crashed',
926 'dying']))
928 sxpr.append(['state', state])
929 if self.infoIsSet('shutdown'):
930 reason = shutdown_reason(self.info['shutdown_reason'])
931 sxpr.append(['shutdown_reason', reason])
932 if self.infoIsSet('cpu_time'):
933 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
934 sxpr.append(['vcpus', self.info['vcpus']])
935 if self.infoIsSet('cpumap'):
936 sxpr.append(['cpumap', self.info['cpumap']])
937 if self.infoIsSet('vcpu_to_cpu'):
938 sxpr.append(['cpu', self.info['vcpu_to_cpu'][0]])
939 sxpr.append(['vcpu_to_cpu', self.prettyVCpuMap()])
941 if self.infoIsSet('start_time'):
942 up_time = time.time() - self.info['start_time']
943 sxpr.append(['up_time', str(up_time) ])
944 sxpr.append(['start_time', str(self.info['start_time']) ])
946 if self.store_mfn:
947 sxpr.append(['store_mfn', self.store_mfn])
948 if self.console_mfn:
949 sxpr.append(['console_mfn', self.console_mfn])
951 return sxpr
954 ## private:
956 def prettyVCpuMap(self):
957 return '|'.join(map(str,
958 self.info['vcpu_to_cpu'][0:self.info['vcpus']]))
961 def check_name(self, name):
962 """Check if a vm name is valid. Valid names contain alphabetic characters,
963 digits, or characters in '_-.:/+'.
964 The same name cannot be used for more than one vm at the same time.
966 @param name: name
967 @raise: VmError if invalid
968 """
969 if name is None or name == '':
970 raise VmError('missing vm name')
971 for c in name:
972 if c in string.digits: continue
973 if c in '_-.:/+': continue
974 if c in string.ascii_letters: continue
975 raise VmError('invalid vm name')
977 dominfo = domain_by_name(name)
978 if not dominfo:
979 return
980 if self.domid is None:
981 raise VmError("VM name '%s' already in use by domain %d" %
982 (name, dominfo.domid))
983 if dominfo.domid != self.domid:
984 raise VmError("VM name '%s' is used in both domains %d and %d" %
985 (name, self.domid, dominfo.domid))
988 def construct(self):
989 """Construct the domain.
991 @raise: VmError on error
992 """
994 log.debug('XendDomainInfo.construct: %s %s',
995 self.domid,
996 self.info['ssidref'])
998 self.domid = xc.domain_create(dom = 0, ssidref = self.info['ssidref'])
1000 if self.domid < 0:
1001 raise VmError('Creating domain failed: name=%s' %
1002 self.info['name'])
1004 self.dompath = DOMROOT + str(self.domid)
1006 # Ensure that the domain entry is clean. This prevents a stale
1007 # shutdown_start_time from killing the domain, for example.
1008 self.removeDom()
1010 # Set maximum number of vcpus in domain
1011 xc.domain_max_vcpus(self.domid, int(self.info['vcpus']))
1013 def initDomain(self):
1014 log.debug('XendDomainInfo.initDomain: %s %s %s',
1015 self.domid,
1016 self.info['memory_KiB'],
1017 self.info['cpu_weight'])
1019 if not self.infoIsSet('image'):
1020 raise VmError('Missing image in configuration')
1022 self.image = image.create(self,
1023 self.info['image'],
1024 self.info['device'])
1026 if self.info['bootloader']:
1027 self.image.handleBootloading()
1029 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1031 # XXX Merge with configure_maxmem?
1032 m = self.image.getDomainMemory(self.info['memory_KiB'])
1033 xc.domain_setmaxmem(self.domid, m)
1034 xc.domain_memory_increase_reservation(self.domid, m, 0, 0)
1036 cpu = self.info['cpu']
1037 if cpu is not None and cpu != -1:
1038 xc.domain_pincpu(self.domid, 0, 1 << cpu)
1040 self.info['start_time'] = time.time()
1042 log.debug('init_domain> Created domain=%d name=%s memory=%d',
1043 self.domid, self.info['name'], self.info['memory_KiB'])
1046 def construct_image(self):
1047 """Construct the boot image for the domain.
1048 """
1049 self.create_channel()
1050 self.image.createImage()
1051 IntroduceDomain(self.domid, self.store_mfn,
1052 self.store_channel, self.dompath)
1055 ## public:
1057 def cleanupDomain(self):
1058 """Cleanup domain resources; release devices. Idempotent. Nothrow
1059 guarantee."""
1061 self.release_devices()
1063 if self.image:
1064 try:
1065 self.image.destroy()
1066 except:
1067 log.exception(
1068 "XendDomainInfo.cleanup: image.destroy() failed.")
1069 self.image = None
1071 try:
1072 self.removeDom()
1073 except:
1074 log.exception("Removing domain path failed.")
1076 try:
1077 if not self.info['name'].startswith(ZOMBIE_PREFIX):
1078 self.info['name'] = ZOMBIE_PREFIX + self.info['name']
1079 except:
1080 log.exception("Renaming Zombie failed.")
1082 self.state_set(STATE_DOM_SHUTDOWN)
1085 def cleanupVm(self):
1086 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1088 try:
1089 self.removeVm()
1090 except:
1091 log.exception("Removing VM path failed.")
1094 def destroy(self):
1095 """Cleanup VM and destroy domain. Nothrow guarantee."""
1097 log.debug("XendDomainInfo.destroy: domid=%s", self.domid)
1099 self.cleanupVm()
1100 if self.dompath is not None:
1101 self.destroyDomain()
1104 def destroyDomain(self):
1105 log.debug("XendDomainInfo.destroyDomain(%s)", self.domid)
1107 try:
1108 if self.domid is not None:
1109 xc.domain_destroy(dom=self.domid)
1110 except:
1111 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1113 self.cleanupDomain()
1116 ## private:
1118 def release_devices(self):
1119 """Release all domain's devices. Nothrow guarantee."""
1121 while True:
1122 t = xstransact("%s/device" % self.dompath)
1123 for n in controllerClasses.keys():
1124 for d in t.list(n):
1125 try:
1126 t.remove(d)
1127 except:
1128 # Log and swallow any exceptions in removal --
1129 # there's nothing more we can do.
1130 log.exception(
1131 "Device release failed: %s; %s; %s",
1132 self.info['name'], n, d)
1133 if t.commit():
1134 break
1137 def eventChannel(self, path=None):
1138 """Create an event channel to the domain.
1140 @param path under which port is stored in db
1141 """
1142 if path:
1143 try:
1144 return int(self.readDom(path))
1145 except:
1146 # The port is not yet set, i.e. the channel has not yet been
1147 # created.
1148 pass
1150 try:
1151 port = xc.evtchn_alloc_unbound(dom=self.domid, remote_dom=0)
1152 except:
1153 log.exception("Exception in alloc_unbound(%d)", self.domid)
1154 raise
1156 self.storeDom(path, port)
1157 return port
1159 def create_channel(self):
1160 """Create the channels to the domain.
1161 """
1162 self.store_channel = self.eventChannel("store/port")
1163 self.console_channel = self.eventChannel("console/port")
1165 def create_configured_devices(self):
1166 for (n, c) in self.info['device']:
1167 self.createDevice(n, c)
1170 def create_devices(self):
1171 """Create the devices for a vm.
1173 @raise: VmError for invalid devices
1174 """
1175 self.create_configured_devices()
1176 if self.image:
1177 self.image.createDeviceModel()
1180 ## public:
1182 def device_create(self, dev_config):
1183 """Create a new device.
1185 @param dev_config: device configuration
1186 """
1187 dev_type = sxp.name(dev_config)
1188 devid = self.createDevice(dev_type, dev_config)
1189 # self.config.append(['device', dev.getConfig()])
1190 return self.getDeviceController(dev_type).sxpr(devid)
1193 def device_configure(self, dev_config, devid):
1194 """Configure an existing device.
1195 @param dev_config: device configuration
1196 @param devid: device id
1197 """
1198 deviceClass = sxp.name(dev_config)
1199 self.reconfigureDevice(deviceClass, devid, dev_config)
1202 ## private:
1204 def restart_check(self):
1205 """Check if domain restart is OK.
1206 To prevent restart loops, raise an error if it is
1207 less than MINIMUM_RESTART_TIME seconds since the last restart.
1208 """
1209 tnow = time.time()
1210 if self.restart_time is not None:
1211 tdelta = tnow - self.restart_time
1212 if tdelta < self.MINIMUM_RESTART_TIME:
1213 self.restart_cancel()
1214 msg = 'VM %s restarting too fast' % self.info['name']
1215 log.error(msg)
1216 raise VmError(msg)
1217 self.restart_time = tnow
1218 self.restart_count += 1
1221 def restart(self, rename = False):
1222 """Restart the domain after it has exited.
1224 @param rename True if the old domain is to be renamed and preserved,
1225 False if it is to be destroyed.
1226 """
1228 # self.restart_check()
1230 config = self.sxpr()
1232 if self.readVm('xend/restart_in_progress'):
1233 log.error('Xend failed during restart of domain %d. '
1234 'Refusing to restart to avoid loops.',
1235 self.domid)
1236 self.destroy()
1237 return
1239 self.writeVm('xend/restart_in_progress', 'True')
1241 try:
1242 if rename:
1243 self.preserveForRestart()
1244 else:
1245 self.destroy()
1247 try:
1248 xd = get_component('xen.xend.XendDomain')
1249 new_dom = xd.domain_create(config)
1250 try:
1251 xc.domain_unpause(new_dom.getDomid())
1252 except:
1253 new_dom.destroy()
1254 raise
1255 except:
1256 log.exception('Failed to restart domain %d.', self.domid)
1257 finally:
1258 self.removeVm('xend/restart_in_progress')
1260 # self.configure_bootloader()
1261 # self.exportToDB()
1264 def preserveForRestart(self):
1265 """Preserve a domain that has been shut down, by giving it a new UUID,
1266 cloning the VM details, and giving it a new name. This allows us to
1267 keep this domain for debugging, but restart a new one in its place
1268 preserving the restart semantics (name and UUID preserved).
1269 """
1271 new_name = self.generateUniqueName()
1272 new_uuid = getUuid()
1273 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1274 self.info['name'], self.domid, self.uuid, new_name, new_uuid)
1275 self.release_devices()
1276 self.info['name'] = new_name
1277 self.uuid = new_uuid
1278 self.vmpath = VMROOT + new_uuid
1279 self.storeVmDetails()
1280 self.preserve()
1283 def preserve(self):
1284 log.info("Preserving dead domain %s (%d).", self.info['name'],
1285 self.domid)
1286 self.storeDom('xend/shutdown_completed', 'True')
1287 self.state_set(STATE_DOM_SHUTDOWN)
1290 # private:
1292 def generateUniqueName(self):
1293 n = 1
1294 while True:
1295 name = "%s-%d" % (self.info['name'], n)
1296 try:
1297 self.check_name(name)
1298 return name
1299 except VmError:
1300 n += 1
1303 def configure_bootloader(self):
1304 if not self.info['bootloader']:
1305 return
1306 # if we're restarting with a bootloader, we need to run it
1307 # FIXME: this assumes the disk is the first device and
1308 # that we're booting from the first disk
1309 blcfg = None
1310 # FIXME: this assumes that we want to use the first disk
1311 dev = sxp.child_value(self.config, "device")
1312 if dev:
1313 disk = sxp.child_value(dev, "uname")
1314 fn = blkdev_uname_to_file(disk)
1315 blcfg = bootloader(self.info['bootloader'], fn, 1,
1316 self.info['vcpus'])
1317 if blcfg is None:
1318 msg = "Had a bootloader specified, but can't find disk"
1319 log.error(msg)
1320 raise VmError(msg)
1321 self.config = sxp.merge(['vm', ['image', blcfg]], self.config)
1324 def configure(self):
1325 """Configure a vm.
1327 """
1328 self.configure_maxmem()
1329 self.create_devices()
1332 def configure_maxmem(self):
1333 if self.image:
1334 m = self.image.getDomainMemory(self.info['memory_KiB'])
1335 xc.domain_setmaxmem(self.domid, maxmem_kb = m)
1338 def vcpu_hotplug(self, vcpu, state):
1339 """Disable or enable VCPU in domain.
1340 """
1341 if vcpu > self.info['vcpus']:
1342 log.error("Invalid VCPU %d" % vcpu)
1343 return
1344 if int(state) == 0:
1345 self.info['vcpu_avail'] &= ~(1 << vcpu)
1346 availability = "offline"
1347 else:
1348 self.info['vcpu_avail'] &= (1 << vcpu)
1349 availability = "online"
1350 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
1351 self.storeDom("cpu/%d/availability" % vcpu, availability)
1354 def send_sysrq(self, key):
1355 asserts.isCharConvertible(key)
1357 self.storeDom("control/sysrq", '%c' % key)
1360 def initStoreConnection(self):
1361 ref = xc.init_store(self.store_channel)
1362 if ref and ref >= 0:
1363 self.setStoreRef(ref)
1364 try:
1365 IntroduceDomain(self.domid, ref, self.store_channel,
1366 self.dompath)
1367 except RuntimeError, ex:
1368 if ex.args[0] == errno.EISCONN:
1369 pass
1370 else:
1371 raise
1374 def dom0_enforce_vcpus(self):
1375 dom = 0
1376 # get max number of vcpus to use for dom0 from config
1377 target = int(xroot.get_dom0_vcpus())
1378 log.debug("number of vcpus to use is %d", target)
1380 # target = 0 means use all processors
1381 if target > 0:
1382 # count the number of online vcpus (cpu values in v2c map >= 0)
1383 vcpus_online = dom_get(dom)['vcpus']
1384 log.debug("found %d vcpus online", vcpus_online)
1386 # disable any extra vcpus that are online over the requested target
1387 for vcpu in range(target, vcpus_online):
1388 log.info("enforcement is disabling DOM%d VCPU%d", dom, vcpu)
1389 self.vcpu_hotplug(vcpu, 0)
1392 def infoIsSet(self, name):
1393 return name in self.info and self.info[name] is not None
1396 #============================================================================
1397 # Register device controllers and their device config types.
1399 """A map from device-class names to the subclass of DevController that
1400 implements the device control specific to that device-class."""
1401 controllerClasses = {}
1404 """A map of backend names and the corresponding flag."""
1405 backendFlags = {}
1408 def addControllerClass(device_class, backend_name, backend_flag, cls):
1409 """Register a subclass of DevController to handle the named device-class.
1411 @param backend_flag One of the SIF_XYZ_BE_DOMAIN constants, or None if
1412 no flag is to be set.
1413 """
1414 cls.deviceClass = device_class
1415 backendFlags[backend_name] = backend_flag
1416 controllerClasses[device_class] = cls
1419 from xen.xend.server import blkif, netif, tpmif, pciif, usbif
1420 addControllerClass('vbd', 'blkif', SIF_BLK_BE_DOMAIN, blkif.BlkifController)
1421 addControllerClass('vif', 'netif', SIF_NET_BE_DOMAIN, netif.NetifController)
1422 addControllerClass('vtpm', 'tpmif', SIF_TPM_BE_DOMAIN, tpmif.TPMifController)
1423 addControllerClass('pci', 'pciif', None, pciif.PciController)
1424 addControllerClass('usb', 'usbif', None, usbif.UsbifController)