ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 12643:bd5ebf33f222

Wait for device-bringup inside domain_start, outside the scope of the
domains_lock. This makes the VM.start call blocking, but allows xm list to
work while it is blocked. Move the handling of start_paused out of
XendDomainInfo.start as part of this.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author Ewan Mellor <ewan@xensource.com>
date Tue Nov 28 18:00:45 2006 +0000 (2006-11-28)
parents 33951d547223
children 5bed7bc05c8a
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):
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 not reason in DOMAIN_SHUTDOWN_REASONS.values():
481 raise XendError('Invalid reason: %s' % reason)
482 self._storeDom("control/shutdown", reason)
484 def pause(self):
485 """Pause domain
487 @raise XendError: Failed pausing a domain
488 """
489 try:
490 xc.domain_pause(self.domid)
491 self._stateSet(DOM_STATE_PAUSED)
492 except Exception, ex:
493 raise XendError("Domain unable to be paused: %s" % str(ex))
495 def unpause(self):
496 """Unpause domain
498 @raise XendError: Failed unpausing a domain
499 """
500 try:
501 xc.domain_unpause(self.domid)
502 self._stateSet(DOM_STATE_RUNNING)
503 except Exception, ex:
504 raise XendError("Domain unable to be unpaused: %s" % str(ex))
506 def send_sysrq(self, key):
507 """ Send a Sysrq equivalent key via xenstored."""
508 asserts.isCharConvertible(key)
509 self._storeDom("control/sysrq", '%c' % key)
511 def device_create(self, dev_config):
512 """Create a new device.
514 @param dev_config: device configuration
515 @type dev_config: dictionary (parsed config)
516 """
517 log.debug("XendDomainInfo.device_create: %s" % dev_config)
518 dev_type = sxp.name(dev_config)
519 devid = self._createDevice(dev_type, dev_config)
520 self.info.device_add(dev_type, cfg_sxp = dev_config)
521 self._waitForDevice(dev_type, devid)
522 return self.getDeviceController(dev_type).sxpr(devid)
524 def device_configure(self, dev_config, devid = None):
525 """Configure an existing device.
527 @param dev_config: device configuration
528 @type dev_config: dictionary (parsed config)
529 @param devid: device id
530 @type devid: int
531 """
532 deviceClass = sxp.name(dev_config)
533 self._reconfigureDevice(deviceClass, devid, dev_config)
535 def waitForDevices(self):
536 """Wait for this domain's configured devices to connect.
538 @raise VmError: if any device fails to initialise.
539 """
540 for devclass in XendDevices.valid_devices():
541 self.getDeviceController(devclass).waitForDevices()
543 def destroyDevice(self, deviceClass, devid):
544 try:
545 devid = int(devid)
546 except ValueError:
547 # devid is not a number, let's search for it in xenstore.
548 devicePath = '%s/device/%s' % (self.dompath, deviceClass)
549 for entry in xstransact.List(devicePath):
550 backend = xstransact.Read('%s/%s' % (devicePath, entry),
551 "backend")
552 devName = xstransact.Read(backend, "dev")
553 if devName == devid:
554 # We found the integer matching our devid, use it instead
555 devid = entry
556 break
558 return self.getDeviceController(deviceClass).destroyDevice(devid)
561 def getDeviceSxprs(self, deviceClass):
562 return self.getDeviceController(deviceClass).sxprs()
565 def setMemoryTarget(self, target):
566 """Set the memory target of this domain.
567 @param target: In MiB.
568 """
569 log.debug("Setting memory target of domain %s (%d) to %d MiB.",
570 self.info['name'], self.domid, target)
572 if target <= 0:
573 raise XendError('Invalid memory size')
575 self.info['memory'] = target
576 self.storeVm("memory", target)
577 self._storeDom("memory/target", target << 10)
579 def getVCPUInfo(self):
580 try:
581 # We include the domain name and ID, to help xm.
582 sxpr = ['domain',
583 ['domid', self.domid],
584 ['name', self.info['name']],
585 ['vcpu_count', self.info['online_vcpus']]]
587 for i in range(0, self.info['max_vcpu_id']+1):
588 info = xc.vcpu_getinfo(self.domid, i)
590 sxpr.append(['vcpu',
591 ['number', i],
592 ['online', info['online']],
593 ['blocked', info['blocked']],
594 ['running', info['running']],
595 ['cpu_time', info['cpu_time'] / 1e9],
596 ['cpu', info['cpu']],
597 ['cpumap', info['cpumap']]])
599 return sxpr
601 except RuntimeError, exn:
602 raise XendError(str(exn))
604 #
605 # internal functions ... TODO: re-categorised
606 #
608 def _augmentInfo(self, priv):
609 """Augment self.info, as given to us through L{recreate}, with
610 values taken from the store. This recovers those values known
611 to xend but not to the hypervisor.
612 """
613 def useIfNeeded(name, val):
614 if not self._infoIsSet(name) and val is not None:
615 self.info[name] = val
617 if priv:
618 entries = VM_STORE_ENTRIES[:]
619 entries.remove(('memory', int))
620 entries.remove(('maxmem', int))
621 else:
622 entries = VM_STORE_ENTRIES
623 entries.append(('image', str))
624 entries.append(('security', str))
626 map(lambda x, y: useIfNeeded(x[0], y), entries,
627 self._readVMDetails(entries))
629 devices = []
631 for devclass in XendDevices.valid_devices():
632 devconfig = self.getDeviceController(devclass).configurations()
633 if devconfig:
634 devices.extend(map(lambda conf: (devclass, conf), devconfig))
636 if not self.info['device'] and devices is not None:
637 for device in devices:
638 self.info.device_add(device[0], cfg_sxp = device)
640 #
641 # Function to update xenstore /vm/*
642 #
644 def _readVm(self, *args):
645 return xstransact.Read(self.vmpath, *args)
647 def _writeVm(self, *args):
648 return xstransact.Write(self.vmpath, *args)
650 def _removeVm(self, *args):
651 return xstransact.Remove(self.vmpath, *args)
653 def _gatherVm(self, *args):
654 return xstransact.Gather(self.vmpath, *args)
656 def storeVm(self, *args):
657 return xstransact.Store(self.vmpath, *args)
659 #
660 # Function to update xenstore /dom/*
661 #
663 def _readDom(self, *args):
664 return xstransact.Read(self.dompath, *args)
666 def _writeDom(self, *args):
667 return xstransact.Write(self.dompath, *args)
669 def _removeDom(self, *args):
670 return xstransact.Remove(self.dompath, *args)
672 def _storeDom(self, *args):
673 return xstransact.Store(self.dompath, *args)
675 def _recreateDom(self):
676 complete(self.dompath, lambda t: self._recreateDomFunc(t))
678 def _recreateDomFunc(self, t):
679 t.remove()
680 t.mkdir()
681 t.set_permissions({ 'dom' : self.domid })
682 t.write('vm', self.vmpath)
684 def _storeDomDetails(self):
685 to_store = {
686 'domid': str(self.domid),
687 'vm': self.vmpath,
688 'name': self.info['name'],
689 'console/limit': str(xroot.get_console_limit() * 1024),
690 'memory/target': str(self.info['memory'] * 1024)
691 }
693 def f(n, v):
694 if v is not None:
695 to_store[n] = str(v)
697 f('console/port', self.console_port)
698 f('console/ring-ref', self.console_mfn)
699 f('store/port', self.store_port)
700 f('store/ring-ref', self.store_mfn)
702 to_store.update(self._vcpuDomDetails())
704 log.debug("Storing domain details: %s", to_store)
706 self._writeDom(to_store)
708 def _vcpuDomDetails(self):
709 def availability(n):
710 if self.info['vcpu_avail'] & (1 << n):
711 return 'online'
712 else:
713 return 'offline'
715 result = {}
716 for v in range(0, self.info['vcpus']):
717 result["cpu/%d/availability" % v] = availability(v)
718 return result
720 #
721 # xenstore watches
722 #
724 def _registerWatches(self):
725 """Register a watch on this VM's entries in the store, and the
726 domain's control/shutdown node, so that when they are changed
727 externally, we keep up to date. This should only be called by {@link
728 #create}, {@link #recreate}, or {@link #restore}, once the domain's
729 details have been written, but before the new instance is returned."""
730 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
731 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
732 self._handleShutdownWatch)
734 def _storeChanged(self, _):
735 log.trace("XendDomainInfo.storeChanged");
737 changed = False
739 def f(x, y):
740 if y is not None and self.info[x[0]] != y:
741 self.info[x[0]] = y
742 changed = True
744 map(f, VM_CONFIG_PARAMS, self._readVMDetails(VM_CONFIG_PARAMS))
746 im = self._readVm('image')
747 current_im = self.info['image']
748 if (im is not None and
749 (current_im is None or sxp.to_string(current_im) != im)):
750 self.info['image'] = sxp.from_string(im)
751 changed = True
753 if changed:
754 # Update the domain section of the store, as this contains some
755 # parameters derived from the VM configuration.
756 self._storeDomDetails()
758 return 1
760 def _handleShutdownWatch(self, _):
761 log.debug('XendDomainInfo.handleShutdownWatch')
763 reason = self._readDom('control/shutdown')
765 if reason and reason != 'suspend':
766 sst = self._readDom('xend/shutdown_start_time')
767 now = time.time()
768 if sst:
769 self.shutdownStartTime = float(sst)
770 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
771 else:
772 self.shutdownStartTime = now
773 self._storeDom('xend/shutdown_start_time', now)
774 timeout = SHUTDOWN_TIMEOUT
776 log.trace(
777 "Scheduling refreshShutdown on domain %d in %ds.",
778 self.domid, timeout)
779 threading.Timer(timeout, self.refreshShutdown).start()
781 return True
784 #
785 # Public Attributes for the VM
786 #
789 def getDomid(self):
790 return self.domid
792 def setName(self, name):
793 self._checkName(name)
794 self.info['name'] = name
795 self.storeVm("name", name)
797 def getName(self):
798 return self.info['name']
800 def getDomainPath(self):
801 return self.dompath
803 def getShutdownReason(self):
804 return self._readDom('control/shutdown')
806 def getStorePort(self):
807 """For use only by image.py and XendCheckpoint.py."""
808 return self.store_port
810 def getConsolePort(self):
811 """For use only by image.py and XendCheckpoint.py"""
812 return self.console_port
814 def getFeatures(self):
815 """For use only by image.py."""
816 return self.info['features']
818 def getVCpuCount(self):
819 return self.info['vcpus']
821 def setVCpuCount(self, vcpus):
822 self.info['vcpu_avail'] = (1 << vcpus) - 1
823 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
824 self._writeDom(self._vcpuDomDetails())
826 def getLabel(self):
827 return security.get_security_info(self.info, 'label')
829 def getMemoryTarget(self):
830 """Get this domain's target memory size, in KB."""
831 return self.info['memory'] * 1024
833 def getResume(self):
834 return "%s" % self.info['resume']
836 def getCap(self):
837 return self.info['cpu_cap']
839 def getWeight(self):
840 return self.info['cpu_weight']
842 def setResume(self, state):
843 self.info['resume'] = state
845 def getRestartCount(self):
846 return self._readVm('xend/restart_count')
848 def refreshShutdown(self, xeninfo = None):
849 """ Checks the domain for whether a shutdown is required.
851 Called from XendDomainInfo and also image.py for HVM images.
852 """
854 # If set at the end of this method, a restart is required, with the
855 # given reason. This restart has to be done out of the scope of
856 # refresh_shutdown_lock.
857 restart_reason = None
859 self.refresh_shutdown_lock.acquire()
860 try:
861 if xeninfo is None:
862 xeninfo = dom_get(self.domid)
863 if xeninfo is None:
864 # The domain no longer exists. This will occur if we have
865 # scheduled a timer to check for shutdown timeouts and the
866 # shutdown succeeded. It will also occur if someone
867 # destroys a domain beneath us. We clean up the domain,
868 # just in case, but we can't clean up the VM, because that
869 # VM may have migrated to a different domain on this
870 # machine.
871 self.cleanupDomain()
872 self._stateSet(DOM_STATE_HALTED)
873 return
875 if xeninfo['dying']:
876 # Dying means that a domain has been destroyed, but has not
877 # yet been cleaned up by Xen. This state could persist
878 # indefinitely if, for example, another domain has some of its
879 # pages mapped. We might like to diagnose this problem in the
880 # future, but for now all we do is make sure that it's not us
881 # holding the pages, by calling cleanupDomain. We can't
882 # clean up the VM, as above.
883 self.cleanupDomain()
884 self._stateSet(DOM_STATE_SHUTDOWN)
885 return
887 elif xeninfo['crashed']:
888 if self._readDom('xend/shutdown_completed'):
889 # We've seen this shutdown already, but we are preserving
890 # the domain for debugging. Leave it alone.
891 return
893 log.warn('Domain has crashed: name=%s id=%d.',
894 self.info['name'], self.domid)
896 if xroot.get_enable_dump():
897 self.dumpCore()
899 restart_reason = 'crash'
900 self._stateSet(DOM_STATE_HALTED)
902 elif xeninfo['shutdown']:
903 self._stateSet(DOM_STATE_SHUTDOWN)
904 if self._readDom('xend/shutdown_completed'):
905 # We've seen this shutdown already, but we are preserving
906 # the domain for debugging. Leave it alone.
907 return
909 else:
910 reason = shutdown_reason(xeninfo['shutdown_reason'])
912 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
913 self.info['name'], self.domid, reason)
915 self._clearRestart()
917 if reason == 'suspend':
918 self._stateSet(DOM_STATE_SUSPENDED)
919 # Don't destroy the domain. XendCheckpoint will do
920 # this once it has finished. However, stop watching
921 # the VM path now, otherwise we will end up with one
922 # watch for the old domain, and one for the new.
923 self._unwatchVm()
924 elif reason in ('poweroff', 'reboot'):
925 restart_reason = reason
926 else:
927 self.destroy()
929 elif self.dompath is None:
930 # We have yet to manage to call introduceDomain on this
931 # domain. This can happen if a restore is in progress, or has
932 # failed. Ignore this domain.
933 pass
934 else:
935 # Domain is alive. If we are shutting it down, then check
936 # the timeout on that, and destroy it if necessary.
937 if xeninfo['paused']:
938 self._stateSet(DOM_STATE_PAUSED)
939 else:
940 self._stateSet(DOM_STATE_RUNNING)
942 if self.shutdownStartTime:
943 timeout = (SHUTDOWN_TIMEOUT - time.time() +
944 self.shutdownStartTime)
945 if timeout < 0:
946 log.info(
947 "Domain shutdown timeout expired: name=%s id=%s",
948 self.info['name'], self.domid)
949 self.destroy()
950 finally:
951 self.refresh_shutdown_lock.release()
953 if restart_reason:
954 self._maybeRestart(restart_reason)
957 #
958 # Restart functions - handling whether we come back up on shutdown.
959 #
961 def _clearRestart(self):
962 self._removeDom("xend/shutdown_start_time")
965 def _maybeRestart(self, reason):
966 # Dispatch to the correct method based upon the configured on_{reason}
967 # behaviour.
968 {"destroy" : self.destroy,
969 "restart" : self._restart,
970 "preserve" : self._preserve,
971 "rename-restart" : self._renameRestart}[self.info['on_' + reason]]()
974 def _renameRestart(self):
975 self._restart(True)
977 def _restart(self, rename = False):
978 """Restart the domain after it has exited.
980 @param rename True if the old domain is to be renamed and preserved,
981 False if it is to be destroyed.
982 """
983 from xen.xend import XendDomain
985 self._configureBootloader()
986 config = self.sxpr()
988 if self._infoIsSet('cpus') and len(self.info['cpus']) != 0:
989 config.append(['cpus', reduce(lambda x, y: str(x) + "," + str(y),
990 self.info['cpus'])])
992 if self._readVm(RESTART_IN_PROGRESS):
993 log.error('Xend failed during restart of domain %s. '
994 'Refusing to restart to avoid loops.',
995 str(self.domid))
996 self.destroy()
997 return
999 old_domid = self.domid
1000 self._writeVm(RESTART_IN_PROGRESS, 'True')
1002 now = time.time()
1003 rst = self._readVm('xend/previous_restart_time')
1004 if rst:
1005 rst = float(rst)
1006 timeout = now - rst
1007 if timeout < MINIMUM_RESTART_TIME:
1008 log.error(
1009 'VM %s restarting too fast (%f seconds since the last '
1010 'restart). Refusing to restart to avoid loops.',
1011 self.info['name'], timeout)
1012 self.destroy()
1013 return
1015 self._writeVm('xend/previous_restart_time', str(now))
1017 try:
1018 if rename:
1019 self._preserveForRestart()
1020 else:
1021 self._unwatchVm()
1022 self.destroyDomain()
1024 # new_dom's VM will be the same as this domain's VM, except where
1025 # the rename flag has instructed us to call preserveForRestart.
1026 # In that case, it is important that we remove the
1027 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1028 # once the new one is available.
1030 new_dom = None
1031 try:
1032 new_dom = XendDomain.instance().domain_create(config)
1033 new_dom.unpause()
1034 rst_cnt = self._readVm('xend/restart_count')
1035 rst_cnt = int(rst_cnt) + 1
1036 self._writeVm('xend/restart_count', str(rst_cnt))
1037 new_dom._removeVm(RESTART_IN_PROGRESS)
1038 except:
1039 if new_dom:
1040 new_dom._removeVm(RESTART_IN_PROGRESS)
1041 new_dom.destroy()
1042 else:
1043 self._removeVm(RESTART_IN_PROGRESS)
1044 raise
1045 except:
1046 log.exception('Failed to restart domain %s.', str(old_domid))
1048 def _preserveForRestart(self):
1049 """Preserve a domain that has been shut down, by giving it a new UUID,
1050 cloning the VM details, and giving it a new name. This allows us to
1051 keep this domain for debugging, but restart a new one in its place
1052 preserving the restart semantics (name and UUID preserved).
1053 """
1055 new_uuid = uuid.createString()
1056 new_name = 'Domain-%s' % new_uuid
1057 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1058 self.info['name'], self.domid, self.info['uuid'],
1059 new_name, new_uuid)
1060 self._unwatchVm()
1061 self._releaseDevices()
1062 self.info['name'] = new_name
1063 self.info['uuid'] = new_uuid
1064 self.vmpath = XS_VMROOT + new_uuid
1065 self._storeVmDetails()
1066 self._preserve()
1069 def _preserve(self):
1070 log.info("Preserving dead domain %s (%d).", self.info['name'],
1071 self.domid)
1072 self._unwatchVm()
1073 self._storeDom('xend/shutdown_completed', 'True')
1074 self._stateSet(DOM_STATE_HALTED)
1077 # Debugging ..
1080 def dumpCore(self, corefile = None):
1081 """Create a core dump for this domain. Nothrow guarantee."""
1083 try:
1084 if not corefile:
1085 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1086 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1087 self.info['name'], self.domid)
1089 if os.path.isdir(corefile):
1090 raise XendError("Cannot dump core in a directory: %s" %
1091 corefile)
1093 xc.domain_dumpcore(self.domid, corefile)
1094 except RuntimeError, ex:
1095 corefile_incomp = corefile+'-incomplete'
1096 os.rename(corefile, corefile_incomp)
1097 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1098 self.domid, self.info['name'])
1099 raise XendError("Failed to dump core: %s" % str(ex))
1102 # Device creation/deletion functions
1105 def _createDevice(self, deviceClass, devConfig):
1106 return self.getDeviceController(deviceClass).createDevice(devConfig)
1108 def _waitForDevice(self, deviceClass, devid):
1109 return self.getDeviceController(deviceClass).waitForDevice(devid)
1111 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1112 return self.getDeviceController(deviceClass).reconfigureDevice(
1113 devid, devconfig)
1115 def _createDevices(self):
1116 """Create the devices for a vm.
1118 @raise: VmError for invalid devices
1119 """
1120 for (devclass, config) in self.info.all_devices_sxpr():
1121 if devclass in XendDevices.valid_devices():
1122 log.info("createDevice: %s : %s" % (devclass, config))
1123 self._createDevice(devclass, config)
1125 if self.image:
1126 self.image.createDeviceModel()
1128 def _releaseDevices(self):
1129 """Release all domain's devices. Nothrow guarantee."""
1131 while True:
1132 t = xstransact("%s/device" % self.dompath)
1133 for devclass in XendDevices.valid_devices():
1134 for dev in t.list(devclass):
1135 try:
1136 t.remove(dev)
1137 except:
1138 # Log and swallow any exceptions in removal --
1139 # there's nothing more we can do.
1140 log.exception(
1141 "Device release failed: %s; %s; %s",
1142 self.info['name'], devclass, dev)
1143 if t.commit():
1144 break
1146 def getDeviceController(self, name):
1147 """Get the device controller for this domain, and if it
1148 doesn't exist, create it.
1150 @param name: device class name
1151 @type name: string
1152 @rtype: subclass of DevController
1153 """
1154 if name not in self._deviceControllers:
1155 devController = XendDevices.make_controller(name, self)
1156 if not devController:
1157 raise XendError("Unknown device type: %s" % name)
1158 self._deviceControllers[name] = devController
1160 return self._deviceControllers[name]
1163 # Migration functions (public)
1166 def testMigrateDevices(self, network, dst):
1167 """ Notify all device about intention of migration
1168 @raise: XendError for a device that cannot be migrated
1169 """
1170 for (n, c) in self.info.all_devices_sxpr():
1171 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1172 if rc != 0:
1173 raise XendError("Device of type '%s' refuses migration." % n)
1175 def migrateDevices(self, network, dst, step, domName=''):
1176 """Notify the devices about migration
1177 """
1178 ctr = 0
1179 try:
1180 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1181 self.migrateDevice(dev_type, dev_conf, network, dst,
1182 step, domName)
1183 ctr = ctr + 1
1184 except:
1185 for dev_type, dev_conf in self.info.all_devices_sxpr():
1186 if ctr == 0:
1187 step = step - 1
1188 ctr = ctr - 1
1189 self._recoverMigrateDevice(dev_type, dev_conf, network,
1190 dst, step, domName)
1191 raise
1193 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1194 step, domName=''):
1195 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1196 network, dst, step, domName)
1198 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1199 dst, step, domName=''):
1200 return self.getDeviceController(deviceClass).recover_migrate(
1201 deviceConfig, network, dst, step, domName)
1204 ## private:
1206 def _constructDomain(self):
1207 """Construct the domain.
1209 @raise: VmError on error
1210 """
1212 log.debug('XendDomainInfo.constructDomain')
1214 hvm = (self._infoIsSet('image') and
1215 sxp.name(self.info['image']) == "hvm")
1216 if hvm:
1217 info = xc.xeninfo()
1218 if not 'hvm' in info['xen_caps']:
1219 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1220 "supported by your CPU and enabled in your "
1221 "BIOS?")
1223 self.domid = xc.domain_create(
1224 domid = 0,
1225 ssidref = security.get_security_info(self.info, 'ssidref'),
1226 handle = uuid.fromString(self.info['uuid']),
1227 hvm = int(hvm))
1229 if self.domid < 0:
1230 raise VmError('Creating domain failed: name=%s' %
1231 self.info['name'])
1233 self.dompath = GetDomainPath(self.domid)
1235 self._recreateDom()
1237 # Set maximum number of vcpus in domain
1238 xc.domain_max_vcpus(self.domid, int(self.info['vcpus']))
1241 def _introduceDomain(self):
1242 assert self.domid is not None
1243 assert self.store_mfn is not None
1244 assert self.store_port is not None
1246 try:
1247 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1248 except RuntimeError, exn:
1249 raise XendError(str(exn))
1252 def _initDomain(self):
1253 log.debug('XendDomainInfo.initDomain: %s %s',
1254 self.domid,
1255 self.info['cpu_weight'])
1257 # if we have a boot loader but no image, then we need to set things
1258 # up by running the boot loader non-interactively
1259 if self._infoIsSet('bootloader') and not self._infoIsSet('image'):
1260 self._configureBootloader()
1262 if not self._infoIsSet('image'):
1263 raise VmError('Missing image in configuration')
1265 try:
1266 self.image = image.create(self,
1267 self.info['image'],
1268 self.info.all_devices_sxpr())
1270 localtime = self.info.get('localtime', 0)
1271 if localtime is not None and localtime == 1:
1272 xc.domain_set_time_offset(self.domid)
1274 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1276 # repin domain vcpus if a restricted cpus list is provided
1277 # this is done prior to memory allocation to aide in memory
1278 # distribution for NUMA systems.
1279 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1280 for v in range(0, self.info['max_vcpu_id']+1):
1281 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1283 # Use architecture- and image-specific calculations to determine
1284 # the various headrooms necessary, given the raw configured
1285 # values. maxmem, memory, and shadow are all in KiB.
1286 maxmem = self.image.getRequiredAvailableMemory(
1287 self.info['maxmem'] * 1024)
1288 memory = self.image.getRequiredAvailableMemory(
1289 self.info['memory'] * 1024)
1290 shadow = self.image.getRequiredShadowMemory(
1291 self.info['shadow_memory'] * 1024,
1292 self.info['maxmem'] * 1024)
1294 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1295 # takes MiB and we must not round down and end up under-providing.
1296 shadow = ((shadow + 1023) / 1024) * 1024
1298 # set memory limit
1299 xc.domain_setmaxmem(self.domid, maxmem)
1301 # Make sure there's enough RAM available for the domain
1302 balloon.free(memory + shadow)
1304 # Set up the shadow memory
1305 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1306 self.info['shadow_memory'] = shadow_cur
1308 self._createChannels()
1310 channel_details = self.image.createImage()
1312 self.store_mfn = channel_details['store_mfn']
1313 if 'console_mfn' in channel_details:
1314 self.console_mfn = channel_details['console_mfn']
1316 self._introduceDomain()
1318 self._createDevices()
1320 if self.info['bootloader'] not in [None, 'kernel_external']:
1321 self.image.cleanupBootloading()
1323 self.info['start_time'] = time.time()
1325 self._stateSet(DOM_STATE_RUNNING)
1326 except RuntimeError, exn:
1327 log.exception("XendDomainInfo.initDomain: exception occurred")
1328 if self.info['bootloader'] not in [None, 'kernel_external'] \
1329 and self.image is not None:
1330 self.image.cleanupBootloading()
1331 raise VmError(str(exn))
1334 def cleanupDomain(self):
1335 """Cleanup domain resources; release devices. Idempotent. Nothrow
1336 guarantee."""
1338 self.refresh_shutdown_lock.acquire()
1339 try:
1340 self.unwatchShutdown()
1342 self._releaseDevices()
1344 if self.image:
1345 try:
1346 self.image.destroy()
1347 except:
1348 log.exception(
1349 "XendDomainInfo.cleanup: image.destroy() failed.")
1350 self.image = None
1352 try:
1353 self._removeDom()
1354 except:
1355 log.exception("Removing domain path failed.")
1357 self._stateSet(DOM_STATE_HALTED)
1358 finally:
1359 self.refresh_shutdown_lock.release()
1362 def unwatchShutdown(self):
1363 """Remove the watch on the domain's control/shutdown node, if any.
1364 Idempotent. Nothrow guarantee. Expects to be protected by the
1365 refresh_shutdown_lock."""
1367 try:
1368 try:
1369 if self.shutdownWatch:
1370 self.shutdownWatch.unwatch()
1371 finally:
1372 self.shutdownWatch = None
1373 except:
1374 log.exception("Unwatching control/shutdown failed.")
1376 def waitForShutdown(self):
1377 self.state_updated.acquire()
1378 try:
1379 while self.state in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1380 self.state_updated.wait()
1381 finally:
1382 self.state_updated.release()
1386 # TODO: recategorise - called from XendCheckpoint
1389 def completeRestore(self, store_mfn, console_mfn):
1391 log.debug("XendDomainInfo.completeRestore")
1393 self.store_mfn = store_mfn
1394 self.console_mfn = console_mfn
1396 self._introduceDomain()
1397 self._storeDomDetails()
1398 self._registerWatches()
1399 self.refreshShutdown()
1401 log.debug("XendDomainInfo.completeRestore done")
1404 def _endRestore(self):
1405 self.setResume(False)
1408 # VM Destroy
1411 def destroy(self):
1412 """Cleanup VM and destroy domain. Nothrow guarantee."""
1414 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1416 self._cleanupVm()
1417 if self.dompath is not None:
1418 self.destroyDomain()
1421 def destroyDomain(self):
1422 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1424 try:
1425 if self.domid is not None:
1426 xc.domain_destroy(self.domid)
1427 self.domid = None
1428 for state in DOM_STATES_OLD:
1429 self.info[state] = 0
1430 except:
1431 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1433 self.cleanupDomain()
1437 # Channels for xenstore and console
1440 def _createChannels(self):
1441 """Create the channels to the domain.
1442 """
1443 self.store_port = self._createChannel()
1444 self.console_port = self._createChannel()
1447 def _createChannel(self):
1448 """Create an event channel to the domain.
1449 """
1450 try:
1451 return xc.evtchn_alloc_unbound(domid=self.domid, remote_dom=0)
1452 except:
1453 log.exception("Exception in alloc_unbound(%d)", self.domid)
1454 raise
1457 # Bootloader configuration
1460 def _configureBootloader(self):
1461 """Run the bootloader if we're configured to do so."""
1462 if not self.info['bootloader']:
1463 return
1464 blcfg = None
1465 # FIXME: this assumes that we want to use the first disk device
1466 for (n, c) in self.info.all_devices_sxpr():
1467 if not n or not c or not(n in ["vbd", "tap"]):
1468 continue
1469 disk = sxp.child_value(c, "uname")
1470 if disk is None:
1471 continue
1472 fn = blkdev_uname_to_file(disk)
1473 blcfg = bootloader(self.info['bootloader'], fn, 1,
1474 self.info['bootloader_args'],
1475 self.info['image'])
1476 break
1477 if blcfg is None:
1478 msg = "Had a bootloader specified, but can't find disk"
1479 log.error(msg)
1480 raise VmError(msg)
1481 self.info['image'] = blcfg
1484 # VM Functions
1487 def _readVMDetails(self, params):
1488 """Read the specified parameters from the store.
1489 """
1490 try:
1491 return self._gatherVm(*params)
1492 except ValueError:
1493 # One of the int/float entries in params has a corresponding store
1494 # entry that is invalid. We recover, because older versions of
1495 # Xend may have put the entry there (memory/target, for example),
1496 # but this is in general a bad situation to have reached.
1497 log.exception(
1498 "Store corrupted at %s! Domain %d's configuration may be "
1499 "affected.", self.vmpath, self.domid)
1500 return []
1502 def _cleanupVm(self):
1503 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1505 self._unwatchVm()
1507 try:
1508 self._removeVm()
1509 except:
1510 log.exception("Removing VM path failed.")
1513 def checkLiveMigrateMemory(self):
1514 """ Make sure there's enough memory to migrate this domain """
1515 overhead_kb = 0
1516 if arch.type == "x86":
1517 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1518 # the minimum that Xen would allocate if no value were given.
1519 overhead_kb = self.info['vcpus'] * 1024 + self.info['maxmem'] * 4
1520 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1521 # The domain might already have some shadow memory
1522 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1523 if overhead_kb > 0:
1524 balloon.free(overhead_kb)
1526 def _unwatchVm(self):
1527 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1528 guarantee."""
1529 try:
1530 try:
1531 if self.vmWatch:
1532 self.vmWatch.unwatch()
1533 finally:
1534 self.vmWatch = None
1535 except:
1536 log.exception("Unwatching VM path failed.")
1538 def testDeviceComplete(self):
1539 """ For Block IO migration safety we must ensure that
1540 the device has shutdown correctly, i.e. all blocks are
1541 flushed to disk
1542 """
1543 start = time.time()
1544 while True:
1545 test = 0
1546 diff = time.time() - start
1547 for i in self.getDeviceController('vbd').deviceIDs():
1548 test = 1
1549 log.info("Dev %s still active, looping...", i)
1550 time.sleep(0.1)
1552 if test == 0:
1553 break
1554 if diff >= MIGRATE_TIMEOUT:
1555 log.info("Dev still active but hit max loop timeout")
1556 break
1558 def _storeVmDetails(self):
1559 to_store = {}
1561 for k in VM_STORE_ENTRIES:
1562 if self._infoIsSet(k[0]):
1563 to_store[k[0]] = str(self.info[k[0]])
1565 if self._infoIsSet('image'):
1566 to_store['image'] = sxp.to_string(self.info['image'])
1568 if self._infoIsSet('security'):
1569 secinfo = self.info['security']
1570 to_store['security'] = sxp.to_string(secinfo)
1571 for idx in range(0, len(secinfo)):
1572 if secinfo[idx][0] == 'access_control':
1573 to_store['security/access_control'] = sxp.to_string(
1574 [secinfo[idx][1], secinfo[idx][2]])
1575 for aidx in range(1, len(secinfo[idx])):
1576 if secinfo[idx][aidx][0] == 'label':
1577 to_store['security/access_control/label'] = \
1578 secinfo[idx][aidx][1]
1579 if secinfo[idx][aidx][0] == 'policy':
1580 to_store['security/access_control/policy'] = \
1581 secinfo[idx][aidx][1]
1582 if secinfo[idx][0] == 'ssidref':
1583 to_store['security/ssidref'] = str(secinfo[idx][1])
1586 if not self._readVm('xend/restart_count'):
1587 to_store['xend/restart_count'] = str(0)
1589 log.debug("Storing VM details: %s", to_store)
1591 self._writeVm(to_store)
1592 self._setVmPermissions()
1595 def _setVmPermissions(self):
1596 """Allow the guest domain to read its UUID. We don't allow it to
1597 access any other entry, for security."""
1598 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1599 { 'dom' : self.domid,
1600 'read' : True,
1601 'write' : False })
1604 # Utility functions
1607 def _stateSet(self, state):
1608 self.state_updated.acquire()
1609 try:
1610 if self.state != state:
1611 self.state = state
1612 self.state_updated.notifyAll()
1613 finally:
1614 self.state_updated.release()
1616 def _infoIsSet(self, name):
1617 return name in self.info and self.info[name] is not None
1619 def _checkName(self, name):
1620 """Check if a vm name is valid. Valid names contain alphabetic
1621 characters, digits, or characters in '_-.:/+'.
1622 The same name cannot be used for more than one vm at the same time.
1624 @param name: name
1625 @raise: VmError if invalid
1626 """
1627 from xen.xend import XendDomain
1629 if name is None or name == '':
1630 raise VmError('Missing VM Name')
1632 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1633 raise VmError('Invalid VM Name')
1635 dom = XendDomain.instance().domain_lookup_nr(name)
1636 if dom and dom != self and not dom.info['dying']:
1637 raise VmError("VM name '%s' already exists" % name)
1640 def update(self, info = None, refresh = True):
1641 """Update with info from xc.domain_getinfo().
1642 """
1643 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1644 str(self.domid))
1646 if not info:
1647 info = dom_get(self.domid)
1648 if not info:
1649 return
1651 #manually update ssidref / security fields
1652 if security.on() and info.has_key('ssidref'):
1653 if (info['ssidref'] != 0) and self.info.has_key('security'):
1654 security_field = self.info['security']
1655 if not security_field:
1656 #create new security element
1657 self.info.update({'security':
1658 [['ssidref', str(info['ssidref'])]]})
1659 #ssidref field not used any longer
1660 if 'ssidref' in info:
1661 info.pop('ssidref')
1663 # make sure state is reset for info
1664 # TODO: we should eventually get rid of old_dom_states
1666 self.info.update(info)
1667 self.info.validate()
1669 if refresh:
1670 self.refreshShutdown(info)
1672 log.trace("XendDomainInfo.update done on domain %s: %s",
1673 str(self.domid), self.info)
1675 def sxpr(self, ignore_store = False):
1676 result = self.info.get_sxp(domain = self,
1677 ignore_devices = ignore_store)
1679 if not ignore_store and self.dompath:
1680 vnc_port = self._readDom('console/vnc-port')
1681 if vnc_port is not None:
1682 result.append(['device',
1683 ['console', ['vnc-port', str(vnc_port)]]])
1685 return result
1687 # Xen API
1688 # ----------------------------------------------------------------
1690 def get_uuid(self):
1691 dom_uuid = self.info.get('uuid')
1692 if not dom_uuid: # if it doesn't exist, make one up
1693 dom_uuid = uuid.createString()
1694 self.info['uuid'] = dom_uuid
1695 return dom_uuid
1697 def get_memory_static_max(self):
1698 return self.info['maxmem']
1699 def get_memory_static_min(self):
1700 return self.info['memory']
1701 def get_vcpus_policy(self):
1702 sched_id = xc.sched_id_get()
1703 if sched_id == xen.lowlevel.xc.XEN_SCHEDULER_SEDF:
1704 return 'sedf'
1705 elif sched_id == xen.lowlevel.xc.XEN_SCHEDULER_CREDIT:
1706 return 'credit'
1707 else:
1708 return 'unknown'
1709 def get_vcpus_params(self):
1710 return '' # TODO
1711 def get_power_state(self):
1712 return XEN_API_VM_POWER_STATE[self.state]
1713 def get_bios_boot(self):
1714 return '' # TODO
1715 def get_platform_std_vga(self):
1716 return False
1717 def get_platform_keymap(self):
1718 return ''
1719 def get_platform_serial(self):
1720 return '' # TODO
1721 def get_platform_localtime(self):
1722 return False # TODO
1723 def get_platform_clock_offset(self):
1724 return False # TODO
1725 def get_platform_enable_audio(self):
1726 return False # TODO
1727 def get_builder(self):
1728 return 'Linux' # TODO
1729 def get_boot_method(self):
1730 bootloader = self.info['bootloader']
1731 if not bootloader or bootloader not in XEN_API_BOOT_TYPE:
1732 return 'kernel_external'
1733 return bootloader
1735 def get_kernel_image(self):
1736 return self.info['kernel_kernel']
1737 def get_kernel_initrd(self):
1738 return self.info['kernel_initrd']
1739 def get_kernel_args(self):
1740 return self.info['kernel_args']
1741 def get_grub_cmdline(self):
1742 return '' # TODO
1743 def get_pci_bus(self):
1744 return 0 # TODO
1745 def get_tools_version(self):
1746 return {} # TODO
1747 def get_other_config(self):
1748 return {} # TODO
1750 def get_on_shutdown(self):
1751 after_shutdown = self.info.get('on_poweroff')
1752 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
1753 return XEN_API_ON_NORMAL_EXIT[-1]
1754 return after_shutdown
1756 def get_on_reboot(self):
1757 after_reboot = self.info.get('on_reboot')
1758 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
1759 return XEN_API_ON_NORMAL_EXIT[-1]
1760 return after_reboot
1762 def get_on_suspend(self):
1763 after_suspend = self.info.get('on_suspend') # TODO: not supported
1764 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
1765 return XEN_API_ON_NORMAL_EXIT[-1]
1766 return after_suspend
1768 def get_on_crash(self):
1769 after_crash = self.info.get('on_crash')
1770 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
1771 return XEN_API_ON_CRASH_BEHAVIOUR[0]
1772 return after_crash
1774 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
1775 """ Get's a device configuration either from XendConfig or
1776 from the DevController.
1778 @param dev_class: device class, either, 'vbd' or 'vif'
1779 @param dev_uuid: device UUID
1781 @rtype: dictionary
1782 """
1783 dev_type_config = self.info['device'].get(dev_uuid)
1785 # shortcut if the domain isn't started because
1786 # the devcontrollers will have no better information
1787 # than XendConfig.
1788 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
1789 if dev_type_config:
1790 return copy.deepcopy(dev_type_config[1])
1791 return None
1793 # instead of using dev_class, we use the dev_type
1794 # that is from XendConfig.
1795 # This will accomdate 'tap' as well as 'vbd'
1796 dev_type = dev_type_config[0]
1798 controller = self.getDeviceController(dev_type)
1799 if not controller:
1800 return None
1802 all_configs = controller.getAllDeviceConfigurations()
1803 if not all_configs:
1804 return None
1806 dev_config = copy.deepcopy(dev_type_config[1])
1807 for _devid, _devcfg in all_configs.items():
1808 if _devcfg.get('uuid') == dev_uuid:
1809 dev_config.update(_devcfg)
1810 dev_config['id'] = _devid
1811 return dev_config
1813 return dev_config
1815 def get_dev_xenapi_config(self, dev_class, dev_uuid):
1816 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
1817 if not config:
1818 return {}
1820 config['VM'] = self.get_uuid()
1822 if dev_class == 'vif':
1823 if not config.has_key('name'):
1824 config['name'] = config.get('vifname', '')
1825 if not config.has_key('MAC'):
1826 config['MAC'] = config.get('mac', '')
1827 if not config.has_key('type'):
1828 config['type'] = 'paravirtualised'
1829 if not config.has_key('device'):
1830 devid = config.get('id')
1831 if devid != None:
1832 config['device'] = 'eth%d' % devid
1833 else:
1834 config['device'] = ''
1836 config['network'] = '' # Invalid for Xend
1837 config['MTU'] = 1500 # TODO
1838 config['network_read_kbs'] = 0.0
1839 config['network_write_kbs'] = 0.0
1840 config['IO_bandwidth_incoming_kbs'] = 0.0
1841 config['IO_bandwidth_outgoing_kbs'] = 0.0
1843 if dev_class =='vbd':
1844 config['VDI'] = '' # TODO
1845 config['device'] = config.get('dev', '')
1846 config['driver'] = 'paravirtualised' # TODO
1847 config['image'] = config.get('uname', '')
1848 config['IO_bandwidth_incoming_kbs'] = 0.0
1849 config['IO_bandwidth_outgoing_kbs'] = 0.0
1850 if config['mode'] == 'r':
1851 config['mode'] = 'RO'
1852 else:
1853 config['mode'] = 'RW'
1855 if dev_class == 'vtpm':
1856 config['driver'] = 'paravirtualised' # TODO
1858 return config
1860 def get_dev_property(self, dev_class, dev_uuid, field):
1861 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
1862 try:
1863 return config[field]
1864 except KeyError:
1865 raise XendError('Invalid property for device: %s' % field)
1867 def get_vcpus_util(self):
1868 # TODO: this returns the total accum cpu time, rather than util
1869 # TODO: spec says that key is int, however, python does not allow
1870 # non-string keys to dictionaries.
1871 vcpu_util = {}
1872 if 'max_vcpu_id' in self.info and self.domid != None:
1873 for i in range(0, self.info['max_vcpu_id']+1):
1874 info = xc.vcpu_getinfo(self.domid, i)
1875 vcpu_util[str(i)] = info['cpu_time']/1000000000.0
1877 return vcpu_util
1879 def get_vifs(self):
1880 return self.info.get('vif_refs', [])
1882 def get_vbds(self):
1883 return self.info.get('vbd_refs', [])
1885 def get_vtpms(self):
1886 return self.info.get('vtpm_refs', [])
1888 def create_vbd(self, xenapi_vbd):
1889 """Create a VBD device from the passed struct in Xen API format.
1891 @return: uuid of the device
1892 @rtype: string
1893 """
1895 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
1896 if not dev_uuid:
1897 raise XendError('Failed to create device')
1899 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1900 sxpr = self.info.device_sxpr(dev_uuid)
1901 devid = self.getDeviceController('vbd').createDevice(sxpr)
1902 raise XendError("Device creation failed")
1904 return dev_uuid
1906 def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
1907 """Create a VBD using a VDI from XendStorageRepository.
1909 @param xenapi_vbd: vbd struct from the Xen API
1910 @param vdi_image_path: VDI UUID
1911 @rtype: string
1912 @return: uuid of the device
1913 """
1914 xenapi_vbd['image'] = vdi_image_path
1915 log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
1916 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
1917 if not dev_uuid:
1918 raise XendError('Failed to create device')
1920 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1921 sxpr = self.info.device_sxpr(dev_uuid)
1922 devid = self.getDeviceController('tap').createDevice(sxpr)
1923 raise XendError("Device creation failed")
1925 return dev_uuid
1927 def create_vif(self, xenapi_vif):
1928 """Create VIF device from the passed struct in Xen API format.
1930 @param xenapi_vif: Xen API VIF Struct.
1931 @rtype: string
1932 @return: UUID
1933 """
1934 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
1935 if not dev_uuid:
1936 raise XendError('Failed to create device')
1938 if self.state in (DOM_STATE_HALTED,):
1939 sxpr = self.info.device_sxpr(dev_uuid)
1940 devid = self.getDeviceController('vif').createDevice(sxpr)
1941 raise XendError("Device creation failed")
1943 return dev_uuid
1945 def create_vtpm(self, xenapi_vtpm):
1946 """Create a VTPM device from the passed struct in Xen API format.
1948 @return: uuid of the device
1949 @rtype: string
1950 """
1952 if self.state not in (DOM_STATE_HALTED,):
1953 raise VmError("Can only add vTPM to a halted domain.")
1954 if self.get_vtpms() != []:
1955 raise VmError('Domain already has a vTPM.')
1956 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
1957 if not dev_uuid:
1958 raise XendError('Failed to create device')
1960 return dev_uuid
1962 def has_device(self, dev_class, dev_uuid):
1963 return (dev_uuid in self.info['%s_refs' % dev_class])
1965 """
1966 def stateChar(name):
1967 if name in self.info:
1968 if self.info[name]:
1969 return name[0]
1970 else:
1971 return '-'
1972 else:
1973 return '?'
1975 state = reduce(lambda x, y: x + y, map(stateChar, DOM_STATES_OLD))
1977 sxpr.append(['state', state])
1979 if self.store_mfn:
1980 sxpr.append(['store_mfn', self.store_mfn])
1981 if self.console_mfn:
1982 sxpr.append(['console_mfn', self.console_mfn])
1983 """
1985 def __str__(self):
1986 return '<domain id=%s name=%s memory=%s state=%s>' % \
1987 (str(self.domid), self.info['name'],
1988 str(self.info['memory']), DOM_STATES[self.state])
1990 __repr__ = __str__