# 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
|