direct-io.hg

view tools/python/xen/xend/XendDomainInfo.py @ 13583:f06f8e134c48

[XEND] Prevent invalid arguments for destroy event channels.

Signed-off-by: Alastair Tse <atse@xensource.com>
author Alastair Tse <atse@xensource.com>
date Wed Jan 24 13:54:37 2007 +0000 (2007-01-24)
parents 248a9c36d816
children bea3d48576c6
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)
553 def getDeviceSxprs(self, deviceClass):
554 if self.state == DOM_STATE_RUNNING:
555 return self.getDeviceController(deviceClass).sxprs()
556 else:
557 sxprs = []
558 dev_num = 0
559 for dev_type, dev_info in self.info.all_devices_sxpr():
560 if dev_type == deviceClass:
561 sxprs.append([dev_num, dev_info])
562 dev_num += 1
563 return sxprs
566 def setMemoryTarget(self, target):
567 """Set the memory target of this domain.
568 @param target: In MiB.
569 """
570 log.debug("Setting memory target of domain %s (%d) to %d MiB.",
571 self.info['name_label'], self.domid, target)
573 if target <= 0:
574 raise XendError('Invalid memory size')
576 self.info['memory_static_min'] = target
577 if self.domid >= 0:
578 self.storeVm("memory", target)
579 self.storeDom("memory/target", target << 10)
580 else:
581 self.info['memory_dynamic_min'] = target
582 xen.xend.XendDomain.instance().managed_config_save(self)
584 def setMemoryMaximum(self, limit):
585 """Set the maximum memory limit of this domain
586 @param limit: In MiB.
587 """
588 log.debug("Setting memory maximum of domain %s (%d) to %d MiB.",
589 self.info['name_label'], self.domid, limit)
591 if limit <= 0:
592 raise XendError('Invalid memory size')
594 self.info['memory_static_max'] = limit
595 if self.domid >= 0:
596 maxmem = int(limit) * 1024
597 try:
598 return xc.domain_setmaxmem(self.domid, maxmem)
599 except Exception, ex:
600 raise XendError(str(ex))
601 else:
602 self.info['memory_dynamic_max'] = limit
603 xen.xend.XendDomain.instance().managed_config_save(self)
606 def getVCPUInfo(self):
607 try:
608 # We include the domain name and ID, to help xm.
609 sxpr = ['domain',
610 ['domid', self.domid],
611 ['name', self.info['name_label']],
612 ['vcpu_count', self.info['vcpus_number']]]
614 for i in range(0, self.info['vcpus_number']):
615 info = xc.vcpu_getinfo(self.domid, i)
617 sxpr.append(['vcpu',
618 ['number', i],
619 ['online', info['online']],
620 ['blocked', info['blocked']],
621 ['running', info['running']],
622 ['cpu_time', info['cpu_time'] / 1e9],
623 ['cpu', info['cpu']],
624 ['cpumap', info['cpumap']]])
626 return sxpr
628 except RuntimeError, exn:
629 raise XendError(str(exn))
631 #
632 # internal functions ... TODO: re-categorised
633 #
635 def _augmentInfo(self, priv):
636 """Augment self.info, as given to us through L{recreate}, with
637 values taken from the store. This recovers those values known
638 to xend but not to the hypervisor.
639 """
640 augment_entries = XendConfig.LEGACY_XENSTORE_VM_PARAMS[:]
641 if priv:
642 augment_entries.remove('memory')
643 augment_entries.remove('maxmem')
644 augment_entries.remove('vcpus')
645 augment_entries.remove('vcpu_avail')
647 vm_config = self._readVMDetails([(k, XendConfig.LEGACY_CFG_TYPES[k])
648 for k in augment_entries])
650 # make returned lists into a dictionary
651 vm_config = dict(zip(augment_entries, vm_config))
653 for arg in augment_entries:
654 xapicfg = arg
655 val = vm_config[arg]
656 if val != None:
657 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
658 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
659 self.info[xapiarg] = val
660 else:
661 self.info[arg] = val
663 # For dom0, we ignore any stored value for the vcpus fields, and
664 # read the current value from Xen instead. This allows boot-time
665 # settings to take precedence over any entries in the store.
666 if priv:
667 xeninfo = dom_get(self.domid)
668 self.info['vcpus_number'] = xeninfo['online_vcpus']
669 self.info['vcpu_avail'] = (1 << xeninfo['online_vcpus']) - 1
671 # read image value
672 image_sxp = self._readVm('image')
673 if image_sxp:
674 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
676 # read devices
677 devices = []
678 for devclass in XendDevices.valid_devices():
679 devconfig = self.getDeviceController(devclass).configurations()
680 if devconfig:
681 devices.extend(devconfig)
683 if not self.info['devices'] and devices is not None:
684 for device in devices:
685 self.info.device_add(device[0], cfg_sxp = device)
687 #
688 # Function to update xenstore /vm/*
689 #
691 def _readVm(self, *args):
692 return xstransact.Read(self.vmpath, *args)
694 def _writeVm(self, *args):
695 return xstransact.Write(self.vmpath, *args)
697 def _removeVm(self, *args):
698 return xstransact.Remove(self.vmpath, *args)
700 def _gatherVm(self, *args):
701 return xstransact.Gather(self.vmpath, *args)
703 def storeVm(self, *args):
704 return xstransact.Store(self.vmpath, *args)
706 #
707 # Function to update xenstore /dom/*
708 #
710 def readDom(self, *args):
711 return xstransact.Read(self.dompath, *args)
713 def gatherDom(self, *args):
714 return xstransact.Gather(self.dompath, *args)
716 def _writeDom(self, *args):
717 return xstransact.Write(self.dompath, *args)
719 def _removeDom(self, *args):
720 return xstransact.Remove(self.dompath, *args)
722 def storeDom(self, *args):
723 return xstransact.Store(self.dompath, *args)
725 def _recreateDom(self):
726 complete(self.dompath, lambda t: self._recreateDomFunc(t))
728 def _recreateDomFunc(self, t):
729 t.remove()
730 t.mkdir()
731 t.set_permissions({'dom' : self.domid})
732 t.write('vm', self.vmpath)
734 def _storeDomDetails(self):
735 to_store = {
736 'domid': str(self.domid),
737 'vm': self.vmpath,
738 'name': self.info['name_label'],
739 'console/limit': str(xoptions.get_console_limit() * 1024),
740 'memory/target': str(self.info['memory_static_min'] * 1024)
741 }
743 def f(n, v):
744 if v is not None:
745 to_store[n] = str(v)
747 f('console/port', self.console_port)
748 f('console/ring-ref', self.console_mfn)
749 f('store/port', self.store_port)
750 f('store/ring-ref', self.store_mfn)
752 to_store.update(self._vcpuDomDetails())
754 log.debug("Storing domain details: %s", scrub_password(to_store))
756 self._writeDom(to_store)
758 def _vcpuDomDetails(self):
759 def availability(n):
760 if self.info['vcpu_avail'] & (1 << n):
761 return 'online'
762 else:
763 return 'offline'
765 result = {}
766 for v in range(0, self.info['vcpus_number']):
767 result["cpu/%d/availability" % v] = availability(v)
768 return result
770 #
771 # xenstore watches
772 #
774 def _registerWatches(self):
775 """Register a watch on this VM's entries in the store, and the
776 domain's control/shutdown node, so that when they are changed
777 externally, we keep up to date. This should only be called by {@link
778 #create}, {@link #recreate}, or {@link #restore}, once the domain's
779 details have been written, but before the new instance is returned."""
780 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
781 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
782 self._handleShutdownWatch)
784 def _storeChanged(self, _):
785 log.trace("XendDomainInfo.storeChanged");
787 changed = False
789 # Check whether values in the configuration have
790 # changed in Xenstore.
792 cfg_vm = ['name', 'on_poweroff', 'on_reboot', 'on_crash']
794 vm_details = self._readVMDetails([(k,XendConfig.LEGACY_CFG_TYPES[k])
795 for k in cfg_vm])
797 # convert two lists into a python dictionary
798 vm_details = dict(zip(cfg_vm, vm_details))
800 for arg, val in vm_details.items():
801 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
802 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
803 if val != None and val != self.info[xapiarg]:
804 self.info[xapiarg] = val
805 changed= True
807 # Check whether image definition has been updated
808 image_sxp = self._readVm('image')
809 if image_sxp and image_sxp != self.info.image_sxpr():
810 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
811 changed = True
813 if changed:
814 # Update the domain section of the store, as this contains some
815 # parameters derived from the VM configuration.
816 self._storeDomDetails()
818 return 1
820 def _handleShutdownWatch(self, _):
821 log.debug('XendDomainInfo.handleShutdownWatch')
823 reason = self.readDom('control/shutdown')
825 if reason and reason != 'suspend':
826 sst = self.readDom('xend/shutdown_start_time')
827 now = time.time()
828 if sst:
829 self.shutdownStartTime = float(sst)
830 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
831 else:
832 self.shutdownStartTime = now
833 self.storeDom('xend/shutdown_start_time', now)
834 timeout = SHUTDOWN_TIMEOUT
836 log.trace(
837 "Scheduling refreshShutdown on domain %d in %ds.",
838 self.domid, timeout)
839 threading.Timer(timeout, self.refreshShutdown).start()
841 return True
844 #
845 # Public Attributes for the VM
846 #
849 def getDomid(self):
850 return self.domid
852 def setName(self, name):
853 self._checkName(name)
854 self.info['name_label'] = name
855 self.storeVm("name", name)
857 def getName(self):
858 return self.info['name_label']
860 def getDomainPath(self):
861 return self.dompath
863 def getShutdownReason(self):
864 return self.readDom('control/shutdown')
866 def getStorePort(self):
867 """For use only by image.py and XendCheckpoint.py."""
868 return self.store_port
870 def getConsolePort(self):
871 """For use only by image.py and XendCheckpoint.py"""
872 return self.console_port
874 def getFeatures(self):
875 """For use only by image.py."""
876 return self.info['features']
878 def getVCpuCount(self):
879 return self.info['vcpus_number']
881 def setVCpuCount(self, vcpus):
882 self.info['vcpu_avail'] = (1 << vcpus) - 1
883 if self.domid >= 0:
884 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
885 # update dom differently depending on whether we are adjusting
886 # vcpu number up or down, otherwise _vcpuDomDetails does not
887 # disable the vcpus
888 if self.info['vcpus_number'] > vcpus:
889 # decreasing
890 self._writeDom(self._vcpuDomDetails())
891 self.info['vcpus_number'] = vcpus
892 else:
893 # same or increasing
894 self.info['vcpus_number'] = vcpus
895 self._writeDom(self._vcpuDomDetails())
896 else:
897 self.info['vcpus_number'] = vcpus
898 xen.xend.XendDomain.instance().managed_config_save(self)
899 log.info("Set VCPU count on domain %s to %d", self.info['name_label'],
900 vcpus)
902 def getLabel(self):
903 return security.get_security_info(self.info, 'label')
905 def getMemoryTarget(self):
906 """Get this domain's target memory size, in KB."""
907 return self.info['memory_static_min'] * 1024
909 def getMemoryMaximum(self):
910 """Get this domain's maximum memory size, in KB."""
911 return self.info['memory_static_max'] * 1024
913 def getResume(self):
914 return str(self._resume)
916 def getCap(self):
917 return self.info.get('cpu_cap', 0)
919 def getWeight(self):
920 return self.info.get('cpu_weight', 256)
922 def setResume(self, state):
923 self._resume = state
925 def getRestartCount(self):
926 return self._readVm('xend/restart_count')
928 def refreshShutdown(self, xeninfo = None):
929 """ Checks the domain for whether a shutdown is required.
931 Called from XendDomainInfo and also image.py for HVM images.
932 """
934 # If set at the end of this method, a restart is required, with the
935 # given reason. This restart has to be done out of the scope of
936 # refresh_shutdown_lock.
937 restart_reason = None
939 self.refresh_shutdown_lock.acquire()
940 try:
941 if xeninfo is None:
942 xeninfo = dom_get(self.domid)
943 if xeninfo is None:
944 # The domain no longer exists. This will occur if we have
945 # scheduled a timer to check for shutdown timeouts and the
946 # shutdown succeeded. It will also occur if someone
947 # destroys a domain beneath us. We clean up the domain,
948 # just in case, but we can't clean up the VM, because that
949 # VM may have migrated to a different domain on this
950 # machine.
951 self.cleanupDomain()
952 self._stateSet(DOM_STATE_HALTED)
953 return
955 if xeninfo['dying']:
956 # Dying means that a domain has been destroyed, but has not
957 # yet been cleaned up by Xen. This state could persist
958 # indefinitely if, for example, another domain has some of its
959 # pages mapped. We might like to diagnose this problem in the
960 # future, but for now all we do is make sure that it's not us
961 # holding the pages, by calling cleanupDomain. We can't
962 # clean up the VM, as above.
963 self.cleanupDomain()
964 self._stateSet(DOM_STATE_SHUTDOWN)
965 return
967 elif xeninfo['crashed']:
968 if self.readDom('xend/shutdown_completed'):
969 # We've seen this shutdown already, but we are preserving
970 # the domain for debugging. Leave it alone.
971 return
973 log.warn('Domain has crashed: name=%s id=%d.',
974 self.info['name_label'], self.domid)
975 self._writeVm(LAST_SHUTDOWN_REASON, 'crash')
977 if xoptions.get_enable_dump():
978 try:
979 self.dumpCore()
980 except XendError:
981 # This error has been logged -- there's nothing more
982 # we can do in this context.
983 pass
985 restart_reason = 'crash'
986 self._stateSet(DOM_STATE_HALTED)
988 elif xeninfo['shutdown']:
989 self._stateSet(DOM_STATE_SHUTDOWN)
990 if self.readDom('xend/shutdown_completed'):
991 # We've seen this shutdown already, but we are preserving
992 # the domain for debugging. Leave it alone.
993 return
995 else:
996 reason = shutdown_reason(xeninfo['shutdown_reason'])
998 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
999 self.info['name_label'], self.domid, reason)
1000 self._writeVm(LAST_SHUTDOWN_REASON, reason)
1002 self._clearRestart()
1004 if reason == 'suspend':
1005 self._stateSet(DOM_STATE_SUSPENDED)
1006 # Don't destroy the domain. XendCheckpoint will do
1007 # this once it has finished. However, stop watching
1008 # the VM path now, otherwise we will end up with one
1009 # watch for the old domain, and one for the new.
1010 self._unwatchVm()
1011 elif reason in ('poweroff', 'reboot'):
1012 restart_reason = reason
1013 else:
1014 self.destroy()
1016 elif self.dompath is None:
1017 # We have yet to manage to call introduceDomain on this
1018 # domain. This can happen if a restore is in progress, or has
1019 # failed. Ignore this domain.
1020 pass
1021 else:
1022 # Domain is alive. If we are shutting it down, then check
1023 # the timeout on that, and destroy it if necessary.
1024 if xeninfo['paused']:
1025 self._stateSet(DOM_STATE_PAUSED)
1026 else:
1027 self._stateSet(DOM_STATE_RUNNING)
1029 if self.shutdownStartTime:
1030 timeout = (SHUTDOWN_TIMEOUT - time.time() +
1031 self.shutdownStartTime)
1032 if timeout < 0:
1033 log.info(
1034 "Domain shutdown timeout expired: name=%s id=%s",
1035 self.info['name_label'], self.domid)
1036 self.destroy()
1037 finally:
1038 self.refresh_shutdown_lock.release()
1040 if restart_reason:
1041 threading.Thread(target = self._maybeRestart,
1042 args = (restart_reason,)).start()
1046 # Restart functions - handling whether we come back up on shutdown.
1049 def _clearRestart(self):
1050 self._removeDom("xend/shutdown_start_time")
1053 def _maybeRestart(self, reason):
1054 # Dispatch to the correct method based upon the configured on_{reason}
1055 # behaviour.
1056 actions = {"destroy" : self.destroy,
1057 "restart" : self._restart,
1058 "preserve" : self._preserve,
1059 "rename-restart" : self._renameRestart}
1061 action_conf = {
1062 'poweroff': 'actions_after_shutdown',
1063 'reboot': 'actions_after_reboot',
1064 'crash': 'actions_after_crash',
1067 action_target = self.info.get(action_conf.get(reason))
1068 func = actions.get(action_target, None)
1069 if func and callable(func):
1070 func()
1071 else:
1072 self.destroy() # default to destroy
1074 def _renameRestart(self):
1075 self._restart(True)
1077 def _restart(self, rename = False):
1078 """Restart the domain after it has exited.
1080 @param rename True if the old domain is to be renamed and preserved,
1081 False if it is to be destroyed.
1082 """
1083 from xen.xend import XendDomain
1085 if self._readVm(RESTART_IN_PROGRESS):
1086 log.error('Xend failed during restart of domain %s. '
1087 'Refusing to restart to avoid loops.',
1088 str(self.domid))
1089 self.destroy()
1090 return
1092 old_domid = self.domid
1093 self._writeVm(RESTART_IN_PROGRESS, 'True')
1095 now = time.time()
1096 rst = self._readVm('xend/previous_restart_time')
1097 if rst:
1098 rst = float(rst)
1099 timeout = now - rst
1100 if timeout < MINIMUM_RESTART_TIME:
1101 log.error(
1102 'VM %s restarting too fast (%f seconds since the last '
1103 'restart). Refusing to restart to avoid loops.',
1104 self.info['name_label'], timeout)
1105 self.destroy()
1106 return
1108 self._writeVm('xend/previous_restart_time', str(now))
1110 try:
1111 if rename:
1112 self._preserveForRestart()
1113 else:
1114 self._unwatchVm()
1115 self.destroyDomain()
1117 # new_dom's VM will be the same as this domain's VM, except where
1118 # the rename flag has instructed us to call preserveForRestart.
1119 # In that case, it is important that we remove the
1120 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1121 # once the new one is available.
1123 new_dom = None
1124 try:
1125 new_dom = XendDomain.instance().domain_create_from_dict(
1126 self.info)
1127 new_dom.unpause()
1128 rst_cnt = self._readVm('xend/restart_count')
1129 rst_cnt = int(rst_cnt) + 1
1130 self._writeVm('xend/restart_count', str(rst_cnt))
1131 new_dom._removeVm(RESTART_IN_PROGRESS)
1132 except:
1133 if new_dom:
1134 new_dom._removeVm(RESTART_IN_PROGRESS)
1135 new_dom.destroy()
1136 else:
1137 self._removeVm(RESTART_IN_PROGRESS)
1138 raise
1139 except:
1140 log.exception('Failed to restart domain %s.', str(old_domid))
1142 def _preserveForRestart(self):
1143 """Preserve a domain that has been shut down, by giving it a new UUID,
1144 cloning the VM details, and giving it a new name. This allows us to
1145 keep this domain for debugging, but restart a new one in its place
1146 preserving the restart semantics (name and UUID preserved).
1147 """
1149 new_uuid = uuid.createString()
1150 new_name = 'Domain-%s' % new_uuid
1151 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1152 self.info['name_label'], self.domid, self.info['uuid'],
1153 new_name, new_uuid)
1154 self._unwatchVm()
1155 self._releaseDevices()
1156 self.info['name_label'] = new_name
1157 self.info['uuid'] = new_uuid
1158 self.vmpath = XS_VMROOT + new_uuid
1159 self._storeVmDetails()
1160 self._preserve()
1163 def _preserve(self):
1164 log.info("Preserving dead domain %s (%d).", self.info['name_label'],
1165 self.domid)
1166 self._unwatchVm()
1167 self.storeDom('xend/shutdown_completed', 'True')
1168 self._stateSet(DOM_STATE_HALTED)
1171 # Debugging ..
1174 def dumpCore(self, corefile = None):
1175 """Create a core dump for this domain.
1177 @raise: XendError if core dumping failed.
1178 """
1180 try:
1181 if not corefile:
1182 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1183 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1184 self.info['name_label'], self.domid)
1186 if os.path.isdir(corefile):
1187 raise XendError("Cannot dump core in a directory: %s" %
1188 corefile)
1190 xc.domain_dumpcore(self.domid, corefile)
1191 except RuntimeError, ex:
1192 corefile_incomp = corefile+'-incomplete'
1193 os.rename(corefile, corefile_incomp)
1194 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1195 self.domid, self.info['name_label'])
1196 raise XendError("Failed to dump core: %s" % str(ex))
1199 # Device creation/deletion functions
1202 def _createDevice(self, deviceClass, devConfig):
1203 return self.getDeviceController(deviceClass).createDevice(devConfig)
1205 def _waitForDevice(self, deviceClass, devid):
1206 return self.getDeviceController(deviceClass).waitForDevice(devid)
1208 def _waitForDeviceUUID(self, dev_uuid):
1209 deviceClass, config = self.info['devices'].get(dev_uuid)
1210 self._waitForDevice(deviceClass, config['devid'])
1212 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1213 return self.getDeviceController(deviceClass).reconfigureDevice(
1214 devid, devconfig)
1216 def _createDevices(self):
1217 """Create the devices for a vm.
1219 @raise: VmError for invalid devices
1220 """
1221 for (devclass, config) in self.info.get('devices', {}).values():
1222 if devclass in XendDevices.valid_devices():
1223 log.info("createDevice: %s : %s" % (devclass, scrub_password(config)))
1224 self._createDevice(devclass, config)
1226 if self.image:
1227 self.image.createDeviceModel()
1229 def _releaseDevices(self, suspend = False):
1230 """Release all domain's devices. Nothrow guarantee."""
1231 if suspend and self.image:
1232 self.image.destroy(suspend)
1233 return
1235 while True:
1236 t = xstransact("%s/device" % self.dompath)
1237 for devclass in XendDevices.valid_devices():
1238 for dev in t.list(devclass):
1239 try:
1240 t.remove(dev)
1241 except:
1242 # Log and swallow any exceptions in removal --
1243 # there's nothing more we can do.
1244 log.exception(
1245 "Device release failed: %s; %s; %s",
1246 self.info['name_label'], devclass, dev)
1247 if t.commit():
1248 break
1250 def getDeviceController(self, name):
1251 """Get the device controller for this domain, and if it
1252 doesn't exist, create it.
1254 @param name: device class name
1255 @type name: string
1256 @rtype: subclass of DevController
1257 """
1258 if name not in self._deviceControllers:
1259 devController = XendDevices.make_controller(name, self)
1260 if not devController:
1261 raise XendError("Unknown device type: %s" % name)
1262 self._deviceControllers[name] = devController
1264 return self._deviceControllers[name]
1267 # Migration functions (public)
1270 def testMigrateDevices(self, network, dst):
1271 """ Notify all device about intention of migration
1272 @raise: XendError for a device that cannot be migrated
1273 """
1274 for (n, c) in self.info.all_devices_sxpr():
1275 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1276 if rc != 0:
1277 raise XendError("Device of type '%s' refuses migration." % n)
1279 def migrateDevices(self, network, dst, step, domName=''):
1280 """Notify the devices about migration
1281 """
1282 ctr = 0
1283 try:
1284 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1285 self.migrateDevice(dev_type, dev_conf, network, dst,
1286 step, domName)
1287 ctr = ctr + 1
1288 except:
1289 for dev_type, dev_conf in self.info.all_devices_sxpr():
1290 if ctr == 0:
1291 step = step - 1
1292 ctr = ctr - 1
1293 self._recoverMigrateDevice(dev_type, dev_conf, network,
1294 dst, step, domName)
1295 raise
1297 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1298 step, domName=''):
1299 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1300 network, dst, step, domName)
1302 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1303 dst, step, domName=''):
1304 return self.getDeviceController(deviceClass).recover_migrate(
1305 deviceConfig, network, dst, step, domName)
1308 ## private:
1310 def _constructDomain(self):
1311 """Construct the domain.
1313 @raise: VmError on error
1314 """
1316 log.debug('XendDomainInfo.constructDomain')
1318 self.shutdownStartTime = None
1320 image_cfg = self.info.get('image', {})
1321 hvm = image_cfg.has_key('hvm')
1323 if hvm:
1324 info = xc.xeninfo()
1325 if 'hvm' not in info['xen_caps']:
1326 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1327 "supported by your CPU and enabled in your "
1328 "BIOS?")
1330 self.domid = xc.domain_create(
1331 domid = 0,
1332 ssidref = security.get_security_info(self.info, 'ssidref'),
1333 handle = uuid.fromString(self.info['uuid']),
1334 hvm = int(hvm))
1336 if self.domid < 0:
1337 raise VmError('Creating domain failed: name=%s' %
1338 self.info['name_label'])
1340 self.dompath = GetDomainPath(self.domid)
1342 self._recreateDom()
1344 # Set maximum number of vcpus in domain
1345 xc.domain_max_vcpus(self.domid, int(self.info['vcpus_number']))
1347 # register the domain in the list
1348 from xen.xend import XendDomain
1349 XendDomain.instance().add_domain(self)
1351 def _introduceDomain(self):
1352 assert self.domid is not None
1353 assert self.store_mfn is not None
1354 assert self.store_port is not None
1356 try:
1357 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1358 except RuntimeError, exn:
1359 raise XendError(str(exn))
1362 def _initDomain(self):
1363 log.debug('XendDomainInfo.initDomain: %s %s',
1364 self.domid,
1365 self.info['cpu_weight'])
1367 self._configureBootloader()
1369 if not self._infoIsSet('image'):
1370 raise VmError('Missing image in configuration')
1372 try:
1373 self.image = image.create(self,
1374 self.info,
1375 self.info['image'],
1376 self.info['devices'])
1378 localtime = self.info.get('localtime', False)
1379 if localtime:
1380 xc.domain_set_time_offset(self.domid)
1382 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1384 # repin domain vcpus if a restricted cpus list is provided
1385 # this is done prior to memory allocation to aide in memory
1386 # distribution for NUMA systems.
1387 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1388 for v in range(0, self.info['vcpus_number']):
1389 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1391 # Use architecture- and image-specific calculations to determine
1392 # the various headrooms necessary, given the raw configured
1393 # values. maxmem, memory, and shadow are all in KiB.
1394 memory = self.image.getRequiredAvailableMemory(
1395 self.info['memory_static_min'] * 1024)
1396 maxmem = self.image.getRequiredAvailableMemory(
1397 self.info['memory_static_max'] * 1024)
1398 shadow = self.image.getRequiredShadowMemory(
1399 self.info['shadow_memory'] * 1024,
1400 self.info['memory_static_max'] * 1024)
1402 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'],)
1403 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1404 # takes MiB and we must not round down and end up under-providing.
1405 shadow = ((shadow + 1023) / 1024) * 1024
1407 # set memory limit
1408 xc.domain_setmaxmem(self.domid, maxmem)
1410 # Make sure there's enough RAM available for the domain
1411 balloon.free(memory + shadow)
1413 # Set up the shadow memory
1414 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1415 self.info['shadow_memory'] = shadow_cur
1417 self._createChannels()
1419 channel_details = self.image.createImage()
1421 self.store_mfn = channel_details['store_mfn']
1422 if 'console_mfn' in channel_details:
1423 self.console_mfn = channel_details['console_mfn']
1425 self._introduceDomain()
1427 self._createDevices()
1429 self.image.cleanupBootloading()
1431 self.info['start_time'] = time.time()
1433 self._stateSet(DOM_STATE_RUNNING)
1434 except RuntimeError, exn:
1435 log.exception("XendDomainInfo.initDomain: exception occurred")
1436 self.image.cleanupBootloading()
1437 raise VmError(str(exn))
1440 def cleanupDomain(self):
1441 """Cleanup domain resources; release devices. Idempotent. Nothrow
1442 guarantee."""
1444 self.refresh_shutdown_lock.acquire()
1445 try:
1446 self.unwatchShutdown()
1447 self._releaseDevices()
1448 bootloader_tidy(self)
1450 if self.image:
1451 try:
1452 self.image.destroy()
1453 except:
1454 log.exception(
1455 "XendDomainInfo.cleanup: image.destroy() failed.")
1456 self.image = None
1458 try:
1459 self._removeDom()
1460 except:
1461 log.exception("Removing domain path failed.")
1463 self._stateSet(DOM_STATE_HALTED)
1464 finally:
1465 self.refresh_shutdown_lock.release()
1468 def unwatchShutdown(self):
1469 """Remove the watch on the domain's control/shutdown node, if any.
1470 Idempotent. Nothrow guarantee. Expects to be protected by the
1471 refresh_shutdown_lock."""
1473 try:
1474 try:
1475 if self.shutdownWatch:
1476 self.shutdownWatch.unwatch()
1477 finally:
1478 self.shutdownWatch = None
1479 except:
1480 log.exception("Unwatching control/shutdown failed.")
1482 def waitForShutdown(self):
1483 self.state_updated.acquire()
1484 try:
1485 while self.state in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1486 self.state_updated.wait()
1487 finally:
1488 self.state_updated.release()
1492 # TODO: recategorise - called from XendCheckpoint
1495 def completeRestore(self, store_mfn, console_mfn):
1497 log.debug("XendDomainInfo.completeRestore")
1499 self.store_mfn = store_mfn
1500 self.console_mfn = console_mfn
1502 self._introduceDomain()
1503 image_cfg = self.info.get('image', {})
1504 hvm = image_cfg.has_key('hvm')
1505 if hvm:
1506 self.image = image.create(self,
1507 self.info,
1508 self.info['image'],
1509 self.info['devices'])
1510 if self.image:
1511 self.image.createDeviceModel(True)
1512 self.image.register_shutdown_watch()
1513 self._storeDomDetails()
1514 self._registerWatches()
1515 self.refreshShutdown()
1517 log.debug("XendDomainInfo.completeRestore done")
1520 def _endRestore(self):
1521 self.setResume(False)
1524 # VM Destroy
1527 def destroy(self):
1528 """Cleanup VM and destroy domain. Nothrow guarantee."""
1530 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1532 self._cleanupVm()
1533 if self.dompath is not None:
1534 self.destroyDomain()
1537 def destroyDomain(self):
1538 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1540 try:
1541 if self.domid is not None:
1542 xc.domain_destroy(self.domid)
1543 self.domid = None
1544 for state in DOM_STATES_OLD:
1545 self.info[state] = 0
1546 except:
1547 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1549 from xen.xend import XendDomain
1550 XendDomain.instance().remove_domain(self)
1552 self.cleanupDomain()
1555 def resumeDomain(self):
1556 log.debug("XendDomainInfo.resumeDomain(%s)", str(self.domid))
1558 try:
1559 if self.domid is not None:
1560 xc.domain_resume(self.domid)
1561 ResumeDomain(self.domid)
1562 except:
1563 log.exception("XendDomainInfo.resume: xc.domain_resume failed on domain %s." % (str(self.domid)))
1566 # Channels for xenstore and console
1569 def _createChannels(self):
1570 """Create the channels to the domain.
1571 """
1572 self.store_port = self._createChannel()
1573 self.console_port = self._createChannel()
1576 def _createChannel(self):
1577 """Create an event channel to the domain.
1578 """
1579 try:
1580 if self.domid != None:
1581 return xc.evtchn_alloc_unbound(domid = self.domid,
1582 remote_dom = 0)
1583 except:
1584 log.exception("Exception in alloc_unbound(%s)", str(self.domid))
1585 raise
1587 def _resetChannels(self):
1588 """Reset all event channels in the domain.
1589 """
1590 try:
1591 if self.domid != None:
1592 return xc.evtchn_reset(dom = self.domid)
1593 except:
1594 log.exception("Exception in evtcnh_reset(%s)", str(self.domid))
1595 raise
1599 # Bootloader configuration
1602 def _configureBootloader(self):
1603 """Run the bootloader if we're configured to do so."""
1605 blexec = self.info['PV_bootloader']
1606 bootloader_args = self.info['PV_bootloader_args']
1607 kernel = self.info['PV_kernel']
1608 ramdisk = self.info['PV_ramdisk']
1609 args = self.info['PV_args']
1610 boot = self.info['HVM_boot']
1612 if boot:
1613 # HVM booting.
1614 self.info['image']['type'] = 'hvm'
1615 if not 'devices' in self.info['image']:
1616 self.info['image']['devices'] = {}
1617 self.info['image']['devices']['boot'] = boot
1618 elif not blexec and kernel:
1619 # Boot from dom0. Nothing left to do -- the kernel and ramdisk
1620 # will be picked up by image.py.
1621 pass
1622 else:
1623 # Boot using bootloader
1624 if not blexec or blexec == 'pygrub':
1625 blexec = osdep.pygrub_path
1627 blcfg = None
1628 for (devtype, devinfo) in self.info.all_devices_sxpr():
1629 if not devtype or not devinfo or devtype not in ('vbd', 'tap'):
1630 continue
1631 disk = None
1632 for param in devinfo:
1633 if param[0] == 'uname':
1634 disk = param[1]
1635 break
1637 if disk is None:
1638 continue
1639 fn = blkdev_uname_to_file(disk)
1640 mounted = devtype == 'tap' and not os.stat(fn).st_rdev
1641 if mounted:
1642 # This is a file, not a device. pygrub can cope with a
1643 # file if it's raw, but if it's QCOW or other such formats
1644 # used through blktap, then we need to mount it first.
1646 log.info("Mounting %s on %s." %
1647 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1649 vbd = {
1650 'mode': 'RO',
1651 'device': BOOTLOADER_LOOPBACK_DEVICE,
1654 from xen.xend import XendDomain
1655 dom0 = XendDomain.instance().privilegedDomain()
1656 dom0._waitForDeviceUUID(dom0.create_vbd_with_vdi(vbd, fn))
1657 fn = BOOTLOADER_LOOPBACK_DEVICE
1659 try:
1660 blcfg = bootloader(blexec, fn, self, False,
1661 bootloader_args, kernel, ramdisk, args)
1662 finally:
1663 if mounted:
1664 log.info("Unmounting %s from %s." %
1665 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1667 dom0.destroyDevice('tap', '/dev/xvdp')
1669 break
1671 if blcfg is None:
1672 msg = "Had a bootloader specified, but can't find disk"
1673 log.error(msg)
1674 raise VmError(msg)
1676 self.info.update_with_image_sxp(blcfg, True)
1680 # VM Functions
1683 def _readVMDetails(self, params):
1684 """Read the specified parameters from the store.
1685 """
1686 try:
1687 return self._gatherVm(*params)
1688 except ValueError:
1689 # One of the int/float entries in params has a corresponding store
1690 # entry that is invalid. We recover, because older versions of
1691 # Xend may have put the entry there (memory/target, for example),
1692 # but this is in general a bad situation to have reached.
1693 log.exception(
1694 "Store corrupted at %s! Domain %d's configuration may be "
1695 "affected.", self.vmpath, self.domid)
1696 return []
1698 def _cleanupVm(self):
1699 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1701 self._unwatchVm()
1703 try:
1704 self._removeVm()
1705 except:
1706 log.exception("Removing VM path failed.")
1709 def checkLiveMigrateMemory(self):
1710 """ Make sure there's enough memory to migrate this domain """
1711 overhead_kb = 0
1712 if arch.type == "x86":
1713 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1714 # the minimum that Xen would allocate if no value were given.
1715 overhead_kb = self.info['vcpus_number'] * 1024 + \
1716 self.info['memory_static_max'] * 4
1717 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1718 # The domain might already have some shadow memory
1719 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1720 if overhead_kb > 0:
1721 balloon.free(overhead_kb)
1723 def _unwatchVm(self):
1724 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1725 guarantee."""
1726 try:
1727 try:
1728 if self.vmWatch:
1729 self.vmWatch.unwatch()
1730 finally:
1731 self.vmWatch = None
1732 except:
1733 log.exception("Unwatching VM path failed.")
1735 def testDeviceComplete(self):
1736 """ For Block IO migration safety we must ensure that
1737 the device has shutdown correctly, i.e. all blocks are
1738 flushed to disk
1739 """
1740 start = time.time()
1741 while True:
1742 test = 0
1743 diff = time.time() - start
1744 for i in self.getDeviceController('vbd').deviceIDs():
1745 test = 1
1746 log.info("Dev %s still active, looping...", i)
1747 time.sleep(0.1)
1749 if test == 0:
1750 break
1751 if diff >= MIGRATE_TIMEOUT:
1752 log.info("Dev still active but hit max loop timeout")
1753 break
1755 def testvifsComplete(self):
1756 """ In case vifs are released and then created for the same
1757 domain, we need to wait the device shut down.
1758 """
1759 start = time.time()
1760 while True:
1761 test = 0
1762 diff = time.time() - start
1763 for i in self.getDeviceController('vif').deviceIDs():
1764 test = 1
1765 log.info("Dev %s still active, looping...", i)
1766 time.sleep(0.1)
1768 if test == 0:
1769 break
1770 if diff >= MIGRATE_TIMEOUT:
1771 log.info("Dev still active but hit max loop timeout")
1772 break
1774 def _storeVmDetails(self):
1775 to_store = {}
1777 for key in XendConfig.LEGACY_XENSTORE_VM_PARAMS:
1778 info_key = XendConfig.LEGACY_CFG_TO_XENAPI_CFG.get(key, key)
1779 if self._infoIsSet(info_key):
1780 to_store[key] = str(self.info[info_key])
1782 if self.info.get('image'):
1783 image_sxpr = self.info.image_sxpr()
1784 if image_sxpr:
1785 to_store['image'] = sxp.to_string(image_sxpr)
1787 if self._infoIsSet('security'):
1788 secinfo = self.info['security']
1789 to_store['security'] = sxp.to_string(secinfo)
1790 for idx in range(0, len(secinfo)):
1791 if secinfo[idx][0] == 'access_control':
1792 to_store['security/access_control'] = sxp.to_string(
1793 [secinfo[idx][1], secinfo[idx][2]])
1794 for aidx in range(1, len(secinfo[idx])):
1795 if secinfo[idx][aidx][0] == 'label':
1796 to_store['security/access_control/label'] = \
1797 secinfo[idx][aidx][1]
1798 if secinfo[idx][aidx][0] == 'policy':
1799 to_store['security/access_control/policy'] = \
1800 secinfo[idx][aidx][1]
1801 if secinfo[idx][0] == 'ssidref':
1802 to_store['security/ssidref'] = str(secinfo[idx][1])
1805 if not self._readVm('xend/restart_count'):
1806 to_store['xend/restart_count'] = str(0)
1808 log.debug("Storing VM details: %s", scrub_password(to_store))
1810 self._writeVm(to_store)
1811 self._setVmPermissions()
1814 def _setVmPermissions(self):
1815 """Allow the guest domain to read its UUID. We don't allow it to
1816 access any other entry, for security."""
1817 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1818 { 'dom' : self.domid,
1819 'read' : True,
1820 'write' : False })
1823 # Utility functions
1826 def _stateSet(self, state):
1827 self.state_updated.acquire()
1828 try:
1829 if self.state != state:
1830 self.state = state
1831 self.state_updated.notifyAll()
1832 finally:
1833 self.state_updated.release()
1835 def _infoIsSet(self, name):
1836 return name in self.info and self.info[name] is not None
1838 def _checkName(self, name):
1839 """Check if a vm name is valid. Valid names contain alphabetic
1840 characters, digits, or characters in '_-.:/+'.
1841 The same name cannot be used for more than one vm at the same time.
1843 @param name: name
1844 @raise: VmError if invalid
1845 """
1846 from xen.xend import XendDomain
1848 if name is None or name == '':
1849 raise VmError('Missing VM Name')
1851 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1852 raise VmError('Invalid VM Name')
1854 dom = XendDomain.instance().domain_lookup_nr(name)
1855 if dom and dom.info['uuid'] != self.info['uuid']:
1856 raise VmError("VM name '%s' already exists%s" %
1857 (name,
1858 dom.domid is not None and
1859 (" as domain %s" % str(dom.domid)) or ""))
1862 def update(self, info = None, refresh = True):
1863 """Update with info from xc.domain_getinfo().
1864 """
1865 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1866 str(self.domid))
1868 if not info:
1869 info = dom_get(self.domid)
1870 if not info:
1871 return
1873 #manually update ssidref / security fields
1874 if security.on() and info.has_key('ssidref'):
1875 if (info['ssidref'] != 0) and self.info.has_key('security'):
1876 security_field = self.info['security']
1877 if not security_field:
1878 #create new security element
1879 self.info.update({'security':
1880 [['ssidref', str(info['ssidref'])]]})
1882 #ssidref field not used any longer
1883 if 'ssidref' in info:
1884 info.pop('ssidref')
1886 # make sure state is reset for info
1887 # TODO: we should eventually get rid of old_dom_states
1889 self.info.update_config(info)
1891 if refresh:
1892 self.refreshShutdown(info)
1894 log.trace("XendDomainInfo.update done on domain %s: %s",
1895 str(self.domid), self.info)
1897 def sxpr(self, ignore_store = False, legacy_only = True):
1898 result = self.info.to_sxp(domain = self,
1899 ignore_devices = ignore_store,
1900 legacy_only = legacy_only)
1902 if not ignore_store and self.dompath:
1903 vnc_port = self.readDom('console/vnc-port')
1904 if vnc_port is not None:
1905 result.append(['device',
1906 ['console', ['vnc-port', str(vnc_port)]]])
1908 return result
1910 # Xen API
1911 # ----------------------------------------------------------------
1913 def get_uuid(self):
1914 dom_uuid = self.info.get('uuid')
1915 if not dom_uuid: # if it doesn't exist, make one up
1916 dom_uuid = uuid.createString()
1917 self.info['uuid'] = dom_uuid
1918 return dom_uuid
1920 def get_memory_static_max(self):
1921 return self.info.get('memory_static_max', 0)
1922 def get_memory_static_min(self):
1923 return self.info.get('memory_static_min', 0)
1924 def get_memory_dynamic_max(self):
1925 return self.info.get('memory_dynamic_max', 0)
1926 def get_memory_dynamic_min(self):
1927 return self.info.get('memory_dynamic_min', 0)
1929 def get_vcpus_policy(self):
1930 sched_id = xc.sched_id_get()
1931 if sched_id == xen.lowlevel.xc.XEN_SCHEDULER_SEDF:
1932 return 'sedf'
1933 elif sched_id == xen.lowlevel.xc.XEN_SCHEDULER_CREDIT:
1934 return 'credit'
1935 else:
1936 return 'unknown'
1937 def get_vcpus_params(self):
1938 return '' # TODO
1939 def get_power_state(self):
1940 return XEN_API_VM_POWER_STATE[self.state]
1941 def get_platform_std_vga(self):
1942 return self.info.get('platform_std_vga', False)
1943 def get_platform_serial(self):
1944 return self.info.get('platform_serial', '')
1945 def get_platform_localtime(self):
1946 return self.info.get('platform_localtime', False)
1947 def get_platform_clock_offset(self):
1948 return self.info.get('platform_clock_offset', False)
1949 def get_platform_enable_audio(self):
1950 return self.info.get('platform_enable_audio', False)
1951 def get_platform_keymap(self):
1952 return self.info.get('platform_keymap', '')
1953 def get_pci_bus(self):
1954 return '' # TODO
1955 def get_tools_version(self):
1956 return {} # TODO
1957 def get_other_config(self):
1958 return {} # TODO
1960 def get_on_shutdown(self):
1961 after_shutdown = self.info.get('action_after_shutdown')
1962 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
1963 return XEN_API_ON_NORMAL_EXIT[-1]
1964 return after_shutdown
1966 def get_on_reboot(self):
1967 after_reboot = self.info.get('action_after_reboot')
1968 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
1969 return XEN_API_ON_NORMAL_EXIT[-1]
1970 return after_reboot
1972 def get_on_suspend(self):
1973 # TODO: not supported
1974 after_suspend = self.info.get('action_after_suspend')
1975 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
1976 return XEN_API_ON_NORMAL_EXIT[-1]
1977 return after_suspend
1979 def get_on_crash(self):
1980 after_crash = self.info.get('action_after_crash')
1981 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
1982 return XEN_API_ON_CRASH_BEHAVIOUR[0]
1983 return after_crash
1985 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
1986 """ Get's a device configuration either from XendConfig or
1987 from the DevController.
1989 @param dev_class: device class, either, 'vbd' or 'vif'
1990 @param dev_uuid: device UUID
1992 @rtype: dictionary
1993 """
1994 dev_type_config = self.info['devices'].get(dev_uuid)
1996 # shortcut if the domain isn't started because
1997 # the devcontrollers will have no better information
1998 # than XendConfig.
1999 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
2000 if dev_type_config:
2001 return copy.deepcopy(dev_type_config[1])
2002 return None
2004 # instead of using dev_class, we use the dev_type
2005 # that is from XendConfig.
2006 # This will accomdate 'tap' as well as 'vbd'
2007 dev_type = dev_type_config[0]
2009 controller = self.getDeviceController(dev_type)
2010 if not controller:
2011 return None
2013 all_configs = controller.getAllDeviceConfigurations()
2014 if not all_configs:
2015 return None
2017 dev_config = copy.deepcopy(dev_type_config[1])
2018 for _devid, _devcfg in all_configs.items():
2019 if _devcfg.get('uuid') == dev_uuid:
2020 dev_config.update(_devcfg)
2021 dev_config['id'] = _devid
2022 return dev_config
2024 return dev_config
2026 def get_dev_xenapi_config(self, dev_class, dev_uuid):
2027 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
2028 if not config:
2029 return {}
2031 config['VM'] = self.get_uuid()
2033 if dev_class == 'vif':
2034 if not config.has_key('name'):
2035 config['name'] = config.get('vifname', '')
2036 if not config.has_key('MAC'):
2037 config['MAC'] = config.get('mac', '')
2038 if not config.has_key('type'):
2039 config['type'] = 'paravirtualised'
2040 if not config.has_key('device'):
2041 devid = config.get('id')
2042 if devid != None:
2043 config['device'] = 'eth%d' % devid
2044 else:
2045 config['device'] = ''
2047 if not config.has_key('network'):
2048 try:
2049 config['network'] = \
2050 XendNode.instance().bridge_to_network(
2051 config.get('bridge')).uuid
2052 except Exception:
2053 log.exception('bridge_to_network')
2054 # Ignore this for now -- it may happen if the device
2055 # has been specified using the legacy methods, but at
2056 # some point we're going to have to figure out how to
2057 # handle that properly.
2059 config['MTU'] = 1500 # TODO
2061 if self.state not in (XEN_API_VM_POWER_STATE_HALTED,):
2062 xennode = XendNode.instance()
2063 rx_bps, tx_bps = xennode.get_vif_util(self.domid, devid)
2064 config['io_read_kbs'] = rx_bps/1024
2065 config['io_write_kbs'] = tx_bps/1024
2066 else:
2067 config['io_read_kbs'] = 0.0
2068 config['io_write_kbs'] = 0.0
2070 if dev_class == 'vbd':
2072 if self.state not in (XEN_API_VM_POWER_STATE_HALTED,):
2073 controller = self.getDeviceController(dev_class)
2074 devid, _1, _2 = controller.getDeviceDetails(config)
2075 xennode = XendNode.instance()
2076 rd_blkps, wr_blkps = xennode.get_vbd_util(self.domid, devid)
2077 config['io_read_kbs'] = rd_blkps
2078 config['io_write_kbs'] = wr_blkps
2079 else:
2080 config['io_read_kbs'] = 0.0
2081 config['io_write_kbs'] = 0.0
2083 config['VDI'] = config.get('VDI', '')
2084 config['device'] = config.get('dev', '')
2085 if ':' in config['device']:
2086 vbd_name, vbd_type = config['device'].split(':', 1)
2087 config['device'] = vbd_name
2088 if vbd_type == 'cdrom':
2089 config['type'] = XEN_API_VBD_TYPE[0]
2090 else:
2091 config['type'] = XEN_API_VBD_TYPE[1]
2093 config['driver'] = 'paravirtualised' # TODO
2094 config['image'] = config.get('uname', '')
2096 if config.get('mode', 'r') == 'r':
2097 config['mode'] = 'RO'
2098 else:
2099 config['mode'] = 'RW'
2101 if dev_class == 'vtpm':
2102 config['driver'] = 'paravirtualised' # TODO
2104 return config
2106 def get_dev_property(self, dev_class, dev_uuid, field):
2107 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
2108 try:
2109 return config[field]
2110 except KeyError:
2111 raise XendError('Invalid property for device: %s' % field)
2113 def get_vcpus_util(self):
2114 vcpu_util = {}
2115 xennode = XendNode.instance()
2116 if 'vcpus_number' in self.info and self.domid != None:
2117 for i in range(0, self.info['vcpus_number']):
2118 util = xennode.get_vcpu_util(self.domid, i)
2119 vcpu_util[str(i)] = util
2121 return vcpu_util
2123 def get_consoles(self):
2124 return self.info.get('console_refs', [])
2126 def get_vifs(self):
2127 return self.info.get('vif_refs', [])
2129 def get_vbds(self):
2130 return self.info.get('vbd_refs', [])
2132 def get_vtpms(self):
2133 return self.info.get('vtpm_refs', [])
2135 def create_vbd(self, xenapi_vbd):
2136 """Create a VBD device from the passed struct in Xen API format.
2138 @return: uuid of the device
2139 @rtype: string
2140 """
2142 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
2143 if not dev_uuid:
2144 raise XendError('Failed to create device')
2146 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2147 _, config = self.info['devices'][dev_uuid]
2148 config['devid'] = self.getDeviceController('vbd').createDevice(config)
2150 return dev_uuid
2152 def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
2153 """Create a VBD using a VDI from XendStorageRepository.
2155 @param xenapi_vbd: vbd struct from the Xen API
2156 @param vdi_image_path: VDI UUID
2157 @rtype: string
2158 @return: uuid of the device
2159 """
2160 xenapi_vbd['image'] = vdi_image_path
2161 log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
2162 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
2163 if not dev_uuid:
2164 raise XendError('Failed to create device')
2166 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2167 _, config = self.info['devices'][dev_uuid]
2168 config['devid'] = self.getDeviceController('tap').createDevice(config)
2170 return dev_uuid
2172 def create_vif(self, xenapi_vif):
2173 """Create VIF device from the passed struct in Xen API format.
2175 @param xenapi_vif: Xen API VIF Struct.
2176 @rtype: string
2177 @return: UUID
2178 """
2179 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
2180 if not dev_uuid:
2181 raise XendError('Failed to create device')
2183 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2184 _, config = self.info['devices'][dev_uuid]
2185 config['devid'] = self.getDeviceController('vif').createDevice(config)
2187 return dev_uuid
2189 def create_vtpm(self, xenapi_vtpm):
2190 """Create a VTPM device from the passed struct in Xen API format.
2192 @return: uuid of the device
2193 @rtype: string
2194 """
2196 if self.state not in (DOM_STATE_HALTED,):
2197 raise VmError("Can only add vTPM to a halted domain.")
2198 if self.get_vtpms() != []:
2199 raise VmError('Domain already has a vTPM.')
2200 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
2201 if not dev_uuid:
2202 raise XendError('Failed to create device')
2204 return dev_uuid
2206 def has_device(self, dev_class, dev_uuid):
2207 return (dev_uuid in self.info['%s_refs' % dev_class.lower()])
2209 def __str__(self):
2210 return '<domain id=%s name=%s memory=%s state=%s>' % \
2211 (str(self.domid), self.info['name_label'],
2212 str(self.info['memory_static_min']), DOM_STATES[self.state])
2214 __repr__ = __str__