direct-io.hg

view tools/python/xen/xend/XendDomainInfo.py @ 8500:dd5649730b32

Fix a couple of bogus dom0_op names:
setdomaininfo -> setvcpucontext
pincpudomain -> setvcpuaffinity

Signed-off-by: Keir Fraser <keir@xensource.com>
author kaf24@firebug.cl.cam.ac.uk
date Fri Jan 06 12:53:19 2006 +0100 (2006-01-06)
parents cd434888abdc
children 0bd023cf351e
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 import balloon
37 import image
38 import sxp
39 import uuid
40 import XendDomain
41 import XendRoot
43 from xen.xend.XendBootloader import bootloader
44 from xen.xend.XendError import XendError, VmError
46 from xen.xend.xenstore.xstransact import xstransact, complete
47 from xen.xend.xenstore.xsutil import GetDomainPath, IntroduceDomain
48 from xen.xend.xenstore.xswatch import xswatch
51 """Shutdown code for poweroff."""
52 DOMAIN_POWEROFF = 0
54 """Shutdown code for reboot."""
55 DOMAIN_REBOOT = 1
57 """Shutdown code for suspend."""
58 DOMAIN_SUSPEND = 2
60 """Shutdown code for crash."""
61 DOMAIN_CRASH = 3
63 """Shutdown code for halt."""
64 DOMAIN_HALT = 4
66 """Map shutdown codes to strings."""
67 shutdown_reasons = {
68 DOMAIN_POWEROFF: "poweroff",
69 DOMAIN_REBOOT : "reboot",
70 DOMAIN_SUSPEND : "suspend",
71 DOMAIN_CRASH : "crash",
72 DOMAIN_HALT : "halt"
73 }
75 restart_modes = [
76 "restart",
77 "destroy",
78 "preserve",
79 "rename-restart"
80 ]
82 STATE_DOM_OK = 1
83 STATE_DOM_SHUTDOWN = 2
85 SHUTDOWN_TIMEOUT = 30
87 ZOMBIE_PREFIX = 'Zombie-'
89 """Minimum time between domain restarts in seconds."""
90 MINIMUM_RESTART_TIME = 20
92 RESTART_IN_PROGRESS = 'xend/restart_in_progress'
95 xc = xen.lowlevel.xc.xc()
96 xroot = XendRoot.instance()
98 log = logging.getLogger("xend.XendDomainInfo")
99 #log.setLevel(logging.TRACE)
102 ##
103 # All parameters of VMs that may be configured on-the-fly, or at start-up.
104 #
105 VM_CONFIG_PARAMS = [
106 ('name', str),
107 ('on_poweroff', str),
108 ('on_reboot', str),
109 ('on_crash', str),
110 ]
113 ##
114 # Configuration entries that we expect to round-trip -- be read from the
115 # config file or xc, written to save-files (i.e. through sxpr), and reused as
116 # config on restart or restore, all without munging. Some configuration
117 # entries are munged for backwards compatibility reasons, or because they
118 # don't come out of xc in the same form as they are specified in the config
119 # file, so those are handled separately.
120 ROUNDTRIPPING_CONFIG_ENTRIES = [
121 ('uuid', str),
122 ('ssidref', int),
123 ('vcpus', int),
124 ('vcpu_avail', int),
125 ('cpu_weight', float),
126 ('memory', int),
127 ('maxmem', int),
128 ('bootloader', str),
129 ]
131 ROUNDTRIPPING_CONFIG_ENTRIES += VM_CONFIG_PARAMS
134 ##
135 # All entries written to the store. This is VM_CONFIG_PARAMS, plus those
136 # entries written to the store that cannot be reconfigured on-the-fly.
137 #
138 VM_STORE_ENTRIES = [
139 ('uuid', str),
140 ('ssidref', int),
141 ('vcpus', int),
142 ('vcpu_avail', int),
143 ('memory', int),
144 ('maxmem', int),
145 ('start_time', float),
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, priv)
231 except Exception, exn:
232 if priv:
233 log.warn(str(exn))
235 vm = XendDomainInfo(xeninfo, domid, dompath, True, priv)
236 vm.recreateDom()
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['cpus'] = get_cfg('cpus', str)
291 result['image'] = get_cfg('image')
293 try:
294 if result['image']:
295 v = sxp.child_value(result['image'], 'vcpus')
296 if v is not None and int(v) != result['vcpus']:
297 log.warn(('Image VCPUs setting overrides vcpus=%d elsewhere.'
298 ' Using %s VCPUs for VM %s.') %
299 (result['vcpus'], v, result['uuid']))
300 result['vcpus'] = int(v)
301 except TypeError, exn:
302 raise VmError(
303 'Invalid configuration setting: vcpus = %s: %s' %
304 (sxp.child_value(result['image'], 'vcpus', 1), str(exn)))
306 try:
307 # support legacy config files with 'cpu' parameter
308 # NB: prepending to list to support previous behavior
309 # where 'cpu' parameter pinned VCPU0.
310 if result['cpu']:
311 if result['cpus']:
312 result['cpus'] = "%s,%s" % (str(result['cpu']), result['cpus'])
313 else:
314 result['cpus'] = str(result['cpu'])
316 # convert 'cpus' string to list of ints
317 # 'cpus' supports a list of ranges (0-3), seperated by
318 # commas, and negation, (^1).
319 # Precedence is settled by order of the string:
320 # "0-3,^1" -> [0,2,3]
321 # "0-3,^1,1" -> [0,1,2,3]
322 if result['cpus']:
323 cpus = []
324 for c in result['cpus'].split(','):
325 if c.find('-') != -1:
326 (x,y) = c.split('-')
327 for i in range(int(x),int(y)+1):
328 cpus.append(int(i))
329 else:
330 # remove this element from the list
331 if c[0] == '^':
332 cpus = [x for x in cpus if x != int(c[1])]
333 else:
334 cpus.append(int(c))
336 result['cpus'] = cpus
338 except ValueError, exn:
339 raise VmError(
340 'Invalid configuration setting: cpus = %s: %s' %
341 (result['cpus'], exn))
343 result['backend'] = []
344 for c in sxp.children(config, 'backend'):
345 result['backend'].append(sxp.name(sxp.child0(c)))
347 result['device'] = []
348 for d in sxp.children(config, 'device'):
349 c = sxp.child0(d)
350 result['device'].append((sxp.name(c), c))
352 # Configuration option "restart" is deprecated. Parse it, but
353 # let on_xyz override it if they are present.
354 restart = get_cfg('restart')
355 if restart:
356 def handle_restart(event, val):
357 if result[event] is None:
358 result[event] = val
360 if restart == "onreboot":
361 handle_restart('on_poweroff', 'destroy')
362 handle_restart('on_reboot', 'restart')
363 handle_restart('on_crash', 'destroy')
364 elif restart == "always":
365 handle_restart('on_poweroff', 'restart')
366 handle_restart('on_reboot', 'restart')
367 handle_restart('on_crash', 'restart')
368 elif restart == "never":
369 handle_restart('on_poweroff', 'destroy')
370 handle_restart('on_reboot', 'destroy')
371 handle_restart('on_crash', 'destroy')
372 else:
373 log.warn("Ignoring malformed and deprecated config option "
374 "restart = %s", restart)
376 log.debug("parseConfig: result is %s", result)
377 return result
380 def domain_by_name(name):
381 return XendDomain.instance().domain_lookup_by_name_nr(name)
384 def shutdown_reason(code):
385 """Get a shutdown reason from a code.
387 @param code: shutdown code
388 @type code: int
389 @return: shutdown reason
390 @rtype: string
391 """
392 return shutdown_reasons.get(code, "?")
394 def dom_get(dom):
395 """Get info from xen for an existing domain.
397 @param dom: domain id
398 @return: info or None
399 """
400 try:
401 domlist = xc.domain_getinfo(dom, 1)
402 if domlist and dom == domlist[0]['dom']:
403 return domlist[0]
404 except Exception, err:
405 # ignore missing domain
406 log.trace("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
407 return None
410 class XendDomainInfo:
412 def __init__(self, info, domid = None, dompath = None, augment = False,
413 priv = False):
415 self.info = info
417 if not self.infoIsSet('uuid'):
418 self.info['uuid'] = uuid.toString(uuid.create())
420 if domid is not None:
421 self.domid = domid
422 elif 'dom' in info:
423 self.domid = int(info['dom'])
424 else:
425 self.domid = None
427 self.vmpath = XendDomain.VMROOT + self.info['uuid']
428 self.dompath = dompath
430 if augment:
431 self.augmentInfo(priv)
433 self.validateInfo()
435 self.image = None
437 self.store_port = None
438 self.store_mfn = None
439 self.console_port = None
440 self.console_mfn = None
442 self.vmWatch = None
444 self.state = STATE_DOM_OK
445 self.state_updated = threading.Condition()
446 self.refresh_shutdown_lock = threading.Condition()
449 ## private:
451 def readVMDetails(self, params):
452 """Read the specified parameters from the store.
453 """
454 try:
455 return self.gatherVm(*params)
456 except ValueError:
457 # One of the int/float entries in params has a corresponding store
458 # entry that is invalid. We recover, because older versions of
459 # Xend may have put the entry there (memory/target, for example),
460 # but this is in general a bad situation to have reached.
461 log.exception(
462 "Store corrupted at %s! Domain %d's configuration may be "
463 "affected.", self.vmpath, self.domid)
464 return []
467 def storeChanged(self, _):
468 log.trace("XendDomainInfo.storeChanged");
470 changed = False
472 def f(x, y):
473 if y is not None and self.info[x[0]] != y:
474 self.info[x[0]] = y
475 changed = True
477 map(f, VM_CONFIG_PARAMS, self.readVMDetails(VM_CONFIG_PARAMS))
479 im = self.readVm('image')
480 current_im = self.info['image']
481 if (im is not None and
482 (current_im is None or sxp.to_string(current_im) != im)):
483 self.info['image'] = sxp.from_string(im)
484 changed = True
486 if changed:
487 # Update the domain section of the store, as this contains some
488 # parameters derived from the VM configuration.
489 self.storeDomDetails()
491 return 1
494 def augmentInfo(self, priv):
495 """Augment self.info, as given to us through {@link #recreate}, with
496 values taken from the store. This recovers those values known to xend
497 but not to the hypervisor.
498 """
499 def useIfNeeded(name, val):
500 if not self.infoIsSet(name) and val is not None:
501 self.info[name] = val
503 if priv:
504 entries = VM_STORE_ENTRIES[:]
505 entries.remove(('memory', int))
506 entries.remove(('maxmem', int))
507 else:
508 entries = VM_STORE_ENTRIES
509 entries.append(('image', str))
511 map(lambda x, y: useIfNeeded(x[0], y), entries,
512 self.readVMDetails(entries))
514 device = []
515 for c in controllerClasses:
516 devconfig = self.getDeviceConfigurations(c)
517 if devconfig:
518 device.extend(map(lambda x: (c, x), devconfig))
519 useIfNeeded('device', device)
522 def validateInfo(self):
523 """Validate and normalise the info block. This has either been parsed
524 by parseConfig, or received from xc through recreate and augmented by
525 the current store contents.
526 """
527 def defaultInfo(name, val):
528 if not self.infoIsSet(name):
529 self.info[name] = val()
531 try:
532 defaultInfo('name', lambda: "Domain-%d" % self.domid)
533 defaultInfo('ssidref', lambda: 0)
534 defaultInfo('on_poweroff', lambda: "destroy")
535 defaultInfo('on_reboot', lambda: "restart")
536 defaultInfo('on_crash', lambda: "restart")
537 defaultInfo('cpu', lambda: None)
538 defaultInfo('cpus', lambda: [])
539 defaultInfo('cpu_weight', lambda: 1.0)
541 # some domains don't have a config file (e.g. dom0 )
542 # to set number of vcpus so we derive available cpus
543 # from max_vcpu_id which is present for running domains.
544 if not self.infoIsSet('vcpus') and self.infoIsSet('max_vcpu_id'):
545 avail = int(self.info['max_vcpu_id'])+1
546 else:
547 avail = int(1)
549 defaultInfo('vcpus', lambda: avail)
550 defaultInfo('online_vcpus', lambda: self.info['vcpus'])
551 defaultInfo('max_vcpu_id', lambda: self.info['vcpus']-1)
552 defaultInfo('vcpu_avail', lambda: (1 << self.info['vcpus']) - 1)
554 defaultInfo('memory', lambda: 0)
555 defaultInfo('maxmem', lambda: 0)
556 defaultInfo('bootloader', lambda: None)
557 defaultInfo('backend', lambda: [])
558 defaultInfo('device', lambda: [])
559 defaultInfo('image', lambda: None)
561 self.check_name(self.info['name'])
563 if isinstance(self.info['image'], str):
564 self.info['image'] = sxp.from_string(self.info['image'])
566 if self.info['memory'] == 0:
567 if self.infoIsSet('mem_kb'):
568 self.info['memory'] = (self.info['mem_kb'] + 1023) / 1024
570 if self.info['maxmem'] < self.info['memory']:
571 self.info['maxmem'] = self.info['memory']
573 for (n, c) in self.info['device']:
574 if not n or not c or n not in controllerClasses:
575 raise VmError('invalid device (%s, %s)' %
576 (str(n), str(c)))
578 for event in ['on_poweroff', 'on_reboot', 'on_crash']:
579 if self.info[event] not in restart_modes:
580 raise VmError('invalid restart event: %s = %s' %
581 (event, str(self.info[event])))
583 except KeyError, exn:
584 log.exception(exn)
585 raise VmError('Unspecified domain detail: %s' % exn)
588 def readVm(self, *args):
589 return xstransact.Read(self.vmpath, *args)
591 def writeVm(self, *args):
592 return xstransact.Write(self.vmpath, *args)
594 def removeVm(self, *args):
595 return xstransact.Remove(self.vmpath, *args)
597 def gatherVm(self, *args):
598 return xstransact.Gather(self.vmpath, *args)
601 ## public:
603 def storeVm(self, *args):
604 return xstransact.Store(self.vmpath, *args)
607 ## private:
609 def readDom(self, *args):
610 return xstransact.Read(self.dompath, *args)
612 def writeDom(self, *args):
613 return xstransact.Write(self.dompath, *args)
616 ## public:
618 def removeDom(self, *args):
619 return xstransact.Remove(self.dompath, *args)
621 def recreateDom(self):
622 complete(self.dompath, lambda t: self._recreateDom(t))
624 def _recreateDom(self, t):
625 t.remove()
626 t.mkdir()
627 t.set_permissions({ 'dom' : self.domid })
630 ## private:
632 def storeDom(self, *args):
633 return xstransact.Store(self.dompath, *args)
636 ## public:
638 def completeRestore(self, store_mfn, console_mfn):
640 log.debug("XendDomainInfo.completeRestore")
642 self.store_mfn = store_mfn
643 self.console_mfn = console_mfn
645 self.introduceDomain()
646 self.storeDomDetails()
647 self.registerWatch()
648 self.refreshShutdown()
650 log.debug("XendDomainInfo.completeRestore done")
653 def storeVmDetails(self):
654 to_store = {}
656 for k in VM_STORE_ENTRIES:
657 if self.infoIsSet(k[0]):
658 to_store[k[0]] = str(self.info[k[0]])
660 if self.infoIsSet('image'):
661 to_store['image'] = sxp.to_string(self.info['image'])
663 log.debug("Storing VM details: %s", to_store)
665 self.writeVm(to_store)
668 def storeDomDetails(self):
669 to_store = {
670 'domid': str(self.domid),
671 'vm': self.vmpath,
672 'name': self.info['name'],
673 'console/limit': str(xroot.get_console_limit() * 1024),
674 'memory/target': str(self.info['memory'] * 1024)
675 }
677 def f(n, v):
678 if v is not None:
679 to_store[n] = str(v)
681 f('console/port', self.console_port)
682 f('console/ring-ref', self.console_mfn)
683 f('store/port', self.store_port)
684 f('store/ring-ref', self.store_mfn)
686 to_store.update(self.vcpuDomDetails())
688 log.debug("Storing domain details: %s", to_store)
690 self.writeDom(to_store)
693 ## private:
695 def vcpuDomDetails(self):
696 def availability(n):
697 if self.info['vcpu_avail'] & (1 << n):
698 return 'online'
699 else:
700 return 'offline'
702 result = {}
703 for v in range(0, self.info['vcpus']):
704 result["cpu/%d/availability" % v] = availability(v)
705 return result
708 ## public:
710 def registerWatch(self):
711 """Register a watch on this VM's entries in the store, so that
712 when they are changed externally, we keep up to date. This should
713 only be called by {@link #create}, {@link #recreate}, or {@link
714 #restore}, once the domain's details have been written, but before the
715 new instance is returned."""
716 self.vmWatch = xswatch(self.vmpath, self.storeChanged)
719 def getDomid(self):
720 return self.domid
722 def setName(self, name):
723 self.check_name(name)
724 self.info['name'] = name
725 self.storeVm("name", name)
727 def getName(self):
728 return self.info['name']
730 def getDomainPath(self):
731 return self.dompath
734 def getStorePort(self):
735 """For use only by image.py and XendCheckpoint.py."""
736 return self.store_port
739 def getConsolePort(self):
740 """For use only by image.py and XendCheckpoint.py"""
741 return self.console_port
744 def getVCpuCount(self):
745 return self.info['vcpus']
748 def setVCpuCount(self, vcpus):
749 self.info['vcpu_avail'] = (1 << vcpus) - 1
750 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
751 self.writeDom(self.vcpuDomDetails())
754 def getSsidref(self):
755 return self.info['ssidref']
757 def getMemoryTarget(self):
758 """Get this domain's target memory size, in KB."""
759 return self.info['memory'] * 1024
762 def refreshShutdown(self, xeninfo = None):
763 # If set at the end of this method, a restart is required, with the
764 # given reason. This restart has to be done out of the scope of
765 # refresh_shutdown_lock.
766 restart_reason = None
768 self.refresh_shutdown_lock.acquire()
769 try:
770 if xeninfo is None:
771 xeninfo = dom_get(self.domid)
772 if xeninfo is None:
773 # The domain no longer exists. This will occur if we have
774 # scheduled a timer to check for shutdown timeouts and the
775 # shutdown succeeded. It will also occur if someone
776 # destroys a domain beneath us. We clean up the domain,
777 # just in case, but we can't clean up the VM, because that
778 # VM may have migrated to a different domain on this
779 # machine.
780 self.cleanupDomain()
781 return
783 if xeninfo['dying']:
784 # Dying means that a domain has been destroyed, but has not
785 # yet been cleaned up by Xen. This state could persist
786 # indefinitely if, for example, another domain has some of its
787 # pages mapped. We might like to diagnose this problem in the
788 # future, but for now all we do is make sure that it's not us
789 # holding the pages, by calling cleanupDomain. We can't
790 # clean up the VM, as above.
791 self.cleanupDomain()
792 return
794 elif xeninfo['crashed']:
795 if self.readDom('xend/shutdown_completed'):
796 # We've seen this shutdown already, but we are preserving
797 # the domain for debugging. Leave it alone.
798 return
800 log.warn('Domain has crashed: name=%s id=%d.',
801 self.info['name'], self.domid)
803 if xroot.get_enable_dump():
804 self.dumpCore()
806 restart_reason = 'crash'
808 elif xeninfo['shutdown']:
809 if self.readDom('xend/shutdown_completed'):
810 # We've seen this shutdown already, but we are preserving
811 # the domain for debugging. Leave it alone.
812 return
814 else:
815 reason = shutdown_reason(xeninfo['shutdown_reason'])
817 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
818 self.info['name'], self.domid, reason)
820 self.clearRestart()
822 if reason == 'suspend':
823 self.state_set(STATE_DOM_SHUTDOWN)
824 # Don't destroy the domain. XendCheckpoint will do
825 # this once it has finished. However, stop watching
826 # the VM path now, otherwise we will end up with one
827 # watch for the old domain, and one for the new.
828 self.unwatchVm()
829 elif reason in ['poweroff', 'reboot']:
830 restart_reason = reason
831 else:
832 self.destroy()
834 elif self.dompath is None:
835 # We have yet to manage to call introduceDomain on this
836 # domain. This can happen if a restore is in progress, or has
837 # failed. Ignore this domain.
838 pass
839 else:
840 # Domain is alive. If we are shutting it down, then check
841 # the timeout on that, and destroy it if necessary.
843 sst = self.readDom('xend/shutdown_start_time')
844 if sst:
845 sst = float(sst)
846 timeout = SHUTDOWN_TIMEOUT - time.time() + sst
847 if timeout < 0:
848 log.info(
849 "Domain shutdown timeout expired: name=%s id=%s",
850 self.info['name'], self.domid)
851 self.destroy()
852 else:
853 log.debug(
854 "Scheduling refreshShutdown on domain %d in %ds.",
855 self.domid, timeout)
856 threading.Timer(timeout, self.refreshShutdown).start()
857 finally:
858 self.refresh_shutdown_lock.release()
860 if restart_reason:
861 self.maybeRestart(restart_reason)
864 def shutdown(self, reason):
865 if not reason in shutdown_reasons.values():
866 raise XendError('Invalid reason: %s' % reason)
867 self.storeDom("control/shutdown", reason)
868 if reason != 'suspend':
869 self.storeDom('xend/shutdown_start_time', time.time())
872 ## private:
874 def clearRestart(self):
875 self.removeDom("xend/shutdown_start_time")
878 def maybeRestart(self, reason):
879 # Dispatch to the correct method based upon the configured on_{reason}
880 # behaviour.
881 {"destroy" : self.destroy,
882 "restart" : self.restart,
883 "preserve" : self.preserve,
884 "rename-restart" : self.renameRestart}[self.info['on_' + reason]]()
887 def renameRestart(self):
888 self.restart(True)
891 def dumpCore(self):
892 """Create a core dump for this domain. Nothrow guarantee."""
894 try:
895 corefile = "/var/xen/dump/%s.%s.core" % (self.info['name'],
896 self.domid)
897 xc.domain_dumpcore(self.domid, corefile)
899 except:
900 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
901 self.domid, self.info['name'])
904 ## public:
906 def setMemoryTarget(self, target):
907 """Set the memory target of this domain.
908 @param target In MiB.
909 """
910 log.debug("Setting memory target of domain %s (%d) to %d MiB.",
911 self.info['name'], self.domid, target)
913 self.info['memory'] = target
914 self.storeVm("memory", target)
915 self.storeDom("memory/target", target << 10)
918 def update(self, info = None):
919 """Update with info from xc.domain_getinfo().
920 """
922 log.trace("XendDomainInfo.update(%s) on domain %d", info, self.domid)
924 if not info:
925 info = dom_get(self.domid)
926 if not info:
927 return
929 self.info.update(info)
930 self.validateInfo()
931 self.refreshShutdown(info)
933 log.trace("XendDomainInfo.update done on domain %d: %s", self.domid,
934 self.info)
937 ## private:
939 def state_set(self, state):
940 self.state_updated.acquire()
941 try:
942 if self.state != state:
943 self.state = state
944 self.state_updated.notifyAll()
945 finally:
946 self.state_updated.release()
949 ## public:
951 def waitForShutdown(self):
952 self.state_updated.acquire()
953 try:
954 while self.state == STATE_DOM_OK:
955 self.state_updated.wait()
956 finally:
957 self.state_updated.release()
960 def __str__(self):
961 s = "<domain"
962 s += " id=" + str(self.domid)
963 s += " name=" + self.info['name']
964 s += " memory=" + str(self.info['memory'])
965 s += " ssidref=" + str(self.info['ssidref'])
966 s += ">"
967 return s
969 __repr__ = __str__
972 ## private:
974 def createDevice(self, deviceClass, devconfig):
975 return self.getDeviceController(deviceClass).createDevice(devconfig)
978 def waitForDevices_(self, deviceClass):
979 return self.getDeviceController(deviceClass).waitForDevices()
982 def waitForDevice(self, deviceClass, devid):
983 return self.getDeviceController(deviceClass).waitForDevice(devid)
986 def reconfigureDevice(self, deviceClass, devid, devconfig):
987 return self.getDeviceController(deviceClass).reconfigureDevice(
988 devid, devconfig)
991 ## public:
993 def destroyDevice(self, deviceClass, devid):
994 return self.getDeviceController(deviceClass).destroyDevice(devid)
997 def getDeviceSxprs(self, deviceClass):
998 return self.getDeviceController(deviceClass).sxprs()
1001 ## private:
1003 def getDeviceConfigurations(self, deviceClass):
1004 return self.getDeviceController(deviceClass).configurations()
1007 def getDeviceController(self, name):
1008 if name not in controllerClasses:
1009 raise XendError("unknown device type: " + str(name))
1011 return controllerClasses[name](self)
1014 ## public:
1016 def sxpr(self):
1017 sxpr = ['domain',
1018 ['domid', self.domid]]
1020 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
1021 if self.infoIsSet(e[0]):
1022 sxpr.append([e[0], self.info[e[0]]])
1024 if self.infoIsSet('image'):
1025 sxpr.append(['image', self.info['image']])
1027 for cls in controllerClasses:
1028 for config in self.getDeviceConfigurations(cls):
1029 sxpr.append(['device', config])
1031 def stateChar(name):
1032 if name in self.info:
1033 if self.info[name]:
1034 return name[0]
1035 else:
1036 return '-'
1037 else:
1038 return '?'
1040 state = reduce(
1041 lambda x, y: x + y,
1042 map(stateChar,
1043 ['running', 'blocked', 'paused', 'shutdown', 'crashed',
1044 'dying']))
1046 sxpr.append(['state', state])
1047 if self.infoIsSet('shutdown'):
1048 reason = shutdown_reason(self.info['shutdown_reason'])
1049 sxpr.append(['shutdown_reason', reason])
1050 if self.infoIsSet('cpu_time'):
1051 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
1052 sxpr.append(['online_vcpus', self.info['online_vcpus']])
1054 if self.infoIsSet('start_time'):
1055 up_time = time.time() - self.info['start_time']
1056 sxpr.append(['up_time', str(up_time) ])
1057 sxpr.append(['start_time', str(self.info['start_time']) ])
1059 if self.store_mfn:
1060 sxpr.append(['store_mfn', self.store_mfn])
1061 if self.console_mfn:
1062 sxpr.append(['console_mfn', self.console_mfn])
1064 return sxpr
1067 def getVCPUInfo(self):
1068 try:
1069 # We include the domain name and ID, to help xm.
1070 sxpr = ['domain',
1071 ['domid', self.domid],
1072 ['name', self.info['name']],
1073 ['vcpu_count', self.info['online_vcpus']]]
1075 for i in range(0, self.info['max_vcpu_id']+1):
1076 info = xc.vcpu_getinfo(self.domid, i)
1078 sxpr.append(['vcpu',
1079 ['number', i],
1080 ['online', info['online']],
1081 ['blocked', info['blocked']],
1082 ['running', info['running']],
1083 ['cpu_time', info['cpu_time'] / 1e9],
1084 ['cpu', info['cpu']],
1085 ['cpumap', info['cpumap']]])
1087 return sxpr
1089 except RuntimeError, exn:
1090 raise XendError(str(exn))
1093 ## private:
1095 def check_name(self, name):
1096 """Check if a vm name is valid. Valid names contain alphabetic characters,
1097 digits, or characters in '_-.:/+'.
1098 The same name cannot be used for more than one vm at the same time.
1100 @param name: name
1101 @raise: VmError if invalid
1102 """
1103 if name is None or name == '':
1104 raise VmError('missing vm name')
1105 for c in name:
1106 if c in string.digits: continue
1107 if c in '_-.:/+': continue
1108 if c in string.ascii_letters: continue
1109 raise VmError('invalid vm name')
1111 dominfo = domain_by_name(name)
1112 if not dominfo:
1113 return
1114 if self.domid is None:
1115 raise VmError("VM name '%s' already in use by domain %d" %
1116 (name, dominfo.domid))
1117 if dominfo.domid != self.domid:
1118 raise VmError("VM name '%s' is used in both domains %d and %d" %
1119 (name, self.domid, dominfo.domid))
1122 def construct(self):
1123 """Construct the domain.
1125 @raise: VmError on error
1126 """
1128 log.debug('XendDomainInfo.construct: %s %s',
1129 self.domid,
1130 self.info['ssidref'])
1132 self.domid = xc.domain_create(
1133 dom = 0, ssidref = self.info['ssidref'],
1134 handle = uuid.fromString(self.info['uuid']))
1136 if self.domid < 0:
1137 raise VmError('Creating domain failed: name=%s' %
1138 self.info['name'])
1140 self.dompath = GetDomainPath(self.domid)
1142 self.recreateDom()
1144 # Set maximum number of vcpus in domain
1145 xc.domain_max_vcpus(self.domid, int(self.info['vcpus']))
1148 def introduceDomain(self):
1149 assert self.domid is not None
1150 assert self.store_mfn is not None
1151 assert self.store_port is not None
1153 try:
1154 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1155 except RuntimeError, exn:
1156 raise XendError(str(exn))
1159 def initDomain(self):
1160 log.debug('XendDomainInfo.initDomain: %s %s',
1161 self.domid,
1162 self.info['cpu_weight'])
1164 if not self.infoIsSet('image'):
1165 raise VmError('Missing image in configuration')
1167 try:
1168 self.image = image.create(self,
1169 self.info['image'],
1170 self.info['device'])
1172 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1174 # repin domain vcpus if a restricted cpus list is provided
1175 # this is done prior to memory allocation to aide in memory
1176 # distribution for NUMA systems.
1177 cpus = self.info['cpus']
1178 if cpus is not None and len(cpus) > 0:
1179 for v in range(0, self.info['max_vcpu_id']+1):
1180 # pincpu takes a list of ints
1181 cpu = [ int( cpus[v % len(cpus)] ) ]
1182 xc.vcpu_setaffinity(self.domid, v, cpu)
1184 m = self.image.getDomainMemory(self.info['memory'] * 1024)
1185 balloon.free(m)
1186 xc.domain_setmaxmem(self.domid, m)
1187 xc.domain_memory_increase_reservation(self.domid, m, 0, 0)
1189 self.createChannels()
1191 channel_details = self.image.createImage()
1193 self.store_mfn = channel_details['store_mfn']
1194 if 'console_mfn' in channel_details:
1195 self.console_mfn = channel_details['console_mfn']
1197 self.introduceDomain()
1199 self.createDevices()
1201 if self.info['bootloader']:
1202 self.image.cleanupBootloading()
1204 self.info['start_time'] = time.time()
1206 except RuntimeError, exn:
1207 raise VmError(str(exn))
1210 ## public:
1212 def cleanupDomain(self):
1213 """Cleanup domain resources; release devices. Idempotent. Nothrow
1214 guarantee."""
1216 self.release_devices()
1218 if self.image:
1219 try:
1220 self.image.destroy()
1221 except:
1222 log.exception(
1223 "XendDomainInfo.cleanup: image.destroy() failed.")
1224 self.image = None
1226 try:
1227 self.removeDom()
1228 except:
1229 log.exception("Removing domain path failed.")
1231 try:
1232 if not self.info['name'].startswith(ZOMBIE_PREFIX):
1233 self.info['name'] = ZOMBIE_PREFIX + self.info['name']
1234 except:
1235 log.exception("Renaming Zombie failed.")
1237 self.state_set(STATE_DOM_SHUTDOWN)
1240 def cleanupVm(self):
1241 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1243 self.unwatchVm()
1245 try:
1246 self.removeVm()
1247 except:
1248 log.exception("Removing VM path failed.")
1251 ## private:
1253 def unwatchVm(self):
1254 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1255 guarantee."""
1257 try:
1258 try:
1259 if self.vmWatch:
1260 self.vmWatch.unwatch()
1261 finally:
1262 self.vmWatch = None
1263 except:
1264 log.exception("Unwatching VM path failed.")
1267 ## public:
1269 def destroy(self):
1270 """Cleanup VM and destroy domain. Nothrow guarantee."""
1272 log.debug("XendDomainInfo.destroy: domid=%s", self.domid)
1274 self.cleanupVm()
1275 if self.dompath is not None:
1276 self.destroyDomain()
1279 def destroyDomain(self):
1280 log.debug("XendDomainInfo.destroyDomain(%s)", self.domid)
1282 try:
1283 if self.domid is not None:
1284 xc.domain_destroy(self.domid)
1285 except:
1286 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1288 self.cleanupDomain()
1291 ## private:
1293 def release_devices(self):
1294 """Release all domain's devices. Nothrow guarantee."""
1296 while True:
1297 t = xstransact("%s/device" % self.dompath)
1298 for n in controllerClasses.keys():
1299 for d in t.list(n):
1300 try:
1301 t.remove(d)
1302 except:
1303 # Log and swallow any exceptions in removal --
1304 # there's nothing more we can do.
1305 log.exception(
1306 "Device release failed: %s; %s; %s",
1307 self.info['name'], n, d)
1308 if t.commit():
1309 break
1312 def createChannels(self):
1313 """Create the channels to the domain.
1314 """
1315 self.store_port = self.createChannel()
1316 self.console_port = self.createChannel()
1319 def createChannel(self):
1320 """Create an event channel to the domain.
1321 """
1322 try:
1323 return xc.evtchn_alloc_unbound(dom=self.domid, remote_dom=0)
1324 except:
1325 log.exception("Exception in alloc_unbound(%d)", self.domid)
1326 raise
1329 ## public:
1331 def createDevices(self):
1332 """Create the devices for a vm.
1334 @raise: VmError for invalid devices
1335 """
1337 for (n, c) in self.info['device']:
1338 self.createDevice(n, c)
1340 if self.image:
1341 self.image.createDeviceModel()
1344 def waitForDevices(self):
1345 """Wait for this domain's configured devices to connect.
1347 @raise: VmError if any device fails to initialise.
1348 """
1349 for c in controllerClasses:
1350 self.waitForDevices_(c)
1353 def device_create(self, dev_config):
1354 """Create a new device.
1356 @param dev_config: device configuration
1357 """
1358 dev_type = sxp.name(dev_config)
1359 devid = self.createDevice(dev_type, dev_config)
1360 self.waitForDevice(dev_type, devid)
1361 self.info['device'].append((dev_type, dev_config))
1362 return self.getDeviceController(dev_type).sxpr(devid)
1365 def device_configure(self, dev_config, devid):
1366 """Configure an existing device.
1367 @param dev_config: device configuration
1368 @param devid: device id
1369 """
1370 deviceClass = sxp.name(dev_config)
1371 self.reconfigureDevice(deviceClass, devid, dev_config)
1374 def pause(self):
1375 xc.domain_pause(self.domid)
1378 def unpause(self):
1379 xc.domain_unpause(self.domid)
1382 ## private:
1384 def restart(self, rename = False):
1385 """Restart the domain after it has exited.
1387 @param rename True if the old domain is to be renamed and preserved,
1388 False if it is to be destroyed.
1389 """
1391 self.configure_bootloader()
1392 config = self.sxpr()
1394 if self.readVm(RESTART_IN_PROGRESS):
1395 log.error('Xend failed during restart of domain %d. '
1396 'Refusing to restart to avoid loops.',
1397 self.domid)
1398 self.destroy()
1399 return
1401 self.writeVm(RESTART_IN_PROGRESS, 'True')
1403 now = time.time()
1404 rst = self.readVm('xend/previous_restart_time')
1405 if rst:
1406 rst = float(rst)
1407 timeout = now - rst
1408 if timeout < MINIMUM_RESTART_TIME:
1409 log.error(
1410 'VM %s restarting too fast (%f seconds since the last '
1411 'restart). Refusing to restart to avoid loops.',
1412 self.info['name'], timeout)
1413 self.destroy()
1414 return
1416 self.writeVm('xend/previous_restart_time', str(now))
1418 try:
1419 if rename:
1420 self.preserveForRestart()
1421 else:
1422 self.unwatchVm()
1423 self.destroyDomain()
1425 # new_dom's VM will be the same as this domain's VM, except where
1426 # the rename flag has instructed us to call preserveForRestart.
1427 # In that case, it is important that we remove the
1428 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1429 # once the new one is available.
1431 new_dom = None
1432 try:
1433 new_dom = XendDomain.instance().domain_create(config)
1434 new_dom.unpause()
1435 new_dom.removeVm(RESTART_IN_PROGRESS)
1436 except:
1437 if new_dom:
1438 new_dom.removeVm(RESTART_IN_PROGRESS)
1439 new_dom.destroy()
1440 else:
1441 self.removeVm(RESTART_IN_PROGRESS)
1442 raise
1443 except:
1444 log.exception('Failed to restart domain %d.', self.domid)
1447 def preserveForRestart(self):
1448 """Preserve a domain that has been shut down, by giving it a new UUID,
1449 cloning the VM details, and giving it a new name. This allows us to
1450 keep this domain for debugging, but restart a new one in its place
1451 preserving the restart semantics (name and UUID preserved).
1452 """
1454 new_name = self.generateUniqueName()
1455 new_uuid = uuid.toString(uuid.create())
1456 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1457 self.info['name'], self.domid, self.info['uuid'],
1458 new_name, new_uuid)
1459 self.unwatchVm()
1460 self.release_devices()
1461 self.info['name'] = new_name
1462 self.info['uuid'] = new_uuid
1463 self.vmpath = XendDomain.VMROOT + new_uuid
1464 self.storeVmDetails()
1465 self.preserve()
1468 def preserve(self):
1469 log.info("Preserving dead domain %s (%d).", self.info['name'],
1470 self.domid)
1471 self.unwatchVm()
1472 self.storeDom('xend/shutdown_completed', 'True')
1473 self.state_set(STATE_DOM_SHUTDOWN)
1476 # private:
1478 def generateUniqueName(self):
1479 n = 1
1480 while True:
1481 name = "%s-%d" % (self.info['name'], n)
1482 try:
1483 self.check_name(name)
1484 return name
1485 except VmError:
1486 n += 1
1489 def configure_bootloader(self):
1490 if not self.info['bootloader']:
1491 return
1492 # if we're restarting with a bootloader, we need to run it
1493 # FIXME: this assumes the disk is the first device and
1494 # that we're booting from the first disk
1495 blcfg = None
1496 config = self.sxpr()
1497 # FIXME: this assumes that we want to use the first disk
1498 dev = sxp.child_value(config, "device")
1499 if dev:
1500 disk = sxp.child_value(dev, "uname")
1501 fn = blkdev_uname_to_file(disk)
1502 blcfg = bootloader(self.info['bootloader'], fn, 1,
1503 self.info['vcpus'])
1504 if blcfg is None:
1505 msg = "Had a bootloader specified, but can't find disk"
1506 log.error(msg)
1507 raise VmError(msg)
1508 self.info['image'] = sxp.to_string(blcfg)
1511 def send_sysrq(self, key):
1512 asserts.isCharConvertible(key)
1514 self.storeDom("control/sysrq", '%c' % key)
1517 def infoIsSet(self, name):
1518 return name in self.info and self.info[name] is not None
1521 #============================================================================
1522 # Register device controllers and their device config types.
1524 """A map from device-class names to the subclass of DevController that
1525 implements the device control specific to that device-class."""
1526 controllerClasses = {}
1528 def addControllerClass(device_class, cls):
1529 """Register a subclass of DevController to handle the named device-class.
1530 """
1531 cls.deviceClass = device_class
1532 controllerClasses[device_class] = cls
1535 from xen.xend.server import blkif, netif, tpmif, pciif, iopif, usbif
1536 addControllerClass('vbd', blkif.BlkifController)
1537 addControllerClass('vif', netif.NetifController)
1538 addControllerClass('vtpm', tpmif.TPMifController)
1539 addControllerClass('pci', pciif.PciController)
1540 addControllerClass('ioports', iopif.IOPortsController)
1541 addControllerClass('usb', usbif.UsbifController)