ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 13609:959e79bfe913

Fix device reordering that occurs when the config gets read into dictionaries.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author Ewan Mellor <ewan@xensource.com>
date Wed Jan 24 16:48:41 2007 +0000 (2007-01-24)
parents 665be23d7fe9
children b111908dd70b
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, 2006 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 time
29 import threading
30 import re
31 import copy
32 import os
33 from types import StringTypes
35 import xen.lowlevel.xc
36 from xen.util import asserts
37 from xen.util.blkif import blkdev_uname_to_file
38 from xen.util import security
40 from xen.xend import balloon, sxp, uuid, image, arch, osdep
41 from xen.xend import XendOptions, XendNode, XendConfig
43 from xen.xend.XendConfig import scrub_password
44 from xen.xend.XendBootloader import bootloader, bootloader_tidy
45 from xen.xend.XendError import XendError, VmError
46 from xen.xend.XendDevices import XendDevices
47 from xen.xend.XendTask import XendTask
48 from xen.xend.xenstore.xstransact import xstransact, complete
49 from xen.xend.xenstore.xsutil import GetDomainPath, IntroduceDomain, ResumeDomain
50 from xen.xend.xenstore.xswatch import xswatch
51 from xen.xend.XendConstants import *
52 from xen.xend.XendAPIConstants import *
54 MIGRATE_TIMEOUT = 30.0
55 BOOTLOADER_LOOPBACK_DEVICE = '/dev/xvdp'
57 xc = xen.lowlevel.xc.xc()
58 xoptions = XendOptions.instance()
60 log = logging.getLogger("xend.XendDomainInfo")
61 #log.setLevel(logging.TRACE)
64 def create(config):
65 """Creates and start a VM using the supplied configuration.
67 @param config: A configuration object involving lists of tuples.
68 @type config: list of lists, eg ['vm', ['image', 'xen.gz']]
70 @rtype: XendDomainInfo
71 @return: An up and running XendDomainInfo instance
72 @raise VmError: Invalid configuration or failure to start.
73 """
75 log.debug("XendDomainInfo.create(%s)", scrub_password(config))
76 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config))
77 try:
78 vm.start()
79 except:
80 log.exception('Domain construction failed')
81 vm.destroy()
82 raise
84 return vm
86 def create_from_dict(config_dict):
87 """Creates and start a VM using the supplied configuration.
89 @param config_dict: An configuration dictionary.
91 @rtype: XendDomainInfo
92 @return: An up and running XendDomainInfo instance
93 @raise VmError: Invalid configuration or failure to start.
94 """
96 log.debug("XendDomainInfo.create_from_dict(%s)",
97 scrub_password(config_dict))
98 vm = XendDomainInfo(XendConfig.XendConfig(xapi = config_dict))
99 try:
100 vm.start()
101 except:
102 log.exception('Domain construction failed')
103 vm.destroy()
104 raise
105 return vm
107 def recreate(info, priv):
108 """Create the VM object for an existing domain. The domain must not
109 be dying, as the paths in the store should already have been removed,
110 and asking us to recreate them causes problems.
112 @param xeninfo: Parsed configuration
113 @type xeninfo: Dictionary
114 @param priv: Is a privileged domain (Dom 0)
115 @type priv: bool
117 @rtype: XendDomainInfo
118 @return: A up and running XendDomainInfo instance
119 @raise VmError: Invalid configuration.
120 @raise XendError: Errors with configuration.
121 """
123 log.debug("XendDomainInfo.recreate(%s)", scrub_password(info))
125 assert not info['dying']
127 xeninfo = XendConfig.XendConfig(dominfo = info)
128 domid = xeninfo['domid']
129 uuid1 = uuid.fromString(xeninfo['uuid'])
130 needs_reinitialising = False
132 dompath = GetDomainPath(domid)
133 if not dompath:
134 raise XendError('No domain path in store for existing '
135 'domain %d' % domid)
137 log.info("Recreating domain %d, UUID %s. at %s" %
138 (domid, xeninfo['uuid'], dompath))
140 # need to verify the path and uuid if not Domain-0
141 # if the required uuid and vm aren't set, then that means
142 # we need to recreate the dom with our own values
143 #
144 # NOTE: this is probably not desirable, really we should just
145 # abort or ignore, but there may be cases where xenstore's
146 # entry disappears (eg. xenstore-rm /)
147 #
148 try:
149 vmpath = xstransact.Read(dompath, "vm")
150 if not vmpath:
151 log.warn('/local/domain/%d/vm is missing. recreate is '
152 'confused, trying our best to recover' % domid)
153 needs_reinitialising = True
154 raise XendError('reinit')
156 uuid2_str = xstransact.Read(vmpath, "uuid")
157 if not uuid2_str:
158 log.warn('%s/uuid/ is missing. recreate is confused, '
159 'trying our best to recover' % vmpath)
160 needs_reinitialising = True
161 raise XendError('reinit')
163 uuid2 = uuid.fromString(uuid2_str)
164 if uuid1 != uuid2:
165 log.warn('UUID in /vm does not match the UUID in /dom/%d.'
166 'Trying out best to recover' % domid)
167 needs_reinitialising = True
168 except XendError:
169 pass # our best shot at 'goto' in python :)
171 vm = XendDomainInfo(xeninfo, domid, dompath, augment = True, priv = priv)
173 if needs_reinitialising:
174 vm._recreateDom()
175 vm._removeVm()
176 vm._storeVmDetails()
177 vm._storeDomDetails()
179 if vm.info['image']: # Only dom0 should be without an image entry when
180 # recreating, but we cope with missing ones
181 # elsewhere just in case.
182 vm.image = image.create(vm,
183 vm.info,
184 vm.info['image'],
185 vm.info['devices'])
186 vm.image.recreate()
188 vm._registerWatches()
189 vm.refreshShutdown(xeninfo)
191 # register the domain in the list
192 from xen.xend import XendDomain
193 XendDomain.instance().add_domain(vm)
195 return vm
198 def restore(config):
199 """Create a domain and a VM object to do a restore.
201 @param config: Domain SXP configuration
202 @type config: list of lists. (see C{create})
204 @rtype: XendDomainInfo
205 @return: A up and running XendDomainInfo instance
206 @raise VmError: Invalid configuration or failure to start.
207 @raise XendError: Errors with configuration.
208 """
210 log.debug("XendDomainInfo.restore(%s)", scrub_password(config))
211 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config),
212 resume = True)
213 try:
214 vm.resume()
215 return vm
216 except:
217 vm.destroy()
218 raise
220 def createDormant(domconfig):
221 """Create a dormant/inactive XenDomainInfo without creating VM.
222 This is for creating instances of persistent domains that are not
223 yet start.
225 @param domconfig: Parsed configuration
226 @type domconfig: XendConfig object
228 @rtype: XendDomainInfo
229 @return: A up and running XendDomainInfo instance
230 @raise XendError: Errors with configuration.
231 """
233 log.debug("XendDomainInfo.createDormant(%s)", scrub_password(domconfig))
235 # domid does not make sense for non-running domains.
236 domconfig.pop('domid', None)
237 vm = XendDomainInfo(domconfig)
238 return vm
240 def domain_by_name(name):
241 """Get domain by name
243 @params name: Name of the domain
244 @type name: string
245 @return: XendDomainInfo or None
246 """
247 from xen.xend import XendDomain
248 return XendDomain.instance().domain_lookup_by_name_nr(name)
251 def shutdown_reason(code):
252 """Get a shutdown reason from a code.
254 @param code: shutdown code
255 @type code: int
256 @return: shutdown reason
257 @rtype: string
258 """
259 return DOMAIN_SHUTDOWN_REASONS.get(code, "?")
261 def dom_get(dom):
262 """Get info from xen for an existing domain.
264 @param dom: domain id
265 @type dom: int
266 @return: info or None
267 @rtype: dictionary
268 """
269 try:
270 domlist = xc.domain_getinfo(dom, 1)
271 if domlist and dom == domlist[0]['domid']:
272 return domlist[0]
273 except Exception, err:
274 # ignore missing domain
275 log.trace("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
276 return None
279 class XendDomainInfo:
280 """An object represents a domain.
282 @TODO: try to unify dom and domid, they mean the same thing, but
283 xc refers to it as dom, and everywhere else, including
284 xenstore it is domid. The best way is to change xc's
285 python interface.
287 @ivar info: Parsed configuration
288 @type info: dictionary
289 @ivar domid: Domain ID (if VM has started)
290 @type domid: int or None
291 @ivar vmpath: XenStore path to this VM.
292 @type vmpath: string
293 @ivar dompath: XenStore path to this Domain.
294 @type dompath: string
295 @ivar image: Reference to the VM Image.
296 @type image: xen.xend.image.ImageHandler
297 @ivar store_port: event channel to xenstored
298 @type store_port: int
299 @ivar console_port: event channel to xenconsoled
300 @type console_port: int
301 @ivar store_mfn: xenstored mfn
302 @type store_mfn: int
303 @ivar console_mfn: xenconsoled mfn
304 @type console_mfn: int
305 @ivar vmWatch: reference to a watch on the xenstored vmpath
306 @type vmWatch: xen.xend.xenstore.xswatch
307 @ivar shutdownWatch: reference to watch on the xenstored domain shutdown
308 @type shutdownWatch: xen.xend.xenstore.xswatch
309 @ivar shutdownStartTime: UNIX Time when domain started shutting down.
310 @type shutdownStartTime: float or None
311 @ivar state: Domain state
312 @type state: enum(DOM_STATE_HALTED, DOM_STATE_RUNNING, ...)
313 @ivar state_updated: lock for self.state
314 @type state_updated: threading.Condition
315 @ivar refresh_shutdown_lock: lock for polling shutdown state
316 @type refresh_shutdown_lock: threading.Condition
317 @ivar _deviceControllers: device controller cache for this domain
318 @type _deviceControllers: dict 'string' to DevControllers
319 """
321 def __init__(self, info, domid = None, dompath = None, augment = False,
322 priv = False, resume = False):
323 """Constructor for a domain
325 @param info: parsed configuration
326 @type info: dictionary
327 @keyword domid: Set initial domain id (if any)
328 @type domid: int
329 @keyword dompath: Set initial dompath (if any)
330 @type dompath: string
331 @keyword augment: Augment given info with xenstored VM info
332 @type augment: bool
333 @keyword priv: Is a privileged domain (Dom 0)
334 @type priv: bool
335 @keyword resume: Is this domain being resumed?
336 @type resume: bool
337 """
339 self.info = info
340 if domid == None:
341 self.domid = self.info.get('domid')
342 else:
343 self.domid = domid
345 #REMOVE: uuid is now generated in XendConfig
346 #if not self._infoIsSet('uuid'):
347 # self.info['uuid'] = uuid.toString(uuid.create())
349 self.vmpath = XS_VMROOT + self.info['uuid']
350 self.dompath = dompath
352 self.image = None
353 self.store_port = None
354 self.store_mfn = None
355 self.console_port = None
356 self.console_mfn = None
358 self.vmWatch = None
359 self.shutdownWatch = None
360 self.shutdownStartTime = None
361 self._resume = resume
363 self.state = DOM_STATE_HALTED
364 self.state_updated = threading.Condition()
365 self.refresh_shutdown_lock = threading.Condition()
367 self._deviceControllers = {}
369 for state in DOM_STATES_OLD:
370 self.info[state] = 0
372 if augment:
373 self._augmentInfo(priv)
375 self._checkName(self.info['name_label'])
378 #
379 # Public functions available through XMLRPC
380 #
383 def start(self, is_managed = False):
384 """Attempts to start the VM by do the appropriate
385 initialisation if it not started.
386 """
387 from xen.xend import XendDomain
389 if self.state == DOM_STATE_HALTED:
390 try:
391 XendTask.log_progress(0, 30, self._constructDomain)
392 XendTask.log_progress(31, 60, self._initDomain)
394 XendTask.log_progress(61, 70, self._storeVmDetails)
395 XendTask.log_progress(71, 80, self._storeDomDetails)
396 XendTask.log_progress(81, 90, self._registerWatches)
397 XendTask.log_progress(91, 100, self.refreshShutdown)
399 # save running configuration if XendDomains believe domain is
400 # persistent
401 if is_managed:
402 xendomains = XendDomain.instance()
403 xendomains.managed_config_save(self)
404 except:
405 log.exception('VM start failed')
406 self.destroy()
407 raise
408 else:
409 raise XendError('VM already running')
411 def resume(self):
412 """Resumes a domain that has come back from suspension."""
413 if self.state in (DOM_STATE_HALTED, DOM_STATE_SUSPENDED):
414 try:
415 self._constructDomain()
416 self._storeVmDetails()
417 self._createDevices()
418 self._createChannels()
419 self._storeDomDetails()
420 self._endRestore()
421 except:
422 log.exception('VM resume failed')
423 raise
424 else:
425 raise XendError('VM already running')
427 def shutdown(self, reason):
428 """Shutdown a domain by signalling this via xenstored."""
429 log.debug('XendDomainInfo.shutdown(%s)', reason)
430 if self.state in (DOM_STATE_SHUTDOWN, DOM_STATE_HALTED,):
431 raise XendError('Domain cannot be shutdown')
433 if self.domid == 0:
434 raise XendError('Domain 0 cannot be shutdown')
436 if reason not in DOMAIN_SHUTDOWN_REASONS.values():
437 raise XendError('Invalid reason: %s' % reason)
438 self._removeVm('xend/previous_restart_time')
439 self.storeDom("control/shutdown", reason)
441 ## shutdown hypercall for hvm domain desides xenstore write
442 image_cfg = self.info.get('image', {})
443 hvm = image_cfg.has_key('hvm')
444 if hvm:
445 for code in DOMAIN_SHUTDOWN_REASONS.keys():
446 if DOMAIN_SHUTDOWN_REASONS[code] == reason:
447 break
448 xc.domain_shutdown(self.domid, code)
451 def pause(self):
452 """Pause domain
454 @raise XendError: Failed pausing a domain
455 """
456 try:
457 xc.domain_pause(self.domid)
458 self._stateSet(DOM_STATE_PAUSED)
459 except Exception, ex:
460 log.exception(ex)
461 raise XendError("Domain unable to be paused: %s" % str(ex))
463 def unpause(self):
464 """Unpause domain
466 @raise XendError: Failed unpausing a domain
467 """
468 try:
469 xc.domain_unpause(self.domid)
470 self._stateSet(DOM_STATE_RUNNING)
471 except Exception, ex:
472 log.exception(ex)
473 raise XendError("Domain unable to be unpaused: %s" % str(ex))
475 def send_sysrq(self, key):
476 """ Send a Sysrq equivalent key via xenstored."""
477 asserts.isCharConvertible(key)
478 self.storeDom("control/sysrq", '%c' % key)
480 def device_create(self, dev_config):
481 """Create a new device.
483 @param dev_config: device configuration
484 @type dev_config: SXP object (parsed config)
485 """
486 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config))
487 dev_type = sxp.name(dev_config)
488 dev_uuid = self.info.device_add(dev_type, cfg_sxp = dev_config)
489 dev_config_dict = self.info['devices'][dev_uuid][1]
490 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config_dict))
491 dev_config_dict['devid'] = devid = \
492 self._createDevice(dev_type, dev_config_dict)
493 self._waitForDevice(dev_type, devid)
494 return self.getDeviceController(dev_type).sxpr(devid)
496 def device_configure(self, dev_sxp, devid = None):
497 """Configure an existing device.
499 @param dev_config: device configuration
500 @type dev_config: SXP object (parsed config)
501 @param devid: device id
502 @type devid: int
503 @return: Returns True if successfully updated device
504 @rtype: boolean
505 """
507 # convert device sxp to a dict
508 dev_class = sxp.name(dev_sxp)
509 dev_config = {}
510 for opt_val in dev_sxp[1:]:
511 try:
512 dev_config[opt_val[0]] = opt_val[1]
513 except IndexError:
514 pass
516 # use DevController.reconfigureDevice to change device config
517 dev_control = self.getDeviceController(dev_class)
518 dev_uuid = dev_control.reconfigureDevice(devid, dev_config)
520 # update XendConfig with new device info
521 if dev_uuid:
522 self.info.device_update(dev_uuid, dev_sxp)
524 return True
526 def waitForDevices(self):
527 """Wait for this domain's configured devices to connect.
529 @raise VmError: if any device fails to initialise.
530 """
531 for devclass in XendDevices.valid_devices():
532 self.getDeviceController(devclass).waitForDevices()
534 def destroyDevice(self, deviceClass, devid, force = False):
535 try:
536 devid = int(devid)
537 except ValueError:
538 # devid is not a number, let's search for it in xenstore.
539 devicePath = '%s/device/%s' % (self.dompath, deviceClass)
540 for entry in xstransact.List(devicePath):
541 backend = xstransact.Read('%s/%s' % (devicePath, entry),
542 "backend")
543 devName = xstransact.Read(backend, "dev")
544 if devName == devid:
545 # We found the integer matching our devid, use it instead
546 devid = entry
547 break
549 return self.getDeviceController(deviceClass).destroyDevice(devid, force)
551 def getDeviceSxprs(self, deviceClass):
552 if self.state == DOM_STATE_RUNNING:
553 return self.getDeviceController(deviceClass).sxprs()
554 else:
555 sxprs = []
556 dev_num = 0
557 for dev_type, dev_info in self.info.all_devices_sxpr():
558 if dev_type == deviceClass:
559 sxprs.append([dev_num, dev_info])
560 dev_num += 1
561 return sxprs
564 def setMemoryTarget(self, target):
565 """Set the memory target of this domain.
566 @param target: In MiB.
567 """
568 log.debug("Setting memory target of domain %s (%d) to %d MiB.",
569 self.info['name_label'], self.domid, target)
571 if target <= 0:
572 raise XendError('Invalid memory size')
574 self.info['memory_static_min'] = target
575 if self.domid >= 0:
576 self.storeVm("memory", target)
577 self.storeDom("memory/target", target << 10)
578 else:
579 self.info['memory_dynamic_min'] = target
580 xen.xend.XendDomain.instance().managed_config_save(self)
582 def setMemoryMaximum(self, limit):
583 """Set the maximum memory limit of this domain
584 @param limit: In MiB.
585 """
586 log.debug("Setting memory maximum of domain %s (%d) to %d MiB.",
587 self.info['name_label'], self.domid, limit)
589 if limit <= 0:
590 raise XendError('Invalid memory size')
592 self.info['memory_static_max'] = limit
593 if self.domid >= 0:
594 maxmem = int(limit) * 1024
595 try:
596 return xc.domain_setmaxmem(self.domid, maxmem)
597 except Exception, ex:
598 raise XendError(str(ex))
599 else:
600 self.info['memory_dynamic_max'] = limit
601 xen.xend.XendDomain.instance().managed_config_save(self)
604 def getVCPUInfo(self):
605 try:
606 # We include the domain name and ID, to help xm.
607 sxpr = ['domain',
608 ['domid', self.domid],
609 ['name', self.info['name_label']],
610 ['vcpu_count', self.info['vcpus_number']]]
612 for i in range(0, self.info['vcpus_number']):
613 info = xc.vcpu_getinfo(self.domid, i)
615 sxpr.append(['vcpu',
616 ['number', i],
617 ['online', info['online']],
618 ['blocked', info['blocked']],
619 ['running', info['running']],
620 ['cpu_time', info['cpu_time'] / 1e9],
621 ['cpu', info['cpu']],
622 ['cpumap', info['cpumap']]])
624 return sxpr
626 except RuntimeError, exn:
627 raise XendError(str(exn))
629 #
630 # internal functions ... TODO: re-categorised
631 #
633 def _augmentInfo(self, priv):
634 """Augment self.info, as given to us through L{recreate}, with
635 values taken from the store. This recovers those values known
636 to xend but not to the hypervisor.
637 """
638 augment_entries = XendConfig.LEGACY_XENSTORE_VM_PARAMS[:]
639 if priv:
640 augment_entries.remove('memory')
641 augment_entries.remove('maxmem')
642 augment_entries.remove('vcpus')
643 augment_entries.remove('vcpu_avail')
645 vm_config = self._readVMDetails([(k, XendConfig.LEGACY_CFG_TYPES[k])
646 for k in augment_entries])
648 # make returned lists into a dictionary
649 vm_config = dict(zip(augment_entries, vm_config))
651 for arg in augment_entries:
652 xapicfg = arg
653 val = vm_config[arg]
654 if val != None:
655 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
656 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
657 self.info[xapiarg] = val
658 else:
659 self.info[arg] = val
661 # For dom0, we ignore any stored value for the vcpus fields, and
662 # read the current value from Xen instead. This allows boot-time
663 # settings to take precedence over any entries in the store.
664 if priv:
665 xeninfo = dom_get(self.domid)
666 self.info['vcpus_number'] = xeninfo['online_vcpus']
667 self.info['vcpu_avail'] = (1 << xeninfo['online_vcpus']) - 1
669 # read image value
670 image_sxp = self._readVm('image')
671 if image_sxp:
672 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
674 # read devices
675 devices = []
676 for devclass in XendDevices.valid_devices():
677 devconfig = self.getDeviceController(devclass).configurations()
678 if devconfig:
679 devices.extend(devconfig)
681 if not self.info['devices'] and devices is not None:
682 for device in devices:
683 self.info.device_add(device[0], cfg_sxp = device)
685 #
686 # Function to update xenstore /vm/*
687 #
689 def _readVm(self, *args):
690 return xstransact.Read(self.vmpath, *args)
692 def _writeVm(self, *args):
693 return xstransact.Write(self.vmpath, *args)
695 def _removeVm(self, *args):
696 return xstransact.Remove(self.vmpath, *args)
698 def _gatherVm(self, *args):
699 return xstransact.Gather(self.vmpath, *args)
701 def storeVm(self, *args):
702 return xstransact.Store(self.vmpath, *args)
704 #
705 # Function to update xenstore /dom/*
706 #
708 def readDom(self, *args):
709 return xstransact.Read(self.dompath, *args)
711 def gatherDom(self, *args):
712 return xstransact.Gather(self.dompath, *args)
714 def _writeDom(self, *args):
715 return xstransact.Write(self.dompath, *args)
717 def _removeDom(self, *args):
718 return xstransact.Remove(self.dompath, *args)
720 def storeDom(self, *args):
721 return xstransact.Store(self.dompath, *args)
723 def _recreateDom(self):
724 complete(self.dompath, lambda t: self._recreateDomFunc(t))
726 def _recreateDomFunc(self, t):
727 t.remove()
728 t.mkdir()
729 t.set_permissions({'dom' : self.domid})
730 t.write('vm', self.vmpath)
732 def _storeDomDetails(self):
733 to_store = {
734 'domid': str(self.domid),
735 'vm': self.vmpath,
736 'name': self.info['name_label'],
737 'console/limit': str(xoptions.get_console_limit() * 1024),
738 'memory/target': str(self.info['memory_static_min'] * 1024)
739 }
741 def f(n, v):
742 if v is not None:
743 to_store[n] = str(v)
745 f('console/port', self.console_port)
746 f('console/ring-ref', self.console_mfn)
747 f('store/port', self.store_port)
748 f('store/ring-ref', self.store_mfn)
750 to_store.update(self._vcpuDomDetails())
752 log.debug("Storing domain details: %s", scrub_password(to_store))
754 self._writeDom(to_store)
756 def _vcpuDomDetails(self):
757 def availability(n):
758 if self.info['vcpu_avail'] & (1 << n):
759 return 'online'
760 else:
761 return 'offline'
763 result = {}
764 for v in range(0, self.info['vcpus_number']):
765 result["cpu/%d/availability" % v] = availability(v)
766 return result
768 #
769 # xenstore watches
770 #
772 def _registerWatches(self):
773 """Register a watch on this VM's entries in the store, and the
774 domain's control/shutdown node, so that when they are changed
775 externally, we keep up to date. This should only be called by {@link
776 #create}, {@link #recreate}, or {@link #restore}, once the domain's
777 details have been written, but before the new instance is returned."""
778 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
779 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
780 self._handleShutdownWatch)
782 def _storeChanged(self, _):
783 log.trace("XendDomainInfo.storeChanged");
785 changed = False
787 # Check whether values in the configuration have
788 # changed in Xenstore.
790 cfg_vm = ['name', 'on_poweroff', 'on_reboot', 'on_crash']
792 vm_details = self._readVMDetails([(k,XendConfig.LEGACY_CFG_TYPES[k])
793 for k in cfg_vm])
795 # convert two lists into a python dictionary
796 vm_details = dict(zip(cfg_vm, vm_details))
798 for arg, val in vm_details.items():
799 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
800 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
801 if val != None and val != self.info[xapiarg]:
802 self.info[xapiarg] = val
803 changed= True
805 # Check whether image definition has been updated
806 image_sxp = self._readVm('image')
807 if image_sxp and image_sxp != self.info.image_sxpr():
808 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
809 changed = True
811 if changed:
812 # Update the domain section of the store, as this contains some
813 # parameters derived from the VM configuration.
814 self._storeDomDetails()
816 return 1
818 def _handleShutdownWatch(self, _):
819 log.debug('XendDomainInfo.handleShutdownWatch')
821 reason = self.readDom('control/shutdown')
823 if reason and reason != 'suspend':
824 sst = self.readDom('xend/shutdown_start_time')
825 now = time.time()
826 if sst:
827 self.shutdownStartTime = float(sst)
828 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
829 else:
830 self.shutdownStartTime = now
831 self.storeDom('xend/shutdown_start_time', now)
832 timeout = SHUTDOWN_TIMEOUT
834 log.trace(
835 "Scheduling refreshShutdown on domain %d in %ds.",
836 self.domid, timeout)
837 threading.Timer(timeout, self.refreshShutdown).start()
839 return True
842 #
843 # Public Attributes for the VM
844 #
847 def getDomid(self):
848 return self.domid
850 def setName(self, name):
851 self._checkName(name)
852 self.info['name_label'] = name
853 self.storeVm("name", name)
855 def getName(self):
856 return self.info['name_label']
858 def getDomainPath(self):
859 return self.dompath
861 def getShutdownReason(self):
862 return self.readDom('control/shutdown')
864 def getStorePort(self):
865 """For use only by image.py and XendCheckpoint.py."""
866 return self.store_port
868 def getConsolePort(self):
869 """For use only by image.py and XendCheckpoint.py"""
870 return self.console_port
872 def getFeatures(self):
873 """For use only by image.py."""
874 return self.info['features']
876 def getVCpuCount(self):
877 return self.info['vcpus_number']
879 def setVCpuCount(self, vcpus):
880 self.info['vcpu_avail'] = (1 << vcpus) - 1
881 if self.domid >= 0:
882 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
883 # update dom differently depending on whether we are adjusting
884 # vcpu number up or down, otherwise _vcpuDomDetails does not
885 # disable the vcpus
886 if self.info['vcpus_number'] > vcpus:
887 # decreasing
888 self._writeDom(self._vcpuDomDetails())
889 self.info['vcpus_number'] = vcpus
890 else:
891 # same or increasing
892 self.info['vcpus_number'] = vcpus
893 self._writeDom(self._vcpuDomDetails())
894 else:
895 self.info['vcpus_number'] = vcpus
896 xen.xend.XendDomain.instance().managed_config_save(self)
897 log.info("Set VCPU count on domain %s to %d", self.info['name_label'],
898 vcpus)
900 def getLabel(self):
901 return security.get_security_info(self.info, 'label')
903 def getMemoryTarget(self):
904 """Get this domain's target memory size, in KB."""
905 return self.info['memory_static_min'] * 1024
907 def getMemoryMaximum(self):
908 """Get this domain's maximum memory size, in KB."""
909 return self.info['memory_static_max'] * 1024
911 def getResume(self):
912 return str(self._resume)
914 def getCap(self):
915 return self.info.get('cpu_cap', 0)
917 def getWeight(self):
918 return self.info.get('cpu_weight', 256)
920 def setResume(self, state):
921 self._resume = state
923 def getRestartCount(self):
924 return self._readVm('xend/restart_count')
926 def refreshShutdown(self, xeninfo = None):
927 """ Checks the domain for whether a shutdown is required.
929 Called from XendDomainInfo and also image.py for HVM images.
930 """
932 # If set at the end of this method, a restart is required, with the
933 # given reason. This restart has to be done out of the scope of
934 # refresh_shutdown_lock.
935 restart_reason = None
937 self.refresh_shutdown_lock.acquire()
938 try:
939 if xeninfo is None:
940 xeninfo = dom_get(self.domid)
941 if xeninfo is None:
942 # The domain no longer exists. This will occur if we have
943 # scheduled a timer to check for shutdown timeouts and the
944 # shutdown succeeded. It will also occur if someone
945 # destroys a domain beneath us. We clean up the domain,
946 # just in case, but we can't clean up the VM, because that
947 # VM may have migrated to a different domain on this
948 # machine.
949 self.cleanupDomain()
950 self._stateSet(DOM_STATE_HALTED)
951 return
953 if xeninfo['dying']:
954 # Dying means that a domain has been destroyed, but has not
955 # yet been cleaned up by Xen. This state could persist
956 # indefinitely if, for example, another domain has some of its
957 # pages mapped. We might like to diagnose this problem in the
958 # future, but for now all we do is make sure that it's not us
959 # holding the pages, by calling cleanupDomain. We can't
960 # clean up the VM, as above.
961 self.cleanupDomain()
962 self._stateSet(DOM_STATE_SHUTDOWN)
963 return
965 elif xeninfo['crashed']:
966 if self.readDom('xend/shutdown_completed'):
967 # We've seen this shutdown already, but we are preserving
968 # the domain for debugging. Leave it alone.
969 return
971 log.warn('Domain has crashed: name=%s id=%d.',
972 self.info['name_label'], self.domid)
973 self._writeVm(LAST_SHUTDOWN_REASON, 'crash')
975 if xoptions.get_enable_dump():
976 try:
977 self.dumpCore()
978 except XendError:
979 # This error has been logged -- there's nothing more
980 # we can do in this context.
981 pass
983 restart_reason = 'crash'
984 self._stateSet(DOM_STATE_HALTED)
986 elif xeninfo['shutdown']:
987 self._stateSet(DOM_STATE_SHUTDOWN)
988 if self.readDom('xend/shutdown_completed'):
989 # We've seen this shutdown already, but we are preserving
990 # the domain for debugging. Leave it alone.
991 return
993 else:
994 reason = shutdown_reason(xeninfo['shutdown_reason'])
996 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
997 self.info['name_label'], self.domid, reason)
998 self._writeVm(LAST_SHUTDOWN_REASON, reason)
1000 self._clearRestart()
1002 if reason == 'suspend':
1003 self._stateSet(DOM_STATE_SUSPENDED)
1004 # Don't destroy the domain. XendCheckpoint will do
1005 # this once it has finished. However, stop watching
1006 # the VM path now, otherwise we will end up with one
1007 # watch for the old domain, and one for the new.
1008 self._unwatchVm()
1009 elif reason in ('poweroff', 'reboot'):
1010 restart_reason = reason
1011 else:
1012 self.destroy()
1014 elif self.dompath is None:
1015 # We have yet to manage to call introduceDomain on this
1016 # domain. This can happen if a restore is in progress, or has
1017 # failed. Ignore this domain.
1018 pass
1019 else:
1020 # Domain is alive. If we are shutting it down, then check
1021 # the timeout on that, and destroy it if necessary.
1022 if xeninfo['paused']:
1023 self._stateSet(DOM_STATE_PAUSED)
1024 else:
1025 self._stateSet(DOM_STATE_RUNNING)
1027 if self.shutdownStartTime:
1028 timeout = (SHUTDOWN_TIMEOUT - time.time() +
1029 self.shutdownStartTime)
1030 if timeout < 0:
1031 log.info(
1032 "Domain shutdown timeout expired: name=%s id=%s",
1033 self.info['name_label'], self.domid)
1034 self.destroy()
1035 finally:
1036 self.refresh_shutdown_lock.release()
1038 if restart_reason:
1039 threading.Thread(target = self._maybeRestart,
1040 args = (restart_reason,)).start()
1044 # Restart functions - handling whether we come back up on shutdown.
1047 def _clearRestart(self):
1048 self._removeDom("xend/shutdown_start_time")
1051 def _maybeRestart(self, reason):
1052 # Dispatch to the correct method based upon the configured on_{reason}
1053 # behaviour.
1054 actions = {"destroy" : self.destroy,
1055 "restart" : self._restart,
1056 "preserve" : self._preserve,
1057 "rename-restart" : self._renameRestart}
1059 action_conf = {
1060 'poweroff': 'actions_after_shutdown',
1061 'reboot': 'actions_after_reboot',
1062 'crash': 'actions_after_crash',
1065 action_target = self.info.get(action_conf.get(reason))
1066 func = actions.get(action_target, None)
1067 if func and callable(func):
1068 func()
1069 else:
1070 self.destroy() # default to destroy
1072 def _renameRestart(self):
1073 self._restart(True)
1075 def _restart(self, rename = False):
1076 """Restart the domain after it has exited.
1078 @param rename True if the old domain is to be renamed and preserved,
1079 False if it is to be destroyed.
1080 """
1081 from xen.xend import XendDomain
1083 if self._readVm(RESTART_IN_PROGRESS):
1084 log.error('Xend failed during restart of domain %s. '
1085 'Refusing to restart to avoid loops.',
1086 str(self.domid))
1087 self.destroy()
1088 return
1090 old_domid = self.domid
1091 self._writeVm(RESTART_IN_PROGRESS, 'True')
1093 now = time.time()
1094 rst = self._readVm('xend/previous_restart_time')
1095 if rst:
1096 rst = float(rst)
1097 timeout = now - rst
1098 if timeout < MINIMUM_RESTART_TIME:
1099 log.error(
1100 'VM %s restarting too fast (%f seconds since the last '
1101 'restart). Refusing to restart to avoid loops.',
1102 self.info['name_label'], timeout)
1103 self.destroy()
1104 return
1106 self._writeVm('xend/previous_restart_time', str(now))
1108 try:
1109 if rename:
1110 self._preserveForRestart()
1111 else:
1112 self._unwatchVm()
1113 self.destroyDomain()
1115 # new_dom's VM will be the same as this domain's VM, except where
1116 # the rename flag has instructed us to call preserveForRestart.
1117 # In that case, it is important that we remove the
1118 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1119 # once the new one is available.
1121 new_dom = None
1122 try:
1123 new_dom = XendDomain.instance().domain_create_from_dict(
1124 self.info)
1125 new_dom.unpause()
1126 rst_cnt = self._readVm('xend/restart_count')
1127 rst_cnt = int(rst_cnt) + 1
1128 self._writeVm('xend/restart_count', str(rst_cnt))
1129 new_dom._removeVm(RESTART_IN_PROGRESS)
1130 except:
1131 if new_dom:
1132 new_dom._removeVm(RESTART_IN_PROGRESS)
1133 new_dom.destroy()
1134 else:
1135 self._removeVm(RESTART_IN_PROGRESS)
1136 raise
1137 except:
1138 log.exception('Failed to restart domain %s.', str(old_domid))
1140 def _preserveForRestart(self):
1141 """Preserve a domain that has been shut down, by giving it a new UUID,
1142 cloning the VM details, and giving it a new name. This allows us to
1143 keep this domain for debugging, but restart a new one in its place
1144 preserving the restart semantics (name and UUID preserved).
1145 """
1147 new_uuid = uuid.createString()
1148 new_name = 'Domain-%s' % new_uuid
1149 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1150 self.info['name_label'], self.domid, self.info['uuid'],
1151 new_name, new_uuid)
1152 self._unwatchVm()
1153 self._releaseDevices()
1154 self.info['name_label'] = new_name
1155 self.info['uuid'] = new_uuid
1156 self.vmpath = XS_VMROOT + new_uuid
1157 self._storeVmDetails()
1158 self._preserve()
1161 def _preserve(self):
1162 log.info("Preserving dead domain %s (%d).", self.info['name_label'],
1163 self.domid)
1164 self._unwatchVm()
1165 self.storeDom('xend/shutdown_completed', 'True')
1166 self._stateSet(DOM_STATE_HALTED)
1169 # Debugging ..
1172 def dumpCore(self, corefile = None):
1173 """Create a core dump for this domain.
1175 @raise: XendError if core dumping failed.
1176 """
1178 try:
1179 if not corefile:
1180 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1181 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1182 self.info['name_label'], self.domid)
1184 if os.path.isdir(corefile):
1185 raise XendError("Cannot dump core in a directory: %s" %
1186 corefile)
1188 xc.domain_dumpcore(self.domid, corefile)
1189 except RuntimeError, ex:
1190 corefile_incomp = corefile+'-incomplete'
1191 os.rename(corefile, corefile_incomp)
1192 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1193 self.domid, self.info['name_label'])
1194 raise XendError("Failed to dump core: %s" % str(ex))
1197 # Device creation/deletion functions
1200 def _createDevice(self, deviceClass, devConfig):
1201 return self.getDeviceController(deviceClass).createDevice(devConfig)
1203 def _waitForDevice(self, deviceClass, devid):
1204 return self.getDeviceController(deviceClass).waitForDevice(devid)
1206 def _waitForDeviceUUID(self, dev_uuid):
1207 deviceClass, config = self.info['devices'].get(dev_uuid)
1208 self._waitForDevice(deviceClass, config['devid'])
1210 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1211 return self.getDeviceController(deviceClass).reconfigureDevice(
1212 devid, devconfig)
1214 def _createDevices(self):
1215 """Create the devices for a vm.
1217 @raise: VmError for invalid devices
1218 """
1219 ordered_refs = self.info.ordered_device_refs()
1220 for dev_uuid in ordered_refs:
1221 devclass, config = self.info['devices'][dev_uuid]
1222 if devclass in XendDevices.valid_devices():
1223 log.info("createDevice: %s : %s" % (devclass, scrub_password(config)))
1224 dev_uuid = config.get('uuid')
1225 devid = self._createDevice(devclass, config)
1227 # store devid in XendConfig for caching reasons
1228 if dev_uuid in self.info['devices']:
1229 self.info['devices'][dev_uuid][1]['devid'] = devid
1231 if self.image:
1232 self.image.createDeviceModel()
1234 def _releaseDevices(self, suspend = False):
1235 """Release all domain's devices. Nothrow guarantee."""
1236 if suspend and self.image:
1237 self.image.destroy(suspend)
1238 return
1240 while True:
1241 t = xstransact("%s/device" % self.dompath)
1242 for devclass in XendDevices.valid_devices():
1243 for dev in t.list(devclass):
1244 try:
1245 t.remove(dev)
1246 except:
1247 # Log and swallow any exceptions in removal --
1248 # there's nothing more we can do.
1249 log.exception(
1250 "Device release failed: %s; %s; %s",
1251 self.info['name_label'], devclass, dev)
1252 if t.commit():
1253 break
1255 def getDeviceController(self, name):
1256 """Get the device controller for this domain, and if it
1257 doesn't exist, create it.
1259 @param name: device class name
1260 @type name: string
1261 @rtype: subclass of DevController
1262 """
1263 if name not in self._deviceControllers:
1264 devController = XendDevices.make_controller(name, self)
1265 if not devController:
1266 raise XendError("Unknown device type: %s" % name)
1267 self._deviceControllers[name] = devController
1269 return self._deviceControllers[name]
1272 # Migration functions (public)
1275 def testMigrateDevices(self, network, dst):
1276 """ Notify all device about intention of migration
1277 @raise: XendError for a device that cannot be migrated
1278 """
1279 for (n, c) in self.info.all_devices_sxpr():
1280 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1281 if rc != 0:
1282 raise XendError("Device of type '%s' refuses migration." % n)
1284 def migrateDevices(self, network, dst, step, domName=''):
1285 """Notify the devices about migration
1286 """
1287 ctr = 0
1288 try:
1289 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1290 self.migrateDevice(dev_type, dev_conf, network, dst,
1291 step, domName)
1292 ctr = ctr + 1
1293 except:
1294 for dev_type, dev_conf in self.info.all_devices_sxpr():
1295 if ctr == 0:
1296 step = step - 1
1297 ctr = ctr - 1
1298 self._recoverMigrateDevice(dev_type, dev_conf, network,
1299 dst, step, domName)
1300 raise
1302 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1303 step, domName=''):
1304 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1305 network, dst, step, domName)
1307 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1308 dst, step, domName=''):
1309 return self.getDeviceController(deviceClass).recover_migrate(
1310 deviceConfig, network, dst, step, domName)
1313 ## private:
1315 def _constructDomain(self):
1316 """Construct the domain.
1318 @raise: VmError on error
1319 """
1321 log.debug('XendDomainInfo.constructDomain')
1323 self.shutdownStartTime = None
1325 image_cfg = self.info.get('image', {})
1326 hvm = image_cfg.has_key('hvm')
1328 if hvm:
1329 info = xc.xeninfo()
1330 if 'hvm' not in info['xen_caps']:
1331 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1332 "supported by your CPU and enabled in your "
1333 "BIOS?")
1335 self.domid = xc.domain_create(
1336 domid = 0,
1337 ssidref = security.get_security_info(self.info, 'ssidref'),
1338 handle = uuid.fromString(self.info['uuid']),
1339 hvm = int(hvm))
1341 if self.domid < 0:
1342 raise VmError('Creating domain failed: name=%s' %
1343 self.info['name_label'])
1345 self.dompath = GetDomainPath(self.domid)
1347 self._recreateDom()
1349 # Set maximum number of vcpus in domain
1350 xc.domain_max_vcpus(self.domid, int(self.info['vcpus_number']))
1352 # register the domain in the list
1353 from xen.xend import XendDomain
1354 XendDomain.instance().add_domain(self)
1356 def _introduceDomain(self):
1357 assert self.domid is not None
1358 assert self.store_mfn is not None
1359 assert self.store_port is not None
1361 try:
1362 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1363 except RuntimeError, exn:
1364 raise XendError(str(exn))
1367 def _initDomain(self):
1368 log.debug('XendDomainInfo.initDomain: %s %s',
1369 self.domid,
1370 self.info['cpu_weight'])
1372 self._configureBootloader()
1374 if not self._infoIsSet('image'):
1375 raise VmError('Missing image in configuration')
1377 try:
1378 self.image = image.create(self,
1379 self.info,
1380 self.info['image'],
1381 self.info['devices'])
1383 localtime = self.info.get('localtime', False)
1384 if localtime:
1385 xc.domain_set_time_offset(self.domid)
1387 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1389 # repin domain vcpus if a restricted cpus list is provided
1390 # this is done prior to memory allocation to aide in memory
1391 # distribution for NUMA systems.
1392 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1393 for v in range(0, self.info['vcpus_number']):
1394 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1396 # Use architecture- and image-specific calculations to determine
1397 # the various headrooms necessary, given the raw configured
1398 # values. maxmem, memory, and shadow are all in KiB.
1399 memory = self.image.getRequiredAvailableMemory(
1400 self.info['memory_static_min'] * 1024)
1401 maxmem = self.image.getRequiredAvailableMemory(
1402 self.info['memory_static_max'] * 1024)
1403 shadow = self.image.getRequiredShadowMemory(
1404 self.info['shadow_memory'] * 1024,
1405 self.info['memory_static_max'] * 1024)
1407 log.debug("_initDomain:shadow_memory=0x%x, memory_static_max=0x%x, memory_static_min=0x%x.", self.info['shadow_memory'], self.info['memory_static_max'], self.info['memory_static_min'],)
1408 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1409 # takes MiB and we must not round down and end up under-providing.
1410 shadow = ((shadow + 1023) / 1024) * 1024
1412 # set memory limit
1413 xc.domain_setmaxmem(self.domid, maxmem)
1415 # Make sure there's enough RAM available for the domain
1416 balloon.free(memory + shadow)
1418 # Set up the shadow memory
1419 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1420 self.info['shadow_memory'] = shadow_cur
1422 self._createChannels()
1424 channel_details = self.image.createImage()
1426 self.store_mfn = channel_details['store_mfn']
1427 if 'console_mfn' in channel_details:
1428 self.console_mfn = channel_details['console_mfn']
1430 self._introduceDomain()
1432 self._createDevices()
1434 self.image.cleanupBootloading()
1436 self.info['start_time'] = time.time()
1438 self._stateSet(DOM_STATE_RUNNING)
1439 except RuntimeError, exn:
1440 log.exception("XendDomainInfo.initDomain: exception occurred")
1441 self.image.cleanupBootloading()
1442 raise VmError(str(exn))
1445 def cleanupDomain(self):
1446 """Cleanup domain resources; release devices. Idempotent. Nothrow
1447 guarantee."""
1449 self.refresh_shutdown_lock.acquire()
1450 try:
1451 self.unwatchShutdown()
1452 self._releaseDevices()
1453 bootloader_tidy(self)
1455 if self.image:
1456 try:
1457 self.image.destroy()
1458 except:
1459 log.exception(
1460 "XendDomainInfo.cleanup: image.destroy() failed.")
1461 self.image = None
1463 try:
1464 self._removeDom()
1465 except:
1466 log.exception("Removing domain path failed.")
1468 self._stateSet(DOM_STATE_HALTED)
1469 finally:
1470 self.refresh_shutdown_lock.release()
1473 def unwatchShutdown(self):
1474 """Remove the watch on the domain's control/shutdown node, if any.
1475 Idempotent. Nothrow guarantee. Expects to be protected by the
1476 refresh_shutdown_lock."""
1478 try:
1479 try:
1480 if self.shutdownWatch:
1481 self.shutdownWatch.unwatch()
1482 finally:
1483 self.shutdownWatch = None
1484 except:
1485 log.exception("Unwatching control/shutdown failed.")
1487 def waitForShutdown(self):
1488 self.state_updated.acquire()
1489 try:
1490 while self.state in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1491 self.state_updated.wait()
1492 finally:
1493 self.state_updated.release()
1497 # TODO: recategorise - called from XendCheckpoint
1500 def completeRestore(self, store_mfn, console_mfn):
1502 log.debug("XendDomainInfo.completeRestore")
1504 self.store_mfn = store_mfn
1505 self.console_mfn = console_mfn
1507 self._introduceDomain()
1508 image_cfg = self.info.get('image', {})
1509 hvm = image_cfg.has_key('hvm')
1510 if hvm:
1511 self.image = image.create(self,
1512 self.info,
1513 self.info['image'],
1514 self.info['devices'])
1515 if self.image:
1516 self.image.createDeviceModel(True)
1517 self.image.register_shutdown_watch()
1518 self._storeDomDetails()
1519 self._registerWatches()
1520 self.refreshShutdown()
1522 log.debug("XendDomainInfo.completeRestore done")
1525 def _endRestore(self):
1526 self.setResume(False)
1529 # VM Destroy
1532 def destroy(self):
1533 """Cleanup VM and destroy domain. Nothrow guarantee."""
1535 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1537 self._cleanupVm()
1538 if self.dompath is not None:
1539 self.destroyDomain()
1542 def destroyDomain(self):
1543 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1545 try:
1546 if self.domid is not None:
1547 xc.domain_destroy(self.domid)
1548 self.domid = None
1549 for state in DOM_STATES_OLD:
1550 self.info[state] = 0
1551 except:
1552 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1554 from xen.xend import XendDomain
1555 XendDomain.instance().remove_domain(self)
1557 self.cleanupDomain()
1560 def resumeDomain(self):
1561 log.debug("XendDomainInfo.resumeDomain(%s)", str(self.domid))
1563 try:
1564 if self.domid is not None:
1565 xc.domain_resume(self.domid)
1566 ResumeDomain(self.domid)
1567 except:
1568 log.exception("XendDomainInfo.resume: xc.domain_resume failed on domain %s." % (str(self.domid)))
1571 # Channels for xenstore and console
1574 def _createChannels(self):
1575 """Create the channels to the domain.
1576 """
1577 self.store_port = self._createChannel()
1578 self.console_port = self._createChannel()
1581 def _createChannel(self):
1582 """Create an event channel to the domain.
1583 """
1584 try:
1585 if self.domid != None:
1586 return xc.evtchn_alloc_unbound(domid = self.domid,
1587 remote_dom = 0)
1588 except:
1589 log.exception("Exception in alloc_unbound(%s)", str(self.domid))
1590 raise
1592 def _resetChannels(self):
1593 """Reset all event channels in the domain.
1594 """
1595 try:
1596 if self.domid != None:
1597 return xc.evtchn_reset(dom = self.domid)
1598 except:
1599 log.exception("Exception in evtcnh_reset(%s)", str(self.domid))
1600 raise
1604 # Bootloader configuration
1607 def _configureBootloader(self):
1608 """Run the bootloader if we're configured to do so."""
1610 blexec = self.info['PV_bootloader']
1611 bootloader_args = self.info['PV_bootloader_args']
1612 kernel = self.info['PV_kernel']
1613 ramdisk = self.info['PV_ramdisk']
1614 args = self.info['PV_args']
1615 boot = self.info['HVM_boot']
1617 if boot:
1618 # HVM booting.
1619 self.info['image']['type'] = 'hvm'
1620 if not 'devices' in self.info['image']:
1621 self.info['image']['devices'] = {}
1622 self.info['image']['devices']['boot'] = boot
1623 elif not blexec and kernel:
1624 # Boot from dom0. Nothing left to do -- the kernel and ramdisk
1625 # will be picked up by image.py.
1626 pass
1627 else:
1628 # Boot using bootloader
1629 if not blexec or blexec == 'pygrub':
1630 blexec = osdep.pygrub_path
1632 blcfg = None
1633 for (devtype, devinfo) in self.info.all_devices_sxpr():
1634 if not devtype or not devinfo or devtype not in ('vbd', 'tap'):
1635 continue
1636 disk = None
1637 for param in devinfo:
1638 if param[0] == 'uname':
1639 disk = param[1]
1640 break
1642 if disk is None:
1643 continue
1644 fn = blkdev_uname_to_file(disk)
1645 mounted = devtype == 'tap' and not os.stat(fn).st_rdev
1646 if mounted:
1647 # This is a file, not a device. pygrub can cope with a
1648 # file if it's raw, but if it's QCOW or other such formats
1649 # used through blktap, then we need to mount it first.
1651 log.info("Mounting %s on %s." %
1652 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1654 vbd = {
1655 'mode': 'RO',
1656 'device': BOOTLOADER_LOOPBACK_DEVICE,
1659 from xen.xend import XendDomain
1660 dom0 = XendDomain.instance().privilegedDomain()
1661 dom0._waitForDeviceUUID(dom0.create_vbd(vbd, fn))
1662 fn = BOOTLOADER_LOOPBACK_DEVICE
1664 try:
1665 blcfg = bootloader(blexec, fn, self, False,
1666 bootloader_args, kernel, ramdisk, args)
1667 finally:
1668 if mounted:
1669 log.info("Unmounting %s from %s." %
1670 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1672 dom0.destroyDevice('tap', '/dev/xvdp')
1674 break
1676 if blcfg is None:
1677 msg = "Had a bootloader specified, but can't find disk"
1678 log.error(msg)
1679 raise VmError(msg)
1681 self.info.update_with_image_sxp(blcfg, True)
1685 # VM Functions
1688 def _readVMDetails(self, params):
1689 """Read the specified parameters from the store.
1690 """
1691 try:
1692 return self._gatherVm(*params)
1693 except ValueError:
1694 # One of the int/float entries in params has a corresponding store
1695 # entry that is invalid. We recover, because older versions of
1696 # Xend may have put the entry there (memory/target, for example),
1697 # but this is in general a bad situation to have reached.
1698 log.exception(
1699 "Store corrupted at %s! Domain %d's configuration may be "
1700 "affected.", self.vmpath, self.domid)
1701 return []
1703 def _cleanupVm(self):
1704 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1706 self._unwatchVm()
1708 try:
1709 self._removeVm()
1710 except:
1711 log.exception("Removing VM path failed.")
1714 def checkLiveMigrateMemory(self):
1715 """ Make sure there's enough memory to migrate this domain """
1716 overhead_kb = 0
1717 if arch.type == "x86":
1718 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1719 # the minimum that Xen would allocate if no value were given.
1720 overhead_kb = self.info['vcpus_number'] * 1024 + \
1721 self.info['memory_static_max'] * 4
1722 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1723 # The domain might already have some shadow memory
1724 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1725 if overhead_kb > 0:
1726 balloon.free(overhead_kb)
1728 def _unwatchVm(self):
1729 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1730 guarantee."""
1731 try:
1732 try:
1733 if self.vmWatch:
1734 self.vmWatch.unwatch()
1735 finally:
1736 self.vmWatch = None
1737 except:
1738 log.exception("Unwatching VM path failed.")
1740 def testDeviceComplete(self):
1741 """ For Block IO migration safety we must ensure that
1742 the device has shutdown correctly, i.e. all blocks are
1743 flushed to disk
1744 """
1745 start = time.time()
1746 while True:
1747 test = 0
1748 diff = time.time() - start
1749 for i in self.getDeviceController('vbd').deviceIDs():
1750 test = 1
1751 log.info("Dev %s still active, looping...", i)
1752 time.sleep(0.1)
1754 if test == 0:
1755 break
1756 if diff >= MIGRATE_TIMEOUT:
1757 log.info("Dev still active but hit max loop timeout")
1758 break
1760 def testvifsComplete(self):
1761 """ In case vifs are released and then created for the same
1762 domain, we need to wait the device shut down.
1763 """
1764 start = time.time()
1765 while True:
1766 test = 0
1767 diff = time.time() - start
1768 for i in self.getDeviceController('vif').deviceIDs():
1769 test = 1
1770 log.info("Dev %s still active, looping...", i)
1771 time.sleep(0.1)
1773 if test == 0:
1774 break
1775 if diff >= MIGRATE_TIMEOUT:
1776 log.info("Dev still active but hit max loop timeout")
1777 break
1779 def _storeVmDetails(self):
1780 to_store = {}
1782 for key in XendConfig.LEGACY_XENSTORE_VM_PARAMS:
1783 info_key = XendConfig.LEGACY_CFG_TO_XENAPI_CFG.get(key, key)
1784 if self._infoIsSet(info_key):
1785 to_store[key] = str(self.info[info_key])
1787 if self.info.get('image'):
1788 image_sxpr = self.info.image_sxpr()
1789 if image_sxpr:
1790 to_store['image'] = sxp.to_string(image_sxpr)
1792 if self._infoIsSet('security'):
1793 secinfo = self.info['security']
1794 to_store['security'] = sxp.to_string(secinfo)
1795 for idx in range(0, len(secinfo)):
1796 if secinfo[idx][0] == 'access_control':
1797 to_store['security/access_control'] = sxp.to_string(
1798 [secinfo[idx][1], secinfo[idx][2]])
1799 for aidx in range(1, len(secinfo[idx])):
1800 if secinfo[idx][aidx][0] == 'label':
1801 to_store['security/access_control/label'] = \
1802 secinfo[idx][aidx][1]
1803 if secinfo[idx][aidx][0] == 'policy':
1804 to_store['security/access_control/policy'] = \
1805 secinfo[idx][aidx][1]
1806 if secinfo[idx][0] == 'ssidref':
1807 to_store['security/ssidref'] = str(secinfo[idx][1])
1810 if not self._readVm('xend/restart_count'):
1811 to_store['xend/restart_count'] = str(0)
1813 log.debug("Storing VM details: %s", scrub_password(to_store))
1815 self._writeVm(to_store)
1816 self._setVmPermissions()
1819 def _setVmPermissions(self):
1820 """Allow the guest domain to read its UUID. We don't allow it to
1821 access any other entry, for security."""
1822 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1823 { 'dom' : self.domid,
1824 'read' : True,
1825 'write' : False })
1828 # Utility functions
1831 def _stateSet(self, state):
1832 self.state_updated.acquire()
1833 try:
1834 if self.state != state:
1835 self.state = state
1836 self.state_updated.notifyAll()
1837 finally:
1838 self.state_updated.release()
1840 def _infoIsSet(self, name):
1841 return name in self.info and self.info[name] is not None
1843 def _checkName(self, name):
1844 """Check if a vm name is valid. Valid names contain alphabetic
1845 characters, digits, or characters in '_-.:/+'.
1846 The same name cannot be used for more than one vm at the same time.
1848 @param name: name
1849 @raise: VmError if invalid
1850 """
1851 from xen.xend import XendDomain
1853 if name is None or name == '':
1854 raise VmError('Missing VM Name')
1856 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1857 raise VmError('Invalid VM Name')
1859 dom = XendDomain.instance().domain_lookup_nr(name)
1860 if dom and dom.info['uuid'] != self.info['uuid']:
1861 raise VmError("VM name '%s' already exists%s" %
1862 (name,
1863 dom.domid is not None and
1864 (" as domain %s" % str(dom.domid)) or ""))
1867 def update(self, info = None, refresh = True):
1868 """Update with info from xc.domain_getinfo().
1869 """
1870 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1871 str(self.domid))
1873 if not info:
1874 info = dom_get(self.domid)
1875 if not info:
1876 return
1878 #manually update ssidref / security fields
1879 if security.on() and info.has_key('ssidref'):
1880 if (info['ssidref'] != 0) and self.info.has_key('security'):
1881 security_field = self.info['security']
1882 if not security_field:
1883 #create new security element
1884 self.info.update({'security':
1885 [['ssidref', str(info['ssidref'])]]})
1887 #ssidref field not used any longer
1888 if 'ssidref' in info:
1889 info.pop('ssidref')
1891 # make sure state is reset for info
1892 # TODO: we should eventually get rid of old_dom_states
1894 self.info.update_config(info)
1896 if refresh:
1897 self.refreshShutdown(info)
1899 log.trace("XendDomainInfo.update done on domain %s: %s",
1900 str(self.domid), self.info)
1902 def sxpr(self, ignore_store = False, legacy_only = True):
1903 result = self.info.to_sxp(domain = self,
1904 ignore_devices = ignore_store,
1905 legacy_only = legacy_only)
1907 if not ignore_store and self.dompath:
1908 vnc_port = self.readDom('console/vnc-port')
1909 if vnc_port is not None:
1910 result.append(['device',
1911 ['console', ['vnc-port', str(vnc_port)]]])
1913 return result
1915 # Xen API
1916 # ----------------------------------------------------------------
1918 def get_uuid(self):
1919 dom_uuid = self.info.get('uuid')
1920 if not dom_uuid: # if it doesn't exist, make one up
1921 dom_uuid = uuid.createString()
1922 self.info['uuid'] = dom_uuid
1923 return dom_uuid
1925 def get_memory_static_max(self):
1926 return self.info.get('memory_static_max', 0)
1927 def get_memory_static_min(self):
1928 return self.info.get('memory_static_min', 0)
1929 def get_memory_dynamic_max(self):
1930 return self.info.get('memory_dynamic_max', 0)
1931 def get_memory_dynamic_min(self):
1932 return self.info.get('memory_dynamic_min', 0)
1934 def get_vcpus_policy(self):
1935 sched_id = xc.sched_id_get()
1936 if sched_id == xen.lowlevel.xc.XEN_SCHEDULER_SEDF:
1937 return 'sedf'
1938 elif sched_id == xen.lowlevel.xc.XEN_SCHEDULER_CREDIT:
1939 return 'credit'
1940 else:
1941 return 'unknown'
1942 def get_vcpus_params(self):
1943 return '' # TODO
1944 def get_power_state(self):
1945 return XEN_API_VM_POWER_STATE[self.state]
1946 def get_platform_std_vga(self):
1947 return self.info.get('platform_std_vga', False)
1948 def get_platform_serial(self):
1949 return self.info.get('platform_serial', '')
1950 def get_platform_localtime(self):
1951 return self.info.get('platform_localtime', False)
1952 def get_platform_clock_offset(self):
1953 return self.info.get('platform_clock_offset', False)
1954 def get_platform_enable_audio(self):
1955 return self.info.get('platform_enable_audio', False)
1956 def get_platform_keymap(self):
1957 return self.info.get('platform_keymap', '')
1958 def get_pci_bus(self):
1959 return self.info.get('pci_bus', '')
1960 def get_tools_version(self):
1961 return self.info.get('tools_version', {})
1963 def get_on_shutdown(self):
1964 after_shutdown = self.info.get('action_after_shutdown')
1965 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
1966 return XEN_API_ON_NORMAL_EXIT[-1]
1967 return after_shutdown
1969 def get_on_reboot(self):
1970 after_reboot = self.info.get('action_after_reboot')
1971 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
1972 return XEN_API_ON_NORMAL_EXIT[-1]
1973 return after_reboot
1975 def get_on_suspend(self):
1976 # TODO: not supported
1977 after_suspend = self.info.get('action_after_suspend')
1978 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
1979 return XEN_API_ON_NORMAL_EXIT[-1]
1980 return after_suspend
1982 def get_on_crash(self):
1983 after_crash = self.info.get('action_after_crash')
1984 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
1985 return XEN_API_ON_CRASH_BEHAVIOUR[0]
1986 return after_crash
1988 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
1989 """ Get's a device configuration either from XendConfig or
1990 from the DevController.
1992 @param dev_class: device class, either, 'vbd' or 'vif'
1993 @param dev_uuid: device UUID
1995 @rtype: dictionary
1996 """
1997 dev_type_config = self.info['devices'].get(dev_uuid)
1999 # shortcut if the domain isn't started because
2000 # the devcontrollers will have no better information
2001 # than XendConfig.
2002 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
2003 if dev_type_config:
2004 return copy.deepcopy(dev_type_config[1])
2005 return None
2007 # instead of using dev_class, we use the dev_type
2008 # that is from XendConfig.
2009 # This will accomdate 'tap' as well as 'vbd'
2010 dev_type = dev_type_config[0]
2012 controller = self.getDeviceController(dev_type)
2013 if not controller:
2014 return None
2016 all_configs = controller.getAllDeviceConfigurations()
2017 if not all_configs:
2018 return None
2020 dev_config = copy.deepcopy(dev_type_config[1])
2021 for _devid, _devcfg in all_configs.items():
2022 if _devcfg.get('uuid') == dev_uuid:
2023 dev_config.update(_devcfg)
2024 dev_config['id'] = _devid
2025 return dev_config
2027 return dev_config
2029 def get_dev_xenapi_config(self, dev_class, dev_uuid):
2030 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
2031 if not config:
2032 return {}
2034 config['VM'] = self.get_uuid()
2036 if dev_class == 'vif':
2037 if not config.has_key('name'):
2038 config['name'] = config.get('vifname', '')
2039 if not config.has_key('MAC'):
2040 config['MAC'] = config.get('mac', '')
2041 if not config.has_key('type'):
2042 config['type'] = 'paravirtualised'
2043 if not config.has_key('device'):
2044 devid = config.get('id')
2045 if devid != None:
2046 config['device'] = 'eth%d' % devid
2047 else:
2048 config['device'] = ''
2050 if not config.has_key('network'):
2051 try:
2052 config['network'] = \
2053 XendNode.instance().bridge_to_network(
2054 config.get('bridge')).uuid
2055 except Exception:
2056 log.exception('bridge_to_network')
2057 # Ignore this for now -- it may happen if the device
2058 # has been specified using the legacy methods, but at
2059 # some point we're going to have to figure out how to
2060 # handle that properly.
2062 config['MTU'] = 1500 # TODO
2064 if self.state not in (XEN_API_VM_POWER_STATE_HALTED,):
2065 xennode = XendNode.instance()
2066 rx_bps, tx_bps = xennode.get_vif_util(self.domid, devid)
2067 config['io_read_kbs'] = rx_bps/1024
2068 config['io_write_kbs'] = tx_bps/1024
2069 else:
2070 config['io_read_kbs'] = 0.0
2071 config['io_write_kbs'] = 0.0
2073 if dev_class == 'vbd':
2075 if self.state not in (XEN_API_VM_POWER_STATE_HALTED,):
2076 controller = self.getDeviceController(dev_class)
2077 devid, _1, _2 = controller.getDeviceDetails(config)
2078 xennode = XendNode.instance()
2079 rd_blkps, wr_blkps = xennode.get_vbd_util(self.domid, devid)
2080 config['io_read_kbs'] = rd_blkps
2081 config['io_write_kbs'] = wr_blkps
2082 else:
2083 config['io_read_kbs'] = 0.0
2084 config['io_write_kbs'] = 0.0
2086 config['VDI'] = config.get('VDI', '')
2087 config['device'] = config.get('dev', '')
2088 if ':' in config['device']:
2089 vbd_name, vbd_type = config['device'].split(':', 1)
2090 config['device'] = vbd_name
2091 if vbd_type == 'cdrom':
2092 config['type'] = XEN_API_VBD_TYPE[0]
2093 else:
2094 config['type'] = XEN_API_VBD_TYPE[1]
2096 config['driver'] = 'paravirtualised' # TODO
2097 config['image'] = config.get('uname', '')
2099 if config.get('mode', 'r') == 'r':
2100 config['mode'] = 'RO'
2101 else:
2102 config['mode'] = 'RW'
2104 if dev_class == 'vtpm':
2105 config['driver'] = 'paravirtualised' # TODO
2107 return config
2109 def get_dev_property(self, dev_class, dev_uuid, field):
2110 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
2111 try:
2112 return config[field]
2113 except KeyError:
2114 raise XendError('Invalid property for device: %s' % field)
2116 def get_vcpus_util(self):
2117 vcpu_util = {}
2118 xennode = XendNode.instance()
2119 if 'vcpus_number' in self.info and self.domid != None:
2120 for i in range(0, self.info['vcpus_number']):
2121 util = xennode.get_vcpu_util(self.domid, i)
2122 vcpu_util[str(i)] = util
2124 return vcpu_util
2126 def get_consoles(self):
2127 return self.info.get('console_refs', [])
2129 def get_vifs(self):
2130 return self.info.get('vif_refs', [])
2132 def get_vbds(self):
2133 return self.info.get('vbd_refs', [])
2135 def get_vtpms(self):
2136 return self.info.get('vtpm_refs', [])
2138 def create_vbd(self, xenapi_vbd, vdi_image_path):
2139 """Create a VBD using a VDI from XendStorageRepository.
2141 @param xenapi_vbd: vbd struct from the Xen API
2142 @param vdi_image_path: VDI UUID
2143 @rtype: string
2144 @return: uuid of the device
2145 """
2146 xenapi_vbd['image'] = vdi_image_path
2147 log.debug('create_vbd: %s' % xenapi_vbd)
2148 dev_uuid = ''
2149 if vdi_image_path.startswith('tap'):
2150 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
2151 else:
2152 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
2154 if not dev_uuid:
2155 raise XendError('Failed to create device')
2157 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2158 _, config = self.info['devices'][dev_uuid]
2159 dev_control = None
2161 if vdi_image_path.startswith('tap'):
2162 dev_control = self.getDeviceController('tap')
2163 else:
2164 dev_control = self.getDeviceController('vbd')
2166 config['devid'] = dev_control.createDevice(config)
2168 return dev_uuid
2170 def create_vif(self, xenapi_vif):
2171 """Create VIF device from the passed struct in Xen API format.
2173 @param xenapi_vif: Xen API VIF Struct.
2174 @rtype: string
2175 @return: UUID
2176 """
2177 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
2178 if not dev_uuid:
2179 raise XendError('Failed to create device')
2181 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2182 _, config = self.info['devices'][dev_uuid]
2183 config['devid'] = self.getDeviceController('vif').createDevice(config)
2185 return dev_uuid
2187 def create_vtpm(self, xenapi_vtpm):
2188 """Create a VTPM device from the passed struct in Xen API format.
2190 @return: uuid of the device
2191 @rtype: string
2192 """
2194 if self.state not in (DOM_STATE_HALTED,):
2195 raise VmError("Can only add vTPM to a halted domain.")
2196 if self.get_vtpms() != []:
2197 raise VmError('Domain already has a vTPM.')
2198 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
2199 if not dev_uuid:
2200 raise XendError('Failed to create device')
2202 return dev_uuid
2204 def destroy_device_by_uuid(self, dev_type, dev_uuid):
2205 if dev_uuid not in self.info['devices']:
2206 raise XendError('Device does not exist')
2208 try:
2209 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2210 _, config = self.info['devices'][dev_uuid]
2211 devid = config.get('devid')
2212 if devid != None:
2213 self.getDeviceController(dev_type).destroyDevice(devid, force = False)
2214 else:
2215 raise XendError('Unable to get devid for device: %s:%s' %
2216 (dev_type, dev_uuid))
2217 finally:
2218 del self.info['devices'][dev_uuid]
2219 self.info['%s_refs' % dev_type].remove(dev_uuid)
2221 def destroy_vbd(self, dev_uuid):
2222 self.destroy_device_by_uuid('vbd', dev_uuid)
2224 def destroy_vif(self, dev_uuid):
2225 self.destroy_device_by_uuid('vif', dev_uuid)
2227 def destroy_vtpm(self, dev_uuid):
2228 self.destroy_device_by_uuid('vtpm', dev_uuid)
2230 def has_device(self, dev_class, dev_uuid):
2231 return (dev_uuid in self.info['%s_refs' % dev_class.lower()])
2233 def __str__(self):
2234 return '<domain id=%s name=%s memory=%s state=%s>' % \
2235 (str(self.domid), self.info['name_label'],
2236 str(self.info['memory_static_min']), DOM_STATES[self.state])
2238 __repr__ = __str__