ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 12740:dfaf8493a211

Merge.
author Steven Smith <ssmith@xensource.com>
date Fri Dec 01 13:12:41 2006 +0000 (2006-12-01)
parents 056050ceb300 000609d8c93f
children 4ede29160b53
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.XendBootloader import bootloader
44 from xen.xend.XendError import XendError, VmError
45 from xen.xend.XendDevices import XendDevices
46 from xen.xend.xenstore.xstransact import xstransact, complete
47 from xen.xend.xenstore.xsutil import GetDomainPath, IntroduceDomain
48 from xen.xend.xenstore.xswatch import xswatch
49 from xen.xend.XendConstants import *
50 from xen.xend.XendAPIConstants import *
52 MIGRATE_TIMEOUT = 30.0
54 xc = xen.lowlevel.xc.xc()
55 xroot = XendRoot.instance()
57 log = logging.getLogger("xend.XendDomainInfo")
58 #log.setLevel(logging.TRACE)
61 def bool0(v):
62 v != "0" and bool(v)
65 ##
66 # All parameters of VMs that may be configured on-the-fly, or at start-up.
67 #
68 VM_CONFIG_PARAMS = [
69 ('name', str),
70 ('on_poweroff', str),
71 ('on_reboot', str),
72 ('on_crash', str),
73 ]
76 ##
77 # Configuration entries that we expect to round-trip -- be read from the
78 # config file or xc, written to save-files (i.e. through sxpr), and reused as
79 # config on restart or restore, all without munging. Some configuration
80 # entries are munged for backwards compatibility reasons, or because they
81 # don't come out of xc in the same form as they are specified in the config
82 # file, so those are handled separately.
83 ROUNDTRIPPING_CONFIG_ENTRIES = [
84 ('uuid', str),
85 ('vcpus', int),
86 ('vcpu_avail', int),
87 ('cpu_cap', int),
88 ('cpu_weight', int),
89 ('memory', int),
90 ('shadow_memory', int),
91 ('maxmem', int),
92 ('bootloader', str),
93 ('bootloader_args', str),
94 ('features', str),
95 ('localtime', bool0),
96 ]
98 ROUNDTRIPPING_CONFIG_ENTRIES += VM_CONFIG_PARAMS
101 ##
102 # All entries written to the store. This is VM_CONFIG_PARAMS, plus those
103 # entries written to the store that cannot be reconfigured on-the-fly.
104 #
105 VM_STORE_ENTRIES = [
106 ('uuid', str),
107 ('vcpus', int),
108 ('vcpu_avail', int),
109 ('memory', int),
110 ('shadow_memory', int),
111 ('maxmem', int),
112 ('start_time', float),
113 ('on_xend_start', str),
114 ('on_xend_stop', str),
115 ]
117 VM_STORE_ENTRIES += VM_CONFIG_PARAMS
120 #
121 # There are a number of CPU-related fields:
122 #
123 # vcpus: the number of virtual CPUs this domain is configured to use.
124 # vcpu_avail: a bitmap telling the guest domain whether it may use each of
125 # its VCPUs. This is translated to
126 # <dompath>/cpu/<id>/availability = {online,offline} for use
127 # by the guest domain.
128 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
129 # CPUs that that VCPU may use.
130 # cpu: a configuration setting requesting that VCPU 0 is pinned to
131 # the specified physical CPU.
132 #
133 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
134 # across save, restore, migrate, and restart). The other settings are only
135 # specific to the domain, so are lost when the VM moves.
136 #
139 def create(config):
140 """Creates and start a VM using the supplied configuration.
141 (called from XMLRPCServer directly)
143 @param config: A configuration object involving lists of tuples.
144 @type config: list of lists, eg ['vm', ['image', 'xen.gz']]
146 @rtype: XendDomainInfo
147 @return: A up and running XendDomainInfo instance
148 @raise VmError: Invalid configuration or failure to start.
149 """
151 log.debug("XendDomainInfo.create(%s)", config)
152 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config))
153 try:
154 vm.start()
155 except:
156 log.exception('Domain construction failed')
157 vm.destroy()
158 raise
160 return vm
162 def recreate(info, priv):
163 """Create the VM object for an existing domain. The domain must not
164 be dying, as the paths in the store should already have been removed,
165 and asking us to recreate them causes problems.
167 @param xeninfo: Parsed configuration
168 @type xeninfo: Dictionary
169 @param priv: TODO, unknown, something to do with memory
170 @type priv: bool
172 @rtype: XendDomainInfo
173 @return: A up and running XendDomainInfo instance
174 @raise VmError: Invalid configuration.
175 @raise XendError: Errors with configuration.
176 """
178 log.debug("XendDomainInfo.recreate(%s)", info)
180 assert not info['dying']
182 xeninfo = XendConfig.XendConfig(dominfo = info)
183 domid = xeninfo['domid']
184 uuid1 = uuid.fromString(xeninfo['uuid'])
185 needs_reinitialising = False
187 dompath = GetDomainPath(domid)
188 if not dompath:
189 raise XendError('No domain path in store for existing '
190 'domain %d' % domid)
192 log.info("Recreating domain %d, UUID %s. at %s" %
193 (domid, xeninfo['uuid'], dompath))
195 # need to verify the path and uuid if not Domain-0
196 # if the required uuid and vm aren't set, then that means
197 # we need to recreate the dom with our own values
198 #
199 # NOTE: this is probably not desirable, really we should just
200 # abort or ignore, but there may be cases where xenstore's
201 # entry disappears (eg. xenstore-rm /)
202 #
203 try:
204 vmpath = xstransact.Read(dompath, "vm")
205 if not vmpath:
206 log.warn('/local/domain/%d/vm is missing. recreate is '
207 'confused, trying our best to recover' % domid)
208 needs_reinitialising = True
209 raise XendError('reinit')
211 uuid2_str = xstransact.Read(vmpath, "uuid")
212 if not uuid2_str:
213 log.warn('%s/uuid/ is missing. recreate is confused, '
214 'trying our best to recover' % vmpath)
215 needs_reinitialising = True
216 raise XendError('reinit')
218 uuid2 = uuid.fromString(uuid2_str)
219 if uuid1 != uuid2:
220 log.warn('UUID in /vm does not match the UUID in /dom/%d.'
221 'Trying out best to recover' % domid)
222 needs_reinitialising = True
223 except XendError:
224 pass # our best shot at 'goto' in python :)
226 vm = XendDomainInfo(xeninfo, domid, dompath, augment = True, priv = priv)
228 if needs_reinitialising:
229 vm._recreateDom()
230 vm._removeVm()
231 vm._storeVmDetails()
232 vm._storeDomDetails()
234 if vm.info['image']: # Only dom0 should be without an image entry when
235 # recreating, but we cope with missing ones
236 # elsewhere just in case.
237 vm.image = image.create(vm,
238 vm.info,
239 vm.info['image'],
240 vm.info['devices'])
241 vm.image.recreate()
243 vm._registerWatches()
244 vm.refreshShutdown(xeninfo)
245 return vm
248 def restore(config):
249 """Create a domain and a VM object to do a restore.
251 @param config: Domain SXP configuration
252 @type config: list of lists. (see C{create})
254 @rtype: XendDomainInfo
255 @return: A up and running XendDomainInfo instance
256 @raise VmError: Invalid configuration or failure to start.
257 @raise XendError: Errors with configuration.
258 """
260 log.debug("XendDomainInfo.restore(%s)", config)
261 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config),
262 resume = True)
263 try:
264 vm.resume()
265 return vm
266 except:
267 vm.destroy()
268 raise
270 def createDormant(domconfig):
271 """Create a dormant/inactive XenDomainInfo without creating VM.
272 This is for creating instances of persistent domains that are not
273 yet start.
275 @param domconfig: Parsed configuration
276 @type domconfig: XendConfig object
278 @rtype: XendDomainInfo
279 @return: A up and running XendDomainInfo instance
280 @raise XendError: Errors with configuration.
281 """
283 log.debug("XendDomainInfo.createDormant(%s)", domconfig)
285 # domid does not make sense for non-running domains.
286 domconfig.pop('domid', None)
287 vm = XendDomainInfo(domconfig)
288 return vm
290 def domain_by_name(name):
291 """Get domain by name
293 @params name: Name of the domain
294 @type name: string
295 @return: XendDomainInfo or None
296 """
297 from xen.xend import XendDomain
298 return XendDomain.instance().domain_lookup_by_name_nr(name)
301 def shutdown_reason(code):
302 """Get a shutdown reason from a code.
304 @param code: shutdown code
305 @type code: int
306 @return: shutdown reason
307 @rtype: string
308 """
309 return DOMAIN_SHUTDOWN_REASONS.get(code, "?")
311 def dom_get(dom):
312 """Get info from xen for an existing domain.
314 @param dom: domain id
315 @type dom: int
316 @return: info or None
317 @rtype: dictionary
318 """
319 try:
320 domlist = xc.domain_getinfo(dom, 1)
321 if domlist and dom == domlist[0]['domid']:
322 return domlist[0]
323 except Exception, err:
324 # ignore missing domain
325 log.trace("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
326 return None
329 class XendDomainInfo:
330 """An object represents a domain.
332 @TODO: try to unify dom and domid, they mean the same thing, but
333 xc refers to it as dom, and everywhere else, including
334 xenstore it is domid. The best way is to change xc's
335 python interface.
337 @ivar info: Parsed configuration
338 @type info: dictionary
339 @ivar domid: Domain ID (if VM has started)
340 @type domid: int or None
341 @ivar vmpath: XenStore path to this VM.
342 @type vmpath: string
343 @ivar dompath: XenStore path to this Domain.
344 @type dompath: string
345 @ivar image: Reference to the VM Image.
346 @type image: xen.xend.image.ImageHandler
347 @ivar store_port: event channel to xenstored
348 @type store_port: int
349 @ivar console_port: event channel to xenconsoled
350 @type console_port: int
351 @ivar store_mfn: xenstored mfn
352 @type store_mfn: int
353 @ivar console_mfn: xenconsoled mfn
354 @type console_mfn: int
355 @ivar vmWatch: reference to a watch on the xenstored vmpath
356 @type vmWatch: xen.xend.xenstore.xswatch
357 @ivar shutdownWatch: reference to watch on the xenstored domain shutdown
358 @type shutdownWatch: xen.xend.xenstore.xswatch
359 @ivar shutdownStartTime: UNIX Time when domain started shutting down.
360 @type shutdownStartTime: float or None
361 @ivar state: Domain state
362 @type state: enum(DOM_STATE_HALTED, DOM_STATE_RUNNING, ...)
363 @ivar state_updated: lock for self.state
364 @type state_updated: threading.Condition
365 @ivar refresh_shutdown_lock: lock for polling shutdown state
366 @type refresh_shutdown_lock: threading.Condition
367 @ivar _deviceControllers: device controller cache for this domain
368 @type _deviceControllers: dict 'string' to DevControllers
369 """
371 def __init__(self, info, domid = None, dompath = None, augment = False,
372 priv = False, resume = False):
373 """Constructor for a domain
375 @param info: parsed configuration
376 @type info: dictionary
377 @keyword domid: Set initial domain id (if any)
378 @type domid: int
379 @keyword dompath: Set initial dompath (if any)
380 @type dompath: string
381 @keyword augment: Augment given info with xenstored VM info
382 @type augment: bool
383 @keyword priv: Is a privledged domain (Dom 0) (TODO: really?)
384 @type priv: bool
385 @keyword resume: Is this domain being resumed?
386 @type resume: bool
387 """
389 self.info = info
390 if domid == None:
391 self.domid = self.info.get('domid')
392 else:
393 self.domid = domid
395 #REMOVE: uuid is now generated in XendConfig
396 #if not self._infoIsSet('uuid'):
397 # self.info['uuid'] = uuid.toString(uuid.create())
399 self.vmpath = XS_VMROOT + self.info['uuid']
400 self.dompath = dompath
402 self.image = None
403 self.store_port = None
404 self.store_mfn = None
405 self.console_port = None
406 self.console_mfn = None
408 self.vmWatch = None
409 self.shutdownWatch = None
410 self.shutdownStartTime = None
411 self._resume = resume
413 self.state = DOM_STATE_HALTED
414 self.state_updated = threading.Condition()
415 self.refresh_shutdown_lock = threading.Condition()
417 self._deviceControllers = {}
419 for state in DOM_STATES_OLD:
420 self.info[state] = 0
422 if augment:
423 self._augmentInfo(priv)
425 self._checkName(self.info['name_label'])
428 #
429 # Public functions available through XMLRPC
430 #
433 def start(self, is_managed = False):
434 """Attempts to start the VM by do the appropriate
435 initialisation if it not started.
436 """
437 from xen.xend import XendDomain
439 if self.state == DOM_STATE_HALTED:
440 try:
441 self._constructDomain()
442 self._initDomain()
443 self._storeVmDetails()
444 self._storeDomDetails()
445 self._registerWatches()
446 self.refreshShutdown()
448 # save running configuration if XendDomains believe domain is
449 # persistent
450 if is_managed:
451 xendomains = XendDomain.instance()
452 xendomains.managed_config_save(self)
453 except:
454 log.exception('VM start failed')
455 self.destroy()
456 raise
457 else:
458 raise XendError('VM already running')
460 def resume(self):
461 """Resumes a domain that has come back from suspension."""
462 if self.state in (DOM_STATE_HALTED, DOM_STATE_SUSPENDED):
463 try:
464 self._constructDomain()
465 self._storeVmDetails()
466 self._createDevices()
467 self._createChannels()
468 self._storeDomDetails()
469 self._endRestore()
470 except:
471 log.exception('VM resume failed')
472 raise
473 else:
474 raise XendError('VM already running')
476 def shutdown(self, reason):
477 """Shutdown a domain by signalling this via xenstored."""
478 log.debug('XendDomainInfo.shutdown')
479 if self.state in (DOM_STATE_SHUTDOWN, DOM_STATE_HALTED,):
480 raise XendError('Domain cannot be shutdown')
482 if self.domid == 0:
483 raise XendError('Domain 0 cannot be shutdown')
485 if reason not in DOMAIN_SHUTDOWN_REASONS.values():
486 raise XendError('Invalid reason: %s' % reason)
487 self._removeVm('xend/previous_restart_time')
488 self.storeDom("control/shutdown", reason)
490 def pause(self):
491 """Pause domain
493 @raise XendError: Failed pausing a domain
494 """
495 try:
496 xc.domain_pause(self.domid)
497 self._stateSet(DOM_STATE_PAUSED)
498 except Exception, ex:
499 raise XendError("Domain unable to be paused: %s" % str(ex))
501 def unpause(self):
502 """Unpause domain
504 @raise XendError: Failed unpausing a domain
505 """
506 try:
507 xc.domain_unpause(self.domid)
508 self._stateSet(DOM_STATE_RUNNING)
509 except Exception, ex:
510 raise XendError("Domain unable to be unpaused: %s" % str(ex))
512 def send_sysrq(self, key):
513 """ Send a Sysrq equivalent key via xenstored."""
514 asserts.isCharConvertible(key)
515 self.storeDom("control/sysrq", '%c' % key)
517 def device_create(self, dev_config):
518 """Create a new device.
520 @param dev_config: device configuration
521 @type dev_config: SXP object (parsed config)
522 """
523 log.debug("XendDomainInfo.device_create: %s" % dev_config)
524 dev_type = sxp.name(dev_config)
525 dev_uuid = self.info.device_add(dev_type, cfg_sxp = dev_config)
526 dev_config_dict = self.info['devices'][dev_uuid][1]
527 log.debug("XendDomainInfo.device_create: %s" % dev_config_dict)
528 devid = self._createDevice(dev_type, dev_config_dict)
529 self._waitForDevice(dev_type, devid)
530 return self.getDeviceController(dev_type).sxpr(devid)
532 def device_configure(self, dev_config, devid = None):
533 """Configure an existing device.
535 @param dev_config: device configuration
536 @type dev_config: SXP object (parsed config)
537 @param devid: device id
538 @type devid: int
539 @return: Returns True if successfully updated device
540 @rtype: boolean
541 """
542 deviceClass = sxp.name(dev_config)
544 # look up uuid of the device
545 dev_control = self.getDeviceController(deviceClass)
546 dev_sxpr = dev_control.sxpr(devid)
547 dev_uuid = sxp.child_value(sxpr, 'uuid')
548 if not dev_uuid:
549 return False
551 self.info.device_update(dev_uuid, dev_config)
552 dev_config_dict = self.info['devices'].get(dev_uuid)
553 if dev_config_dict:
554 dev_control.reconfigureDevice(devid, dev_config_dict[1])
555 return True
557 def waitForDevices(self):
558 """Wait for this domain's configured devices to connect.
560 @raise VmError: if any device fails to initialise.
561 """
562 for devclass in XendDevices.valid_devices():
563 self.getDeviceController(devclass).waitForDevices()
565 def destroyDevice(self, deviceClass, devid):
566 try:
567 devid = int(devid)
568 except ValueError:
569 # devid is not a number, let's search for it in xenstore.
570 devicePath = '%s/device/%s' % (self.dompath, deviceClass)
571 for entry in xstransact.List(devicePath):
572 backend = xstransact.Read('%s/%s' % (devicePath, entry),
573 "backend")
574 devName = xstransact.Read(backend, "dev")
575 if devName == devid:
576 # We found the integer matching our devid, use it instead
577 devid = entry
578 break
580 return self.getDeviceController(deviceClass).destroyDevice(devid)
583 def getDeviceSxprs(self, deviceClass):
584 return self.getDeviceController(deviceClass).sxprs()
587 def setMemoryTarget(self, target):
588 """Set the memory target of this domain.
589 @param target: In MiB.
590 """
591 log.debug("Setting memory target of domain %s (%d) to %d MiB.",
592 self.info['name_label'], self.domid, target)
594 if target <= 0:
595 raise XendError('Invalid memory size')
597 self.info['memory_static_min'] = target
598 self.storeVm("memory", target)
599 self.storeDom("memory/target", target << 10)
601 def getVCPUInfo(self):
602 try:
603 # We include the domain name and ID, to help xm.
604 sxpr = ['domain',
605 ['domid', self.domid],
606 ['name', self.info['name_label']],
607 ['vcpu_count', self.info['vcpus_number']]]
609 for i in range(0, self.info['max_vcpu_id']+1):
610 info = xc.vcpu_getinfo(self.domid, i)
612 sxpr.append(['vcpu',
613 ['number', i],
614 ['online', info['online']],
615 ['blocked', info['blocked']],
616 ['running', info['running']],
617 ['cpu_time', info['cpu_time'] / 1e9],
618 ['cpu', info['cpu']],
619 ['cpumap', info['cpumap']]])
621 return sxpr
623 except RuntimeError, exn:
624 raise XendError(str(exn))
626 #
627 # internal functions ... TODO: re-categorised
628 #
630 def _augmentInfo(self, priv):
631 """Augment self.info, as given to us through L{recreate}, with
632 values taken from the store. This recovers those values known
633 to xend but not to the hypervisor.
634 """
635 augment_entries = XendConfig.LEGACY_XENSTORE_VM_PARAMS[:]
636 if priv:
637 augment_entries.remove('memory')
638 augment_entries.remove('maxmem')
640 vm_config = self._readVMDetails([(k, XendConfig.LEGACY_CFG_TYPES[k])
641 for k in augment_entries])
643 # make returned lists into a dictionary
644 vm_config = dict(zip(augment_entries, vm_config))
646 for arg in augment_entries:
647 xapicfg = arg
648 val = vm_config[arg]
649 if val != None:
650 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
651 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
652 self.info[xapiarg] = val
653 else:
654 self.info[arg] = val
656 # read image value
657 image_sxp = self._readVm('image')
658 if image_sxp:
659 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
661 # read devices
662 devices = []
663 for devclass in XendDevices.valid_devices():
664 devconfig = self.getDeviceController(devclass).configurations()
665 if devconfig:
666 devices.extend(devconfig)
668 if not self.info['devices'] and devices is not None:
669 for device in devices:
670 self.info.device_add(device[0], cfg_sxp = device)
672 #
673 # Function to update xenstore /vm/*
674 #
676 def _readVm(self, *args):
677 return xstransact.Read(self.vmpath, *args)
679 def _writeVm(self, *args):
680 return xstransact.Write(self.vmpath, *args)
682 def _removeVm(self, *args):
683 return xstransact.Remove(self.vmpath, *args)
685 def _gatherVm(self, *args):
686 return xstransact.Gather(self.vmpath, *args)
688 def storeVm(self, *args):
689 return xstransact.Store(self.vmpath, *args)
691 #
692 # Function to update xenstore /dom/*
693 #
695 def readDom(self, *args):
696 return xstransact.Read(self.dompath, *args)
698 def gatherDom(self, *args):
699 return xstransact.Gather(self.dompath, *args)
701 def _writeDom(self, *args):
702 return xstransact.Write(self.dompath, *args)
704 def _removeDom(self, *args):
705 return xstransact.Remove(self.dompath, *args)
707 def storeDom(self, *args):
708 return xstransact.Store(self.dompath, *args)
710 def _recreateDom(self):
711 complete(self.dompath, lambda t: self._recreateDomFunc(t))
713 def _recreateDomFunc(self, t):
714 t.remove()
715 t.mkdir()
716 t.set_permissions({'dom' : self.domid})
717 t.write('vm', self.vmpath)
719 def _storeDomDetails(self):
720 to_store = {
721 'domid': str(self.domid),
722 'vm': self.vmpath,
723 'name': self.info['name_label'],
724 'console/limit': str(xroot.get_console_limit() * 1024),
725 'memory/target': str(self.info['memory_static_min'] * 1024)
726 }
728 def f(n, v):
729 if v is not None:
730 to_store[n] = str(v)
732 f('console/port', self.console_port)
733 f('console/ring-ref', self.console_mfn)
734 f('store/port', self.store_port)
735 f('store/ring-ref', self.store_mfn)
737 to_store.update(self._vcpuDomDetails())
739 log.debug("Storing domain details: %s", to_store)
741 self._writeDom(to_store)
743 def _vcpuDomDetails(self):
744 def availability(n):
745 if self.info['vcpu_avail'] & (1 << n):
746 return 'online'
747 else:
748 return 'offline'
750 result = {}
751 for v in range(0, self.info['vcpus_number']):
752 result["cpu/%d/availability" % v] = availability(v)
753 return result
755 #
756 # xenstore watches
757 #
759 def _registerWatches(self):
760 """Register a watch on this VM's entries in the store, and the
761 domain's control/shutdown node, so that when they are changed
762 externally, we keep up to date. This should only be called by {@link
763 #create}, {@link #recreate}, or {@link #restore}, once the domain's
764 details have been written, but before the new instance is returned."""
765 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
766 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
767 self._handleShutdownWatch)
769 def _storeChanged(self, _):
770 log.trace("XendDomainInfo.storeChanged");
772 changed = False
774 # Check whether values in the configuration have
775 # changed in Xenstore.
777 cfg_vm = ['name', 'on_poweroff', 'on_reboot', 'on_crash']
779 vm_details = self._readVMDetails([(k,XendConfig.LEGACY_CFG_TYPES[k])
780 for k in cfg_vm])
782 # convert two lists into a python dictionary
783 vm_details = dict(zip(cfg_vm, vm_details))
785 for arg, val in vm_details.items():
786 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
787 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
788 if val != None and val != self.info[xapiarg]:
789 self.info[xapiarg] = val
790 changed= True
792 # Check whether image definition has been updated
793 image_sxp = self._readVm('image')
794 if image_sxp and image_sxp != self.info.image_sxpr():
795 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
796 changed = True
798 if changed:
799 # Update the domain section of the store, as this contains some
800 # parameters derived from the VM configuration.
801 self._storeDomDetails()
803 return 1
805 def _handleShutdownWatch(self, _):
806 log.debug('XendDomainInfo.handleShutdownWatch')
808 reason = self.readDom('control/shutdown')
810 if reason and reason != 'suspend':
811 sst = self.readDom('xend/shutdown_start_time')
812 now = time.time()
813 if sst:
814 self.shutdownStartTime = float(sst)
815 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
816 else:
817 self.shutdownStartTime = now
818 self.storeDom('xend/shutdown_start_time', now)
819 timeout = SHUTDOWN_TIMEOUT
821 log.trace(
822 "Scheduling refreshShutdown on domain %d in %ds.",
823 self.domid, timeout)
824 threading.Timer(timeout, self.refreshShutdown).start()
826 return True
829 #
830 # Public Attributes for the VM
831 #
834 def getDomid(self):
835 return self.domid
837 def setName(self, name):
838 self._checkName(name)
839 self.info['name_label'] = name
840 self.storeVm("name", name)
842 def getName(self):
843 return self.info['name_label']
845 def getDomainPath(self):
846 return self.dompath
848 def getShutdownReason(self):
849 return self.readDom('control/shutdown')
851 def getStorePort(self):
852 """For use only by image.py and XendCheckpoint.py."""
853 return self.store_port
855 def getConsolePort(self):
856 """For use only by image.py and XendCheckpoint.py"""
857 return self.console_port
859 def getFeatures(self):
860 """For use only by image.py."""
861 return self.info['features']
863 def getVCpuCount(self):
864 return self.info['vcpus_number']
866 def setVCpuCount(self, vcpus):
867 self.info['vcpu_avail'] = (1 << vcpus) - 1
868 self.info['vcpus_number'] = vcpus
869 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
870 self.storeVm('vcpus', self.info['vcpus_number'])
871 self._writeDom(self._vcpuDomDetails())
873 def getLabel(self):
874 return security.get_security_info(self.info, 'label')
876 def getMemoryTarget(self):
877 """Get this domain's target memory size, in KB."""
878 return self.info['memory_static_min'] * 1024
880 def getResume(self):
881 return str(self._resume)
883 def getCap(self):
884 return self.info.get('cpu_cap', 0)
886 def getWeight(self):
887 return self.info['cpu_weight']
889 def setResume(self, state):
890 self._resume = state
892 def getRestartCount(self):
893 return self._readVm('xend/restart_count')
895 def refreshShutdown(self, xeninfo = None):
896 """ Checks the domain for whether a shutdown is required.
898 Called from XendDomainInfo and also image.py for HVM images.
899 """
901 # If set at the end of this method, a restart is required, with the
902 # given reason. This restart has to be done out of the scope of
903 # refresh_shutdown_lock.
904 restart_reason = None
906 self.refresh_shutdown_lock.acquire()
907 try:
908 if xeninfo is None:
909 xeninfo = dom_get(self.domid)
910 if xeninfo is None:
911 # The domain no longer exists. This will occur if we have
912 # scheduled a timer to check for shutdown timeouts and the
913 # shutdown succeeded. It will also occur if someone
914 # destroys a domain beneath us. We clean up the domain,
915 # just in case, but we can't clean up the VM, because that
916 # VM may have migrated to a different domain on this
917 # machine.
918 self.cleanupDomain()
919 self._stateSet(DOM_STATE_HALTED)
920 return
922 if xeninfo['dying']:
923 # Dying means that a domain has been destroyed, but has not
924 # yet been cleaned up by Xen. This state could persist
925 # indefinitely if, for example, another domain has some of its
926 # pages mapped. We might like to diagnose this problem in the
927 # future, but for now all we do is make sure that it's not us
928 # holding the pages, by calling cleanupDomain. We can't
929 # clean up the VM, as above.
930 self.cleanupDomain()
931 self._stateSet(DOM_STATE_SHUTDOWN)
932 return
934 elif xeninfo['crashed']:
935 if self.readDom('xend/shutdown_completed'):
936 # We've seen this shutdown already, but we are preserving
937 # the domain for debugging. Leave it alone.
938 return
940 log.warn('Domain has crashed: name=%s id=%d.',
941 self.info['name_label'], self.domid)
943 if xroot.get_enable_dump():
944 self.dumpCore()
946 restart_reason = 'crash'
947 self._stateSet(DOM_STATE_HALTED)
949 elif xeninfo['shutdown']:
950 self._stateSet(DOM_STATE_SHUTDOWN)
951 if self.readDom('xend/shutdown_completed'):
952 # We've seen this shutdown already, but we are preserving
953 # the domain for debugging. Leave it alone.
954 return
956 else:
957 reason = shutdown_reason(xeninfo['shutdown_reason'])
959 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
960 self.info['name_label'], self.domid, reason)
962 self._clearRestart()
964 if reason == 'suspend':
965 self._stateSet(DOM_STATE_SUSPENDED)
966 # Don't destroy the domain. XendCheckpoint will do
967 # this once it has finished. However, stop watching
968 # the VM path now, otherwise we will end up with one
969 # watch for the old domain, and one for the new.
970 self._unwatchVm()
971 elif reason in ('poweroff', 'reboot'):
972 restart_reason = reason
973 else:
974 self.destroy()
976 elif self.dompath is None:
977 # We have yet to manage to call introduceDomain on this
978 # domain. This can happen if a restore is in progress, or has
979 # failed. Ignore this domain.
980 pass
981 else:
982 # Domain is alive. If we are shutting it down, then check
983 # the timeout on that, and destroy it if necessary.
984 if xeninfo['paused']:
985 self._stateSet(DOM_STATE_PAUSED)
986 else:
987 self._stateSet(DOM_STATE_RUNNING)
989 if self.shutdownStartTime:
990 timeout = (SHUTDOWN_TIMEOUT - time.time() +
991 self.shutdownStartTime)
992 if timeout < 0:
993 log.info(
994 "Domain shutdown timeout expired: name=%s id=%s",
995 self.info['name_label'], self.domid)
996 self.destroy()
997 finally:
998 self.refresh_shutdown_lock.release()
1000 if restart_reason:
1001 self._maybeRestart(restart_reason)
1005 # Restart functions - handling whether we come back up on shutdown.
1008 def _clearRestart(self):
1009 self._removeDom("xend/shutdown_start_time")
1012 def _maybeRestart(self, reason):
1013 # Dispatch to the correct method based upon the configured on_{reason}
1014 # behaviour.
1015 actions = {"destroy" : self.destroy,
1016 "restart" : self._restart,
1017 "preserve" : self._preserve,
1018 "rename-restart" : self._renameRestart}
1020 action_conf = {
1021 'poweroff': 'actions_after_shutdown',
1022 'reboot': 'actions_after_reboot',
1023 'crash': 'actions_after_crash',
1026 action_target = self.info.get(action_conf.get(reason))
1027 func = actions.get(action_target, None)
1028 if func and callable(func):
1029 func()
1030 else:
1031 self.destroy() # default to destroy
1033 def _renameRestart(self):
1034 self._restart(True)
1036 def _restart(self, rename = False):
1037 """Restart the domain after it has exited.
1039 @param rename True if the old domain is to be renamed and preserved,
1040 False if it is to be destroyed.
1041 """
1042 from xen.xend import XendDomain
1044 self._configureBootloader()
1045 config = self.sxpr()
1047 if self._infoIsSet('cpus') and len(self.info['cpus']) != 0:
1048 config.append(['cpus', reduce(lambda x, y: str(x) + "," + str(y),
1049 self.info['cpus'])])
1051 if self._readVm(RESTART_IN_PROGRESS):
1052 log.error('Xend failed during restart of domain %s. '
1053 'Refusing to restart to avoid loops.',
1054 str(self.domid))
1055 self.destroy()
1056 return
1058 old_domid = self.domid
1059 self._writeVm(RESTART_IN_PROGRESS, 'True')
1061 now = time.time()
1062 rst = self._readVm('xend/previous_restart_time')
1063 if rst:
1064 rst = float(rst)
1065 timeout = now - rst
1066 if timeout < MINIMUM_RESTART_TIME:
1067 log.error(
1068 'VM %s restarting too fast (%f seconds since the last '
1069 'restart). Refusing to restart to avoid loops.',
1070 self.info['name_label'], timeout)
1071 self.destroy()
1072 return
1074 self._writeVm('xend/previous_restart_time', str(now))
1076 try:
1077 if rename:
1078 self._preserveForRestart()
1079 else:
1080 self._unwatchVm()
1081 self.destroyDomain()
1083 # new_dom's VM will be the same as this domain's VM, except where
1084 # the rename flag has instructed us to call preserveForRestart.
1085 # In that case, it is important that we remove the
1086 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1087 # once the new one is available.
1089 new_dom = None
1090 try:
1091 new_dom = XendDomain.instance().domain_create(config)
1092 new_dom.unpause()
1093 rst_cnt = self._readVm('xend/restart_count')
1094 rst_cnt = int(rst_cnt) + 1
1095 self._writeVm('xend/restart_count', str(rst_cnt))
1096 new_dom._removeVm(RESTART_IN_PROGRESS)
1097 except:
1098 if new_dom:
1099 new_dom._removeVm(RESTART_IN_PROGRESS)
1100 new_dom.destroy()
1101 else:
1102 self._removeVm(RESTART_IN_PROGRESS)
1103 raise
1104 except:
1105 log.exception('Failed to restart domain %s.', str(old_domid))
1107 def _preserveForRestart(self):
1108 """Preserve a domain that has been shut down, by giving it a new UUID,
1109 cloning the VM details, and giving it a new name. This allows us to
1110 keep this domain for debugging, but restart a new one in its place
1111 preserving the restart semantics (name and UUID preserved).
1112 """
1114 new_uuid = uuid.createString()
1115 new_name = 'Domain-%s' % new_uuid
1116 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1117 self.info['name_label'], self.domid, self.info['uuid'],
1118 new_name, new_uuid)
1119 self._unwatchVm()
1120 self._releaseDevices()
1121 self.info['name_label'] = new_name
1122 self.info['uuid'] = new_uuid
1123 self.vmpath = XS_VMROOT + new_uuid
1124 self._storeVmDetails()
1125 self._preserve()
1128 def _preserve(self):
1129 log.info("Preserving dead domain %s (%d).", self.info['name_label'],
1130 self.domid)
1131 self._unwatchVm()
1132 self.storeDom('xend/shutdown_completed', 'True')
1133 self._stateSet(DOM_STATE_HALTED)
1136 # Debugging ..
1139 def dumpCore(self, corefile = None):
1140 """Create a core dump for this domain. Nothrow guarantee."""
1142 try:
1143 if not corefile:
1144 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1145 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1146 self.info['name_label'], self.domid)
1148 if os.path.isdir(corefile):
1149 raise XendError("Cannot dump core in a directory: %s" %
1150 corefile)
1152 xc.domain_dumpcore(self.domid, corefile)
1153 except RuntimeError, ex:
1154 corefile_incomp = corefile+'-incomplete'
1155 os.rename(corefile, corefile_incomp)
1156 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1157 self.domid, self.info['name_label'])
1158 raise XendError("Failed to dump core: %s" % str(ex))
1161 # Device creation/deletion functions
1164 def _createDevice(self, deviceClass, devConfig):
1165 return self.getDeviceController(deviceClass).createDevice(devConfig)
1167 def _waitForDevice(self, deviceClass, devid):
1168 return self.getDeviceController(deviceClass).waitForDevice(devid)
1170 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1171 return self.getDeviceController(deviceClass).reconfigureDevice(
1172 devid, devconfig)
1174 def _createDevices(self):
1175 """Create the devices for a vm.
1177 @raise: VmError for invalid devices
1178 """
1179 for (devclass, config) in self.info.get('devices', {}).values():
1180 if devclass in XendDevices.valid_devices():
1181 log.info("createDevice: %s : %s" % (devclass, config))
1182 self._createDevice(devclass, config)
1184 if self.image:
1185 self.image.createDeviceModel()
1187 def _releaseDevices(self):
1188 """Release all domain's devices. Nothrow guarantee."""
1190 while True:
1191 t = xstransact("%s/device" % self.dompath)
1192 for devclass in XendDevices.valid_devices():
1193 for dev in t.list(devclass):
1194 try:
1195 t.remove(dev)
1196 except:
1197 # Log and swallow any exceptions in removal --
1198 # there's nothing more we can do.
1199 log.exception(
1200 "Device release failed: %s; %s; %s",
1201 self.info['name_label'], devclass, dev)
1202 if t.commit():
1203 break
1205 def getDeviceController(self, name):
1206 """Get the device controller for this domain, and if it
1207 doesn't exist, create it.
1209 @param name: device class name
1210 @type name: string
1211 @rtype: subclass of DevController
1212 """
1213 if name not in self._deviceControllers:
1214 devController = XendDevices.make_controller(name, self)
1215 if not devController:
1216 raise XendError("Unknown device type: %s" % name)
1217 self._deviceControllers[name] = devController
1219 return self._deviceControllers[name]
1222 # Migration functions (public)
1225 def testMigrateDevices(self, network, dst):
1226 """ Notify all device about intention of migration
1227 @raise: XendError for a device that cannot be migrated
1228 """
1229 for (n, c) in self.info.all_devices_sxpr():
1230 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1231 if rc != 0:
1232 raise XendError("Device of type '%s' refuses migration." % n)
1234 def migrateDevices(self, network, dst, step, domName=''):
1235 """Notify the devices about migration
1236 """
1237 ctr = 0
1238 try:
1239 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1240 self.migrateDevice(dev_type, dev_conf, network, dst,
1241 step, domName)
1242 ctr = ctr + 1
1243 except:
1244 for dev_type, dev_conf in self.info.all_devices_sxpr():
1245 if ctr == 0:
1246 step = step - 1
1247 ctr = ctr - 1
1248 self._recoverMigrateDevice(dev_type, dev_conf, network,
1249 dst, step, domName)
1250 raise
1252 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1253 step, domName=''):
1254 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1255 network, dst, step, domName)
1257 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1258 dst, step, domName=''):
1259 return self.getDeviceController(deviceClass).recover_migrate(
1260 deviceConfig, network, dst, step, domName)
1263 ## private:
1265 def _constructDomain(self):
1266 """Construct the domain.
1268 @raise: VmError on error
1269 """
1271 log.debug('XendDomainInfo.constructDomain')
1273 image_cfg = self.info.get('image', {})
1274 hvm = image_cfg.has_key('hvm')
1276 if hvm:
1277 info = xc.xeninfo()
1278 if 'hvm' not in info['xen_caps']:
1279 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1280 "supported by your CPU and enabled in your "
1281 "BIOS?")
1283 self.domid = xc.domain_create(
1284 domid = 0,
1285 ssidref = security.get_security_info(self.info, 'ssidref'),
1286 handle = uuid.fromString(self.info['uuid']),
1287 hvm = int(hvm))
1289 if self.domid < 0:
1290 raise VmError('Creating domain failed: name=%s' %
1291 self.info['name_label'])
1293 self.dompath = GetDomainPath(self.domid)
1295 self._recreateDom()
1297 # Set maximum number of vcpus in domain
1298 xc.domain_max_vcpus(self.domid, int(self.info['vcpus_number']))
1301 def _introduceDomain(self):
1302 assert self.domid is not None
1303 assert self.store_mfn is not None
1304 assert self.store_port is not None
1306 try:
1307 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1308 except RuntimeError, exn:
1309 raise XendError(str(exn))
1312 def _initDomain(self):
1313 log.debug('XendDomainInfo.initDomain: %s %s',
1314 self.domid,
1315 self.info['cpu_weight'])
1317 # if we have a boot loader but no image, then we need to set things
1318 # up by running the boot loader non-interactively
1319 if self.info.get('bootloader') and self.info.get('image'):
1320 self._configureBootloader()
1322 if not self._infoIsSet('image'):
1323 raise VmError('Missing image in configuration')
1325 try:
1326 self.image = image.create(self,
1327 self.info,
1328 self.info['image'],
1329 self.info['devices'])
1331 localtime = self.info.get('localtime', False)
1332 if localtime:
1333 xc.domain_set_time_offset(self.domid)
1335 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1337 # repin domain vcpus if a restricted cpus list is provided
1338 # this is done prior to memory allocation to aide in memory
1339 # distribution for NUMA systems.
1340 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1341 for v in range(0, self.info['max_vcpu_id']+1):
1342 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1344 # Use architecture- and image-specific calculations to determine
1345 # the various headrooms necessary, given the raw configured
1346 # values. maxmem, memory, and shadow are all in KiB.
1347 maxmem = self.image.getRequiredAvailableMemory(
1348 self.info['memory_static_min'] * 1024)
1349 memory = self.image.getRequiredAvailableMemory(
1350 self.info['memory_static_max'] * 1024)
1351 shadow = self.image.getRequiredShadowMemory(
1352 self.info['shadow_memory'] * 1024,
1353 self.info['memory_static_max'] * 1024)
1355 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1356 # takes MiB and we must not round down and end up under-providing.
1357 shadow = ((shadow + 1023) / 1024) * 1024
1359 # set memory limit
1360 xc.domain_setmaxmem(self.domid, maxmem)
1362 # Make sure there's enough RAM available for the domain
1363 balloon.free(memory + shadow)
1365 # Set up the shadow memory
1366 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1367 self.info['shadow_memory'] = shadow_cur
1369 self._createChannels()
1371 channel_details = self.image.createImage()
1373 self.store_mfn = channel_details['store_mfn']
1374 if 'console_mfn' in channel_details:
1375 self.console_mfn = channel_details['console_mfn']
1377 self._introduceDomain()
1379 self._createDevices()
1381 if self.info['bootloader']:
1382 self.image.cleanupBootloading()
1384 self.info['start_time'] = time.time()
1386 self._stateSet(DOM_STATE_RUNNING)
1387 except RuntimeError, exn:
1388 log.exception("XendDomainInfo.initDomain: exception occurred")
1389 if self.info['bootloader'] not in (None, 'kernel_external') \
1390 and self.image is not None:
1391 self.image.cleanupBootloading()
1392 raise VmError(str(exn))
1395 def cleanupDomain(self):
1396 """Cleanup domain resources; release devices. Idempotent. Nothrow
1397 guarantee."""
1399 self.refresh_shutdown_lock.acquire()
1400 try:
1401 self.unwatchShutdown()
1402 self._releaseDevices()
1404 if self.image:
1405 try:
1406 self.image.destroy()
1407 except:
1408 log.exception(
1409 "XendDomainInfo.cleanup: image.destroy() failed.")
1410 self.image = None
1412 try:
1413 self._removeDom()
1414 except:
1415 log.exception("Removing domain path failed.")
1417 self._stateSet(DOM_STATE_HALTED)
1418 finally:
1419 self.refresh_shutdown_lock.release()
1422 def unwatchShutdown(self):
1423 """Remove the watch on the domain's control/shutdown node, if any.
1424 Idempotent. Nothrow guarantee. Expects to be protected by the
1425 refresh_shutdown_lock."""
1427 try:
1428 try:
1429 if self.shutdownWatch:
1430 self.shutdownWatch.unwatch()
1431 finally:
1432 self.shutdownWatch = None
1433 except:
1434 log.exception("Unwatching control/shutdown failed.")
1436 def waitForShutdown(self):
1437 self.state_updated.acquire()
1438 try:
1439 while self.state in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1440 self.state_updated.wait()
1441 finally:
1442 self.state_updated.release()
1446 # TODO: recategorise - called from XendCheckpoint
1449 def completeRestore(self, store_mfn, console_mfn):
1451 log.debug("XendDomainInfo.completeRestore")
1453 self.store_mfn = store_mfn
1454 self.console_mfn = console_mfn
1456 self._introduceDomain()
1457 self._storeDomDetails()
1458 self._registerWatches()
1459 self.refreshShutdown()
1461 log.debug("XendDomainInfo.completeRestore done")
1464 def _endRestore(self):
1465 self.setResume(False)
1468 # VM Destroy
1471 def destroy(self):
1472 """Cleanup VM and destroy domain. Nothrow guarantee."""
1474 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1476 self._cleanupVm()
1477 if self.dompath is not None:
1478 self.destroyDomain()
1481 def destroyDomain(self):
1482 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1484 try:
1485 if self.domid is not None:
1486 xc.domain_destroy(self.domid)
1487 self.domid = None
1488 for state in DOM_STATES_OLD:
1489 self.info[state] = 0
1490 except:
1491 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1493 self.cleanupDomain()
1497 # Channels for xenstore and console
1500 def _createChannels(self):
1501 """Create the channels to the domain.
1502 """
1503 self.store_port = self._createChannel()
1504 self.console_port = self._createChannel()
1507 def _createChannel(self):
1508 """Create an event channel to the domain.
1509 """
1510 try:
1511 return xc.evtchn_alloc_unbound(domid=self.domid, remote_dom=0)
1512 except:
1513 log.exception("Exception in alloc_unbound(%d)", self.domid)
1514 raise
1517 # Bootloader configuration
1520 def _configureBootloader(self):
1521 """Run the bootloader if we're configured to do so."""
1522 if not self.info.get('bootloader'):
1523 return
1524 blcfg = None
1525 # FIXME: this assumes that we want to use the first disk device
1526 for devuuid, (devtype, devinfo) in self.info.all_devices_sxpr():
1527 if not devtype or not devinfo or devtype not in ('vbd', 'tap'):
1528 continue
1529 disk = devinfo.get('uname')
1530 if disk is None:
1531 continue
1532 fn = blkdev_uname_to_file(disk)
1533 blcfg = bootloader(self.info['bootloader'], fn, 1,
1534 self.info['bootloader_args'],
1535 self.info['image'])
1536 break
1537 if blcfg is None:
1538 msg = "Had a bootloader specified, but can't find disk"
1539 log.error(msg)
1540 raise VmError(msg)
1542 self.info.update_with_image_sxp(blcfg)
1545 # VM Functions
1548 def _readVMDetails(self, params):
1549 """Read the specified parameters from the store.
1550 """
1551 try:
1552 return self._gatherVm(*params)
1553 except ValueError:
1554 # One of the int/float entries in params has a corresponding store
1555 # entry that is invalid. We recover, because older versions of
1556 # Xend may have put the entry there (memory/target, for example),
1557 # but this is in general a bad situation to have reached.
1558 log.exception(
1559 "Store corrupted at %s! Domain %d's configuration may be "
1560 "affected.", self.vmpath, self.domid)
1561 return []
1563 def _cleanupVm(self):
1564 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1566 self._unwatchVm()
1568 try:
1569 self._removeVm()
1570 except:
1571 log.exception("Removing VM path failed.")
1574 def checkLiveMigrateMemory(self):
1575 """ Make sure there's enough memory to migrate this domain """
1576 overhead_kb = 0
1577 if arch.type == "x86":
1578 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1579 # the minimum that Xen would allocate if no value were given.
1580 overhead_kb = self.info['vcpus_number'] * 1024 + \
1581 self.info['memory_static_max'] * 4
1582 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1583 # The domain might already have some shadow memory
1584 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1585 if overhead_kb > 0:
1586 balloon.free(overhead_kb)
1588 def _unwatchVm(self):
1589 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1590 guarantee."""
1591 try:
1592 try:
1593 if self.vmWatch:
1594 self.vmWatch.unwatch()
1595 finally:
1596 self.vmWatch = None
1597 except:
1598 log.exception("Unwatching VM path failed.")
1600 def testDeviceComplete(self):
1601 """ For Block IO migration safety we must ensure that
1602 the device has shutdown correctly, i.e. all blocks are
1603 flushed to disk
1604 """
1605 start = time.time()
1606 while True:
1607 test = 0
1608 diff = time.time() - start
1609 for i in self.getDeviceController('vbd').deviceIDs():
1610 test = 1
1611 log.info("Dev %s still active, looping...", i)
1612 time.sleep(0.1)
1614 if test == 0:
1615 break
1616 if diff >= MIGRATE_TIMEOUT:
1617 log.info("Dev still active but hit max loop timeout")
1618 break
1620 def _storeVmDetails(self):
1621 to_store = {}
1623 for key in XendConfig.LEGACY_XENSTORE_VM_PARAMS:
1624 info_key = XendConfig.LEGACY_CFG_TO_XENAPI_CFG.get(key, key)
1625 if self._infoIsSet(info_key):
1626 to_store[key] = str(self.info[info_key])
1628 if self.info.get('image'):
1629 image_sxpr = self.info.image_sxpr()
1630 if image_sxpr:
1631 to_store['image'] = sxp.to_string(image_sxpr)
1633 if self._infoIsSet('security'):
1634 secinfo = self.info['security']
1635 to_store['security'] = sxp.to_string(secinfo)
1636 for idx in range(0, len(secinfo)):
1637 if secinfo[idx][0] == 'access_control':
1638 to_store['security/access_control'] = sxp.to_string(
1639 [secinfo[idx][1], secinfo[idx][2]])
1640 for aidx in range(1, len(secinfo[idx])):
1641 if secinfo[idx][aidx][0] == 'label':
1642 to_store['security/access_control/label'] = \
1643 secinfo[idx][aidx][1]
1644 if secinfo[idx][aidx][0] == 'policy':
1645 to_store['security/access_control/policy'] = \
1646 secinfo[idx][aidx][1]
1647 if secinfo[idx][0] == 'ssidref':
1648 to_store['security/ssidref'] = str(secinfo[idx][1])
1651 if not self._readVm('xend/restart_count'):
1652 to_store['xend/restart_count'] = str(0)
1654 log.debug("Storing VM details: %s", to_store)
1656 self._writeVm(to_store)
1657 self._setVmPermissions()
1660 def _setVmPermissions(self):
1661 """Allow the guest domain to read its UUID. We don't allow it to
1662 access any other entry, for security."""
1663 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1664 { 'dom' : self.domid,
1665 'read' : True,
1666 'write' : False })
1669 # Utility functions
1672 def _stateSet(self, state):
1673 self.state_updated.acquire()
1674 try:
1675 if self.state != state:
1676 self.state = state
1677 self.state_updated.notifyAll()
1678 finally:
1679 self.state_updated.release()
1681 def _infoIsSet(self, name):
1682 return name in self.info and self.info[name] is not None
1684 def _checkName(self, name):
1685 """Check if a vm name is valid. Valid names contain alphabetic
1686 characters, digits, or characters in '_-.:/+'.
1687 The same name cannot be used for more than one vm at the same time.
1689 @param name: name
1690 @raise: VmError if invalid
1691 """
1692 from xen.xend import XendDomain
1694 if name is None or name == '':
1695 raise VmError('Missing VM Name')
1697 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1698 raise VmError('Invalid VM Name')
1700 dom = XendDomain.instance().domain_lookup_nr(name)
1701 if dom and dom.info['uuid'] != self.info['uuid']:
1702 raise VmError("VM name '%s' already exists as domain %s" %
1703 (name, str(dom.domid)))
1706 def update(self, info = None, refresh = True):
1707 """Update with info from xc.domain_getinfo().
1708 """
1709 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1710 str(self.domid))
1712 if not info:
1713 info = dom_get(self.domid)
1714 if not info:
1715 return
1717 #manually update ssidref / security fields
1718 if security.on() and info.has_key('ssidref'):
1719 if (info['ssidref'] != 0) and self.info.has_key('security'):
1720 security_field = self.info['security']
1721 if not security_field:
1722 #create new security element
1723 self.info.update({'security':
1724 [['ssidref', str(info['ssidref'])]]})
1726 #ssidref field not used any longer
1727 if 'ssidref' in info:
1728 info.pop('ssidref')
1730 # make sure state is reset for info
1731 # TODO: we should eventually get rid of old_dom_states
1733 self.info.update_config(info)
1735 if refresh:
1736 self.refreshShutdown(info)
1738 log.trace("XendDomainInfo.update done on domain %s: %s",
1739 str(self.domid), self.info)
1741 def sxpr(self, ignore_store = False):
1742 result = self.info.to_sxp(domain = self,
1743 ignore_devices = ignore_store)
1745 if not ignore_store and self.dompath:
1746 vnc_port = self.readDom('console/vnc-port')
1747 if vnc_port is not None:
1748 result.append(['device',
1749 ['console', ['vnc-port', str(vnc_port)]]])
1751 return result
1753 # Xen API
1754 # ----------------------------------------------------------------
1756 def get_uuid(self):
1757 dom_uuid = self.info.get('uuid')
1758 if not dom_uuid: # if it doesn't exist, make one up
1759 dom_uuid = uuid.createString()
1760 self.info['uuid'] = dom_uuid
1761 return dom_uuid
1763 def get_memory_static_max(self):
1764 return self.info.get('memory_static_max')
1765 def get_memory_static_min(self):
1766 return self.info.get('memory_static_min')
1767 def get_memory_dynamic_max(self):
1768 return self.info.get('memory_dynamic_min')
1769 def get_memory_dynamic_min(self):
1770 return self.info.get('memory_dynamic_max')
1773 def get_vcpus_policy(self):
1774 sched_id = xc.sched_id_get()
1775 if sched_id == xen.lowlevel.xc.XEN_SCHEDULER_SEDF:
1776 return 'sedf'
1777 elif sched_id == xen.lowlevel.xc.XEN_SCHEDULER_CREDIT:
1778 return 'credit'
1779 else:
1780 return 'unknown'
1781 def get_vcpus_params(self):
1782 return '' # TODO
1783 def get_power_state(self):
1784 return XEN_API_VM_POWER_STATE[self.state]
1785 def get_bios_boot(self):
1786 return '' # TODO
1787 def get_platform_std_vga(self):
1788 return self.info.get('platform_std_vga', False)
1789 def get_platform_keymap(self):
1790 return ''
1791 def get_platform_serial(self):
1792 return self.info.get('platform_serial', '')
1793 def get_platform_localtime(self):
1794 return self.info.get('platform_localtime', False)
1795 def get_platform_clock_offset(self):
1796 return self.info.get('platform_clock_offset', False)
1797 def get_platform_enable_audio(self):
1798 return self.info.get('platform_enable_audio', False)
1799 def get_platform_keymap(self):
1800 return self.info.get('platform_keymap', '')
1801 def get_builder(self):
1802 return self.info.get('builder', 0)
1803 def get_boot_method(self):
1804 return self.info.get('boot_method', '')
1805 def get_kernel_image(self):
1806 return self.info.get('kernel_kernel', '')
1807 def get_kernel_initrd(self):
1808 return self.info.get('kernel_initrd', '')
1809 def get_kernel_args(self):
1810 return self.info.get('kernel_args', '')
1811 def get_grub_cmdline(self):
1812 return '' # TODO
1813 def get_pci_bus(self):
1814 return 0 # TODO
1815 def get_tools_version(self):
1816 return {} # TODO
1817 def get_other_config(self):
1818 return {} # TODO
1820 def get_on_shutdown(self):
1821 after_shutdown = self.info.get('action_after_shutdown')
1822 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
1823 return XEN_API_ON_NORMAL_EXIT[-1]
1824 return after_shutdown
1826 def get_on_reboot(self):
1827 after_reboot = self.info.get('action_after_reboot')
1828 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
1829 return XEN_API_ON_NORMAL_EXIT[-1]
1830 return after_reboot
1832 def get_on_suspend(self):
1833 # TODO: not supported
1834 after_suspend = self.info.get('action_after_suspend')
1835 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
1836 return XEN_API_ON_NORMAL_EXIT[-1]
1837 return after_suspend
1839 def get_on_crash(self):
1840 after_crash = self.info.get('action_after_crash')
1841 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
1842 return XEN_API_ON_CRASH_BEHAVIOUR[0]
1843 return after_crash
1845 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
1846 """ Get's a device configuration either from XendConfig or
1847 from the DevController.
1849 @param dev_class: device class, either, 'vbd' or 'vif'
1850 @param dev_uuid: device UUID
1852 @rtype: dictionary
1853 """
1854 dev_type_config = self.info['devices'].get(dev_uuid)
1856 # shortcut if the domain isn't started because
1857 # the devcontrollers will have no better information
1858 # than XendConfig.
1859 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
1860 if dev_type_config:
1861 return copy.deepcopy(dev_type_config[1])
1862 return None
1864 # instead of using dev_class, we use the dev_type
1865 # that is from XendConfig.
1866 # This will accomdate 'tap' as well as 'vbd'
1867 dev_type = dev_type_config[0]
1869 controller = self.getDeviceController(dev_type)
1870 if not controller:
1871 return None
1873 all_configs = controller.getAllDeviceConfigurations()
1874 if not all_configs:
1875 return None
1877 dev_config = copy.deepcopy(dev_type_config[1])
1878 for _devid, _devcfg in all_configs.items():
1879 if _devcfg.get('uuid') == dev_uuid:
1880 dev_config.update(_devcfg)
1881 dev_config['id'] = _devid
1882 return dev_config
1884 return dev_config
1886 def get_dev_xenapi_config(self, dev_class, dev_uuid):
1887 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
1888 if not config:
1889 return {}
1891 config['VM'] = self.get_uuid()
1893 if dev_class == 'vif':
1894 if not config.has_key('name'):
1895 config['name'] = config.get('vifname', '')
1896 if not config.has_key('MAC'):
1897 config['MAC'] = config.get('mac', '')
1898 if not config.has_key('type'):
1899 config['type'] = 'paravirtualised'
1900 if not config.has_key('device'):
1901 devid = config.get('id')
1902 if devid != None:
1903 config['device'] = 'eth%d' % devid
1904 else:
1905 config['device'] = ''
1907 config['network'] = '' # Invalid for Xend
1908 config['MTU'] = 1500 # TODO
1909 config['network_read_kbs'] = 0.0
1910 config['network_write_kbs'] = 0.0
1911 config['IO_bandwidth_incoming_kbs'] = 0.0
1912 config['IO_bandwidth_outgoing_kbs'] = 0.0
1914 if dev_class == 'vbd':
1915 config['VDI'] = '' # TODO
1916 config['device'] = config.get('dev', '')
1917 config['driver'] = 'paravirtualised' # TODO
1918 config['image'] = config.get('uname', '')
1919 config['IO_bandwidth_incoming_kbs'] = 0.0
1920 config['IO_bandwidth_outgoing_kbs'] = 0.0
1921 if config['mode'] == 'r':
1922 config['mode'] = 'RO'
1923 else:
1924 config['mode'] = 'RW'
1926 if dev_class == 'vtpm':
1927 config['driver'] = 'paravirtualised' # TODO
1929 return config
1931 def get_dev_property(self, dev_class, dev_uuid, field):
1932 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
1933 try:
1934 return config[field]
1935 except KeyError:
1936 raise XendError('Invalid property for device: %s' % field)
1938 def get_vcpus_util(self):
1939 # TODO: this returns the total accum cpu time, rather than util
1940 # TODO: spec says that key is int, however, python does not allow
1941 # non-string keys to dictionaries.
1942 vcpu_util = {}
1943 if 'max_vcpu_id' in self.info and self.domid != None:
1944 for i in range(0, self.info['max_vcpu_id']+1):
1945 info = xc.vcpu_getinfo(self.domid, i)
1946 vcpu_util[str(i)] = info['cpu_time']/1000000000.0
1948 return vcpu_util
1950 def get_vifs(self):
1951 return self.info.get('vif_refs', [])
1953 def get_vbds(self):
1954 return self.info.get('vbd_refs', [])
1956 def get_vtpms(self):
1957 return self.info.get('vtpm_refs', [])
1959 def create_vbd(self, xenapi_vbd):
1960 """Create a VBD device from the passed struct in Xen API format.
1962 @return: uuid of the device
1963 @rtype: string
1964 """
1966 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
1967 if not dev_uuid:
1968 raise XendError('Failed to create device')
1970 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1971 sxpr = self.info.device_sxpr(dev_uuid)
1972 devid = self.getDeviceController('vbd').createDevice(sxpr)
1973 raise XendError("Device creation failed")
1975 return dev_uuid
1977 def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
1978 """Create a VBD using a VDI from XendStorageRepository.
1980 @param xenapi_vbd: vbd struct from the Xen API
1981 @param vdi_image_path: VDI UUID
1982 @rtype: string
1983 @return: uuid of the device
1984 """
1985 xenapi_vbd['image'] = vdi_image_path
1986 log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
1987 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
1988 if not dev_uuid:
1989 raise XendError('Failed to create device')
1991 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1992 sxpr = self.info.device_sxpr(dev_uuid)
1993 devid = self.getDeviceController('tap').createDevice(sxpr)
1994 raise XendError("Device creation failed")
1996 return dev_uuid
1998 def create_vif(self, xenapi_vif):
1999 """Create VIF device from the passed struct in Xen API format.
2001 @param xenapi_vif: Xen API VIF Struct.
2002 @rtype: string
2003 @return: UUID
2004 """
2005 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
2006 if not dev_uuid:
2007 raise XendError('Failed to create device')
2009 if self.state in (DOM_STATE_HALTED,):
2010 sxpr = self.info.device_sxpr(dev_uuid)
2011 devid = self.getDeviceController('vif').createDevice(sxpr)
2012 raise XendError("Device creation failed")
2014 return dev_uuid
2016 def create_vtpm(self, xenapi_vtpm):
2017 """Create a VTPM device from the passed struct in Xen API format.
2019 @return: uuid of the device
2020 @rtype: string
2021 """
2023 if self.state not in (DOM_STATE_HALTED,):
2024 raise VmError("Can only add vTPM to a halted domain.")
2025 if self.get_vtpms() != []:
2026 raise VmError('Domain already has a vTPM.')
2027 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
2028 if not dev_uuid:
2029 raise XendError('Failed to create device')
2031 return dev_uuid
2033 def has_device(self, dev_class, dev_uuid):
2034 return (dev_uuid in self.info['%s_refs' % dev_class])
2036 """
2037 def stateChar(name):
2038 if name in self.info:
2039 if self.info[name]:
2040 return name[0]
2041 else:
2042 return '-'
2043 else:
2044 return '?'
2046 state = reduce(lambda x, y: x + y, map(stateChar, DOM_STATES_OLD))
2048 sxpr.append(['state', state])
2050 if self.store_mfn:
2051 sxpr.append(['store_mfn', self.store_mfn])
2052 if self.console_mfn:
2053 sxpr.append(['console_mfn', self.console_mfn])
2054 """
2056 def __str__(self):
2057 return '<domain id=%s name=%s memory=%s state=%s>' % \
2058 (str(self.domid), self.info['name_label'],
2059 str(self.info['memory_static_min']), DOM_STATES[self.state])
2061 __repr__ = __str__