ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 14210:bb22c21e1af7

tools: Advertise save/restore multiple VCPU contexts capability.
This capability means that save/restore will remember contexts of all
online VCPUs at the time of final guest suspend. Any initialised but
offline VCPUs will not be saved!
Signed-off-by: Keir Fraser <keir@xensource.com>
author kfraser@localhost.localdomain
date Fri Mar 02 12:11:10 2007 +0000 (2007-03-02)
parents 270a5e2ead43
children 88282487c28a
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 notes: OS image notes
306 @type notes: dictionary
307 @ivar vmWatch: reference to a watch on the xenstored vmpath
308 @type vmWatch: xen.xend.xenstore.xswatch
309 @ivar shutdownWatch: reference to watch on the xenstored domain shutdown
310 @type shutdownWatch: xen.xend.xenstore.xswatch
311 @ivar shutdownStartTime: UNIX Time when domain started shutting down.
312 @type shutdownStartTime: float or None
313 @ivar state: Domain state
314 @type state: enum(DOM_STATE_HALTED, DOM_STATE_RUNNING, ...)
315 @ivar state_updated: lock for self.state
316 @type state_updated: threading.Condition
317 @ivar refresh_shutdown_lock: lock for polling shutdown state
318 @type refresh_shutdown_lock: threading.Condition
319 @ivar _deviceControllers: device controller cache for this domain
320 @type _deviceControllers: dict 'string' to DevControllers
321 """
323 def __init__(self, info, domid = None, dompath = None, augment = False,
324 priv = False, resume = False):
325 """Constructor for a domain
327 @param info: parsed configuration
328 @type info: dictionary
329 @keyword domid: Set initial domain id (if any)
330 @type domid: int
331 @keyword dompath: Set initial dompath (if any)
332 @type dompath: string
333 @keyword augment: Augment given info with xenstored VM info
334 @type augment: bool
335 @keyword priv: Is a privileged domain (Dom 0)
336 @type priv: bool
337 @keyword resume: Is this domain being resumed?
338 @type resume: bool
339 """
341 self.info = info
342 if domid == None:
343 self.domid = self.info.get('domid')
344 else:
345 self.domid = domid
347 #REMOVE: uuid is now generated in XendConfig
348 #if not self._infoIsSet('uuid'):
349 # self.info['uuid'] = uuid.toString(uuid.create())
351 self.vmpath = XS_VMROOT + self.info['uuid']
352 self.dompath = dompath
354 self.image = None
355 self.store_port = None
356 self.store_mfn = None
357 self.console_port = None
358 self.console_mfn = None
360 self.vmWatch = None
361 self.shutdownWatch = None
362 self.shutdownStartTime = None
363 self._resume = resume
365 self.state = DOM_STATE_HALTED
366 self.state_updated = threading.Condition()
367 self.refresh_shutdown_lock = threading.Condition()
369 self._deviceControllers = {}
371 for state in DOM_STATES_OLD:
372 self.info[state] = 0
374 if augment:
375 self._augmentInfo(priv)
377 self._checkName(self.info['name_label'])
380 #
381 # Public functions available through XMLRPC
382 #
385 def start(self, is_managed = False):
386 """Attempts to start the VM by do the appropriate
387 initialisation if it not started.
388 """
389 from xen.xend import XendDomain
391 if self.state == DOM_STATE_HALTED:
392 try:
393 XendTask.log_progress(0, 30, self._constructDomain)
394 XendTask.log_progress(31, 60, self._initDomain)
396 XendTask.log_progress(61, 70, self._storeVmDetails)
397 XendTask.log_progress(71, 80, self._storeDomDetails)
398 XendTask.log_progress(81, 90, self._registerWatches)
399 XendTask.log_progress(91, 100, self.refreshShutdown)
401 xendomains = XendDomain.instance()
402 xennode = XendNode.instance()
404 # save running configuration if XendDomains believe domain is
405 # persistent
406 if is_managed:
407 xendomains.managed_config_save(self)
409 if xennode.xenschedinfo() == 'credit':
410 xendomains.domain_sched_credit_set(self.getDomid(),
411 self.getWeight(),
412 self.getCap())
413 except:
414 log.exception('VM start failed')
415 self.destroy()
416 raise
417 else:
418 raise XendError('VM already running')
420 def resume(self):
421 """Resumes a domain that has come back from suspension."""
422 if self.state in (DOM_STATE_HALTED, DOM_STATE_SUSPENDED):
423 try:
424 self._constructDomain()
425 self._storeVmDetails()
426 self._createDevices()
427 self._createChannels()
428 self._storeDomDetails()
429 self._endRestore()
430 except:
431 log.exception('VM resume failed')
432 raise
433 else:
434 raise XendError('VM already running')
436 def shutdown(self, reason):
437 """Shutdown a domain by signalling this via xenstored."""
438 log.debug('XendDomainInfo.shutdown(%s)', reason)
439 if self.state in (DOM_STATE_SHUTDOWN, DOM_STATE_HALTED,):
440 raise XendError('Domain cannot be shutdown')
442 if self.domid == 0:
443 raise XendError('Domain 0 cannot be shutdown')
445 if reason not in DOMAIN_SHUTDOWN_REASONS.values():
446 raise XendError('Invalid reason: %s' % reason)
447 self._removeVm('xend/previous_restart_time')
448 self.storeDom("control/shutdown", reason)
450 ## shutdown hypercall for hvm domain desides xenstore write
451 image_cfg = self.info.get('image', {})
452 hvm = image_cfg.has_key('hvm')
453 if hvm:
454 for code in DOMAIN_SHUTDOWN_REASONS.keys():
455 if DOMAIN_SHUTDOWN_REASONS[code] == reason:
456 break
457 xc.domain_shutdown(self.domid, code)
460 def pause(self):
461 """Pause domain
463 @raise XendError: Failed pausing a domain
464 """
465 try:
466 xc.domain_pause(self.domid)
467 self._stateSet(DOM_STATE_PAUSED)
468 except Exception, ex:
469 log.exception(ex)
470 raise XendError("Domain unable to be paused: %s" % str(ex))
472 def unpause(self):
473 """Unpause domain
475 @raise XendError: Failed unpausing a domain
476 """
477 try:
478 xc.domain_unpause(self.domid)
479 self._stateSet(DOM_STATE_RUNNING)
480 except Exception, ex:
481 log.exception(ex)
482 raise XendError("Domain unable to be unpaused: %s" % str(ex))
484 def send_sysrq(self, key):
485 """ Send a Sysrq equivalent key via xenstored."""
486 asserts.isCharConvertible(key)
487 self.storeDom("control/sysrq", '%c' % key)
489 def device_create(self, dev_config):
490 """Create a new device.
492 @param dev_config: device configuration
493 @type dev_config: SXP object (parsed config)
494 """
495 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config))
496 dev_type = sxp.name(dev_config)
497 dev_uuid = self.info.device_add(dev_type, cfg_sxp = dev_config)
498 dev_config_dict = self.info['devices'][dev_uuid][1]
499 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config_dict))
500 dev_config_dict['devid'] = devid = \
501 self._createDevice(dev_type, dev_config_dict)
502 self._waitForDevice(dev_type, devid)
503 return self.getDeviceController(dev_type).sxpr(devid)
505 def device_configure(self, dev_sxp, devid = None):
506 """Configure an existing device.
508 @param dev_config: device configuration
509 @type dev_config: SXP object (parsed config)
510 @param devid: device id
511 @type devid: int
512 @return: Returns True if successfully updated device
513 @rtype: boolean
514 """
516 # convert device sxp to a dict
517 dev_class = sxp.name(dev_sxp)
518 dev_config = {}
519 for opt_val in dev_sxp[1:]:
520 try:
521 dev_config[opt_val[0]] = opt_val[1]
522 except IndexError:
523 pass
525 # use DevController.reconfigureDevice to change device config
526 dev_control = self.getDeviceController(dev_class)
527 dev_uuid = dev_control.reconfigureDevice(devid, dev_config)
529 # update XendConfig with new device info
530 if dev_uuid:
531 self.info.device_update(dev_uuid, dev_sxp)
533 return True
535 def waitForDevices(self):
536 """Wait for this domain's configured devices to connect.
538 @raise VmError: if any device fails to initialise.
539 """
540 for devclass in XendDevices.valid_devices():
541 self.getDeviceController(devclass).waitForDevices()
543 def destroyDevice(self, deviceClass, devid, force = False):
544 try:
545 devid = int(devid)
546 except ValueError:
547 # devid is not a number, let's search for it in xenstore.
548 devicePath = '%s/device/%s' % (self.dompath, deviceClass)
549 for entry in xstransact.List(devicePath):
550 backend = xstransact.Read('%s/%s' % (devicePath, entry),
551 "backend")
552 devName = xstransact.Read(backend, "dev")
553 if devName == devid:
554 # We found the integer matching our devid, use it instead
555 devid = entry
556 break
558 return self.getDeviceController(deviceClass).destroyDevice(devid, force)
560 def getDeviceSxprs(self, deviceClass):
561 if self.state == DOM_STATE_RUNNING:
562 return self.getDeviceController(deviceClass).sxprs()
563 else:
564 sxprs = []
565 dev_num = 0
566 for dev_type, dev_info in self.info.all_devices_sxpr():
567 if dev_type == deviceClass:
568 sxprs.append([dev_num, dev_info])
569 dev_num += 1
570 return sxprs
573 def setMemoryTarget(self, target):
574 """Set the memory target of this domain.
575 @param target: In MiB.
576 """
577 log.debug("Setting memory target of domain %s (%d) to %d MiB.",
578 self.info['name_label'], self.domid, target)
580 if target <= 0:
581 raise XendError('Invalid memory size')
583 self.info['memory_static_min'] = target
584 if self.domid >= 0:
585 self.storeVm("memory", target)
586 self.storeDom("memory/target", target << 10)
587 else:
588 self.info['memory_dynamic_min'] = target
589 xen.xend.XendDomain.instance().managed_config_save(self)
591 def setMemoryMaximum(self, limit):
592 """Set the maximum memory limit of this domain
593 @param limit: In MiB.
594 """
595 log.debug("Setting memory maximum of domain %s (%d) to %d MiB.",
596 self.info['name_label'], self.domid, limit)
598 if limit <= 0:
599 raise XendError('Invalid memory size')
601 self.info['memory_static_max'] = limit
602 if self.domid >= 0:
603 maxmem = int(limit) * 1024
604 try:
605 return xc.domain_setmaxmem(self.domid, maxmem)
606 except Exception, ex:
607 raise XendError(str(ex))
608 else:
609 self.info['memory_dynamic_max'] = limit
610 xen.xend.XendDomain.instance().managed_config_save(self)
613 def getVCPUInfo(self):
614 try:
615 # We include the domain name and ID, to help xm.
616 sxpr = ['domain',
617 ['domid', self.domid],
618 ['name', self.info['name_label']],
619 ['vcpu_count', self.info['vcpus_number']]]
621 for i in range(0, self.info['vcpus_number']):
622 info = xc.vcpu_getinfo(self.domid, i)
624 sxpr.append(['vcpu',
625 ['number', i],
626 ['online', info['online']],
627 ['blocked', info['blocked']],
628 ['running', info['running']],
629 ['cpu_time', info['cpu_time'] / 1e9],
630 ['cpu', info['cpu']],
631 ['cpumap', info['cpumap']]])
633 return sxpr
635 except RuntimeError, exn:
636 raise XendError(str(exn))
638 #
639 # internal functions ... TODO: re-categorised
640 #
642 def _augmentInfo(self, priv):
643 """Augment self.info, as given to us through L{recreate}, with
644 values taken from the store. This recovers those values known
645 to xend but not to the hypervisor.
646 """
647 augment_entries = XendConfig.LEGACY_XENSTORE_VM_PARAMS[:]
648 if priv:
649 augment_entries.remove('memory')
650 augment_entries.remove('maxmem')
651 augment_entries.remove('vcpus')
652 augment_entries.remove('vcpu_avail')
654 vm_config = self._readVMDetails([(k, XendConfig.LEGACY_CFG_TYPES[k])
655 for k in augment_entries])
657 # make returned lists into a dictionary
658 vm_config = dict(zip(augment_entries, vm_config))
660 for arg in augment_entries:
661 xapicfg = arg
662 val = vm_config[arg]
663 if val != None:
664 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
665 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
666 self.info[xapiarg] = val
667 else:
668 self.info[arg] = val
670 # For dom0, we ignore any stored value for the vcpus fields, and
671 # read the current value from Xen instead. This allows boot-time
672 # settings to take precedence over any entries in the store.
673 if priv:
674 xeninfo = dom_get(self.domid)
675 self.info['vcpus_number'] = xeninfo['online_vcpus']
676 self.info['vcpu_avail'] = (1 << xeninfo['online_vcpus']) - 1
678 # read image value
679 image_sxp = self._readVm('image')
680 if image_sxp:
681 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
683 # read devices
684 devices = []
685 for devclass in XendDevices.valid_devices():
686 devconfig = self.getDeviceController(devclass).configurations()
687 if devconfig:
688 devices.extend(devconfig)
690 if not self.info['devices'] and devices is not None:
691 for device in devices:
692 self.info.device_add(device[0], cfg_sxp = device)
694 self._update_consoles()
696 def _update_consoles(self):
697 if self.domid == None or self.domid == 0:
698 return
700 # Update VT100 port if it exists
701 self.console_port = self.readDom('console/port')
702 if self.console_port is not None:
703 serial_consoles = self.info.console_get_all('vt100')
704 if not serial_consoles:
705 cfg = self.info.console_add('vt100', self.console_port)
706 self._createDevice('console', cfg)
707 else:
708 console_uuid = serial_consoles[0].get('uuid')
709 self.info.console_update(console_uuid, 'location',
710 self.console_port)
713 # Update VNC port if it exists and write to xenstore
714 vnc_port = self.readDom('console/vnc-port')
715 if vnc_port is not None:
716 for dev_uuid, (dev_type, dev_info) in self.info['devices'].items():
717 if dev_type == 'vfb':
718 old_location = dev_info.get('location')
719 listen_host = dev_info.get('vnclisten', 'localhost')
720 new_location = '%s:%s' % (listen_host, str(vnc_port))
721 if old_location == new_location:
722 break
724 dev_info['location'] = new_location
725 self.info.device_update(dev_uuid, cfg_xenapi = dev_info)
726 vfb_ctrl = self.getDeviceController('vfb')
727 vfb_ctrl.reconfigureDevice(0, dev_info)
728 break
730 #
731 # Function to update xenstore /vm/*
732 #
734 def _readVm(self, *args):
735 return xstransact.Read(self.vmpath, *args)
737 def _writeVm(self, *args):
738 return xstransact.Write(self.vmpath, *args)
740 def _removeVm(self, *args):
741 return xstransact.Remove(self.vmpath, *args)
743 def _gatherVm(self, *args):
744 return xstransact.Gather(self.vmpath, *args)
746 def storeVm(self, *args):
747 return xstransact.Store(self.vmpath, *args)
749 #
750 # Function to update xenstore /dom/*
751 #
753 def readDom(self, *args):
754 return xstransact.Read(self.dompath, *args)
756 def gatherDom(self, *args):
757 return xstransact.Gather(self.dompath, *args)
759 def _writeDom(self, *args):
760 return xstransact.Write(self.dompath, *args)
762 def _removeDom(self, *args):
763 return xstransact.Remove(self.dompath, *args)
765 def storeDom(self, *args):
766 return xstransact.Store(self.dompath, *args)
768 def _recreateDom(self):
769 complete(self.dompath, lambda t: self._recreateDomFunc(t))
771 def _recreateDomFunc(self, t):
772 t.remove()
773 t.mkdir()
774 t.set_permissions({'dom' : self.domid})
775 t.write('vm', self.vmpath)
777 def _storeDomDetails(self):
778 to_store = {
779 'domid': str(self.domid),
780 'vm': self.vmpath,
781 'name': self.info['name_label'],
782 'console/limit': str(xoptions.get_console_limit() * 1024),
783 'memory/target': str(self.info['memory_static_min'] * 1024),
784 'control/platform-feature-multiprocessor-suspend': str(1)
785 }
787 def f(n, v):
788 if v is not None:
789 if type(v) == bool:
790 to_store[n] = v and "1" or "0"
791 else:
792 to_store[n] = str(v)
794 f('console/port', self.console_port)
795 f('console/ring-ref', self.console_mfn)
796 f('store/port', self.store_port)
797 f('store/ring-ref', self.store_mfn)
799 # elfnotes
800 for n, v in self.info.get_notes().iteritems():
801 n = n.lower().replace('_', '-')
802 if n == 'features':
803 for v in v.split('|'):
804 v = v.replace('_', '-')
805 if v.startswith('!'):
806 f('image/%s/%s' % (n, v[1:]), False)
807 else:
808 f('image/%s/%s' % (n, v), True)
809 else:
810 f('image/%s' % n, v)
812 to_store.update(self._vcpuDomDetails())
814 log.debug("Storing domain details: %s", scrub_password(to_store))
816 self._writeDom(to_store)
818 def _vcpuDomDetails(self):
819 def availability(n):
820 if self.info['vcpu_avail'] & (1 << n):
821 return 'online'
822 else:
823 return 'offline'
825 result = {}
826 for v in range(0, self.info['vcpus_number']):
827 result["cpu/%d/availability" % v] = availability(v)
828 return result
830 #
831 # xenstore watches
832 #
834 def _registerWatches(self):
835 """Register a watch on this VM's entries in the store, and the
836 domain's control/shutdown node, so that when they are changed
837 externally, we keep up to date. This should only be called by {@link
838 #create}, {@link #recreate}, or {@link #restore}, once the domain's
839 details have been written, but before the new instance is returned."""
840 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
841 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
842 self._handleShutdownWatch)
844 def _storeChanged(self, _):
845 log.trace("XendDomainInfo.storeChanged");
847 changed = False
849 # Check whether values in the configuration have
850 # changed in Xenstore.
852 cfg_vm = ['name', 'on_poweroff', 'on_reboot', 'on_crash']
854 vm_details = self._readVMDetails([(k,XendConfig.LEGACY_CFG_TYPES[k])
855 for k in cfg_vm])
857 # convert two lists into a python dictionary
858 vm_details = dict(zip(cfg_vm, vm_details))
860 for arg, val in vm_details.items():
861 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
862 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
863 if val != None and val != self.info[xapiarg]:
864 self.info[xapiarg] = val
865 changed= True
867 # Check whether image definition has been updated
868 image_sxp = self._readVm('image')
869 if image_sxp and image_sxp != self.info.image_sxpr():
870 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
871 changed = True
873 if changed:
874 # Update the domain section of the store, as this contains some
875 # parameters derived from the VM configuration.
876 self._storeDomDetails()
878 return 1
880 def _handleShutdownWatch(self, _):
881 log.debug('XendDomainInfo.handleShutdownWatch')
883 reason = self.readDom('control/shutdown')
885 if reason and reason != 'suspend':
886 sst = self.readDom('xend/shutdown_start_time')
887 now = time.time()
888 if sst:
889 self.shutdownStartTime = float(sst)
890 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
891 else:
892 self.shutdownStartTime = now
893 self.storeDom('xend/shutdown_start_time', now)
894 timeout = SHUTDOWN_TIMEOUT
896 log.trace(
897 "Scheduling refreshShutdown on domain %d in %ds.",
898 self.domid, timeout)
899 threading.Timer(timeout, self.refreshShutdown).start()
901 return True
904 #
905 # Public Attributes for the VM
906 #
909 def getDomid(self):
910 return self.domid
912 def setName(self, name):
913 self._checkName(name)
914 self.info['name_label'] = name
915 self.storeVm("name", name)
917 def getName(self):
918 return self.info['name_label']
920 def getDomainPath(self):
921 return self.dompath
923 def getShutdownReason(self):
924 return self.readDom('control/shutdown')
926 def getStorePort(self):
927 """For use only by image.py and XendCheckpoint.py."""
928 return self.store_port
930 def getConsolePort(self):
931 """For use only by image.py and XendCheckpoint.py"""
932 return self.console_port
934 def getFeatures(self):
935 """For use only by image.py."""
936 return self.info['features']
938 def getVCpuCount(self):
939 return self.info['vcpus_number']
941 def setVCpuCount(self, vcpus):
942 if vcpus <= 0:
943 raise XendError('Invalid VCPUs')
945 self.info['vcpu_avail'] = (1 << vcpus) - 1
946 if self.domid >= 0:
947 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
948 # update dom differently depending on whether we are adjusting
949 # vcpu number up or down, otherwise _vcpuDomDetails does not
950 # disable the vcpus
951 if self.info['vcpus_number'] > vcpus:
952 # decreasing
953 self._writeDom(self._vcpuDomDetails())
954 self.info['vcpus_number'] = vcpus
955 else:
956 # same or increasing
957 self.info['vcpus_number'] = vcpus
958 self._writeDom(self._vcpuDomDetails())
959 else:
960 self.info['vcpus_number'] = vcpus
961 xen.xend.XendDomain.instance().managed_config_save(self)
962 log.info("Set VCPU count on domain %s to %d", self.info['name_label'],
963 vcpus)
965 def getLabel(self):
966 return security.get_security_info(self.info, 'label')
968 def getMemoryTarget(self):
969 """Get this domain's target memory size, in KB."""
970 return self.info['memory_static_min'] * 1024
972 def getMemoryMaximum(self):
973 """Get this domain's maximum memory size, in KB."""
974 return self.info['memory_static_max'] * 1024
976 def getResume(self):
977 return str(self._resume)
979 def getCap(self):
980 return self.info.get('cpu_cap', 0)
982 def getWeight(self):
983 return self.info.get('cpu_weight', 256)
985 def setResume(self, state):
986 self._resume = state
988 def getRestartCount(self):
989 return self._readVm('xend/restart_count')
991 def refreshShutdown(self, xeninfo = None):
992 """ Checks the domain for whether a shutdown is required.
994 Called from XendDomainInfo and also image.py for HVM images.
995 """
997 # If set at the end of this method, a restart is required, with the
998 # given reason. This restart has to be done out of the scope of
999 # refresh_shutdown_lock.
1000 restart_reason = None
1002 self.refresh_shutdown_lock.acquire()
1003 try:
1004 if xeninfo is None:
1005 xeninfo = dom_get(self.domid)
1006 if xeninfo is None:
1007 # The domain no longer exists. This will occur if we have
1008 # scheduled a timer to check for shutdown timeouts and the
1009 # shutdown succeeded. It will also occur if someone
1010 # destroys a domain beneath us. We clean up the domain,
1011 # just in case, but we can't clean up the VM, because that
1012 # VM may have migrated to a different domain on this
1013 # machine.
1014 self.cleanupDomain()
1015 self._stateSet(DOM_STATE_HALTED)
1016 return
1018 if xeninfo['dying']:
1019 # Dying means that a domain has been destroyed, but has not
1020 # yet been cleaned up by Xen. This state could persist
1021 # indefinitely if, for example, another domain has some of its
1022 # pages mapped. We might like to diagnose this problem in the
1023 # future, but for now all we do is make sure that it's not us
1024 # holding the pages, by calling cleanupDomain. We can't
1025 # clean up the VM, as above.
1026 self.cleanupDomain()
1027 self._stateSet(DOM_STATE_SHUTDOWN)
1028 return
1030 elif xeninfo['crashed']:
1031 if self.readDom('xend/shutdown_completed'):
1032 # We've seen this shutdown already, but we are preserving
1033 # the domain for debugging. Leave it alone.
1034 return
1036 log.warn('Domain has crashed: name=%s id=%d.',
1037 self.info['name_label'], self.domid)
1038 self._writeVm(LAST_SHUTDOWN_REASON, 'crash')
1040 if xoptions.get_enable_dump():
1041 try:
1042 self.dumpCore()
1043 except XendError:
1044 # This error has been logged -- there's nothing more
1045 # we can do in this context.
1046 pass
1048 restart_reason = 'crash'
1049 self._stateSet(DOM_STATE_HALTED)
1051 elif xeninfo['shutdown']:
1052 self._stateSet(DOM_STATE_SHUTDOWN)
1053 if self.readDom('xend/shutdown_completed'):
1054 # We've seen this shutdown already, but we are preserving
1055 # the domain for debugging. Leave it alone.
1056 return
1058 else:
1059 reason = shutdown_reason(xeninfo['shutdown_reason'])
1061 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
1062 self.info['name_label'], self.domid, reason)
1063 self._writeVm(LAST_SHUTDOWN_REASON, reason)
1065 self._clearRestart()
1067 if reason == 'suspend':
1068 self._stateSet(DOM_STATE_SUSPENDED)
1069 # Don't destroy the domain. XendCheckpoint will do
1070 # this once it has finished. However, stop watching
1071 # the VM path now, otherwise we will end up with one
1072 # watch for the old domain, and one for the new.
1073 self._unwatchVm()
1074 elif reason in ('poweroff', 'reboot'):
1075 restart_reason = reason
1076 else:
1077 self.destroy()
1079 elif self.dompath is None:
1080 # We have yet to manage to call introduceDomain on this
1081 # domain. This can happen if a restore is in progress, or has
1082 # failed. Ignore this domain.
1083 pass
1084 else:
1085 # Domain is alive. If we are shutting it down, then check
1086 # the timeout on that, and destroy it if necessary.
1087 if xeninfo['paused']:
1088 self._stateSet(DOM_STATE_PAUSED)
1089 else:
1090 self._stateSet(DOM_STATE_RUNNING)
1092 if self.shutdownStartTime:
1093 timeout = (SHUTDOWN_TIMEOUT - time.time() +
1094 self.shutdownStartTime)
1095 if timeout < 0:
1096 log.info(
1097 "Domain shutdown timeout expired: name=%s id=%s",
1098 self.info['name_label'], self.domid)
1099 self.destroy()
1100 finally:
1101 self.refresh_shutdown_lock.release()
1103 if restart_reason:
1104 threading.Thread(target = self._maybeRestart,
1105 args = (restart_reason,)).start()
1109 # Restart functions - handling whether we come back up on shutdown.
1112 def _clearRestart(self):
1113 self._removeDom("xend/shutdown_start_time")
1116 def _maybeRestart(self, reason):
1117 # Dispatch to the correct method based upon the configured on_{reason}
1118 # behaviour.
1119 actions = {"destroy" : self.destroy,
1120 "restart" : self._restart,
1121 "preserve" : self._preserve,
1122 "rename-restart" : self._renameRestart}
1124 action_conf = {
1125 'poweroff': 'actions_after_shutdown',
1126 'reboot': 'actions_after_reboot',
1127 'crash': 'actions_after_crash',
1130 action_target = self.info.get(action_conf.get(reason))
1131 func = actions.get(action_target, None)
1132 if func and callable(func):
1133 func()
1134 else:
1135 self.destroy() # default to destroy
1137 def _renameRestart(self):
1138 self._restart(True)
1140 def _restart(self, rename = False):
1141 """Restart the domain after it has exited.
1143 @param rename True if the old domain is to be renamed and preserved,
1144 False if it is to be destroyed.
1145 """
1146 from xen.xend import XendDomain
1148 if self._readVm(RESTART_IN_PROGRESS):
1149 log.error('Xend failed during restart of domain %s. '
1150 'Refusing to restart to avoid loops.',
1151 str(self.domid))
1152 self.destroy()
1153 return
1155 old_domid = self.domid
1156 self._writeVm(RESTART_IN_PROGRESS, 'True')
1158 now = time.time()
1159 rst = self._readVm('xend/previous_restart_time')
1160 if rst:
1161 rst = float(rst)
1162 timeout = now - rst
1163 if timeout < MINIMUM_RESTART_TIME:
1164 log.error(
1165 'VM %s restarting too fast (%f seconds since the last '
1166 'restart). Refusing to restart to avoid loops.',
1167 self.info['name_label'], timeout)
1168 self.destroy()
1169 return
1171 self._writeVm('xend/previous_restart_time', str(now))
1173 try:
1174 if rename:
1175 self._preserveForRestart()
1176 else:
1177 self._unwatchVm()
1178 self.destroyDomain()
1180 # new_dom's VM will be the same as this domain's VM, except where
1181 # the rename flag has instructed us to call preserveForRestart.
1182 # In that case, it is important that we remove the
1183 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1184 # once the new one is available.
1186 new_dom = None
1187 try:
1188 new_dom = XendDomain.instance().domain_create_from_dict(
1189 self.info)
1190 new_dom.unpause()
1191 rst_cnt = self._readVm('xend/restart_count')
1192 rst_cnt = int(rst_cnt) + 1
1193 self._writeVm('xend/restart_count', str(rst_cnt))
1194 new_dom._removeVm(RESTART_IN_PROGRESS)
1195 except:
1196 if new_dom:
1197 new_dom._removeVm(RESTART_IN_PROGRESS)
1198 new_dom.destroy()
1199 else:
1200 self._removeVm(RESTART_IN_PROGRESS)
1201 raise
1202 except:
1203 log.exception('Failed to restart domain %s.', str(old_domid))
1205 def _preserveForRestart(self):
1206 """Preserve a domain that has been shut down, by giving it a new UUID,
1207 cloning the VM details, and giving it a new name. This allows us to
1208 keep this domain for debugging, but restart a new one in its place
1209 preserving the restart semantics (name and UUID preserved).
1210 """
1212 new_uuid = uuid.createString()
1213 new_name = 'Domain-%s' % new_uuid
1214 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1215 self.info['name_label'], self.domid, self.info['uuid'],
1216 new_name, new_uuid)
1217 self._unwatchVm()
1218 self._releaseDevices()
1219 self.info['name_label'] = new_name
1220 self.info['uuid'] = new_uuid
1221 self.vmpath = XS_VMROOT + new_uuid
1222 self._storeVmDetails()
1223 self._preserve()
1226 def _preserve(self):
1227 log.info("Preserving dead domain %s (%d).", self.info['name_label'],
1228 self.domid)
1229 self._unwatchVm()
1230 self.storeDom('xend/shutdown_completed', 'True')
1231 self._stateSet(DOM_STATE_HALTED)
1234 # Debugging ..
1237 def dumpCore(self, corefile = None):
1238 """Create a core dump for this domain.
1240 @raise: XendError if core dumping failed.
1241 """
1243 try:
1244 if not corefile:
1245 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1246 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1247 self.info['name_label'], self.domid)
1249 if os.path.isdir(corefile):
1250 raise XendError("Cannot dump core in a directory: %s" %
1251 corefile)
1253 xc.domain_dumpcore(self.domid, corefile)
1254 except RuntimeError, ex:
1255 corefile_incomp = corefile+'-incomplete'
1256 os.rename(corefile, corefile_incomp)
1257 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1258 self.domid, self.info['name_label'])
1259 raise XendError("Failed to dump core: %s" % str(ex))
1262 # Device creation/deletion functions
1265 def _createDevice(self, deviceClass, devConfig):
1266 return self.getDeviceController(deviceClass).createDevice(devConfig)
1268 def _waitForDevice(self, deviceClass, devid):
1269 return self.getDeviceController(deviceClass).waitForDevice(devid)
1271 def _waitForDeviceUUID(self, dev_uuid):
1272 deviceClass, config = self.info['devices'].get(dev_uuid)
1273 self._waitForDevice(deviceClass, config['devid'])
1275 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1276 return self.getDeviceController(deviceClass).reconfigureDevice(
1277 devid, devconfig)
1279 def _createDevices(self):
1280 """Create the devices for a vm.
1282 @raise: VmError for invalid devices
1283 """
1284 ordered_refs = self.info.ordered_device_refs()
1285 for dev_uuid in ordered_refs:
1286 devclass, config = self.info['devices'][dev_uuid]
1287 if devclass in XendDevices.valid_devices():
1288 log.info("createDevice: %s : %s" % (devclass, scrub_password(config)))
1289 dev_uuid = config.get('uuid')
1290 devid = self._createDevice(devclass, config)
1292 # store devid in XendConfig for caching reasons
1293 if dev_uuid in self.info['devices']:
1294 self.info['devices'][dev_uuid][1]['devid'] = devid
1296 if self.image:
1297 self.image.createDeviceModel()
1299 def _releaseDevices(self, suspend = False):
1300 """Release all domain's devices. Nothrow guarantee."""
1301 if suspend and self.image:
1302 self.image.destroy(suspend)
1303 return
1305 while True:
1306 t = xstransact("%s/device" % self.dompath)
1307 for devclass in XendDevices.valid_devices():
1308 for dev in t.list(devclass):
1309 try:
1310 t.remove(dev)
1311 except:
1312 # Log and swallow any exceptions in removal --
1313 # there's nothing more we can do.
1314 log.exception(
1315 "Device release failed: %s; %s; %s",
1316 self.info['name_label'], devclass, dev)
1317 if t.commit():
1318 break
1320 def getDeviceController(self, name):
1321 """Get the device controller for this domain, and if it
1322 doesn't exist, create it.
1324 @param name: device class name
1325 @type name: string
1326 @rtype: subclass of DevController
1327 """
1328 if name not in self._deviceControllers:
1329 devController = XendDevices.make_controller(name, self)
1330 if not devController:
1331 raise XendError("Unknown device type: %s" % name)
1332 self._deviceControllers[name] = devController
1334 return self._deviceControllers[name]
1337 # Migration functions (public)
1340 def testMigrateDevices(self, network, dst):
1341 """ Notify all device about intention of migration
1342 @raise: XendError for a device that cannot be migrated
1343 """
1344 for (n, c) in self.info.all_devices_sxpr():
1345 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1346 if rc != 0:
1347 raise XendError("Device of type '%s' refuses migration." % n)
1349 def migrateDevices(self, network, dst, step, domName=''):
1350 """Notify the devices about migration
1351 """
1352 ctr = 0
1353 try:
1354 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1355 self.migrateDevice(dev_type, dev_conf, network, dst,
1356 step, domName)
1357 ctr = ctr + 1
1358 except:
1359 for dev_type, dev_conf in self.info.all_devices_sxpr():
1360 if ctr == 0:
1361 step = step - 1
1362 ctr = ctr - 1
1363 self._recoverMigrateDevice(dev_type, dev_conf, network,
1364 dst, step, domName)
1365 raise
1367 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1368 step, domName=''):
1369 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1370 network, dst, step, domName)
1372 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1373 dst, step, domName=''):
1374 return self.getDeviceController(deviceClass).recover_migrate(
1375 deviceConfig, network, dst, step, domName)
1378 ## private:
1380 def _constructDomain(self):
1381 """Construct the domain.
1383 @raise: VmError on error
1384 """
1386 log.debug('XendDomainInfo.constructDomain')
1388 self.shutdownStartTime = None
1390 image_cfg = self.info.get('image', {})
1391 hvm = image_cfg.has_key('hvm')
1393 if hvm:
1394 info = xc.xeninfo()
1395 if 'hvm' not in info['xen_caps']:
1396 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1397 "supported by your CPU and enabled in your "
1398 "BIOS?")
1400 self.domid = xc.domain_create(
1401 domid = 0,
1402 ssidref = security.get_security_info(self.info, 'ssidref'),
1403 handle = uuid.fromString(self.info['uuid']),
1404 hvm = int(hvm))
1406 if self.domid < 0:
1407 raise VmError('Creating domain failed: name=%s' %
1408 self.info['name_label'])
1410 self.dompath = GetDomainPath(self.domid)
1412 self._recreateDom()
1414 # Set maximum number of vcpus in domain
1415 xc.domain_max_vcpus(self.domid, int(self.info['vcpus_number']))
1417 # register the domain in the list
1418 from xen.xend import XendDomain
1419 XendDomain.instance().add_domain(self)
1421 def _introduceDomain(self):
1422 assert self.domid is not None
1423 assert self.store_mfn is not None
1424 assert self.store_port is not None
1426 try:
1427 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1428 except RuntimeError, exn:
1429 raise XendError(str(exn))
1432 def _initDomain(self):
1433 log.debug('XendDomainInfo.initDomain: %s %s',
1434 self.domid,
1435 self.info['cpu_weight'])
1437 self._configureBootloader()
1439 if not self._infoIsSet('image'):
1440 raise VmError('Missing image in configuration')
1442 try:
1443 self.image = image.create(self,
1444 self.info,
1445 self.info['image'],
1446 self.info['devices'])
1448 localtime = self.info.get('platform_localtime', False)
1449 if localtime:
1450 xc.domain_set_time_offset(self.domid)
1452 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1454 # repin domain vcpus if a restricted cpus list is provided
1455 # this is done prior to memory allocation to aide in memory
1456 # distribution for NUMA systems.
1457 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1458 for v in range(0, self.info['vcpus_number']):
1459 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1461 # Use architecture- and image-specific calculations to determine
1462 # the various headrooms necessary, given the raw configured
1463 # values. maxmem, memory, and shadow are all in KiB.
1464 memory = self.image.getRequiredAvailableMemory(
1465 self.info['memory_static_min'] * 1024)
1466 maxmem = self.image.getRequiredAvailableMemory(
1467 self.info['memory_static_max'] * 1024)
1468 shadow = self.image.getRequiredShadowMemory(
1469 self.info['shadow_memory'] * 1024,
1470 self.info['memory_static_max'] * 1024)
1472 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'],)
1473 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1474 # takes MiB and we must not round down and end up under-providing.
1475 shadow = ((shadow + 1023) / 1024) * 1024
1477 # set memory limit
1478 xc.domain_setmaxmem(self.domid, maxmem)
1480 # Make sure there's enough RAM available for the domain
1481 balloon.free(memory + shadow)
1483 # Set up the shadow memory
1484 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1485 self.info['shadow_memory'] = shadow_cur
1487 self._createChannels()
1489 channel_details = self.image.createImage()
1491 self.store_mfn = channel_details['store_mfn']
1492 if 'console_mfn' in channel_details:
1493 self.console_mfn = channel_details['console_mfn']
1494 if 'notes' in channel_details:
1495 self.info.set_notes(channel_details['notes'])
1497 self._introduceDomain()
1499 self._createDevices()
1501 self.image.cleanupBootloading()
1503 self.info['start_time'] = time.time()
1505 self._stateSet(DOM_STATE_RUNNING)
1506 except RuntimeError, exn:
1507 log.exception("XendDomainInfo.initDomain: exception occurred")
1508 self.image.cleanupBootloading()
1509 raise VmError(str(exn))
1512 def cleanupDomain(self):
1513 """Cleanup domain resources; release devices. Idempotent. Nothrow
1514 guarantee."""
1516 self.refresh_shutdown_lock.acquire()
1517 try:
1518 self.unwatchShutdown()
1519 self._releaseDevices()
1520 bootloader_tidy(self)
1522 if self.image:
1523 try:
1524 self.image.destroy()
1525 except:
1526 log.exception(
1527 "XendDomainInfo.cleanup: image.destroy() failed.")
1528 self.image = None
1530 try:
1531 self._removeDom()
1532 except:
1533 log.exception("Removing domain path failed.")
1535 self._stateSet(DOM_STATE_HALTED)
1536 finally:
1537 self.refresh_shutdown_lock.release()
1540 def unwatchShutdown(self):
1541 """Remove the watch on the domain's control/shutdown node, if any.
1542 Idempotent. Nothrow guarantee. Expects to be protected by the
1543 refresh_shutdown_lock."""
1545 try:
1546 try:
1547 if self.shutdownWatch:
1548 self.shutdownWatch.unwatch()
1549 finally:
1550 self.shutdownWatch = None
1551 except:
1552 log.exception("Unwatching control/shutdown failed.")
1554 def waitForShutdown(self):
1555 self.state_updated.acquire()
1556 try:
1557 while self.state in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1558 self.state_updated.wait()
1559 finally:
1560 self.state_updated.release()
1564 # TODO: recategorise - called from XendCheckpoint
1567 def completeRestore(self, store_mfn, console_mfn):
1569 log.debug("XendDomainInfo.completeRestore")
1571 self.store_mfn = store_mfn
1572 self.console_mfn = console_mfn
1574 self._introduceDomain()
1575 image_cfg = self.info.get('image', {})
1576 hvm = image_cfg.has_key('hvm')
1577 if hvm:
1578 self.image = image.create(self,
1579 self.info,
1580 self.info['image'],
1581 self.info['devices'])
1582 if self.image:
1583 self.image.createDeviceModel(True)
1584 self.image.register_shutdown_watch()
1585 self._storeDomDetails()
1586 self._registerWatches()
1587 self.refreshShutdown()
1589 log.debug("XendDomainInfo.completeRestore done")
1592 def _endRestore(self):
1593 self.setResume(False)
1596 # VM Destroy
1599 def _prepare_phantom_paths(self):
1600 # get associated devices to destroy
1601 # build list of phantom devices to be removed after normal devices
1602 plist = []
1603 if self.domid is not None:
1604 from xen.xend.xenstore.xstransact import xstransact
1605 t = xstransact("%s/device/vbd" % GetDomainPath(self.domid))
1606 for dev in t.list():
1607 backend_phantom_vbd = xstransact.Read("%s/device/vbd/%s/phantom_vbd" \
1608 % (self.dompath, dev))
1609 if backend_phantom_vbd is not None:
1610 frontend_phantom_vbd = xstransact.Read("%s/frontend" \
1611 % backend_phantom_vbd)
1612 plist.append(backend_phantom_vbd)
1613 plist.append(frontend_phantom_vbd)
1614 return plist
1616 def _cleanup_phantom_devs(self, plist):
1617 # remove phantom devices
1618 if not plist == []:
1619 time.sleep(2)
1620 for paths in plist:
1621 if paths.find('backend') != -1:
1622 from xen.xend.server import DevController
1623 # Modify online status /before/ updating state (latter is watched by
1624 # drivers, so this ordering avoids a race).
1625 xstransact.Write(paths, 'online', "0")
1626 xstransact.Write(paths, 'state', str(DevController.xenbusState['Closing']))
1627 # force
1628 xstransact.Remove(paths)
1630 def destroy(self):
1631 """Cleanup VM and destroy domain. Nothrow guarantee."""
1633 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1635 paths = self._prepare_phantom_paths()
1637 self._cleanupVm()
1638 if self.dompath is not None:
1639 self.destroyDomain()
1641 self._cleanup_phantom_devs(paths)
1643 def destroyDomain(self):
1644 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1646 paths = self._prepare_phantom_paths()
1648 try:
1649 if self.domid is not None:
1650 xc.domain_destroy(self.domid)
1651 self.domid = None
1652 for state in DOM_STATES_OLD:
1653 self.info[state] = 0
1654 except:
1655 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1657 from xen.xend import XendDomain
1658 XendDomain.instance().remove_domain(self)
1660 self.cleanupDomain()
1661 self._cleanup_phantom_devs(paths)
1663 def resumeDomain(self):
1664 log.debug("XendDomainInfo.resumeDomain(%s)", str(self.domid))
1666 if self.domid is None:
1667 return
1668 try:
1669 # could also fetch a parsed note from xenstore
1670 fast = self.info.get_notes().get('SUSPEND_CANCEL') and 1 or 0
1671 if not fast:
1672 self._releaseDevices()
1673 self.testDeviceComplete()
1674 self.testvifsComplete()
1675 log.debug("XendDomainInfo.resumeDomain: devices released")
1677 self._resetChannels()
1679 self._removeDom('control/shutdown')
1680 self._removeDom('device-misc/vif/nextDeviceID')
1682 self._createChannels()
1683 self._introduceDomain()
1684 self._storeDomDetails()
1686 self._createDevices()
1687 log.debug("XendDomainInfo.resumeDomain: devices created")
1689 xc.domain_resume(self.domid, fast)
1690 ResumeDomain(self.domid)
1691 except:
1692 log.exception("XendDomainInfo.resume: xc.domain_resume failed on domain %s." % (str(self.domid)))
1695 # Channels for xenstore and console
1698 def _createChannels(self):
1699 """Create the channels to the domain.
1700 """
1701 self.store_port = self._createChannel()
1702 self.console_port = self._createChannel()
1705 def _createChannel(self):
1706 """Create an event channel to the domain.
1707 """
1708 try:
1709 if self.domid != None:
1710 return xc.evtchn_alloc_unbound(domid = self.domid,
1711 remote_dom = 0)
1712 except:
1713 log.exception("Exception in alloc_unbound(%s)", str(self.domid))
1714 raise
1716 def _resetChannels(self):
1717 """Reset all event channels in the domain.
1718 """
1719 try:
1720 if self.domid != None:
1721 return xc.evtchn_reset(dom = self.domid)
1722 except:
1723 log.exception("Exception in evtcnh_reset(%s)", str(self.domid))
1724 raise
1728 # Bootloader configuration
1731 def _configureBootloader(self):
1732 """Run the bootloader if we're configured to do so."""
1734 blexec = self.info['PV_bootloader']
1735 bootloader_args = self.info['PV_bootloader_args']
1736 kernel = self.info['PV_kernel']
1737 ramdisk = self.info['PV_ramdisk']
1738 args = self.info['PV_args']
1739 boot = self.info['HVM_boot_policy']
1741 if boot:
1742 # HVM booting.
1743 self.info['image']['type'] = 'hvm'
1744 if not 'devices' in self.info['image']:
1745 self.info['image']['devices'] = {}
1746 self.info['image']['devices']['boot'] = \
1747 self.info['HVM_boot_params'].get('order', 'dc')
1748 elif not blexec and kernel:
1749 # Boot from dom0. Nothing left to do -- the kernel and ramdisk
1750 # will be picked up by image.py.
1751 pass
1752 else:
1753 # Boot using bootloader
1754 if not blexec or blexec == 'pygrub':
1755 blexec = osdep.pygrub_path
1757 blcfg = None
1758 disks = [x for x in self.info['vbd_refs']
1759 if self.info['devices'][x][1]['bootable']]
1761 if not disks:
1762 msg = "Had a bootloader specified, but no disks are bootable"
1763 log.error(msg)
1764 raise VmError(msg)
1766 devinfo = self.info['devices'][disks[0]]
1767 devtype = devinfo[0]
1768 disk = devinfo[1]['uname']
1770 fn = blkdev_uname_to_file(disk)
1771 mounted = devtype == 'tap' and not os.stat(fn).st_rdev
1772 if mounted:
1773 # This is a file, not a device. pygrub can cope with a
1774 # file if it's raw, but if it's QCOW or other such formats
1775 # used through blktap, then we need to mount it first.
1777 log.info("Mounting %s on %s." %
1778 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1780 vbd = {
1781 'mode': 'RO',
1782 'device': BOOTLOADER_LOOPBACK_DEVICE,
1785 from xen.xend import XendDomain
1786 dom0 = XendDomain.instance().privilegedDomain()
1787 dom0._waitForDeviceUUID(dom0.create_vbd(vbd, fn))
1788 fn = BOOTLOADER_LOOPBACK_DEVICE
1790 try:
1791 blcfg = bootloader(blexec, fn, self, False,
1792 bootloader_args, kernel, ramdisk, args)
1793 finally:
1794 if mounted:
1795 log.info("Unmounting %s from %s." %
1796 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1798 dom0.destroyDevice('tap', '/dev/xvdp')
1800 if blcfg is None:
1801 msg = "Had a bootloader specified, but can't find disk"
1802 log.error(msg)
1803 raise VmError(msg)
1805 self.info.update_with_image_sxp(blcfg, True)
1809 # VM Functions
1812 def _readVMDetails(self, params):
1813 """Read the specified parameters from the store.
1814 """
1815 try:
1816 return self._gatherVm(*params)
1817 except ValueError:
1818 # One of the int/float entries in params has a corresponding store
1819 # entry that is invalid. We recover, because older versions of
1820 # Xend may have put the entry there (memory/target, for example),
1821 # but this is in general a bad situation to have reached.
1822 log.exception(
1823 "Store corrupted at %s! Domain %d's configuration may be "
1824 "affected.", self.vmpath, self.domid)
1825 return []
1827 def _cleanupVm(self):
1828 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1830 self._unwatchVm()
1832 try:
1833 self._removeVm()
1834 except:
1835 log.exception("Removing VM path failed.")
1838 def checkLiveMigrateMemory(self):
1839 """ Make sure there's enough memory to migrate this domain """
1840 overhead_kb = 0
1841 if arch.type == "x86":
1842 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1843 # the minimum that Xen would allocate if no value were given.
1844 overhead_kb = self.info['vcpus_number'] * 1024 + \
1845 self.info['memory_static_max'] * 4
1846 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1847 # The domain might already have some shadow memory
1848 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1849 if overhead_kb > 0:
1850 balloon.free(overhead_kb)
1852 def _unwatchVm(self):
1853 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1854 guarantee."""
1855 try:
1856 try:
1857 if self.vmWatch:
1858 self.vmWatch.unwatch()
1859 finally:
1860 self.vmWatch = None
1861 except:
1862 log.exception("Unwatching VM path failed.")
1864 def testDeviceComplete(self):
1865 """ For Block IO migration safety we must ensure that
1866 the device has shutdown correctly, i.e. all blocks are
1867 flushed to disk
1868 """
1869 start = time.time()
1870 while True:
1871 test = 0
1872 diff = time.time() - start
1873 for i in self.getDeviceController('vbd').deviceIDs():
1874 test = 1
1875 log.info("Dev %s still active, looping...", i)
1876 time.sleep(0.1)
1878 if test == 0:
1879 break
1880 if diff >= MIGRATE_TIMEOUT:
1881 log.info("Dev still active but hit max loop timeout")
1882 break
1884 def testvifsComplete(self):
1885 """ In case vifs are released and then created for the same
1886 domain, we need to wait the device shut down.
1887 """
1888 start = time.time()
1889 while True:
1890 test = 0
1891 diff = time.time() - start
1892 for i in self.getDeviceController('vif').deviceIDs():
1893 test = 1
1894 log.info("Dev %s still active, looping...", i)
1895 time.sleep(0.1)
1897 if test == 0:
1898 break
1899 if diff >= MIGRATE_TIMEOUT:
1900 log.info("Dev still active but hit max loop timeout")
1901 break
1903 def _storeVmDetails(self):
1904 to_store = {}
1906 for key in XendConfig.LEGACY_XENSTORE_VM_PARAMS:
1907 info_key = XendConfig.LEGACY_CFG_TO_XENAPI_CFG.get(key, key)
1908 if self._infoIsSet(info_key):
1909 to_store[key] = str(self.info[info_key])
1911 if self.info.get('image'):
1912 image_sxpr = self.info.image_sxpr()
1913 if image_sxpr:
1914 to_store['image'] = sxp.to_string(image_sxpr)
1916 if self._infoIsSet('security'):
1917 secinfo = self.info['security']
1918 to_store['security'] = sxp.to_string(secinfo)
1919 for idx in range(0, len(secinfo)):
1920 if secinfo[idx][0] == 'access_control':
1921 to_store['security/access_control'] = sxp.to_string(
1922 [secinfo[idx][1], secinfo[idx][2]])
1923 for aidx in range(1, len(secinfo[idx])):
1924 if secinfo[idx][aidx][0] == 'label':
1925 to_store['security/access_control/label'] = \
1926 secinfo[idx][aidx][1]
1927 if secinfo[idx][aidx][0] == 'policy':
1928 to_store['security/access_control/policy'] = \
1929 secinfo[idx][aidx][1]
1930 if secinfo[idx][0] == 'ssidref':
1931 to_store['security/ssidref'] = str(secinfo[idx][1])
1934 if not self._readVm('xend/restart_count'):
1935 to_store['xend/restart_count'] = str(0)
1937 log.debug("Storing VM details: %s", scrub_password(to_store))
1939 self._writeVm(to_store)
1940 self._setVmPermissions()
1943 def _setVmPermissions(self):
1944 """Allow the guest domain to read its UUID. We don't allow it to
1945 access any other entry, for security."""
1946 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1947 { 'dom' : self.domid,
1948 'read' : True,
1949 'write' : False })
1952 # Utility functions
1955 def _stateSet(self, state):
1956 self.state_updated.acquire()
1957 try:
1958 if self.state != state:
1959 self.state = state
1960 self.state_updated.notifyAll()
1961 finally:
1962 self.state_updated.release()
1964 def _infoIsSet(self, name):
1965 return name in self.info and self.info[name] is not None
1967 def _checkName(self, name):
1968 """Check if a vm name is valid. Valid names contain alphabetic
1969 characters, digits, or characters in '_-.:/+'.
1970 The same name cannot be used for more than one vm at the same time.
1972 @param name: name
1973 @raise: VmError if invalid
1974 """
1975 from xen.xend import XendDomain
1977 if name is None or name == '':
1978 raise VmError('Missing VM Name')
1980 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1981 raise VmError('Invalid VM Name')
1983 dom = XendDomain.instance().domain_lookup_nr(name)
1984 if dom and dom.info['uuid'] != self.info['uuid']:
1985 raise VmError("VM name '%s' already exists%s" %
1986 (name,
1987 dom.domid is not None and
1988 (" as domain %s" % str(dom.domid)) or ""))
1991 def update(self, info = None, refresh = True):
1992 """Update with info from xc.domain_getinfo().
1993 """
1994 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1995 str(self.domid))
1997 if not info:
1998 info = dom_get(self.domid)
1999 if not info:
2000 return
2002 #manually update ssidref / security fields
2003 if security.on() and info.has_key('ssidref'):
2004 if (info['ssidref'] != 0) and self.info.has_key('security'):
2005 security_field = self.info['security']
2006 if not security_field:
2007 #create new security element
2008 self.info.update({'security':
2009 [['ssidref', str(info['ssidref'])]]})
2011 #ssidref field not used any longer
2012 if 'ssidref' in info:
2013 info.pop('ssidref')
2015 # make sure state is reset for info
2016 # TODO: we should eventually get rid of old_dom_states
2018 self.info.update_config(info)
2019 self._update_consoles()
2021 if refresh:
2022 self.refreshShutdown(info)
2024 log.trace("XendDomainInfo.update done on domain %s: %s",
2025 str(self.domid), self.info)
2027 def sxpr(self, ignore_store = False, legacy_only = True):
2028 result = self.info.to_sxp(domain = self,
2029 ignore_devices = ignore_store,
2030 legacy_only = legacy_only)
2032 #if not ignore_store and self.dompath:
2033 # vnc_port = self.readDom('console/vnc-port')
2034 # if vnc_port is not None:
2035 # result.append(['device',
2036 # ['console', ['vnc-port', str(vnc_port)]]])
2038 return result
2040 # Xen API
2041 # ----------------------------------------------------------------
2043 def get_uuid(self):
2044 dom_uuid = self.info.get('uuid')
2045 if not dom_uuid: # if it doesn't exist, make one up
2046 dom_uuid = uuid.createString()
2047 self.info['uuid'] = dom_uuid
2048 return dom_uuid
2050 def get_memory_static_max(self):
2051 return self.info.get('memory_static_max', 0)
2052 def get_memory_static_min(self):
2053 return self.info.get('memory_static_min', 0)
2054 def get_memory_dynamic_max(self):
2055 return self.info.get('memory_dynamic_max', 0)
2056 def get_memory_dynamic_min(self):
2057 return self.info.get('memory_dynamic_min', 0)
2059 def get_vcpus_policy(self):
2060 sched_id = xc.sched_id_get()
2061 if sched_id == xen.lowlevel.xc.XEN_SCHEDULER_SEDF:
2062 return 'sedf'
2063 elif sched_id == xen.lowlevel.xc.XEN_SCHEDULER_CREDIT:
2064 return 'credit'
2065 else:
2066 return 'unknown'
2067 def get_vcpus_params(self):
2068 if self.getDomid() is None:
2069 return self.info['vcpus_params']
2071 retval = xc.sched_credit_domain_get(self.getDomid())
2072 return retval
2073 def get_power_state(self):
2074 return XEN_API_VM_POWER_STATE[self.state]
2075 def get_platform_std_vga(self):
2076 return self.info.get('platform_std_vga', False)
2077 def get_platform_serial(self):
2078 return self.info.get('platform_serial', '')
2079 def get_platform_localtime(self):
2080 return self.info.get('platform_localtime', False)
2081 def get_platform_clock_offset(self):
2082 return self.info.get('platform_clock_offset', False)
2083 def get_platform_enable_audio(self):
2084 return self.info.get('platform_enable_audio', False)
2085 def get_platform_keymap(self):
2086 return self.info.get('platform_keymap', '')
2087 def get_pci_bus(self):
2088 return self.info.get('pci_bus', '')
2089 def get_tools_version(self):
2090 return self.info.get('tools_version', {})
2092 def get_on_shutdown(self):
2093 after_shutdown = self.info.get('action_after_shutdown')
2094 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
2095 return XEN_API_ON_NORMAL_EXIT[-1]
2096 return after_shutdown
2098 def get_on_reboot(self):
2099 after_reboot = self.info.get('action_after_reboot')
2100 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
2101 return XEN_API_ON_NORMAL_EXIT[-1]
2102 return after_reboot
2104 def get_on_suspend(self):
2105 # TODO: not supported
2106 after_suspend = self.info.get('action_after_suspend')
2107 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
2108 return XEN_API_ON_NORMAL_EXIT[-1]
2109 return after_suspend
2111 def get_on_crash(self):
2112 after_crash = self.info.get('action_after_crash')
2113 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
2114 return XEN_API_ON_CRASH_BEHAVIOUR[0]
2115 return after_crash
2117 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
2118 """ Get's a device configuration either from XendConfig or
2119 from the DevController.
2121 @param dev_class: device class, either, 'vbd' or 'vif'
2122 @param dev_uuid: device UUID
2124 @rtype: dictionary
2125 """
2126 dev_type, dev_config = self.info['devices'].get(dev_uuid, (None, None))
2128 # shortcut if the domain isn't started because
2129 # the devcontrollers will have no better information
2130 # than XendConfig.
2131 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
2132 if dev_config:
2133 return copy.deepcopy(dev_config)
2134 return None
2136 # instead of using dev_class, we use the dev_type
2137 # that is from XendConfig.
2138 controller = self.getDeviceController(dev_type)
2139 if not controller:
2140 return None
2142 all_configs = controller.getAllDeviceConfigurations()
2143 if not all_configs:
2144 return None
2146 updated_dev_config = copy.deepcopy(dev_config)
2147 for _devid, _devcfg in all_configs.items():
2148 if _devcfg.get('uuid') == dev_uuid:
2149 updated_dev_config.update(_devcfg)
2150 updated_dev_config['id'] = _devid
2151 return updated_dev_config
2153 return updated_dev_config
2155 def get_dev_xenapi_config(self, dev_class, dev_uuid):
2156 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
2157 if not config:
2158 return {}
2160 config['VM'] = self.get_uuid()
2162 if dev_class == 'vif':
2163 if not config.has_key('name'):
2164 config['name'] = config.get('vifname', '')
2165 if not config.has_key('MAC'):
2166 config['MAC'] = config.get('mac', '')
2167 if not config.has_key('type'):
2168 config['type'] = 'paravirtualised'
2169 if not config.has_key('device'):
2170 devid = config.get('id')
2171 if devid != None:
2172 config['device'] = 'eth%d' % devid
2173 else:
2174 config['device'] = ''
2176 if not config.has_key('network'):
2177 try:
2178 config['network'] = \
2179 XendNode.instance().bridge_to_network(
2180 config.get('bridge')).uuid
2181 except Exception:
2182 log.exception('bridge_to_network')
2183 # Ignore this for now -- it may happen if the device
2184 # has been specified using the legacy methods, but at
2185 # some point we're going to have to figure out how to
2186 # handle that properly.
2188 config['MTU'] = 1500 # TODO
2190 if self.state not in (XEN_API_VM_POWER_STATE_HALTED,):
2191 xennode = XendNode.instance()
2192 rx_bps, tx_bps = xennode.get_vif_util(self.domid, devid)
2193 config['io_read_kbs'] = rx_bps/1024
2194 config['io_write_kbs'] = tx_bps/1024
2195 else:
2196 config['io_read_kbs'] = 0.0
2197 config['io_write_kbs'] = 0.0
2199 if dev_class == 'vbd':
2201 if self.state not in (XEN_API_VM_POWER_STATE_HALTED,):
2202 controller = self.getDeviceController(dev_class)
2203 devid, _1, _2 = controller.getDeviceDetails(config)
2204 xennode = XendNode.instance()
2205 rd_blkps, wr_blkps = xennode.get_vbd_util(self.domid, devid)
2206 config['io_read_kbs'] = rd_blkps
2207 config['io_write_kbs'] = wr_blkps
2208 else:
2209 config['io_read_kbs'] = 0.0
2210 config['io_write_kbs'] = 0.0
2212 config['VDI'] = config.get('VDI', '')
2213 config['device'] = config.get('dev', '')
2214 if ':' in config['device']:
2215 vbd_name, vbd_type = config['device'].split(':', 1)
2216 config['device'] = vbd_name
2217 if vbd_type == 'cdrom':
2218 config['type'] = XEN_API_VBD_TYPE[0]
2219 else:
2220 config['type'] = XEN_API_VBD_TYPE[1]
2222 config['driver'] = 'paravirtualised' # TODO
2223 config['image'] = config.get('uname', '')
2225 if config.get('mode', 'r') == 'r':
2226 config['mode'] = 'RO'
2227 else:
2228 config['mode'] = 'RW'
2230 if dev_class == 'vtpm':
2231 if not config.has_key('type'):
2232 config['type'] = 'paravirtualised' # TODO
2233 if not config.has_key('backend'):
2234 config['backend'] = "00000000-0000-0000-0000-000000000000"
2236 return config
2238 def get_dev_property(self, dev_class, dev_uuid, field):
2239 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
2240 try:
2241 return config[field]
2242 except KeyError:
2243 raise XendError('Invalid property for device: %s' % field)
2245 def set_dev_property(self, dev_class, dev_uuid, field, value):
2246 self.info['devices'][dev_uuid][1][field] = value
2248 def get_vcpus_util(self):
2249 vcpu_util = {}
2250 xennode = XendNode.instance()
2251 if 'vcpus_number' in self.info and self.domid != None:
2252 for i in range(0, self.info['vcpus_number']):
2253 util = xennode.get_vcpu_util(self.domid, i)
2254 vcpu_util[str(i)] = util
2256 return vcpu_util
2258 def get_consoles(self):
2259 return self.info.get('console_refs', [])
2261 def get_vifs(self):
2262 return self.info.get('vif_refs', [])
2264 def get_vbds(self):
2265 return self.info.get('vbd_refs', [])
2267 def get_vtpms(self):
2268 return self.info.get('vtpm_refs', [])
2270 def create_vbd(self, xenapi_vbd, vdi_image_path):
2271 """Create a VBD using a VDI from XendStorageRepository.
2273 @param xenapi_vbd: vbd struct from the Xen API
2274 @param vdi_image_path: VDI UUID
2275 @rtype: string
2276 @return: uuid of the device
2277 """
2278 xenapi_vbd['image'] = vdi_image_path
2279 log.debug('create_vbd: %s' % xenapi_vbd)
2280 dev_uuid = ''
2281 if vdi_image_path.startswith('tap'):
2282 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
2283 else:
2284 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
2286 if not dev_uuid:
2287 raise XendError('Failed to create device')
2289 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2290 _, config = self.info['devices'][dev_uuid]
2291 dev_control = None
2293 if vdi_image_path.startswith('tap'):
2294 dev_control = self.getDeviceController('tap')
2295 else:
2296 dev_control = self.getDeviceController('vbd')
2298 config['devid'] = dev_control.createDevice(config)
2300 return dev_uuid
2302 def create_phantom_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
2303 """Create a VBD using a VDI from XendStorageRepository.
2305 @param xenapi_vbd: vbd struct from the Xen API
2306 @param vdi_image_path: VDI UUID
2307 @rtype: string
2308 @return: uuid of the device
2309 """
2310 xenapi_vbd['image'] = vdi_image_path
2311 dev_uuid = self.info.phantom_device_add('tap', cfg_xenapi = xenapi_vbd)
2312 if not dev_uuid:
2313 raise XendError('Failed to create device')
2315 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2316 _, config = self.info['devices'][dev_uuid]
2317 config['devid'] = self.getDeviceController('tap').createDevice(config)
2319 return config['devid']
2321 def create_vif(self, xenapi_vif):
2322 """Create VIF device from the passed struct in Xen API format.
2324 @param xenapi_vif: Xen API VIF Struct.
2325 @rtype: string
2326 @return: UUID
2327 """
2328 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
2329 if not dev_uuid:
2330 raise XendError('Failed to create device')
2332 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2333 _, config = self.info['devices'][dev_uuid]
2334 config['devid'] = self.getDeviceController('vif').createDevice(config)
2336 return dev_uuid
2338 def create_vtpm(self, xenapi_vtpm):
2339 """Create a VTPM device from the passed struct in Xen API format.
2341 @return: uuid of the device
2342 @rtype: string
2343 """
2345 if self.state not in (DOM_STATE_HALTED,):
2346 raise VmError("Can only add vTPM to a halted domain.")
2347 if self.get_vtpms() != []:
2348 raise VmError('Domain already has a vTPM.')
2349 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
2350 if not dev_uuid:
2351 raise XendError('Failed to create device')
2353 return dev_uuid
2355 def create_console(self, xenapi_console):
2356 """ Create a console device from a Xen API struct.
2358 @return: uuid of device
2359 @rtype: string
2360 """
2361 if self.state not in (DOM_STATE_HALTED,):
2362 raise VmError("Can only add console to a halted domain.")
2364 dev_uuid = self.info.device_add('console', cfg_xenapi = xenapi_console)
2365 if not dev_uuid:
2366 raise XendError('Failed to create device')
2368 return dev_uuid
2370 def destroy_device_by_uuid(self, dev_type, dev_uuid):
2371 if dev_uuid not in self.info['devices']:
2372 raise XendError('Device does not exist')
2374 try:
2375 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2376 _, config = self.info['devices'][dev_uuid]
2377 devid = config.get('devid')
2378 if devid != None:
2379 self.getDeviceController(dev_type).destroyDevice(devid, force = False)
2380 else:
2381 raise XendError('Unable to get devid for device: %s:%s' %
2382 (dev_type, dev_uuid))
2383 finally:
2384 del self.info['devices'][dev_uuid]
2385 self.info['%s_refs' % dev_type].remove(dev_uuid)
2387 def destroy_vbd(self, dev_uuid):
2388 self.destroy_device_by_uuid('vbd', dev_uuid)
2390 def destroy_vif(self, dev_uuid):
2391 self.destroy_device_by_uuid('vif', dev_uuid)
2393 def destroy_vtpm(self, dev_uuid):
2394 self.destroy_device_by_uuid('vtpm', dev_uuid)
2396 def has_device(self, dev_class, dev_uuid):
2397 return (dev_uuid in self.info['%s_refs' % dev_class.lower()])
2399 def __str__(self):
2400 return '<domain id=%s name=%s memory=%s state=%s>' % \
2401 (str(self.domid), self.info['name_label'],
2402 str(self.info['memory_static_min']), DOM_STATES[self.state])
2404 __repr__ = __str__