ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 13284:e81c9fc5b431

Catch exception from dumpCore when inside refreshShutdown.

Thanks to John Levon <levon@movementarian.org> for diagnosis and suggesting the
fix.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author Ewan Mellor <ewan@xensource.com>
date Fri Jan 05 16:11:49 2007 +0000 (2007-01-05)
parents 5f86325c62f6
children 2dba70eb5bd5
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
41 from xen.xend import XendRoot, XendNode, XendConfig
43 from xen.xend.XendConfig import scrub_password
44 from xen.xend.XendBootloader import bootloader
45 from xen.xend.XendError import XendError, VmError
46 from xen.xend.XendDevices import XendDevices
47 from xen.xend.xenstore.xstransact import xstransact, complete
48 from xen.xend.xenstore.xsutil import GetDomainPath, IntroduceDomain
49 from xen.xend.xenstore.xswatch import xswatch
50 from xen.xend.XendConstants import *
51 from xen.xend.XendAPIConstants import *
53 MIGRATE_TIMEOUT = 30.0
54 BOOTLOADER_LOOPBACK_DEVICE = '/dev/xvdp'
56 xc = xen.lowlevel.xc.xc()
57 xroot = XendRoot.instance()
59 log = logging.getLogger("xend.XendDomainInfo")
60 #log.setLevel(logging.TRACE)
63 #
64 # There are a number of CPU-related fields:
65 #
66 # vcpus: the number of virtual CPUs this domain is configured to use.
67 # vcpu_avail: a bitmap telling the guest domain whether it may use each of
68 # its VCPUs. This is translated to
69 # <dompath>/cpu/<id>/availability = {online,offline} for use
70 # by the guest domain.
71 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
72 # CPUs that that VCPU may use.
73 # cpu: a configuration setting requesting that VCPU 0 is pinned to
74 # the specified physical CPU.
75 #
76 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
77 # across save, restore, migrate, and restart). The other settings are only
78 # specific to the domain, so are lost when the VM moves.
79 #
82 def create(config):
83 """Creates and start a VM using the supplied configuration.
85 @param config: A configuration object involving lists of tuples.
86 @type config: list of lists, eg ['vm', ['image', 'xen.gz']]
88 @rtype: XendDomainInfo
89 @return: An up and running XendDomainInfo instance
90 @raise VmError: Invalid configuration or failure to start.
91 """
93 log.debug("XendDomainInfo.create(%s)", scrub_password(config))
94 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config))
95 try:
96 vm.start()
97 except:
98 log.exception('Domain construction failed')
99 vm.destroy()
100 raise
102 return vm
104 def create_from_dict(config_dict):
105 """Creates and start a VM using the supplied configuration.
107 @param config_dict: An configuration dictionary.
109 @rtype: XendDomainInfo
110 @return: An up and running XendDomainInfo instance
111 @raise VmError: Invalid configuration or failure to start.
112 """
114 log.debug("XendDomainInfo.create_from_dict(%s)",
115 scrub_password(config_dict))
116 vm = XendDomainInfo(XendConfig.XendConfig(xapi = config_dict))
117 try:
118 vm.start()
119 except:
120 log.exception('Domain construction failed')
121 vm.destroy()
122 raise
124 return vm
126 def recreate(info, priv):
127 """Create the VM object for an existing domain. The domain must not
128 be dying, as the paths in the store should already have been removed,
129 and asking us to recreate them causes problems.
131 @param xeninfo: Parsed configuration
132 @type xeninfo: Dictionary
133 @param priv: Is a privileged domain (Dom 0)
134 @type priv: bool
136 @rtype: XendDomainInfo
137 @return: A up and running XendDomainInfo instance
138 @raise VmError: Invalid configuration.
139 @raise XendError: Errors with configuration.
140 """
142 log.debug("XendDomainInfo.recreate(%s)", scrub_password(info))
144 assert not info['dying']
146 xeninfo = XendConfig.XendConfig(dominfo = info)
147 domid = xeninfo['domid']
148 uuid1 = uuid.fromString(xeninfo['uuid'])
149 needs_reinitialising = False
151 dompath = GetDomainPath(domid)
152 if not dompath:
153 raise XendError('No domain path in store for existing '
154 'domain %d' % domid)
156 log.info("Recreating domain %d, UUID %s. at %s" %
157 (domid, xeninfo['uuid'], dompath))
159 # need to verify the path and uuid if not Domain-0
160 # if the required uuid and vm aren't set, then that means
161 # we need to recreate the dom with our own values
162 #
163 # NOTE: this is probably not desirable, really we should just
164 # abort or ignore, but there may be cases where xenstore's
165 # entry disappears (eg. xenstore-rm /)
166 #
167 try:
168 vmpath = xstransact.Read(dompath, "vm")
169 if not vmpath:
170 log.warn('/local/domain/%d/vm is missing. recreate is '
171 'confused, trying our best to recover' % domid)
172 needs_reinitialising = True
173 raise XendError('reinit')
175 uuid2_str = xstransact.Read(vmpath, "uuid")
176 if not uuid2_str:
177 log.warn('%s/uuid/ is missing. recreate is confused, '
178 'trying our best to recover' % vmpath)
179 needs_reinitialising = True
180 raise XendError('reinit')
182 uuid2 = uuid.fromString(uuid2_str)
183 if uuid1 != uuid2:
184 log.warn('UUID in /vm does not match the UUID in /dom/%d.'
185 'Trying out best to recover' % domid)
186 needs_reinitialising = True
187 except XendError:
188 pass # our best shot at 'goto' in python :)
190 vm = XendDomainInfo(xeninfo, domid, dompath, augment = True, priv = priv)
192 if needs_reinitialising:
193 vm._recreateDom()
194 vm._removeVm()
195 vm._storeVmDetails()
196 vm._storeDomDetails()
198 if vm.info['image']: # Only dom0 should be without an image entry when
199 # recreating, but we cope with missing ones
200 # elsewhere just in case.
201 vm.image = image.create(vm,
202 vm.info,
203 vm.info['image'],
204 vm.info['devices'])
205 vm.image.recreate()
207 vm._registerWatches()
208 vm.refreshShutdown(xeninfo)
209 return vm
212 def restore(config):
213 """Create a domain and a VM object to do a restore.
215 @param config: Domain SXP configuration
216 @type config: list of lists. (see C{create})
218 @rtype: XendDomainInfo
219 @return: A up and running XendDomainInfo instance
220 @raise VmError: Invalid configuration or failure to start.
221 @raise XendError: Errors with configuration.
222 """
224 log.debug("XendDomainInfo.restore(%s)", scrub_password(config))
225 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config),
226 resume = True)
227 try:
228 vm.resume()
229 return vm
230 except:
231 vm.destroy()
232 raise
234 def createDormant(domconfig):
235 """Create a dormant/inactive XenDomainInfo without creating VM.
236 This is for creating instances of persistent domains that are not
237 yet start.
239 @param domconfig: Parsed configuration
240 @type domconfig: XendConfig object
242 @rtype: XendDomainInfo
243 @return: A up and running XendDomainInfo instance
244 @raise XendError: Errors with configuration.
245 """
247 log.debug("XendDomainInfo.createDormant(%s)", scrub_password(domconfig))
249 # domid does not make sense for non-running domains.
250 domconfig.pop('domid', None)
251 vm = XendDomainInfo(domconfig)
252 return vm
254 def domain_by_name(name):
255 """Get domain by name
257 @params name: Name of the domain
258 @type name: string
259 @return: XendDomainInfo or None
260 """
261 from xen.xend import XendDomain
262 return XendDomain.instance().domain_lookup_by_name_nr(name)
265 def shutdown_reason(code):
266 """Get a shutdown reason from a code.
268 @param code: shutdown code
269 @type code: int
270 @return: shutdown reason
271 @rtype: string
272 """
273 return DOMAIN_SHUTDOWN_REASONS.get(code, "?")
275 def dom_get(dom):
276 """Get info from xen for an existing domain.
278 @param dom: domain id
279 @type dom: int
280 @return: info or None
281 @rtype: dictionary
282 """
283 try:
284 domlist = xc.domain_getinfo(dom, 1)
285 if domlist and dom == domlist[0]['domid']:
286 return domlist[0]
287 except Exception, err:
288 # ignore missing domain
289 log.trace("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
290 return None
293 class XendDomainInfo:
294 """An object represents a domain.
296 @TODO: try to unify dom and domid, they mean the same thing, but
297 xc refers to it as dom, and everywhere else, including
298 xenstore it is domid. The best way is to change xc's
299 python interface.
301 @ivar info: Parsed configuration
302 @type info: dictionary
303 @ivar domid: Domain ID (if VM has started)
304 @type domid: int or None
305 @ivar vmpath: XenStore path to this VM.
306 @type vmpath: string
307 @ivar dompath: XenStore path to this Domain.
308 @type dompath: string
309 @ivar image: Reference to the VM Image.
310 @type image: xen.xend.image.ImageHandler
311 @ivar store_port: event channel to xenstored
312 @type store_port: int
313 @ivar console_port: event channel to xenconsoled
314 @type console_port: int
315 @ivar store_mfn: xenstored mfn
316 @type store_mfn: int
317 @ivar console_mfn: xenconsoled mfn
318 @type console_mfn: int
319 @ivar vmWatch: reference to a watch on the xenstored vmpath
320 @type vmWatch: xen.xend.xenstore.xswatch
321 @ivar shutdownWatch: reference to watch on the xenstored domain shutdown
322 @type shutdownWatch: xen.xend.xenstore.xswatch
323 @ivar shutdownStartTime: UNIX Time when domain started shutting down.
324 @type shutdownStartTime: float or None
325 @ivar state: Domain state
326 @type state: enum(DOM_STATE_HALTED, DOM_STATE_RUNNING, ...)
327 @ivar state_updated: lock for self.state
328 @type state_updated: threading.Condition
329 @ivar refresh_shutdown_lock: lock for polling shutdown state
330 @type refresh_shutdown_lock: threading.Condition
331 @ivar _deviceControllers: device controller cache for this domain
332 @type _deviceControllers: dict 'string' to DevControllers
333 """
335 def __init__(self, info, domid = None, dompath = None, augment = False,
336 priv = False, resume = False):
337 """Constructor for a domain
339 @param info: parsed configuration
340 @type info: dictionary
341 @keyword domid: Set initial domain id (if any)
342 @type domid: int
343 @keyword dompath: Set initial dompath (if any)
344 @type dompath: string
345 @keyword augment: Augment given info with xenstored VM info
346 @type augment: bool
347 @keyword priv: Is a privileged domain (Dom 0)
348 @type priv: bool
349 @keyword resume: Is this domain being resumed?
350 @type resume: bool
351 """
353 self.info = info
354 if domid == None:
355 self.domid = self.info.get('domid')
356 else:
357 self.domid = domid
359 #REMOVE: uuid is now generated in XendConfig
360 #if not self._infoIsSet('uuid'):
361 # self.info['uuid'] = uuid.toString(uuid.create())
363 self.vmpath = XS_VMROOT + self.info['uuid']
364 self.dompath = dompath
366 self.image = None
367 self.store_port = None
368 self.store_mfn = None
369 self.console_port = None
370 self.console_mfn = None
372 self.vmWatch = None
373 self.shutdownWatch = None
374 self.shutdownStartTime = None
375 self._resume = resume
377 self.state = DOM_STATE_HALTED
378 self.state_updated = threading.Condition()
379 self.refresh_shutdown_lock = threading.Condition()
381 self._deviceControllers = {}
383 for state in DOM_STATES_OLD:
384 self.info[state] = 0
386 if augment:
387 self._augmentInfo(priv)
389 self._checkName(self.info['name_label'])
392 #
393 # Public functions available through XMLRPC
394 #
397 def start(self, is_managed = False):
398 """Attempts to start the VM by do the appropriate
399 initialisation if it not started.
400 """
401 from xen.xend import XendDomain
403 if self.state == DOM_STATE_HALTED:
404 try:
405 self._constructDomain()
406 self._initDomain()
407 self._storeVmDetails()
408 self._storeDomDetails()
409 self._registerWatches()
410 self.refreshShutdown()
412 # save running configuration if XendDomains believe domain is
413 # persistent
414 if is_managed:
415 xendomains = XendDomain.instance()
416 xendomains.managed_config_save(self)
417 except:
418 log.exception('VM start failed')
419 self.destroy()
420 raise
421 else:
422 raise XendError('VM already running')
424 def resume(self):
425 """Resumes a domain that has come back from suspension."""
426 if self.state in (DOM_STATE_HALTED, DOM_STATE_SUSPENDED):
427 try:
428 self._constructDomain()
429 self._storeVmDetails()
430 self._createDevices()
431 self._createChannels()
432 self._storeDomDetails()
433 self._endRestore()
434 except:
435 log.exception('VM resume failed')
436 raise
437 else:
438 raise XendError('VM already running')
440 def shutdown(self, reason):
441 """Shutdown a domain by signalling this via xenstored."""
442 log.debug('XendDomainInfo.shutdown(%s)', reason)
443 if self.state in (DOM_STATE_SHUTDOWN, DOM_STATE_HALTED,):
444 raise XendError('Domain cannot be shutdown')
446 if self.domid == 0:
447 raise XendError('Domain 0 cannot be shutdown')
449 if reason not in DOMAIN_SHUTDOWN_REASONS.values():
450 raise XendError('Invalid reason: %s' % reason)
451 self._removeVm('xend/previous_restart_time')
452 self.storeDom("control/shutdown", reason)
454 def pause(self):
455 """Pause domain
457 @raise XendError: Failed pausing a domain
458 """
459 try:
460 xc.domain_pause(self.domid)
461 self._stateSet(DOM_STATE_PAUSED)
462 except Exception, ex:
463 log.exception(ex)
464 raise XendError("Domain unable to be paused: %s" % str(ex))
466 def unpause(self):
467 """Unpause domain
469 @raise XendError: Failed unpausing a domain
470 """
471 try:
472 xc.domain_unpause(self.domid)
473 self._stateSet(DOM_STATE_RUNNING)
474 except Exception, ex:
475 log.exception(ex)
476 raise XendError("Domain unable to be unpaused: %s" % str(ex))
478 def send_sysrq(self, key):
479 """ Send a Sysrq equivalent key via xenstored."""
480 asserts.isCharConvertible(key)
481 self.storeDom("control/sysrq", '%c' % key)
483 def device_create(self, dev_config):
484 """Create a new device.
486 @param dev_config: device configuration
487 @type dev_config: SXP object (parsed config)
488 """
489 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config))
490 dev_type = sxp.name(dev_config)
491 dev_uuid = self.info.device_add(dev_type, cfg_sxp = dev_config)
492 dev_config_dict = self.info['devices'][dev_uuid][1]
493 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config_dict))
494 dev_config_dict['devid'] = devid = \
495 self._createDevice(dev_type, dev_config_dict)
496 self._waitForDevice(dev_type, devid)
497 return self.getDeviceController(dev_type).sxpr(devid)
499 def device_configure(self, dev_config, devid = None):
500 """Configure an existing device.
502 @param dev_config: device configuration
503 @type dev_config: SXP object (parsed config)
504 @param devid: device id
505 @type devid: int
506 @return: Returns True if successfully updated device
507 @rtype: boolean
508 """
509 deviceClass = sxp.name(dev_config)
511 # look up uuid of the device
512 dev_control = self.getDeviceController(deviceClass)
513 dev_sxpr = dev_control.sxpr(devid)
514 dev_uuid = sxp.child_value(dev_sxpr, 'uuid')
515 if not dev_uuid:
516 return False
518 self.info.device_update(dev_uuid, dev_config)
519 dev_config_dict = self.info['devices'].get(dev_uuid)
520 if dev_config_dict:
521 dev_control.reconfigureDevice(devid, dev_config_dict[1])
522 return True
524 def waitForDevices(self):
525 """Wait for this domain's configured devices to connect.
527 @raise VmError: if any device fails to initialise.
528 """
529 for devclass in XendDevices.valid_devices():
530 self.getDeviceController(devclass).waitForDevices()
532 def destroyDevice(self, deviceClass, devid, force = False):
533 try:
534 devid = int(devid)
535 except ValueError:
536 # devid is not a number, let's search for it in xenstore.
537 devicePath = '%s/device/%s' % (self.dompath, deviceClass)
538 for entry in xstransact.List(devicePath):
539 backend = xstransact.Read('%s/%s' % (devicePath, entry),
540 "backend")
541 devName = xstransact.Read(backend, "dev")
542 if devName == devid:
543 # We found the integer matching our devid, use it instead
544 devid = entry
545 break
547 return self.getDeviceController(deviceClass).destroyDevice(devid, force)
551 def getDeviceSxprs(self, deviceClass):
552 if self.state == DOM_STATE_RUNNING:
553 return self.getDeviceController(deviceClass).sxprs()
554 else:
555 sxprs = []
556 dev_num = 0
557 for dev_type, dev_info in self.info.all_devices_sxpr():
558 if dev_type == deviceClass:
559 sxprs.append([dev_num, dev_info])
560 dev_num += 1
561 return sxprs
564 def setMemoryTarget(self, target):
565 """Set the memory target of this domain.
566 @param target: In MiB.
567 """
568 log.debug("Setting memory target of domain %s (%d) to %d MiB.",
569 self.info['name_label'], self.domid, target)
571 if target <= 0:
572 raise XendError('Invalid memory size')
574 self.info['memory_static_min'] = target
575 if self.domid >= 0:
576 self.storeVm("memory", target)
577 self.storeDom("memory/target", target << 10)
578 else:
579 self.info['memory_dynamic_min'] = target
580 xen.xend.XendDomain.instance().managed_config_save(self)
582 def setMemoryMaximum(self, limit):
583 """Set the maximum memory limit of this domain
584 @param limit: In MiB.
585 """
586 log.debug("Setting memory maximum of domain %s (%d) to %d MiB.",
587 self.info['name_label'], self.domid, limit)
589 if limit <= 0:
590 raise XendError('Invalid memory size')
592 self.info['memory_static_max'] = limit
593 if self.domid >= 0:
594 maxmem = int(limit) * 1024
595 try:
596 return xc.domain_setmaxmem(self.domid, maxmem)
597 except Exception, ex:
598 raise XendError(str(ex))
599 else:
600 self.info['memory_dynamic_max'] = limit
601 xen.xend.XendDomain.instance().managed_config_save(self)
604 def getVCPUInfo(self):
605 try:
606 # We include the domain name and ID, to help xm.
607 sxpr = ['domain',
608 ['domid', self.domid],
609 ['name', self.info['name_label']],
610 ['vcpu_count', self.info['vcpus_number']]]
612 for i in range(0, self.info['max_vcpu_id']+1):
613 info = xc.vcpu_getinfo(self.domid, i)
615 sxpr.append(['vcpu',
616 ['number', i],
617 ['online', info['online']],
618 ['blocked', info['blocked']],
619 ['running', info['running']],
620 ['cpu_time', info['cpu_time'] / 1e9],
621 ['cpu', info['cpu']],
622 ['cpumap', info['cpumap']]])
624 return sxpr
626 except RuntimeError, exn:
627 raise XendError(str(exn))
629 #
630 # internal functions ... TODO: re-categorised
631 #
633 def _augmentInfo(self, priv):
634 """Augment self.info, as given to us through L{recreate}, with
635 values taken from the store. This recovers those values known
636 to xend but not to the hypervisor.
637 """
638 augment_entries = XendConfig.LEGACY_XENSTORE_VM_PARAMS[:]
639 if priv:
640 augment_entries.remove('memory')
641 augment_entries.remove('maxmem')
642 augment_entries.remove('vcpus')
643 augment_entries.remove('vcpu_avail')
645 vm_config = self._readVMDetails([(k, XendConfig.LEGACY_CFG_TYPES[k])
646 for k in augment_entries])
648 # make returned lists into a dictionary
649 vm_config = dict(zip(augment_entries, vm_config))
651 for arg in augment_entries:
652 xapicfg = arg
653 val = vm_config[arg]
654 if val != None:
655 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
656 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
657 self.info[xapiarg] = val
658 else:
659 self.info[arg] = val
661 # For dom0, we ignore any stored value for the vcpus fields, and
662 # read the current value from Xen instead. This allows boot-time
663 # settings to take precedence over any entries in the store.
664 if priv:
665 xeninfo = dom_get(self.domid)
666 self.info['vcpus_number'] = xeninfo['online_vcpus']
667 self.info['vcpu_avail'] = (1 << xeninfo['online_vcpus']) - 1
669 # read image value
670 image_sxp = self._readVm('image')
671 if image_sxp:
672 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
674 # read devices
675 devices = []
676 for devclass in XendDevices.valid_devices():
677 devconfig = self.getDeviceController(devclass).configurations()
678 if devconfig:
679 devices.extend(devconfig)
681 if not self.info['devices'] and devices is not None:
682 for device in devices:
683 self.info.device_add(device[0], cfg_sxp = device)
685 #
686 # Function to update xenstore /vm/*
687 #
689 def _readVm(self, *args):
690 return xstransact.Read(self.vmpath, *args)
692 def _writeVm(self, *args):
693 return xstransact.Write(self.vmpath, *args)
695 def _removeVm(self, *args):
696 return xstransact.Remove(self.vmpath, *args)
698 def _gatherVm(self, *args):
699 return xstransact.Gather(self.vmpath, *args)
701 def storeVm(self, *args):
702 return xstransact.Store(self.vmpath, *args)
704 #
705 # Function to update xenstore /dom/*
706 #
708 def readDom(self, *args):
709 return xstransact.Read(self.dompath, *args)
711 def gatherDom(self, *args):
712 return xstransact.Gather(self.dompath, *args)
714 def _writeDom(self, *args):
715 return xstransact.Write(self.dompath, *args)
717 def _removeDom(self, *args):
718 return xstransact.Remove(self.dompath, *args)
720 def storeDom(self, *args):
721 return xstransact.Store(self.dompath, *args)
723 def _recreateDom(self):
724 complete(self.dompath, lambda t: self._recreateDomFunc(t))
726 def _recreateDomFunc(self, t):
727 t.remove()
728 t.mkdir()
729 t.set_permissions({'dom' : self.domid})
730 t.write('vm', self.vmpath)
732 def _storeDomDetails(self):
733 to_store = {
734 'domid': str(self.domid),
735 'vm': self.vmpath,
736 'name': self.info['name_label'],
737 'console/limit': str(xroot.get_console_limit() * 1024),
738 'memory/target': str(self.info['memory_static_min'] * 1024)
739 }
741 def f(n, v):
742 if v is not None:
743 to_store[n] = str(v)
745 f('console/port', self.console_port)
746 f('console/ring-ref', self.console_mfn)
747 f('store/port', self.store_port)
748 f('store/ring-ref', self.store_mfn)
750 to_store.update(self._vcpuDomDetails())
752 log.debug("Storing domain details: %s", scrub_password(to_store))
754 self._writeDom(to_store)
756 def _vcpuDomDetails(self):
757 def availability(n):
758 if self.info['vcpu_avail'] & (1 << n):
759 return 'online'
760 else:
761 return 'offline'
763 result = {}
764 for v in range(0, self.info['vcpus_number']):
765 result["cpu/%d/availability" % v] = availability(v)
766 return result
768 #
769 # xenstore watches
770 #
772 def _registerWatches(self):
773 """Register a watch on this VM's entries in the store, and the
774 domain's control/shutdown node, so that when they are changed
775 externally, we keep up to date. This should only be called by {@link
776 #create}, {@link #recreate}, or {@link #restore}, once the domain's
777 details have been written, but before the new instance is returned."""
778 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
779 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
780 self._handleShutdownWatch)
782 def _storeChanged(self, _):
783 log.trace("XendDomainInfo.storeChanged");
785 changed = False
787 # Check whether values in the configuration have
788 # changed in Xenstore.
790 cfg_vm = ['name', 'on_poweroff', 'on_reboot', 'on_crash']
792 vm_details = self._readVMDetails([(k,XendConfig.LEGACY_CFG_TYPES[k])
793 for k in cfg_vm])
795 # convert two lists into a python dictionary
796 vm_details = dict(zip(cfg_vm, vm_details))
798 for arg, val in vm_details.items():
799 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
800 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
801 if val != None and val != self.info[xapiarg]:
802 self.info[xapiarg] = val
803 changed= True
805 # Check whether image definition has been updated
806 image_sxp = self._readVm('image')
807 if image_sxp and image_sxp != self.info.image_sxpr():
808 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
809 changed = True
811 if changed:
812 # Update the domain section of the store, as this contains some
813 # parameters derived from the VM configuration.
814 self._storeDomDetails()
816 return 1
818 def _handleShutdownWatch(self, _):
819 log.debug('XendDomainInfo.handleShutdownWatch')
821 reason = self.readDom('control/shutdown')
823 if reason and reason != 'suspend':
824 sst = self.readDom('xend/shutdown_start_time')
825 now = time.time()
826 if sst:
827 self.shutdownStartTime = float(sst)
828 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
829 else:
830 self.shutdownStartTime = now
831 self.storeDom('xend/shutdown_start_time', now)
832 timeout = SHUTDOWN_TIMEOUT
834 log.trace(
835 "Scheduling refreshShutdown on domain %d in %ds.",
836 self.domid, timeout)
837 threading.Timer(timeout, self.refreshShutdown).start()
839 return True
842 #
843 # Public Attributes for the VM
844 #
847 def getDomid(self):
848 return self.domid
850 def setName(self, name):
851 self._checkName(name)
852 self.info['name_label'] = name
853 self.storeVm("name", name)
855 def getName(self):
856 return self.info['name_label']
858 def getDomainPath(self):
859 return self.dompath
861 def getShutdownReason(self):
862 return self.readDom('control/shutdown')
864 def getStorePort(self):
865 """For use only by image.py and XendCheckpoint.py."""
866 return self.store_port
868 def getConsolePort(self):
869 """For use only by image.py and XendCheckpoint.py"""
870 return self.console_port
872 def getFeatures(self):
873 """For use only by image.py."""
874 return self.info['features']
876 def getVCpuCount(self):
877 return self.info['vcpus_number']
879 def setVCpuCount(self, vcpus):
880 self.info['vcpu_avail'] = (1 << vcpus) - 1
881 if self.domid >= 0:
882 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
883 # update dom differently depending on whether we are adjusting
884 # vcpu number up or down, otherwise _vcpuDomDetails does not
885 # disable the vcpus
886 if self.info['vcpus_number'] > vcpus:
887 # decreasing
888 self._writeDom(self._vcpuDomDetails())
889 self.info['vcpus_number'] = vcpus
890 else:
891 # same or increasing
892 self.info['vcpus_number'] = vcpus
893 self._writeDom(self._vcpuDomDetails())
894 else:
895 self.info['vcpus_number'] = vcpus
896 self.info['online_vcpus'] = vcpus
897 xen.xend.XendDomain.instance().managed_config_save(self)
899 def getLabel(self):
900 return security.get_security_info(self.info, 'label')
902 def getMemoryTarget(self):
903 """Get this domain's target memory size, in KB."""
904 return self.info['memory_static_min'] * 1024
906 def getMemoryMaximum(self):
907 """Get this domain's maximum memory size, in KB."""
908 return self.info['memory_static_max'] * 1024
910 def getResume(self):
911 return str(self._resume)
913 def getCap(self):
914 return self.info.get('cpu_cap', 0)
916 def getWeight(self):
917 return self.info.get('cpu_weight', 256)
919 def setResume(self, state):
920 self._resume = state
922 def getRestartCount(self):
923 return self._readVm('xend/restart_count')
925 def refreshShutdown(self, xeninfo = None):
926 """ Checks the domain for whether a shutdown is required.
928 Called from XendDomainInfo and also image.py for HVM images.
929 """
931 # If set at the end of this method, a restart is required, with the
932 # given reason. This restart has to be done out of the scope of
933 # refresh_shutdown_lock.
934 restart_reason = None
936 self.refresh_shutdown_lock.acquire()
937 try:
938 if xeninfo is None:
939 xeninfo = dom_get(self.domid)
940 if xeninfo is None:
941 # The domain no longer exists. This will occur if we have
942 # scheduled a timer to check for shutdown timeouts and the
943 # shutdown succeeded. It will also occur if someone
944 # destroys a domain beneath us. We clean up the domain,
945 # just in case, but we can't clean up the VM, because that
946 # VM may have migrated to a different domain on this
947 # machine.
948 self.cleanupDomain()
949 self._stateSet(DOM_STATE_HALTED)
950 return
952 if xeninfo['dying']:
953 # Dying means that a domain has been destroyed, but has not
954 # yet been cleaned up by Xen. This state could persist
955 # indefinitely if, for example, another domain has some of its
956 # pages mapped. We might like to diagnose this problem in the
957 # future, but for now all we do is make sure that it's not us
958 # holding the pages, by calling cleanupDomain. We can't
959 # clean up the VM, as above.
960 self.cleanupDomain()
961 self._stateSet(DOM_STATE_SHUTDOWN)
962 return
964 elif xeninfo['crashed']:
965 if self.readDom('xend/shutdown_completed'):
966 # We've seen this shutdown already, but we are preserving
967 # the domain for debugging. Leave it alone.
968 return
970 log.warn('Domain has crashed: name=%s id=%d.',
971 self.info['name_label'], self.domid)
972 self._writeVm(LAST_SHUTDOWN_REASON, 'crash')
974 if xroot.get_enable_dump():
975 try:
976 self.dumpCore()
977 except XendError:
978 # This error has been logged -- there's nothing more
979 # we can do in this context.
980 pass
982 restart_reason = 'crash'
983 self._stateSet(DOM_STATE_HALTED)
985 elif xeninfo['shutdown']:
986 self._stateSet(DOM_STATE_SHUTDOWN)
987 if self.readDom('xend/shutdown_completed'):
988 # We've seen this shutdown already, but we are preserving
989 # the domain for debugging. Leave it alone.
990 return
992 else:
993 reason = shutdown_reason(xeninfo['shutdown_reason'])
995 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
996 self.info['name_label'], self.domid, reason)
997 self._writeVm(LAST_SHUTDOWN_REASON, reason)
999 self._clearRestart()
1001 if reason == 'suspend':
1002 self._stateSet(DOM_STATE_SUSPENDED)
1003 # Don't destroy the domain. XendCheckpoint will do
1004 # this once it has finished. However, stop watching
1005 # the VM path now, otherwise we will end up with one
1006 # watch for the old domain, and one for the new.
1007 self._unwatchVm()
1008 elif reason in ('poweroff', 'reboot'):
1009 restart_reason = reason
1010 else:
1011 self.destroy()
1013 elif self.dompath is None:
1014 # We have yet to manage to call introduceDomain on this
1015 # domain. This can happen if a restore is in progress, or has
1016 # failed. Ignore this domain.
1017 pass
1018 else:
1019 # Domain is alive. If we are shutting it down, then check
1020 # the timeout on that, and destroy it if necessary.
1021 if xeninfo['paused']:
1022 self._stateSet(DOM_STATE_PAUSED)
1023 else:
1024 self._stateSet(DOM_STATE_RUNNING)
1026 if self.shutdownStartTime:
1027 timeout = (SHUTDOWN_TIMEOUT - time.time() +
1028 self.shutdownStartTime)
1029 if timeout < 0:
1030 log.info(
1031 "Domain shutdown timeout expired: name=%s id=%s",
1032 self.info['name_label'], self.domid)
1033 self.destroy()
1034 finally:
1035 self.refresh_shutdown_lock.release()
1037 if restart_reason:
1038 threading.Thread(target = self._maybeRestart,
1039 args = (restart_reason,)).start()
1043 # Restart functions - handling whether we come back up on shutdown.
1046 def _clearRestart(self):
1047 self._removeDom("xend/shutdown_start_time")
1050 def _maybeRestart(self, reason):
1051 # Dispatch to the correct method based upon the configured on_{reason}
1052 # behaviour.
1053 actions = {"destroy" : self.destroy,
1054 "restart" : self._restart,
1055 "preserve" : self._preserve,
1056 "rename-restart" : self._renameRestart}
1058 action_conf = {
1059 'poweroff': 'actions_after_shutdown',
1060 'reboot': 'actions_after_reboot',
1061 'crash': 'actions_after_crash',
1064 action_target = self.info.get(action_conf.get(reason))
1065 func = actions.get(action_target, None)
1066 if func and callable(func):
1067 func()
1068 else:
1069 self.destroy() # default to destroy
1071 def _renameRestart(self):
1072 self._restart(True)
1074 def _restart(self, rename = False):
1075 """Restart the domain after it has exited.
1077 @param rename True if the old domain is to be renamed and preserved,
1078 False if it is to be destroyed.
1079 """
1080 from xen.xend import XendDomain
1082 if self._readVm(RESTART_IN_PROGRESS):
1083 log.error('Xend failed during restart of domain %s. '
1084 'Refusing to restart to avoid loops.',
1085 str(self.domid))
1086 self.destroy()
1087 return
1089 old_domid = self.domid
1090 self._writeVm(RESTART_IN_PROGRESS, 'True')
1092 now = time.time()
1093 rst = self._readVm('xend/previous_restart_time')
1094 if rst:
1095 rst = float(rst)
1096 timeout = now - rst
1097 if timeout < MINIMUM_RESTART_TIME:
1098 log.error(
1099 'VM %s restarting too fast (%f seconds since the last '
1100 'restart). Refusing to restart to avoid loops.',
1101 self.info['name_label'], timeout)
1102 self.destroy()
1103 return
1105 self._writeVm('xend/previous_restart_time', str(now))
1107 try:
1108 if rename:
1109 self._preserveForRestart()
1110 else:
1111 self._unwatchVm()
1112 self.destroyDomain()
1114 # new_dom's VM will be the same as this domain's VM, except where
1115 # the rename flag has instructed us to call preserveForRestart.
1116 # In that case, it is important that we remove the
1117 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1118 # once the new one is available.
1120 new_dom = None
1121 try:
1122 new_dom = XendDomain.instance().domain_create_from_dict(
1123 self.info)
1124 new_dom.unpause()
1125 rst_cnt = self._readVm('xend/restart_count')
1126 rst_cnt = int(rst_cnt) + 1
1127 self._writeVm('xend/restart_count', str(rst_cnt))
1128 new_dom._removeVm(RESTART_IN_PROGRESS)
1129 except:
1130 if new_dom:
1131 new_dom._removeVm(RESTART_IN_PROGRESS)
1132 new_dom.destroy()
1133 else:
1134 self._removeVm(RESTART_IN_PROGRESS)
1135 raise
1136 except:
1137 log.exception('Failed to restart domain %s.', str(old_domid))
1139 def _preserveForRestart(self):
1140 """Preserve a domain that has been shut down, by giving it a new UUID,
1141 cloning the VM details, and giving it a new name. This allows us to
1142 keep this domain for debugging, but restart a new one in its place
1143 preserving the restart semantics (name and UUID preserved).
1144 """
1146 new_uuid = uuid.createString()
1147 new_name = 'Domain-%s' % new_uuid
1148 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1149 self.info['name_label'], self.domid, self.info['uuid'],
1150 new_name, new_uuid)
1151 self._unwatchVm()
1152 self._releaseDevices()
1153 self.info['name_label'] = new_name
1154 self.info['uuid'] = new_uuid
1155 self.vmpath = XS_VMROOT + new_uuid
1156 self._storeVmDetails()
1157 self._preserve()
1160 def _preserve(self):
1161 log.info("Preserving dead domain %s (%d).", self.info['name_label'],
1162 self.domid)
1163 self._unwatchVm()
1164 self.storeDom('xend/shutdown_completed', 'True')
1165 self._stateSet(DOM_STATE_HALTED)
1168 # Debugging ..
1171 def dumpCore(self, corefile = None):
1172 """Create a core dump for this domain.
1174 @raise: XendError if core dumping failed.
1175 """
1177 try:
1178 if not corefile:
1179 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1180 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1181 self.info['name_label'], self.domid)
1183 if os.path.isdir(corefile):
1184 raise XendError("Cannot dump core in a directory: %s" %
1185 corefile)
1187 xc.domain_dumpcore(self.domid, corefile)
1188 except RuntimeError, ex:
1189 corefile_incomp = corefile+'-incomplete'
1190 os.rename(corefile, corefile_incomp)
1191 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1192 self.domid, self.info['name_label'])
1193 raise XendError("Failed to dump core: %s" % str(ex))
1196 # Device creation/deletion functions
1199 def _createDevice(self, deviceClass, devConfig):
1200 return self.getDeviceController(deviceClass).createDevice(devConfig)
1202 def _waitForDevice(self, deviceClass, devid):
1203 return self.getDeviceController(deviceClass).waitForDevice(devid)
1205 def _waitForDeviceUUID(self, dev_uuid):
1206 deviceClass, config = self.info['devices'].get(dev_uuid)
1207 self._waitForDevice(deviceClass, config['devid'])
1209 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1210 return self.getDeviceController(deviceClass).reconfigureDevice(
1211 devid, devconfig)
1213 def _createDevices(self):
1214 """Create the devices for a vm.
1216 @raise: VmError for invalid devices
1217 """
1218 for (devclass, config) in self.info.get('devices', {}).values():
1219 if devclass in XendDevices.valid_devices():
1220 log.info("createDevice: %s : %s" % (devclass, scrub_password(config)))
1221 self._createDevice(devclass, config)
1223 if self.image:
1224 self.image.createDeviceModel()
1226 def _releaseDevices(self):
1227 """Release all domain's devices. Nothrow guarantee."""
1229 while True:
1230 t = xstransact("%s/device" % self.dompath)
1231 for devclass in XendDevices.valid_devices():
1232 for dev in t.list(devclass):
1233 try:
1234 t.remove(dev)
1235 except:
1236 # Log and swallow any exceptions in removal --
1237 # there's nothing more we can do.
1238 log.exception(
1239 "Device release failed: %s; %s; %s",
1240 self.info['name_label'], devclass, dev)
1241 if t.commit():
1242 break
1244 def getDeviceController(self, name):
1245 """Get the device controller for this domain, and if it
1246 doesn't exist, create it.
1248 @param name: device class name
1249 @type name: string
1250 @rtype: subclass of DevController
1251 """
1252 if name not in self._deviceControllers:
1253 devController = XendDevices.make_controller(name, self)
1254 if not devController:
1255 raise XendError("Unknown device type: %s" % name)
1256 self._deviceControllers[name] = devController
1258 return self._deviceControllers[name]
1261 # Migration functions (public)
1264 def testMigrateDevices(self, network, dst):
1265 """ Notify all device about intention of migration
1266 @raise: XendError for a device that cannot be migrated
1267 """
1268 for (n, c) in self.info.all_devices_sxpr():
1269 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1270 if rc != 0:
1271 raise XendError("Device of type '%s' refuses migration." % n)
1273 def migrateDevices(self, network, dst, step, domName=''):
1274 """Notify the devices about migration
1275 """
1276 ctr = 0
1277 try:
1278 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1279 self.migrateDevice(dev_type, dev_conf, network, dst,
1280 step, domName)
1281 ctr = ctr + 1
1282 except:
1283 for dev_type, dev_conf in self.info.all_devices_sxpr():
1284 if ctr == 0:
1285 step = step - 1
1286 ctr = ctr - 1
1287 self._recoverMigrateDevice(dev_type, dev_conf, network,
1288 dst, step, domName)
1289 raise
1291 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1292 step, domName=''):
1293 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1294 network, dst, step, domName)
1296 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1297 dst, step, domName=''):
1298 return self.getDeviceController(deviceClass).recover_migrate(
1299 deviceConfig, network, dst, step, domName)
1302 ## private:
1304 def _constructDomain(self):
1305 """Construct the domain.
1307 @raise: VmError on error
1308 """
1310 log.debug('XendDomainInfo.constructDomain')
1312 self.shutdownStartTime = None
1314 image_cfg = self.info.get('image', {})
1315 hvm = image_cfg.has_key('hvm')
1317 if hvm:
1318 info = xc.xeninfo()
1319 if 'hvm' not in info['xen_caps']:
1320 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1321 "supported by your CPU and enabled in your "
1322 "BIOS?")
1324 self.domid = xc.domain_create(
1325 domid = 0,
1326 ssidref = security.get_security_info(self.info, 'ssidref'),
1327 handle = uuid.fromString(self.info['uuid']),
1328 hvm = int(hvm))
1330 if self.domid < 0:
1331 raise VmError('Creating domain failed: name=%s' %
1332 self.info['name_label'])
1334 self.dompath = GetDomainPath(self.domid)
1336 self._recreateDom()
1338 # Set maximum number of vcpus in domain
1339 xc.domain_max_vcpus(self.domid, int(self.info['vcpus_number']))
1342 def _introduceDomain(self):
1343 assert self.domid is not None
1344 assert self.store_mfn is not None
1345 assert self.store_port is not None
1347 try:
1348 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1349 except RuntimeError, exn:
1350 raise XendError(str(exn))
1353 def _initDomain(self):
1354 log.debug('XendDomainInfo.initDomain: %s %s',
1355 self.domid,
1356 self.info['cpu_weight'])
1358 self._configureBootloader()
1360 if not self._infoIsSet('image'):
1361 raise VmError('Missing image in configuration')
1363 try:
1364 self.image = image.create(self,
1365 self.info,
1366 self.info['image'],
1367 self.info['devices'])
1369 localtime = self.info.get('localtime', False)
1370 if localtime:
1371 xc.domain_set_time_offset(self.domid)
1373 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1375 # repin domain vcpus if a restricted cpus list is provided
1376 # this is done prior to memory allocation to aide in memory
1377 # distribution for NUMA systems.
1378 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1379 for v in range(0, self.info['max_vcpu_id']+1):
1380 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1382 # Use architecture- and image-specific calculations to determine
1383 # the various headrooms necessary, given the raw configured
1384 # values. maxmem, memory, and shadow are all in KiB.
1385 memory = self.image.getRequiredAvailableMemory(
1386 self.info['memory_static_min'] * 1024)
1387 maxmem = self.image.getRequiredAvailableMemory(
1388 self.info['memory_static_max'] * 1024)
1389 shadow = self.image.getRequiredShadowMemory(
1390 self.info['shadow_memory'] * 1024,
1391 self.info['memory_static_max'] * 1024)
1393 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1394 # takes MiB and we must not round down and end up under-providing.
1395 shadow = ((shadow + 1023) / 1024) * 1024
1397 # set memory limit
1398 xc.domain_setmaxmem(self.domid, maxmem)
1400 # Make sure there's enough RAM available for the domain
1401 balloon.free(memory + shadow)
1403 # Set up the shadow memory
1404 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1405 self.info['shadow_memory'] = shadow_cur
1407 self._createChannels()
1409 channel_details = self.image.createImage()
1411 self.store_mfn = channel_details['store_mfn']
1412 if 'console_mfn' in channel_details:
1413 self.console_mfn = channel_details['console_mfn']
1415 self._introduceDomain()
1417 self._createDevices()
1419 self.image.cleanupBootloading()
1421 self.info['start_time'] = time.time()
1423 self._stateSet(DOM_STATE_RUNNING)
1424 except RuntimeError, exn:
1425 log.exception("XendDomainInfo.initDomain: exception occurred")
1426 self.image.cleanupBootloading()
1427 raise VmError(str(exn))
1430 def cleanupDomain(self):
1431 """Cleanup domain resources; release devices. Idempotent. Nothrow
1432 guarantee."""
1434 self.refresh_shutdown_lock.acquire()
1435 try:
1436 self.unwatchShutdown()
1437 self._releaseDevices()
1439 if self.image:
1440 try:
1441 self.image.destroy()
1442 except:
1443 log.exception(
1444 "XendDomainInfo.cleanup: image.destroy() failed.")
1445 self.image = None
1447 try:
1448 self._removeDom()
1449 except:
1450 log.exception("Removing domain path failed.")
1452 self._stateSet(DOM_STATE_HALTED)
1453 finally:
1454 self.refresh_shutdown_lock.release()
1457 def unwatchShutdown(self):
1458 """Remove the watch on the domain's control/shutdown node, if any.
1459 Idempotent. Nothrow guarantee. Expects to be protected by the
1460 refresh_shutdown_lock."""
1462 try:
1463 try:
1464 if self.shutdownWatch:
1465 self.shutdownWatch.unwatch()
1466 finally:
1467 self.shutdownWatch = None
1468 except:
1469 log.exception("Unwatching control/shutdown failed.")
1471 def waitForShutdown(self):
1472 self.state_updated.acquire()
1473 try:
1474 while self.state in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1475 self.state_updated.wait()
1476 finally:
1477 self.state_updated.release()
1481 # TODO: recategorise - called from XendCheckpoint
1484 def completeRestore(self, store_mfn, console_mfn):
1486 log.debug("XendDomainInfo.completeRestore")
1488 self.store_mfn = store_mfn
1489 self.console_mfn = console_mfn
1491 self._introduceDomain()
1492 self._storeDomDetails()
1493 self._registerWatches()
1494 self.refreshShutdown()
1496 log.debug("XendDomainInfo.completeRestore done")
1499 def _endRestore(self):
1500 self.setResume(False)
1503 # VM Destroy
1506 def destroy(self):
1507 """Cleanup VM and destroy domain. Nothrow guarantee."""
1509 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1511 self._cleanupVm()
1512 if self.dompath is not None:
1513 self.destroyDomain()
1516 def destroyDomain(self):
1517 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1519 try:
1520 if self.domid is not None:
1521 xc.domain_destroy(self.domid)
1522 self.domid = None
1523 for state in DOM_STATES_OLD:
1524 self.info[state] = 0
1525 except:
1526 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1528 self.cleanupDomain()
1532 # Channels for xenstore and console
1535 def _createChannels(self):
1536 """Create the channels to the domain.
1537 """
1538 self.store_port = self._createChannel()
1539 self.console_port = self._createChannel()
1542 def _createChannel(self):
1543 """Create an event channel to the domain.
1544 """
1545 try:
1546 return xc.evtchn_alloc_unbound(domid=self.domid, remote_dom=0)
1547 except:
1548 log.exception("Exception in alloc_unbound(%d)", self.domid)
1549 raise
1552 # Bootloader configuration
1555 def _configureBootloader(self):
1556 """Run the bootloader if we're configured to do so."""
1558 blexec = self.info['PV_bootloader']
1559 bootloader_args = self.info['PV_bootloader_args']
1560 kernel = self.info['PV_kernel']
1561 ramdisk = self.info['PV_ramdisk']
1562 args = self.info['PV_args']
1563 boot = self.info['HVM_boot']
1565 if boot:
1566 # HVM booting.
1567 self.info['image']['type'] = 'hvm'
1568 if not 'devices' in self.info['image']:
1569 self.info['image']['devices'] = {}
1570 self.info['image']['devices']['boot'] = boot
1571 elif not blexec and kernel:
1572 # Boot from dom0. Nothing left to do -- the kernel and ramdisk
1573 # will be picked up by image.py.
1574 pass
1575 else:
1576 # Boot using bootloader
1577 if not blexec or blexec == 'pygrub':
1578 blexec = '/usr/bin/pygrub'
1580 blcfg = None
1581 for (devtype, devinfo) in self.info.all_devices_sxpr():
1582 if not devtype or not devinfo or devtype not in ('vbd', 'tap'):
1583 continue
1584 disk = None
1585 for param in devinfo:
1586 if param[0] == 'uname':
1587 disk = param[1]
1588 break
1590 if disk is None:
1591 continue
1592 fn = blkdev_uname_to_file(disk)
1593 mounted = devtype == 'tap' and not os.stat(fn).st_rdev
1594 if mounted:
1595 # This is a file, not a device. pygrub can cope with a
1596 # file if it's raw, but if it's QCOW or other such formats
1597 # used through blktap, then we need to mount it first.
1599 log.info("Mounting %s on %s." %
1600 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1602 vbd = {
1603 'mode': 'RO',
1604 'device': BOOTLOADER_LOOPBACK_DEVICE,
1607 from xen.xend import XendDomain
1608 dom0 = XendDomain.instance().privilegedDomain()
1609 dom0._waitForDeviceUUID(dom0.create_vbd_with_vdi(vbd, fn))
1610 fn = BOOTLOADER_LOOPBACK_DEVICE
1612 try:
1613 blcfg = bootloader(blexec, fn, True,
1614 bootloader_args, kernel, ramdisk, args)
1615 finally:
1616 if mounted:
1617 log.info("Unmounting %s from %s." %
1618 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1620 dom0.destroyDevice('tap', '/dev/xvdp')
1622 break
1624 if blcfg is None:
1625 msg = "Had a bootloader specified, but can't find disk"
1626 log.error(msg)
1627 raise VmError(msg)
1629 self.info.update_with_image_sxp(blcfg, True)
1633 # VM Functions
1636 def _readVMDetails(self, params):
1637 """Read the specified parameters from the store.
1638 """
1639 try:
1640 return self._gatherVm(*params)
1641 except ValueError:
1642 # One of the int/float entries in params has a corresponding store
1643 # entry that is invalid. We recover, because older versions of
1644 # Xend may have put the entry there (memory/target, for example),
1645 # but this is in general a bad situation to have reached.
1646 log.exception(
1647 "Store corrupted at %s! Domain %d's configuration may be "
1648 "affected.", self.vmpath, self.domid)
1649 return []
1651 def _cleanupVm(self):
1652 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1654 self._unwatchVm()
1656 try:
1657 self._removeVm()
1658 except:
1659 log.exception("Removing VM path failed.")
1662 def checkLiveMigrateMemory(self):
1663 """ Make sure there's enough memory to migrate this domain """
1664 overhead_kb = 0
1665 if arch.type == "x86":
1666 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1667 # the minimum that Xen would allocate if no value were given.
1668 overhead_kb = self.info['vcpus_number'] * 1024 + \
1669 self.info['memory_static_max'] * 4
1670 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1671 # The domain might already have some shadow memory
1672 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1673 if overhead_kb > 0:
1674 balloon.free(overhead_kb)
1676 def _unwatchVm(self):
1677 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1678 guarantee."""
1679 try:
1680 try:
1681 if self.vmWatch:
1682 self.vmWatch.unwatch()
1683 finally:
1684 self.vmWatch = None
1685 except:
1686 log.exception("Unwatching VM path failed.")
1688 def testDeviceComplete(self):
1689 """ For Block IO migration safety we must ensure that
1690 the device has shutdown correctly, i.e. all blocks are
1691 flushed to disk
1692 """
1693 start = time.time()
1694 while True:
1695 test = 0
1696 diff = time.time() - start
1697 for i in self.getDeviceController('vbd').deviceIDs():
1698 test = 1
1699 log.info("Dev %s still active, looping...", i)
1700 time.sleep(0.1)
1702 if test == 0:
1703 break
1704 if diff >= MIGRATE_TIMEOUT:
1705 log.info("Dev still active but hit max loop timeout")
1706 break
1708 def _storeVmDetails(self):
1709 to_store = {}
1711 for key in XendConfig.LEGACY_XENSTORE_VM_PARAMS:
1712 info_key = XendConfig.LEGACY_CFG_TO_XENAPI_CFG.get(key, key)
1713 if self._infoIsSet(info_key):
1714 to_store[key] = str(self.info[info_key])
1716 if self.info.get('image'):
1717 image_sxpr = self.info.image_sxpr()
1718 if image_sxpr:
1719 to_store['image'] = sxp.to_string(image_sxpr)
1721 if self._infoIsSet('security'):
1722 secinfo = self.info['security']
1723 to_store['security'] = sxp.to_string(secinfo)
1724 for idx in range(0, len(secinfo)):
1725 if secinfo[idx][0] == 'access_control':
1726 to_store['security/access_control'] = sxp.to_string(
1727 [secinfo[idx][1], secinfo[idx][2]])
1728 for aidx in range(1, len(secinfo[idx])):
1729 if secinfo[idx][aidx][0] == 'label':
1730 to_store['security/access_control/label'] = \
1731 secinfo[idx][aidx][1]
1732 if secinfo[idx][aidx][0] == 'policy':
1733 to_store['security/access_control/policy'] = \
1734 secinfo[idx][aidx][1]
1735 if secinfo[idx][0] == 'ssidref':
1736 to_store['security/ssidref'] = str(secinfo[idx][1])
1739 if not self._readVm('xend/restart_count'):
1740 to_store['xend/restart_count'] = str(0)
1742 log.debug("Storing VM details: %s", scrub_password(to_store))
1744 self._writeVm(to_store)
1745 self._setVmPermissions()
1748 def _setVmPermissions(self):
1749 """Allow the guest domain to read its UUID. We don't allow it to
1750 access any other entry, for security."""
1751 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1752 { 'dom' : self.domid,
1753 'read' : True,
1754 'write' : False })
1757 # Utility functions
1760 def _stateSet(self, state):
1761 self.state_updated.acquire()
1762 try:
1763 if self.state != state:
1764 self.state = state
1765 self.state_updated.notifyAll()
1766 finally:
1767 self.state_updated.release()
1769 def _infoIsSet(self, name):
1770 return name in self.info and self.info[name] is not None
1772 def _checkName(self, name):
1773 """Check if a vm name is valid. Valid names contain alphabetic
1774 characters, digits, or characters in '_-.:/+'.
1775 The same name cannot be used for more than one vm at the same time.
1777 @param name: name
1778 @raise: VmError if invalid
1779 """
1780 from xen.xend import XendDomain
1782 if name is None or name == '':
1783 raise VmError('Missing VM Name')
1785 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1786 raise VmError('Invalid VM Name')
1788 dom = XendDomain.instance().domain_lookup_nr(name)
1789 if dom and dom.info['uuid'] != self.info['uuid']:
1790 raise VmError("VM name '%s' already exists%s" %
1791 (name,
1792 dom.domid is not None and
1793 (" as domain %s" % str(dom.domid)) or ""))
1796 def update(self, info = None, refresh = True):
1797 """Update with info from xc.domain_getinfo().
1798 """
1799 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1800 str(self.domid))
1802 if not info:
1803 info = dom_get(self.domid)
1804 if not info:
1805 return
1807 #manually update ssidref / security fields
1808 if security.on() and info.has_key('ssidref'):
1809 if (info['ssidref'] != 0) and self.info.has_key('security'):
1810 security_field = self.info['security']
1811 if not security_field:
1812 #create new security element
1813 self.info.update({'security':
1814 [['ssidref', str(info['ssidref'])]]})
1816 #ssidref field not used any longer
1817 if 'ssidref' in info:
1818 info.pop('ssidref')
1820 # make sure state is reset for info
1821 # TODO: we should eventually get rid of old_dom_states
1823 self.info.update_config(info)
1825 if refresh:
1826 self.refreshShutdown(info)
1828 log.trace("XendDomainInfo.update done on domain %s: %s",
1829 str(self.domid), self.info)
1831 def sxpr(self, ignore_store = False, legacy_only = True):
1832 result = self.info.to_sxp(domain = self,
1833 ignore_devices = ignore_store,
1834 legacy_only = legacy_only)
1836 if not ignore_store and self.dompath:
1837 vnc_port = self.readDom('console/vnc-port')
1838 if vnc_port is not None:
1839 result.append(['device',
1840 ['console', ['vnc-port', str(vnc_port)]]])
1842 return result
1844 # Xen API
1845 # ----------------------------------------------------------------
1847 def get_uuid(self):
1848 dom_uuid = self.info.get('uuid')
1849 if not dom_uuid: # if it doesn't exist, make one up
1850 dom_uuid = uuid.createString()
1851 self.info['uuid'] = dom_uuid
1852 return dom_uuid
1854 def get_memory_static_max(self):
1855 return self.info.get('memory_static_max', 0)
1856 def get_memory_static_min(self):
1857 return self.info.get('memory_static_min', 0)
1858 def get_memory_dynamic_max(self):
1859 return self.info.get('memory_dynamic_max', 0)
1860 def get_memory_dynamic_min(self):
1861 return self.info.get('memory_dynamic_min', 0)
1863 def get_vcpus_policy(self):
1864 sched_id = xc.sched_id_get()
1865 if sched_id == xen.lowlevel.xc.XEN_SCHEDULER_SEDF:
1866 return 'sedf'
1867 elif sched_id == xen.lowlevel.xc.XEN_SCHEDULER_CREDIT:
1868 return 'credit'
1869 else:
1870 return 'unknown'
1871 def get_vcpus_params(self):
1872 return '' # TODO
1873 def get_power_state(self):
1874 return XEN_API_VM_POWER_STATE[self.state]
1875 def get_platform_std_vga(self):
1876 return self.info.get('platform_std_vga', False)
1877 def get_platform_serial(self):
1878 return self.info.get('platform_serial', '')
1879 def get_platform_localtime(self):
1880 return self.info.get('platform_localtime', False)
1881 def get_platform_clock_offset(self):
1882 return self.info.get('platform_clock_offset', False)
1883 def get_platform_enable_audio(self):
1884 return self.info.get('platform_enable_audio', False)
1885 def get_platform_keymap(self):
1886 return self.info.get('platform_keymap', '')
1887 def get_pci_bus(self):
1888 return '' # TODO
1889 def get_tools_version(self):
1890 return {} # TODO
1891 def get_other_config(self):
1892 return {} # TODO
1894 def get_on_shutdown(self):
1895 after_shutdown = self.info.get('action_after_shutdown')
1896 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
1897 return XEN_API_ON_NORMAL_EXIT[-1]
1898 return after_shutdown
1900 def get_on_reboot(self):
1901 after_reboot = self.info.get('action_after_reboot')
1902 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
1903 return XEN_API_ON_NORMAL_EXIT[-1]
1904 return after_reboot
1906 def get_on_suspend(self):
1907 # TODO: not supported
1908 after_suspend = self.info.get('action_after_suspend')
1909 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
1910 return XEN_API_ON_NORMAL_EXIT[-1]
1911 return after_suspend
1913 def get_on_crash(self):
1914 after_crash = self.info.get('action_after_crash')
1915 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
1916 return XEN_API_ON_CRASH_BEHAVIOUR[0]
1917 return after_crash
1919 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
1920 """ Get's a device configuration either from XendConfig or
1921 from the DevController.
1923 @param dev_class: device class, either, 'vbd' or 'vif'
1924 @param dev_uuid: device UUID
1926 @rtype: dictionary
1927 """
1928 dev_type_config = self.info['devices'].get(dev_uuid)
1930 # shortcut if the domain isn't started because
1931 # the devcontrollers will have no better information
1932 # than XendConfig.
1933 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
1934 if dev_type_config:
1935 return copy.deepcopy(dev_type_config[1])
1936 return None
1938 # instead of using dev_class, we use the dev_type
1939 # that is from XendConfig.
1940 # This will accomdate 'tap' as well as 'vbd'
1941 dev_type = dev_type_config[0]
1943 controller = self.getDeviceController(dev_type)
1944 if not controller:
1945 return None
1947 all_configs = controller.getAllDeviceConfigurations()
1948 if not all_configs:
1949 return None
1951 dev_config = copy.deepcopy(dev_type_config[1])
1952 for _devid, _devcfg in all_configs.items():
1953 if _devcfg.get('uuid') == dev_uuid:
1954 dev_config.update(_devcfg)
1955 dev_config['id'] = _devid
1956 return dev_config
1958 return dev_config
1960 def get_dev_xenapi_config(self, dev_class, dev_uuid):
1961 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
1962 if not config:
1963 return {}
1965 config['VM'] = self.get_uuid()
1967 if dev_class == 'vif':
1968 if not config.has_key('name'):
1969 config['name'] = config.get('vifname', '')
1970 if not config.has_key('MAC'):
1971 config['MAC'] = config.get('mac', '')
1972 if not config.has_key('type'):
1973 config['type'] = 'paravirtualised'
1974 if not config.has_key('device'):
1975 devid = config.get('id')
1976 if devid != None:
1977 config['device'] = 'eth%d' % devid
1978 else:
1979 config['device'] = ''
1981 if not config.has_key('network'):
1982 try:
1983 config['network'] = \
1984 XendNode.instance().bridge_to_network(
1985 config.get('bridge')).uuid
1986 except Exception:
1987 log.exception('bridge_to_network')
1988 # Ignore this for now -- it may happen if the device
1989 # has been specified using the legacy methods, but at
1990 # some point we're going to have to figure out how to
1991 # handle that properly.
1993 config['MTU'] = 1500 # TODO
1994 config['io_read_kbs'] = 0.0
1995 config['io_write_kbs'] = 0.0
1997 if dev_class == 'vbd':
1998 config['VDI'] = config.get('VDI', '')
1999 config['device'] = config.get('dev', '')
2000 config['driver'] = 'paravirtualised' # TODO
2001 config['image'] = config.get('uname', '')
2002 config['io_read_kbs'] = 0.0
2003 config['io_write_kbs'] = 0.0
2004 if config.get('mode', 'r') == 'r':
2005 config['mode'] = 'RO'
2006 else:
2007 config['mode'] = 'RW'
2009 if dev_class == 'vtpm':
2010 config['driver'] = 'paravirtualised' # TODO
2012 return config
2014 def get_dev_property(self, dev_class, dev_uuid, field):
2015 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
2016 try:
2017 return config[field]
2018 except KeyError:
2019 raise XendError('Invalid property for device: %s' % field)
2021 def get_vcpus_util(self):
2022 # TODO: this returns the total accum cpu time, rather than util
2023 # TODO: spec says that key is int, however, python does not allow
2024 # non-string keys to dictionaries.
2025 vcpu_util = {}
2026 if 'max_vcpu_id' in self.info and self.domid != None:
2027 for i in range(0, self.info['max_vcpu_id']+1):
2028 info = xc.vcpu_getinfo(self.domid, i)
2029 vcpu_util[str(i)] = info['cpu_time']/1000000000.0
2031 return vcpu_util
2033 def get_consoles(self):
2034 return self.info.get('console_refs', [])
2036 def get_vifs(self):
2037 return self.info.get('vif_refs', [])
2039 def get_vbds(self):
2040 return self.info.get('vbd_refs', [])
2042 def get_vtpms(self):
2043 return self.info.get('vtpm_refs', [])
2045 def create_vbd(self, xenapi_vbd):
2046 """Create a VBD device from the passed struct in Xen API format.
2048 @return: uuid of the device
2049 @rtype: string
2050 """
2052 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
2053 if not dev_uuid:
2054 raise XendError('Failed to create device')
2056 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2057 _, config = self.info['devices'][dev_uuid]
2058 config['devid'] = self.getDeviceController('vbd').createDevice(config)
2060 return dev_uuid
2062 def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
2063 """Create a VBD using a VDI from XendStorageRepository.
2065 @param xenapi_vbd: vbd struct from the Xen API
2066 @param vdi_image_path: VDI UUID
2067 @rtype: string
2068 @return: uuid of the device
2069 """
2070 xenapi_vbd['image'] = vdi_image_path
2071 log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
2072 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
2073 if not dev_uuid:
2074 raise XendError('Failed to create device')
2076 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2077 _, config = self.info['devices'][dev_uuid]
2078 config['devid'] = self.getDeviceController('tap').createDevice(config)
2080 return dev_uuid
2082 def create_vif(self, xenapi_vif):
2083 """Create VIF device from the passed struct in Xen API format.
2085 @param xenapi_vif: Xen API VIF Struct.
2086 @rtype: string
2087 @return: UUID
2088 """
2089 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
2090 if not dev_uuid:
2091 raise XendError('Failed to create device')
2093 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2094 _, config = self.info['devices'][dev_uuid]
2095 config['devid'] = self.getDeviceController('vif').createDevice(config)
2097 return dev_uuid
2099 def create_vtpm(self, xenapi_vtpm):
2100 """Create a VTPM device from the passed struct in Xen API format.
2102 @return: uuid of the device
2103 @rtype: string
2104 """
2106 if self.state not in (DOM_STATE_HALTED,):
2107 raise VmError("Can only add vTPM to a halted domain.")
2108 if self.get_vtpms() != []:
2109 raise VmError('Domain already has a vTPM.')
2110 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
2111 if not dev_uuid:
2112 raise XendError('Failed to create device')
2114 return dev_uuid
2116 def has_device(self, dev_class, dev_uuid):
2117 return (dev_uuid in self.info['%s_refs' % dev_class.lower()])
2119 def __str__(self):
2120 return '<domain id=%s name=%s memory=%s state=%s>' % \
2121 (str(self.domid), self.info['name_label'],
2122 str(self.info['memory_static_min']), DOM_STATES[self.state])
2124 __repr__ = __str__