ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 13008:10f51535cc84

Allow adding VIF devices to a VM when in halted state. Do not attempt to create the VIF device if VM is in halted state.

Signed-off-by: Jim Fehlig <jfehlig@novell.com>
author jfehlig@jfehlig2.provo.novell.com
date Tue Dec 12 15:28:23 2006 -0700 (2006-12-12)
parents dd6bbf10e79a
children ef5e6df3ba9e
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, XendConfig
43 from xen.xend.XendConfig import scrub_password
44 from xen.xend.XendBootloader import bootloader
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)
62 def bool0(v):
63 return v != "0" and bool(v)
66 ##
67 # All parameters of VMs that may be configured on-the-fly, or at start-up.
68 #
69 VM_CONFIG_PARAMS = [
70 ('name', str),
71 ('on_poweroff', str),
72 ('on_reboot', str),
73 ('on_crash', str),
74 ]
77 ##
78 # Configuration entries that we expect to round-trip -- be read from the
79 # config file or xc, written to save-files (i.e. through sxpr), and reused as
80 # config on restart or restore, all without munging. Some configuration
81 # entries are munged for backwards compatibility reasons, or because they
82 # don't come out of xc in the same form as they are specified in the config
83 # file, so those are handled separately.
84 ROUNDTRIPPING_CONFIG_ENTRIES = [
85 ('uuid', str),
86 ('vcpus', int),
87 ('vcpu_avail', int),
88 ('cpu_cap', int),
89 ('cpu_weight', int),
90 ('memory', int),
91 ('shadow_memory', int),
92 ('maxmem', int),
93 ('bootloader', str),
94 ('bootloader_args', str),
95 ('features', str),
96 ('localtime', bool0),
97 ]
99 ROUNDTRIPPING_CONFIG_ENTRIES += VM_CONFIG_PARAMS
102 ##
103 # All entries written to the store. This is VM_CONFIG_PARAMS, plus those
104 # entries written to the store that cannot be reconfigured on-the-fly.
105 #
106 VM_STORE_ENTRIES = [
107 ('uuid', str),
108 ('vcpus', int),
109 ('vcpu_avail', int),
110 ('memory', int),
111 ('shadow_memory', int),
112 ('maxmem', int),
113 ('start_time', float),
114 ('on_xend_start', str),
115 ('on_xend_stop', str),
116 ]
118 VM_STORE_ENTRIES += VM_CONFIG_PARAMS
121 #
122 # There are a number of CPU-related fields:
123 #
124 # vcpus: the number of virtual CPUs this domain is configured to use.
125 # vcpu_avail: a bitmap telling the guest domain whether it may use each of
126 # its VCPUs. This is translated to
127 # <dompath>/cpu/<id>/availability = {online,offline} for use
128 # by the guest domain.
129 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
130 # CPUs that that VCPU may use.
131 # cpu: a configuration setting requesting that VCPU 0 is pinned to
132 # the specified physical CPU.
133 #
134 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
135 # across save, restore, migrate, and restart). The other settings are only
136 # specific to the domain, so are lost when the VM moves.
137 #
140 def create(config):
141 """Creates and start a VM using the supplied configuration.
142 (called from XMLRPCServer directly)
144 @param config: A configuration object involving lists of tuples.
145 @type config: list of lists, eg ['vm', ['image', 'xen.gz']]
147 @rtype: XendDomainInfo
148 @return: A up and running XendDomainInfo instance
149 @raise VmError: Invalid configuration or failure to start.
150 """
152 log.debug("XendDomainInfo.create(%s)", scrub_password(config))
153 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config))
154 try:
155 vm.start()
156 except:
157 log.exception('Domain construction failed')
158 vm.destroy()
159 raise
161 return vm
163 def recreate(info, priv):
164 """Create the VM object for an existing domain. The domain must not
165 be dying, as the paths in the store should already have been removed,
166 and asking us to recreate them causes problems.
168 @param xeninfo: Parsed configuration
169 @type xeninfo: Dictionary
170 @param priv: Is a privileged domain (Dom 0)
171 @type priv: bool
173 @rtype: XendDomainInfo
174 @return: A up and running XendDomainInfo instance
175 @raise VmError: Invalid configuration.
176 @raise XendError: Errors with configuration.
177 """
179 log.debug("XendDomainInfo.recreate(%s)", scrub_password(info))
181 assert not info['dying']
183 xeninfo = XendConfig.XendConfig(dominfo = info)
184 domid = xeninfo['domid']
185 uuid1 = uuid.fromString(xeninfo['uuid'])
186 needs_reinitialising = False
188 dompath = GetDomainPath(domid)
189 if not dompath:
190 raise XendError('No domain path in store for existing '
191 'domain %d' % domid)
193 log.info("Recreating domain %d, UUID %s. at %s" %
194 (domid, xeninfo['uuid'], dompath))
196 # need to verify the path and uuid if not Domain-0
197 # if the required uuid and vm aren't set, then that means
198 # we need to recreate the dom with our own values
199 #
200 # NOTE: this is probably not desirable, really we should just
201 # abort or ignore, but there may be cases where xenstore's
202 # entry disappears (eg. xenstore-rm /)
203 #
204 try:
205 vmpath = xstransact.Read(dompath, "vm")
206 if not vmpath:
207 log.warn('/local/domain/%d/vm is missing. recreate is '
208 'confused, trying our best to recover' % domid)
209 needs_reinitialising = True
210 raise XendError('reinit')
212 uuid2_str = xstransact.Read(vmpath, "uuid")
213 if not uuid2_str:
214 log.warn('%s/uuid/ is missing. recreate is confused, '
215 'trying our best to recover' % vmpath)
216 needs_reinitialising = True
217 raise XendError('reinit')
219 uuid2 = uuid.fromString(uuid2_str)
220 if uuid1 != uuid2:
221 log.warn('UUID in /vm does not match the UUID in /dom/%d.'
222 'Trying out best to recover' % domid)
223 needs_reinitialising = True
224 except XendError:
225 pass # our best shot at 'goto' in python :)
227 vm = XendDomainInfo(xeninfo, domid, dompath, augment = True, priv = priv)
229 if needs_reinitialising:
230 vm._recreateDom()
231 vm._removeVm()
232 vm._storeVmDetails()
233 vm._storeDomDetails()
235 if vm.info['image']: # Only dom0 should be without an image entry when
236 # recreating, but we cope with missing ones
237 # elsewhere just in case.
238 vm.image = image.create(vm,
239 vm.info,
240 vm.info['image'],
241 vm.info['devices'])
242 vm.image.recreate()
244 vm._registerWatches()
245 vm.refreshShutdown(xeninfo)
246 return vm
249 def restore(config):
250 """Create a domain and a VM object to do a restore.
252 @param config: Domain SXP configuration
253 @type config: list of lists. (see C{create})
255 @rtype: XendDomainInfo
256 @return: A up and running XendDomainInfo instance
257 @raise VmError: Invalid configuration or failure to start.
258 @raise XendError: Errors with configuration.
259 """
261 log.debug("XendDomainInfo.restore(%s)", scrub_password(config))
262 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config),
263 resume = True)
264 try:
265 vm.resume()
266 return vm
267 except:
268 vm.destroy()
269 raise
271 def createDormant(domconfig):
272 """Create a dormant/inactive XenDomainInfo without creating VM.
273 This is for creating instances of persistent domains that are not
274 yet start.
276 @param domconfig: Parsed configuration
277 @type domconfig: XendConfig object
279 @rtype: XendDomainInfo
280 @return: A up and running XendDomainInfo instance
281 @raise XendError: Errors with configuration.
282 """
284 log.debug("XendDomainInfo.createDormant(%s)", scrub_password(domconfig))
286 # domid does not make sense for non-running domains.
287 domconfig.pop('domid', None)
288 vm = XendDomainInfo(domconfig)
289 return vm
291 def domain_by_name(name):
292 """Get domain by name
294 @params name: Name of the domain
295 @type name: string
296 @return: XendDomainInfo or None
297 """
298 from xen.xend import XendDomain
299 return XendDomain.instance().domain_lookup_by_name_nr(name)
302 def shutdown_reason(code):
303 """Get a shutdown reason from a code.
305 @param code: shutdown code
306 @type code: int
307 @return: shutdown reason
308 @rtype: string
309 """
310 return DOMAIN_SHUTDOWN_REASONS.get(code, "?")
312 def dom_get(dom):
313 """Get info from xen for an existing domain.
315 @param dom: domain id
316 @type dom: int
317 @return: info or None
318 @rtype: dictionary
319 """
320 try:
321 domlist = xc.domain_getinfo(dom, 1)
322 if domlist and dom == domlist[0]['domid']:
323 return domlist[0]
324 except Exception, err:
325 # ignore missing domain
326 log.trace("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
327 return None
330 class XendDomainInfo:
331 """An object represents a domain.
333 @TODO: try to unify dom and domid, they mean the same thing, but
334 xc refers to it as dom, and everywhere else, including
335 xenstore it is domid. The best way is to change xc's
336 python interface.
338 @ivar info: Parsed configuration
339 @type info: dictionary
340 @ivar domid: Domain ID (if VM has started)
341 @type domid: int or None
342 @ivar vmpath: XenStore path to this VM.
343 @type vmpath: string
344 @ivar dompath: XenStore path to this Domain.
345 @type dompath: string
346 @ivar image: Reference to the VM Image.
347 @type image: xen.xend.image.ImageHandler
348 @ivar store_port: event channel to xenstored
349 @type store_port: int
350 @ivar console_port: event channel to xenconsoled
351 @type console_port: int
352 @ivar store_mfn: xenstored mfn
353 @type store_mfn: int
354 @ivar console_mfn: xenconsoled mfn
355 @type console_mfn: int
356 @ivar vmWatch: reference to a watch on the xenstored vmpath
357 @type vmWatch: xen.xend.xenstore.xswatch
358 @ivar shutdownWatch: reference to watch on the xenstored domain shutdown
359 @type shutdownWatch: xen.xend.xenstore.xswatch
360 @ivar shutdownStartTime: UNIX Time when domain started shutting down.
361 @type shutdownStartTime: float or None
362 @ivar state: Domain state
363 @type state: enum(DOM_STATE_HALTED, DOM_STATE_RUNNING, ...)
364 @ivar state_updated: lock for self.state
365 @type state_updated: threading.Condition
366 @ivar refresh_shutdown_lock: lock for polling shutdown state
367 @type refresh_shutdown_lock: threading.Condition
368 @ivar _deviceControllers: device controller cache for this domain
369 @type _deviceControllers: dict 'string' to DevControllers
370 """
372 def __init__(self, info, domid = None, dompath = None, augment = False,
373 priv = False, resume = False):
374 """Constructor for a domain
376 @param info: parsed configuration
377 @type info: dictionary
378 @keyword domid: Set initial domain id (if any)
379 @type domid: int
380 @keyword dompath: Set initial dompath (if any)
381 @type dompath: string
382 @keyword augment: Augment given info with xenstored VM info
383 @type augment: bool
384 @keyword priv: Is a privileged domain (Dom 0)
385 @type priv: bool
386 @keyword resume: Is this domain being resumed?
387 @type resume: bool
388 """
390 self.info = info
391 if domid == None:
392 self.domid = self.info.get('domid')
393 else:
394 self.domid = domid
396 #REMOVE: uuid is now generated in XendConfig
397 #if not self._infoIsSet('uuid'):
398 # self.info['uuid'] = uuid.toString(uuid.create())
400 self.vmpath = XS_VMROOT + self.info['uuid']
401 self.dompath = dompath
403 self.image = None
404 self.store_port = None
405 self.store_mfn = None
406 self.console_port = None
407 self.console_mfn = None
409 self.vmWatch = None
410 self.shutdownWatch = None
411 self.shutdownStartTime = None
412 self._resume = resume
414 self.state = DOM_STATE_HALTED
415 self.state_updated = threading.Condition()
416 self.refresh_shutdown_lock = threading.Condition()
418 self._deviceControllers = {}
420 for state in DOM_STATES_OLD:
421 self.info[state] = 0
423 if augment:
424 self._augmentInfo(priv)
426 self._checkName(self.info['name_label'])
429 #
430 # Public functions available through XMLRPC
431 #
434 def start(self, is_managed = False):
435 """Attempts to start the VM by do the appropriate
436 initialisation if it not started.
437 """
438 from xen.xend import XendDomain
440 if self.state == DOM_STATE_HALTED:
441 try:
442 self._constructDomain()
443 self._initDomain()
444 self._storeVmDetails()
445 self._storeDomDetails()
446 self._registerWatches()
447 self.refreshShutdown()
449 # save running configuration if XendDomains believe domain is
450 # persistent
451 if is_managed:
452 xendomains = XendDomain.instance()
453 xendomains.managed_config_save(self)
454 except:
455 log.exception('VM start failed')
456 self.destroy()
457 raise
458 else:
459 raise XendError('VM already running')
461 def resume(self):
462 """Resumes a domain that has come back from suspension."""
463 if self.state in (DOM_STATE_HALTED, DOM_STATE_SUSPENDED):
464 try:
465 self._constructDomain()
466 self._storeVmDetails()
467 self._createDevices()
468 self._createChannels()
469 self._storeDomDetails()
470 self._endRestore()
471 except:
472 log.exception('VM resume failed')
473 raise
474 else:
475 raise XendError('VM already running')
477 def shutdown(self, reason):
478 """Shutdown a domain by signalling this via xenstored."""
479 log.debug('XendDomainInfo.shutdown')
480 if self.state in (DOM_STATE_SHUTDOWN, DOM_STATE_HALTED,):
481 raise XendError('Domain cannot be shutdown')
483 if self.domid == 0:
484 raise XendError('Domain 0 cannot be shutdown')
486 if reason not in DOMAIN_SHUTDOWN_REASONS.values():
487 raise XendError('Invalid reason: %s' % reason)
488 self._removeVm('xend/previous_restart_time')
489 self.storeDom("control/shutdown", reason)
491 def pause(self):
492 """Pause domain
494 @raise XendError: Failed pausing a domain
495 """
496 try:
497 xc.domain_pause(self.domid)
498 self._stateSet(DOM_STATE_PAUSED)
499 except Exception, ex:
500 raise XendError("Domain unable to be paused: %s" % str(ex))
502 def unpause(self):
503 """Unpause domain
505 @raise XendError: Failed unpausing a domain
506 """
507 try:
508 xc.domain_unpause(self.domid)
509 self._stateSet(DOM_STATE_RUNNING)
510 except Exception, ex:
511 raise XendError("Domain unable to be unpaused: %s" % str(ex))
513 def send_sysrq(self, key):
514 """ Send a Sysrq equivalent key via xenstored."""
515 asserts.isCharConvertible(key)
516 self.storeDom("control/sysrq", '%c' % key)
518 def device_create(self, dev_config):
519 """Create a new device.
521 @param dev_config: device configuration
522 @type dev_config: SXP object (parsed config)
523 """
524 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config))
525 dev_type = sxp.name(dev_config)
526 dev_uuid = self.info.device_add(dev_type, cfg_sxp = dev_config)
527 dev_config_dict = self.info['devices'][dev_uuid][1]
528 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config_dict))
529 devid = self._createDevice(dev_type, dev_config_dict)
530 self._waitForDevice(dev_type, devid)
531 return self.getDeviceController(dev_type).sxpr(devid)
533 def device_configure(self, dev_config, devid = None):
534 """Configure an existing device.
536 @param dev_config: device configuration
537 @type dev_config: SXP object (parsed config)
538 @param devid: device id
539 @type devid: int
540 @return: Returns True if successfully updated device
541 @rtype: boolean
542 """
543 deviceClass = sxp.name(dev_config)
545 # look up uuid of the device
546 dev_control = self.getDeviceController(deviceClass)
547 dev_sxpr = dev_control.sxpr(devid)
548 dev_uuid = sxp.child_value(sxpr, 'uuid')
549 if not dev_uuid:
550 return False
552 self.info.device_update(dev_uuid, dev_config)
553 dev_config_dict = self.info['devices'].get(dev_uuid)
554 if dev_config_dict:
555 dev_control.reconfigureDevice(devid, dev_config_dict[1])
556 return True
558 def waitForDevices(self):
559 """Wait for this domain's configured devices to connect.
561 @raise VmError: if any device fails to initialise.
562 """
563 for devclass in XendDevices.valid_devices():
564 self.getDeviceController(devclass).waitForDevices()
566 def destroyDevice(self, deviceClass, devid):
567 try:
568 devid = int(devid)
569 except ValueError:
570 # devid is not a number, let's search for it in xenstore.
571 devicePath = '%s/device/%s' % (self.dompath, deviceClass)
572 for entry in xstransact.List(devicePath):
573 backend = xstransact.Read('%s/%s' % (devicePath, entry),
574 "backend")
575 devName = xstransact.Read(backend, "dev")
576 if devName == devid:
577 # We found the integer matching our devid, use it instead
578 devid = entry
579 break
581 return self.getDeviceController(deviceClass).destroyDevice(devid)
585 def getDeviceSxprs(self, deviceClass):
586 if self.state == DOM_STATE_RUNNING:
587 return self.getDeviceController(deviceClass).sxprs()
588 else:
589 sxprs = []
590 dev_num = 0
591 for dev_type, dev_info in self.info.all_devices_sxpr():
592 if dev_type == deviceClass:
593 sxprs.append([dev_num, dev_info])
594 dev_num += 1
595 return sxprs
598 def setMemoryTarget(self, target):
599 """Set the memory target of this domain.
600 @param target: In MiB.
601 """
602 log.debug("Setting memory target of domain %s (%d) to %d MiB.",
603 self.info['name_label'], self.domid, target)
605 if target <= 0:
606 raise XendError('Invalid memory size')
608 self.info['memory_static_min'] = target
609 self.storeVm("memory", target)
610 self.storeDom("memory/target", target << 10)
612 def getVCPUInfo(self):
613 try:
614 # We include the domain name and ID, to help xm.
615 sxpr = ['domain',
616 ['domid', self.domid],
617 ['name', self.info['name_label']],
618 ['vcpu_count', self.info['vcpus_number']]]
620 for i in range(0, self.info['max_vcpu_id']+1):
621 info = xc.vcpu_getinfo(self.domid, i)
623 sxpr.append(['vcpu',
624 ['number', i],
625 ['online', info['online']],
626 ['blocked', info['blocked']],
627 ['running', info['running']],
628 ['cpu_time', info['cpu_time'] / 1e9],
629 ['cpu', info['cpu']],
630 ['cpumap', info['cpumap']]])
632 return sxpr
634 except RuntimeError, exn:
635 raise XendError(str(exn))
637 #
638 # internal functions ... TODO: re-categorised
639 #
641 def _augmentInfo(self, priv):
642 """Augment self.info, as given to us through L{recreate}, with
643 values taken from the store. This recovers those values known
644 to xend but not to the hypervisor.
645 """
646 augment_entries = XendConfig.LEGACY_XENSTORE_VM_PARAMS[:]
647 if priv:
648 augment_entries.remove('memory')
649 augment_entries.remove('maxmem')
650 augment_entries.remove('vcpus')
651 augment_entries.remove('vcpu_avail')
653 vm_config = self._readVMDetails([(k, XendConfig.LEGACY_CFG_TYPES[k])
654 for k in augment_entries])
656 # make returned lists into a dictionary
657 vm_config = dict(zip(augment_entries, vm_config))
659 for arg in augment_entries:
660 xapicfg = arg
661 val = vm_config[arg]
662 if val != None:
663 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
664 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
665 self.info[xapiarg] = val
666 else:
667 self.info[arg] = val
669 # For dom0, we ignore any stored value for the vcpus fields, and
670 # read the current value from Xen instead. This allows boot-time
671 # settings to take precedence over any entries in the store.
672 if priv:
673 xeninfo = dom_get(self.domid)
674 self.info['vcpus_number'] = xeninfo['online_vcpus']
675 self.info['vcpu_avail'] = (1 << xeninfo['online_vcpus']) - 1
677 # read image value
678 image_sxp = self._readVm('image')
679 if image_sxp:
680 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
682 # read devices
683 devices = []
684 for devclass in XendDevices.valid_devices():
685 devconfig = self.getDeviceController(devclass).configurations()
686 if devconfig:
687 devices.extend(devconfig)
689 if not self.info['devices'] and devices is not None:
690 for device in devices:
691 self.info.device_add(device[0], cfg_sxp = device)
693 #
694 # Function to update xenstore /vm/*
695 #
697 def _readVm(self, *args):
698 return xstransact.Read(self.vmpath, *args)
700 def _writeVm(self, *args):
701 return xstransact.Write(self.vmpath, *args)
703 def _removeVm(self, *args):
704 return xstransact.Remove(self.vmpath, *args)
706 def _gatherVm(self, *args):
707 return xstransact.Gather(self.vmpath, *args)
709 def storeVm(self, *args):
710 return xstransact.Store(self.vmpath, *args)
712 #
713 # Function to update xenstore /dom/*
714 #
716 def readDom(self, *args):
717 return xstransact.Read(self.dompath, *args)
719 def gatherDom(self, *args):
720 return xstransact.Gather(self.dompath, *args)
722 def _writeDom(self, *args):
723 return xstransact.Write(self.dompath, *args)
725 def _removeDom(self, *args):
726 return xstransact.Remove(self.dompath, *args)
728 def storeDom(self, *args):
729 return xstransact.Store(self.dompath, *args)
731 def _recreateDom(self):
732 complete(self.dompath, lambda t: self._recreateDomFunc(t))
734 def _recreateDomFunc(self, t):
735 t.remove()
736 t.mkdir()
737 t.set_permissions({'dom' : self.domid})
738 t.write('vm', self.vmpath)
740 def _storeDomDetails(self):
741 to_store = {
742 'domid': str(self.domid),
743 'vm': self.vmpath,
744 'name': self.info['name_label'],
745 'console/limit': str(xroot.get_console_limit() * 1024),
746 'memory/target': str(self.info['memory_static_min'] * 1024)
747 }
749 def f(n, v):
750 if v is not None:
751 to_store[n] = str(v)
753 f('console/port', self.console_port)
754 f('console/ring-ref', self.console_mfn)
755 f('store/port', self.store_port)
756 f('store/ring-ref', self.store_mfn)
758 to_store.update(self._vcpuDomDetails())
760 log.debug("Storing domain details: %s", scrub_password(to_store))
762 self._writeDom(to_store)
764 def _vcpuDomDetails(self):
765 def availability(n):
766 if self.info['vcpu_avail'] & (1 << n):
767 return 'online'
768 else:
769 return 'offline'
771 result = {}
772 for v in range(0, self.info['vcpus_number']):
773 result["cpu/%d/availability" % v] = availability(v)
774 return result
776 #
777 # xenstore watches
778 #
780 def _registerWatches(self):
781 """Register a watch on this VM's entries in the store, and the
782 domain's control/shutdown node, so that when they are changed
783 externally, we keep up to date. This should only be called by {@link
784 #create}, {@link #recreate}, or {@link #restore}, once the domain's
785 details have been written, but before the new instance is returned."""
786 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
787 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
788 self._handleShutdownWatch)
790 def _storeChanged(self, _):
791 log.trace("XendDomainInfo.storeChanged");
793 changed = False
795 # Check whether values in the configuration have
796 # changed in Xenstore.
798 cfg_vm = ['name', 'on_poweroff', 'on_reboot', 'on_crash']
800 vm_details = self._readVMDetails([(k,XendConfig.LEGACY_CFG_TYPES[k])
801 for k in cfg_vm])
803 # convert two lists into a python dictionary
804 vm_details = dict(zip(cfg_vm, vm_details))
806 for arg, val in vm_details.items():
807 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
808 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
809 if val != None and val != self.info[xapiarg]:
810 self.info[xapiarg] = val
811 changed= True
813 # Check whether image definition has been updated
814 image_sxp = self._readVm('image')
815 if image_sxp and image_sxp != self.info.image_sxpr():
816 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
817 changed = True
819 if changed:
820 # Update the domain section of the store, as this contains some
821 # parameters derived from the VM configuration.
822 self._storeDomDetails()
824 return 1
826 def _handleShutdownWatch(self, _):
827 log.debug('XendDomainInfo.handleShutdownWatch')
829 reason = self.readDom('control/shutdown')
831 if reason and reason != 'suspend':
832 sst = self.readDom('xend/shutdown_start_time')
833 now = time.time()
834 if sst:
835 self.shutdownStartTime = float(sst)
836 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
837 else:
838 self.shutdownStartTime = now
839 self.storeDom('xend/shutdown_start_time', now)
840 timeout = SHUTDOWN_TIMEOUT
842 log.trace(
843 "Scheduling refreshShutdown on domain %d in %ds.",
844 self.domid, timeout)
845 threading.Timer(timeout, self.refreshShutdown).start()
847 return True
850 #
851 # Public Attributes for the VM
852 #
855 def getDomid(self):
856 return self.domid
858 def setName(self, name):
859 self._checkName(name)
860 self.info['name_label'] = name
861 self.storeVm("name", name)
863 def getName(self):
864 return self.info['name_label']
866 def getDomainPath(self):
867 return self.dompath
869 def getShutdownReason(self):
870 return self.readDom('control/shutdown')
872 def getStorePort(self):
873 """For use only by image.py and XendCheckpoint.py."""
874 return self.store_port
876 def getConsolePort(self):
877 """For use only by image.py and XendCheckpoint.py"""
878 return self.console_port
880 def getFeatures(self):
881 """For use only by image.py."""
882 return self.info['features']
884 def getVCpuCount(self):
885 return self.info['vcpus_number']
887 def setVCpuCount(self, vcpus):
888 self.info['vcpu_avail'] = (1 << vcpus) - 1
889 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
890 # update dom differently depending on whether we are adjusting
891 # vcpu number up or down, otherwise _vcpuDomDetails does not
892 # disable the vcpus
893 if self.info['vcpus_number'] > vcpus:
894 # decreasing
895 self._writeDom(self._vcpuDomDetails())
896 self.info['vcpus_number'] = vcpus
897 else:
898 # same or increasing
899 self.info['vcpus_number'] = vcpus
900 self._writeDom(self._vcpuDomDetails())
902 def getLabel(self):
903 return security.get_security_info(self.info, 'label')
905 def getMemoryTarget(self):
906 """Get this domain's target memory size, in KB."""
907 return self.info['memory_static_min'] * 1024
909 def getMemoryMaximum(self):
910 """Get this domain's maximum memory size, in KB."""
911 return self.info['memory_static_max'] * 1024
913 def getResume(self):
914 return str(self._resume)
916 def getCap(self):
917 return self.info.get('cpu_cap', 0)
919 def getWeight(self):
920 return self.info['cpu_weight']
922 def setResume(self, state):
923 self._resume = state
925 def getRestartCount(self):
926 return self._readVm('xend/restart_count')
928 def refreshShutdown(self, xeninfo = None):
929 """ Checks the domain for whether a shutdown is required.
931 Called from XendDomainInfo and also image.py for HVM images.
932 """
934 # If set at the end of this method, a restart is required, with the
935 # given reason. This restart has to be done out of the scope of
936 # refresh_shutdown_lock.
937 restart_reason = None
939 self.refresh_shutdown_lock.acquire()
940 try:
941 if xeninfo is None:
942 xeninfo = dom_get(self.domid)
943 if xeninfo is None:
944 # The domain no longer exists. This will occur if we have
945 # scheduled a timer to check for shutdown timeouts and the
946 # shutdown succeeded. It will also occur if someone
947 # destroys a domain beneath us. We clean up the domain,
948 # just in case, but we can't clean up the VM, because that
949 # VM may have migrated to a different domain on this
950 # machine.
951 self.cleanupDomain()
952 self._stateSet(DOM_STATE_HALTED)
953 return
955 if xeninfo['dying']:
956 # Dying means that a domain has been destroyed, but has not
957 # yet been cleaned up by Xen. This state could persist
958 # indefinitely if, for example, another domain has some of its
959 # pages mapped. We might like to diagnose this problem in the
960 # future, but for now all we do is make sure that it's not us
961 # holding the pages, by calling cleanupDomain. We can't
962 # clean up the VM, as above.
963 self.cleanupDomain()
964 self._stateSet(DOM_STATE_SHUTDOWN)
965 return
967 elif xeninfo['crashed']:
968 if self.readDom('xend/shutdown_completed'):
969 # We've seen this shutdown already, but we are preserving
970 # the domain for debugging. Leave it alone.
971 return
973 log.warn('Domain has crashed: name=%s id=%d.',
974 self.info['name_label'], self.domid)
976 if xroot.get_enable_dump():
977 self.dumpCore()
979 restart_reason = 'crash'
980 self._stateSet(DOM_STATE_HALTED)
982 elif xeninfo['shutdown']:
983 self._stateSet(DOM_STATE_SHUTDOWN)
984 if self.readDom('xend/shutdown_completed'):
985 # We've seen this shutdown already, but we are preserving
986 # the domain for debugging. Leave it alone.
987 return
989 else:
990 reason = shutdown_reason(xeninfo['shutdown_reason'])
992 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
993 self.info['name_label'], self.domid, reason)
995 self._clearRestart()
997 if reason == 'suspend':
998 self._stateSet(DOM_STATE_SUSPENDED)
999 # Don't destroy the domain. XendCheckpoint will do
1000 # this once it has finished. However, stop watching
1001 # the VM path now, otherwise we will end up with one
1002 # watch for the old domain, and one for the new.
1003 self._unwatchVm()
1004 elif reason in ('poweroff', 'reboot'):
1005 restart_reason = reason
1006 else:
1007 self.destroy()
1009 elif self.dompath is None:
1010 # We have yet to manage to call introduceDomain on this
1011 # domain. This can happen if a restore is in progress, or has
1012 # failed. Ignore this domain.
1013 pass
1014 else:
1015 # Domain is alive. If we are shutting it down, then check
1016 # the timeout on that, and destroy it if necessary.
1017 if xeninfo['paused']:
1018 self._stateSet(DOM_STATE_PAUSED)
1019 else:
1020 self._stateSet(DOM_STATE_RUNNING)
1022 if self.shutdownStartTime:
1023 timeout = (SHUTDOWN_TIMEOUT - time.time() +
1024 self.shutdownStartTime)
1025 if timeout < 0:
1026 log.info(
1027 "Domain shutdown timeout expired: name=%s id=%s",
1028 self.info['name_label'], self.domid)
1029 self.destroy()
1030 finally:
1031 self.refresh_shutdown_lock.release()
1033 if restart_reason:
1034 self._maybeRestart(restart_reason)
1038 # Restart functions - handling whether we come back up on shutdown.
1041 def _clearRestart(self):
1042 self._removeDom("xend/shutdown_start_time")
1045 def _maybeRestart(self, reason):
1046 # Dispatch to the correct method based upon the configured on_{reason}
1047 # behaviour.
1048 actions = {"destroy" : self.destroy,
1049 "restart" : self._restart,
1050 "preserve" : self._preserve,
1051 "rename-restart" : self._renameRestart}
1053 action_conf = {
1054 'poweroff': 'actions_after_shutdown',
1055 'reboot': 'actions_after_reboot',
1056 'crash': 'actions_after_crash',
1059 action_target = self.info.get(action_conf.get(reason))
1060 func = actions.get(action_target, None)
1061 if func and callable(func):
1062 func()
1063 else:
1064 self.destroy() # default to destroy
1066 def _renameRestart(self):
1067 self._restart(True)
1069 def _restart(self, rename = False):
1070 """Restart the domain after it has exited.
1072 @param rename True if the old domain is to be renamed and preserved,
1073 False if it is to be destroyed.
1074 """
1075 from xen.xend import XendDomain
1077 self._configureBootloader()
1078 config = self.sxpr()
1080 if self._infoIsSet('cpus') and len(self.info['cpus']) != 0:
1081 config.append(['cpus', reduce(lambda x, y: str(x) + "," + str(y),
1082 self.info['cpus'])])
1084 if self._readVm(RESTART_IN_PROGRESS):
1085 log.error('Xend failed during restart of domain %s. '
1086 'Refusing to restart to avoid loops.',
1087 str(self.domid))
1088 self.destroy()
1089 return
1091 old_domid = self.domid
1092 self._writeVm(RESTART_IN_PROGRESS, 'True')
1094 now = time.time()
1095 rst = self._readVm('xend/previous_restart_time')
1096 if rst:
1097 rst = float(rst)
1098 timeout = now - rst
1099 if timeout < MINIMUM_RESTART_TIME:
1100 log.error(
1101 'VM %s restarting too fast (%f seconds since the last '
1102 'restart). Refusing to restart to avoid loops.',
1103 self.info['name_label'], timeout)
1104 self.destroy()
1105 return
1107 self._writeVm('xend/previous_restart_time', str(now))
1109 try:
1110 if rename:
1111 self._preserveForRestart()
1112 else:
1113 self._unwatchVm()
1114 self.destroyDomain()
1116 # new_dom's VM will be the same as this domain's VM, except where
1117 # the rename flag has instructed us to call preserveForRestart.
1118 # In that case, it is important that we remove the
1119 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1120 # once the new one is available.
1122 new_dom = None
1123 try:
1124 new_dom = XendDomain.instance().domain_create(config)
1125 new_dom.unpause()
1126 rst_cnt = self._readVm('xend/restart_count')
1127 rst_cnt = int(rst_cnt) + 1
1128 self._writeVm('xend/restart_count', str(rst_cnt))
1129 new_dom._removeVm(RESTART_IN_PROGRESS)
1130 except:
1131 if new_dom:
1132 new_dom._removeVm(RESTART_IN_PROGRESS)
1133 new_dom.destroy()
1134 else:
1135 self._removeVm(RESTART_IN_PROGRESS)
1136 raise
1137 except:
1138 log.exception('Failed to restart domain %s.', str(old_domid))
1140 def _preserveForRestart(self):
1141 """Preserve a domain that has been shut down, by giving it a new UUID,
1142 cloning the VM details, and giving it a new name. This allows us to
1143 keep this domain for debugging, but restart a new one in its place
1144 preserving the restart semantics (name and UUID preserved).
1145 """
1147 new_uuid = uuid.createString()
1148 new_name = 'Domain-%s' % new_uuid
1149 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1150 self.info['name_label'], self.domid, self.info['uuid'],
1151 new_name, new_uuid)
1152 self._unwatchVm()
1153 self._releaseDevices()
1154 self.info['name_label'] = new_name
1155 self.info['uuid'] = new_uuid
1156 self.vmpath = XS_VMROOT + new_uuid
1157 self._storeVmDetails()
1158 self._preserve()
1161 def _preserve(self):
1162 log.info("Preserving dead domain %s (%d).", self.info['name_label'],
1163 self.domid)
1164 self._unwatchVm()
1165 self.storeDom('xend/shutdown_completed', 'True')
1166 self._stateSet(DOM_STATE_HALTED)
1169 # Debugging ..
1172 def dumpCore(self, corefile = None):
1173 """Create a core dump for this domain. Nothrow guarantee."""
1175 try:
1176 if not corefile:
1177 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1178 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1179 self.info['name_label'], self.domid)
1181 if os.path.isdir(corefile):
1182 raise XendError("Cannot dump core in a directory: %s" %
1183 corefile)
1185 xc.domain_dumpcore(self.domid, corefile)
1186 except RuntimeError, ex:
1187 corefile_incomp = corefile+'-incomplete'
1188 os.rename(corefile, corefile_incomp)
1189 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1190 self.domid, self.info['name_label'])
1191 raise XendError("Failed to dump core: %s" % str(ex))
1194 # Device creation/deletion functions
1197 def _createDevice(self, deviceClass, devConfig):
1198 return self.getDeviceController(deviceClass).createDevice(devConfig)
1200 def _waitForDevice(self, deviceClass, devid):
1201 return self.getDeviceController(deviceClass).waitForDevice(devid)
1203 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1204 return self.getDeviceController(deviceClass).reconfigureDevice(
1205 devid, devconfig)
1207 def _createDevices(self):
1208 """Create the devices for a vm.
1210 @raise: VmError for invalid devices
1211 """
1212 for (devclass, config) in self.info.get('devices', {}).values():
1213 if devclass in XendDevices.valid_devices():
1214 log.info("createDevice: %s : %s" % (devclass, scrub_password(config)))
1215 self._createDevice(devclass, config)
1217 if self.image:
1218 self.image.createDeviceModel()
1220 def _releaseDevices(self):
1221 """Release all domain's devices. Nothrow guarantee."""
1223 while True:
1224 t = xstransact("%s/device" % self.dompath)
1225 for devclass in XendDevices.valid_devices():
1226 for dev in t.list(devclass):
1227 try:
1228 t.remove(dev)
1229 except:
1230 # Log and swallow any exceptions in removal --
1231 # there's nothing more we can do.
1232 log.exception(
1233 "Device release failed: %s; %s; %s",
1234 self.info['name_label'], devclass, dev)
1235 if t.commit():
1236 break
1238 def getDeviceController(self, name):
1239 """Get the device controller for this domain, and if it
1240 doesn't exist, create it.
1242 @param name: device class name
1243 @type name: string
1244 @rtype: subclass of DevController
1245 """
1246 if name not in self._deviceControllers:
1247 devController = XendDevices.make_controller(name, self)
1248 if not devController:
1249 raise XendError("Unknown device type: %s" % name)
1250 self._deviceControllers[name] = devController
1252 return self._deviceControllers[name]
1255 # Migration functions (public)
1258 def testMigrateDevices(self, network, dst):
1259 """ Notify all device about intention of migration
1260 @raise: XendError for a device that cannot be migrated
1261 """
1262 for (n, c) in self.info.all_devices_sxpr():
1263 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1264 if rc != 0:
1265 raise XendError("Device of type '%s' refuses migration." % n)
1267 def migrateDevices(self, network, dst, step, domName=''):
1268 """Notify the devices about migration
1269 """
1270 ctr = 0
1271 try:
1272 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1273 self.migrateDevice(dev_type, dev_conf, network, dst,
1274 step, domName)
1275 ctr = ctr + 1
1276 except:
1277 for dev_type, dev_conf in self.info.all_devices_sxpr():
1278 if ctr == 0:
1279 step = step - 1
1280 ctr = ctr - 1
1281 self._recoverMigrateDevice(dev_type, dev_conf, network,
1282 dst, step, domName)
1283 raise
1285 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1286 step, domName=''):
1287 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1288 network, dst, step, domName)
1290 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1291 dst, step, domName=''):
1292 return self.getDeviceController(deviceClass).recover_migrate(
1293 deviceConfig, network, dst, step, domName)
1296 ## private:
1298 def _constructDomain(self):
1299 """Construct the domain.
1301 @raise: VmError on error
1302 """
1304 log.debug('XendDomainInfo.constructDomain')
1306 image_cfg = self.info.get('image', {})
1307 hvm = image_cfg.has_key('hvm')
1309 if hvm:
1310 info = xc.xeninfo()
1311 if 'hvm' not in info['xen_caps']:
1312 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1313 "supported by your CPU and enabled in your "
1314 "BIOS?")
1316 self.domid = xc.domain_create(
1317 domid = 0,
1318 ssidref = security.get_security_info(self.info, 'ssidref'),
1319 handle = uuid.fromString(self.info['uuid']),
1320 hvm = int(hvm))
1322 if self.domid < 0:
1323 raise VmError('Creating domain failed: name=%s' %
1324 self.info['name_label'])
1326 self.dompath = GetDomainPath(self.domid)
1328 self._recreateDom()
1330 # Set maximum number of vcpus in domain
1331 xc.domain_max_vcpus(self.domid, int(self.info['vcpus_number']))
1334 def _introduceDomain(self):
1335 assert self.domid is not None
1336 assert self.store_mfn is not None
1337 assert self.store_port is not None
1339 try:
1340 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1341 except RuntimeError, exn:
1342 raise XendError(str(exn))
1345 def _initDomain(self):
1346 log.debug('XendDomainInfo.initDomain: %s %s',
1347 self.domid,
1348 self.info['cpu_weight'])
1350 # if we have a boot loader but no image, then we need to set things
1351 # up by running the boot loader non-interactively
1352 if self.info.get('bootloader'):
1353 self._configureBootloader()
1355 if not self._infoIsSet('image'):
1356 raise VmError('Missing image in configuration')
1358 try:
1359 self.image = image.create(self,
1360 self.info,
1361 self.info['image'],
1362 self.info['devices'])
1364 localtime = self.info.get('localtime', False)
1365 if localtime:
1366 xc.domain_set_time_offset(self.domid)
1368 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1370 # repin domain vcpus if a restricted cpus list is provided
1371 # this is done prior to memory allocation to aide in memory
1372 # distribution for NUMA systems.
1373 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1374 for v in range(0, self.info['max_vcpu_id']+1):
1375 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1377 # Use architecture- and image-specific calculations to determine
1378 # the various headrooms necessary, given the raw configured
1379 # values. maxmem, memory, and shadow are all in KiB.
1380 memory = self.image.getRequiredAvailableMemory(
1381 self.info['memory_static_min'] * 1024)
1382 maxmem = self.image.getRequiredAvailableMemory(
1383 self.info['memory_static_max'] * 1024)
1384 shadow = self.image.getRequiredShadowMemory(
1385 self.info['shadow_memory'] * 1024,
1386 self.info['memory_static_max'] * 1024)
1388 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1389 # takes MiB and we must not round down and end up under-providing.
1390 shadow = ((shadow + 1023) / 1024) * 1024
1392 # set memory limit
1393 xc.domain_setmaxmem(self.domid, maxmem)
1395 # Make sure there's enough RAM available for the domain
1396 balloon.free(memory + shadow)
1398 # Set up the shadow memory
1399 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1400 self.info['shadow_memory'] = shadow_cur
1402 self._createChannels()
1404 channel_details = self.image.createImage()
1406 self.store_mfn = channel_details['store_mfn']
1407 if 'console_mfn' in channel_details:
1408 self.console_mfn = channel_details['console_mfn']
1410 self._introduceDomain()
1412 self._createDevices()
1414 if self.info['bootloader']:
1415 self.image.cleanupBootloading()
1417 self.info['start_time'] = time.time()
1419 self._stateSet(DOM_STATE_RUNNING)
1420 except RuntimeError, exn:
1421 log.exception("XendDomainInfo.initDomain: exception occurred")
1422 if self.info['bootloader'] not in (None, 'kernel_external') \
1423 and self.image is not None:
1424 self.image.cleanupBootloading()
1425 raise VmError(str(exn))
1428 def cleanupDomain(self):
1429 """Cleanup domain resources; release devices. Idempotent. Nothrow
1430 guarantee."""
1432 self.refresh_shutdown_lock.acquire()
1433 try:
1434 self.unwatchShutdown()
1435 self._releaseDevices()
1437 if self.image:
1438 try:
1439 self.image.destroy()
1440 except:
1441 log.exception(
1442 "XendDomainInfo.cleanup: image.destroy() failed.")
1443 self.image = None
1445 try:
1446 self._removeDom()
1447 except:
1448 log.exception("Removing domain path failed.")
1450 self._stateSet(DOM_STATE_HALTED)
1451 finally:
1452 self.refresh_shutdown_lock.release()
1455 def unwatchShutdown(self):
1456 """Remove the watch on the domain's control/shutdown node, if any.
1457 Idempotent. Nothrow guarantee. Expects to be protected by the
1458 refresh_shutdown_lock."""
1460 try:
1461 try:
1462 if self.shutdownWatch:
1463 self.shutdownWatch.unwatch()
1464 finally:
1465 self.shutdownWatch = None
1466 except:
1467 log.exception("Unwatching control/shutdown failed.")
1469 def waitForShutdown(self):
1470 self.state_updated.acquire()
1471 try:
1472 while self.state in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1473 self.state_updated.wait()
1474 finally:
1475 self.state_updated.release()
1479 # TODO: recategorise - called from XendCheckpoint
1482 def completeRestore(self, store_mfn, console_mfn):
1484 log.debug("XendDomainInfo.completeRestore")
1486 self.store_mfn = store_mfn
1487 self.console_mfn = console_mfn
1489 self._introduceDomain()
1490 self._storeDomDetails()
1491 self._registerWatches()
1492 self.refreshShutdown()
1494 log.debug("XendDomainInfo.completeRestore done")
1497 def _endRestore(self):
1498 self.setResume(False)
1501 # VM Destroy
1504 def destroy(self):
1505 """Cleanup VM and destroy domain. Nothrow guarantee."""
1507 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1509 self._cleanupVm()
1510 if self.dompath is not None:
1511 self.destroyDomain()
1514 def destroyDomain(self):
1515 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1517 try:
1518 if self.domid is not None:
1519 xc.domain_destroy(self.domid)
1520 self.domid = None
1521 for state in DOM_STATES_OLD:
1522 self.info[state] = 0
1523 except:
1524 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1526 self.cleanupDomain()
1530 # Channels for xenstore and console
1533 def _createChannels(self):
1534 """Create the channels to the domain.
1535 """
1536 self.store_port = self._createChannel()
1537 self.console_port = self._createChannel()
1540 def _createChannel(self):
1541 """Create an event channel to the domain.
1542 """
1543 try:
1544 return xc.evtchn_alloc_unbound(domid=self.domid, remote_dom=0)
1545 except:
1546 log.exception("Exception in alloc_unbound(%d)", self.domid)
1547 raise
1550 # Bootloader configuration
1553 def _configureBootloader(self):
1554 """Run the bootloader if we're configured to do so."""
1555 if not self.info.get('bootloader'):
1556 return
1557 blcfg = None
1559 # FIXME: this assumes that we want to use the first disk device
1560 for (devtype, devinfo) in self.info.all_devices_sxpr():
1561 if not devtype or not devinfo or devtype not in ('vbd', 'tap'):
1562 continue
1563 disk = None
1564 for param in devinfo:
1565 if param[0] == 'uname':
1566 disk = param[1]
1567 break
1569 if disk is None:
1570 continue
1571 fn = blkdev_uname_to_file(disk)
1572 blcfg = bootloader(self.info['bootloader'], fn, 1,
1573 self.info['bootloader_args'],
1574 self.info['image'])
1575 break
1576 if blcfg is None:
1577 msg = "Had a bootloader specified, but can't find disk"
1578 log.error(msg)
1579 raise VmError(msg)
1581 self.info.update_with_image_sxp(blcfg)
1584 # VM Functions
1587 def _readVMDetails(self, params):
1588 """Read the specified parameters from the store.
1589 """
1590 try:
1591 return self._gatherVm(*params)
1592 except ValueError:
1593 # One of the int/float entries in params has a corresponding store
1594 # entry that is invalid. We recover, because older versions of
1595 # Xend may have put the entry there (memory/target, for example),
1596 # but this is in general a bad situation to have reached.
1597 log.exception(
1598 "Store corrupted at %s! Domain %d's configuration may be "
1599 "affected.", self.vmpath, self.domid)
1600 return []
1602 def _cleanupVm(self):
1603 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1605 self._unwatchVm()
1607 try:
1608 self._removeVm()
1609 except:
1610 log.exception("Removing VM path failed.")
1613 def checkLiveMigrateMemory(self):
1614 """ Make sure there's enough memory to migrate this domain """
1615 overhead_kb = 0
1616 if arch.type == "x86":
1617 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1618 # the minimum that Xen would allocate if no value were given.
1619 overhead_kb = self.info['vcpus_number'] * 1024 + \
1620 self.info['memory_static_max'] * 4
1621 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1622 # The domain might already have some shadow memory
1623 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1624 if overhead_kb > 0:
1625 balloon.free(overhead_kb)
1627 def _unwatchVm(self):
1628 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1629 guarantee."""
1630 try:
1631 try:
1632 if self.vmWatch:
1633 self.vmWatch.unwatch()
1634 finally:
1635 self.vmWatch = None
1636 except:
1637 log.exception("Unwatching VM path failed.")
1639 def testDeviceComplete(self):
1640 """ For Block IO migration safety we must ensure that
1641 the device has shutdown correctly, i.e. all blocks are
1642 flushed to disk
1643 """
1644 start = time.time()
1645 while True:
1646 test = 0
1647 diff = time.time() - start
1648 for i in self.getDeviceController('vbd').deviceIDs():
1649 test = 1
1650 log.info("Dev %s still active, looping...", i)
1651 time.sleep(0.1)
1653 if test == 0:
1654 break
1655 if diff >= MIGRATE_TIMEOUT:
1656 log.info("Dev still active but hit max loop timeout")
1657 break
1659 def _storeVmDetails(self):
1660 to_store = {}
1662 for key in XendConfig.LEGACY_XENSTORE_VM_PARAMS:
1663 info_key = XendConfig.LEGACY_CFG_TO_XENAPI_CFG.get(key, key)
1664 if self._infoIsSet(info_key):
1665 to_store[key] = str(self.info[info_key])
1667 if self.info.get('image'):
1668 image_sxpr = self.info.image_sxpr()
1669 if image_sxpr:
1670 to_store['image'] = sxp.to_string(image_sxpr)
1672 if self._infoIsSet('security'):
1673 secinfo = self.info['security']
1674 to_store['security'] = sxp.to_string(secinfo)
1675 for idx in range(0, len(secinfo)):
1676 if secinfo[idx][0] == 'access_control':
1677 to_store['security/access_control'] = sxp.to_string(
1678 [secinfo[idx][1], secinfo[idx][2]])
1679 for aidx in range(1, len(secinfo[idx])):
1680 if secinfo[idx][aidx][0] == 'label':
1681 to_store['security/access_control/label'] = \
1682 secinfo[idx][aidx][1]
1683 if secinfo[idx][aidx][0] == 'policy':
1684 to_store['security/access_control/policy'] = \
1685 secinfo[idx][aidx][1]
1686 if secinfo[idx][0] == 'ssidref':
1687 to_store['security/ssidref'] = str(secinfo[idx][1])
1690 if not self._readVm('xend/restart_count'):
1691 to_store['xend/restart_count'] = str(0)
1693 log.debug("Storing VM details: %s", scrub_password(to_store))
1695 self._writeVm(to_store)
1696 self._setVmPermissions()
1699 def _setVmPermissions(self):
1700 """Allow the guest domain to read its UUID. We don't allow it to
1701 access any other entry, for security."""
1702 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1703 { 'dom' : self.domid,
1704 'read' : True,
1705 'write' : False })
1708 # Utility functions
1711 def _stateSet(self, state):
1712 self.state_updated.acquire()
1713 try:
1714 if self.state != state:
1715 self.state = state
1716 self.state_updated.notifyAll()
1717 finally:
1718 self.state_updated.release()
1720 def _infoIsSet(self, name):
1721 return name in self.info and self.info[name] is not None
1723 def _checkName(self, name):
1724 """Check if a vm name is valid. Valid names contain alphabetic
1725 characters, digits, or characters in '_-.:/+'.
1726 The same name cannot be used for more than one vm at the same time.
1728 @param name: name
1729 @raise: VmError if invalid
1730 """
1731 from xen.xend import XendDomain
1733 if name is None or name == '':
1734 raise VmError('Missing VM Name')
1736 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1737 raise VmError('Invalid VM Name')
1739 dom = XendDomain.instance().domain_lookup_nr(name)
1740 if dom and dom.info['uuid'] != self.info['uuid']:
1741 raise VmError("VM name '%s' already exists%s" %
1742 (name,
1743 dom.domid is not None and
1744 (" as domain %s" % str(dom.domid)) or ""))
1747 def update(self, info = None, refresh = True):
1748 """Update with info from xc.domain_getinfo().
1749 """
1750 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1751 str(self.domid))
1753 if not info:
1754 info = dom_get(self.domid)
1755 if not info:
1756 return
1758 #manually update ssidref / security fields
1759 if security.on() and info.has_key('ssidref'):
1760 if (info['ssidref'] != 0) and self.info.has_key('security'):
1761 security_field = self.info['security']
1762 if not security_field:
1763 #create new security element
1764 self.info.update({'security':
1765 [['ssidref', str(info['ssidref'])]]})
1767 #ssidref field not used any longer
1768 if 'ssidref' in info:
1769 info.pop('ssidref')
1771 # make sure state is reset for info
1772 # TODO: we should eventually get rid of old_dom_states
1774 self.info.update_config(info)
1776 if refresh:
1777 self.refreshShutdown(info)
1779 log.trace("XendDomainInfo.update done on domain %s: %s",
1780 str(self.domid), self.info)
1782 def sxpr(self, ignore_store = False):
1783 result = self.info.to_sxp(domain = self,
1784 ignore_devices = ignore_store)
1786 if not ignore_store and self.dompath:
1787 vnc_port = self.readDom('console/vnc-port')
1788 if vnc_port is not None:
1789 result.append(['device',
1790 ['console', ['vnc-port', str(vnc_port)]]])
1792 return result
1794 # Xen API
1795 # ----------------------------------------------------------------
1797 def get_uuid(self):
1798 dom_uuid = self.info.get('uuid')
1799 if not dom_uuid: # if it doesn't exist, make one up
1800 dom_uuid = uuid.createString()
1801 self.info['uuid'] = dom_uuid
1802 return dom_uuid
1804 def get_memory_static_max(self):
1805 return self.info.get('memory_static_max', 0)
1806 def get_memory_static_min(self):
1807 return self.info.get('memory_static_min', 0)
1808 def get_memory_dynamic_max(self):
1809 return self.info.get('memory_dynamic_max', 0)
1810 def get_memory_dynamic_min(self):
1811 return self.info.get('memory_dynamic_min', 0)
1814 def get_vcpus_policy(self):
1815 sched_id = xc.sched_id_get()
1816 if sched_id == xen.lowlevel.xc.XEN_SCHEDULER_SEDF:
1817 return 'sedf'
1818 elif sched_id == xen.lowlevel.xc.XEN_SCHEDULER_CREDIT:
1819 return 'credit'
1820 else:
1821 return 'unknown'
1822 def get_vcpus_params(self):
1823 return '' # TODO
1824 def get_power_state(self):
1825 return XEN_API_VM_POWER_STATE[self.state]
1826 def get_bios_boot(self):
1827 return '' # TODO
1828 def get_platform_std_vga(self):
1829 return self.info.get('platform_std_vga', False)
1830 def get_platform_keymap(self):
1831 return ''
1832 def get_platform_serial(self):
1833 return self.info.get('platform_serial', '')
1834 def get_platform_localtime(self):
1835 return self.info.get('platform_localtime', False)
1836 def get_platform_clock_offset(self):
1837 return self.info.get('platform_clock_offset', False)
1838 def get_platform_enable_audio(self):
1839 return self.info.get('platform_enable_audio', False)
1840 def get_platform_keymap(self):
1841 return self.info.get('platform_keymap', '')
1842 def get_builder(self):
1843 return self.info.get('builder', 0)
1844 def get_boot_method(self):
1845 return self.info.get('boot_method', XEN_API_BOOT_TYPE[2])
1846 def get_kernel_image(self):
1847 return self.info.get('kernel_kernel', '')
1848 def get_kernel_initrd(self):
1849 return self.info.get('kernel_initrd', '')
1850 def get_kernel_args(self):
1851 return self.info.get('kernel_args', '')
1852 def get_grub_cmdline(self):
1853 return '' # TODO
1854 def get_pci_bus(self):
1855 return '' # TODO
1856 def get_tools_version(self):
1857 return {} # TODO
1858 def get_other_config(self):
1859 return {} # TODO
1861 def get_on_shutdown(self):
1862 after_shutdown = self.info.get('action_after_shutdown')
1863 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
1864 return XEN_API_ON_NORMAL_EXIT[-1]
1865 return after_shutdown
1867 def get_on_reboot(self):
1868 after_reboot = self.info.get('action_after_reboot')
1869 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
1870 return XEN_API_ON_NORMAL_EXIT[-1]
1871 return after_reboot
1873 def get_on_suspend(self):
1874 # TODO: not supported
1875 after_suspend = self.info.get('action_after_suspend')
1876 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
1877 return XEN_API_ON_NORMAL_EXIT[-1]
1878 return after_suspend
1880 def get_on_crash(self):
1881 after_crash = self.info.get('action_after_crash')
1882 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
1883 return XEN_API_ON_CRASH_BEHAVIOUR[0]
1884 return after_crash
1886 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
1887 """ Get's a device configuration either from XendConfig or
1888 from the DevController.
1890 @param dev_class: device class, either, 'vbd' or 'vif'
1891 @param dev_uuid: device UUID
1893 @rtype: dictionary
1894 """
1895 dev_type_config = self.info['devices'].get(dev_uuid)
1897 # shortcut if the domain isn't started because
1898 # the devcontrollers will have no better information
1899 # than XendConfig.
1900 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
1901 if dev_type_config:
1902 return copy.deepcopy(dev_type_config[1])
1903 return None
1905 # instead of using dev_class, we use the dev_type
1906 # that is from XendConfig.
1907 # This will accomdate 'tap' as well as 'vbd'
1908 dev_type = dev_type_config[0]
1910 controller = self.getDeviceController(dev_type)
1911 if not controller:
1912 return None
1914 all_configs = controller.getAllDeviceConfigurations()
1915 if not all_configs:
1916 return None
1918 dev_config = copy.deepcopy(dev_type_config[1])
1919 for _devid, _devcfg in all_configs.items():
1920 if _devcfg.get('uuid') == dev_uuid:
1921 dev_config.update(_devcfg)
1922 dev_config['id'] = _devid
1923 return dev_config
1925 return dev_config
1927 def get_dev_xenapi_config(self, dev_class, dev_uuid):
1928 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
1929 if not config:
1930 return {}
1932 config['VM'] = self.get_uuid()
1934 if dev_class == 'vif':
1935 if not config.has_key('name'):
1936 config['name'] = config.get('vifname', '')
1937 if not config.has_key('MAC'):
1938 config['MAC'] = config.get('mac', '')
1939 if not config.has_key('type'):
1940 config['type'] = 'paravirtualised'
1941 if not config.has_key('device'):
1942 devid = config.get('id')
1943 if devid != None:
1944 config['device'] = 'eth%d' % devid
1945 else:
1946 config['device'] = ''
1948 config['network'] = '' # Invalid for Xend
1949 config['MTU'] = 1500 # TODO
1950 config['io_read_kbs'] = 0.0
1951 config['io_write_kbs'] = 0.0
1953 if dev_class == 'vbd':
1954 config['VDI'] = config.get('VDI', '')
1955 config['device'] = config.get('dev', '')
1956 config['driver'] = 'paravirtualised' # TODO
1957 config['image'] = config.get('uname', '')
1958 config['io_read_kbs'] = 0.0
1959 config['io_write_kbs'] = 0.0
1960 if config['mode'] == 'r':
1961 config['mode'] = 'RO'
1962 else:
1963 config['mode'] = 'RW'
1965 if dev_class == 'vtpm':
1966 config['driver'] = 'paravirtualised' # TODO
1968 return config
1970 def get_dev_property(self, dev_class, dev_uuid, field):
1971 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
1972 try:
1973 return config[field]
1974 except KeyError:
1975 raise XendError('Invalid property for device: %s' % field)
1977 def get_vcpus_util(self):
1978 # TODO: this returns the total accum cpu time, rather than util
1979 # TODO: spec says that key is int, however, python does not allow
1980 # non-string keys to dictionaries.
1981 vcpu_util = {}
1982 if 'max_vcpu_id' in self.info and self.domid != None:
1983 for i in range(0, self.info['max_vcpu_id']+1):
1984 info = xc.vcpu_getinfo(self.domid, i)
1985 vcpu_util[str(i)] = info['cpu_time']/1000000000.0
1987 return vcpu_util
1989 def get_vifs(self):
1990 return self.info.get('vif_refs', [])
1992 def get_vbds(self):
1993 return self.info.get('vbd_refs', [])
1995 def get_vtpms(self):
1996 return self.info.get('vtpm_refs', [])
1998 def create_vbd(self, xenapi_vbd):
1999 """Create a VBD device from the passed struct in Xen API format.
2001 @return: uuid of the device
2002 @rtype: string
2003 """
2005 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
2006 if not dev_uuid:
2007 raise XendError('Failed to create device')
2009 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
2010 sxpr = self.info.device_sxpr(dev_uuid)
2011 devid = self.getDeviceController('vbd').createDevice(sxpr)
2012 raise XendError("Device creation failed")
2014 return dev_uuid
2016 def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
2017 """Create a VBD using a VDI from XendStorageRepository.
2019 @param xenapi_vbd: vbd struct from the Xen API
2020 @param vdi_image_path: VDI UUID
2021 @rtype: string
2022 @return: uuid of the device
2023 """
2024 xenapi_vbd['image'] = vdi_image_path
2025 log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
2026 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
2027 if not dev_uuid:
2028 raise XendError('Failed to create device')
2030 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
2031 sxpr = self.info.device_sxpr(dev_uuid)
2032 devid = self.getDeviceController('tap').createDevice(sxpr)
2033 raise XendError("Device creation failed")
2035 return dev_uuid
2037 def create_vif(self, xenapi_vif):
2038 """Create VIF device from the passed struct in Xen API format.
2040 @param xenapi_vif: Xen API VIF Struct.
2041 @rtype: string
2042 @return: UUID
2043 """
2044 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
2045 if not dev_uuid:
2046 raise XendError('Failed to create device')
2048 if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
2049 sxpr = self.info.device_sxpr(dev_uuid)
2050 devid = self.getDeviceController('vif').createDevice(sxpr)
2051 raise XendError("Device creation failed")
2053 return dev_uuid
2055 def create_vtpm(self, xenapi_vtpm):
2056 """Create a VTPM device from the passed struct in Xen API format.
2058 @return: uuid of the device
2059 @rtype: string
2060 """
2062 if self.state not in (DOM_STATE_HALTED,):
2063 raise VmError("Can only add vTPM to a halted domain.")
2064 if self.get_vtpms() != []:
2065 raise VmError('Domain already has a vTPM.')
2066 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
2067 if not dev_uuid:
2068 raise XendError('Failed to create device')
2070 return dev_uuid
2072 def has_device(self, dev_class, dev_uuid):
2073 return (dev_uuid in self.info['%s_refs' % dev_class.lower()])
2075 def __str__(self):
2076 return '<domain id=%s name=%s memory=%s state=%s>' % \
2077 (str(self.domid), self.info['name_label'],
2078 str(self.info['memory_static_min']), DOM_STATES[self.state])
2080 __repr__ = __str__