[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-changelog] [xen-unstable] [XEND] Massive XendDomain XendDomainInfo reorganisation to use XendConfig.
# HG changeset patch # User Alastair Tse <atse@xxxxxxxxxxxxx> # Node ID 9a932b5c7947cb1512e3f3a1fe65a5541e56e425 # Parent ec29b6262a8bb7a3c659b208ffeb310ecd9595e0 [XEND] Massive XendDomain XendDomainInfo reorganisation to use XendConfig. * Added some constants to do with lifecycle management in XendDomain * Added docstrings * Made all private functions prefixed with an underscore (_) * Added lifecycle management in XendDomain which stores configs in SXP format. * Added bits of XenAPI support. * Moved all XendDomainInfo constants to a separate file * Moved most of the domain creation login into XendDomainInfo rather than in module level functions. * Moved to use Xen API compatible reporting of state. * Reorganised methods into logical groups in XendDomain and XendDomainInfo * Moved all domain configuration validation into XendConfig * Removed DevController cruft from the bottom of XendDomainInfo to XendDevices. Signed-off-by: Alastair Tse <atse@xxxxxxxxxxxxx> --- tools/python/xen/xend/XendDomain.py | 1268 ++++++++++---- tools/python/xen/xend/XendDomainInfo.py | 2342 +++++++++++++------------- tools/python/xen/xend/server/DevController.py | 66 tools/python/xen/xend/server/SrvDomainDir.py | 2 tools/python/xen/xend/server/netif.py | 38 tools/python/xen/xend/server/pciif.py | 53 6 files changed, 2244 insertions(+), 1525 deletions(-) diff -r ec29b6262a8b -r 9a932b5c7947 tools/python/xen/xend/XendDomain.py --- a/tools/python/xen/xend/XendDomain.py Thu Oct 05 17:29:19 2006 +0100 +++ b/tools/python/xen/xend/XendDomain.py Thu Oct 05 17:29:19 2006 +0100 @@ -22,45 +22,59 @@ Needs to be persistent for one uptime. """ -import logging import os +import shutil import socket -import sys import threading import xen.lowlevel.xc -import XendDomainInfo - -from xen.xend import XendRoot -from xen.xend import XendCheckpoint + +from xen.xend import XendRoot, XendCheckpoint, XendDomainInfo +from xen.xend.PrettyPrint import prettyprint +from xen.xend.XendConfig import XendConfig from xen.xend.XendError import XendError, XendInvalidDomain from xen.xend.XendLogging import log +from xen.xend.XendConstants import XS_VMROOT + from xen.xend.xenstore.xstransact import xstransact from xen.xend.xenstore.xswatch import xswatch from xen.util import security - +from xen.xend import uuid xc = xen.lowlevel.xc.xc() -xroot = XendRoot.instance() - +xroot = XendRoot.instance() __all__ = [ "XendDomain" ] -PRIV_DOMAIN = 0 -VMROOT = '/vm/' - +CACHED_CONFIG_FILE = 'config.sxp' +CHECK_POINT_FILE = 'checkpoint.chk' +DOM0_UUID = "00000000-0000-0000-0000-000000000000" +DOM0_NAME = "Domain-0" +DOM0_ID = 0 class XendDomain: """Index of all domains. Singleton. + + @ivar domains: map of domains indexed by UUID Strings + @type domains: dict of XendDomainInfo + @ivar domains_managed: uuid of domains that are managed by Xend + @type managed_domains: list of (uuids, dom_name) + @ivar domains_lock: lock that must be held when manipulating self.domains + @type domains_lock: threaading.RLock + @ivar _allow_new_domains: Flag to set that allows creating of new domains. + @type _allow_new_domains: boolean + """ - ## public: - def __init__(self): self.domains = {} + self.managed_domains = [] self.domains_lock = threading.RLock() + # xen api instance vars + # TODO: nothing uses this at the moment + self._allow_new_domains = True # This must be called only the once, by instance() below. It is separate # from the constructor because XendDomainInfo calls back into this class @@ -68,85 +82,266 @@ class XendDomain: # instance() must be able to return a valid instance of this class even # during this initialisation. def init(self): - xstransact.Mkdir(VMROOT) - xstransact.SetPermissions(VMROOT, { 'dom' : PRIV_DOMAIN }) - - self.domains_lock.acquire() - try: - self._add_domain( - XendDomainInfo.recreate(self.xen_domains()[PRIV_DOMAIN], - True)) - self.dom0_setup() + """Singleton initialisation function.""" + + xstransact.Mkdir(XS_VMROOT) + xstransact.SetPermissions(XS_VMROOT, {'dom': DOM0_ID}) + + self.domains_lock.acquire() + try: + try: + dom0info = [d for d in self._running_domains() \ + if d['domid'] == DOM0_ID][0] + + dom0 = XendDomainInfo.recreate(dom0info, True) + # Sometimes this is not set? + dom0.setName(DOM0_NAME) + self._add_domain(dom0) + except IndexError: + raise XendError('Unable to find Domain 0') + + self._setDom0CPUCount() # This watch registration needs to be before the refresh call, so # that we're sure that we haven't missed any releases, but inside # the domains_lock, as we don't want the watch to fire until after # the refresh call has completed. - xswatch("@introduceDomain", self.onChangeDomain) - xswatch("@releaseDomain", self.onChangeDomain) + xswatch("@introduceDomain", self._on_domains_changed) + xswatch("@releaseDomain", self._on_domains_changed) + + self._init_domains() + finally: + self.domains_lock.release() + + + def _on_domains_changed(self, _): + """ Callback method when xenstore changes. + + Calls refresh which will keep the local cache of domains + in sync. + + @rtype: int + @return: 1 + """ + self.domains_lock.acquire() + try: + self._refresh() + finally: + self.domains_lock.release() + return 1 + + def _init_domains(self): + """Does the initial scan of managed and active domains to + populate self.domains. + + Note: L{XendDomainInfo._checkName} will call back into XendDomain + to make sure domain name is not a duplicate. + + """ + self.domains_lock.acquire() + try: + running = self._running_domains() + managed = self._managed_domains() + + # add all active domains, replacing managed ones + for dom in running: + if dom['domid'] != DOM0_ID: + try: + new_dom = XendDomainInfo.recreate(dom, False) + self._add_domain(new_dom) + except Exception: + log.exception("Failed to create reference to running " + "domain id: %d" % dom['domid']) + + # add all managed domains as dormant domains. + for dom in managed: + dom_uuid = dom.get('uuid', uuid.createString()) + dom['uuid'] = dom_uuid + dom_name = dom.get('name', 'Domain-%s' % dom_uuid) + + try: + # instantiate domain if not started. + if not self.domain_lookup_nr(dom_name): + new_dom = XendDomainInfo.createDormant(dom) + self._add_domain(new_dom) + # keep track of maanged domains + self._managed_domain_register(new_dom) + except Exception: + log.exception("Failed to create reference to managed " + "domain: %s" % dom_name) + + finally: + self.domains_lock.release() + + + # ----------------------------------------------------------------- + # Getting managed domains storage path names + + def _managed_path(self, domuuid = None): + """Returns the path of the directory where managed domain + information is stored. + + @keyword domuuid: If not None, will return the path to the domain + otherwise, will return the path containing + the directories which represent each domain. + @type: None or String. + @rtype: String + @return: Path. + """ + dom_path = xroot.get_xend_domains_path() + if domuuid: + dom_path = os.path.join(dom_path, domuuid) + return dom_path + + def _managed_config_path(self, domuuid): + """Returns the path to the configuration file of a managed domain. + + @param domname: Domain uuid + @type domname: String + @rtype: String + @return: path to config file. + """ + return os.path.join(self._managed_path(domuuid), CACHED_CONFIG_FILE) + + def _managed_check_point_path(self, domuuid): + """Returns absolute path to check point file for managed domain. + + @param domuuid: Name of managed domain + @type domname: String + @rtype: String + @return: Path + """ + return os.path.join(self._managed_path(domuuid), CHECK_POINT_FILE) + + def _managed_config_remove(self, domuuid): + """Removes a domain configuration from managed list + + @param domuuid: Name of managed domain + @type domname: String + @raise XendError: fails to remove the domain. + """ + config_path = self._managed_path(domuuid) + try: + if os.path.exists(config_path) and os.path.isdir(config_path): + shutil.rmtree(config_path) + except IOError: + log.exception('managed_config_remove failed removing conf') + raise XendError("Unable to remove managed configuration" + " for domain: %s" % domuuid) + + def managed_config_save(self, dominfo): + """Save a domain's configuration to disk + + @param domninfo: Managed domain to save. + @type dominfo: XendDomainInfo + @raise XendError: fails to save configuration. + @rtype: None + """ + if dominfo: + domains_dir = self._managed_path() + dom_uuid = dominfo.get_uuid() + domain_config_dir = self._managed_path(dom_uuid) + + # make sure the domain dir exists + if not os.path.exists(domains_dir): + os.makedirs(domains_dir, 0755) + elif not os.path.isdir(domains_dir): + log.error("xend_domain_dir is not a directory.") + raise XendError("Unable to save managed configuration " + "because %s is not a directory." % + domains_dir) - self.refresh(True) - finally: - self.domains_lock.release() - - - def list(self): - """Get list of domain objects. - - @return: domain objects - """ - self.domains_lock.acquire() - try: - self.refresh() - return self.domains.values() - finally: - self.domains_lock.release() - - - def list_sorted(self): - """Get list of domain objects, sorted by name. - - @return: domain objects - """ - doms = self.list() - doms.sort(lambda x, y: cmp(x.getName(), y.getName())) + if not os.path.exists(domain_config_dir): + try: + os.makedirs(domain_config_dir, 0755) + except IOError: + log.exception("Failed to create directory: %s" % + domain_config_dir) + raise XendError("Failed to create directory: %s" % + domain_config_dir) + + try: + sxp_cache_file = open(self._managed_config_path(dom_uuid),'w') + prettyprint(dominfo.sxpr(), sxp_cache_file, width = 78) + sxp_cache_file.close() + except IOError: + log.error("Error occurred saving configuration file to %s" % + domain_config_dir) + raise XendError("Failed to save configuration file to: %s" % + domain_config_dir) + else: + log.warn("Trying to save configuration for invalid domain") + + + def _managed_domains(self): + """ Returns list of domains that are managed. + + Expects to be protected by domains_lock. + + @rtype: list of XendConfig + @return: List of domain configurations that are managed. + """ + dom_path = self._managed_path() + dom_uuids = os.listdir(dom_path) + doms = [] + for dom_uuid in dom_uuids: + try: + cfg_file = self._managed_config_path(dom_uuid) + cfg = XendConfig(filename = cfg_file) + doms.append(cfg) + except Exception: + log.exception('Unable to open or parse config.sxp: %s' % \ + cfg_file) return doms - def list_names(self): - """Get list of domain names. - - @return: domain names - """ - doms = self.list_sorted() - return map(lambda x: x.getName(), doms) - - - ## private: - - def onChangeDomain(self, _): - self.domains_lock.acquire() - try: - self.refresh() - finally: - self.domains_lock.release() - return 1 - - - def xen_domains(self): - """Get table of domains indexed by id from xc. Expects to be - protected by the domains_lock. - """ - domlist = xc.domain_getinfo() - doms = {} - for d in domlist: - domid = d['dom'] - doms[domid] = d - return doms - - - def dom0_setup(self): - """Expects to be protected by the domains_lock.""" - dom0 = self.domains[PRIV_DOMAIN] + def _managed_domain_unregister(self, dom): + try: + self.managed_domains.remove((dom.get_uuid(), dom.getName())) + except ValueError: + log.warn("Domain is not registered: %s" % dom.get_uuid()) + + def _managed_domain_register(self, dom): + self.managed_domains.append((dom.get_uuid(), dom.getName())) + + def _managed_domain_rename(self, dom, new_name): + for i in range(len(self.managed_domains)): + if self.managed_domains[i][0] == dom.get_uuid(): + self.managed_domains[i][1] = new_name + return True + return False + + def is_domain_managed(self, dom = None, dom_name = None): + dom_uuid = dom.get_uuid() + dom_name = dom.getName() + if dom: + return ((dom_uuid, dom_name) in self.managed_domains) + if dom_name: + results = [d for d in self.managed_domains if d[1] == dom_name] + return (len(results) > 0) + return False + + + + # End of Managed Domain Access + # -------------------------------------------------------------------- + + def _running_domains(self): + """Get table of domains indexed by id from xc. + + @requires: Expects to be protected by domains_lock. + @rtype: list of dicts + @return: A list of dicts representing the running domains. + """ + return xc.domain_getinfo() + + def _setDom0CPUCount(self): + """Sets the number of VCPUs dom0 has. Retreived from the + Xend configuration, L{XendRoot}. + + @requires: Expects to be protected by domains_lock. + @rtype: None + """ + dom0 = self.privilegedDomain() # get max number of vcpus to use for dom0 from config target = int(xroot.get_dom0_vcpus()) @@ -157,71 +352,426 @@ class XendDomain: dom0.setVCpuCount(target) - def _add_domain(self, info): + def _refresh(self): + """Refresh the domain list. Needs to be called when + either xenstore has changed or when a method requires + up to date information (like uptime, cputime stats). + + @rtype: None + """ + self.domains_lock.acquire() + try: + # update information for all running domains + # - like cpu_time, status, dying, etc. + running = self._running_domains() + for dom in running: + dom_info = self.domain_lookup_nr(dom['domid']) + if dom_info: + dom_info.update(dom) + + # clean up unmanaged domains + for dom in self.domains.values(): + if (dom.getDomid() == None) and \ + not self.is_domain_managed(dom): + self._remove_domain(dom) + + finally: + self.domains_lock.release() + + def _add_domain(self, info, managed = False): """Add the given domain entry to this instance's internal cache. - Expects to be protected by the domains_lock. - """ - self.domains[info.getDomid()] = info - - - def _delete_domain(self, domid): + + @requires: Expects to be protected by the domains_lock. + @param info: XendDomainInfo of a domain to be added. + @type info: XendDomainInfo + @keyword managed: Whether this domain is maanged by Xend + @type managed: boolean + """ + log.debug("Adding Domain: %s" % info.get_uuid()) + self.domains[info.get_uuid()] = info + if managed and not self.is_domain_managed(info): + self._managed_domain_register(info) + + def _remove_domain(self, info): """Remove the given domain from this instance's internal cache. - Expects to be protected by the domains_lock. - """ - info = self.domains.get(domid) + + @requires: Expects to be protected by the domains_lock. + @param info: XendDomainInfo of a domain to be removed. + @type info: XendDomainInfo + """ if info: - del self.domains[domid] - info.cleanupDomain() - - - def refresh(self, initialising = False): - """Refresh domain list from Xen. Expects to be protected by the - domains_lock. - - @param initialising True if this is the first refresh after starting - Xend. This does not change this method's behaviour, except for - logging. - """ - doms = self.xen_domains() - for d in self.domains.values(): - info = doms.get(d.getDomid()) - if info: - d.update(info) - else: - self._delete_domain(d.getDomid()) - for d in doms: - if d not in self.domains: - if doms[d]['dying']: - log.log(initialising and logging.ERROR or logging.DEBUG, - 'Cannot recreate information for dying domain %d.' - ' Xend will ignore this domain from now on.', - doms[d]['dom']) - elif d == PRIV_DOMAIN: - log.fatal( - "No record of privileged domain %d! Terminating.", d) - sys.exit(1) - else: - try: - self._add_domain( - XendDomainInfo.recreate(doms[d], False)) - except: - log.exception( - "Failed to recreate information for domain " - "%d. Destroying it in the hope of " - "recovery.", d) - try: - xc.domain_destroy(d) - except: - log.exception('Destruction of %d failed.', d) - - - ## public: + dom_name = info.getName() + dom_uuid = info.get_uuid() + + if info.state != XendDomainInfo.DOM_STATE_HALTED: + info.cleanupDomain() + + if self.is_domain_managed(info): + self._managed_config_remove(dom_uuid) + self._managed_domain_unregister(info) + + try: + del self.domains[dom_uuid] + except KeyError: + pass + else: + log.warning("Attempted to remove non-existent domain.") + + def restore_(self, config): + """Create a domain as part of the restore process. This is called + only from L{XendCheckpoint}. + + A restore request comes into XendDomain through L{domain_restore} + or L{domain_restore_fd}. That request is + forwarded immediately to XendCheckpoint which, when it is ready, will + call this method. It is necessary to come through here rather than go + directly to L{XendDomainInfo.restore} because we need to + serialise the domain creation process, but cannot lock + domain_restore_fd as a whole, otherwise we will deadlock waiting for + the old domain to die. + + @param config: Configuration of domain to restore + @type config: SXP Object (eg. list of lists) + """ + self.domains_lock.acquire() + try: + security.refresh_ssidref(config) + dominfo = XendDomainInfo.restore(config) + self._add_domain(dominfo) + return dominfo + finally: + self.domains_lock.release() + + + def domain_lookup(self, domid): + """Look up given I{domid} in the list of managed and running + domains. + + @note: Will cause a refresh before lookup up domains, for + a version that does not need to re-read xenstore + use L{domain_lookup_nr}. + + @param domid: Domain ID or Domain Name. + @type domid: int or string + @return: Found domain. + @rtype: XendDomainInfo + @raise XendError: If domain is not found. + """ + self.domains_lock.acquire() + try: + self._refresh() + dom = self.domain_lookup_nr(domid) + if not dom: + raise XendError("No domain named '%s'." % str(domid)) + return dom + finally: + self.domains_lock.release() + + + def domain_lookup_nr(self, domid): + """Look up given I{domid} in the list of managed and running + domains. + + @param domid: Domain ID or Domain Name. + @type domid: int or string + @return: Found domain. + @rtype: XendDomainInfo or None + """ + self.domains_lock.acquire() + try: + # lookup by name + match = [dom for dom in self.domains.values() \ + if dom.getName() == domid] + if match: + return match[0] + + # lookup by id + try: + match = [d for d in self.domains.values() \ + if d.getDomid() == int(domid)] + if match: + return match[0] + except (ValueError, TypeError): + pass + + return None + finally: + self.domains_lock.release() + + def privilegedDomain(self): + """ Get the XendDomainInfo of a dom0 + + @rtype: XendDomainInfo + """ + self.domains_lock.acquire() + try: + return self.domains[DOM0_UUID] + finally: + self.domains_lock.release() + + def cleanup_domains(self): + """Clean up domains that are marked as autostop. + Should be called when Xend goes down. This is currently + called from L{xen.xend.servers.XMLRPCServer}. + + """ + log.debug('cleanup_domains') + self.domains_lock.acquire() + try: + for dom in self.domains.values(): + if dom.getName() == DOM0_NAME: + continue + + if dom.state == XendDomainInfo.DOM_STATE_RUNNING: + shouldShutdown = dom.info.get('autostop', 0) + shutdownAction = dom.info.get('on_xend_stop', 'shutdown') + if shouldShutdown and shutdownAction == 'shutdown': + log.debug('Shutting down domain: %s' % dom.getName()) + dom.shutdown("poweroff") + elif shouldShutdown and shutdownAction == 'suspend': + chkfile = self._managed_check_point_path(dom.getName()) + self.domain_save(dom.domid, chkfile) + finally: + self.domains_lock.release() + + + + # ---------------------------------------------------------------- + # Xen API + + + def set_allow_new_domains(self, allow_new_domains): + self._allow_new_domains = allow_new_domains + + def allow_new_domains(self): + return self._allow_new_domains + + def get_domain_refs(self): + result = [] + try: + self.domains_lock.acquire() + result = [d.getVMRef() for d in self.domains] + finally: + self.domains_lock.release() + return result + + def get_vm_by_uuid(self, vm_uuid): + self.domains_lock.acquire() + try: + if vm_uuid in self.domains: + return self.domains[vm_uuid] + return None + finally: + self.domains_lock.release() + + def get_dev_by_uuid(self, klass, dev_uuid, field): + parts = dev_uuid.split('-%s-' % klass, 1) + try: + if len(parts) > 1: + dom = self.get_vm_by_uuid(parts[0]) + if not dom: + return None + + if field == 'VM': + return dom.get_uuid() + if field == 'uuid': + return dev_uuid + + devid = int(parts[1]) + value = dom.get_device_property(klass, devid, field) + return value + except ValueError, e: + pass + + return None + + def is_valid_vm(self, vm_ref): + return (self.get_vm_by_uuid(vm_ref) != None) + + def is_valid_dev(self, klass, dev_uuid): + parts = dev_uuid.split('-%s-' % klass, 1) + try: + if len(parts) > 1: + dom = self.get_vm_by_uuid(parts[0]) + if not dom: + return False + devid = int(parts[1]) + return dom.isDeviceValid(klass, devid) + except ValueError, e: + pass + + return False + + def do_legacy_api_with_uuid(self, fn, vm_uuid, *args): + self.domains_lock.acquire() + try: + if vm_uuid in self.domains: + # problem is domid does not exist for unstarted + # domains, so in that case, we use the name. + # TODO: probably want to modify domain_lookup_nr + # to lookup uuids, or just ignore + # the legacy api and reimplement all these + # calls. + domid = self.domains[vm_uuid].getDomid() + if domid == None: + domid = self.domains[vm_uuid].getName() + return fn(domid, *args) + raise XendInvalidDomain("Domain does not exist") + finally: + self.domains_lock.release() + + + def create_domain(self, xenapi_vm): + self.domains_lock.acquire() + try: + try: + xeninfo = XendConfig(xenapi_vm = xenapi_vm) + dominfo = XendDomainInfo.createDormant(xeninfo) + log.debug("Creating new managed domain: %s: %s" % + (dominfo.getName(), dominfo.get_uuid())) + self._add_domain(dominfo, managed = True) + self.managed_config_save(dominfo) + return dominfo.get_uuid() + except XendError, e: + raise + except Exception, e: + raise XendError(str(e)) + finally: + self.domains_lock.release() + + def rename_domain(self, dom, new_name): + self.domains_lock.acquire() + try: + old_name = dom.getName() + dom.setName(new_name) + + if self.is_domain_managed(dom): + self._managed_domain_rename(dom, new_name) + + finally: + self.domains_lock.release() + + + # + # End of Xen API + # ---------------------------------------------------------------- + + # ------------------------------------------------------------ + # Xen Legacy API + + def list(self): + """Get list of domain objects. + + @return: domains + @rtype: list of XendDomainInfo + """ + self.domains_lock.acquire() + try: + self._refresh() + return self.domains.values() + finally: + self.domains_lock.release() + + + def list_sorted(self): + """Get list of domain objects, sorted by name. + + @return: domain objects + @rtype: list of XendDomainInfo + """ + doms = self.list() + doms.sort(lambda x, y: cmp(x.getName(), y.getName())) + return doms + + def list_names(self): + """Get list of domain names. + + @return: domain names + @rtype: list of strings. + """ + return [d.getName() for d in self.list_sorted()] + + def domain_suspend(self, domname): + """Suspends a domain that is persistently managed by Xend + + @param domname: Domain Name + @type domname: string + @rtype: None + @raise XendError: Failure during checkpointing. + """ + + try: + dominfo = self.domain_lookup_nr(domname) + if not dominfo: + raise XendInvalidDomain(domname) + + if dominfo.getDomid() == DOM0_ID: + raise XendError("Cannot save privileged domain %s" % domname) + + if dominfo.state != XendDomainInfo.DOM_STATE_RUNNING: + raise XendError("Cannot suspend domain that is not running.") + + if not os.path.exists(self._managed_config_path(domname)): + raise XendError("Domain is not managed by Xend lifecycle " + + "support.") + + path = self._managed_check_point_path(domname) + fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC) + try: + # For now we don't support 'live checkpoint' + XendCheckpoint.save(fd, dominfo, False, False, path) + finally: + os.close(fd) + except OSError, ex: + raise XendError("can't write guest state file %s: %s" % + (path, ex[1])) + + def domain_resume(self, domname): + """Resumes a domain that is persistently managed by Xend. + + @param domname: Domain Name + @type domname: string + @rtype: None + @raise XendError: If failed to restore. + """ + try: + dominfo = self.domain_lookup_nr(domname) + + if not dominfo: + raise XendInvalidDomain(domname) + + if dominfo.getDomid() == DOM0_ID: + raise XendError("Cannot save privileged domain %s" % domname) + + if dominfo.state != XendDomainInfo.DOM_STATE_HALTED: + raise XendError("Cannot suspend domain that is not running.") + + chkpath = self._managed_check_point_path(domname) + if not os.path.exists(chkpath): + raise XendError("Domain was not suspended by Xend") + + # Restore that replaces the existing XendDomainInfo + try: + log.debug('Current DomainInfo state: %d' % dominfo.state) + XendCheckpoint.restore(self, + os.open(chkpath, os.O_RDONLY), + dominfo) + os.unlink(chkpath) + except OSError, ex: + raise XendError("Failed to read stored checkpoint file") + except IOError, ex: + raise XendError("Failed to delete checkpoint file") + except Exception, ex: + log.exception("Exception occurred when resuming") + raise XendError("Error occurred when resuming: %s" % str(ex)) + def domain_create(self, config): """Create a domain from a configuration. @param config: configuration - @return: domain + @type config: SXP Object (list of lists) + @rtype: XendDomainInfo """ self.domains_lock.acquire() try: @@ -232,10 +782,89 @@ class XendDomain: self.domains_lock.release() + def domain_new(self, config): + """Create a domain from a configuration but do not start it. + + @param config: configuration + @type config: SXP Object (list of lists) + @rtype: XendDomainInfo + """ + self.domains_lock.acquire() + try: + try: + xeninfo = XendConfig(sxp = config) + dominfo = XendDomainInfo.createDormant(xeninfo) + log.debug("Creating new managed domain: %s" % + dominfo.getName()) + self._add_domain(dominfo, managed = True) + self.managed_config_save(dominfo) + # no return value because it isn't meaningful for client + except XendError, e: + raise + except Exception, e: + raise XendError(str(e)) + finally: + self.domains_lock.release() + + def domain_start(self, domid): + """Start a managed domain + + @require: Domain must not be running. + @param domid: Domain name or domain ID. + @type domid: string or int + @rtype: None + @raise XendError: If domain is still running + @rtype: None + """ + self.domains_lock.acquire() + try: + dominfo = self.domain_lookup_nr(domid) + if not dominfo: + raise XendInvalidDomain(str(domid)) + + if dominfo.state != XendDomainInfo.DOM_STATE_HALTED: + raise XendError("Domain is already running") + + dominfo.start(is_managed = True) + + + finally: + self.domains_lock.release() + + + def domain_delete(self, domid): + """Remove a domain from database + + @require: Domain must not be running. + @param domid: Domain name or domain ID. + @type domid: string or int + @rtype: None + @raise XendError: If domain is still running + """ + self.domains_lock.acquire() + try: + try: + dominfo = self.domain_lookup_nr(domid) + if not dominfo: + raise XendInvalidDomain(str(domid)) + + if dominfo.state != XendDomainInfo.DOM_STATE_HALTED: + raise XendError("Domain is still running") + + self._remove_domain(dominfo) + + except Exception, ex: + raise XendError(str(ex)) + finally: + self.domains_lock.release() + + def domain_configure(self, config): """Configure an existing domain. @param vmconfig: vm configuration + @type vmconfig: SXP Object (list of lists) + @todo: Not implemented """ # !!! raise XendError("Unsupported") @@ -243,9 +872,12 @@ class XendDomain: def domain_restore(self, src): """Restore a domain from file. - @param src: source file - """ - + @param src: filename of checkpoint file to restore from + @type src: string + @return: Restored domain + @rtype: XendDomainInfo + @raise XendError: Failure to restore domain + """ try: fd = os.open(src, os.O_RDONLY) try: @@ -257,7 +889,13 @@ class XendDomain: (src, ex[1])) def domain_restore_fd(self, fd): - """Restore a domain from the given file descriptor.""" + """Restore a domain from the given file descriptor. + + @param fd: file descriptor of the checkpoint file + @type fd: File object + @rtype: XendDomainInfo + @raise XendError: if failed to restore + """ try: return XendCheckpoint.restore(self, fd) @@ -267,137 +905,63 @@ class XendDomain: # poor, so we need to log this for debugging. log.exception("Restore failed") raise XendError("Restore failed") - - - def restore_(self, config): - """Create a domain as part of the restore process. This is called - only from {@link XendCheckpoint}. - - A restore request comes into XendDomain through {@link - #domain_restore} or {@link #domain_restore_fd}. That request is - forwarded immediately to XendCheckpoint which, when it is ready, will - call this method. It is necessary to come through here rather than go - directly to {@link XendDomainInfo.restore} because we need to - serialise the domain creation process, but cannot lock - domain_restore_fd as a whole, otherwise we will deadlock waiting for - the old domain to die. - """ - self.domains_lock.acquire() - try: - security.refresh_ssidref(config) - dominfo = XendDomainInfo.restore(config) - self._add_domain(dominfo) - return dominfo - finally: - self.domains_lock.release() - - - def domain_lookup(self, domid): - self.domains_lock.acquire() - try: - self.refresh() - return self.domains.get(domid) - finally: - self.domains_lock.release() - - - def domain_lookup_nr(self, domid): - self.domains_lock.acquire() - try: - return self.domains.get(domid) - finally: - self.domains_lock.release() - - - def domain_lookup_by_name_or_id(self, name): - self.domains_lock.acquire() - try: - self.refresh() - return self.domain_lookup_by_name_or_id_nr(name) - finally: - self.domains_lock.release() - - - def domain_lookup_by_name_or_id_nr(self, name): - self.domains_lock.acquire() - try: - dominfo = self.domain_lookup_by_name_nr(name) - - if dominfo: - return dominfo - else: - try: - return self.domains.get(int(name)) - except ValueError: - return None - finally: - self.domains_lock.release() - - - def domain_lookup_by_name_nr(self, name): - self.domains_lock.acquire() - try: - matching = filter(lambda d: d.getName() == name, - self.domains.values()) - n = len(matching) - if n == 1: - return matching[0] - return None - finally: - self.domains_lock.release() - - - def privilegedDomain(self): - self.domains_lock.acquire() - try: - return self.domains[PRIV_DOMAIN] - finally: - self.domains_lock.release() - def domain_unpause(self, domid): - """Unpause domain execution.""" + """Unpause domain execution. + + @param domid: Domain ID or Name + @type domid: int or string. + @rtype: None + @raise XendError: Failed to unpause + @raise XendInvalidDomain: Domain is not valid + """ + try: + dominfo = self.domain_lookup_nr(domid) + if not dominfo: + raise XendInvalidDomain(str(domid)) + + log.info("Domain %s (%d) unpaused.", dominfo.getName(), + int(dominfo.getDomid())) + + dominfo.unpause() + except XendInvalidDomain: + log.exception("domain_unpause") + raise + except Exception, ex: + log.exception("domain_unpause") + raise XendError(str(ex)) + + def domain_pause(self, domid): + """Pause domain execution. + + @param domid: Domain ID or Name + @type domid: int or string. + @rtype: None + @raise XendError: Failed to pause + @raise XendInvalidDomain: Domain is not valid + """ + try: + dominfo = self.domain_lookup_nr(domid) + if not dominfo: + raise XendInvalidDomain(str(domid)) + log.info("Domain %s (%d) paused.", dominfo.getName(), + int(dominfo.getDomid())) + dominfo.pause() + except XendInvalidDomain: + log.exception("domain_pause") + raise + except Exception, ex: + log.exception("domain_pause") + raise XendError(str(ex)) + + def domain_dump(self, domid, filename, live, crash): + """Dump domain core.""" dominfo = self.domain_lookup_by_name_or_id_nr(domid) if not dominfo: raise XendInvalidDomain(str(domid)) if dominfo.getDomid() == PRIV_DOMAIN: - raise XendError("Cannot unpause privileged domain %s" % domid) - - try: - log.info("Domain %s (%d) unpaused.", dominfo.getName(), - dominfo.getDomid()) - return dominfo.unpause() - except Exception, ex: - raise XendError(str(ex)) - - - def domain_pause(self, domid): - """Pause domain execution.""" - - dominfo = self.domain_lookup_by_name_or_id_nr(domid) - if not dominfo: - raise XendInvalidDomain(str(domid)) - - if dominfo.getDomid() == PRIV_DOMAIN: - raise XendError("Cannot pause privileged domain %s" % domid) - - try: - log.info("Domain %s (%d) paused.", dominfo.getName(), - dominfo.getDomid()) - return dominfo.pause() - except Exception, ex: - raise XendError(str(ex)) - - def domain_dump(self, domid, filename, live, crash): - """Dump domain core.""" - - dominfo = self.domain_lookup_by_name_or_id_nr(domid) - if not dominfo: - raise XendInvalidDomain(str(domid)) - - if dominfo.getDomid() == PRIV_DOMAIN: raise XendError("Cannot dump core for privileged domain %s" % domid) try: @@ -408,10 +972,17 @@ class XendDomain: raise XendError(str(ex)) def domain_destroy(self, domid): - """Terminate domain immediately.""" - - dominfo = self.domain_lookup_by_name_or_id_nr(domid) - if dominfo and dominfo.getDomid() == PRIV_DOMAIN: + """Terminate domain immediately. + + @param domid: Domain ID or Name + @type domid: int or string. + @rtype: None + @raise XendError: Failed to destroy + @raise XendInvalidDomain: Domain is not valid + """ + + dominfo = self.domain_lookup_nr(domid) + if dominfo and dominfo.getDomid() == DOM0_ID: raise XendError("Cannot destroy privileged domain %s" % domid) if dominfo: @@ -419,19 +990,36 @@ class XendDomain: else: try: val = xc.domain_destroy(int(domid)) - except Exception, ex: - raise XendInvalidDomain(str(domid)) + except ValueError: + raise XendInvalidDomain(domid) + except Exception, e: + raise XendError(str(e)) + return val def domain_migrate(self, domid, dst, live=False, resource=0, port=0): - """Start domain migration.""" - - dominfo = self.domain_lookup_by_name_or_id_nr(domid) + """Start domain migration. + + @param domid: Domain ID or Name + @type domid: int or string. + @param dst: Destination IP address + @type dst: string + @keyword port: relocation port on destination + @type port: int + @keyword live: Live migration + @type live: bool + @keyword resource: not used?? + @rtype: None + @raise XendError: Failed to migrate + @raise XendInvalidDomain: Domain is not valid + """ + + dominfo = self.domain_lookup_nr(domid) if not dominfo: raise XendInvalidDomain(str(domid)) - if dominfo.getDomid() == PRIV_DOMAIN: - raise XendError("Cannot migrate privileged domain %s" % domid) + if dominfo.getDomid() == DOM0_ID: + raise XendError("Cannot migrate privileged domain %i" % domid) """ The following call may raise a XendError exception """ dominfo.testMigrateDevices(True, dst) @@ -457,21 +1045,26 @@ class XendDomain: def domain_save(self, domid, dst): """Start saving a domain to file. - @param dst: destination file - """ - - try: - dominfo = self.domain_lookup_by_name_or_id_nr(domid) + @param domid: Domain ID or Name + @type domid: int or string. + @param dst: Destination filename + @type dst: string + @rtype: None + @raise XendError: Failed to save domain + @raise XendInvalidDomain: Domain is not valid + """ + try: + dominfo = self.domain_lookup_nr(domid) if not dominfo: raise XendInvalidDomain(str(domid)) - if dominfo.getDomid() == PRIV_DOMAIN: - raise XendError("Cannot save privileged domain %s" % domid) + if dominfo.getDomid() == DOM0_ID: + raise XendError("Cannot save privileged domain %i" % domid) fd = os.open(dst, os.O_WRONLY | os.O_CREAT | os.O_TRUNC) try: # For now we don't support 'live checkpoint' - return XendCheckpoint.save(fd, dominfo, False, False, dst) + XendCheckpoint.save(fd, dominfo, False, False, dst) finally: os.close(fd) except OSError, ex: @@ -481,9 +1074,15 @@ class XendDomain: def domain_pincpu(self, domid, vcpu, cpumap): """Set which cpus vcpu can use - @param cpumap: string repr of list of usable cpus - """ - dominfo = self.domain_lookup_by_name_or_id_nr(domid) + @param domid: Domain ID or Name + @type domid: int or string. + @param vcpu: vcpu to pin to + @type vcpu: int + @param cpumap: string repr of usable cpus + @type cpumap: string + @rtype: 0 + """ + dominfo = self.domain_lookup_nr(domid) if not dominfo: raise XendInvalidDomain(str(domid)) @@ -504,8 +1103,12 @@ class XendDomain: def domain_cpu_sedf_set(self, domid, period, slice_, latency, extratime, weight): """Set Simple EDF scheduler parameters for a domain. - """ - dominfo = self.domain_lookup_by_name_or_id_nr(domid) + + @param domid: Domain ID or Name + @type domid: int or string. + @rtype: 0 + """ + dominfo = self.domain_lookup_nr(domid) if not dominfo: raise XendInvalidDomain(str(domid)) try: @@ -516,8 +1119,13 @@ class XendDomain: def domain_cpu_sedf_get(self, domid): """Get Simple EDF scheduler parameters for a domain. - """ - dominfo = self.domain_lookup_by_name_or_id_nr(domid) + + @param domid: Domain ID or Name + @type domid: int or string. + @rtype: SXP object + @return: The parameters for Simple EDF schedule for a domain. + """ + dominfo = self.domain_lookup_nr(domid) if not dominfo: raise XendInvalidDomain(str(domid)) try: @@ -535,7 +1143,14 @@ class XendDomain: raise XendError(str(ex)) def domain_shadow_control(self, domid, op): - """Shadow page control.""" + """Shadow page control. + + @param domid: Domain ID or Name + @type domid: int or string. + @param op: operation + @type op: int + @rtype: 0 + """ dominfo = self.domain_lookup(domid) try: return xc.shadow_control(dominfo.getDomid(), op) @@ -543,7 +1158,13 @@ class XendDomain: raise XendError(str(ex)) def domain_shadow_mem_get(self, domid): - """Get shadow pagetable memory allocation.""" + """Get shadow pagetable memory allocation. + + @param domid: Domain ID or Name + @type domid: int or string. + @rtype: int + @return: shadow memory in MB + """ dominfo = self.domain_lookup(domid) try: return xc.shadow_mem_control(dominfo.getDomid()) @@ -551,7 +1172,15 @@ class XendDomain: raise XendError(str(ex)) def domain_shadow_mem_set(self, domid, mb): - """Set shadow pagetable memory allocation.""" + """Set shadow pagetable memory allocation. + + @param domid: Domain ID or Name + @type domid: int or string. + @param mb: shadow memory to set in MB + @type: mb: int + @rtype: int + @return: shadow memory in MB + """ dominfo = self.domain_lookup(domid) try: return xc.shadow_mem_control(dominfo.getDomid(), mb=mb) @@ -560,8 +1189,13 @@ class XendDomain: def domain_sched_credit_get(self, domid): """Get credit scheduler parameters for a domain. - """ - dominfo = self.domain_lookup_by_name_or_id_nr(domid) + + @param domid: Domain ID or Name + @type domid: int or string. + @rtype: dict with keys 'weight' and 'cap' + @return: credit scheduler parameters + """ + dominfo = self.domain_lookup_nr(domid) if not dominfo: raise XendInvalidDomain(str(domid)) try: @@ -571,8 +1205,14 @@ class XendDomain: def domain_sched_credit_set(self, domid, weight = None, cap = None): """Set credit scheduler parameters for a domain. - """ - dominfo = self.domain_lookup_by_name_or_id_nr(domid) + + @param domid: Domain ID or Name + @type domid: int or string. + @type weight: int + @type cap: int + @rtype: 0 + """ + dominfo = self.domain_lookup_nr(domid) if not dominfo: raise XendInvalidDomain(str(domid)) try: @@ -593,10 +1233,14 @@ class XendDomain: def domain_maxmem_set(self, domid, mem): """Set the memory limit for a domain. + @param domid: Domain ID or Name + @type domid: int or string. @param mem: memory limit (in MiB) - @return: 0 on success, -1 on error - """ - dominfo = self.domain_lookup_by_name_or_id_nr(domid) + @type mem: int + @raise XendError: fail to set memory + @rtype: 0 + """ + dominfo = self.domain_lookup_nr(domid) if not dominfo: raise XendInvalidDomain(str(domid)) maxmem = int(mem) * 1024 @@ -610,9 +1254,10 @@ class XendDomain: @param first: first IO port @param last: last IO port - @return: 0 on success, -1 on error - """ - dominfo = self.domain_lookup_by_name_or_id_nr(domid) + @raise XendError: failed to set range + @rtype: 0 + """ + dominfo = self.domain_lookup_nr(domid) if not dominfo: raise XendInvalidDomain(str(domid)) nr_ports = last - first + 1 @@ -629,9 +1274,10 @@ class XendDomain: @param first: first IO port @param last: last IO port - @return: 0 on success, -1 on error - """ - dominfo = self.domain_lookup_by_name_or_id_nr(domid) + @raise XendError: failed to set range + @rtype: 0 + """ + dominfo = self.domain_lookup_nr(domid) if not dominfo: raise XendInvalidDomain(str(domid)) nr_ports = last - first + 1 diff -r ec29b6262a8b -r 9a932b5c7947 tools/python/xen/xend/XendDomainInfo.py --- a/tools/python/xen/xend/XendDomainInfo.py Thu Oct 05 17:29:19 2006 +0100 +++ b/tools/python/xen/xend/XendDomainInfo.py Thu Oct 05 17:29:19 2006 +0100 @@ -24,90 +24,36 @@ Author: Mike Wray <mike.wray@xxxxxx> """ -import errno import logging -import string import time import threading -import os +import re import xen.lowlevel.xc from xen.util import asserts from xen.util.blkif import blkdev_uname_to_file from xen.util import security -import balloon -import image -import sxp -import uuid -import XendDomain -import XendRoot + +from xen.xend import balloon, sxp, uuid, image, arch +from xen.xend import XendRoot, XendNode from xen.xend.XendBootloader import bootloader +from xen.xend.XendConfig import XendConfig from xen.xend.XendError import XendError, VmError - +from xen.xend.XendDevices import XendDevices from xen.xend.xenstore.xstransact import xstransact, complete from xen.xend.xenstore.xsutil import GetDomainPath, IntroduceDomain from xen.xend.xenstore.xswatch import xswatch - -from xen.xend import arch - -"""Shutdown code for poweroff.""" -DOMAIN_POWEROFF = 0 - -"""Shutdown code for reboot.""" -DOMAIN_REBOOT = 1 - -"""Shutdown code for suspend.""" -DOMAIN_SUSPEND = 2 - -"""Shutdown code for crash.""" -DOMAIN_CRASH = 3 - -"""Shutdown code for halt.""" -DOMAIN_HALT = 4 - -"""Map shutdown codes to strings.""" -shutdown_reasons = { - DOMAIN_POWEROFF: "poweroff", - DOMAIN_REBOOT : "reboot", - DOMAIN_SUSPEND : "suspend", - DOMAIN_CRASH : "crash", - DOMAIN_HALT : "halt" - } - -restart_modes = [ - "restart", - "destroy", - "preserve", - "rename-restart" - ] - -STATE_DOM_OK = 1 -STATE_DOM_SHUTDOWN = 2 - -SHUTDOWN_TIMEOUT = 30.0 +from xen.xend.XendConstants import * +from xen.xend.XendAPIConstants import * + MIGRATE_TIMEOUT = 30.0 - -ZOMBIE_PREFIX = 'Zombie-' - -"""Constants for the different stages of ext. device migration """ -DEV_MIGRATE_TEST = 0 -DEV_MIGRATE_STEP1 = 1 -DEV_MIGRATE_STEP2 = 2 -DEV_MIGRATE_STEP3 = 3 - -"""Minimum time between domain restarts in seconds.""" -MINIMUM_RESTART_TIME = 20 - -RESTART_IN_PROGRESS = 'xend/restart_in_progress' - xc = xen.lowlevel.xc.xc() xroot = XendRoot.instance() log = logging.getLogger("xend.XendDomainInfo") #log.setLevel(logging.TRACE) - ## # All parameters of VMs that may be configured on-the-fly, or at start-up. @@ -156,6 +102,9 @@ VM_STORE_ENTRIES = [ ('shadow_memory', int), ('maxmem', int), ('start_time', float), + ('autostart', int), + ('autostop', int), + ('on_xend_stop', str), ] VM_STORE_ENTRIES += VM_CONFIG_PARAMS @@ -181,222 +130,151 @@ VM_STORE_ENTRIES += VM_CONFIG_PARAMS def create(config): - """Create a VM from a configuration. - - @param config configuration - @raise: VmError for invalid configuration + """Creates and start a VM using the supplied configuration. + (called from XMLRPCServer directly) + + @param config: A configuration object involving lists of tuples. + @type config: list of lists, eg ['vm', ['image', 'xen.gz']] + + @rtype: XendDomainInfo + @return: A up and running XendDomainInfo instance + @raise VmError: Invalid configuration or failure to start. """ log.debug("XendDomainInfo.create(%s)", config) - - vm = XendDomainInfo(parseConfig(config)) + vm = XendDomainInfo(XendConfig(sxp = config)) try: - vm.construct() - vm.initDomain() - vm.storeVmDetails() - vm.storeDomDetails() - vm.registerWatches() - vm.refreshShutdown() - return vm + vm.start() except: log.exception('Domain construction failed') vm.destroy() raise - -def recreate(xeninfo, priv): + return vm + +def recreate(info, priv): """Create the VM object for an existing domain. The domain must not be dying, as the paths in the store should already have been removed, - and asking us to recreate them causes problems.""" - - log.debug("XendDomainInfo.recreate(%s)", xeninfo) - - assert not xeninfo['dying'] - - domid = xeninfo['dom'] + and asking us to recreate them causes problems. + + @param xeninfo: Parsed configuration + @type xeninfo: Dictionary + @param priv: TODO, unknown, something to do with memory + @type priv: bool + + @rtype: XendDomainInfo + @return: A up and running XendDomainInfo instance + @raise VmError: Invalid configuration. + @raise XendError: Errors with configuration. + """ + + log.debug("XendDomainInfo.recreate(%s)", info) + + assert not info['dying'] + + xeninfo = XendConfig(cfg = info) + domid = xeninfo['domid'] uuid1 = xeninfo['handle'] xeninfo['uuid'] = uuid.toString(uuid1) + needs_reinitialising = False + dompath = GetDomainPath(domid) if not dompath: - raise XendError( - 'No domain path in store for existing domain %d' % domid) + raise XendError('No domain path in store for existing ' + 'domain %d' % domid) log.info("Recreating domain %d, UUID %s.", domid, xeninfo['uuid']) - try: + + # need to verify the path and uuid if not Domain-0 + # if the required uuid and vm aren't set, then that means + # we need to recreate the dom with our own values + # + # NOTE: this is probably not desirable, really we should just + # abort or ignore, but there may be cases where xenstore's + # entry disappears (eg. xenstore-rm /) + # + if domid != 0: vmpath = xstransact.Read(dompath, "vm") if not vmpath: - raise XendError( - 'No vm path in store for existing domain %d' % domid) + log.warn('/dom/%d/vm is missing. recreate is confused, trying ' + 'our best to recover' % domid) + needs_reinitialising = True + uuid2_str = xstransact.Read(vmpath, "uuid") if not uuid2_str: - raise XendError( - 'No vm/uuid path in store for existing domain %d' % domid) + log.warn('%s/uuid/ is missing. recreate is confused, trying ' + 'our best to recover' % vmpath) + needs_reinitialising = True uuid2 = uuid.fromString(uuid2_str) - if uuid1 != uuid2: - raise XendError( - 'Uuid in store does not match uuid for existing domain %d: ' - '%s != %s' % (domid, uuid2_str, xeninfo['uuid'])) - - vm = XendDomainInfo(xeninfo, domid, dompath, True, priv) - - except Exception, exn: - if priv: - log.warn(str(exn)) - - vm = XendDomainInfo(xeninfo, domid, dompath, True, priv) - vm.recreateDom() - vm.removeVm() - vm.storeVmDetails() - vm.storeDomDetails() - - vm.registerWatches() - vm.refreshShutdown(xeninfo) + log.warn('UUID in /vm does not match the UUID in /dom/%d.' + 'Trying out best to recover' % domid) + needs_reinitialising = True + + vm = XendDomainInfo(xeninfo, domid, dompath, augment = True, priv = priv) + + if needs_reinitialising: + vm._recreateDom() + vm._removeVm() + vm._storeVmDetails() + vm._storeDomDetails() + + vm._registerWatches() + vm._refreshShutdown(xeninfo) return vm def restore(config): """Create a domain and a VM object to do a restore. - @param config: domain configuration + @param config: Domain configuration object + @type config: list of lists. (see C{create}) + + @rtype: XendDomainInfo + @return: A up and running XendDomainInfo instance + @raise VmError: Invalid configuration or failure to start. + @raise XendError: Errors with configuration. """ log.debug("XendDomainInfo.restore(%s)", config) - vm = XendDomainInfo(parseConfig(config), None, None, False, False, True) + vm = XendDomainInfo(XendConfig(sxp = config), resume = True) try: - vm.construct() - vm.storeVmDetails() - vm.createDevices() - vm.createChannels() - vm.storeDomDetails() - vm.endRestore() - return vm + vm.resume() except: vm.destroy() raise - -def parseConfig(config): - def get_cfg(name, conv = None): - val = sxp.child_value(config, name) - - if conv and not val is None: - try: - return conv(val) - except TypeError, exn: - raise VmError( - 'Invalid setting %s = %s in configuration: %s' % - (name, val, str(exn))) - else: - return val - - - log.debug("parseConfig: config is %s", config) - - result = {} - - for e in ROUNDTRIPPING_CONFIG_ENTRIES: - result[e[0]] = get_cfg(e[0], e[1]) - - result['cpu'] = get_cfg('cpu', int) - result['cpus'] = get_cfg('cpus', str) - result['image'] = get_cfg('image') - tmp_security = get_cfg('security') - if tmp_security: - result['security'] = tmp_security - - try: - if result['image']: - v = sxp.child_value(result['image'], 'vcpus') - if result['vcpus'] is None and v is not None: - result['vcpus'] = int(v) - elif v is not None and int(v) != result['vcpus']: - log.warn(('Image VCPUs setting overrides vcpus=%d elsewhere.' - ' Using %s VCPUs for VM %s.') % - (result['vcpus'], v, result['uuid'])) - result['vcpus'] = int(v) - except TypeError, exn: - raise VmError( - 'Invalid configuration setting: vcpus = %s: %s' % - (sxp.child_value(result['image'], 'vcpus', 1), str(exn))) - - try: - # support legacy config files with 'cpu' parameter - # NB: prepending to list to support previous behavior - # where 'cpu' parameter pinned VCPU0. - if result['cpu']: - if result['cpus']: - result['cpus'] = "%s,%s" % (str(result['cpu']), result['cpus']) - else: - result['cpus'] = str(result['cpu']) - - # convert 'cpus' string to list of ints - # 'cpus' supports a list of ranges (0-3), seperated by - # commas, and negation, (^1). - # Precedence is settled by order of the string: - # "0-3,^1" -> [0,2,3] - # "0-3,^1,1" -> [0,1,2,3] - if result['cpus']: - cpus = [] - for c in result['cpus'].split(','): - if c.find('-') != -1: - (x,y) = c.split('-') - for i in range(int(x),int(y)+1): - cpus.append(int(i)) - else: - # remove this element from the list - if c[0] == '^': - cpus = [x for x in cpus if x != int(c[1:])] - else: - cpus.append(int(c)) - - result['cpus'] = cpus - - except ValueError, exn: - raise VmError( - 'Invalid configuration setting: cpus = %s: %s' % - (result['cpus'], exn)) - - result['backend'] = [] - for c in sxp.children(config, 'backend'): - result['backend'].append(sxp.name(sxp.child0(c))) - - result['device'] = [] - for d in sxp.children(config, 'device'): - c = sxp.child0(d) - result['device'].append((sxp.name(c), c)) - - # Configuration option "restart" is deprecated. Parse it, but - # let on_xyz override it if they are present. - restart = get_cfg('restart') - if restart: - def handle_restart(event, val): - if result[event] is None: - result[event] = val - - if restart == "onreboot": - handle_restart('on_poweroff', 'destroy') - handle_restart('on_reboot', 'restart') - handle_restart('on_crash', 'destroy') - elif restart == "always": - handle_restart('on_poweroff', 'restart') - handle_restart('on_reboot', 'restart') - handle_restart('on_crash', 'restart') - elif restart == "never": - handle_restart('on_poweroff', 'destroy') - handle_restart('on_reboot', 'destroy') - handle_restart('on_crash', 'destroy') - else: - log.warn("Ignoring malformed and deprecated config option " - "restart = %s", restart) - - log.debug("parseConfig: result is %s", result) - return result - +def createDormant(xeninfo): + """Create a dormant/inactive XenDomainInfo without creating VM. + This is for creating instances of persistent domains that are not + yet start. + + @param xeninfo: Parsed configuration + @type xeninfo: dictionary + + @rtype: XendDomainInfo + @return: A up and running XendDomainInfo instance + @raise XendError: Errors with configuration. + """ + + log.debug("XendDomainInfo.createDormant(%s)", xeninfo) + + # Remove domid and uuid do not make sense for non-running domains. + xeninfo.pop('domid', None) + xeninfo.pop('uuid', None) + vm = XendDomainInfo(XendConfig(cfg = xeninfo)) + return vm def domain_by_name(name): + """Get domain by name + + @params name: Name of the domain + @type name: string + @return: XendDomainInfo or None + """ + from xen.xend import XendDomain return XendDomain.instance().domain_lookup_by_name_nr(name) @@ -408,17 +286,19 @@ def shutdown_reason(code): @return: shutdown reason @rtype: string """ - return shutdown_reasons.get(code, "?") + return DOMAIN_SHUTDOWN_REASONS.get(code, "?") def dom_get(dom): """Get info from xen for an existing domain. @param dom: domain id + @type dom: int @return: info or None + @rtype: dictionary """ try: domlist = xc.domain_getinfo(dom, 1) - if domlist and dom == domlist[0]['dom']: + if domlist and dom == domlist[0]['domid']: return domlist[0] except Exception, err: # ignore missing domain @@ -427,32 +307,87 @@ def dom_get(dom): class XendDomainInfo: - + """An object represents a domain. + + @TODO: try to unify dom and domid, they mean the same thing, but + xc refers to it as dom, and everywhere else, including + xenstore it is domid. The best way is to change xc's + python interface. + + @ivar info: Parsed configuration + @type info: dictionary + @ivar domid: Domain ID (if VM has started) + @type domid: int or None + @ivar vmpath: XenStore path to this VM. + @type vmpath: string + @ivar dompath: XenStore path to this Domain. + @type dompath: string + @ivar image: Reference to the VM Image. + @type image: xen.xend.image.ImageHandler + @ivar store_port: event channel to xenstored + @type store_port: int + @ivar console_port: event channel to xenconsoled + @type console_port: int + @ivar store_mfn: xenstored mfn + @type store_mfn: int + @ivar console_mfn: xenconsoled mfn + @type console_mfn: int + @ivar vmWatch: reference to a watch on the xenstored vmpath + @type vmWatch: xen.xend.xenstore.xswatch + @ivar shutdownWatch: reference to watch on the xenstored domain shutdown + @type shutdownWatch: xen.xend.xenstore.xswatch + @ivar shutdownStartTime: UNIX Time when domain started shutting down. + @type shutdownStartTime: float or None + @ivar state: Domain state + @type state: enum(DOM_STATE_HALTED, DOM_STATE_RUNNING, ...) + @ivar state_updated: lock for self.state + @type state_updated: threading.Condition + @ivar refresh_shutdown_lock: lock for polling shutdown state + @type refresh_shutdown_lock: threading.Condition + @ivar _deviceControllers: device controller cache for this domain + @type _deviceControllers: dict 'string' to DevControllers + """ + def __init__(self, info, domid = None, dompath = None, augment = False, priv = False, resume = False): + """Constructor for a domain + + @param info: parsed configuration + @type info: dictionary + @keyword domid: Set initial domain id (if any) + @type domid: int + @keyword dompath: Set initial dompath (if any) + @type dompath: string + @keyword augment: Augment given info with xenstored VM info + @type augment: bool + @keyword priv: Is a privledged domain (Dom 0) (TODO: really?) + @type priv: bool + @keyword resume: Is this domain being resumed? + @type resume: bool + """ self.info = info - - if not self.infoIsSet('uuid'): - self.info['uuid'] = uuid.toString(uuid.create()) - - if domid is not None: + if domid == None: + self.domid = self.info.get('domid') + else: self.domid = domid - elif 'dom' in info: - self.domid = int(info['dom']) - else: - self.domid = None - - self.vmpath = XendDomain.VMROOT + self.info['uuid'] + + #REMOVE: uuid is now generated in XendConfig + #if not self._infoIsSet('uuid'): + # self.info['uuid'] = uuid.toString(uuid.create()) + + #REMOVE: domid logic can be shortened + #if domid is not None: + # self.domid = domid + #elif info.has_key('dom'): + # self.domid = int(info['dom']) + #else: + # self.domid = None + + self.vmpath = XS_VMROOT + self.info['uuid'] self.dompath = dompath - if augment: - self.augmentInfo(priv) - - self.validateInfo() - self.image = None - self.security = None self.store_port = None self.store_mfn = None self.console_port = None @@ -460,67 +395,210 @@ class XendDomainInfo: self.vmWatch = None self.shutdownWatch = None - self.shutdownStartTime = None - self.state = STATE_DOM_OK + self.state = DOM_STATE_HALTED self.state_updated = threading.Condition() self.refresh_shutdown_lock = threading.Condition() + self._deviceControllers = {} + + for state in DOM_STATES_OLD: + self.info[state] = 0 + + if augment: + self._augmentInfo(priv) + + self._checkName(self.info['name']) self.setResume(resume) - - ## private: - - def readVMDetails(self, params): - """Read the specified parameters from the store. - """ - try: - return self.gatherVm(*params) - except ValueError: - # One of the int/float entries in params has a corresponding store - # entry that is invalid. We recover, because older versions of - # Xend may have put the entry there (memory/target, for example), - # but this is in general a bad situation to have reached. - log.exception( - "Store corrupted at %s! Domain %d's configuration may be " - "affected.", self.vmpath, self.domid) - return [] - - - def storeChanged(self, _): - log.trace("XendDomainInfo.storeChanged"); - - changed = False - - def f(x, y): - if y is not None and self.info[x[0]] != y: - self.info[x[0]] = y - changed = True - - map(f, VM_CONFIG_PARAMS, self.readVMDetails(VM_CONFIG_PARAMS)) - - im = self.readVm('image') - current_im = self.info['image'] - if (im is not None and - (current_im is None or sxp.to_string(current_im) != im)): - self.info['image'] = sxp.from_string(im) - changed = True - - if changed: - # Update the domain section of the store, as this contains some - # parameters derived from the VM configuration. - self.storeDomDetails() - - return 1 - - - def augmentInfo(self, priv): - """Augment self.info, as given to us through {@link #recreate}, with - values taken from the store. This recovers those values known to xend - but not to the hypervisor. + + + # + # Public functions available through XMLRPC + # + + + def start(self, is_managed = False): + """Attempts to start the VM by do the appropriate + initialisation if it not started. + """ + from xen.xend import XendDomain + + if self.state == DOM_STATE_HALTED: + try: + self._constructDomain() + self._initDomain() + self._storeVmDetails() + self._storeDomDetails() + self._registerWatches() + self._refreshShutdown() + self.unpause() + + # save running configuration if XendDomains believe domain is + # persistent + # + if is_managed: + xendomains = XendDomain.instance() + xendomains.managed_config_save(self) + except: + log.exception('VM start failed') + self.destroy() + raise + else: + raise XendError('VM already running') + + def resume(self): + """Resumes a domain that has come back from suspension.""" + if self.state in (DOM_STATE_HALTED, DOM_STATE_SUSPENDED): + try: + self._constructDomain() + self._storeVmDetails() + self._createDevices() + self._createChannels() + self._storeDomDetails() + self._endRestore() + except: + log.exception('VM resume failed') + raise + else: + raise XendError('VM already running') + + def shutdown(self, reason): + """Shutdown a domain by signalling this via xenstored.""" + log.debug('XendDomainInfo.shutdown') + if self.state in (DOM_STATE_SHUTDOWN, DOM_STATE_HALTED,): + raise XendError('Domain cannot be shutdown') + + if not reason in DOMAIN_SHUTDOWN_REASONS.values(): + raise XendError('Invalid reason: %s' % reason) + self._storeDom("control/shutdown", reason) + + def pause(self): + """Pause domain + + @raise XendError: Failed pausing a domain + """ + try: + xc.domain_pause(self.domid) + self._stateSet(DOM_STATE_PAUSED) + except Exception, ex: + raise XendError("Domain unable to be paused: %s" % str(ex)) + + def unpause(self): + """Unpause domain + + @raise XendError: Failed unpausing a domain + """ + try: + xc.domain_unpause(self.domid) + self._stateSet(DOM_STATE_RUNNING) + except Exception, ex: + raise XendError("Domain unable to be unpaused: %s" % str(ex)) + + def send_sysrq(self, key): + """ Send a Sysrq equivalent key via xenstored.""" + asserts.isCharConvertible(key) + self._storeDom("control/sysrq", '%c' % key) + + def device_create(self, dev_config): + """Create a new device. + + @param dev_config: device configuration + @type dev_config: dictionary (parsed config) + """ + dev_type = sxp.name(dev_config) + devid = self._createDevice(dev_type, dev_config) + self._waitForDevice(dev_type, devid) + self.info.device_add(dev_type, cfg_sxp = dev_config) + return self.getDeviceController(dev_type).sxpr(devid) + + def device_configure(self, dev_config, devid): + """Configure an existing device. + + @param dev_config: device configuration + @type dev_config: dictionary (parsed config) + @param devid: device id + @type devid: int + """ + deviceClass = sxp.name(dev_config) + self._reconfigureDevice(deviceClass, devid, dev_config) + + def waitForDevices(self): + """Wait for this domain's configured devices to connect. + + @raise VmError: if any device fails to initialise. + """ + for devclass in XendDevices.valid_devices(): + self.getDeviceController(devclass).waitForDevices() + + def destroyDevice(self, deviceClass, devid): + if type(devid) is str: + devicePath = '%s/device/%s' % (self.dompath, deviceClass) + for entry in xstransact.List(devicePath): + backend = xstransact.Read('%s/%s' % (devicePath, entry), + "backend") + devName = xstransact.Read(backend, "dev") + if devName == devid: + # We found the integer matching our devid, use it instead + devid = entry + break + return self.getDeviceController(deviceClass).destroyDevice(devid) + + + def getDeviceSxprs(self, deviceClass): + return self.getDeviceController(deviceClass).sxprs() + + + def setMemoryTarget(self, target): + """Set the memory target of this domain. + @param target: In MiB. + """ + log.debug("Setting memory target of domain %s (%d) to %d MiB.", + self.info['name'], self.domid, target) + + if target <= 0: + raise XendError('Invalid memory size') + + self.info['memory'] = target + self.storeVm("memory", target) + self._storeDom("memory/target", target << 10) + + def getVCPUInfo(self): + try: + # We include the domain name and ID, to help xm. + sxpr = ['domain', + ['domid', self.domid], + ['name', self.info['name']], + ['vcpu_count', self.info['online_vcpus']]] + + for i in range(0, self.info['max_vcpu_id']+1): + info = xc.vcpu_getinfo(self.domid, i) + + sxpr.append(['vcpu', + ['number', i], + ['online', info['online']], + ['blocked', info['blocked']], + ['running', info['running']], + ['cpu_time', info['cpu_time'] / 1e9], + ['cpu', info['cpu']], + ['cpumap', info['cpumap']]]) + + return sxpr + + except RuntimeError, exn: + raise XendError(str(exn)) + + # + # internal functions ... TODO: re-categorised + # + + def _augmentInfo(self, priv): + """Augment self.info, as given to us through L{recreate}, with + values taken from the store. This recovers those values known + to xend but not to the hypervisor. """ def useIfNeeded(name, val): - if not self.infoIsSet(name) and val is not None: + if not self._infoIsSet(name) and val is not None: self.info[name] = val if priv: @@ -533,198 +611,63 @@ class XendDomainInfo: entries.append(('security', str)) map(lambda x, y: useIfNeeded(x[0], y), entries, - self.readVMDetails(entries)) - - device = [] - for c in controllerClasses: - devconfig = self.getDeviceConfigurations(c) + self._readVMDetails(entries)) + + devices = [] + + for devclass in XendDevices.valid_devices(): + devconfig = self.getDeviceController(devclass).configurations() if devconfig: - device.extend(map(lambda x: (c, x), devconfig)) - useIfNeeded('device', device) - - - def validateInfo(self): - """Validate and normalise the info block. This has either been parsed - by parseConfig, or received from xc through recreate and augmented by - the current store contents. - """ - def defaultInfo(name, val): - if not self.infoIsSet(name): - self.info[name] = val() - - try: - defaultInfo('name', lambda: "Domain-%d" % self.domid) - defaultInfo('on_poweroff', lambda: "destroy") - defaultInfo('on_reboot', lambda: "restart") - defaultInfo('on_crash', lambda: "restart") - defaultInfo('features', lambda: "") - defaultInfo('cpu', lambda: None) - defaultInfo('cpus', lambda: []) - defaultInfo('cpu_weight', lambda: 1.0) - - # some domains don't have a config file (e.g. dom0 ) - # to set number of vcpus so we derive available cpus - # from max_vcpu_id which is present for running domains. - if not self.infoIsSet('vcpus') and self.infoIsSet('max_vcpu_id'): - avail = int(self.info['max_vcpu_id'])+1 - else: - avail = int(1) - - defaultInfo('vcpus', lambda: avail) - defaultInfo('online_vcpus', lambda: self.info['vcpus']) - defaultInfo('max_vcpu_id', lambda: self.info['vcpus']-1) - defaultInfo('vcpu_avail', lambda: (1 << self.info['vcpus']) - 1) - - defaultInfo('memory', lambda: 0) - defaultInfo('shadow_memory', lambda: 0) - defaultInfo('maxmem', lambda: 0) - defaultInfo('bootloader', lambda: None) - defaultInfo('bootloader_args', lambda: None) - defaultInfo('backend', lambda: []) - defaultInfo('device', lambda: []) - defaultInfo('image', lambda: None) - defaultInfo('security', lambda: None) - - self.check_name(self.info['name']) - - if isinstance(self.info['image'], str): - self.info['image'] = sxp.from_string(self.info['image']) - - if isinstance(self.info['security'], str): - self.info['security'] = sxp.from_string(self.info['security']) - - if self.info['memory'] == 0: - if self.infoIsSet('mem_kb'): - self.info['memory'] = (self.info['mem_kb'] + 1023) / 1024 - if self.info['memory'] <= 0: - raise VmError('Invalid memory size') - - if self.info['maxmem'] < self.info['memory']: - self.info['maxmem'] = self.info['memory'] - - for (n, c) in self.info['device']: - if not n or not c or n not in controllerClasses: - raise VmError('invalid device (%s, %s)' % - (str(n), str(c))) - - for event in ['on_poweroff', 'on_reboot', 'on_crash']: - if self.info[event] not in restart_modes: - raise VmError('invalid restart event: %s = %s' % - (event, str(self.info[event]))) - - except KeyError, exn: - log.exception(exn) - raise VmError('Unspecified domain detail: %s' % exn) - - - def readVm(self, *args): + devices.extend(map(lambda conf: (devclass, conf), devconfig)) + + if not self.info['device'] and devices is not None: + for device in devices: + self.info.device_add(device[0], cfg_sxp = device[1]) + + # + # Function to update xenstore /vm/* + # + + def _readVm(self, *args): return xstransact.Read(self.vmpath, *args) - def writeVm(self, *args): + def _writeVm(self, *args): return xstransact.Write(self.vmpath, *args) - def removeVm(self, *args): + def _removeVm(self, *args): return xstransact.Remove(self.vmpath, *args) - def gatherVm(self, *args): + def _gatherVm(self, *args): return xstransact.Gather(self.vmpath, *args) - - - ## public: def storeVm(self, *args): return xstransact.Store(self.vmpath, *args) - - ## private: - - def readDom(self, *args): + # + # Function to update xenstore /dom/* + # + + def _readDom(self, *args): return xstransact.Read(self.dompath, *args) - def writeDom(self, *args): + def _writeDom(self, *args): return xstransact.Write(self.dompath, *args) - - ## public: - - def removeDom(self, *args): + def _removeDom(self, *args): return xstransact.Remove(self.dompath, *args) - def recreateDom(self): - complete(self.dompath, lambda t: self._recreateDom(t)) - - def _recreateDom(self, t): + def _storeDom(self, *args): + return xstransact.Store(self.dompath, *args) + + def _recreateDom(self): + complete(self.dompath, lambda t: self._recreateDomFunc(t)) + + def _recreateDomFunc(self, t): t.remove() t.mkdir() t.set_permissions({ 'dom' : self.domid }) - - ## private: - - def storeDom(self, *args): - return xstransact.Store(self.dompath, *args) - - - ## public: - - def completeRestore(self, store_mfn, console_mfn): - - log.debug("XendDomainInfo.completeRestore") - - self.store_mfn = store_mfn - self.console_mfn = console_mfn - - self.introduceDomain() - self.storeDomDetails() - self.registerWatches() - self.refreshShutdown() - - log.debug("XendDomainInfo.completeRestore done") - - - def storeVmDetails(self): - to_store = {} - - for k in VM_STORE_ENTRIES: - if self.infoIsSet(k[0]): - to_store[k[0]] = str(self.info[k[0]]) - - if self.infoIsSet('image'): - to_store['image'] = sxp.to_string(self.info['image']) - - if self.infoIsSet('security'): - security = self.info['security'] - to_store['security'] = sxp.to_string(security) - for idx in range(0, len(security)): - if security[idx][0] == 'access_control': - to_store['security/access_control'] = sxp.to_string([ security[idx][1] , security[idx][2] ]) - for aidx in range(1, len(security[idx])): - if security[idx][aidx][0] == 'label': - to_store['security/access_control/label'] = security[idx][aidx][1] - if security[idx][aidx][0] == 'policy': - to_store['security/access_control/policy'] = security[idx][aidx][1] - if security[idx][0] == 'ssidref': - to_store['security/ssidref'] = str(security[idx][1]) - - if not self.readVm('xend/restart_count'): - to_store['xend/restart_count'] = str(0) - - log.debug("Storing VM details: %s", to_store) - - self.writeVm(to_store) - self.setVmPermissions() - - - def setVmPermissions(self): - """Allow the guest domain to read its UUID. We don't allow it to - access any other entry, for security.""" - xstransact.SetPermissions('%s/uuid' % self.vmpath, - { 'dom' : self.domid, - 'read' : True, - 'write' : False }) - - - def storeDomDetails(self): + def _storeDomDetails(self): to_store = { 'domid': str(self.domid), 'vm': self.vmpath, @@ -742,16 +685,13 @@ class XendDomainInfo: f('store/port', self.store_port) f('store/ring-ref', self.store_mfn) - to_store.update(self.vcpuDomDetails()) + to_store.update(self._vcpuDomDetails()) log.debug("Storing domain details: %s", to_store) - self.writeDom(to_store) - - - ## private: - - def vcpuDomDetails(self): + self._writeDom(to_store) + + def _vcpuDomDetails(self): def availability(n): if self.info['vcpu_avail'] & (1 << n): return 'online' @@ -763,25 +703,80 @@ class XendDomainInfo: result["cpu/%d/availability" % v] = availability(v) return result - - ## public: - - def registerWatches(self): + # + # xenstore watches + # + + def _registerWatches(self): """Register a watch on this VM's entries in the store, and the domain's control/shutdown node, so that when they are changed externally, we keep up to date. This should only be called by {@link #create}, {@link #recreate}, or {@link #restore}, once the domain's details have been written, but before the new instance is returned.""" - self.vmWatch = xswatch(self.vmpath, self.storeChanged) + self.vmWatch = xswatch(self.vmpath, self._storeChanged) self.shutdownWatch = xswatch(self.dompath + '/control/shutdown', - self.handleShutdownWatch) + self._handleShutdownWatch) + + def _storeChanged(self, _): + log.trace("XendDomainInfo.storeChanged"); + + changed = False + + def f(x, y): + if y is not None and self.info[x[0]] != y: + self.info[x[0]] = y + changed = True + + map(f, VM_CONFIG_PARAMS, self._readVMDetails(VM_CONFIG_PARAMS)) + + im = self._readVm('image') + current_im = self.info['image'] + if (im is not None and + (current_im is None or sxp.to_string(current_im) != im)): + self.info['image'] = sxp.from_string(im) + changed = True + + if changed: + # Update the domain section of the store, as this contains some + # parameters derived from the VM configuration. + self._storeDomDetails() + + return 1 + + def _handleShutdownWatch(self, _): + log.debug('XendDomainInfo.handleShutdownWatch') + + reason = self._readDom('control/shutdown') + + if reason and reason != 'suspend': + sst = self._readDom('xend/shutdown_start_time') + now = time.time() + if sst: + self.shutdownStartTime = float(sst) + timeout = float(sst) + SHUTDOWN_TIMEOUT - now + else: + self.shutdownStartTime = now + self._storeDom('xend/shutdown_start_time', now) + timeout = SHUTDOWN_TIMEOUT + + log.trace( + "Scheduling refreshShutdown on domain %d in %ds.", + self.domid, timeout) + threading.Timer(timeout, self._refreshShutdown).start() + + return True + + + # + # Public Attributes for the VM + # def getDomid(self): return self.domid def setName(self, name): - self.check_name(name) + self._checkName(name) self.info['name'] = name self.storeVm("name", name) @@ -812,7 +807,7 @@ class XendDomainInfo: def setVCpuCount(self, vcpus): self.info['vcpu_avail'] = (1 << vcpus) - 1 self.storeVm('vcpu_avail', self.info['vcpu_avail']) - self.writeDom(self.vcpuDomDetails()) + self._writeDom(self._vcpuDomDetails()) def getLabel(self): return security.get_security_info(self.info, 'label') @@ -824,16 +819,13 @@ class XendDomainInfo: def getResume(self): return "%s" % self.info['resume'] - def endRestore(self): - self.setResume(False) - def setResume(self, state): self.info['resume'] = state def getRestartCount(self): - return self.readVm('xend/restart_count') - - def refreshShutdown(self, xeninfo = None): + return self._readVm('xend/restart_count') + + def _refreshShutdown(self, xeninfo = None): # If set at the end of this method, a restart is required, with the # given reason. This restart has to be done out of the scope of # refresh_shutdown_lock. @@ -852,6 +844,7 @@ class XendDomainInfo: # VM may have migrated to a different domain on this # machine. self.cleanupDomain() + self._stateSet(DOM_STATE_HALTED) return if xeninfo['dying']: @@ -863,10 +856,11 @@ class XendDomainInfo: # holding the pages, by calling cleanupDomain. We can't # clean up the VM, as above. self.cleanupDomain() + self._stateSet(DOM_STATE_SHUTDOWN) return elif xeninfo['crashed']: - if self.readDom('xend/shutdown_completed'): + if self._readDom('xend/shutdown_completed'): # We've seen this shutdown already, but we are preserving # the domain for debugging. Leave it alone. return @@ -878,9 +872,11 @@ class XendDomainInfo: self.dumpCore() restart_reason = 'crash' + self._stateSet(DOM_STATE_HALTED) elif xeninfo['shutdown']: - if self.readDom('xend/shutdown_completed'): + self._stateSet(DOM_STATE_SHUTDOWN) + if self._readDom('xend/shutdown_completed'): # We've seen this shutdown already, but we are preserving # the domain for debugging. Leave it alone. return @@ -891,15 +887,15 @@ class XendDomainInfo: log.info('Domain has shutdown: name=%s id=%d reason=%s.', self.info['name'], self.domid, reason) - self.clearRestart() + self._clearRestart() if reason == 'suspend': - self.state_set(STATE_DOM_SHUTDOWN) + self._stateSet(DOM_STATE_SUSPENDED) # Don't destroy the domain. XendCheckpoint will do # this once it has finished. However, stop watching # the VM path now, otherwise we will end up with one # watch for the old domain, and one for the new. - self.unwatchVm() + self._unwatchVm() elif reason in ['poweroff', 'reboot']: restart_reason = reason else: @@ -913,7 +909,8 @@ class XendDomainInfo: else: # Domain is alive. If we are shutting it down, then check # the timeout on that, and destroy it if necessary. - + self._stateSet(DOM_STATE_RUNNING) + if self.shutdownStartTime: timeout = (SHUTDOWN_TIMEOUT - time.time() + self.shutdownStartTime) @@ -926,61 +923,133 @@ class XendDomainInfo: self.refresh_shutdown_lock.release() if restart_reason: - self.maybeRestart(restart_reason) - - - def handleShutdownWatch(self, _): - log.debug('XendDomainInfo.handleShutdownWatch') - - reason = self.readDom('control/shutdown') - - if reason and reason != 'suspend': - sst = self.readDom('xend/shutdown_start_time') - now = time.time() - if sst: - self.shutdownStartTime = float(sst) - timeout = float(sst) + SHUTDOWN_TIMEOUT - now - else: - self.shutdownStartTime = now - self.storeDom('xend/shutdown_start_time', now) - timeout = SHUTDOWN_TIMEOUT - - log.trace( - "Scheduling refreshShutdown on domain %d in %ds.", - self.domid, timeout) - threading.Timer(timeout, self.refreshShutdown).start() - - return True - - - def shutdown(self, reason): - if not reason in shutdown_reasons.values(): - raise XendError('Invalid reason: %s' % reason) - if self.domid == 0: - raise XendError("Can't specify Domain-0") - self.storeDom("control/shutdown", reason) - - - ## private: - - def clearRestart(self): - self.removeDom("xend/shutdown_start_time") - - - def maybeRestart(self, reason): + self._maybeRestart(restart_reason) + + + # + # Restart functions - handling whether we come back up on shutdown. + # + + def _clearRestart(self): + self._removeDom("xend/shutdown_start_time") + + + def _maybeRestart(self, reason): # Dispatch to the correct method based upon the configured on_{reason} # behaviour. {"destroy" : self.destroy, - "restart" : self.restart, - "preserve" : self.preserve, - "rename-restart" : self.renameRestart}[self.info['on_' + reason]]() - - - def renameRestart(self): - self.restart(True) - - - def dumpCore(self,corefile=None): + "restart" : self._restart, + "preserve" : self._preserve, + "rename-restart" : self._renameRestart}[self.info['on_' + reason]]() + + + def _renameRestart(self): + self._restart(True) + + def _restart(self, rename = False): + """Restart the domain after it has exited. + + @param rename True if the old domain is to be renamed and preserved, + False if it is to be destroyed. + """ + from xen.xend import XendDomain + + self._configureBootloader() + config = self.sxpr() + + if self._infoIsSet('cpus') and len(self.info['cpus']) != 0: + config.append(['cpus', reduce(lambda x, y: str(x) + "," + str(y), + self.info['cpus'])]) + + if self._readVm(RESTART_IN_PROGRESS): + log.error('Xend failed during restart of domain %s. ' + 'Refusing to restart to avoid loops.', + str(self.domid)) + self.destroy() + return + + self._writeVm(RESTART_IN_PROGRESS, 'True') + + now = time.time() + rst = self._readVm('xend/previous_restart_time') + if rst: + rst = float(rst) + timeout = now - rst + if timeout < MINIMUM_RESTART_TIME: + log.error( + 'VM %s restarting too fast (%f seconds since the last ' + 'restart). Refusing to restart to avoid loops.', + self.info['name'], timeout) + self.destroy() + return + + self._writeVm('xend/previous_restart_time', str(now)) + + try: + if rename: + self._preserveForRestart() + else: + self._unwatchVm() + self.destroyDomain() + + # new_dom's VM will be the same as this domain's VM, except where + # the rename flag has instructed us to call preserveForRestart. + # In that case, it is important that we remove the + # RESTART_IN_PROGRESS node from the new domain, not the old one, + # once the new one is available. + + new_dom = None + try: + new_dom = XendDomain.instance().domain_create(config) + new_dom.unpause() + rst_cnt = self._readVm('xend/restart_count') + rst_cnt = int(rst_cnt) + 1 + self._writeVm('xend/restart_count', str(rst_cnt)) + new_dom._removeVm(RESTART_IN_PROGRESS) + except: + if new_dom: + new_dom._removeVm(RESTART_IN_PROGRESS) + new_dom.destroy() + else: + self._removeVm(RESTART_IN_PROGRESS) + raise + except: + log.exception('Failed to restart domain %s.', str(self.domid)) + + + def _preserveForRestart(self): + """Preserve a domain that has been shut down, by giving it a new UUID, + cloning the VM details, and giving it a new name. This allows us to + keep this domain for debugging, but restart a new one in its place + preserving the restart semantics (name and UUID preserved). + """ + + new_uuid = uuid.createString() + new_name = 'Domain-%s' % new_uuid + log.info("Renaming dead domain %s (%d, %s) to %s (%s).", + self.info['name'], self.domid, self.info['uuid'], + new_name, new_uuid) + self._unwatchVm() + self._releaseDevices() + self.info['name'] = new_name + self.info['uuid'] = new_uuid + self.vmpath = XS_VMROOT + new_uuid + self._storeVmDetails() + self._preserve() + + + def _preserve(self): + log.info("Preserving dead domain %s (%d).", self.info['name'], + self.domid) + self._unwatchVm() + self._storeDom('xend/shutdown_completed', 'True') + self._stateSet(DOM_STATE_HALTED) + + # + # Debugging .. + # + + def dumpCore(self, corefile = None): """Create a core dump for this domain. Nothrow guarantee.""" try: @@ -1001,259 +1070,121 @@ class XendDomainInfo: self.domid, self.info['name']) raise XendError("Failed to dump core: %s" % str(ex)) - ## public: - - def setMemoryTarget(self, target): - """Set the memory target of this domain. - @param target In MiB. - """ - if target <= 0: - raise XendError('Invalid memory size') - - log.debug("Setting memory target of domain %s (%d) to %d MiB.", - self.info['name'], self.domid, target) - - self.info['memory'] = target - self.storeVm("memory", target) - self.storeDom("memory/target", target << 10) - - - def update(self, info = None): - """Update with info from xc.domain_getinfo(). - """ - - log.trace("XendDomainInfo.update(%s) on domain %d", info, self.domid) - if not info: - info = dom_get(self.domid) - if not info: - return - - #manually update ssidref / security fields - if security.on() and info.has_key('ssidref'): - if (info['ssidref'] != 0) and self.info.has_key('security'): - security_field = self.info['security'] - if not security_field: - #create new security element - self.info.update({'security': [['ssidref', str(info['ssidref'])]]}) - #ssidref field not used any longer - info.pop('ssidref') - - self.info.update(info) - self.validateInfo() - self.refreshShutdown(info) - - log.trace("XendDomainInfo.update done on domain %d: %s", self.domid, - self.info) - - - ## private: - - def state_set(self, state): - self.state_updated.acquire() - try: - if self.state != state: - self.state = state - self.state_updated.notifyAll() - finally: - self.state_updated.release() - - - ## public: - - def waitForShutdown(self): - self.state_updated.acquire() - try: - while self.state == STATE_DOM_OK: - self.state_updated.wait() - finally: - self.state_updated.release() - - - def __str__(self): - s = "<domain" - s += " id=" + str(self.domid) - s += " name=" + self.info['name'] - s += " memory=" + str(self.info['memory']) - s += ">" - return s - - __repr__ = __str__ - - - ## private: - - def createDevice(self, deviceClass, devconfig): - return self.getDeviceController(deviceClass).createDevice(devconfig) - - - def waitForDevices_(self, deviceClass): - return self.getDeviceController(deviceClass).waitForDevices() - - - def waitForDevice(self, deviceClass, devid): + # + # Device creation/deletion functions + # + + def _createDevice(self, deviceClass, devConfig): + return self.getDeviceController(deviceClass).createDevice(devConfig) + + def _waitForDevice(self, deviceClass, devid): return self.getDeviceController(deviceClass).waitForDevice(devid) - - def reconfigureDevice(self, deviceClass, devid, devconfig): + def _reconfigureDevice(self, deviceClass, devid, devconfig): return self.getDeviceController(deviceClass).reconfigureDevice( devid, devconfig) - - ## public: - - def destroyDevice(self, deviceClass, devid): - if type(devid) is str: - devicePath = '%s/device/%s' % (self.dompath, deviceClass) - for entry in xstransact.List(devicePath): - backend = xstransact.Read('%s/%s' % (devicePath, entry), - "backend") - devName = xstransact.Read(backend, "dev") - if devName == devid: - # We found the integer matching our devid, use it instead - devid = entry - break - return self.getDeviceController(deviceClass).destroyDevice(devid) - - - def getDeviceSxprs(self, deviceClass): - return self.getDeviceController(deviceClass).sxprs() + def _createDevices(self): + """Create the devices for a vm. + + @raise: VmError for invalid devices + """ + for (devclass, config) in self.info.all_devices_sxpr(): + log.info("createDevice: %s : %s" % (devclass, config)) + self._createDevice(devclass, config) + + if self.image: + self.image.createDeviceModel() + + def _releaseDevices(self): + """Release all domain's devices. Nothrow guarantee.""" + + while True: + t = xstransact("%s/device" % self.dompath) + for devclass in XendDevices.valid_devices(): + for dev in t.list(devclass): + try: + t.remove(dev) + except: + # Log and swallow any exceptions in removal -- + # there's nothing more we can do. + log.exception( + "Device release failed: %s; %s; %s", + self.info['name'], devclass, dev) + if t.commit(): + break + + def getDeviceController(self, name): + """Get the device controller for this domain, and if it + doesn't exist, create it. + + @param name: device class name + @type name: string + @rtype: subclass of DevController + """ + if name not in self._deviceControllers: + devController = XendDevices.make_controller(name, self) + if not devController: + raise XendError("Unknown device type: %s" % name) + self._deviceControllers[name] = devController + + return self._deviceControllers[name] + + # + # Migration functions (public) + # + + def testMigrateDevices(self, network, dst): + """ Notify all device about intention of migration + @raise: XendError for a device that cannot be migrated + """ + for (n, c) in self.info.all_devices_sxpr(): + rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST) + if rc != 0: + raise XendError("Device of type '%s' refuses migration." % n) + + def migrateDevices(self, network, dst, step, domName=''): + """Notify the devices about migration + """ + ctr = 0 + try: + for (dev_type, dev_conf) in self.info.all_devices_sxpr(): + self.migrateDevice(dev_type, dev_conf, network, dst, + step, domName) + ctr = ctr + 1 + except: + for dev_type, dev_conf in self.info.all_devices_sxpr(): + if ctr == 0: + step = step - 1 + ctr = ctr - 1 + self._recoverMigrateDevice(dev_type, dev_conf, network, + dst, step, domName) + raise + + def migrateDevice(self, deviceClass, deviceConfig, network, dst, + step, domName=''): + return self.getDeviceController(deviceClass).migrate(deviceConfig, + network, dst, step, domName) + + def _recoverMigrateDevice(self, deviceClass, deviceConfig, network, + dst, step, domName=''): + return self.getDeviceController(deviceClass).recover_migrate( + deviceConfig, network, dst, step, domName) ## private: - def getDeviceConfigurations(self, deviceClass): - return self.getDeviceController(deviceClass).configurations() - - - def getDeviceController(self, name): - if name not in controllerClasses: - raise XendError("unknown device type: " + str(name)) - - return controllerClasses[name](self) - - - ## public: - - def sxpr(self): - sxpr = ['domain', - ['domid', self.domid]] - - for e in ROUNDTRIPPING_CONFIG_ENTRIES: - if self.infoIsSet(e[0]): - sxpr.append([e[0], self.info[e[0]]]) - - if self.infoIsSet('image'): - sxpr.append(['image', self.info['image']]) - - if self.infoIsSet('security'): - sxpr.append(['security', self.info['security']]) - - for cls in controllerClasses: - for config in self.getDeviceConfigurations(cls): - sxpr.append(['device', config]) - - def stateChar(name): - if name in self.info: - if self.info[name]: - return name[0] - else: - return '-' - else: - return '?' - - state = reduce( - lambda x, y: x + y, - map(stateChar, - ['running', 'blocked', 'paused', 'shutdown', 'crashed', - 'dying'])) - - sxpr.append(['state', state]) - if self.infoIsSet('shutdown'): - reason = shutdown_reason(self.info['shutdown_reason']) - sxpr.append(['shutdown_reason', reason]) - if self.infoIsSet('cpu_time'): - sxpr.append(['cpu_time', self.info['cpu_time']/1e9]) - sxpr.append(['online_vcpus', self.info['online_vcpus']]) - - if self.infoIsSet('start_time'): - up_time = time.time() - self.info['start_time'] - sxpr.append(['up_time', str(up_time) ]) - sxpr.append(['start_time', str(self.info['start_time']) ]) - - if self.store_mfn: - sxpr.append(['store_mfn', self.store_mfn]) - if self.console_mfn: - sxpr.append(['console_mfn', self.console_mfn]) - - return sxpr - - - def getVCPUInfo(self): - try: - # We include the domain name and ID, to help xm. - sxpr = ['domain', - ['domid', self.domid], - ['name', self.info['name']], - ['vcpu_count', self.info['online_vcpus']]] - - for i in range(0, self.info['max_vcpu_id']+1): - info = xc.vcpu_getinfo(self.domid, i) - - sxpr.append(['vcpu', - ['number', i], - ['online', info['online']], - ['blocked', info['blocked']], - ['running', info['running']], - ['cpu_time', info['cpu_time'] / 1e9], - ['cpu', info['cpu']], - ['cpumap', info['cpumap']]]) - - return sxpr - - except RuntimeError, exn: - raise XendError(str(exn)) - - - ## private: - - def check_name(self, name): - """Check if a vm name is valid. Valid names contain alphabetic characters, - digits, or characters in '_-.:/+'. - The same name cannot be used for more than one vm at the same time. - - @param name: name - @raise: VmError if invalid - """ - if name is None or name == '': - raise VmError('missing vm name') - for c in name: - if c in string.digits: continue - if c in '_-.:/+': continue - if c in string.ascii_letters: continue - raise VmError('invalid vm name') - - dominfo = domain_by_name(name) - if not dominfo: - return - if self.domid is None: - raise VmError("VM name '%s' already in use by domain %d" % - (name, dominfo.domid)) - if dominfo.domid != self.domid: - raise VmError("VM name '%s' is used in both domains %d and %d" % - (name, self.domid, dominfo.domid)) - - - def construct(self): + def _constructDomain(self): """Construct the domain. @raise: VmError on error """ - log.debug('XendDomainInfo.construct: %s', - self.domid) + log.debug('XendDomainInfo.constructDomain') self.domid = xc.domain_create( - dom = 0, ssidref = security.get_security_info(self.info, 'ssidref'), + domid = 0, + ssidref = security.get_security_info(self.info, 'ssidref'), handle = uuid.fromString(self.info['uuid'])) if self.domid < 0: @@ -1262,13 +1193,13 @@ class XendDomainInfo: self.dompath = GetDomainPath(self.domid) - self.recreateDom() + self._recreateDom() # Set maximum number of vcpus in domain xc.domain_max_vcpus(self.domid, int(self.info['vcpus'])) - def introduceDomain(self): + def _introduceDomain(self): assert self.domid is not None assert self.store_mfn is not None assert self.store_port is not None @@ -1279,25 +1210,25 @@ class XendDomainInfo: raise XendError(str(exn)) - def initDomain(self): + def _initDomain(self): log.debug('XendDomainInfo.initDomain: %s %s', self.domid, self.info['cpu_weight']) # if we have a boot loader but no image, then we need to set things # up by running the boot loader non-interactively - if self.infoIsSet('bootloader') and not self.infoIsSet('image'): - self.configure_bootloader() - - if not self.infoIsSet('image'): + if self._infoIsSet('bootloader') and not self._infoIsSet('image'): + self._configureBootloader() + + if not self._infoIsSet('image'): raise VmError('Missing image in configuration') try: self.image = image.create(self, self.info['image'], - self.info['device']) - - localtime = self.info['localtime'] + self.info.all_devices_sxpr()) + + localtime = self.info.get('localtime', 0) if localtime is not None and localtime == 1: xc.domain_set_time_offset(self.domid) @@ -1342,7 +1273,7 @@ class XendDomainInfo: xc.domain_memory_increase_reservation(self.domid, reservation, 0, 0) - self.createChannels() + self._createChannels() channel_details = self.image.createImage() @@ -1350,20 +1281,20 @@ class XendDomainInfo: if 'console_mfn' in channel_details: self.console_mfn = channel_details['console_mfn'] - self.introduceDomain() - - self.createDevices() + self._introduceDomain() + + self._createDevices() if self.info['bootloader']: self.image.cleanupBootloading() self.info['start_time'] = time.time() + self._stateSet(DOM_STATE_RUNNING) except RuntimeError, exn: + log.exception("XendDomainInfo.initDomain: exception occurred") raise VmError(str(exn)) - - ## public: def cleanupDomain(self): """Cleanup domain resources; release devices. Idempotent. Nothrow @@ -1373,7 +1304,7 @@ class XendDomainInfo: try: self.unwatchShutdown() - self.release_devices() + self._releaseDevices() if self.image: try: @@ -1384,46 +1315,15 @@ class XendDomainInfo: self.image = None try: - self.removeDom() + self._removeDom() except: log.exception("Removing domain path failed.") - try: - if not self.info['name'].startswith(ZOMBIE_PREFIX): - self.info['name'] = ZOMBIE_PREFIX + self.info['name'] - except: - log.exception("Renaming Zombie failed.") - - self.state_set(STATE_DOM_SHUTDOWN) + self.info['dying'] = 0 + self.info['shutdown'] = 0 + self._stateSet(DOM_STATE_HALTED) finally: self.refresh_shutdown_lock.release() - - - def cleanupVm(self): - """Cleanup VM resources. Idempotent. Nothrow guarantee.""" - - self.unwatchVm() - - try: - self.removeVm() - except: - log.exception("Removing VM path failed.") - - - ## private: - - def unwatchVm(self): - """Remove the watch on the VM path, if any. Idempotent. Nothrow - guarantee.""" - - try: - try: - if self.vmWatch: - self.vmWatch.unwatch() - finally: - self.vmWatch = None - except: - log.exception("Unwatching VM path failed.") def unwatchShutdown(self): @@ -1440,311 +1340,97 @@ class XendDomainInfo: except: log.exception("Unwatching control/shutdown failed.") - - ## public: + def waitForShutdown(self): + self.state_updated.acquire() + try: + while self.state in (DOM_STATE_RUNNING,): + self.state_updated.wait() + finally: + self.state_updated.release() + + + # + # TODO: recategorise - called from XendCheckpoint + # + + def completeRestore(self, store_mfn, console_mfn): + + log.debug("XendDomainInfo.completeRestore") + + self.store_mfn = store_mfn + self.console_mfn = console_mfn + + self._introduceDomain() + self._storeDomDetails() + self._registerWatches() + self._refreshShutdown() + + log.debug("XendDomainInfo.completeRestore done") + + + def _endRestore(self): + self.setResume(False) + + # + # VM Destroy + # def destroy(self): """Cleanup VM and destroy domain. Nothrow guarantee.""" - log.debug("XendDomainInfo.destroy: domid=%s", self.domid) - - self.cleanupVm() + log.debug("XendDomainInfo.destroy: domid=%s", str(self.domid)) + + self._cleanupVm() if self.dompath is not None: - self.destroyDomain() + self.destroyDomain() def destroyDomain(self): - log.debug("XendDomainInfo.destroyDomain(%s)", self.domid) + log.debug("XendDomainInfo.destroyDomain(%s)", str(self.domid)) try: if self.domid is not None: xc.domain_destroy(self.domid) + self.domid = None + for state in DOM_STATES_OLD: + self.info[state] = 0 except: log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.") self.cleanupDomain() - ## private: - - def release_devices(self): - """Release all domain's devices. Nothrow guarantee.""" - - while True: - t = xstransact("%s/device" % self.dompath) - for n in controllerClasses.keys(): - for d in t.list(n): - try: - t.remove(d) - except: - # Log and swallow any exceptions in removal -- - # there's nothing more we can do. - log.exception( - "Device release failed: %s; %s; %s", - self.info['name'], n, d) - if t.commit(): - break - - - def createChannels(self): + # + # Channels for xenstore and console + # + + def _createChannels(self): """Create the channels to the domain. """ - self.store_port = self.createChannel() - self.console_port = self.createChannel() - - - def createChannel(self): + self.store_port = self._createChannel() + self.console_port = self._createChannel() + + + def _createChannel(self): """Create an event channel to the domain. """ try: - return xc.evtchn_alloc_unbound(dom=self.domid, remote_dom=0) + return xc.evtchn_alloc_unbound(domid=self.domid, remote_dom=0) except: log.exception("Exception in alloc_unbound(%d)", self.domid) raise - - ## public: - - def createDevices(self): - """Create the devices for a vm. - - @raise: VmError for invalid devices - """ - - for (n, c) in self.info['device']: - self.createDevice(n, c) - - if self.image: - self.image.createDeviceModel() - - ## public: - - def checkLiveMigrateMemory(self): - """ Make sure there's enough memory to migrate this domain """ - overhead_kb = 0 - if arch.type == "x86": - # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than - # the minimum that Xen would allocate if no value were given. - overhead_kb = self.info['vcpus'] * 1024 + self.info['maxmem'] * 4 - overhead_kb = ((overhead_kb + 1023) / 1024) * 1024 - # The domain might already have some shadow memory - overhead_kb -= xc.shadow_mem_control(self.domid) * 1024 - if overhead_kb > 0: - balloon.free(overhead_kb) - - def testMigrateDevices(self, network, dst): - """ Notify all device about intention of migration - @raise: XendError for a device that cannot be migrated - """ - for (n, c) in self.info['device']: - rc = self.migrateDevice(n, c, network, dst, DEV_MIGRATE_TEST) - if rc != 0: - raise XendError("Device of type '%s' refuses migration." % n) - - def testDeviceComplete(self): - """ For Block IO migration safety we must ensure that - the device has shutdown correctly, i.e. all blocks are - flushed to disk - """ - start = time.time() - while True: - test = 0 - diff = time.time() - start - for i in self.getDeviceController('vbd').deviceIDs(): - test = 1 - log.info("Dev %s still active, looping...", i) - time.sleep(0.1) - - if test == 0: - break - if diff >= MIGRATE_TIMEOUT: - log.info("Dev still active but hit max loop timeout") - break - - def migrateDevices(self, network, dst, step, domName=''): - """Notify the devices about migration - """ - ctr = 0 - try: - for (n, c) in self.info['device']: - self.migrateDevice(n, c, network, dst, step, domName) - ctr = ctr + 1 - except: - for (n, c) in self.info['device']: - if ctr == 0: - step = step - 1 - ctr = ctr - 1 - self.recoverMigrateDevice(n, c, network, dst, step, domName) - raise - - def migrateDevice(self, deviceClass, deviceConfig, network, dst, - step, domName=''): - return self.getDeviceController(deviceClass).migrate(deviceConfig, - network, dst, step, domName) - - def recoverMigrateDevice(self, deviceClass, deviceConfig, network, - dst, step, domName=''): - return self.getDeviceController(deviceClass).recover_migrate( - deviceConfig, network, dst, step, domName) - - def waitForDevices(self): - """Wait for this domain's configured devices to connect. - - @raise: VmError if any device fails to initialise. - """ - for c in controllerClasses: - self.waitForDevices_(c) - - - def device_create(self, dev_config): - """Create a new device. - - @param dev_config: device configuration - """ - dev_type = sxp.name(dev_config) - devid = self.createDevice(dev_type, dev_config) - self.waitForDevice(dev_type, devid) - self.info['device'].append((dev_type, dev_config)) - return self.getDeviceController(dev_type).sxpr(devid) - - - def device_configure(self, dev_config): - """Configure an existing device. - @param dev_config: device configuration - """ - deviceClass = sxp.name(dev_config) - self.reconfigureDevice(deviceClass, None, dev_config) - - - def pause(self): - xc.domain_pause(self.domid) - - - def unpause(self): - xc.domain_unpause(self.domid) - - - ## private: - - def restart(self, rename = False): - """Restart the domain after it has exited. - - @param rename True if the old domain is to be renamed and preserved, - False if it is to be destroyed. - """ - - self.configure_bootloader() - config = self.sxpr() - - if self.infoIsSet('cpus') and len(self.info['cpus']) != 0: - config.append(['cpus', reduce(lambda x, y: str(x) + "," + str(y), - self.info['cpus'])]) - - if self.readVm(RESTART_IN_PROGRESS): - log.error('Xend failed during restart of domain %d. ' - 'Refusing to restart to avoid loops.', - self.domid) - self.destroy() - return - - self.writeVm(RESTART_IN_PROGRESS, 'True') - - now = time.time() - rst = self.readVm('xend/previous_restart_time') - if rst: - rst = float(rst) - timeout = now - rst - if timeout < MINIMUM_RESTART_TIME: - log.error( - 'VM %s restarting too fast (%f seconds since the last ' - 'restart). Refusing to restart to avoid loops.', - self.info['name'], timeout) - self.destroy() - return - - self.writeVm('xend/previous_restart_time', str(now)) - - try: - if rename: - self.preserveForRestart() - else: - self.unwatchVm() - self.destroyDomain() - - # new_dom's VM will be the same as this domain's VM, except where - # the rename flag has instructed us to call preserveForRestart. - # In that case, it is important that we remove the - # RESTART_IN_PROGRESS node from the new domain, not the old one, - # once the new one is available. - - new_dom = None - try: - new_dom = XendDomain.instance().domain_create(config) - new_dom.unpause() - rst_cnt = self.readVm('xend/restart_count') - rst_cnt = int(rst_cnt) + 1 - self.writeVm('xend/restart_count', str(rst_cnt)) - new_dom.removeVm(RESTART_IN_PROGRESS) - except: - if new_dom: - new_dom.removeVm(RESTART_IN_PROGRESS) - new_dom.destroy() - else: - self.removeVm(RESTART_IN_PROGRESS) - raise - except: - log.exception('Failed to restart domain %d.', self.domid) - - - def preserveForRestart(self): - """Preserve a domain that has been shut down, by giving it a new UUID, - cloning the VM details, and giving it a new name. This allows us to - keep this domain for debugging, but restart a new one in its place - preserving the restart semantics (name and UUID preserved). - """ - - new_name = self.generateUniqueName() - new_uuid = uuid.toString(uuid.create()) - log.info("Renaming dead domain %s (%d, %s) to %s (%s).", - self.info['name'], self.domid, self.info['uuid'], - new_name, new_uuid) - self.unwatchVm() - self.release_devices() - self.info['name'] = new_name - self.info['uuid'] = new_uuid - self.vmpath = XendDomain.VMROOT + new_uuid - self.storeVmDetails() - self.preserve() - - - def preserve(self): - log.info("Preserving dead domain %s (%d).", self.info['name'], - self.domid) - self.unwatchVm() - self.storeDom('xend/shutdown_completed', 'True') - self.state_set(STATE_DOM_SHUTDOWN) - - - # private: - - def generateUniqueName(self): - n = 1 - while True: - name = "%s-%d" % (self.info['name'], n) - try: - self.check_name(name) - return name - except VmError: - n += 1 - - - def configure_bootloader(self): + # + # Bootloader configuration + # + + def _configureBootloader(self): """Run the bootloader if we're configured to do so.""" if not self.info['bootloader']: return blcfg = None # FIXME: this assumes that we want to use the first disk device - for (n,c) in self.info['device']: + for (n, c) in self.info.all_devices_sxpr(): if not n or not c or n != "vbd": continue disk = sxp.child_value(c, "uname") @@ -1761,38 +1447,386 @@ class XendDomainInfo: raise VmError(msg) self.info['image'] = blcfg - - def send_sysrq(self, key): - asserts.isCharConvertible(key) - - self.storeDom("control/sysrq", '%c' % key) - - - def infoIsSet(self, name): + # + # VM Functions + # + + def _readVMDetails(self, params): + """Read the specified parameters from the store. + """ + try: + return self._gatherVm(*params) + except ValueError: + # One of the int/float entries in params has a corresponding store + # entry that is invalid. We recover, because older versions of + # Xend may have put the entry there (memory/target, for example), + # but this is in general a bad situation to have reached. + log.exception( + "Store corrupted at %s! Domain %d's configuration may be " + "affected.", self.vmpath, self.domid) + return [] + + def _cleanupVm(self): + """Cleanup VM resources. Idempotent. Nothrow guarantee.""" + + self._unwatchVm() + + try: + self._removeVm() + except: + log.exception("Removing VM path failed.") + + + def checkLiveMigrateMemory(self): + """ Make sure there's enough memory to migrate this domain """ + overhead_kb = 0 + if arch.type == "x86": + # 1MB per vcpu plus 4Kib/Mib of RAM. This is higher than + # the minimum that Xen would allocate if no value were given. + overhead_kb = self.info['vcpus'] * 1024 + self.info['maxmem'] * 4 + overhead_kb = ((overhead_kb + 1023) / 1024) * 1024 + # The domain might already have some shadow memory + overhead_kb -= xc.shadow_mem_control(self.domid) * 1024 + if overhead_kb > 0: + balloon.free(overhead_kb) + + def _unwatchVm(self): + """Remove the watch on the VM path, if any. Idempotent. Nothrow + guarantee.""" + + def testDeviceComplete(self): + """ For Block IO migration safety we must ensure that + the device has shutdown correctly, i.e. all blocks are + flushed to disk + """ + start = time.time() + while True: + test = 0 + diff = time.time() - start + for i in self.getDeviceController('vbd').deviceIDs(): + test = 1 + log.info("Dev %s still active, looping...", i) + time.sleep(0.1) + + if test == 0: + break + if diff >= MIGRATE_TIMEOUT: + log.info("Dev still active but hit max loop timeout") + break + + def _storeVmDetails(self): + to_store = {} + + for k in VM_STORE_ENTRIES: + if self._infoIsSet(k[0]): + to_store[k[0]] = str(self.info[k[0]]) + + if self._infoIsSet('image'): + to_store['image'] = sxp.to_string(self.info['image']) + + if self._infoIsSet('security'): + secinfo = self.info['security'] + to_store['security'] = sxp.to_string(secinfo) + for idx in range(0, len(secinfo)): + if secinfo[idx][0] == 'access_control': + to_store['security/access_control'] = sxp.to_string( + [secinfo[idx][1], secinfo[idx][2]]) + for aidx in range(1, len(secinfo[idx])): + if secinfo[idx][aidx][0] == 'label': + to_store['security/access_control/label'] = \ + secinfo[idx][aidx][1] + if secinfo[idx][aidx][0] == 'policy': + to_store['security/access_control/policy'] = \ + secinfo[idx][aidx][1] + if secinfo[idx][0] == 'ssidref': + to_store['security/ssidref'] = str(secinfo[idx][1]) + + + if not self._readVm('xend/restart_count'): + to_store['xend/restart_count'] = str(0) + + log.debug("Storing VM details: %s", to_store) + + self._writeVm(to_store) + self._setVmPermissions() + + + def _setVmPermissions(self): + """Allow the guest domain to read its UUID. We don't allow it to + access any other entry, for security.""" + xstransact.SetPermissions('%s/uuid' % self.vmpath, + { 'dom' : self.domid, + 'read' : True, + 'write' : False }) + + # + # Utility functions + # + + def _stateSet(self, state): + self.state_updated.acquire() + try: + if self.state != state: + self.state = state + self.state_updated.notifyAll() + finally: + self.state_updated.release() + + def _infoIsSet(self, name): return name in self.info and self.info[name] is not None - -#============================================================================ -# Register device controllers and their device config types. - -"""A map from device-class names to the subclass of DevController that -implements the device control specific to that device-class.""" -controllerClasses = {} - -def addControllerClass(device_class, cls): - """Register a subclass of DevController to handle the named device-class. + def _checkName(self, name): + """Check if a vm name is valid. Valid names contain alphabetic + characters, digits, or characters in '_-.:/+'. + The same name cannot be used for more than one vm at the same time. + + @param name: name + @raise: VmError if invalid + """ + from xen.xend import XendDomain + + if name is None or name == '': + raise VmError('Missing VM Name') + + if not re.search(r'^[A-Za-z0-9_\-\.\:\/\+]+$', name): + raise VmError('Invalid VM Name') + + dom = XendDomain.instance().domain_lookup_nr(name) + if dom and dom != self: + raise VmError("VM name '%s' already exists" % name) + + + def update(self, info = None, refresh = True): + """Update with info from xc.domain_getinfo(). + """ + + log.trace("XendDomainInfo.update(%s) on domain %s", info, + str(self.domid)) + + if not info: + info = dom_get(self.domid) + if not info: + return + + #manually update ssidref / security fields + if security.on() and info.has_key('ssidref'): + if (info['ssidref'] != 0) and self.info.has_key('security'): + security_field = self.info['security'] + if not security_field: + #create new security element + self.info.update({'security': + [['ssidref', str(info['ssidref'])]]}) + #ssidref field not used any longer + if 'ssidref' in info: + info.pop('ssidref') + + # make sure state is reset for info + # TODO: we should eventually get rid of old_dom_states + + self.info.update(info) + self.info.validate() + + if refresh: + self._refreshShutdown(info) + + log.trace("XendDomainInfo.update done on domain %s: %s", + str(self.domid), self.info) + + def sxpr(self, ignore_devices = False): + return self.info.get_sxp(domain = self, + ignore_devices = ignore_devices) + + # Xen API + # ---------------------------------------------------------------- + + def get_uuid(self): + return self.info['uuid'] + def get_memory_static_max(self): + return self.info['memmax'] + def get_memory_static_min(self): + return self.info['memory'] + def get_vcpus_policy(self): + return '' # TODO + def get_vcpus_params(self): + return '' # TODO + def get_power_state(self): + return self.state + def get_tpm_instance(self): + return '' # TODO + def get_tpm_backend(self): + return '' # TODO + def get_bios_boot(self): + return '' # TODO + def get_platform_std_vga(self): + return False + def get_platform_serial(self): + return '' # TODO + def get_platform_localtime(self): + return False # TODO + def get_platform_clock_offset(self): + return False # TODO + def get_platform_enable_audio(self): + return False # TODO + def get_builder(self): + return 'Linux' # TODO + def get_boot_method(self): + return self.info['bootloader'] + def get_kernel_image(self): + return self.info['kernel_kernel'] + def get_kernel_initrd(self): + return self.info['kernel_initrd'] + def get_kernel_args(self): + return self.info['kernel_args'] + def get_grub_cmdline(self): + return '' # TODO + def get_pci_bus(self): + return 0 # TODO + def get_tools_version(self): + return {} # TODO + def get_other_config(self): + return {} # TODO + + def get_on_shutdown(self): + try: + return XEN_API_ON_NORMAL_EXIT.index(self.info['on_poweroff']) + except ValueError, e: + return XEN_API_ON_NORMAL_EXIT.index('restart') + + def get_on_reboot(self): + try: + return XEN_API_ON_NORMAL_EXIT.index(self.info['on_reboot']) + except ValueError, e: + return XEN_API_ON_NORMAL_EXIT.index('restart') + + def get_on_suspend(self): + return 0 # TODO + + def get_on_crash(self): + try: + return XEN_API_ON_CRASH_BEHAVIOUR.index(self.info['on_crash']) + except ValueError, e: + return XEN_API_ON_CRASH_BEHAVIOUR.index('destroy') + + + def get_device_property(self, devclass, devid, field): + controller = self.getDeviceController(devclass) + + if devclass == 'vif': + if field in ('name', 'MAC', 'type'): + config = controller.getDeviceConfiguration(devid) + if field == 'name': + return config['vifname'] + if field == 'mac': + return config['mac'] + if field == 'type': + return config['type'] + if field == 'device': + return 'eth%s' % devid + if field == 'network': + return None # TODO + if field == 'VM': + return self.get_uuid() + if field == 'MTU': + return 0 # TODO + # TODO: network bandwidth values + return 0.0 + + if devclass == 'vbd': + if field == 'VM': + return self.get_uuid() + if field == 'VDI': + return '' # TODO + if field in ('device', 'mode', 'driver'): + config = controller.getDeviceConfiguration(devid) + if field == 'device': + return config['dev'] # TODO + if field == 'mode': + return config['mode'] + if field == 'driver': + return config['uname'] # TODO + + # TODO network bandwidth values + return 0.0 + + raise XendError("Unrecognised dev class or property") + + + def is_device_valid(self, devclass, devid): + controller = self.getDeviceController(devclass) + return (devid in controller.deviceIDs()) + + def get_vcpus_util(self): + # TODO: this returns the total accum cpu time, rather than util + vcpu_util = {} + if 'max_vcpu_id' in self.info: + for i in range(0, self.info['max_vcpu_id']+1): + info = xc.vcpu_getinfo(self.domid, i) + vcpu_util[i] = info['cpu_time'] + + return vcpu_util + + def get_vifs(self): + return self.info['vif_refs'] + + def get_vbds(self): + return self.info['vbd_refs'] + + def create_vbd(self, xenapi_vbd): + """Create a VBD device from the passed struct in Xen API format. + + @return: uuid of the device + @rtype: string + """ + + dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd) + if not dev_uuid: + raise XendError('Failed to create device') + + if self.state in (XEN_API_VM_POWER_STATE_RUNNING,): + sxpr = self.info.device_sxpr(dev_uuid) + devid = self.getDeviceController('vbd').createDevice(sxpr) + raise XendError("Device creation failed") + + return dev_uuid + + def create_vif(self, xenapi_vif): + + dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif) + if not dev_uuid: + raise XendError('Failed to create device') + + if self.state in (XEN_API_VM_POWER_STATE_RUNNING,): + sxpr = self.info.device_sxpr(dev_uuid) + devid = self.getDeviceController('vif').createDevice(sxpr) + raise XendError("Device creation failed") + + return dev_uuid + + """ - cls.deviceClass = device_class - controllerClasses[device_class] = cls - - -from xen.xend.server import blkif, netif, tpmif, pciif, iopif, irqif, usbif -from xen.xend.server.BlktapController import BlktapController -addControllerClass('vbd', blkif.BlkifController) -addControllerClass('vif', netif.NetifController) -addControllerClass('vtpm', tpmif.TPMifController) -addControllerClass('pci', pciif.PciController) -addControllerClass('ioports', iopif.IOPortsController) -addControllerClass('irq', irqif.IRQController) -addControllerClass('usb', usbif.UsbifController) -addControllerClass('tap', BlktapController) + def stateChar(name): + if name in self.info: + if self.info[name]: + return name[0] + else: + return '-' + else: + return '?' + + state = reduce(lambda x, y: x + y, map(stateChar, DOM_STATES_OLD)) + + sxpr.append(['state', state]) + + if self.store_mfn: + sxpr.append(['store_mfn', self.store_mfn]) + if self.console_mfn: + sxpr.append(['console_mfn', self.console_mfn]) + """ + + def __str__(self): + return '<domain id=%s name=%s memory=%s state=%s>' % \ + (str(self.domid), self.info['name'], + str(self.info['memory']), DOM_STATES[self.state]) + + __repr__ = __str__ + diff -r ec29b6262a8b -r 9a932b5c7947 tools/python/xen/xend/server/DevController.py --- a/tools/python/xen/xend/server/DevController.py Thu Oct 05 17:29:19 2006 +0100 +++ b/tools/python/xen/xend/server/DevController.py Thu Oct 05 17:29:19 2006 +0100 @@ -88,9 +88,9 @@ class DevController: xd = xen.xend.XendDomain.instance() backdom_name = sxp.child_value(config, 'backend') if backdom_name is None: - backdom = xen.xend.XendDomain.PRIV_DOMAIN - else: - bd = xd.domain_lookup_by_name_or_id_nr(backdom_name) + backdom = xen.xend.XendDomain.DOM0_ID + else: + bd = xd.domain_lookup_nr(backdom_name) backdom = bd.getDomid() count = 0 while True: @@ -141,7 +141,6 @@ class DevController: def waitForDevices(self): log.debug("Waiting for devices %s.", self.deviceClass) - return map(self.waitForDevice, self.deviceIDs()) @@ -221,27 +220,41 @@ class DevController: """@return an s-expression giving the current configuration of the specified device. This would be suitable for giving to {@link #createDevice} in order to recreate that device.""" - + configDict = self.getDeviceConfiguration(devid) + sxpr = [self.deviceClass] + for key, val in configDict.items(): + if type(val) == type(list()): + for v in val: + sxpr.append([key, v]) + else: + sxpr.append([key, val]) + return sxpr + + def sxprs(self): + """@return an s-expression describing all the devices of this + controller's device-class. + """ + return xstransact.ListRecursive(self.frontendRoot()) + + + def sxpr(self, devid): + """@return an s-expression describing the specified device. + """ + return [self.deviceClass, ['dom', self.vm.getDomid(), + 'id', devid]] + + + def getDeviceConfiguration(self, devid): + """Returns the configuration of a device. + + @note: Similar to L{configuration} except it returns a dict. + @return: dict + """ backdomid = xstransact.Read(self.frontendPath(devid), "backend-id") if backdomid is None: raise VmError("Device %s not connected" % devid) - - return [self.deviceClass, ['backend', int(backdomid)]] - - - def sxprs(self): - """@return an s-expression describing all the devices of this - controller's device-class. - """ - return xstransact.ListRecursive(self.frontendRoot()) - - - def sxpr(self, devid): - """@return an s-expression describing the specified device. - """ - return [self.deviceClass, ['dom', self.vm.getDomid(), - 'id', devid]] - + + return {'backend': int(backdomid)} ## protected: @@ -387,7 +400,7 @@ class DevController: backdom_name = sxp.child_value(config, 'backend') if backdom_name: - backdom = xd.domain_lookup_by_name_or_id_nr(backdom_name) + backdom = xd.domain_lookup_nr(backdom_name) else: backdom = xd.privilegedDomain() @@ -451,9 +464,10 @@ class DevController: def backendRoot(self): import xen.xend.XendDomain - from xen.xend.xenstore.xsutil import GetDomainPath - backdom = xen.xend.XendDomain.PRIV_DOMAIN - return "%s/backend/%s/%s" % (GetDomainPath(backdom), self.deviceClass, self.vm.getDomid()) + from xen.xend.xenstore.xsutil import GetDomainPath + backdom = xen.xend.XendDomain.DOM0_ID + return "%s/backend/%s/%s" % (GetDomainPath(backdom), + self.deviceClass, self.vm.getDomid()) def frontendMiscPath(self): return "%s/device-misc/%s" % (self.vm.getDomainPath(), diff -r ec29b6262a8b -r 9a932b5c7947 tools/python/xen/xend/server/SrvDomainDir.py --- a/tools/python/xen/xend/server/SrvDomainDir.py Thu Oct 05 17:29:19 2006 +0100 +++ b/tools/python/xen/xend/server/SrvDomainDir.py Thu Oct 05 17:29:19 2006 +0100 @@ -39,7 +39,7 @@ class SrvDomainDir(SrvDir): self.xd = XendDomain.instance() def domain(self, x): - dom = self.xd.domain_lookup_by_name_or_id(x) + dom = self.xd.domain_lookup(x) if not dom: raise XendError('No such domain ' + str(x)) return SrvDomain(dom) diff -r ec29b6262a8b -r 9a932b5c7947 tools/python/xen/xend/server/netif.py --- a/tools/python/xen/xend/server/netif.py Thu Oct 05 17:29:19 2006 +0100 +++ b/tools/python/xen/xend/server/netif.py Thu Oct 05 17:29:19 2006 +0100 @@ -26,12 +26,9 @@ import re from xen.xend import sxp from xen.xend import XendRoot - from xen.xend.server.DevController import DevController - xroot = XendRoot.instance() - def randomMAC(): """Generate a random MAC address. @@ -138,7 +135,6 @@ class NetifController(DevController): def __init__(self, vm): DevController.__init__(self, vm) - def getDeviceDetails(self, config): """@see DevController.getDeviceDetails""" @@ -157,6 +153,7 @@ class NetifController(DevController): mac = sxp.child_value(config, 'mac') vifname = sxp.child_value(config, 'vifname') rate = sxp.child_value(config, 'rate') + uuid = sxp.child_value(config, 'uuid') ipaddr = _get_config_ipaddr(config) devid = self.allocateDeviceID() @@ -182,34 +179,37 @@ class NetifController(DevController): back['vifname'] = vifname if rate: back['rate'] = parseRate(rate) + if uuid: + back['uuid'] = uuid return (devid, back, front) - def configuration(self, devid): + def getDeviceConfiguration(self, devid): """@see DevController.configuration""" - result = DevController.configuration(self, devid) - - (script, ip, bridge, mac, typ, vifname, rate) = self.readBackend( - devid, 'script', 'ip', 'bridge', 'mac', 'type', 'vifname', 'rate') + result = DevController.getDeviceConfiguration(self, devid) + devinfo = self.readBackend(devid, 'script', 'ip', 'bridge', + 'mac', 'type', 'vifname', 'rate', 'uuid') + (script, ip, bridge, mac, typ, vifname, rate, uuid) = devinfo if script: - result.append(['script', - script.replace(xroot.network_script_dir + os.sep, - "")]) + network_script_dir = xroot.network_script_dir + os.sep + result['script'] = script.replace(network_script_dir, "") if ip: - for i in ip.split(" "): - result.append(['ip', i]) + result['ip'] = ip.split(" ") if bridge: - result.append(['bridge', bridge]) + result['bridge'] = bridge if mac: - result.append(['mac', mac]) + result['mac'] = mac if typ: - result.append(['type', typ]) + result['type'] = typ if vifname: - result.append(['vifname', vifname]) + result['vifname'] = vifname if rate: - result.append(['rate', formatRate(rate)]) + result['rate'] = formatRate(rate) + if uuid: + result['uuid'] = uuid return result + diff -r ec29b6262a8b -r 9a932b5c7947 tools/python/xen/xend/server/pciif.py --- a/tools/python/xen/xend/server/pciif.py Thu Oct 05 17:29:19 2006 +0100 +++ b/tools/python/xen/xend/server/pciif.py Thu Oct 05 17:29:19 2006 +0100 @@ -109,29 +109,54 @@ class PciController(DevController): return (0, back, {}) - def configuration(self, devid): - """@see DevController.configuration""" - - result = DevController.configuration(self, devid) - - (num_devs) = self.readBackend(devid, 'num_devs') - + def getDeviceConfiguration(self, devid): + result = DevController.getDeviceConfiguration(self, devid) + num_devs = self.readBackend(devid, 'num_devs') + pci_devs = [] + for i in range(int(num_devs)): - (dev_config) = self.readBackend(devid, 'dev-%d'%(i)) + (dev_config,) = self.readBackend(devid, 'dev-%d'%(i)) pci_match = re.match(r"((?P<domain>[0-9a-fA-F]{1,4})[:,])?" + \ r"(?P<bus>[0-9a-fA-F]{1,2})[:,]" + \ r"(?P<slot>[0-9a-fA-F]{1,2})[.,]" + \ r"(?P<func>[0-9a-fA-F]{1,2})", dev_config) + if pci_match!=None: pci_dev_info = pci_match.groupdict('0') - result.append( ['dev', \ - ['domain', '0x'+pci_dev_info['domain']], \ - ['bus', '0x'+pci_dev_info['bus']], \ - ['slot', '0x'+pci_dev_info['slot']], \ - ['func', '0x'+pci_dev_info['func']]]) - + pci_devs.append({'domain': '0x%(domain)s' % pci_dev_info, + 'bus': '0x%(bus)s' % pci_dev_info, + 'slot': '0x(slot)s' % pci_dev_info, + 'func': '0x(func)s' % pci_dev_info}) + + result['dev'] = pci_devs return result + + def configuration(self, devid): + """Returns SXPR for devices on domain. + + @note: we treat this dict especially to convert to + SXP because it is not a straight dict of strings.""" + + configDict = self.getDeviceConfiguration(devid) + sxpr = [self.deviceClass] + + # remove devs + devs = configDict.pop('dev', []) + for dev in devs: + dev_sxpr = ['dev'] + for dev_item in dev.items(): + dev_sxpr.append(list(dev_item)) + sxpr.append(dev_sxpr) + + for key, val in configDict.items(): + if type(val) == type(list()): + for v in val: + sxpr.append([key, v]) + else: + sxpr.append([key, val]) + + return sxpr def setupDevice(self, domain, bus, slot, func): """ Attach I/O resources for device to frontend domain _______________________________________________ Xen-changelog mailing list Xen-changelog@xxxxxxxxxxxxxxxxxxx http://lists.xensource.com/xen-changelog
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |