debuggers.hg

view tools/python/xen/xend/XendDomainInfo.py @ 13615:4f5772324e67

[XEND] Strip suffix from device name and add support for 'VBD.type'

Signed-off-by: Alastair Tse <atse@xensource.com>
author Alastair Tse <atse@xensource.com>
date Wed Jan 24 12:07:54 2007 +0000 (2007-01-24)
parents 207523704fb1
children f7a52957b427
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, osdep
41 from xen.xend import XendOptions, XendNode, XendConfig
43 from xen.xend.XendConfig import scrub_password
44 from xen.xend.XendBootloader import bootloader, bootloader_tidy
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, ResumeDomain
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 xoptions = XendOptions.instance()
59 log = logging.getLogger("xend.XendDomainInfo")
60 #log.setLevel(logging.TRACE)
63 def create(config):
64 """Creates and start a VM using the supplied configuration.
66 @param config: A configuration object involving lists of tuples.
67 @type config: list of lists, eg ['vm', ['image', 'xen.gz']]
69 @rtype: XendDomainInfo
70 @return: An up and running XendDomainInfo instance
71 @raise VmError: Invalid configuration or failure to start.
72 """
74 log.debug("XendDomainInfo.create(%s)", scrub_password(config))
75 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config))
76 try:
77 vm.start()
78 except:
79 log.exception('Domain construction failed')
80 vm.destroy()
81 raise
83 return vm
85 def create_from_dict(config_dict):
86 """Creates and start a VM using the supplied configuration.
88 @param config_dict: An configuration dictionary.
90 @rtype: XendDomainInfo
91 @return: An up and running XendDomainInfo instance
92 @raise VmError: Invalid configuration or failure to start.
93 """
95 log.debug("XendDomainInfo.create_from_dict(%s)",
96 scrub_password(config_dict))
97 vm = XendDomainInfo(XendConfig.XendConfig(xapi = config_dict))
98 try:
99 vm.start()
100 except:
101 log.exception('Domain construction failed')
102 vm.destroy()
103 raise
104 return vm
106 def recreate(info, priv):
107 """Create the VM object for an existing domain. The domain must not
108 be dying, as the paths in the store should already have been removed,
109 and asking us to recreate them causes problems.
111 @param xeninfo: Parsed configuration
112 @type xeninfo: Dictionary
113 @param priv: Is a privileged domain (Dom 0)
114 @type priv: bool
116 @rtype: XendDomainInfo
117 @return: A up and running XendDomainInfo instance
118 @raise VmError: Invalid configuration.
119 @raise XendError: Errors with configuration.
120 """
122 log.debug("XendDomainInfo.recreate(%s)", scrub_password(info))
124 assert not info['dying']
126 xeninfo = XendConfig.XendConfig(dominfo = info)
127 domid = xeninfo['domid']
128 uuid1 = uuid.fromString(xeninfo['uuid'])
129 needs_reinitialising = False
131 dompath = GetDomainPath(domid)
132 if not dompath:
133 raise XendError('No domain path in store for existing '
134 'domain %d' % domid)
136 log.info("Recreating domain %d, UUID %s. at %s" %
137 (domid, xeninfo['uuid'], dompath))
139 # need to verify the path and uuid if not Domain-0
140 # if the required uuid and vm aren't set, then that means
141 # we need to recreate the dom with our own values
142 #
143 # NOTE: this is probably not desirable, really we should just
144 # abort or ignore, but there may be cases where xenstore's
145 # entry disappears (eg. xenstore-rm /)
146 #
147 try:
148 vmpath = xstransact.Read(dompath, "vm")
149 if not vmpath:
150 log.warn('/local/domain/%d/vm is missing. recreate is '
151 'confused, trying our best to recover' % domid)
152 needs_reinitialising = True
153 raise XendError('reinit')
155 uuid2_str = xstransact.Read(vmpath, "uuid")
156 if not uuid2_str:
157 log.warn('%s/uuid/ is missing. recreate is confused, '
158 'trying our best to recover' % vmpath)
159 needs_reinitialising = True
160 raise XendError('reinit')
162 uuid2 = uuid.fromString(uuid2_str)
163 if uuid1 != uuid2:
164 log.warn('UUID in /vm does not match the UUID in /dom/%d.'
165 'Trying out best to recover' % domid)
166 needs_reinitialising = True
167 except XendError:
168 pass # our best shot at 'goto' in python :)
170 vm = XendDomainInfo(xeninfo, domid, dompath, augment = True, priv = priv)
172 if needs_reinitialising:
173 vm._recreateDom()
174 vm._removeVm()
175 vm._storeVmDetails()
176 vm._storeDomDetails()
178 if vm.info['image']: # Only dom0 should be without an image entry when
179 # recreating, but we cope with missing ones
180 # elsewhere just in case.
181 vm.image = image.create(vm,
182 vm.info,
183 vm.info['image'],
184 vm.info['devices'])
185 vm.image.recreate()
187 vm._registerWatches()
188 vm.refreshShutdown(xeninfo)
190 # register the domain in the list
191 from xen.xend import XendDomain
192 XendDomain.instance().add_domain(vm)
194 return vm
197 def restore(config):
198 """Create a domain and a VM object to do a restore.
200 @param config: Domain SXP configuration
201 @type config: list of lists. (see C{create})
203 @rtype: XendDomainInfo
204 @return: A up and running XendDomainInfo instance
205 @raise VmError: Invalid configuration or failure to start.
206 @raise XendError: Errors with configuration.
207 """
209 log.debug("XendDomainInfo.restore(%s)", scrub_password(config))
210 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config),
211 resume = True)
212 try:
213 vm.resume()
214 return vm
215 except:
216 vm.destroy()
217 raise
219 def createDormant(domconfig):
220 """Create a dormant/inactive XenDomainInfo without creating VM.
221 This is for creating instances of persistent domains that are not
222 yet start.
224 @param domconfig: Parsed configuration
225 @type domconfig: XendConfig object
227 @rtype: XendDomainInfo
228 @return: A up and running XendDomainInfo instance
229 @raise XendError: Errors with configuration.
230 """
232 log.debug("XendDomainInfo.createDormant(%s)", scrub_password(domconfig))
234 # domid does not make sense for non-running domains.
235 domconfig.pop('domid', None)
236 vm = XendDomainInfo(domconfig)
237 return vm
239 def domain_by_name(name):
240 """Get domain by name
242 @params name: Name of the domain
243 @type name: string
244 @return: XendDomainInfo or None
245 """
246 from xen.xend import XendDomain
247 return XendDomain.instance().domain_lookup_by_name_nr(name)
250 def shutdown_reason(code):
251 """Get a shutdown reason from a code.
253 @param code: shutdown code
254 @type code: int
255 @return: shutdown reason
256 @rtype: string
257 """
258 return DOMAIN_SHUTDOWN_REASONS.get(code, "?")
260 def dom_get(dom):
261 """Get info from xen for an existing domain.
263 @param dom: domain id
264 @type dom: int
265 @return: info or None
266 @rtype: dictionary
267 """
268 try:
269 domlist = xc.domain_getinfo(dom, 1)
270 if domlist and dom == domlist[0]['domid']:
271 return domlist[0]
272 except Exception, err:
273 # ignore missing domain
274 log.trace("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
275 return None
278 class XendDomainInfo:
279 """An object represents a domain.
281 @TODO: try to unify dom and domid, they mean the same thing, but
282 xc refers to it as dom, and everywhere else, including
283 xenstore it is domid. The best way is to change xc's
284 python interface.
286 @ivar info: Parsed configuration
287 @type info: dictionary
288 @ivar domid: Domain ID (if VM has started)
289 @type domid: int or None
290 @ivar vmpath: XenStore path to this VM.
291 @type vmpath: string
292 @ivar dompath: XenStore path to this Domain.
293 @type dompath: string
294 @ivar image: Reference to the VM Image.
295 @type image: xen.xend.image.ImageHandler
296 @ivar store_port: event channel to xenstored
297 @type store_port: int
298 @ivar console_port: event channel to xenconsoled
299 @type console_port: int
300 @ivar store_mfn: xenstored mfn
301 @type store_mfn: int
302 @ivar console_mfn: xenconsoled mfn
303 @type console_mfn: int
304 @ivar vmWatch: reference to a watch on the xenstored vmpath
305 @type vmWatch: xen.xend.xenstore.xswatch
306 @ivar shutdownWatch: reference to watch on the xenstored domain shutdown
307 @type shutdownWatch: xen.xend.xenstore.xswatch
308 @ivar shutdownStartTime: UNIX Time when domain started shutting down.
309 @type shutdownStartTime: float or None
310 @ivar state: Domain state
311 @type state: enum(DOM_STATE_HALTED, DOM_STATE_RUNNING, ...)
312 @ivar state_updated: lock for self.state
313 @type state_updated: threading.Condition
314 @ivar refresh_shutdown_lock: lock for polling shutdown state
315 @type refresh_shutdown_lock: threading.Condition
316 @ivar _deviceControllers: device controller cache for this domain
317 @type _deviceControllers: dict 'string' to DevControllers
318 """
320 def __init__(self, info, domid = None, dompath = None, augment = False,
321 priv = False, resume = False):
322 """Constructor for a domain
324 @param info: parsed configuration
325 @type info: dictionary
326 @keyword domid: Set initial domain id (if any)
327 @type domid: int
328 @keyword dompath: Set initial dompath (if any)
329 @type dompath: string
330 @keyword augment: Augment given info with xenstored VM info
331 @type augment: bool
332 @keyword priv: Is a privileged domain (Dom 0)
333 @type priv: bool
334 @keyword resume: Is this domain being resumed?
335 @type resume: bool
336 """
338 self.info = info
339 if domid == None:
340 self.domid = self.info.get('domid')
341 else:
342 self.domid = domid
344 #REMOVE: uuid is now generated in XendConfig
345 #if not self._infoIsSet('uuid'):
346 # self.info['uuid'] = uuid.toString(uuid.create())
348 self.vmpath = XS_VMROOT + self.info['uuid']
349 self.dompath = dompath
351 self.image = None
352 self.store_port = None
353 self.store_mfn = None
354 self.console_port = None
355 self.console_mfn = None
357 self.vmWatch = None
358 self.shutdownWatch = None
359 self.shutdownStartTime = None
360 self._resume = resume
362 self.state = DOM_STATE_HALTED
363 self.state_updated = threading.Condition()
364 self.refresh_shutdown_lock = threading.Condition()
366 self._deviceControllers = {}
368 for state in DOM_STATES_OLD:
369 self.info[state] = 0
371 if augment:
372 self._augmentInfo(priv)
374 self._checkName(self.info['name_label'])
377 #
378 # Public functions available through XMLRPC
379 #
382 def start(self, is_managed = False):
383 """Attempts to start the VM by do the appropriate
384 initialisation if it not started.
385 """
386 from xen.xend import XendDomain
388 if self.state == DOM_STATE_HALTED:
389 try:
390 self._constructDomain()
391 self._initDomain()
392 self._storeVmDetails()
393 self._storeDomDetails()
394 self._registerWatches()
395 self.refreshShutdown()
397 # save running configuration if XendDomains believe domain is
398 # persistent
399 if is_managed:
400 xendomains = XendDomain.instance()
401 xendomains.managed_config_save(self)
402 except:
403 log.exception('VM start failed')
404 self.destroy()
405 raise
406 else:
407 raise XendError('VM already running')
409 def resume(self):
410 """Resumes a domain that has come back from suspension."""
411 if self.state in (DOM_STATE_HALTED, DOM_STATE_SUSPENDED):
412 try:
413 self._constructDomain()
414 self._storeVmDetails()
415 self._createDevices()
416 self._createChannels()
417 self._storeDomDetails()
418 self._endRestore()
419 except:
420 log.exception('VM resume failed')
421 raise
422 else:
423 raise XendError('VM already running')
425 def shutdown(self, reason):
426 """Shutdown a domain by signalling this via xenstored."""
427 log.debug('XendDomainInfo.shutdown(%s)', reason)
428 if self.state in (DOM_STATE_SHUTDOWN, DOM_STATE_HALTED,):
429 raise XendError('Domain cannot be shutdown')
431 if self.domid == 0:
432 raise XendError('Domain 0 cannot be shutdown')
434 if reason not in DOMAIN_SHUTDOWN_REASONS.values():
435 raise XendError('Invalid reason: %s' % reason)
436 self._removeVm('xend/previous_restart_time')
437 self.storeDom("control/shutdown", reason)
439 ## shutdown hypercall for hvm domain desides xenstore write
440 image_cfg = self.info.get('image', {})
441 hvm = image_cfg.has_key('hvm')
442 if hvm:
443 for code in DOMAIN_SHUTDOWN_REASONS.keys():
444 if DOMAIN_SHUTDOWN_REASONS[code] == reason:
445 break
446 xc.domain_shutdown(self.domid, code)
449 def pause(self):
450 """Pause domain
452 @raise XendError: Failed pausing a domain
453 """
454 try:
455 xc.domain_pause(self.domid)
456 self._stateSet(DOM_STATE_PAUSED)
457 except Exception, ex:
458 log.exception(ex)
459 raise XendError("Domain unable to be paused: %s" % str(ex))
461 def unpause(self):
462 """Unpause domain
464 @raise XendError: Failed unpausing a domain
465 """
466 try:
467 xc.domain_unpause(self.domid)
468 self._stateSet(DOM_STATE_RUNNING)
469 except Exception, ex:
470 log.exception(ex)
471 raise XendError("Domain unable to be unpaused: %s" % str(ex))
473 def send_sysrq(self, key):
474 """ Send a Sysrq equivalent key via xenstored."""
475 asserts.isCharConvertible(key)
476 self.storeDom("control/sysrq", '%c' % key)
478 def device_create(self, dev_config):
479 """Create a new device.
481 @param dev_config: device configuration
482 @type dev_config: SXP object (parsed config)
483 """
484 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config))
485 dev_type = sxp.name(dev_config)
486 dev_uuid = self.info.device_add(dev_type, cfg_sxp = dev_config)
487 dev_config_dict = self.info['devices'][dev_uuid][1]
488 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config_dict))
489 dev_config_dict['devid'] = devid = \
490 self._createDevice(dev_type, dev_config_dict)
491 self._waitForDevice(dev_type, devid)
492 return self.getDeviceController(dev_type).sxpr(devid)
494 def device_configure(self, dev_sxp, devid = None):
495 """Configure an existing device.
497 @param dev_config: device configuration
498 @type dev_config: SXP object (parsed config)
499 @param devid: device id
500 @type devid: int
501 @return: Returns True if successfully updated device
502 @rtype: boolean
503 """
505 # convert device sxp to a dict
506 dev_class = sxp.name(dev_sxp)
507 dev_config = {}
508 for opt_val in dev_sxp[1:]:
509 try:
510 dev_config[opt_val[0]] = opt_val[1]
511 except IndexError:
512 pass
514 # use DevController.reconfigureDevice to change device config
515 dev_control = self.getDeviceController(dev_class)
516 dev_uuid = dev_control.reconfigureDevice(devid, dev_config)
518 # update XendConfig with new device info
519 if dev_uuid:
520 self.info.device_update(dev_uuid, dev_sxp)
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['vcpus_number']):
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(xoptions.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 xen.xend.XendDomain.instance().managed_config_save(self)
897 log.info("Set VCPU count on domain %s to %d", self.info['name_label'],
898 vcpus)
900 def getLabel(self):
901 return security.get_security_info(self.info, 'label')
903 def getMemoryTarget(self):
904 """Get this domain's target memory size, in KB."""
905 return self.info['memory_static_min'] * 1024
907 def getMemoryMaximum(self):
908 """Get this domain's maximum memory size, in KB."""
909 return self.info['memory_static_max'] * 1024
911 def getResume(self):
912 return str(self._resume)
914 def getCap(self):
915 return self.info.get('cpu_cap', 0)
917 def getWeight(self):
918 return self.info.get('cpu_weight', 256)
920 def setResume(self, state):
921 self._resume = state
923 def getRestartCount(self):
924 return self._readVm('xend/restart_count')
926 def refreshShutdown(self, xeninfo = None):
927 """ Checks the domain for whether a shutdown is required.
929 Called from XendDomainInfo and also image.py for HVM images.
930 """
932 # If set at the end of this method, a restart is required, with the
933 # given reason. This restart has to be done out of the scope of
934 # refresh_shutdown_lock.
935 restart_reason = None
937 self.refresh_shutdown_lock.acquire()
938 try:
939 if xeninfo is None:
940 xeninfo = dom_get(self.domid)
941 if xeninfo is None:
942 # The domain no longer exists. This will occur if we have
943 # scheduled a timer to check for shutdown timeouts and the
944 # shutdown succeeded. It will also occur if someone
945 # destroys a domain beneath us. We clean up the domain,
946 # just in case, but we can't clean up the VM, because that
947 # VM may have migrated to a different domain on this
948 # machine.
949 self.cleanupDomain()
950 self._stateSet(DOM_STATE_HALTED)
951 return
953 if xeninfo['dying']:
954 # Dying means that a domain has been destroyed, but has not
955 # yet been cleaned up by Xen. This state could persist
956 # indefinitely if, for example, another domain has some of its
957 # pages mapped. We might like to diagnose this problem in the
958 # future, but for now all we do is make sure that it's not us
959 # holding the pages, by calling cleanupDomain. We can't
960 # clean up the VM, as above.
961 self.cleanupDomain()
962 self._stateSet(DOM_STATE_SHUTDOWN)
963 return
965 elif xeninfo['crashed']:
966 if self.readDom('xend/shutdown_completed'):
967 # We've seen this shutdown already, but we are preserving
968 # the domain for debugging. Leave it alone.
969 return
971 log.warn('Domain has crashed: name=%s id=%d.',
972 self.info['name_label'], self.domid)
973 self._writeVm(LAST_SHUTDOWN_REASON, 'crash')
975 if xoptions.get_enable_dump():
976 try:
977 self.dumpCore()
978 except XendError:
979 # This error has been logged -- there's nothing more
980 # we can do in this context.
981 pass
983 restart_reason = 'crash'
984 self._stateSet(DOM_STATE_HALTED)
986 elif xeninfo['shutdown']:
987 self._stateSet(DOM_STATE_SHUTDOWN)
988 if self.readDom('xend/shutdown_completed'):
989 # We've seen this shutdown already, but we are preserving
990 # the domain for debugging. Leave it alone.
991 return
993 else:
994 reason = shutdown_reason(xeninfo['shutdown_reason'])
996 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
997 self.info['name_label'], self.domid, reason)
998 self._writeVm(LAST_SHUTDOWN_REASON, reason)
1000 self._clearRestart()
1002 if reason == 'suspend':
1003 self._stateSet(DOM_STATE_SUSPENDED)
1004 # Don't destroy the domain. XendCheckpoint will do
1005 # this once it has finished. However, stop watching
1006 # the VM path now, otherwise we will end up with one
1007 # watch for the old domain, and one for the new.
1008 self._unwatchVm()
1009 elif reason in ('poweroff', 'reboot'):
1010 restart_reason = reason
1011 else:
1012 self.destroy()
1014 elif self.dompath is None:
1015 # We have yet to manage to call introduceDomain on this
1016 # domain. This can happen if a restore is in progress, or has
1017 # failed. Ignore this domain.
1018 pass
1019 else:
1020 # Domain is alive. If we are shutting it down, then check
1021 # the timeout on that, and destroy it if necessary.
1022 if xeninfo['paused']:
1023 self._stateSet(DOM_STATE_PAUSED)
1024 else:
1025 self._stateSet(DOM_STATE_RUNNING)
1027 if self.shutdownStartTime:
1028 timeout = (SHUTDOWN_TIMEOUT - time.time() +
1029 self.shutdownStartTime)
1030 if timeout < 0:
1031 log.info(
1032 "Domain shutdown timeout expired: name=%s id=%s",
1033 self.info['name_label'], self.domid)
1034 self.destroy()
1035 finally:
1036 self.refresh_shutdown_lock.release()
1038 if restart_reason:
1039 threading.Thread(target = self._maybeRestart,
1040 args = (restart_reason,)).start()
1044 # Restart functions - handling whether we come back up on shutdown.
1047 def _clearRestart(self):
1048 self._removeDom("xend/shutdown_start_time")
1051 def _maybeRestart(self, reason):
1052 # Dispatch to the correct method based upon the configured on_{reason}
1053 # behaviour.
1054 actions = {"destroy" : self.destroy,
1055 "restart" : self._restart,
1056 "preserve" : self._preserve,
1057 "rename-restart" : self._renameRestart}
1059 action_conf = {
1060 'poweroff': 'actions_after_shutdown',
1061 'reboot': 'actions_after_reboot',
1062 'crash': 'actions_after_crash',
1065 action_target = self.info.get(action_conf.get(reason))
1066 func = actions.get(action_target, None)
1067 if func and callable(func):
1068 func()
1069 else:
1070 self.destroy() # default to destroy
1072 def _renameRestart(self):
1073 self._restart(True)
1075 def _restart(self, rename = False):
1076 """Restart the domain after it has exited.
1078 @param rename True if the old domain is to be renamed and preserved,
1079 False if it is to be destroyed.
1080 """
1081 from xen.xend import XendDomain
1083 if self._readVm(RESTART_IN_PROGRESS):
1084 log.error('Xend failed during restart of domain %s. '
1085 'Refusing to restart to avoid loops.',
1086 str(self.domid))
1087 self.destroy()
1088 return
1090 old_domid = self.domid
1091 self._writeVm(RESTART_IN_PROGRESS, 'True')
1093 now = time.time()
1094 rst = self._readVm('xend/previous_restart_time')
1095 if rst:
1096 rst = float(rst)
1097 timeout = now - rst
1098 if timeout < MINIMUM_RESTART_TIME:
1099 log.error(
1100 'VM %s restarting too fast (%f seconds since the last '
1101 'restart). Refusing to restart to avoid loops.',
1102 self.info['name_label'], timeout)
1103 self.destroy()
1104 return
1106 self._writeVm('xend/previous_restart_time', str(now))
1108 try:
1109 if rename:
1110 self._preserveForRestart()
1111 else:
1112 self._unwatchVm()
1113 self.destroyDomain()
1115 # new_dom's VM will be the same as this domain's VM, except where
1116 # the rename flag has instructed us to call preserveForRestart.
1117 # In that case, it is important that we remove the
1118 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1119 # once the new one is available.
1121 new_dom = None
1122 try:
1123 new_dom = XendDomain.instance().domain_create_from_dict(
1124 self.info)
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.
1175 @raise: XendError if core dumping failed.
1176 """
1178 try:
1179 if not corefile:
1180 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1181 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1182 self.info['name_label'], self.domid)
1184 if os.path.isdir(corefile):
1185 raise XendError("Cannot dump core in a directory: %s" %
1186 corefile)
1188 xc.domain_dumpcore(self.domid, corefile)
1189 except RuntimeError, ex:
1190 corefile_incomp = corefile+'-incomplete'
1191 os.rename(corefile, corefile_incomp)
1192 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1193 self.domid, self.info['name_label'])
1194 raise XendError("Failed to dump core: %s" % str(ex))
1197 # Device creation/deletion functions
1200 def _createDevice(self, deviceClass, devConfig):
1201 return self.getDeviceController(deviceClass).createDevice(devConfig)
1203 def _waitForDevice(self, deviceClass, devid):
1204 return self.getDeviceController(deviceClass).waitForDevice(devid)
1206 def _waitForDeviceUUID(self, dev_uuid):
1207 deviceClass, config = self.info['devices'].get(dev_uuid)
1208 self._waitForDevice(deviceClass, config['devid'])
1210 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1211 return self.getDeviceController(deviceClass).reconfigureDevice(
1212 devid, devconfig)
1214 def _createDevices(self):
1215 """Create the devices for a vm.
1217 @raise: VmError for invalid devices
1218 """
1219 for (devclass, config) in self.info.get('devices', {}).values():
1220 if devclass in XendDevices.valid_devices():
1221 log.info("createDevice: %s : %s" % (devclass, scrub_password(config)))
1222 self._createDevice(devclass, config)
1224 if self.image:
1225 self.image.createDeviceModel()
1227 def _releaseDevices(self, suspend = False):
1228 """Release all domain's devices. Nothrow guarantee."""
1229 if suspend and self.image:
1230 self.image.destroy(suspend)
1231 return
1233 while True:
1234 t = xstransact("%s/device" % self.dompath)
1235 for devclass in XendDevices.valid_devices():
1236 for dev in t.list(devclass):
1237 try:
1238 t.remove(dev)
1239 except:
1240 # Log and swallow any exceptions in removal --
1241 # there's nothing more we can do.
1242 log.exception(
1243 "Device release failed: %s; %s; %s",
1244 self.info['name_label'], devclass, dev)
1245 if t.commit():
1246 break
1248 def getDeviceController(self, name):
1249 """Get the device controller for this domain, and if it
1250 doesn't exist, create it.
1252 @param name: device class name
1253 @type name: string
1254 @rtype: subclass of DevController
1255 """
1256 if name not in self._deviceControllers:
1257 devController = XendDevices.make_controller(name, self)
1258 if not devController:
1259 raise XendError("Unknown device type: %s" % name)
1260 self._deviceControllers[name] = devController
1262 return self._deviceControllers[name]
1265 # Migration functions (public)
1268 def testMigrateDevices(self, network, dst):
1269 """ Notify all device about intention of migration
1270 @raise: XendError for a device that cannot be migrated
1271 """
1272 for (n, c) in self.info.all_devices_sxpr():
1273 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1274 if rc != 0:
1275 raise XendError("Device of type '%s' refuses migration." % n)
1277 def migrateDevices(self, network, dst, step, domName=''):
1278 """Notify the devices about migration
1279 """
1280 ctr = 0
1281 try:
1282 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1283 self.migrateDevice(dev_type, dev_conf, network, dst,
1284 step, domName)
1285 ctr = ctr + 1
1286 except:
1287 for dev_type, dev_conf in self.info.all_devices_sxpr():
1288 if ctr == 0:
1289 step = step - 1
1290 ctr = ctr - 1
1291 self._recoverMigrateDevice(dev_type, dev_conf, network,
1292 dst, step, domName)
1293 raise
1295 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1296 step, domName=''):
1297 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1298 network, dst, step, domName)
1300 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1301 dst, step, domName=''):
1302 return self.getDeviceController(deviceClass).recover_migrate(
1303 deviceConfig, network, dst, step, domName)
1306 ## private:
1308 def _constructDomain(self):
1309 """Construct the domain.
1311 @raise: VmError on error
1312 """
1314 log.debug('XendDomainInfo.constructDomain')
1316 self.shutdownStartTime = None
1318 image_cfg = self.info.get('image', {})
1319 hvm = image_cfg.has_key('hvm')
1321 if hvm:
1322 info = xc.xeninfo()
1323 if 'hvm' not in info['xen_caps']:
1324 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1325 "supported by your CPU and enabled in your "
1326 "BIOS?")
1328 self.domid = xc.domain_create(
1329 domid = 0,
1330 ssidref = security.get_security_info(self.info, 'ssidref'),
1331 handle = uuid.fromString(self.info['uuid']),
1332 hvm = int(hvm))
1334 if self.domid < 0:
1335 raise VmError('Creating domain failed: name=%s' %
1336 self.info['name_label'])
1338 self.dompath = GetDomainPath(self.domid)
1340 self._recreateDom()
1342 # Set maximum number of vcpus in domain
1343 xc.domain_max_vcpus(self.domid, int(self.info['vcpus_number']))
1345 # register the domain in the list
1346 from xen.xend import XendDomain
1347 XendDomain.instance().add_domain(self)
1349 def _introduceDomain(self):
1350 assert self.domid is not None
1351 assert self.store_mfn is not None
1352 assert self.store_port is not None
1354 try:
1355 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1356 except RuntimeError, exn:
1357 raise XendError(str(exn))
1360 def _initDomain(self):
1361 log.debug('XendDomainInfo.initDomain: %s %s',
1362 self.domid,
1363 self.info['cpu_weight'])
1365 self._configureBootloader()
1367 if not self._infoIsSet('image'):
1368 raise VmError('Missing image in configuration')
1370 try:
1371 self.image = image.create(self,
1372 self.info,
1373 self.info['image'],
1374 self.info['devices'])
1376 localtime = self.info.get('localtime', False)
1377 if localtime:
1378 xc.domain_set_time_offset(self.domid)
1380 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1382 # repin domain vcpus if a restricted cpus list is provided
1383 # this is done prior to memory allocation to aide in memory
1384 # distribution for NUMA systems.
1385 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1386 for v in range(0, self.info['vcpus_number']):
1387 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1389 # Use architecture- and image-specific calculations to determine
1390 # the various headrooms necessary, given the raw configured
1391 # values. maxmem, memory, and shadow are all in KiB.
1392 memory = self.image.getRequiredAvailableMemory(
1393 self.info['memory_static_min'] * 1024)
1394 maxmem = self.image.getRequiredAvailableMemory(
1395 self.info['memory_static_max'] * 1024)
1396 shadow = self.image.getRequiredShadowMemory(
1397 self.info['shadow_memory'] * 1024,
1398 self.info['memory_static_max'] * 1024)
1400 log.debug("_initDomain:shadow_memory=0x%x, memory_static_max=0x%x, memory_static_min=0x%x.", self.info['shadow_memory'], self.info['memory_static_max'], self.info['memory_static_min'],)
1401 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1402 # takes MiB and we must not round down and end up under-providing.
1403 shadow = ((shadow + 1023) / 1024) * 1024
1405 # set memory limit
1406 xc.domain_setmaxmem(self.domid, maxmem)
1408 # Make sure there's enough RAM available for the domain
1409 balloon.free(memory + shadow)
1411 # Set up the shadow memory
1412 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1413 self.info['shadow_memory'] = shadow_cur
1415 self._createChannels()
1417 channel_details = self.image.createImage()
1419 self.store_mfn = channel_details['store_mfn']
1420 if 'console_mfn' in channel_details:
1421 self.console_mfn = channel_details['console_mfn']
1423 self._introduceDomain()
1425 self._createDevices()
1427 self.image.cleanupBootloading()
1429 self.info['start_time'] = time.time()
1431 self._stateSet(DOM_STATE_RUNNING)
1432 except RuntimeError, exn:
1433 log.exception("XendDomainInfo.initDomain: exception occurred")
1434 self.image.cleanupBootloading()
1435 raise VmError(str(exn))
1438 def cleanupDomain(self):
1439 """Cleanup domain resources; release devices. Idempotent. Nothrow
1440 guarantee."""
1442 self.refresh_shutdown_lock.acquire()
1443 try:
1444 self.unwatchShutdown()
1445 self._releaseDevices()
1446 bootloader_tidy(self)
1448 if self.image:
1449 try:
1450 self.image.destroy()
1451 except:
1452 log.exception(
1453 "XendDomainInfo.cleanup: image.destroy() failed.")
1454 self.image = None
1456 try:
1457 self._removeDom()
1458 except:
1459 log.exception("Removing domain path failed.")
1461 self._stateSet(DOM_STATE_HALTED)
1462 finally:
1463 self.refresh_shutdown_lock.release()
1466 def unwatchShutdown(self):
1467 """Remove the watch on the domain's control/shutdown node, if any.
1468 Idempotent. Nothrow guarantee. Expects to be protected by the
1469 refresh_shutdown_lock."""
1471 try:
1472 try:
1473 if self.shutdownWatch:
1474 self.shutdownWatch.unwatch()
1475 finally:
1476 self.shutdownWatch = None
1477 except:
1478 log.exception("Unwatching control/shutdown failed.")
1480 def waitForShutdown(self):
1481 self.state_updated.acquire()
1482 try:
1483 while self.state in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1484 self.state_updated.wait()
1485 finally:
1486 self.state_updated.release()
1490 # TODO: recategorise - called from XendCheckpoint
1493 def completeRestore(self, store_mfn, console_mfn):
1495 log.debug("XendDomainInfo.completeRestore")
1497 self.store_mfn = store_mfn
1498 self.console_mfn = console_mfn
1500 self._introduceDomain()
1501 image_cfg = self.info.get('image', {})
1502 hvm = image_cfg.has_key('hvm')
1503 if hvm:
1504 self.image = image.create(self,
1505 self.info,
1506 self.info['image'],
1507 self.info['devices'])
1508 if self.image:
1509 self.image.createDeviceModel(True)
1510 self.image.register_shutdown_watch()
1511 self._storeDomDetails()
1512 self._registerWatches()
1513 self.refreshShutdown()
1515 log.debug("XendDomainInfo.completeRestore done")
1518 def _endRestore(self):
1519 self.setResume(False)
1522 # VM Destroy
1525 def destroy(self):
1526 """Cleanup VM and destroy domain. Nothrow guarantee."""
1528 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1530 self._cleanupVm()
1531 if self.dompath is not None:
1532 self.destroyDomain()
1535 def destroyDomain(self):
1536 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1538 try:
1539 if self.domid is not None:
1540 xc.domain_destroy(self.domid)
1541 self.domid = None
1542 for state in DOM_STATES_OLD:
1543 self.info[state] = 0
1544 except:
1545 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1547 from xen.xend import XendDomain
1548 XendDomain.instance().remove_domain(self)
1550 self.cleanupDomain()
1553 def resumeDomain(self):
1554 log.debug("XendDomainInfo.resumeDomain(%s)", str(self.domid))
1556 try:
1557 if self.domid is not None:
1558 xc.domain_resume(self.domid)
1559 ResumeDomain(self.domid)
1560 except:
1561 log.exception("XendDomainInfo.resume: xc.domain_resume failed on domain %s." % (str(self.domid)))
1564 # Channels for xenstore and console
1567 def _createChannels(self):
1568 """Create the channels to the domain.
1569 """
1570 self.store_port = self._createChannel()
1571 self.console_port = self._createChannel()
1574 def _createChannel(self):
1575 """Create an event channel to the domain.
1576 """
1577 try:
1578 return xc.evtchn_alloc_unbound(domid=self.domid, remote_dom=0)
1579 except:
1580 log.exception("Exception in alloc_unbound(%d)", self.domid)
1581 raise
1583 def _resetChannels(self):
1584 """Reset all event channels in the domain.
1585 """
1586 try:
1587 return xc.evtchn_reset(dom=self.domid)
1588 except:
1589 log.exception("Exception in evtcnh_reset(%d)", self.domid)
1590 raise
1594 # Bootloader configuration
1597 def _configureBootloader(self):
1598 """Run the bootloader if we're configured to do so."""
1600 blexec = self.info['PV_bootloader']
1601 bootloader_args = self.info['PV_bootloader_args']
1602 kernel = self.info['PV_kernel']
1603 ramdisk = self.info['PV_ramdisk']
1604 args = self.info['PV_args']
1605 boot = self.info['HVM_boot']
1607 if boot:
1608 # HVM booting.
1609 self.info['image']['type'] = 'hvm'
1610 if not 'devices' in self.info['image']:
1611 self.info['image']['devices'] = {}
1612 self.info['image']['devices']['boot'] = boot
1613 elif not blexec and kernel:
1614 # Boot from dom0. Nothing left to do -- the kernel and ramdisk
1615 # will be picked up by image.py.
1616 pass
1617 else:
1618 # Boot using bootloader
1619 if not blexec or blexec == 'pygrub':
1620 blexec = osdep.pygrub_path
1622 blcfg = None
1623 for (devtype, devinfo) in self.info.all_devices_sxpr():
1624 if not devtype or not devinfo or devtype not in ('vbd', 'tap'):
1625 continue
1626 disk = None
1627 for param in devinfo:
1628 if param[0] == 'uname':
1629 disk = param[1]
1630 break
1632 if disk is None:
1633 continue
1634 fn = blkdev_uname_to_file(disk)
1635 mounted = devtype == 'tap' and not os.stat(fn).st_rdev
1636 if mounted:
1637 # This is a file, not a device. pygrub can cope with a
1638 # file if it's raw, but if it's QCOW or other such formats
1639 # used through blktap, then we need to mount it first.
1641 log.info("Mounting %s on %s." %
1642 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1644 vbd = {
1645 'mode': 'RO',
1646 'device': BOOTLOADER_LOOPBACK_DEVICE,
1649 from xen.xend import XendDomain
1650 dom0 = XendDomain.instance().privilegedDomain()
1651 dom0._waitForDeviceUUID(dom0.create_vbd_with_vdi(vbd, fn))
1652 fn = BOOTLOADER_LOOPBACK_DEVICE
1654 try:
1655 blcfg = bootloader(blexec, fn, self, False,
1656 bootloader_args, kernel, ramdisk, args)
1657 finally:
1658 if mounted:
1659 log.info("Unmounting %s from %s." %
1660 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1662 dom0.destroyDevice('tap', '/dev/xvdp')
1664 break
1666 if blcfg is None:
1667 msg = "Had a bootloader specified, but can't find disk"
1668 log.error(msg)
1669 raise VmError(msg)
1671 self.info.update_with_image_sxp(blcfg, True)
1675 # VM Functions
1678 def _readVMDetails(self, params):
1679 """Read the specified parameters from the store.
1680 """
1681 try:
1682 return self._gatherVm(*params)
1683 except ValueError:
1684 # One of the int/float entries in params has a corresponding store
1685 # entry that is invalid. We recover, because older versions of
1686 # Xend may have put the entry there (memory/target, for example),
1687 # but this is in general a bad situation to have reached.
1688 log.exception(
1689 "Store corrupted at %s! Domain %d's configuration may be "
1690 "affected.", self.vmpath, self.domid)
1691 return []
1693 def _cleanupVm(self):
1694 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1696 self._unwatchVm()
1698 try:
1699 self._removeVm()
1700 except:
1701 log.exception("Removing VM path failed.")
1704 def checkLiveMigrateMemory(self):
1705 """ Make sure there's enough memory to migrate this domain """
1706 overhead_kb = 0
1707 if arch.type == "x86":
1708 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1709 # the minimum that Xen would allocate if no value were given.
1710 overhead_kb = self.info['vcpus_number'] * 1024 + \
1711 self.info['memory_static_max'] * 4
1712 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1713 # The domain might already have some shadow memory
1714 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1715 if overhead_kb > 0:
1716 balloon.free(overhead_kb)
1718 def _unwatchVm(self):
1719 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1720 guarantee."""
1721 try:
1722 try:
1723 if self.vmWatch:
1724 self.vmWatch.unwatch()
1725 finally:
1726 self.vmWatch = None
1727 except:
1728 log.exception("Unwatching VM path failed.")
1730 def testDeviceComplete(self):
1731 """ For Block IO migration safety we must ensure that
1732 the device has shutdown correctly, i.e. all blocks are
1733 flushed to disk
1734 """
1735 start = time.time()
1736 while True:
1737 test = 0
1738 diff = time.time() - start
1739 for i in self.getDeviceController('vbd').deviceIDs():
1740 test = 1
1741 log.info("Dev %s still active, looping...", i)
1742 time.sleep(0.1)
1744 if test == 0:
1745 break
1746 if diff >= MIGRATE_TIMEOUT:
1747 log.info("Dev still active but hit max loop timeout")
1748 break
1750 def testvifsComplete(self):
1751 """ In case vifs are released and then created for the same
1752 domain, we need to wait the device shut down.
1753 """
1754 start = time.time()
1755 while True:
1756 test = 0
1757 diff = time.time() - start
1758 for i in self.getDeviceController('vif').deviceIDs():
1759 test = 1
1760 log.info("Dev %s still active, looping...", i)
1761 time.sleep(0.1)
1763 if test == 0:
1764 break
1765 if diff >= MIGRATE_TIMEOUT:
1766 log.info("Dev still active but hit max loop timeout")
1767 break
1769 def _storeVmDetails(self):
1770 to_store = {}
1772 for key in XendConfig.LEGACY_XENSTORE_VM_PARAMS:
1773 info_key = XendConfig.LEGACY_CFG_TO_XENAPI_CFG.get(key, key)
1774 if self._infoIsSet(info_key):
1775 to_store[key] = str(self.info[info_key])
1777 if self.info.get('image'):
1778 image_sxpr = self.info.image_sxpr()
1779 if image_sxpr:
1780 to_store['image'] = sxp.to_string(image_sxpr)
1782 if self._infoIsSet('security'):
1783 secinfo = self.info['security']
1784 to_store['security'] = sxp.to_string(secinfo)
1785 for idx in range(0, len(secinfo)):
1786 if secinfo[idx][0] == 'access_control':
1787 to_store['security/access_control'] = sxp.to_string(
1788 [secinfo[idx][1], secinfo[idx][2]])
1789 for aidx in range(1, len(secinfo[idx])):
1790 if secinfo[idx][aidx][0] == 'label':
1791 to_store['security/access_control/label'] = \
1792 secinfo[idx][aidx][1]
1793 if secinfo[idx][aidx][0] == 'policy':
1794 to_store['security/access_control/policy'] = \
1795 secinfo[idx][aidx][1]
1796 if secinfo[idx][0] == 'ssidref':
1797 to_store['security/ssidref'] = str(secinfo[idx][1])
1800 if not self._readVm('xend/restart_count'):
1801 to_store['xend/restart_count'] = str(0)
1803 log.debug("Storing VM details: %s", scrub_password(to_store))
1805 self._writeVm(to_store)
1806 self._setVmPermissions()
1809 def _setVmPermissions(self):
1810 """Allow the guest domain to read its UUID. We don't allow it to
1811 access any other entry, for security."""
1812 xstransact.SetPermissions('%s/uuid' % self.vmpath,
1813 { 'dom' : self.domid,
1814 'read' : True,
1815 'write' : False })
1818 # Utility functions
1821 def _stateSet(self, state):
1822 self.state_updated.acquire()
1823 try:
1824 if self.state != state:
1825 self.state = state
1826 self.state_updated.notifyAll()
1827 finally:
1828 self.state_updated.release()
1830 def _infoIsSet(self, name):
1831 return name in self.info and self.info[name] is not None
1833 def _checkName(self, name):
1834 """Check if a vm name is valid. Valid names contain alphabetic
1835 characters, digits, or characters in '_-.:/+'.
1836 The same name cannot be used for more than one vm at the same time.
1838 @param name: name
1839 @raise: VmError if invalid
1840 """
1841 from xen.xend import XendDomain
1843 if name is None or name == '':
1844 raise VmError('Missing VM Name')
1846 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
1847 raise VmError('Invalid VM Name')
1849 dom = XendDomain.instance().domain_lookup_nr(name)
1850 if dom and dom.info['uuid'] != self.info['uuid']:
1851 raise VmError("VM name '%s' already exists%s" %
1852 (name,
1853 dom.domid is not None and
1854 (" as domain %s" % str(dom.domid)) or ""))
1857 def update(self, info = None, refresh = True):
1858 """Update with info from xc.domain_getinfo().
1859 """
1860 log.trace("XendDomainInfo.update(%s) on domain %s", info,
1861 str(self.domid))
1863 if not info:
1864 info = dom_get(self.domid)
1865 if not info:
1866 return
1868 #manually update ssidref / security fields
1869 if security.on() and info.has_key('ssidref'):
1870 if (info['ssidref'] != 0) and self.info.has_key('security'):
1871 security_field = self.info['security']
1872 if not security_field:
1873 #create new security element
1874 self.info.update({'security':
1875 [['ssidref', str(info['ssidref'])]]})
1877 #ssidref field not used any longer
1878 if 'ssidref' in info:
1879 info.pop('ssidref')
1881 # make sure state is reset for info
1882 # TODO: we should eventually get rid of old_dom_states
1884 self.info.update_config(info)
1886 if refresh:
1887 self.refreshShutdown(info)
1889 log.trace("XendDomainInfo.update done on domain %s: %s",
1890 str(self.domid), self.info)
1892 def sxpr(self, ignore_store = False, legacy_only = True):
1893 result = self.info.to_sxp(domain = self,
1894 ignore_devices = ignore_store,
1895 legacy_only = legacy_only)
1897 if not ignore_store and self.dompath:
1898 vnc_port = self.readDom('console/vnc-port')
1899 if vnc_port is not None:
1900 result.append(['device',
1901 ['console', ['vnc-port', str(vnc_port)]]])
1903 return result
1905 # Xen API
1906 # ----------------------------------------------------------------
1908 def get_uuid(self):
1909 dom_uuid = self.info.get('uuid')
1910 if not dom_uuid: # if it doesn't exist, make one up
1911 dom_uuid = uuid.createString()
1912 self.info['uuid'] = dom_uuid
1913 return dom_uuid
1915 def get_memory_static_max(self):
1916 return self.info.get('memory_static_max', 0)
1917 def get_memory_static_min(self):
1918 return self.info.get('memory_static_min', 0)
1919 def get_memory_dynamic_max(self):
1920 return self.info.get('memory_dynamic_max', 0)
1921 def get_memory_dynamic_min(self):
1922 return self.info.get('memory_dynamic_min', 0)
1924 def get_vcpus_policy(self):
1925 sched_id = xc.sched_id_get()
1926 if sched_id == xen.lowlevel.xc.XEN_SCHEDULER_SEDF:
1927 return 'sedf'
1928 elif sched_id == xen.lowlevel.xc.XEN_SCHEDULER_CREDIT:
1929 return 'credit'
1930 else:
1931 return 'unknown'
1932 def get_vcpus_params(self):
1933 return '' # TODO
1934 def get_power_state(self):
1935 return XEN_API_VM_POWER_STATE[self.state]
1936 def get_platform_std_vga(self):
1937 return self.info.get('platform_std_vga', False)
1938 def get_platform_serial(self):
1939 return self.info.get('platform_serial', '')
1940 def get_platform_localtime(self):
1941 return self.info.get('platform_localtime', False)
1942 def get_platform_clock_offset(self):
1943 return self.info.get('platform_clock_offset', False)
1944 def get_platform_enable_audio(self):
1945 return self.info.get('platform_enable_audio', False)
1946 def get_platform_keymap(self):
1947 return self.info.get('platform_keymap', '')
1948 def get_pci_bus(self):
1949 return '' # TODO
1950 def get_tools_version(self):
1951 return {} # TODO
1952 def get_other_config(self):
1953 return {} # TODO
1955 def get_on_shutdown(self):
1956 after_shutdown = self.info.get('action_after_shutdown')
1957 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
1958 return XEN_API_ON_NORMAL_EXIT[-1]
1959 return after_shutdown
1961 def get_on_reboot(self):
1962 after_reboot = self.info.get('action_after_reboot')
1963 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
1964 return XEN_API_ON_NORMAL_EXIT[-1]
1965 return after_reboot
1967 def get_on_suspend(self):
1968 # TODO: not supported
1969 after_suspend = self.info.get('action_after_suspend')
1970 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
1971 return XEN_API_ON_NORMAL_EXIT[-1]
1972 return after_suspend
1974 def get_on_crash(self):
1975 after_crash = self.info.get('action_after_crash')
1976 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
1977 return XEN_API_ON_CRASH_BEHAVIOUR[0]
1978 return after_crash
1980 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
1981 """ Get's a device configuration either from XendConfig or
1982 from the DevController.
1984 @param dev_class: device class, either, 'vbd' or 'vif'
1985 @param dev_uuid: device UUID
1987 @rtype: dictionary
1988 """
1989 dev_type_config = self.info['devices'].get(dev_uuid)
1991 # shortcut if the domain isn't started because
1992 # the devcontrollers will have no better information
1993 # than XendConfig.
1994 if self.state in (XEN_API_VM_POWER_STATE_HALTED,):
1995 if dev_type_config:
1996 return copy.deepcopy(dev_type_config[1])
1997 return None
1999 # instead of using dev_class, we use the dev_type
2000 # that is from XendConfig.
2001 # This will accomdate 'tap' as well as 'vbd'
2002 dev_type = dev_type_config[0]
2004 controller = self.getDeviceController(dev_type)
2005 if not controller:
2006 return None
2008 all_configs = controller.getAllDeviceConfigurations()
2009 if not all_configs:
2010 return None
2012 dev_config = copy.deepcopy(dev_type_config[1])
2013 for _devid, _devcfg in all_configs.items():
2014 if _devcfg.get('uuid') == dev_uuid:
2015 dev_config.update(_devcfg)
2016 dev_config['id'] = _devid
2017 return dev_config
2019 return dev_config
2021 def get_dev_xenapi_config(self, dev_class, dev_uuid):
2022 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
2023 if not config:
2024 return {}
2026 config['VM'] = self.get_uuid()
2028 if dev_class == 'vif':
2029 if not config.has_key('name'):
2030 config['name'] = config.get('vifname', '')
2031 if not config.has_key('MAC'):
2032 config['MAC'] = config.get('mac', '')
2033 if not config.has_key('type'):
2034 config['type'] = 'paravirtualised'
2035 if not config.has_key('device'):
2036 devid = config.get('id')
2037 if devid != None:
2038 config['device'] = 'eth%d' % devid
2039 else:
2040 config['device'] = ''
2042 if not config.has_key('network'):
2043 try:
2044 config['network'] = \
2045 XendNode.instance().bridge_to_network(
2046 config.get('bridge')).uuid
2047 except Exception:
2048 log.exception('bridge_to_network')
2049 # Ignore this for now -- it may happen if the device
2050 # has been specified using the legacy methods, but at
2051 # some point we're going to have to figure out how to
2052 # handle that properly.
2054 config['MTU'] = 1500 # TODO
2055 config['io_read_kbs'] = 0.0
2056 config['io_write_kbs'] = 0.0
2058 if dev_class == 'vbd':
2059 config['VDI'] = config.get('VDI', '')
2060 config['device'] = config.get('dev', '')
2061 if ':' in config['device']:
2062 vbd_name, vbd_type = config['device'].split(':', 1)
2063 config['device'] = vbd_name
2064 if vbd_type == 'cdrom':
2065 config['type'] = XEN_API_VBD_TYPE[0]
2066 else:
2067 config['type'] = XEN_API_VBD_TYPE[1]
2069 config['driver'] = 'paravirtualised' # TODO
2070 config['image'] = config.get('uname', '')
2071 config['io_read_kbs'] = 0.0
2072 config['io_write_kbs'] = 0.0
2073 if config.get('mode', 'r') == 'r':
2074 config['mode'] = 'RO'
2075 else:
2076 config['mode'] = 'RW'
2078 if dev_class == 'vtpm':
2079 config['driver'] = 'paravirtualised' # TODO
2081 return config
2083 def get_dev_property(self, dev_class, dev_uuid, field):
2084 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
2085 try:
2086 return config[field]
2087 except KeyError:
2088 raise XendError('Invalid property for device: %s' % field)
2090 def get_vcpus_util(self):
2091 # TODO: this returns the total accum cpu time, rather than util
2092 # TODO: spec says that key is int, however, python does not allow
2093 # non-string keys to dictionaries.
2094 vcpu_util = {}
2095 if 'vcpus_number' in self.info and self.domid != None:
2096 for i in range(0, self.info['vcpus_number']):
2097 info = xc.vcpu_getinfo(self.domid, i)
2098 vcpu_util[str(i)] = info['cpu_time']/1000000000.0
2100 return vcpu_util
2102 def get_consoles(self):
2103 return self.info.get('console_refs', [])
2105 def get_vifs(self):
2106 return self.info.get('vif_refs', [])
2108 def get_vbds(self):
2109 return self.info.get('vbd_refs', [])
2111 def get_vtpms(self):
2112 return self.info.get('vtpm_refs', [])
2114 def create_vbd(self, xenapi_vbd):
2115 """Create a VBD device from the passed struct in Xen API format.
2117 @return: uuid of the device
2118 @rtype: string
2119 """
2121 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
2122 if not dev_uuid:
2123 raise XendError('Failed to create device')
2125 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2126 _, config = self.info['devices'][dev_uuid]
2127 config['devid'] = self.getDeviceController('vbd').createDevice(config)
2129 return dev_uuid
2131 def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
2132 """Create a VBD using a VDI from XendStorageRepository.
2134 @param xenapi_vbd: vbd struct from the Xen API
2135 @param vdi_image_path: VDI UUID
2136 @rtype: string
2137 @return: uuid of the device
2138 """
2139 xenapi_vbd['image'] = vdi_image_path
2140 log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
2141 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
2142 if not dev_uuid:
2143 raise XendError('Failed to create device')
2145 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2146 _, config = self.info['devices'][dev_uuid]
2147 config['devid'] = self.getDeviceController('tap').createDevice(config)
2149 return dev_uuid
2151 def create_vif(self, xenapi_vif):
2152 """Create VIF device from the passed struct in Xen API format.
2154 @param xenapi_vif: Xen API VIF Struct.
2155 @rtype: string
2156 @return: UUID
2157 """
2158 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
2159 if not dev_uuid:
2160 raise XendError('Failed to create device')
2162 if self.state == XEN_API_VM_POWER_STATE_RUNNING:
2163 _, config = self.info['devices'][dev_uuid]
2164 config['devid'] = self.getDeviceController('vif').createDevice(config)
2166 return dev_uuid
2168 def create_vtpm(self, xenapi_vtpm):
2169 """Create a VTPM device from the passed struct in Xen API format.
2171 @return: uuid of the device
2172 @rtype: string
2173 """
2175 if self.state not in (DOM_STATE_HALTED,):
2176 raise VmError("Can only add vTPM to a halted domain.")
2177 if self.get_vtpms() != []:
2178 raise VmError('Domain already has a vTPM.')
2179 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
2180 if not dev_uuid:
2181 raise XendError('Failed to create device')
2183 return dev_uuid
2185 def has_device(self, dev_class, dev_uuid):
2186 return (dev_uuid in self.info['%s_refs' % dev_class.lower()])
2188 def __str__(self):
2189 return '<domain id=%s name=%s memory=%s state=%s>' % \
2190 (str(self.domid), self.info['name_label'],
2191 str(self.info['memory_static_min']), DOM_STATES[self.state])
2193 __repr__ = __str__