ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 13208:5c268a24e44b

Do not update the PV_ variables with the values outputted by the bootloader --
this gets us into all sorts of trouble when Xend is restarted and then the
domain is rebooted, because we expect to be able to handle the PV_kernel == ''
case by defaulting to pygrub.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author Ewan Mellor <ewan@xensource.com>
date Thu Dec 28 15:26:19 2006 +0000 (2006-12-28)
parents d759e9df902f
children 615109616bb6
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
54 BOOTLOADER_LOOPBACK_DEVICE = '/dev/xvdp'
56 xc = xen.lowlevel.xc.xc()
57 xroot = XendRoot.instance()
59 log = logging.getLogger("xend.XendDomainInfo")
60 #log.setLevel(logging.TRACE)
63 #
64 # There are a number of CPU-related fields:
65 #
66 # vcpus: the number of virtual CPUs this domain is configured to use.
67 # vcpu_avail: a bitmap telling the guest domain whether it may use each of
68 # its VCPUs. This is translated to
69 # <dompath>/cpu/<id>/availability = {online,offline} for use
70 # by the guest domain.
71 # cpumap: a list of bitmaps, one for each VCPU, giving the physical
72 # CPUs that that VCPU may use.
73 # cpu: a configuration setting requesting that VCPU 0 is pinned to
74 # the specified physical CPU.
75 #
76 # vcpus and vcpu_avail settings persist with the VM (i.e. they are persistent
77 # across save, restore, migrate, and restart). The other settings are only
78 # specific to the domain, so are lost when the VM moves.
79 #
82 def create(config):
83 """Creates and start a VM using the supplied configuration.
85 @param config: A configuration object involving lists of tuples.
86 @type config: list of lists, eg ['vm', ['image', 'xen.gz']]
88 @rtype: XendDomainInfo
89 @return: An up and running XendDomainInfo instance
90 @raise VmError: Invalid configuration or failure to start.
91 """
93 log.debug("XendDomainInfo.create(%s)", scrub_password(config))
94 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config))
95 try:
96 vm.start()
97 except:
98 log.exception('Domain construction failed')
99 vm.destroy()
100 raise
102 return vm
104 def create_from_dict(config_dict):
105 """Creates and start a VM using the supplied configuration.
107 @param config_dict: An configuration dictionary.
109 @rtype: XendDomainInfo
110 @return: An up and running XendDomainInfo instance
111 @raise VmError: Invalid configuration or failure to start.
112 """
114 log.debug("XendDomainInfo.create_from_dict(%s)",
115 scrub_password(config_dict))
116 vm = XendDomainInfo(XendConfig.XendConfig(xapi = config_dict))
117 try:
118 vm.start()
119 except:
120 log.exception('Domain construction failed')
121 vm.destroy()
122 raise
124 return vm
126 def recreate(info, priv):
127 """Create the VM object for an existing domain. The domain must not
128 be dying, as the paths in the store should already have been removed,
129 and asking us to recreate them causes problems.
131 @param xeninfo: Parsed configuration
132 @type xeninfo: Dictionary
133 @param priv: Is a privileged domain (Dom 0)
134 @type priv: bool
136 @rtype: XendDomainInfo
137 @return: A up and running XendDomainInfo instance
138 @raise VmError: Invalid configuration.
139 @raise XendError: Errors with configuration.
140 """
142 log.debug("XendDomainInfo.recreate(%s)", scrub_password(info))
144 assert not info['dying']
146 xeninfo = XendConfig.XendConfig(dominfo = info)
147 domid = xeninfo['domid']
148 uuid1 = uuid.fromString(xeninfo['uuid'])
149 needs_reinitialising = False
151 dompath = GetDomainPath(domid)
152 if not dompath:
153 raise XendError('No domain path in store for existing '
154 'domain %d' % domid)
156 log.info("Recreating domain %d, UUID %s. at %s" %
157 (domid, xeninfo['uuid'], dompath))
159 # need to verify the path and uuid if not Domain-0
160 # if the required uuid and vm aren't set, then that means
161 # we need to recreate the dom with our own values
162 #
163 # NOTE: this is probably not desirable, really we should just
164 # abort or ignore, but there may be cases where xenstore's
165 # entry disappears (eg. xenstore-rm /)
166 #
167 try:
168 vmpath = xstransact.Read(dompath, "vm")
169 if not vmpath:
170 log.warn('/local/domain/%d/vm is missing. recreate is '
171 'confused, trying our best to recover' % domid)
172 needs_reinitialising = True
173 raise XendError('reinit')
175 uuid2_str = xstransact.Read(vmpath, "uuid")
176 if not uuid2_str:
177 log.warn('%s/uuid/ is missing. recreate is confused, '
178 'trying our best to recover' % vmpath)
179 needs_reinitialising = True
180 raise XendError('reinit')
182 uuid2 = uuid.fromString(uuid2_str)
183 if uuid1 != uuid2:
184 log.warn('UUID in /vm does not match the UUID in /dom/%d.'
185 'Trying out best to recover' % domid)
186 needs_reinitialising = True
187 except XendError:
188 pass # our best shot at 'goto' in python :)
190 vm = XendDomainInfo(xeninfo, domid, dompath, augment = True, priv = priv)
192 if needs_reinitialising:
193 vm._recreateDom()
194 vm._removeVm()
195 vm._storeVmDetails()
196 vm._storeDomDetails()
198 if vm.info['image']: # Only dom0 should be without an image entry when
199 # recreating, but we cope with missing ones
200 # elsewhere just in case.
201 vm.image = image.create(vm,
202 vm.info,
203 vm.info['image'],
204 vm.info['devices'])
205 vm.image.recreate()
207 vm._registerWatches()
208 vm.refreshShutdown(xeninfo)
209 return vm
212 def restore(config):
213 """Create a domain and a VM object to do a restore.
215 @param config: Domain SXP configuration
216 @type config: list of lists. (see C{create})
218 @rtype: XendDomainInfo
219 @return: A up and running XendDomainInfo instance
220 @raise VmError: Invalid configuration or failure to start.
221 @raise XendError: Errors with configuration.
222 """
224 log.debug("XendDomainInfo.restore(%s)", scrub_password(config))
225 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config),
226 resume = True)
227 try:
228 vm.resume()
229 return vm
230 except:
231 vm.destroy()
232 raise
234 def createDormant(domconfig):
235 """Create a dormant/inactive XenDomainInfo without creating VM.
236 This is for creating instances of persistent domains that are not
237 yet start.
239 @param domconfig: Parsed configuration
240 @type domconfig: XendConfig object
242 @rtype: XendDomainInfo
243 @return: A up and running XendDomainInfo instance
244 @raise XendError: Errors with configuration.
245 """
247 log.debug("XendDomainInfo.createDormant(%s)", scrub_password(domconfig))
249 # domid does not make sense for non-running domains.
250 domconfig.pop('domid', None)
251 vm = XendDomainInfo(domconfig)
252 return vm
254 def domain_by_name(name):
255 """Get domain by name
257 @params name: Name of the domain
258 @type name: string
259 @return: XendDomainInfo or None
260 """
261 from xen.xend import XendDomain
262 return XendDomain.instance().domain_lookup_by_name_nr(name)
265 def shutdown_reason(code):
266 """Get a shutdown reason from a code.
268 @param code: shutdown code
269 @type code: int
270 @return: shutdown reason
271 @rtype: string
272 """
273 return DOMAIN_SHUTDOWN_REASONS.get(code, "?")
275 def dom_get(dom):
276 """Get info from xen for an existing domain.
278 @param dom: domain id
279 @type dom: int
280 @return: info or None
281 @rtype: dictionary
282 """
283 try:
284 domlist = xc.domain_getinfo(dom, 1)
285 if domlist and dom == domlist[0]['domid']:
286 return domlist[0]
287 except Exception, err:
288 # ignore missing domain
289 log.trace("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
290 return None
293 class XendDomainInfo:
294 """An object represents a domain.
296 @TODO: try to unify dom and domid, they mean the same thing, but
297 xc refers to it as dom, and everywhere else, including
298 xenstore it is domid. The best way is to change xc's
299 python interface.
301 @ivar info: Parsed configuration
302 @type info: dictionary
303 @ivar domid: Domain ID (if VM has started)
304 @type domid: int or None
305 @ivar vmpath: XenStore path to this VM.
306 @type vmpath: string
307 @ivar dompath: XenStore path to this Domain.
308 @type dompath: string
309 @ivar image: Reference to the VM Image.
310 @type image: xen.xend.image.ImageHandler
311 @ivar store_port: event channel to xenstored
312 @type store_port: int
313 @ivar console_port: event channel to xenconsoled
314 @type console_port: int
315 @ivar store_mfn: xenstored mfn
316 @type store_mfn: int
317 @ivar console_mfn: xenconsoled mfn
318 @type console_mfn: int
319 @ivar vmWatch: reference to a watch on the xenstored vmpath
320 @type vmWatch: xen.xend.xenstore.xswatch
321 @ivar shutdownWatch: reference to watch on the xenstored domain shutdown
322 @type shutdownWatch: xen.xend.xenstore.xswatch
323 @ivar shutdownStartTime: UNIX Time when domain started shutting down.
324 @type shutdownStartTime: float or None
325 @ivar state: Domain state
326 @type state: enum(DOM_STATE_HALTED, DOM_STATE_RUNNING, ...)
327 @ivar state_updated: lock for self.state
328 @type state_updated: threading.Condition
329 @ivar refresh_shutdown_lock: lock for polling shutdown state
330 @type refresh_shutdown_lock: threading.Condition
331 @ivar _deviceControllers: device controller cache for this domain
332 @type _deviceControllers: dict 'string' to DevControllers
333 """
335 def __init__(self, info, domid = None, dompath = None, augment = False,
336 priv = False, resume = False):
337 """Constructor for a domain
339 @param info: parsed configuration
340 @type info: dictionary
341 @keyword domid: Set initial domain id (if any)
342 @type domid: int
343 @keyword dompath: Set initial dompath (if any)
344 @type dompath: string
345 @keyword augment: Augment given info with xenstored VM info
346 @type augment: bool
347 @keyword priv: Is a privileged domain (Dom 0)
348 @type priv: bool
349 @keyword resume: Is this domain being resumed?
350 @type resume: bool
351 """
353 self.info = info
354 if domid == None:
355 self.domid = self.info.get('domid')
356 else:
357 self.domid = domid
359 #REMOVE: uuid is now generated in XendConfig
360 #if not self._infoIsSet('uuid'):
361 # self.info['uuid'] = uuid.toString(uuid.create())
363 self.vmpath = XS_VMROOT + self.info['uuid']
364 self.dompath = dompath
366 self.image = None
367 self.store_port = None
368 self.store_mfn = None
369 self.console_port = None
370 self.console_mfn = None
372 self.vmWatch = None
373 self.shutdownWatch = None
374 self.shutdownStartTime = None
375 self._resume = resume
377 self.state = DOM_STATE_HALTED
378 self.state_updated = threading.Condition()
379 self.refresh_shutdown_lock = threading.Condition()
381 self._deviceControllers = {}
383 for state in DOM_STATES_OLD:
384 self.info[state] = 0
386 if augment:
387 self._augmentInfo(priv)
389 self._checkName(self.info['name_label'])
392 #
393 # Public functions available through XMLRPC
394 #
397 def start(self, is_managed = False):
398 """Attempts to start the VM by do the appropriate
399 initialisation if it not started.
400 """
401 from xen.xend import XendDomain
403 if self.state == DOM_STATE_HALTED:
404 try:
405 self._constructDomain()
406 self._initDomain()
407 self._storeVmDetails()
408 self._storeDomDetails()
409 self._registerWatches()
410 self.refreshShutdown()
412 # save running configuration if XendDomains believe domain is
413 # persistent
414 if is_managed:
415 xendomains = XendDomain.instance()
416 xendomains.managed_config_save(self)
417 except:
418 log.exception('VM start failed')
419 self.destroy()
420 raise
421 else:
422 raise XendError('VM already running')
424 def resume(self):
425 """Resumes a domain that has come back from suspension."""
426 if self.state in (DOM_STATE_HALTED, DOM_STATE_SUSPENDED):
427 try:
428 self._constructDomain()
429 self._storeVmDetails()
430 self._createDevices()
431 self._createChannels()
432 self._storeDomDetails()
433 self._endRestore()
434 except:
435 log.exception('VM resume failed')
436 raise
437 else:
438 raise XendError('VM already running')
440 def shutdown(self, reason):
441 """Shutdown a domain by signalling this via xenstored."""
442 log.debug('XendDomainInfo.shutdown')
443 if self.state in (DOM_STATE_SHUTDOWN, DOM_STATE_HALTED,):
444 raise XendError('Domain cannot be shutdown')
446 if self.domid == 0:
447 raise XendError('Domain 0 cannot be shutdown')
449 if reason not in DOMAIN_SHUTDOWN_REASONS.values():
450 raise XendError('Invalid reason: %s' % reason)
451 self._removeVm('xend/previous_restart_time')
452 self.storeDom("control/shutdown", reason)
454 def pause(self):
455 """Pause domain
457 @raise XendError: Failed pausing a domain
458 """
459 try:
460 xc.domain_pause(self.domid)
461 self._stateSet(DOM_STATE_PAUSED)
462 except Exception, ex:
463 log.exception(ex)
464 raise XendError("Domain unable to be paused: %s" % str(ex))
466 def unpause(self):
467 """Unpause domain
469 @raise XendError: Failed unpausing a domain
470 """
471 try:
472 xc.domain_unpause(self.domid)
473 self._stateSet(DOM_STATE_RUNNING)
474 except Exception, ex:
475 log.exception(ex)
476 raise XendError("Domain unable to be unpaused: %s" % str(ex))
478 def send_sysrq(self, key):
479 """ Send a Sysrq equivalent key via xenstored."""
480 asserts.isCharConvertible(key)
481 self.storeDom("control/sysrq", '%c' % key)
483 def device_create(self, dev_config):
484 """Create a new device.
486 @param dev_config: device configuration
487 @type dev_config: SXP object (parsed config)
488 """
489 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config))
490 dev_type = sxp.name(dev_config)
491 dev_uuid = self.info.device_add(dev_type, cfg_sxp = dev_config)
492 dev_config_dict = self.info['devices'][dev_uuid][1]
493 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config_dict))
494 dev_config_dict['devid'] = devid = \
495 self._createDevice(dev_type, dev_config_dict)
496 self._waitForDevice(dev_type, devid)
497 return self.getDeviceController(dev_type).sxpr(devid)
499 def device_configure(self, dev_config, devid = None):
500 """Configure an existing device.
502 @param dev_config: device configuration
503 @type dev_config: SXP object (parsed config)
504 @param devid: device id
505 @type devid: int
506 @return: Returns True if successfully updated device
507 @rtype: boolean
508 """
509 deviceClass = sxp.name(dev_config)
511 # look up uuid of the device
512 dev_control = self.getDeviceController(deviceClass)
513 dev_sxpr = dev_control.sxpr(devid)
514 dev_uuid = sxp.child_value(sxpr, 'uuid')
515 if not dev_uuid:
516 return False
518 self.info.device_update(dev_uuid, dev_config)
519 dev_config_dict = self.info['devices'].get(dev_uuid)
520 if dev_config_dict:
521 dev_control.reconfigureDevice(devid, dev_config_dict[1])
522 return True
524 def waitForDevices(self):
525 """Wait for this domain's configured devices to connect.
527 @raise VmError: if any device fails to initialise.
528 """
529 for devclass in XendDevices.valid_devices():
530 self.getDeviceController(devclass).waitForDevices()
532 def destroyDevice(self, deviceClass, devid, force = False):
533 try:
534 devid = int(devid)
535 except ValueError:
536 # devid is not a number, let's search for it in xenstore.
537 devicePath = '%s/device/%s' % (self.dompath, deviceClass)
538 for entry in xstransact.List(devicePath):
539 backend = xstransact.Read('%s/%s' % (devicePath, entry),
540 "backend")
541 devName = xstransact.Read(backend, "dev")
542 if devName == devid:
543 # We found the integer matching our devid, use it instead
544 devid = entry
545 break
547 return self.getDeviceController(deviceClass).destroyDevice(devid, force)
551 def getDeviceSxprs(self, deviceClass):
552 if self.state == DOM_STATE_RUNNING:
553 return self.getDeviceController(deviceClass).sxprs()
554 else:
555 sxprs = []
556 dev_num = 0
557 for dev_type, dev_info in self.info.all_devices_sxpr():
558 if dev_type == deviceClass:
559 sxprs.append([dev_num, dev_info])
560 dev_num += 1
561 return 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_label'], self.domid, target)
571 if target <= 0:
572 raise XendError('Invalid memory size')
574 self.info['memory_static_min'] = target
575 if self.domid >= 0:
576 self.storeVm("memory", target)
577 self.storeDom("memory/target", target << 10)
578 else:
579 self.info['memory_dynamic_min'] = target
580 xen.xend.XendDomain.instance().managed_config_save(self)
582 def setMemoryMaximum(self, limit):
583 """Set the maximum memory limit of this domain
584 @param limit: In MiB.
585 """
586 log.debug("Setting memory maximum of domain %s (%d) to %d MiB.",
587 self.info['name_label'], self.domid, limit)
589 if limit <= 0:
590 raise XendError('Invalid memory size')
592 self.info['memory_static_max'] = limit
593 if self.domid >= 0:
594 maxmem = int(limit) * 1024
595 try:
596 return xc.domain_setmaxmem(self.domid, maxmem)
597 except Exception, ex:
598 raise XendError(str(ex))
599 else:
600 self.info['memory_dynamic_max'] = limit
601 xen.xend.XendDomain.instance().managed_config_save(self)
604 def getVCPUInfo(self):
605 try:
606 # We include the domain name and ID, to help xm.
607 sxpr = ['domain',
608 ['domid', self.domid],
609 ['name', self.info['name_label']],
610 ['vcpu_count', self.info['vcpus_number']]]
612 for i in range(0, self.info['max_vcpu_id']+1):
613 info = xc.vcpu_getinfo(self.domid, i)
615 sxpr.append(['vcpu',
616 ['number', i],
617 ['online', info['online']],
618 ['blocked', info['blocked']],
619 ['running', info['running']],
620 ['cpu_time', info['cpu_time'] / 1e9],
621 ['cpu', info['cpu']],
622 ['cpumap', info['cpumap']]])
624 return sxpr
626 except RuntimeError, exn:
627 raise XendError(str(exn))
629 #
630 # internal functions ... TODO: re-categorised
631 #
633 def _augmentInfo(self, priv):
634 """Augment self.info, as given to us through L{recreate}, with
635 values taken from the store. This recovers those values known
636 to xend but not to the hypervisor.
637 """
638 augment_entries = XendConfig.LEGACY_XENSTORE_VM_PARAMS[:]
639 if priv:
640 augment_entries.remove('memory')
641 augment_entries.remove('maxmem')
642 augment_entries.remove('vcpus')
643 augment_entries.remove('vcpu_avail')
645 vm_config = self._readVMDetails([(k, XendConfig.LEGACY_CFG_TYPES[k])
646 for k in augment_entries])
648 # make returned lists into a dictionary
649 vm_config = dict(zip(augment_entries, vm_config))
651 for arg in augment_entries:
652 xapicfg = arg
653 val = vm_config[arg]
654 if val != None:
655 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
656 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
657 self.info[xapiarg] = val
658 else:
659 self.info[arg] = val
661 # For dom0, we ignore any stored value for the vcpus fields, and
662 # read the current value from Xen instead. This allows boot-time
663 # settings to take precedence over any entries in the store.
664 if priv:
665 xeninfo = dom_get(self.domid)
666 self.info['vcpus_number'] = xeninfo['online_vcpus']
667 self.info['vcpu_avail'] = (1 << xeninfo['online_vcpus']) - 1
669 # read image value
670 image_sxp = self._readVm('image')
671 if image_sxp:
672 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
674 # read devices
675 devices = []
676 for devclass in XendDevices.valid_devices():
677 devconfig = self.getDeviceController(devclass).configurations()
678 if devconfig:
679 devices.extend(devconfig)
681 if not self.info['devices'] and devices is not None:
682 for device in devices:
683 self.info.device_add(device[0], cfg_sxp = device)
685 #
686 # Function to update xenstore /vm/*
687 #
689 def _readVm(self, *args):
690 return xstransact.Read(self.vmpath, *args)
692 def _writeVm(self, *args):
693 return xstransact.Write(self.vmpath, *args)
695 def _removeVm(self, *args):
696 return xstransact.Remove(self.vmpath, *args)
698 def _gatherVm(self, *args):
699 return xstransact.Gather(self.vmpath, *args)
701 def storeVm(self, *args):
702 return xstransact.Store(self.vmpath, *args)
704 #
705 # Function to update xenstore /dom/*
706 #
708 def readDom(self, *args):
709 return xstransact.Read(self.dompath, *args)
711 def gatherDom(self, *args):
712 return xstransact.Gather(self.dompath, *args)
714 def _writeDom(self, *args):
715 return xstransact.Write(self.dompath, *args)
717 def _removeDom(self, *args):
718 return xstransact.Remove(self.dompath, *args)
720 def storeDom(self, *args):
721 return xstransact.Store(self.dompath, *args)
723 def _recreateDom(self):
724 complete(self.dompath, lambda t: self._recreateDomFunc(t))
726 def _recreateDomFunc(self, t):
727 t.remove()
728 t.mkdir()
729 t.set_permissions({'dom' : self.domid})
730 t.write('vm', self.vmpath)
732 def _storeDomDetails(self):
733 to_store = {
734 'domid': str(self.domid),
735 'vm': self.vmpath,
736 'name': self.info['name_label'],
737 'console/limit': str(xroot.get_console_limit() * 1024),
738 'memory/target': str(self.info['memory_static_min'] * 1024)
739 }
741 def f(n, v):
742 if v is not None:
743 to_store[n] = str(v)
745 f('console/port', self.console_port)
746 f('console/ring-ref', self.console_mfn)
747 f('store/port', self.store_port)
748 f('store/ring-ref', self.store_mfn)
750 to_store.update(self._vcpuDomDetails())
752 log.debug("Storing domain details: %s", scrub_password(to_store))
754 self._writeDom(to_store)
756 def _vcpuDomDetails(self):
757 def availability(n):
758 if self.info['vcpu_avail'] & (1 << n):
759 return 'online'
760 else:
761 return 'offline'
763 result = {}
764 for v in range(0, self.info['vcpus_number']):
765 result["cpu/%d/availability" % v] = availability(v)
766 return result
768 #
769 # xenstore watches
770 #
772 def _registerWatches(self):
773 """Register a watch on this VM's entries in the store, and the
774 domain's control/shutdown node, so that when they are changed
775 externally, we keep up to date. This should only be called by {@link
776 #create}, {@link #recreate}, or {@link #restore}, once the domain's
777 details have been written, but before the new instance is returned."""
778 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
779 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
780 self._handleShutdownWatch)
782 def _storeChanged(self, _):
783 log.trace("XendDomainInfo.storeChanged");
785 changed = False
787 # Check whether values in the configuration have
788 # changed in Xenstore.
790 cfg_vm = ['name', 'on_poweroff', 'on_reboot', 'on_crash']
792 vm_details = self._readVMDetails([(k,XendConfig.LEGACY_CFG_TYPES[k])
793 for k in cfg_vm])
795 # convert two lists into a python dictionary
796 vm_details = dict(zip(cfg_vm, vm_details))
798 for arg, val in vm_details.items():
799 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
800 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
801 if val != None and val != self.info[xapiarg]:
802 self.info[xapiarg] = val
803 changed= True
805 # Check whether image definition has been updated
806 image_sxp = self._readVm('image')
807 if image_sxp and image_sxp != self.info.image_sxpr():
808 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
809 changed = True
811 if changed:
812 # Update the domain section of the store, as this contains some
813 # parameters derived from the VM configuration.
814 self._storeDomDetails()
816 return 1
818 def _handleShutdownWatch(self, _):
819 log.debug('XendDomainInfo.handleShutdownWatch')
821 reason = self.readDom('control/shutdown')
823 if reason and reason != 'suspend':
824 sst = self.readDom('xend/shutdown_start_time')
825 now = time.time()
826 if sst:
827 self.shutdownStartTime = float(sst)
828 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
829 else:
830 self.shutdownStartTime = now
831 self.storeDom('xend/shutdown_start_time', now)
832 timeout = SHUTDOWN_TIMEOUT
834 log.trace(
835 "Scheduling refreshShutdown on domain %d in %ds.",
836 self.domid, timeout)
837 threading.Timer(timeout, self.refreshShutdown).start()
839 return True
842 #
843 # Public Attributes for the VM
844 #
847 def getDomid(self):
848 return self.domid
850 def setName(self, name):
851 self._checkName(name)
852 self.info['name_label'] = name
853 self.storeVm("name", name)
855 def getName(self):
856 return self.info['name_label']
858 def getDomainPath(self):
859 return self.dompath
861 def getShutdownReason(self):
862 return self.readDom('control/shutdown')
864 def getStorePort(self):
865 """For use only by image.py and XendCheckpoint.py."""
866 return self.store_port
868 def getConsolePort(self):
869 """For use only by image.py and XendCheckpoint.py"""
870 return self.console_port
872 def getFeatures(self):
873 """For use only by image.py."""
874 return self.info['features']
876 def getVCpuCount(self):
877 return self.info['vcpus_number']
879 def setVCpuCount(self, vcpus):
880 self.info['vcpu_avail'] = (1 << vcpus) - 1
881 if self.domid >= 0:
882 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
883 # update dom differently depending on whether we are adjusting
884 # vcpu number up or down, otherwise _vcpuDomDetails does not
885 # disable the vcpus
886 if self.info['vcpus_number'] > vcpus:
887 # decreasing
888 self._writeDom(self._vcpuDomDetails())
889 self.info['vcpus_number'] = vcpus
890 else:
891 # same or increasing
892 self.info['vcpus_number'] = vcpus
893 self._writeDom(self._vcpuDomDetails())
894 else:
895 self.info['vcpus_number'] = vcpus
896 self.info['online_vcpus'] = vcpus
897 xen.xend.XendDomain.instance().managed_config_save(self)
899 def getLabel(self):
900 return security.get_security_info(self.info, 'label')
902 def getMemoryTarget(self):
903 """Get this domain's target memory size, in KB."""
904 return self.info['memory_static_min'] * 1024
906 def getMemoryMaximum(self):
907 """Get this domain's maximum memory size, in KB."""
908 return self.info['memory_static_max'] * 1024
910 def getResume(self):
911 return str(self._resume)
913 def getCap(self):
914 return self.info.get('cpu_cap', 0)
916 def getWeight(self):
917 return self.info['cpu_weight']
919 def setResume(self, state):
920 self._resume = state
922 def getRestartCount(self):
923 return self._readVm('xend/restart_count')
925 def refreshShutdown(self, xeninfo = None):
926 """ Checks the domain for whether a shutdown is required.
928 Called from XendDomainInfo and also image.py for HVM images.
929 """
931 # If set at the end of this method, a restart is required, with the
932 # given reason. This restart has to be done out of the scope of
933 # refresh_shutdown_lock.
934 restart_reason = None
936 self.refresh_shutdown_lock.acquire()
937 try:
938 if xeninfo is None:
939 xeninfo = dom_get(self.domid)
940 if xeninfo is None:
941 # The domain no longer exists. This will occur if we have
942 # scheduled a timer to check for shutdown timeouts and the
943 # shutdown succeeded. It will also occur if someone
944 # destroys a domain beneath us. We clean up the domain,
945 # just in case, but we can't clean up the VM, because that
946 # VM may have migrated to a different domain on this
947 # machine.
948 self.cleanupDomain()
949 self._stateSet(DOM_STATE_HALTED)
950 return
952 if xeninfo['dying']:
953 # Dying means that a domain has been destroyed, but has not
954 # yet been cleaned up by Xen. This state could persist
955 # indefinitely if, for example, another domain has some of its
956 # pages mapped. We might like to diagnose this problem in the
957 # future, but for now all we do is make sure that it's not us
958 # holding the pages, by calling cleanupDomain. We can't
959 # clean up the VM, as above.
960 self.cleanupDomain()
961 self._stateSet(DOM_STATE_SHUTDOWN)
962 return
964 elif xeninfo['crashed']:
965 if self.readDom('xend/shutdown_completed'):
966 # We've seen this shutdown already, but we are preserving
967 # the domain for debugging. Leave it alone.
968 return
970 log.warn('Domain has crashed: name=%s id=%d.',
971 self.info['name_label'], self.domid)
973 if xroot.get_enable_dump():
974 self.dumpCore()
976 restart_reason = 'crash'
977 self._stateSet(DOM_STATE_HALTED)
979 elif xeninfo['shutdown']:
980 self._stateSet(DOM_STATE_SHUTDOWN)
981 if self.readDom('xend/shutdown_completed'):
982 # We've seen this shutdown already, but we are preserving
983 # the domain for debugging. Leave it alone.
984 return
986 else:
987 reason = shutdown_reason(xeninfo['shutdown_reason'])
989 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
990 self.info['name_label'], self.domid, reason)
992 self._clearRestart()
994 if reason == 'suspend':
995 self._stateSet(DOM_STATE_SUSPENDED)
996 # Don't destroy the domain. XendCheckpoint will do
997 # this once it has finished. However, stop watching
998 # the VM path now, otherwise we will end up with one
999 # watch for the old domain, and one for the new.
1000 self._unwatchVm()
1001 elif reason in ('poweroff', 'reboot'):
1002 restart_reason = reason
1003 else:
1004 self.destroy()
1006 elif self.dompath is None:
1007 # We have yet to manage to call introduceDomain on this
1008 # domain. This can happen if a restore is in progress, or has
1009 # failed. Ignore this domain.
1010 pass
1011 else:
1012 # Domain is alive. If we are shutting it down, then check
1013 # the timeout on that, and destroy it if necessary.
1014 if xeninfo['paused']:
1015 self._stateSet(DOM_STATE_PAUSED)
1016 else:
1017 self._stateSet(DOM_STATE_RUNNING)
1019 if self.shutdownStartTime:
1020 timeout = (SHUTDOWN_TIMEOUT - time.time() +
1021 self.shutdownStartTime)
1022 if timeout < 0:
1023 log.info(
1024 "Domain shutdown timeout expired: name=%s id=%s",
1025 self.info['name_label'], self.domid)
1026 self.destroy()
1027 finally:
1028 self.refresh_shutdown_lock.release()
1030 if restart_reason:
1031 threading.Thread(target = self._maybeRestart,
1032 args = (restart_reason,)).start()
1036 # Restart functions - handling whether we come back up on shutdown.
1039 def _clearRestart(self):
1040 self._removeDom("xend/shutdown_start_time")
1043 def _maybeRestart(self, reason):
1044 # Dispatch to the correct method based upon the configured on_{reason}
1045 # behaviour.
1046 actions = {"destroy" : self.destroy,
1047 "restart" : self._restart,
1048 "preserve" : self._preserve,
1049 "rename-restart" : self._renameRestart}
1051 action_conf = {
1052 'poweroff': 'actions_after_shutdown',
1053 'reboot': 'actions_after_reboot',
1054 'crash': 'actions_after_crash',
1057 action_target = self.info.get(action_conf.get(reason))
1058 func = actions.get(action_target, None)
1059 if func and callable(func):
1060 func()
1061 else:
1062 self.destroy() # default to destroy
1064 def _renameRestart(self):
1065 self._restart(True)
1067 def _restart(self, rename = False):
1068 """Restart the domain after it has exited.
1070 @param rename True if the old domain is to be renamed and preserved,
1071 False if it is to be destroyed.
1072 """
1073 from xen.xend import XendDomain
1075 if self._readVm(RESTART_IN_PROGRESS):
1076 log.error('Xend failed during restart of domain %s. '
1077 'Refusing to restart to avoid loops.',
1078 str(self.domid))
1079 self.destroy()
1080 return
1082 old_domid = self.domid
1083 self._writeVm(RESTART_IN_PROGRESS, 'True')
1085 now = time.time()
1086 rst = self._readVm('xend/previous_restart_time')
1087 if rst:
1088 rst = float(rst)
1089 timeout = now - rst
1090 if timeout < MINIMUM_RESTART_TIME:
1091 log.error(
1092 'VM %s restarting too fast (%f seconds since the last '
1093 'restart). Refusing to restart to avoid loops.',
1094 self.info['name_label'], timeout)
1095 self.destroy()
1096 return
1098 self._writeVm('xend/previous_restart_time', str(now))
1100 try:
1101 if rename:
1102 self._preserveForRestart()
1103 else:
1104 self._unwatchVm()
1105 self.destroyDomain()
1107 # new_dom's VM will be the same as this domain's VM, except where
1108 # the rename flag has instructed us to call preserveForRestart.
1109 # In that case, it is important that we remove the
1110 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1111 # once the new one is available.
1113 new_dom = None
1114 try:
1115 new_dom = XendDomain.instance().domain_create_from_dict(
1116 self.info)
1117 new_dom.unpause()
1118 rst_cnt = self._readVm('xend/restart_count')
1119 rst_cnt = int(rst_cnt) + 1
1120 self._writeVm('xend/restart_count', str(rst_cnt))
1121 new_dom._removeVm(RESTART_IN_PROGRESS)
1122 except:
1123 if new_dom:
1124 new_dom._removeVm(RESTART_IN_PROGRESS)
1125 new_dom.destroy()
1126 else:
1127 self._removeVm(RESTART_IN_PROGRESS)
1128 raise
1129 except:
1130 log.exception('Failed to restart domain %s.', str(old_domid))
1132 def _preserveForRestart(self):
1133 """Preserve a domain that has been shut down, by giving it a new UUID,
1134 cloning the VM details, and giving it a new name. This allows us to
1135 keep this domain for debugging, but restart a new one in its place
1136 preserving the restart semantics (name and UUID preserved).
1137 """
1139 new_uuid = uuid.createString()
1140 new_name = 'Domain-%s' % new_uuid
1141 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1142 self.info['name_label'], self.domid, self.info['uuid'],
1143 new_name, new_uuid)
1144 self._unwatchVm()
1145 self._releaseDevices()
1146 self.info['name_label'] = new_name
1147 self.info['uuid'] = new_uuid
1148 self.vmpath = XS_VMROOT + new_uuid
1149 self._storeVmDetails()
1150 self._preserve()
1153 def _preserve(self):
1154 log.info("Preserving dead domain %s (%d).", self.info['name_label'],
1155 self.domid)
1156 self._unwatchVm()
1157 self.storeDom('xend/shutdown_completed', 'True')
1158 self._stateSet(DOM_STATE_HALTED)
1161 # Debugging ..
1164 def dumpCore(self, corefile = None):
1165 """Create a core dump for this domain. Nothrow guarantee."""
1167 try:
1168 if not corefile:
1169 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1170 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1171 self.info['name_label'], self.domid)
1173 if os.path.isdir(corefile):
1174 raise XendError("Cannot dump core in a directory: %s" %
1175 corefile)
1177 xc.domain_dumpcore(self.domid, corefile)
1178 except RuntimeError, ex:
1179 corefile_incomp = corefile+'-incomplete'
1180 os.rename(corefile, corefile_incomp)
1181 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1182 self.domid, self.info['name_label'])
1183 raise XendError("Failed to dump core: %s" % str(ex))
1186 # Device creation/deletion functions
1189 def _createDevice(self, deviceClass, devConfig):
1190 return self.getDeviceController(deviceClass).createDevice(devConfig)
1192 def _waitForDevice(self, deviceClass, devid):
1193 return self.getDeviceController(deviceClass).waitForDevice(devid)
1195 def _waitForDeviceUUID(self, dev_uuid):
1196 deviceClass, config = self.info['devices'].get(dev_uuid)
1197 self._waitForDevice(deviceClass, config['devid'])
1199 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1200 return self.getDeviceController(deviceClass).reconfigureDevice(
1201 devid, devconfig)
1203 def _createDevices(self):
1204 """Create the devices for a vm.
1206 @raise: VmError for invalid devices
1207 """
1208 for (devclass, config) in self.info.get('devices', {}).values():
1209 if devclass in XendDevices.valid_devices():
1210 log.info("createDevice: %s : %s" % (devclass, scrub_password(config)))
1211 self._createDevice(devclass, config)
1213 if self.image:
1214 self.image.createDeviceModel()
1216 def _releaseDevices(self):
1217 """Release all domain's devices. Nothrow guarantee."""
1219 while True:
1220 t = xstransact("%s/device" % self.dompath)
1221 for devclass in XendDevices.valid_devices():
1222 for dev in t.list(devclass):
1223 try:
1224 t.remove(dev)
1225 except:
1226 # Log and swallow any exceptions in removal --
1227 # there's nothing more we can do.
1228 log.exception(
1229 "Device release failed: %s; %s; %s",
1230 self.info['name_label'], devclass, dev)
1231 if t.commit():
1232 break
1234 def getDeviceController(self, name):
1235 """Get the device controller for this domain, and if it
1236 doesn't exist, create it.
1238 @param name: device class name
1239 @type name: string
1240 @rtype: subclass of DevController
1241 """
1242 if name not in self._deviceControllers:
1243 devController = XendDevices.make_controller(name, self)
1244 if not devController:
1245 raise XendError("Unknown device type: %s" % name)
1246 self._deviceControllers[name] = devController
1248 return self._deviceControllers[name]
1251 # Migration functions (public)
1254 def testMigrateDevices(self, network, dst):
1255 """ Notify all device about intention of migration
1256 @raise: XendError for a device that cannot be migrated
1257 """
1258 for (n, c) in self.info.all_devices_sxpr():
1259 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1260 if rc != 0:
1261 raise XendError("Device of type '%s' refuses migration." % n)
1263 def migrateDevices(self, network, dst, step, domName=''):
1264 """Notify the devices about migration
1265 """
1266 ctr = 0
1267 try:
1268 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1269 self.migrateDevice(dev_type, dev_conf, network, dst,
1270 step, domName)
1271 ctr = ctr + 1
1272 except:
1273 for dev_type, dev_conf in self.info.all_devices_sxpr():
1274 if ctr == 0:
1275 step = step - 1
1276 ctr = ctr - 1
1277 self._recoverMigrateDevice(dev_type, dev_conf, network,
1278 dst, step, domName)
1279 raise
1281 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1282 step, domName=''):
1283 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1284 network, dst, step, domName)
1286 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1287 dst, step, domName=''):
1288 return self.getDeviceController(deviceClass).recover_migrate(
1289 deviceConfig, network, dst, step, domName)
1292 ## private:
1294 def _constructDomain(self):
1295 """Construct the domain.
1297 @raise: VmError on error
1298 """
1300 log.debug('XendDomainInfo.constructDomain')
1302 self.shutdownStartTime = None
1304 image_cfg = self.info.get('image', {})
1305 hvm = image_cfg.has_key('hvm')
1307 if hvm:
1308 info = xc.xeninfo()
1309 if 'hvm' not in info['xen_caps']:
1310 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1311 "supported by your CPU and enabled in your "
1312 "BIOS?")
1314 self.domid = xc.domain_create(
1315 domid = 0,
1316 ssidref = security.get_security_info(self.info, 'ssidref'),
1317 handle = uuid.fromString(self.info['uuid']),
1318 hvm = int(hvm))
1320 if self.domid < 0:
1321 raise VmError('Creating domain failed: name=%s' %
1322 self.info['name_label'])
1324 self.dompath = GetDomainPath(self.domid)
1326 self._recreateDom()
1328 # Set maximum number of vcpus in domain
1329 xc.domain_max_vcpus(self.domid, int(self.info['vcpus_number']))
1332 def _introduceDomain(self):
1333 assert self.domid is not None
1334 assert self.store_mfn is not None
1335 assert self.store_port is not None
1337 try:
1338 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1339 except RuntimeError, exn:
1340 raise XendError(str(exn))
1343 def _initDomain(self):
1344 log.debug('XendDomainInfo.initDomain: %s %s',
1345 self.domid,
1346 self.info['cpu_weight'])
1348 self._configureBootloader()
1350 if not self._infoIsSet('image'):
1351 raise VmError('Missing image in configuration')
1353 try:
1354 self.image = image.create(self,
1355 self.info,
1356 self.info['image'],
1357 self.info['devices'])
1359 localtime = self.info.get('localtime', False)
1360 if localtime:
1361 xc.domain_set_time_offset(self.domid)
1363 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1365 # repin domain vcpus if a restricted cpus list is provided
1366 # this is done prior to memory allocation to aide in memory
1367 # distribution for NUMA systems.
1368 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1369 for v in range(0, self.info['max_vcpu_id']+1):
1370 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1372 # Use architecture- and image-specific calculations to determine
1373 # the various headrooms necessary, given the raw configured
1374 # values. maxmem, memory, and shadow are all in KiB.
1375 memory = self.image.getRequiredAvailableMemory(
1376 self.info['memory_static_min'] * 1024)
1377 maxmem = self.image.getRequiredAvailableMemory(
1378 self.info['memory_static_max'] * 1024)
1379 shadow = self.image.getRequiredShadowMemory(
1380 self.info['shadow_memory'] * 1024,
1381 self.info['memory_static_max'] * 1024)
1383 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1384 # takes MiB and we must not round down and end up under-providing.
1385 shadow = ((shadow + 1023) / 1024) * 1024
1387 # set memory limit
1388 xc.domain_setmaxmem(self.domid, maxmem)
1390 # Make sure there's enough RAM available for the domain
1391 balloon.free(memory + shadow)
1393 # Set up the shadow memory
1394 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1395 self.info['shadow_memory'] = shadow_cur
1397 self._createChannels()
1399 channel_details = self.image.createImage()
1401 self.store_mfn = channel_details['store_mfn']
1402 if 'console_mfn' in channel_details:
1403 self.console_mfn = channel_details['console_mfn']
1405 self._introduceDomain()
1407 self._createDevices()
1409 self.image.cleanupBootloading()
1411 self.info['start_time'] = time.time()
1413 self._stateSet(DOM_STATE_RUNNING)
1414 except RuntimeError, exn:
1415 log.exception("XendDomainInfo.initDomain: exception occurred")
1416 self.image.cleanupBootloading()
1417 raise VmError(str(exn))
1420 def cleanupDomain(self):
1421 """Cleanup domain resources; release devices. Idempotent. Nothrow
1422 guarantee."""
1424 self.refresh_shutdown_lock.acquire()
1425 try:
1426 self.unwatchShutdown()
1427 self._releaseDevices()
1429 if self.image:
1430 try:
1431 self.image.destroy()
1432 except:
1433 log.exception(
1434 "XendDomainInfo.cleanup: image.destroy() failed.")
1435 self.image = None
1437 try:
1438 self._removeDom()
1439 except:
1440 log.exception("Removing domain path failed.")
1442 self._stateSet(DOM_STATE_HALTED)
1443 finally:
1444 self.refresh_shutdown_lock.release()
1447 def unwatchShutdown(self):
1448 """Remove the watch on the domain's control/shutdown node, if any.
1449 Idempotent. Nothrow guarantee. Expects to be protected by the
1450 refresh_shutdown_lock."""
1452 try:
1453 try:
1454 if self.shutdownWatch:
1455 self.shutdownWatch.unwatch()
1456 finally:
1457 self.shutdownWatch = None
1458 except:
1459 log.exception("Unwatching control/shutdown failed.")
1461 def waitForShutdown(self):
1462 self.state_updated.acquire()
1463 try:
1464 while self.state in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1465 self.state_updated.wait()
1466 finally:
1467 self.state_updated.release()
1471 # TODO: recategorise - called from XendCheckpoint
1474 def completeRestore(self, store_mfn, console_mfn):
1476 log.debug("XendDomainInfo.completeRestore")
1478 self.store_mfn = store_mfn
1479 self.console_mfn = console_mfn
1481 self._introduceDomain()
1482 self._storeDomDetails()
1483 self._registerWatches()
1484 self.refreshShutdown()
1486 log.debug("XendDomainInfo.completeRestore done")
1489 def _endRestore(self):
1490 self.setResume(False)
1493 # VM Destroy
1496 def destroy(self):
1497 """Cleanup VM and destroy domain. Nothrow guarantee."""
1499 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1501 self._cleanupVm()
1502 if self.dompath is not None:
1503 self.destroyDomain()
1506 def destroyDomain(self):
1507 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1509 try:
1510 if self.domid is not None:
1511 xc.domain_destroy(self.domid)
1512 self.domid = None
1513 for state in DOM_STATES_OLD:
1514 self.info[state] = 0
1515 except:
1516 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1518 self.cleanupDomain()
1522 # Channels for xenstore and console
1525 def _createChannels(self):
1526 """Create the channels to the domain.
1527 """
1528 self.store_port = self._createChannel()
1529 self.console_port = self._createChannel()
1532 def _createChannel(self):
1533 """Create an event channel to the domain.
1534 """
1535 try:
1536 return xc.evtchn_alloc_unbound(domid=self.domid, remote_dom=0)
1537 except:
1538 log.exception("Exception in alloc_unbound(%d)", self.domid)
1539 raise
1542 # Bootloader configuration
1545 def _configureBootloader(self):
1546 """Run the bootloader if we're configured to do so."""
1548 blexec = self.info['PV_bootloader']
1549 bootloader_args = self.info['PV_bootloader_args']
1550 kernel = self.info['PV_kernel']
1551 ramdisk = self.info['PV_ramdisk']
1552 args = self.info['PV_args']
1553 boot = self.info['HVM_boot']
1555 if boot:
1556 # HVM booting.
1557 self.info['image']['type'] = 'hvm'
1558 if not 'devices' in self.info['image']:
1559 self.info['image']['devices'] = {}
1560 self.info['image']['devices']['boot'] = boot
1561 elif not blexec and kernel:
1562 # Boot from dom0. Nothing left to do -- the kernel and ramdisk
1563 # will be picked up by image.py.
1564 pass
1565 else:
1566 # Boot using bootloader
1567 if not blexec or blexec == 'pygrub':
1568 blexec = '/usr/bin/pygrub'
1570 blcfg = None
1571 for (devtype, devinfo) in self.info.all_devices_sxpr():
1572 if not devtype or not devinfo or devtype not in ('vbd', 'tap'):
1573 continue
1574 disk = None
1575 for param in devinfo:
1576 if param[0] == 'uname':
1577 disk = param[1]
1578 break
1580 if disk is None:
1581 continue
1582 fn = blkdev_uname_to_file(disk)
1583 mounted = devtype == 'tap' and not os.stat(fn).st_rdev
1584 if mounted:
1585 # This is a file, not a device. pygrub can cope with a
1586 # file if it's raw, but if it's QCOW or other such formats
1587 # used through blktap, then we need to mount it first.
1589 log.info("Mounting %s on %s." %
1590 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1592 vbd = {
1593 'mode': 'RO',
1594 'device': BOOTLOADER_LOOPBACK_DEVICE,
1597 from xen.xend import XendDomain
1598 dom0 = XendDomain.instance().privilegedDomain()
1599 dom0._waitForDeviceUUID(dom0.create_vbd_with_vdi(vbd, fn))
1600 fn = BOOTLOADER_LOOPBACK_DEVICE
1602 try:
1603 blcfg = bootloader(blexec, fn, True,
1604 bootloader_args, kernel, ramdisk, args)
1605 finally:
1606 if mounted:
1607 log.info("Unmounting %s from %s." %
1608 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1610 dom0.destroyDevice('tap', '/dev/xvdp')
1612 break
1614 if blcfg is None:
1615 msg = "Had a bootloader specified, but can't find disk"
1616 log.error(msg)
1617 raise VmError(msg)
1619 self.info.update_with_image_sxp(blcfg, True)
1623 # VM Functions
1626 def _readVMDetails(self, params):
1627 """Read the specified parameters from the store.
1628 """
1629 try:
1630 return self._gatherVm(*params)
1631 except ValueError:
1632 # One of the int/float entries in params has a corresponding store
1633 # entry that is invalid. We recover, because older versions of
1634 # Xend may have put the entry there (memory/target, for example),
1635 # but this is in general a bad situation to have reached.
1636 log.exception(
1637 "Store corrupted at %s! Domain %d's configuration may be "
1638 "affected.", self.vmpath, self.domid)
1639 return []
1641 def _cleanupVm(self):
1642 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1644 self._unwatchVm()
1646 try:
1647 self._removeVm()
1648 except:
1649 log.exception("Removing VM path failed.")
1652 def checkLiveMigrateMemory(self):
1653 """ Make sure there's enough memory to migrate this domain """
1654 overhead_kb = 0
1655 if arch.type == "x86":
1656 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1657 # the minimum that Xen would allocate if no value were given.
1658 overhead_kb = self.info['vcpus_number'] * 1024 + \
1659 self.info['memory_static_max'] * 4
1660 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1661 # The domain might already have some shadow memory
1662 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1663 if overhead_kb > 0:
1664 balloon.free(overhead_kb)
1666 def _unwatchVm(self):
1667 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1668 guarantee."""
1669 try:
1670 try:
1671 if self.vmWatch:
1672 self.vmWatch.unwatch()
1673 finally:
1674 self.vmWatch = None
1675 except:
1676 log.exception("Unwatching VM path failed.")
1678 def testDeviceComplete(self):
1679 """ For Block IO migration safety we must ensure that
1680 the device has shutdown correctly, i.e. all blocks are
1681 flushed to disk
1682 """
1683 start = time.time()
1684 while True:
1685 test = 0
1686 diff = time.time() - start
1687 for i in self.getDeviceController('vbd').deviceIDs():
1688 test = 1
1689 log.info("Dev %s still active, looping...", i)
1690 time.sleep(0.1)
1692 if test == 0:
1693 break
1694 if diff >= MIGRATE_TIMEOUT:
1695 log.info("Dev still active but hit max loop timeout")
1696 break
1698 def _storeVmDetails(self):
1699 to_store = {}
1701 for key in XendConfig.LEGACY_XENSTORE_VM_PARAMS:
1702 info_key = XendConfig.LEGACY_CFG_TO_XENAPI_CFG.get(key, key)
1703 if self._infoIsSet(info_key):
1704 to_store[key] = str(self.info[info_key])
1706 if self.info.get('image'):
1707 image_sxpr = self.info.image_sxpr()
1708 if image_sxpr:
1709 to_store['image'] = sxp.to_string(image_sxpr)
1711 if self._infoIsSet('security'):
1712 secinfo = self.info['security']
1713 to_store['security'] = sxp.to_string(secinfo)
1714 for idx in range(0, len(secinfo)):
1715 if secinfo[idx][0] == 'access_control':
1716 to_store['security/access_control'] = sxp.to_string(
1717 [secinfo[idx][1], secinfo[idx][2]])
1718 for aidx in range(1, len(secinfo[idx])):
1719 if secinfo[idx][aidx][0] == 'label':
1720 to_store['security/access_control/label'] = \
1721 secinfo[idx][aidx][1]
1722 if secinfo[idx][aidx][0] == 'policy':
1723 to_store['security/access_control/policy'] = \
1724 secinfo[idx][aidx][1]
1725 if secinfo[idx][0] == 'ssidref':
1726 to_store['security/ssidref'] = str(secinfo[idx][1])
1729 if not self._readVm('xend/restart_count'):
1730 to_store['xend/restart_count'] = str(0)
1732 log.debug("Storing VM details: %s", scrub_password(to_store))
1734 self._writeVm(to_store)
1735 self._setVmPermissions()
1738 def _setVmPermissions(self):
1739 """Allow the guest domain to read its UUID. We don't allow it to
1740 access any other entry, for security."""
1741 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1742 { 'dom' : self.domid,
1743 'read' : True,
1744 'write' : False })
1747 # Utility functions
1750 def _stateSet(self, state):
1751 self.state_updated.acquire()
1752 try:
1753 if self.state != state:
1754 self.state = state
1755 self.state_updated.notifyAll()
1756 finally:
1757 self.state_updated.release()
1759 def _infoIsSet(self, name):
1760 return name in self.info and self.info[name] is not None
1762 def _checkName(self, name):
1763 """Check if a vm name is valid. Valid names contain alphabetic
1764 characters, digits, or characters in '_-.:/+'.
1765 The same name cannot be used for more than one vm at the same time.
1767 @param name: name
1768 @raise: VmError if invalid
1769 """
1770 from xen.xend import XendDomain
1772 if name is None or name == '':
1773 raise VmError('Missing VM Name')
1775 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1776 raise VmError('Invalid VM Name')
1778 dom = XendDomain.instance().domain_lookup_nr(name)
1779 if dom and dom.info['uuid'] != self.info['uuid']:
1780 raise VmError("VM name '%s' already exists%s" %
1781 (name,
1782 dom.domid is not None and
1783 (" as domain %s" % str(dom.domid)) or ""))
1786 def update(self, info = None, refresh = True):
1787 """Update with info from xc.domain_getinfo().
1788 """
1789 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1790 str(self.domid))
1792 if not info:
1793 info = dom_get(self.domid)
1794 if not info:
1795 return
1797 #manually update ssidref / security fields
1798 if security.on() and info.has_key('ssidref'):
1799 if (info['ssidref'] != 0) and self.info.has_key('security'):
1800 security_field = self.info['security']
1801 if not security_field:
1802 #create new security element
1803 self.info.update({'security':
1804 [['ssidref', str(info['ssidref'])]]})
1806 #ssidref field not used any longer
1807 if 'ssidref' in info:
1808 info.pop('ssidref')
1810 # make sure state is reset for info
1811 # TODO: we should eventually get rid of old_dom_states
1813 self.info.update_config(info)
1815 if refresh:
1816 self.refreshShutdown(info)
1818 log.trace("XendDomainInfo.update done on domain %s: %s",
1819 str(self.domid), self.info)
1821 def sxpr(self, ignore_store = False, legacy_only = True):
1822 result = self.info.to_sxp(domain = self,
1823 ignore_devices = ignore_store,
1824 legacy_only = legacy_only)
1826 if not ignore_store and self.dompath:
1827 vnc_port = self.readDom('console/vnc-port')
1828 if vnc_port is not None:
1829 result.append(['device',
1830 ['console', ['vnc-port', str(vnc_port)]]])
1832 return result
1834 # Xen API
1835 # ----------------------------------------------------------------
1837 def get_uuid(self):
1838 dom_uuid = self.info.get('uuid')
1839 if not dom_uuid: # if it doesn't exist, make one up
1840 dom_uuid = uuid.createString()
1841 self.info['uuid'] = dom_uuid
1842 return dom_uuid
1844 def get_memory_static_max(self):
1845 return self.info.get('memory_static_max', 0)
1846 def get_memory_static_min(self):
1847 return self.info.get('memory_static_min', 0)
1848 def get_memory_dynamic_max(self):
1849 return self.info.get('memory_dynamic_max', 0)
1850 def get_memory_dynamic_min(self):
1851 return self.info.get('memory_dynamic_min', 0)
1853 def get_vcpus_policy(self):
1854 sched_id = xc.sched_id_get()
1855 if sched_id == xen.lowlevel.xc.XEN_SCHEDULER_SEDF:
1856 return 'sedf'
1857 elif sched_id == xen.lowlevel.xc.XEN_SCHEDULER_CREDIT:
1858 return 'credit'
1859 else:
1860 return 'unknown'
1861 def get_vcpus_params(self):
1862 return '' # TODO
1863 def get_power_state(self):
1864 return XEN_API_VM_POWER_STATE[self.state]
1865 def get_platform_std_vga(self):
1866 return self.info.get('platform_std_vga', False)
1867 def get_platform_serial(self):
1868 return self.info.get('platform_serial', '')
1869 def get_platform_localtime(self):
1870 return self.info.get('platform_localtime', False)
1871 def get_platform_clock_offset(self):
1872 return self.info.get('platform_clock_offset', False)
1873 def get_platform_enable_audio(self):
1874 return self.info.get('platform_enable_audio', False)
1875 def get_platform_keymap(self):
1876 return self.info.get('platform_keymap', '')
1877 def get_pci_bus(self):
1878 return '' # TODO
1879 def get_tools_version(self):
1880 return {} # TODO
1881 def get_other_config(self):
1882 return {} # TODO
1884 def get_on_shutdown(self):
1885 after_shutdown = self.info.get('action_after_shutdown')
1886 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
1887 return XEN_API_ON_NORMAL_EXIT[-1]
1888 return after_shutdown
1890 def get_on_reboot(self):
1891 after_reboot = self.info.get('action_after_reboot')
1892 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
1893 return XEN_API_ON_NORMAL_EXIT[-1]
1894 return after_reboot
1896 def get_on_suspend(self):
1897 # TODO: not supported
1898 after_suspend = self.info.get('action_after_suspend')
1899 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
1900 return XEN_API_ON_NORMAL_EXIT[-1]
1901 return after_suspend
1903 def get_on_crash(self):
1904 after_crash = self.info.get('action_after_crash')
1905 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
1906 return XEN_API_ON_CRASH_BEHAVIOUR[0]
1907 return after_crash
1909 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
1910 """ Get's a device configuration either from XendConfig or
1911 from the DevController.
1913 @param dev_class: device class, either, 'vbd' or 'vif'
1914 @param dev_uuid: device UUID
1916 @rtype: dictionary
1917 """
1918 dev_type_config = self.info['devices'].get(dev_uuid)
1920 # shortcut if the domain isn't started because
1921 # the devcontrollers will have no better information
1922 # than XendConfig.
1923 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
1924 if dev_type_config:
1925 return copy.deepcopy(dev_type_config[1])
1926 return None
1928 # instead of using dev_class, we use the dev_type
1929 # that is from XendConfig.
1930 # This will accomdate 'tap' as well as 'vbd'
1931 dev_type = dev_type_config[0]
1933 controller = self.getDeviceController(dev_type)
1934 if not controller:
1935 return None
1937 all_configs = controller.getAllDeviceConfigurations()
1938 if not all_configs:
1939 return None
1941 dev_config = copy.deepcopy(dev_type_config[1])
1942 for _devid, _devcfg in all_configs.items():
1943 if _devcfg.get('uuid') == dev_uuid:
1944 dev_config.update(_devcfg)
1945 dev_config['id'] = _devid
1946 return dev_config
1948 return dev_config
1950 def get_dev_xenapi_config(self, dev_class, dev_uuid):
1951 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
1952 if not config:
1953 return {}
1955 config['VM'] = self.get_uuid()
1957 if dev_class == 'vif':
1958 if not config.has_key('name'):
1959 config['name'] = config.get('vifname', '')
1960 if not config.has_key('MAC'):
1961 config['MAC'] = config.get('mac', '')
1962 if not config.has_key('type'):
1963 config['type'] = 'paravirtualised'
1964 if not config.has_key('device'):
1965 devid = config.get('id')
1966 if devid != None:
1967 config['device'] = 'eth%d' % devid
1968 else:
1969 config['device'] = ''
1971 if not config.has_key('network'):
1972 config['network'] = \
1973 XendNode.instance().bridge_to_network(
1974 config.get('bridge')).uuid
1976 config['MTU'] = 1500 # TODO
1977 config['io_read_kbs'] = 0.0
1978 config['io_write_kbs'] = 0.0
1980 if dev_class == 'vbd':
1981 config['VDI'] = config.get('VDI', '')
1982 config['device'] = config.get('dev', '')
1983 config['driver'] = 'paravirtualised' # TODO
1984 config['image'] = config.get('uname', '')
1985 config['io_read_kbs'] = 0.0
1986 config['io_write_kbs'] = 0.0
1987 if config['mode'] == 'r':
1988 config['mode'] = 'RO'
1989 else:
1990 config['mode'] = 'RW'
1992 if dev_class == 'vtpm':
1993 config['driver'] = 'paravirtualised' # TODO
1995 return config
1997 def get_dev_property(self, dev_class, dev_uuid, field):
1998 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
1999 try:
2000 return config[field]
2001 except KeyError:
2002 raise XendError('Invalid property for device: %s' % field)
2004 def get_vcpus_util(self):
2005 # TODO: this returns the total accum cpu time, rather than util
2006 # TODO: spec says that key is int, however, python does not allow
2007 # non-string keys to dictionaries.
2008 vcpu_util = {}
2009 if 'max_vcpu_id' in self.info and self.domid != None:
2010 for i in range(0, self.info['max_vcpu_id']+1):
2011 info = xc.vcpu_getinfo(self.domid, i)
2012 vcpu_util[str(i)] = info['cpu_time']/1000000000.0
2014 return vcpu_util
2016 def get_consoles(self):
2017 return self.info.get('console_refs', [])
2019 def get_vifs(self):
2020 return self.info.get('vif_refs', [])
2022 def get_vbds(self):
2023 return self.info.get('vbd_refs', [])
2025 def get_vtpms(self):
2026 return self.info.get('vtpm_refs', [])
2028 def create_vbd(self, xenapi_vbd):
2029 """Create a VBD device from the passed struct in Xen API format.
2031 @return: uuid of the device
2032 @rtype: string
2033 """
2035 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
2036 if not dev_uuid:
2037 raise XendError('Failed to create device')
2039 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2040 _, config = self.info['devices'][dev_uuid]
2041 config['devid'] = self.getDeviceController('vbd').createDevice(config)
2043 return dev_uuid
2045 def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
2046 """Create a VBD using a VDI from XendStorageRepository.
2048 @param xenapi_vbd: vbd struct from the Xen API
2049 @param vdi_image_path: VDI UUID
2050 @rtype: string
2051 @return: uuid of the device
2052 """
2053 xenapi_vbd['image'] = vdi_image_path
2054 log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
2055 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
2056 if not dev_uuid:
2057 raise XendError('Failed to create device')
2059 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2060 _, config = self.info['devices'][dev_uuid]
2061 config['devid'] = self.getDeviceController('tap').createDevice(config)
2063 return dev_uuid
2065 def create_vif(self, xenapi_vif):
2066 """Create VIF device from the passed struct in Xen API format.
2068 @param xenapi_vif: Xen API VIF Struct.
2069 @rtype: string
2070 @return: UUID
2071 """
2072 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
2073 if not dev_uuid:
2074 raise XendError('Failed to create device')
2076 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2077 _, config = self.info['devices'][dev_uuid]
2078 config['devid'] = self.getDeviceController('vif').createDevice(config)
2080 return dev_uuid
2082 def create_vtpm(self, xenapi_vtpm):
2083 """Create a VTPM device from the passed struct in Xen API format.
2085 @return: uuid of the device
2086 @rtype: string
2087 """
2089 if self.state not in (DOM_STATE_HALTED,):
2090 raise VmError("Can only add vTPM to a halted domain.")
2091 if self.get_vtpms() != []:
2092 raise VmError('Domain already has a vTPM.')
2093 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
2094 if not dev_uuid:
2095 raise XendError('Failed to create device')
2097 return dev_uuid
2099 def has_device(self, dev_class, dev_uuid):
2100 return (dev_uuid in self.info['%s_refs' % dev_class.lower()])
2102 def __str__(self):
2103 return '<domain id=%s name=%s memory=%s state=%s>' % \
2104 (str(self.domid), self.info['name_label'],
2105 str(self.info['memory_static_min']), DOM_STATES[self.state])
2107 __repr__ = __str__