ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 7074:63f06da6c5b0

Remove the domain and VM paths from the store when destroying a domain. This
goes a long way towards fixing the problem of stale entries in the store.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author emellor@ewan
date Tue Sep 27 14:36:58 2005 +0100 (2005-09-27)
parents 9ff1bea68d51
children a8ed2f186c23
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.blkif import blkdev_uname_to_file
35 from xen.xend.server.channel import EventChannel
37 from xen.xend import image
38 from xen.xend import scheduler
39 from xen.xend import sxp
40 from xen.xend import XendRoot
41 from xen.xend.XendBootloader import bootloader
42 from xen.xend.XendLogging import log
43 from xen.xend.XendError import XendError, VmError
44 from xen.xend.XendRoot import get_component
46 from xen.xend.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 """Map shutdown codes to strings."""
63 shutdown_reasons = {
64 DOMAIN_POWEROFF: "poweroff",
65 DOMAIN_REBOOT : "reboot",
66 DOMAIN_SUSPEND : "suspend",
67 DOMAIN_CRASH : "crash",
68 }
70 RESTART_ALWAYS = 'always'
71 RESTART_ONREBOOT = 'onreboot'
72 RESTART_NEVER = 'never'
74 restart_modes = [
75 RESTART_ALWAYS,
76 RESTART_ONREBOOT,
77 RESTART_NEVER,
78 ]
80 STATE_VM_OK = "ok"
81 STATE_VM_TERMINATED = "terminated"
82 STATE_VM_SUSPENDED = "suspended"
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 = '/domain'
98 VMROOT = '/domain'
101 xc = xen.lowlevel.xc.new()
102 xroot = XendRoot.instance()
105 ## Configuration entries that we expect to round-trip -- be read from the
106 # config file or xc, written to save-files (i.e. through sxpr), and reused as
107 # config on restart or restore, all without munging. Some configuration
108 # entries are munged for backwards compatibility reasons, or because they
109 # don't come out of xc in the same form as they are specified in the config
110 # file, so those are handled separately.
111 ROUNDTRIPPING_CONFIG_ENTRIES = [
112 ('name', str),
113 ('ssidref', int),
114 ('cpu_weight', float),
115 ('bootloader', str)
116 ]
119 def domain_exists(name):
120 # See comment in XendDomain constructor.
121 xd = get_component('xen.xend.XendDomain')
122 return xd.domain_lookup_by_name(name)
124 def shutdown_reason(code):
125 """Get a shutdown reason from a code.
127 @param code: shutdown code
128 @type code: int
129 @return: shutdown reason
130 @rtype: string
131 """
132 return shutdown_reasons.get(code, "?")
134 def dom_get(dom):
135 """Get info from xen for an existing domain.
137 @param dom: domain id
138 @return: info or None
139 """
140 try:
141 domlist = xc.domain_getinfo(dom, 1)
142 if domlist and dom == domlist[0]['dom']:
143 return domlist[0]
144 except Exception, err:
145 # ignore missing domain
146 log.exception("domain_getinfo(%d) failed, ignoring", dom)
147 return None
149 class XendDomainInfo:
150 """Virtual machine object."""
152 """Minimum time between domain restarts in seconds.
153 """
154 MINIMUM_RESTART_TIME = 20
157 def create(cls, dompath, config):
158 """Create a VM from a configuration.
160 @param dompath: The path to all domain information
161 @param config configuration
162 @raise: VmError for invalid configuration
163 """
165 log.debug("XendDomainInfo.create(%s, ...)", dompath)
167 vm = cls(getUuid(), dompath, cls.parseConfig(config))
168 vm.construct()
169 vm.refreshShutdown()
170 return vm
172 create = classmethod(create)
175 def recreate(cls, xeninfo):
176 """Create the VM object for an existing domain."""
178 log.debug("XendDomainInfo.recreate(%s)", xeninfo)
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 dompath = "/".join(dompath.split("/")[0:-1])
196 except Exception, exn:
197 log.warn(str(exn))
198 dompath = DOMROOT
199 uuid = getUuid()
201 log.info("Recreating domain %d, uuid %s", domid, uuid)
203 vm = cls(uuid, dompath, xeninfo, domid, True)
204 vm.refreshShutdown(xeninfo)
205 return vm
207 recreate = classmethod(recreate)
210 def restore(cls, dompath, config, uuid = None):
211 """Create a domain and a VM object to do a restore.
213 @param dompath: The path to all domain information
214 @param config: domain configuration
215 @param uuid: uuid to use
216 """
218 log.debug("XendDomainInfo.restore(%s, %s, %s)", dompath, config, uuid)
220 if not uuid:
221 uuid = getUuid()
223 try:
224 ssidref = int(sxp.child_value(config, 'ssidref'))
225 except TypeError, exn:
226 raise VmError('Invalid ssidref in config: %s' % exn)
228 vm = cls(uuid, dompath, cls.parseConfig(config),
229 xc.domain_create(ssidref = ssidref))
230 vm.create_channel()
231 vm.configure()
232 vm.exportToDB()
233 vm.refreshShutdown()
234 return vm
236 restore = classmethod(restore)
239 def parseConfig(cls, 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" % str(config))
256 result = {}
258 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
259 result[e[0]] = get_cfg(e[0], e[1])
261 result['memory'] = get_cfg('memory', int)
262 result['mem_kb'] = get_cfg('mem_kb', int)
263 result['maxmem'] = get_cfg('maxmem', int)
264 result['maxmem_kb'] = get_cfg('maxmem_kb', int)
265 result['cpu'] = get_cfg('cpu', int)
266 result['restart_mode'] = get_cfg('restart')
267 result['image'] = get_cfg('image')
269 try:
270 if result['image']:
271 result['vcpus'] = int(sxp.child_value(result['image'],
272 'vcpus', 1))
273 else:
274 result['vcpus'] = 1
275 except TypeError, exn:
276 raise VmError(
277 'Invalid configuration setting: vcpus = %s: %s' %
278 (sxp.child_value(result['image'], 'vcpus', 1), str(exn)))
280 result['backend'] = []
281 for c in sxp.children(config, 'backend'):
282 result['backend'].append(sxp.name(sxp.child0(c)))
284 result['device'] = []
285 for d in sxp.children(config, 'device'):
286 c = sxp.child0(d)
287 result['device'].append((sxp.name(c), c))
289 log.debug("parseConfig: result is %s" % str(result))
290 return result
293 parseConfig = classmethod(parseConfig)
296 def __init__(self, uuid, parentpath, info, domid = None, augment = False):
298 self.uuid = uuid
299 self.info = info
301 self.path = parentpath + "/" + uuid
303 if domid:
304 self.domid = domid
305 elif 'dom' in info:
306 self.domid = int(info['dom'])
307 else:
308 self.domid = None
310 if augment:
311 self.augmentInfo()
313 self.validateInfo()
315 self.image = None
317 self.store_channel = None
318 self.store_mfn = None
319 self.console_channel = None
320 self.console_mfn = None
322 self.state = STATE_VM_OK
323 self.state_updated = threading.Condition()
325 self.writeVm("uuid", self.uuid)
326 self.storeDom("vm", self.path)
329 def augmentInfo(self):
330 """Augment self.info, as given to us through {@link #recreate}, with
331 values taken from the store. This recovers those values known to xend
332 but not to the hypervisor.
333 """
334 def useIfNeeded(name, val):
335 if not self.infoIsSet(name) and val is not None:
336 self.info[name] = val
338 params = (("name", str),
339 ("restart-mode", str),
340 ("image", str),
341 ("start-time", float))
343 from_store = self.gatherVm(*params)
345 map(lambda x, y: useIfNeeded(x[0], y), params, from_store)
348 def validateInfo(self):
349 """Validate and normalise the info block. This has either been parsed
350 by parseConfig, or received from xc through recreate.
351 """
352 def defaultInfo(name, val):
353 if not self.infoIsSet(name):
354 self.info[name] = val()
356 try:
357 defaultInfo('name', lambda: "Domain-%d" % self.domid)
358 defaultInfo('ssidref', lambda: 0)
359 defaultInfo('restart_mode', lambda: RESTART_ONREBOOT)
360 defaultInfo('cpu', lambda: None)
361 defaultInfo('cpu_weight', lambda: 1.0)
362 defaultInfo('bootloader', lambda: None)
363 defaultInfo('backend', lambda: [])
364 defaultInfo('device', lambda: [])
365 defaultInfo('image', lambda: None)
367 self.check_name(self.info['name'])
369 if isinstance(self.info['image'], str):
370 self.info['image'] = sxp.from_string(self.info['image'])
372 # Internally, we keep only maxmem_KiB, and not maxmem or maxmem_kb
373 # (which come from outside, and are in MiB and KiB respectively).
374 # This means that any maxmem or maxmem_kb settings here have come
375 # from outside, and maxmem_KiB must be updated to reflect them.
376 # If we have both maxmem and maxmem_kb and these are not
377 # consistent, then this is an error, as we've no way to tell which
378 # one takes precedence.
380 # Exactly the same thing applies to memory_KiB, memory, and
381 # mem_kb.
383 def discard_negatives(name):
384 if self.infoIsSet(name) and self.info[name] <= 0:
385 del self.info[name]
387 def valid_KiB_(mb_name, kb_name):
388 discard_negatives(kb_name)
389 discard_negatives(mb_name)
391 if self.infoIsSet(kb_name):
392 if self.infoIsSet(mb_name):
393 mb = self.info[mb_name]
394 kb = self.info[kb_name]
395 if mb * 1024 == kb:
396 return kb
397 else:
398 raise VmError(
399 'Inconsistent %s / %s settings: %s / %s' %
400 (mb_name, kb_name, mb, kb))
401 else:
402 return self.info[kb_name]
403 elif self.infoIsSet(mb_name):
404 return self.info[mb_name] * 1024
405 else:
406 return None
408 def valid_KiB(mb_name, kb_name):
409 result = valid_KiB_(mb_name, kb_name)
410 if result <= 0:
411 raise VmError('Invalid %s / %s: %s' %
412 (mb_name, kb_name, result))
413 else:
414 return result
416 def delIf(name):
417 if name in self.info:
418 del self.info[name]
420 self.info['memory_KiB'] = valid_KiB('memory', 'mem_kb')
421 delIf('memory')
422 delIf('mem_kb')
423 self.info['maxmem_KiB'] = valid_KiB_('maxmem', 'maxmem_kb')
424 delIf('maxmem')
425 delIf('maxmem_kb')
427 if not self.info['maxmem_KiB']:
428 self.info['maxmem_KiB'] = 1 << 30
430 if self.info['maxmem_KiB'] > self.info['memory_KiB']:
431 self.info['maxmem_KiB'] = self.info['memory_KiB']
433 # Validate the given backend names.
434 for s in self.info['backend']:
435 if s not in backendFlags:
436 raise VmError('Invalid backend type: %s' % s)
438 for (n, c) in self.info['device']:
439 if not n or not c or n not in controllerClasses:
440 raise VmError('invalid device (%s, %s)' %
441 (str(n), str(c)))
443 if self.info['restart_mode'] not in restart_modes:
444 raise VmError('invalid restart mode: ' +
445 str(self.info['restart_mode']))
447 if 'cpumap' not in self.info:
448 if [self.info['vcpus'] == 1]:
449 self.info['cpumap'] = [1];
450 else:
451 raise VmError('Cannot create CPU map')
453 except KeyError, exn:
454 log.exception(exn)
455 raise VmError('Unspecified domain detail: %s' % str(exn))
458 def readVm(self, *args):
459 return xstransact.Read(self.path, *args)
461 def writeVm(self, *args):
462 return xstransact.Write(self.path, *args)
464 def removeVm(self, *args):
465 return xstransact.Remove(self.path, *args)
467 def gatherVm(self, *args):
468 return xstransact.Gather(self.path, *args)
470 def storeVm(self, *args):
471 return xstransact.Store(self.path, *args)
473 def readDom(self, *args):
474 return xstransact.Read(self.path, *args)
476 def writeDom(self, *args):
477 return xstransact.Write(self.path, *args)
479 def removeDom(self, *args):
480 return xstransact.Remove(self.path, *args)
482 def gatherDom(self, *args):
483 return xstransact.Gather(self.path, *args)
485 def storeDom(self, *args):
486 return xstransact.Store(self.path, *args)
489 def exportToDB(self):
490 to_store = {
491 'domid': str(self.domid),
492 'uuid': self.uuid,
494 'xend/restart_mode': str(self.info['restart_mode']),
496 'memory/target': str(self.info['memory_KiB'])
497 }
499 for (k, v) in self.info.items():
500 if v:
501 to_store[k] = str(v)
503 to_store['image'] = sxp.to_string(self.info['image'])
505 log.debug("Storing %s" % str(to_store))
507 self.writeVm(to_store)
510 def setDomid(self, domid):
511 """Set the domain id.
513 @param dom: domain id
514 """
515 self.domid = domid
516 self.storeDom("domid", self.domid)
518 def getDomid(self):
519 return self.domid
521 def setName(self, name):
522 self.check_name(name)
523 self.info['name'] = name
524 self.storeVm("name", name)
526 def getName(self):
527 return self.info['name']
529 def getPath(self):
530 return self.path
532 def getUuid(self):
533 return self.uuid
535 def getVCpuCount(self):
536 return self.info['vcpus']
538 def getSsidref(self):
539 return self.info['ssidref']
541 def getMemoryTarget(self):
542 """Get this domain's target memory size, in KiB."""
543 return self.info['memory_KiB']
545 def setStoreRef(self, ref):
546 self.store_mfn = ref
547 self.storeDom("store/ring-ref", ref)
550 def getBackendFlags(self):
551 return reduce(lambda x, y: x | backendFlags[y],
552 self.info['backend'], 0)
555 def refreshShutdown(self, xeninfo = None):
556 if xeninfo is None:
557 xeninfo = dom_get(self.domid)
558 if xeninfo is None:
559 # The domain no longer exists. This will occur if we have
560 # scheduled a timer to check for shutdown timeouts and the
561 # shutdown succeeded.
562 return
564 if xeninfo['dying']:
565 # Dying means that a domain has been destroyed, but has not yet
566 # been cleaned up by Xen. This could persist indefinitely if,
567 # for example, another domain has some of its pages mapped.
568 # We might like to diagnose this problem in the future, but for
569 # now all we can sensibly do is ignore it.
570 pass
572 elif xeninfo['crashed']:
573 log.warn('Domain has crashed: name=%s id=%d.',
574 self.info['name'], self.domid)
576 if xroot.get_enable_dump():
577 self.dumpCore()
579 self.maybeRestart('crashed')
581 elif xeninfo['shutdown']:
582 reason = shutdown_reason(xeninfo['shutdown_reason'])
584 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
585 self.info['name'], self.domid, reason)
587 self.clearRestart()
589 if reason == 'suspend':
590 self.state_set(STATE_VM_SUSPENDED)
591 # Don't destroy the domain. XendCheckpoint will do this once
592 # it has finished.
593 elif reason in ['poweroff', 'reboot']:
594 self.maybeRestart(reason)
595 else:
596 self.destroy()
598 else:
599 # Domain is alive. If we are shutting it down, then check
600 # the timeout on that, and destroy it if necessary.
602 sst = self.readVm('xend/shutdown_start_time')
603 if sst:
604 sst = float(sst)
605 timeout = SHUTDOWN_TIMEOUT - time.time() + sst
606 if timeout < 0:
607 log.info(
608 "Domain shutdown timeout expired: name=%s id=%s",
609 self.info['name'], self.domid)
610 self.destroy()
611 else:
612 log.debug(
613 "Scheduling refreshShutdown on domain %d in %ds.",
614 self.domid, timeout)
615 scheduler.later(timeout, self.refreshShutdown)
618 def shutdown(self, reason):
619 if not reason in shutdown_reasons.values():
620 raise XendError('invalid reason:' + reason)
621 self.storeVm("control/shutdown", reason)
622 if not reason in ['suspend']:
623 self.storeVm('xend/shutdown_start_time', time.time())
626 def clearRestart(self):
627 self.removeVm("xend/shutdown_start_time")
630 def maybeRestart(self, reason):
631 if self.restart_needed(reason):
632 self.restart()
633 else:
634 self.destroy()
637 def dumpCore(self):
638 """Create a core dump for this domain. Nothrow guarantee."""
640 try:
641 corefile = "/var/xen/dump/%s.%s.core" % (self.info['name'],
642 self.domid)
643 xc.domain_dumpcore(dom = self.domid, corefile = corefile)
645 except Exception, exn:
646 log.error("XendDomainInfo.dumpCore failed: id = %s name = %s: %s",
647 self.domid, self.info['name'], str(exn))
650 def closeChannel(self, channel, entry):
651 """Close the given channel, if set, and remove the given entry in the
652 store. Nothrow guarantee."""
654 try:
655 try:
656 if channel:
657 channel.close()
658 finally:
659 self.removeDom(entry)
660 except Exception, exn:
661 log.exception(exn)
664 def closeStoreChannel(self):
665 """Close the store channel, if any. Nothrow guarantee."""
667 self.closeChannel(self.store_channel, "store/port")
668 self.store_channel = None
671 def closeConsoleChannel(self):
672 """Close the console channel, if any. Nothrow guarantee."""
674 self.closeChannel(self.console_channel, "console/port")
675 self.console_channel = None
678 def setConsoleRef(self, ref):
679 self.console_mfn = ref
680 self.storeDom("console/ring-ref", ref)
683 def setMemoryTarget(self, target):
684 """Set the memory target of this domain.
685 @param target In KiB.
686 """
687 self.info['memory_KiB'] = target
688 self.storeDom("memory/target", target)
691 def update(self, info = None):
692 """Update with info from xc.domain_getinfo().
693 """
695 log.debug("XendDomainInfo.update(%s) on domain %d", info, self.domid)
697 if not info:
698 info = dom_get(self.domid)
699 if not info:
700 return
702 self.info.update(info)
703 self.validateInfo()
704 self.refreshShutdown(info)
706 log.debug("XendDomainInfo.update done on domain %d: %s", self.domid,
707 self.info)
710 ## private:
712 def state_set(self, state):
713 self.state_updated.acquire()
714 if self.state != state:
715 self.state = state
716 self.state_updated.notifyAll()
717 self.state_updated.release()
720 ## public:
722 def state_wait(self, state):
723 self.state_updated.acquire()
724 while self.state != state:
725 self.state_updated.wait()
726 self.state_updated.release()
729 def __str__(self):
730 s = "<domain"
731 s += " id=" + str(self.domid)
732 s += " name=" + self.info['name']
733 s += " memory=" + str(self.info['memory_KiB'] / 1024)
734 s += " ssidref=" + str(self.info['ssidref'])
735 s += ">"
736 return s
738 __repr__ = __str__
741 def getDeviceController(self, name):
742 if name not in controllerClasses:
743 raise XendError("unknown device type: " + str(name))
745 return controllerClasses[name](self)
748 def createDevice(self, deviceClass, devconfig):
749 return self.getDeviceController(deviceClass).createDevice(devconfig)
752 def configureDevice(self, deviceClass, devid, devconfig):
753 return self.getDeviceController(deviceClass).configureDevice(
754 devid, devconfig)
757 def destroyDevice(self, deviceClass, devid):
758 return self.getDeviceController(deviceClass).destroyDevice(devid)
761 def sxpr(self):
762 sxpr = ['domain',
763 ['domid', self.domid],
764 ['uuid', self.uuid],
765 ['memory', self.info['memory_KiB'] / 1024]]
767 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
768 if self.infoIsSet(e[0]):
769 sxpr.append([e[0], self.info[e[0]]])
771 sxpr.append(['maxmem', self.info['maxmem_KiB'] / 1024])
773 if self.infoIsSet('image'):
774 sxpr.append(['image', self.info['image']])
776 if self.infoIsSet('device'):
777 for (_, c) in self.info['device']:
778 sxpr.append(['device', c])
780 def stateChar(name):
781 if name in self.info:
782 if self.info[name]:
783 return name[0]
784 else:
785 return '-'
786 else:
787 return '?'
789 state = reduce(
790 lambda x, y: x + y,
791 map(stateChar,
792 ['running', 'blocked', 'paused', 'shutdown', 'crashed',
793 'dying']))
795 sxpr.append(['state', state])
796 if self.infoIsSet('shutdown'):
797 reason = shutdown_reason(self.info['shutdown_reason'])
798 sxpr.append(['shutdown_reason', reason])
799 if self.infoIsSet('cpu_time'):
800 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
801 sxpr.append(['vcpus', self.info['vcpus']])
802 sxpr.append(['cpumap', self.info['cpumap']])
803 if self.infoIsSet('vcpu_to_cpu'):
804 sxpr.append(['cpu', self.info['vcpu_to_cpu'][0]])
805 sxpr.append(['vcpu_to_cpu', self.prettyVCpuMap()])
807 if self.infoIsSet('start_time'):
808 up_time = time.time() - self.info['start_time']
809 sxpr.append(['up_time', str(up_time) ])
810 sxpr.append(['start_time', str(self.info['start_time']) ])
812 if self.store_channel:
813 sxpr.append(self.store_channel.sxpr())
814 if self.store_mfn:
815 sxpr.append(['store_mfn', self.store_mfn])
816 if self.console_channel:
817 sxpr.append(['console_channel', self.console_channel.sxpr()])
818 if self.console_mfn:
819 sxpr.append(['console_mfn', self.console_mfn])
821 return sxpr
824 ## private:
826 def prettyVCpuMap(self):
827 return '|'.join(map(str,
828 self.info['vcpu_to_cpu'][0:self.info['vcpus']]))
831 def check_name(self, name):
832 """Check if a vm name is valid. Valid names contain alphabetic characters,
833 digits, or characters in '_-.:/+'.
834 The same name cannot be used for more than one vm at the same time.
836 @param name: name
837 @raise: VmError if invalid
838 """
839 if name is None or name == '':
840 raise VmError('missing vm name')
841 for c in name:
842 if c in string.digits: continue
843 if c in '_-.:/+': continue
844 if c in string.ascii_letters: continue
845 raise VmError('invalid vm name')
846 dominfo = domain_exists(name)
847 # When creating or rebooting, a domain with my name should not exist.
848 # When restoring, a domain with my name will exist, but it should have
849 # my domain id.
850 if not dominfo:
851 return
852 if dominfo.is_terminated():
853 return
854 if self.domid is None:
855 raise VmError("VM name '%s' already in use by domain %d" %
856 (name, dominfo.domid))
857 if dominfo.domid != self.domid:
858 raise VmError("VM name '%s' is used in both domains %d and %d" %
859 (name, self.domid, dominfo.domid))
862 def construct(self):
863 """Construct the vm instance from its configuration.
865 @param config: configuration
866 @raise: VmError on error
867 """
869 log.debug('XendDomainInfo.construct: %s %s',
870 str(self.domid),
871 str(self.info['ssidref']))
873 self.domid = xc.domain_create(dom = 0, ssidref = self.info['ssidref'])
875 if self.domid <= 0:
876 raise VmError('Creating domain failed: name=%s' %
877 self.info['name'])
879 try:
880 self.initDomain()
881 self.construct_image()
882 self.configure()
883 self.exportToDB()
884 except Exception, ex:
885 # Catch errors, cleanup and re-raise.
886 print 'Domain construction error:', ex
887 import traceback
888 traceback.print_exc()
889 self.destroy()
890 raise
893 def initDomain(self):
894 log.debug('XendDomainInfo.initDomain: %s %s %s',
895 str(self.domid),
896 str(self.info['memory_KiB']),
897 str(self.info['cpu_weight']))
899 if not self.infoIsSet('image'):
900 raise VmError('Missing image in configuration')
902 self.image = image.create(self,
903 self.info['image'],
904 self.info['device'])
906 if self.info['bootloader']:
907 self.image.handleBootloading()
909 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
910 # XXX Merge with configure_maxmem?
911 m = self.image.getDomainMemory(self.info['memory_KiB'])
912 xc.domain_setmaxmem(self.domid, m)
913 xc.domain_memory_increase_reservation(self.domid, m, 0, 0)
915 cpu = self.info['cpu']
916 if cpu is not None and cpu != -1:
917 xc.domain_pincpu(self.domid, 0, 1 << cpu)
919 self.info['start_time'] = time.time()
921 log.debug('init_domain> Created domain=%d name=%s memory=%d',
922 self.domid, self.info['name'], self.info['memory_KiB'])
925 def configure_vcpus(self, vcpus):
926 d = {}
927 for v in range(0, vcpus):
928 d["cpu/%d/availability" % v] = "online"
929 self.writeVm(d)
931 def construct_image(self):
932 """Construct the boot image for the domain.
933 """
934 self.create_channel()
935 self.image.createImage()
936 self.exportToDB()
937 if self.store_channel and self.store_mfn >= 0:
938 IntroduceDomain(self.domid, self.store_mfn,
939 self.store_channel.port1, self.path)
940 # get the configured value of vcpus and update store
941 self.configure_vcpus(self.info['vcpus'])
944 ## public:
946 def delete(self):
947 """Delete the vm's db.
948 """
949 try:
950 xstransact.Remove(self.path, 'domid')
951 except Exception, ex:
952 log.warning("error in domain db delete: %s", ex)
955 def cleanup(self):
956 """Cleanup vm resources: release devices. Nothrow guarantee."""
958 self.state_set(STATE_VM_TERMINATED)
959 self.release_devices()
960 self.closeStoreChannel()
961 self.closeConsoleChannel()
963 if self.image:
964 try:
965 self.image.destroy()
966 except:
967 log.exception(
968 "XendDomainInfo.cleanup: image.destroy() failed.")
969 self.image = None
972 def destroy(self):
973 """Cleanup vm and destroy domain. Nothrow guarantee."""
975 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
977 self.cleanup()
979 try:
980 self.removeVm()
981 except Exception:
982 log.exception("Removing VM path failed.")
984 try:
985 self.removeDom()
986 except Exception:
987 log.exception("Removing domain path failed.")
989 try:
990 if self.domid is not None:
991 xc.domain_destroy(dom=self.domid)
992 except Exception:
993 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
996 def is_terminated(self):
997 """Check if a domain has been terminated.
998 """
999 return self.state == STATE_VM_TERMINATED
1001 def release_devices(self):
1002 """Release all vm devices. Nothrow guarantee."""
1004 while True:
1005 t = xstransact("%s/device" % self.path)
1006 for n in controllerClasses.keys():
1007 for d in t.list(n):
1008 try:
1009 t.remove(d)
1010 except ex:
1011 # Log and swallow any exceptions in removal --
1012 # there's nothing more we can do.
1013 log.exception(
1014 "Device release failed: %s; %s; %s",
1015 self.info['name'], n, d)
1016 if t.commit():
1017 break
1019 def eventChannel(self, path=None):
1020 """Create an event channel to the domain.
1022 @param path under which port is stored in db
1023 """
1024 port = 0
1025 if path:
1026 try:
1027 port = int(self.readDom(path))
1028 except:
1029 # if anything goes wrong, assume the port was not yet set
1030 pass
1031 ret = EventChannel.interdomain(0, self.domid, port1=port, port2=0)
1032 self.storeDom(path, ret.port1)
1033 return ret
1035 def create_channel(self):
1036 """Create the channels to the domain.
1037 """
1038 self.store_channel = self.eventChannel("store/port")
1039 self.console_channel = self.eventChannel("console/port")
1041 def create_configured_devices(self):
1042 for (n, c) in self.info['device']:
1043 self.createDevice(n, c)
1046 def create_devices(self):
1047 """Create the devices for a vm.
1049 @raise: VmError for invalid devices
1050 """
1051 self.create_configured_devices()
1052 if self.image:
1053 self.image.createDeviceModel()
1055 def device_create(self, dev_config):
1056 """Create a new device.
1058 @param dev_config: device configuration
1059 """
1060 dev_type = sxp.name(dev_config)
1061 devid = self.createDevice(dev_type, dev_config)
1062 # self.config.append(['device', dev.getConfig()])
1063 return self.getDeviceController(dev_type).sxpr(devid)
1066 def device_configure(self, dev_config, devid):
1067 """Configure an existing device.
1068 @param dev_config: device configuration
1069 @param devid: device id
1070 """
1071 deviceClass = sxp.name(dev_config)
1072 self.configureDevice(deviceClass, devid, dev_config)
1075 def restart_needed(self, reason):
1076 """Determine if the vm needs to be restarted when shutdown
1077 for the given reason.
1079 @param reason: shutdown reason
1080 @return True if needs restart, False otherwise
1081 """
1082 if self.info['restart_mode'] == RESTART_NEVER:
1083 return False
1084 if self.info['restart_mode'] == RESTART_ALWAYS:
1085 return True
1086 if self.info['restart_mode'] == RESTART_ONREBOOT:
1087 return reason == 'reboot'
1088 return False
1091 def restart_check(self):
1092 """Check if domain restart is OK.
1093 To prevent restart loops, raise an error if it is
1094 less than MINIMUM_RESTART_TIME seconds since the last restart.
1095 """
1096 tnow = time.time()
1097 if self.restart_time is not None:
1098 tdelta = tnow - self.restart_time
1099 if tdelta < self.MINIMUM_RESTART_TIME:
1100 self.restart_cancel()
1101 msg = 'VM %s restarting too fast' % self.info['name']
1102 log.error(msg)
1103 raise VmError(msg)
1104 self.restart_time = tnow
1105 self.restart_count += 1
1108 def restart(self):
1109 """Restart the domain after it has exited. """
1111 # self.restart_check()
1112 self.cleanup()
1114 config = self.sxpr()
1116 if self.readVm('xend/restart_in_progress'):
1117 log.error('Xend failed during restart of domain %d. '
1118 'Refusing to restart to avoid loops.',
1119 self.domid)
1120 self.destroy()
1121 return
1123 self.writeVm('xend/restart_in_progress', 'True')
1125 try:
1126 self.destroy()
1127 try:
1128 xd = get_component('xen.xend.XendDomain')
1129 xd.domain_unpause(xd.domain_create(config).getDomid())
1130 except Exception, exn:
1131 log.exception('Failed to restart domain %d.', self.domid)
1132 finally:
1133 self.removeVm('xend/restart_in_progress')
1135 # self.configure_bootloader()
1136 # self.exportToDB()
1139 def configure_bootloader(self):
1140 if not self.info['bootloader']:
1141 return
1142 # if we're restarting with a bootloader, we need to run it
1143 # FIXME: this assumes the disk is the first device and
1144 # that we're booting from the first disk
1145 blcfg = None
1146 # FIXME: this assumes that we want to use the first disk
1147 dev = sxp.child_value(self.config, "device")
1148 if dev:
1149 disk = sxp.child_value(dev, "uname")
1150 fn = blkdev_uname_to_file(disk)
1151 blcfg = bootloader(self.info['bootloader'], fn, 1,
1152 self.info['vcpus'])
1153 if blcfg is None:
1154 msg = "Had a bootloader specified, but can't find disk"
1155 log.error(msg)
1156 raise VmError(msg)
1157 self.config = sxp.merge(['vm', ['image', blcfg]], self.config)
1160 def configure(self):
1161 """Configure a vm.
1163 """
1164 self.configure_maxmem()
1165 self.create_devices()
1168 def configure_maxmem(self):
1169 if self.image:
1170 m = self.image.getDomainMemory(self.info['memory_KiB'])
1171 xc.domain_setmaxmem(self.domid, maxmem_kb = m)
1174 def vcpu_hotplug(self, vcpu, state):
1175 """Disable or enable VCPU in domain.
1176 """
1177 if vcpu > self.info['vcpus']:
1178 log.error("Invalid VCPU %d" % vcpu)
1179 return
1180 if int(state) == 0:
1181 availability = "offline"
1182 else:
1183 availability = "online"
1184 self.storeVm("cpu/%d/availability" % vcpu, availability)
1186 def send_sysrq(self, key=0):
1187 self.storeVm("control/sysrq", '%c' % key)
1189 def dom0_init_store(self):
1190 if not self.store_channel:
1191 self.store_channel = self.eventChannel("store/port")
1192 if not self.store_channel:
1193 return
1194 ref = xc.init_store(self.store_channel.port2)
1195 if ref and ref >= 0:
1196 self.setStoreRef(ref)
1197 try:
1198 IntroduceDomain(self.domid, ref, self.store_channel.port1,
1199 self.path)
1200 except RuntimeError, ex:
1201 if ex.args[0] == errno.EISCONN:
1202 pass
1203 else:
1204 raise
1205 # get run-time value of vcpus and update store
1206 self.configure_vcpus(dom_get(self.domid)['vcpus'])
1208 def dom0_enforce_vcpus(self):
1209 dom = 0
1210 # get max number of vcpus to use for dom0 from config
1211 target = int(xroot.get_dom0_vcpus())
1212 log.debug("number of vcpus to use is %d" % (target))
1214 # target = 0 means use all processors
1215 if target > 0:
1216 # count the number of online vcpus (cpu values in v2c map >= 0)
1217 vcpu_to_cpu = dom_get(dom)['vcpu_to_cpu']
1218 vcpus_online = len(filter(lambda x: x >= 0, vcpu_to_cpu))
1219 log.debug("found %d vcpus online" % (vcpus_online))
1221 # disable any extra vcpus that are online over the requested target
1222 for vcpu in range(target, vcpus_online):
1223 log.info("enforcement is disabling DOM%d VCPU%d" % (dom, vcpu))
1224 self.vcpu_hotplug(vcpu, 0)
1227 def infoIsSet(self, name):
1228 return name in self.info and self.info[name] is not None
1231 #============================================================================
1232 # Register device controllers and their device config types.
1234 """A map from device-class names to the subclass of DevController that
1235 implements the device control specific to that device-class."""
1236 controllerClasses = {}
1239 """A map of backend names and the corresponding flag."""
1240 backendFlags = {}
1243 def addControllerClass(device_class, backend_name, backend_flag, cls):
1244 """Register a subclass of DevController to handle the named device-class.
1246 @param backend_flag One of the SIF_XYZ_BE_DOMAIN constants, or None if
1247 no flag is to be set.
1248 """
1249 cls.deviceClass = device_class
1250 backendFlags[backend_name] = backend_flag
1251 controllerClasses[device_class] = cls
1254 from xen.xend.server import blkif, netif, tpmif, pciif, usbif
1255 addControllerClass('vbd', 'blkif', SIF_BLK_BE_DOMAIN, blkif.BlkifController)
1256 addControllerClass('vif', 'netif', SIF_NET_BE_DOMAIN, netif.NetifController)
1257 addControllerClass('vtpm', 'tpmif', SIF_TPM_BE_DOMAIN, tpmif.TPMifController)
1258 addControllerClass('pci', 'pciif', None, pciif.PciController)
1259 addControllerClass('usb', 'usbif', None, usbif.UsbifController)