direct-io.hg

view tools/python/xen/xend/XendDomainInfo.py @ 7480:a90d670c98b9

Change the semantics of GetDomainPath so that it always succeeds, regardless of
whether a domain has been introduced to the store. Added a separate message
XS_IS_DOMAIN_INTRODUCED and API for that (xs_is_domain_introduced) to determine
whether the domain has really been introduced. This change means that the
tools can determine the correct domain path earlier in the domain creation
process, which is particularly a factor with live migration, as it allows us
to create the devices earlier in the process, and unpause the new domain before
performing the introduce. Until recently we already had these features, but
the simplification of the interface between xend and xenstored caused breakage.

No longer clear out the domain path when a domain is introduced -- this was a
hack to work around the recent problematic semantics of GetDomainPath.

Do not write the contents of the info block to the store. All the configuration
info is written to the /vm path, and anything else in the info block is either
dealt with explicitly or is ephemeral and has no place in the store.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author emellor@leeni.uk.xensource.com
date Sun Oct 23 22:45:15 2005 +0100 (2005-10-23)
parents 94cee9a918de
children e70ea9465b31
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 """Minimum time between domain restarts in seconds."""
91 MINIMUM_RESTART_TIME = 20
94 xc = xen.lowlevel.xc.new()
95 xroot = XendRoot.instance()
97 log = logging.getLogger("xend.XendDomainInfo")
98 #log.setLevel(logging.TRACE)
101 ## Configuration entries that we expect to round-trip -- be read from the
102 # config file or xc, written to save-files (i.e. through sxpr), and reused as
103 # config on restart or restore, all without munging. Some configuration
104 # entries are munged for backwards compatibility reasons, or because they
105 # don't come out of xc in the same form as they are specified in the config
106 # file, so those are handled separately.
107 ROUNDTRIPPING_CONFIG_ENTRIES = [
108 ('name', str),
109 ('uuid', str),
110 ('ssidref', int),
111 ('vcpus', int),
112 ('vcpu_avail', int),
113 ('cpu_weight', float),
114 ('bootloader', str),
115 ('on_poweroff', str),
116 ('on_reboot', str),
117 ('on_crash', str)
118 ]
121 #
122 # There are a number of CPU-related fields:
123 #
124 # vcpus: the number of virtual CPUs this domain is configured to use.
125 # vcpu_avail: a bitmap telling the guest domain whether it may use each of
126 # its VCPUs. This is translated to
127 # <dompath>/cpu/<id>/availability = {online,offline} for use
128 # by the guest domain.
129 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
130 # CPUs that that VCPU may use.
131 # cpu: a configuration setting requesting that VCPU 0 is pinned to
132 # the specified physical CPU.
133 #
134 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
135 # across save, restore, migrate, and restart). The other settings are only
136 # specific to the domain, so are lost when the VM moves.
137 #
140 def create(config):
141 """Create a VM from a configuration.
143 @param config configuration
144 @raise: VmError for invalid configuration
145 """
147 log.debug("XendDomainInfo.create(%s)", config)
149 vm = XendDomainInfo(parseConfig(config))
150 try:
151 vm.construct()
152 vm.initDomain()
153 vm.storeVmDetails()
154 vm.storeDomDetails()
155 vm.refreshShutdown()
156 return vm
157 except:
158 log.exception('Domain construction failed')
159 vm.destroy()
160 raise
163 def recreate(xeninfo, priv):
164 """Create the VM object for an existing domain. The domain must not
165 be dying, as the paths in the store should already have been removed,
166 and asking us to recreate them causes problems."""
168 log.debug("XendDomainInfo.recreate(%s)", xeninfo)
170 assert not xeninfo['dying']
172 domid = xeninfo['dom']
173 uuid1 = xeninfo['handle']
174 xeninfo['uuid'] = uuid.toString(uuid1)
175 dompath = GetDomainPath(domid)
176 if not dompath:
177 raise XendError(
178 'No domain path in store for existing domain %d' % domid)
180 log.info("Recreating domain %d, UUID %s.", domid, xeninfo['uuid'])
181 try:
182 vmpath = xstransact.Read(dompath, "vm")
183 if not vmpath:
184 raise XendError(
185 'No vm path in store for existing domain %d' % domid)
186 uuid2_str = xstransact.Read(vmpath, "uuid")
187 if not uuid2_str:
188 raise XendError(
189 'No vm/uuid path in store for existing domain %d' % domid)
191 uuid2 = uuid.fromString(uuid2_str)
193 if uuid1 != uuid2:
194 raise XendError(
195 'Uuid in store does not match uuid for existing domain %d: '
196 '%s != %s' % (domid, uuid2_str, xeninfo['uuid']))
198 vm = XendDomainInfo(xeninfo, domid, dompath, True)
200 except Exception, exn:
201 if priv:
202 log.warn(str(exn))
204 vm = XendDomainInfo(xeninfo, domid, dompath, True)
205 vm.removeDom()
206 vm.removeVm()
207 vm.storeVmDetails()
208 vm.storeDomDetails()
210 vm.refreshShutdown(xeninfo)
211 return vm
214 def restore(config):
215 """Create a domain and a VM object to do a restore.
217 @param config: domain configuration
218 """
220 log.debug("XendDomainInfo.restore(%s)", config)
222 vm = XendDomainInfo(parseConfig(config))
223 try:
224 vm.construct()
225 vm.storeVmDetails()
226 vm.createDevices()
227 vm.createChannels()
228 vm.storeDomDetails()
229 return vm
230 except:
231 vm.destroy()
232 raise
235 def parseConfig(config):
236 def get_cfg(name, conv = None):
237 val = sxp.child_value(config, name)
239 if conv and not val is None:
240 try:
241 return conv(val)
242 except TypeError, exn:
243 raise VmError(
244 'Invalid setting %s = %s in configuration: %s' %
245 (name, val, str(exn)))
246 else:
247 return val
250 log.debug("parseConfig: config is %s", config)
252 result = {}
254 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
255 result[e[0]] = get_cfg(e[0], e[1])
257 result['memory'] = get_cfg('memory', int)
258 result['mem_kb'] = get_cfg('mem_kb', int)
259 result['maxmem'] = get_cfg('maxmem', int)
260 result['maxmem_kb'] = get_cfg('maxmem_kb', int)
261 result['cpu'] = get_cfg('cpu', int)
262 result['image'] = get_cfg('image')
264 try:
265 if result['image']:
266 result['vcpus'] = int(sxp.child_value(result['image'],
267 'vcpus', 1))
268 else:
269 result['vcpus'] = 1
270 except TypeError, exn:
271 raise VmError(
272 'Invalid configuration setting: vcpus = %s: %s' %
273 (sxp.child_value(result['image'], 'vcpus', 1), str(exn)))
275 result['backend'] = []
276 for c in sxp.children(config, 'backend'):
277 result['backend'].append(sxp.name(sxp.child0(c)))
279 result['device'] = []
280 for d in sxp.children(config, 'device'):
281 c = sxp.child0(d)
282 result['device'].append((sxp.name(c), c))
284 # Configuration option "restart" is deprecated. Parse it, but
285 # let on_xyz override it if they are present.
286 restart = get_cfg('restart')
287 if restart:
288 def handle_restart(event, val):
289 if result[event] is None:
290 result[event] = val
292 if restart == "onreboot":
293 handle_restart('on_poweroff', 'destroy')
294 handle_restart('on_reboot', 'restart')
295 handle_restart('on_crash', 'destroy')
296 elif restart == "always":
297 handle_restart('on_poweroff', 'restart')
298 handle_restart('on_reboot', 'restart')
299 handle_restart('on_crash', 'restart')
300 elif restart == "never":
301 handle_restart('on_poweroff', 'destroy')
302 handle_restart('on_reboot', 'destroy')
303 handle_restart('on_crash', 'destroy')
304 else:
305 log.warn("Ignoring malformed and deprecated config option "
306 "restart = %s", restart)
308 log.debug("parseConfig: result is %s", result)
309 return result
312 def domain_by_name(name):
313 # See comment in XendDomain constructor.
314 xd = get_component('xen.xend.XendDomain')
315 return xd.domain_lookup_by_name_nr(name)
317 def shutdown_reason(code):
318 """Get a shutdown reason from a code.
320 @param code: shutdown code
321 @type code: int
322 @return: shutdown reason
323 @rtype: string
324 """
325 return shutdown_reasons.get(code, "?")
327 def dom_get(dom):
328 """Get info from xen for an existing domain.
330 @param dom: domain id
331 @return: info or None
332 """
333 try:
334 domlist = xc.domain_getinfo(dom, 1)
335 if domlist and dom == domlist[0]['dom']:
336 return domlist[0]
337 except Exception, err:
338 # ignore missing domain
339 log.debug("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
340 return None
343 class XendDomainInfo:
345 def __init__(self, info, domid = None, dompath = None, augment = False):
347 self.info = info
349 if not self.infoIsSet('uuid'):
350 self.info['uuid'] = uuid.toString(uuid.create())
352 if domid is not None:
353 self.domid = domid
354 elif 'dom' in info:
355 self.domid = int(info['dom'])
356 else:
357 self.domid = None
359 self.vmpath = VMROOT + self.info['uuid']
360 self.dompath = dompath
362 if augment:
363 self.augmentInfo()
365 self.validateInfo()
367 self.image = None
369 self.store_port = None
370 self.store_mfn = None
371 self.console_port = None
372 self.console_mfn = None
374 self.state = STATE_DOM_OK
375 self.state_updated = threading.Condition()
376 self.refresh_shutdown_lock = threading.Condition()
379 ## private:
381 def augmentInfo(self):
382 """Augment self.info, as given to us through {@link #recreate}, with
383 values taken from the store. This recovers those values known to xend
384 but not to the hypervisor.
385 """
386 def useIfNeeded(name, val):
387 if not self.infoIsSet(name) and val is not None:
388 self.info[name] = val
390 params = (("name", str),
391 ("on_poweroff", str),
392 ("on_reboot", str),
393 ("on_crash", str),
394 ("image", str),
395 ("vcpus", int),
396 ("vcpu_avail", int),
397 ("start_time", float))
399 from_store = self.gatherVm(*params)
401 map(lambda x, y: useIfNeeded(x[0], y), params, from_store)
403 device = []
404 for c in controllerClasses:
405 devconfig = self.getDeviceConfigurations(c)
406 if devconfig:
407 device.extend(map(lambda x: (c, x), devconfig))
408 useIfNeeded('device', device)
411 def validateInfo(self):
412 """Validate and normalise the info block. This has either been parsed
413 by parseConfig, or received from xc through recreate and augmented by
414 the current store contents.
415 """
416 def defaultInfo(name, val):
417 if not self.infoIsSet(name):
418 self.info[name] = val()
420 try:
421 defaultInfo('name', lambda: "Domain-%d" % self.domid)
422 defaultInfo('ssidref', lambda: 0)
423 defaultInfo('on_poweroff', lambda: "destroy")
424 defaultInfo('on_reboot', lambda: "restart")
425 defaultInfo('on_crash', lambda: "restart")
426 defaultInfo('cpu', lambda: None)
427 defaultInfo('cpu_weight', lambda: 1.0)
428 defaultInfo('vcpus', lambda: int(1))
430 self.info['vcpus'] = int(self.info['vcpus'])
432 defaultInfo('vcpu_avail', lambda: (1 << self.info['vcpus']) - 1)
433 defaultInfo('bootloader', lambda: None)
434 defaultInfo('backend', lambda: [])
435 defaultInfo('device', lambda: [])
436 defaultInfo('image', lambda: None)
438 self.check_name(self.info['name'])
440 if isinstance(self.info['image'], str):
441 self.info['image'] = sxp.from_string(self.info['image'])
443 # Internally, we keep only maxmem_KiB, and not maxmem or maxmem_kb
444 # (which come from outside, and are in MiB and KiB respectively).
445 # This means that any maxmem or maxmem_kb settings here have come
446 # from outside, and maxmem_KiB must be updated to reflect them.
447 # If we have both maxmem and maxmem_kb and these are not
448 # consistent, then this is an error, as we've no way to tell which
449 # one takes precedence.
451 # Exactly the same thing applies to memory_KiB, memory, and
452 # mem_kb.
454 def discard_negatives(name):
455 if self.infoIsSet(name) and self.info[name] < 0:
456 del self.info[name]
458 def valid_KiB_(mb_name, kb_name):
459 discard_negatives(kb_name)
460 discard_negatives(mb_name)
462 if self.infoIsSet(kb_name):
463 if self.infoIsSet(mb_name):
464 mb = self.info[mb_name]
465 kb = self.info[kb_name]
466 if mb * 1024 == kb:
467 return kb
468 else:
469 raise VmError(
470 'Inconsistent %s / %s settings: %s / %s' %
471 (mb_name, kb_name, mb, kb))
472 else:
473 return self.info[kb_name]
474 elif self.infoIsSet(mb_name):
475 return self.info[mb_name] * 1024
476 else:
477 return None
479 def valid_KiB(mb_name, kb_name):
480 result = valid_KiB_(mb_name, kb_name)
481 if result is None or result < 0:
482 raise VmError('Invalid %s / %s: %s' %
483 (mb_name, kb_name, result))
484 else:
485 return result
487 def delIf(name):
488 if name in self.info:
489 del self.info[name]
491 self.info['memory_KiB'] = valid_KiB('memory', 'mem_kb')
492 delIf('memory')
493 delIf('mem_kb')
494 self.info['maxmem_KiB'] = valid_KiB_('maxmem', 'maxmem_kb')
495 delIf('maxmem')
496 delIf('maxmem_kb')
498 if not self.info['maxmem_KiB']:
499 self.info['maxmem_KiB'] = 1 << 30
501 if self.info['maxmem_KiB'] > self.info['memory_KiB']:
502 self.info['maxmem_KiB'] = self.info['memory_KiB']
504 for (n, c) in self.info['device']:
505 if not n or not c or n not in controllerClasses:
506 raise VmError('invalid device (%s, %s)' %
507 (str(n), str(c)))
509 for event in ['on_poweroff', 'on_reboot', 'on_crash']:
510 if self.info[event] not in restart_modes:
511 raise VmError('invalid restart event: %s = %s' %
512 (event, str(self.info[event])))
514 except KeyError, exn:
515 log.exception(exn)
516 raise VmError('Unspecified domain detail: %s' % exn)
519 def readVm(self, *args):
520 return xstransact.Read(self.vmpath, *args)
522 def writeVm(self, *args):
523 return xstransact.Write(self.vmpath, *args)
525 def removeVm(self, *args):
526 return xstransact.Remove(self.vmpath, *args)
528 def gatherVm(self, *args):
529 return xstransact.Gather(self.vmpath, *args)
532 ## public:
534 def storeVm(self, *args):
535 return xstransact.Store(self.vmpath, *args)
538 ## private:
540 def readDom(self, *args):
541 return xstransact.Read(self.dompath, *args)
543 def writeDom(self, *args):
544 return xstransact.Write(self.dompath, *args)
547 ## public:
549 def removeDom(self, *args):
550 return xstransact.Remove(self.dompath, *args)
553 ## private:
555 def storeDom(self, *args):
556 return xstransact.Store(self.dompath, *args)
559 ## public:
561 def completeRestore(self, store_mfn, console_mfn):
563 log.debug("XendDomainInfo.completeRestore")
565 self.store_mfn = store_mfn
566 self.console_mfn = console_mfn
568 self.introduceDomain()
569 self.storeDomDetails()
570 self.refreshShutdown()
573 def storeVmDetails(self):
574 to_store = {
575 'uuid': self.info['uuid'],
577 # XXX
578 'memory/target': str(self.info['memory_KiB'])
579 }
581 if self.infoIsSet('image'):
582 to_store['image'] = sxp.to_string(self.info['image'])
584 for k in ['name', 'ssidref', 'on_poweroff', 'on_reboot', 'on_crash',
585 'vcpus', 'vcpu_avail']:
586 if self.infoIsSet(k):
587 to_store[k] = str(self.info[k])
589 log.debug("Storing VM details: %s", to_store)
591 self.writeVm(to_store)
594 def storeDomDetails(self):
595 to_store = {
596 'domid': str(self.domid),
597 'vm': self.vmpath,
598 'console/limit': str(xroot.get_console_limit() * 1024),
599 'memory/target': str(self.info['memory_KiB'])
600 }
602 def f(n, v):
603 if v is not None:
604 to_store[n] = str(v)
606 f('console/port', self.console_port)
607 f('console/ring-ref', self.console_mfn)
608 f('store/port', self.store_port)
609 f('store/ring-ref', self.store_mfn)
611 to_store.update(self.vcpuDomDetails())
613 log.debug("Storing domain details: %s", to_store)
615 self.writeDom(to_store)
618 ## private:
620 def vcpuDomDetails(self):
621 def availability(n):
622 if self.info['vcpu_avail'] & (1 << n):
623 return 'online'
624 else:
625 return 'offline'
627 result = {}
628 for v in range(0, self.info['vcpus']):
629 result["cpu/%d/availability" % v] = availability(v)
630 return result
633 def setDomid(self, domid):
634 """Set the domain id.
636 @param dom: domain id
637 """
638 self.domid = domid
639 self.storeDom("domid", self.domid)
641 def getDomid(self):
642 return self.domid
644 def setName(self, name):
645 self.check_name(name)
646 self.info['name'] = name
647 self.storeVm("name", name)
649 def getName(self):
650 return self.info['name']
652 def getDomainPath(self):
653 return self.dompath
656 def getStorePort(self):
657 """For use only by image.py and XendCheckpoint.py."""
658 return self.store_port
661 def getConsolePort(self):
662 """For use only by image.py and XendCheckpoint.py"""
663 return self.console_port
666 def getVCpuCount(self):
667 return self.info['vcpus']
670 def setVCpuCount(self, vcpus):
671 self.info['vcpu_avail'] = (1 << vcpus) - 1
672 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
673 self.writeDom(self.vcpuDomDetails())
676 def getSsidref(self):
677 return self.info['ssidref']
679 def getMemoryTarget(self):
680 """Get this domain's target memory size, in KiB."""
681 return self.info['memory_KiB']
684 def refreshShutdown(self, xeninfo = None):
685 # If set at the end of this method, a restart is required, with the
686 # given reason. This restart has to be done out of the scope of
687 # refresh_shutdown_lock.
688 restart_reason = None
690 self.refresh_shutdown_lock.acquire()
691 try:
692 if xeninfo is None:
693 xeninfo = dom_get(self.domid)
694 if xeninfo is None:
695 # The domain no longer exists. This will occur if we have
696 # scheduled a timer to check for shutdown timeouts and the
697 # shutdown succeeded. It will also occur if someone
698 # destroys a domain beneath us. We clean up the domain,
699 # just in case, but we can't clean up the VM, because that
700 # VM may have migrated to a different domain on this
701 # machine.
702 self.cleanupDomain()
703 return
705 if xeninfo['dying']:
706 # Dying means that a domain has been destroyed, but has not
707 # yet been cleaned up by Xen. This state could persist
708 # indefinitely if, for example, another domain has some of its
709 # pages mapped. We might like to diagnose this problem in the
710 # future, but for now all we do is make sure that it's not us
711 # holding the pages, by calling cleanupDomain. We can't
712 # clean up the VM, as above.
713 self.cleanupDomain()
714 return
716 elif xeninfo['crashed']:
717 if self.readDom('xend/shutdown_completed'):
718 # We've seen this shutdown already, but we are preserving
719 # the domain for debugging. Leave it alone.
720 return
722 log.warn('Domain has crashed: name=%s id=%d.',
723 self.info['name'], self.domid)
725 if xroot.get_enable_dump():
726 self.dumpCore()
728 restart_reason = 'crash'
730 elif xeninfo['shutdown']:
731 if self.readDom('xend/shutdown_completed'):
732 # We've seen this shutdown already, but we are preserving
733 # the domain for debugging. Leave it alone.
734 return
736 else:
737 reason = shutdown_reason(xeninfo['shutdown_reason'])
739 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
740 self.info['name'], self.domid, reason)
742 self.clearRestart()
744 if reason == 'suspend':
745 self.state_set(STATE_DOM_SHUTDOWN)
746 # Don't destroy the domain. XendCheckpoint will do
747 # this once it has finished.
748 elif reason in ['poweroff', 'reboot']:
749 restart_reason = reason
750 else:
751 self.destroy()
753 elif self.dompath is None:
754 # We have yet to manage to call introduceDomain on this
755 # domain. This can happen if a restore is in progress, or has
756 # failed. Ignore this domain.
757 pass
758 else:
759 # Domain is alive. If we are shutting it down, then check
760 # the timeout on that, and destroy it if necessary.
762 sst = self.readDom('xend/shutdown_start_time')
763 if sst:
764 sst = float(sst)
765 timeout = SHUTDOWN_TIMEOUT - time.time() + sst
766 if timeout < 0:
767 log.info(
768 "Domain shutdown timeout expired: name=%s id=%s",
769 self.info['name'], self.domid)
770 self.destroy()
771 else:
772 log.debug(
773 "Scheduling refreshShutdown on domain %d in %ds.",
774 self.domid, timeout)
775 scheduler.later(timeout, self.refreshShutdown)
776 finally:
777 self.refresh_shutdown_lock.release()
779 if restart_reason:
780 self.maybeRestart(restart_reason)
783 def shutdown(self, reason):
784 if not reason in shutdown_reasons.values():
785 raise XendError('Invalid reason: %s' % reason)
786 self.storeDom("control/shutdown", reason)
787 if reason != 'suspend':
788 self.storeDom('xend/shutdown_start_time', time.time())
791 ## private:
793 def clearRestart(self):
794 self.removeDom("xend/shutdown_start_time")
797 def maybeRestart(self, reason):
798 # Dispatch to the correct method based upon the configured on_{reason}
799 # behaviour.
800 {"destroy" : self.destroy,
801 "restart" : self.restart,
802 "preserve" : self.preserve,
803 "rename-restart" : self.renameRestart}[self.info['on_' + reason]]()
806 def renameRestart(self):
807 self.restart(True)
810 def dumpCore(self):
811 """Create a core dump for this domain. Nothrow guarantee."""
813 try:
814 corefile = "/var/xen/dump/%s.%s.core" % (self.info['name'],
815 self.domid)
816 xc.domain_dumpcore(dom = self.domid, corefile = corefile)
818 except:
819 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
820 self.domid, self.info['name'])
823 ## public:
825 def setMemoryTarget(self, target):
826 """Set the memory target of this domain.
827 @param target In MiB.
828 """
829 # Internally we use KiB, but the command interface uses MiB.
830 t = target << 10
831 self.info['memory_KiB'] = t
832 self.storeDom("memory/target", t)
835 def update(self, info = None):
836 """Update with info from xc.domain_getinfo().
837 """
839 log.trace("XendDomainInfo.update(%s) on domain %d", info, self.domid)
841 if not info:
842 info = dom_get(self.domid)
843 if not info:
844 return
846 self.info.update(info)
847 self.validateInfo()
848 self.refreshShutdown(info)
850 log.trace("XendDomainInfo.update done on domain %d: %s", self.domid,
851 self.info)
854 ## private:
856 def state_set(self, state):
857 self.state_updated.acquire()
858 try:
859 if self.state != state:
860 self.state = state
861 self.state_updated.notifyAll()
862 finally:
863 self.state_updated.release()
866 ## public:
868 def waitForShutdown(self):
869 self.state_updated.acquire()
870 try:
871 while self.state == STATE_DOM_OK:
872 self.state_updated.wait()
873 finally:
874 self.state_updated.release()
877 def __str__(self):
878 s = "<domain"
879 s += " id=" + str(self.domid)
880 s += " name=" + self.info['name']
881 s += " memory=" + str(self.info['memory_KiB'] / 1024)
882 s += " ssidref=" + str(self.info['ssidref'])
883 s += ">"
884 return s
886 __repr__ = __str__
889 ## private:
891 def createDevice(self, deviceClass, devconfig):
892 return self.getDeviceController(deviceClass).createDevice(devconfig)
895 def reconfigureDevice(self, deviceClass, devid, devconfig):
896 return self.getDeviceController(deviceClass).reconfigureDevice(
897 devid, devconfig)
900 ## public:
902 def destroyDevice(self, deviceClass, devid):
903 return self.getDeviceController(deviceClass).destroyDevice(devid)
906 def getDeviceSxprs(self, deviceClass):
907 return self.getDeviceController(deviceClass).sxprs()
910 ## private:
912 def getDeviceConfigurations(self, deviceClass):
913 return self.getDeviceController(deviceClass).configurations()
916 def getDeviceController(self, name):
917 if name not in controllerClasses:
918 raise XendError("unknown device type: " + str(name))
920 return controllerClasses[name](self)
923 ## public:
925 def sxpr(self):
926 sxpr = ['domain',
927 ['domid', self.domid],
928 ['memory', self.info['memory_KiB'] / 1024]]
930 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
931 if self.infoIsSet(e[0]):
932 sxpr.append([e[0], self.info[e[0]]])
934 sxpr.append(['maxmem', self.info['maxmem_KiB'] / 1024])
936 if self.infoIsSet('image'):
937 sxpr.append(['image', self.info['image']])
939 if self.infoIsSet('device'):
940 for (_, c) in self.info['device']:
941 sxpr.append(['device', c])
943 def stateChar(name):
944 if name in self.info:
945 if self.info[name]:
946 return name[0]
947 else:
948 return '-'
949 else:
950 return '?'
952 state = reduce(
953 lambda x, y: x + y,
954 map(stateChar,
955 ['running', 'blocked', 'paused', 'shutdown', 'crashed',
956 'dying']))
958 sxpr.append(['state', state])
959 if self.infoIsSet('shutdown'):
960 reason = shutdown_reason(self.info['shutdown_reason'])
961 sxpr.append(['shutdown_reason', reason])
962 if self.infoIsSet('cpu_time'):
963 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
964 sxpr.append(['vcpus', self.info['vcpus']])
966 if self.infoIsSet('start_time'):
967 up_time = time.time() - self.info['start_time']
968 sxpr.append(['up_time', str(up_time) ])
969 sxpr.append(['start_time', str(self.info['start_time']) ])
971 if self.store_mfn:
972 sxpr.append(['store_mfn', self.store_mfn])
973 if self.console_mfn:
974 sxpr.append(['console_mfn', self.console_mfn])
976 return sxpr
979 def getVCPUInfo(self):
980 try:
981 def filter_cpumap(map, max):
982 return filter(lambda x: x >= 0, map[0:max])
984 # We include the domain name and ID, to help xm.
985 sxpr = ['domain',
986 ['domid', self.domid],
987 ['name', self.info['name']],
988 ['vcpu_count', self.info['vcpus']]]
990 for i in range(0, self.info['vcpus']):
991 info = xc.vcpu_getinfo(self.domid, i)
993 sxpr.append(['vcpu',
994 ['number', i],
995 ['online', info['online']],
996 ['blocked', info['blocked']],
997 ['running', info['running']],
998 ['cpu_time', info['cpu_time'] / 1e9],
999 ['cpu', info['cpu']],
1000 ['cpumap', filter_cpumap(info['cpumap'],
1001 self.info['vcpus'])]])
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(
1049 dom = 0, ssidref = self.info['ssidref'],
1050 handle = uuid.fromString(self.info['uuid']))
1052 if self.domid < 0:
1053 raise VmError('Creating domain failed: name=%s' %
1054 self.info['name'])
1056 self.dompath = GetDomainPath(self.domid)
1058 self.removeDom()
1060 # Set maximum number of vcpus in domain
1061 xc.domain_max_vcpus(self.domid, int(self.info['vcpus']))
1064 def introduceDomain(self):
1065 assert self.domid is not None
1066 assert self.store_mfn is not None
1067 assert self.store_port is not None
1069 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1072 def initDomain(self):
1073 log.debug('XendDomainInfo.initDomain: %s %s %s',
1074 self.domid,
1075 self.info['memory_KiB'],
1076 self.info['cpu_weight'])
1078 if not self.infoIsSet('image'):
1079 raise VmError('Missing image in configuration')
1081 self.image = image.create(self,
1082 self.info['image'],
1083 self.info['device'])
1085 if self.info['bootloader']:
1086 self.image.handleBootloading()
1088 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1090 m = self.image.getDomainMemory(self.info['memory_KiB'])
1091 xc.domain_setmaxmem(self.domid, maxmem_kb = m)
1092 xc.domain_memory_increase_reservation(self.domid, m, 0, 0)
1094 cpu = self.info['cpu']
1095 if cpu is not None and cpu != -1:
1096 xc.domain_pincpu(self.domid, 0, 1 << cpu)
1098 self.createChannels()
1100 channel_details = self.image.createImage()
1102 self.store_mfn = channel_details['store_mfn']
1103 if 'console_mfn' in channel_details:
1104 self.console_mfn = channel_details['console_mfn']
1106 self.introduceDomain()
1108 self.createDevices()
1110 self.info['start_time'] = time.time()
1113 ## public:
1115 def cleanupDomain(self):
1116 """Cleanup domain resources; release devices. Idempotent. Nothrow
1117 guarantee."""
1119 self.release_devices()
1121 if self.image:
1122 try:
1123 self.image.destroy()
1124 except:
1125 log.exception(
1126 "XendDomainInfo.cleanup: image.destroy() failed.")
1127 self.image = None
1129 try:
1130 self.removeDom()
1131 except:
1132 log.exception("Removing domain path failed.")
1134 try:
1135 if not self.info['name'].startswith(ZOMBIE_PREFIX):
1136 self.info['name'] = ZOMBIE_PREFIX + self.info['name']
1137 except:
1138 log.exception("Renaming Zombie failed.")
1140 self.state_set(STATE_DOM_SHUTDOWN)
1143 def cleanupVm(self):
1144 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1146 try:
1147 self.removeVm()
1148 except:
1149 log.exception("Removing VM path failed.")
1152 def destroy(self):
1153 """Cleanup VM and destroy domain. Nothrow guarantee."""
1155 log.debug("XendDomainInfo.destroy: domid=%s", self.domid)
1157 self.cleanupVm()
1158 if self.dompath is not None:
1159 self.destroyDomain()
1162 def destroyDomain(self):
1163 log.debug("XendDomainInfo.destroyDomain(%s)", self.domid)
1165 try:
1166 if self.domid is not None:
1167 xc.domain_destroy(dom=self.domid)
1168 except:
1169 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1171 self.cleanupDomain()
1174 ## private:
1176 def release_devices(self):
1177 """Release all domain's devices. Nothrow guarantee."""
1179 while True:
1180 t = xstransact("%s/device" % self.dompath)
1181 for n in controllerClasses.keys():
1182 for d in t.list(n):
1183 try:
1184 t.remove(d)
1185 except:
1186 # Log and swallow any exceptions in removal --
1187 # there's nothing more we can do.
1188 log.exception(
1189 "Device release failed: %s; %s; %s",
1190 self.info['name'], n, d)
1191 if t.commit():
1192 break
1195 def createChannels(self):
1196 """Create the channels to the domain.
1197 """
1198 self.store_port = self.createChannel()
1199 self.console_port = self.createChannel()
1202 def createChannel(self):
1203 """Create an event channel to the domain.
1204 """
1205 try:
1206 return xc.evtchn_alloc_unbound(dom=self.domid, remote_dom=0)
1207 except:
1208 log.exception("Exception in alloc_unbound(%d)", self.domid)
1209 raise
1212 ## public:
1214 def createDevices(self):
1215 """Create the devices for a vm.
1217 @raise: VmError for invalid devices
1218 """
1220 for (n, c) in self.info['device']:
1221 self.createDevice(n, c)
1223 if self.image:
1224 self.image.createDeviceModel()
1227 def device_create(self, dev_config):
1228 """Create a new device.
1230 @param dev_config: device configuration
1231 """
1232 dev_type = sxp.name(dev_config)
1233 devid = self.createDevice(dev_type, dev_config)
1234 # self.config.append(['device', dev.getConfig()])
1235 return self.getDeviceController(dev_type).sxpr(devid)
1238 def device_configure(self, dev_config, devid):
1239 """Configure an existing device.
1240 @param dev_config: device configuration
1241 @param devid: device id
1242 """
1243 deviceClass = sxp.name(dev_config)
1244 self.reconfigureDevice(deviceClass, devid, dev_config)
1247 def pause(self):
1248 xc.domain_pause(self.domid)
1251 def unpause(self):
1252 xc.domain_unpause(self.domid)
1255 ## private:
1257 def restart(self, rename = False):
1258 """Restart the domain after it has exited.
1260 @param rename True if the old domain is to be renamed and preserved,
1261 False if it is to be destroyed.
1262 """
1264 config = self.sxpr()
1266 if self.readVm('xend/restart_in_progress'):
1267 log.error('Xend failed during restart of domain %d. '
1268 'Refusing to restart to avoid loops.',
1269 self.domid)
1270 self.destroy()
1271 return
1273 self.writeVm('xend/restart_in_progress', 'True')
1275 now = time.time()
1276 rst = self.readVm('xend/previous_restart_time')
1277 log.error(rst)
1278 if rst:
1279 rst = float(rst)
1280 timeout = now - rst
1281 if timeout < MINIMUM_RESTART_TIME:
1282 log.error(
1283 'VM %s restarting too fast (%f seconds since the last '
1284 'restart). Refusing to restart to avoid loops.',
1285 self.info['name'], timeout)
1286 self.destroy()
1287 return
1289 self.writeVm('xend/previous_restart_time', str(now))
1291 try:
1292 if rename:
1293 self.preserveForRestart()
1294 else:
1295 self.destroyDomain()
1297 try:
1298 xd = get_component('xen.xend.XendDomain')
1299 new_dom = xd.domain_create(config)
1300 try:
1301 new_dom.unpause()
1302 except:
1303 new_dom.destroy()
1304 raise
1305 except:
1306 log.exception('Failed to restart domain %d.', self.domid)
1307 finally:
1308 self.removeVm('xend/restart_in_progress')
1310 # self.configure_bootloader()
1311 # self.exportToDB()
1314 def preserveForRestart(self):
1315 """Preserve a domain that has been shut down, by giving it a new UUID,
1316 cloning the VM details, and giving it a new name. This allows us to
1317 keep this domain for debugging, but restart a new one in its place
1318 preserving the restart semantics (name and UUID preserved).
1319 """
1321 new_name = self.generateUniqueName()
1322 new_uuid = uuid.toString(uuid.create())
1323 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1324 self.info['name'], self.domid, self.info['uuid'],
1325 new_name, new_uuid)
1326 self.release_devices()
1327 self.info['name'] = new_name
1328 self.info['uuid'] = new_uuid
1329 self.vmpath = VMROOT + new_uuid
1330 self.storeVmDetails()
1331 self.preserve()
1334 def preserve(self):
1335 log.info("Preserving dead domain %s (%d).", self.info['name'],
1336 self.domid)
1337 self.storeDom('xend/shutdown_completed', 'True')
1338 self.state_set(STATE_DOM_SHUTDOWN)
1341 # private:
1343 def generateUniqueName(self):
1344 n = 1
1345 while True:
1346 name = "%s-%d" % (self.info['name'], n)
1347 try:
1348 self.check_name(name)
1349 return name
1350 except VmError:
1351 n += 1
1354 def configure_bootloader(self):
1355 if not self.info['bootloader']:
1356 return
1357 # if we're restarting with a bootloader, we need to run it
1358 # FIXME: this assumes the disk is the first device and
1359 # that we're booting from the first disk
1360 blcfg = None
1361 # FIXME: this assumes that we want to use the first disk
1362 dev = sxp.child_value(self.config, "device")
1363 if dev:
1364 disk = sxp.child_value(dev, "uname")
1365 fn = blkdev_uname_to_file(disk)
1366 blcfg = bootloader(self.info['bootloader'], fn, 1,
1367 self.info['vcpus'])
1368 if blcfg is None:
1369 msg = "Had a bootloader specified, but can't find disk"
1370 log.error(msg)
1371 raise VmError(msg)
1372 self.config = sxp.merge(['vm', ['image', blcfg]], self.config)
1375 def send_sysrq(self, key):
1376 asserts.isCharConvertible(key)
1378 self.storeDom("control/sysrq", '%c' % key)
1381 def infoIsSet(self, name):
1382 return name in self.info and self.info[name] is not None
1385 #============================================================================
1386 # Register device controllers and their device config types.
1388 """A map from device-class names to the subclass of DevController that
1389 implements the device control specific to that device-class."""
1390 controllerClasses = {}
1392 def addControllerClass(device_class, cls):
1393 """Register a subclass of DevController to handle the named device-class.
1394 """
1395 cls.deviceClass = device_class
1396 controllerClasses[device_class] = cls
1399 from xen.xend.server import blkif, netif, tpmif, pciif, usbif
1400 addControllerClass('vbd', blkif.BlkifController)
1401 addControllerClass('vif', netif.NetifController)
1402 addControllerClass('vtpm', tpmif.TPMifController)
1403 addControllerClass('pci', pciif.PciController)
1404 addControllerClass('usb', usbif.UsbifController)