ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 7083:679e93b5c6cd

Added getDeviceSxprs, to support xm block-list.
author emellor@ewan
date Tue Sep 27 22:05:03 2005 +0100 (2005-09-27)
parents a8ed2f186c23
children b56364e269c7
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, config):
158 """Create a VM from a configuration.
160 @param config configuration
161 @raise: VmError for invalid configuration
162 """
164 log.debug("XendDomainInfo.create(...)")
166 vm = cls(getUuid(), cls.parseConfig(config))
167 vm.construct()
168 vm.refreshShutdown()
169 return vm
171 create = classmethod(create)
174 def recreate(cls, xeninfo):
175 """Create the VM object for an existing domain."""
177 log.debug("XendDomainInfo.recreate(%s)", xeninfo)
179 domid = xeninfo['dom']
180 try:
181 dompath = GetDomainPath(domid)
182 if not dompath:
183 raise XendError(
184 'No domain path in store for existing domain %d' % domid)
185 vmpath = xstransact.Read(dompath, "vm")
186 if not vmpath:
187 raise XendError(
188 'No vm path in store for existing domain %d' % domid)
189 uuid = xstransact.Read(vmpath, "uuid")
190 if not uuid:
191 raise XendError(
192 'No vm/uuid path in store for existing domain %d' % domid)
194 except Exception, exn:
195 log.warn(str(exn))
196 uuid = getUuid()
198 log.info("Recreating domain %d, uuid %s", domid, uuid)
200 vm = cls(uuid, xeninfo, domid, True)
201 vm.refreshShutdown(xeninfo)
202 return vm
204 recreate = classmethod(recreate)
207 def restore(cls, config, uuid = None):
208 """Create a domain and a VM object to do a restore.
210 @param config: domain configuration
211 @param uuid: uuid to use
212 """
214 log.debug("XendDomainInfo.restore(%s, %s)", config, uuid)
216 if not uuid:
217 uuid = getUuid()
219 try:
220 ssidref = int(sxp.child_value(config, 'ssidref'))
221 except TypeError, exn:
222 raise VmError('Invalid ssidref in config: %s' % exn)
224 vm = cls(uuid, cls.parseConfig(config),
225 xc.domain_create(ssidref = ssidref))
226 vm.create_channel()
227 vm.configure()
228 vm.exportToDB()
229 vm.refreshShutdown()
230 return vm
232 restore = classmethod(restore)
235 def parseConfig(cls, config):
236 def get_cfg(name, conv = None):
237 val = sxp.child_value(config, name)
239 if conv and not val is None:
240 try:
241 return conv(val)
242 except TypeError, exn:
243 raise VmError(
244 'Invalid setting %s = %s in configuration: %s' %
245 (name, val, str(exn)))
246 else:
247 return val
250 log.debug("parseConfig: config is %s" % str(config))
252 result = {}
254 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
255 result[e[0]] = get_cfg(e[0], e[1])
257 result['memory'] = get_cfg('memory', int)
258 result['mem_kb'] = get_cfg('mem_kb', int)
259 result['maxmem'] = get_cfg('maxmem', int)
260 result['maxmem_kb'] = get_cfg('maxmem_kb', int)
261 result['cpu'] = get_cfg('cpu', int)
262 result['restart_mode'] = get_cfg('restart')
263 result['image'] = get_cfg('image')
265 try:
266 if result['image']:
267 result['vcpus'] = int(sxp.child_value(result['image'],
268 'vcpus', 1))
269 else:
270 result['vcpus'] = 1
271 except TypeError, exn:
272 raise VmError(
273 'Invalid configuration setting: vcpus = %s: %s' %
274 (sxp.child_value(result['image'], 'vcpus', 1), str(exn)))
276 result['backend'] = []
277 for c in sxp.children(config, 'backend'):
278 result['backend'].append(sxp.name(sxp.child0(c)))
280 result['device'] = []
281 for d in sxp.children(config, 'device'):
282 c = sxp.child0(d)
283 result['device'].append((sxp.name(c), c))
285 log.debug("parseConfig: result is %s" % str(result))
286 return result
289 parseConfig = classmethod(parseConfig)
292 def __init__(self, uuid, info, domid = None, augment = False):
294 self.uuid = uuid
295 self.info = info
297 self.path = DOMROOT + "/" + uuid
299 if domid:
300 self.domid = domid
301 elif 'dom' in info:
302 self.domid = int(info['dom'])
303 else:
304 self.domid = None
306 if augment:
307 self.augmentInfo()
309 self.validateInfo()
311 self.image = None
313 self.store_channel = None
314 self.store_mfn = None
315 self.console_channel = None
316 self.console_mfn = None
318 self.state = STATE_VM_OK
319 self.state_updated = threading.Condition()
321 self.writeVm("uuid", self.uuid)
322 self.storeDom("vm", self.path)
325 def augmentInfo(self):
326 """Augment self.info, as given to us through {@link #recreate}, with
327 values taken from the store. This recovers those values known to xend
328 but not to the hypervisor.
329 """
330 def useIfNeeded(name, val):
331 if not self.infoIsSet(name) and val is not None:
332 self.info[name] = val
334 params = (("name", str),
335 ("restart-mode", str),
336 ("image", str),
337 ("start-time", float))
339 from_store = self.gatherVm(*params)
341 map(lambda x, y: useIfNeeded(x[0], y), params, from_store)
344 def validateInfo(self):
345 """Validate and normalise the info block. This has either been parsed
346 by parseConfig, or received from xc through recreate.
347 """
348 def defaultInfo(name, val):
349 if not self.infoIsSet(name):
350 self.info[name] = val()
352 try:
353 defaultInfo('name', lambda: "Domain-%d" % self.domid)
354 defaultInfo('ssidref', lambda: 0)
355 defaultInfo('restart_mode', lambda: RESTART_ONREBOOT)
356 defaultInfo('cpu', lambda: None)
357 defaultInfo('cpu_weight', lambda: 1.0)
358 defaultInfo('bootloader', lambda: None)
359 defaultInfo('backend', lambda: [])
360 defaultInfo('device', lambda: [])
361 defaultInfo('image', lambda: None)
363 self.check_name(self.info['name'])
365 if isinstance(self.info['image'], str):
366 self.info['image'] = sxp.from_string(self.info['image'])
368 # Internally, we keep only maxmem_KiB, and not maxmem or maxmem_kb
369 # (which come from outside, and are in MiB and KiB respectively).
370 # This means that any maxmem or maxmem_kb settings here have come
371 # from outside, and maxmem_KiB must be updated to reflect them.
372 # If we have both maxmem and maxmem_kb and these are not
373 # consistent, then this is an error, as we've no way to tell which
374 # one takes precedence.
376 # Exactly the same thing applies to memory_KiB, memory, and
377 # mem_kb.
379 def discard_negatives(name):
380 if self.infoIsSet(name) and self.info[name] <= 0:
381 del self.info[name]
383 def valid_KiB_(mb_name, kb_name):
384 discard_negatives(kb_name)
385 discard_negatives(mb_name)
387 if self.infoIsSet(kb_name):
388 if self.infoIsSet(mb_name):
389 mb = self.info[mb_name]
390 kb = self.info[kb_name]
391 if mb * 1024 == kb:
392 return kb
393 else:
394 raise VmError(
395 'Inconsistent %s / %s settings: %s / %s' %
396 (mb_name, kb_name, mb, kb))
397 else:
398 return self.info[kb_name]
399 elif self.infoIsSet(mb_name):
400 return self.info[mb_name] * 1024
401 else:
402 return None
404 def valid_KiB(mb_name, kb_name):
405 result = valid_KiB_(mb_name, kb_name)
406 if result <= 0:
407 raise VmError('Invalid %s / %s: %s' %
408 (mb_name, kb_name, result))
409 else:
410 return result
412 def delIf(name):
413 if name in self.info:
414 del self.info[name]
416 self.info['memory_KiB'] = valid_KiB('memory', 'mem_kb')
417 delIf('memory')
418 delIf('mem_kb')
419 self.info['maxmem_KiB'] = valid_KiB_('maxmem', 'maxmem_kb')
420 delIf('maxmem')
421 delIf('maxmem_kb')
423 if not self.info['maxmem_KiB']:
424 self.info['maxmem_KiB'] = 1 << 30
426 if self.info['maxmem_KiB'] > self.info['memory_KiB']:
427 self.info['maxmem_KiB'] = self.info['memory_KiB']
429 # Validate the given backend names.
430 for s in self.info['backend']:
431 if s not in backendFlags:
432 raise VmError('Invalid backend type: %s' % s)
434 for (n, c) in self.info['device']:
435 if not n or not c or n not in controllerClasses:
436 raise VmError('invalid device (%s, %s)' %
437 (str(n), str(c)))
439 if self.info['restart_mode'] not in restart_modes:
440 raise VmError('invalid restart mode: ' +
441 str(self.info['restart_mode']))
443 if 'cpumap' not in self.info:
444 if [self.info['vcpus'] == 1]:
445 self.info['cpumap'] = [1];
446 else:
447 raise VmError('Cannot create CPU map')
449 except KeyError, exn:
450 log.exception(exn)
451 raise VmError('Unspecified domain detail: %s' % str(exn))
454 def readVm(self, *args):
455 return xstransact.Read(self.path, *args)
457 def writeVm(self, *args):
458 return xstransact.Write(self.path, *args)
460 def removeVm(self, *args):
461 return xstransact.Remove(self.path, *args)
463 def gatherVm(self, *args):
464 return xstransact.Gather(self.path, *args)
466 def storeVm(self, *args):
467 return xstransact.Store(self.path, *args)
469 def readDom(self, *args):
470 return xstransact.Read(self.path, *args)
472 def writeDom(self, *args):
473 return xstransact.Write(self.path, *args)
475 def removeDom(self, *args):
476 return xstransact.Remove(self.path, *args)
478 def gatherDom(self, *args):
479 return xstransact.Gather(self.path, *args)
481 def storeDom(self, *args):
482 return xstransact.Store(self.path, *args)
485 def exportToDB(self):
486 to_store = {
487 'domid': str(self.domid),
488 'uuid': self.uuid,
490 'xend/restart_mode': str(self.info['restart_mode']),
492 'memory/target': str(self.info['memory_KiB'])
493 }
495 for (k, v) in self.info.items():
496 if v:
497 to_store[k] = str(v)
499 to_store['image'] = sxp.to_string(self.info['image'])
501 log.debug("Storing %s" % str(to_store))
503 self.writeVm(to_store)
506 def setDomid(self, domid):
507 """Set the domain id.
509 @param dom: domain id
510 """
511 self.domid = domid
512 self.storeDom("domid", self.domid)
514 def getDomid(self):
515 return self.domid
517 def setName(self, name):
518 self.check_name(name)
519 self.info['name'] = name
520 self.storeVm("name", name)
522 def getName(self):
523 return self.info['name']
525 def getPath(self):
526 return self.path
528 def getUuid(self):
529 return self.uuid
531 def getVCpuCount(self):
532 return self.info['vcpus']
534 def getSsidref(self):
535 return self.info['ssidref']
537 def getMemoryTarget(self):
538 """Get this domain's target memory size, in KiB."""
539 return self.info['memory_KiB']
541 def setStoreRef(self, ref):
542 self.store_mfn = ref
543 self.storeDom("store/ring-ref", ref)
546 def getBackendFlags(self):
547 return reduce(lambda x, y: x | backendFlags[y],
548 self.info['backend'], 0)
551 def refreshShutdown(self, xeninfo = None):
552 if xeninfo is None:
553 xeninfo = dom_get(self.domid)
554 if xeninfo is None:
555 # The domain no longer exists. This will occur if we have
556 # scheduled a timer to check for shutdown timeouts and the
557 # shutdown succeeded.
558 return
560 if xeninfo['dying']:
561 # Dying means that a domain has been destroyed, but has not yet
562 # been cleaned up by Xen. This could persist indefinitely if,
563 # for example, another domain has some of its pages mapped.
564 # We might like to diagnose this problem in the future, but for
565 # now all we can sensibly do is ignore it.
566 pass
568 elif xeninfo['crashed']:
569 log.warn('Domain has crashed: name=%s id=%d.',
570 self.info['name'], self.domid)
572 if xroot.get_enable_dump():
573 self.dumpCore()
575 self.maybeRestart('crashed')
577 elif xeninfo['shutdown']:
578 reason = shutdown_reason(xeninfo['shutdown_reason'])
580 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
581 self.info['name'], self.domid, reason)
583 self.clearRestart()
585 if reason == 'suspend':
586 self.state_set(STATE_VM_SUSPENDED)
587 # Don't destroy the domain. XendCheckpoint will do this once
588 # it has finished.
589 elif reason in ['poweroff', 'reboot']:
590 self.maybeRestart(reason)
591 else:
592 self.destroy()
594 else:
595 # Domain is alive. If we are shutting it down, then check
596 # the timeout on that, and destroy it if necessary.
598 sst = self.readVm('xend/shutdown_start_time')
599 if sst:
600 sst = float(sst)
601 timeout = SHUTDOWN_TIMEOUT - time.time() + sst
602 if timeout < 0:
603 log.info(
604 "Domain shutdown timeout expired: name=%s id=%s",
605 self.info['name'], self.domid)
606 self.destroy()
607 else:
608 log.debug(
609 "Scheduling refreshShutdown on domain %d in %ds.",
610 self.domid, timeout)
611 scheduler.later(timeout, self.refreshShutdown)
614 def shutdown(self, reason):
615 if not reason in shutdown_reasons.values():
616 raise XendError('invalid reason:' + reason)
617 self.storeVm("control/shutdown", reason)
618 if not reason in ['suspend']:
619 self.storeVm('xend/shutdown_start_time', time.time())
622 def clearRestart(self):
623 self.removeVm("xend/shutdown_start_time")
626 def maybeRestart(self, reason):
627 if self.restart_needed(reason):
628 self.restart()
629 else:
630 self.destroy()
633 def dumpCore(self):
634 """Create a core dump for this domain. Nothrow guarantee."""
636 try:
637 corefile = "/var/xen/dump/%s.%s.core" % (self.info['name'],
638 self.domid)
639 xc.domain_dumpcore(dom = self.domid, corefile = corefile)
641 except Exception, exn:
642 log.error("XendDomainInfo.dumpCore failed: id = %s name = %s: %s",
643 self.domid, self.info['name'], str(exn))
646 def closeChannel(self, channel, entry):
647 """Close the given channel, if set, and remove the given entry in the
648 store. Nothrow guarantee."""
650 try:
651 try:
652 if channel:
653 channel.close()
654 finally:
655 self.removeDom(entry)
656 except Exception, exn:
657 log.exception(exn)
660 def closeStoreChannel(self):
661 """Close the store channel, if any. Nothrow guarantee."""
663 self.closeChannel(self.store_channel, "store/port")
664 self.store_channel = None
667 def closeConsoleChannel(self):
668 """Close the console channel, if any. Nothrow guarantee."""
670 self.closeChannel(self.console_channel, "console/port")
671 self.console_channel = None
674 def setConsoleRef(self, ref):
675 self.console_mfn = ref
676 self.storeDom("console/ring-ref", ref)
679 def setMemoryTarget(self, target):
680 """Set the memory target of this domain.
681 @param target In KiB.
682 """
683 self.info['memory_KiB'] = target
684 self.storeDom("memory/target", target)
687 def update(self, info = None):
688 """Update with info from xc.domain_getinfo().
689 """
691 log.debug("XendDomainInfo.update(%s) on domain %d", info, self.domid)
693 if not info:
694 info = dom_get(self.domid)
695 if not info:
696 return
698 self.info.update(info)
699 self.validateInfo()
700 self.refreshShutdown(info)
702 log.debug("XendDomainInfo.update done on domain %d: %s", self.domid,
703 self.info)
706 ## private:
708 def state_set(self, state):
709 self.state_updated.acquire()
710 if self.state != state:
711 self.state = state
712 self.state_updated.notifyAll()
713 self.state_updated.release()
716 ## public:
718 def state_wait(self, state):
719 self.state_updated.acquire()
720 while self.state != state:
721 self.state_updated.wait()
722 self.state_updated.release()
725 def __str__(self):
726 s = "<domain"
727 s += " id=" + str(self.domid)
728 s += " name=" + self.info['name']
729 s += " memory=" + str(self.info['memory_KiB'] / 1024)
730 s += " ssidref=" + str(self.info['ssidref'])
731 s += ">"
732 return s
734 __repr__ = __str__
737 def createDevice(self, deviceClass, devconfig):
738 return self.getDeviceController(deviceClass).createDevice(devconfig)
741 def configureDevice(self, deviceClass, devid, devconfig):
742 return self.getDeviceController(deviceClass).configureDevice(
743 devid, devconfig)
746 def destroyDevice(self, deviceClass, devid):
747 return self.getDeviceController(deviceClass).destroyDevice(devid)
750 def getDeviceSxprs(self, deviceClass):
751 return self.getDeviceController(deviceClass).sxprs()
754 ## private:
756 def getDeviceController(self, name):
757 if name not in controllerClasses:
758 raise XendError("unknown device type: " + str(name))
760 return controllerClasses[name](self)
763 ## public:
765 def sxpr(self):
766 sxpr = ['domain',
767 ['domid', self.domid],
768 ['uuid', self.uuid],
769 ['memory', self.info['memory_KiB'] / 1024]]
771 for e in ROUNDTRIPPING_CONFIG_ENTRIES:
772 if self.infoIsSet(e[0]):
773 sxpr.append([e[0], self.info[e[0]]])
775 sxpr.append(['maxmem', self.info['maxmem_KiB'] / 1024])
777 if self.infoIsSet('image'):
778 sxpr.append(['image', self.info['image']])
780 if self.infoIsSet('device'):
781 for (_, c) in self.info['device']:
782 sxpr.append(['device', c])
784 def stateChar(name):
785 if name in self.info:
786 if self.info[name]:
787 return name[0]
788 else:
789 return '-'
790 else:
791 return '?'
793 state = reduce(
794 lambda x, y: x + y,
795 map(stateChar,
796 ['running', 'blocked', 'paused', 'shutdown', 'crashed',
797 'dying']))
799 sxpr.append(['state', state])
800 if self.infoIsSet('shutdown'):
801 reason = shutdown_reason(self.info['shutdown_reason'])
802 sxpr.append(['shutdown_reason', reason])
803 if self.infoIsSet('cpu_time'):
804 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
805 sxpr.append(['vcpus', self.info['vcpus']])
806 sxpr.append(['cpumap', self.info['cpumap']])
807 if self.infoIsSet('vcpu_to_cpu'):
808 sxpr.append(['cpu', self.info['vcpu_to_cpu'][0]])
809 sxpr.append(['vcpu_to_cpu', self.prettyVCpuMap()])
811 if self.infoIsSet('start_time'):
812 up_time = time.time() - self.info['start_time']
813 sxpr.append(['up_time', str(up_time) ])
814 sxpr.append(['start_time', str(self.info['start_time']) ])
816 if self.store_channel:
817 sxpr.append(self.store_channel.sxpr())
818 if self.store_mfn:
819 sxpr.append(['store_mfn', self.store_mfn])
820 if self.console_channel:
821 sxpr.append(['console_channel', self.console_channel.sxpr()])
822 if self.console_mfn:
823 sxpr.append(['console_mfn', self.console_mfn])
825 return sxpr
828 ## private:
830 def prettyVCpuMap(self):
831 return '|'.join(map(str,
832 self.info['vcpu_to_cpu'][0:self.info['vcpus']]))
835 def check_name(self, name):
836 """Check if a vm name is valid. Valid names contain alphabetic characters,
837 digits, or characters in '_-.:/+'.
838 The same name cannot be used for more than one vm at the same time.
840 @param name: name
841 @raise: VmError if invalid
842 """
843 if name is None or name == '':
844 raise VmError('missing vm name')
845 for c in name:
846 if c in string.digits: continue
847 if c in '_-.:/+': continue
848 if c in string.ascii_letters: continue
849 raise VmError('invalid vm name')
850 dominfo = domain_exists(name)
851 # When creating or rebooting, a domain with my name should not exist.
852 # When restoring, a domain with my name will exist, but it should have
853 # my domain id.
854 if not dominfo:
855 return
856 if dominfo.is_terminated():
857 return
858 if self.domid is None:
859 raise VmError("VM name '%s' already in use by domain %d" %
860 (name, dominfo.domid))
861 if dominfo.domid != self.domid:
862 raise VmError("VM name '%s' is used in both domains %d and %d" %
863 (name, self.domid, dominfo.domid))
866 def construct(self):
867 """Construct the vm instance from its configuration.
869 @param config: configuration
870 @raise: VmError on error
871 """
873 log.debug('XendDomainInfo.construct: %s %s',
874 str(self.domid),
875 str(self.info['ssidref']))
877 self.domid = xc.domain_create(dom = 0, ssidref = self.info['ssidref'])
879 if self.domid <= 0:
880 raise VmError('Creating domain failed: name=%s' %
881 self.info['name'])
883 try:
884 self.initDomain()
885 self.construct_image()
886 self.configure()
887 self.exportToDB()
888 except Exception, ex:
889 # Catch errors, cleanup and re-raise.
890 print 'Domain construction error:', ex
891 import traceback
892 traceback.print_exc()
893 self.destroy()
894 raise
897 def initDomain(self):
898 log.debug('XendDomainInfo.initDomain: %s %s %s',
899 str(self.domid),
900 str(self.info['memory_KiB']),
901 str(self.info['cpu_weight']))
903 if not self.infoIsSet('image'):
904 raise VmError('Missing image in configuration')
906 self.image = image.create(self,
907 self.info['image'],
908 self.info['device'])
910 if self.info['bootloader']:
911 self.image.handleBootloading()
913 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
914 # XXX Merge with configure_maxmem?
915 m = self.image.getDomainMemory(self.info['memory_KiB'])
916 xc.domain_setmaxmem(self.domid, m)
917 xc.domain_memory_increase_reservation(self.domid, m, 0, 0)
919 cpu = self.info['cpu']
920 if cpu is not None and cpu != -1:
921 xc.domain_pincpu(self.domid, 0, 1 << cpu)
923 self.info['start_time'] = time.time()
925 log.debug('init_domain> Created domain=%d name=%s memory=%d',
926 self.domid, self.info['name'], self.info['memory_KiB'])
929 def configure_vcpus(self, vcpus):
930 d = {}
931 for v in range(0, vcpus):
932 d["cpu/%d/availability" % v] = "online"
933 self.writeVm(d)
935 def construct_image(self):
936 """Construct the boot image for the domain.
937 """
938 self.create_channel()
939 self.image.createImage()
940 self.exportToDB()
941 if self.store_channel and self.store_mfn >= 0:
942 IntroduceDomain(self.domid, self.store_mfn,
943 self.store_channel.port1, self.path)
944 # get the configured value of vcpus and update store
945 self.configure_vcpus(self.info['vcpus'])
948 ## public:
950 def delete(self):
951 """Delete the vm's db.
952 """
953 try:
954 xstransact.Remove(self.path, 'domid')
955 except Exception, ex:
956 log.warning("error in domain db delete: %s", ex)
959 def cleanup(self):
960 """Cleanup vm resources: release devices. Nothrow guarantee."""
962 self.state_set(STATE_VM_TERMINATED)
963 self.release_devices()
964 self.closeStoreChannel()
965 self.closeConsoleChannel()
967 if self.image:
968 try:
969 self.image.destroy()
970 except:
971 log.exception(
972 "XendDomainInfo.cleanup: image.destroy() failed.")
973 self.image = None
976 def destroy(self):
977 """Cleanup vm and destroy domain. Nothrow guarantee."""
979 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
981 self.cleanup()
983 try:
984 self.removeVm()
985 except Exception:
986 log.exception("Removing VM path failed.")
988 try:
989 self.removeDom()
990 except Exception:
991 log.exception("Removing domain path failed.")
993 try:
994 if self.domid is not None:
995 xc.domain_destroy(dom=self.domid)
996 except Exception:
997 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1000 def is_terminated(self):
1001 """Check if a domain has been terminated.
1002 """
1003 return self.state == STATE_VM_TERMINATED
1005 def release_devices(self):
1006 """Release all vm devices. Nothrow guarantee."""
1008 while True:
1009 t = xstransact("%s/device" % self.path)
1010 for n in controllerClasses.keys():
1011 for d in t.list(n):
1012 try:
1013 t.remove(d)
1014 except ex:
1015 # Log and swallow any exceptions in removal --
1016 # there's nothing more we can do.
1017 log.exception(
1018 "Device release failed: %s; %s; %s",
1019 self.info['name'], n, d)
1020 if t.commit():
1021 break
1023 def eventChannel(self, path=None):
1024 """Create an event channel to the domain.
1026 @param path under which port is stored in db
1027 """
1028 port = 0
1029 if path:
1030 try:
1031 port = int(self.readDom(path))
1032 except:
1033 # if anything goes wrong, assume the port was not yet set
1034 pass
1035 ret = EventChannel.interdomain(0, self.domid, port1=port, port2=0)
1036 self.storeDom(path, ret.port1)
1037 return ret
1039 def create_channel(self):
1040 """Create the channels to the domain.
1041 """
1042 self.store_channel = self.eventChannel("store/port")
1043 self.console_channel = self.eventChannel("console/port")
1045 def create_configured_devices(self):
1046 for (n, c) in self.info['device']:
1047 self.createDevice(n, c)
1050 def create_devices(self):
1051 """Create the devices for a vm.
1053 @raise: VmError for invalid devices
1054 """
1055 self.create_configured_devices()
1056 if self.image:
1057 self.image.createDeviceModel()
1059 def device_create(self, dev_config):
1060 """Create a new device.
1062 @param dev_config: device configuration
1063 """
1064 dev_type = sxp.name(dev_config)
1065 devid = self.createDevice(dev_type, dev_config)
1066 # self.config.append(['device', dev.getConfig()])
1067 return self.getDeviceController(dev_type).sxpr(devid)
1070 def device_configure(self, dev_config, devid):
1071 """Configure an existing device.
1072 @param dev_config: device configuration
1073 @param devid: device id
1074 """
1075 deviceClass = sxp.name(dev_config)
1076 self.configureDevice(deviceClass, devid, dev_config)
1079 def restart_needed(self, reason):
1080 """Determine if the vm needs to be restarted when shutdown
1081 for the given reason.
1083 @param reason: shutdown reason
1084 @return True if needs restart, False otherwise
1085 """
1086 if self.info['restart_mode'] == RESTART_NEVER:
1087 return False
1088 if self.info['restart_mode'] == RESTART_ALWAYS:
1089 return True
1090 if self.info['restart_mode'] == RESTART_ONREBOOT:
1091 return reason == 'reboot'
1092 return False
1095 def restart_check(self):
1096 """Check if domain restart is OK.
1097 To prevent restart loops, raise an error if it is
1098 less than MINIMUM_RESTART_TIME seconds since the last restart.
1099 """
1100 tnow = time.time()
1101 if self.restart_time is not None:
1102 tdelta = tnow - self.restart_time
1103 if tdelta < self.MINIMUM_RESTART_TIME:
1104 self.restart_cancel()
1105 msg = 'VM %s restarting too fast' % self.info['name']
1106 log.error(msg)
1107 raise VmError(msg)
1108 self.restart_time = tnow
1109 self.restart_count += 1
1112 def restart(self):
1113 """Restart the domain after it has exited. """
1115 # self.restart_check()
1116 self.cleanup()
1118 config = self.sxpr()
1120 if self.readVm('xend/restart_in_progress'):
1121 log.error('Xend failed during restart of domain %d. '
1122 'Refusing to restart to avoid loops.',
1123 self.domid)
1124 self.destroy()
1125 return
1127 self.writeVm('xend/restart_in_progress', 'True')
1129 try:
1130 self.destroy()
1131 try:
1132 xd = get_component('xen.xend.XendDomain')
1133 xd.domain_unpause(xd.domain_create(config).getDomid())
1134 except Exception, exn:
1135 log.exception('Failed to restart domain %d.', self.domid)
1136 finally:
1137 self.removeVm('xend/restart_in_progress')
1139 # self.configure_bootloader()
1140 # self.exportToDB()
1143 def configure_bootloader(self):
1144 if not self.info['bootloader']:
1145 return
1146 # if we're restarting with a bootloader, we need to run it
1147 # FIXME: this assumes the disk is the first device and
1148 # that we're booting from the first disk
1149 blcfg = None
1150 # FIXME: this assumes that we want to use the first disk
1151 dev = sxp.child_value(self.config, "device")
1152 if dev:
1153 disk = sxp.child_value(dev, "uname")
1154 fn = blkdev_uname_to_file(disk)
1155 blcfg = bootloader(self.info['bootloader'], fn, 1,
1156 self.info['vcpus'])
1157 if blcfg is None:
1158 msg = "Had a bootloader specified, but can't find disk"
1159 log.error(msg)
1160 raise VmError(msg)
1161 self.config = sxp.merge(['vm', ['image', blcfg]], self.config)
1164 def configure(self):
1165 """Configure a vm.
1167 """
1168 self.configure_maxmem()
1169 self.create_devices()
1172 def configure_maxmem(self):
1173 if self.image:
1174 m = self.image.getDomainMemory(self.info['memory_KiB'])
1175 xc.domain_setmaxmem(self.domid, maxmem_kb = m)
1178 def vcpu_hotplug(self, vcpu, state):
1179 """Disable or enable VCPU in domain.
1180 """
1181 if vcpu > self.info['vcpus']:
1182 log.error("Invalid VCPU %d" % vcpu)
1183 return
1184 if int(state) == 0:
1185 availability = "offline"
1186 else:
1187 availability = "online"
1188 self.storeVm("cpu/%d/availability" % vcpu, availability)
1190 def send_sysrq(self, key=0):
1191 self.storeVm("control/sysrq", '%c' % key)
1193 def dom0_init_store(self):
1194 if not self.store_channel:
1195 self.store_channel = self.eventChannel("store/port")
1196 if not self.store_channel:
1197 return
1198 ref = xc.init_store(self.store_channel.port2)
1199 if ref and ref >= 0:
1200 self.setStoreRef(ref)
1201 try:
1202 IntroduceDomain(self.domid, ref, self.store_channel.port1,
1203 self.path)
1204 except RuntimeError, ex:
1205 if ex.args[0] == errno.EISCONN:
1206 pass
1207 else:
1208 raise
1209 # get run-time value of vcpus and update store
1210 self.configure_vcpus(dom_get(self.domid)['vcpus'])
1212 def dom0_enforce_vcpus(self):
1213 dom = 0
1214 # get max number of vcpus to use for dom0 from config
1215 target = int(xroot.get_dom0_vcpus())
1216 log.debug("number of vcpus to use is %d" % (target))
1218 # target = 0 means use all processors
1219 if target > 0:
1220 # count the number of online vcpus (cpu values in v2c map >= 0)
1221 vcpu_to_cpu = dom_get(dom)['vcpu_to_cpu']
1222 vcpus_online = len(filter(lambda x: x >= 0, vcpu_to_cpu))
1223 log.debug("found %d vcpus online" % (vcpus_online))
1225 # disable any extra vcpus that are online over the requested target
1226 for vcpu in range(target, vcpus_online):
1227 log.info("enforcement is disabling DOM%d VCPU%d" % (dom, vcpu))
1228 self.vcpu_hotplug(vcpu, 0)
1231 def infoIsSet(self, name):
1232 return name in self.info and self.info[name] is not None
1235 #============================================================================
1236 # Register device controllers and their device config types.
1238 """A map from device-class names to the subclass of DevController that
1239 implements the device control specific to that device-class."""
1240 controllerClasses = {}
1243 """A map of backend names and the corresponding flag."""
1244 backendFlags = {}
1247 def addControllerClass(device_class, backend_name, backend_flag, cls):
1248 """Register a subclass of DevController to handle the named device-class.
1250 @param backend_flag One of the SIF_XYZ_BE_DOMAIN constants, or None if
1251 no flag is to be set.
1252 """
1253 cls.deviceClass = device_class
1254 backendFlags[backend_name] = backend_flag
1255 controllerClasses[device_class] = cls
1258 from xen.xend.server import blkif, netif, tpmif, pciif, usbif
1259 addControllerClass('vbd', 'blkif', SIF_BLK_BE_DOMAIN, blkif.BlkifController)
1260 addControllerClass('vif', 'netif', SIF_NET_BE_DOMAIN, netif.NetifController)
1261 addControllerClass('vtpm', 'tpmif', SIF_TPM_BE_DOMAIN, tpmif.TPMifController)
1262 addControllerClass('pci', 'pciif', None, pciif.PciController)
1263 addControllerClass('usb', 'usbif', None, usbif.UsbifController)