ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 12642:33951d547223

Plumb the "start_paused" flag through for VM.start and VM.resume. Do not
unpause the VM on start by default. This should fix problems seen recently
whereby devices are not attached to the VM by the time they boot, as
xm create was expecting to be able to wait for the devices before unpausing.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author Ewan Mellor <ewan@xensource.com>
date Tue Nov 28 17:28:43 2006 +0000 (2006-11-28)
parents 221b8ea81823
children bd5ebf33f222
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
43 from xen.xend.XendBootloader import bootloader
44 from xen.xend.XendConfig import XendConfig
45 from xen.xend.XendError import XendError, VmError
46 from xen.xend.XendDevices import XendDevices
47 from xen.xend.xenstore.xstransact import xstransact, complete
48 from xen.xend.xenstore.xsutil import GetDomainPath, IntroduceDomain
49 from xen.xend.xenstore.xswatch import xswatch
50 from xen.xend.XendConstants import *
51 from xen.xend.XendAPIConstants import *
53 MIGRATE_TIMEOUT = 30.0
55 xc = xen.lowlevel.xc.xc()
56 xroot = XendRoot.instance()
58 log = logging.getLogger("xend.XendDomainInfo")
59 #log.setLevel(logging.TRACE)
61 ##
62 # All parameters of VMs that may be configured on-the-fly, or at start-up.
63 #
64 VM_CONFIG_PARAMS = [
65 ('name', str),
66 ('on_poweroff', str),
67 ('on_reboot', str),
68 ('on_crash', str),
69 ]
72 ##
73 # Configuration entries that we expect to round-trip -- be read from the
74 # config file or xc, written to save-files (i.e. through sxpr), and reused as
75 # config on restart or restore, all without munging. Some configuration
76 # entries are munged for backwards compatibility reasons, or because they
77 # don't come out of xc in the same form as they are specified in the config
78 # file, so those are handled separately.
79 ROUNDTRIPPING_CONFIG_ENTRIES = [
80 ('uuid', str),
81 ('vcpus', int),
82 ('vcpu_avail', int),
83 ('cpu_cap', int),
84 ('cpu_weight', int),
85 ('memory', int),
86 ('shadow_memory', int),
87 ('maxmem', int),
88 ('bootloader', str),
89 ('bootloader_args', str),
90 ('features', str),
91 ('localtime', int),
92 ]
94 ROUNDTRIPPING_CONFIG_ENTRIES += VM_CONFIG_PARAMS
97 ##
98 # All entries written to the store. This is VM_CONFIG_PARAMS, plus those
99 # entries written to the store that cannot be reconfigured on-the-fly.
100 #
101 VM_STORE_ENTRIES = [
102 ('uuid', str),
103 ('vcpus', int),
104 ('vcpu_avail', int),
105 ('memory', int),
106 ('shadow_memory', int),
107 ('maxmem', int),
108 ('start_time', float),
109 ('on_xend_start', str),
110 ('on_xend_stop', str),
111 ]
113 VM_STORE_ENTRIES += VM_CONFIG_PARAMS
116 #
117 # There are a number of CPU-related fields:
118 #
119 # vcpus: the number of virtual CPUs this domain is configured to use.
120 # vcpu_avail: a bitmap telling the guest domain whether it may use each of
121 # its VCPUs. This is translated to
122 # <dompath>/cpu/<id>/availability = {online,offline} for use
123 # by the guest domain.
124 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
125 # CPUs that that VCPU may use.
126 # cpu: a configuration setting requesting that VCPU 0 is pinned to
127 # the specified physical CPU.
128 #
129 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
130 # across save, restore, migrate, and restart). The other settings are only
131 # specific to the domain, so are lost when the VM moves.
132 #
135 def create(config):
136 """Creates and start a VM using the supplied configuration.
137 (called from XMLRPCServer directly)
139 @param config: A configuration object involving lists of tuples.
140 @type config: list of lists, eg ['vm', ['image', 'xen.gz']]
142 @rtype: XendDomainInfo
143 @return: A up and running XendDomainInfo instance
144 @raise VmError: Invalid configuration or failure to start.
145 """
147 log.debug("XendDomainInfo.create(%s)", config)
148 vm = XendDomainInfo(XendConfig(sxp = config))
149 try:
150 vm.start()
151 except:
152 log.exception('Domain construction failed')
153 vm.destroy()
154 raise
156 return vm
158 def recreate(info, priv):
159 """Create the VM object for an existing domain. The domain must not
160 be dying, as the paths in the store should already have been removed,
161 and asking us to recreate them causes problems.
163 @param xeninfo: Parsed configuration
164 @type xeninfo: Dictionary
165 @param priv: TODO, unknown, something to do with memory
166 @type priv: bool
168 @rtype: XendDomainInfo
169 @return: A up and running XendDomainInfo instance
170 @raise VmError: Invalid configuration.
171 @raise XendError: Errors with configuration.
172 """
174 log.debug("XendDomainInfo.recreate(%s)", info)
176 assert not info['dying']
178 xeninfo = XendConfig(cfg = info)
179 domid = xeninfo['domid']
180 uuid1 = xeninfo['handle']
181 xeninfo['uuid'] = uuid.toString(uuid1)
182 needs_reinitialising = False
184 dompath = GetDomainPath(domid)
185 if not dompath:
186 raise XendError('No domain path in store for existing '
187 'domain %d' % domid)
189 log.info("Recreating domain %d, UUID %s. at %s" %
190 (domid, xeninfo['uuid'], dompath))
192 # need to verify the path and uuid if not Domain-0
193 # if the required uuid and vm aren't set, then that means
194 # we need to recreate the dom with our own values
195 #
196 # NOTE: this is probably not desirable, really we should just
197 # abort or ignore, but there may be cases where xenstore's
198 # entry disappears (eg. xenstore-rm /)
199 #
200 try:
201 vmpath = xstransact.Read(dompath, "vm")
202 if not vmpath:
203 log.warn('/local/domain/%d/vm is missing. recreate is '
204 'confused, trying our best to recover' % domid)
205 needs_reinitialising = True
206 raise XendError('reinit')
208 uuid2_str = xstransact.Read(vmpath, "uuid")
209 if not uuid2_str:
210 log.warn('%s/uuid/ is missing. recreate is confused, '
211 'trying our best to recover' % vmpath)
212 needs_reinitialising = True
213 raise XendError('reinit')
215 uuid2 = uuid.fromString(uuid2_str)
216 if uuid1 != uuid2:
217 log.warn('UUID in /vm does not match the UUID in /dom/%d.'
218 'Trying out best to recover' % domid)
219 needs_reinitialising = True
220 except XendError:
221 pass # our best shot at 'goto' in python :)
223 vm = XendDomainInfo(xeninfo, domid, dompath, augment = True, priv = priv)
225 if needs_reinitialising:
226 vm._recreateDom()
227 vm._removeVm()
228 vm._storeVmDetails()
229 vm._storeDomDetails()
231 vm._registerWatches()
232 vm.refreshShutdown(xeninfo)
233 return vm
236 def restore(config):
237 """Create a domain and a VM object to do a restore.
239 @param config: Domain configuration object
240 @type config: list of lists. (see C{create})
242 @rtype: XendDomainInfo
243 @return: A up and running XendDomainInfo instance
244 @raise VmError: Invalid configuration or failure to start.
245 @raise XendError: Errors with configuration.
246 """
248 log.debug("XendDomainInfo.restore(%s)", config)
249 vm = XendDomainInfo(XendConfig(sxp = config), resume = True)
250 try:
251 vm.resume()
252 return vm
253 except:
254 vm.destroy()
255 raise
257 def createDormant(xeninfo):
258 """Create a dormant/inactive XenDomainInfo without creating VM.
259 This is for creating instances of persistent domains that are not
260 yet start.
262 @param xeninfo: Parsed configuration
263 @type xeninfo: dictionary
265 @rtype: XendDomainInfo
266 @return: A up and running XendDomainInfo instance
267 @raise XendError: Errors with configuration.
268 """
270 log.debug("XendDomainInfo.createDormant(%s)", xeninfo)
272 # domid does not make sense for non-running domains.
273 xeninfo.pop('domid', None)
274 vm = XendDomainInfo(XendConfig(cfg = xeninfo))
275 return vm
277 def domain_by_name(name):
278 """Get domain by name
280 @params name: Name of the domain
281 @type name: string
282 @return: XendDomainInfo or None
283 """
284 from xen.xend import XendDomain
285 return XendDomain.instance().domain_lookup_by_name_nr(name)
288 def shutdown_reason(code):
289 """Get a shutdown reason from a code.
291 @param code: shutdown code
292 @type code: int
293 @return: shutdown reason
294 @rtype: string
295 """
296 return DOMAIN_SHUTDOWN_REASONS.get(code, "?")
298 def dom_get(dom):
299 """Get info from xen for an existing domain.
301 @param dom: domain id
302 @type dom: int
303 @return: info or None
304 @rtype: dictionary
305 """
306 try:
307 domlist = xc.domain_getinfo(dom, 1)
308 if domlist and dom == domlist[0]['domid']:
309 return domlist[0]
310 except Exception, err:
311 # ignore missing domain
312 log.trace("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
313 return None
316 class XendDomainInfo:
317 """An object represents a domain.
319 @TODO: try to unify dom and domid, they mean the same thing, but
320 xc refers to it as dom, and everywhere else, including
321 xenstore it is domid. The best way is to change xc's
322 python interface.
324 @ivar info: Parsed configuration
325 @type info: dictionary
326 @ivar domid: Domain ID (if VM has started)
327 @type domid: int or None
328 @ivar vmpath: XenStore path to this VM.
329 @type vmpath: string
330 @ivar dompath: XenStore path to this Domain.
331 @type dompath: string
332 @ivar image: Reference to the VM Image.
333 @type image: xen.xend.image.ImageHandler
334 @ivar store_port: event channel to xenstored
335 @type store_port: int
336 @ivar console_port: event channel to xenconsoled
337 @type console_port: int
338 @ivar store_mfn: xenstored mfn
339 @type store_mfn: int
340 @ivar console_mfn: xenconsoled mfn
341 @type console_mfn: int
342 @ivar vmWatch: reference to a watch on the xenstored vmpath
343 @type vmWatch: xen.xend.xenstore.xswatch
344 @ivar shutdownWatch: reference to watch on the xenstored domain shutdown
345 @type shutdownWatch: xen.xend.xenstore.xswatch
346 @ivar shutdownStartTime: UNIX Time when domain started shutting down.
347 @type shutdownStartTime: float or None
348 @ivar state: Domain state
349 @type state: enum(DOM_STATE_HALTED, DOM_STATE_RUNNING, ...)
350 @ivar state_updated: lock for self.state
351 @type state_updated: threading.Condition
352 @ivar refresh_shutdown_lock: lock for polling shutdown state
353 @type refresh_shutdown_lock: threading.Condition
354 @ivar _deviceControllers: device controller cache for this domain
355 @type _deviceControllers: dict 'string' to DevControllers
356 """
358 def __init__(self, info, domid = None, dompath = None, augment = False,
359 priv = False, resume = False):
360 """Constructor for a domain
362 @param info: parsed configuration
363 @type info: dictionary
364 @keyword domid: Set initial domain id (if any)
365 @type domid: int
366 @keyword dompath: Set initial dompath (if any)
367 @type dompath: string
368 @keyword augment: Augment given info with xenstored VM info
369 @type augment: bool
370 @keyword priv: Is a privledged domain (Dom 0) (TODO: really?)
371 @type priv: bool
372 @keyword resume: Is this domain being resumed?
373 @type resume: bool
374 """
376 self.info = info
377 if domid == None:
378 self.domid = self.info.get('domid')
379 else:
380 self.domid = domid
382 #REMOVE: uuid is now generated in XendConfig
383 #if not self._infoIsSet('uuid'):
384 # self.info['uuid'] = uuid.toString(uuid.create())
386 #REMOVE: domid logic can be shortened
387 #if domid is not None:
388 # self.domid = domid
389 #elif info.has_key('dom'):
390 # self.domid = int(info['dom'])
391 #else:
392 # self.domid = None
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
407 self.state = DOM_STATE_HALTED
408 self.state_updated = threading.Condition()
409 self.refresh_shutdown_lock = threading.Condition()
411 self._deviceControllers = {}
413 for state in DOM_STATES_OLD:
414 self.info[state] = 0
416 if augment:
417 self._augmentInfo(priv)
419 self._checkName(self.info['name'])
420 self.setResume(resume)
423 #
424 # Public functions available through XMLRPC
425 #
428 def start(self, is_managed = False, start_paused = True):
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()
442 if not start_paused:
443 self.unpause()
445 # save running configuration if XendDomains believe domain is
446 # persistent
447 if is_managed:
448 xendomains = XendDomain.instance()
449 xendomains.managed_config_save(self)
450 except:
451 log.exception('VM start failed')
452 self.destroy()
453 raise
454 else:
455 raise XendError('VM already running')
457 def resume(self):
458 """Resumes a domain that has come back from suspension."""
459 if self.state in (DOM_STATE_HALTED, DOM_STATE_SUSPENDED):
460 try:
461 self._constructDomain()
462 self._storeVmDetails()
463 self._createDevices()
464 self._createChannels()
465 self._storeDomDetails()
466 self._endRestore()
467 except:
468 log.exception('VM resume failed')
469 raise
470 else:
471 raise XendError('VM already running')
473 def shutdown(self, reason):
474 """Shutdown a domain by signalling this via xenstored."""
475 log.debug('XendDomainInfo.shutdown')
476 if self.state in (DOM_STATE_SHUTDOWN, DOM_STATE_HALTED,):
477 raise XendError('Domain cannot be shutdown')
479 if self.domid == 0:
480 raise XendError('Domain 0 cannot be shutdown')
482 if not reason in DOMAIN_SHUTDOWN_REASONS.values():
483 raise XendError('Invalid reason: %s' % reason)
484 self._storeDom("control/shutdown", reason)
486 def pause(self):
487 """Pause domain
489 @raise XendError: Failed pausing a domain
490 """
491 try:
492 xc.domain_pause(self.domid)
493 self._stateSet(DOM_STATE_PAUSED)
494 except Exception, ex:
495 raise XendError("Domain unable to be paused: %s" % str(ex))
497 def unpause(self):
498 """Unpause domain
500 @raise XendError: Failed unpausing a domain
501 """
502 try:
503 xc.domain_unpause(self.domid)
504 self._stateSet(DOM_STATE_RUNNING)
505 except Exception, ex:
506 raise XendError("Domain unable to be unpaused: %s" % str(ex))
508 def send_sysrq(self, key):
509 """ Send a Sysrq equivalent key via xenstored."""
510 asserts.isCharConvertible(key)
511 self._storeDom("control/sysrq", '%c' % key)
513 def device_create(self, dev_config):
514 """Create a new device.
516 @param dev_config: device configuration
517 @type dev_config: dictionary (parsed config)
518 """
519 log.debug("XendDomainInfo.device_create: %s" % dev_config)
520 dev_type = sxp.name(dev_config)
521 devid = self._createDevice(dev_type, dev_config)
522 self.info.device_add(dev_type, cfg_sxp = dev_config)
523 self._waitForDevice(dev_type, devid)
524 return self.getDeviceController(dev_type).sxpr(devid)
526 def device_configure(self, dev_config, devid = None):
527 """Configure an existing device.
529 @param dev_config: device configuration
530 @type dev_config: dictionary (parsed config)
531 @param devid: device id
532 @type devid: int
533 """
534 deviceClass = sxp.name(dev_config)
535 self._reconfigureDevice(deviceClass, devid, dev_config)
537 def waitForDevices(self):
538 """Wait for this domain's configured devices to connect.
540 @raise VmError: if any device fails to initialise.
541 """
542 for devclass in XendDevices.valid_devices():
543 self.getDeviceController(devclass).waitForDevices()
545 def destroyDevice(self, deviceClass, devid):
546 try:
547 devid = int(devid)
548 except ValueError:
549 # devid is not a number, let's search for it in xenstore.
550 devicePath = '%s/device/%s' % (self.dompath, deviceClass)
551 for entry in xstransact.List(devicePath):
552 backend = xstransact.Read('%s/%s' % (devicePath, entry),
553 "backend")
554 devName = xstransact.Read(backend, "dev")
555 if devName == devid:
556 # We found the integer matching our devid, use it instead
557 devid = entry
558 break
560 return self.getDeviceController(deviceClass).destroyDevice(devid)
563 def getDeviceSxprs(self, deviceClass):
564 return self.getDeviceController(deviceClass).sxprs()
567 def setMemoryTarget(self, target):
568 """Set the memory target of this domain.
569 @param target: In MiB.
570 """
571 log.debug("Setting memory target of domain %s (%d) to %d MiB.",
572 self.info['name'], self.domid, target)
574 if target <= 0:
575 raise XendError('Invalid memory size')
577 self.info['memory'] = target
578 self.storeVm("memory", target)
579 self._storeDom("memory/target", target << 10)
581 def getVCPUInfo(self):
582 try:
583 # We include the domain name and ID, to help xm.
584 sxpr = ['domain',
585 ['domid', self.domid],
586 ['name', self.info['name']],
587 ['vcpu_count', self.info['online_vcpus']]]
589 for i in range(0, self.info['max_vcpu_id']+1):
590 info = xc.vcpu_getinfo(self.domid, i)
592 sxpr.append(['vcpu',
593 ['number', i],
594 ['online', info['online']],
595 ['blocked', info['blocked']],
596 ['running', info['running']],
597 ['cpu_time', info['cpu_time'] / 1e9],
598 ['cpu', info['cpu']],
599 ['cpumap', info['cpumap']]])
601 return sxpr
603 except RuntimeError, exn:
604 raise XendError(str(exn))
606 #
607 # internal functions ... TODO: re-categorised
608 #
610 def _augmentInfo(self, priv):
611 """Augment self.info, as given to us through L{recreate}, with
612 values taken from the store. This recovers those values known
613 to xend but not to the hypervisor.
614 """
615 def useIfNeeded(name, val):
616 if not self._infoIsSet(name) and val is not None:
617 self.info[name] = val
619 if priv:
620 entries = VM_STORE_ENTRIES[:]
621 entries.remove(('memory', int))
622 entries.remove(('maxmem', int))
623 else:
624 entries = VM_STORE_ENTRIES
625 entries.append(('image', str))
626 entries.append(('security', str))
628 map(lambda x, y: useIfNeeded(x[0], y), entries,
629 self._readVMDetails(entries))
631 devices = []
633 for devclass in XendDevices.valid_devices():
634 devconfig = self.getDeviceController(devclass).configurations()
635 if devconfig:
636 devices.extend(map(lambda conf: (devclass, conf), devconfig))
638 if not self.info['device'] and devices is not None:
639 for device in devices:
640 self.info.device_add(device[0], cfg_sxp = device)
642 #
643 # Function to update xenstore /vm/*
644 #
646 def _readVm(self, *args):
647 return xstransact.Read(self.vmpath, *args)
649 def _writeVm(self, *args):
650 return xstransact.Write(self.vmpath, *args)
652 def _removeVm(self, *args):
653 return xstransact.Remove(self.vmpath, *args)
655 def _gatherVm(self, *args):
656 return xstransact.Gather(self.vmpath, *args)
658 def storeVm(self, *args):
659 return xstransact.Store(self.vmpath, *args)
661 #
662 # Function to update xenstore /dom/*
663 #
665 def _readDom(self, *args):
666 return xstransact.Read(self.dompath, *args)
668 def _writeDom(self, *args):
669 return xstransact.Write(self.dompath, *args)
671 def _removeDom(self, *args):
672 return xstransact.Remove(self.dompath, *args)
674 def _storeDom(self, *args):
675 return xstransact.Store(self.dompath, *args)
677 def _recreateDom(self):
678 complete(self.dompath, lambda t: self._recreateDomFunc(t))
680 def _recreateDomFunc(self, t):
681 t.remove()
682 t.mkdir()
683 t.set_permissions({ 'dom' : self.domid })
684 t.write('vm', self.vmpath)
686 def _storeDomDetails(self):
687 to_store = {
688 'domid': str(self.domid),
689 'vm': self.vmpath,
690 'name': self.info['name'],
691 'console/limit': str(xroot.get_console_limit() * 1024),
692 'memory/target': str(self.info['memory'] * 1024)
693 }
695 def f(n, v):
696 if v is not None:
697 to_store[n] = str(v)
699 f('console/port', self.console_port)
700 f('console/ring-ref', self.console_mfn)
701 f('store/port', self.store_port)
702 f('store/ring-ref', self.store_mfn)
704 to_store.update(self._vcpuDomDetails())
706 log.debug("Storing domain details: %s", to_store)
708 self._writeDom(to_store)
710 def _vcpuDomDetails(self):
711 def availability(n):
712 if self.info['vcpu_avail'] & (1 << n):
713 return 'online'
714 else:
715 return 'offline'
717 result = {}
718 for v in range(0, self.info['vcpus']):
719 result["cpu/%d/availability" % v] = availability(v)
720 return result
722 #
723 # xenstore watches
724 #
726 def _registerWatches(self):
727 """Register a watch on this VM's entries in the store, and the
728 domain's control/shutdown node, so that when they are changed
729 externally, we keep up to date. This should only be called by {@link
730 #create}, {@link #recreate}, or {@link #restore}, once the domain's
731 details have been written, but before the new instance is returned."""
732 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
733 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
734 self._handleShutdownWatch)
736 def _storeChanged(self, _):
737 log.trace("XendDomainInfo.storeChanged");
739 changed = False
741 def f(x, y):
742 if y is not None and self.info[x[0]] != y:
743 self.info[x[0]] = y
744 changed = True
746 map(f, VM_CONFIG_PARAMS, self._readVMDetails(VM_CONFIG_PARAMS))
748 im = self._readVm('image')
749 current_im = self.info['image']
750 if (im is not None and
751 (current_im is None or sxp.to_string(current_im) != im)):
752 self.info['image'] = sxp.from_string(im)
753 changed = True
755 if changed:
756 # Update the domain section of the store, as this contains some
757 # parameters derived from the VM configuration.
758 self._storeDomDetails()
760 return 1
762 def _handleShutdownWatch(self, _):
763 log.debug('XendDomainInfo.handleShutdownWatch')
765 reason = self._readDom('control/shutdown')
767 if reason and reason != 'suspend':
768 sst = self._readDom('xend/shutdown_start_time')
769 now = time.time()
770 if sst:
771 self.shutdownStartTime = float(sst)
772 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
773 else:
774 self.shutdownStartTime = now
775 self._storeDom('xend/shutdown_start_time', now)
776 timeout = SHUTDOWN_TIMEOUT
778 log.trace(
779 "Scheduling refreshShutdown on domain %d in %ds.",
780 self.domid, timeout)
781 threading.Timer(timeout, self.refreshShutdown).start()
783 return True
786 #
787 # Public Attributes for the VM
788 #
791 def getDomid(self):
792 return self.domid
794 def setName(self, name):
795 self._checkName(name)
796 self.info['name'] = name
797 self.storeVm("name", name)
799 def getName(self):
800 return self.info['name']
802 def getDomainPath(self):
803 return self.dompath
805 def getShutdownReason(self):
806 return self._readDom('control/shutdown')
808 def getStorePort(self):
809 """For use only by image.py and XendCheckpoint.py."""
810 return self.store_port
812 def getConsolePort(self):
813 """For use only by image.py and XendCheckpoint.py"""
814 return self.console_port
816 def getFeatures(self):
817 """For use only by image.py."""
818 return self.info['features']
820 def getVCpuCount(self):
821 return self.info['vcpus']
823 def setVCpuCount(self, vcpus):
824 self.info['vcpu_avail'] = (1 << vcpus) - 1
825 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
826 self._writeDom(self._vcpuDomDetails())
828 def getLabel(self):
829 return security.get_security_info(self.info, 'label')
831 def getMemoryTarget(self):
832 """Get this domain's target memory size, in KB."""
833 return self.info['memory'] * 1024
835 def getResume(self):
836 return "%s" % self.info['resume']
838 def getCap(self):
839 return self.info['cpu_cap']
841 def getWeight(self):
842 return self.info['cpu_weight']
844 def setResume(self, state):
845 self.info['resume'] = state
847 def getRestartCount(self):
848 return self._readVm('xend/restart_count')
850 def refreshShutdown(self, xeninfo = None):
851 """ Checks the domain for whether a shutdown is required.
853 Called from XendDomainInfo and also image.py for HVM images.
854 """
856 # If set at the end of this method, a restart is required, with the
857 # given reason. This restart has to be done out of the scope of
858 # refresh_shutdown_lock.
859 restart_reason = None
861 self.refresh_shutdown_lock.acquire()
862 try:
863 if xeninfo is None:
864 xeninfo = dom_get(self.domid)
865 if xeninfo is None:
866 # The domain no longer exists. This will occur if we have
867 # scheduled a timer to check for shutdown timeouts and the
868 # shutdown succeeded. It will also occur if someone
869 # destroys a domain beneath us. We clean up the domain,
870 # just in case, but we can't clean up the VM, because that
871 # VM may have migrated to a different domain on this
872 # machine.
873 self.cleanupDomain()
874 self._stateSet(DOM_STATE_HALTED)
875 return
877 if xeninfo['dying']:
878 # Dying means that a domain has been destroyed, but has not
879 # yet been cleaned up by Xen. This state could persist
880 # indefinitely if, for example, another domain has some of its
881 # pages mapped. We might like to diagnose this problem in the
882 # future, but for now all we do is make sure that it's not us
883 # holding the pages, by calling cleanupDomain. We can't
884 # clean up the VM, as above.
885 self.cleanupDomain()
886 self._stateSet(DOM_STATE_SHUTDOWN)
887 return
889 elif xeninfo['crashed']:
890 if self._readDom('xend/shutdown_completed'):
891 # We've seen this shutdown already, but we are preserving
892 # the domain for debugging. Leave it alone.
893 return
895 log.warn('Domain has crashed: name=%s id=%d.',
896 self.info['name'], self.domid)
898 if xroot.get_enable_dump():
899 self.dumpCore()
901 restart_reason = 'crash'
902 self._stateSet(DOM_STATE_HALTED)
904 elif xeninfo['shutdown']:
905 self._stateSet(DOM_STATE_SHUTDOWN)
906 if self._readDom('xend/shutdown_completed'):
907 # We've seen this shutdown already, but we are preserving
908 # the domain for debugging. Leave it alone.
909 return
911 else:
912 reason = shutdown_reason(xeninfo['shutdown_reason'])
914 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
915 self.info['name'], self.domid, reason)
917 self._clearRestart()
919 if reason == 'suspend':
920 self._stateSet(DOM_STATE_SUSPENDED)
921 # Don't destroy the domain. XendCheckpoint will do
922 # this once it has finished. However, stop watching
923 # the VM path now, otherwise we will end up with one
924 # watch for the old domain, and one for the new.
925 self._unwatchVm()
926 elif reason in ('poweroff', 'reboot'):
927 restart_reason = reason
928 else:
929 self.destroy()
931 elif self.dompath is None:
932 # We have yet to manage to call introduceDomain on this
933 # domain. This can happen if a restore is in progress, or has
934 # failed. Ignore this domain.
935 pass
936 else:
937 # Domain is alive. If we are shutting it down, then check
938 # the timeout on that, and destroy it if necessary.
939 if xeninfo['paused']:
940 self._stateSet(DOM_STATE_PAUSED)
941 else:
942 self._stateSet(DOM_STATE_RUNNING)
944 if self.shutdownStartTime:
945 timeout = (SHUTDOWN_TIMEOUT - time.time() +
946 self.shutdownStartTime)
947 if timeout < 0:
948 log.info(
949 "Domain shutdown timeout expired: name=%s id=%s",
950 self.info['name'], self.domid)
951 self.destroy()
952 finally:
953 self.refresh_shutdown_lock.release()
955 if restart_reason:
956 self._maybeRestart(restart_reason)
959 #
960 # Restart functions - handling whether we come back up on shutdown.
961 #
963 def _clearRestart(self):
964 self._removeDom("xend/shutdown_start_time")
967 def _maybeRestart(self, reason):
968 # Dispatch to the correct method based upon the configured on_{reason}
969 # behaviour.
970 {"destroy" : self.destroy,
971 "restart" : self._restart,
972 "preserve" : self._preserve,
973 "rename-restart" : self._renameRestart}[self.info['on_' + reason]]()
976 def _renameRestart(self):
977 self._restart(True)
979 def _restart(self, rename = False):
980 """Restart the domain after it has exited.
982 @param rename True if the old domain is to be renamed and preserved,
983 False if it is to be destroyed.
984 """
985 from xen.xend import XendDomain
987 self._configureBootloader()
988 config = self.sxpr()
990 if self._infoIsSet('cpus') and len(self.info['cpus']) != 0:
991 config.append(['cpus', reduce(lambda x, y: str(x) + "," + str(y),
992 self.info['cpus'])])
994 if self._readVm(RESTART_IN_PROGRESS):
995 log.error('Xend failed during restart of domain %s. '
996 'Refusing to restart to avoid loops.',
997 str(self.domid))
998 self.destroy()
999 return
1001 old_domid = self.domid
1002 self._writeVm(RESTART_IN_PROGRESS, 'True')
1004 now = time.time()
1005 rst = self._readVm('xend/previous_restart_time')
1006 if rst:
1007 rst = float(rst)
1008 timeout = now - rst
1009 if timeout < MINIMUM_RESTART_TIME:
1010 log.error(
1011 'VM %s restarting too fast (%f seconds since the last '
1012 'restart). Refusing to restart to avoid loops.',
1013 self.info['name'], timeout)
1014 self.destroy()
1015 return
1017 self._writeVm('xend/previous_restart_time', str(now))
1019 try:
1020 if rename:
1021 self._preserveForRestart()
1022 else:
1023 self._unwatchVm()
1024 self.destroyDomain()
1026 # new_dom's VM will be the same as this domain's VM, except where
1027 # the rename flag has instructed us to call preserveForRestart.
1028 # In that case, it is important that we remove the
1029 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1030 # once the new one is available.
1032 new_dom = None
1033 try:
1034 new_dom = XendDomain.instance().domain_create(config)
1035 new_dom.unpause()
1036 rst_cnt = self._readVm('xend/restart_count')
1037 rst_cnt = int(rst_cnt) + 1
1038 self._writeVm('xend/restart_count', str(rst_cnt))
1039 new_dom._removeVm(RESTART_IN_PROGRESS)
1040 except:
1041 if new_dom:
1042 new_dom._removeVm(RESTART_IN_PROGRESS)
1043 new_dom.destroy()
1044 else:
1045 self._removeVm(RESTART_IN_PROGRESS)
1046 raise
1047 except:
1048 log.exception('Failed to restart domain %s.', str(old_domid))
1050 def _preserveForRestart(self):
1051 """Preserve a domain that has been shut down, by giving it a new UUID,
1052 cloning the VM details, and giving it a new name. This allows us to
1053 keep this domain for debugging, but restart a new one in its place
1054 preserving the restart semantics (name and UUID preserved).
1055 """
1057 new_uuid = uuid.createString()
1058 new_name = 'Domain-%s' % new_uuid
1059 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1060 self.info['name'], self.domid, self.info['uuid'],
1061 new_name, new_uuid)
1062 self._unwatchVm()
1063 self._releaseDevices()
1064 self.info['name'] = new_name
1065 self.info['uuid'] = new_uuid
1066 self.vmpath = XS_VMROOT + new_uuid
1067 self._storeVmDetails()
1068 self._preserve()
1071 def _preserve(self):
1072 log.info("Preserving dead domain %s (%d).", self.info['name'],
1073 self.domid)
1074 self._unwatchVm()
1075 self._storeDom('xend/shutdown_completed', 'True')
1076 self._stateSet(DOM_STATE_HALTED)
1079 # Debugging ..
1082 def dumpCore(self, corefile = None):
1083 """Create a core dump for this domain. Nothrow guarantee."""
1085 try:
1086 if not corefile:
1087 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1088 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1089 self.info['name'], self.domid)
1091 if os.path.isdir(corefile):
1092 raise XendError("Cannot dump core in a directory: %s" %
1093 corefile)
1095 xc.domain_dumpcore(self.domid, corefile)
1096 except RuntimeError, ex:
1097 corefile_incomp = corefile+'-incomplete'
1098 os.rename(corefile, corefile_incomp)
1099 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1100 self.domid, self.info['name'])
1101 raise XendError("Failed to dump core: %s" % str(ex))
1104 # Device creation/deletion functions
1107 def _createDevice(self, deviceClass, devConfig):
1108 return self.getDeviceController(deviceClass).createDevice(devConfig)
1110 def _waitForDevice(self, deviceClass, devid):
1111 return self.getDeviceController(deviceClass).waitForDevice(devid)
1113 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1114 return self.getDeviceController(deviceClass).reconfigureDevice(
1115 devid, devconfig)
1117 def _createDevices(self):
1118 """Create the devices for a vm.
1120 @raise: VmError for invalid devices
1121 """
1122 for (devclass, config) in self.info.all_devices_sxpr():
1123 if devclass in XendDevices.valid_devices():
1124 log.info("createDevice: %s : %s" % (devclass, config))
1125 self._createDevice(devclass, config)
1127 if self.image:
1128 self.image.createDeviceModel()
1130 def _releaseDevices(self):
1131 """Release all domain's devices. Nothrow guarantee."""
1133 while True:
1134 t = xstransact("%s/device" % self.dompath)
1135 for devclass in XendDevices.valid_devices():
1136 for dev in t.list(devclass):
1137 try:
1138 t.remove(dev)
1139 except:
1140 # Log and swallow any exceptions in removal --
1141 # there's nothing more we can do.
1142 log.exception(
1143 "Device release failed: %s; %s; %s",
1144 self.info['name'], devclass, dev)
1145 if t.commit():
1146 break
1148 def getDeviceController(self, name):
1149 """Get the device controller for this domain, and if it
1150 doesn't exist, create it.
1152 @param name: device class name
1153 @type name: string
1154 @rtype: subclass of DevController
1155 """
1156 if name not in self._deviceControllers:
1157 devController = XendDevices.make_controller(name, self)
1158 if not devController:
1159 raise XendError("Unknown device type: %s" % name)
1160 self._deviceControllers[name] = devController
1162 return self._deviceControllers[name]
1165 # Migration functions (public)
1168 def testMigrateDevices(self, network, dst):
1169 """ Notify all device about intention of migration
1170 @raise: XendError for a device that cannot be migrated
1171 """
1172 for (n, c) in self.info.all_devices_sxpr():
1173 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1174 if rc != 0:
1175 raise XendError("Device of type '%s' refuses migration." % n)
1177 def migrateDevices(self, network, dst, step, domName=''):
1178 """Notify the devices about migration
1179 """
1180 ctr = 0
1181 try:
1182 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1183 self.migrateDevice(dev_type, dev_conf, network, dst,
1184 step, domName)
1185 ctr = ctr + 1
1186 except:
1187 for dev_type, dev_conf in self.info.all_devices_sxpr():
1188 if ctr == 0:
1189 step = step - 1
1190 ctr = ctr - 1
1191 self._recoverMigrateDevice(dev_type, dev_conf, network,
1192 dst, step, domName)
1193 raise
1195 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1196 step, domName=''):
1197 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1198 network, dst, step, domName)
1200 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1201 dst, step, domName=''):
1202 return self.getDeviceController(deviceClass).recover_migrate(
1203 deviceConfig, network, dst, step, domName)
1206 ## private:
1208 def _constructDomain(self):
1209 """Construct the domain.
1211 @raise: VmError on error
1212 """
1214 log.debug('XendDomainInfo.constructDomain')
1216 hvm = (self._infoIsSet('image') and
1217 sxp.name(self.info['image']) == "hvm")
1218 if hvm:
1219 info = xc.xeninfo()
1220 if not 'hvm' in info['xen_caps']:
1221 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1222 "supported by your CPU and enabled in your "
1223 "BIOS?")
1225 self.domid = xc.domain_create(
1226 domid = 0,
1227 ssidref = security.get_security_info(self.info, 'ssidref'),
1228 handle = uuid.fromString(self.info['uuid']),
1229 hvm = int(hvm))
1231 if self.domid < 0:
1232 raise VmError('Creating domain failed: name=%s' %
1233 self.info['name'])
1235 self.dompath = GetDomainPath(self.domid)
1237 self._recreateDom()
1239 # Set maximum number of vcpus in domain
1240 xc.domain_max_vcpus(self.domid, int(self.info['vcpus']))
1243 def _introduceDomain(self):
1244 assert self.domid is not None
1245 assert self.store_mfn is not None
1246 assert self.store_port is not None
1248 try:
1249 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1250 except RuntimeError, exn:
1251 raise XendError(str(exn))
1254 def _initDomain(self):
1255 log.debug('XendDomainInfo.initDomain: %s %s',
1256 self.domid,
1257 self.info['cpu_weight'])
1259 # if we have a boot loader but no image, then we need to set things
1260 # up by running the boot loader non-interactively
1261 if self._infoIsSet('bootloader') and not self._infoIsSet('image'):
1262 self._configureBootloader()
1264 if not self._infoIsSet('image'):
1265 raise VmError('Missing image in configuration')
1267 try:
1268 self.image = image.create(self,
1269 self.info['image'],
1270 self.info.all_devices_sxpr())
1272 localtime = self.info.get('localtime', 0)
1273 if localtime is not None and localtime == 1:
1274 xc.domain_set_time_offset(self.domid)
1276 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1278 # repin domain vcpus if a restricted cpus list is provided
1279 # this is done prior to memory allocation to aide in memory
1280 # distribution for NUMA systems.
1281 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1282 for v in range(0, self.info['max_vcpu_id']+1):
1283 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1285 # Use architecture- and image-specific calculations to determine
1286 # the various headrooms necessary, given the raw configured
1287 # values. maxmem, memory, and shadow are all in KiB.
1288 maxmem = self.image.getRequiredAvailableMemory(
1289 self.info['maxmem'] * 1024)
1290 memory = self.image.getRequiredAvailableMemory(
1291 self.info['memory'] * 1024)
1292 shadow = self.image.getRequiredShadowMemory(
1293 self.info['shadow_memory'] * 1024,
1294 self.info['maxmem'] * 1024)
1296 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1297 # takes MiB and we must not round down and end up under-providing.
1298 shadow = ((shadow + 1023) / 1024) * 1024
1300 # set memory limit
1301 xc.domain_setmaxmem(self.domid, maxmem)
1303 # Make sure there's enough RAM available for the domain
1304 balloon.free(memory + shadow)
1306 # Set up the shadow memory
1307 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1308 self.info['shadow_memory'] = shadow_cur
1310 self._createChannels()
1312 channel_details = self.image.createImage()
1314 self.store_mfn = channel_details['store_mfn']
1315 if 'console_mfn' in channel_details:
1316 self.console_mfn = channel_details['console_mfn']
1318 self._introduceDomain()
1320 self._createDevices()
1322 if self.info['bootloader'] not in [None, 'kernel_external']:
1323 self.image.cleanupBootloading()
1325 self.info['start_time'] = time.time()
1327 self._stateSet(DOM_STATE_RUNNING)
1328 except RuntimeError, exn:
1329 log.exception("XendDomainInfo.initDomain: exception occurred")
1330 if self.info['bootloader'] not in [None, 'kernel_external'] \
1331 and self.image is not None:
1332 self.image.cleanupBootloading()
1333 raise VmError(str(exn))
1336 def cleanupDomain(self):
1337 """Cleanup domain resources; release devices. Idempotent. Nothrow
1338 guarantee."""
1340 self.refresh_shutdown_lock.acquire()
1341 try:
1342 self.unwatchShutdown()
1344 self._releaseDevices()
1346 if self.image:
1347 try:
1348 self.image.destroy()
1349 except:
1350 log.exception(
1351 "XendDomainInfo.cleanup: image.destroy() failed.")
1352 self.image = None
1354 try:
1355 self._removeDom()
1356 except:
1357 log.exception("Removing domain path failed.")
1359 self._stateSet(DOM_STATE_HALTED)
1360 finally:
1361 self.refresh_shutdown_lock.release()
1364 def unwatchShutdown(self):
1365 """Remove the watch on the domain's control/shutdown node, if any.
1366 Idempotent. Nothrow guarantee. Expects to be protected by the
1367 refresh_shutdown_lock."""
1369 try:
1370 try:
1371 if self.shutdownWatch:
1372 self.shutdownWatch.unwatch()
1373 finally:
1374 self.shutdownWatch = None
1375 except:
1376 log.exception("Unwatching control/shutdown failed.")
1378 def waitForShutdown(self):
1379 self.state_updated.acquire()
1380 try:
1381 while self.state in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1382 self.state_updated.wait()
1383 finally:
1384 self.state_updated.release()
1388 # TODO: recategorise - called from XendCheckpoint
1391 def completeRestore(self, store_mfn, console_mfn):
1393 log.debug("XendDomainInfo.completeRestore")
1395 self.store_mfn = store_mfn
1396 self.console_mfn = console_mfn
1398 self._introduceDomain()
1399 self._storeDomDetails()
1400 self._registerWatches()
1401 self.refreshShutdown()
1403 log.debug("XendDomainInfo.completeRestore done")
1406 def _endRestore(self):
1407 self.setResume(False)
1410 # VM Destroy
1413 def destroy(self):
1414 """Cleanup VM and destroy domain. Nothrow guarantee."""
1416 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1418 self._cleanupVm()
1419 if self.dompath is not None:
1420 self.destroyDomain()
1423 def destroyDomain(self):
1424 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1426 try:
1427 if self.domid is not None:
1428 xc.domain_destroy(self.domid)
1429 self.domid = None
1430 for state in DOM_STATES_OLD:
1431 self.info[state] = 0
1432 except:
1433 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1435 self.cleanupDomain()
1439 # Channels for xenstore and console
1442 def _createChannels(self):
1443 """Create the channels to the domain.
1444 """
1445 self.store_port = self._createChannel()
1446 self.console_port = self._createChannel()
1449 def _createChannel(self):
1450 """Create an event channel to the domain.
1451 """
1452 try:
1453 return xc.evtchn_alloc_unbound(domid=self.domid, remote_dom=0)
1454 except:
1455 log.exception("Exception in alloc_unbound(%d)", self.domid)
1456 raise
1459 # Bootloader configuration
1462 def _configureBootloader(self):
1463 """Run the bootloader if we're configured to do so."""
1464 if not self.info['bootloader']:
1465 return
1466 blcfg = None
1467 # FIXME: this assumes that we want to use the first disk device
1468 for (n, c) in self.info.all_devices_sxpr():
1469 if not n or not c or not(n in ["vbd", "tap"]):
1470 continue
1471 disk = sxp.child_value(c, "uname")
1472 if disk is None:
1473 continue
1474 fn = blkdev_uname_to_file(disk)
1475 blcfg = bootloader(self.info['bootloader'], fn, 1,
1476 self.info['bootloader_args'],
1477 self.info['image'])
1478 break
1479 if blcfg is None:
1480 msg = "Had a bootloader specified, but can't find disk"
1481 log.error(msg)
1482 raise VmError(msg)
1483 self.info['image'] = blcfg
1486 # VM Functions
1489 def _readVMDetails(self, params):
1490 """Read the specified parameters from the store.
1491 """
1492 try:
1493 return self._gatherVm(*params)
1494 except ValueError:
1495 # One of the int/float entries in params has a corresponding store
1496 # entry that is invalid. We recover, because older versions of
1497 # Xend may have put the entry there (memory/target, for example),
1498 # but this is in general a bad situation to have reached.
1499 log.exception(
1500 "Store corrupted at %s! Domain %d's configuration may be "
1501 "affected.", self.vmpath, self.domid)
1502 return []
1504 def _cleanupVm(self):
1505 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1507 self._unwatchVm()
1509 try:
1510 self._removeVm()
1511 except:
1512 log.exception("Removing VM path failed.")
1515 def checkLiveMigrateMemory(self):
1516 """ Make sure there's enough memory to migrate this domain """
1517 overhead_kb = 0
1518 if arch.type == "x86":
1519 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1520 # the minimum that Xen would allocate if no value were given.
1521 overhead_kb = self.info['vcpus'] * 1024 + self.info['maxmem'] * 4
1522 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1523 # The domain might already have some shadow memory
1524 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1525 if overhead_kb > 0:
1526 balloon.free(overhead_kb)
1528 def _unwatchVm(self):
1529 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1530 guarantee."""
1531 try:
1532 try:
1533 if self.vmWatch:
1534 self.vmWatch.unwatch()
1535 finally:
1536 self.vmWatch = None
1537 except:
1538 log.exception("Unwatching VM path failed.")
1540 def testDeviceComplete(self):
1541 """ For Block IO migration safety we must ensure that
1542 the device has shutdown correctly, i.e. all blocks are
1543 flushed to disk
1544 """
1545 start = time.time()
1546 while True:
1547 test = 0
1548 diff = time.time() - start
1549 for i in self.getDeviceController('vbd').deviceIDs():
1550 test = 1
1551 log.info("Dev %s still active, looping...", i)
1552 time.sleep(0.1)
1554 if test == 0:
1555 break
1556 if diff >= MIGRATE_TIMEOUT:
1557 log.info("Dev still active but hit max loop timeout")
1558 break
1560 def _storeVmDetails(self):
1561 to_store = {}
1563 for k in VM_STORE_ENTRIES:
1564 if self._infoIsSet(k[0]):
1565 to_store[k[0]] = str(self.info[k[0]])
1567 if self._infoIsSet('image'):
1568 to_store['image'] = sxp.to_string(self.info['image'])
1570 if self._infoIsSet('security'):
1571 secinfo = self.info['security']
1572 to_store['security'] = sxp.to_string(secinfo)
1573 for idx in range(0, len(secinfo)):
1574 if secinfo[idx][0] == 'access_control':
1575 to_store['security/access_control'] = sxp.to_string(
1576 [secinfo[idx][1], secinfo[idx][2]])
1577 for aidx in range(1, len(secinfo[idx])):
1578 if secinfo[idx][aidx][0] == 'label':
1579 to_store['security/access_control/label'] = \
1580 secinfo[idx][aidx][1]
1581 if secinfo[idx][aidx][0] == 'policy':
1582 to_store['security/access_control/policy'] = \
1583 secinfo[idx][aidx][1]
1584 if secinfo[idx][0] == 'ssidref':
1585 to_store['security/ssidref'] = str(secinfo[idx][1])
1588 if not self._readVm('xend/restart_count'):
1589 to_store['xend/restart_count'] = str(0)
1591 log.debug("Storing VM details: %s", to_store)
1593 self._writeVm(to_store)
1594 self._setVmPermissions()
1597 def _setVmPermissions(self):
1598 """Allow the guest domain to read its UUID. We don't allow it to
1599 access any other entry, for security."""
1600 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1601 { 'dom' : self.domid,
1602 'read' : True,
1603 'write' : False })
1606 # Utility functions
1609 def _stateSet(self, state):
1610 self.state_updated.acquire()
1611 try:
1612 if self.state != state:
1613 self.state = state
1614 self.state_updated.notifyAll()
1615 finally:
1616 self.state_updated.release()
1618 def _infoIsSet(self, name):
1619 return name in self.info and self.info[name] is not None
1621 def _checkName(self, name):
1622 """Check if a vm name is valid. Valid names contain alphabetic
1623 characters, digits, or characters in '_-.:/+'.
1624 The same name cannot be used for more than one vm at the same time.
1626 @param name: name
1627 @raise: VmError if invalid
1628 """
1629 from xen.xend import XendDomain
1631 if name is None or name == '':
1632 raise VmError('Missing VM Name')
1634 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1635 raise VmError('Invalid VM Name')
1637 dom = XendDomain.instance().domain_lookup_nr(name)
1638 if dom and dom != self and not dom.info['dying']:
1639 raise VmError("VM name '%s' already exists" % name)
1642 def update(self, info = None, refresh = True):
1643 """Update with info from xc.domain_getinfo().
1644 """
1645 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1646 str(self.domid))
1648 if not info:
1649 info = dom_get(self.domid)
1650 if not info:
1651 return
1653 #manually update ssidref / security fields
1654 if security.on() and info.has_key('ssidref'):
1655 if (info['ssidref'] != 0) and self.info.has_key('security'):
1656 security_field = self.info['security']
1657 if not security_field:
1658 #create new security element
1659 self.info.update({'security':
1660 [['ssidref', str(info['ssidref'])]]})
1661 #ssidref field not used any longer
1662 if 'ssidref' in info:
1663 info.pop('ssidref')
1665 # make sure state is reset for info
1666 # TODO: we should eventually get rid of old_dom_states
1668 self.info.update(info)
1669 self.info.validate()
1671 if refresh:
1672 self.refreshShutdown(info)
1674 log.trace("XendDomainInfo.update done on domain %s: %s",
1675 str(self.domid), self.info)
1677 def sxpr(self, ignore_store = False):
1678 result = self.info.get_sxp(domain = self,
1679 ignore_devices = ignore_store)
1681 if not ignore_store and self.dompath:
1682 vnc_port = self._readDom('console/vnc-port')
1683 if vnc_port is not None:
1684 result.append(['device',
1685 ['console', ['vnc-port', str(vnc_port)]]])
1687 return result
1689 # Xen API
1690 # ----------------------------------------------------------------
1692 def get_uuid(self):
1693 dom_uuid = self.info.get('uuid')
1694 if not dom_uuid: # if it doesn't exist, make one up
1695 dom_uuid = uuid.createString()
1696 self.info['uuid'] = dom_uuid
1697 return dom_uuid
1699 def get_memory_static_max(self):
1700 return self.info['maxmem']
1701 def get_memory_static_min(self):
1702 return self.info['memory']
1703 def get_vcpus_policy(self):
1704 sched_id = xc.sched_id_get()
1705 if sched_id == xen.lowlevel.xc.XEN_SCHEDULER_SEDF:
1706 return 'sedf'
1707 elif sched_id == xen.lowlevel.xc.XEN_SCHEDULER_CREDIT:
1708 return 'credit'
1709 else:
1710 return 'unknown'
1711 def get_vcpus_params(self):
1712 return '' # TODO
1713 def get_power_state(self):
1714 return XEN_API_VM_POWER_STATE[self.state]
1715 def get_bios_boot(self):
1716 return '' # TODO
1717 def get_platform_std_vga(self):
1718 return False
1719 def get_platform_keymap(self):
1720 return ''
1721 def get_platform_serial(self):
1722 return '' # TODO
1723 def get_platform_localtime(self):
1724 return False # TODO
1725 def get_platform_clock_offset(self):
1726 return False # TODO
1727 def get_platform_enable_audio(self):
1728 return False # TODO
1729 def get_builder(self):
1730 return 'Linux' # TODO
1731 def get_boot_method(self):
1732 bootloader = self.info['bootloader']
1733 if not bootloader or bootloader not in XEN_API_BOOT_TYPE:
1734 return 'kernel_external'
1735 return bootloader
1737 def get_kernel_image(self):
1738 return self.info['kernel_kernel']
1739 def get_kernel_initrd(self):
1740 return self.info['kernel_initrd']
1741 def get_kernel_args(self):
1742 return self.info['kernel_args']
1743 def get_grub_cmdline(self):
1744 return '' # TODO
1745 def get_pci_bus(self):
1746 return 0 # TODO
1747 def get_tools_version(self):
1748 return {} # TODO
1749 def get_other_config(self):
1750 return {} # TODO
1752 def get_on_shutdown(self):
1753 after_shutdown = self.info.get('on_poweroff')
1754 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
1755 return XEN_API_ON_NORMAL_EXIT[-1]
1756 return after_shutdown
1758 def get_on_reboot(self):
1759 after_reboot = self.info.get('on_reboot')
1760 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
1761 return XEN_API_ON_NORMAL_EXIT[-1]
1762 return after_reboot
1764 def get_on_suspend(self):
1765 after_suspend = self.info.get('on_suspend') # TODO: not supported
1766 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
1767 return XEN_API_ON_NORMAL_EXIT[-1]
1768 return after_suspend
1770 def get_on_crash(self):
1771 after_crash = self.info.get('on_crash')
1772 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
1773 return XEN_API_ON_CRASH_BEHAVIOUR[0]
1774 return after_crash
1776 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
1777 """ Get's a device configuration either from XendConfig or
1778 from the DevController.
1780 @param dev_class: device class, either, 'vbd' or 'vif'
1781 @param dev_uuid: device UUID
1783 @rtype: dictionary
1784 """
1785 dev_type_config = self.info['device'].get(dev_uuid)
1787 # shortcut if the domain isn't started because
1788 # the devcontrollers will have no better information
1789 # than XendConfig.
1790 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
1791 if dev_type_config:
1792 return copy.deepcopy(dev_type_config[1])
1793 return None
1795 # instead of using dev_class, we use the dev_type
1796 # that is from XendConfig.
1797 # This will accomdate 'tap' as well as 'vbd'
1798 dev_type = dev_type_config[0]
1800 controller = self.getDeviceController(dev_type)
1801 if not controller:
1802 return None
1804 all_configs = controller.getAllDeviceConfigurations()
1805 if not all_configs:
1806 return None
1808 dev_config = copy.deepcopy(dev_type_config[1])
1809 for _devid, _devcfg in all_configs.items():
1810 if _devcfg.get('uuid') == dev_uuid:
1811 dev_config.update(_devcfg)
1812 dev_config['id'] = _devid
1813 return dev_config
1815 return dev_config
1817 def get_dev_xenapi_config(self, dev_class, dev_uuid):
1818 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
1819 if not config:
1820 return {}
1822 config['VM'] = self.get_uuid()
1824 if dev_class == 'vif':
1825 if not config.has_key('name'):
1826 config['name'] = config.get('vifname', '')
1827 if not config.has_key('MAC'):
1828 config['MAC'] = config.get('mac', '')
1829 if not config.has_key('type'):
1830 config['type'] = 'paravirtualised'
1831 if not config.has_key('device'):
1832 devid = config.get('id')
1833 if devid != None:
1834 config['device'] = 'eth%d' % devid
1835 else:
1836 config['device'] = ''
1838 config['network'] = '' # Invalid for Xend
1839 config['MTU'] = 1500 # TODO
1840 config['network_read_kbs'] = 0.0
1841 config['network_write_kbs'] = 0.0
1842 config['IO_bandwidth_incoming_kbs'] = 0.0
1843 config['IO_bandwidth_outgoing_kbs'] = 0.0
1845 if dev_class =='vbd':
1846 config['VDI'] = '' # TODO
1847 config['device'] = config.get('dev', '')
1848 config['driver'] = 'paravirtualised' # TODO
1849 config['image'] = config.get('uname', '')
1850 config['IO_bandwidth_incoming_kbs'] = 0.0
1851 config['IO_bandwidth_outgoing_kbs'] = 0.0
1852 if config['mode'] == 'r':
1853 config['mode'] = 'RO'
1854 else:
1855 config['mode'] = 'RW'
1857 if dev_class == 'vtpm':
1858 config['driver'] = 'paravirtualised' # TODO
1860 return config
1862 def get_dev_property(self, dev_class, dev_uuid, field):
1863 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
1864 try:
1865 return config[field]
1866 except KeyError:
1867 raise XendError('Invalid property for device: %s' % field)
1869 def get_vcpus_util(self):
1870 # TODO: this returns the total accum cpu time, rather than util
1871 # TODO: spec says that key is int, however, python does not allow
1872 # non-string keys to dictionaries.
1873 vcpu_util = {}
1874 if 'max_vcpu_id' in self.info and self.domid != None:
1875 for i in range(0, self.info['max_vcpu_id']+1):
1876 info = xc.vcpu_getinfo(self.domid, i)
1877 vcpu_util[str(i)] = info['cpu_time']/1000000000.0
1879 return vcpu_util
1881 def get_vifs(self):
1882 return self.info.get('vif_refs', [])
1884 def get_vbds(self):
1885 return self.info.get('vbd_refs', [])
1887 def get_vtpms(self):
1888 return self.info.get('vtpm_refs', [])
1890 def create_vbd(self, xenapi_vbd):
1891 """Create a VBD device from the passed struct in Xen API format.
1893 @return: uuid of the device
1894 @rtype: string
1895 """
1897 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
1898 if not dev_uuid:
1899 raise XendError('Failed to create device')
1901 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1902 sxpr = self.info.device_sxpr(dev_uuid)
1903 devid = self.getDeviceController('vbd').createDevice(sxpr)
1904 raise XendError("Device creation failed")
1906 return dev_uuid
1908 def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
1909 """Create a VBD using a VDI from XendStorageRepository.
1911 @param xenapi_vbd: vbd struct from the Xen API
1912 @param vdi_image_path: VDI UUID
1913 @rtype: string
1914 @return: uuid of the device
1915 """
1916 xenapi_vbd['image'] = vdi_image_path
1917 log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
1918 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
1919 if not dev_uuid:
1920 raise XendError('Failed to create device')
1922 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1923 sxpr = self.info.device_sxpr(dev_uuid)
1924 devid = self.getDeviceController('tap').createDevice(sxpr)
1925 raise XendError("Device creation failed")
1927 return dev_uuid
1929 def create_vif(self, xenapi_vif):
1930 """Create VIF device from the passed struct in Xen API format.
1932 @param xenapi_vif: Xen API VIF Struct.
1933 @rtype: string
1934 @return: UUID
1935 """
1936 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
1937 if not dev_uuid:
1938 raise XendError('Failed to create device')
1940 if self.state in (DOM_STATE_HALTED,):
1941 sxpr = self.info.device_sxpr(dev_uuid)
1942 devid = self.getDeviceController('vif').createDevice(sxpr)
1943 raise XendError("Device creation failed")
1945 return dev_uuid
1947 def create_vtpm(self, xenapi_vtpm):
1948 """Create a VTPM device from the passed struct in Xen API format.
1950 @return: uuid of the device
1951 @rtype: string
1952 """
1954 if self.state not in (DOM_STATE_HALTED,):
1955 raise VmError("Can only add vTPM to a halted domain.")
1956 if self.get_vtpms() != []:
1957 raise VmError('Domain already has a vTPM.')
1958 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
1959 if not dev_uuid:
1960 raise XendError('Failed to create device')
1962 return dev_uuid
1964 def has_device(self, dev_class, dev_uuid):
1965 return (dev_uuid in self.info['%s_refs' % dev_class])
1967 """
1968 def stateChar(name):
1969 if name in self.info:
1970 if self.info[name]:
1971 return name[0]
1972 else:
1973 return '-'
1974 else:
1975 return '?'
1977 state = reduce(lambda x, y: x + y, map(stateChar, DOM_STATES_OLD))
1979 sxpr.append(['state', state])
1981 if self.store_mfn:
1982 sxpr.append(['store_mfn', self.store_mfn])
1983 if self.console_mfn:
1984 sxpr.append(['console_mfn', self.console_mfn])
1985 """
1987 def __str__(self):
1988 return '<domain id=%s name=%s memory=%s state=%s>' % \
1989 (str(self.domid), self.info['name'],
1990 str(self.info['memory']), DOM_STATES[self.state])
1992 __repr__ = __str__