ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 12443:f026d4091322

Fix xm block-configure, by allowing the device ID to be unspecified inside
device_configure (in which case it will be specified by the incoming config).

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author Ewan Mellor <ewan@xensource.com>
date Tue Nov 14 18:52:58 2006 +0000 (2006-11-14)
parents 913324616183
children 6e22ba721720
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 = None):
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 _preserveForRestart(self):
1048 """Preserve a domain that has been shut down, by giving it a new UUID,
1049 cloning the VM details, and giving it a new name. This allows us to
1050 keep this domain for debugging, but restart a new one in its place
1051 preserving the restart semantics (name and UUID preserved).
1052 """
1054 new_uuid = uuid.createString()
1055 new_name = 'Domain-%s' % new_uuid
1056 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1057 self.info['name'], self.domid, self.info['uuid'],
1058 new_name, new_uuid)
1059 self._unwatchVm()
1060 self._releaseDevices()
1061 self.info['name'] = new_name
1062 self.info['uuid'] = new_uuid
1063 self.vmpath = XS_VMROOT + new_uuid
1064 self._storeVmDetails()
1065 self._preserve()
1068 def _preserve(self):
1069 log.info("Preserving dead domain %s (%d).", self.info['name'],
1070 self.domid)
1071 self._unwatchVm()
1072 self._storeDom('xend/shutdown_completed', 'True')
1073 self._stateSet(DOM_STATE_HALTED)
1076 # Debugging ..
1079 def dumpCore(self, corefile = None):
1080 """Create a core dump for this domain. Nothrow guarantee."""
1082 try:
1083 if not corefile:
1084 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1085 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1086 self.info['name'], self.domid)
1088 if os.path.isdir(corefile):
1089 raise XendError("Cannot dump core in a directory: %s" %
1090 corefile)
1092 xc.domain_dumpcore(self.domid, corefile)
1093 except RuntimeError, ex:
1094 corefile_incomp = corefile+'-incomplete'
1095 os.rename(corefile, corefile_incomp)
1096 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1097 self.domid, self.info['name'])
1098 raise XendError("Failed to dump core: %s" % str(ex))
1101 # Device creation/deletion functions
1104 def _createDevice(self, deviceClass, devConfig):
1105 return self.getDeviceController(deviceClass).createDevice(devConfig)
1107 def _waitForDevice(self, deviceClass, devid):
1108 return self.getDeviceController(deviceClass).waitForDevice(devid)
1110 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1111 return self.getDeviceController(deviceClass).reconfigureDevice(
1112 devid, devconfig)
1114 def _createDevices(self):
1115 """Create the devices for a vm.
1117 @raise: VmError for invalid devices
1118 """
1119 for (devclass, config) in self.info.all_devices_sxpr():
1120 log.info("createDevice: %s : %s" % (devclass, config))
1121 self._createDevice(devclass, config)
1123 if self.image:
1124 self.image.createDeviceModel()
1126 def _releaseDevices(self):
1127 """Release all domain's devices. Nothrow guarantee."""
1129 while True:
1130 t = xstransact("%s/device" % self.dompath)
1131 for devclass in XendDevices.valid_devices():
1132 for dev in t.list(devclass):
1133 try:
1134 t.remove(dev)
1135 except:
1136 # Log and swallow any exceptions in removal --
1137 # there's nothing more we can do.
1138 log.exception(
1139 "Device release failed: %s; %s; %s",
1140 self.info['name'], devclass, dev)
1141 if t.commit():
1142 break
1144 def getDeviceController(self, name):
1145 """Get the device controller for this domain, and if it
1146 doesn't exist, create it.
1148 @param name: device class name
1149 @type name: string
1150 @rtype: subclass of DevController
1151 """
1152 if name not in self._deviceControllers:
1153 devController = XendDevices.make_controller(name, self)
1154 if not devController:
1155 raise XendError("Unknown device type: %s" % name)
1156 self._deviceControllers[name] = devController
1158 return self._deviceControllers[name]
1161 # Migration functions (public)
1164 def testMigrateDevices(self, network, dst):
1165 """ Notify all device about intention of migration
1166 @raise: XendError for a device that cannot be migrated
1167 """
1168 for (n, c) in self.info.all_devices_sxpr():
1169 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1170 if rc != 0:
1171 raise XendError("Device of type '%s' refuses migration." % n)
1173 def migrateDevices(self, network, dst, step, domName=''):
1174 """Notify the devices about migration
1175 """
1176 ctr = 0
1177 try:
1178 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1179 self.migrateDevice(dev_type, dev_conf, network, dst,
1180 step, domName)
1181 ctr = ctr + 1
1182 except:
1183 for dev_type, dev_conf in self.info.all_devices_sxpr():
1184 if ctr == 0:
1185 step = step - 1
1186 ctr = ctr - 1
1187 self._recoverMigrateDevice(dev_type, dev_conf, network,
1188 dst, step, domName)
1189 raise
1191 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1192 step, domName=''):
1193 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1194 network, dst, step, domName)
1196 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1197 dst, step, domName=''):
1198 return self.getDeviceController(deviceClass).recover_migrate(
1199 deviceConfig, network, dst, step, domName)
1202 ## private:
1204 def _constructDomain(self):
1205 """Construct the domain.
1207 @raise: VmError on error
1208 """
1210 log.debug('XendDomainInfo.constructDomain')
1212 hvm = (self._infoIsSet('image') and
1213 sxp.name(self.info['image']) == "hvm")
1214 if hvm:
1215 info = xc.xeninfo()
1216 if not 'hvm' in info['xen_caps']:
1217 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1218 "supported by your CPU and enabled in your "
1219 "BIOS?")
1221 self.domid = xc.domain_create(
1222 domid = 0,
1223 ssidref = security.get_security_info(self.info, 'ssidref'),
1224 handle = uuid.fromString(self.info['uuid']),
1225 hvm = int(hvm))
1227 if self.domid < 0:
1228 raise VmError('Creating domain failed: name=%s' %
1229 self.info['name'])
1231 self.dompath = GetDomainPath(self.domid)
1233 self._recreateDom()
1235 # Set maximum number of vcpus in domain
1236 xc.domain_max_vcpus(self.domid, int(self.info['vcpus']))
1239 def _introduceDomain(self):
1240 assert self.domid is not None
1241 assert self.store_mfn is not None
1242 assert self.store_port is not None
1244 try:
1245 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1246 except RuntimeError, exn:
1247 raise XendError(str(exn))
1250 def _initDomain(self):
1251 log.debug('XendDomainInfo.initDomain: %s %s',
1252 self.domid,
1253 self.info['cpu_weight'])
1255 # if we have a boot loader but no image, then we need to set things
1256 # up by running the boot loader non-interactively
1257 if self._infoIsSet('bootloader') and not self._infoIsSet('image'):
1258 self._configureBootloader()
1260 if not self._infoIsSet('image'):
1261 raise VmError('Missing image in configuration')
1263 try:
1264 self.image = image.create(self,
1265 self.info['image'],
1266 self.info.all_devices_sxpr())
1268 localtime = self.info.get('localtime', 0)
1269 if localtime is not None and localtime == 1:
1270 xc.domain_set_time_offset(self.domid)
1272 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1274 # repin domain vcpus if a restricted cpus list is provided
1275 # this is done prior to memory allocation to aide in memory
1276 # distribution for NUMA systems.
1277 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1278 for v in range(0, self.info['max_vcpu_id']+1):
1279 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1281 # Use architecture- and image-specific calculations to determine
1282 # the various headrooms necessary, given the raw configured
1283 # values. maxmem, memory, and shadow are all in KiB.
1284 maxmem = self.image.getRequiredAvailableMemory(
1285 self.info['maxmem'] * 1024)
1286 memory = self.image.getRequiredAvailableMemory(
1287 self.info['memory'] * 1024)
1288 shadow = self.image.getRequiredShadowMemory(
1289 self.info['shadow_memory'] * 1024,
1290 self.info['maxmem'] * 1024)
1292 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1293 # takes MiB and we must not round down and end up under-providing.
1294 shadow = ((shadow + 1023) / 1024) * 1024
1296 # set memory limit
1297 xc.domain_setmaxmem(self.domid, maxmem)
1299 # Make sure there's enough RAM available for the domain
1300 balloon.free(memory + shadow)
1302 # Set up the shadow memory
1303 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1304 self.info['shadow_memory'] = shadow_cur
1306 self._createChannels()
1308 channel_details = self.image.createImage()
1310 self.store_mfn = channel_details['store_mfn']
1311 if 'console_mfn' in channel_details:
1312 self.console_mfn = channel_details['console_mfn']
1314 self._introduceDomain()
1316 self._createDevices()
1318 if self.info['bootloader']:
1319 self.image.cleanupBootloading()
1321 self.info['start_time'] = time.time()
1323 self._stateSet(DOM_STATE_RUNNING)
1324 except RuntimeError, exn:
1325 log.exception("XendDomainInfo.initDomain: exception occurred")
1326 raise VmError(str(exn))
1329 def cleanupDomain(self):
1330 """Cleanup domain resources; release devices. Idempotent. Nothrow
1331 guarantee."""
1333 self.refresh_shutdown_lock.acquire()
1334 try:
1335 self.unwatchShutdown()
1337 self._releaseDevices()
1339 if self.image:
1340 try:
1341 self.image.destroy()
1342 except:
1343 log.exception(
1344 "XendDomainInfo.cleanup: image.destroy() failed.")
1345 self.image = None
1347 try:
1348 self._removeDom()
1349 except:
1350 log.exception("Removing domain path failed.")
1352 self._stateSet(DOM_STATE_HALTED)
1353 finally:
1354 self.refresh_shutdown_lock.release()
1357 def unwatchShutdown(self):
1358 """Remove the watch on the domain's control/shutdown node, if any.
1359 Idempotent. Nothrow guarantee. Expects to be protected by the
1360 refresh_shutdown_lock."""
1362 try:
1363 try:
1364 if self.shutdownWatch:
1365 self.shutdownWatch.unwatch()
1366 finally:
1367 self.shutdownWatch = None
1368 except:
1369 log.exception("Unwatching control/shutdown failed.")
1371 def waitForShutdown(self):
1372 self.state_updated.acquire()
1373 try:
1374 while self.state in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1375 self.state_updated.wait()
1376 finally:
1377 self.state_updated.release()
1381 # TODO: recategorise - called from XendCheckpoint
1384 def completeRestore(self, store_mfn, console_mfn):
1386 log.debug("XendDomainInfo.completeRestore")
1388 self.store_mfn = store_mfn
1389 self.console_mfn = console_mfn
1391 self._introduceDomain()
1392 self._storeDomDetails()
1393 self._registerWatches()
1394 self.refreshShutdown()
1396 log.debug("XendDomainInfo.completeRestore done")
1399 def _endRestore(self):
1400 self.setResume(False)
1403 # VM Destroy
1406 def destroy(self):
1407 """Cleanup VM and destroy domain. Nothrow guarantee."""
1409 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1411 self._cleanupVm()
1412 if self.dompath is not None:
1413 self.destroyDomain()
1416 def destroyDomain(self):
1417 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1419 try:
1420 if self.domid is not None:
1421 xc.domain_destroy(self.domid)
1422 self.domid = None
1423 for state in DOM_STATES_OLD:
1424 self.info[state] = 0
1425 except:
1426 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1428 self.cleanupDomain()
1432 # Channels for xenstore and console
1435 def _createChannels(self):
1436 """Create the channels to the domain.
1437 """
1438 self.store_port = self._createChannel()
1439 self.console_port = self._createChannel()
1442 def _createChannel(self):
1443 """Create an event channel to the domain.
1444 """
1445 try:
1446 return xc.evtchn_alloc_unbound(domid=self.domid, remote_dom=0)
1447 except:
1448 log.exception("Exception in alloc_unbound(%d)", self.domid)
1449 raise
1452 # Bootloader configuration
1455 def _configureBootloader(self):
1456 """Run the bootloader if we're configured to do so."""
1457 if not self.info['bootloader']:
1458 return
1459 blcfg = None
1460 # FIXME: this assumes that we want to use the first disk device
1461 for (n, c) in self.info.all_devices_sxpr():
1462 if not n or not c or not(n in ["vbd", "tap"]):
1463 continue
1464 disk = sxp.child_value(c, "uname")
1465 if disk is None:
1466 continue
1467 fn = blkdev_uname_to_file(disk)
1468 blcfg = bootloader(self.info['bootloader'], fn, 1,
1469 self.info['bootloader_args'],
1470 self.info['image'])
1471 break
1472 if blcfg is None:
1473 msg = "Had a bootloader specified, but can't find disk"
1474 log.error(msg)
1475 raise VmError(msg)
1476 self.info['image'] = blcfg
1479 # VM Functions
1482 def _readVMDetails(self, params):
1483 """Read the specified parameters from the store.
1484 """
1485 try:
1486 return self._gatherVm(*params)
1487 except ValueError:
1488 # One of the int/float entries in params has a corresponding store
1489 # entry that is invalid. We recover, because older versions of
1490 # Xend may have put the entry there (memory/target, for example),
1491 # but this is in general a bad situation to have reached.
1492 log.exception(
1493 "Store corrupted at %s! Domain %d's configuration may be "
1494 "affected.", self.vmpath, self.domid)
1495 return []
1497 def _cleanupVm(self):
1498 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1500 self._unwatchVm()
1502 try:
1503 self._removeVm()
1504 except:
1505 log.exception("Removing VM path failed.")
1508 def checkLiveMigrateMemory(self):
1509 """ Make sure there's enough memory to migrate this domain """
1510 overhead_kb = 0
1511 if arch.type == "x86":
1512 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1513 # the minimum that Xen would allocate if no value were given.
1514 overhead_kb = self.info['vcpus'] * 1024 + self.info['maxmem'] * 4
1515 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1516 # The domain might already have some shadow memory
1517 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1518 if overhead_kb > 0:
1519 balloon.free(overhead_kb)
1521 def _unwatchVm(self):
1522 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1523 guarantee."""
1525 def testDeviceComplete(self):
1526 """ For Block IO migration safety we must ensure that
1527 the device has shutdown correctly, i.e. all blocks are
1528 flushed to disk
1529 """
1530 start = time.time()
1531 while True:
1532 test = 0
1533 diff = time.time() - start
1534 for i in self.getDeviceController('vbd').deviceIDs():
1535 test = 1
1536 log.info("Dev %s still active, looping...", i)
1537 time.sleep(0.1)
1539 if test == 0:
1540 break
1541 if diff >= MIGRATE_TIMEOUT:
1542 log.info("Dev still active but hit max loop timeout")
1543 break
1545 def _storeVmDetails(self):
1546 to_store = {}
1548 for k in VM_STORE_ENTRIES:
1549 if self._infoIsSet(k[0]):
1550 to_store[k[0]] = str(self.info[k[0]])
1552 if self._infoIsSet('image'):
1553 to_store['image'] = sxp.to_string(self.info['image'])
1555 if self._infoIsSet('security'):
1556 secinfo = self.info['security']
1557 to_store['security'] = sxp.to_string(secinfo)
1558 for idx in range(0, len(secinfo)):
1559 if secinfo[idx][0] == 'access_control':
1560 to_store['security/access_control'] = sxp.to_string(
1561 [secinfo[idx][1], secinfo[idx][2]])
1562 for aidx in range(1, len(secinfo[idx])):
1563 if secinfo[idx][aidx][0] == 'label':
1564 to_store['security/access_control/label'] = \
1565 secinfo[idx][aidx][1]
1566 if secinfo[idx][aidx][0] == 'policy':
1567 to_store['security/access_control/policy'] = \
1568 secinfo[idx][aidx][1]
1569 if secinfo[idx][0] == 'ssidref':
1570 to_store['security/ssidref'] = str(secinfo[idx][1])
1573 if not self._readVm('xend/restart_count'):
1574 to_store['xend/restart_count'] = str(0)
1576 log.debug("Storing VM details: %s", to_store)
1578 self._writeVm(to_store)
1579 self._setVmPermissions()
1582 def _setVmPermissions(self):
1583 """Allow the guest domain to read its UUID. We don't allow it to
1584 access any other entry, for security."""
1585 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1586 { 'dom' : self.domid,
1587 'read' : True,
1588 'write' : False })
1591 # Utility functions
1594 def _stateSet(self, state):
1595 self.state_updated.acquire()
1596 try:
1597 if self.state != state:
1598 self.state = state
1599 self.state_updated.notifyAll()
1600 finally:
1601 self.state_updated.release()
1603 def _infoIsSet(self, name):
1604 return name in self.info and self.info[name] is not None
1606 def _checkName(self, name):
1607 """Check if a vm name is valid. Valid names contain alphabetic
1608 characters, digits, or characters in '_-.:/+'.
1609 The same name cannot be used for more than one vm at the same time.
1611 @param name: name
1612 @raise: VmError if invalid
1613 """
1614 from xen.xend import XendDomain
1616 if name is None or name == '':
1617 raise VmError('Missing VM Name')
1619 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1620 raise VmError('Invalid VM Name')
1622 dom = XendDomain.instance().domain_lookup_nr(name)
1623 if dom and dom != self and not dom.info['dying']:
1624 raise VmError("VM name '%s' already exists" % name)
1627 def update(self, info = None, refresh = True):
1628 """Update with info from xc.domain_getinfo().
1629 """
1630 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1631 str(self.domid))
1633 if not info:
1634 info = dom_get(self.domid)
1635 if not info:
1636 return
1638 #manually update ssidref / security fields
1639 if security.on() and info.has_key('ssidref'):
1640 if (info['ssidref'] != 0) and self.info.has_key('security'):
1641 security_field = self.info['security']
1642 if not security_field:
1643 #create new security element
1644 self.info.update({'security':
1645 [['ssidref', str(info['ssidref'])]]})
1646 #ssidref field not used any longer
1647 if 'ssidref' in info:
1648 info.pop('ssidref')
1650 # make sure state is reset for info
1651 # TODO: we should eventually get rid of old_dom_states
1653 self.info.update(info)
1654 self.info.validate()
1656 if refresh:
1657 self.refreshShutdown(info)
1659 log.trace("XendDomainInfo.update done on domain %s: %s",
1660 str(self.domid), self.info)
1662 def sxpr(self, ignore_devices = False):
1663 return self.info.get_sxp(domain = self,
1664 ignore_devices = ignore_devices)
1666 # Xen API
1667 # ----------------------------------------------------------------
1669 def get_uuid(self):
1670 dom_uuid = self.info.get('uuid')
1671 if not dom_uuid: # if it doesn't exist, make one up
1672 dom_uuid = uuid.createString()
1673 self.info['uuid'] = dom_uuid
1674 return dom_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_bios_boot(self):
1693 return '' # TODO
1694 def get_platform_std_vga(self):
1695 return False
1696 def get_platform_serial(self):
1697 return '' # TODO
1698 def get_platform_localtime(self):
1699 return False # TODO
1700 def get_platform_clock_offset(self):
1701 return False # TODO
1702 def get_platform_enable_audio(self):
1703 return False # TODO
1704 def get_builder(self):
1705 return 'Linux' # TODO
1706 def get_boot_method(self):
1707 bootloader = self.info['bootloader']
1708 if not bootloader or bootloader not in XEN_API_BOOT_TYPE:
1709 return 'kernel_external'
1710 return bootloader
1712 def get_kernel_image(self):
1713 return self.info['kernel_kernel']
1714 def get_kernel_initrd(self):
1715 return self.info['kernel_initrd']
1716 def get_kernel_args(self):
1717 return self.info['kernel_args']
1718 def get_grub_cmdline(self):
1719 return '' # TODO
1720 def get_pci_bus(self):
1721 return 0 # TODO
1722 def get_tools_version(self):
1723 return {} # TODO
1724 def get_other_config(self):
1725 return {} # TODO
1727 def get_on_shutdown(self):
1728 after_shutdown = self.info.get('on_poweroff')
1729 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
1730 return XEN_API_ON_NORMAL_EXIT[-1]
1731 return after_shutdown
1733 def get_on_reboot(self):
1734 after_reboot = self.info.get('on_reboot')
1735 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
1736 return XEN_API_ON_NORMAL_EXIT[-1]
1737 return after_reboot
1739 def get_on_suspend(self):
1740 after_suspend = self.info.get('on_suspend') # TODO: not supported
1741 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
1742 return XEN_API_ON_NORMAL_EXIT[-1]
1743 return after_suspend
1745 def get_on_crash(self):
1746 after_crash = self.info.get('on_crash')
1747 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
1748 return XEN_API_ON_CRASH_BEHAVIOUR[0]
1749 return after_crash
1751 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
1752 """ Get's a device configuration either from XendConfig or
1753 from the DevController.
1755 @param dev_class: device class, either, 'vbd' or 'vif'
1756 @param dev_uuid: device UUID
1758 @rtype: dictionary
1759 """
1760 dev_type_config = self.info['device'].get(dev_uuid)
1762 # shortcut if the domain isn't started because
1763 # the devcontrollers will have no better information
1764 # than XendConfig.
1765 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
1766 if dev_type_config:
1767 return copy.deepcopy(dev_type_config[1])
1768 return None
1770 # instead of using dev_class, we use the dev_type
1771 # that is from XendConfig.
1772 # This will accomdate 'tap' as well as 'vbd'
1773 dev_type = dev_type_config[0]
1775 controller = self.getDeviceController(dev_type)
1776 if not controller:
1777 return None
1779 all_configs = controller.getAllDeviceConfigurations()
1780 if not all_configs:
1781 return None
1783 dev_config = copy.deepcopy(dev_type_config[1])
1784 for _devid, _devcfg in all_configs.items():
1785 if _devcfg.get('uuid') == dev_uuid:
1786 dev_config.update(_devcfg)
1787 dev_config['id'] = _devid
1788 return dev_config
1790 return dev_config
1792 def get_dev_xenapi_config(self, dev_class, dev_uuid):
1793 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
1794 if not config:
1795 return {}
1797 config['VM'] = self.get_uuid()
1799 if dev_class == 'vif':
1800 if not config.has_key('name'):
1801 config['name'] = config.get('vifname', '')
1802 if not config.has_key('MAC'):
1803 config['MAC'] = config.get('mac', '')
1804 if not config.has_key('type'):
1805 config['type'] = 'paravirtualised'
1806 if not config.has_key('device'):
1807 devid = config.get('id')
1808 if devid != None:
1809 config['device'] = 'eth%d' % devid
1810 else:
1811 config['device'] = ''
1813 config['network'] = '' # Invalid for Xend
1814 config['MTU'] = 1500 # TODO
1815 config['network_read_kbs'] = 0.0
1816 config['network_write_kbs'] = 0.0
1817 config['IO_bandwidth_incoming_kbs'] = 0.0
1818 config['IO_bandwidth_outgoing_kbs'] = 0.0
1820 if dev_class =='vbd':
1821 config['VDI'] = '' # TODO
1822 config['device'] = config.get('dev', '')
1823 config['driver'] = 'paravirtualised' # TODO
1824 config['image'] = config.get('uname', '')
1825 config['IO_bandwidth_incoming_kbs'] = 0.0
1826 config['IO_bandwidth_outgoing_kbs'] = 0.0
1827 if config['mode'] == 'r':
1828 config['mode'] = 'RO'
1829 else:
1830 config['mode'] = 'RW'
1832 if dev_class == 'vtpm':
1833 config['driver'] = 'paravirtualised' # TODO
1835 return config
1837 def get_dev_property(self, dev_class, dev_uuid, field):
1838 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
1839 try:
1840 return config[field]
1841 except KeyError:
1842 raise XendError('Invalid property for device: %s' % field)
1844 def get_vcpus_util(self):
1845 # TODO: this returns the total accum cpu time, rather than util
1846 # TODO: spec says that key is int, however, python does not allow
1847 # non-string keys to dictionaries.
1848 vcpu_util = {}
1849 if 'max_vcpu_id' in self.info and self.domid != None:
1850 for i in range(0, self.info['max_vcpu_id']+1):
1851 info = xc.vcpu_getinfo(self.domid, i)
1852 vcpu_util[str(i)] = info['cpu_time']/1000000000.0
1854 return vcpu_util
1856 def get_vifs(self):
1857 return self.info.get('vif_refs', [])
1859 def get_vbds(self):
1860 return self.info.get('vbd_refs', [])
1862 def get_vtpms(self):
1863 return self.info.get('vtpm_refs', [])
1865 def create_vbd(self, xenapi_vbd):
1866 """Create a VBD device from the passed struct in Xen API format.
1868 @return: uuid of the device
1869 @rtype: string
1870 """
1872 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
1873 if not dev_uuid:
1874 raise XendError('Failed to create device')
1876 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1877 sxpr = self.info.device_sxpr(dev_uuid)
1878 devid = self.getDeviceController('vbd').createDevice(sxpr)
1879 raise XendError("Device creation failed")
1881 return dev_uuid
1883 def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
1884 """Create a VBD using a VDI from XendStorageRepository.
1886 @param xenapi_vbd: vbd struct from the Xen API
1887 @param vdi_image_path: VDI UUID
1888 @rtype: string
1889 @return: uuid of the device
1890 """
1891 xenapi_vbd['image'] = vdi_image_path
1892 log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
1893 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
1894 if not dev_uuid:
1895 raise XendError('Failed to create device')
1897 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
1898 sxpr = self.info.device_sxpr(dev_uuid)
1899 devid = self.getDeviceController('tap').createDevice(sxpr)
1900 raise XendError("Device creation failed")
1902 return dev_uuid
1904 def create_vif(self, xenapi_vif):
1905 """Create VIF device from the passed struct in Xen API format.
1907 @param xenapi_vif: Xen API VIF Struct.
1908 @rtype: string
1909 @return: UUID
1910 """
1911 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
1912 if not dev_uuid:
1913 raise XendError('Failed to create device')
1915 if self.state in (DOM_STATE_HALTED,):
1916 sxpr = self.info.device_sxpr(dev_uuid)
1917 devid = self.getDeviceController('vif').createDevice(sxpr)
1918 raise XendError("Device creation failed")
1920 return dev_uuid
1922 def create_vtpm(self, xenapi_vtpm):
1923 """Create a VTPM device from the passed struct in Xen API format.
1925 @return: uuid of the device
1926 @rtype: string
1927 """
1929 if self.state not in (DOM_STATE_HALTED,):
1930 raise VmError("Can only add vTPM to a halted domain.")
1931 if self.get_vtpms() != []:
1932 raise VmError('Domain already has a vTPM.')
1933 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
1934 if not dev_uuid:
1935 raise XendError('Failed to create device')
1937 return dev_uuid
1939 def has_device(self, dev_class, dev_uuid):
1940 return (dev_uuid in self.info['%s_refs' % dev_class])
1942 """
1943 def stateChar(name):
1944 if name in self.info:
1945 if self.info[name]:
1946 return name[0]
1947 else:
1948 return '-'
1949 else:
1950 return '?'
1952 state = reduce(lambda x, y: x + y, map(stateChar, DOM_STATES_OLD))
1954 sxpr.append(['state', state])
1956 if self.store_mfn:
1957 sxpr.append(['store_mfn', self.store_mfn])
1958 if self.console_mfn:
1959 sxpr.append(['console_mfn', self.console_mfn])
1960 """
1962 def __str__(self):
1963 return '<domain id=%s name=%s memory=%s state=%s>' % \
1964 (str(self.domid), self.info['name'],
1965 str(self.info['memory']), DOM_STATES[self.state])
1967 __repr__ = __str__