ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 15749:d8b5b02c52cf

[HVM] [TOOLS] Move device state save earlier in suspend path
Signed-off-by: Zhai Edwin <edwin.zhai@intel.com>
author Tim Deegan <Tim.Deegan@xensource.com>
date Mon Aug 13 16:47:11 2007 +0100 (2007-08-13)
parents 95f90f24f3b1
children 2ece8ff05ce7
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-2007 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 import traceback
34 from types import StringTypes
36 import xen.lowlevel.xc
37 from xen.util import asserts
38 from xen.util.blkif import blkdev_uname_to_file, blkdev_uname_to_taptype
39 from xen.util import security
41 from xen.xend import balloon, sxp, uuid, image, arch, osdep
42 from xen.xend import XendOptions, XendNode, XendConfig
44 from xen.xend.XendConfig import scrub_password
45 from xen.xend.XendBootloader import bootloader, bootloader_tidy
46 from xen.xend.XendError import XendError, VmError
47 from xen.xend.XendDevices import XendDevices
48 from xen.xend.XendTask import XendTask
49 from xen.xend.xenstore.xstransact import xstransact, complete
50 from xen.xend.xenstore.xsutil import GetDomainPath, IntroduceDomain, ResumeDomain
51 from xen.xend.xenstore.xswatch import xswatch
52 from xen.xend.XendConstants import *
53 from xen.xend.XendAPIConstants import *
55 from xen.xend.XendVMMetrics import XendVMMetrics
57 MIGRATE_TIMEOUT = 30.0
58 BOOTLOADER_LOOPBACK_DEVICE = '/dev/xvdp'
60 xc = xen.lowlevel.xc.xc()
61 xoptions = XendOptions.instance()
63 log = logging.getLogger("xend.XendDomainInfo")
64 #log.setLevel(logging.TRACE)
67 def create(config):
68 """Creates and start a VM using the supplied configuration.
70 @param config: A configuration object involving lists of tuples.
71 @type config: list of lists, eg ['vm', ['image', 'xen.gz']]
73 @rtype: XendDomainInfo
74 @return: An up and running XendDomainInfo instance
75 @raise VmError: Invalid configuration or failure to start.
76 """
78 log.debug("XendDomainInfo.create(%s)", scrub_password(config))
79 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config))
80 try:
81 vm.start()
82 except:
83 log.exception('Domain construction failed')
84 vm.destroy()
85 raise
87 return vm
89 def create_from_dict(config_dict):
90 """Creates and start a VM using the supplied configuration.
92 @param config_dict: An configuration dictionary.
94 @rtype: XendDomainInfo
95 @return: An up and running XendDomainInfo instance
96 @raise VmError: Invalid configuration or failure to start.
97 """
99 log.debug("XendDomainInfo.create_from_dict(%s)",
100 scrub_password(config_dict))
101 vm = XendDomainInfo(XendConfig.XendConfig(xapi = config_dict))
102 try:
103 vm.start()
104 except:
105 log.exception('Domain construction failed')
106 vm.destroy()
107 raise
108 return vm
110 def recreate(info, priv):
111 """Create the VM object for an existing domain. The domain must not
112 be dying, as the paths in the store should already have been removed,
113 and asking us to recreate them causes problems.
115 @param xeninfo: Parsed configuration
116 @type xeninfo: Dictionary
117 @param priv: Is a privileged domain (Dom 0)
118 @type priv: bool
120 @rtype: XendDomainInfo
121 @return: A up and running XendDomainInfo instance
122 @raise VmError: Invalid configuration.
123 @raise XendError: Errors with configuration.
124 """
126 log.debug("XendDomainInfo.recreate(%s)", scrub_password(info))
128 assert not info['dying']
130 xeninfo = XendConfig.XendConfig(dominfo = info)
131 xeninfo['is_control_domain'] = priv
132 xeninfo['is_a_template'] = False
133 domid = xeninfo['domid']
134 uuid1 = uuid.fromString(xeninfo['uuid'])
135 needs_reinitialising = False
137 dompath = GetDomainPath(domid)
138 if not dompath:
139 raise XendError('No domain path in store for existing '
140 'domain %d' % domid)
142 log.info("Recreating domain %d, UUID %s. at %s" %
143 (domid, xeninfo['uuid'], dompath))
145 # need to verify the path and uuid if not Domain-0
146 # if the required uuid and vm aren't set, then that means
147 # we need to recreate the dom with our own values
148 #
149 # NOTE: this is probably not desirable, really we should just
150 # abort or ignore, but there may be cases where xenstore's
151 # entry disappears (eg. xenstore-rm /)
152 #
153 try:
154 vmpath = xstransact.Read(dompath, "vm")
155 if not vmpath:
156 if not priv:
157 log.warn('/local/domain/%d/vm is missing. recreate is '
158 'confused, trying our best to recover' % domid)
159 needs_reinitialising = True
160 raise XendError('reinit')
162 uuid2_str = xstransact.Read(vmpath, "uuid")
163 if not uuid2_str:
164 log.warn('%s/uuid/ is missing. recreate is confused, '
165 'trying our best to recover' % vmpath)
166 needs_reinitialising = True
167 raise XendError('reinit')
169 uuid2 = uuid.fromString(uuid2_str)
170 if uuid1 != uuid2:
171 log.warn('UUID in /vm does not match the UUID in /dom/%d.'
172 'Trying out best to recover' % domid)
173 needs_reinitialising = True
174 except XendError:
175 pass # our best shot at 'goto' in python :)
177 vm = XendDomainInfo(xeninfo, domid, dompath, augment = True, priv = priv)
179 if needs_reinitialising:
180 vm._recreateDom()
181 vm._removeVm()
182 vm._storeVmDetails()
183 vm._storeDomDetails()
185 vm.image = image.create(vm, vm.info)
186 vm.image.recreate()
188 vm._registerWatches()
189 vm.refreshShutdown(xeninfo)
191 # register the domain in the list
192 from xen.xend import XendDomain
193 XendDomain.instance().add_domain(vm)
195 return vm
198 def restore(config):
199 """Create a domain and a VM object to do a restore.
201 @param config: Domain SXP configuration
202 @type config: list of lists. (see C{create})
204 @rtype: XendDomainInfo
205 @return: A up and running XendDomainInfo instance
206 @raise VmError: Invalid configuration or failure to start.
207 @raise XendError: Errors with configuration.
208 """
210 log.debug("XendDomainInfo.restore(%s)", scrub_password(config))
211 vm = XendDomainInfo(XendConfig.XendConfig(sxp_obj = config),
212 resume = True)
213 try:
214 vm.resume()
215 return vm
216 except:
217 vm.destroy()
218 raise
220 def createDormant(domconfig):
221 """Create a dormant/inactive XenDomainInfo without creating VM.
222 This is for creating instances of persistent domains that are not
223 yet start.
225 @param domconfig: Parsed configuration
226 @type domconfig: XendConfig object
228 @rtype: XendDomainInfo
229 @return: A up and running XendDomainInfo instance
230 @raise XendError: Errors with configuration.
231 """
233 log.debug("XendDomainInfo.createDormant(%s)", scrub_password(domconfig))
235 # domid does not make sense for non-running domains.
236 domconfig.pop('domid', None)
237 vm = XendDomainInfo(domconfig)
238 return vm
240 def domain_by_name(name):
241 """Get domain by name
243 @params name: Name of the domain
244 @type name: string
245 @return: XendDomainInfo or None
246 """
247 from xen.xend import XendDomain
248 return XendDomain.instance().domain_lookup_by_name_nr(name)
251 def shutdown_reason(code):
252 """Get a shutdown reason from a code.
254 @param code: shutdown code
255 @type code: int
256 @return: shutdown reason
257 @rtype: string
258 """
259 return DOMAIN_SHUTDOWN_REASONS.get(code, "?")
261 def dom_get(dom):
262 """Get info from xen for an existing domain.
264 @param dom: domain id
265 @type dom: int
266 @return: info or None
267 @rtype: dictionary
268 """
269 try:
270 domlist = xc.domain_getinfo(dom, 1)
271 if domlist and dom == domlist[0]['domid']:
272 return domlist[0]
273 except Exception, err:
274 # ignore missing domain
275 log.trace("domain_getinfo(%d) failed, ignoring: %s", dom, str(err))
276 return None
279 class XendDomainInfo:
280 """An object represents a domain.
282 @TODO: try to unify dom and domid, they mean the same thing, but
283 xc refers to it as dom, and everywhere else, including
284 xenstore it is domid. The best way is to change xc's
285 python interface.
287 @ivar info: Parsed configuration
288 @type info: dictionary
289 @ivar domid: Domain ID (if VM has started)
290 @type domid: int or None
291 @ivar vmpath: XenStore path to this VM.
292 @type vmpath: string
293 @ivar dompath: XenStore path to this Domain.
294 @type dompath: string
295 @ivar image: Reference to the VM Image.
296 @type image: xen.xend.image.ImageHandler
297 @ivar store_port: event channel to xenstored
298 @type store_port: int
299 @ivar console_port: event channel to xenconsoled
300 @type console_port: int
301 @ivar store_mfn: xenstored mfn
302 @type store_mfn: int
303 @ivar console_mfn: xenconsoled mfn
304 @type console_mfn: int
305 @ivar notes: OS image notes
306 @type notes: dictionary
307 @ivar vmWatch: reference to a watch on the xenstored vmpath
308 @type vmWatch: xen.xend.xenstore.xswatch
309 @ivar shutdownWatch: reference to watch on the xenstored domain shutdown
310 @type shutdownWatch: xen.xend.xenstore.xswatch
311 @ivar shutdownStartTime: UNIX Time when domain started shutting down.
312 @type shutdownStartTime: float or None
313 # @ivar state: Domain state
314 # @type state: enum(DOM_STATE_HALTED, DOM_STATE_RUNNING, ...)
315 @ivar state_updated: lock for self.state
316 @type state_updated: threading.Condition
317 @ivar refresh_shutdown_lock: lock for polling shutdown state
318 @type refresh_shutdown_lock: threading.Condition
319 @ivar _deviceControllers: device controller cache for this domain
320 @type _deviceControllers: dict 'string' to DevControllers
321 """
323 def __init__(self, info, domid = None, dompath = None, augment = False,
324 priv = False, resume = False):
325 """Constructor for a domain
327 @param info: parsed configuration
328 @type info: dictionary
329 @keyword domid: Set initial domain id (if any)
330 @type domid: int
331 @keyword dompath: Set initial dompath (if any)
332 @type dompath: string
333 @keyword augment: Augment given info with xenstored VM info
334 @type augment: bool
335 @keyword priv: Is a privileged domain (Dom 0)
336 @type priv: bool
337 @keyword resume: Is this domain being resumed?
338 @type resume: bool
339 """
341 self.info = info
342 if domid == None:
343 self.domid = self.info.get('domid')
344 else:
345 self.domid = domid
347 #REMOVE: uuid is now generated in XendConfig
348 #if not self._infoIsSet('uuid'):
349 # self.info['uuid'] = uuid.toString(uuid.create())
351 self.vmpath = XS_VMROOT + self.info['uuid']
352 self.dompath = dompath
354 self.image = None
355 self.store_port = None
356 self.store_mfn = None
357 self.console_port = None
358 self.console_mfn = None
360 self.native_protocol = None
362 self.vmWatch = None
363 self.shutdownWatch = None
364 self.shutdownStartTime = None
365 self._resume = resume
367 self.state_updated = threading.Condition()
368 self.refresh_shutdown_lock = threading.Condition()
369 self._stateSet(DOM_STATE_HALTED)
371 self._deviceControllers = {}
373 for state in DOM_STATES_OLD:
374 self.info[state] = 0
376 if augment:
377 self._augmentInfo(priv)
379 self._checkName(self.info['name_label'])
381 self.metrics = XendVMMetrics(uuid.createString(), self)
384 #
385 # Public functions available through XMLRPC
386 #
389 def start(self, is_managed = False):
390 """Attempts to start the VM by do the appropriate
391 initialisation if it not started.
392 """
393 from xen.xend import XendDomain
395 if self._stateGet() in (XEN_API_VM_POWER_STATE_HALTED, XEN_API_VM_POWER_STATE_SUSPENDED):
396 try:
397 XendTask.log_progress(0, 30, self._constructDomain)
398 XendTask.log_progress(31, 60, self._initDomain)
400 XendTask.log_progress(61, 70, self._storeVmDetails)
401 XendTask.log_progress(71, 80, self._storeDomDetails)
402 XendTask.log_progress(81, 90, self._registerWatches)
403 XendTask.log_progress(91, 100, self.refreshShutdown)
405 xendomains = XendDomain.instance()
406 xennode = XendNode.instance()
408 # save running configuration if XendDomains believe domain is
409 # persistent
410 if is_managed:
411 xendomains.managed_config_save(self)
413 if xennode.xenschedinfo() == 'credit':
414 xendomains.domain_sched_credit_set(self.getDomid(),
415 self.getWeight(),
416 self.getCap())
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 state = self._stateGet()
427 if state in (DOM_STATE_SUSPENDED, DOM_STATE_HALTED):
428 try:
429 self._constructDomain()
430 self._storeVmDetails()
431 self._createDevices()
432 self._createChannels()
433 self._storeDomDetails()
434 self._endRestore()
435 except:
436 log.exception('VM resume failed')
437 self.destroy()
438 raise
439 else:
440 raise XendError('VM is not susupened; it is %s'
441 % XEN_API_VM_POWER_STATE[state])
443 def shutdown(self, reason):
444 """Shutdown a domain by signalling this via xenstored."""
445 log.debug('XendDomainInfo.shutdown(%s)', reason)
446 if self._stateGet() in (DOM_STATE_SHUTDOWN, DOM_STATE_HALTED,):
447 raise XendError('Domain cannot be shutdown')
449 if self.domid == 0:
450 raise XendError('Domain 0 cannot be shutdown')
452 if reason not in DOMAIN_SHUTDOWN_REASONS.values():
453 raise XendError('Invalid reason: %s' % reason)
454 self._removeVm('xend/previous_restart_time')
455 self.storeDom("control/shutdown", reason)
457 # HVM domain shuts itself down only if it has PV drivers
458 if self.info.is_hvm():
459 hvm_pvdrv = xc.hvm_get_param(self.domid, HVM_PARAM_CALLBACK_IRQ)
460 if not hvm_pvdrv:
461 code = REVERSE_DOMAIN_SHUTDOWN_REASONS[reason]
462 xc.domain_destroy_hook(self.domid)
463 log.info("HVM save:remote shutdown dom %d!", self.domid)
464 xc.domain_shutdown(self.domid, code)
466 def pause(self):
467 """Pause domain
469 @raise XendError: Failed pausing a domain
470 """
471 try:
472 xc.domain_pause(self.domid)
473 self._stateSet(DOM_STATE_PAUSED)
474 except Exception, ex:
475 log.exception(ex)
476 raise XendError("Domain unable to be paused: %s" % str(ex))
478 def unpause(self):
479 """Unpause domain
481 @raise XendError: Failed unpausing a domain
482 """
483 try:
484 xc.domain_unpause(self.domid)
485 self._stateSet(DOM_STATE_RUNNING)
486 except Exception, ex:
487 log.exception(ex)
488 raise XendError("Domain unable to be unpaused: %s" % str(ex))
490 def send_sysrq(self, key):
491 """ Send a Sysrq equivalent key via xenstored."""
492 if self._stateGet() not in (DOM_STATE_RUNNING, DOM_STATE_PAUSED):
493 raise XendError("Domain '%s' is not started" % self.info['name_label'])
495 asserts.isCharConvertible(key)
496 self.storeDom("control/sysrq", '%c' % key)
498 def device_create(self, dev_config):
499 """Create a new device.
501 @param dev_config: device configuration
502 @type dev_config: SXP object (parsed config)
503 """
504 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config))
505 dev_type = sxp.name(dev_config)
506 dev_uuid = self.info.device_add(dev_type, cfg_sxp = dev_config)
507 dev_config_dict = self.info['devices'][dev_uuid][1]
508 log.debug("XendDomainInfo.device_create: %s" % scrub_password(dev_config_dict))
510 if self.domid is not None:
511 try:
512 dev_config_dict['devid'] = devid = \
513 self._createDevice(dev_type, dev_config_dict)
514 self._waitForDevice(dev_type, devid)
515 except VmError, ex:
516 raise ex
517 else:
518 devid = None
520 xen.xend.XendDomain.instance().managed_config_save(self)
521 return self.getDeviceController(dev_type).sxpr(devid)
523 def device_configure(self, dev_sxp, devid = None):
524 """Configure an existing device.
526 @param dev_config: device configuration
527 @type dev_config: SXP object (parsed config)
528 @param devid: device id
529 @type devid: int
530 @return: Returns True if successfully updated device
531 @rtype: boolean
532 """
534 # convert device sxp to a dict
535 dev_class = sxp.name(dev_sxp)
536 dev_config = {}
537 for opt_val in dev_sxp[1:]:
538 try:
539 dev_config[opt_val[0]] = opt_val[1]
540 except IndexError:
541 pass
543 # use DevController.reconfigureDevice to change device config
544 dev_control = self.getDeviceController(dev_class)
545 dev_uuid = dev_control.reconfigureDevice(devid, dev_config)
547 # update XendConfig with new device info
548 if dev_uuid:
549 self.info.device_update(dev_uuid, dev_sxp)
551 return True
553 def waitForDevices(self):
554 """Wait for this domain's configured devices to connect.
556 @raise VmError: if any device fails to initialise.
557 """
558 for devclass in XendDevices.valid_devices():
559 self.getDeviceController(devclass).waitForDevices()
561 def destroyDevice(self, deviceClass, devid, force = False, rm_cfg = False):
562 log.debug("XendDomainInfo.destroyDevice: deviceClass = %s, device = %s",
563 deviceClass, devid)
565 if rm_cfg:
566 # Convert devid to device number. A device number is
567 # needed to remove its configuration.
568 dev = self.getDeviceController(deviceClass).convertToDeviceNumber(devid)
570 # Save current sxprs. A device number and a backend
571 # path are needed to remove its configuration but sxprs
572 # do not have those after calling destroyDevice.
573 sxprs = self.getDeviceSxprs(deviceClass)
575 rc = None
576 if self.domid is not None:
577 rc = self.getDeviceController(deviceClass).destroyDevice(devid, force)
578 if not force and rm_cfg:
579 # The backend path, other than the device itself,
580 # has to be passed because its accompanied frontend
581 # path may be void until its removal is actually
582 # issued. It is probable because destroyDevice is
583 # issued first.
584 for dev_num, dev_info in sxprs:
585 dev_num = int(dev_num)
586 if dev_num == dev:
587 for x in dev_info:
588 if x[0] == 'backend':
589 backend = x[1]
590 break
591 break
592 self._waitForDevice_destroy(deviceClass, devid, backend)
594 if rm_cfg:
595 if deviceClass == 'vif':
596 if self.domid is not None:
597 for dev_num, dev_info in sxprs:
598 dev_num = int(dev_num)
599 if dev_num == dev:
600 for x in dev_info:
601 if x[0] == 'mac':
602 mac = x[1]
603 break
604 break
605 dev_info = self.getDeviceInfo_vif(mac)
606 else:
607 _, dev_info = sxprs[dev]
608 else: # 'vbd' or 'tap'
609 dev_info = self.getDeviceInfo_vbd(dev)
610 if dev_info is None:
611 return rc
613 dev_uuid = sxp.child_value(dev_info, 'uuid')
614 del self.info['devices'][dev_uuid]
615 self.info['%s_refs' % deviceClass].remove(dev_uuid)
616 xen.xend.XendDomain.instance().managed_config_save(self)
618 return rc
620 def getDeviceSxprs(self, deviceClass):
621 if self._stateGet() in (DOM_STATE_RUNNING, DOM_STATE_PAUSED):
622 return self.getDeviceController(deviceClass).sxprs()
623 else:
624 sxprs = []
625 dev_num = 0
626 for dev_type, dev_info in self.info.all_devices_sxpr():
627 if dev_type == deviceClass:
628 sxprs.append([dev_num, dev_info])
629 dev_num += 1
630 return sxprs
632 def getDeviceInfo_vif(self, mac):
633 for dev_type, dev_info in self.info.all_devices_sxpr():
634 if dev_type != 'vif':
635 continue
636 if mac == sxp.child_value(dev_info, 'mac'):
637 return dev_info
639 def getDeviceInfo_vbd(self, devid):
640 for dev_type, dev_info in self.info.all_devices_sxpr():
641 if dev_type != 'vbd' and dev_type != 'tap':
642 continue
643 dev = sxp.child_value(dev_info, 'dev')
644 dev = dev.split(':')[0]
645 dev = self.getDeviceController(dev_type).convertToDeviceNumber(dev)
646 if devid == dev:
647 return dev_info
650 def setMemoryTarget(self, target):
651 """Set the memory target of this domain.
652 @param target: In MiB.
653 """
654 log.debug("Setting memory target of domain %s (%s) to %d MiB.",
655 self.info['name_label'], str(self.domid), target)
657 MiB = 1024 * 1024
658 self._safe_set_memory('memory_dynamic_min', target * MiB)
659 self._safe_set_memory('memory_dynamic_max', target * MiB)
661 if self.domid >= 0:
662 self.storeVm("memory", target)
663 self.storeDom("memory/target", target << 10)
664 xen.xend.XendDomain.instance().managed_config_save(self)
666 def setMemoryMaximum(self, limit):
667 """Set the maximum memory limit of this domain
668 @param limit: In MiB.
669 """
670 log.debug("Setting memory maximum of domain %s (%s) to %d MiB.",
671 self.info['name_label'], str(self.domid), limit)
673 if limit <= 0:
674 raise XendError('Invalid memory size')
676 MiB = 1024 * 1024
677 self._safe_set_memory('memory_static_max', limit * MiB)
679 if self.domid >= 0:
680 maxmem = int(limit) * 1024
681 try:
682 return xc.domain_setmaxmem(self.domid, maxmem)
683 except Exception, ex:
684 raise XendError(str(ex))
685 xen.xend.XendDomain.instance().managed_config_save(self)
688 def getVCPUInfo(self):
689 try:
690 # We include the domain name and ID, to help xm.
691 sxpr = ['domain',
692 ['domid', self.domid],
693 ['name', self.info['name_label']],
694 ['vcpu_count', self.info['VCPUs_max']]]
696 for i in range(0, self.info['VCPUs_max']):
697 if self.domid is not None:
698 info = xc.vcpu_getinfo(self.domid, i)
700 sxpr.append(['vcpu',
701 ['number', i],
702 ['online', info['online']],
703 ['blocked', info['blocked']],
704 ['running', info['running']],
705 ['cpu_time', info['cpu_time'] / 1e9],
706 ['cpu', info['cpu']],
707 ['cpumap', info['cpumap']]])
708 else:
709 sxpr.append(['vcpu',
710 ['number', i],
711 ['online', 0],
712 ['blocked', 0],
713 ['running', 0],
714 ['cpu_time', 0.0],
715 ['cpu', -1],
716 ['cpumap', self.info['cpus'] and \
717 self.info['cpus'] or range(64)]])
719 return sxpr
721 except RuntimeError, exn:
722 raise XendError(str(exn))
725 def getDomInfo(self):
726 return dom_get(self.domid)
728 #
729 # internal functions ... TODO: re-categorised
730 #
732 def _augmentInfo(self, priv):
733 """Augment self.info, as given to us through L{recreate}, with
734 values taken from the store. This recovers those values known
735 to xend but not to the hypervisor.
736 """
737 augment_entries = XendConfig.LEGACY_XENSTORE_VM_PARAMS[:]
738 if priv:
739 augment_entries.remove('memory')
740 augment_entries.remove('maxmem')
741 augment_entries.remove('vcpus')
742 augment_entries.remove('vcpu_avail')
744 vm_config = self._readVMDetails([(k, XendConfig.LEGACY_CFG_TYPES[k])
745 for k in augment_entries])
747 # make returned lists into a dictionary
748 vm_config = dict(zip(augment_entries, vm_config))
750 for arg in augment_entries:
751 val = vm_config[arg]
752 if val != None:
753 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
754 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
755 self.info[xapiarg] = val
756 elif arg == "memory":
757 self.info["static_memory_min"] = val
758 elif arg == "maxmem":
759 self.info["static_memory_max"] = val
760 else:
761 self.info[arg] = val
763 # For dom0, we ignore any stored value for the vcpus fields, and
764 # read the current value from Xen instead. This allows boot-time
765 # settings to take precedence over any entries in the store.
766 if priv:
767 xeninfo = dom_get(self.domid)
768 self.info['VCPUs_max'] = xeninfo['online_vcpus']
769 self.info['vcpu_avail'] = (1 << xeninfo['online_vcpus']) - 1
771 # read image value
772 image_sxp = self._readVm('image')
773 if image_sxp:
774 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
776 # read devices
777 devices = []
778 for devclass in XendDevices.valid_devices():
779 devconfig = self.getDeviceController(devclass).configurations()
780 if devconfig:
781 devices.extend(devconfig)
783 if not self.info['devices'] and devices is not None:
784 for device in devices:
785 self.info.device_add(device[0], cfg_sxp = device)
787 self._update_consoles()
789 def _update_consoles(self):
790 if self.domid == None or self.domid == 0:
791 return
793 # Update VT100 port if it exists
794 self.console_port = self.readDom('console/port')
795 if self.console_port is not None:
796 serial_consoles = self.info.console_get_all('vt100')
797 if not serial_consoles:
798 cfg = self.info.console_add('vt100', self.console_port)
799 self._createDevice('console', cfg)
800 else:
801 console_uuid = serial_consoles[0].get('uuid')
802 self.info.console_update(console_uuid, 'location',
803 self.console_port)
806 # Update VNC port if it exists and write to xenstore
807 vnc_port = self.readDom('console/vnc-port')
808 if vnc_port is not None:
809 for dev_uuid, (dev_type, dev_info) in self.info['devices'].items():
810 if dev_type == 'vfb':
811 old_location = dev_info.get('location')
812 listen_host = dev_info.get('vnclisten', 'localhost')
813 new_location = '%s:%s' % (listen_host, str(vnc_port))
814 if old_location == new_location:
815 break
817 dev_info['location'] = new_location
818 self.info.device_update(dev_uuid, cfg_xenapi = dev_info)
819 vfb_ctrl = self.getDeviceController('vfb')
820 vfb_ctrl.reconfigureDevice(0, dev_info)
821 break
823 #
824 # Function to update xenstore /vm/*
825 #
827 def _readVm(self, *args):
828 return xstransact.Read(self.vmpath, *args)
830 def _writeVm(self, *args):
831 return xstransact.Write(self.vmpath, *args)
833 def _removeVm(self, *args):
834 return xstransact.Remove(self.vmpath, *args)
836 def _gatherVm(self, *args):
837 return xstransact.Gather(self.vmpath, *args)
839 def storeVm(self, *args):
840 return xstransact.Store(self.vmpath, *args)
842 #
843 # Function to update xenstore /dom/*
844 #
846 def readDom(self, *args):
847 return xstransact.Read(self.dompath, *args)
849 def gatherDom(self, *args):
850 return xstransact.Gather(self.dompath, *args)
852 def _writeDom(self, *args):
853 return xstransact.Write(self.dompath, *args)
855 def _removeDom(self, *args):
856 return xstransact.Remove(self.dompath, *args)
858 def storeDom(self, *args):
859 return xstransact.Store(self.dompath, *args)
861 def _recreateDom(self):
862 complete(self.dompath, lambda t: self._recreateDomFunc(t))
864 def _recreateDomFunc(self, t):
865 t.remove()
866 t.mkdir()
867 t.set_permissions({'dom' : self.domid})
868 t.write('vm', self.vmpath)
870 def _storeDomDetails(self):
871 to_store = {
872 'domid': str(self.domid),
873 'vm': self.vmpath,
874 'name': self.info['name_label'],
875 'console/limit': str(xoptions.get_console_limit() * 1024),
876 'memory/target': str(self.info['memory_dynamic_max'] / 1024),
877 }
879 def f(n, v):
880 if v is not None:
881 if type(v) == bool:
882 to_store[n] = v and "1" or "0"
883 else:
884 to_store[n] = str(v)
886 f('console/port', self.console_port)
887 f('console/ring-ref', self.console_mfn)
888 f('store/port', self.store_port)
889 f('store/ring-ref', self.store_mfn)
891 if arch.type == "x86":
892 f('control/platform-feature-multiprocessor-suspend', True)
894 # elfnotes
895 for n, v in self.info.get_notes().iteritems():
896 n = n.lower().replace('_', '-')
897 if n == 'features':
898 for v in v.split('|'):
899 v = v.replace('_', '-')
900 if v.startswith('!'):
901 f('image/%s/%s' % (n, v[1:]), False)
902 else:
903 f('image/%s/%s' % (n, v), True)
904 else:
905 f('image/%s' % n, v)
907 if self.info.has_key('security_label'):
908 f('security_label', self.info['security_label'])
910 to_store.update(self._vcpuDomDetails())
912 log.debug("Storing domain details: %s", scrub_password(to_store))
914 self._writeDom(to_store)
916 def _vcpuDomDetails(self):
917 def availability(n):
918 if self.info['vcpu_avail'] & (1 << n):
919 return 'online'
920 else:
921 return 'offline'
923 result = {}
924 for v in range(0, self.info['VCPUs_max']):
925 result["cpu/%d/availability" % v] = availability(v)
926 return result
928 #
929 # xenstore watches
930 #
932 def _registerWatches(self):
933 """Register a watch on this VM's entries in the store, and the
934 domain's control/shutdown node, so that when they are changed
935 externally, we keep up to date. This should only be called by {@link
936 #create}, {@link #recreate}, or {@link #restore}, once the domain's
937 details have been written, but before the new instance is returned."""
938 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
939 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
940 self._handleShutdownWatch)
942 def _storeChanged(self, _):
943 log.trace("XendDomainInfo.storeChanged");
945 changed = False
947 # Check whether values in the configuration have
948 # changed in Xenstore.
950 cfg_vm = ['name', 'on_poweroff', 'on_reboot', 'on_crash',
951 'rtc/timeoffset']
953 vm_details = self._readVMDetails([(k,XendConfig.LEGACY_CFG_TYPES[k])
954 for k in cfg_vm])
956 # convert two lists into a python dictionary
957 vm_details = dict(zip(cfg_vm, vm_details))
959 if vm_details['rtc/timeoffset'] == None:
960 vm_details['rtc/timeoffset'] = "0"
962 for arg, val in vm_details.items():
963 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
964 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
965 if val != None and val != self.info[xapiarg]:
966 self.info[xapiarg] = val
967 changed = True
968 elif arg == "memory":
969 if val != None and val != self.info["static_memory_min"]:
970 self.info["static_memory_min"] = val
971 changed = True
972 elif arg == "maxmem":
973 if val != None and val != self.info["static_memory_max"]:
974 self.info["static_memory_max"] = val
975 changed = True
977 # Check whether image definition has been updated
978 image_sxp = self._readVm('image')
979 if image_sxp and image_sxp != sxp.to_string(self.info.image_sxpr()):
980 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
981 changed = True
983 # Check if the rtc offset has changes
984 if vm_details.get("rtc/timeoffset", 0) != self.info["platform"].get("rtc_timeoffset", 0):
985 self.info["platform"]["rtc_timeoffset"] = vm_details.get("rtc/timeoffset", 0)
986 changed = True
988 if changed:
989 # Update the domain section of the store, as this contains some
990 # parameters derived from the VM configuration.
991 self._storeDomDetails()
993 return 1
995 def _handleShutdownWatch(self, _):
996 log.debug('XendDomainInfo.handleShutdownWatch')
998 reason = self.readDom('control/shutdown')
1000 if reason and reason != 'suspend':
1001 sst = self.readDom('xend/shutdown_start_time')
1002 now = time.time()
1003 if sst:
1004 self.shutdownStartTime = float(sst)
1005 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
1006 else:
1007 self.shutdownStartTime = now
1008 self.storeDom('xend/shutdown_start_time', now)
1009 timeout = SHUTDOWN_TIMEOUT
1011 log.trace(
1012 "Scheduling refreshShutdown on domain %d in %ds.",
1013 self.domid, timeout)
1014 threading.Timer(timeout, self.refreshShutdown).start()
1016 return True
1020 # Public Attributes for the VM
1024 def getDomid(self):
1025 return self.domid
1027 def setName(self, name):
1028 self._checkName(name)
1029 self.info['name_label'] = name
1030 self.storeVm("name", name)
1032 def getName(self):
1033 return self.info['name_label']
1035 def getDomainPath(self):
1036 return self.dompath
1038 def getShutdownReason(self):
1039 return self.readDom('control/shutdown')
1041 def getStorePort(self):
1042 """For use only by image.py and XendCheckpoint.py."""
1043 return self.store_port
1045 def getConsolePort(self):
1046 """For use only by image.py and XendCheckpoint.py"""
1047 return self.console_port
1049 def getFeatures(self):
1050 """For use only by image.py."""
1051 return self.info['features']
1053 def getVCpuCount(self):
1054 return self.info['VCPUs_max']
1056 def setVCpuCount(self, vcpus):
1057 if vcpus <= 0:
1058 raise XendError('Invalid VCPUs')
1060 self.info['vcpu_avail'] = (1 << vcpus) - 1
1061 if self.domid >= 0:
1062 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
1063 # update dom differently depending on whether we are adjusting
1064 # vcpu number up or down, otherwise _vcpuDomDetails does not
1065 # disable the vcpus
1066 if self.info['VCPUs_max'] > vcpus:
1067 # decreasing
1068 self._writeDom(self._vcpuDomDetails())
1069 self.info['VCPUs_live'] = vcpus
1070 else:
1071 # same or increasing
1072 self.info['VCPUs_live'] = vcpus
1073 self._writeDom(self._vcpuDomDetails())
1074 else:
1075 self.info['VCPUs_max'] = vcpus
1076 xen.xend.XendDomain.instance().managed_config_save(self)
1077 log.info("Set VCPU count on domain %s to %d", self.info['name_label'],
1078 vcpus)
1080 def getMemoryTarget(self):
1081 """Get this domain's target memory size, in KB."""
1082 return self.info['memory_dynamic_max'] / 1024
1084 def getMemoryMaximum(self):
1085 """Get this domain's maximum memory size, in KB."""
1086 # remember, info now stores memory in bytes
1087 return self.info['memory_static_max'] / 1024
1089 def getResume(self):
1090 return str(self._resume)
1092 def getCap(self):
1093 return self.info.get('cpu_cap', 0)
1095 def setCap(self, cpu_cap):
1096 self.info['cpu_cap'] = cpu_cap
1098 def getWeight(self):
1099 return self.info.get('cpu_weight', 256)
1101 def setWeight(self, cpu_weight):
1102 self.info['cpu_weight'] = cpu_weight
1104 def setResume(self, state):
1105 self._resume = state
1107 def getRestartCount(self):
1108 return self._readVm('xend/restart_count')
1110 def refreshShutdown(self, xeninfo = None):
1111 """ Checks the domain for whether a shutdown is required.
1113 Called from XendDomainInfo and also image.py for HVM images.
1114 """
1116 # If set at the end of this method, a restart is required, with the
1117 # given reason. This restart has to be done out of the scope of
1118 # refresh_shutdown_lock.
1119 restart_reason = None
1121 self.refresh_shutdown_lock.acquire()
1122 try:
1123 if xeninfo is None:
1124 xeninfo = dom_get(self.domid)
1125 if xeninfo is None:
1126 # The domain no longer exists. This will occur if we have
1127 # scheduled a timer to check for shutdown timeouts and the
1128 # shutdown succeeded. It will also occur if someone
1129 # destroys a domain beneath us. We clean up the domain,
1130 # just in case, but we can't clean up the VM, because that
1131 # VM may have migrated to a different domain on this
1132 # machine.
1133 self.cleanupDomain()
1134 self._stateSet(DOM_STATE_HALTED)
1135 return
1137 if xeninfo['dying']:
1138 # Dying means that a domain has been destroyed, but has not
1139 # yet been cleaned up by Xen. This state could persist
1140 # indefinitely if, for example, another domain has some of its
1141 # pages mapped. We might like to diagnose this problem in the
1142 # future, but for now all we do is make sure that it's not us
1143 # holding the pages, by calling cleanupDomain. We can't
1144 # clean up the VM, as above.
1145 self.cleanupDomain()
1146 self._stateSet(DOM_STATE_SHUTDOWN)
1147 return
1149 elif xeninfo['crashed']:
1150 if self.readDom('xend/shutdown_completed'):
1151 # We've seen this shutdown already, but we are preserving
1152 # the domain for debugging. Leave it alone.
1153 return
1155 log.warn('Domain has crashed: name=%s id=%d.',
1156 self.info['name_label'], self.domid)
1157 self._writeVm(LAST_SHUTDOWN_REASON, 'crash')
1159 if xoptions.get_enable_dump():
1160 try:
1161 self.dumpCore()
1162 except XendError:
1163 # This error has been logged -- there's nothing more
1164 # we can do in this context.
1165 pass
1167 restart_reason = 'crash'
1168 self._stateSet(DOM_STATE_HALTED)
1170 elif xeninfo['shutdown']:
1171 self._stateSet(DOM_STATE_SHUTDOWN)
1172 if self.readDom('xend/shutdown_completed'):
1173 # We've seen this shutdown already, but we are preserving
1174 # the domain for debugging. Leave it alone.
1175 return
1177 else:
1178 reason = shutdown_reason(xeninfo['shutdown_reason'])
1180 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
1181 self.info['name_label'], self.domid, reason)
1182 self._writeVm(LAST_SHUTDOWN_REASON, reason)
1184 self._clearRestart()
1186 if reason == 'suspend':
1187 self._stateSet(DOM_STATE_SUSPENDED)
1188 # Don't destroy the domain. XendCheckpoint will do
1189 # this once it has finished. However, stop watching
1190 # the VM path now, otherwise we will end up with one
1191 # watch for the old domain, and one for the new.
1192 self._unwatchVm()
1193 elif reason in ('poweroff', 'reboot'):
1194 restart_reason = reason
1195 else:
1196 self.destroy()
1198 elif self.dompath is None:
1199 # We have yet to manage to call introduceDomain on this
1200 # domain. This can happen if a restore is in progress, or has
1201 # failed. Ignore this domain.
1202 pass
1203 else:
1204 # Domain is alive. If we are shutting it down, log a message
1205 # if it seems unresponsive.
1206 if xeninfo['paused']:
1207 self._stateSet(DOM_STATE_PAUSED)
1208 else:
1209 self._stateSet(DOM_STATE_RUNNING)
1211 if self.shutdownStartTime:
1212 timeout = (SHUTDOWN_TIMEOUT - time.time() +
1213 self.shutdownStartTime)
1214 if (timeout < 0 and not self.readDom('xend/unresponsive')):
1215 log.info(
1216 "Domain shutdown timeout expired: name=%s id=%s",
1217 self.info['name_label'], self.domid)
1218 self.storeDom('xend/unresponsive', 'True')
1219 finally:
1220 self.refresh_shutdown_lock.release()
1222 if restart_reason:
1223 threading.Thread(target = self._maybeRestart,
1224 args = (restart_reason,)).start()
1228 # Restart functions - handling whether we come back up on shutdown.
1231 def _clearRestart(self):
1232 self._removeDom("xend/shutdown_start_time")
1235 def _maybeRestart(self, reason):
1236 # Dispatch to the correct method based upon the configured on_{reason}
1237 # behaviour.
1238 actions = {"destroy" : self.destroy,
1239 "restart" : self._restart,
1240 "preserve" : self._preserve,
1241 "rename-restart" : self._renameRestart}
1243 action_conf = {
1244 'poweroff': 'actions_after_shutdown',
1245 'reboot': 'actions_after_reboot',
1246 'crash': 'actions_after_crash',
1249 action_target = self.info.get(action_conf.get(reason))
1250 func = actions.get(action_target, None)
1251 if func and callable(func):
1252 func()
1253 else:
1254 self.destroy() # default to destroy
1256 def _renameRestart(self):
1257 self._restart(True)
1259 def _restart(self, rename = False):
1260 """Restart the domain after it has exited.
1262 @param rename True if the old domain is to be renamed and preserved,
1263 False if it is to be destroyed.
1264 """
1265 from xen.xend import XendDomain
1267 if self._readVm(RESTART_IN_PROGRESS):
1268 log.error('Xend failed during restart of domain %s. '
1269 'Refusing to restart to avoid loops.',
1270 str(self.domid))
1271 self.destroy()
1272 return
1274 old_domid = self.domid
1275 self._writeVm(RESTART_IN_PROGRESS, 'True')
1277 now = time.time()
1278 rst = self._readVm('xend/previous_restart_time')
1279 if rst:
1280 rst = float(rst)
1281 timeout = now - rst
1282 if timeout < MINIMUM_RESTART_TIME:
1283 log.error(
1284 'VM %s restarting too fast (%f seconds since the last '
1285 'restart). Refusing to restart to avoid loops.',
1286 self.info['name_label'], timeout)
1287 self.destroy()
1288 return
1290 self._writeVm('xend/previous_restart_time', str(now))
1292 try:
1293 if rename:
1294 self._preserveForRestart()
1295 else:
1296 self._unwatchVm()
1297 self.destroyDomain()
1299 # new_dom's VM will be the same as this domain's VM, except where
1300 # the rename flag has instructed us to call preserveForRestart.
1301 # In that case, it is important that we remove the
1302 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1303 # once the new one is available.
1305 new_dom = None
1306 try:
1307 new_dom = XendDomain.instance().domain_create_from_dict(
1308 self.info)
1309 new_dom.unpause()
1310 rst_cnt = self._readVm('xend/restart_count')
1311 rst_cnt = int(rst_cnt) + 1
1312 self._writeVm('xend/restart_count', str(rst_cnt))
1313 new_dom._removeVm(RESTART_IN_PROGRESS)
1314 except:
1315 if new_dom:
1316 new_dom._removeVm(RESTART_IN_PROGRESS)
1317 new_dom.destroy()
1318 else:
1319 self._removeVm(RESTART_IN_PROGRESS)
1320 raise
1321 except:
1322 log.exception('Failed to restart domain %s.', str(old_domid))
1324 def _preserveForRestart(self):
1325 """Preserve a domain that has been shut down, by giving it a new UUID,
1326 cloning the VM details, and giving it a new name. This allows us to
1327 keep this domain for debugging, but restart a new one in its place
1328 preserving the restart semantics (name and UUID preserved).
1329 """
1331 new_uuid = uuid.createString()
1332 new_name = 'Domain-%s' % new_uuid
1333 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1334 self.info['name_label'], self.domid, self.info['uuid'],
1335 new_name, new_uuid)
1336 self._unwatchVm()
1337 self._releaseDevices()
1338 self.info['name_label'] = new_name
1339 self.info['uuid'] = new_uuid
1340 self.vmpath = XS_VMROOT + new_uuid
1341 self._storeVmDetails()
1342 self._preserve()
1345 def _preserve(self):
1346 log.info("Preserving dead domain %s (%d).", self.info['name_label'],
1347 self.domid)
1348 self._unwatchVm()
1349 self.storeDom('xend/shutdown_completed', 'True')
1350 self._stateSet(DOM_STATE_HALTED)
1353 # Debugging ..
1356 def dumpCore(self, corefile = None):
1357 """Create a core dump for this domain.
1359 @raise: XendError if core dumping failed.
1360 """
1362 try:
1363 if not corefile:
1364 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1365 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1366 self.info['name_label'], self.domid)
1368 if os.path.isdir(corefile):
1369 raise XendError("Cannot dump core in a directory: %s" %
1370 corefile)
1372 xc.domain_dumpcore(self.domid, corefile)
1373 except RuntimeError, ex:
1374 corefile_incomp = corefile+'-incomplete'
1375 os.rename(corefile, corefile_incomp)
1376 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1377 self.domid, self.info['name_label'])
1378 raise XendError("Failed to dump core: %s" % str(ex))
1381 # Device creation/deletion functions
1384 def _createDevice(self, deviceClass, devConfig):
1385 return self.getDeviceController(deviceClass).createDevice(devConfig)
1387 def _waitForDevice(self, deviceClass, devid):
1388 return self.getDeviceController(deviceClass).waitForDevice(devid)
1390 def _waitForDeviceUUID(self, dev_uuid):
1391 deviceClass, config = self.info['devices'].get(dev_uuid)
1392 self._waitForDevice(deviceClass, config['devid'])
1394 def _waitForDevice_destroy(self, deviceClass, devid, backpath):
1395 return self.getDeviceController(deviceClass).waitForDevice_destroy(
1396 devid, backpath)
1398 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1399 return self.getDeviceController(deviceClass).reconfigureDevice(
1400 devid, devconfig)
1402 def _createDevices(self):
1403 """Create the devices for a vm.
1405 @raise: VmError for invalid devices
1406 """
1407 ordered_refs = self.info.ordered_device_refs()
1408 for dev_uuid in ordered_refs:
1409 devclass, config = self.info['devices'][dev_uuid]
1410 if devclass in XendDevices.valid_devices():
1411 log.info("createDevice: %s : %s" % (devclass, scrub_password(config)))
1412 dev_uuid = config.get('uuid')
1413 devid = self._createDevice(devclass, config)
1415 # store devid in XendConfig for caching reasons
1416 if dev_uuid in self.info['devices']:
1417 self.info['devices'][dev_uuid][1]['devid'] = devid
1419 if self.image:
1420 self.image.createDeviceModel()
1422 def _releaseDevices(self, suspend = False):
1423 """Release all domain's devices. Nothrow guarantee."""
1424 if suspend and self.image:
1425 self.image.destroy(suspend)
1426 return
1428 t = xstransact("%s/device" % self.dompath)
1429 for devclass in XendDevices.valid_devices():
1430 for dev in t.list(devclass):
1431 try:
1432 log.debug("Removing %s", dev);
1433 self.destroyDevice(devclass, dev, False);
1434 except:
1435 # Log and swallow any exceptions in removal --
1436 # there's nothing more we can do.
1437 log.exception("Device release failed: %s; %s; %s",
1438 self.info['name_label'], devclass, dev)
1442 def getDeviceController(self, name):
1443 """Get the device controller for this domain, and if it
1444 doesn't exist, create it.
1446 @param name: device class name
1447 @type name: string
1448 @rtype: subclass of DevController
1449 """
1450 if name not in self._deviceControllers:
1451 devController = XendDevices.make_controller(name, self)
1452 if not devController:
1453 raise XendError("Unknown device type: %s" % name)
1454 self._deviceControllers[name] = devController
1456 return self._deviceControllers[name]
1459 # Migration functions (public)
1462 def testMigrateDevices(self, network, dst):
1463 """ Notify all device about intention of migration
1464 @raise: XendError for a device that cannot be migrated
1465 """
1466 for (n, c) in self.info.all_devices_sxpr():
1467 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1468 if rc != 0:
1469 raise XendError("Device of type '%s' refuses migration." % n)
1471 def migrateDevices(self, network, dst, step, domName=''):
1472 """Notify the devices about migration
1473 """
1474 ctr = 0
1475 try:
1476 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1477 self.migrateDevice(dev_type, dev_conf, network, dst,
1478 step, domName)
1479 ctr = ctr + 1
1480 except:
1481 for dev_type, dev_conf in self.info.all_devices_sxpr():
1482 if ctr == 0:
1483 step = step - 1
1484 ctr = ctr - 1
1485 self._recoverMigrateDevice(dev_type, dev_conf, network,
1486 dst, step, domName)
1487 raise
1489 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1490 step, domName=''):
1491 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1492 network, dst, step, domName)
1494 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1495 dst, step, domName=''):
1496 return self.getDeviceController(deviceClass).recover_migrate(
1497 deviceConfig, network, dst, step, domName)
1500 ## private:
1502 def _constructDomain(self):
1503 """Construct the domain.
1505 @raise: VmError on error
1506 """
1508 log.debug('XendDomainInfo.constructDomain')
1510 self.shutdownStartTime = None
1512 hvm = self.info.is_hvm()
1513 if hvm:
1514 info = xc.xeninfo()
1515 if 'hvm' not in info['xen_caps']:
1516 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1517 "supported by your CPU and enabled in your "
1518 "BIOS?")
1520 # Hack to pre-reserve some memory for initial domain creation.
1521 # There is an implicit memory overhead for any domain creation. This
1522 # overhead is greater for some types of domain than others. For
1523 # example, an x86 HVM domain will have a default shadow-pagetable
1524 # allocation of 1MB. We free up 2MB here to be on the safe side.
1525 balloon.free(2*1024) # 2MB should be plenty
1527 ssidref = 0
1528 if security.on():
1529 ssidref = security.calc_dom_ssidref_from_info(self.info)
1530 if security.has_authorization(ssidref) == False:
1531 raise VmError("VM is not authorized to run.")
1533 try:
1534 self.domid = xc.domain_create(
1535 domid = 0,
1536 ssidref = ssidref,
1537 handle = uuid.fromString(self.info['uuid']),
1538 hvm = int(hvm))
1539 except Exception, e:
1540 # may get here if due to ACM the operation is not permitted
1541 if security.on():
1542 raise VmError('Domain in conflict set with running domain?')
1544 if self.domid < 0:
1545 raise VmError('Creating domain failed: name=%s' %
1546 self.info['name_label'])
1548 self.dompath = GetDomainPath(self.domid)
1550 self._recreateDom()
1552 # Set maximum number of vcpus in domain
1553 xc.domain_max_vcpus(self.domid, int(self.info['VCPUs_max']))
1555 # register the domain in the list
1556 from xen.xend import XendDomain
1557 XendDomain.instance().add_domain(self)
1559 def _introduceDomain(self):
1560 assert self.domid is not None
1561 assert self.store_mfn is not None
1562 assert self.store_port is not None
1564 try:
1565 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1566 except RuntimeError, exn:
1567 raise XendError(str(exn))
1570 def _initDomain(self):
1571 log.debug('XendDomainInfo.initDomain: %s %s',
1572 self.domid,
1573 self.info['cpu_weight'])
1575 self._configureBootloader()
1577 try:
1578 self.image = image.create(self, self.info)
1580 if self.info['platform'].get('localtime', 0):
1581 xc.domain_set_time_offset(self.domid)
1583 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1585 # repin domain vcpus if a restricted cpus list is provided
1586 # this is done prior to memory allocation to aide in memory
1587 # distribution for NUMA systems.
1588 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1589 for v in range(0, self.info['VCPUs_max']):
1590 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1592 # Use architecture- and image-specific calculations to determine
1593 # the various headrooms necessary, given the raw configured
1594 # values. maxmem, memory, and shadow are all in KiB.
1595 # but memory_static_max etc are all stored in bytes now.
1596 memory = self.image.getRequiredAvailableMemory(
1597 self.info['memory_dynamic_max'] / 1024)
1598 maxmem = self.image.getRequiredAvailableMemory(
1599 self.info['memory_static_max'] / 1024)
1600 shadow = self.image.getRequiredShadowMemory(
1601 self.info['shadow_memory'] / 1024,
1602 self.info['memory_static_max'] / 1024)
1604 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'],)
1605 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1606 # takes MiB and we must not round down and end up under-providing.
1607 shadow = ((shadow + 1023) / 1024) * 1024
1609 # set memory limit
1610 xc.domain_setmaxmem(self.domid, maxmem)
1612 # Make sure there's enough RAM available for the domain
1613 balloon.free(memory + shadow)
1615 # Set up the shadow memory
1616 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1617 self.info['shadow_memory'] = shadow_cur
1619 self._createChannels()
1621 channel_details = self.image.createImage()
1623 self.store_mfn = channel_details['store_mfn']
1624 if 'console_mfn' in channel_details:
1625 self.console_mfn = channel_details['console_mfn']
1626 if 'notes' in channel_details:
1627 self.info.set_notes(channel_details['notes'])
1628 if 'native_protocol' in channel_details:
1629 self.native_protocol = channel_details['native_protocol'];
1631 self._introduceDomain()
1633 self._createDevices()
1635 self.image.cleanupBootloading()
1637 self.info['start_time'] = time.time()
1639 self._stateSet(DOM_STATE_RUNNING)
1640 except VmError, exn:
1641 log.exception("XendDomainInfo.initDomain: exception occurred")
1642 if self.image:
1643 self.image.cleanupBootloading()
1644 raise exn
1645 except RuntimeError, exn:
1646 log.exception("XendDomainInfo.initDomain: exception occurred")
1647 if self.image:
1648 self.image.cleanupBootloading()
1649 raise VmError(str(exn))
1652 def cleanupDomain(self):
1653 """Cleanup domain resources; release devices. Idempotent. Nothrow
1654 guarantee."""
1656 self.refresh_shutdown_lock.acquire()
1657 try:
1658 self.unwatchShutdown()
1659 self._releaseDevices()
1660 bootloader_tidy(self)
1662 if self.image:
1663 try:
1664 self.image.destroy()
1665 except:
1666 log.exception(
1667 "XendDomainInfo.cleanup: image.destroy() failed.")
1668 self.image = None
1670 try:
1671 self._removeDom()
1672 except:
1673 log.exception("Removing domain path failed.")
1675 self._stateSet(DOM_STATE_HALTED)
1676 self.domid = None # Do not push into _stateSet()!
1677 finally:
1678 self.refresh_shutdown_lock.release()
1681 def unwatchShutdown(self):
1682 """Remove the watch on the domain's control/shutdown node, if any.
1683 Idempotent. Nothrow guarantee. Expects to be protected by the
1684 refresh_shutdown_lock."""
1686 try:
1687 try:
1688 if self.shutdownWatch:
1689 self.shutdownWatch.unwatch()
1690 finally:
1691 self.shutdownWatch = None
1692 except:
1693 log.exception("Unwatching control/shutdown failed.")
1695 def waitForShutdown(self):
1696 self.state_updated.acquire()
1697 try:
1698 while self._stateGet() in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1699 self.state_updated.wait()
1700 finally:
1701 self.state_updated.release()
1704 # TODO: recategorise - called from XendCheckpoint
1707 def completeRestore(self, store_mfn, console_mfn):
1709 log.debug("XendDomainInfo.completeRestore")
1711 self.store_mfn = store_mfn
1712 self.console_mfn = console_mfn
1714 self._introduceDomain()
1715 if self.info.is_hvm():
1716 self.image = image.create(self, self.info)
1717 if self.image:
1718 self.image.createDeviceModel(True)
1719 self._storeDomDetails()
1720 self._registerWatches()
1721 self.refreshShutdown()
1723 log.debug("XendDomainInfo.completeRestore done")
1726 def _endRestore(self):
1727 self.setResume(False)
1730 # VM Destroy
1733 def _prepare_phantom_paths(self):
1734 # get associated devices to destroy
1735 # build list of phantom devices to be removed after normal devices
1736 plist = []
1737 if self.domid is not None:
1738 from xen.xend.xenstore.xstransact import xstransact
1739 t = xstransact("%s/device/vbd" % GetDomainPath(self.domid))
1740 for dev in t.list():
1741 backend_phantom_vbd = xstransact.Read("%s/device/vbd/%s/phantom_vbd" \
1742 % (self.dompath, dev))
1743 if backend_phantom_vbd is not None:
1744 frontend_phantom_vbd = xstransact.Read("%s/frontend" \
1745 % backend_phantom_vbd)
1746 plist.append(backend_phantom_vbd)
1747 plist.append(frontend_phantom_vbd)
1748 return plist
1750 def _cleanup_phantom_devs(self, plist):
1751 # remove phantom devices
1752 if not plist == []:
1753 time.sleep(2)
1754 for paths in plist:
1755 if paths.find('backend') != -1:
1756 from xen.xend.server import DevController
1757 # Modify online status /before/ updating state (latter is watched by
1758 # drivers, so this ordering avoids a race).
1759 xstransact.Write(paths, 'online', "0")
1760 xstransact.Write(paths, 'state', str(DevController.xenbusState['Closing']))
1761 # force
1762 xstransact.Remove(paths)
1764 def destroy(self):
1765 """Cleanup VM and destroy domain. Nothrow guarantee."""
1767 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1769 paths = self._prepare_phantom_paths()
1771 self._cleanupVm()
1772 if self.dompath is not None:
1773 xc.domain_destroy_hook(self.domid)
1774 self.destroyDomain()
1776 self._cleanup_phantom_devs(paths)
1778 if "transient" in self.info["other_config"] \
1779 and bool(self.info["other_config"]["transient"]):
1780 from xen.xend import XendDomain
1781 XendDomain.instance().domain_delete_by_dominfo(self)
1784 def destroyDomain(self):
1785 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1787 paths = self._prepare_phantom_paths()
1789 try:
1790 if self.domid is not None:
1791 xc.domain_destroy(self.domid)
1792 for state in DOM_STATES_OLD:
1793 self.info[state] = 0
1794 self._stateSet(DOM_STATE_HALTED)
1795 except:
1796 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1798 from xen.xend import XendDomain
1799 XendDomain.instance().remove_domain(self)
1801 self.cleanupDomain()
1802 self._cleanup_phantom_devs(paths)
1805 def resumeDomain(self):
1806 log.debug("XendDomainInfo.resumeDomain(%s)", str(self.domid))
1808 if self.domid is None:
1809 return
1810 try:
1811 # could also fetch a parsed note from xenstore
1812 fast = self.info.get_notes().get('SUSPEND_CANCEL') and 1 or 0
1813 if not fast:
1814 self._releaseDevices()
1815 self.testDeviceComplete()
1816 self.testvifsComplete()
1817 log.debug("XendDomainInfo.resumeDomain: devices released")
1819 self._resetChannels()
1821 self._removeDom('control/shutdown')
1822 self._removeDom('device-misc/vif/nextDeviceID')
1824 self._createChannels()
1825 self._introduceDomain()
1826 self._storeDomDetails()
1828 self._createDevices()
1829 log.debug("XendDomainInfo.resumeDomain: devices created")
1831 xc.domain_resume(self.domid, fast)
1832 ResumeDomain(self.domid)
1833 except:
1834 log.exception("XendDomainInfo.resume: xc.domain_resume failed on domain %s." % (str(self.domid)))
1835 if self.is_hvm():
1836 self.image.resumeDeviceModel()
1840 # Channels for xenstore and console
1843 def _createChannels(self):
1844 """Create the channels to the domain.
1845 """
1846 self.store_port = self._createChannel()
1847 self.console_port = self._createChannel()
1850 def _createChannel(self):
1851 """Create an event channel to the domain.
1852 """
1853 try:
1854 if self.domid != None:
1855 return xc.evtchn_alloc_unbound(domid = self.domid,
1856 remote_dom = 0)
1857 except:
1858 log.exception("Exception in alloc_unbound(%s)", str(self.domid))
1859 raise
1861 def _resetChannels(self):
1862 """Reset all event channels in the domain.
1863 """
1864 try:
1865 if self.domid != None:
1866 return xc.evtchn_reset(dom = self.domid)
1867 except:
1868 log.exception("Exception in evtcnh_reset(%s)", str(self.domid))
1869 raise
1873 # Bootloader configuration
1876 def _configureBootloader(self):
1877 """Run the bootloader if we're configured to do so."""
1879 blexec = self.info['PV_bootloader']
1880 bootloader_args = self.info['PV_bootloader_args']
1881 kernel = self.info['PV_kernel']
1882 ramdisk = self.info['PV_ramdisk']
1883 args = self.info['PV_args']
1884 boot = self.info['HVM_boot_policy']
1886 if boot:
1887 # HVM booting.
1888 pass
1889 elif not blexec and kernel:
1890 # Boot from dom0. Nothing left to do -- the kernel and ramdisk
1891 # will be picked up by image.py.
1892 pass
1893 else:
1894 # Boot using bootloader
1895 if not blexec or blexec == 'pygrub':
1896 blexec = osdep.pygrub_path
1898 blcfg = None
1899 disks = [x for x in self.info['vbd_refs']
1900 if self.info['devices'][x][1]['bootable']]
1902 if not disks:
1903 msg = "Had a bootloader specified, but no disks are bootable"
1904 log.error(msg)
1905 raise VmError(msg)
1907 devinfo = self.info['devices'][disks[0]]
1908 devtype = devinfo[0]
1909 disk = devinfo[1]['uname']
1911 fn = blkdev_uname_to_file(disk)
1912 taptype = blkdev_uname_to_taptype(disk)
1913 mounted = devtype == 'tap' and taptype != 'aio' and taptype != 'sync' and not os.stat(fn).st_rdev
1914 if mounted:
1915 # This is a file, not a device. pygrub can cope with a
1916 # file if it's raw, but if it's QCOW or other such formats
1917 # used through blktap, then we need to mount it first.
1919 log.info("Mounting %s on %s." %
1920 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1922 vbd = {
1923 'mode': 'RO',
1924 'device': BOOTLOADER_LOOPBACK_DEVICE,
1927 from xen.xend import XendDomain
1928 dom0 = XendDomain.instance().privilegedDomain()
1929 dom0._waitForDeviceUUID(dom0.create_vbd(vbd, disk))
1930 fn = BOOTLOADER_LOOPBACK_DEVICE
1932 try:
1933 blcfg = bootloader(blexec, fn, self, False,
1934 bootloader_args, kernel, ramdisk, args)
1935 finally:
1936 if mounted:
1937 log.info("Unmounting %s from %s." %
1938 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1940 dom0.destroyDevice('tap', BOOTLOADER_LOOPBACK_DEVICE)
1942 if blcfg is None:
1943 msg = "Had a bootloader specified, but can't find disk"
1944 log.error(msg)
1945 raise VmError(msg)
1947 self.info.update_with_image_sxp(blcfg, True)
1951 # VM Functions
1954 def _readVMDetails(self, params):
1955 """Read the specified parameters from the store.
1956 """
1957 try:
1958 return self._gatherVm(*params)
1959 except ValueError:
1960 # One of the int/float entries in params has a corresponding store
1961 # entry that is invalid. We recover, because older versions of
1962 # Xend may have put the entry there (memory/target, for example),
1963 # but this is in general a bad situation to have reached.
1964 log.exception(
1965 "Store corrupted at %s! Domain %d's configuration may be "
1966 "affected.", self.vmpath, self.domid)
1967 return []
1969 def _cleanupVm(self):
1970 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1972 self._unwatchVm()
1974 try:
1975 self._removeVm()
1976 except:
1977 log.exception("Removing VM path failed.")
1980 def checkLiveMigrateMemory(self):
1981 """ Make sure there's enough memory to migrate this domain """
1982 overhead_kb = 0
1983 if arch.type == "x86":
1984 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1985 # the minimum that Xen would allocate if no value were given.
1986 overhead_kb = self.info['VCPUs_max'] * 1024 + \
1987 (self.info['memory_static_max'] / 1024 / 1024) * 4
1988 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1989 # The domain might already have some shadow memory
1990 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1991 if overhead_kb > 0:
1992 balloon.free(overhead_kb)
1994 def _unwatchVm(self):
1995 """Remove the watch on the VM path, if any. Idempotent. Nothrow
1996 guarantee."""
1997 try:
1998 try:
1999 if self.vmWatch:
2000 self.vmWatch.unwatch()
2001 finally:
2002 self.vmWatch = None
2003 except:
2004 log.exception("Unwatching VM path failed.")
2006 def testDeviceComplete(self):
2007 """ For Block IO migration safety we must ensure that
2008 the device has shutdown correctly, i.e. all blocks are
2009 flushed to disk
2010 """
2011 start = time.time()
2012 while True:
2013 test = 0
2014 diff = time.time() - start
2015 for i in self.getDeviceController('vbd').deviceIDs():
2016 test = 1
2017 log.info("Dev %s still active, looping...", i)
2018 time.sleep(0.1)
2020 if test == 0:
2021 break
2022 if diff >= MIGRATE_TIMEOUT:
2023 log.info("Dev still active but hit max loop timeout")
2024 break
2026 def testvifsComplete(self):
2027 """ In case vifs are released and then created for the same
2028 domain, we need to wait the device shut down.
2029 """
2030 start = time.time()
2031 while True:
2032 test = 0
2033 diff = time.time() - start
2034 for i in self.getDeviceController('vif').deviceIDs():
2035 test = 1
2036 log.info("Dev %s still active, looping...", i)
2037 time.sleep(0.1)
2039 if test == 0:
2040 break
2041 if diff >= MIGRATE_TIMEOUT:
2042 log.info("Dev still active but hit max loop timeout")
2043 break
2045 def _storeVmDetails(self):
2046 to_store = {}
2048 for key in XendConfig.LEGACY_XENSTORE_VM_PARAMS:
2049 info_key = XendConfig.LEGACY_CFG_TO_XENAPI_CFG.get(key, key)
2050 if self._infoIsSet(info_key):
2051 to_store[key] = str(self.info[info_key])
2053 if self._infoIsSet("static_memory_min"):
2054 to_store["memory"] = str(self.info["static_memory_min"])
2055 if self._infoIsSet("static_memory_max"):
2056 to_store["maxmem"] = str(self.info["static_memory_max"])
2058 image_sxpr = self.info.image_sxpr()
2059 if image_sxpr:
2060 to_store['image'] = sxp.to_string(image_sxpr)
2062 if not self._readVm('xend/restart_count'):
2063 to_store['xend/restart_count'] = str(0)
2065 log.debug("Storing VM details: %s", scrub_password(to_store))
2067 self._writeVm(to_store)
2068 self._setVmPermissions()
2071 def _setVmPermissions(self):
2072 """Allow the guest domain to read its UUID. We don't allow it to
2073 access any other entry, for security."""
2074 xstransact.SetPermissions('%s/uuid' % self.vmpath,
2075 { 'dom' : self.domid,
2076 'read' : True,
2077 'write' : False })
2080 # Utility functions
2083 def __getattr__(self, name):
2084 if name == "state":
2085 log.warn("Somebody tried to read XendDomainInfo.state... should us _stateGet()!!!")
2086 log.warn("".join(traceback.format_stack()))
2087 return self._stateGet()
2088 else:
2089 raise AttributeError()
2091 def __setattr__(self, name, value):
2092 if name == "state":
2093 log.warn("Somebody tried to set XendDomainInfo.state... should us _stateGet()!!!")
2094 log.warn("".join(traceback.format_stack()))
2095 self._stateSet(value)
2096 else:
2097 self.__dict__[name] = value
2099 def _stateSet(self, state):
2100 self.state_updated.acquire()
2101 try:
2102 # TODO Not sure this is correct...
2103 # _stateGet is live now. Why not fire event
2104 # even when it hasn't changed?
2105 if self._stateGet() != state:
2106 self.state_updated.notifyAll()
2107 import XendAPI
2108 XendAPI.event_dispatch('mod', 'VM', self.info['uuid'],
2109 'power_state')
2110 finally:
2111 self.state_updated.release()
2113 def _stateGet(self):
2114 # Lets try and reconsitute the state from xc
2115 # first lets try and get the domain info
2116 # from xc - this will tell us if the domain
2117 # exists
2118 info = dom_get(self.getDomid())
2119 if info is None or info['shutdown']:
2120 # We are either HALTED or SUSPENDED
2121 # check saved image exists
2122 from xen.xend import XendDomain
2123 managed_config_path = \
2124 XendDomain.instance()._managed_check_point_path( \
2125 self.get_uuid())
2126 if os.path.exists(managed_config_path):
2127 return XEN_API_VM_POWER_STATE_SUSPENDED
2128 else:
2129 return XEN_API_VM_POWER_STATE_HALTED
2130 else:
2131 # We are either RUNNING or PAUSED
2132 if info['paused']:
2133 return XEN_API_VM_POWER_STATE_PAUSED
2134 else:
2135 return XEN_API_VM_POWER_STATE_RUNNING
2137 def _infoIsSet(self, name):
2138 return name in self.info and self.info[name] is not None
2140 def _checkName(self, name):
2141 """Check if a vm name is valid. Valid names contain alphabetic
2142 characters, digits, or characters in '_-.:/+'.
2143 The same name cannot be used for more than one vm at the same time.
2145 @param name: name
2146 @raise: VmError if invalid
2147 """
2148 from xen.xend import XendDomain
2150 if name is None or name == '':
2151 raise VmError('Missing VM Name')
2153 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
2154 raise VmError('Invalid VM Name')
2156 dom = XendDomain.instance().domain_lookup_nr(name)
2157 if dom and dom.domid != self.domid:
2158 raise VmError("VM name '%s' already exists%s" %
2159 (name,
2160 dom.domid is not None and
2161 (" as domain %s" % str(dom.domid)) or ""))
2164 def update(self, info = None, refresh = True):
2165 """Update with info from xc.domain_getinfo().
2166 """
2167 log.trace("XendDomainInfo.update(%s) on domain %s", info,
2168 str(self.domid))
2170 if not info:
2171 info = dom_get(self.domid)
2172 if not info:
2173 return
2175 if info["maxmem_kb"] < 0:
2176 info["maxmem_kb"] = XendNode.instance() \
2177 .physinfo_dict()['total_memory'] * 1024
2179 #ssidref field not used any longer
2180 if 'ssidref' in info:
2181 info.pop('ssidref')
2183 # make sure state is reset for info
2184 # TODO: we should eventually get rid of old_dom_states
2186 self.info.update_config(info)
2187 self._update_consoles()
2189 if refresh:
2190 self.refreshShutdown(info)
2192 log.trace("XendDomainInfo.update done on domain %s: %s",
2193 str(self.domid), self.info)
2195 def sxpr(self, ignore_store = False, legacy_only = True):
2196 result = self.info.to_sxp(domain = self,
2197 ignore_devices = ignore_store,
2198 legacy_only = legacy_only)
2200 #if not ignore_store and self.dompath:
2201 # vnc_port = self.readDom('console/vnc-port')
2202 # if vnc_port is not None:
2203 # result.append(['device',
2204 # ['console', ['vnc-port', str(vnc_port)]]])
2206 return result
2208 # Xen API
2209 # ----------------------------------------------------------------
2211 def get_uuid(self):
2212 dom_uuid = self.info.get('uuid')
2213 if not dom_uuid: # if it doesn't exist, make one up
2214 dom_uuid = uuid.createString()
2215 self.info['uuid'] = dom_uuid
2216 return dom_uuid
2218 def get_memory_static_max(self):
2219 return self.info.get('memory_static_max', 0)
2220 def get_memory_static_min(self):
2221 return self.info.get('memory_static_min', 0)
2222 def get_memory_dynamic_max(self):
2223 return self.info.get('memory_dynamic_max', 0)
2224 def get_memory_dynamic_min(self):
2225 return self.info.get('memory_dynamic_min', 0)
2227 # only update memory-related config values if they maintain sanity
2228 def _safe_set_memory(self, key, newval):
2229 oldval = self.info.get(key, 0)
2230 try:
2231 self.info[key] = newval
2232 self.info._memory_sanity_check()
2233 except Exception, ex:
2234 self.info[key] = oldval
2235 raise
2237 def set_memory_static_max(self, val):
2238 self._safe_set_memory('memory_static_max', val)
2239 def set_memory_static_min(self, val):
2240 self._safe_set_memory('memory_static_min', val)
2241 def set_memory_dynamic_max(self, val):
2242 self._safe_set_memory('memory_dynamic_max', val)
2243 def set_memory_dynamic_min(self, val):
2244 self._safe_set_memory('memory_dynamic_min', val)
2246 def get_vcpus_params(self):
2247 if self.getDomid() is None:
2248 return self.info['vcpus_params']
2250 retval = xc.sched_credit_domain_get(self.getDomid())
2251 return retval
2252 def get_power_state(self):
2253 return XEN_API_VM_POWER_STATE[self._stateGet()]
2254 def get_platform(self):
2255 return self.info.get('platform', {})
2256 def get_pci_bus(self):
2257 return self.info.get('pci_bus', '')
2258 def get_tools_version(self):
2259 return self.info.get('tools_version', {})
2260 def get_metrics(self):
2261 return self.metrics.get_uuid();
2264 def get_security_label(self, xspol=None):
2265 """
2266 Get the security label of a domain
2267 @param xspol The policy to use when converting the ssid into
2268 a label; only to be passed during the updating
2269 of the policy
2270 """
2271 domid = self.getDomid()
2273 if not xspol:
2274 from xen.xend.XendXSPolicyAdmin import XSPolicyAdminInstance
2275 xspol = XSPolicyAdminInstance().get_loaded_policy()
2277 if domid == 0:
2278 if xspol:
2279 label = xspol.policy_get_domain_label_formatted(domid)
2280 else:
2281 label = ""
2282 else:
2283 label = self.info.get('security_label', '')
2284 return label
2286 def set_security_label(self, seclab, old_seclab, xspol=None,
2287 xspol_old=None):
2288 """
2289 Set the security label of a domain from its old to
2290 a new value.
2291 @param seclab New security label formatted in the form
2292 <policy type>:<policy name>:<vm label>
2293 @param old_seclab The current security label that the
2294 VM must have.
2295 @param xspol An optional policy under which this
2296 update should be done. If not given,
2297 then the current active policy is used.
2298 @param xspol_old The old policy; only to be passed during
2299 the updating of a policy
2300 @return Returns return code, a string with errors from
2301 the hypervisor's operation, old label of the
2302 domain
2303 """
2304 rc = 0
2305 errors = ""
2306 old_label = ""
2307 new_ssidref = 0
2308 domid = self.getDomid()
2309 res_labels = None
2310 is_policy_update = (xspol_old != None)
2312 from xen.xend.XendXSPolicyAdmin import XSPolicyAdminInstance
2313 from xen.util import xsconstants
2315 state = self._stateGet()
2316 # Relabel only HALTED or RUNNING or PAUSED domains
2317 if domid != 0 and \
2318 state not in \
2319 [ DOM_STATE_HALTED, DOM_STATE_RUNNING, DOM_STATE_PAUSED, \
2320 DOM_STATE_SUSPENDED ]:
2321 log.warn("Relabeling domain not possible in state '%s'" %
2322 DOM_STATES[state])
2323 return (-xsconstants.XSERR_VM_WRONG_STATE, "", "", 0)
2325 # Remove security label. Works only for halted domains
2326 if not seclab or seclab == "":
2327 if state not in [ DOM_STATE_HALTED ]:
2328 return (-xsconstants.XSERR_VM_WRONG_STATE, "", "", 0)
2330 if self.info.has_key('security_label'):
2331 old_label = self.info['security_label']
2332 # Check label against expected one.
2333 if old_label != old_seclab:
2334 return (-xsconstants.XSERR_BAD_LABEL, "", "", 0)
2335 del self.info['security_label']
2336 xen.xend.XendDomain.instance().managed_config_save(self)
2337 return (xsconstants.XSERR_SUCCESS, "", "", 0)
2339 tmp = seclab.split(":")
2340 if len(tmp) != 3:
2341 return (-xsconstants.XSERR_BAD_LABEL_FORMAT, "", "", 0)
2342 typ, policy, label = tmp
2344 poladmin = XSPolicyAdminInstance()
2345 if not xspol:
2346 xspol = poladmin.get_policy_by_name(policy)
2348 if state in [ DOM_STATE_RUNNING, DOM_STATE_PAUSED ]:
2349 #if domain is running or paused try to relabel in hypervisor
2350 if not xspol:
2351 return (-xsconstants.XSERR_POLICY_NOT_LOADED, "", "", 0)
2353 if typ != xspol.get_type_name() or \
2354 policy != xspol.get_name():
2355 return (-xsconstants.XSERR_BAD_LABEL, "", "", 0)
2357 if typ == xsconstants.ACM_POLICY_ID:
2358 new_ssidref = xspol.vmlabel_to_ssidref(label)
2359 if new_ssidref == xsconstants.INVALID_SSIDREF:
2360 return (-xsconstants.XSERR_BAD_LABEL, "", "", 0)
2362 # Check that all used resources are accessible under the
2363 # new label
2364 if not is_policy_update and \
2365 not security.resources_compatible_with_vmlabel(xspol,
2366 self, label):
2367 return (-xsconstants.XSERR_BAD_LABEL, "", "", 0)
2369 #Check label against expected one.
2370 old_label = self.get_security_label(xspol_old)
2371 if old_label != old_seclab:
2372 log.info("old_label != old_seclab: %s != %s" %
2373 (old_label, old_seclab))
2374 return (-xsconstants.XSERR_BAD_LABEL, "", "", 0)
2376 # relabel domain in the hypervisor
2377 rc, errors = security.relabel_domains([[domid, new_ssidref]])
2378 log.info("rc from relabeling in HV: %d" % rc)
2379 else:
2380 return (-xsconstants.XSERR_POLICY_TYPE_UNSUPPORTED, "", "", 0)
2382 if rc == 0:
2383 # HALTED, RUNNING or PAUSED
2384 if domid == 0:
2385 if xspol:
2386 ssidref = poladmin.set_domain0_bootlabel(xspol, label)
2387 else:
2388 return (-xsconstants.XSERR_POLICY_NOT_LOADED, "", "", 0)
2389 else:
2390 if self.info.has_key('security_label'):
2391 old_label = self.info['security_label']
2392 # Check label against expected one, unless wildcard
2393 if old_label != old_seclab:
2394 return (-xsconstants.XSERR_BAD_LABEL, "", "", 0)
2396 self.info['security_label'] = seclab
2397 try:
2398 xen.xend.XendDomain.instance().managed_config_save(self)
2399 except:
2400 pass
2401 return (rc, errors, old_label, new_ssidref)
2403 def get_on_shutdown(self):
2404 after_shutdown = self.info.get('actions_after_shutdown')
2405 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
2406 return XEN_API_ON_NORMAL_EXIT[-1]
2407 return after_shutdown
2409 def get_on_reboot(self):
2410 after_reboot = self.info.get('actions_after_reboot')
2411 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
2412 return XEN_API_ON_NORMAL_EXIT[-1]
2413 return after_reboot
2415 def get_on_suspend(self):
2416 # TODO: not supported
2417 after_suspend = self.info.get('actions_after_suspend')
2418 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
2419 return XEN_API_ON_NORMAL_EXIT[-1]
2420 return after_suspend
2422 def get_on_crash(self):
2423 after_crash = self.info.get('actions_after_crash')
2424 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
2425 return XEN_API_ON_CRASH_BEHAVIOUR[0]
2426 return after_crash
2428 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
2429 """ Get's a device configuration either from XendConfig or
2430 from the DevController.
2432 @param dev_class: device class, either, 'vbd' or 'vif'
2433 @param dev_uuid: device UUID
2435 @rtype: dictionary
2436 """
2437 dev_type, dev_config = self.info['devices'].get(dev_uuid, (None, None))
2439 # shortcut if the domain isn't started because
2440 # the devcontrollers will have no better information
2441 # than XendConfig.
2442 if self._stateGet() in (XEN_API_VM_POWER_STATE_HALTED,):
2443 if dev_config:
2444 return copy.deepcopy(dev_config)
2445 return None
2447 # instead of using dev_class, we use the dev_type
2448 # that is from XendConfig.
2449 controller = self.getDeviceController(dev_type)
2450 if not controller:
2451 return None
2453 all_configs = controller.getAllDeviceConfigurations()
2454 if not all_configs:
2455 return None
2457 updated_dev_config = copy.deepcopy(dev_config)
2458 for _devid, _devcfg in all_configs.items():
2459 if _devcfg.get('uuid') == dev_uuid:
2460 updated_dev_config.update(_devcfg)
2461 updated_dev_config['id'] = _devid
2462 return updated_dev_config
2464 return updated_dev_config
2466 def get_dev_xenapi_config(self, dev_class, dev_uuid):
2467 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
2468 if not config:
2469 return {}
2471 config['VM'] = self.get_uuid()
2473 if dev_class == 'vif':
2474 if not config.has_key('name'):
2475 config['name'] = config.get('vifname', '')
2476 if not config.has_key('MAC'):
2477 config['MAC'] = config.get('mac', '')
2478 if not config.has_key('type'):
2479 config['type'] = 'paravirtualised'
2480 if not config.has_key('device'):
2481 devid = config.get('id')
2482 if devid != None:
2483 config['device'] = 'eth%d' % devid
2484 else:
2485 config['device'] = ''
2487 if not config.has_key('network'):
2488 try:
2489 bridge = config.get('bridge', None)
2490 if bridge is None:
2491 from xen.util import Brctl
2492 if_to_br = dict([(i,b)
2493 for (b,ifs) in Brctl.get_state().items()
2494 for i in ifs])
2495 vifname = "vif%s.%s" % (self.getDomid(),
2496 config.get('id'))
2497 bridge = if_to_br.get(vifname, None)
2498 config['network'] = \
2499 XendNode.instance().bridge_to_network(
2500 config.get('bridge')).get_uuid()
2501 except Exception:
2502 log.exception('bridge_to_network')
2503 # Ignore this for now -- it may happen if the device
2504 # has been specified using the legacy methods, but at
2505 # some point we're going to have to figure out how to
2506 # handle that properly.
2508 config['MTU'] = 1500 # TODO
2510 if self._stateGet() not in (XEN_API_VM_POWER_STATE_HALTED,):
2511 xennode = XendNode.instance()
2512 rx_bps, tx_bps = xennode.get_vif_util(self.domid, devid)
2513 config['io_read_kbs'] = rx_bps/1024
2514 config['io_write_kbs'] = tx_bps/1024
2515 else:
2516 config['io_read_kbs'] = 0.0
2517 config['io_write_kbs'] = 0.0
2519 config['security_label'] = config.get('security_label', '')
2521 if dev_class == 'vbd':
2523 if self._stateGet() not in (XEN_API_VM_POWER_STATE_HALTED,):
2524 controller = self.getDeviceController(dev_class)
2525 devid, _1, _2 = controller.getDeviceDetails(config)
2526 xennode = XendNode.instance()
2527 rd_blkps, wr_blkps = xennode.get_vbd_util(self.domid, devid)
2528 config['io_read_kbs'] = rd_blkps
2529 config['io_write_kbs'] = wr_blkps
2530 else:
2531 config['io_read_kbs'] = 0.0
2532 config['io_write_kbs'] = 0.0
2534 config['VDI'] = config.get('VDI', '')
2535 config['device'] = config.get('dev', '')
2536 if ':' in config['device']:
2537 vbd_name, vbd_type = config['device'].split(':', 1)
2538 config['device'] = vbd_name
2539 if vbd_type == 'cdrom':
2540 config['type'] = XEN_API_VBD_TYPE[0]
2541 else:
2542 config['type'] = XEN_API_VBD_TYPE[1]
2544 config['driver'] = 'paravirtualised' # TODO
2545 config['image'] = config.get('uname', '')
2547 if config.get('mode', 'r') == 'r':
2548 config['mode'] = 'RO'
2549 else:
2550 config['mode'] = 'RW'
2552 if dev_class == 'vtpm':
2553 if not config.has_key('type'):
2554 config['type'] = 'paravirtualised' # TODO
2555 if not config.has_key('backend'):
2556 config['backend'] = "00000000-0000-0000-0000-000000000000"
2558 return config
2560 def get_dev_property(self, dev_class, dev_uuid, field):
2561 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
2562 try:
2563 return config[field]
2564 except KeyError:
2565 raise XendError('Invalid property for device: %s' % field)
2567 def set_dev_property(self, dev_class, dev_uuid, field, value):
2568 self.info['devices'][dev_uuid][1][field] = value
2570 def get_vcpus_util(self):
2571 vcpu_util = {}
2572 xennode = XendNode.instance()
2573 if 'VCPUs_max' in self.info and self.domid != None:
2574 for i in range(0, self.info['VCPUs_max']):
2575 util = xennode.get_vcpu_util(self.domid, i)
2576 vcpu_util[str(i)] = util
2578 return vcpu_util
2580 def get_consoles(self):
2581 return self.info.get('console_refs', [])
2583 def get_vifs(self):
2584 return self.info.get('vif_refs', [])
2586 def get_vbds(self):
2587 return self.info.get('vbd_refs', [])
2589 def get_vtpms(self):
2590 return self.info.get('vtpm_refs', [])
2592 def create_vbd(self, xenapi_vbd, vdi_image_path):
2593 """Create a VBD using a VDI from XendStorageRepository.
2595 @param xenapi_vbd: vbd struct from the Xen API
2596 @param vdi_image_path: VDI UUID
2597 @rtype: string
2598 @return: uuid of the device
2599 """
2600 xenapi_vbd['image'] = vdi_image_path
2601 if vdi_image_path.startswith('tap'):
2602 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
2603 else:
2604 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
2606 if not dev_uuid:
2607 raise XendError('Failed to create device')
2609 if self._stateGet() in (XEN_API_VM_POWER_STATE_RUNNING,
2610 XEN_API_VM_POWER_STATE_PAUSED):
2611 _, config = self.info['devices'][dev_uuid]
2613 if vdi_image_path.startswith('tap'):
2614 dev_control = self.getDeviceController('tap')
2615 else:
2616 dev_control = self.getDeviceController('vbd')
2618 try:
2619 devid = dev_control.createDevice(config)
2620 dev_control.waitForDevice(devid)
2621 self.info.device_update(dev_uuid,
2622 cfg_xenapi = {'devid': devid})
2623 except Exception, exn:
2624 log.exception(exn)
2625 del self.info['devices'][dev_uuid]
2626 self.info['vbd_refs'].remove(dev_uuid)
2627 raise
2629 return dev_uuid
2631 def create_phantom_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
2632 """Create a VBD using a VDI from XendStorageRepository.
2634 @param xenapi_vbd: vbd struct from the Xen API
2635 @param vdi_image_path: VDI UUID
2636 @rtype: string
2637 @return: uuid of the device
2638 """
2639 xenapi_vbd['image'] = vdi_image_path
2640 dev_uuid = self.info.phantom_device_add('tap', cfg_xenapi = xenapi_vbd)
2641 if not dev_uuid:
2642 raise XendError('Failed to create device')
2644 if self._stateGet() == XEN_API_VM_POWER_STATE_RUNNING:
2645 _, config = self.info['devices'][dev_uuid]
2646 config['devid'] = self.getDeviceController('tap').createDevice(config)
2648 return config['devid']
2650 def create_vif(self, xenapi_vif):
2651 """Create VIF device from the passed struct in Xen API format.
2653 @param xenapi_vif: Xen API VIF Struct.
2654 @rtype: string
2655 @return: UUID
2656 """
2657 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
2658 if not dev_uuid:
2659 raise XendError('Failed to create device')
2661 if self._stateGet() in (XEN_API_VM_POWER_STATE_RUNNING,
2662 XEN_API_VM_POWER_STATE_PAUSED):
2664 _, config = self.info['devices'][dev_uuid]
2665 dev_control = self.getDeviceController('vif')
2667 try:
2668 devid = dev_control.createDevice(config)
2669 dev_control.waitForDevice(devid)
2670 self.info.device_update(dev_uuid,
2671 cfg_xenapi = {'devid': devid})
2672 except Exception, exn:
2673 log.exception(exn)
2674 del self.info['devices'][dev_uuid]
2675 self.info['vif_refs'].remove(dev_uuid)
2676 raise
2678 return dev_uuid
2680 def create_vtpm(self, xenapi_vtpm):
2681 """Create a VTPM device from the passed struct in Xen API format.
2683 @return: uuid of the device
2684 @rtype: string
2685 """
2687 if self._stateGet() not in (DOM_STATE_HALTED,):
2688 raise VmError("Can only add vTPM to a halted domain.")
2689 if self.get_vtpms() != []:
2690 raise VmError('Domain already has a vTPM.')
2691 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
2692 if not dev_uuid:
2693 raise XendError('Failed to create device')
2695 return dev_uuid
2697 def create_console(self, xenapi_console):
2698 """ Create a console device from a Xen API struct.
2700 @return: uuid of device
2701 @rtype: string
2702 """
2703 if self._stateGet() not in (DOM_STATE_HALTED,):
2704 raise VmError("Can only add console to a halted domain.")
2706 dev_uuid = self.info.device_add('console', cfg_xenapi = xenapi_console)
2707 if not dev_uuid:
2708 raise XendError('Failed to create device')
2710 return dev_uuid
2712 def set_console_other_config(self, console_uuid, other_config):
2713 self.info.console_update(console_uuid, 'other_config', other_config)
2715 def destroy_device_by_uuid(self, dev_type, dev_uuid):
2716 if dev_uuid not in self.info['devices']:
2717 raise XendError('Device does not exist')
2719 try:
2720 if self._stateGet() in (XEN_API_VM_POWER_STATE_RUNNING,
2721 XEN_API_VM_POWER_STATE_PAUSED):
2722 _, config = self.info['devices'][dev_uuid]
2723 devid = config.get('devid')
2724 if devid != None:
2725 self.getDeviceController(dev_type).destroyDevice(devid, force = False)
2726 else:
2727 raise XendError('Unable to get devid for device: %s:%s' %
2728 (dev_type, dev_uuid))
2729 finally:
2730 del self.info['devices'][dev_uuid]
2731 self.info['%s_refs' % dev_type].remove(dev_uuid)
2733 def destroy_vbd(self, dev_uuid):
2734 self.destroy_device_by_uuid('vbd', dev_uuid)
2736 def destroy_vif(self, dev_uuid):
2737 self.destroy_device_by_uuid('vif', dev_uuid)
2739 def destroy_vtpm(self, dev_uuid):
2740 self.destroy_device_by_uuid('vtpm', dev_uuid)
2742 def has_device(self, dev_class, dev_uuid):
2743 return (dev_uuid in self.info['%s_refs' % dev_class.lower()])
2745 def __str__(self):
2746 return '<domain id=%s name=%s memory=%s state=%s>' % \
2747 (str(self.domid), self.info['name_label'],
2748 str(self.info['memory_dynamic_max']), DOM_STATES[self._stateGet()])
2750 __repr__ = __str__