ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 7947:52a3c06be4f8

Downgrade debug message to level "trace".

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author emellor@leeni.uk.xensource.com
date Mon Nov 21 13:09:45 2005 +0100 (2005-11-21)
parents c5a49efa11d6
children c7a46ec8d4df
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
48 from xen.xend.xenstore.xswatch import xswatch
51 """Shutdown code for poweroff."""
52 DOMAIN_POWEROFF = 0
54 """Shutdown code for reboot."""
55 DOMAIN_REBOOT = 1
57 """Shutdown code for suspend."""
58 DOMAIN_SUSPEND = 2
60 """Shutdown code for crash."""
61 DOMAIN_CRASH = 3
63 """Shutdown code for halt."""
64 DOMAIN_HALT = 4
66 """Map shutdown codes to strings."""
67 shutdown_reasons = {
68 DOMAIN_POWEROFF: "poweroff",
69 DOMAIN_REBOOT : "reboot",
70 DOMAIN_SUSPEND : "suspend",
71 DOMAIN_CRASH : "crash",
72 DOMAIN_HALT : "halt"
73 }
75 restart_modes = [
76 "restart",
77 "destroy",
78 "preserve",
79 "rename-restart"
80 ]
82 STATE_DOM_OK = 1
83 STATE_DOM_SHUTDOWN = 2
85 SHUTDOWN_TIMEOUT = 30
87 VMROOT = '/vm/'
89 ZOMBIE_PREFIX = 'Zombie-'
91 """Minimum time between domain restarts in seconds."""
92 MINIMUM_RESTART_TIME = 20
94 RESTART_IN_PROGRESS = 'xend/restart_in_progress'
97 xc = xen.lowlevel.xc.new()
98 xroot = XendRoot.instance()
100 log = logging.getLogger("xend.XendDomainInfo")
101 #log.setLevel(logging.TRACE)
104 ##
105 # All parameters of VMs that may be configured on-the-fly, or at start-up.
106 #
107 VM_CONFIG_PARAMS = [
108 ('name', str),
109 ('on_poweroff', str),
110 ('on_reboot', str),
111 ('on_crash', str),
112 ]
115 ##
116 # Configuration entries that we expect to round-trip -- be read from the
117 # config file or xc, written to save-files (i.e. through sxpr), and reused as
118 # config on restart or restore, all without munging. Some configuration
119 # entries are munged for backwards compatibility reasons, or because they
120 # don't come out of xc in the same form as they are specified in the config
121 # file, so those are handled separately.
122 ROUNDTRIPPING_CONFIG_ENTRIES = [
123 ('uuid', str),
124 ('ssidref', int),
125 ('vcpus', int),
126 ('vcpu_avail', int),
127 ('cpu_weight', float),
128 ('memory', int),
129 ('maxmem', int),
130 ('bootloader', str),
131 ]
133 ROUNDTRIPPING_CONFIG_ENTRIES += VM_CONFIG_PARAMS
136 ##
137 # All entries written to the store. This is VM_CONFIGURATION_PARAMS, plus
138 # those entries written to the store that cannot be reconfigured on-the-fly.
139 #
140 VM_STORE_ENTRIES = [
141 ('uuid', str),
142 ('ssidref', int),
143 ('vcpus', int),
144 ('vcpu_avail', int),
145 ('memory', int),
146 ('maxmem', int),
147 ]
149 VM_STORE_ENTRIES += VM_CONFIG_PARAMS
152 #
153 # There are a number of CPU-related fields:
154 #
155 # vcpus: the number of virtual CPUs this domain is configured to use.
156 # vcpu_avail: a bitmap telling the guest domain whether it may use each of
157 # its VCPUs. This is translated to
158 # <dompath>/cpu/<id>/availability = {online,offline} for use
159 # by the guest domain.
160 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
161 # CPUs that that VCPU may use.
162 # cpu: a configuration setting requesting that VCPU 0 is pinned to
163 # the specified physical CPU.
164 #
165 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
166 # across save, restore, migrate, and restart). The other settings are only
167 # specific to the domain, so are lost when the VM moves.
168 #
171 def create(config):
172 """Create a VM from a configuration.
174 @param config configuration
175 @raise: VmError for invalid configuration
176 """
178 log.debug("XendDomainInfo.create(%s)", config)
180 vm = XendDomainInfo(parseConfig(config))
181 try:
182 vm.construct()
183 vm.initDomain()
184 vm.storeVmDetails()
185 vm.storeDomDetails()
186 vm.registerWatch()
187 vm.refreshShutdown()
188 return vm
189 except:
190 log.exception('Domain construction failed')
191 vm.destroy()
192 raise
195 def recreate(xeninfo, priv):
196 """Create the VM object for an existing domain. The domain must not
197 be dying, as the paths in the store should already have been removed,
198 and asking us to recreate them causes problems."""
200 log.debug("XendDomainInfo.recreate(%s)", xeninfo)
202 assert not xeninfo['dying']
204 domid = xeninfo['dom']
205 uuid1 = xeninfo['handle']
206 xeninfo['uuid'] = uuid.toString(uuid1)
207 dompath = GetDomainPath(domid)
208 if not dompath:
209 raise XendError(
210 'No domain path in store for existing domain %d' % domid)
212 log.info("Recreating domain %d, UUID %s.", domid, xeninfo['uuid'])
213 try:
214 vmpath = xstransact.Read(dompath, "vm")
215 if not vmpath:
216 raise XendError(
217 'No vm path in store for existing domain %d' % domid)
218 uuid2_str = xstransact.Read(vmpath, "uuid")
219 if not uuid2_str:
220 raise XendError(
221 'No vm/uuid path in store for existing domain %d' % domid)
223 uuid2 = uuid.fromString(uuid2_str)
225 if uuid1 != uuid2:
226 raise XendError(
227 'Uuid in store does not match uuid for existing domain %d: '
228 '%s != %s' % (domid, uuid2_str, xeninfo['uuid']))
230 vm = XendDomainInfo(xeninfo, domid, dompath, True)
232 except Exception, exn:
233 if priv:
234 log.warn(str(exn))
236 vm = XendDomainInfo(xeninfo, domid, dompath, True)
237 vm.removeDom()
238 vm.removeVm()
239 vm.storeVmDetails()
240 vm.storeDomDetails()
242 vm.registerWatch()
243 vm.refreshShutdown(xeninfo)
244 return vm
247 def restore(config):
248 """Create a domain and a VM object to do a restore.
250 @param config: domain configuration
251 """
253 log.debug("XendDomainInfo.restore(%s)", config)
255 vm = XendDomainInfo(parseConfig(config))
256 try:
257 vm.construct()
258 vm.storeVmDetails()
259 vm.createDevices()
260 vm.createChannels()
261 vm.storeDomDetails()
262 return vm
263 except:
264 vm.destroy()
265 raise
268 def parseConfig(config):
269 def get_cfg(name, conv = None):
270 val = sxp.child_value(config, name)
272 if conv and not val is None:
273 try:
274 return conv(val)
275 except TypeError, exn:
276 raise VmError(
277 'Invalid setting %s = %s in configuration: %s' %
278 (name, val, str(exn)))
279 else:
280 return val
283 log.debug("parseConfig: config is %s", config)
285 result = {}
287 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
288 result[e[0]] = get_cfg(e[0], e[1])
290 result['cpu'] = get_cfg('cpu', int)
291 result['image'] = get_cfg('image')
293 try:
294 if result['image']:
295 result['vcpus'] = int(sxp.child_value(result['image'],
296 'vcpus', 1))
297 else:
298 result['vcpus'] = 1
299 except TypeError, exn:
300 raise VmError(
301 'Invalid configuration setting: vcpus = %s: %s' %
302 (sxp.child_value(result['image'], 'vcpus', 1), str(exn)))
304 result['backend'] = []
305 for c in sxp.children(config, 'backend'):
306 result['backend'].append(sxp.name(sxp.child0(c)))
308 result['device'] = []
309 for d in sxp.children(config, 'device'):
310 c = sxp.child0(d)
311 result['device'].append((sxp.name(c), c))
313 # Configuration option "restart" is deprecated. Parse it, but
314 # let on_xyz override it if they are present.
315 restart = get_cfg('restart')
316 if restart:
317 def handle_restart(event, val):
318 if result[event] is None:
319 result[event] = val
321 if restart == "onreboot":
322 handle_restart('on_poweroff', 'destroy')
323 handle_restart('on_reboot', 'restart')
324 handle_restart('on_crash', 'destroy')
325 elif restart == "always":
326 handle_restart('on_poweroff', 'restart')
327 handle_restart('on_reboot', 'restart')
328 handle_restart('on_crash', 'restart')
329 elif restart == "never":
330 handle_restart('on_poweroff', 'destroy')
331 handle_restart('on_reboot', 'destroy')
332 handle_restart('on_crash', 'destroy')
333 else:
334 log.warn("Ignoring malformed and deprecated config option "
335 "restart = %s", restart)
337 log.debug("parseConfig: result is %s", result)
338 return result
341 def domain_by_name(name):
342 # See comment in XendDomain constructor.
343 xd = get_component('xen.xend.XendDomain')
344 return xd.domain_lookup_by_name_nr(name)
346 def shutdown_reason(code):
347 """Get a shutdown reason from a code.
349 @param code: shutdown code
350 @type code: int
351 @return: shutdown reason
352 @rtype: string
353 """
354 return shutdown_reasons.get(code, "?")
356 def dom_get(dom):
357 """Get info from xen for an existing domain.
359 @param dom: domain id
360 @return: info or None
361 """
362 try:
363 domlist = xc.domain_getinfo(dom, 1)
364 if domlist and dom == domlist[0]['dom']:
365 return domlist[0]
366 except Exception, err:
367 # ignore missing domain
368 log.trace("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
369 return None
372 class XendDomainInfo:
374 def __init__(self, info, domid = None, dompath = None, augment = False):
376 self.info = info
378 if not self.infoIsSet('uuid'):
379 self.info['uuid'] = uuid.toString(uuid.create())
381 if domid is not None:
382 self.domid = domid
383 elif 'dom' in info:
384 self.domid = int(info['dom'])
385 else:
386 self.domid = None
388 self.vmpath = VMROOT + self.info['uuid']
389 self.dompath = dompath
391 if augment:
392 self.augmentInfo()
394 self.validateInfo()
396 self.image = None
398 self.store_port = None
399 self.store_mfn = None
400 self.console_port = None
401 self.console_mfn = None
403 self.vmWatch = None
405 self.state = STATE_DOM_OK
406 self.state_updated = threading.Condition()
407 self.refresh_shutdown_lock = threading.Condition()
410 ## private:
412 def readVMDetails(self, params):
413 """Read from the store all of those entries that we consider
414 """
415 try:
416 return self.gatherVm(*params)
417 except ValueError:
418 # One of the int/float entries in params has a corresponding store
419 # entry that is invalid. We recover, because older versions of
420 # Xend may have put the entry there (memory/target, for example),
421 # but this is in general a bad situation to have reached.
422 log.exception(
423 "Store corrupted at %s! Domain %d's configuration may be "
424 "affected.", self.vmpath, self.domid)
425 return []
428 def storeChanged(self):
429 log.trace("XendDomainInfo.storeChanged");
431 changed = False
433 def f(x, y):
434 if y is not None and self.info[x[0]] != y:
435 self.info[x[0]] = y
436 changed = True
438 map(f, VM_CONFIG_PARAMS, self.readVMDetails(VM_CONFIG_PARAMS))
440 if changed:
441 # Update the domain section of the store, as this contains some
442 # parameters derived from the VM configuration.
443 self.storeDomDetails()
445 return 1
448 def augmentInfo(self):
449 """Augment self.info, as given to us through {@link #recreate}, with
450 values taken from the store. This recovers those values known to xend
451 but not to the hypervisor.
452 """
453 def useIfNeeded(name, val):
454 if not self.infoIsSet(name) and val is not None:
455 self.info[name] = val
457 map(lambda x, y: useIfNeeded(x[0], y), VM_STORE_ENTRIES,
458 self.readVMDetails(VM_STORE_ENTRIES))
460 device = []
461 for c in controllerClasses:
462 devconfig = self.getDeviceConfigurations(c)
463 if devconfig:
464 device.extend(map(lambda x: (c, x), devconfig))
465 useIfNeeded('device', device)
468 def validateInfo(self):
469 """Validate and normalise the info block. This has either been parsed
470 by parseConfig, or received from xc through recreate and augmented by
471 the current store contents.
472 """
473 def defaultInfo(name, val):
474 if not self.infoIsSet(name):
475 self.info[name] = val()
477 try:
478 defaultInfo('name', lambda: "Domain-%d" % self.domid)
479 defaultInfo('ssidref', lambda: 0)
480 defaultInfo('on_poweroff', lambda: "destroy")
481 defaultInfo('on_reboot', lambda: "restart")
482 defaultInfo('on_crash', lambda: "restart")
483 defaultInfo('cpu', lambda: None)
484 defaultInfo('cpu_weight', lambda: 1.0)
486 # some domains don't have a config file (e.g. dom0 )
487 # to set number of vcpus so we derive available cpus
488 # from max_vcpu_id which is present for running domains.
489 if not self.infoIsSet('vcpus') and self.infoIsSet('max_vcpu_id'):
490 avail = int(self.info['max_vcpu_id'])+1
491 else:
492 avail = int(1)
494 defaultInfo('vcpus', lambda: avail)
495 defaultInfo('online_vcpus', lambda: self.info['vcpus'])
496 defaultInfo('max_vcpu_id', lambda: self.info['vcpus']-1)
497 defaultInfo('vcpu_avail', lambda: (1 << self.info['vcpus']) - 1)
499 defaultInfo('memory', lambda: 0)
500 defaultInfo('maxmem', lambda: 0)
501 defaultInfo('bootloader', lambda: None)
502 defaultInfo('backend', lambda: [])
503 defaultInfo('device', lambda: [])
504 defaultInfo('image', lambda: None)
506 self.check_name(self.info['name'])
508 if isinstance(self.info['image'], str):
509 self.info['image'] = sxp.from_string(self.info['image'])
511 if self.info['memory'] == 0:
512 if self.infoIsSet('mem_kb'):
513 self.info['memory'] = (self.info['mem_kb'] + 1023) / 1024
515 if self.info['maxmem'] < self.info['memory']:
516 self.info['maxmem'] = self.info['memory']
518 for (n, c) in self.info['device']:
519 if not n or not c or n not in controllerClasses:
520 raise VmError('invalid device (%s, %s)' %
521 (str(n), str(c)))
523 for event in ['on_poweroff', 'on_reboot', 'on_crash']:
524 if self.info[event] not in restart_modes:
525 raise VmError('invalid restart event: %s = %s' %
526 (event, str(self.info[event])))
528 except KeyError, exn:
529 log.exception(exn)
530 raise VmError('Unspecified domain detail: %s' % exn)
533 def readVm(self, *args):
534 return xstransact.Read(self.vmpath, *args)
536 def writeVm(self, *args):
537 return xstransact.Write(self.vmpath, *args)
539 def removeVm(self, *args):
540 return xstransact.Remove(self.vmpath, *args)
542 def gatherVm(self, *args):
543 return xstransact.Gather(self.vmpath, *args)
546 ## public:
548 def storeVm(self, *args):
549 return xstransact.Store(self.vmpath, *args)
552 ## private:
554 def readDom(self, *args):
555 return xstransact.Read(self.dompath, *args)
557 def writeDom(self, *args):
558 return xstransact.Write(self.dompath, *args)
561 ## public:
563 def removeDom(self, *args):
564 return xstransact.Remove(self.dompath, *args)
567 ## private:
569 def storeDom(self, *args):
570 return xstransact.Store(self.dompath, *args)
573 ## public:
575 def completeRestore(self, store_mfn, console_mfn):
577 log.debug("XendDomainInfo.completeRestore")
579 self.store_mfn = store_mfn
580 self.console_mfn = console_mfn
582 self.introduceDomain()
583 self.storeDomDetails()
584 self.registerWatch()
585 self.refreshShutdown()
587 log.debug("XendDomainInfo.completeRestore done")
590 def storeVmDetails(self):
591 to_store = {}
593 for k in VM_STORE_ENTRIES:
594 if self.infoIsSet(k[0]):
595 to_store[k[0]] = str(self.info[k[0]])
597 if self.infoIsSet('image'):
598 to_store['image'] = sxp.to_string(self.info['image'])
600 if self.infoIsSet('start_time'):
601 to_store['start_time'] = str(self.info['start_time'])
603 log.debug("Storing VM details: %s", to_store)
605 self.writeVm(to_store)
608 def storeDomDetails(self):
609 to_store = {
610 'domid': str(self.domid),
611 'vm': self.vmpath,
612 'name': self.info['name'],
613 'console/limit': str(xroot.get_console_limit() * 1024),
614 'memory/target': str(self.info['memory'] * 1024)
615 }
617 def f(n, v):
618 if v is not None:
619 to_store[n] = str(v)
621 f('console/port', self.console_port)
622 f('console/ring-ref', self.console_mfn)
623 f('store/port', self.store_port)
624 f('store/ring-ref', self.store_mfn)
626 to_store.update(self.vcpuDomDetails())
628 log.debug("Storing domain details: %s", to_store)
630 self.writeDom(to_store)
633 ## private:
635 def vcpuDomDetails(self):
636 def availability(n):
637 if self.info['vcpu_avail'] & (1 << n):
638 return 'online'
639 else:
640 return 'offline'
642 result = {}
643 for v in range(0, self.info['vcpus']):
644 result["cpu/%d/availability" % v] = availability(v)
645 return result
648 ## public:
650 def registerWatch(self):
651 """Register a watch on this VM's entries in the store, so that
652 when they are changed externally, we keep up to date. This should
653 only be called by {@link #create}, {@link #recreate}, or {@link
654 #restore}, once the domain's details have been written, but before the
655 new instance is returned."""
656 self.vmWatch = xswatch(self.vmpath, self.storeChanged)
659 def getDomid(self):
660 return self.domid
662 def setName(self, name):
663 self.check_name(name)
664 self.info['name'] = name
665 self.storeVm("name", name)
667 def getName(self):
668 return self.info['name']
670 def getDomainPath(self):
671 return self.dompath
674 def getStorePort(self):
675 """For use only by image.py and XendCheckpoint.py."""
676 return self.store_port
679 def getConsolePort(self):
680 """For use only by image.py and XendCheckpoint.py"""
681 return self.console_port
684 def getVCpuCount(self):
685 return self.info['vcpus']
688 def setVCpuCount(self, vcpus):
689 self.info['vcpu_avail'] = (1 << vcpus) - 1
690 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
691 self.writeDom(self.vcpuDomDetails())
694 def getSsidref(self):
695 return self.info['ssidref']
697 def getMemoryTarget(self):
698 """Get this domain's target memory size, in KB."""
699 return self.info['memory'] * 1024
702 def refreshShutdown(self, xeninfo = None):
703 # If set at the end of this method, a restart is required, with the
704 # given reason. This restart has to be done out of the scope of
705 # refresh_shutdown_lock.
706 restart_reason = None
708 self.refresh_shutdown_lock.acquire()
709 try:
710 if xeninfo is None:
711 xeninfo = dom_get(self.domid)
712 if xeninfo is None:
713 # The domain no longer exists. This will occur if we have
714 # scheduled a timer to check for shutdown timeouts and the
715 # shutdown succeeded. It will also occur if someone
716 # destroys a domain beneath us. We clean up the domain,
717 # just in case, but we can't clean up the VM, because that
718 # VM may have migrated to a different domain on this
719 # machine.
720 self.cleanupDomain()
721 return
723 if xeninfo['dying']:
724 # Dying means that a domain has been destroyed, but has not
725 # yet been cleaned up by Xen. This state could persist
726 # indefinitely if, for example, another domain has some of its
727 # pages mapped. We might like to diagnose this problem in the
728 # future, but for now all we do is make sure that it's not us
729 # holding the pages, by calling cleanupDomain. We can't
730 # clean up the VM, as above.
731 self.cleanupDomain()
732 return
734 elif xeninfo['crashed']:
735 if self.readDom('xend/shutdown_completed'):
736 # We've seen this shutdown already, but we are preserving
737 # the domain for debugging. Leave it alone.
738 return
740 log.warn('Domain has crashed: name=%s id=%d.',
741 self.info['name'], self.domid)
743 if xroot.get_enable_dump():
744 self.dumpCore()
746 restart_reason = 'crash'
748 elif xeninfo['shutdown']:
749 if self.readDom('xend/shutdown_completed'):
750 # We've seen this shutdown already, but we are preserving
751 # the domain for debugging. Leave it alone.
752 return
754 else:
755 reason = shutdown_reason(xeninfo['shutdown_reason'])
757 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
758 self.info['name'], self.domid, reason)
760 self.clearRestart()
762 if reason == 'suspend':
763 self.state_set(STATE_DOM_SHUTDOWN)
764 # Don't destroy the domain. XendCheckpoint will do
765 # this once it has finished.
766 elif reason in ['poweroff', 'reboot']:
767 restart_reason = reason
768 else:
769 self.destroy()
771 elif self.dompath is None:
772 # We have yet to manage to call introduceDomain on this
773 # domain. This can happen if a restore is in progress, or has
774 # failed. Ignore this domain.
775 pass
776 else:
777 # Domain is alive. If we are shutting it down, then check
778 # the timeout on that, and destroy it if necessary.
780 sst = self.readDom('xend/shutdown_start_time')
781 if sst:
782 sst = float(sst)
783 timeout = SHUTDOWN_TIMEOUT - time.time() + sst
784 if timeout < 0:
785 log.info(
786 "Domain shutdown timeout expired: name=%s id=%s",
787 self.info['name'], self.domid)
788 self.destroy()
789 else:
790 log.debug(
791 "Scheduling refreshShutdown on domain %d in %ds.",
792 self.domid, timeout)
793 scheduler.later(timeout, self.refreshShutdown)
794 finally:
795 self.refresh_shutdown_lock.release()
797 if restart_reason:
798 self.maybeRestart(restart_reason)
801 def shutdown(self, reason):
802 if not reason in shutdown_reasons.values():
803 raise XendError('Invalid reason: %s' % reason)
804 self.storeDom("control/shutdown", reason)
805 if reason != 'suspend':
806 self.storeDom('xend/shutdown_start_time', time.time())
809 ## private:
811 def clearRestart(self):
812 self.removeDom("xend/shutdown_start_time")
815 def maybeRestart(self, reason):
816 # Dispatch to the correct method based upon the configured on_{reason}
817 # behaviour.
818 {"destroy" : self.destroy,
819 "restart" : self.restart,
820 "preserve" : self.preserve,
821 "rename-restart" : self.renameRestart}[self.info['on_' + reason]]()
824 def renameRestart(self):
825 self.restart(True)
828 def dumpCore(self):
829 """Create a core dump for this domain. Nothrow guarantee."""
831 try:
832 corefile = "/var/xen/dump/%s.%s.core" % (self.info['name'],
833 self.domid)
834 xc.domain_dumpcore(dom = self.domid, corefile = corefile)
836 except:
837 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
838 self.domid, self.info['name'])
841 ## public:
843 def setMemoryTarget(self, target):
844 """Set the memory target of this domain.
845 @param target In MiB.
846 """
847 self.info['memory'] = target
848 self.storeVm("memory", target)
849 self.storeDom("memory/target", target << 10)
852 def update(self, info = None):
853 """Update with info from xc.domain_getinfo().
854 """
856 log.trace("XendDomainInfo.update(%s) on domain %d", info, self.domid)
858 if not info:
859 info = dom_get(self.domid)
860 if not info:
861 return
863 self.info.update(info)
864 self.validateInfo()
865 self.refreshShutdown(info)
867 log.trace("XendDomainInfo.update done on domain %d: %s", self.domid,
868 self.info)
871 ## private:
873 def state_set(self, state):
874 self.state_updated.acquire()
875 try:
876 if self.state != state:
877 self.state = state
878 self.state_updated.notifyAll()
879 finally:
880 self.state_updated.release()
883 ## public:
885 def waitForShutdown(self):
886 self.state_updated.acquire()
887 try:
888 while self.state == STATE_DOM_OK:
889 self.state_updated.wait()
890 finally:
891 self.state_updated.release()
894 def __str__(self):
895 s = "<domain"
896 s += " id=" + str(self.domid)
897 s += " name=" + self.info['name']
898 s += " memory=" + str(self.info['memory'])
899 s += " ssidref=" + str(self.info['ssidref'])
900 s += ">"
901 return s
903 __repr__ = __str__
906 ## private:
908 def createDevice(self, deviceClass, devconfig):
909 return self.getDeviceController(deviceClass).createDevice(devconfig)
912 def waitForDevices_(self, deviceClass):
913 return self.getDeviceController(deviceClass).waitForDevices()
916 def waitForDevice(self, deviceClass, devid):
917 return self.getDeviceController(deviceClass).waitForDevice(devid)
920 def reconfigureDevice(self, deviceClass, devid, devconfig):
921 return self.getDeviceController(deviceClass).reconfigureDevice(
922 devid, devconfig)
925 ## public:
927 def destroyDevice(self, deviceClass, devid):
928 return self.getDeviceController(deviceClass).destroyDevice(devid)
931 def getDeviceSxprs(self, deviceClass):
932 return self.getDeviceController(deviceClass).sxprs()
935 ## private:
937 def getDeviceConfigurations(self, deviceClass):
938 return self.getDeviceController(deviceClass).configurations()
941 def getDeviceController(self, name):
942 if name not in controllerClasses:
943 raise XendError("unknown device type: " + str(name))
945 return controllerClasses[name](self)
948 ## public:
950 def sxpr(self):
951 sxpr = ['domain',
952 ['domid', self.domid]]
954 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
955 if self.infoIsSet(e[0]):
956 sxpr.append([e[0], self.info[e[0]]])
958 if self.infoIsSet('image'):
959 sxpr.append(['image', self.info['image']])
961 if self.infoIsSet('device'):
962 for (_, c) in self.info['device']:
963 sxpr.append(['device', c])
965 def stateChar(name):
966 if name in self.info:
967 if self.info[name]:
968 return name[0]
969 else:
970 return '-'
971 else:
972 return '?'
974 state = reduce(
975 lambda x, y: x + y,
976 map(stateChar,
977 ['running', 'blocked', 'paused', 'shutdown', 'crashed',
978 'dying']))
980 sxpr.append(['state', state])
981 if self.infoIsSet('shutdown'):
982 reason = shutdown_reason(self.info['shutdown_reason'])
983 sxpr.append(['shutdown_reason', reason])
984 if self.infoIsSet('cpu_time'):
985 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
986 sxpr.append(['vcpus', self.info['vcpus']])
987 sxpr.append(['online_vcpus', self.info['online_vcpus']])
989 if self.infoIsSet('start_time'):
990 up_time = time.time() - self.info['start_time']
991 sxpr.append(['up_time', str(up_time) ])
992 sxpr.append(['start_time', str(self.info['start_time']) ])
994 if self.store_mfn:
995 sxpr.append(['store_mfn', self.store_mfn])
996 if self.console_mfn:
997 sxpr.append(['console_mfn', self.console_mfn])
999 return sxpr
1002 def getVCPUInfo(self):
1003 try:
1004 # We include the domain name and ID, to help xm.
1005 sxpr = ['domain',
1006 ['domid', self.domid],
1007 ['name', self.info['name']],
1008 ['vcpu_count', self.info['online_vcpus']]]
1010 for i in range(0, self.info['max_vcpu_id']+1):
1011 info = xc.vcpu_getinfo(self.domid, i)
1013 sxpr.append(['vcpu',
1014 ['number', i],
1015 ['online', info['online']],
1016 ['blocked', info['blocked']],
1017 ['running', info['running']],
1018 ['cpu_time', info['cpu_time'] / 1e9],
1019 ['cpu', info['cpu']],
1020 ['cpumap', info['cpumap']]])
1022 return sxpr
1024 except RuntimeError, exn:
1025 raise XendError(str(exn))
1028 ## private:
1030 def check_name(self, name):
1031 """Check if a vm name is valid. Valid names contain alphabetic characters,
1032 digits, or characters in '_-.:/+'.
1033 The same name cannot be used for more than one vm at the same time.
1035 @param name: name
1036 @raise: VmError if invalid
1037 """
1038 if name is None or name == '':
1039 raise VmError('missing vm name')
1040 for c in name:
1041 if c in string.digits: continue
1042 if c in '_-.:/+': continue
1043 if c in string.ascii_letters: continue
1044 raise VmError('invalid vm name')
1046 dominfo = domain_by_name(name)
1047 if not dominfo:
1048 return
1049 if self.domid is None:
1050 raise VmError("VM name '%s' already in use by domain %d" %
1051 (name, dominfo.domid))
1052 if dominfo.domid != self.domid:
1053 raise VmError("VM name '%s' is used in both domains %d and %d" %
1054 (name, self.domid, dominfo.domid))
1057 def construct(self):
1058 """Construct the domain.
1060 @raise: VmError on error
1061 """
1063 log.debug('XendDomainInfo.construct: %s %s',
1064 self.domid,
1065 self.info['ssidref'])
1067 self.domid = xc.domain_create(
1068 dom = 0, ssidref = self.info['ssidref'],
1069 handle = uuid.fromString(self.info['uuid']))
1071 if self.domid < 0:
1072 raise VmError('Creating domain failed: name=%s' %
1073 self.info['name'])
1075 self.dompath = GetDomainPath(self.domid)
1077 self.removeDom()
1079 # Set maximum number of vcpus in domain
1080 xc.domain_max_vcpus(self.domid, int(self.info['vcpus']))
1083 def introduceDomain(self):
1084 assert self.domid is not None
1085 assert self.store_mfn is not None
1086 assert self.store_port is not None
1088 try:
1089 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1090 except RuntimeError, exn:
1091 raise XendError(str(exn))
1094 def initDomain(self):
1095 log.debug('XendDomainInfo.initDomain: %s %s',
1096 self.domid,
1097 self.info['cpu_weight'])
1099 if not self.infoIsSet('image'):
1100 raise VmError('Missing image in configuration')
1102 self.image = image.create(self,
1103 self.info['image'],
1104 self.info['device'])
1106 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1108 m = self.image.getDomainMemory(self.info['memory'] * 1024)
1109 xc.domain_setmaxmem(self.domid, maxmem_kb = m)
1110 xc.domain_memory_increase_reservation(self.domid, m, 0, 0)
1112 cpu = self.info['cpu']
1113 if cpu is not None and cpu != -1:
1114 xc.domain_pincpu(self.domid, 0, 1 << cpu)
1116 self.createChannels()
1118 channel_details = self.image.createImage()
1120 self.store_mfn = channel_details['store_mfn']
1121 if 'console_mfn' in channel_details:
1122 self.console_mfn = channel_details['console_mfn']
1124 self.introduceDomain()
1126 self.createDevices()
1128 if self.info['bootloader']:
1129 self.image.cleanupBootloading()
1131 self.info['start_time'] = time.time()
1134 ## public:
1136 def cleanupDomain(self):
1137 """Cleanup domain resources; release devices. Idempotent. Nothrow
1138 guarantee."""
1140 self.release_devices()
1142 if self.image:
1143 try:
1144 self.image.destroy()
1145 except:
1146 log.exception(
1147 "XendDomainInfo.cleanup: image.destroy() failed.")
1148 self.image = None
1150 try:
1151 self.removeDom()
1152 except:
1153 log.exception("Removing domain path failed.")
1155 try:
1156 if not self.info['name'].startswith(ZOMBIE_PREFIX):
1157 self.info['name'] = ZOMBIE_PREFIX + self.info['name']
1158 except:
1159 log.exception("Renaming Zombie failed.")
1161 self.state_set(STATE_DOM_SHUTDOWN)
1164 def cleanupVm(self):
1165 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1167 try:
1168 try:
1169 if self.vmWatch:
1170 self.vmWatch.unwatch()
1171 self.vmWatch = None
1172 except:
1173 log.exception("Unwatching VM path failed.")
1175 self.removeVm()
1176 except:
1177 log.exception("Removing VM path failed.")
1180 def destroy(self):
1181 """Cleanup VM and destroy domain. Nothrow guarantee."""
1183 log.debug("XendDomainInfo.destroy: domid=%s", self.domid)
1185 self.cleanupVm()
1186 if self.dompath is not None:
1187 self.destroyDomain()
1190 def destroyDomain(self):
1191 log.debug("XendDomainInfo.destroyDomain(%s)", self.domid)
1193 try:
1194 if self.domid is not None:
1195 xc.domain_destroy(dom=self.domid)
1196 except:
1197 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1199 self.cleanupDomain()
1202 ## private:
1204 def release_devices(self):
1205 """Release all domain's devices. Nothrow guarantee."""
1207 while True:
1208 t = xstransact("%s/device" % self.dompath)
1209 for n in controllerClasses.keys():
1210 for d in t.list(n):
1211 try:
1212 t.remove(d)
1213 except:
1214 # Log and swallow any exceptions in removal --
1215 # there's nothing more we can do.
1216 log.exception(
1217 "Device release failed: %s; %s; %s",
1218 self.info['name'], n, d)
1219 if t.commit():
1220 break
1223 def createChannels(self):
1224 """Create the channels to the domain.
1225 """
1226 self.store_port = self.createChannel()
1227 self.console_port = self.createChannel()
1230 def createChannel(self):
1231 """Create an event channel to the domain.
1232 """
1233 try:
1234 return xc.evtchn_alloc_unbound(dom=self.domid, remote_dom=0)
1235 except:
1236 log.exception("Exception in alloc_unbound(%d)", self.domid)
1237 raise
1240 ## public:
1242 def createDevices(self):
1243 """Create the devices for a vm.
1245 @raise: VmError for invalid devices
1246 """
1248 for (n, c) in self.info['device']:
1249 self.createDevice(n, c)
1251 if self.image:
1252 self.image.createDeviceModel()
1255 def waitForDevices(self):
1256 """Wait for this domain's configured devices to connect.
1258 @raise: VmError if any device fails to initialise.
1259 """
1260 for c in controllerClasses:
1261 self.waitForDevices_(c)
1264 def device_create(self, dev_config):
1265 """Create a new device.
1267 @param dev_config: device configuration
1268 """
1269 dev_type = sxp.name(dev_config)
1270 devid = self.createDevice(dev_type, dev_config)
1271 self.waitForDevice(dev_type, devid)
1272 # self.config.append(['device', dev.getConfig()])
1273 return self.getDeviceController(dev_type).sxpr(devid)
1276 def device_configure(self, dev_config, devid):
1277 """Configure an existing device.
1278 @param dev_config: device configuration
1279 @param devid: device id
1280 """
1281 deviceClass = sxp.name(dev_config)
1282 self.reconfigureDevice(deviceClass, devid, dev_config)
1285 def pause(self):
1286 xc.domain_pause(self.domid)
1289 def unpause(self):
1290 xc.domain_unpause(self.domid)
1293 ## private:
1295 def restart(self, rename = False):
1296 """Restart the domain after it has exited.
1298 @param rename True if the old domain is to be renamed and preserved,
1299 False if it is to be destroyed.
1300 """
1302 self.configure_bootloader()
1303 config = self.sxpr()
1305 if self.readVm(RESTART_IN_PROGRESS):
1306 log.error('Xend failed during restart of domain %d. '
1307 'Refusing to restart to avoid loops.',
1308 self.domid)
1309 self.destroy()
1310 return
1312 self.writeVm(RESTART_IN_PROGRESS, 'True')
1314 now = time.time()
1315 rst = self.readVm('xend/previous_restart_time')
1316 if rst:
1317 rst = float(rst)
1318 timeout = now - rst
1319 if timeout < MINIMUM_RESTART_TIME:
1320 log.error(
1321 'VM %s restarting too fast (%f seconds since the last '
1322 'restart). Refusing to restart to avoid loops.',
1323 self.info['name'], timeout)
1324 self.destroy()
1325 return
1327 self.writeVm('xend/previous_restart_time', str(now))
1329 try:
1330 if rename:
1331 self.preserveForRestart()
1332 else:
1333 self.destroyDomain()
1335 # new_dom's VM will be the same as this domain's VM, except where
1336 # the rename flag has instructed us to call preserveForRestart.
1337 # In that case, it is important that we remove the
1338 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1339 # once the new one is available.
1341 new_dom = None
1342 try:
1343 xd = get_component('xen.xend.XendDomain')
1344 new_dom = xd.domain_create(config)
1345 new_dom.unpause()
1346 new_dom.removeVm(RESTART_IN_PROGRESS)
1347 except:
1348 if new_dom:
1349 new_dom.removeVm(RESTART_IN_PROGRESS)
1350 new_dom.destroy()
1351 else:
1352 self.removeVm(RESTART_IN_PROGRESS)
1353 raise
1354 except:
1355 log.exception('Failed to restart domain %d.', self.domid)
1358 def preserveForRestart(self):
1359 """Preserve a domain that has been shut down, by giving it a new UUID,
1360 cloning the VM details, and giving it a new name. This allows us to
1361 keep this domain for debugging, but restart a new one in its place
1362 preserving the restart semantics (name and UUID preserved).
1363 """
1365 new_name = self.generateUniqueName()
1366 new_uuid = uuid.toString(uuid.create())
1367 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1368 self.info['name'], self.domid, self.info['uuid'],
1369 new_name, new_uuid)
1370 self.release_devices()
1371 self.info['name'] = new_name
1372 self.info['uuid'] = new_uuid
1373 self.vmpath = VMROOT + new_uuid
1374 self.storeVmDetails()
1375 self.preserve()
1378 def preserve(self):
1379 log.info("Preserving dead domain %s (%d).", self.info['name'],
1380 self.domid)
1381 self.storeDom('xend/shutdown_completed', 'True')
1382 self.state_set(STATE_DOM_SHUTDOWN)
1385 # private:
1387 def generateUniqueName(self):
1388 n = 1
1389 while True:
1390 name = "%s-%d" % (self.info['name'], n)
1391 try:
1392 self.check_name(name)
1393 return name
1394 except VmError:
1395 n += 1
1398 def configure_bootloader(self):
1399 if not self.info['bootloader']:
1400 return
1401 # if we're restarting with a bootloader, we need to run it
1402 # FIXME: this assumes the disk is the first device and
1403 # that we're booting from the first disk
1404 blcfg = None
1405 config = self.sxpr()
1406 # FIXME: this assumes that we want to use the first disk
1407 dev = sxp.child_value(config, "device")
1408 if dev:
1409 disk = sxp.child_value(dev, "uname")
1410 fn = blkdev_uname_to_file(disk)
1411 blcfg = bootloader(self.info['bootloader'], fn, 1,
1412 self.info['vcpus'])
1413 if blcfg is None:
1414 msg = "Had a bootloader specified, but can't find disk"
1415 log.error(msg)
1416 raise VmError(msg)
1417 self.info['image'] = sxp.to_string(blcfg)
1420 def send_sysrq(self, key):
1421 asserts.isCharConvertible(key)
1423 self.storeDom("control/sysrq", '%c' % key)
1426 def infoIsSet(self, name):
1427 return name in self.info and self.info[name] is not None
1430 #============================================================================
1431 # Register device controllers and their device config types.
1433 """A map from device-class names to the subclass of DevController that
1434 implements the device control specific to that device-class."""
1435 controllerClasses = {}
1437 def addControllerClass(device_class, cls):
1438 """Register a subclass of DevController to handle the named device-class.
1439 """
1440 cls.deviceClass = device_class
1441 controllerClasses[device_class] = cls
1444 from xen.xend.server import blkif, netif, tpmif, pciif, iopif, usbif
1445 addControllerClass('vbd', blkif.BlkifController)
1446 addControllerClass('vif', netif.NetifController)
1447 addControllerClass('vtpm', tpmif.TPMifController)
1448 addControllerClass('pci', pciif.PciController)
1449 addControllerClass('ioports', iopif.IOPortsController)
1450 addControllerClass('usb', usbif.UsbifController)