direct-io.hg

view tools/python/xen/xend/XendDomainInfo.py @ 7293:0f33cbec4e36

This patch fixes an error in the xm create path when the
xc.domain_create call fails (eg, when ACM policy prevents creation of a
domain). When xc.domain_create fails, dompath never gets set.

Signed-off-by: Tom Lendacky <toml@us.ibm.com>
author emellor@ewan
date Mon Oct 10 19:06:14 2005 +0100 (2005-10-10)
parents fe4c1d44e899
children 74d56b7ff46c
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 string
28 import time
29 import threading
30 import errno
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.XendLogging import log
42 from xen.xend.XendError import XendError, VmError
43 from xen.xend.XendRoot import get_component
45 from uuid import getUuid
47 from xen.xend.xenstore.xstransact import xstransact
48 from xen.xend.xenstore.xsutil import GetDomainPath, IntroduceDomain
50 """Shutdown code for poweroff."""
51 DOMAIN_POWEROFF = 0
53 """Shutdown code for reboot."""
54 DOMAIN_REBOOT = 1
56 """Shutdown code for suspend."""
57 DOMAIN_SUSPEND = 2
59 """Shutdown code for crash."""
60 DOMAIN_CRASH = 3
62 """Shutdown code for halt."""
63 DOMAIN_HALT = 4
65 """Map shutdown codes to strings."""
66 shutdown_reasons = {
67 DOMAIN_POWEROFF: "poweroff",
68 DOMAIN_REBOOT : "reboot",
69 DOMAIN_SUSPEND : "suspend",
70 DOMAIN_CRASH : "crash",
71 DOMAIN_HALT : "halt"
72 }
74 restart_modes = [
75 "restart",
76 "destroy",
77 "preserve",
78 "rename-restart"
79 ]
81 STATE_VM_OK = "ok"
82 STATE_VM_TERMINATED = "terminated"
84 """Flag for a block device backend domain."""
85 SIF_BLK_BE_DOMAIN = (1<<4)
87 """Flag for a net device backend domain."""
88 SIF_NET_BE_DOMAIN = (1<<5)
90 """Flag for a TPM device backend domain."""
91 SIF_TPM_BE_DOMAIN = (1<<7)
94 SHUTDOWN_TIMEOUT = 30
97 DOMROOT = '/local/domain/'
98 VMROOT = '/vm/'
100 ZOMBIE_PREFIX = 'Zombie-'
102 xc = xen.lowlevel.xc.new()
103 xroot = XendRoot.instance()
106 ## Configuration entries that we expect to round-trip -- be read from the
107 # config file or xc, written to save-files (i.e. through sxpr), and reused as
108 # config on restart or restore, all without munging. Some configuration
109 # entries are munged for backwards compatibility reasons, or because they
110 # don't come out of xc in the same form as they are specified in the config
111 # file, so those are handled separately.
112 ROUNDTRIPPING_CONFIG_ENTRIES = [
113 ('name', str),
114 ('ssidref', int),
115 ('vcpus', int),
116 ('vcpu_avail', int),
117 ('cpu_weight', float),
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 # vcpu_to_cpu: the current mapping between virtual CPUs and the physical
134 # CPU it is using.
135 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
136 # CPUs that that VCPU may use.
137 # cpu: a configuration setting requesting that VCPU 0 is pinned to
138 # the specified physical CPU.
139 #
140 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
141 # across save, restore, migrate, and restart). The other settings are only
142 # specific to the domain, so are lost when the VM moves.
143 #
146 def create(config):
147 """Create a VM from a configuration.
149 @param config configuration
150 @raise: VmError for invalid configuration
151 """
153 log.debug("XendDomainInfo.create(%s)", config)
155 vm = XendDomainInfo(getUuid(), parseConfig(config))
156 try:
157 vm.construct()
158 vm.initDomain()
159 vm.construct_image()
160 vm.configure()
161 vm.storeVmDetails()
162 vm.storeDomDetails()
163 vm.refreshShutdown()
164 return vm
165 except:
166 log.exception('Domain construction failed')
167 vm.destroy()
168 raise
171 def recreate(xeninfo):
172 """Create the VM object for an existing domain. The domain must not
173 be dying, as the paths in the store should already have been removed,
174 and asking us to recreate them causes problems."""
176 log.debug("XendDomainInfo.recreate(%s)", xeninfo)
178 assert not xeninfo['dying']
180 domid = xeninfo['dom']
181 try:
182 dompath = GetDomainPath(domid)
183 if not dompath:
184 raise XendError(
185 'No domain path in store for existing domain %d' % domid)
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 uuid = xstransact.Read(vmpath, "uuid")
191 if not uuid:
192 raise XendError(
193 'No vm/uuid path in store for existing domain %d' % domid)
195 log.info("Recreating domain %d, UUID %s.", domid, uuid)
197 vm = XendDomainInfo(uuid, xeninfo, domid, True)
199 except Exception, exn:
200 log.warn(str(exn))
202 uuid = getUuid()
204 log.info("Recreating domain %d with new UUID %s.", domid, uuid)
206 vm = XendDomainInfo(uuid, xeninfo, domid, True)
207 vm.removeDom()
208 vm.storeVmDetails()
209 vm.storeDomDetails()
211 vm.create_channel()
212 if domid == 0:
213 vm.initStoreConnection()
215 vm.refreshShutdown(xeninfo)
216 return vm
219 def restore(config):
220 """Create a domain and a VM object to do a restore.
222 @param config: domain configuration
223 """
225 log.debug("XendDomainInfo.restore(%s)", config)
227 uuid = sxp.child_value(config, 'uuid')
228 vm = XendDomainInfo(uuid, parseConfig(config))
229 try:
230 vm.construct()
231 vm.configure()
232 vm.create_channel()
233 vm.storeVmDetails()
234 vm.storeDomDetails()
235 vm.refreshShutdown()
236 return vm
237 except:
238 vm.destroy()
239 raise
242 def parseConfig(config):
243 def get_cfg(name, conv = None):
244 val = sxp.child_value(config, name)
246 if conv and not val is None:
247 try:
248 return conv(val)
249 except TypeError, exn:
250 raise VmError(
251 'Invalid setting %s = %s in configuration: %s' %
252 (name, val, str(exn)))
253 else:
254 return val
257 log.debug("parseConfig: config is %s", config)
259 result = {}
261 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
262 result[e[0]] = get_cfg(e[0], e[1])
264 result['memory'] = get_cfg('memory', int)
265 result['mem_kb'] = get_cfg('mem_kb', int)
266 result['maxmem'] = get_cfg('maxmem', int)
267 result['maxmem_kb'] = get_cfg('maxmem_kb', int)
268 result['cpu'] = get_cfg('cpu', int)
269 result['image'] = get_cfg('image')
271 try:
272 if result['image']:
273 result['vcpus'] = int(sxp.child_value(result['image'],
274 'vcpus', 1))
275 else:
276 result['vcpus'] = 1
277 except TypeError, exn:
278 raise VmError(
279 'Invalid configuration setting: vcpus = %s: %s' %
280 (sxp.child_value(result['image'], 'vcpus', 1), str(exn)))
282 result['backend'] = []
283 for c in sxp.children(config, 'backend'):
284 result['backend'].append(sxp.name(sxp.child0(c)))
286 result['device'] = []
287 for d in sxp.children(config, 'device'):
288 c = sxp.child0(d)
289 result['device'].append((sxp.name(c), c))
291 # Configuration option "restart" is deprecated. Parse it, but
292 # let on_xyz override it if they are present.
293 restart = get_cfg('restart')
294 if restart:
295 def handle_restart(event, val):
296 if not event in result:
297 result[event] = val
299 if restart == "onreboot":
300 handle_restart('on_poweroff', 'destroy')
301 handle_restart('on_reboot', 'restart')
302 handle_restart('on_crash', 'destroy')
303 elif restart == "always":
304 handle_restart('on_poweroff', 'restart')
305 handle_restart('on_reboot', 'restart')
306 handle_restart('on_crash', 'restart')
307 elif restart == "never":
308 handle_restart('on_poweroff', 'destroy')
309 handle_restart('on_reboot', 'destroy')
310 handle_restart('on_crash', 'destroy')
311 else:
312 log.warn("Ignoring malformed and deprecated config option "
313 "restart = %s", restart)
315 log.debug("parseConfig: result is %s", result)
316 return result
319 def domain_by_name(name):
320 # See comment in XendDomain constructor.
321 xd = get_component('xen.xend.XendDomain')
322 return xd.domain_lookup_by_name_nr(name)
324 def shutdown_reason(code):
325 """Get a shutdown reason from a code.
327 @param code: shutdown code
328 @type code: int
329 @return: shutdown reason
330 @rtype: string
331 """
332 return shutdown_reasons.get(code, "?")
334 def dom_get(dom):
335 """Get info from xen for an existing domain.
337 @param dom: domain id
338 @return: info or None
339 """
340 try:
341 domlist = xc.domain_getinfo(dom, 1)
342 if domlist and dom == domlist[0]['dom']:
343 return domlist[0]
344 except Exception, err:
345 # ignore missing domain
346 log.debug("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
347 return None
349 class XendDomainInfo:
350 """Virtual machine object."""
352 """Minimum time between domain restarts in seconds.
353 """
354 MINIMUM_RESTART_TIME = 20
357 def __init__(self, uuid, info, domid = None, augment = False):
359 self.uuid = uuid
360 self.info = info
362 if domid is not None:
363 self.domid = domid
364 elif 'dom' in info:
365 self.domid = int(info['dom'])
366 else:
367 self.domid = None
369 self.vmpath = VMROOT + uuid
370 if self.domid is None:
371 self.dompath = None
372 else:
373 self.dompath = DOMROOT + str(self.domid)
375 if augment:
376 self.augmentInfo()
378 self.validateInfo()
380 self.image = None
382 self.store_channel = None
383 self.store_mfn = None
384 self.console_channel = None
385 self.console_mfn = None
387 self.state = STATE_VM_OK
388 self.state_updated = threading.Condition()
389 self.refresh_shutdown_lock = threading.Condition()
392 def augmentInfo(self):
393 """Augment self.info, as given to us through {@link #recreate}, with
394 values taken from the store. This recovers those values known to xend
395 but not to the hypervisor.
396 """
397 def useIfNeeded(name, val):
398 if not self.infoIsSet(name) and val is not None:
399 self.info[name] = val
401 params = (("name", str),
402 ("on_poweroff", str),
403 ("on_reboot", str),
404 ("on_crash", str),
405 ("image", str),
406 ("vcpus", int),
407 ("vcpu_avail", int),
408 ("start_time", float))
410 from_store = self.gatherVm(*params)
412 map(lambda x, y: useIfNeeded(x[0], y), params, from_store)
414 device = []
415 for c in controllerClasses:
416 devconfig = self.getDeviceConfigurations(c)
417 if devconfig:
418 device.extend(map(lambda x: (c, x), devconfig))
419 useIfNeeded('device', device)
422 def validateInfo(self):
423 """Validate and normalise the info block. This has either been parsed
424 by parseConfig, or received from xc through recreate and augmented by
425 the current store contents.
426 """
427 def defaultInfo(name, val):
428 if not self.infoIsSet(name):
429 self.info[name] = val()
431 try:
432 defaultInfo('name', lambda: "Domain-%d" % self.domid)
433 defaultInfo('ssidref', lambda: 0)
434 defaultInfo('on_poweroff', lambda: "destroy")
435 defaultInfo('on_reboot', lambda: "restart")
436 defaultInfo('on_crash', lambda: "restart")
437 defaultInfo('cpu', lambda: None)
438 defaultInfo('cpu_weight', lambda: 1.0)
439 defaultInfo('vcpus', lambda: 1)
440 defaultInfo('vcpu_avail', lambda: (1 << self.info['vcpus']) - 1)
441 defaultInfo('bootloader', lambda: None)
442 defaultInfo('backend', lambda: [])
443 defaultInfo('device', lambda: [])
444 defaultInfo('image', lambda: None)
446 self.check_name(self.info['name'])
448 if isinstance(self.info['image'], str):
449 self.info['image'] = sxp.from_string(self.info['image'])
451 # Internally, we keep only maxmem_KiB, and not maxmem or maxmem_kb
452 # (which come from outside, and are in MiB and KiB respectively).
453 # This means that any maxmem or maxmem_kb settings here have come
454 # from outside, and maxmem_KiB must be updated to reflect them.
455 # If we have both maxmem and maxmem_kb and these are not
456 # consistent, then this is an error, as we've no way to tell which
457 # one takes precedence.
459 # Exactly the same thing applies to memory_KiB, memory, and
460 # mem_kb.
462 def discard_negatives(name):
463 if self.infoIsSet(name) and self.info[name] < 0:
464 del self.info[name]
466 def valid_KiB_(mb_name, kb_name):
467 discard_negatives(kb_name)
468 discard_negatives(mb_name)
470 if self.infoIsSet(kb_name):
471 if self.infoIsSet(mb_name):
472 mb = self.info[mb_name]
473 kb = self.info[kb_name]
474 if mb * 1024 == kb:
475 return kb
476 else:
477 raise VmError(
478 'Inconsistent %s / %s settings: %s / %s' %
479 (mb_name, kb_name, mb, kb))
480 else:
481 return self.info[kb_name]
482 elif self.infoIsSet(mb_name):
483 return self.info[mb_name] * 1024
484 else:
485 return None
487 def valid_KiB(mb_name, kb_name):
488 result = valid_KiB_(mb_name, kb_name)
489 if result is None or result < 0:
490 raise VmError('Invalid %s / %s: %s' %
491 (mb_name, kb_name, result))
492 else:
493 return result
495 def delIf(name):
496 if name in self.info:
497 del self.info[name]
499 self.info['memory_KiB'] = valid_KiB('memory', 'mem_kb')
500 delIf('memory')
501 delIf('mem_kb')
502 self.info['maxmem_KiB'] = valid_KiB_('maxmem', 'maxmem_kb')
503 delIf('maxmem')
504 delIf('maxmem_kb')
506 if not self.info['maxmem_KiB']:
507 self.info['maxmem_KiB'] = 1 << 30
509 if self.info['maxmem_KiB'] > self.info['memory_KiB']:
510 self.info['maxmem_KiB'] = self.info['memory_KiB']
512 # Validate the given backend names.
513 for s in self.info['backend']:
514 if s not in backendFlags:
515 raise VmError('Invalid backend type: %s' % s)
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)
544 def storeVm(self, *args):
545 return xstransact.Store(self.vmpath, *args)
547 def readDom(self, *args):
548 return xstransact.Read(self.dompath, *args)
550 def writeDom(self, *args):
551 return xstransact.Write(self.dompath, *args)
553 def removeDom(self, *args):
554 return xstransact.Remove(self.dompath, *args)
556 def gatherDom(self, *args):
557 return xstransact.Gather(self.dompath, *args)
559 def storeDom(self, *args):
560 return xstransact.Store(self.dompath, *args)
563 def storeVmDetails(self):
564 to_store = {
565 'uuid': self.uuid,
567 # XXX
568 'memory/target': str(self.info['memory_KiB'])
569 }
571 if self.infoIsSet('image'):
572 to_store['image'] = sxp.to_string(self.info['image'])
574 for k in ['name', 'ssidref', 'on_poweroff', 'on_reboot', 'on_crash',
575 'vcpus', 'vcpu_avail']:
576 if self.infoIsSet(k):
577 to_store[k] = str(self.info[k])
579 log.debug("Storing VM details: %s", to_store)
581 self.writeVm(to_store)
584 def storeDomDetails(self):
585 to_store = {
586 'domid': str(self.domid),
587 'vm': self.vmpath,
589 'memory/target': str(self.info['memory_KiB'])
590 }
592 for (k, v) in self.info.items():
593 if v:
594 to_store[k] = str(v)
596 def availability(n):
597 if self.info['vcpu_avail'] & (1 << n):
598 return 'online'
599 else:
600 return 'offline'
602 for v in range(0, self.info['vcpus']):
603 to_store["cpu/%d/availability" % v] = availability(v)
605 log.debug("Storing domain details: %s", to_store)
607 self.writeDom(to_store)
610 def setDomid(self, domid):
611 """Set the domain id.
613 @param dom: domain id
614 """
615 self.domid = domid
616 self.storeDom("domid", self.domid)
618 def getDomid(self):
619 return self.domid
621 def setName(self, name):
622 self.check_name(name)
623 self.info['name'] = name
624 self.storeVm("name", name)
626 def getName(self):
627 return self.info['name']
629 def getDomainPath(self):
630 return self.dompath
632 def getUuid(self):
633 return self.uuid
635 def getVCpuCount(self):
636 return self.info['vcpus']
638 def getSsidref(self):
639 return self.info['ssidref']
641 def getMemoryTarget(self):
642 """Get this domain's target memory size, in KiB."""
643 return self.info['memory_KiB']
645 def setStoreRef(self, ref):
646 self.store_mfn = ref
647 self.storeDom("store/ring-ref", ref)
650 def getBackendFlags(self):
651 return reduce(lambda x, y: x | backendFlags[y],
652 self.info['backend'], 0)
655 def refreshShutdown(self, xeninfo = None):
656 # If set at the end of this method, a restart is required, with the
657 # given reason. This restart has to be done out of the scope of
658 # refresh_shutdown_lock.
659 restart_reason = None
661 self.refresh_shutdown_lock.acquire()
662 try:
663 if xeninfo is None:
664 xeninfo = dom_get(self.domid)
665 if xeninfo is None:
666 # The domain no longer exists. This will occur if we have
667 # scheduled a timer to check for shutdown timeouts and the
668 # shutdown succeeded. It will also occur if someone
669 # destroys a domain beneath us. We clean up the domain,
670 # just in case, but we can't clean up the VM, because that
671 # VM may have migrated to a different domain on this
672 # machine.
673 self.cleanupDomain()
674 return
676 if xeninfo['dying']:
677 # Dying means that a domain has been destroyed, but has not
678 # yet been cleaned up by Xen. This state could persist
679 # indefinitely if, for example, another domain has some of its
680 # pages mapped. We might like to diagnose this problem in the
681 # future, but for now all we do is make sure that it's not us
682 # holding the pages, by calling cleanupDomain. We can't
683 # clean up the VM, as above.
684 self.cleanupDomain()
685 return
687 elif xeninfo['crashed']:
688 log.warn('Domain has crashed: name=%s id=%d.',
689 self.info['name'], self.domid)
691 if xroot.get_enable_dump():
692 self.dumpCore()
694 restart_reason = 'crash'
696 elif xeninfo['shutdown']:
697 if self.readDom('xend/shutdown_completed'):
698 # We've seen this shutdown already, but we are preserving
699 # the domain for debugging. Leave it alone.
700 return
702 else:
703 reason = shutdown_reason(xeninfo['shutdown_reason'])
705 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
706 self.info['name'], self.domid, reason)
708 self.clearRestart()
710 if reason == 'suspend':
711 self.state_set(STATE_VM_TERMINATED)
712 # Don't destroy the domain. XendCheckpoint will do
713 # this once it has finished.
714 elif reason in ['poweroff', 'reboot']:
715 restart_reason = reason
716 else:
717 self.destroy()
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 setConsoleRef(self, ref):
787 self.console_mfn = ref
788 self.storeDom("console/ring-ref", ref)
791 def setMemoryTarget(self, target):
792 """Set the memory target of this domain.
793 @param target In MiB.
794 """
795 # Internally we use KiB, but the command interface uses MiB.
796 t = target << 10
797 self.info['memory_KiB'] = t
798 self.storeDom("memory/target", t)
801 def update(self, info = None):
802 """Update with info from xc.domain_getinfo().
803 """
805 log.debug("XendDomainInfo.update(%s) on domain %d", info, self.domid)
807 if not info:
808 info = dom_get(self.domid)
809 if not info:
810 return
812 self.info.update(info)
813 self.validateInfo()
814 self.refreshShutdown(info)
816 log.debug("XendDomainInfo.update done on domain %d: %s", self.domid,
817 self.info)
820 ## private:
822 def state_set(self, state):
823 self.state_updated.acquire()
824 if self.state != state:
825 self.state = state
826 self.state_updated.notifyAll()
827 self.state_updated.release()
830 ## public:
832 def waitForShutdown(self):
833 self.state_updated.acquire()
834 while self.state == STATE_VM_OK:
835 self.state_updated.wait()
836 self.state_updated.release()
839 def __str__(self):
840 s = "<domain"
841 s += " id=" + str(self.domid)
842 s += " name=" + self.info['name']
843 s += " memory=" + str(self.info['memory_KiB'] / 1024)
844 s += " ssidref=" + str(self.info['ssidref'])
845 s += ">"
846 return s
848 __repr__ = __str__
851 ## private:
853 def createDevice(self, deviceClass, devconfig):
854 return self.getDeviceController(deviceClass).createDevice(devconfig)
857 def reconfigureDevice(self, deviceClass, devid, devconfig):
858 return self.getDeviceController(deviceClass).reconfigureDevice(
859 devid, devconfig)
862 ## public:
864 def destroyDevice(self, deviceClass, devid):
865 return self.getDeviceController(deviceClass).destroyDevice(devid)
868 ## private:
870 def getDeviceSxprs(self, deviceClass):
871 return self.getDeviceController(deviceClass).sxprs()
874 def getDeviceConfigurations(self, deviceClass):
875 return self.getDeviceController(deviceClass).configurations()
878 def getDeviceController(self, name):
879 if name not in controllerClasses:
880 raise XendError("unknown device type: " + str(name))
882 return controllerClasses[name](self)
885 ## public:
887 def sxpr(self):
888 sxpr = ['domain',
889 ['domid', self.domid],
890 ['uuid', self.uuid],
891 ['memory', self.info['memory_KiB'] / 1024]]
893 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
894 if self.infoIsSet(e[0]):
895 sxpr.append([e[0], self.info[e[0]]])
897 sxpr.append(['maxmem', self.info['maxmem_KiB'] / 1024])
899 if self.infoIsSet('image'):
900 sxpr.append(['image', self.info['image']])
902 if self.infoIsSet('device'):
903 for (_, c) in self.info['device']:
904 sxpr.append(['device', c])
906 def stateChar(name):
907 if name in self.info:
908 if self.info[name]:
909 return name[0]
910 else:
911 return '-'
912 else:
913 return '?'
915 state = reduce(
916 lambda x, y: x + y,
917 map(stateChar,
918 ['running', 'blocked', 'paused', 'shutdown', 'crashed',
919 'dying']))
921 sxpr.append(['state', state])
922 if self.infoIsSet('shutdown'):
923 reason = shutdown_reason(self.info['shutdown_reason'])
924 sxpr.append(['shutdown_reason', reason])
925 if self.infoIsSet('cpu_time'):
926 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
927 sxpr.append(['vcpus', self.info['vcpus']])
928 if self.infoIsSet('cpumap'):
929 sxpr.append(['cpumap', self.info['cpumap']])
930 if self.infoIsSet('vcpu_to_cpu'):
931 sxpr.append(['cpu', self.info['vcpu_to_cpu'][0]])
932 sxpr.append(['vcpu_to_cpu', self.prettyVCpuMap()])
934 if self.infoIsSet('start_time'):
935 up_time = time.time() - self.info['start_time']
936 sxpr.append(['up_time', str(up_time) ])
937 sxpr.append(['start_time', str(self.info['start_time']) ])
939 if self.store_mfn:
940 sxpr.append(['store_mfn', self.store_mfn])
941 if self.console_mfn:
942 sxpr.append(['console_mfn', self.console_mfn])
944 return sxpr
947 ## private:
949 def prettyVCpuMap(self):
950 return '|'.join(map(str,
951 self.info['vcpu_to_cpu'][0:self.info['vcpus']]))
954 def check_name(self, name):
955 """Check if a vm name is valid. Valid names contain alphabetic characters,
956 digits, or characters in '_-.:/+'.
957 The same name cannot be used for more than one vm at the same time.
959 @param name: name
960 @raise: VmError if invalid
961 """
962 if name is None or name == '':
963 raise VmError('missing vm name')
964 for c in name:
965 if c in string.digits: continue
966 if c in '_-.:/+': continue
967 if c in string.ascii_letters: continue
968 raise VmError('invalid vm name')
970 dominfo = domain_by_name(name)
971 if not dominfo:
972 return
973 if self.domid is None:
974 raise VmError("VM name '%s' already in use by domain %d" %
975 (name, dominfo.domid))
976 if dominfo.domid != self.domid:
977 raise VmError("VM name '%s' is used in both domains %d and %d" %
978 (name, self.domid, dominfo.domid))
981 def construct(self):
982 """Construct the domain.
984 @raise: VmError on error
985 """
987 log.debug('XendDomainInfo.construct: %s %s',
988 self.domid,
989 self.info['ssidref'])
991 self.domid = xc.domain_create(dom = 0, ssidref = self.info['ssidref'])
993 if self.domid < 0:
994 raise VmError('Creating domain failed: name=%s' %
995 self.info['name'])
997 self.dompath = DOMROOT + str(self.domid)
999 # Ensure that the domain entry is clean. This prevents a stale
1000 # shutdown_start_time from killing the domain, for example.
1001 self.removeDom()
1004 def initDomain(self):
1005 log.debug('XendDomainInfo.initDomain: %s %s %s',
1006 self.domid,
1007 self.info['memory_KiB'],
1008 self.info['cpu_weight'])
1010 if not self.infoIsSet('image'):
1011 raise VmError('Missing image in configuration')
1013 self.image = image.create(self,
1014 self.info['image'],
1015 self.info['device'])
1017 if self.info['bootloader']:
1018 self.image.handleBootloading()
1020 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1021 # XXX Merge with configure_maxmem?
1022 m = self.image.getDomainMemory(self.info['memory_KiB'])
1023 xc.domain_setmaxmem(self.domid, m)
1024 xc.domain_memory_increase_reservation(self.domid, m, 0, 0)
1026 cpu = self.info['cpu']
1027 if cpu is not None and cpu != -1:
1028 xc.domain_pincpu(self.domid, 0, 1 << cpu)
1030 self.info['start_time'] = time.time()
1032 log.debug('init_domain> Created domain=%d name=%s memory=%d',
1033 self.domid, self.info['name'], self.info['memory_KiB'])
1036 def construct_image(self):
1037 """Construct the boot image for the domain.
1038 """
1039 self.create_channel()
1040 self.image.createImage()
1041 IntroduceDomain(self.domid, self.store_mfn,
1042 self.store_channel, self.dompath)
1045 ## public:
1047 def cleanupDomain(self):
1048 """Cleanup domain resources; release devices. Idempotent. Nothrow
1049 guarantee."""
1051 self.release_devices()
1053 if self.image:
1054 try:
1055 self.image.destroy()
1056 except:
1057 log.exception(
1058 "XendDomainInfo.cleanup: image.destroy() failed.")
1059 self.image = None
1061 try:
1062 self.removeDom()
1063 except:
1064 log.exception("Removing domain path failed.")
1066 try:
1067 if not self.info['name'].startswith(ZOMBIE_PREFIX):
1068 self.info['name'] = self.generateZombieName()
1069 except:
1070 log.exception("Renaming Zombie failed.")
1072 self.state_set(STATE_VM_TERMINATED)
1075 def cleanupVm(self):
1076 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1078 try:
1079 self.removeVm()
1080 except:
1081 log.exception("Removing VM path failed.")
1084 def destroy(self):
1085 """Cleanup VM and destroy domain. Nothrow guarantee."""
1087 log.debug("XendDomainInfo.destroy: domid=%s", self.domid)
1089 self.cleanupVm()
1090 if self.dompath is not None:
1091 self.destroyDomain()
1094 def destroyDomain(self):
1095 log.debug("XendDomainInfo.destroyDomain(%s)", self.domid)
1097 try:
1098 if self.domid is not None:
1099 xc.domain_destroy(dom=self.domid)
1100 except:
1101 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1103 self.cleanupDomain()
1106 ## private:
1108 def release_devices(self):
1109 """Release all domain's devices. Nothrow guarantee."""
1111 while True:
1112 t = xstransact("%s/device" % self.dompath)
1113 for n in controllerClasses.keys():
1114 for d in t.list(n):
1115 try:
1116 t.remove(d)
1117 except:
1118 # Log and swallow any exceptions in removal --
1119 # there's nothing more we can do.
1120 log.exception(
1121 "Device release failed: %s; %s; %s",
1122 self.info['name'], n, d)
1123 if t.commit():
1124 break
1127 def eventChannel(self, path=None):
1128 """Create an event channel to the domain.
1130 @param path under which port is stored in db
1131 """
1132 if path:
1133 try:
1134 return int(self.readDom(path))
1135 except:
1136 # The port is not yet set, i.e. the channel has not yet been
1137 # created.
1138 pass
1140 try:
1141 port = xc.evtchn_alloc_unbound(dom=self.domid, remote_dom=0)
1142 except:
1143 log.exception("Exception in alloc_unbound(%d)", self.domid)
1144 raise
1146 self.storeDom(path, port)
1147 return port
1149 def create_channel(self):
1150 """Create the channels to the domain.
1151 """
1152 self.store_channel = self.eventChannel("store/port")
1153 self.console_channel = self.eventChannel("console/port")
1155 def create_configured_devices(self):
1156 for (n, c) in self.info['device']:
1157 self.createDevice(n, c)
1160 def create_devices(self):
1161 """Create the devices for a vm.
1163 @raise: VmError for invalid devices
1164 """
1165 self.create_configured_devices()
1166 if self.image:
1167 self.image.createDeviceModel()
1170 ## public:
1172 def device_create(self, dev_config):
1173 """Create a new device.
1175 @param dev_config: device configuration
1176 """
1177 dev_type = sxp.name(dev_config)
1178 devid = self.createDevice(dev_type, dev_config)
1179 # self.config.append(['device', dev.getConfig()])
1180 return self.getDeviceController(dev_type).sxpr(devid)
1183 def device_configure(self, dev_config, devid):
1184 """Configure an existing device.
1185 @param dev_config: device configuration
1186 @param devid: device id
1187 """
1188 deviceClass = sxp.name(dev_config)
1189 self.reconfigureDevice(deviceClass, devid, dev_config)
1192 ## private:
1194 def restart_check(self):
1195 """Check if domain restart is OK.
1196 To prevent restart loops, raise an error if it is
1197 less than MINIMUM_RESTART_TIME seconds since the last restart.
1198 """
1199 tnow = time.time()
1200 if self.restart_time is not None:
1201 tdelta = tnow - self.restart_time
1202 if tdelta < self.MINIMUM_RESTART_TIME:
1203 self.restart_cancel()
1204 msg = 'VM %s restarting too fast' % self.info['name']
1205 log.error(msg)
1206 raise VmError(msg)
1207 self.restart_time = tnow
1208 self.restart_count += 1
1211 def restart(self, rename = False):
1212 """Restart the domain after it has exited.
1214 @param rename True if the old domain is to be renamed and preserved,
1215 False if it is to be destroyed.
1216 """
1218 # self.restart_check()
1220 config = self.sxpr()
1222 if self.readVm('xend/restart_in_progress'):
1223 log.error('Xend failed during restart of domain %d. '
1224 'Refusing to restart to avoid loops.',
1225 self.domid)
1226 self.destroy()
1227 return
1229 self.writeVm('xend/restart_in_progress', 'True')
1231 try:
1232 if rename:
1233 self.preserveForRestart()
1234 else:
1235 self.destroy()
1237 try:
1238 xd = get_component('xen.xend.XendDomain')
1239 new_dom = xd.domain_create(config)
1240 try:
1241 xc.domain_unpause(new_dom.getDomid())
1242 except:
1243 new_dom.destroy()
1244 raise
1245 except:
1246 log.exception('Failed to restart domain %d.', self.domid)
1247 finally:
1248 self.removeVm('xend/restart_in_progress')
1250 # self.configure_bootloader()
1251 # self.exportToDB()
1254 def preserveForRestart(self):
1255 """Preserve a domain that has been shut down, by giving it a new UUID,
1256 cloning the VM details, and giving it a new name. This allows us to
1257 keep this domain for debugging, but restart a new one in its place
1258 preserving the restart semantics (name and UUID preserved).
1259 """
1261 new_name = self.generateUniqueName()
1262 new_uuid = getUuid()
1263 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1264 self.info['name'], self.domid, self.uuid, new_name, new_uuid)
1265 self.release_devices()
1266 self.info['name'] = new_name
1267 self.uuid = new_uuid
1268 self.vmpath = VMROOT + new_uuid
1269 self.storeVmDetails()
1270 self.preserve()
1273 def preserve(self):
1274 log.info("Preserving dead domain %s (%d).", self.info['name'],
1275 self.domid)
1276 self.storeDom('xend/shutdown_completed', 'True')
1277 self.state_set(STATE_VM_TERMINATED)
1280 ## public:
1282 def renameUniquely(self):
1283 """Rename this domain so that it has a unique name. This is used by
1284 XendDomain to recover from non-uniqueness errors; we should never have
1285 allowed the system to reach this state in the first place."""
1286 new_name = self.generateUniqueName()
1288 log.error('Renaming %s (%d, %s) to %s', self.info['name'], self.domid,
1289 self.uuid, new_name)
1291 self.setName(new_name)
1294 # private:
1296 def generateUniqueName(self):
1297 n = 1
1298 while True:
1299 name = "%s-%d" % (self.info['name'], n)
1300 try:
1301 self.check_name(name)
1302 return name
1303 except VmError:
1304 n += 1
1307 def generateZombieName(self):
1308 n = 0
1309 name = ZOMBIE_PREFIX + self.info['name']
1310 while True:
1311 try:
1312 self.check_name(name)
1313 return name
1314 except VmError:
1315 n += 1
1316 name = "%s%d-%s" % (ZOMBIE_PREFIX, n, self.info['name'])
1319 def configure_bootloader(self):
1320 if not self.info['bootloader']:
1321 return
1322 # if we're restarting with a bootloader, we need to run it
1323 # FIXME: this assumes the disk is the first device and
1324 # that we're booting from the first disk
1325 blcfg = None
1326 # FIXME: this assumes that we want to use the first disk
1327 dev = sxp.child_value(self.config, "device")
1328 if dev:
1329 disk = sxp.child_value(dev, "uname")
1330 fn = blkdev_uname_to_file(disk)
1331 blcfg = bootloader(self.info['bootloader'], fn, 1,
1332 self.info['vcpus'])
1333 if blcfg is None:
1334 msg = "Had a bootloader specified, but can't find disk"
1335 log.error(msg)
1336 raise VmError(msg)
1337 self.config = sxp.merge(['vm', ['image', blcfg]], self.config)
1340 def configure(self):
1341 """Configure a vm.
1343 """
1344 self.configure_maxmem()
1345 self.create_devices()
1348 def configure_maxmem(self):
1349 if self.image:
1350 m = self.image.getDomainMemory(self.info['memory_KiB'])
1351 xc.domain_setmaxmem(self.domid, maxmem_kb = m)
1354 def vcpu_hotplug(self, vcpu, state):
1355 """Disable or enable VCPU in domain.
1356 """
1357 if vcpu > self.info['vcpus']:
1358 log.error("Invalid VCPU %d" % vcpu)
1359 return
1360 if int(state) == 0:
1361 self.info['vcpu_avail'] &= ~(1 << vcpu)
1362 availability = "offline"
1363 else:
1364 self.info['vcpu_avail'] &= (1 << vcpu)
1365 availability = "online"
1366 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
1367 self.storeDom("cpu/%d/availability" % vcpu, availability)
1370 def send_sysrq(self, key):
1371 asserts.isCharConvertible(key)
1373 self.storeDom("control/sysrq", '%c' % key)
1376 def initStoreConnection(self):
1377 ref = xc.init_store(self.store_channel)
1378 if ref and ref >= 0:
1379 self.setStoreRef(ref)
1380 try:
1381 IntroduceDomain(self.domid, ref, self.store_channel,
1382 self.dompath)
1383 except RuntimeError, ex:
1384 if ex.args[0] == errno.EISCONN:
1385 pass
1386 else:
1387 raise
1390 def dom0_enforce_vcpus(self):
1391 dom = 0
1392 # get max number of vcpus to use for dom0 from config
1393 target = int(xroot.get_dom0_vcpus())
1394 log.debug("number of vcpus to use is %d", target)
1396 # target = 0 means use all processors
1397 if target > 0:
1398 # count the number of online vcpus (cpu values in v2c map >= 0)
1399 vcpu_to_cpu = dom_get(dom)['vcpu_to_cpu']
1400 vcpus_online = len(filter(lambda x: x >= 0, vcpu_to_cpu))
1401 log.debug("found %d vcpus online", vcpus_online)
1403 # disable any extra vcpus that are online over the requested target
1404 for vcpu in range(target, vcpus_online):
1405 log.info("enforcement is disabling DOM%d VCPU%d", dom, vcpu)
1406 self.vcpu_hotplug(vcpu, 0)
1409 def infoIsSet(self, name):
1410 return name in self.info and self.info[name] is not None
1413 #============================================================================
1414 # Register device controllers and their device config types.
1416 """A map from device-class names to the subclass of DevController that
1417 implements the device control specific to that device-class."""
1418 controllerClasses = {}
1421 """A map of backend names and the corresponding flag."""
1422 backendFlags = {}
1425 def addControllerClass(device_class, backend_name, backend_flag, cls):
1426 """Register a subclass of DevController to handle the named device-class.
1428 @param backend_flag One of the SIF_XYZ_BE_DOMAIN constants, or None if
1429 no flag is to be set.
1430 """
1431 cls.deviceClass = device_class
1432 backendFlags[backend_name] = backend_flag
1433 controllerClasses[device_class] = cls
1436 from xen.xend.server import blkif, netif, tpmif, pciif, usbif
1437 addControllerClass('vbd', 'blkif', SIF_BLK_BE_DOMAIN, blkif.BlkifController)
1438 addControllerClass('vif', 'netif', SIF_NET_BE_DOMAIN, netif.NetifController)
1439 addControllerClass('vtpm', 'tpmif', SIF_TPM_BE_DOMAIN, tpmif.TPMifController)
1440 addControllerClass('pci', 'pciif', None, pciif.PciController)
1441 addControllerClass('usb', 'usbif', None, usbif.UsbifController)