direct-io.hg

view tools/python/xen/xend/XendDomainInfo.py @ 7932:c7a46ec8d4df

Remove last remaining use of scheduler.py, so we can remove the whole module.
It's not worth a whole module to save us 8 characters of typing to start a
timer.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author emellor@leeni.uk.xensource.com
date Mon Nov 21 13:12:09 2005 +0100 (2005-11-21)
parents 52a3c06be4f8
children 8fb11d28df0f
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 sxp
38 from xen.xend import XendRoot
39 from xen.xend.XendBootloader import bootloader
40 from xen.xend.XendError import XendError, VmError
41 from xen.xend.XendRoot import get_component
43 import uuid
45 from xen.xend.xenstore.xstransact import xstransact
46 from xen.xend.xenstore.xsutil import GetDomainPath, IntroduceDomain
47 from xen.xend.xenstore.xswatch import xswatch
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 SHUTDOWN_TIMEOUT = 30
86 VMROOT = '/vm/'
88 ZOMBIE_PREFIX = 'Zombie-'
90 """Minimum time between domain restarts in seconds."""
91 MINIMUM_RESTART_TIME = 20
93 RESTART_IN_PROGRESS = 'xend/restart_in_progress'
96 xc = xen.lowlevel.xc.new()
97 xroot = XendRoot.instance()
99 log = logging.getLogger("xend.XendDomainInfo")
100 #log.setLevel(logging.TRACE)
103 ##
104 # All parameters of VMs that may be configured on-the-fly, or at start-up.
105 #
106 VM_CONFIG_PARAMS = [
107 ('name', str),
108 ('on_poweroff', str),
109 ('on_reboot', str),
110 ('on_crash', str),
111 ]
114 ##
115 # Configuration entries that we expect to round-trip -- be read from the
116 # config file or xc, written to save-files (i.e. through sxpr), and reused as
117 # config on restart or restore, all without munging. Some configuration
118 # entries are munged for backwards compatibility reasons, or because they
119 # don't come out of xc in the same form as they are specified in the config
120 # file, so those are handled separately.
121 ROUNDTRIPPING_CONFIG_ENTRIES = [
122 ('uuid', str),
123 ('ssidref', int),
124 ('vcpus', int),
125 ('vcpu_avail', int),
126 ('cpu_weight', float),
127 ('memory', int),
128 ('maxmem', int),
129 ('bootloader', str),
130 ]
132 ROUNDTRIPPING_CONFIG_ENTRIES += VM_CONFIG_PARAMS
135 ##
136 # All entries written to the store. This is VM_CONFIGURATION_PARAMS, plus
137 # those entries written to the store that cannot be reconfigured on-the-fly.
138 #
139 VM_STORE_ENTRIES = [
140 ('uuid', str),
141 ('ssidref', int),
142 ('vcpus', int),
143 ('vcpu_avail', int),
144 ('memory', int),
145 ('maxmem', int),
146 ]
148 VM_STORE_ENTRIES += VM_CONFIG_PARAMS
151 #
152 # There are a number of CPU-related fields:
153 #
154 # vcpus: the number of virtual CPUs this domain is configured to use.
155 # vcpu_avail: a bitmap telling the guest domain whether it may use each of
156 # its VCPUs. This is translated to
157 # <dompath>/cpu/<id>/availability = {online,offline} for use
158 # by the guest domain.
159 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
160 # CPUs that that VCPU may use.
161 # cpu: a configuration setting requesting that VCPU 0 is pinned to
162 # the specified physical CPU.
163 #
164 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
165 # across save, restore, migrate, and restart). The other settings are only
166 # specific to the domain, so are lost when the VM moves.
167 #
170 def create(config):
171 """Create a VM from a configuration.
173 @param config configuration
174 @raise: VmError for invalid configuration
175 """
177 log.debug("XendDomainInfo.create(%s)", config)
179 vm = XendDomainInfo(parseConfig(config))
180 try:
181 vm.construct()
182 vm.initDomain()
183 vm.storeVmDetails()
184 vm.storeDomDetails()
185 vm.registerWatch()
186 vm.refreshShutdown()
187 return vm
188 except:
189 log.exception('Domain construction failed')
190 vm.destroy()
191 raise
194 def recreate(xeninfo, priv):
195 """Create the VM object for an existing domain. The domain must not
196 be dying, as the paths in the store should already have been removed,
197 and asking us to recreate them causes problems."""
199 log.debug("XendDomainInfo.recreate(%s)", xeninfo)
201 assert not xeninfo['dying']
203 domid = xeninfo['dom']
204 uuid1 = xeninfo['handle']
205 xeninfo['uuid'] = uuid.toString(uuid1)
206 dompath = GetDomainPath(domid)
207 if not dompath:
208 raise XendError(
209 'No domain path in store for existing domain %d' % domid)
211 log.info("Recreating domain %d, UUID %s.", domid, xeninfo['uuid'])
212 try:
213 vmpath = xstransact.Read(dompath, "vm")
214 if not vmpath:
215 raise XendError(
216 'No vm path in store for existing domain %d' % domid)
217 uuid2_str = xstransact.Read(vmpath, "uuid")
218 if not uuid2_str:
219 raise XendError(
220 'No vm/uuid path in store for existing domain %d' % domid)
222 uuid2 = uuid.fromString(uuid2_str)
224 if uuid1 != uuid2:
225 raise XendError(
226 'Uuid in store does not match uuid for existing domain %d: '
227 '%s != %s' % (domid, uuid2_str, xeninfo['uuid']))
229 vm = XendDomainInfo(xeninfo, domid, dompath, True)
231 except Exception, exn:
232 if priv:
233 log.warn(str(exn))
235 vm = XendDomainInfo(xeninfo, domid, dompath, True)
236 vm.removeDom()
237 vm.removeVm()
238 vm.storeVmDetails()
239 vm.storeDomDetails()
241 vm.registerWatch()
242 vm.refreshShutdown(xeninfo)
243 return vm
246 def restore(config):
247 """Create a domain and a VM object to do a restore.
249 @param config: domain configuration
250 """
252 log.debug("XendDomainInfo.restore(%s)", config)
254 vm = XendDomainInfo(parseConfig(config))
255 try:
256 vm.construct()
257 vm.storeVmDetails()
258 vm.createDevices()
259 vm.createChannels()
260 vm.storeDomDetails()
261 return vm
262 except:
263 vm.destroy()
264 raise
267 def parseConfig(config):
268 def get_cfg(name, conv = None):
269 val = sxp.child_value(config, name)
271 if conv and not val is None:
272 try:
273 return conv(val)
274 except TypeError, exn:
275 raise VmError(
276 'Invalid setting %s = %s in configuration: %s' %
277 (name, val, str(exn)))
278 else:
279 return val
282 log.debug("parseConfig: config is %s", config)
284 result = {}
286 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
287 result[e[0]] = get_cfg(e[0], e[1])
289 result['cpu'] = get_cfg('cpu', int)
290 result['image'] = get_cfg('image')
292 try:
293 if result['image']:
294 result['vcpus'] = int(sxp.child_value(result['image'],
295 'vcpus', 1))
296 else:
297 result['vcpus'] = 1
298 except TypeError, exn:
299 raise VmError(
300 'Invalid configuration setting: vcpus = %s: %s' %
301 (sxp.child_value(result['image'], 'vcpus', 1), str(exn)))
303 result['backend'] = []
304 for c in sxp.children(config, 'backend'):
305 result['backend'].append(sxp.name(sxp.child0(c)))
307 result['device'] = []
308 for d in sxp.children(config, 'device'):
309 c = sxp.child0(d)
310 result['device'].append((sxp.name(c), c))
312 # Configuration option "restart" is deprecated. Parse it, but
313 # let on_xyz override it if they are present.
314 restart = get_cfg('restart')
315 if restart:
316 def handle_restart(event, val):
317 if result[event] is None:
318 result[event] = val
320 if restart == "onreboot":
321 handle_restart('on_poweroff', 'destroy')
322 handle_restart('on_reboot', 'restart')
323 handle_restart('on_crash', 'destroy')
324 elif restart == "always":
325 handle_restart('on_poweroff', 'restart')
326 handle_restart('on_reboot', 'restart')
327 handle_restart('on_crash', 'restart')
328 elif restart == "never":
329 handle_restart('on_poweroff', 'destroy')
330 handle_restart('on_reboot', 'destroy')
331 handle_restart('on_crash', 'destroy')
332 else:
333 log.warn("Ignoring malformed and deprecated config option "
334 "restart = %s", restart)
336 log.debug("parseConfig: result is %s", result)
337 return result
340 def domain_by_name(name):
341 # See comment in XendDomain constructor.
342 xd = get_component('xen.xend.XendDomain')
343 return xd.domain_lookup_by_name_nr(name)
345 def shutdown_reason(code):
346 """Get a shutdown reason from a code.
348 @param code: shutdown code
349 @type code: int
350 @return: shutdown reason
351 @rtype: string
352 """
353 return shutdown_reasons.get(code, "?")
355 def dom_get(dom):
356 """Get info from xen for an existing domain.
358 @param dom: domain id
359 @return: info or None
360 """
361 try:
362 domlist = xc.domain_getinfo(dom, 1)
363 if domlist and dom == domlist[0]['dom']:
364 return domlist[0]
365 except Exception, err:
366 # ignore missing domain
367 log.trace("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
368 return None
371 class XendDomainInfo:
373 def __init__(self, info, domid = None, dompath = None, augment = False):
375 self.info = info
377 if not self.infoIsSet('uuid'):
378 self.info['uuid'] = uuid.toString(uuid.create())
380 if domid is not None:
381 self.domid = domid
382 elif 'dom' in info:
383 self.domid = int(info['dom'])
384 else:
385 self.domid = None
387 self.vmpath = VMROOT + self.info['uuid']
388 self.dompath = dompath
390 if augment:
391 self.augmentInfo()
393 self.validateInfo()
395 self.image = None
397 self.store_port = None
398 self.store_mfn = None
399 self.console_port = None
400 self.console_mfn = None
402 self.vmWatch = None
404 self.state = STATE_DOM_OK
405 self.state_updated = threading.Condition()
406 self.refresh_shutdown_lock = threading.Condition()
409 ## private:
411 def readVMDetails(self, params):
412 """Read from the store all of those entries that we consider
413 """
414 try:
415 return self.gatherVm(*params)
416 except ValueError:
417 # One of the int/float entries in params has a corresponding store
418 # entry that is invalid. We recover, because older versions of
419 # Xend may have put the entry there (memory/target, for example),
420 # but this is in general a bad situation to have reached.
421 log.exception(
422 "Store corrupted at %s! Domain %d's configuration may be "
423 "affected.", self.vmpath, self.domid)
424 return []
427 def storeChanged(self):
428 log.trace("XendDomainInfo.storeChanged");
430 changed = False
432 def f(x, y):
433 if y is not None and self.info[x[0]] != y:
434 self.info[x[0]] = y
435 changed = True
437 map(f, VM_CONFIG_PARAMS, self.readVMDetails(VM_CONFIG_PARAMS))
439 if changed:
440 # Update the domain section of the store, as this contains some
441 # parameters derived from the VM configuration.
442 self.storeDomDetails()
444 return 1
447 def augmentInfo(self):
448 """Augment self.info, as given to us through {@link #recreate}, with
449 values taken from the store. This recovers those values known to xend
450 but not to the hypervisor.
451 """
452 def useIfNeeded(name, val):
453 if not self.infoIsSet(name) and val is not None:
454 self.info[name] = val
456 map(lambda x, y: useIfNeeded(x[0], y), VM_STORE_ENTRIES,
457 self.readVMDetails(VM_STORE_ENTRIES))
459 device = []
460 for c in controllerClasses:
461 devconfig = self.getDeviceConfigurations(c)
462 if devconfig:
463 device.extend(map(lambda x: (c, x), devconfig))
464 useIfNeeded('device', device)
467 def validateInfo(self):
468 """Validate and normalise the info block. This has either been parsed
469 by parseConfig, or received from xc through recreate and augmented by
470 the current store contents.
471 """
472 def defaultInfo(name, val):
473 if not self.infoIsSet(name):
474 self.info[name] = val()
476 try:
477 defaultInfo('name', lambda: "Domain-%d" % self.domid)
478 defaultInfo('ssidref', lambda: 0)
479 defaultInfo('on_poweroff', lambda: "destroy")
480 defaultInfo('on_reboot', lambda: "restart")
481 defaultInfo('on_crash', lambda: "restart")
482 defaultInfo('cpu', lambda: None)
483 defaultInfo('cpu_weight', lambda: 1.0)
485 # some domains don't have a config file (e.g. dom0 )
486 # to set number of vcpus so we derive available cpus
487 # from max_vcpu_id which is present for running domains.
488 if not self.infoIsSet('vcpus') and self.infoIsSet('max_vcpu_id'):
489 avail = int(self.info['max_vcpu_id'])+1
490 else:
491 avail = int(1)
493 defaultInfo('vcpus', lambda: avail)
494 defaultInfo('online_vcpus', lambda: self.info['vcpus'])
495 defaultInfo('max_vcpu_id', lambda: self.info['vcpus']-1)
496 defaultInfo('vcpu_avail', lambda: (1 << self.info['vcpus']) - 1)
498 defaultInfo('memory', lambda: 0)
499 defaultInfo('maxmem', lambda: 0)
500 defaultInfo('bootloader', lambda: None)
501 defaultInfo('backend', lambda: [])
502 defaultInfo('device', lambda: [])
503 defaultInfo('image', lambda: None)
505 self.check_name(self.info['name'])
507 if isinstance(self.info['image'], str):
508 self.info['image'] = sxp.from_string(self.info['image'])
510 if self.info['memory'] == 0:
511 if self.infoIsSet('mem_kb'):
512 self.info['memory'] = (self.info['mem_kb'] + 1023) / 1024
514 if self.info['maxmem'] < self.info['memory']:
515 self.info['maxmem'] = self.info['memory']
517 for (n, c) in self.info['device']:
518 if not n or not c or n not in controllerClasses:
519 raise VmError('invalid device (%s, %s)' %
520 (str(n), str(c)))
522 for event in ['on_poweroff', 'on_reboot', 'on_crash']:
523 if self.info[event] not in restart_modes:
524 raise VmError('invalid restart event: %s = %s' %
525 (event, str(self.info[event])))
527 except KeyError, exn:
528 log.exception(exn)
529 raise VmError('Unspecified domain detail: %s' % exn)
532 def readVm(self, *args):
533 return xstransact.Read(self.vmpath, *args)
535 def writeVm(self, *args):
536 return xstransact.Write(self.vmpath, *args)
538 def removeVm(self, *args):
539 return xstransact.Remove(self.vmpath, *args)
541 def gatherVm(self, *args):
542 return xstransact.Gather(self.vmpath, *args)
545 ## public:
547 def storeVm(self, *args):
548 return xstransact.Store(self.vmpath, *args)
551 ## private:
553 def readDom(self, *args):
554 return xstransact.Read(self.dompath, *args)
556 def writeDom(self, *args):
557 return xstransact.Write(self.dompath, *args)
560 ## public:
562 def removeDom(self, *args):
563 return xstransact.Remove(self.dompath, *args)
566 ## private:
568 def storeDom(self, *args):
569 return xstransact.Store(self.dompath, *args)
572 ## public:
574 def completeRestore(self, store_mfn, console_mfn):
576 log.debug("XendDomainInfo.completeRestore")
578 self.store_mfn = store_mfn
579 self.console_mfn = console_mfn
581 self.introduceDomain()
582 self.storeDomDetails()
583 self.registerWatch()
584 self.refreshShutdown()
586 log.debug("XendDomainInfo.completeRestore done")
589 def storeVmDetails(self):
590 to_store = {}
592 for k in VM_STORE_ENTRIES:
593 if self.infoIsSet(k[0]):
594 to_store[k[0]] = str(self.info[k[0]])
596 if self.infoIsSet('image'):
597 to_store['image'] = sxp.to_string(self.info['image'])
599 if self.infoIsSet('start_time'):
600 to_store['start_time'] = str(self.info['start_time'])
602 log.debug("Storing VM details: %s", to_store)
604 self.writeVm(to_store)
607 def storeDomDetails(self):
608 to_store = {
609 'domid': str(self.domid),
610 'vm': self.vmpath,
611 'name': self.info['name'],
612 'console/limit': str(xroot.get_console_limit() * 1024),
613 'memory/target': str(self.info['memory'] * 1024)
614 }
616 def f(n, v):
617 if v is not None:
618 to_store[n] = str(v)
620 f('console/port', self.console_port)
621 f('console/ring-ref', self.console_mfn)
622 f('store/port', self.store_port)
623 f('store/ring-ref', self.store_mfn)
625 to_store.update(self.vcpuDomDetails())
627 log.debug("Storing domain details: %s", to_store)
629 self.writeDom(to_store)
632 ## private:
634 def vcpuDomDetails(self):
635 def availability(n):
636 if self.info['vcpu_avail'] & (1 << n):
637 return 'online'
638 else:
639 return 'offline'
641 result = {}
642 for v in range(0, self.info['vcpus']):
643 result["cpu/%d/availability" % v] = availability(v)
644 return result
647 ## public:
649 def registerWatch(self):
650 """Register a watch on this VM's entries in the store, so that
651 when they are changed externally, we keep up to date. This should
652 only be called by {@link #create}, {@link #recreate}, or {@link
653 #restore}, once the domain's details have been written, but before the
654 new instance is returned."""
655 self.vmWatch = xswatch(self.vmpath, self.storeChanged)
658 def getDomid(self):
659 return self.domid
661 def setName(self, name):
662 self.check_name(name)
663 self.info['name'] = name
664 self.storeVm("name", name)
666 def getName(self):
667 return self.info['name']
669 def getDomainPath(self):
670 return self.dompath
673 def getStorePort(self):
674 """For use only by image.py and XendCheckpoint.py."""
675 return self.store_port
678 def getConsolePort(self):
679 """For use only by image.py and XendCheckpoint.py"""
680 return self.console_port
683 def getVCpuCount(self):
684 return self.info['vcpus']
687 def setVCpuCount(self, vcpus):
688 self.info['vcpu_avail'] = (1 << vcpus) - 1
689 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
690 self.writeDom(self.vcpuDomDetails())
693 def getSsidref(self):
694 return self.info['ssidref']
696 def getMemoryTarget(self):
697 """Get this domain's target memory size, in KB."""
698 return self.info['memory'] * 1024
701 def refreshShutdown(self, xeninfo = None):
702 # If set at the end of this method, a restart is required, with the
703 # given reason. This restart has to be done out of the scope of
704 # refresh_shutdown_lock.
705 restart_reason = None
707 self.refresh_shutdown_lock.acquire()
708 try:
709 if xeninfo is None:
710 xeninfo = dom_get(self.domid)
711 if xeninfo is None:
712 # The domain no longer exists. This will occur if we have
713 # scheduled a timer to check for shutdown timeouts and the
714 # shutdown succeeded. It will also occur if someone
715 # destroys a domain beneath us. We clean up the domain,
716 # just in case, but we can't clean up the VM, because that
717 # VM may have migrated to a different domain on this
718 # machine.
719 self.cleanupDomain()
720 return
722 if xeninfo['dying']:
723 # Dying means that a domain has been destroyed, but has not
724 # yet been cleaned up by Xen. This state could persist
725 # indefinitely if, for example, another domain has some of its
726 # pages mapped. We might like to diagnose this problem in the
727 # future, but for now all we do is make sure that it's not us
728 # holding the pages, by calling cleanupDomain. We can't
729 # clean up the VM, as above.
730 self.cleanupDomain()
731 return
733 elif xeninfo['crashed']:
734 if self.readDom('xend/shutdown_completed'):
735 # We've seen this shutdown already, but we are preserving
736 # the domain for debugging. Leave it alone.
737 return
739 log.warn('Domain has crashed: name=%s id=%d.',
740 self.info['name'], self.domid)
742 if xroot.get_enable_dump():
743 self.dumpCore()
745 restart_reason = 'crash'
747 elif xeninfo['shutdown']:
748 if self.readDom('xend/shutdown_completed'):
749 # We've seen this shutdown already, but we are preserving
750 # the domain for debugging. Leave it alone.
751 return
753 else:
754 reason = shutdown_reason(xeninfo['shutdown_reason'])
756 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
757 self.info['name'], self.domid, reason)
759 self.clearRestart()
761 if reason == 'suspend':
762 self.state_set(STATE_DOM_SHUTDOWN)
763 # Don't destroy the domain. XendCheckpoint will do
764 # this once it has finished.
765 elif reason in ['poweroff', 'reboot']:
766 restart_reason = reason
767 else:
768 self.destroy()
770 elif self.dompath is None:
771 # We have yet to manage to call introduceDomain on this
772 # domain. This can happen if a restore is in progress, or has
773 # failed. Ignore this domain.
774 pass
775 else:
776 # Domain is alive. If we are shutting it down, then check
777 # the timeout on that, and destroy it if necessary.
779 sst = self.readDom('xend/shutdown_start_time')
780 if sst:
781 sst = float(sst)
782 timeout = SHUTDOWN_TIMEOUT - time.time() + sst
783 if timeout < 0:
784 log.info(
785 "Domain shutdown timeout expired: name=%s id=%s",
786 self.info['name'], self.domid)
787 self.destroy()
788 else:
789 log.debug(
790 "Scheduling refreshShutdown on domain %d in %ds.",
791 self.domid, timeout)
792 scheduler.later(timeout, self.refreshShutdown)
793 finally:
794 self.refresh_shutdown_lock.release()
796 if restart_reason:
797 self.maybeRestart(restart_reason)
800 def shutdown(self, reason):
801 if not reason in shutdown_reasons.values():
802 raise XendError('Invalid reason: %s' % reason)
803 self.storeDom("control/shutdown", reason)
804 if reason != 'suspend':
805 self.storeDom('xend/shutdown_start_time', time.time())
808 ## private:
810 def clearRestart(self):
811 self.removeDom("xend/shutdown_start_time")
814 def maybeRestart(self, reason):
815 # Dispatch to the correct method based upon the configured on_{reason}
816 # behaviour.
817 {"destroy" : self.destroy,
818 "restart" : self.restart,
819 "preserve" : self.preserve,
820 "rename-restart" : self.renameRestart}[self.info['on_' + reason]]()
823 def renameRestart(self):
824 self.restart(True)
827 def dumpCore(self):
828 """Create a core dump for this domain. Nothrow guarantee."""
830 try:
831 corefile = "/var/xen/dump/%s.%s.core" % (self.info['name'],
832 self.domid)
833 xc.domain_dumpcore(dom = self.domid, corefile = corefile)
835 except:
836 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
837 self.domid, self.info['name'])
840 ## public:
842 def setMemoryTarget(self, target):
843 """Set the memory target of this domain.
844 @param target In MiB.
845 """
846 self.info['memory'] = target
847 self.storeVm("memory", target)
848 self.storeDom("memory/target", target << 10)
851 def update(self, info = None):
852 """Update with info from xc.domain_getinfo().
853 """
855 log.trace("XendDomainInfo.update(%s) on domain %d", info, self.domid)
857 if not info:
858 info = dom_get(self.domid)
859 if not info:
860 return
862 self.info.update(info)
863 self.validateInfo()
864 self.refreshShutdown(info)
866 log.trace("XendDomainInfo.update done on domain %d: %s", self.domid,
867 self.info)
870 ## private:
872 def state_set(self, state):
873 self.state_updated.acquire()
874 try:
875 if self.state != state:
876 self.state = state
877 self.state_updated.notifyAll()
878 finally:
879 self.state_updated.release()
882 ## public:
884 def waitForShutdown(self):
885 self.state_updated.acquire()
886 try:
887 while self.state == STATE_DOM_OK:
888 self.state_updated.wait()
889 finally:
890 self.state_updated.release()
893 def __str__(self):
894 s = "<domain"
895 s += " id=" + str(self.domid)
896 s += " name=" + self.info['name']
897 s += " memory=" + str(self.info['memory'])
898 s += " ssidref=" + str(self.info['ssidref'])
899 s += ">"
900 return s
902 __repr__ = __str__
905 ## private:
907 def createDevice(self, deviceClass, devconfig):
908 return self.getDeviceController(deviceClass).createDevice(devconfig)
911 def waitForDevices_(self, deviceClass):
912 return self.getDeviceController(deviceClass).waitForDevices()
915 def waitForDevice(self, deviceClass, devid):
916 return self.getDeviceController(deviceClass).waitForDevice(devid)
919 def reconfigureDevice(self, deviceClass, devid, devconfig):
920 return self.getDeviceController(deviceClass).reconfigureDevice(
921 devid, devconfig)
924 ## public:
926 def destroyDevice(self, deviceClass, devid):
927 return self.getDeviceController(deviceClass).destroyDevice(devid)
930 def getDeviceSxprs(self, deviceClass):
931 return self.getDeviceController(deviceClass).sxprs()
934 ## private:
936 def getDeviceConfigurations(self, deviceClass):
937 return self.getDeviceController(deviceClass).configurations()
940 def getDeviceController(self, name):
941 if name not in controllerClasses:
942 raise XendError("unknown device type: " + str(name))
944 return controllerClasses[name](self)
947 ## public:
949 def sxpr(self):
950 sxpr = ['domain',
951 ['domid', self.domid]]
953 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
954 if self.infoIsSet(e[0]):
955 sxpr.append([e[0], self.info[e[0]]])
957 if self.infoIsSet('image'):
958 sxpr.append(['image', self.info['image']])
960 if self.infoIsSet('device'):
961 for (_, c) in self.info['device']:
962 sxpr.append(['device', c])
964 def stateChar(name):
965 if name in self.info:
966 if self.info[name]:
967 return name[0]
968 else:
969 return '-'
970 else:
971 return '?'
973 state = reduce(
974 lambda x, y: x + y,
975 map(stateChar,
976 ['running', 'blocked', 'paused', 'shutdown', 'crashed',
977 'dying']))
979 sxpr.append(['state', state])
980 if self.infoIsSet('shutdown'):
981 reason = shutdown_reason(self.info['shutdown_reason'])
982 sxpr.append(['shutdown_reason', reason])
983 if self.infoIsSet('cpu_time'):
984 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
985 sxpr.append(['vcpus', self.info['vcpus']])
986 sxpr.append(['online_vcpus', self.info['online_vcpus']])
988 if self.infoIsSet('start_time'):
989 up_time = time.time() - self.info['start_time']
990 sxpr.append(['up_time', str(up_time) ])
991 sxpr.append(['start_time', str(self.info['start_time']) ])
993 if self.store_mfn:
994 sxpr.append(['store_mfn', self.store_mfn])
995 if self.console_mfn:
996 sxpr.append(['console_mfn', self.console_mfn])
998 return sxpr
1001 def getVCPUInfo(self):
1002 try:
1003 # We include the domain name and ID, to help xm.
1004 sxpr = ['domain',
1005 ['domid', self.domid],
1006 ['name', self.info['name']],
1007 ['vcpu_count', self.info['online_vcpus']]]
1009 for i in range(0, self.info['max_vcpu_id']+1):
1010 info = xc.vcpu_getinfo(self.domid, i)
1012 sxpr.append(['vcpu',
1013 ['number', i],
1014 ['online', info['online']],
1015 ['blocked', info['blocked']],
1016 ['running', info['running']],
1017 ['cpu_time', info['cpu_time'] / 1e9],
1018 ['cpu', info['cpu']],
1019 ['cpumap', info['cpumap']]])
1021 return sxpr
1023 except RuntimeError, exn:
1024 raise XendError(str(exn))
1027 ## private:
1029 def check_name(self, name):
1030 """Check if a vm name is valid. Valid names contain alphabetic characters,
1031 digits, or characters in '_-.:/+'.
1032 The same name cannot be used for more than one vm at the same time.
1034 @param name: name
1035 @raise: VmError if invalid
1036 """
1037 if name is None or name == '':
1038 raise VmError('missing vm name')
1039 for c in name:
1040 if c in string.digits: continue
1041 if c in '_-.:/+': continue
1042 if c in string.ascii_letters: continue
1043 raise VmError('invalid vm name')
1045 dominfo = domain_by_name(name)
1046 if not dominfo:
1047 return
1048 if self.domid is None:
1049 raise VmError("VM name '%s' already in use by domain %d" %
1050 (name, dominfo.domid))
1051 if dominfo.domid != self.domid:
1052 raise VmError("VM name '%s' is used in both domains %d and %d" %
1053 (name, self.domid, dominfo.domid))
1056 def construct(self):
1057 """Construct the domain.
1059 @raise: VmError on error
1060 """
1062 log.debug('XendDomainInfo.construct: %s %s',
1063 self.domid,
1064 self.info['ssidref'])
1066 self.domid = xc.domain_create(
1067 dom = 0, ssidref = self.info['ssidref'],
1068 handle = uuid.fromString(self.info['uuid']))
1070 if self.domid < 0:
1071 raise VmError('Creating domain failed: name=%s' %
1072 self.info['name'])
1074 self.dompath = GetDomainPath(self.domid)
1076 self.removeDom()
1078 # Set maximum number of vcpus in domain
1079 xc.domain_max_vcpus(self.domid, int(self.info['vcpus']))
1082 def introduceDomain(self):
1083 assert self.domid is not None
1084 assert self.store_mfn is not None
1085 assert self.store_port is not None
1087 try:
1088 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1089 except RuntimeError, exn:
1090 raise XendError(str(exn))
1093 def initDomain(self):
1094 log.debug('XendDomainInfo.initDomain: %s %s',
1095 self.domid,
1096 self.info['cpu_weight'])
1098 if not self.infoIsSet('image'):
1099 raise VmError('Missing image in configuration')
1101 self.image = image.create(self,
1102 self.info['image'],
1103 self.info['device'])
1105 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1107 m = self.image.getDomainMemory(self.info['memory'] * 1024)
1108 xc.domain_setmaxmem(self.domid, maxmem_kb = m)
1109 xc.domain_memory_increase_reservation(self.domid, m, 0, 0)
1111 cpu = self.info['cpu']
1112 if cpu is not None and cpu != -1:
1113 xc.domain_pincpu(self.domid, 0, 1 << cpu)
1115 self.createChannels()
1117 channel_details = self.image.createImage()
1119 self.store_mfn = channel_details['store_mfn']
1120 if 'console_mfn' in channel_details:
1121 self.console_mfn = channel_details['console_mfn']
1123 self.introduceDomain()
1125 self.createDevices()
1127 if self.info['bootloader']:
1128 self.image.cleanupBootloading()
1130 self.info['start_time'] = time.time()
1133 ## public:
1135 def cleanupDomain(self):
1136 """Cleanup domain resources; release devices. Idempotent. Nothrow
1137 guarantee."""
1139 self.release_devices()
1141 if self.image:
1142 try:
1143 self.image.destroy()
1144 except:
1145 log.exception(
1146 "XendDomainInfo.cleanup: image.destroy() failed.")
1147 self.image = None
1149 try:
1150 self.removeDom()
1151 except:
1152 log.exception("Removing domain path failed.")
1154 try:
1155 if not self.info['name'].startswith(ZOMBIE_PREFIX):
1156 self.info['name'] = ZOMBIE_PREFIX + self.info['name']
1157 except:
1158 log.exception("Renaming Zombie failed.")
1160 self.state_set(STATE_DOM_SHUTDOWN)
1163 def cleanupVm(self):
1164 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1166 try:
1167 try:
1168 if self.vmWatch:
1169 self.vmWatch.unwatch()
1170 self.vmWatch = None
1171 except:
1172 log.exception("Unwatching VM path failed.")
1174 self.removeVm()
1175 except:
1176 log.exception("Removing VM path failed.")
1179 def destroy(self):
1180 """Cleanup VM and destroy domain. Nothrow guarantee."""
1182 log.debug("XendDomainInfo.destroy: domid=%s", self.domid)
1184 self.cleanupVm()
1185 if self.dompath is not None:
1186 self.destroyDomain()
1189 def destroyDomain(self):
1190 log.debug("XendDomainInfo.destroyDomain(%s)", self.domid)
1192 try:
1193 if self.domid is not None:
1194 xc.domain_destroy(dom=self.domid)
1195 except:
1196 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1198 self.cleanupDomain()
1201 ## private:
1203 def release_devices(self):
1204 """Release all domain's devices. Nothrow guarantee."""
1206 while True:
1207 t = xstransact("%s/device" % self.dompath)
1208 for n in controllerClasses.keys():
1209 for d in t.list(n):
1210 try:
1211 t.remove(d)
1212 except:
1213 # Log and swallow any exceptions in removal --
1214 # there's nothing more we can do.
1215 log.exception(
1216 "Device release failed: %s; %s; %s",
1217 self.info['name'], n, d)
1218 if t.commit():
1219 break
1222 def createChannels(self):
1223 """Create the channels to the domain.
1224 """
1225 self.store_port = self.createChannel()
1226 self.console_port = self.createChannel()
1229 def createChannel(self):
1230 """Create an event channel to the domain.
1231 """
1232 try:
1233 return xc.evtchn_alloc_unbound(dom=self.domid, remote_dom=0)
1234 except:
1235 log.exception("Exception in alloc_unbound(%d)", self.domid)
1236 raise
1239 ## public:
1241 def createDevices(self):
1242 """Create the devices for a vm.
1244 @raise: VmError for invalid devices
1245 """
1247 for (n, c) in self.info['device']:
1248 self.createDevice(n, c)
1250 if self.image:
1251 self.image.createDeviceModel()
1254 def waitForDevices(self):
1255 """Wait for this domain's configured devices to connect.
1257 @raise: VmError if any device fails to initialise.
1258 """
1259 for c in controllerClasses:
1260 self.waitForDevices_(c)
1263 def device_create(self, dev_config):
1264 """Create a new device.
1266 @param dev_config: device configuration
1267 """
1268 dev_type = sxp.name(dev_config)
1269 devid = self.createDevice(dev_type, dev_config)
1270 self.waitForDevice(dev_type, devid)
1271 # self.config.append(['device', dev.getConfig()])
1272 return self.getDeviceController(dev_type).sxpr(devid)
1275 def device_configure(self, dev_config, devid):
1276 """Configure an existing device.
1277 @param dev_config: device configuration
1278 @param devid: device id
1279 """
1280 deviceClass = sxp.name(dev_config)
1281 self.reconfigureDevice(deviceClass, devid, dev_config)
1284 def pause(self):
1285 xc.domain_pause(self.domid)
1288 def unpause(self):
1289 xc.domain_unpause(self.domid)
1292 ## private:
1294 def restart(self, rename = False):
1295 """Restart the domain after it has exited.
1297 @param rename True if the old domain is to be renamed and preserved,
1298 False if it is to be destroyed.
1299 """
1301 self.configure_bootloader()
1302 config = self.sxpr()
1304 if self.readVm(RESTART_IN_PROGRESS):
1305 log.error('Xend failed during restart of domain %d. '
1306 'Refusing to restart to avoid loops.',
1307 self.domid)
1308 self.destroy()
1309 return
1311 self.writeVm(RESTART_IN_PROGRESS, 'True')
1313 now = time.time()
1314 rst = self.readVm('xend/previous_restart_time')
1315 if rst:
1316 rst = float(rst)
1317 timeout = now - rst
1318 if timeout < MINIMUM_RESTART_TIME:
1319 log.error(
1320 'VM %s restarting too fast (%f seconds since the last '
1321 'restart). Refusing to restart to avoid loops.',
1322 self.info['name'], timeout)
1323 self.destroy()
1324 return
1326 self.writeVm('xend/previous_restart_time', str(now))
1328 try:
1329 if rename:
1330 self.preserveForRestart()
1331 else:
1332 self.destroyDomain()
1334 # new_dom's VM will be the same as this domain's VM, except where
1335 # the rename flag has instructed us to call preserveForRestart.
1336 # In that case, it is important that we remove the
1337 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1338 # once the new one is available.
1340 new_dom = None
1341 try:
1342 xd = get_component('xen.xend.XendDomain')
1343 new_dom = xd.domain_create(config)
1344 new_dom.unpause()
1345 new_dom.removeVm(RESTART_IN_PROGRESS)
1346 except:
1347 if new_dom:
1348 new_dom.removeVm(RESTART_IN_PROGRESS)
1349 new_dom.destroy()
1350 else:
1351 self.removeVm(RESTART_IN_PROGRESS)
1352 raise
1353 except:
1354 log.exception('Failed to restart domain %d.', self.domid)
1357 def preserveForRestart(self):
1358 """Preserve a domain that has been shut down, by giving it a new UUID,
1359 cloning the VM details, and giving it a new name. This allows us to
1360 keep this domain for debugging, but restart a new one in its place
1361 preserving the restart semantics (name and UUID preserved).
1362 """
1364 new_name = self.generateUniqueName()
1365 new_uuid = uuid.toString(uuid.create())
1366 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1367 self.info['name'], self.domid, self.info['uuid'],
1368 new_name, new_uuid)
1369 self.release_devices()
1370 self.info['name'] = new_name
1371 self.info['uuid'] = new_uuid
1372 self.vmpath = VMROOT + new_uuid
1373 self.storeVmDetails()
1374 self.preserve()
1377 def preserve(self):
1378 log.info("Preserving dead domain %s (%d).", self.info['name'],
1379 self.domid)
1380 self.storeDom('xend/shutdown_completed', 'True')
1381 self.state_set(STATE_DOM_SHUTDOWN)
1384 # private:
1386 def generateUniqueName(self):
1387 n = 1
1388 while True:
1389 name = "%s-%d" % (self.info['name'], n)
1390 try:
1391 self.check_name(name)
1392 return name
1393 except VmError:
1394 n += 1
1397 def configure_bootloader(self):
1398 if not self.info['bootloader']:
1399 return
1400 # if we're restarting with a bootloader, we need to run it
1401 # FIXME: this assumes the disk is the first device and
1402 # that we're booting from the first disk
1403 blcfg = None
1404 config = self.sxpr()
1405 # FIXME: this assumes that we want to use the first disk
1406 dev = sxp.child_value(config, "device")
1407 if dev:
1408 disk = sxp.child_value(dev, "uname")
1409 fn = blkdev_uname_to_file(disk)
1410 blcfg = bootloader(self.info['bootloader'], fn, 1,
1411 self.info['vcpus'])
1412 if blcfg is None:
1413 msg = "Had a bootloader specified, but can't find disk"
1414 log.error(msg)
1415 raise VmError(msg)
1416 self.info['image'] = sxp.to_string(blcfg)
1419 def send_sysrq(self, key):
1420 asserts.isCharConvertible(key)
1422 self.storeDom("control/sysrq", '%c' % key)
1425 def infoIsSet(self, name):
1426 return name in self.info and self.info[name] is not None
1429 #============================================================================
1430 # Register device controllers and their device config types.
1432 """A map from device-class names to the subclass of DevController that
1433 implements the device control specific to that device-class."""
1434 controllerClasses = {}
1436 def addControllerClass(device_class, cls):
1437 """Register a subclass of DevController to handle the named device-class.
1438 """
1439 cls.deviceClass = device_class
1440 controllerClasses[device_class] = cls
1443 from xen.xend.server import blkif, netif, tpmif, pciif, iopif, usbif
1444 addControllerClass('vbd', blkif.BlkifController)
1445 addControllerClass('vif', netif.NetifController)
1446 addControllerClass('vtpm', tpmif.TPMifController)
1447 addControllerClass('pci', pciif.PciController)
1448 addControllerClass('ioports', iopif.IOPortsController)
1449 addControllerClass('usb', usbif.UsbifController)