WARNING - OLD ARCHIVES

This is an archived copy of the Xen.org mailing list, which we have preserved to ensure that existing links to archives are not broken. The live archive, which contains the latest emails, can be found at http://lists.xen.org/
   
 
 
Xen 
 
Home Products Support Community News
 
   
 

xen-changelog

[Xen-changelog] [xen-unstable] [XEND] Massive XendDomain XendDomainInfo

To: xen-changelog@xxxxxxxxxxxxxxxxxxx
Subject: [Xen-changelog] [xen-unstable] [XEND] Massive XendDomain XendDomainInfo reorganisation to use XendConfig.
From: Xen patchbot-unstable <patchbot-unstable@xxxxxxxxxxxxxxxxxxx>
Date: Thu, 02 Nov 2006 22:08:27 +0000
Delivery-date: Thu, 02 Nov 2006 21:39:36 -0800
Envelope-to: www-data@xxxxxxxxxxxxxxxxxx
List-help: <mailto:xen-changelog-request@lists.xensource.com?subject=help>
List-id: BK change log <xen-changelog.lists.xensource.com>
List-post: <mailto:xen-changelog@lists.xensource.com>
List-subscribe: <http://lists.xensource.com/cgi-bin/mailman/listinfo/xen-changelog>, <mailto:xen-changelog-request@lists.xensource.com?subject=subscribe>
List-unsubscribe: <http://lists.xensource.com/cgi-bin/mailman/listinfo/xen-changelog>, <mailto:xen-changelog-request@lists.xensource.com?subject=unsubscribe>
Reply-to: xen-devel@xxxxxxxxxxxxxxxxxxx
Sender: xen-changelog-bounces@xxxxxxxxxxxxxxxxxxx
# 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

<Prev in Thread] Current Thread [Next in Thread>
  • [Xen-changelog] [xen-unstable] [XEND] Massive XendDomain XendDomainInfo reorganisation to use XendConfig., Xen patchbot-unstable <=