ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 15788:8e3abd893835

xm: Fix error message for xm create command.

When I tested xm create command, I saw the following error message.
I expected an error message "Error: (12, 'Cannot allocate memory')"
because I intentionally caused a memory shortage on the test.
But the error message was different from my expectation.

# xm create /xen/HVMdomain.1
Using config file "/xen/HVMdomain.1".
Error: an integer is required

I looked at xend.log to examine the cause why the error message was
shown. (Could you see the attached xend.log?)
xend had the error message "Error: (12, 'Cannot allocate memory')"
first. But xend changed the error message to "Error: an integer is
required" halfway. I'm not sure about the cause why an exception
occurred in logging processing. But when I applied an attached patch,
I confirmed that the error message that I expected was shown. The
patch does not call xc.domain_destroy_hook() if self.domid is None.

Signed-off-by: Masaki Kanno <kanno.masaki@jp.fujitsu.com>
author kfraser@localhost.localdomain
date Tue Aug 28 16:11:05 2007 +0100 (2007-08-28)
parents ac7a8c9d6202
children 993655d24b55
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 # To remove the UUID of the device from refs,
611 # deviceClass must be always 'vbd'.
612 deviceClass = 'vbd'
613 if dev_info is None:
614 return rc
616 dev_uuid = sxp.child_value(dev_info, 'uuid')
617 del self.info['devices'][dev_uuid]
618 self.info['%s_refs' % deviceClass].remove(dev_uuid)
619 xen.xend.XendDomain.instance().managed_config_save(self)
621 return rc
623 def getDeviceSxprs(self, deviceClass):
624 if self._stateGet() in (DOM_STATE_RUNNING, DOM_STATE_PAUSED):
625 return self.getDeviceController(deviceClass).sxprs()
626 else:
627 sxprs = []
628 dev_num = 0
629 for dev_type, dev_info in self.info.all_devices_sxpr():
630 if dev_type == deviceClass:
631 sxprs.append([dev_num, dev_info])
632 dev_num += 1
633 return sxprs
635 def getDeviceInfo_vif(self, mac):
636 for dev_type, dev_info in self.info.all_devices_sxpr():
637 if dev_type != 'vif':
638 continue
639 if mac == sxp.child_value(dev_info, 'mac'):
640 return dev_info
642 def getDeviceInfo_vbd(self, devid):
643 for dev_type, dev_info in self.info.all_devices_sxpr():
644 if dev_type != 'vbd' and dev_type != 'tap':
645 continue
646 dev = sxp.child_value(dev_info, 'dev')
647 dev = dev.split(':')[0]
648 dev = self.getDeviceController(dev_type).convertToDeviceNumber(dev)
649 if devid == dev:
650 return dev_info
653 def setMemoryTarget(self, target):
654 """Set the memory target of this domain.
655 @param target: In MiB.
656 """
657 log.debug("Setting memory target of domain %s (%s) to %d MiB.",
658 self.info['name_label'], str(self.domid), target)
660 MiB = 1024 * 1024
661 self._safe_set_memory('memory_dynamic_min', target * MiB)
662 self._safe_set_memory('memory_dynamic_max', target * MiB)
664 if self.domid >= 0:
665 self.storeVm("memory", target)
666 self.storeDom("memory/target", target << 10)
667 xen.xend.XendDomain.instance().managed_config_save(self)
669 def setMemoryMaximum(self, limit):
670 """Set the maximum memory limit of this domain
671 @param limit: In MiB.
672 """
673 log.debug("Setting memory maximum of domain %s (%s) to %d MiB.",
674 self.info['name_label'], str(self.domid), limit)
676 if limit <= 0:
677 raise XendError('Invalid memory size')
679 MiB = 1024 * 1024
680 self._safe_set_memory('memory_static_max', limit * MiB)
682 if self.domid >= 0:
683 maxmem = int(limit) * 1024
684 try:
685 return xc.domain_setmaxmem(self.domid, maxmem)
686 except Exception, ex:
687 raise XendError(str(ex))
688 xen.xend.XendDomain.instance().managed_config_save(self)
691 def getVCPUInfo(self):
692 try:
693 # We include the domain name and ID, to help xm.
694 sxpr = ['domain',
695 ['domid', self.domid],
696 ['name', self.info['name_label']],
697 ['vcpu_count', self.info['VCPUs_max']]]
699 for i in range(0, self.info['VCPUs_max']):
700 if self.domid is not None:
701 info = xc.vcpu_getinfo(self.domid, i)
703 sxpr.append(['vcpu',
704 ['number', i],
705 ['online', info['online']],
706 ['blocked', info['blocked']],
707 ['running', info['running']],
708 ['cpu_time', info['cpu_time'] / 1e9],
709 ['cpu', info['cpu']],
710 ['cpumap', info['cpumap']]])
711 else:
712 sxpr.append(['vcpu',
713 ['number', i],
714 ['online', 0],
715 ['blocked', 0],
716 ['running', 0],
717 ['cpu_time', 0.0],
718 ['cpu', -1],
719 ['cpumap', self.info['cpus'] and \
720 self.info['cpus'] or range(64)]])
722 return sxpr
724 except RuntimeError, exn:
725 raise XendError(str(exn))
728 def getDomInfo(self):
729 return dom_get(self.domid)
731 #
732 # internal functions ... TODO: re-categorised
733 #
735 def _augmentInfo(self, priv):
736 """Augment self.info, as given to us through L{recreate}, with
737 values taken from the store. This recovers those values known
738 to xend but not to the hypervisor.
739 """
740 augment_entries = XendConfig.LEGACY_XENSTORE_VM_PARAMS[:]
741 if priv:
742 augment_entries.remove('memory')
743 augment_entries.remove('maxmem')
744 augment_entries.remove('vcpus')
745 augment_entries.remove('vcpu_avail')
747 vm_config = self._readVMDetails([(k, XendConfig.LEGACY_CFG_TYPES[k])
748 for k in augment_entries])
750 # make returned lists into a dictionary
751 vm_config = dict(zip(augment_entries, vm_config))
753 for arg in augment_entries:
754 val = vm_config[arg]
755 if val != None:
756 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
757 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
758 self.info[xapiarg] = val
759 elif arg == "memory":
760 self.info["static_memory_min"] = val
761 elif arg == "maxmem":
762 self.info["static_memory_max"] = val
763 else:
764 self.info[arg] = val
766 # For dom0, we ignore any stored value for the vcpus fields, and
767 # read the current value from Xen instead. This allows boot-time
768 # settings to take precedence over any entries in the store.
769 if priv:
770 xeninfo = dom_get(self.domid)
771 self.info['VCPUs_max'] = xeninfo['online_vcpus']
772 self.info['vcpu_avail'] = (1 << xeninfo['online_vcpus']) - 1
774 # read image value
775 image_sxp = self._readVm('image')
776 if image_sxp:
777 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
779 # read devices
780 devices = []
781 for devclass in XendDevices.valid_devices():
782 devconfig = self.getDeviceController(devclass).configurations()
783 if devconfig:
784 devices.extend(devconfig)
786 if not self.info['devices'] and devices is not None:
787 for device in devices:
788 self.info.device_add(device[0], cfg_sxp = device)
790 self._update_consoles()
792 def _update_consoles(self):
793 if self.domid == None or self.domid == 0:
794 return
796 # Update VT100 port if it exists
797 self.console_port = self.readDom('console/port')
798 if self.console_port is not None:
799 serial_consoles = self.info.console_get_all('vt100')
800 if not serial_consoles:
801 cfg = self.info.console_add('vt100', self.console_port)
802 self._createDevice('console', cfg)
803 else:
804 console_uuid = serial_consoles[0].get('uuid')
805 self.info.console_update(console_uuid, 'location',
806 self.console_port)
809 # Update VNC port if it exists and write to xenstore
810 vnc_port = self.readDom('console/vnc-port')
811 if vnc_port is not None:
812 for dev_uuid, (dev_type, dev_info) in self.info['devices'].items():
813 if dev_type == 'vfb':
814 old_location = dev_info.get('location')
815 listen_host = dev_info.get('vnclisten', 'localhost')
816 new_location = '%s:%s' % (listen_host, str(vnc_port))
817 if old_location == new_location:
818 break
820 dev_info['location'] = new_location
821 self.info.device_update(dev_uuid, cfg_xenapi = dev_info)
822 vfb_ctrl = self.getDeviceController('vfb')
823 vfb_ctrl.reconfigureDevice(0, dev_info)
824 break
826 #
827 # Function to update xenstore /vm/*
828 #
830 def _readVm(self, *args):
831 return xstransact.Read(self.vmpath, *args)
833 def _writeVm(self, *args):
834 return xstransact.Write(self.vmpath, *args)
836 def _removeVm(self, *args):
837 return xstransact.Remove(self.vmpath, *args)
839 def _gatherVm(self, *args):
840 return xstransact.Gather(self.vmpath, *args)
842 def storeVm(self, *args):
843 return xstransact.Store(self.vmpath, *args)
845 #
846 # Function to update xenstore /dom/*
847 #
849 def readDom(self, *args):
850 return xstransact.Read(self.dompath, *args)
852 def gatherDom(self, *args):
853 return xstransact.Gather(self.dompath, *args)
855 def _writeDom(self, *args):
856 return xstransact.Write(self.dompath, *args)
858 def _removeDom(self, *args):
859 return xstransact.Remove(self.dompath, *args)
861 def storeDom(self, *args):
862 return xstransact.Store(self.dompath, *args)
864 def _recreateDom(self):
865 complete(self.dompath, lambda t: self._recreateDomFunc(t))
867 def _recreateDomFunc(self, t):
868 t.remove()
869 t.mkdir()
870 t.set_permissions({'dom' : self.domid})
871 t.write('vm', self.vmpath)
873 def _storeDomDetails(self):
874 to_store = {
875 'domid': str(self.domid),
876 'vm': self.vmpath,
877 'name': self.info['name_label'],
878 'console/limit': str(xoptions.get_console_limit() * 1024),
879 'memory/target': str(self.info['memory_dynamic_max'] / 1024),
880 }
882 def f(n, v):
883 if v is not None:
884 if type(v) == bool:
885 to_store[n] = v and "1" or "0"
886 else:
887 to_store[n] = str(v)
889 f('console/port', self.console_port)
890 f('console/ring-ref', self.console_mfn)
891 f('store/port', self.store_port)
892 f('store/ring-ref', self.store_mfn)
894 if arch.type == "x86":
895 f('control/platform-feature-multiprocessor-suspend', True)
897 # elfnotes
898 for n, v in self.info.get_notes().iteritems():
899 n = n.lower().replace('_', '-')
900 if n == 'features':
901 for v in v.split('|'):
902 v = v.replace('_', '-')
903 if v.startswith('!'):
904 f('image/%s/%s' % (n, v[1:]), False)
905 else:
906 f('image/%s/%s' % (n, v), True)
907 else:
908 f('image/%s' % n, v)
910 if self.info.has_key('security_label'):
911 f('security_label', self.info['security_label'])
913 to_store.update(self._vcpuDomDetails())
915 log.debug("Storing domain details: %s", scrub_password(to_store))
917 self._writeDom(to_store)
919 def _vcpuDomDetails(self):
920 def availability(n):
921 if self.info['vcpu_avail'] & (1 << n):
922 return 'online'
923 else:
924 return 'offline'
926 result = {}
927 for v in range(0, self.info['VCPUs_max']):
928 result["cpu/%d/availability" % v] = availability(v)
929 return result
931 #
932 # xenstore watches
933 #
935 def _registerWatches(self):
936 """Register a watch on this VM's entries in the store, and the
937 domain's control/shutdown node, so that when they are changed
938 externally, we keep up to date. This should only be called by {@link
939 #create}, {@link #recreate}, or {@link #restore}, once the domain's
940 details have been written, but before the new instance is returned."""
941 self.vmWatch = xswatch(self.vmpath, self._storeChanged)
942 self.shutdownWatch = xswatch(self.dompath + '/control/shutdown',
943 self._handleShutdownWatch)
945 def _storeChanged(self, _):
946 log.trace("XendDomainInfo.storeChanged");
948 changed = False
950 # Check whether values in the configuration have
951 # changed in Xenstore.
953 cfg_vm = ['name', 'on_poweroff', 'on_reboot', 'on_crash',
954 'rtc/timeoffset']
956 vm_details = self._readVMDetails([(k,XendConfig.LEGACY_CFG_TYPES[k])
957 for k in cfg_vm])
959 # convert two lists into a python dictionary
960 vm_details = dict(zip(cfg_vm, vm_details))
962 if vm_details['rtc/timeoffset'] == None:
963 vm_details['rtc/timeoffset'] = "0"
965 for arg, val in vm_details.items():
966 if arg in XendConfig.LEGACY_CFG_TO_XENAPI_CFG:
967 xapiarg = XendConfig.LEGACY_CFG_TO_XENAPI_CFG[arg]
968 if val != None and val != self.info[xapiarg]:
969 self.info[xapiarg] = val
970 changed = True
971 elif arg == "memory":
972 if val != None and val != self.info["static_memory_min"]:
973 self.info["static_memory_min"] = val
974 changed = True
975 elif arg == "maxmem":
976 if val != None and val != self.info["static_memory_max"]:
977 self.info["static_memory_max"] = val
978 changed = True
980 # Check whether image definition has been updated
981 image_sxp = self._readVm('image')
982 if image_sxp and image_sxp != sxp.to_string(self.info.image_sxpr()):
983 self.info.update_with_image_sxp(sxp.from_string(image_sxp))
984 changed = True
986 # Check if the rtc offset has changes
987 if vm_details.get("rtc/timeoffset", "0") != self.info["platform"].get("rtc_timeoffset", "0"):
988 self.info["platform"]["rtc_timeoffset"] = vm_details.get("rtc/timeoffset", 0)
989 changed = True
991 if changed:
992 # Update the domain section of the store, as this contains some
993 # parameters derived from the VM configuration.
994 self._storeDomDetails()
996 return 1
998 def _handleShutdownWatch(self, _):
999 log.debug('XendDomainInfo.handleShutdownWatch')
1001 reason = self.readDom('control/shutdown')
1003 if reason and reason != 'suspend':
1004 sst = self.readDom('xend/shutdown_start_time')
1005 now = time.time()
1006 if sst:
1007 self.shutdownStartTime = float(sst)
1008 timeout = float(sst) + SHUTDOWN_TIMEOUT - now
1009 else:
1010 self.shutdownStartTime = now
1011 self.storeDom('xend/shutdown_start_time', now)
1012 timeout = SHUTDOWN_TIMEOUT
1014 log.trace(
1015 "Scheduling refreshShutdown on domain %d in %ds.",
1016 self.domid, timeout)
1017 threading.Timer(timeout, self.refreshShutdown).start()
1019 return True
1023 # Public Attributes for the VM
1027 def getDomid(self):
1028 return self.domid
1030 def setName(self, name):
1031 self._checkName(name)
1032 self.info['name_label'] = name
1033 self.storeVm("name", name)
1035 def getName(self):
1036 return self.info['name_label']
1038 def getDomainPath(self):
1039 return self.dompath
1041 def getShutdownReason(self):
1042 return self.readDom('control/shutdown')
1044 def getStorePort(self):
1045 """For use only by image.py and XendCheckpoint.py."""
1046 return self.store_port
1048 def getConsolePort(self):
1049 """For use only by image.py and XendCheckpoint.py"""
1050 return self.console_port
1052 def getFeatures(self):
1053 """For use only by image.py."""
1054 return self.info['features']
1056 def getVCpuCount(self):
1057 return self.info['VCPUs_max']
1059 def setVCpuCount(self, vcpus):
1060 if vcpus <= 0:
1061 raise XendError('Invalid VCPUs')
1063 self.info['vcpu_avail'] = (1 << vcpus) - 1
1064 if self.domid >= 0:
1065 self.storeVm('vcpu_avail', self.info['vcpu_avail'])
1066 # update dom differently depending on whether we are adjusting
1067 # vcpu number up or down, otherwise _vcpuDomDetails does not
1068 # disable the vcpus
1069 if self.info['VCPUs_max'] > vcpus:
1070 # decreasing
1071 self._writeDom(self._vcpuDomDetails())
1072 self.info['VCPUs_live'] = vcpus
1073 else:
1074 # same or increasing
1075 self.info['VCPUs_live'] = vcpus
1076 self._writeDom(self._vcpuDomDetails())
1077 else:
1078 self.info['VCPUs_max'] = vcpus
1079 xen.xend.XendDomain.instance().managed_config_save(self)
1080 log.info("Set VCPU count on domain %s to %d", self.info['name_label'],
1081 vcpus)
1083 def getMemoryTarget(self):
1084 """Get this domain's target memory size, in KB."""
1085 return self.info['memory_dynamic_max'] / 1024
1087 def getMemoryMaximum(self):
1088 """Get this domain's maximum memory size, in KB."""
1089 # remember, info now stores memory in bytes
1090 return self.info['memory_static_max'] / 1024
1092 def getResume(self):
1093 return str(self._resume)
1095 def getCap(self):
1096 return self.info.get('cpu_cap', 0)
1098 def setCap(self, cpu_cap):
1099 self.info['cpu_cap'] = cpu_cap
1101 def getWeight(self):
1102 return self.info.get('cpu_weight', 256)
1104 def setWeight(self, cpu_weight):
1105 self.info['cpu_weight'] = cpu_weight
1107 def setResume(self, state):
1108 self._resume = state
1110 def getRestartCount(self):
1111 return self._readVm('xend/restart_count')
1113 def refreshShutdown(self, xeninfo = None):
1114 """ Checks the domain for whether a shutdown is required.
1116 Called from XendDomainInfo and also image.py for HVM images.
1117 """
1119 # If set at the end of this method, a restart is required, with the
1120 # given reason. This restart has to be done out of the scope of
1121 # refresh_shutdown_lock.
1122 restart_reason = None
1124 self.refresh_shutdown_lock.acquire()
1125 try:
1126 if xeninfo is None:
1127 xeninfo = dom_get(self.domid)
1128 if xeninfo is None:
1129 # The domain no longer exists. This will occur if we have
1130 # scheduled a timer to check for shutdown timeouts and the
1131 # shutdown succeeded. It will also occur if someone
1132 # destroys a domain beneath us. We clean up the domain,
1133 # just in case, but we can't clean up the VM, because that
1134 # VM may have migrated to a different domain on this
1135 # machine.
1136 self.cleanupDomain()
1137 self._stateSet(DOM_STATE_HALTED)
1138 return
1140 if xeninfo['dying']:
1141 # Dying means that a domain has been destroyed, but has not
1142 # yet been cleaned up by Xen. This state could persist
1143 # indefinitely if, for example, another domain has some of its
1144 # pages mapped. We might like to diagnose this problem in the
1145 # future, but for now all we do is make sure that it's not us
1146 # holding the pages, by calling cleanupDomain. We can't
1147 # clean up the VM, as above.
1148 self.cleanupDomain()
1149 self._stateSet(DOM_STATE_SHUTDOWN)
1150 return
1152 elif xeninfo['crashed']:
1153 if self.readDom('xend/shutdown_completed'):
1154 # We've seen this shutdown already, but we are preserving
1155 # the domain for debugging. Leave it alone.
1156 return
1158 log.warn('Domain has crashed: name=%s id=%d.',
1159 self.info['name_label'], self.domid)
1160 self._writeVm(LAST_SHUTDOWN_REASON, 'crash')
1162 if xoptions.get_enable_dump():
1163 try:
1164 self.dumpCore()
1165 except XendError:
1166 # This error has been logged -- there's nothing more
1167 # we can do in this context.
1168 pass
1170 restart_reason = 'crash'
1171 self._stateSet(DOM_STATE_HALTED)
1173 elif xeninfo['shutdown']:
1174 self._stateSet(DOM_STATE_SHUTDOWN)
1175 if self.readDom('xend/shutdown_completed'):
1176 # We've seen this shutdown already, but we are preserving
1177 # the domain for debugging. Leave it alone.
1178 return
1180 else:
1181 reason = shutdown_reason(xeninfo['shutdown_reason'])
1183 log.info('Domain has shutdown: name=%s id=%d reason=%s.',
1184 self.info['name_label'], self.domid, reason)
1185 self._writeVm(LAST_SHUTDOWN_REASON, reason)
1187 self._clearRestart()
1189 if reason == 'suspend':
1190 self._stateSet(DOM_STATE_SUSPENDED)
1191 # Don't destroy the domain. XendCheckpoint will do
1192 # this once it has finished. However, stop watching
1193 # the VM path now, otherwise we will end up with one
1194 # watch for the old domain, and one for the new.
1195 self._unwatchVm()
1196 elif reason in ('poweroff', 'reboot'):
1197 restart_reason = reason
1198 else:
1199 self.destroy()
1201 elif self.dompath is None:
1202 # We have yet to manage to call introduceDomain on this
1203 # domain. This can happen if a restore is in progress, or has
1204 # failed. Ignore this domain.
1205 pass
1206 else:
1207 # Domain is alive. If we are shutting it down, log a message
1208 # if it seems unresponsive.
1209 if xeninfo['paused']:
1210 self._stateSet(DOM_STATE_PAUSED)
1211 else:
1212 self._stateSet(DOM_STATE_RUNNING)
1214 if self.shutdownStartTime:
1215 timeout = (SHUTDOWN_TIMEOUT - time.time() +
1216 self.shutdownStartTime)
1217 if (timeout < 0 and not self.readDom('xend/unresponsive')):
1218 log.info(
1219 "Domain shutdown timeout expired: name=%s id=%s",
1220 self.info['name_label'], self.domid)
1221 self.storeDom('xend/unresponsive', 'True')
1222 finally:
1223 self.refresh_shutdown_lock.release()
1225 if restart_reason:
1226 threading.Thread(target = self._maybeRestart,
1227 args = (restart_reason,)).start()
1231 # Restart functions - handling whether we come back up on shutdown.
1234 def _clearRestart(self):
1235 self._removeDom("xend/shutdown_start_time")
1238 def _maybeRestart(self, reason):
1239 # Dispatch to the correct method based upon the configured on_{reason}
1240 # behaviour.
1241 actions = {"destroy" : self.destroy,
1242 "restart" : self._restart,
1243 "preserve" : self._preserve,
1244 "rename-restart" : self._renameRestart}
1246 action_conf = {
1247 'poweroff': 'actions_after_shutdown',
1248 'reboot': 'actions_after_reboot',
1249 'crash': 'actions_after_crash',
1252 action_target = self.info.get(action_conf.get(reason))
1253 func = actions.get(action_target, None)
1254 if func and callable(func):
1255 func()
1256 else:
1257 self.destroy() # default to destroy
1259 def _renameRestart(self):
1260 self._restart(True)
1262 def _restart(self, rename = False):
1263 """Restart the domain after it has exited.
1265 @param rename True if the old domain is to be renamed and preserved,
1266 False if it is to be destroyed.
1267 """
1268 from xen.xend import XendDomain
1270 if self._readVm(RESTART_IN_PROGRESS):
1271 log.error('Xend failed during restart of domain %s. '
1272 'Refusing to restart to avoid loops.',
1273 str(self.domid))
1274 self.destroy()
1275 return
1277 old_domid = self.domid
1278 self._writeVm(RESTART_IN_PROGRESS, 'True')
1280 now = time.time()
1281 rst = self._readVm('xend/previous_restart_time')
1282 if rst:
1283 rst = float(rst)
1284 timeout = now - rst
1285 if timeout < MINIMUM_RESTART_TIME:
1286 log.error(
1287 'VM %s restarting too fast (%f seconds since the last '
1288 'restart). Refusing to restart to avoid loops.',
1289 self.info['name_label'], timeout)
1290 self.destroy()
1291 return
1293 self._writeVm('xend/previous_restart_time', str(now))
1295 try:
1296 if rename:
1297 self._preserveForRestart()
1298 else:
1299 self._unwatchVm()
1300 self.destroyDomain()
1302 # new_dom's VM will be the same as this domain's VM, except where
1303 # the rename flag has instructed us to call preserveForRestart.
1304 # In that case, it is important that we remove the
1305 # RESTART_IN_PROGRESS node from the new domain, not the old one,
1306 # once the new one is available.
1308 new_dom = None
1309 try:
1310 new_dom = XendDomain.instance().domain_create_from_dict(
1311 self.info)
1312 new_dom.unpause()
1313 rst_cnt = self._readVm('xend/restart_count')
1314 rst_cnt = int(rst_cnt) + 1
1315 self._writeVm('xend/restart_count', str(rst_cnt))
1316 new_dom._removeVm(RESTART_IN_PROGRESS)
1317 except:
1318 if new_dom:
1319 new_dom._removeVm(RESTART_IN_PROGRESS)
1320 new_dom.destroy()
1321 else:
1322 self._removeVm(RESTART_IN_PROGRESS)
1323 raise
1324 except:
1325 log.exception('Failed to restart domain %s.', str(old_domid))
1327 def _preserveForRestart(self):
1328 """Preserve a domain that has been shut down, by giving it a new UUID,
1329 cloning the VM details, and giving it a new name. This allows us to
1330 keep this domain for debugging, but restart a new one in its place
1331 preserving the restart semantics (name and UUID preserved).
1332 """
1334 new_uuid = uuid.createString()
1335 new_name = 'Domain-%s' % new_uuid
1336 log.info("Renaming dead domain %s (%d, %s) to %s (%s).",
1337 self.info['name_label'], self.domid, self.info['uuid'],
1338 new_name, new_uuid)
1339 self._unwatchVm()
1340 self._releaseDevices()
1341 self.info['name_label'] = new_name
1342 self.info['uuid'] = new_uuid
1343 self.vmpath = XS_VMROOT + new_uuid
1344 self._storeVmDetails()
1345 self._preserve()
1348 def _preserve(self):
1349 log.info("Preserving dead domain %s (%d).", self.info['name_label'],
1350 self.domid)
1351 self._unwatchVm()
1352 self.storeDom('xend/shutdown_completed', 'True')
1353 self._stateSet(DOM_STATE_HALTED)
1356 # Debugging ..
1359 def dumpCore(self, corefile = None):
1360 """Create a core dump for this domain.
1362 @raise: XendError if core dumping failed.
1363 """
1365 try:
1366 if not corefile:
1367 this_time = time.strftime("%Y-%m%d-%H%M.%S", time.localtime())
1368 corefile = "/var/xen/dump/%s-%s.%s.core" % (this_time,
1369 self.info['name_label'], self.domid)
1371 if os.path.isdir(corefile):
1372 raise XendError("Cannot dump core in a directory: %s" %
1373 corefile)
1375 xc.domain_dumpcore(self.domid, corefile)
1376 except RuntimeError, ex:
1377 corefile_incomp = corefile+'-incomplete'
1378 os.rename(corefile, corefile_incomp)
1379 log.exception("XendDomainInfo.dumpCore failed: id = %s name = %s",
1380 self.domid, self.info['name_label'])
1381 raise XendError("Failed to dump core: %s" % str(ex))
1384 # Device creation/deletion functions
1387 def _createDevice(self, deviceClass, devConfig):
1388 return self.getDeviceController(deviceClass).createDevice(devConfig)
1390 def _waitForDevice(self, deviceClass, devid):
1391 return self.getDeviceController(deviceClass).waitForDevice(devid)
1393 def _waitForDeviceUUID(self, dev_uuid):
1394 deviceClass, config = self.info['devices'].get(dev_uuid)
1395 self._waitForDevice(deviceClass, config['devid'])
1397 def _waitForDevice_destroy(self, deviceClass, devid, backpath):
1398 return self.getDeviceController(deviceClass).waitForDevice_destroy(
1399 devid, backpath)
1401 def _reconfigureDevice(self, deviceClass, devid, devconfig):
1402 return self.getDeviceController(deviceClass).reconfigureDevice(
1403 devid, devconfig)
1405 def _createDevices(self):
1406 """Create the devices for a vm.
1408 @raise: VmError for invalid devices
1409 """
1410 ordered_refs = self.info.ordered_device_refs()
1411 for dev_uuid in ordered_refs:
1412 devclass, config = self.info['devices'][dev_uuid]
1413 if devclass in XendDevices.valid_devices():
1414 log.info("createDevice: %s : %s" % (devclass, scrub_password(config)))
1415 dev_uuid = config.get('uuid')
1416 devid = self._createDevice(devclass, config)
1418 # store devid in XendConfig for caching reasons
1419 if dev_uuid in self.info['devices']:
1420 self.info['devices'][dev_uuid][1]['devid'] = devid
1422 if self.image:
1423 self.image.createDeviceModel()
1425 def _releaseDevices(self, suspend = False):
1426 """Release all domain's devices. Nothrow guarantee."""
1427 if suspend and self.image:
1428 self.image.destroy(suspend)
1429 return
1431 t = xstransact("%s/device" % self.dompath)
1432 for devclass in XendDevices.valid_devices():
1433 for dev in t.list(devclass):
1434 try:
1435 log.debug("Removing %s", dev);
1436 self.destroyDevice(devclass, dev, False);
1437 except:
1438 # Log and swallow any exceptions in removal --
1439 # there's nothing more we can do.
1440 log.exception("Device release failed: %s; %s; %s",
1441 self.info['name_label'], devclass, dev)
1445 def getDeviceController(self, name):
1446 """Get the device controller for this domain, and if it
1447 doesn't exist, create it.
1449 @param name: device class name
1450 @type name: string
1451 @rtype: subclass of DevController
1452 """
1453 if name not in self._deviceControllers:
1454 devController = XendDevices.make_controller(name, self)
1455 if not devController:
1456 raise XendError("Unknown device type: %s" % name)
1457 self._deviceControllers[name] = devController
1459 return self._deviceControllers[name]
1462 # Migration functions (public)
1465 def testMigrateDevices(self, network, dst):
1466 """ Notify all device about intention of migration
1467 @raise: XendError for a device that cannot be migrated
1468 """
1469 for (n, c) in self.info.all_devices_sxpr():
1470 rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST)
1471 if rc != 0:
1472 raise XendError("Device of type '%s' refuses migration." % n)
1474 def migrateDevices(self, network, dst, step, domName=''):
1475 """Notify the devices about migration
1476 """
1477 ctr = 0
1478 try:
1479 for (dev_type, dev_conf) in self.info.all_devices_sxpr():
1480 self.migrateDevice(dev_type, dev_conf, network, dst,
1481 step, domName)
1482 ctr = ctr + 1
1483 except:
1484 for dev_type, dev_conf in self.info.all_devices_sxpr():
1485 if ctr == 0:
1486 step = step - 1
1487 ctr = ctr - 1
1488 self._recoverMigrateDevice(dev_type, dev_conf, network,
1489 dst, step, domName)
1490 raise
1492 def migrateDevice(self, deviceClass, deviceConfig, network, dst,
1493 step, domName=''):
1494 return self.getDeviceController(deviceClass).migrate(deviceConfig,
1495 network, dst, step, domName)
1497 def _recoverMigrateDevice(self, deviceClass, deviceConfig, network,
1498 dst, step, domName=''):
1499 return self.getDeviceController(deviceClass).recover_migrate(
1500 deviceConfig, network, dst, step, domName)
1503 ## private:
1505 def _constructDomain(self):
1506 """Construct the domain.
1508 @raise: VmError on error
1509 """
1511 log.debug('XendDomainInfo.constructDomain')
1513 self.shutdownStartTime = None
1515 hvm = self.info.is_hvm()
1516 if hvm:
1517 info = xc.xeninfo()
1518 if 'hvm' not in info['xen_caps']:
1519 raise VmError("HVM guest support is unavailable: is VT/AMD-V "
1520 "supported by your CPU and enabled in your "
1521 "BIOS?")
1523 # Hack to pre-reserve some memory for initial domain creation.
1524 # There is an implicit memory overhead for any domain creation. This
1525 # overhead is greater for some types of domain than others. For
1526 # example, an x86 HVM domain will have a default shadow-pagetable
1527 # allocation of 1MB. We free up 2MB here to be on the safe side.
1528 balloon.free(2*1024) # 2MB should be plenty
1530 ssidref = 0
1531 if security.on():
1532 ssidref = security.calc_dom_ssidref_from_info(self.info)
1533 if security.has_authorization(ssidref) == False:
1534 raise VmError("VM is not authorized to run.")
1536 try:
1537 self.domid = xc.domain_create(
1538 domid = 0,
1539 ssidref = ssidref,
1540 handle = uuid.fromString(self.info['uuid']),
1541 hvm = int(hvm))
1542 except Exception, e:
1543 # may get here if due to ACM the operation is not permitted
1544 if security.on():
1545 raise VmError('Domain in conflict set with running domain?')
1547 if self.domid < 0:
1548 raise VmError('Creating domain failed: name=%s' %
1549 self.info['name_label'])
1551 self.dompath = GetDomainPath(self.domid)
1553 self._recreateDom()
1555 # Set maximum number of vcpus in domain
1556 xc.domain_max_vcpus(self.domid, int(self.info['VCPUs_max']))
1558 # register the domain in the list
1559 from xen.xend import XendDomain
1560 XendDomain.instance().add_domain(self)
1562 def _introduceDomain(self):
1563 assert self.domid is not None
1564 assert self.store_mfn is not None
1565 assert self.store_port is not None
1567 try:
1568 IntroduceDomain(self.domid, self.store_mfn, self.store_port)
1569 except RuntimeError, exn:
1570 raise XendError(str(exn))
1573 def _initDomain(self):
1574 log.debug('XendDomainInfo.initDomain: %s %s',
1575 self.domid,
1576 self.info['cpu_weight'])
1578 self._configureBootloader()
1580 try:
1581 self.image = image.create(self, self.info)
1583 if self.info['platform'].get('localtime', 0):
1584 xc.domain_set_time_offset(self.domid)
1586 xc.domain_setcpuweight(self.domid, self.info['cpu_weight'])
1588 # repin domain vcpus if a restricted cpus list is provided
1589 # this is done prior to memory allocation to aide in memory
1590 # distribution for NUMA systems.
1591 if self.info['cpus'] is not None and len(self.info['cpus']) > 0:
1592 for v in range(0, self.info['VCPUs_max']):
1593 xc.vcpu_setaffinity(self.domid, v, self.info['cpus'])
1595 # Use architecture- and image-specific calculations to determine
1596 # the various headrooms necessary, given the raw configured
1597 # values. maxmem, memory, and shadow are all in KiB.
1598 # but memory_static_max etc are all stored in bytes now.
1599 memory = self.image.getRequiredAvailableMemory(
1600 self.info['memory_dynamic_max'] / 1024)
1601 maxmem = self.image.getRequiredAvailableMemory(
1602 self.info['memory_static_max'] / 1024)
1603 shadow = self.image.getRequiredShadowMemory(
1604 self.info['shadow_memory'] / 1024,
1605 self.info['memory_static_max'] / 1024)
1607 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'],)
1608 # Round shadow up to a multiple of a MiB, as shadow_mem_control
1609 # takes MiB and we must not round down and end up under-providing.
1610 shadow = ((shadow + 1023) / 1024) * 1024
1612 # set memory limit
1613 xc.domain_setmaxmem(self.domid, maxmem)
1615 # Make sure there's enough RAM available for the domain
1616 balloon.free(memory + shadow)
1618 # Set up the shadow memory
1619 shadow_cur = xc.shadow_mem_control(self.domid, shadow / 1024)
1620 self.info['shadow_memory'] = shadow_cur
1622 self._createChannels()
1624 channel_details = self.image.createImage()
1626 self.store_mfn = channel_details['store_mfn']
1627 if 'console_mfn' in channel_details:
1628 self.console_mfn = channel_details['console_mfn']
1629 if 'notes' in channel_details:
1630 self.info.set_notes(channel_details['notes'])
1631 if 'native_protocol' in channel_details:
1632 self.native_protocol = channel_details['native_protocol'];
1634 self._introduceDomain()
1636 self._createDevices()
1638 self.image.cleanupBootloading()
1640 self.info['start_time'] = time.time()
1642 self._stateSet(DOM_STATE_RUNNING)
1643 except VmError, exn:
1644 log.exception("XendDomainInfo.initDomain: exception occurred")
1645 if self.image:
1646 self.image.cleanupBootloading()
1647 raise exn
1648 except RuntimeError, exn:
1649 log.exception("XendDomainInfo.initDomain: exception occurred")
1650 if self.image:
1651 self.image.cleanupBootloading()
1652 raise VmError(str(exn))
1655 def cleanupDomain(self):
1656 """Cleanup domain resources; release devices. Idempotent. Nothrow
1657 guarantee."""
1659 self.refresh_shutdown_lock.acquire()
1660 try:
1661 self.unwatchShutdown()
1662 self._releaseDevices()
1663 bootloader_tidy(self)
1665 if self.image:
1666 try:
1667 self.image.destroy()
1668 except:
1669 log.exception(
1670 "XendDomainInfo.cleanup: image.destroy() failed.")
1671 self.image = None
1673 try:
1674 self._removeDom()
1675 except:
1676 log.exception("Removing domain path failed.")
1678 self._stateSet(DOM_STATE_HALTED)
1679 self.domid = None # Do not push into _stateSet()!
1680 finally:
1681 self.refresh_shutdown_lock.release()
1684 def unwatchShutdown(self):
1685 """Remove the watch on the domain's control/shutdown node, if any.
1686 Idempotent. Nothrow guarantee. Expects to be protected by the
1687 refresh_shutdown_lock."""
1689 try:
1690 try:
1691 if self.shutdownWatch:
1692 self.shutdownWatch.unwatch()
1693 finally:
1694 self.shutdownWatch = None
1695 except:
1696 log.exception("Unwatching control/shutdown failed.")
1698 def waitForShutdown(self):
1699 self.state_updated.acquire()
1700 try:
1701 while self._stateGet() in (DOM_STATE_RUNNING,DOM_STATE_PAUSED):
1702 self.state_updated.wait()
1703 finally:
1704 self.state_updated.release()
1707 # TODO: recategorise - called from XendCheckpoint
1710 def completeRestore(self, store_mfn, console_mfn):
1712 log.debug("XendDomainInfo.completeRestore")
1714 self.store_mfn = store_mfn
1715 self.console_mfn = console_mfn
1717 self._introduceDomain()
1718 if self.info.is_hvm():
1719 self.image = image.create(self, self.info)
1720 if self.image:
1721 self.image.createDeviceModel(True)
1722 self._storeDomDetails()
1723 self._registerWatches()
1724 self.refreshShutdown()
1726 log.debug("XendDomainInfo.completeRestore done")
1729 def _endRestore(self):
1730 self.setResume(False)
1733 # VM Destroy
1736 def _prepare_phantom_paths(self):
1737 # get associated devices to destroy
1738 # build list of phantom devices to be removed after normal devices
1739 plist = []
1740 if self.domid is not None:
1741 from xen.xend.xenstore.xstransact import xstransact
1742 t = xstransact("%s/device/vbd" % GetDomainPath(self.domid))
1743 for dev in t.list():
1744 backend_phantom_vbd = xstransact.Read("%s/device/vbd/%s/phantom_vbd" \
1745 % (self.dompath, dev))
1746 if backend_phantom_vbd is not None:
1747 frontend_phantom_vbd = xstransact.Read("%s/frontend" \
1748 % backend_phantom_vbd)
1749 plist.append(backend_phantom_vbd)
1750 plist.append(frontend_phantom_vbd)
1751 return plist
1753 def _cleanup_phantom_devs(self, plist):
1754 # remove phantom devices
1755 if not plist == []:
1756 time.sleep(2)
1757 for paths in plist:
1758 if paths.find('backend') != -1:
1759 from xen.xend.server import DevController
1760 # Modify online status /before/ updating state (latter is watched by
1761 # drivers, so this ordering avoids a race).
1762 xstransact.Write(paths, 'online', "0")
1763 xstransact.Write(paths, 'state', str(DevController.xenbusState['Closing']))
1764 # force
1765 xstransact.Remove(paths)
1767 def destroy(self):
1768 """Cleanup VM and destroy domain. Nothrow guarantee."""
1770 log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid))
1772 paths = self._prepare_phantom_paths()
1774 self._cleanupVm()
1775 if self.dompath is not None:
1776 if self.domid is not None:
1777 xc.domain_destroy_hook(self.domid)
1778 self.destroyDomain()
1780 self._cleanup_phantom_devs(paths)
1782 if "transient" in self.info["other_config"] \
1783 and bool(self.info["other_config"]["transient"]):
1784 from xen.xend import XendDomain
1785 XendDomain.instance().domain_delete_by_dominfo(self)
1788 def destroyDomain(self):
1789 log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid))
1791 paths = self._prepare_phantom_paths()
1793 try:
1794 if self.domid is not None:
1795 xc.domain_destroy(self.domid)
1796 for state in DOM_STATES_OLD:
1797 self.info[state] = 0
1798 self._stateSet(DOM_STATE_HALTED)
1799 except:
1800 log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.")
1802 from xen.xend import XendDomain
1803 XendDomain.instance().remove_domain(self)
1805 self.cleanupDomain()
1806 self._cleanup_phantom_devs(paths)
1809 def resumeDomain(self):
1810 log.debug("XendDomainInfo.resumeDomain(%s)", str(self.domid))
1812 if self.domid is None:
1813 return
1814 try:
1815 # could also fetch a parsed note from xenstore
1816 fast = self.info.get_notes().get('SUSPEND_CANCEL') and 1 or 0
1817 if not fast:
1818 self._releaseDevices()
1819 self.testDeviceComplete()
1820 self.testvifsComplete()
1821 log.debug("XendDomainInfo.resumeDomain: devices released")
1823 self._resetChannels()
1825 self._removeDom('control/shutdown')
1826 self._removeDom('device-misc/vif/nextDeviceID')
1828 self._createChannels()
1829 self._introduceDomain()
1830 self._storeDomDetails()
1832 self._createDevices()
1833 log.debug("XendDomainInfo.resumeDomain: devices created")
1835 xc.domain_resume(self.domid, fast)
1836 ResumeDomain(self.domid)
1837 except:
1838 log.exception("XendDomainInfo.resume: xc.domain_resume failed on domain %s." % (str(self.domid)))
1839 if self.is_hvm():
1840 self.image.resumeDeviceModel()
1844 # Channels for xenstore and console
1847 def _createChannels(self):
1848 """Create the channels to the domain.
1849 """
1850 self.store_port = self._createChannel()
1851 self.console_port = self._createChannel()
1854 def _createChannel(self):
1855 """Create an event channel to the domain.
1856 """
1857 try:
1858 if self.domid != None:
1859 return xc.evtchn_alloc_unbound(domid = self.domid,
1860 remote_dom = 0)
1861 except:
1862 log.exception("Exception in alloc_unbound(%s)", str(self.domid))
1863 raise
1865 def _resetChannels(self):
1866 """Reset all event channels in the domain.
1867 """
1868 try:
1869 if self.domid != None:
1870 return xc.evtchn_reset(dom = self.domid)
1871 except:
1872 log.exception("Exception in evtcnh_reset(%s)", str(self.domid))
1873 raise
1877 # Bootloader configuration
1880 def _configureBootloader(self):
1881 """Run the bootloader if we're configured to do so."""
1883 blexec = self.info['PV_bootloader']
1884 bootloader_args = self.info['PV_bootloader_args']
1885 kernel = self.info['PV_kernel']
1886 ramdisk = self.info['PV_ramdisk']
1887 args = self.info['PV_args']
1888 boot = self.info['HVM_boot_policy']
1890 if boot:
1891 # HVM booting.
1892 pass
1893 elif not blexec and kernel:
1894 # Boot from dom0. Nothing left to do -- the kernel and ramdisk
1895 # will be picked up by image.py.
1896 pass
1897 else:
1898 # Boot using bootloader
1899 if not blexec or blexec == 'pygrub':
1900 blexec = osdep.pygrub_path
1902 blcfg = None
1903 disks = [x for x in self.info['vbd_refs']
1904 if self.info['devices'][x][1]['bootable']]
1906 if not disks:
1907 msg = "Had a bootloader specified, but no disks are bootable"
1908 log.error(msg)
1909 raise VmError(msg)
1911 devinfo = self.info['devices'][disks[0]]
1912 devtype = devinfo[0]
1913 disk = devinfo[1]['uname']
1915 fn = blkdev_uname_to_file(disk)
1916 taptype = blkdev_uname_to_taptype(disk)
1917 mounted = devtype == 'tap' and taptype != 'aio' and taptype != 'sync' and not os.stat(fn).st_rdev
1918 if mounted:
1919 # This is a file, not a device. pygrub can cope with a
1920 # file if it's raw, but if it's QCOW or other such formats
1921 # used through blktap, then we need to mount it first.
1923 log.info("Mounting %s on %s." %
1924 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1926 vbd = {
1927 'mode': 'RO',
1928 'device': BOOTLOADER_LOOPBACK_DEVICE,
1931 from xen.xend import XendDomain
1932 dom0 = XendDomain.instance().privilegedDomain()
1933 dom0._waitForDeviceUUID(dom0.create_vbd(vbd, disk))
1934 fn = BOOTLOADER_LOOPBACK_DEVICE
1936 try:
1937 blcfg = bootloader(blexec, fn, self, False,
1938 bootloader_args, kernel, ramdisk, args)
1939 finally:
1940 if mounted:
1941 log.info("Unmounting %s from %s." %
1942 (fn, BOOTLOADER_LOOPBACK_DEVICE))
1944 dom0.destroyDevice('tap', BOOTLOADER_LOOPBACK_DEVICE)
1946 if blcfg is None:
1947 msg = "Had a bootloader specified, but can't find disk"
1948 log.error(msg)
1949 raise VmError(msg)
1951 self.info.update_with_image_sxp(blcfg, True)
1955 # VM Functions
1958 def _readVMDetails(self, params):
1959 """Read the specified parameters from the store.
1960 """
1961 try:
1962 return self._gatherVm(*params)
1963 except ValueError:
1964 # One of the int/float entries in params has a corresponding store
1965 # entry that is invalid. We recover, because older versions of
1966 # Xend may have put the entry there (memory/target, for example),
1967 # but this is in general a bad situation to have reached.
1968 log.exception(
1969 "Store corrupted at %s! Domain %d's configuration may be "
1970 "affected.", self.vmpath, self.domid)
1971 return []
1973 def _cleanupVm(self):
1974 """Cleanup VM resources. Idempotent. Nothrow guarantee."""
1976 self._unwatchVm()
1978 try:
1979 self._removeVm()
1980 except:
1981 log.exception("Removing VM path failed.")
1984 def checkLiveMigrateMemory(self):
1985 """ Make sure there's enough memory to migrate this domain """
1986 overhead_kb = 0
1987 if arch.type == "x86":
1988 # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than
1989 # the minimum that Xen would allocate if no value were given.
1990 overhead_kb = self.info['VCPUs_max'] * 1024 + \
1991 (self.info['memory_static_max'] / 1024 / 1024) * 4
1992 overhead_kb = ((overhead_kb + 1023) / 1024) * 1024
1993 # The domain might already have some shadow memory
1994 overhead_kb -= xc.shadow_mem_control(self.domid) * 1024
1995 if overhead_kb > 0:
1996 balloon.free(overhead_kb)
1998 def _unwatchVm(self):
1999 """Remove the watch on the VM path, if any. Idempotent. Nothrow
2000 guarantee."""
2001 try:
2002 try:
2003 if self.vmWatch:
2004 self.vmWatch.unwatch()
2005 finally:
2006 self.vmWatch = None
2007 except:
2008 log.exception("Unwatching VM path failed.")
2010 def testDeviceComplete(self):
2011 """ For Block IO migration safety we must ensure that
2012 the device has shutdown correctly, i.e. all blocks are
2013 flushed to disk
2014 """
2015 start = time.time()
2016 while True:
2017 test = 0
2018 diff = time.time() - start
2019 for i in self.getDeviceController('vbd').deviceIDs():
2020 test = 1
2021 log.info("Dev %s still active, looping...", i)
2022 time.sleep(0.1)
2024 if test == 0:
2025 break
2026 if diff >= MIGRATE_TIMEOUT:
2027 log.info("Dev still active but hit max loop timeout")
2028 break
2030 def testvifsComplete(self):
2031 """ In case vifs are released and then created for the same
2032 domain, we need to wait the device shut down.
2033 """
2034 start = time.time()
2035 while True:
2036 test = 0
2037 diff = time.time() - start
2038 for i in self.getDeviceController('vif').deviceIDs():
2039 test = 1
2040 log.info("Dev %s still active, looping...", i)
2041 time.sleep(0.1)
2043 if test == 0:
2044 break
2045 if diff >= MIGRATE_TIMEOUT:
2046 log.info("Dev still active but hit max loop timeout")
2047 break
2049 def _storeVmDetails(self):
2050 to_store = {}
2052 for key in XendConfig.LEGACY_XENSTORE_VM_PARAMS:
2053 info_key = XendConfig.LEGACY_CFG_TO_XENAPI_CFG.get(key, key)
2054 if self._infoIsSet(info_key):
2055 to_store[key] = str(self.info[info_key])
2057 if self._infoIsSet("static_memory_min"):
2058 to_store["memory"] = str(self.info["static_memory_min"])
2059 if self._infoIsSet("static_memory_max"):
2060 to_store["maxmem"] = str(self.info["static_memory_max"])
2062 image_sxpr = self.info.image_sxpr()
2063 if image_sxpr:
2064 to_store['image'] = sxp.to_string(image_sxpr)
2066 if not self._readVm('xend/restart_count'):
2067 to_store['xend/restart_count'] = str(0)
2069 log.debug("Storing VM details: %s", scrub_password(to_store))
2071 self._writeVm(to_store)
2072 self._setVmPermissions()
2075 def _setVmPermissions(self):
2076 """Allow the guest domain to read its UUID. We don't allow it to
2077 access any other entry, for security."""
2078 xstransact.SetPermissions('%s/uuid' % self.vmpath,
2079 { 'dom' : self.domid,
2080 'read' : True,
2081 'write' : False })
2084 # Utility functions
2087 def __getattr__(self, name):
2088 if name == "state":
2089 log.warn("Somebody tried to read XendDomainInfo.state... should us _stateGet()!!!")
2090 log.warn("".join(traceback.format_stack()))
2091 return self._stateGet()
2092 else:
2093 raise AttributeError()
2095 def __setattr__(self, name, value):
2096 if name == "state":
2097 log.warn("Somebody tried to set XendDomainInfo.state... should us _stateGet()!!!")
2098 log.warn("".join(traceback.format_stack()))
2099 self._stateSet(value)
2100 else:
2101 self.__dict__[name] = value
2103 def _stateSet(self, state):
2104 self.state_updated.acquire()
2105 try:
2106 # TODO Not sure this is correct...
2107 # _stateGet is live now. Why not fire event
2108 # even when it hasn't changed?
2109 if self._stateGet() != state:
2110 self.state_updated.notifyAll()
2111 import XendAPI
2112 XendAPI.event_dispatch('mod', 'VM', self.info['uuid'],
2113 'power_state')
2114 finally:
2115 self.state_updated.release()
2117 def _stateGet(self):
2118 # Lets try and reconsitute the state from xc
2119 # first lets try and get the domain info
2120 # from xc - this will tell us if the domain
2121 # exists
2122 info = dom_get(self.getDomid())
2123 if info is None or info['shutdown']:
2124 # We are either HALTED or SUSPENDED
2125 # check saved image exists
2126 from xen.xend import XendDomain
2127 managed_config_path = \
2128 XendDomain.instance()._managed_check_point_path( \
2129 self.get_uuid())
2130 if os.path.exists(managed_config_path):
2131 return XEN_API_VM_POWER_STATE_SUSPENDED
2132 else:
2133 return XEN_API_VM_POWER_STATE_HALTED
2134 else:
2135 # We are either RUNNING or PAUSED
2136 if info['paused']:
2137 return XEN_API_VM_POWER_STATE_PAUSED
2138 else:
2139 return XEN_API_VM_POWER_STATE_RUNNING
2141 def _infoIsSet(self, name):
2142 return name in self.info and self.info[name] is not None
2144 def _checkName(self, name):
2145 """Check if a vm name is valid. Valid names contain alphabetic
2146 characters, digits, or characters in '_-.:/+'.
2147 The same name cannot be used for more than one vm at the same time.
2149 @param name: name
2150 @raise: VmError if invalid
2151 """
2152 from xen.xend import XendDomain
2154 if name is None or name == '':
2155 raise VmError('Missing VM Name')
2157 if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name):
2158 raise VmError('Invalid VM Name')
2160 dom = XendDomain.instance().domain_lookup_nr(name)
2161 if dom and dom.domid != self.domid:
2162 raise VmError("VM name '%s' already exists%s" %
2163 (name,
2164 dom.domid is not None and
2165 (" as domain %s" % str(dom.domid)) or ""))
2168 def update(self, info = None, refresh = True):
2169 """Update with info from xc.domain_getinfo().
2170 """
2171 log.trace("XendDomainInfo.update(%s) on domain %s", info,
2172 str(self.domid))
2174 if not info:
2175 info = dom_get(self.domid)
2176 if not info:
2177 return
2179 if info["maxmem_kb"] < 0:
2180 info["maxmem_kb"] = XendNode.instance() \
2181 .physinfo_dict()['total_memory'] * 1024
2183 #ssidref field not used any longer
2184 if 'ssidref' in info:
2185 info.pop('ssidref')
2187 # make sure state is reset for info
2188 # TODO: we should eventually get rid of old_dom_states
2190 self.info.update_config(info)
2191 self._update_consoles()
2193 if refresh:
2194 self.refreshShutdown(info)
2196 log.trace("XendDomainInfo.update done on domain %s: %s",
2197 str(self.domid), self.info)
2199 def sxpr(self, ignore_store = False, legacy_only = True):
2200 result = self.info.to_sxp(domain = self,
2201 ignore_devices = ignore_store,
2202 legacy_only = legacy_only)
2204 #if not ignore_store and self.dompath:
2205 # vnc_port = self.readDom('console/vnc-port')
2206 # if vnc_port is not None:
2207 # result.append(['device',
2208 # ['console', ['vnc-port', str(vnc_port)]]])
2210 return result
2212 # Xen API
2213 # ----------------------------------------------------------------
2215 def get_uuid(self):
2216 dom_uuid = self.info.get('uuid')
2217 if not dom_uuid: # if it doesn't exist, make one up
2218 dom_uuid = uuid.createString()
2219 self.info['uuid'] = dom_uuid
2220 return dom_uuid
2222 def get_memory_static_max(self):
2223 return self.info.get('memory_static_max', 0)
2224 def get_memory_static_min(self):
2225 return self.info.get('memory_static_min', 0)
2226 def get_memory_dynamic_max(self):
2227 return self.info.get('memory_dynamic_max', 0)
2228 def get_memory_dynamic_min(self):
2229 return self.info.get('memory_dynamic_min', 0)
2231 # only update memory-related config values if they maintain sanity
2232 def _safe_set_memory(self, key, newval):
2233 oldval = self.info.get(key, 0)
2234 try:
2235 self.info[key] = newval
2236 self.info._memory_sanity_check()
2237 except Exception, ex:
2238 self.info[key] = oldval
2239 raise
2241 def set_memory_static_max(self, val):
2242 self._safe_set_memory('memory_static_max', val)
2243 def set_memory_static_min(self, val):
2244 self._safe_set_memory('memory_static_min', val)
2245 def set_memory_dynamic_max(self, val):
2246 self._safe_set_memory('memory_dynamic_max', val)
2247 def set_memory_dynamic_min(self, val):
2248 self._safe_set_memory('memory_dynamic_min', val)
2250 def get_vcpus_params(self):
2251 if self.getDomid() is None:
2252 return self.info['vcpus_params']
2254 retval = xc.sched_credit_domain_get(self.getDomid())
2255 return retval
2256 def get_power_state(self):
2257 return XEN_API_VM_POWER_STATE[self._stateGet()]
2258 def get_platform(self):
2259 return self.info.get('platform', {})
2260 def get_pci_bus(self):
2261 return self.info.get('pci_bus', '')
2262 def get_tools_version(self):
2263 return self.info.get('tools_version', {})
2264 def get_metrics(self):
2265 return self.metrics.get_uuid();
2268 def get_security_label(self, xspol=None):
2269 """
2270 Get the security label of a domain
2271 @param xspol The policy to use when converting the ssid into
2272 a label; only to be passed during the updating
2273 of the policy
2274 """
2275 domid = self.getDomid()
2277 if not xspol:
2278 from xen.xend.XendXSPolicyAdmin import XSPolicyAdminInstance
2279 xspol = XSPolicyAdminInstance().get_loaded_policy()
2281 if domid == 0:
2282 if xspol:
2283 label = xspol.policy_get_domain_label_formatted(domid)
2284 else:
2285 label = ""
2286 else:
2287 label = self.info.get('security_label', '')
2288 return label
2290 def set_security_label(self, seclab, old_seclab, xspol=None,
2291 xspol_old=None):
2292 """
2293 Set the security label of a domain from its old to
2294 a new value.
2295 @param seclab New security label formatted in the form
2296 <policy type>:<policy name>:<vm label>
2297 @param old_seclab The current security label that the
2298 VM must have.
2299 @param xspol An optional policy under which this
2300 update should be done. If not given,
2301 then the current active policy is used.
2302 @param xspol_old The old policy; only to be passed during
2303 the updating of a policy
2304 @return Returns return code, a string with errors from
2305 the hypervisor's operation, old label of the
2306 domain
2307 """
2308 rc = 0
2309 errors = ""
2310 old_label = ""
2311 new_ssidref = 0
2312 domid = self.getDomid()
2313 res_labels = None
2314 is_policy_update = (xspol_old != None)
2316 from xen.xend.XendXSPolicyAdmin import XSPolicyAdminInstance
2317 from xen.util import xsconstants
2319 state = self._stateGet()
2320 # Relabel only HALTED or RUNNING or PAUSED domains
2321 if domid != 0 and \
2322 state not in \
2323 [ DOM_STATE_HALTED, DOM_STATE_RUNNING, DOM_STATE_PAUSED, \
2324 DOM_STATE_SUSPENDED ]:
2325 log.warn("Relabeling domain not possible in state '%s'" %
2326 DOM_STATES[state])
2327 return (-xsconstants.XSERR_VM_WRONG_STATE, "", "", 0)
2329 # Remove security label. Works only for halted domains
2330 if not seclab or seclab == "":
2331 if state not in [ DOM_STATE_HALTED ]:
2332 return (-xsconstants.XSERR_VM_WRONG_STATE, "", "", 0)
2334 if self.info.has_key('security_label'):
2335 old_label = self.info['security_label']
2336 # Check label against expected one.
2337 if old_label != old_seclab:
2338 return (-xsconstants.XSERR_BAD_LABEL, "", "", 0)
2339 del self.info['security_label']
2340 xen.xend.XendDomain.instance().managed_config_save(self)
2341 return (xsconstants.XSERR_SUCCESS, "", "", 0)
2343 tmp = seclab.split(":")
2344 if len(tmp) != 3:
2345 return (-xsconstants.XSERR_BAD_LABEL_FORMAT, "", "", 0)
2346 typ, policy, label = tmp
2348 poladmin = XSPolicyAdminInstance()
2349 if not xspol:
2350 xspol = poladmin.get_policy_by_name(policy)
2352 if state in [ DOM_STATE_RUNNING, DOM_STATE_PAUSED ]:
2353 #if domain is running or paused try to relabel in hypervisor
2354 if not xspol:
2355 return (-xsconstants.XSERR_POLICY_NOT_LOADED, "", "", 0)
2357 if typ != xspol.get_type_name() or \
2358 policy != xspol.get_name():
2359 return (-xsconstants.XSERR_BAD_LABEL, "", "", 0)
2361 if typ == xsconstants.ACM_POLICY_ID:
2362 new_ssidref = xspol.vmlabel_to_ssidref(label)
2363 if new_ssidref == xsconstants.INVALID_SSIDREF:
2364 return (-xsconstants.XSERR_BAD_LABEL, "", "", 0)
2366 # Check that all used resources are accessible under the
2367 # new label
2368 if not is_policy_update and \
2369 not security.resources_compatible_with_vmlabel(xspol,
2370 self, label):
2371 return (-xsconstants.XSERR_BAD_LABEL, "", "", 0)
2373 #Check label against expected one.
2374 old_label = self.get_security_label(xspol_old)
2375 if old_label != old_seclab:
2376 log.info("old_label != old_seclab: %s != %s" %
2377 (old_label, old_seclab))
2378 return (-xsconstants.XSERR_BAD_LABEL, "", "", 0)
2380 # relabel domain in the hypervisor
2381 rc, errors = security.relabel_domains([[domid, new_ssidref]])
2382 log.info("rc from relabeling in HV: %d" % rc)
2383 else:
2384 return (-xsconstants.XSERR_POLICY_TYPE_UNSUPPORTED, "", "", 0)
2386 if rc == 0:
2387 # HALTED, RUNNING or PAUSED
2388 if domid == 0:
2389 if xspol:
2390 ssidref = poladmin.set_domain0_bootlabel(xspol, label)
2391 else:
2392 return (-xsconstants.XSERR_POLICY_NOT_LOADED, "", "", 0)
2393 else:
2394 if self.info.has_key('security_label'):
2395 old_label = self.info['security_label']
2396 # Check label against expected one, unless wildcard
2397 if old_label != old_seclab:
2398 return (-xsconstants.XSERR_BAD_LABEL, "", "", 0)
2400 self.info['security_label'] = seclab
2401 try:
2402 xen.xend.XendDomain.instance().managed_config_save(self)
2403 except:
2404 pass
2405 return (rc, errors, old_label, new_ssidref)
2407 def get_on_shutdown(self):
2408 after_shutdown = self.info.get('actions_after_shutdown')
2409 if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT:
2410 return XEN_API_ON_NORMAL_EXIT[-1]
2411 return after_shutdown
2413 def get_on_reboot(self):
2414 after_reboot = self.info.get('actions_after_reboot')
2415 if not after_reboot or after_reboot not in XEN_API_ON_NORMAL_EXIT:
2416 return XEN_API_ON_NORMAL_EXIT[-1]
2417 return after_reboot
2419 def get_on_suspend(self):
2420 # TODO: not supported
2421 after_suspend = self.info.get('actions_after_suspend')
2422 if not after_suspend or after_suspend not in XEN_API_ON_NORMAL_EXIT:
2423 return XEN_API_ON_NORMAL_EXIT[-1]
2424 return after_suspend
2426 def get_on_crash(self):
2427 after_crash = self.info.get('actions_after_crash')
2428 if not after_crash or after_crash not in XEN_API_ON_CRASH_BEHAVIOUR:
2429 return XEN_API_ON_CRASH_BEHAVIOUR[0]
2430 return after_crash
2432 def get_dev_config_by_uuid(self, dev_class, dev_uuid):
2433 """ Get's a device configuration either from XendConfig or
2434 from the DevController.
2436 @param dev_class: device class, either, 'vbd' or 'vif'
2437 @param dev_uuid: device UUID
2439 @rtype: dictionary
2440 """
2441 dev_type, dev_config = self.info['devices'].get(dev_uuid, (None, None))
2443 # shortcut if the domain isn't started because
2444 # the devcontrollers will have no better information
2445 # than XendConfig.
2446 if self._stateGet() in (XEN_API_VM_POWER_STATE_HALTED,):
2447 if dev_config:
2448 return copy.deepcopy(dev_config)
2449 return None
2451 # instead of using dev_class, we use the dev_type
2452 # that is from XendConfig.
2453 controller = self.getDeviceController(dev_type)
2454 if not controller:
2455 return None
2457 all_configs = controller.getAllDeviceConfigurations()
2458 if not all_configs:
2459 return None
2461 updated_dev_config = copy.deepcopy(dev_config)
2462 for _devid, _devcfg in all_configs.items():
2463 if _devcfg.get('uuid') == dev_uuid:
2464 updated_dev_config.update(_devcfg)
2465 updated_dev_config['id'] = _devid
2466 return updated_dev_config
2468 return updated_dev_config
2470 def get_dev_xenapi_config(self, dev_class, dev_uuid):
2471 config = self.get_dev_config_by_uuid(dev_class, dev_uuid)
2472 if not config:
2473 return {}
2475 config['VM'] = self.get_uuid()
2477 if dev_class == 'vif':
2478 if not config.has_key('name'):
2479 config['name'] = config.get('vifname', '')
2480 if not config.has_key('MAC'):
2481 config['MAC'] = config.get('mac', '')
2482 if not config.has_key('type'):
2483 config['type'] = 'paravirtualised'
2484 if not config.has_key('device'):
2485 devid = config.get('id')
2486 if devid != None:
2487 config['device'] = 'eth%d' % devid
2488 else:
2489 config['device'] = ''
2491 if not config.has_key('network'):
2492 try:
2493 bridge = config.get('bridge', None)
2494 if bridge is None:
2495 from xen.util import Brctl
2496 if_to_br = dict([(i,b)
2497 for (b,ifs) in Brctl.get_state().items()
2498 for i in ifs])
2499 vifname = "vif%s.%s" % (self.getDomid(),
2500 config.get('id'))
2501 bridge = if_to_br.get(vifname, None)
2502 config['network'] = \
2503 XendNode.instance().bridge_to_network(
2504 config.get('bridge')).get_uuid()
2505 except Exception:
2506 log.exception('bridge_to_network')
2507 # Ignore this for now -- it may happen if the device
2508 # has been specified using the legacy methods, but at
2509 # some point we're going to have to figure out how to
2510 # handle that properly.
2512 config['MTU'] = 1500 # TODO
2514 if self._stateGet() not in (XEN_API_VM_POWER_STATE_HALTED,):
2515 xennode = XendNode.instance()
2516 rx_bps, tx_bps = xennode.get_vif_util(self.domid, devid)
2517 config['io_read_kbs'] = rx_bps/1024
2518 config['io_write_kbs'] = tx_bps/1024
2519 else:
2520 config['io_read_kbs'] = 0.0
2521 config['io_write_kbs'] = 0.0
2523 config['security_label'] = config.get('security_label', '')
2525 if dev_class == 'vbd':
2527 if self._stateGet() not in (XEN_API_VM_POWER_STATE_HALTED,):
2528 controller = self.getDeviceController(dev_class)
2529 devid, _1, _2 = controller.getDeviceDetails(config)
2530 xennode = XendNode.instance()
2531 rd_blkps, wr_blkps = xennode.get_vbd_util(self.domid, devid)
2532 config['io_read_kbs'] = rd_blkps
2533 config['io_write_kbs'] = wr_blkps
2534 else:
2535 config['io_read_kbs'] = 0.0
2536 config['io_write_kbs'] = 0.0
2538 config['VDI'] = config.get('VDI', '')
2539 config['device'] = config.get('dev', '')
2540 if ':' in config['device']:
2541 vbd_name, vbd_type = config['device'].split(':', 1)
2542 config['device'] = vbd_name
2543 if vbd_type == 'cdrom':
2544 config['type'] = XEN_API_VBD_TYPE[0]
2545 else:
2546 config['type'] = XEN_API_VBD_TYPE[1]
2548 config['driver'] = 'paravirtualised' # TODO
2549 config['image'] = config.get('uname', '')
2551 if config.get('mode', 'r') == 'r':
2552 config['mode'] = 'RO'
2553 else:
2554 config['mode'] = 'RW'
2556 if dev_class == 'vtpm':
2557 if not config.has_key('type'):
2558 config['type'] = 'paravirtualised' # TODO
2559 if not config.has_key('backend'):
2560 config['backend'] = "00000000-0000-0000-0000-000000000000"
2562 return config
2564 def get_dev_property(self, dev_class, dev_uuid, field):
2565 config = self.get_dev_xenapi_config(dev_class, dev_uuid)
2566 try:
2567 return config[field]
2568 except KeyError:
2569 raise XendError('Invalid property for device: %s' % field)
2571 def set_dev_property(self, dev_class, dev_uuid, field, value):
2572 self.info['devices'][dev_uuid][1][field] = value
2574 def get_vcpus_util(self):
2575 vcpu_util = {}
2576 xennode = XendNode.instance()
2577 if 'VCPUs_max' in self.info and self.domid != None:
2578 for i in range(0, self.info['VCPUs_max']):
2579 util = xennode.get_vcpu_util(self.domid, i)
2580 vcpu_util[str(i)] = util
2582 return vcpu_util
2584 def get_consoles(self):
2585 return self.info.get('console_refs', [])
2587 def get_vifs(self):
2588 return self.info.get('vif_refs', [])
2590 def get_vbds(self):
2591 return self.info.get('vbd_refs', [])
2593 def get_vtpms(self):
2594 return self.info.get('vtpm_refs', [])
2596 def create_vbd(self, xenapi_vbd, vdi_image_path):
2597 """Create a VBD using a VDI from XendStorageRepository.
2599 @param xenapi_vbd: vbd struct from the Xen API
2600 @param vdi_image_path: VDI UUID
2601 @rtype: string
2602 @return: uuid of the device
2603 """
2604 xenapi_vbd['image'] = vdi_image_path
2605 if vdi_image_path.startswith('tap'):
2606 dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
2607 else:
2608 dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
2610 if not dev_uuid:
2611 raise XendError('Failed to create device')
2613 if self._stateGet() in (XEN_API_VM_POWER_STATE_RUNNING,
2614 XEN_API_VM_POWER_STATE_PAUSED):
2615 _, config = self.info['devices'][dev_uuid]
2617 if vdi_image_path.startswith('tap'):
2618 dev_control = self.getDeviceController('tap')
2619 else:
2620 dev_control = self.getDeviceController('vbd')
2622 try:
2623 devid = dev_control.createDevice(config)
2624 dev_control.waitForDevice(devid)
2625 self.info.device_update(dev_uuid,
2626 cfg_xenapi = {'devid': devid})
2627 except Exception, exn:
2628 log.exception(exn)
2629 del self.info['devices'][dev_uuid]
2630 self.info['vbd_refs'].remove(dev_uuid)
2631 raise
2633 return dev_uuid
2635 def create_phantom_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
2636 """Create a VBD using a VDI from XendStorageRepository.
2638 @param xenapi_vbd: vbd struct from the Xen API
2639 @param vdi_image_path: VDI UUID
2640 @rtype: string
2641 @return: uuid of the device
2642 """
2643 xenapi_vbd['image'] = vdi_image_path
2644 dev_uuid = self.info.phantom_device_add('tap', cfg_xenapi = xenapi_vbd)
2645 if not dev_uuid:
2646 raise XendError('Failed to create device')
2648 if self._stateGet() == XEN_API_VM_POWER_STATE_RUNNING:
2649 _, config = self.info['devices'][dev_uuid]
2650 config['devid'] = self.getDeviceController('tap').createDevice(config)
2652 return config['devid']
2654 def create_vif(self, xenapi_vif):
2655 """Create VIF device from the passed struct in Xen API format.
2657 @param xenapi_vif: Xen API VIF Struct.
2658 @rtype: string
2659 @return: UUID
2660 """
2661 dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
2662 if not dev_uuid:
2663 raise XendError('Failed to create device')
2665 if self._stateGet() in (XEN_API_VM_POWER_STATE_RUNNING,
2666 XEN_API_VM_POWER_STATE_PAUSED):
2668 _, config = self.info['devices'][dev_uuid]
2669 dev_control = self.getDeviceController('vif')
2671 try:
2672 devid = dev_control.createDevice(config)
2673 dev_control.waitForDevice(devid)
2674 self.info.device_update(dev_uuid,
2675 cfg_xenapi = {'devid': devid})
2676 except Exception, exn:
2677 log.exception(exn)
2678 del self.info['devices'][dev_uuid]
2679 self.info['vif_refs'].remove(dev_uuid)
2680 raise
2682 return dev_uuid
2684 def create_vtpm(self, xenapi_vtpm):
2685 """Create a VTPM device from the passed struct in Xen API format.
2687 @return: uuid of the device
2688 @rtype: string
2689 """
2691 if self._stateGet() not in (DOM_STATE_HALTED,):
2692 raise VmError("Can only add vTPM to a halted domain.")
2693 if self.get_vtpms() != []:
2694 raise VmError('Domain already has a vTPM.')
2695 dev_uuid = self.info.device_add('vtpm', cfg_xenapi = xenapi_vtpm)
2696 if not dev_uuid:
2697 raise XendError('Failed to create device')
2699 return dev_uuid
2701 def create_console(self, xenapi_console):
2702 """ Create a console device from a Xen API struct.
2704 @return: uuid of device
2705 @rtype: string
2706 """
2707 if self._stateGet() not in (DOM_STATE_HALTED,):
2708 raise VmError("Can only add console to a halted domain.")
2710 dev_uuid = self.info.device_add('console', cfg_xenapi = xenapi_console)
2711 if not dev_uuid:
2712 raise XendError('Failed to create device')
2714 return dev_uuid
2716 def set_console_other_config(self, console_uuid, other_config):
2717 self.info.console_update(console_uuid, 'other_config', other_config)
2719 def destroy_device_by_uuid(self, dev_type, dev_uuid):
2720 if dev_uuid not in self.info['devices']:
2721 raise XendError('Device does not exist')
2723 try:
2724 if self._stateGet() in (XEN_API_VM_POWER_STATE_RUNNING,
2725 XEN_API_VM_POWER_STATE_PAUSED):
2726 _, config = self.info['devices'][dev_uuid]
2727 devid = config.get('devid')
2728 if devid != None:
2729 self.getDeviceController(dev_type).destroyDevice(devid, force = False)
2730 else:
2731 raise XendError('Unable to get devid for device: %s:%s' %
2732 (dev_type, dev_uuid))
2733 finally:
2734 del self.info['devices'][dev_uuid]
2735 self.info['%s_refs' % dev_type].remove(dev_uuid)
2737 def destroy_vbd(self, dev_uuid):
2738 self.destroy_device_by_uuid('vbd', dev_uuid)
2740 def destroy_vif(self, dev_uuid):
2741 self.destroy_device_by_uuid('vif', dev_uuid)
2743 def destroy_vtpm(self, dev_uuid):
2744 self.destroy_device_by_uuid('vtpm', dev_uuid)
2746 def has_device(self, dev_class, dev_uuid):
2747 return (dev_uuid in self.info['%s_refs' % dev_class.lower()])
2749 def __str__(self):
2750 return '<domain id=%s name=%s memory=%s state=%s>' % \
2751 (str(self.domid), self.info['name_label'],
2752 str(self.info['memory_dynamic_max']), DOM_STATES[self._stateGet()])
2754 __repr__ = __str__