[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Xen-changelog] [xen-unstable] [XEND] Massive XendDomain XendDomainInfo reorganisation to use XendConfig.



# HG changeset patch
# User Alastair Tse <atse@xxxxxxxxxxxxx>
# Node ID 9a932b5c7947cb1512e3f3a1fe65a5541e56e425
# Parent  ec29b6262a8bb7a3c659b208ffeb310ecd9595e0
[XEND] Massive XendDomain XendDomainInfo reorganisation to use XendConfig.

* Added some constants to do with lifecycle management in XendDomain
* Added docstrings
* Made all private functions prefixed with an underscore (_)
* Added lifecycle management in XendDomain which stores configs in SXP
  format.
* Added bits of XenAPI support.
* Moved all XendDomainInfo constants to a separate file
* Moved most of the domain creation login into XendDomainInfo rather
  than in module level functions.
* Moved to use Xen API compatible reporting of state.
* Reorganised methods into logical groups in XendDomain and XendDomainInfo
* Moved all domain configuration validation into XendConfig
* Removed DevController cruft from the bottom of XendDomainInfo to
  XendDevices.

Signed-off-by: Alastair Tse <atse@xxxxxxxxxxxxx>
---
 tools/python/xen/xend/XendDomain.py           | 1268 ++++++++++----
 tools/python/xen/xend/XendDomainInfo.py       | 2342 +++++++++++++-------------
 tools/python/xen/xend/server/DevController.py |   66 
 tools/python/xen/xend/server/SrvDomainDir.py  |    2 
 tools/python/xen/xend/server/netif.py         |   38 
 tools/python/xen/xend/server/pciif.py         |   53 
 6 files changed, 2244 insertions(+), 1525 deletions(-)

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

_______________________________________________
Xen-changelog mailing list
Xen-changelog@xxxxxxxxxxxxxxxxxxx
http://lists.xensource.com/xen-changelog


 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.