direct-io.hg

view tools/python/xen/xend/XendDomainInfo.py @ 7713:136b2d20dc81

Cope with the ValueError exception that we get if you use Xend with the recent
change to parsing memory configuration against a store with entries written
by an older Xend.

Added maxmem field to list of things to be read from the store on recreate.
author emellor@leeni.uk.xensource.com
date Wed Nov 09 16:08:37 2005 +0100 (2005-11-09)
parents 5066d2aa2fb0
children bffbe58801d0
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
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 ## Configuration entries that we expect to round-trip -- be read from the
104 # config file or xc, written to save-files (i.e. through sxpr), and reused as
105 # config on restart or restore, all without munging. Some configuration
106 # entries are munged for backwards compatibility reasons, or because they
107 # don't come out of xc in the same form as they are specified in the config
108 # file, so those are handled separately.
109 ROUNDTRIPPING_CONFIG_ENTRIES = [
110 ('name', str),
111 ('uuid', str),
112 ('ssidref', int),
113 ('vcpus', int),
114 ('vcpu_avail', int),
115 ('cpu_weight', float),
116 ('memory', int),
117 ('maxmem', int),
118 ('bootloader', str),
119 ('on_poweroff', str),
120 ('on_reboot', str),
121 ('on_crash', str)
122 ]
125 #
126 # There are a number of CPU-related fields:
127 #
128 # vcpus: the number of virtual CPUs this domain is configured to use.
129 # vcpu_avail: a bitmap telling the guest domain whether it may use each of
130 # its VCPUs. This is translated to
131 # <dompath>/cpu/<id>/availability = {online,offline} for use
132 # by the guest domain.
133 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
134 # CPUs that that VCPU may use.
135 # cpu: a configuration setting requesting that VCPU 0 is pinned to
136 # the specified physical CPU.
137 #
138 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
139 # across save, restore, migrate, and restart). The other settings are only
140 # specific to the domain, so are lost when the VM moves.
141 #
144 def create(config):
145 """Create a VM from a configuration.
147 @param config configuration
148 @raise: VmError for invalid configuration
149 """
151 log.debug("XendDomainInfo.create(%s)", config)
153 vm = XendDomainInfo(parseConfig(config))
154 try:
155 vm.construct()
156 vm.initDomain()
157 vm.storeVmDetails()
158 vm.storeDomDetails()
159 vm.refreshShutdown()
160 return vm
161 except:
162 log.exception('Domain construction failed')
163 vm.destroy()
164 raise
167 def recreate(xeninfo, priv):
168 """Create the VM object for an existing domain. The domain must not
169 be dying, as the paths in the store should already have been removed,
170 and asking us to recreate them causes problems."""
172 log.debug("XendDomainInfo.recreate(%s)", xeninfo)
174 assert not xeninfo['dying']
176 domid = xeninfo['dom']
177 uuid1 = xeninfo['handle']
178 xeninfo['uuid'] = uuid.toString(uuid1)
179 dompath = GetDomainPath(domid)
180 if not dompath:
181 raise XendError(
182 'No domain path in store for existing domain %d' % domid)
184 log.info("Recreating domain %d, UUID %s.", domid, xeninfo['uuid'])
185 try:
186 vmpath = xstransact.Read(dompath, "vm")
187 if not vmpath:
188 raise XendError(
189 'No vm path in store for existing domain %d' % domid)
190 uuid2_str = xstransact.Read(vmpath, "uuid")
191 if not uuid2_str:
192 raise XendError(
193 'No vm/uuid path in store for existing domain %d' % domid)
195 uuid2 = uuid.fromString(uuid2_str)
197 if uuid1 != uuid2:
198 raise XendError(
199 'Uuid in store does not match uuid for existing domain %d: '
200 '%s != %s' % (domid, uuid2_str, xeninfo['uuid']))
202 vm = XendDomainInfo(xeninfo, domid, dompath, True)
204 except Exception, exn:
205 if priv:
206 log.warn(str(exn))
208 vm = XendDomainInfo(xeninfo, domid, dompath, True)
209 vm.removeDom()
210 vm.removeVm()
211 vm.storeVmDetails()
212 vm.storeDomDetails()
214 vm.refreshShutdown(xeninfo)
215 return vm
218 def restore(config):
219 """Create a domain and a VM object to do a restore.
221 @param config: domain configuration
222 """
224 log.debug("XendDomainInfo.restore(%s)", config)
226 vm = XendDomainInfo(parseConfig(config))
227 try:
228 vm.construct()
229 vm.storeVmDetails()
230 vm.createDevices()
231 vm.createChannels()
232 vm.storeDomDetails()
233 return vm
234 except:
235 vm.destroy()
236 raise
239 def parseConfig(config):
240 def get_cfg(name, conv = None):
241 val = sxp.child_value(config, name)
243 if conv and not val is None:
244 try:
245 return conv(val)
246 except TypeError, exn:
247 raise VmError(
248 'Invalid setting %s = %s in configuration: %s' %
249 (name, val, str(exn)))
250 else:
251 return val
254 log.debug("parseConfig: config is %s", config)
256 result = {}
258 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
259 result[e[0]] = get_cfg(e[0], e[1])
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.trace("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 ("memory", int),
396 ("maxmem", int),
397 ("vcpus", int),
398 ("vcpu_avail", int),
399 ("start_time", float))
401 try:
402 from_store = self.gatherVm(*params)
403 except ValueError, exn:
404 # One of the int/float entries in params has a corresponding store
405 # entry that is invalid. We recover, because older versions of
406 # Xend may have put the entry there (memory/target, for example),
407 # but this is in general a bad situation to have reached.
408 log.exception(
409 "Store corrupted at %s! Domain %d's configuration may be "
410 "affected.", self.vmpath, self.domid)
411 return
413 map(lambda x, y: useIfNeeded(x[0], y), params, from_store)
415 device = []
416 for c in controllerClasses:
417 devconfig = self.getDeviceConfigurations(c)
418 if devconfig:
419 device.extend(map(lambda x: (c, x), devconfig))
420 useIfNeeded('device', device)
423 def validateInfo(self):
424 """Validate and normalise the info block. This has either been parsed
425 by parseConfig, or received from xc through recreate and augmented by
426 the current store contents.
427 """
428 def defaultInfo(name, val):
429 if not self.infoIsSet(name):
430 self.info[name] = val()
432 try:
433 defaultInfo('name', lambda: "Domain-%d" % self.domid)
434 defaultInfo('ssidref', lambda: 0)
435 defaultInfo('on_poweroff', lambda: "destroy")
436 defaultInfo('on_reboot', lambda: "restart")
437 defaultInfo('on_crash', lambda: "restart")
438 defaultInfo('cpu', lambda: None)
439 defaultInfo('cpu_weight', lambda: 1.0)
440 defaultInfo('vcpus', lambda: int(1))
442 self.info['vcpus'] = int(self.info['vcpus'])
444 defaultInfo('vcpu_avail', lambda: (1 << self.info['vcpus']) - 1)
446 defaultInfo('memory', lambda: 0)
447 defaultInfo('maxmem', lambda: 0)
448 defaultInfo('bootloader', lambda: None)
449 defaultInfo('backend', lambda: [])
450 defaultInfo('device', lambda: [])
451 defaultInfo('image', lambda: None)
453 self.check_name(self.info['name'])
455 if isinstance(self.info['image'], str):
456 self.info['image'] = sxp.from_string(self.info['image'])
458 if self.info['memory'] == 0:
459 if self.infoIsSet('mem_kb'):
460 self.info['memory'] = (self.info['mem_kb'] + 1023) / 1024
462 if self.info['maxmem'] < self.info['memory']:
463 self.info['maxmem'] = self.info['memory']
465 for (n, c) in self.info['device']:
466 if not n or not c or n not in controllerClasses:
467 raise VmError('invalid device (%s, %s)' %
468 (str(n), str(c)))
470 for event in ['on_poweroff', 'on_reboot', 'on_crash']:
471 if self.info[event] not in restart_modes:
472 raise VmError('invalid restart event: %s = %s' %
473 (event, str(self.info[event])))
475 except KeyError, exn:
476 log.exception(exn)
477 raise VmError('Unspecified domain detail: %s' % exn)
480 def readVm(self, *args):
481 return xstransact.Read(self.vmpath, *args)
483 def writeVm(self, *args):
484 return xstransact.Write(self.vmpath, *args)
486 def removeVm(self, *args):
487 return xstransact.Remove(self.vmpath, *args)
489 def gatherVm(self, *args):
490 return xstransact.Gather(self.vmpath, *args)
493 ## public:
495 def storeVm(self, *args):
496 return xstransact.Store(self.vmpath, *args)
499 ## private:
501 def readDom(self, *args):
502 return xstransact.Read(self.dompath, *args)
504 def writeDom(self, *args):
505 return xstransact.Write(self.dompath, *args)
508 ## public:
510 def removeDom(self, *args):
511 return xstransact.Remove(self.dompath, *args)
514 ## private:
516 def storeDom(self, *args):
517 return xstransact.Store(self.dompath, *args)
520 ## public:
522 def completeRestore(self, store_mfn, console_mfn):
524 log.debug("XendDomainInfo.completeRestore")
526 self.store_mfn = store_mfn
527 self.console_mfn = console_mfn
529 self.introduceDomain()
530 self.storeDomDetails()
531 self.refreshShutdown()
533 log.debug("XendDomainInfo.completeRestore done")
536 def storeVmDetails(self):
537 to_store = {
538 'uuid': self.info['uuid']
539 }
541 if self.infoIsSet('image'):
542 to_store['image'] = sxp.to_string(self.info['image'])
544 for k in ['name', 'ssidref', 'memory', 'maxmem', 'on_poweroff',
545 'on_reboot', 'on_crash', 'vcpus', 'vcpu_avail']:
546 if self.infoIsSet(k):
547 to_store[k] = str(self.info[k])
549 log.debug("Storing VM details: %s", to_store)
551 self.writeVm(to_store)
554 def storeDomDetails(self):
555 to_store = {
556 'domid': str(self.domid),
557 'vm': self.vmpath,
558 'name': self.info['name'],
559 'console/limit': str(xroot.get_console_limit() * 1024),
560 'memory/target': str(self.info['memory'] * 1024)
561 }
563 def f(n, v):
564 if v is not None:
565 to_store[n] = str(v)
567 f('console/port', self.console_port)
568 f('console/ring-ref', self.console_mfn)
569 f('store/port', self.store_port)
570 f('store/ring-ref', self.store_mfn)
572 to_store.update(self.vcpuDomDetails())
574 log.debug("Storing domain details: %s", to_store)
576 self.writeDom(to_store)
579 ## private:
581 def vcpuDomDetails(self):
582 def availability(n):
583 if self.info['vcpu_avail'] & (1 << n):
584 return 'online'
585 else:
586 return 'offline'
588 result = {}
589 for v in range(0, self.info['vcpus']):
590 result["cpu/%d/availability" % v] = availability(v)
591 return result
594 def setDomid(self, domid):
595 """Set the domain id.
597 @param dom: domain id
598 """
599 self.domid = domid
600 self.storeDom("domid", self.domid)
602 def getDomid(self):
603 return self.domid
605 def setName(self, name):
606 self.check_name(name)
607 self.info['name'] = name
608 self.storeVm("name", name)
610 def getName(self):
611 return self.info['name']
613 def getDomainPath(self):
614 return self.dompath
617 def getStorePort(self):
618 """For use only by image.py and XendCheckpoint.py."""
619 return self.store_port
622 def getConsolePort(self):
623 """For use only by image.py and XendCheckpoint.py"""
624 return self.console_port
627 def getVCpuCount(self):
628 return self.info['vcpus']
631 def setVCpuCount(self, vcpus):
632 self.info['vcpu_avail'] = (1 << vcpus) - 1
633 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
634 self.writeDom(self.vcpuDomDetails())
637 def getSsidref(self):
638 return self.info['ssidref']
640 def getMemoryTarget(self):
641 """Get this domain's target memory size, in KB."""
642 return self.info['memory'] * 1024
645 def refreshShutdown(self, xeninfo = None):
646 # If set at the end of this method, a restart is required, with the
647 # given reason. This restart has to be done out of the scope of
648 # refresh_shutdown_lock.
649 restart_reason = None
651 self.refresh_shutdown_lock.acquire()
652 try:
653 if xeninfo is None:
654 xeninfo = dom_get(self.domid)
655 if xeninfo is None:
656 # The domain no longer exists. This will occur if we have
657 # scheduled a timer to check for shutdown timeouts and the
658 # shutdown succeeded. It will also occur if someone
659 # destroys a domain beneath us. We clean up the domain,
660 # just in case, but we can't clean up the VM, because that
661 # VM may have migrated to a different domain on this
662 # machine.
663 self.cleanupDomain()
664 return
666 if xeninfo['dying']:
667 # Dying means that a domain has been destroyed, but has not
668 # yet been cleaned up by Xen. This state could persist
669 # indefinitely if, for example, another domain has some of its
670 # pages mapped. We might like to diagnose this problem in the
671 # future, but for now all we do is make sure that it's not us
672 # holding the pages, by calling cleanupDomain. We can't
673 # clean up the VM, as above.
674 self.cleanupDomain()
675 return
677 elif xeninfo['crashed']:
678 if self.readDom('xend/shutdown_completed'):
679 # We've seen this shutdown already, but we are preserving
680 # the domain for debugging. Leave it alone.
681 return
683 log.warn('Domain has crashed: name=%s id=%d.',
684 self.info['name'], self.domid)
686 if xroot.get_enable_dump():
687 self.dumpCore()
689 restart_reason = 'crash'
691 elif xeninfo['shutdown']:
692 if self.readDom('xend/shutdown_completed'):
693 # We've seen this shutdown already, but we are preserving
694 # the domain for debugging. Leave it alone.
695 return
697 else:
698 reason = shutdown_reason(xeninfo['shutdown_reason'])
700 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
701 self.info['name'], self.domid, reason)
703 self.clearRestart()
705 if reason == 'suspend':
706 self.state_set(STATE_DOM_SHUTDOWN)
707 # Don't destroy the domain. XendCheckpoint will do
708 # this once it has finished.
709 elif reason in ['poweroff', 'reboot']:
710 restart_reason = reason
711 else:
712 self.destroy()
714 elif self.dompath is None:
715 # We have yet to manage to call introduceDomain on this
716 # domain. This can happen if a restore is in progress, or has
717 # failed. Ignore this domain.
718 pass
719 else:
720 # Domain is alive. If we are shutting it down, then check
721 # the timeout on that, and destroy it if necessary.
723 sst = self.readDom('xend/shutdown_start_time')
724 if sst:
725 sst = float(sst)
726 timeout = SHUTDOWN_TIMEOUT - time.time() + sst
727 if timeout < 0:
728 log.info(
729 "Domain shutdown timeout expired: name=%s id=%s",
730 self.info['name'], self.domid)
731 self.destroy()
732 else:
733 log.debug(
734 "Scheduling refreshShutdown on domain %d in %ds.",
735 self.domid, timeout)
736 scheduler.later(timeout, self.refreshShutdown)
737 finally:
738 self.refresh_shutdown_lock.release()
740 if restart_reason:
741 self.maybeRestart(restart_reason)
744 def shutdown(self, reason):
745 if not reason in shutdown_reasons.values():
746 raise XendError('Invalid reason: %s' % reason)
747 self.storeDom("control/shutdown", reason)
748 if reason != 'suspend':
749 self.storeDom('xend/shutdown_start_time', time.time())
752 ## private:
754 def clearRestart(self):
755 self.removeDom("xend/shutdown_start_time")
758 def maybeRestart(self, reason):
759 # Dispatch to the correct method based upon the configured on_{reason}
760 # behaviour.
761 {"destroy" : self.destroy,
762 "restart" : self.restart,
763 "preserve" : self.preserve,
764 "rename-restart" : self.renameRestart}[self.info['on_' + reason]]()
767 def renameRestart(self):
768 self.restart(True)
771 def dumpCore(self):
772 """Create a core dump for this domain. Nothrow guarantee."""
774 try:
775 corefile = "/var/xen/dump/%s.%s.core" % (self.info['name'],
776 self.domid)
777 xc.domain_dumpcore(dom = self.domid, corefile = corefile)
779 except:
780 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
781 self.domid, self.info['name'])
784 ## public:
786 def setMemoryTarget(self, target):
787 """Set the memory target of this domain.
788 @param target In MiB.
789 """
790 self.info['memory'] = target
791 self.storeVm("memory", target)
792 self.storeDom("memory/target", target << 10)
795 def update(self, info = None):
796 """Update with info from xc.domain_getinfo().
797 """
799 log.trace("XendDomainInfo.update(%s) on domain %d", info, self.domid)
801 if not info:
802 info = dom_get(self.domid)
803 if not info:
804 return
806 self.info.update(info)
807 self.validateInfo()
808 self.refreshShutdown(info)
810 log.trace("XendDomainInfo.update done on domain %d: %s", self.domid,
811 self.info)
814 ## private:
816 def state_set(self, state):
817 self.state_updated.acquire()
818 try:
819 if self.state != state:
820 self.state = state
821 self.state_updated.notifyAll()
822 finally:
823 self.state_updated.release()
826 ## public:
828 def waitForShutdown(self):
829 self.state_updated.acquire()
830 try:
831 while self.state == STATE_DOM_OK:
832 self.state_updated.wait()
833 finally:
834 self.state_updated.release()
837 def __str__(self):
838 s = "<domain"
839 s += " id=" + str(self.domid)
840 s += " name=" + self.info['name']
841 s += " memory=" + str(self.info['memory'])
842 s += " ssidref=" + str(self.info['ssidref'])
843 s += ">"
844 return s
846 __repr__ = __str__
849 ## private:
851 def createDevice(self, deviceClass, devconfig):
852 return self.getDeviceController(deviceClass).createDevice(devconfig)
855 def waitForDevices_(self, deviceClass):
856 return self.getDeviceController(deviceClass).waitForDevices()
859 def waitForDevice(self, deviceClass, devid):
860 return self.getDeviceController(deviceClass).waitForDevice(devid)
863 def reconfigureDevice(self, deviceClass, devid, devconfig):
864 return self.getDeviceController(deviceClass).reconfigureDevice(
865 devid, devconfig)
868 ## public:
870 def destroyDevice(self, deviceClass, devid):
871 return self.getDeviceController(deviceClass).destroyDevice(devid)
874 def getDeviceSxprs(self, deviceClass):
875 return self.getDeviceController(deviceClass).sxprs()
878 ## private:
880 def getDeviceConfigurations(self, deviceClass):
881 return self.getDeviceController(deviceClass).configurations()
884 def getDeviceController(self, name):
885 if name not in controllerClasses:
886 raise XendError("unknown device type: " + str(name))
888 return controllerClasses[name](self)
891 ## public:
893 def sxpr(self):
894 sxpr = ['domain',
895 ['domid', self.domid]]
897 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
898 if self.infoIsSet(e[0]):
899 sxpr.append([e[0], self.info[e[0]]])
901 if self.infoIsSet('image'):
902 sxpr.append(['image', self.info['image']])
904 if self.infoIsSet('device'):
905 for (_, c) in self.info['device']:
906 sxpr.append(['device', c])
908 def stateChar(name):
909 if name in self.info:
910 if self.info[name]:
911 return name[0]
912 else:
913 return '-'
914 else:
915 return '?'
917 state = reduce(
918 lambda x, y: x + y,
919 map(stateChar,
920 ['running', 'blocked', 'paused', 'shutdown', 'crashed',
921 'dying']))
923 sxpr.append(['state', state])
924 if self.infoIsSet('shutdown'):
925 reason = shutdown_reason(self.info['shutdown_reason'])
926 sxpr.append(['shutdown_reason', reason])
927 if self.infoIsSet('cpu_time'):
928 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
929 sxpr.append(['vcpus', self.info['vcpus']])
931 if self.infoIsSet('start_time'):
932 up_time = time.time() - self.info['start_time']
933 sxpr.append(['up_time', str(up_time) ])
934 sxpr.append(['start_time', str(self.info['start_time']) ])
936 if self.store_mfn:
937 sxpr.append(['store_mfn', self.store_mfn])
938 if self.console_mfn:
939 sxpr.append(['console_mfn', self.console_mfn])
941 return sxpr
944 def getVCPUInfo(self):
945 try:
946 def filter_cpumap(map, max):
947 return filter(lambda x: x >= 0, map[0:max])
949 # We include the domain name and ID, to help xm.
950 sxpr = ['domain',
951 ['domid', self.domid],
952 ['name', self.info['name']],
953 ['vcpu_count', self.info['vcpus']]]
955 for i in range(0, self.info['vcpus']):
956 info = xc.vcpu_getinfo(self.domid, i)
958 sxpr.append(['vcpu',
959 ['number', i],
960 ['online', info['online']],
961 ['blocked', info['blocked']],
962 ['running', info['running']],
963 ['cpu_time', info['cpu_time'] / 1e9],
964 ['cpu', info['cpu']],
965 ['cpumap', filter_cpumap(info['cpumap'],
966 self.info['vcpus'])]])
968 return sxpr
970 except RuntimeError, exn:
971 raise XendError(str(exn))
974 ## private:
976 def check_name(self, name):
977 """Check if a vm name is valid. Valid names contain alphabetic characters,
978 digits, or characters in '_-.:/+'.
979 The same name cannot be used for more than one vm at the same time.
981 @param name: name
982 @raise: VmError if invalid
983 """
984 if name is None or name == '':
985 raise VmError('missing vm name')
986 for c in name:
987 if c in string.digits: continue
988 if c in '_-.:/+': continue
989 if c in string.ascii_letters: continue
990 raise VmError('invalid vm name')
992 dominfo = domain_by_name(name)
993 if not dominfo:
994 return
995 if self.domid is None:
996 raise VmError("VM name '%s' already in use by domain %d" %
997 (name, dominfo.domid))
998 if dominfo.domid != self.domid:
999 raise VmError("VM name '%s' is used in both domains %d and %d" %
1000 (name, self.domid, dominfo.domid))
1003 def construct(self):
1004 """Construct the domain.
1006 @raise: VmError on error
1007 """
1009 log.debug('XendDomainInfo.construct: %s %s',
1010 self.domid,
1011 self.info['ssidref'])
1013 self.domid = xc.domain_create(
1014 dom = 0, ssidref = self.info['ssidref'],
1015 handle = uuid.fromString(self.info['uuid']))
1017 if self.domid < 0:
1018 raise VmError('Creating domain failed: name=%s' %
1019 self.info['name'])
1021 self.dompath = GetDomainPath(self.domid)
1023 self.removeDom()
1025 # Set maximum number of vcpus in domain
1026 xc.domain_max_vcpus(self.domid, int(self.info['vcpus']))
1029 def introduceDomain(self):
1030 assert self.domid is not None
1031 assert self.store_mfn is not None
1032 assert self.store_port is not None
1034 try:
1035 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1036 except RuntimeError, exn:
1037 raise XendError(str(exn))
1040 def initDomain(self):
1041 log.debug('XendDomainInfo.initDomain: %s %s',
1042 self.domid,
1043 self.info['cpu_weight'])
1045 if not self.infoIsSet('image'):
1046 raise VmError('Missing image in configuration')
1048 self.image = image.create(self,
1049 self.info['image'],
1050 self.info['device'])
1052 if self.info['bootloader']:
1053 self.image.handleBootloading()
1055 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1057 m = self.image.getDomainMemory(self.info['memory'] * 1024)
1058 xc.domain_setmaxmem(self.domid, maxmem_kb = m)
1059 xc.domain_memory_increase_reservation(self.domid, m, 0, 0)
1061 cpu = self.info['cpu']
1062 if cpu is not None and cpu != -1:
1063 xc.domain_pincpu(self.domid, 0, 1 << cpu)
1065 self.createChannels()
1067 channel_details = self.image.createImage()
1069 self.store_mfn = channel_details['store_mfn']
1070 if 'console_mfn' in channel_details:
1071 self.console_mfn = channel_details['console_mfn']
1073 self.introduceDomain()
1075 self.createDevices()
1077 self.info['start_time'] = time.time()
1080 ## public:
1082 def cleanupDomain(self):
1083 """Cleanup domain resources; release devices. Idempotent. Nothrow
1084 guarantee."""
1086 self.release_devices()
1088 if self.image:
1089 try:
1090 self.image.destroy()
1091 except:
1092 log.exception(
1093 "XendDomainInfo.cleanup: image.destroy() failed.")
1094 self.image = None
1096 try:
1097 self.removeDom()
1098 except:
1099 log.exception("Removing domain path failed.")
1101 try:
1102 if not self.info['name'].startswith(ZOMBIE_PREFIX):
1103 self.info['name'] = ZOMBIE_PREFIX + self.info['name']
1104 except:
1105 log.exception("Renaming Zombie failed.")
1107 self.state_set(STATE_DOM_SHUTDOWN)
1110 def cleanupVm(self):
1111 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1113 try:
1114 self.removeVm()
1115 except:
1116 log.exception("Removing VM path failed.")
1119 def destroy(self):
1120 """Cleanup VM and destroy domain. Nothrow guarantee."""
1122 log.debug("XendDomainInfo.destroy: domid=%s", self.domid)
1124 self.cleanupVm()
1125 if self.dompath is not None:
1126 self.destroyDomain()
1129 def destroyDomain(self):
1130 log.debug("XendDomainInfo.destroyDomain(%s)", self.domid)
1132 try:
1133 if self.domid is not None:
1134 xc.domain_destroy(dom=self.domid)
1135 except:
1136 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1138 self.cleanupDomain()
1141 ## private:
1143 def release_devices(self):
1144 """Release all domain's devices. Nothrow guarantee."""
1146 while True:
1147 t = xstransact("%s/device" % self.dompath)
1148 for n in controllerClasses.keys():
1149 for d in t.list(n):
1150 try:
1151 t.remove(d)
1152 except:
1153 # Log and swallow any exceptions in removal --
1154 # there's nothing more we can do.
1155 log.exception(
1156 "Device release failed: %s; %s; %s",
1157 self.info['name'], n, d)
1158 if t.commit():
1159 break
1162 def createChannels(self):
1163 """Create the channels to the domain.
1164 """
1165 self.store_port = self.createChannel()
1166 self.console_port = self.createChannel()
1169 def createChannel(self):
1170 """Create an event channel to the domain.
1171 """
1172 try:
1173 return xc.evtchn_alloc_unbound(dom=self.domid, remote_dom=0)
1174 except:
1175 log.exception("Exception in alloc_unbound(%d)", self.domid)
1176 raise
1179 ## public:
1181 def createDevices(self):
1182 """Create the devices for a vm.
1184 @raise: VmError for invalid devices
1185 """
1187 for (n, c) in self.info['device']:
1188 self.createDevice(n, c)
1190 if self.image:
1191 self.image.createDeviceModel()
1194 def waitForDevices(self):
1195 """Wait for this domain's configured devices to connect.
1197 @raise: VmError if any device fails to initialise.
1198 """
1199 for c in controllerClasses:
1200 self.waitForDevices_(c)
1203 def device_create(self, dev_config):
1204 """Create a new device.
1206 @param dev_config: device configuration
1207 """
1208 dev_type = sxp.name(dev_config)
1209 devid = self.createDevice(dev_type, dev_config)
1210 self.waitForDevice(dev_type, devid)
1211 # self.config.append(['device', dev.getConfig()])
1212 return self.getDeviceController(dev_type).sxpr(devid)
1215 def device_configure(self, dev_config, devid):
1216 """Configure an existing device.
1217 @param dev_config: device configuration
1218 @param devid: device id
1219 """
1220 deviceClass = sxp.name(dev_config)
1221 self.reconfigureDevice(deviceClass, devid, dev_config)
1224 def pause(self):
1225 xc.domain_pause(self.domid)
1228 def unpause(self):
1229 xc.domain_unpause(self.domid)
1232 ## private:
1234 def restart(self, rename = False):
1235 """Restart the domain after it has exited.
1237 @param rename True if the old domain is to be renamed and preserved,
1238 False if it is to be destroyed.
1239 """
1241 config = self.sxpr()
1243 if self.readVm(RESTART_IN_PROGRESS):
1244 log.error('Xend failed during restart of domain %d. '
1245 'Refusing to restart to avoid loops.',
1246 self.domid)
1247 self.destroy()
1248 return
1250 self.writeVm(RESTART_IN_PROGRESS, 'True')
1252 now = time.time()
1253 rst = self.readVm('xend/previous_restart_time')
1254 if rst:
1255 rst = float(rst)
1256 timeout = now - rst
1257 if timeout < MINIMUM_RESTART_TIME:
1258 log.error(
1259 'VM %s restarting too fast (%f seconds since the last '
1260 'restart). Refusing to restart to avoid loops.',
1261 self.info['name'], timeout)
1262 self.destroy()
1263 return
1265 self.writeVm('xend/previous_restart_time', str(now))
1267 try:
1268 if rename:
1269 self.preserveForRestart()
1270 else:
1271 self.destroyDomain()
1273 # new_dom's VM will be the same as this domain's VM, except where
1274 # the rename flag has instructed us to call preserveForRestart.
1275 # In that case, it is important that we remove the
1276 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1277 # once the new one is available.
1279 new_dom = None
1280 try:
1281 xd = get_component('xen.xend.XendDomain')
1282 new_dom = xd.domain_create(config)
1283 new_dom.unpause()
1284 new_dom.removeVm(RESTART_IN_PROGRESS)
1285 except:
1286 if new_dom:
1287 new_dom.removeVm(RESTART_IN_PROGRESS)
1288 new_dom.destroy()
1289 else:
1290 self.removeVm(RESTART_IN_PROGRESS)
1291 raise
1292 except:
1293 log.exception('Failed to restart domain %d.', self.domid)
1296 def preserveForRestart(self):
1297 """Preserve a domain that has been shut down, by giving it a new UUID,
1298 cloning the VM details, and giving it a new name. This allows us to
1299 keep this domain for debugging, but restart a new one in its place
1300 preserving the restart semantics (name and UUID preserved).
1301 """
1303 new_name = self.generateUniqueName()
1304 new_uuid = uuid.toString(uuid.create())
1305 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1306 self.info['name'], self.domid, self.info['uuid'],
1307 new_name, new_uuid)
1308 self.release_devices()
1309 self.info['name'] = new_name
1310 self.info['uuid'] = new_uuid
1311 self.vmpath = VMROOT + new_uuid
1312 self.storeVmDetails()
1313 self.preserve()
1316 def preserve(self):
1317 log.info("Preserving dead domain %s (%d).", self.info['name'],
1318 self.domid)
1319 self.storeDom('xend/shutdown_completed', 'True')
1320 self.state_set(STATE_DOM_SHUTDOWN)
1323 # private:
1325 def generateUniqueName(self):
1326 n = 1
1327 while True:
1328 name = "%s-%d" % (self.info['name'], n)
1329 try:
1330 self.check_name(name)
1331 return name
1332 except VmError:
1333 n += 1
1336 def configure_bootloader(self):
1337 if not self.info['bootloader']:
1338 return
1339 # if we're restarting with a bootloader, we need to run it
1340 # FIXME: this assumes the disk is the first device and
1341 # that we're booting from the first disk
1342 blcfg = None
1343 # FIXME: this assumes that we want to use the first disk
1344 dev = sxp.child_value(self.config, "device")
1345 if dev:
1346 disk = sxp.child_value(dev, "uname")
1347 fn = blkdev_uname_to_file(disk)
1348 blcfg = bootloader(self.info['bootloader'], fn, 1,
1349 self.info['vcpus'])
1350 if blcfg is None:
1351 msg = "Had a bootloader specified, but can't find disk"
1352 log.error(msg)
1353 raise VmError(msg)
1354 self.config = sxp.merge(['vm', ['image', blcfg]], self.config)
1357 def send_sysrq(self, key):
1358 asserts.isCharConvertible(key)
1360 self.storeDom("control/sysrq", '%c' % key)
1363 def infoIsSet(self, name):
1364 return name in self.info and self.info[name] is not None
1367 #============================================================================
1368 # Register device controllers and their device config types.
1370 """A map from device-class names to the subclass of DevController that
1371 implements the device control specific to that device-class."""
1372 controllerClasses = {}
1374 def addControllerClass(device_class, cls):
1375 """Register a subclass of DevController to handle the named device-class.
1376 """
1377 cls.deviceClass = device_class
1378 controllerClasses[device_class] = cls
1381 from xen.xend.server import blkif, netif, tpmif, pciif, iopif, usbif
1382 addControllerClass('vbd', blkif.BlkifController)
1383 addControllerClass('vif', netif.NetifController)
1384 addControllerClass('vtpm', tpmif.TPMifController)
1385 addControllerClass('pci', pciif.PciController)
1386 addControllerClass('ioports', iopif.IOPortsController)
1387 addControllerClass('usb', usbif.UsbifController)