direct-io.hg

view tools/python/xen/xend/XendDomainInfo.py @ 12312:b8a2db59150a

Write the /local/domain/<domid>/vm node early in the startup process
(immediately after the domain is created). QEMU needs this in place when it
starts up, in order to find the VNC password.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author Ewan Mellor <ewan@xensource.com>
date Wed Nov 08 18:29:16 2006 +0000 (2006-11-08)
parents 69d8263d5f85
children 33ae8ae8693c
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()
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 })
681 t.write('vm', self.vmpath)
683 def _storeDomDetails(self):
684 to_store = {
685 'domid': str(self.domid),
686 'vm': self.vmpath,
687 'name': self.info['name'],
688 'console/limit': str(xroot.get_console_limit() * 1024),
689 'memory/target': str(self.info['memory'] * 1024)
690 }
692 def f(n, v):
693 if v is not None:
694 to_store[n] = str(v)
696 f('console/port', self.console_port)
697 f('console/ring-ref', self.console_mfn)
698 f('store/port', self.store_port)
699 f('store/ring-ref', self.store_mfn)
701 to_store.update(self._vcpuDomDetails())
703 log.debug("Storing domain details: %s", to_store)
705 self._writeDom(to_store)
707 def _vcpuDomDetails(self):
708 def availability(n):
709 if self.info['vcpu_avail'] & (1 << n):
710 return 'online'
711 else:
712 return 'offline'
714 result = {}
715 for v in range(0, self.info['vcpus']):
716 result["cpu/%d/availability" % v] = availability(v)
717 return result
719 #
720 # xenstore watches
721 #
723 def _registerWatches(self):
724 """Register a watch on this VM's entries in the store, and the
725 domain's control/shutdown node, so that when they are changed
726 externally, we keep up to date. This should only be called by {@link
727 #create}, {@link #recreate}, or {@link #restore}, once the domain's
728 details have been written, but before the new instance is returned."""
729 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
730 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
731 self._handleShutdownWatch)
733 def _storeChanged(self, _):
734 log.trace("XendDomainInfo.storeChanged");
736 changed = False
738 def f(x, y):
739 if y is not None and self.info[x[0]] != y:
740 self.info[x[0]] = y
741 changed = True
743 map(f, VM_CONFIG_PARAMS, self._readVMDetails(VM_CONFIG_PARAMS))
745 im = self._readVm('image')
746 current_im = self.info['image']
747 if (im is not None and
748 (current_im is None or sxp.to_string(current_im) != im)):
749 self.info['image'] = sxp.from_string(im)
750 changed = True
752 if changed:
753 # Update the domain section of the store, as this contains some
754 # parameters derived from the VM configuration.
755 self._storeDomDetails()
757 return 1
759 def _handleShutdownWatch(self, _):
760 log.debug('XendDomainInfo.handleShutdownWatch')
762 reason = self._readDom('control/shutdown')
764 if reason and reason != 'suspend':
765 sst = self._readDom('xend/shutdown_start_time')
766 now = time.time()
767 if sst:
768 self.shutdownStartTime = float(sst)
769 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
770 else:
771 self.shutdownStartTime = now
772 self._storeDom('xend/shutdown_start_time', now)
773 timeout = SHUTDOWN_TIMEOUT
775 log.trace(
776 "Scheduling refreshShutdown on domain %d in %ds.",
777 self.domid, timeout)
778 threading.Timer(timeout, self.refreshShutdown).start()
780 return True
783 #
784 # Public Attributes for the VM
785 #
788 def getDomid(self):
789 return self.domid
791 def setName(self, name):
792 self._checkName(name)
793 self.info['name'] = name
794 self.storeVm("name", name)
796 def getName(self):
797 return self.info['name']
799 def getDomainPath(self):
800 return self.dompath
802 def getShutdownReason(self):
803 return self._readDom('control/shutdown')
805 def getStorePort(self):
806 """For use only by image.py and XendCheckpoint.py."""
807 return self.store_port
809 def getConsolePort(self):
810 """For use only by image.py and XendCheckpoint.py"""
811 return self.console_port
813 def getFeatures(self):
814 """For use only by image.py."""
815 return self.info['features']
817 def getVCpuCount(self):
818 return self.info['vcpus']
820 def setVCpuCount(self, vcpus):
821 self.info['vcpu_avail'] = (1 << vcpus) - 1
822 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
823 self._writeDom(self._vcpuDomDetails())
825 def getLabel(self):
826 return security.get_security_info(self.info, 'label')
828 def getMemoryTarget(self):
829 """Get this domain's target memory size, in KB."""
830 return self.info['memory'] * 1024
832 def getResume(self):
833 return "%s" % self.info['resume']
835 def getCap(self):
836 return self.info['cpu_cap']
838 def getWeight(self):
839 return self.info['cpu_weight']
841 def setResume(self, state):
842 self.info['resume'] = state
844 def getRestartCount(self):
845 return self._readVm('xend/restart_count')
847 def refreshShutdown(self, xeninfo = None):
848 """ Checks the domain for whether a shutdown is required.
850 Called from XendDomainInfo and also image.py for HVM images.
851 """
853 # If set at the end of this method, a restart is required, with the
854 # given reason. This restart has to be done out of the scope of
855 # refresh_shutdown_lock.
856 restart_reason = None
858 self.refresh_shutdown_lock.acquire()
859 try:
860 if xeninfo is None:
861 xeninfo = dom_get(self.domid)
862 if xeninfo is None:
863 # The domain no longer exists. This will occur if we have
864 # scheduled a timer to check for shutdown timeouts and the
865 # shutdown succeeded. It will also occur if someone
866 # destroys a domain beneath us. We clean up the domain,
867 # just in case, but we can't clean up the VM, because that
868 # VM may have migrated to a different domain on this
869 # machine.
870 self.cleanupDomain()
871 self._stateSet(DOM_STATE_HALTED)
872 return
874 if xeninfo['dying']:
875 # Dying means that a domain has been destroyed, but has not
876 # yet been cleaned up by Xen. This state could persist
877 # indefinitely if, for example, another domain has some of its
878 # pages mapped. We might like to diagnose this problem in the
879 # future, but for now all we do is make sure that it's not us
880 # holding the pages, by calling cleanupDomain. We can't
881 # clean up the VM, as above.
882 self.cleanupDomain()
883 self._stateSet(DOM_STATE_SHUTDOWN)
884 return
886 elif xeninfo['crashed']:
887 if self._readDom('xend/shutdown_completed'):
888 # We've seen this shutdown already, but we are preserving
889 # the domain for debugging. Leave it alone.
890 return
892 log.warn('Domain has crashed: name=%s id=%d.',
893 self.info['name'], self.domid)
895 if xroot.get_enable_dump():
896 self.dumpCore()
898 restart_reason = 'crash'
899 self._stateSet(DOM_STATE_HALTED)
901 elif xeninfo['shutdown']:
902 self._stateSet(DOM_STATE_SHUTDOWN)
903 if self._readDom('xend/shutdown_completed'):
904 # We've seen this shutdown already, but we are preserving
905 # the domain for debugging. Leave it alone.
906 return
908 else:
909 reason = shutdown_reason(xeninfo['shutdown_reason'])
911 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
912 self.info['name'], self.domid, reason)
914 self._clearRestart()
916 if reason == 'suspend':
917 self._stateSet(DOM_STATE_SUSPENDED)
918 # Don't destroy the domain. XendCheckpoint will do
919 # this once it has finished. However, stop watching
920 # the VM path now, otherwise we will end up with one
921 # watch for the old domain, and one for the new.
922 self._unwatchVm()
923 elif reason in ['poweroff', 'reboot']:
924 restart_reason = reason
925 else:
926 self.destroy()
928 elif self.dompath is None:
929 # We have yet to manage to call introduceDomain on this
930 # domain. This can happen if a restore is in progress, or has
931 # failed. Ignore this domain.
932 pass
933 else:
934 # Domain is alive. If we are shutting it down, then check
935 # the timeout on that, and destroy it if necessary.
936 if xeninfo['paused']:
937 self._stateSet(DOM_STATE_PAUSED)
938 else:
939 self._stateSet(DOM_STATE_RUNNING)
941 if self.shutdownStartTime:
942 timeout = (SHUTDOWN_TIMEOUT - time.time() +
943 self.shutdownStartTime)
944 if timeout < 0:
945 log.info(
946 "Domain shutdown timeout expired: name=%s id=%s",
947 self.info['name'], self.domid)
948 self.destroy()
949 finally:
950 self.refresh_shutdown_lock.release()
952 if restart_reason:
953 self._maybeRestart(restart_reason)
956 #
957 # Restart functions - handling whether we come back up on shutdown.
958 #
960 def _clearRestart(self):
961 self._removeDom("xend/shutdown_start_time")
964 def _maybeRestart(self, reason):
965 # Dispatch to the correct method based upon the configured on_{reason}
966 # behaviour.
967 {"destroy" : self.destroy,
968 "restart" : self._restart,
969 "preserve" : self._preserve,
970 "rename-restart" : self._renameRestart}[self.info['on_' + reason]]()
973 def _renameRestart(self):
974 self._restart(True)
976 def _restart(self, rename = False):
977 """Restart the domain after it has exited.
979 @param rename True if the old domain is to be renamed and preserved,
980 False if it is to be destroyed.
981 """
982 from xen.xend import XendDomain
984 self._configureBootloader()
985 config = self.sxpr()
987 if self._infoIsSet('cpus') and len(self.info['cpus']) != 0:
988 config.append(['cpus', reduce(lambda x, y: str(x) + "," + str(y),
989 self.info['cpus'])])
991 if self._readVm(RESTART_IN_PROGRESS):
992 log.error('Xend failed during restart of domain %s. '
993 'Refusing to restart to avoid loops.',
994 str(self.domid))
995 self.destroy()
996 return
998 old_domid = self.domid
999 self._writeVm(RESTART_IN_PROGRESS, 'True')
1001 now = time.time()
1002 rst = self._readVm('xend/previous_restart_time')
1003 if rst:
1004 rst = float(rst)
1005 timeout = now - rst
1006 if timeout < MINIMUM_RESTART_TIME:
1007 log.error(
1008 'VM %s restarting too fast (%f seconds since the last '
1009 'restart). Refusing to restart to avoid loops.',
1010 self.info['name'], timeout)
1011 self.destroy()
1012 return
1014 self._writeVm('xend/previous_restart_time', str(now))
1016 try:
1017 if rename:
1018 self._preserveForRestart()
1019 else:
1020 self._unwatchVm()
1021 self.destroyDomain()
1023 # new_dom's VM will be the same as this domain's VM, except where
1024 # the rename flag has instructed us to call preserveForRestart.
1025 # In that case, it is important that we remove the
1026 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1027 # once the new one is available.
1029 new_dom = None
1030 try:
1031 new_dom = XendDomain.instance().domain_create(config)
1032 new_dom.unpause()
1033 rst_cnt = self._readVm('xend/restart_count')
1034 rst_cnt = int(rst_cnt) + 1
1035 self._writeVm('xend/restart_count', str(rst_cnt))
1036 new_dom._removeVm(RESTART_IN_PROGRESS)
1037 except:
1038 if new_dom:
1039 new_dom._removeVm(RESTART_IN_PROGRESS)
1040 new_dom.destroy()
1041 else:
1042 self._removeVm(RESTART_IN_PROGRESS)
1043 raise
1044 except:
1045 log.exception('Failed to restart domain %s.', str(old_domid))
1047 def getSysMem(self):
1048 info = dom_get(self.domid)
1049 xs_memory=int(info['mem_kb'])/1024
1050 return xs_memory
1052 def _preserveForRestart(self):
1053 """Preserve a domain that has been shut down, by giving it a new UUID,
1054 cloning the VM details, and giving it a new name. This allows us to
1055 keep this domain for debugging, but restart a new one in its place
1056 preserving the restart semantics (name and UUID preserved).
1057 """
1059 new_uuid = uuid.createString()
1060 new_name = 'Domain-%s' % new_uuid
1061 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1062 self.info['name'], self.domid, self.info['uuid'],
1063 new_name, new_uuid)
1064 self._unwatchVm()
1065 self._releaseDevices()
1066 self.info['name'] = new_name
1067 self.info['uuid'] = new_uuid
1068 self.vmpath = XS_VMROOT + new_uuid
1069 self._storeVmDetails()
1070 self._preserve()
1073 def _preserve(self):
1074 log.info("Preserving dead domain %s (%d).", self.info['name'],
1075 self.domid)
1076 self._unwatchVm()
1077 self._storeDom('xend/shutdown_completed', 'True')
1078 self._stateSet(DOM_STATE_HALTED)
1081 # Debugging ..
1084 def dumpCore(self, corefile = None):
1085 """Create a core dump for this domain. Nothrow guarantee."""
1087 try:
1088 if not corefile:
1089 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1090 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1091 self.info['name'], self.domid)
1093 if os.path.isdir(corefile):
1094 raise XendError("Cannot dump core in a directory: %s" %
1095 corefile)
1097 xc.domain_dumpcore(self.domid, corefile)
1098 except RuntimeError, ex:
1099 corefile_incomp = corefile+'-incomplete'
1100 os.rename(corefile, corefile_incomp)
1101 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1102 self.domid, self.info['name'])
1103 raise XendError("Failed to dump core: %s" % str(ex))
1106 # Device creation/deletion functions
1109 def _createDevice(self, deviceClass, devConfig):
1110 return self.getDeviceController(deviceClass).createDevice(devConfig)
1112 def _waitForDevice(self, deviceClass, devid):
1113 return self.getDeviceController(deviceClass).waitForDevice(devid)
1115 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1116 return self.getDeviceController(deviceClass).reconfigureDevice(
1117 devid, devconfig)
1119 def _createDevices(self):
1120 """Create the devices for a vm.
1122 @raise: VmError for invalid devices
1123 """
1124 for (devclass, config) in self.info.all_devices_sxpr():
1125 log.info("createDevice: %s : %s" % (devclass, config))
1126 self._createDevice(devclass, config)
1128 if self.image:
1129 self.image.createDeviceModel()
1131 def _releaseDevices(self):
1132 """Release all domain's devices. Nothrow guarantee."""
1134 while True:
1135 t = xstransact("%s/device" % self.dompath)
1136 for devclass in XendDevices.valid_devices():
1137 for dev in t.list(devclass):
1138 try:
1139 t.remove(dev)
1140 except:
1141 # Log and swallow any exceptions in removal --
1142 # there's nothing more we can do.
1143 log.exception(
1144 "Device release failed: %s; %s; %s",
1145 self.info['name'], devclass, dev)
1146 if t.commit():
1147 break
1149 def getDeviceController(self, name):
1150 """Get the device controller for this domain, and if it
1151 doesn't exist, create it.
1153 @param name: device class name
1154 @type name: string
1155 @rtype: subclass of DevController
1156 """
1157 if name not in self._deviceControllers:
1158 devController = XendDevices.make_controller(name, self)
1159 if not devController:
1160 raise XendError("Unknown device type: %s" % name)
1161 self._deviceControllers[name] = devController
1163 return self._deviceControllers[name]
1166 # Migration functions (public)
1169 def testMigrateDevices(self, network, dst):
1170 """ Notify all device about intention of migration
1171 @raise: XendError for a device that cannot be migrated
1172 """
1173 for (n, c) in self.info.all_devices_sxpr():
1174 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1175 if rc != 0:
1176 raise XendError("Device of type '%s' refuses migration." % n)
1178 def migrateDevices(self, network, dst, step, domName=''):
1179 """Notify the devices about migration
1180 """
1181 ctr = 0
1182 try:
1183 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1184 self.migrateDevice(dev_type, dev_conf, network, dst,
1185 step, domName)
1186 ctr = ctr + 1
1187 except:
1188 for dev_type, dev_conf in self.info.all_devices_sxpr():
1189 if ctr == 0:
1190 step = step - 1
1191 ctr = ctr - 1
1192 self._recoverMigrateDevice(dev_type, dev_conf, network,
1193 dst, step, domName)
1194 raise
1196 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1197 step, domName=''):
1198 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1199 network, dst, step, domName)
1201 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1202 dst, step, domName=''):
1203 return self.getDeviceController(deviceClass).recover_migrate(
1204 deviceConfig, network, dst, step, domName)
1207 ## private:
1209 def _constructDomain(self):
1210 """Construct the domain.
1212 @raise: VmError on error
1213 """
1215 log.debug('XendDomainInfo.constructDomain')
1217 hvm = (self._infoIsSet('image') and
1218 sxp.name(self.info['image']) == "hvm")
1219 if hvm:
1220 info = xc.xeninfo()
1221 if not 'hvm' in info['xen_caps']:
1222 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1223 "supported by your CPU and enabled in your "
1224 "BIOS?")
1226 self.domid = xc.domain_create(
1227 domid = 0,
1228 ssidref = security.get_security_info(self.info, 'ssidref'),
1229 handle = uuid.fromString(self.info['uuid']),
1230 hvm = int(hvm))
1232 if self.domid < 0:
1233 raise VmError('Creating domain failed: name=%s' %
1234 self.info['name'])
1236 self.dompath = GetDomainPath(self.domid)
1238 self._recreateDom()
1240 # Set maximum number of vcpus in domain
1241 xc.domain_max_vcpus(self.domid, int(self.info['vcpus']))
1244 def _introduceDomain(self):
1245 assert self.domid is not None
1246 assert self.store_mfn is not None
1247 assert self.store_port is not None
1249 try:
1250 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1251 except RuntimeError, exn:
1252 raise XendError(str(exn))
1255 def _initDomain(self):
1256 log.debug('XendDomainInfo.initDomain: %s %s',
1257 self.domid,
1258 self.info['cpu_weight'])
1260 # if we have a boot loader but no image, then we need to set things
1261 # up by running the boot loader non-interactively
1262 if self._infoIsSet('bootloader') and not self._infoIsSet('image'):
1263 self._configureBootloader()
1265 if not self._infoIsSet('image'):
1266 raise VmError('Missing image in configuration')
1268 try:
1269 self.image = image.create(self,
1270 self.info['image'],
1271 self.info.all_devices_sxpr())
1273 localtime = self.info.get('localtime', 0)
1274 if localtime is not None and localtime == 1:
1275 xc.domain_set_time_offset(self.domid)
1277 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1279 # repin domain vcpus if a restricted cpus list is provided
1280 # this is done prior to memory allocation to aide in memory
1281 # distribution for NUMA systems.
1282 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1283 for v in range(0, self.info['max_vcpu_id']+1):
1284 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1286 # Use architecture- and image-specific calculations to determine
1287 # the various headrooms necessary, given the raw configured
1288 # values. maxmem, memory, and shadow are all in KiB.
1289 maxmem = self.image.getRequiredAvailableMemory(
1290 self.info['maxmem'] * 1024)
1291 memory = self.image.getRequiredAvailableMemory(
1292 self.info['memory'] * 1024)
1293 shadow = self.image.getRequiredShadowMemory(
1294 self.info['shadow_memory'] * 1024,
1295 self.info['maxmem'] * 1024)
1297 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1298 # takes MiB and we must not round down and end up under-providing.
1299 shadow = ((shadow + 1023) / 1024) * 1024
1301 # set memory limit
1302 xc.domain_setmaxmem(self.domid, maxmem)
1304 # Make sure there's enough RAM available for the domain
1305 balloon.free(memory + shadow)
1307 # Set up the shadow memory
1308 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1309 self.info['shadow_memory'] = shadow_cur
1311 self._createChannels()
1313 channel_details = self.image.createImage()
1315 self.store_mfn = channel_details['store_mfn']
1316 if 'console_mfn' in channel_details:
1317 self.console_mfn = channel_details['console_mfn']
1319 self._introduceDomain()
1321 self._createDevices()
1323 if self.info['bootloader']:
1324 self.image.cleanupBootloading()
1326 self.info['start_time'] = time.time()
1328 self._stateSet(DOM_STATE_RUNNING)
1329 except RuntimeError, exn:
1330 log.exception("XendDomainInfo.initDomain: exception occurred")
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."""
1530 def testDeviceComplete(self):
1531 """ For Block IO migration safety we must ensure that
1532 the device has shutdown correctly, i.e. all blocks are
1533 flushed to disk
1534 """
1535 start = time.time()
1536 while True:
1537 test = 0
1538 diff = time.time() - start
1539 for i in self.getDeviceController('vbd').deviceIDs():
1540 test = 1
1541 log.info("Dev %s still active, looping...", i)
1542 time.sleep(0.1)
1544 if test == 0:
1545 break
1546 if diff >= MIGRATE_TIMEOUT:
1547 log.info("Dev still active but hit max loop timeout")
1548 break
1550 def _storeVmDetails(self):
1551 to_store = {}
1553 for k in VM_STORE_ENTRIES:
1554 if self._infoIsSet(k[0]):
1555 to_store[k[0]] = str(self.info[k[0]])
1557 if self._infoIsSet('image'):
1558 to_store['image'] = sxp.to_string(self.info['image'])
1560 if self._infoIsSet('security'):
1561 secinfo = self.info['security']
1562 to_store['security'] = sxp.to_string(secinfo)
1563 for idx in range(0, len(secinfo)):
1564 if secinfo[idx][0] == 'access_control':
1565 to_store['security/access_control'] = sxp.to_string(
1566 [secinfo[idx][1], secinfo[idx][2]])
1567 for aidx in range(1, len(secinfo[idx])):
1568 if secinfo[idx][aidx][0] == 'label':
1569 to_store['security/access_control/label'] = \
1570 secinfo[idx][aidx][1]
1571 if secinfo[idx][aidx][0] == 'policy':
1572 to_store['security/access_control/policy'] = \
1573 secinfo[idx][aidx][1]
1574 if secinfo[idx][0] == 'ssidref':
1575 to_store['security/ssidref'] = str(secinfo[idx][1])
1578 if not self._readVm('xend/restart_count'):
1579 to_store['xend/restart_count'] = str(0)
1581 log.debug("Storing VM details: %s", to_store)
1583 self._writeVm(to_store)
1584 self._setVmPermissions()
1587 def _setVmPermissions(self):
1588 """Allow the guest domain to read its UUID. We don't allow it to
1589 access any other entry, for security."""
1590 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1591 { 'dom' : self.domid,
1592 'read' : True,
1593 'write' : False })
1596 # Utility functions
1599 def _stateSet(self, state):
1600 self.state_updated.acquire()
1601 try:
1602 if self.state != state:
1603 self.state = state
1604 self.state_updated.notifyAll()
1605 finally:
1606 self.state_updated.release()
1608 def _infoIsSet(self, name):
1609 return name in self.info and self.info[name] is not None
1611 def _checkName(self, name):
1612 """Check if a vm name is valid. Valid names contain alphabetic
1613 characters, digits, or characters in '_-.:/+'.
1614 The same name cannot be used for more than one vm at the same time.
1616 @param name: name
1617 @raise: VmError if invalid
1618 """
1619 from xen.xend import XendDomain
1621 if name is None or name == '':
1622 raise VmError('Missing VM Name')
1624 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1625 raise VmError('Invalid VM Name')
1627 dom = XendDomain.instance().domain_lookup_nr(name)
1628 if dom and dom != self and not dom.info['dying']:
1629 raise VmError("VM name '%s' already exists" % name)
1632 def update(self, info = None, refresh = True):
1633 """Update with info from xc.domain_getinfo().
1634 """
1635 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1636 str(self.domid))
1638 if not info:
1639 info = dom_get(self.domid)
1640 if not info:
1641 return
1643 #manually update ssidref / security fields
1644 if security.on() and info.has_key('ssidref'):
1645 if (info['ssidref'] != 0) and self.info.has_key('security'):
1646 security_field = self.info['security']
1647 if not security_field:
1648 #create new security element
1649 self.info.update({'security':
1650 [['ssidref', str(info['ssidref'])]]})
1651 #ssidref field not used any longer
1652 if 'ssidref' in info:
1653 info.pop('ssidref')
1655 # make sure state is reset for info
1656 # TODO: we should eventually get rid of old_dom_states
1658 self.info.update(info)
1659 self.info.validate()
1661 if refresh:
1662 self.refreshShutdown(info)
1664 log.trace("XendDomainInfo.update done on domain %s: %s",
1665 str(self.domid), self.info)
1667 def sxpr(self, ignore_devices = False):
1668 return self.info.get_sxp(domain = self,
1669 ignore_devices = ignore_devices)
1671 # Xen API
1672 # ----------------------------------------------------------------
1674 def get_uuid(self):
1675 return self.info['uuid']
1676 def get_memory_static_max(self):
1677 return self.info['maxmem']
1678 def get_memory_static_min(self):
1679 return self.info['memory']
1680 def get_vcpus_policy(self):
1681 sched_id = xc.sched_id_get()
1682 if sched_id == xen.lowlevel.xc.XEN_SCHEDULER_SEDF:
1683 return 'sedf'
1684 elif sched_id == xen.lowlevel.xc.XEN_SCHEDULER_CREDIT:
1685 return 'credit'
1686 else:
1687 return 'unknown'
1688 def get_vcpus_params(self):
1689 return '' # TODO
1690 def get_power_state(self):
1691 return XEN_API_VM_POWER_STATE[self.state]
1692 def get_tpm_instance(self):
1693 return '' # TODO
1694 def get_tpm_backend(self):
1695 return '' # TODO
1696 def get_bios_boot(self):
1697 return '' # TODO
1698 def get_platform_std_vga(self):
1699 return False
1700 def get_platform_serial(self):
1701 return '' # TODO
1702 def get_platform_localtime(self):
1703 return False # TODO
1704 def get_platform_clock_offset(self):
1705 return False # TODO
1706 def get_platform_enable_audio(self):
1707 return False # TODO
1708 def get_builder(self):
1709 return 'Linux' # TODO
1710 def get_boot_method(self):
1711 bootloader = self.info['bootloader']
1712 if not bootloader or bootloader not in XEN_API_BOOT_TYPE:
1713 return 'kernel_external'
1714 return bootloader
1716 def get_kernel_image(self):
1717 return self.info['kernel_kernel']
1718 def get_kernel_initrd(self):
1719 return self.info['kernel_initrd']
1720 def get_kernel_args(self):
1721 return self.info['kernel_args']
1722 def get_grub_cmdline(self):
1723 return '' # TODO
1724 def get_pci_bus(self):
1725 return 0 # TODO
1726 def get_tools_version(self):
1727 return {} # TODO
1728 def get_other_config(self):
1729 return {} # TODO
1731 def get_on_shutdown(self):
1732 after_shutdown = self.info.get('on_poweroff')
1733 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
1734 return XEN_API_ON_NORMAL_EXIT[-1]
1735 return after_shutdown
1737 def get_on_reboot(self):
1738 after_reboot = self.info.get('on_reboot')
1739 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
1740 return XEN_API_ON_NORMAL_EXIT[-1]
1741 return after_reboot
1743 def get_on_suspend(self):
1744 after_suspend = self.info.get('on_suspend') # TODO: not supported
1745 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
1746 return XEN_API_ON_NORMAL_EXIT[-1]
1747 return after_suspend
1749 def get_on_crash(self):
1750 after_crash = self.info.get('on_crash')
1751 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
1752 return XEN_API_ON_CRASH_BEHAVIOUR[0]
1753 return after_crash
1755 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
1756 """ Get's a device configuration either from XendConfig or
1757 from the DevController.
1759 @param dev_class: device class, either, 'vbd' or 'vif'
1760 @param dev_uuid: device UUID
1762 @rtype: dictionary
1763 """
1764 dev_type_config = self.info['device'].get(dev_uuid)
1766 # shortcut if the domain isn't started because
1767 # the devcontrollers will have no better information
1768 # than XendConfig.
1769 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
1770 if dev_type_config:
1771 return copy.deepcopy(dev_type_config[1])
1772 return None
1774 # instead of using dev_class, we use the dev_type
1775 # that is from XendConfig.
1776 # This will accomdate 'tap' as well as 'vbd'
1777 dev_type = dev_type_config[0]
1779 controller = self.getDeviceController(dev_type)
1780 if not controller:
1781 return None
1783 all_configs = controller.getAllDeviceConfigurations()
1784 if not all_configs:
1785 return None
1787 dev_config = copy.deepcopy(dev_type_config[1])
1788 for _devid, _devcfg in all_configs.items():
1789 if _devcfg.get('uuid') == dev_uuid:
1790 dev_config.update(_devcfg)
1791 dev_config['id'] = _devid
1792 return dev_config
1794 return dev_config
1796 def get_dev_xenapi_config(self, dev_class, dev_uuid):
1797 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
1798 if not config:
1799 return {}
1801 config['VM'] = self.get_uuid()
1803 if dev_class == 'vif':
1804 if not config.has_key('name'):
1805 config['name'] = config.get('vifname', '')
1806 if not config.has_key('MAC'):
1807 config['MAC'] = config.get('mac', '')
1808 if not config.has_key('type'):
1809 config['type'] = 'paravirtualised'
1810 if not config.has_key('device'):
1811 devid = config.get('id')
1812 if devid != None:
1813 config['device'] = 'eth%d' % devid
1814 else:
1815 config['device'] = ''
1817 config['network'] = '' # Invalid for Xend
1818 config['MTU'] = 1500 # TODO
1819 config['network_read_kbs'] = 0.0
1820 config['network_write_kbs'] = 0.0
1821 config['IO_bandwidth_incoming_kbs'] = 0.0
1822 config['IO_bandwidth_outgoing_kbs'] = 0.0
1824 if dev_class =='vbd':
1825 config['VDI'] = '' # TODO
1826 config['device'] = config.get('dev', '')
1827 config['driver'] = 'paravirtualised' # TODO
1828 config['image'] = config.get('uname', '')
1829 config['IO_bandwidth_incoming_kbs'] = 0.0
1830 config['IO_bandwidth_outgoing_kbs'] = 0.0
1831 if config['mode'] == 'r':
1832 config['mode'] = 'RO'
1833 else:
1834 config['mode'] = 'RW'
1836 return config
1838 def get_dev_property(self, dev_class, dev_uuid, field):
1839 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
1840 try:
1841 return config[field]
1842 except KeyError:
1843 raise XendError('Invalid property for device: %s' % field)
1845 def get_vcpus_util(self):
1846 # TODO: this returns the total accum cpu time, rather than util
1847 # TODO: spec says that key is int, however, python does not allow
1848 # non-string keys to dictionaries.
1849 vcpu_util = {}
1850 if 'max_vcpu_id' in self.info and self.domid != None:
1851 for i in range(0, self.info['max_vcpu_id']+1):
1852 info = xc.vcpu_getinfo(self.domid, i)
1853 vcpu_util[str(i)] = info['cpu_time']/1000000000.0
1855 return vcpu_util
1857 def get_vifs(self):
1858 return self.info.get('vif_refs', [])
1860 def get_vbds(self):
1861 return self.info.get('vbd_refs', [])
1863 def get_vtpms(self):
1864 return self.info.get('vtpm_refs', [])
1866 def create_vbd(self, xenapi_vbd):
1867 """Create a VBD device from the passed struct in Xen API format.
1869 @return: uuid of the device
1870 @rtype: string
1871 """
1873 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
1874 if not dev_uuid:
1875 raise XendError('Failed to create device')
1877 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1878 sxpr = self.info.device_sxpr(dev_uuid)
1879 devid = self.getDeviceController('vbd').createDevice(sxpr)
1880 raise XendError("Device creation failed")
1882 return dev_uuid
1884 def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
1885 """Create a VBD using a VDI from XendStorageRepository.
1887 @param xenapi_vbd: vbd struct from the Xen API
1888 @param vdi_image_path: VDI UUID
1889 @rtype: string
1890 @return: uuid of the device
1891 """
1892 xenapi_vbd['image'] = vdi_image_path
1893 log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
1894 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
1895 if not dev_uuid:
1896 raise XendError('Failed to create device')
1898 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1899 sxpr = self.info.device_sxpr(dev_uuid)
1900 devid = self.getDeviceController('tap').createDevice(sxpr)
1901 raise XendError("Device creation failed")
1903 return dev_uuid
1905 def create_vif(self, xenapi_vif):
1906 """Create VIF device from the passed struct in Xen API format.
1908 @param xenapi_vif: Xen API VIF Struct.
1909 @rtype: string
1910 @return: UUID
1911 """
1912 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
1913 if not dev_uuid:
1914 raise XendError('Failed to create device')
1916 if self.state in (DOM_STATE_HALTED,):
1917 sxpr = self.info.device_sxpr(dev_uuid)
1918 devid = self.getDeviceController('vif').createDevice(sxpr)
1919 raise XendError("Device creation failed")
1921 return dev_uuid
1923 def create_vtpm(self, xenapi_vtpm):
1924 """Create a VTPM device from the passed struct in Xen API format.
1926 @return: uuid of the device
1927 @rtype: string
1928 """
1930 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
1931 if not dev_uuid:
1932 raise XendError('Failed to create device')
1934 if self.state in (DOM_STATE_HALTED,):
1935 sxpr = self.info.device_sxpr(dev_uuid)
1936 devid = self.getDeviceController('vtpm').createDevice(sxpr)
1937 raise XendError("Device creation failed")
1939 return dev_uuid
1941 def has_device(self, dev_class, dev_uuid):
1942 return (dev_uuid in self.info['%s_refs' % dev_class])
1944 """
1945 def stateChar(name):
1946 if name in self.info:
1947 if self.info[name]:
1948 return name[0]
1949 else:
1950 return '-'
1951 else:
1952 return '?'
1954 state = reduce(lambda x, y: x + y, map(stateChar, DOM_STATES_OLD))
1956 sxpr.append(['state', state])
1958 if self.store_mfn:
1959 sxpr.append(['store_mfn', self.store_mfn])
1960 if self.console_mfn:
1961 sxpr.append(['console_mfn', self.console_mfn])
1962 """
1964 def __str__(self):
1965 return '<domain id=%s name=%s memory=%s state=%s>' % \
1966 (str(self.domid), self.info['name'],
1967 str(self.info['memory']), DOM_STATES[self.state])
1969 __repr__ = __str__