ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 12725:36fe7ca48e54

Tidy up the creation of directories that Xend needs. This avoids potential
races in this creation.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author Ewan Mellor <ewan@xensource.com>
date Fri Dec 01 11:32:32 2006 +0000 (2006-12-01)
parents fbfbc6ed47d9
children 056050ceb300 260426e3924f
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)
60 ##
61 # All parameters of VMs that may be configured on-the-fly, or at start-up.
62 #
63 VM_CONFIG_PARAMS = [
64 ('name', str),
65 ('on_poweroff', str),
66 ('on_reboot', str),
67 ('on_crash', str),
68 ]
71 ##
72 # Configuration entries that we expect to round-trip -- be read from the
73 # config file or xc, written to save-files (i.e. through sxpr), and reused as
74 # config on restart or restore, all without munging. Some configuration
75 # entries are munged for backwards compatibility reasons, or because they
76 # don't come out of xc in the same form as they are specified in the config
77 # file, so those are handled separately.
78 ROUNDTRIPPING_CONFIG_ENTRIES = [
79 ('uuid', str),
80 ('vcpus', int),
81 ('vcpu_avail', int),
82 ('cpu_cap', int),
83 ('cpu_weight', int),
84 ('memory', int),
85 ('shadow_memory', int),
86 ('maxmem', int),
87 ('bootloader', str),
88 ('bootloader_args', str),
89 ('features', str),
90 ('localtime', int),
91 ]
93 ROUNDTRIPPING_CONFIG_ENTRIES += VM_CONFIG_PARAMS
96 ##
97 # All entries written to the store. This is VM_CONFIG_PARAMS, plus those
98 # entries written to the store that cannot be reconfigured on-the-fly.
99 #
100 VM_STORE_ENTRIES = [
101 ('uuid', str),
102 ('vcpus', int),
103 ('vcpu_avail', int),
104 ('memory', int),
105 ('shadow_memory', int),
106 ('maxmem', int),
107 ('start_time', float),
108 ('on_xend_start', str),
109 ('on_xend_stop', str),
110 ]
112 VM_STORE_ENTRIES += VM_CONFIG_PARAMS
115 #
116 # There are a number of CPU-related fields:
117 #
118 # vcpus: the number of virtual CPUs this domain is configured to use.
119 # vcpu_avail: a bitmap telling the guest domain whether it may use each of
120 # its VCPUs. This is translated to
121 # <dompath>/cpu/<id>/availability = {online,offline} for use
122 # by the guest domain.
123 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
124 # CPUs that that VCPU may use.
125 # cpu: a configuration setting requesting that VCPU 0 is pinned to
126 # the specified physical CPU.
127 #
128 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
129 # across save, restore, migrate, and restart). The other settings are only
130 # specific to the domain, so are lost when the VM moves.
131 #
134 def create(config):
135 """Creates and start a VM using the supplied configuration.
136 (called from XMLRPCServer directly)
138 @param config: A configuration object involving lists of tuples.
139 @type config: list of lists, eg ['vm', ['image', 'xen.gz']]
141 @rtype: XendDomainInfo
142 @return: A up and running XendDomainInfo instance
143 @raise VmError: Invalid configuration or failure to start.
144 """
146 log.debug("XendDomainInfo.create(%s)", config)
147 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config))
148 try:
149 vm.start()
150 except:
151 log.exception('Domain construction failed')
152 vm.destroy()
153 raise
155 return vm
157 def recreate(info, priv):
158 """Create the VM object for an existing domain. The domain must not
159 be dying, as the paths in the store should already have been removed,
160 and asking us to recreate them causes problems.
162 @param xeninfo: Parsed configuration
163 @type xeninfo: Dictionary
164 @param priv: TODO, unknown, something to do with memory
165 @type priv: bool
167 @rtype: XendDomainInfo
168 @return: A up and running XendDomainInfo instance
169 @raise VmError: Invalid configuration.
170 @raise XendError: Errors with configuration.
171 """
173 log.debug("XendDomainInfo.recreate(%s)", info)
175 assert not info['dying']
177 xeninfo = XendConfig.XendConfig(dominfo = info)
178 domid = xeninfo['domid']
179 uuid1 = uuid.fromString(xeninfo['uuid'])
180 needs_reinitialising = False
182 dompath = GetDomainPath(domid)
183 if not dompath:
184 raise XendError('No domain path in store for existing '
185 'domain %d' % domid)
187 log.info("Recreating domain %d, UUID %s. at %s" %
188 (domid, xeninfo['uuid'], dompath))
190 # need to verify the path and uuid if not Domain-0
191 # if the required uuid and vm aren't set, then that means
192 # we need to recreate the dom with our own values
193 #
194 # NOTE: this is probably not desirable, really we should just
195 # abort or ignore, but there may be cases where xenstore's
196 # entry disappears (eg. xenstore-rm /)
197 #
198 try:
199 vmpath = xstransact.Read(dompath, "vm")
200 if not vmpath:
201 log.warn('/local/domain/%d/vm is missing. recreate is '
202 'confused, trying our best to recover' % domid)
203 needs_reinitialising = True
204 raise XendError('reinit')
206 uuid2_str = xstransact.Read(vmpath, "uuid")
207 if not uuid2_str:
208 log.warn('%s/uuid/ is missing. recreate is confused, '
209 'trying our best to recover' % vmpath)
210 needs_reinitialising = True
211 raise XendError('reinit')
213 uuid2 = uuid.fromString(uuid2_str)
214 if uuid1 != uuid2:
215 log.warn('UUID in /vm does not match the UUID in /dom/%d.'
216 'Trying out best to recover' % domid)
217 needs_reinitialising = True
218 except XendError:
219 pass # our best shot at 'goto' in python :)
221 vm = XendDomainInfo(xeninfo, domid, dompath, augment = True, priv = priv)
223 if needs_reinitialising:
224 vm._recreateDom()
225 vm._removeVm()
226 vm._storeVmDetails()
227 vm._storeDomDetails()
229 if vm.info['image']: # Only dom0 should be without an image entry when
230 # recreating, but we cope with missing ones
231 # elsewhere just in case.
232 vm.image = image.create(vm,
233 vm.info,
234 vm.info['image'],
235 vm.info['devices'])
236 vm.image.recreate()
238 vm._registerWatches()
239 vm.refreshShutdown(xeninfo)
240 return vm
243 def restore(config):
244 """Create a domain and a VM object to do a restore.
246 @param config: Domain SXP configuration
247 @type config: list of lists. (see C{create})
249 @rtype: XendDomainInfo
250 @return: A up and running XendDomainInfo instance
251 @raise VmError: Invalid configuration or failure to start.
252 @raise XendError: Errors with configuration.
253 """
255 log.debug("XendDomainInfo.restore(%s)", config)
256 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config),
257 resume = True)
258 try:
259 vm.resume()
260 return vm
261 except:
262 vm.destroy()
263 raise
265 def createDormant(domconfig):
266 """Create a dormant/inactive XenDomainInfo without creating VM.
267 This is for creating instances of persistent domains that are not
268 yet start.
270 @param domconfig: Parsed configuration
271 @type domconfig: XendConfig object
273 @rtype: XendDomainInfo
274 @return: A up and running XendDomainInfo instance
275 @raise XendError: Errors with configuration.
276 """
278 log.debug("XendDomainInfo.createDormant(%s)", domconfig)
280 # domid does not make sense for non-running domains.
281 domconfig.pop('domid', None)
282 vm = XendDomainInfo(domconfig)
283 return vm
285 def domain_by_name(name):
286 """Get domain by name
288 @params name: Name of the domain
289 @type name: string
290 @return: XendDomainInfo or None
291 """
292 from xen.xend import XendDomain
293 return XendDomain.instance().domain_lookup_by_name_nr(name)
296 def shutdown_reason(code):
297 """Get a shutdown reason from a code.
299 @param code: shutdown code
300 @type code: int
301 @return: shutdown reason
302 @rtype: string
303 """
304 return DOMAIN_SHUTDOWN_REASONS.get(code, "?")
306 def dom_get(dom):
307 """Get info from xen for an existing domain.
309 @param dom: domain id
310 @type dom: int
311 @return: info or None
312 @rtype: dictionary
313 """
314 try:
315 domlist = xc.domain_getinfo(dom, 1)
316 if domlist and dom == domlist[0]['domid']:
317 return domlist[0]
318 except Exception, err:
319 # ignore missing domain
320 log.trace("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
321 return None
324 class XendDomainInfo:
325 """An object represents a domain.
327 @TODO: try to unify dom and domid, they mean the same thing, but
328 xc refers to it as dom, and everywhere else, including
329 xenstore it is domid. The best way is to change xc's
330 python interface.
332 @ivar info: Parsed configuration
333 @type info: dictionary
334 @ivar domid: Domain ID (if VM has started)
335 @type domid: int or None
336 @ivar vmpath: XenStore path to this VM.
337 @type vmpath: string
338 @ivar dompath: XenStore path to this Domain.
339 @type dompath: string
340 @ivar image: Reference to the VM Image.
341 @type image: xen.xend.image.ImageHandler
342 @ivar store_port: event channel to xenstored
343 @type store_port: int
344 @ivar console_port: event channel to xenconsoled
345 @type console_port: int
346 @ivar store_mfn: xenstored mfn
347 @type store_mfn: int
348 @ivar console_mfn: xenconsoled mfn
349 @type console_mfn: int
350 @ivar vmWatch: reference to a watch on the xenstored vmpath
351 @type vmWatch: xen.xend.xenstore.xswatch
352 @ivar shutdownWatch: reference to watch on the xenstored domain shutdown
353 @type shutdownWatch: xen.xend.xenstore.xswatch
354 @ivar shutdownStartTime: UNIX Time when domain started shutting down.
355 @type shutdownStartTime: float or None
356 @ivar state: Domain state
357 @type state: enum(DOM_STATE_HALTED, DOM_STATE_RUNNING, ...)
358 @ivar state_updated: lock for self.state
359 @type state_updated: threading.Condition
360 @ivar refresh_shutdown_lock: lock for polling shutdown state
361 @type refresh_shutdown_lock: threading.Condition
362 @ivar _deviceControllers: device controller cache for this domain
363 @type _deviceControllers: dict 'string' to DevControllers
364 """
366 def __init__(self, info, domid = None, dompath = None, augment = False,
367 priv = False, resume = False):
368 """Constructor for a domain
370 @param info: parsed configuration
371 @type info: dictionary
372 @keyword domid: Set initial domain id (if any)
373 @type domid: int
374 @keyword dompath: Set initial dompath (if any)
375 @type dompath: string
376 @keyword augment: Augment given info with xenstored VM info
377 @type augment: bool
378 @keyword priv: Is a privledged domain (Dom 0) (TODO: really?)
379 @type priv: bool
380 @keyword resume: Is this domain being resumed?
381 @type resume: bool
382 """
384 self.info = info
385 if domid == None:
386 self.domid = self.info.get('domid')
387 else:
388 self.domid = domid
390 #REMOVE: uuid is now generated in XendConfig
391 #if not self._infoIsSet('uuid'):
392 # self.info['uuid'] = uuid.toString(uuid.create())
394 self.vmpath = XS_VMROOT + self.info['uuid']
395 self.dompath = dompath
397 self.image = None
398 self.store_port = None
399 self.store_mfn = None
400 self.console_port = None
401 self.console_mfn = None
403 self.vmWatch = None
404 self.shutdownWatch = None
405 self.shutdownStartTime = None
406 self._resume = resume
408 self.state = DOM_STATE_HALTED
409 self.state_updated = threading.Condition()
410 self.refresh_shutdown_lock = threading.Condition()
412 self._deviceControllers = {}
414 for state in DOM_STATES_OLD:
415 self.info[state] = 0
417 if augment:
418 self._augmentInfo(priv)
420 self._checkName(self.info['name_label'])
423 #
424 # Public functions available through XMLRPC
425 #
428 def start(self, is_managed = False):
429 """Attempts to start the VM by do the appropriate
430 initialisation if it not started.
431 """
432 from xen.xend import XendDomain
434 if self.state == DOM_STATE_HALTED:
435 try:
436 self._constructDomain()
437 self._initDomain()
438 self._storeVmDetails()
439 self._storeDomDetails()
440 self._registerWatches()
441 self.refreshShutdown()
443 # save running configuration if XendDomains believe domain is
444 # persistent
445 if is_managed:
446 xendomains = XendDomain.instance()
447 xendomains.managed_config_save(self)
448 except:
449 log.exception('VM start failed')
450 self.destroy()
451 raise
452 else:
453 raise XendError('VM already running')
455 def resume(self):
456 """Resumes a domain that has come back from suspension."""
457 if self.state in (DOM_STATE_HALTED, DOM_STATE_SUSPENDED):
458 try:
459 self._constructDomain()
460 self._storeVmDetails()
461 self._createDevices()
462 self._createChannels()
463 self._storeDomDetails()
464 self._endRestore()
465 except:
466 log.exception('VM resume failed')
467 raise
468 else:
469 raise XendError('VM already running')
471 def shutdown(self, reason):
472 """Shutdown a domain by signalling this via xenstored."""
473 log.debug('XendDomainInfo.shutdown')
474 if self.state in (DOM_STATE_SHUTDOWN, DOM_STATE_HALTED,):
475 raise XendError('Domain cannot be shutdown')
477 if self.domid == 0:
478 raise XendError('Domain 0 cannot be shutdown')
480 if reason not in DOMAIN_SHUTDOWN_REASONS.values():
481 raise XendError('Invalid reason: %s' % reason)
482 self._removeVm('xend/previous_restart_time')
483 self.storeDom("control/shutdown", reason)
485 def pause(self):
486 """Pause domain
488 @raise XendError: Failed pausing a domain
489 """
490 try:
491 xc.domain_pause(self.domid)
492 self._stateSet(DOM_STATE_PAUSED)
493 except Exception, ex:
494 raise XendError("Domain unable to be paused: %s" % str(ex))
496 def unpause(self):
497 """Unpause domain
499 @raise XendError: Failed unpausing a domain
500 """
501 try:
502 xc.domain_unpause(self.domid)
503 self._stateSet(DOM_STATE_RUNNING)
504 except Exception, ex:
505 raise XendError("Domain unable to be unpaused: %s" % str(ex))
507 def send_sysrq(self, key):
508 """ Send a Sysrq equivalent key via xenstored."""
509 asserts.isCharConvertible(key)
510 self.storeDom("control/sysrq", '%c' % key)
512 def device_create(self, dev_config):
513 """Create a new device.
515 @param dev_config: device configuration
516 @type dev_config: SXP object (parsed config)
517 """
518 log.debug("XendDomainInfo.device_create: %s" % dev_config)
519 dev_type = sxp.name(dev_config)
520 dev_uuid = self.info.device_add(dev_type, cfg_sxp = dev_config)
521 dev_config_dict = self.info['devices'][dev_uuid][1]
522 log.debug("XendDomainInfo.device_create: %s" % dev_config_dict)
523 devid = self._createDevice(dev_type, dev_config_dict)
524 self._waitForDevice(dev_type, devid)
525 return self.getDeviceController(dev_type).sxpr(devid)
527 def device_configure(self, dev_config, devid = None):
528 """Configure an existing device.
530 @param dev_config: device configuration
531 @type dev_config: SXP object (parsed config)
532 @param devid: device id
533 @type devid: int
534 @return: Returns True if successfully updated device
535 @rtype: boolean
536 """
537 deviceClass = sxp.name(dev_config)
539 # look up uuid of the device
540 dev_control = self.getDeviceController(deviceClass)
541 dev_sxpr = dev_control.sxpr(devid)
542 dev_uuid = sxp.child_value(sxpr, 'uuid')
543 if not dev_uuid:
544 return False
546 self.info.device_update(dev_uuid, dev_config)
547 dev_config_dict = self.info['devices'].get(dev_uuid)
548 if dev_config_dict:
549 dev_control.reconfigureDevice(devid, dev_config_dict[1])
550 return True
552 def waitForDevices(self):
553 """Wait for this domain's configured devices to connect.
555 @raise VmError: if any device fails to initialise.
556 """
557 for devclass in XendDevices.valid_devices():
558 self.getDeviceController(devclass).waitForDevices()
560 def destroyDevice(self, deviceClass, devid):
561 try:
562 devid = int(devid)
563 except ValueError:
564 # devid is not a number, let's search for it in xenstore.
565 devicePath = '%s/device/%s' % (self.dompath, deviceClass)
566 for entry in xstransact.List(devicePath):
567 backend = xstransact.Read('%s/%s' % (devicePath, entry),
568 "backend")
569 devName = xstransact.Read(backend, "dev")
570 if devName == devid:
571 # We found the integer matching our devid, use it instead
572 devid = entry
573 break
575 return self.getDeviceController(deviceClass).destroyDevice(devid)
578 def getDeviceSxprs(self, deviceClass):
579 return self.getDeviceController(deviceClass).sxprs()
582 def setMemoryTarget(self, target):
583 """Set the memory target of this domain.
584 @param target: In MiB.
585 """
586 log.debug("Setting memory target of domain %s (%d) to %d MiB.",
587 self.info['name_label'], self.domid, target)
589 if target <= 0:
590 raise XendError('Invalid memory size')
592 self.info['memory_static_min'] = target
593 self.storeVm("memory", target)
594 self.storeDom("memory/target", target << 10)
596 def getVCPUInfo(self):
597 try:
598 # We include the domain name and ID, to help xm.
599 sxpr = ['domain',
600 ['domid', self.domid],
601 ['name', self.info['name_label']],
602 ['vcpu_count', self.info['vcpus_number']]]
604 for i in range(0, self.info['max_vcpu_id']+1):
605 info = xc.vcpu_getinfo(self.domid, i)
607 sxpr.append(['vcpu',
608 ['number', i],
609 ['online', info['online']],
610 ['blocked', info['blocked']],
611 ['running', info['running']],
612 ['cpu_time', info['cpu_time'] / 1e9],
613 ['cpu', info['cpu']],
614 ['cpumap', info['cpumap']]])
616 return sxpr
618 except RuntimeError, exn:
619 raise XendError(str(exn))
621 #
622 # internal functions ... TODO: re-categorised
623 #
625 def _augmentInfo(self, priv):
626 """Augment self.info, as given to us through L{recreate}, with
627 values taken from the store. This recovers those values known
628 to xend but not to the hypervisor.
629 """
630 augment_entries = XendConfig.LEGACY_XENSTORE_VM_PARAMS[:]
631 if priv:
632 augment_entries.remove('memory')
633 augment_entries.remove('maxmem')
635 vm_config = self._readVMDetails([(k, XendConfig.LEGACY_CFG_TYPES[k])
636 for k in augment_entries])
638 # make returned lists into a dictionary
639 vm_config = dict(zip(augment_entries, vm_config))
641 for arg in augment_entries:
642 xapicfg = arg
643 val = vm_config[arg]
644 if val != None:
645 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
646 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
647 self.info[xapiarg] = val
648 else:
649 self.info[arg] = val
651 # read image value
652 image_sxp = self._readVm('image')
653 if image_sxp:
654 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
656 # read devices
657 devices = []
658 for devclass in XendDevices.valid_devices():
659 devconfig = self.getDeviceController(devclass).configurations()
660 if devconfig:
661 devices.extend(devconfig)
663 if not self.info['devices'] and devices is not None:
664 for device in devices:
665 self.info.device_add(device[0], cfg_sxp = device)
667 #
668 # Function to update xenstore /vm/*
669 #
671 def _readVm(self, *args):
672 return xstransact.Read(self.vmpath, *args)
674 def _writeVm(self, *args):
675 return xstransact.Write(self.vmpath, *args)
677 def _removeVm(self, *args):
678 return xstransact.Remove(self.vmpath, *args)
680 def _gatherVm(self, *args):
681 return xstransact.Gather(self.vmpath, *args)
683 def storeVm(self, *args):
684 return xstransact.Store(self.vmpath, *args)
686 #
687 # Function to update xenstore /dom/*
688 #
690 def readDom(self, *args):
691 return xstransact.Read(self.dompath, *args)
693 def gatherDom(self, *args):
694 return xstransact.Gather(self.dompath, *args)
696 def _writeDom(self, *args):
697 return xstransact.Write(self.dompath, *args)
699 def _removeDom(self, *args):
700 return xstransact.Remove(self.dompath, *args)
702 def storeDom(self, *args):
703 return xstransact.Store(self.dompath, *args)
705 def _recreateDom(self):
706 complete(self.dompath, lambda t: self._recreateDomFunc(t))
708 def _recreateDomFunc(self, t):
709 t.remove()
710 t.mkdir()
711 t.set_permissions({'dom' : self.domid})
712 t.write('vm', self.vmpath)
714 def _storeDomDetails(self):
715 to_store = {
716 'domid': str(self.domid),
717 'vm': self.vmpath,
718 'name': self.info['name_label'],
719 'console/limit': str(xroot.get_console_limit() * 1024),
720 'memory/target': str(self.info['memory_static_min'] * 1024)
721 }
723 def f(n, v):
724 if v is not None:
725 to_store[n] = str(v)
727 f('console/port', self.console_port)
728 f('console/ring-ref', self.console_mfn)
729 f('store/port', self.store_port)
730 f('store/ring-ref', self.store_mfn)
732 to_store.update(self._vcpuDomDetails())
734 log.debug("Storing domain details: %s", to_store)
736 self._writeDom(to_store)
738 def _vcpuDomDetails(self):
739 def availability(n):
740 if self.info['vcpu_avail'] & (1 << n):
741 return 'online'
742 else:
743 return 'offline'
745 result = {}
746 for v in range(0, self.info['vcpus_number']):
747 result["cpu/%d/availability" % v] = availability(v)
748 return result
750 #
751 # xenstore watches
752 #
754 def _registerWatches(self):
755 """Register a watch on this VM's entries in the store, and the
756 domain's control/shutdown node, so that when they are changed
757 externally, we keep up to date. This should only be called by {@link
758 #create}, {@link #recreate}, or {@link #restore}, once the domain's
759 details have been written, but before the new instance is returned."""
760 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
761 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
762 self._handleShutdownWatch)
764 def _storeChanged(self, _):
765 log.trace("XendDomainInfo.storeChanged");
767 changed = False
769 # Check whether values in the configuration have
770 # changed in Xenstore.
772 cfg_vm = ['name', 'on_poweroff', 'on_reboot', 'on_crash']
774 vm_details = self._readVMDetails([(k,XendConfig.LEGACY_CFG_TYPES[k])
775 for k in cfg_vm])
777 # convert two lists into a python dictionary
778 vm_details = dict(zip(cfg_vm, vm_details))
780 for arg, val in vm_details.items():
781 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
782 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
783 if val != None and val != self.info[xapiarg]:
784 self.info[xapiarg] = val
785 changed= True
787 # Check whether image definition has been updated
788 image_sxp = self._readVm('image')
789 if image_sxp and image_sxp != self.info.image_sxpr():
790 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
791 changed = True
793 if changed:
794 # Update the domain section of the store, as this contains some
795 # parameters derived from the VM configuration.
796 self._storeDomDetails()
798 return 1
800 def _handleShutdownWatch(self, _):
801 log.debug('XendDomainInfo.handleShutdownWatch')
803 reason = self.readDom('control/shutdown')
805 if reason and reason != 'suspend':
806 sst = self.readDom('xend/shutdown_start_time')
807 now = time.time()
808 if sst:
809 self.shutdownStartTime = float(sst)
810 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
811 else:
812 self.shutdownStartTime = now
813 self.storeDom('xend/shutdown_start_time', now)
814 timeout = SHUTDOWN_TIMEOUT
816 log.trace(
817 "Scheduling refreshShutdown on domain %d in %ds.",
818 self.domid, timeout)
819 threading.Timer(timeout, self.refreshShutdown).start()
821 return True
824 #
825 # Public Attributes for the VM
826 #
829 def getDomid(self):
830 return self.domid
832 def setName(self, name):
833 self._checkName(name)
834 self.info['name_label'] = name
835 self.storeVm("name", name)
837 def getName(self):
838 return self.info['name_label']
840 def getDomainPath(self):
841 return self.dompath
843 def getShutdownReason(self):
844 return self.readDom('control/shutdown')
846 def getStorePort(self):
847 """For use only by image.py and XendCheckpoint.py."""
848 return self.store_port
850 def getConsolePort(self):
851 """For use only by image.py and XendCheckpoint.py"""
852 return self.console_port
854 def getFeatures(self):
855 """For use only by image.py."""
856 return self.info['features']
858 def getVCpuCount(self):
859 return self.info['vcpus_number']
861 def setVCpuCount(self, vcpus):
862 self.info['vcpu_avail'] = (1 << vcpus) - 1
863 self.info['vcpus_number'] = vcpus
864 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
865 self.storeVm('vcpus', self.info['vcpus_number'])
866 self._writeDom(self._vcpuDomDetails())
868 def getLabel(self):
869 return security.get_security_info(self.info, 'label')
871 def getMemoryTarget(self):
872 """Get this domain's target memory size, in KB."""
873 return self.info['memory_static_min'] * 1024
875 def getResume(self):
876 return str(self._resume)
878 def getCap(self):
879 return self.info.get('cpu_cap', 0)
881 def getWeight(self):
882 return self.info['cpu_weight']
884 def setResume(self, state):
885 self._resume = state
887 def getRestartCount(self):
888 return self._readVm('xend/restart_count')
890 def refreshShutdown(self, xeninfo = None):
891 """ Checks the domain for whether a shutdown is required.
893 Called from XendDomainInfo and also image.py for HVM images.
894 """
896 # If set at the end of this method, a restart is required, with the
897 # given reason. This restart has to be done out of the scope of
898 # refresh_shutdown_lock.
899 restart_reason = None
901 self.refresh_shutdown_lock.acquire()
902 try:
903 if xeninfo is None:
904 xeninfo = dom_get(self.domid)
905 if xeninfo is None:
906 # The domain no longer exists. This will occur if we have
907 # scheduled a timer to check for shutdown timeouts and the
908 # shutdown succeeded. It will also occur if someone
909 # destroys a domain beneath us. We clean up the domain,
910 # just in case, but we can't clean up the VM, because that
911 # VM may have migrated to a different domain on this
912 # machine.
913 self.cleanupDomain()
914 self._stateSet(DOM_STATE_HALTED)
915 return
917 if xeninfo['dying']:
918 # Dying means that a domain has been destroyed, but has not
919 # yet been cleaned up by Xen. This state could persist
920 # indefinitely if, for example, another domain has some of its
921 # pages mapped. We might like to diagnose this problem in the
922 # future, but for now all we do is make sure that it's not us
923 # holding the pages, by calling cleanupDomain. We can't
924 # clean up the VM, as above.
925 self.cleanupDomain()
926 self._stateSet(DOM_STATE_SHUTDOWN)
927 return
929 elif xeninfo['crashed']:
930 if self.readDom('xend/shutdown_completed'):
931 # We've seen this shutdown already, but we are preserving
932 # the domain for debugging. Leave it alone.
933 return
935 log.warn('Domain has crashed: name=%s id=%d.',
936 self.info['name_label'], self.domid)
938 if xroot.get_enable_dump():
939 self.dumpCore()
941 restart_reason = 'crash'
942 self._stateSet(DOM_STATE_HALTED)
944 elif xeninfo['shutdown']:
945 self._stateSet(DOM_STATE_SHUTDOWN)
946 if self.readDom('xend/shutdown_completed'):
947 # We've seen this shutdown already, but we are preserving
948 # the domain for debugging. Leave it alone.
949 return
951 else:
952 reason = shutdown_reason(xeninfo['shutdown_reason'])
954 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
955 self.info['name_label'], self.domid, reason)
957 self._clearRestart()
959 if reason == 'suspend':
960 self._stateSet(DOM_STATE_SUSPENDED)
961 # Don't destroy the domain. XendCheckpoint will do
962 # this once it has finished. However, stop watching
963 # the VM path now, otherwise we will end up with one
964 # watch for the old domain, and one for the new.
965 self._unwatchVm()
966 elif reason in ('poweroff', 'reboot'):
967 restart_reason = reason
968 else:
969 self.destroy()
971 elif self.dompath is None:
972 # We have yet to manage to call introduceDomain on this
973 # domain. This can happen if a restore is in progress, or has
974 # failed. Ignore this domain.
975 pass
976 else:
977 # Domain is alive. If we are shutting it down, then check
978 # the timeout on that, and destroy it if necessary.
979 if xeninfo['paused']:
980 self._stateSet(DOM_STATE_PAUSED)
981 else:
982 self._stateSet(DOM_STATE_RUNNING)
984 if self.shutdownStartTime:
985 timeout = (SHUTDOWN_TIMEOUT - time.time() +
986 self.shutdownStartTime)
987 if timeout < 0:
988 log.info(
989 "Domain shutdown timeout expired: name=%s id=%s",
990 self.info['name_label'], self.domid)
991 self.destroy()
992 finally:
993 self.refresh_shutdown_lock.release()
995 if restart_reason:
996 self._maybeRestart(restart_reason)
999 #
1000 # Restart functions - handling whether we come back up on shutdown.
1003 def _clearRestart(self):
1004 self._removeDom("xend/shutdown_start_time")
1007 def _maybeRestart(self, reason):
1008 # Dispatch to the correct method based upon the configured on_{reason}
1009 # behaviour.
1010 actions = {"destroy" : self.destroy,
1011 "restart" : self._restart,
1012 "preserve" : self._preserve,
1013 "rename-restart" : self._renameRestart}
1015 action_conf = {
1016 'poweroff': 'actions_after_shutdown',
1017 'reboot': 'actions_after_reboot',
1018 'crash': 'actions_after_crash',
1021 action_target = self.info.get(action_conf.get(reason))
1022 func = actions.get(action_target, None)
1023 if func and callable(func):
1024 func()
1025 else:
1026 self.destroy() # default to destroy
1028 def _renameRestart(self):
1029 self._restart(True)
1031 def _restart(self, rename = False):
1032 """Restart the domain after it has exited.
1034 @param rename True if the old domain is to be renamed and preserved,
1035 False if it is to be destroyed.
1036 """
1037 from xen.xend import XendDomain
1039 self._configureBootloader()
1040 config = self.sxpr()
1042 if self._infoIsSet('cpus') and len(self.info['cpus']) != 0:
1043 config.append(['cpus', reduce(lambda x, y: str(x) + "," + str(y),
1044 self.info['cpus'])])
1046 if self._readVm(RESTART_IN_PROGRESS):
1047 log.error('Xend failed during restart of domain %s. '
1048 'Refusing to restart to avoid loops.',
1049 str(self.domid))
1050 self.destroy()
1051 return
1053 old_domid = self.domid
1054 self._writeVm(RESTART_IN_PROGRESS, 'True')
1056 now = time.time()
1057 rst = self._readVm('xend/previous_restart_time')
1058 if rst:
1059 rst = float(rst)
1060 timeout = now - rst
1061 if timeout < MINIMUM_RESTART_TIME:
1062 log.error(
1063 'VM %s restarting too fast (%f seconds since the last '
1064 'restart). Refusing to restart to avoid loops.',
1065 self.info['name_label'], timeout)
1066 self.destroy()
1067 return
1069 self._writeVm('xend/previous_restart_time', str(now))
1071 try:
1072 if rename:
1073 self._preserveForRestart()
1074 else:
1075 self._unwatchVm()
1076 self.destroyDomain()
1078 # new_dom's VM will be the same as this domain's VM, except where
1079 # the rename flag has instructed us to call preserveForRestart.
1080 # In that case, it is important that we remove the
1081 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1082 # once the new one is available.
1084 new_dom = None
1085 try:
1086 new_dom = XendDomain.instance().domain_create(config)
1087 new_dom.unpause()
1088 rst_cnt = self._readVm('xend/restart_count')
1089 rst_cnt = int(rst_cnt) + 1
1090 self._writeVm('xend/restart_count', str(rst_cnt))
1091 new_dom._removeVm(RESTART_IN_PROGRESS)
1092 except:
1093 if new_dom:
1094 new_dom._removeVm(RESTART_IN_PROGRESS)
1095 new_dom.destroy()
1096 else:
1097 self._removeVm(RESTART_IN_PROGRESS)
1098 raise
1099 except:
1100 log.exception('Failed to restart domain %s.', str(old_domid))
1102 def _preserveForRestart(self):
1103 """Preserve a domain that has been shut down, by giving it a new UUID,
1104 cloning the VM details, and giving it a new name. This allows us to
1105 keep this domain for debugging, but restart a new one in its place
1106 preserving the restart semantics (name and UUID preserved).
1107 """
1109 new_uuid = uuid.createString()
1110 new_name = 'Domain-%s' % new_uuid
1111 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1112 self.info['name_label'], self.domid, self.info['uuid'],
1113 new_name, new_uuid)
1114 self._unwatchVm()
1115 self._releaseDevices()
1116 self.info['name_label'] = new_name
1117 self.info['uuid'] = new_uuid
1118 self.vmpath = XS_VMROOT + new_uuid
1119 self._storeVmDetails()
1120 self._preserve()
1123 def _preserve(self):
1124 log.info("Preserving dead domain %s (%d).", self.info['name_label'],
1125 self.domid)
1126 self._unwatchVm()
1127 self.storeDom('xend/shutdown_completed', 'True')
1128 self._stateSet(DOM_STATE_HALTED)
1131 # Debugging ..
1134 def dumpCore(self, corefile = None):
1135 """Create a core dump for this domain. Nothrow guarantee."""
1137 try:
1138 if not corefile:
1139 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1140 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1141 self.info['name_label'], self.domid)
1143 if os.path.isdir(corefile):
1144 raise XendError("Cannot dump core in a directory: %s" %
1145 corefile)
1147 xc.domain_dumpcore(self.domid, corefile)
1148 except RuntimeError, ex:
1149 corefile_incomp = corefile+'-incomplete'
1150 os.rename(corefile, corefile_incomp)
1151 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1152 self.domid, self.info['name_label'])
1153 raise XendError("Failed to dump core: %s" % str(ex))
1156 # Device creation/deletion functions
1159 def _createDevice(self, deviceClass, devConfig):
1160 return self.getDeviceController(deviceClass).createDevice(devConfig)
1162 def _waitForDevice(self, deviceClass, devid):
1163 return self.getDeviceController(deviceClass).waitForDevice(devid)
1165 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1166 return self.getDeviceController(deviceClass).reconfigureDevice(
1167 devid, devconfig)
1169 def _createDevices(self):
1170 """Create the devices for a vm.
1172 @raise: VmError for invalid devices
1173 """
1174 for (devclass, config) in self.info.get('devices', {}).values():
1175 if devclass in XendDevices.valid_devices():
1176 log.info("createDevice: %s : %s" % (devclass, config))
1177 self._createDevice(devclass, config)
1179 if self.image:
1180 self.image.createDeviceModel()
1182 def _releaseDevices(self):
1183 """Release all domain's devices. Nothrow guarantee."""
1185 while True:
1186 t = xstransact("%s/device" % self.dompath)
1187 for devclass in XendDevices.valid_devices():
1188 for dev in t.list(devclass):
1189 try:
1190 t.remove(dev)
1191 except:
1192 # Log and swallow any exceptions in removal --
1193 # there's nothing more we can do.
1194 log.exception(
1195 "Device release failed: %s; %s; %s",
1196 self.info['name_label'], devclass, dev)
1197 if t.commit():
1198 break
1200 def getDeviceController(self, name):
1201 """Get the device controller for this domain, and if it
1202 doesn't exist, create it.
1204 @param name: device class name
1205 @type name: string
1206 @rtype: subclass of DevController
1207 """
1208 if name not in self._deviceControllers:
1209 devController = XendDevices.make_controller(name, self)
1210 if not devController:
1211 raise XendError("Unknown device type: %s" % name)
1212 self._deviceControllers[name] = devController
1214 return self._deviceControllers[name]
1217 # Migration functions (public)
1220 def testMigrateDevices(self, network, dst):
1221 """ Notify all device about intention of migration
1222 @raise: XendError for a device that cannot be migrated
1223 """
1224 for (n, c) in self.info.all_devices_sxpr():
1225 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1226 if rc != 0:
1227 raise XendError("Device of type '%s' refuses migration." % n)
1229 def migrateDevices(self, network, dst, step, domName=''):
1230 """Notify the devices about migration
1231 """
1232 ctr = 0
1233 try:
1234 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1235 self.migrateDevice(dev_type, dev_conf, network, dst,
1236 step, domName)
1237 ctr = ctr + 1
1238 except:
1239 for dev_type, dev_conf in self.info.all_devices_sxpr():
1240 if ctr == 0:
1241 step = step - 1
1242 ctr = ctr - 1
1243 self._recoverMigrateDevice(dev_type, dev_conf, network,
1244 dst, step, domName)
1245 raise
1247 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1248 step, domName=''):
1249 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1250 network, dst, step, domName)
1252 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1253 dst, step, domName=''):
1254 return self.getDeviceController(deviceClass).recover_migrate(
1255 deviceConfig, network, dst, step, domName)
1258 ## private:
1260 def _constructDomain(self):
1261 """Construct the domain.
1263 @raise: VmError on error
1264 """
1266 log.debug('XendDomainInfo.constructDomain')
1268 image_cfg = self.info.get('image', {})
1269 hvm = image_cfg.has_key('hvm')
1271 if hvm:
1272 info = xc.xeninfo()
1273 if 'hvm' not in info['xen_caps']:
1274 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1275 "supported by your CPU and enabled in your "
1276 "BIOS?")
1278 self.domid = xc.domain_create(
1279 domid = 0,
1280 ssidref = security.get_security_info(self.info, 'ssidref'),
1281 handle = uuid.fromString(self.info['uuid']),
1282 hvm = int(hvm))
1284 if self.domid < 0:
1285 raise VmError('Creating domain failed: name=%s' %
1286 self.info['name_label'])
1288 self.dompath = GetDomainPath(self.domid)
1290 self._recreateDom()
1292 # Set maximum number of vcpus in domain
1293 xc.domain_max_vcpus(self.domid, int(self.info['vcpus_number']))
1296 def _introduceDomain(self):
1297 assert self.domid is not None
1298 assert self.store_mfn is not None
1299 assert self.store_port is not None
1301 try:
1302 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1303 except RuntimeError, exn:
1304 raise XendError(str(exn))
1307 def _initDomain(self):
1308 log.debug('XendDomainInfo.initDomain: %s %s',
1309 self.domid,
1310 self.info['cpu_weight'])
1312 # if we have a boot loader but no image, then we need to set things
1313 # up by running the boot loader non-interactively
1314 if self.info.get('bootloader') and self.info.get('image'):
1315 self._configureBootloader()
1317 if not self._infoIsSet('image'):
1318 raise VmError('Missing image in configuration')
1320 try:
1321 self.image = image.create(self,
1322 self.info,
1323 self.info['image'],
1324 self.info['devices'])
1326 localtime = self.info.get('localtime', 0)
1327 if localtime is not None and localtime == 1:
1328 xc.domain_set_time_offset(self.domid)
1330 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1332 # repin domain vcpus if a restricted cpus list is provided
1333 # this is done prior to memory allocation to aide in memory
1334 # distribution for NUMA systems.
1335 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1336 for v in range(0, self.info['max_vcpu_id']+1):
1337 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1339 # Use architecture- and image-specific calculations to determine
1340 # the various headrooms necessary, given the raw configured
1341 # values. maxmem, memory, and shadow are all in KiB.
1342 maxmem = self.image.getRequiredAvailableMemory(
1343 self.info['memory_static_min'] * 1024)
1344 memory = self.image.getRequiredAvailableMemory(
1345 self.info['memory_static_max'] * 1024)
1346 shadow = self.image.getRequiredShadowMemory(
1347 self.info['shadow_memory'] * 1024,
1348 self.info['memory_static_max'] * 1024)
1350 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1351 # takes MiB and we must not round down and end up under-providing.
1352 shadow = ((shadow + 1023) / 1024) * 1024
1354 # set memory limit
1355 xc.domain_setmaxmem(self.domid, maxmem)
1357 # Make sure there's enough RAM available for the domain
1358 balloon.free(memory + shadow)
1360 # Set up the shadow memory
1361 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1362 self.info['shadow_memory'] = shadow_cur
1364 self._createChannels()
1366 channel_details = self.image.createImage()
1368 self.store_mfn = channel_details['store_mfn']
1369 if 'console_mfn' in channel_details:
1370 self.console_mfn = channel_details['console_mfn']
1372 self._introduceDomain()
1374 self._createDevices()
1376 if self.info['bootloader']:
1377 self.image.cleanupBootloading()
1379 self.info['start_time'] = time.time()
1381 self._stateSet(DOM_STATE_RUNNING)
1382 except RuntimeError, exn:
1383 log.exception("XendDomainInfo.initDomain: exception occurred")
1384 if self.info['bootloader'] not in (None, 'kernel_external') \
1385 and self.image is not None:
1386 self.image.cleanupBootloading()
1387 raise VmError(str(exn))
1390 def cleanupDomain(self):
1391 """Cleanup domain resources; release devices. Idempotent. Nothrow
1392 guarantee."""
1394 self.refresh_shutdown_lock.acquire()
1395 try:
1396 self.unwatchShutdown()
1397 self._releaseDevices()
1399 if self.image:
1400 try:
1401 self.image.destroy()
1402 except:
1403 log.exception(
1404 "XendDomainInfo.cleanup: image.destroy() failed.")
1405 self.image = None
1407 try:
1408 self._removeDom()
1409 except:
1410 log.exception("Removing domain path failed.")
1412 self._stateSet(DOM_STATE_HALTED)
1413 finally:
1414 self.refresh_shutdown_lock.release()
1417 def unwatchShutdown(self):
1418 """Remove the watch on the domain's control/shutdown node, if any.
1419 Idempotent. Nothrow guarantee. Expects to be protected by the
1420 refresh_shutdown_lock."""
1422 try:
1423 try:
1424 if self.shutdownWatch:
1425 self.shutdownWatch.unwatch()
1426 finally:
1427 self.shutdownWatch = None
1428 except:
1429 log.exception("Unwatching control/shutdown failed.")
1431 def waitForShutdown(self):
1432 self.state_updated.acquire()
1433 try:
1434 while self.state in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1435 self.state_updated.wait()
1436 finally:
1437 self.state_updated.release()
1441 # TODO: recategorise - called from XendCheckpoint
1444 def completeRestore(self, store_mfn, console_mfn):
1446 log.debug("XendDomainInfo.completeRestore")
1448 self.store_mfn = store_mfn
1449 self.console_mfn = console_mfn
1451 self._introduceDomain()
1452 self._storeDomDetails()
1453 self._registerWatches()
1454 self.refreshShutdown()
1456 log.debug("XendDomainInfo.completeRestore done")
1459 def _endRestore(self):
1460 self.setResume(False)
1463 # VM Destroy
1466 def destroy(self):
1467 """Cleanup VM and destroy domain. Nothrow guarantee."""
1469 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1471 self._cleanupVm()
1472 if self.dompath is not None:
1473 self.destroyDomain()
1476 def destroyDomain(self):
1477 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1479 try:
1480 if self.domid is not None:
1481 xc.domain_destroy(self.domid)
1482 self.domid = None
1483 for state in DOM_STATES_OLD:
1484 self.info[state] = 0
1485 except:
1486 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1488 self.cleanupDomain()
1492 # Channels for xenstore and console
1495 def _createChannels(self):
1496 """Create the channels to the domain.
1497 """
1498 self.store_port = self._createChannel()
1499 self.console_port = self._createChannel()
1502 def _createChannel(self):
1503 """Create an event channel to the domain.
1504 """
1505 try:
1506 return xc.evtchn_alloc_unbound(domid=self.domid, remote_dom=0)
1507 except:
1508 log.exception("Exception in alloc_unbound(%d)", self.domid)
1509 raise
1512 # Bootloader configuration
1515 def _configureBootloader(self):
1516 """Run the bootloader if we're configured to do so."""
1517 if not self.info.get('bootloader'):
1518 return
1519 blcfg = None
1520 # FIXME: this assumes that we want to use the first disk device
1521 for devuuid, (devtype, devinfo) in self.info.all_devices_sxpr():
1522 if not devtype or not devinfo or devtype not in ('vbd', 'tap'):
1523 continue
1524 disk = devinfo.get('uname')
1525 if disk is None:
1526 continue
1527 fn = blkdev_uname_to_file(disk)
1528 blcfg = bootloader(self.info['bootloader'], fn, 1,
1529 self.info['bootloader_args'],
1530 self.info['image'])
1531 break
1532 if blcfg is None:
1533 msg = "Had a bootloader specified, but can't find disk"
1534 log.error(msg)
1535 raise VmError(msg)
1537 self.info.update_with_image_sxp(blcfg)
1540 # VM Functions
1543 def _readVMDetails(self, params):
1544 """Read the specified parameters from the store.
1545 """
1546 try:
1547 return self._gatherVm(*params)
1548 except ValueError:
1549 # One of the int/float entries in params has a corresponding store
1550 # entry that is invalid. We recover, because older versions of
1551 # Xend may have put the entry there (memory/target, for example),
1552 # but this is in general a bad situation to have reached.
1553 log.exception(
1554 "Store corrupted at %s! Domain %d's configuration may be "
1555 "affected.", self.vmpath, self.domid)
1556 return []
1558 def _cleanupVm(self):
1559 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1561 self._unwatchVm()
1563 try:
1564 self._removeVm()
1565 except:
1566 log.exception("Removing VM path failed.")
1569 def checkLiveMigrateMemory(self):
1570 """ Make sure there's enough memory to migrate this domain """
1571 overhead_kb = 0
1572 if arch.type == "x86":
1573 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1574 # the minimum that Xen would allocate if no value were given.
1575 overhead_kb = self.info['vcpus_number'] * 1024 + \
1576 self.info['memory_static_max'] * 4
1577 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1578 # The domain might already have some shadow memory
1579 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1580 if overhead_kb > 0:
1581 balloon.free(overhead_kb)
1583 def _unwatchVm(self):
1584 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1585 guarantee."""
1586 try:
1587 try:
1588 if self.vmWatch:
1589 self.vmWatch.unwatch()
1590 finally:
1591 self.vmWatch = None
1592 except:
1593 log.exception("Unwatching VM path failed.")
1595 def testDeviceComplete(self):
1596 """ For Block IO migration safety we must ensure that
1597 the device has shutdown correctly, i.e. all blocks are
1598 flushed to disk
1599 """
1600 start = time.time()
1601 while True:
1602 test = 0
1603 diff = time.time() - start
1604 for i in self.getDeviceController('vbd').deviceIDs():
1605 test = 1
1606 log.info("Dev %s still active, looping...", i)
1607 time.sleep(0.1)
1609 if test == 0:
1610 break
1611 if diff >= MIGRATE_TIMEOUT:
1612 log.info("Dev still active but hit max loop timeout")
1613 break
1615 def _storeVmDetails(self):
1616 to_store = {}
1618 for key in XendConfig.LEGACY_XENSTORE_VM_PARAMS:
1619 info_key = XendConfig.LEGACY_CFG_TO_XENAPI_CFG.get(key, key)
1620 if self._infoIsSet(info_key):
1621 to_store[key] = str(self.info[info_key])
1623 if self.info.get('image'):
1624 image_sxpr = self.info.image_sxpr()
1625 if image_sxpr:
1626 to_store['image'] = sxp.to_string(image_sxpr)
1628 if self._infoIsSet('security'):
1629 secinfo = self.info['security']
1630 to_store['security'] = sxp.to_string(secinfo)
1631 for idx in range(0, len(secinfo)):
1632 if secinfo[idx][0] == 'access_control':
1633 to_store['security/access_control'] = sxp.to_string(
1634 [secinfo[idx][1], secinfo[idx][2]])
1635 for aidx in range(1, len(secinfo[idx])):
1636 if secinfo[idx][aidx][0] == 'label':
1637 to_store['security/access_control/label'] = \
1638 secinfo[idx][aidx][1]
1639 if secinfo[idx][aidx][0] == 'policy':
1640 to_store['security/access_control/policy'] = \
1641 secinfo[idx][aidx][1]
1642 if secinfo[idx][0] == 'ssidref':
1643 to_store['security/ssidref'] = str(secinfo[idx][1])
1646 if not self._readVm('xend/restart_count'):
1647 to_store['xend/restart_count'] = str(0)
1649 log.debug("Storing VM details: %s", to_store)
1651 self._writeVm(to_store)
1652 self._setVmPermissions()
1655 def _setVmPermissions(self):
1656 """Allow the guest domain to read its UUID. We don't allow it to
1657 access any other entry, for security."""
1658 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1659 { 'dom' : self.domid,
1660 'read' : True,
1661 'write' : False })
1664 # Utility functions
1667 def _stateSet(self, state):
1668 self.state_updated.acquire()
1669 try:
1670 if self.state != state:
1671 self.state = state
1672 self.state_updated.notifyAll()
1673 finally:
1674 self.state_updated.release()
1676 def _infoIsSet(self, name):
1677 return name in self.info and self.info[name] is not None
1679 def _checkName(self, name):
1680 """Check if a vm name is valid. Valid names contain alphabetic
1681 characters, digits, or characters in '_-.:/+'.
1682 The same name cannot be used for more than one vm at the same time.
1684 @param name: name
1685 @raise: VmError if invalid
1686 """
1687 from xen.xend import XendDomain
1689 if name is None or name == '':
1690 raise VmError('Missing VM Name')
1692 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1693 raise VmError('Invalid VM Name')
1695 dom = XendDomain.instance().domain_lookup_nr(name)
1696 if dom and dom.info['uuid'] != self.info['uuid']:
1697 raise VmError("VM name '%s' already exists as domain %s" %
1698 (name, str(dom.domid)))
1701 def update(self, info = None, refresh = True):
1702 """Update with info from xc.domain_getinfo().
1703 """
1704 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1705 str(self.domid))
1707 if not info:
1708 info = dom_get(self.domid)
1709 if not info:
1710 return
1712 #manually update ssidref / security fields
1713 if security.on() and info.has_key('ssidref'):
1714 if (info['ssidref'] != 0) and self.info.has_key('security'):
1715 security_field = self.info['security']
1716 if not security_field:
1717 #create new security element
1718 self.info.update({'security':
1719 [['ssidref', str(info['ssidref'])]]})
1721 #ssidref field not used any longer
1722 if 'ssidref' in info:
1723 info.pop('ssidref')
1725 # make sure state is reset for info
1726 # TODO: we should eventually get rid of old_dom_states
1728 self.info.update_config(info)
1730 if refresh:
1731 self.refreshShutdown(info)
1733 log.trace("XendDomainInfo.update done on domain %s: %s",
1734 str(self.domid), self.info)
1736 def sxpr(self, ignore_store = False):
1737 result = self.info.to_sxp(domain = self,
1738 ignore_devices = ignore_store)
1740 if not ignore_store and self.dompath:
1741 vnc_port = self.readDom('console/vnc-port')
1742 if vnc_port is not None:
1743 result.append(['device',
1744 ['console', ['vnc-port', str(vnc_port)]]])
1746 return result
1748 # Xen API
1749 # ----------------------------------------------------------------
1751 def get_uuid(self):
1752 dom_uuid = self.info.get('uuid')
1753 if not dom_uuid: # if it doesn't exist, make one up
1754 dom_uuid = uuid.createString()
1755 self.info['uuid'] = dom_uuid
1756 return dom_uuid
1758 def get_memory_static_max(self):
1759 return self.info.get('memory_static_max')
1760 def get_memory_static_min(self):
1761 return self.info.get('memory_static_min')
1762 def get_memory_dynamic_max(self):
1763 return self.info.get('memory_dynamic_min')
1764 def get_memory_dynamic_min(self):
1765 return self.info.get('memory_dynamic_max')
1768 def get_vcpus_policy(self):
1769 sched_id = xc.sched_id_get()
1770 if sched_id == xen.lowlevel.xc.XEN_SCHEDULER_SEDF:
1771 return 'sedf'
1772 elif sched_id == xen.lowlevel.xc.XEN_SCHEDULER_CREDIT:
1773 return 'credit'
1774 else:
1775 return 'unknown'
1776 def get_vcpus_params(self):
1777 return '' # TODO
1778 def get_power_state(self):
1779 return XEN_API_VM_POWER_STATE[self.state]
1780 def get_bios_boot(self):
1781 return '' # TODO
1782 def get_platform_std_vga(self):
1783 return self.info.get('platform_std_vga', 0)
1784 def get_platform_keymap(self):
1785 return ''
1786 def get_platform_serial(self):
1787 return self.info.get('platform_serial', '')
1788 def get_platform_localtime(self):
1789 return self.info.get('platform_localtime', 0)
1790 def get_platform_clock_offset(self):
1791 return self.info.get('platform_clock_offset', 0)
1792 def get_platform_enable_audio(self):
1793 return self.info.get('platform_enable_audio', 0)
1794 def get_platform_keymap(self):
1795 return self.info.get('platform_keymap', '')
1796 def get_builder(self):
1797 return self.info.get('builder', 0)
1798 def get_boot_method(self):
1799 return self.info.get('boot_method', '')
1800 def get_kernel_image(self):
1801 return self.info.get('kernel_kernel', '')
1802 def get_kernel_initrd(self):
1803 return self.info.get('kernel_initrd', '')
1804 def get_kernel_args(self):
1805 return self.info.get('kernel_args', '')
1806 def get_grub_cmdline(self):
1807 return '' # TODO
1808 def get_pci_bus(self):
1809 return 0 # TODO
1810 def get_tools_version(self):
1811 return {} # TODO
1812 def get_other_config(self):
1813 return {} # TODO
1815 def get_on_shutdown(self):
1816 after_shutdown = self.info.get('action_after_shutdown')
1817 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
1818 return XEN_API_ON_NORMAL_EXIT[-1]
1819 return after_shutdown
1821 def get_on_reboot(self):
1822 after_reboot = self.info.get('action_after_reboot')
1823 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
1824 return XEN_API_ON_NORMAL_EXIT[-1]
1825 return after_reboot
1827 def get_on_suspend(self):
1828 # TODO: not supported
1829 after_suspend = self.info.get('action_after_suspend')
1830 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
1831 return XEN_API_ON_NORMAL_EXIT[-1]
1832 return after_suspend
1834 def get_on_crash(self):
1835 after_crash = self.info.get('action_after_crash')
1836 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
1837 return XEN_API_ON_CRASH_BEHAVIOUR[0]
1838 return after_crash
1840 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
1841 """ Get's a device configuration either from XendConfig or
1842 from the DevController.
1844 @param dev_class: device class, either, 'vbd' or 'vif'
1845 @param dev_uuid: device UUID
1847 @rtype: dictionary
1848 """
1849 dev_type_config = self.info['devices'].get(dev_uuid)
1851 # shortcut if the domain isn't started because
1852 # the devcontrollers will have no better information
1853 # than XendConfig.
1854 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
1855 if dev_type_config:
1856 return copy.deepcopy(dev_type_config[1])
1857 return None
1859 # instead of using dev_class, we use the dev_type
1860 # that is from XendConfig.
1861 # This will accomdate 'tap' as well as 'vbd'
1862 dev_type = dev_type_config[0]
1864 controller = self.getDeviceController(dev_type)
1865 if not controller:
1866 return None
1868 all_configs = controller.getAllDeviceConfigurations()
1869 if not all_configs:
1870 return None
1872 dev_config = copy.deepcopy(dev_type_config[1])
1873 for _devid, _devcfg in all_configs.items():
1874 if _devcfg.get('uuid') == dev_uuid:
1875 dev_config.update(_devcfg)
1876 dev_config['id'] = _devid
1877 return dev_config
1879 return dev_config
1881 def get_dev_xenapi_config(self, dev_class, dev_uuid):
1882 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
1883 if not config:
1884 return {}
1886 config['VM'] = self.get_uuid()
1888 if dev_class == 'vif':
1889 if not config.has_key('name'):
1890 config['name'] = config.get('vifname', '')
1891 if not config.has_key('MAC'):
1892 config['MAC'] = config.get('mac', '')
1893 if not config.has_key('type'):
1894 config['type'] = 'paravirtualised'
1895 if not config.has_key('device'):
1896 devid = config.get('id')
1897 if devid != None:
1898 config['device'] = 'eth%d' % devid
1899 else:
1900 config['device'] = ''
1902 config['network'] = '' # Invalid for Xend
1903 config['MTU'] = 1500 # TODO
1904 config['network_read_kbs'] = 0.0
1905 config['network_write_kbs'] = 0.0
1906 config['IO_bandwidth_incoming_kbs'] = 0.0
1907 config['IO_bandwidth_outgoing_kbs'] = 0.0
1909 if dev_class == 'vbd':
1910 config['VDI'] = '' # TODO
1911 config['device'] = config.get('dev', '')
1912 config['driver'] = 'paravirtualised' # TODO
1913 config['image'] = config.get('uname', '')
1914 config['IO_bandwidth_incoming_kbs'] = 0.0
1915 config['IO_bandwidth_outgoing_kbs'] = 0.0
1916 if config['mode'] == 'r':
1917 config['mode'] = 'RO'
1918 else:
1919 config['mode'] = 'RW'
1921 if dev_class == 'vtpm':
1922 config['driver'] = 'paravirtualised' # TODO
1924 return config
1926 def get_dev_property(self, dev_class, dev_uuid, field):
1927 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
1928 try:
1929 return config[field]
1930 except KeyError:
1931 raise XendError('Invalid property for device: %s' % field)
1933 def get_vcpus_util(self):
1934 # TODO: this returns the total accum cpu time, rather than util
1935 # TODO: spec says that key is int, however, python does not allow
1936 # non-string keys to dictionaries.
1937 vcpu_util = {}
1938 if 'max_vcpu_id' in self.info and self.domid != None:
1939 for i in range(0, self.info['max_vcpu_id']+1):
1940 info = xc.vcpu_getinfo(self.domid, i)
1941 vcpu_util[str(i)] = info['cpu_time']/1000000000.0
1943 return vcpu_util
1945 def get_vifs(self):
1946 return self.info.get('vif_refs', [])
1948 def get_vbds(self):
1949 return self.info.get('vbd_refs', [])
1951 def get_vtpms(self):
1952 return self.info.get('vtpm_refs', [])
1954 def create_vbd(self, xenapi_vbd):
1955 """Create a VBD device from the passed struct in Xen API format.
1957 @return: uuid of the device
1958 @rtype: string
1959 """
1961 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
1962 if not dev_uuid:
1963 raise XendError('Failed to create device')
1965 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1966 sxpr = self.info.device_sxpr(dev_uuid)
1967 devid = self.getDeviceController('vbd').createDevice(sxpr)
1968 raise XendError("Device creation failed")
1970 return dev_uuid
1972 def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
1973 """Create a VBD using a VDI from XendStorageRepository.
1975 @param xenapi_vbd: vbd struct from the Xen API
1976 @param vdi_image_path: VDI UUID
1977 @rtype: string
1978 @return: uuid of the device
1979 """
1980 xenapi_vbd['image'] = vdi_image_path
1981 log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
1982 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
1983 if not dev_uuid:
1984 raise XendError('Failed to create device')
1986 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1987 sxpr = self.info.device_sxpr(dev_uuid)
1988 devid = self.getDeviceController('tap').createDevice(sxpr)
1989 raise XendError("Device creation failed")
1991 return dev_uuid
1993 def create_vif(self, xenapi_vif):
1994 """Create VIF device from the passed struct in Xen API format.
1996 @param xenapi_vif: Xen API VIF Struct.
1997 @rtype: string
1998 @return: UUID
1999 """
2000 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
2001 if not dev_uuid:
2002 raise XendError('Failed to create device')
2004 if self.state in (DOM_STATE_HALTED,):
2005 sxpr = self.info.device_sxpr(dev_uuid)
2006 devid = self.getDeviceController('vif').createDevice(sxpr)
2007 raise XendError("Device creation failed")
2009 return dev_uuid
2011 def create_vtpm(self, xenapi_vtpm):
2012 """Create a VTPM device from the passed struct in Xen API format.
2014 @return: uuid of the device
2015 @rtype: string
2016 """
2018 if self.state not in (DOM_STATE_HALTED,):
2019 raise VmError("Can only add vTPM to a halted domain.")
2020 if self.get_vtpms() != []:
2021 raise VmError('Domain already has a vTPM.')
2022 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
2023 if not dev_uuid:
2024 raise XendError('Failed to create device')
2026 return dev_uuid
2028 def has_device(self, dev_class, dev_uuid):
2029 return (dev_uuid in self.info['%s_refs' % dev_class])
2031 """
2032 def stateChar(name):
2033 if name in self.info:
2034 if self.info[name]:
2035 return name[0]
2036 else:
2037 return '-'
2038 else:
2039 return '?'
2041 state = reduce(lambda x, y: x + y, map(stateChar, DOM_STATES_OLD))
2043 sxpr.append(['state', state])
2045 if self.store_mfn:
2046 sxpr.append(['store_mfn', self.store_mfn])
2047 if self.console_mfn:
2048 sxpr.append(['console_mfn', self.console_mfn])
2049 """
2051 def __str__(self):
2052 return '<domain id=%s name=%s memory=%s state=%s>' % \
2053 (str(self.domid), self.info['name_label'],
2054 str(self.info['memory_static_min']), DOM_STATES[self.state])
2056 __repr__ = __str__