ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 12229:96ad1d72fccf

[XEND] Fix paused state being overriden by refreshShutdown

Signed-off-by: Alastair Tse <atse@xensource.com>
author Alastair Tse <atse@xensource.com>
date Thu Nov 02 15:48:32 2006 +0000 (2006-11-02)
parents e6fdb32b786c
children ec7e7e946496
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 from types import StringTypes
34 import xen.lowlevel.xc
35 from xen.util import asserts
36 from xen.util.blkif import blkdev_uname_to_file
37 from xen.util import security
39 from xen.xend import balloon, sxp, uuid, image, arch
40 from xen.xend import XendRoot, XendNode
42 from xen.xend.XendBootloader import bootloader
43 from xen.xend.XendConfig import XendConfig
44 from xen.xend.XendError import XendError, VmError
45 from xen.xend.XendDevices import XendDevices
46 from xen.xend.xenstore.xstransact import xstransact, complete
47 from xen.xend.xenstore.xsutil import GetDomainPath, IntroduceDomain
48 from xen.xend.xenstore.xswatch import xswatch
49 from xen.xend.XendConstants import *
50 from xen.xend.XendAPIConstants import *
52 MIGRATE_TIMEOUT = 30.0
54 xc = xen.lowlevel.xc.xc()
55 xroot = XendRoot.instance()
57 log = logging.getLogger("xend.XendDomainInfo")
58 #log.setLevel(logging.TRACE)
60 ##
61 # All parameters of VMs that may be configured on-the-fly, or at start-up.
62 #
63 VM_CONFIG_PARAMS = [
64 ('name', str),
65 ('on_poweroff', str),
66 ('on_reboot', str),
67 ('on_crash', str),
68 ]
71 ##
72 # Configuration entries that we expect to round-trip -- be read from the
73 # config file or xc, written to save-files (i.e. through sxpr), and reused as
74 # config on restart or restore, all without munging. Some configuration
75 # entries are munged for backwards compatibility reasons, or because they
76 # don't come out of xc in the same form as they are specified in the config
77 # file, so those are handled separately.
78 ROUNDTRIPPING_CONFIG_ENTRIES = [
79 ('uuid', str),
80 ('vcpus', int),
81 ('vcpu_avail', int),
82 ('cpu_cap', int),
83 ('cpu_weight', int),
84 ('memory', int),
85 ('shadow_memory', int),
86 ('maxmem', int),
87 ('bootloader', str),
88 ('bootloader_args', str),
89 ('features', str),
90 ('localtime', int),
91 ]
93 ROUNDTRIPPING_CONFIG_ENTRIES += VM_CONFIG_PARAMS
96 ##
97 # All entries written to the store. This is VM_CONFIG_PARAMS, plus those
98 # entries written to the store that cannot be reconfigured on-the-fly.
99 #
100 VM_STORE_ENTRIES = [
101 ('uuid', str),
102 ('vcpus', int),
103 ('vcpu_avail', int),
104 ('memory', int),
105 ('shadow_memory', int),
106 ('maxmem', int),
107 ('start_time', float),
108 ('on_xend_start', str),
109 ('on_xend_stop', str),
110 ]
112 VM_STORE_ENTRIES += VM_CONFIG_PARAMS
115 #
116 # There are a number of CPU-related fields:
117 #
118 # vcpus: the number of virtual CPUs this domain is configured to use.
119 # vcpu_avail: a bitmap telling the guest domain whether it may use each of
120 # its VCPUs. This is translated to
121 # <dompath>/cpu/<id>/availability = {online,offline} for use
122 # by the guest domain.
123 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
124 # CPUs that that VCPU may use.
125 # cpu: a configuration setting requesting that VCPU 0 is pinned to
126 # the specified physical CPU.
127 #
128 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
129 # across save, restore, migrate, and restart). The other settings are only
130 # specific to the domain, so are lost when the VM moves.
131 #
134 def create(config):
135 """Creates and start a VM using the supplied configuration.
136 (called from XMLRPCServer directly)
138 @param config: A configuration object involving lists of tuples.
139 @type config: list of lists, eg ['vm', ['image', 'xen.gz']]
141 @rtype: XendDomainInfo
142 @return: A up and running XendDomainInfo instance
143 @raise VmError: Invalid configuration or failure to start.
144 """
146 log.debug("XendDomainInfo.create(%s)", config)
147 vm = XendDomainInfo(XendConfig(sxp = config))
148 try:
149 vm.start()
150 except:
151 log.exception('Domain construction failed')
152 vm.destroy()
153 raise
155 return vm
157 def recreate(info, priv):
158 """Create the VM object for an existing domain. The domain must not
159 be dying, as the paths in the store should already have been removed,
160 and asking us to recreate them causes problems.
162 @param xeninfo: Parsed configuration
163 @type xeninfo: Dictionary
164 @param priv: TODO, unknown, something to do with memory
165 @type priv: bool
167 @rtype: XendDomainInfo
168 @return: A up and running XendDomainInfo instance
169 @raise VmError: Invalid configuration.
170 @raise XendError: Errors with configuration.
171 """
173 log.debug("XendDomainInfo.recreate(%s)", info)
175 assert not info['dying']
177 xeninfo = XendConfig(cfg = info)
178 domid = xeninfo['domid']
179 uuid1 = xeninfo['handle']
180 xeninfo['uuid'] = uuid.toString(uuid1)
181 needs_reinitialising = False
183 dompath = GetDomainPath(domid)
184 if not dompath:
185 raise XendError('No domain path in store for existing '
186 'domain %d' % domid)
188 log.info("Recreating domain %d, UUID %s. at %s" %
189 (domid, xeninfo['uuid'], dompath))
191 # need to verify the path and uuid if not Domain-0
192 # if the required uuid and vm aren't set, then that means
193 # we need to recreate the dom with our own values
194 #
195 # NOTE: this is probably not desirable, really we should just
196 # abort or ignore, but there may be cases where xenstore's
197 # entry disappears (eg. xenstore-rm /)
198 #
199 try:
200 vmpath = xstransact.Read(dompath, "vm")
201 if not vmpath:
202 log.warn('/local/domain/%d/vm is missing. recreate is '
203 'confused, trying our best to recover' % domid)
204 needs_reinitialising = True
205 raise XendError('reinit')
207 uuid2_str = xstransact.Read(vmpath, "uuid")
208 if not uuid2_str:
209 log.warn('%s/uuid/ is missing. recreate is confused, '
210 'trying our best to recover' % vmpath)
211 needs_reinitialising = True
212 raise XendError('reinit')
214 uuid2 = uuid.fromString(uuid2_str)
215 if uuid1 != uuid2:
216 log.warn('UUID in /vm does not match the UUID in /dom/%d.'
217 'Trying out best to recover' % domid)
218 needs_reinitialising = True
219 except XendError:
220 pass # our best shot at 'goto' in python :)
222 vm = XendDomainInfo(xeninfo, domid, dompath, augment = True, priv = priv)
224 if needs_reinitialising:
225 vm._recreateDom()
226 vm._removeVm()
227 vm._storeVmDetails()
228 vm._storeDomDetails()
230 vm._registerWatches()
231 vm._refreshShutdown(xeninfo)
232 return vm
235 def restore(config):
236 """Create a domain and a VM object to do a restore.
238 @param config: Domain configuration object
239 @type config: list of lists. (see C{create})
241 @rtype: XendDomainInfo
242 @return: A up and running XendDomainInfo instance
243 @raise VmError: Invalid configuration or failure to start.
244 @raise XendError: Errors with configuration.
245 """
247 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()
442 self.unpause()
444 # save running configuration if XendDomains believe domain is
445 # persistent
446 #
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 not reason in DOMAIN_SHUTDOWN_REASONS.values():
480 raise XendError('Invalid reason: %s' % reason)
481 self._storeDom("control/shutdown", reason)
483 def pause(self):
484 """Pause domain
486 @raise XendError: Failed pausing a domain
487 """
488 try:
489 xc.domain_pause(self.domid)
490 self._stateSet(DOM_STATE_PAUSED)
491 except Exception, ex:
492 raise XendError("Domain unable to be paused: %s" % str(ex))
494 def unpause(self):
495 """Unpause domain
497 @raise XendError: Failed unpausing a domain
498 """
499 try:
500 xc.domain_unpause(self.domid)
501 self._stateSet(DOM_STATE_RUNNING)
502 except Exception, ex:
503 raise XendError("Domain unable to be unpaused: %s" % str(ex))
505 def send_sysrq(self, key):
506 """ Send a Sysrq equivalent key via xenstored."""
507 asserts.isCharConvertible(key)
508 self._storeDom("control/sysrq", '%c' % key)
510 def device_create(self, dev_config):
511 """Create a new device.
513 @param dev_config: device configuration
514 @type dev_config: dictionary (parsed config)
515 """
516 log.debug("XendDomainInfo.device_create: %s" % dev_config)
517 dev_type = sxp.name(dev_config)
518 devid = self._createDevice(dev_type, dev_config)
519 self.info.device_add(dev_type, cfg_sxp = dev_config)
520 self._waitForDevice(dev_type, devid)
521 return self.getDeviceController(dev_type).sxpr(devid)
523 def device_configure(self, dev_config, devid):
524 """Configure an existing device.
526 @param dev_config: device configuration
527 @type dev_config: dictionary (parsed config)
528 @param devid: device id
529 @type devid: int
530 """
531 deviceClass = sxp.name(dev_config)
532 self._reconfigureDevice(deviceClass, devid, dev_config)
534 def waitForDevices(self):
535 """Wait for this domain's configured devices to connect.
537 @raise VmError: if any device fails to initialise.
538 """
539 for devclass in XendDevices.valid_devices():
540 self.getDeviceController(devclass).waitForDevices()
542 def destroyDevice(self, deviceClass, devid):
543 try:
544 devid = int(devid)
545 except ValueError:
546 # devid is not a number, let's search for it in xenstore.
547 devicePath = '%s/device/%s' % (self.dompath, deviceClass)
548 for entry in xstransact.List(devicePath):
549 backend = xstransact.Read('%s/%s' % (devicePath, entry),
550 "backend")
551 devName = xstransact.Read(backend, "dev")
552 if devName == devid:
553 # We found the integer matching our devid, use it instead
554 devid = entry
555 break
557 return self.getDeviceController(deviceClass).destroyDevice(devid)
560 def getDeviceSxprs(self, deviceClass):
561 return self.getDeviceController(deviceClass).sxprs()
564 def setMemoryTarget(self, target):
565 """Set the memory target of this domain.
566 @param target: In MiB.
567 """
568 log.debug("Setting memory target of domain %s (%d) to %d MiB.",
569 self.info['name'], self.domid, target)
571 if target <= 0:
572 raise XendError('Invalid memory size')
574 self.info['memory'] = target
575 self.storeVm("memory", target)
576 self._storeDom("memory/target", target << 10)
578 def getVCPUInfo(self):
579 try:
580 # We include the domain name and ID, to help xm.
581 sxpr = ['domain',
582 ['domid', self.domid],
583 ['name', self.info['name']],
584 ['vcpu_count', self.info['online_vcpus']]]
586 for i in range(0, self.info['max_vcpu_id']+1):
587 info = xc.vcpu_getinfo(self.domid, i)
589 sxpr.append(['vcpu',
590 ['number', i],
591 ['online', info['online']],
592 ['blocked', info['blocked']],
593 ['running', info['running']],
594 ['cpu_time', info['cpu_time'] / 1e9],
595 ['cpu', info['cpu']],
596 ['cpumap', info['cpumap']]])
598 return sxpr
600 except RuntimeError, exn:
601 raise XendError(str(exn))
603 #
604 # internal functions ... TODO: re-categorised
605 #
607 def _augmentInfo(self, priv):
608 """Augment self.info, as given to us through L{recreate}, with
609 values taken from the store. This recovers those values known
610 to xend but not to the hypervisor.
611 """
612 def useIfNeeded(name, val):
613 if not self._infoIsSet(name) and val is not None:
614 self.info[name] = val
616 if priv:
617 entries = VM_STORE_ENTRIES[:]
618 entries.remove(('memory', int))
619 entries.remove(('maxmem', int))
620 else:
621 entries = VM_STORE_ENTRIES
622 entries.append(('image', str))
623 entries.append(('security', str))
625 map(lambda x, y: useIfNeeded(x[0], y), entries,
626 self._readVMDetails(entries))
628 devices = []
630 for devclass in XendDevices.valid_devices():
631 devconfig = self.getDeviceController(devclass).configurations()
632 if devconfig:
633 devices.extend(map(lambda conf: (devclass, conf), devconfig))
635 if not self.info['device'] and devices is not None:
636 for device in devices:
637 self.info.device_add(device[0], cfg_sxp = device)
639 #
640 # Function to update xenstore /vm/*
641 #
643 def _readVm(self, *args):
644 return xstransact.Read(self.vmpath, *args)
646 def _writeVm(self, *args):
647 return xstransact.Write(self.vmpath, *args)
649 def _removeVm(self, *args):
650 return xstransact.Remove(self.vmpath, *args)
652 def _gatherVm(self, *args):
653 return xstransact.Gather(self.vmpath, *args)
655 def storeVm(self, *args):
656 return xstransact.Store(self.vmpath, *args)
658 #
659 # Function to update xenstore /dom/*
660 #
662 def _readDom(self, *args):
663 return xstransact.Read(self.dompath, *args)
665 def _writeDom(self, *args):
666 return xstransact.Write(self.dompath, *args)
668 def _removeDom(self, *args):
669 return xstransact.Remove(self.dompath, *args)
671 def _storeDom(self, *args):
672 return xstransact.Store(self.dompath, *args)
674 def _recreateDom(self):
675 complete(self.dompath, lambda t: self._recreateDomFunc(t))
677 def _recreateDomFunc(self, t):
678 t.remove()
679 t.mkdir()
680 t.set_permissions({ 'dom' : self.domid })
682 def _storeDomDetails(self):
683 to_store = {
684 'domid': str(self.domid),
685 'vm': self.vmpath,
686 'name': self.info['name'],
687 'console/limit': str(xroot.get_console_limit() * 1024),
688 'memory/target': str(self.info['memory'] * 1024)
689 }
691 def f(n, v):
692 if v is not None:
693 to_store[n] = str(v)
695 f('console/port', self.console_port)
696 f('console/ring-ref', self.console_mfn)
697 f('store/port', self.store_port)
698 f('store/ring-ref', self.store_mfn)
700 to_store.update(self._vcpuDomDetails())
702 log.debug("Storing domain details: %s", to_store)
704 self._writeDom(to_store)
706 def _vcpuDomDetails(self):
707 def availability(n):
708 if self.info['vcpu_avail'] & (1 << n):
709 return 'online'
710 else:
711 return 'offline'
713 result = {}
714 for v in range(0, self.info['vcpus']):
715 result["cpu/%d/availability" % v] = availability(v)
716 return result
718 #
719 # xenstore watches
720 #
722 def _registerWatches(self):
723 """Register a watch on this VM's entries in the store, and the
724 domain's control/shutdown node, so that when they are changed
725 externally, we keep up to date. This should only be called by {@link
726 #create}, {@link #recreate}, or {@link #restore}, once the domain's
727 details have been written, but before the new instance is returned."""
728 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
729 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
730 self._handleShutdownWatch)
732 def _storeChanged(self, _):
733 log.trace("XendDomainInfo.storeChanged");
735 changed = False
737 def f(x, y):
738 if y is not None and self.info[x[0]] != y:
739 self.info[x[0]] = y
740 changed = True
742 map(f, VM_CONFIG_PARAMS, self._readVMDetails(VM_CONFIG_PARAMS))
744 im = self._readVm('image')
745 current_im = self.info['image']
746 if (im is not None and
747 (current_im is None or sxp.to_string(current_im) != im)):
748 self.info['image'] = sxp.from_string(im)
749 changed = True
751 if changed:
752 # Update the domain section of the store, as this contains some
753 # parameters derived from the VM configuration.
754 self._storeDomDetails()
756 return 1
758 def _handleShutdownWatch(self, _):
759 log.debug('XendDomainInfo.handleShutdownWatch')
761 reason = self._readDom('control/shutdown')
763 if reason and reason != 'suspend':
764 sst = self._readDom('xend/shutdown_start_time')
765 now = time.time()
766 if sst:
767 self.shutdownStartTime = float(sst)
768 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
769 else:
770 self.shutdownStartTime = now
771 self._storeDom('xend/shutdown_start_time', now)
772 timeout = SHUTDOWN_TIMEOUT
774 log.trace(
775 "Scheduling refreshShutdown on domain %d in %ds.",
776 self.domid, timeout)
777 threading.Timer(timeout, self._refreshShutdown).start()
779 return True
782 #
783 # Public Attributes for the VM
784 #
787 def getDomid(self):
788 return self.domid
790 def setName(self, name):
791 self._checkName(name)
792 self.info['name'] = name
793 self.storeVm("name", name)
795 def getName(self):
796 return self.info['name']
798 def getDomainPath(self):
799 return self.dompath
802 def getStorePort(self):
803 """For use only by image.py and XendCheckpoint.py."""
804 return self.store_port
807 def getConsolePort(self):
808 """For use only by image.py and XendCheckpoint.py"""
809 return self.console_port
811 def getFeatures(self):
812 """For use only by image.py."""
813 return self.info['features']
815 def getVCpuCount(self):
816 return self.info['vcpus']
818 def setVCpuCount(self, vcpus):
819 self.info['vcpu_avail'] = (1 << vcpus) - 1
820 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
821 self._writeDom(self._vcpuDomDetails())
823 def getLabel(self):
824 return security.get_security_info(self.info, 'label')
826 def getMemoryTarget(self):
827 """Get this domain's target memory size, in KB."""
828 return self.info['memory'] * 1024
830 def getResume(self):
831 return "%s" % self.info['resume']
833 def getCap(self):
834 return self.info['cpu_cap']
836 def getWeight(self):
837 return self.info['cpu_weight']
839 def setResume(self, state):
840 self.info['resume'] = state
842 def getRestartCount(self):
843 return self._readVm('xend/restart_count')
845 def _refreshShutdown(self, xeninfo = None):
846 """ Checks the domain for whether a shutdown is required. """
848 # If set at the end of this method, a restart is required, with the
849 # given reason. This restart has to be done out of the scope of
850 # refresh_shutdown_lock.
851 restart_reason = None
853 self.refresh_shutdown_lock.acquire()
854 try:
855 if xeninfo is None:
856 xeninfo = dom_get(self.domid)
857 if xeninfo is None:
858 # The domain no longer exists. This will occur if we have
859 # scheduled a timer to check for shutdown timeouts and the
860 # shutdown succeeded. It will also occur if someone
861 # destroys a domain beneath us. We clean up the domain,
862 # just in case, but we can't clean up the VM, because that
863 # VM may have migrated to a different domain on this
864 # machine.
865 self.cleanupDomain()
866 self._stateSet(DOM_STATE_HALTED)
867 return
869 if xeninfo['dying']:
870 # Dying means that a domain has been destroyed, but has not
871 # yet been cleaned up by Xen. This state could persist
872 # indefinitely if, for example, another domain has some of its
873 # pages mapped. We might like to diagnose this problem in the
874 # future, but for now all we do is make sure that it's not us
875 # holding the pages, by calling cleanupDomain. We can't
876 # clean up the VM, as above.
877 self.cleanupDomain()
878 self._stateSet(DOM_STATE_SHUTDOWN)
879 return
881 elif xeninfo['crashed']:
882 if self._readDom('xend/shutdown_completed'):
883 # We've seen this shutdown already, but we are preserving
884 # the domain for debugging. Leave it alone.
885 return
887 log.warn('Domain has crashed: name=%s id=%d.',
888 self.info['name'], self.domid)
890 if xroot.get_enable_dump():
891 self.dumpCore()
893 restart_reason = 'crash'
894 self._stateSet(DOM_STATE_HALTED)
896 elif xeninfo['shutdown']:
897 self._stateSet(DOM_STATE_SHUTDOWN)
898 if self._readDom('xend/shutdown_completed'):
899 # We've seen this shutdown already, but we are preserving
900 # the domain for debugging. Leave it alone.
901 return
903 else:
904 reason = shutdown_reason(xeninfo['shutdown_reason'])
906 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
907 self.info['name'], self.domid, reason)
909 self._clearRestart()
911 if reason == 'suspend':
912 self._stateSet(DOM_STATE_SUSPENDED)
913 # Don't destroy the domain. XendCheckpoint will do
914 # this once it has finished. However, stop watching
915 # the VM path now, otherwise we will end up with one
916 # watch for the old domain, and one for the new.
917 self._unwatchVm()
918 elif reason in ['poweroff', 'reboot']:
919 restart_reason = reason
920 else:
921 self.destroy()
923 elif self.dompath is None:
924 # We have yet to manage to call introduceDomain on this
925 # domain. This can happen if a restore is in progress, or has
926 # failed. Ignore this domain.
927 pass
928 else:
929 # Domain is alive. If we are shutting it down, then check
930 # the timeout on that, and destroy it if necessary.
931 if xeninfo['paused']:
932 self._stateSet(DOM_STATE_PAUSED)
933 else:
934 self._stateSet(DOM_STATE_RUNNING)
936 if self.shutdownStartTime:
937 timeout = (SHUTDOWN_TIMEOUT - time.time() +
938 self.shutdownStartTime)
939 if timeout < 0:
940 log.info(
941 "Domain shutdown timeout expired: name=%s id=%s",
942 self.info['name'], self.domid)
943 self.destroy()
944 finally:
945 self.refresh_shutdown_lock.release()
947 if restart_reason:
948 self._maybeRestart(restart_reason)
951 #
952 # Restart functions - handling whether we come back up on shutdown.
953 #
955 def _clearRestart(self):
956 self._removeDom("xend/shutdown_start_time")
959 def _maybeRestart(self, reason):
960 # Dispatch to the correct method based upon the configured on_{reason}
961 # behaviour.
962 {"destroy" : self.destroy,
963 "restart" : self._restart,
964 "preserve" : self._preserve,
965 "rename-restart" : self._renameRestart}[self.info['on_' + reason]]()
968 def _renameRestart(self):
969 self._restart(True)
971 def _restart(self, rename = False):
972 """Restart the domain after it has exited.
974 @param rename True if the old domain is to be renamed and preserved,
975 False if it is to be destroyed.
976 """
977 from xen.xend import XendDomain
979 self._configureBootloader()
980 config = self.sxpr()
982 if self._infoIsSet('cpus') and len(self.info['cpus']) != 0:
983 config.append(['cpus', reduce(lambda x, y: str(x) + "," + str(y),
984 self.info['cpus'])])
986 if self._readVm(RESTART_IN_PROGRESS):
987 log.error('Xend failed during restart of domain %s. '
988 'Refusing to restart to avoid loops.',
989 str(self.domid))
990 self.destroy()
991 return
993 self._writeVm(RESTART_IN_PROGRESS, 'True')
995 now = time.time()
996 rst = self._readVm('xend/previous_restart_time')
997 if rst:
998 rst = float(rst)
999 timeout = now - rst
1000 if timeout < MINIMUM_RESTART_TIME:
1001 log.error(
1002 'VM %s restarting too fast (%f seconds since the last '
1003 'restart). Refusing to restart to avoid loops.',
1004 self.info['name'], timeout)
1005 self.destroy()
1006 return
1008 self._writeVm('xend/previous_restart_time', str(now))
1010 try:
1011 if rename:
1012 self._preserveForRestart()
1013 else:
1014 self._unwatchVm()
1015 self.destroyDomain()
1017 # new_dom's VM will be the same as this domain's VM, except where
1018 # the rename flag has instructed us to call preserveForRestart.
1019 # In that case, it is important that we remove the
1020 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1021 # once the new one is available.
1023 new_dom = None
1024 try:
1025 new_dom = XendDomain.instance().domain_create(config)
1026 new_dom.unpause()
1027 rst_cnt = self._readVm('xend/restart_count')
1028 rst_cnt = int(rst_cnt) + 1
1029 self._writeVm('xend/restart_count', str(rst_cnt))
1030 new_dom._removeVm(RESTART_IN_PROGRESS)
1031 except:
1032 if new_dom:
1033 new_dom._removeVm(RESTART_IN_PROGRESS)
1034 new_dom.destroy()
1035 else:
1036 self._removeVm(RESTART_IN_PROGRESS)
1037 raise
1038 except:
1039 log.exception('Failed to restart domain %s.', str(self.domid))
1042 def _preserveForRestart(self):
1043 """Preserve a domain that has been shut down, by giving it a new UUID,
1044 cloning the VM details, and giving it a new name. This allows us to
1045 keep this domain for debugging, but restart a new one in its place
1046 preserving the restart semantics (name and UUID preserved).
1047 """
1049 new_uuid = uuid.createString()
1050 new_name = 'Domain-%s' % new_uuid
1051 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1052 self.info['name'], self.domid, self.info['uuid'],
1053 new_name, new_uuid)
1054 self._unwatchVm()
1055 self._releaseDevices()
1056 self.info['name'] = new_name
1057 self.info['uuid'] = new_uuid
1058 self.vmpath = XS_VMROOT + new_uuid
1059 self._storeVmDetails()
1060 self._preserve()
1063 def _preserve(self):
1064 log.info("Preserving dead domain %s (%d).", self.info['name'],
1065 self.domid)
1066 self._unwatchVm()
1067 self._storeDom('xend/shutdown_completed', 'True')
1068 self._stateSet(DOM_STATE_HALTED)
1071 # Debugging ..
1074 def dumpCore(self, corefile = None):
1075 """Create a core dump for this domain. Nothrow guarantee."""
1077 try:
1078 if not corefile:
1079 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1080 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1081 self.info['name'], self.domid)
1083 if os.path.isdir(corefile):
1084 raise XendError("Cannot dump core in a directory: %s" %
1085 corefile)
1087 xc.domain_dumpcore(self.domid, corefile)
1088 except RuntimeError, ex:
1089 corefile_incomp = corefile+'-incomplete'
1090 os.rename(corefile, corefile_incomp)
1091 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1092 self.domid, self.info['name'])
1093 raise XendError("Failed to dump core: %s" % str(ex))
1096 # Device creation/deletion functions
1099 def _createDevice(self, deviceClass, devConfig):
1100 return self.getDeviceController(deviceClass).createDevice(devConfig)
1102 def _waitForDevice(self, deviceClass, devid):
1103 return self.getDeviceController(deviceClass).waitForDevice(devid)
1105 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1106 return self.getDeviceController(deviceClass).reconfigureDevice(
1107 devid, devconfig)
1109 def _createDevices(self):
1110 """Create the devices for a vm.
1112 @raise: VmError for invalid devices
1113 """
1114 for (devclass, config) in self.info.all_devices_sxpr():
1115 log.info("createDevice: %s : %s" % (devclass, config))
1116 self._createDevice(devclass, config)
1118 if self.image:
1119 self.image.createDeviceModel()
1121 def _releaseDevices(self):
1122 """Release all domain's devices. Nothrow guarantee."""
1124 while True:
1125 t = xstransact("%s/device" % self.dompath)
1126 for devclass in XendDevices.valid_devices():
1127 for dev in t.list(devclass):
1128 try:
1129 t.remove(dev)
1130 except:
1131 # Log and swallow any exceptions in removal --
1132 # there's nothing more we can do.
1133 log.exception(
1134 "Device release failed: %s; %s; %s",
1135 self.info['name'], devclass, dev)
1136 if t.commit():
1137 break
1139 def getDeviceController(self, name):
1140 """Get the device controller for this domain, and if it
1141 doesn't exist, create it.
1143 @param name: device class name
1144 @type name: string
1145 @rtype: subclass of DevController
1146 """
1147 if name not in self._deviceControllers:
1148 devController = XendDevices.make_controller(name, self)
1149 if not devController:
1150 raise XendError("Unknown device type: %s" % name)
1151 self._deviceControllers[name] = devController
1153 return self._deviceControllers[name]
1156 # Migration functions (public)
1159 def testMigrateDevices(self, network, dst):
1160 """ Notify all device about intention of migration
1161 @raise: XendError for a device that cannot be migrated
1162 """
1163 for (n, c) in self.info.all_devices_sxpr():
1164 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1165 if rc != 0:
1166 raise XendError("Device of type '%s' refuses migration." % n)
1168 def migrateDevices(self, network, dst, step, domName=''):
1169 """Notify the devices about migration
1170 """
1171 ctr = 0
1172 try:
1173 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1174 self.migrateDevice(dev_type, dev_conf, network, dst,
1175 step, domName)
1176 ctr = ctr + 1
1177 except:
1178 for dev_type, dev_conf in self.info.all_devices_sxpr():
1179 if ctr == 0:
1180 step = step - 1
1181 ctr = ctr - 1
1182 self._recoverMigrateDevice(dev_type, dev_conf, network,
1183 dst, step, domName)
1184 raise
1186 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1187 step, domName=''):
1188 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1189 network, dst, step, domName)
1191 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1192 dst, step, domName=''):
1193 return self.getDeviceController(deviceClass).recover_migrate(
1194 deviceConfig, network, dst, step, domName)
1197 ## private:
1199 def _constructDomain(self):
1200 """Construct the domain.
1202 @raise: VmError on error
1203 """
1205 log.debug('XendDomainInfo.constructDomain')
1207 hvm = (self._infoIsSet('image') and
1208 sxp.name(self.info['image']) == "hvm")
1209 if hvm:
1210 info = xc.xeninfo()
1211 if not 'hvm' in info['xen_caps']:
1212 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1213 "supported by your CPU and enabled in your "
1214 "BIOS?")
1216 self.domid = xc.domain_create(
1217 domid = 0,
1218 ssidref = security.get_security_info(self.info, 'ssidref'),
1219 handle = uuid.fromString(self.info['uuid']),
1220 hvm = int(hvm))
1222 if self.domid < 0:
1223 raise VmError('Creating domain failed: name=%s' %
1224 self.info['name'])
1226 self.dompath = GetDomainPath(self.domid)
1228 self._recreateDom()
1230 # Set maximum number of vcpus in domain
1231 xc.domain_max_vcpus(self.domid, int(self.info['vcpus']))
1234 def _introduceDomain(self):
1235 assert self.domid is not None
1236 assert self.store_mfn is not None
1237 assert self.store_port is not None
1239 try:
1240 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1241 except RuntimeError, exn:
1242 raise XendError(str(exn))
1245 def _initDomain(self):
1246 log.debug('XendDomainInfo.initDomain: %s %s',
1247 self.domid,
1248 self.info['cpu_weight'])
1250 # if we have a boot loader but no image, then we need to set things
1251 # up by running the boot loader non-interactively
1252 if self._infoIsSet('bootloader') and not self._infoIsSet('image'):
1253 self._configureBootloader()
1255 if not self._infoIsSet('image'):
1256 raise VmError('Missing image in configuration')
1258 try:
1259 self.image = image.create(self,
1260 self.info['image'],
1261 self.info.all_devices_sxpr())
1263 localtime = self.info.get('localtime', 0)
1264 if localtime is not None and localtime == 1:
1265 xc.domain_set_time_offset(self.domid)
1267 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1269 # repin domain vcpus if a restricted cpus list is provided
1270 # this is done prior to memory allocation to aide in memory
1271 # distribution for NUMA systems.
1272 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1273 for v in range(0, self.info['max_vcpu_id']+1):
1274 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1276 # Use architecture- and image-specific calculations to determine
1277 # the various headrooms necessary, given the raw configured
1278 # values. maxmem, memory, and shadow are all in KiB.
1279 maxmem = self.image.getRequiredAvailableMemory(
1280 self.info['maxmem'] * 1024)
1281 memory = self.image.getRequiredAvailableMemory(
1282 self.info['memory'] * 1024)
1283 shadow = self.image.getRequiredShadowMemory(
1284 self.info['shadow_memory'] * 1024,
1285 self.info['maxmem'] * 1024)
1287 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1288 # takes MiB and we must not round down and end up under-providing.
1289 shadow = ((shadow + 1023) / 1024) * 1024
1291 # set memory limit
1292 xc.domain_setmaxmem(self.domid, maxmem)
1294 # Make sure there's enough RAM available for the domain
1295 balloon.free(memory + shadow)
1297 # Set up the shadow memory
1298 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1299 self.info['shadow_memory'] = shadow_cur
1301 self._createChannels()
1303 channel_details = self.image.createImage()
1305 self.store_mfn = channel_details['store_mfn']
1306 if 'console_mfn' in channel_details:
1307 self.console_mfn = channel_details['console_mfn']
1309 self._introduceDomain()
1311 self._createDevices()
1313 if self.info['bootloader']:
1314 self.image.cleanupBootloading()
1316 self.info['start_time'] = time.time()
1318 self._stateSet(DOM_STATE_RUNNING)
1319 except RuntimeError, exn:
1320 log.exception("XendDomainInfo.initDomain: exception occurred")
1321 raise VmError(str(exn))
1324 def cleanupDomain(self):
1325 """Cleanup domain resources; release devices. Idempotent. Nothrow
1326 guarantee."""
1328 self.refresh_shutdown_lock.acquire()
1329 try:
1330 self.unwatchShutdown()
1332 self._releaseDevices()
1334 if self.image:
1335 try:
1336 self.image.destroy()
1337 except:
1338 log.exception(
1339 "XendDomainInfo.cleanup: image.destroy() failed.")
1340 self.image = None
1342 try:
1343 self._removeDom()
1344 except:
1345 log.exception("Removing domain path failed.")
1347 self.info['dying'] = 0
1348 self.info['shutdown'] = 0
1349 self._stateSet(DOM_STATE_HALTED)
1350 finally:
1351 self.refresh_shutdown_lock.release()
1354 def unwatchShutdown(self):
1355 """Remove the watch on the domain's control/shutdown node, if any.
1356 Idempotent. Nothrow guarantee. Expects to be protected by the
1357 refresh_shutdown_lock."""
1359 try:
1360 try:
1361 if self.shutdownWatch:
1362 self.shutdownWatch.unwatch()
1363 finally:
1364 self.shutdownWatch = None
1365 except:
1366 log.exception("Unwatching control/shutdown failed.")
1368 def waitForShutdown(self):
1369 self.state_updated.acquire()
1370 try:
1371 while self.state in (DOM_STATE_RUNNING,):
1372 self.state_updated.wait()
1373 finally:
1374 self.state_updated.release()
1378 # TODO: recategorise - called from XendCheckpoint
1381 def completeRestore(self, store_mfn, console_mfn):
1383 log.debug("XendDomainInfo.completeRestore")
1385 self.store_mfn = store_mfn
1386 self.console_mfn = console_mfn
1388 self._introduceDomain()
1389 self._storeDomDetails()
1390 self._registerWatches()
1391 self._refreshShutdown()
1393 log.debug("XendDomainInfo.completeRestore done")
1396 def _endRestore(self):
1397 self.setResume(False)
1400 # VM Destroy
1403 def destroy(self):
1404 """Cleanup VM and destroy domain. Nothrow guarantee."""
1406 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1408 self._cleanupVm()
1409 if self.dompath is not None:
1410 self.destroyDomain()
1413 def destroyDomain(self):
1414 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1416 try:
1417 if self.domid is not None:
1418 xc.domain_destroy(self.domid)
1419 self.domid = None
1420 for state in DOM_STATES_OLD:
1421 self.info[state] = 0
1422 except:
1423 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1425 self.cleanupDomain()
1429 # Channels for xenstore and console
1432 def _createChannels(self):
1433 """Create the channels to the domain.
1434 """
1435 self.store_port = self._createChannel()
1436 self.console_port = self._createChannel()
1439 def _createChannel(self):
1440 """Create an event channel to the domain.
1441 """
1442 try:
1443 return xc.evtchn_alloc_unbound(domid=self.domid, remote_dom=0)
1444 except:
1445 log.exception("Exception in alloc_unbound(%d)", self.domid)
1446 raise
1449 # Bootloader configuration
1452 def _configureBootloader(self):
1453 """Run the bootloader if we're configured to do so."""
1454 if not self.info['bootloader']:
1455 return
1456 blcfg = None
1457 # FIXME: this assumes that we want to use the first disk device
1458 for (n, c) in self.info.all_devices_sxpr():
1459 if not n or not c or not(n in ["vbd", "tap"]):
1460 continue
1461 disk = sxp.child_value(c, "uname")
1462 if disk is None:
1463 continue
1464 fn = blkdev_uname_to_file(disk)
1465 blcfg = bootloader(self.info['bootloader'], fn, 1,
1466 self.info['bootloader_args'],
1467 self.info['image'])
1468 break
1469 if blcfg is None:
1470 msg = "Had a bootloader specified, but can't find disk"
1471 log.error(msg)
1472 raise VmError(msg)
1473 self.info['image'] = blcfg
1476 # VM Functions
1479 def _readVMDetails(self, params):
1480 """Read the specified parameters from the store.
1481 """
1482 try:
1483 return self._gatherVm(*params)
1484 except ValueError:
1485 # One of the int/float entries in params has a corresponding store
1486 # entry that is invalid. We recover, because older versions of
1487 # Xend may have put the entry there (memory/target, for example),
1488 # but this is in general a bad situation to have reached.
1489 log.exception(
1490 "Store corrupted at %s! Domain %d's configuration may be "
1491 "affected.", self.vmpath, self.domid)
1492 return []
1494 def _cleanupVm(self):
1495 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1497 self._unwatchVm()
1499 try:
1500 self._removeVm()
1501 except:
1502 log.exception("Removing VM path failed.")
1505 def checkLiveMigrateMemory(self):
1506 """ Make sure there's enough memory to migrate this domain """
1507 overhead_kb = 0
1508 if arch.type == "x86":
1509 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1510 # the minimum that Xen would allocate if no value were given.
1511 overhead_kb = self.info['vcpus'] * 1024 + self.info['maxmem'] * 4
1512 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1513 # The domain might already have some shadow memory
1514 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1515 if overhead_kb > 0:
1516 balloon.free(overhead_kb)
1518 def _unwatchVm(self):
1519 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1520 guarantee."""
1522 def testDeviceComplete(self):
1523 """ For Block IO migration safety we must ensure that
1524 the device has shutdown correctly, i.e. all blocks are
1525 flushed to disk
1526 """
1527 start = time.time()
1528 while True:
1529 test = 0
1530 diff = time.time() - start
1531 for i in self.getDeviceController('vbd').deviceIDs():
1532 test = 1
1533 log.info("Dev %s still active, looping...", i)
1534 time.sleep(0.1)
1536 if test == 0:
1537 break
1538 if diff >= MIGRATE_TIMEOUT:
1539 log.info("Dev still active but hit max loop timeout")
1540 break
1542 def _storeVmDetails(self):
1543 to_store = {}
1545 for k in VM_STORE_ENTRIES:
1546 if self._infoIsSet(k[0]):
1547 to_store[k[0]] = str(self.info[k[0]])
1549 if self._infoIsSet('image'):
1550 to_store['image'] = sxp.to_string(self.info['image'])
1552 if self._infoIsSet('security'):
1553 secinfo = self.info['security']
1554 to_store['security'] = sxp.to_string(secinfo)
1555 for idx in range(0, len(secinfo)):
1556 if secinfo[idx][0] == 'access_control':
1557 to_store['security/access_control'] = sxp.to_string(
1558 [secinfo[idx][1], secinfo[idx][2]])
1559 for aidx in range(1, len(secinfo[idx])):
1560 if secinfo[idx][aidx][0] == 'label':
1561 to_store['security/access_control/label'] = \
1562 secinfo[idx][aidx][1]
1563 if secinfo[idx][aidx][0] == 'policy':
1564 to_store['security/access_control/policy'] = \
1565 secinfo[idx][aidx][1]
1566 if secinfo[idx][0] == 'ssidref':
1567 to_store['security/ssidref'] = str(secinfo[idx][1])
1570 if not self._readVm('xend/restart_count'):
1571 to_store['xend/restart_count'] = str(0)
1573 log.debug("Storing VM details: %s", to_store)
1575 self._writeVm(to_store)
1576 self._setVmPermissions()
1579 def _setVmPermissions(self):
1580 """Allow the guest domain to read its UUID. We don't allow it to
1581 access any other entry, for security."""
1582 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1583 { 'dom' : self.domid,
1584 'read' : True,
1585 'write' : False })
1588 # Utility functions
1591 def _stateSet(self, state):
1592 self.state_updated.acquire()
1593 try:
1594 if self.state != state:
1595 self.state = state
1596 self.state_updated.notifyAll()
1597 finally:
1598 self.state_updated.release()
1600 def _infoIsSet(self, name):
1601 return name in self.info and self.info[name] is not None
1603 def _checkName(self, name):
1604 """Check if a vm name is valid. Valid names contain alphabetic
1605 characters, digits, or characters in '_-.:/+'.
1606 The same name cannot be used for more than one vm at the same time.
1608 @param name: name
1609 @raise: VmError if invalid
1610 """
1611 from xen.xend import XendDomain
1613 if name is None or name == '':
1614 raise VmError('Missing VM Name')
1616 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1617 raise VmError('Invalid VM Name')
1619 dom = XendDomain.instance().domain_lookup_nr(name)
1620 if dom and dom != self:
1621 raise VmError("VM name '%s' already exists" % name)
1624 def update(self, info = None, refresh = True):
1625 """Update with info from xc.domain_getinfo().
1626 """
1628 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1629 str(self.domid))
1631 if not info:
1632 info = dom_get(self.domid)
1633 if not info:
1634 return
1636 #manually update ssidref / security fields
1637 if security.on() and info.has_key('ssidref'):
1638 if (info['ssidref'] != 0) and self.info.has_key('security'):
1639 security_field = self.info['security']
1640 if not security_field:
1641 #create new security element
1642 self.info.update({'security':
1643 [['ssidref', str(info['ssidref'])]]})
1644 #ssidref field not used any longer
1645 if 'ssidref' in info:
1646 info.pop('ssidref')
1648 # make sure state is reset for info
1649 # TODO: we should eventually get rid of old_dom_states
1651 self.info.update(info)
1652 self.info.validate()
1654 if refresh:
1655 self._refreshShutdown(info)
1657 log.trace("XendDomainInfo.update done on domain %s: %s",
1658 str(self.domid), self.info)
1660 def sxpr(self, ignore_devices = False):
1661 return self.info.get_sxp(domain = self,
1662 ignore_devices = ignore_devices)
1664 # Xen API
1665 # ----------------------------------------------------------------
1667 def get_uuid(self):
1668 return self.info['uuid']
1669 def get_memory_static_max(self):
1670 return self.info['maxmem']
1671 def get_memory_static_min(self):
1672 return self.info['memory']
1673 def get_vcpus_policy(self):
1674 sched_id = xc.sched_id_get()
1675 if sched_id == xen.lowlevel.xc.XEN_SCHEDULER_SEDF:
1676 return 'sedf'
1677 elif sched_id == xen.lowlevel.xc.XEN_SCHEDULER_CREDIT:
1678 return 'credit'
1679 else:
1680 return 'unknown'
1681 def get_vcpus_params(self):
1682 return '' # TODO
1683 def get_power_state(self):
1684 return XEN_API_VM_POWER_STATE[self.state]
1685 def get_tpm_instance(self):
1686 return '' # TODO
1687 def get_tpm_backend(self):
1688 return '' # TODO
1689 def get_bios_boot(self):
1690 return '' # TODO
1691 def get_platform_std_vga(self):
1692 return False
1693 def get_platform_serial(self):
1694 return '' # TODO
1695 def get_platform_localtime(self):
1696 return False # TODO
1697 def get_platform_clock_offset(self):
1698 return False # TODO
1699 def get_platform_enable_audio(self):
1700 return False # TODO
1701 def get_builder(self):
1702 return 'Linux' # TODO
1703 def get_boot_method(self):
1704 bootloader = self.info['bootloader']
1705 if not bootloader or bootloader not in XEN_API_BOOT_TYPE:
1706 return 'kernel_external'
1707 return bootloader
1709 def get_kernel_image(self):
1710 return self.info['kernel_kernel']
1711 def get_kernel_initrd(self):
1712 return self.info['kernel_initrd']
1713 def get_kernel_args(self):
1714 return self.info['kernel_args']
1715 def get_grub_cmdline(self):
1716 return '' # TODO
1717 def get_pci_bus(self):
1718 return 0 # TODO
1719 def get_tools_version(self):
1720 return {} # TODO
1721 def get_other_config(self):
1722 return {} # TODO
1724 def get_on_shutdown(self):
1725 after_shutdown = self.info.get('on_poweroff')
1726 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
1727 return XEN_API_ON_NORMAL_EXIT[-1]
1728 return after_shutdown
1730 def get_on_reboot(self):
1731 after_reboot = self.info.get('on_reboot')
1732 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
1733 return XEN_API_ON_NORMAL_EXIT[-1]
1734 return after_reboot
1736 def get_on_suspend(self):
1737 after_suspend = self.info.get('on_suspend') # TODO: not supported
1738 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
1739 return XEN_API_ON_NORMAL_EXIT[-1]
1740 return after_suspend
1742 def get_on_crash(self):
1743 after_crash = self.info.get('on_crash')
1744 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
1745 return XEN_API_ON_CRASH_BEHAVIOUR[0]
1746 return after_crash
1748 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
1749 """ Get's a device configuration either from XendConfig or
1750 from the DevController.
1752 @param dev_class: device class, either, 'vbd' or 'vif'
1753 @param dev_uuid: device UUID
1755 @rtype: dictionary
1756 """
1757 dev_type_config = self.info['device'].get(dev_uuid)
1759 # shortcut if the domain isn't started because
1760 # the devcontrollers will have no better information
1761 # than XendConfig.
1762 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
1763 if dev_type_config:
1764 return copy.deepcopy(dev_type_config[1])
1765 return None
1767 # instead of using dev_class, we use the dev_type
1768 # that is from XendConfig.
1769 # This will accomdate 'tap' as well as 'vbd'
1770 dev_type = dev_type_config[0]
1772 controller = self.getDeviceController(dev_type)
1773 if not controller:
1774 return None
1776 all_configs = controller.getAllDeviceConfigurations()
1777 if not all_configs:
1778 return None
1780 dev_config = copy.deepcopy(dev_type_config[1])
1781 for _devid, _devcfg in all_configs.items():
1782 if _devcfg.get('uuid') == dev_uuid:
1783 dev_config.update(_devcfg)
1784 dev_config['id'] = _devid
1785 return dev_config
1787 return dev_config
1789 def get_dev_xenapi_config(self, dev_class, dev_uuid):
1790 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
1791 if not config:
1792 return {}
1794 config['VM'] = self.get_uuid()
1796 if dev_class == 'vif':
1797 if not config.has_key('name'):
1798 config['name'] = config.get('vifname', '')
1799 if not config.has_key('MAC'):
1800 config['MAC'] = config.get('mac', '')
1801 if not config.has_key('type'):
1802 config['type'] = 'paravirtualised'
1803 if not config.has_key('device'):
1804 devid = config.get('id')
1805 if devid != None:
1806 config['device'] = 'eth%d' % devid
1807 else:
1808 config['device'] = ''
1810 config['network'] = '' # Invalid for Xend
1811 config['MTU'] = 1500 # TODO
1812 config['network_read_kbs'] = 0.0
1813 config['network_write_kbs'] = 0.0
1814 config['IO_bandwidth_incoming_kbs'] = 0.0
1815 config['IO_bandwidth_outgoing_kbs'] = 0.0
1817 if dev_class =='vbd':
1818 config['VDI'] = '' # TODO
1819 config['device'] = config.get('dev', '')
1820 config['driver'] = 'paravirtualised' # TODO
1821 config['image'] = config.get('uname', '')
1822 config['IO_bandwidth_incoming_kbs'] = 0.0
1823 config['IO_bandwidth_outgoing_kbs'] = 0.0
1824 if config['mode'] == 'r':
1825 config['mode'] = 'RO'
1826 else:
1827 config['mode'] = 'RW'
1829 return config
1831 def get_dev_property(self, dev_class, dev_uuid, field):
1832 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
1833 try:
1834 return config[field]
1835 except KeyError:
1836 raise XendError('Invalid property for device: %s' % field)
1838 def get_vcpus_util(self):
1839 # TODO: this returns the total accum cpu time, rather than util
1840 # TODO: spec says that key is int, however, python does not allow
1841 # non-string keys to dictionaries.
1842 vcpu_util = {}
1843 if 'max_vcpu_id' in self.info and self.domid != None:
1844 for i in range(0, self.info['max_vcpu_id']+1):
1845 info = xc.vcpu_getinfo(self.domid, i)
1846 vcpu_util[str(i)] = info['cpu_time']/1000000000.0
1848 return vcpu_util
1850 def get_vifs(self):
1851 return self.info.get('vif_refs', [])
1853 def get_vbds(self):
1854 return self.info.get('vbd_refs', [])
1856 def get_vtpms(self):
1857 return self.info.get('vtpm_refs', [])
1859 def create_vbd(self, xenapi_vbd):
1860 """Create a VBD device from the passed struct in Xen API format.
1862 @return: uuid of the device
1863 @rtype: string
1864 """
1866 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
1867 if not dev_uuid:
1868 raise XendError('Failed to create device')
1870 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1871 sxpr = self.info.device_sxpr(dev_uuid)
1872 devid = self.getDeviceController('vbd').createDevice(sxpr)
1873 raise XendError("Device creation failed")
1875 return dev_uuid
1877 def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
1878 """Create a VBD using a VDI from XendStorageRepository.
1880 @param xenapi_vbd: vbd struct from the Xen API
1881 @param vdi_image_path: VDI UUID
1882 @rtype: string
1883 @return: uuid of the device
1884 """
1885 xenapi_vbd['image'] = vdi_image_path
1886 log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
1887 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
1888 if not dev_uuid:
1889 raise XendError('Failed to create device')
1891 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1892 sxpr = self.info.device_sxpr(dev_uuid)
1893 devid = self.getDeviceController('tap').createDevice(sxpr)
1894 raise XendError("Device creation failed")
1896 return dev_uuid
1898 def create_vif(self, xenapi_vif):
1899 """Create VIF device from the passed struct in Xen API format.
1901 @param xenapi_vif: Xen API VIF Struct.
1902 @rtype: string
1903 @return: UUID
1904 """
1905 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
1906 if not dev_uuid:
1907 raise XendError('Failed to create device')
1909 if self.state in (DOM_STATE_HALTED,):
1910 sxpr = self.info.device_sxpr(dev_uuid)
1911 devid = self.getDeviceController('vif').createDevice(sxpr)
1912 raise XendError("Device creation failed")
1914 return dev_uuid
1916 def create_vtpm(self, xenapi_vtpm):
1917 """Create a VTPM device from the passed struct in Xen API format.
1919 @return: uuid of the device
1920 @rtype: string
1921 """
1923 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
1924 if not dev_uuid:
1925 raise XendError('Failed to create device')
1927 if self.state in (DOM_STATE_HALTED,):
1928 sxpr = self.info.device_sxpr(dev_uuid)
1929 devid = self.getDeviceController('vtpm').createDevice(sxpr)
1930 raise XendError("Device creation failed")
1932 return dev_uuid
1934 def has_device(self, dev_class, dev_uuid):
1935 return (dev_uuid in self.info['%s_refs' % dev_class])
1937 """
1938 def stateChar(name):
1939 if name in self.info:
1940 if self.info[name]:
1941 return name[0]
1942 else:
1943 return '-'
1944 else:
1945 return '?'
1947 state = reduce(lambda x, y: x + y, map(stateChar, DOM_STATES_OLD))
1949 sxpr.append(['state', state])
1951 if self.store_mfn:
1952 sxpr.append(['store_mfn', self.store_mfn])
1953 if self.console_mfn:
1954 sxpr.append(['console_mfn', self.console_mfn])
1955 """
1957 def __str__(self):
1958 return '<domain id=%s name=%s memory=%s state=%s>' % \
1959 (str(self.domid), self.info['name'],
1960 str(self.info['memory']), DOM_STATES[self.state])
1962 __repr__ = __str__