msmith - in flumotion/trunk: . flumotion/admin flumotion/common flumotion/launch flumotion/manager flumotion/test po

flumotion-commit at lists.fluendo.com flumotion-commit at lists.fluendo.com
Fri Dec 28 13:04:47 CET 2007


Author: msmith
Date: Fri Dec 28 13:04:37 2007
New Revision: 6050

Added:
   flumotion/trunk/flumotion/admin/config.py
   flumotion/trunk/flumotion/manager/config.py
   flumotion/trunk/flumotion/test/test_admin_config.py
Modified:
   flumotion/trunk/ChangeLog
   flumotion/trunk/flumotion/admin/Makefile.am
   flumotion/trunk/flumotion/common/config.py
   flumotion/trunk/flumotion/common/testsuite.py
   flumotion/trunk/flumotion/launch/parse.py
   flumotion/trunk/flumotion/manager/Makefile.am
   flumotion/trunk/flumotion/manager/component.py
   flumotion/trunk/flumotion/manager/main.py
   flumotion/trunk/flumotion/manager/manager.py
   flumotion/trunk/flumotion/test/Makefile.am
   flumotion/trunk/flumotion/test/test_config.py
   flumotion/trunk/po/POTFILES.in
Log:
    * flumotion/admin/Makefile.am:
    * flumotion/admin/config.py:
      Split out admin config parser from common/config.py
    * flumotion/common/config.py:
      Split out admin and manager config parsers into seperate files, keep
      only the generic bits here.
    * flumotion/common/testsuite.py:
    * flumotion/launch/parse.py:
      Use manager.config
    * flumotion/manager/Makefile.am:
    * flumotion/manager/config.py:
      Split out manager/flow config parsers into here.
      Fixed some debug messages.
    * flumotion/manager/main.py:
    * flumotion/manager/manager.py:
    * flumotion/manager/component.py:
      Adapted for new imports.
    * flumotion/test/Makefile.am:
      Added test_admin_config.py
    * flumotion/test/test_admin_config.py:
      Moved these tests from test_config.py
    * flumotion/test/test_config.py:
      Changes for file/import movements, move admin config tests into another
      file.



Modified: flumotion/trunk/ChangeLog
==============================================================================
--- flumotion/trunk/ChangeLog	(original)
+++ flumotion/trunk/ChangeLog	Fri Dec 28 13:04:37 2007
@@ -1,3 +1,30 @@
+2007-12-28  Michael Smith <msmith at fluendo.com>
+
+	* flumotion/admin/Makefile.am:
+	* flumotion/admin/config.py:
+	  Split out admin config parser from common/config.py
+	* flumotion/common/config.py:
+	  Split out admin and manager config parsers into seperate files, keep
+	  only the generic bits here.
+	* flumotion/common/testsuite.py:
+	* flumotion/launch/parse.py:
+	  Use manager.config
+	* flumotion/manager/Makefile.am:
+	* flumotion/manager/config.py:
+	  Split out manager/flow config parsers into here.
+	  Fixed some debug messages.
+	* flumotion/manager/main.py:
+	* flumotion/manager/manager.py:
+	* flumotion/manager/component.py:
+	  Adapted for new imports.
+	* flumotion/test/Makefile.am:
+	  Added test_admin_config.py
+	* flumotion/test/test_admin_config.py:
+	  Moved these tests from test_config.py
+	* flumotion/test/test_config.py:
+	  Changes for file/import movements, move admin config tests into another
+	  file.
+
 2007-12-27  Johan Dahlin  <johan at gnome.org>
 
 	* flumotion/component/encoders/encodingwizardplugin.py:

Modified: flumotion/trunk/flumotion/admin/Makefile.am
==============================================================================
--- flumotion/trunk/flumotion/admin/Makefile.am	(original)
+++ flumotion/trunk/flumotion/admin/Makefile.am	Fri Dec 28 13:04:37 2007
@@ -4,6 +4,7 @@
 component_PYTHON = \
 	__init__.py \
 	admin.py \
+	config.py \
 	connections.py \
 	multi.py
 

Added: flumotion/trunk/flumotion/admin/config.py
==============================================================================
--- (empty file)
+++ flumotion/trunk/flumotion/admin/config.py	Fri Dec 28 13:04:37 2007
@@ -0,0 +1,78 @@
+# -*- Mode: Python; test-case-name: flumotion.test.test_config -*-
+# vi:si:et:sw=4:sts=4:ts=4
+#
+# Flumotion - a streaming media server
+# Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com).
+# All rights reserved.
+
+# This file may be distributed and/or modified under the terms of
+# the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+# This file is distributed without any warranty; without even the implied
+# warranty of merchantability or fitness for a particular purpose.
+# See "LICENSE.GPL" in the source distribution for more information.
+
+# Licensees having purchased or holding a valid Flumotion Advanced
+# Streaming Server license may use this file in accordance with the
+# Flumotion Advanced Streaming Server Commercial License Agreement.
+# See "LICENSE.Flumotion" in the source distribution for more information.
+
+# Headers in this file shall remain intact.
+
+__version__ = "$Rev: 6049 $"
+
+"""
+parsing of admin configuration files
+"""
+
+from flumotion.common import errors
+from flumotion.common import config as fluconfig
+
+class AdminConfigParser(fluconfig.BaseConfigParser):
+    """
+    Admin configuration file parser.
+    """
+    logCategory = 'config'
+
+    def __init__(self, sockets, file):
+        """
+        @param file: The file to parse, either as an open file object,
+        or as the name of a file to open.
+        @type  file: str or file.
+        """
+        self.plugs = {}
+        for socket in sockets:
+            self.plugs[socket] = []
+
+        # will start the parse via self.add()
+        fluconfig.BaseConfigParser.__init__(self, file)
+
+    def _parse(self):
+        # <admin>
+        #   <plugs>
+        root = self.doc.documentElement
+        if not root.nodeName == 'admin':
+            raise errors.ConfigError("unexpected root node': %s" % 
+                (root.nodeName,))
+
+        def parseplugs(node):
+            return fluconfig.buildPlugsSet(self.parsePlugs(node),
+                                 self.plugs.keys())
+        def addplugs(plugs):
+            for socket in plugs:
+                self.plugs[socket].extend(plugs[socket])
+        parsers = {'plugs': (parseplugs, addplugs)}
+
+        self.parseFromTable(root, parsers)
+        self.doc.unlink()
+        self.doc = None
+
+    def add(self, file):
+        """
+        @param file: The file to parse, either as an open file object,
+        or as the name of a file to open.
+        @type  file: str or file.
+        """
+        fluconfig.BaseConfigParser.add(self, file)
+        self._parse()
+

Modified: flumotion/trunk/flumotion/common/config.py
==============================================================================
--- flumotion/trunk/flumotion/common/config.py	(original)
+++ flumotion/trunk/flumotion/common/config.py	Fri Dec 28 13:04:37 2007
@@ -22,7 +22,7 @@
 __version__ = "$Rev$"
 
 """
-parsing of configuration files
+Base classes for parsing of flumotion configuration files
 """
 
 import os
@@ -38,125 +38,6 @@
 
 from errors import ConfigError
 
-def _ignore(*args):
-    pass
-
-def upgradeEaters(conf):
-    def parseFeedId(feedId):
-        if feedId.find(':') == -1:
-            return "%s:default" % feedId
-        else:
-            return feedId
-
-    eaterConfig = conf.get('eater', {})
-    sourceConfig = conf.get('source', [])
-    if eaterConfig == {} and sourceConfig != []:
-        eaters = registry.getRegistry().getComponent(
-            conf.get('type')).getEaters()
-        eatersDict = {}
-        eatersTuple = [(None, parseFeedId(s)) for s in sourceConfig]
-        eatersDict = buildEatersDict(eatersTuple, eaters)
-        conf['eater'] =  eatersDict
-
-    if sourceConfig:
-        sources = []
-        for s in sourceConfig:
-            sources.append(parseFeedId(s))
-        conf['source'] = sources
-
-def upgradeAliases(conf):
-    eaters = dict(conf.get('eater', {})) # a copy
-    concat = lambda lists: reduce(list.__add__, lists, [])
-    if not reduce(lambda x,y: y and isinstance(x, tuple),
-                  concat(eaters.values()),
-                  True):
-        for eater in eaters:
-            aliases = []
-            feeders = eaters[eater]
-            for i in range(len(feeders)):
-                val = feeders[i]
-                if isinstance(val, tuple):
-                    feedId, alias = val
-                    aliases.append(val[1])
-                else:
-                    feedId = val
-                    alias = eater
-                    while alias in aliases:
-                        log.warning('config', "Duplicate alias %s for "
-                                    "eater %s, uniquifying", alias, eater)
-                        alias += '-bis'
-                    aliases.append(alias)
-                    feeders[i] = (feedId, val)
-        conf['eater'] = eaters
-
-UPGRADERS = [upgradeEaters, upgradeAliases]
-CURRENT_VERSION = len(UPGRADERS)
-
-def buildEatersDict(eatersList, eaterDefs):
-    """Build a eaters dict suitable for forming part of a component
-    config.
-
-    @param eatersList: List of eaters. For example,
-                       [('default', 'othercomp:feeder', 'foo')] says
-                       that our eater 'default' will be fed by the feed
-                       identified by the feedId 'othercomp:feeder', and
-                       that it has the alias 'foo'. Alias is optional.
-    @type  eatersList: List of (eaterName, feedId, eaterAlias?)
-    @param  eaterDefs: The set of allowed and required eaters
-    @type   eaterDefs: List of
-                       L{flumotion.common.registry.RegistryEntryEater}
-    @returns: Dict of eaterName => [(feedId, eaterAlias)]
-    """
-    def parseEaterTuple(tup):
-        def parse(eaterName, feedId, eaterAlias=None):
-            if eaterAlias is None:
-                eaterAlias = eaterName
-            return (eaterName, feedId, eaterAlias)
-        return parse(*tup)
-
-    eaters = {}
-    for eater, feedId, alias in [parseEaterTuple(t) for t in eatersList]:
-        if eater is None:
-            if not eaterDefs:
-                raise ConfigError("Feed %r cannot be connected, component has "
-                    "no eaters" % (feedId,))
-            # cope with old <source> entries
-            eater = eaterDefs[0].getName()
-        if alias is None:
-            alias = eater
-        feeders = eaters.get(eater, [])
-        if feedId in feeders:
-            raise ConfigError("Already have a feedId %s eating "
-                              "from %s", feedId, eater)
-        while alias in [a for f, a in feeders]:
-            log.warning('config', "Duplicate alias %s for eater %s, "
-                        "uniquifying", alias, eater)
-            alias += '-bis'
-
-        feeders.append((feedId, alias))
-        eaters[eater] = feeders
-    for e in eaterDefs:
-        eater = e.getName()
-        if e.getRequired() and not eater in eaters:
-            raise ConfigError("Component wants to eat on %s,"
-                              " but no feeders specified."
-                              % (e.getName(),))
-        if not e.getMultiple() and len(eaters.get(eater, [])) > 1:
-            raise ConfigError("Component does not support multiple "
-                              "sources feeding %s (%r)"
-                              % (eater, eaters[eater]))
-    aliases = reduce(list.__add__,
-                     [[x[1] for x in tups] for tups in eaters.values()],
-                     [])
-    # FIXME: Python 2.3 has no sets
-    # if len(aliases) != len(set(aliases):
-    while aliases:
-        alias = aliases.pop()
-        if alias in aliases:
-            raise ConfigError("Duplicate alias: %s" % alias)
-
-    return eaters
-
 def parsePropertyValue(propName, type, value):
     # XXX: We might end up calling float(), which breaks
     #      when using LC_NUMERIC when it is not C -- only in python
@@ -285,91 +166,6 @@
         ret[plug.socket].append(plug.config)
     return ret
 
-def buildVirtualFeeds(feedPairs, feeders):
-    """Build a virtual feeds dict suitable for forming part of a
-    component config.
-
-    @param feedPairs: List of virtual feeds, as name-feederName pairs. For
-                      example, [('bar:baz', 'qux')] defines one
-                      virtual feed 'bar:baz', which is provided by
-                      the component's 'qux' feed.
-    @type  feedPairs: List of (feedId, feedName) -- both strings.
-    @param feeders: The feeders exported by this component, from the
-                    registry.
-    @type  feeders: List of str.
-    """
-    ret = {}
-    for virtual, real in feedPairs:
-        if real not in feeders:
-            raise ConfigError('virtual feed maps to unknown feeder: '
-                              '%s -> %s' % (virtual, real))
-        try:
-            common.parseFeedId(virtual)
-        except:
-            raise ConfigError('virtual feed name not a valid feedId: %s'
-                              % (virtual,))
-        ret[virtual] = real
-    return ret
-
-def dictDiff(old, new, onlyOld=None, onlyNew=None, diff=None,
-             keyBase=None):
-    """Compute the difference between two config dicts.
-
-    @returns: 3 tuple: (onlyOld, onlyNew, diff) where:
-              onlyOld is a list of (key, value), representing key-value
-              pairs that are only in old;
-              onlyNew is a list of (key, value), representing key-value
-              pairs that are only in new;
-              diff is a list of (key, oldValue, newValue), representing
-              keys with different values in old and new; and
-              key is a tuple of strings representing the recursive key
-              to get to a value. For example, ('foo', 'bar') represents
-              the value d['foo']['bar'] on a dict d.
-    """
-    # key := tuple of strings
-
-    if onlyOld is None:
-        onlyOld = [] # key, value
-        onlyNew = [] # key, value
-        diff = [] # key, oldvalue, newvalue
-        keyBase = ()
-
-    for k in old:
-        key = (keyBase + (k,))
-        if k not in new:
-            onlyOld.append((key, old[k]))
-        elif old[k] != new[k]:
-            if isinstance(old[k], dict) and isinstance(new[k], dict):
-                dictDiff(old[k], new[k], onlyOld, onlyNew, diff, key)
-            else:
-                diff.append((key, old[k], new[k]))
-
-    for k in new:
-        key = (keyBase + (k,))
-        if k not in old:
-            onlyNew.append((key, new[k]))
-
-    return onlyOld, onlyNew, diff
-
-def dictDiffMessageString((old, new, diff), oldLabel='old',
-                          newLabel='new'):
-    def ref(label, k):
-        return "%s%s: '%s'" % (label,
-                               ''.join(["[%r]" % (subk,)
-                                        for subk in k[:-1]]),
-                               k[-1])
-
-    out = []
-    for k, v in old:
-        out.append('Only in %s = %r' % (ref(oldLabel, k), v))
-    for k, v in new:
-        out.append('Only in %s = %r' % (ref(newLabel, k), v))
-    for k, oldv, newv in diff:
-        out.append('Value mismatch:')
-        out.append('    %s = %r' % (ref(oldLabel, k), oldv))
-        out.append('    %s = %r' % (ref(newLabel, k), newv))
-    return '\n'.join(out)
-
 class ConfigEntryPlug(log.Loggable):
     "I represent a <plug> entry in a planet config file"
     def __init__(self, type, propertyList):
@@ -388,137 +184,6 @@
                        'function-name': defs.entry.getFunction(),
                        'properties': self.properties}
 
-class ConfigEntryComponent(log.Loggable):
-    "I represent a <component> entry in a planet config file"
-    nice = 0
-    logCategory = 'config'
-
-    __pychecker__ = 'maxargs=13'
-
-    def __init__(self, name, parent, type, label, propertyList, plugList,
-                 worker, eatersList, isClockMaster, project, version,
-                 virtualFeeds=None):
-        self.name = name
-        self.parent = parent
-        self.type = type
-        self.label = label
-        self.worker = worker
-        self.defs = registry.getRegistry().getComponent(self.type)
-        try:
-            self.config = self._buildConfig(propertyList, plugList,
-                                            eatersList, isClockMaster,
-                                            project, version,
-                                            virtualFeeds)
-        except ConfigError, e:
-            # reuse the original exception?
-            e.args = ("While parsing component %s: %s"
-                      % (name, log.getExceptionMessage(e)),)
-            raise e
-
-    def _buildVersionTuple(self, version):
-        if version is None:
-            return configure.versionTuple
-        elif isinstance(version, tuple):
-            assert len(version) == 4
-            return version
-        elif isinstance(version, str):
-            try:
-                def parse(maj, min, mic, nan=0):
-                    return maj, min, mic, nan
-                return parse(*map(int, version.split('.')))
-            except:
-                raise ConfigError("<component> version not "
-                                  "parseable")
-        raise ConfigError("<component> version not parseable")
-
-    def _buildConfig(self, propertyList, plugsList, eatersList,
-                     isClockMaster, project, version, virtualFeeds):
-        """
-        Build a component configuration dictionary.
-        """
-        # clock-master should be either an avatar id or None.
-        # It can temporarily be set to True, and the flow parsing
-        # code will change it to the avatar id or None.
-        config = {'name': self.name,
-                  'parent': self.parent,
-                  'type': self.type,
-                  'config-version': CURRENT_VERSION,
-                  'avatarId': common.componentId(self.parent, self.name),
-                  'project': project or 'flumotion',
-                  'version': self._buildVersionTuple(version),
-                  'clock-master': isClockMaster or None,
-                  'feed': self.defs.getFeeders(),
-                  'properties': buildPropertyDict(propertyList,
-                                                  self.defs.getProperties()),
-                  'plugs': buildPlugsSet(plugsList,
-                                         self.defs.getSockets()),
-                  'eater': buildEatersDict(eatersList,
-                                           self.defs.getEaters()),
-                  'source': [tup[1] for tup in eatersList],
-                  'virtual-feeds': buildVirtualFeeds(virtualFeeds or [],
-                                                     self.defs.getFeeders())}
-
-        if self.label:
-            # only add a label attribute if it was specified
-            config['label'] = self.label
-
-        if not config['source']:
-            # preserve old behavior
-            del config['source']
-        # FIXME: verify that config['project'] matches the defs
-
-        return config
-
-    def getType(self):
-        return self.type
-
-    def getLabel(self):
-        return self.label
-
-    def getName(self):
-        return self.name
-
-    def getParent(self):
-        return self.parent
-
-    def getConfigDict(self):
-        return self.config
-
-    def getWorker(self):
-        return self.worker
-
-class ConfigEntryFlow:
-    "I represent a <flow> entry in a planet config file"
-    def __init__(self, name, components):
-        self.name = name
-        self.components = {}
-        for c in components:
-            if c.name in self.components:
-                raise ConfigError('flow %s already has component named %s'
-                                  % (name, c.name))
-            self.components[c.name] = c
-
-class ConfigEntryManager:
-    "I represent a <manager> entry in a planet config file"
-    def __init__(self, name, host, port, transport, certificate, bouncer,
-            fludebug, plugs):
-        self.name = name
-        self.host = host
-        self.port = port
-        self.transport = transport
-        self.certificate = certificate
-        self.bouncer = bouncer
-        self.fludebug = fludebug
-        self.plugs = plugs
-
-class ConfigEntryAtmosphere:
-    "I represent a <atmosphere> entry in a planet config file"
-    def __init__(self):
-        self.components = {}
-
-    def __len__(self):
-        return len(self.components)
-
 class BaseConfigParser(fxml.Parser):
     parserError = ConfigError
 
@@ -574,100 +239,6 @@
         self.parseFromTable(node, parsers)
         return plugs
 
-    def _parseFeedId(self, feedId):
-        if feedId.find(':') == -1:
-            return "%s:default" % feedId
-        else:
-            return feedId
-
-    def parseComponent(self, node, parent, isFeedComponent,
-                       needsWorker):
-        """
-        Parse a <component></component> block.
-
-        @rtype: L{ConfigEntryComponent}
-        """
-        # <component name="..." type="..." label="..."? worker="..."?
-        #            project="..."? version="..."?>
-        #   <source>...</source>*
-        #   <eater name="...">...</eater>*
-        #   <property name="name">value</property>*
-        #   <clock-master>...</clock-master>?
-        #   <plugs>...</plugs>*
-        #   <virtual-feed name="foo" real="bar"/>*
-        # </component>
-
-        attrs = self.parseAttributes(node, ('name', 'type'),
-                ('label', 'worker', 'project', 'version',))
-        name, type, label, worker, project, version = attrs
-        if needsWorker and not worker:
-            raise ConfigError('component %s does not specify the worker '
-                              'that it is to run on' % (name,))
-        elif worker and not needsWorker:
-            raise ConfigError('component %s specifies a worker to run '
-                              'on, but does not need a worker' % (name,))
-
-        properties = []
-        plugs = []
-        eaters = []
-        clockmasters = []
-        sources = []
-        virtual_feeds = []
-
-        def parseBool(node):
-            return self.parseTextNode(node, common.strToBool)
-        parsers = {'property': (self._parseProperty, properties.append),
-                   'compound-property': (self._parseCompoundProperty,
-                                         properties.append),
-                   'plugs': (self.parsePlugs, plugs.extend)}
-
-        if isFeedComponent:
-            parsers.update({'eater': (self._parseEater, eaters.extend),
-                            'clock-master': (parseBool, clockmasters.append),
-                            'source': (self._parseSource, sources.append),
-                            'virtual-feed': (self._parseVirtualFeed,
-                                             virtual_feeds.append)})
-
-        self.parseFromTable(node, parsers)
-
-        if len(clockmasters) == 0:
-            isClockMaster = None
-        elif len(clockmasters) == 1:
-            isClockMaster = clockmasters[0]
-        else:
-            raise ConfigError("Only one <clock-master> node allowed")
-
-        for feedId in sources:
-            # map old <source> nodes to new <eater> nodes
-            eaters.append((None, feedId))
-
-        return ConfigEntryComponent(name, parent, type, label, properties,
-                                    plugs, worker, eaters,
-                                    isClockMaster, project, version,
-                                    virtual_feeds)
-
-    def _parseSource(self, node):
-        return self._parseFeedId(self.parseTextNode(node))
-
-    def _parseFeed(self, node):
-        alias, = self.parseAttributes(node, (), ('alias',))
-        feedId = self._parseFeedId(self.parseTextNode(node))
-        return feedId, alias
-
-    def _parseEater(self, node):
-        # <eater name="eater-name">
-        #   <feed alias="foo"?>feeding-component:feed-name</feed>*
-        # </eater>
-        name, = self.parseAttributes(node, ('name',))
-        feeds = []
-        parsers = {'feed': (self._parseFeed, feeds.append)}
-        self.parseFromTable(node, parsers)
-        if len(feeds) == 0:
-            # we have an eater node with no feeds
-            raise ConfigError(
-                "Eater node %s with no <feed> nodes, is not allowed" % name)
-        return [(name, feedId, alias) for feedId, alias in feeds]
-
     def _parseProperty(self, node):
         name, = self.parseAttributes(node, ('name',))
         return name, self.parseTextNode(node, lambda x: x)
@@ -685,349 +256,3 @@
         self.parseFromTable(node, parsers)
         return name, properties
 
-    def _parseVirtualFeed(self, node):
-        #  <virtual-feed name="foo" real="bar"/>
-        name, real = self.parseAttributes(node, ('name', 'real'))
-        # assert no content
-        self.parseFromTable(node, {})
-        return name, real
-
-# FIXME: rename to PlanetConfigParser or something (should include the
-# word 'planet' in the name)
-class FlumotionConfigXML(BaseConfigParser):
-    """
-    I represent a planet configuration file for Flumotion.
-
-    @ivar atmosphere: A L{ConfigEntryAtmosphere}, filled in when parse() is
-                      called.
-    @ivar flows:      A list of L{ConfigEntryFlow}, filled in when parse() is
-                      called.
-    """
-    logCategory = 'config'
-
-    def __init__(self, file):
-        BaseConfigParser.__init__(self, file)
-
-        self.flows = []
-        self.atmosphere = ConfigEntryAtmosphere()
-
-    def parse(self):
-        # <planet>
-        #     <manager>?
-        #     <atmosphere>*
-        #     <flow>*
-        # </planet>
-        root = self.doc.documentElement
-        if root.nodeName != 'planet':
-            raise ConfigError("unexpected root node': %s" % root.nodeName)
-
-        parsers = {'atmosphere': (self._parseAtmosphere,
-                                  self.atmosphere.components.update),
-                   'flow': (self._parseFlow,
-                            self.flows.append),
-                   'manager': (_ignore, _ignore)}
-        self.parseFromTable(root, parsers)
-        self.doc.unlink()
-        self.doc = None
-        
-    def _parseAtmosphere(self, node):
-        # <atmosphere>
-        #   <component>
-        #   ...
-        # </atmosphere>
-        ret = {}
-        def parseComponent(node):
-            return self.parseComponent(node, 'atmosphere', False, True)
-        def gotComponent(comp):
-            ret[comp.name] = comp
-        parsers = {'component': (parseComponent, gotComponent)}
-        self.parseFromTable(node, parsers)
-        return ret
-
-    def _parseFlow(self, node):
-        # <flow name="...">
-        #   <component>
-        #   ...
-        # </flow>
-        # "name" cannot be atmosphere or manager
-        name, = self.parseAttributes(node, ('name',))
-        if name == 'atmosphere':
-            raise ConfigError("<flow> cannot have 'atmosphere' as name")
-        if name == 'manager':
-            raise ConfigError("<flow> cannot have 'manager' as name")
-
-        components = []
-        def parseComponent(node):
-            return self.parseComponent(node, name, True, True)
-        parsers = {'component': (parseComponent, components.append)}
-        self.parseFromTable(node, parsers)
-
-        # handle master clock selection; probably should be done in the
-        # manager in persistent "flow" objects rather than here in the
-        # config
-        masters = [x for x in components if x.config['clock-master']]
-        if len(masters) > 1:
-            raise ConfigError("Multiple clock masters in flow %s: %r"
-                              % (name, masters))
-
-        need_sync = [(x.defs.getClockPriority(), x) for x in components
-                     if x.defs.getNeedsSynchronization()]
-        need_sync.sort()
-        need_sync = [x[1] for x in need_sync]
-
-        if need_sync:
-            if masters:
-                master = masters[0]
-            else:
-                master = need_sync[-1]
-
-            masterAvatarId = master.config['avatarId']
-            self.info("Setting %s as clock master" % masterAvatarId)
-
-            for c in need_sync:
-                c.config['clock-master'] = masterAvatarId
-        elif masters:
-            self.info('master clock specified, but no synchronization '
-                      'necessary -- ignoring')
-            masters[0].config['clock-master'] = None
-
-        return ConfigEntryFlow(name, components)
-
-    # FIXME: remove, this is only used by the tests
-    def getComponentEntries(self):
-        """
-        Get all component entries from both atmosphere and all flows
-        from the configuration.
-
-        @rtype: dictionary of /parent/name -> L{ConfigEntryComponent}
-        """
-        entries = {}
-        if self.atmosphere and self.atmosphere.components:
-            for c in self.atmosphere.components.values():
-                path = common.componentId('atmosphere', c.name)
-                entries[path] = c
-
-        for flowEntry in self.flows:
-            for c in flowEntry.components.values():
-                path = common.componentId(c.parent, c.name)
-                entries[path] = c
-
-        return entries
-
-# FIXME: manager config and flow configs are currently conflated in the
-# planet config files; need to separate.
-class ManagerConfigParser(BaseConfigParser):
-    """
-    I parse manager configuration out of a planet configuration file.
-
-    @ivar manager:    A L{ConfigEntryManager} containing options for the manager
-                      section, filled in at construction time.
-    """
-    logCategory = 'config'
-
-    MANAGER_SOCKETS = \
-        ['flumotion.component.plugs.adminaction.AdminAction',
-         'flumotion.component.plugs.lifecycle.ManagerLifecycle',
-         'flumotion.component.plugs.identity.IdentityProvider']
-
-    def __init__(self, file):
-        BaseConfigParser.__init__(self, file)
-
-        # the base config: host, port, etc
-        self.manager = None
-
-        # the bouncer ConfigEntryComponent
-        self.bouncer = None
-
-        self.plugs = {}
-        for socket in self.MANAGER_SOCKETS:
-            self.plugs[socket] = []
-
-        self._parseParameters()
-
-    def _parseParameters(self):
-        root = self.doc.documentElement
-        if not root.nodeName == 'planet':
-            raise ConfigError("unexpected root node': %s" % root.nodeName)
-
-        parsers = {'atmosphere': (_ignore, _ignore),
-                   'flow': (_ignore, _ignore),
-                   'manager': (lambda n: self._parseManagerWithoutRegistry(n),
-                               lambda v: setattr(self, 'manager', v))}
-        self.parseFromTable(root, parsers)
-
-    def _parseManagerWithoutRegistry(self, node):
-        # We parse without asking for a registry so the registry doesn't
-        # verify before knowing the debug level
-        name, = self.parseAttributes(node, (), ('name',))
-        ret = ConfigEntryManager(name, None, None, None, None, None,
-                                 None, self.plugs)
-
-        def simpleparse(proc):
-            return lambda node: self.parseTextNode(node, proc)
-        def recordval(k):
-            def record(v):
-                if getattr(ret, k):
-                    raise ConfigError('duplicate %s: %s'
-                                      % (k, getattr(ret, k)))
-                setattr(ret, k, v)
-            return record
-        def enum(*allowed):
-            def eparse(v):
-                v = str(v)
-                if v not in allowed:
-                    raise ConfigError('unknown value %s (should be '
-                                      'one of %r)' % (v, allowed))
-                return v
-            return eparse
-
-        parsers = {'host': (simpleparse(str), recordval('host')),
-                   'port': (simpleparse(int), recordval('port')),
-                   'transport': (simpleparse(enum('tcp', 'ssl')),
-                                 recordval('transport')),
-                   'certificate': (simpleparse(str), recordval('certificate')),
-                   'component': (_ignore, _ignore),
-                   'plugs': (_ignore, _ignore),
-                   'debug': (simpleparse(str), recordval('fludebug'))}
-        self.parseFromTable(node, parsers)
-        return ret
-
-    def _parseManagerWithRegistry(self, node):
-        def parsecomponent(node):
-            return self.parseComponent(node, 'manager', False, False)
-        def gotcomponent(val):
-            if self.bouncer is not None:
-                raise ConfigError('can only have one bouncer '
-                                  '(%s is superfluous)' % val.name)
-            # FIXME: assert that it is a bouncer !
-            self.bouncer = val
-        def parseplugs(node):
-            return buildPlugsSet(self.parsePlugs(node),
-                                 self.MANAGER_SOCKETS)
-        def gotplugs(newplugs):
-            for socket in self.plugs:
-                self.plugs[socket].extend(newplugs[socket])
-
-        parsers = {'host': (_ignore, _ignore),
-                   'port': (_ignore, _ignore),
-                   'transport': (_ignore, _ignore),
-                   'certificate': (_ignore, _ignore),
-                   'component': (parsecomponent, gotcomponent),
-                   'plugs': (parseplugs, gotplugs),
-                   'debug': (_ignore, _ignore)}
-        self.parseFromTable(node, parsers)
-
-    def parseBouncerAndPlugs(self):
-        # <planet>
-        #     <manager>?
-        #     <atmosphere>*
-        #     <flow>*
-        # </planet>
-        root = self.doc.documentElement
-        if not root.nodeName == 'planet':
-            raise ConfigError("unexpected root node': %s" % root.nodeName)
-
-        parsers = {'atmosphere': (_ignore, _ignore),
-                   'flow': (_ignore, _ignore),
-                   'manager': (self._parseManagerWithRegistry, _ignore)}
-        self.parseFromTable(root, parsers)
-
-    def unlink(self):
-        self.doc.unlink()
-        self.doc = None
-
-class AdminConfigParser(BaseConfigParser):
-    """
-    Admin configuration file parser.
-    """
-    logCategory = 'config'
-
-    def __init__(self, sockets, file):
-        """
-        @param file: The file to parse, either as an open file object,
-        or as the name of a file to open.
-        @type  file: str or file.
-        """
-        self.plugs = {}
-        for socket in sockets:
-            self.plugs[socket] = []
-
-        # will start the parse via self.add()
-        BaseConfigParser.__init__(self, file)
-
-    def _parse(self):
-        # <admin>
-        #   <plugs>
-        root = self.doc.documentElement
-        if not root.nodeName == 'admin':
-            raise ConfigError("unexpected root node': %s" % root.nodeName)
-
-        def parseplugs(node):
-            return buildPlugsSet(self.parsePlugs(node),
-                                 self.plugs.keys())
-        def addplugs(plugs):
-            for socket in plugs:
-                self.plugs[socket].extend(plugs[socket])
-        parsers = {'plugs': (parseplugs, addplugs)}
-
-        self.parseFromTable(root, parsers)
-        self.doc.unlink()
-        self.doc = None
-
-    def add(self, file):
-        """
-        @param file: The file to parse, either as an open file object,
-        or as the name of a file to open.
-        @type  file: str or file.
-        """
-        BaseConfigParser.add(self, file)
-        self._parse()
-
-def exportPlanetXml(p):
-    from flumotion.common.fxml import SXML
-    X = SXML()
-
-    def serialise(propVal):
-        if isinstance(propVal, tuple): # fractions are our only tuple type
-            return "%d/%d" % propVal
-        return propVal
-
-    def component(c):
-        concat = lambda lists: reduce(list.__add__, lists, [])
-        C = c.get('config')
-        return ([X.component(name=c.get('name'),
-                             type=c.get('type'),
-                             label=C.get('label', c.get('name')),
-                             worker=c.get('workerRequested'),
-                             project=C['project'],
-                             version=common.versionTupleToString(C['version']))]
-                + [[X.eater(name=name)]
-                   + [[X.feed(alias=alias), feedId]
-                      for feedId, alias in feeders]
-                   for name, feeders in  C['eater'].items()]
-                + [[X.property(name=name), serialise(value)]
-                   for name, value in C['properties'].items()]
-                + [[X.clock_master(),
-                    C['clock-master'] == C['avatarId'] and 'true' or 'false']]
-                + [[X.plugs()]
-                   + concat([[[X.plug(socket=socket, type=plug['type'])]
-                              + [[X.property(name=name), value]
-                                 for name, value in plug['properties'].items()]
-                              for plug in plugs]
-                             for socket, plugs in C['plugs'].items()])]
-                + [[X.virtual_feed(name=name, real=real)]
-                   for name, real in C['virtual-feeds'].items()])
-
-    def flow(f):
-        return ([X.flow(name=f.get('name'))]
-                 + [component(c) for c in f.get('components')])
-
-    def atmosphere(a):
-        return ([X.atmosphere()]
-                + [component(c) for c in a.get('components')])
-
-    def planet(p):
-        return ([X.planet(name=p.get('name')),
-                 atmosphere(p.get('atmosphere'))]
-                + [flow(f) for f in p.get('flows')])
-    return fxml.sxml2unicode(planet(p))

Modified: flumotion/trunk/flumotion/common/testsuite.py
==============================================================================
--- flumotion/trunk/flumotion/common/testsuite.py	(original)
+++ flumotion/trunk/flumotion/common/testsuite.py	Fri Dec 28 13:04:37 2007
@@ -179,8 +179,8 @@
 class TestCaseWithManager(TestCase):
     def setUp(self):
         from flumotion.twisted import pb
-        from flumotion.common import config, server, connection
-        from flumotion.manager import manager
+        from flumotion.common import server, connection
+        from flumotion.manager import manager, config
         from StringIO import StringIO
 
         managerConf = """

Modified: flumotion/trunk/flumotion/launch/parse.py
==============================================================================
--- flumotion/trunk/flumotion/launch/parse.py	(original)
+++ flumotion/trunk/flumotion/launch/parse.py	Fri Dec 28 13:04:37 2007
@@ -30,7 +30,8 @@
 import copy
 import sys
 
-from flumotion.common import log, config, common, dag, registry
+from flumotion.common import log, common, dag, registry
+from flumotion.manager import config
 
 __all__ = ['parse_args']
 

Modified: flumotion/trunk/flumotion/manager/Makefile.am
==============================================================================
--- flumotion/trunk/flumotion/manager/Makefile.am	(original)
+++ flumotion/trunk/flumotion/manager/Makefile.am	Fri Dec 28 13:04:37 2007
@@ -7,6 +7,7 @@
 	admin.py	\
 	base.py		\
 	component.py	\
+	config.py   \
 	main.py		\
 	manager.py	\
 	worker.py

Modified: flumotion/trunk/flumotion/manager/component.py
==============================================================================
--- flumotion/trunk/flumotion/manager/component.py	(original)
+++ flumotion/trunk/flumotion/manager/component.py	Fri Dec 28 13:04:37 2007
@@ -36,8 +36,8 @@
 from zope.interface import implements
 
 from flumotion.configure import configure
-from flumotion.manager import base
-from flumotion.common import errors, interfaces, keycards, log, config, planet
+from flumotion.manager import base, config
+from flumotion.common import errors, interfaces, keycards, log, planet
 from flumotion.common import messages, common
 from flumotion.twisted import flavors
 from flumotion.common.planet import moods

Added: flumotion/trunk/flumotion/manager/config.py
==============================================================================
--- (empty file)
+++ flumotion/trunk/flumotion/manager/config.py	Fri Dec 28 13:04:37 2007
@@ -0,0 +1,777 @@
+# -*- Mode: Python; test-case-name: flumotion.test.test_config -*-
+# vi:si:et:sw=4:sts=4:ts=4
+#
+# Flumotion - a streaming media server
+# Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com).
+# All rights reserved.
+
+# This file may be distributed and/or modified under the terms of
+# the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+# This file is distributed without any warranty; without even the implied
+# warranty of merchantability or fitness for a particular purpose.
+# See "LICENSE.GPL" in the source distribution for more information.
+
+# Licensees having purchased or holding a valid Flumotion Advanced
+# Streaming Server license may use this file in accordance with the
+# Flumotion Advanced Streaming Server Commercial License Agreement.
+# See "LICENSE.Flumotion" in the source distribution for more information.
+
+# Headers in this file shall remain intact.
+
+__version__ = "$Rev: 6049 $"
+
+"""
+parsing of manager configuration files
+"""
+
+import os
+from xml.dom import minidom, Node
+from xml.parsers import expat
+
+from twisted.python import reflect
+
+from flumotion.common import log, errors, common, registry, fxml
+from flumotion.common import config as fluconfig
+from flumotion.configure import configure
+
+def _ignore(*args):
+    pass
+
+def upgradeEaters(conf):
+    def parseFeedId(feedId):
+        if feedId.find(':') == -1:
+            return "%s:default" % feedId
+        else:
+            return feedId
+
+    eaterConfig = conf.get('eater', {})
+    sourceConfig = conf.get('source', [])
+    if eaterConfig == {} and sourceConfig != []:
+        eaters = registry.getRegistry().getComponent(
+            conf.get('type')).getEaters()
+        eatersDict = {}
+        eatersTuple = [(None, parseFeedId(s)) for s in sourceConfig]
+        eatersDict = buildEatersDict(eatersTuple, eaters)
+        conf['eater'] =  eatersDict
+
+    if sourceConfig:
+        sources = []
+        for s in sourceConfig:
+            sources.append(parseFeedId(s))
+        conf['source'] = sources
+
+def upgradeAliases(conf):
+    eaters = dict(conf.get('eater', {})) # a copy
+    concat = lambda lists: reduce(list.__add__, lists, [])
+    if not reduce(lambda x,y: y and isinstance(x, tuple),
+                  concat(eaters.values()),
+                  True):
+        for eater in eaters:
+            aliases = []
+            feeders = eaters[eater]
+            for i in range(len(feeders)):
+                val = feeders[i]
+                if isinstance(val, tuple):
+                    feedId, alias = val
+                    aliases.append(val[1])
+                else:
+                    feedId = val
+                    alias = eater
+                    while alias in aliases:
+                        log.warning('config', "Duplicate alias %s for "
+                                    "eater %s, uniquifying", alias, eater)
+                        alias += '-bis'
+                    aliases.append(alias)
+                    feeders[i] = (feedId, val)
+        conf['eater'] = eaters
+
+UPGRADERS = [upgradeEaters, upgradeAliases]
+CURRENT_VERSION = len(UPGRADERS)
+
+def buildEatersDict(eatersList, eaterDefs):
+    """Build a eaters dict suitable for forming part of a component
+    config.
+
+    @param eatersList: List of eaters. For example,
+                       [('default', 'othercomp:feeder', 'foo')] says
+                       that our eater 'default' will be fed by the feed
+                       identified by the feedId 'othercomp:feeder', and
+                       that it has the alias 'foo'. Alias is optional.
+    @type  eatersList: List of (eaterName, feedId, eaterAlias?)
+    @param  eaterDefs: The set of allowed and required eaters
+    @type   eaterDefs: List of
+                       L{flumotion.common.registry.RegistryEntryEater}
+    @returns: Dict of eaterName => [(feedId, eaterAlias)]
+    """
+    def parseEaterTuple(tup):
+        def parse(eaterName, feedId, eaterAlias=None):
+            if eaterAlias is None:
+                eaterAlias = eaterName
+            return (eaterName, feedId, eaterAlias)
+        return parse(*tup)
+
+    eaters = {}
+    for eater, feedId, alias in [parseEaterTuple(t) for t in eatersList]:
+        if eater is None:
+            if not eaterDefs:
+                raise errors.ConfigError(
+                    "Feed %r cannot be connected, component has no eaters" % 
+                    (feedId,))
+            # cope with old <source> entries
+            eater = eaterDefs[0].getName()
+        if alias is None:
+            alias = eater
+        feeders = eaters.get(eater, [])
+        if feedId in feeders:
+            raise errors.ConfigError("Already have a feedId %s eating from %s" %
+                (feedId, eater))
+        while alias in [a for f, a in feeders]:
+            log.warning('config', "Duplicate alias %s for eater %s, "
+                        "uniquifying", alias, eater)
+            alias += '-bis'
+
+        feeders.append((feedId, alias))
+        eaters[eater] = feeders
+    for e in eaterDefs:
+        eater = e.getName()
+        if e.getRequired() and not eater in eaters:
+            raise errors.ConfigError("Component wants to eat on %s,"
+                              " but no feeders specified."
+                              % (e.getName(),))
+        if not e.getMultiple() and len(eaters.get(eater, [])) > 1:
+            raise errors.ConfigError("Component does not support multiple "
+                              "sources feeding %s (%r)"
+                              % (eater, eaters[eater]))
+    aliases = reduce(list.__add__,
+                     [[x[1] for x in tups] for tups in eaters.values()],
+                     [])
+    # FIXME: Python 2.3 has no sets
+    # if len(aliases) != len(set(aliases):
+    while aliases:
+        alias = aliases.pop()
+        if alias in aliases:
+            raise errors.ConfigError("Duplicate alias: %s" % (alias,))
+
+    return eaters
+
+
+def buildVirtualFeeds(feedPairs, feeders):
+    """Build a virtual feeds dict suitable for forming part of a
+    component config.
+
+    @param feedPairs: List of virtual feeds, as name-feederName pairs. For
+                      example, [('bar:baz', 'qux')] defines one
+                      virtual feed 'bar:baz', which is provided by
+                      the component's 'qux' feed.
+    @type  feedPairs: List of (feedId, feedName) -- both strings.
+    @param feeders: The feeders exported by this component, from the
+                    registry.
+    @type  feeders: List of str.
+    """
+    ret = {}
+    for virtual, real in feedPairs:
+        if real not in feeders:
+            raise errors.ConfigError('virtual feed maps to unknown feeder: '
+                              '%s -> %s' % (virtual, real))
+        try:
+            common.parseFeedId(virtual)
+        except:
+            raise errors.ConfigError('virtual feed name not a valid feedId: %s'
+                              % (virtual,))
+        ret[virtual] = real
+    return ret
+
+def dictDiff(old, new, onlyOld=None, onlyNew=None, diff=None,
+             keyBase=None):
+    """Compute the difference between two config dicts.
+
+    @returns: 3 tuple: (onlyOld, onlyNew, diff) where:
+              onlyOld is a list of (key, value), representing key-value
+              pairs that are only in old;
+              onlyNew is a list of (key, value), representing key-value
+              pairs that are only in new;
+              diff is a list of (key, oldValue, newValue), representing
+              keys with different values in old and new; and
+              key is a tuple of strings representing the recursive key
+              to get to a value. For example, ('foo', 'bar') represents
+              the value d['foo']['bar'] on a dict d.
+    """
+    # key := tuple of strings
+
+    if onlyOld is None:
+        onlyOld = [] # key, value
+        onlyNew = [] # key, value
+        diff = [] # key, oldvalue, newvalue
+        keyBase = ()
+
+    for k in old:
+        key = (keyBase + (k,))
+        if k not in new:
+            onlyOld.append((key, old[k]))
+        elif old[k] != new[k]:
+            if isinstance(old[k], dict) and isinstance(new[k], dict):
+                dictDiff(old[k], new[k], onlyOld, onlyNew, diff, key)
+            else:
+                diff.append((key, old[k], new[k]))
+
+    for k in new:
+        key = (keyBase + (k,))
+        if k not in old:
+            onlyNew.append((key, new[k]))
+
+    return onlyOld, onlyNew, diff
+
+def dictDiffMessageString((old, new, diff), oldLabel='old',
+                          newLabel='new'):
+    def ref(label, k):
+        return "%s%s: '%s'" % (label,
+                               ''.join(["[%r]" % (subk,)
+                                        for subk in k[:-1]]),
+                               k[-1])
+
+    out = []
+    for k, v in old:
+        out.append('Only in %s = %r' % (ref(oldLabel, k), v))
+    for k, v in new:
+        out.append('Only in %s = %r' % (ref(newLabel, k), v))
+    for k, oldv, newv in diff:
+        out.append('Value mismatch:')
+        out.append('    %s = %r' % (ref(oldLabel, k), oldv))
+        out.append('    %s = %r' % (ref(newLabel, k), newv))
+    return '\n'.join(out)
+
+class ConfigEntryComponent(log.Loggable):
+    "I represent a <component> entry in a planet config file"
+    nice = 0
+    logCategory = 'config'
+
+    __pychecker__ = 'maxargs=13'
+
+    def __init__(self, name, parent, type, label, propertyList, plugList,
+                 worker, eatersList, isClockMaster, project, version,
+                 virtualFeeds=None):
+        self.name = name
+        self.parent = parent
+        self.type = type
+        self.label = label
+        self.worker = worker
+        self.defs = registry.getRegistry().getComponent(self.type)
+        try:
+            self.config = self._buildConfig(propertyList, plugList,
+                                            eatersList, isClockMaster,
+                                            project, version,
+                                            virtualFeeds)
+        except errors.ConfigError, e:
+            # reuse the original exception?
+            e.args = ("While parsing component %s: %s"
+                      % (name, log.getExceptionMessage(e)),)
+            raise
+
+    def _buildVersionTuple(self, version):
+        if version is None:
+            return configure.versionTuple
+        elif isinstance(version, tuple):
+            assert len(version) == 4
+            return version
+        elif isinstance(version, str):
+            try:
+                def parse(maj, min, mic, nan=0):
+                    return maj, min, mic, nan
+                return parse(*map(int, version.split('.')))
+            except:
+                raise errors.ConfigError("<component> version not parseable")
+        raise errors.ConfigError("<component> version not parseable")
+
+    def _buildConfig(self, propertyList, plugsList, eatersList,
+                     isClockMaster, project, version, virtualFeeds):
+        """
+        Build a component configuration dictionary.
+        """
+        # clock-master should be either an avatar id or None.
+        # It can temporarily be set to True, and the flow parsing
+        # code will change it to the avatar id or None.
+        config = {'name': self.name,
+                  'parent': self.parent,
+                  'type': self.type,
+                  'config-version': CURRENT_VERSION,
+                  'avatarId': common.componentId(self.parent, self.name),
+                  'project': project or 'flumotion',
+                  'version': self._buildVersionTuple(version),
+                  'clock-master': isClockMaster or None,
+                  'feed': self.defs.getFeeders(),
+                  'properties': fluconfig.buildPropertyDict(propertyList,
+                                                  self.defs.getProperties()),
+                  'plugs': fluconfig.buildPlugsSet(plugsList,
+                                         self.defs.getSockets()),
+                  'eater': buildEatersDict(eatersList,
+                                           self.defs.getEaters()),
+                  'source': [tup[1] for tup in eatersList],
+                  'virtual-feeds': buildVirtualFeeds(virtualFeeds or [],
+                                                     self.defs.getFeeders())}
+
+        if self.label:
+            # only add a label attribute if it was specified
+            config['label'] = self.label
+
+        if not config['source']:
+            # preserve old behavior
+            del config['source']
+        # FIXME: verify that config['project'] matches the defs
+
+        return config
+
+    def getType(self):
+        return self.type
+
+    def getLabel(self):
+        return self.label
+
+    def getName(self):
+        return self.name
+
+    def getParent(self):
+        return self.parent
+
+    def getConfigDict(self):
+        return self.config
+
+    def getWorker(self):
+        return self.worker
+
+class ConfigEntryFlow:
+    "I represent a <flow> entry in a planet config file"
+    def __init__(self, name, components):
+        self.name = name
+        self.components = {}
+        for c in components:
+            if c.name in self.components:
+                raise errors.ConfigError(
+                    'flow %s already has component named %s' % (name, c.name))
+            self.components[c.name] = c
+
+class ConfigEntryManager:
+    "I represent a <manager> entry in a planet config file"
+    def __init__(self, name, host, port, transport, certificate, bouncer,
+            fludebug, plugs):
+        self.name = name
+        self.host = host
+        self.port = port
+        self.transport = transport
+        self.certificate = certificate
+        self.bouncer = bouncer
+        self.fludebug = fludebug
+        self.plugs = plugs
+
+class ConfigEntryAtmosphere:
+    "I represent a <atmosphere> entry in a planet config file"
+    def __init__(self):
+        self.components = {}
+
+    def __len__(self):
+        return len(self.components)
+
+class FlumotionConfigParser(fluconfig.BaseConfigParser):
+    """
+    This is a base class for parsing planet configuration files (both manager
+    and flow files).
+    """
+    logCategory = 'config'
+
+    def _parseFeedId(self, feedId):
+        if feedId.find(':') == -1:
+            return "%s:default" % feedId
+        else:
+            return feedId
+
+    def _parseVirtualFeed(self, node):
+        #  <virtual-feed name="foo" real="bar"/>
+        name, real = self.parseAttributes(node, ('name', 'real'))
+        # assert no content
+        self.parseFromTable(node, {})
+        return name, real
+
+    def parseComponent(self, node, parent, isFeedComponent,
+                       needsWorker):
+        """
+        Parse a <component></component> block.
+
+        @rtype: L{ConfigEntryComponent}
+        """
+        # <component name="..." type="..." label="..."? worker="..."?
+        #            project="..."? version="..."?>
+        #   <source>...</source>*
+        #   <eater name="...">...</eater>*
+        #   <property name="name">value</property>*
+        #   <clock-master>...</clock-master>?
+        #   <plugs>...</plugs>*
+        #   <virtual-feed name="foo" real="bar"/>*
+        # </component>
+
+        attrs = self.parseAttributes(node, ('name', 'type'),
+                ('label', 'worker', 'project', 'version',))
+        name, type, label, worker, project, version = attrs
+        if needsWorker and not worker:
+            raise errors.ConfigError('component %s does not specify the worker '
+                              'that it is to run on' % (name,))
+        elif worker and not needsWorker:
+            raise errors.ConfigError('component %s specifies a worker to run '
+                              'on, but does not need a worker' % (name,))
+
+        properties = []
+        plugs = []
+        eaters = []
+        clockmasters = []
+        sources = []
+        virtual_feeds = []
+
+        def parseBool(node):
+            return self.parseTextNode(node, common.strToBool)
+        parsers = {'property': (self._parseProperty, properties.append),
+                   'compound-property': (self._parseCompoundProperty,
+                                         properties.append),
+                   'plugs': (self.parsePlugs, plugs.extend)}
+
+        if isFeedComponent:
+            parsers.update({'eater': (self._parseEater, eaters.extend),
+                            'clock-master': (parseBool, clockmasters.append),
+                            'source': (self._parseSource, sources.append),
+                            'virtual-feed': (self._parseVirtualFeed,
+                                             virtual_feeds.append)})
+
+        self.parseFromTable(node, parsers)
+
+        if len(clockmasters) == 0:
+            isClockMaster = None
+        elif len(clockmasters) == 1:
+            isClockMaster = clockmasters[0]
+        else:
+            raise errors.ConfigError("Only one <clock-master> node allowed")
+
+        for feedId in sources:
+            # map old <source> nodes to new <eater> nodes
+            eaters.append((None, feedId))
+
+        return ConfigEntryComponent(name, parent, type, label, properties,
+                                    plugs, worker, eaters,
+                                    isClockMaster, project, version,
+                                    virtual_feeds)
+
+    def _parseSource(self, node):
+        return self._parseFeedId(self.parseTextNode(node))
+
+    def _parseFeed(self, node):
+        alias, = self.parseAttributes(node, (), ('alias',))
+        feedId = self._parseFeedId(self.parseTextNode(node))
+        return feedId, alias
+
+    def _parseEater(self, node):
+        # <eater name="eater-name">
+        #   <feed alias="foo"?>feeding-component:feed-name</feed>*
+        # </eater>
+        name, = self.parseAttributes(node, ('name',))
+        feeds = []
+        parsers = {'feed': (self._parseFeed, feeds.append)}
+        self.parseFromTable(node, parsers)
+        if len(feeds) == 0:
+            # we have an eater node with no feeds
+            raise errors.ConfigError(
+                "Eater node %s with no <feed> nodes, is not allowed" % (name,))
+        return [(name, feedId, alias) for feedId, alias in feeds]
+
+class PlanetConfigParser(FlumotionConfigParser):
+    """
+    I represent a planet configuration file for Flumotion.
+
+    @ivar atmosphere: A L{ConfigEntryAtmosphere}, filled in when parse() is
+                      called.
+    @ivar flows:      A list of L{ConfigEntryFlow}, filled in when parse() is
+                      called.
+    """
+    logCategory = 'config'
+
+    def __init__(self, file):
+        FlumotionConfigParser.__init__(self, file)
+
+        self.flows = []
+        self.atmosphere = ConfigEntryAtmosphere()
+
+    def parse(self):
+        # <planet>
+        #     <manager>?
+        #     <atmosphere>*
+        #     <flow>*
+        # </planet>
+        root = self.doc.documentElement
+        if root.nodeName != 'planet':
+            raise errors.ConfigError("unexpected root node': %s" % 
+                (root.nodeName,))
+
+        parsers = {'atmosphere': (self._parseAtmosphere,
+                                  self.atmosphere.components.update),
+                   'flow': (self._parseFlow,
+                            self.flows.append),
+                   'manager': (_ignore, _ignore)}
+        self.parseFromTable(root, parsers)
+        self.doc.unlink()
+        self.doc = None
+
+        
+    def _parseAtmosphere(self, node):
+        # <atmosphere>
+        #   <component>
+        #   ...
+        # </atmosphere>
+        ret = {}
+        def parseComponent(node):
+            return self.parseComponent(node, 'atmosphere', False, True)
+        def gotComponent(comp):
+            ret[comp.name] = comp
+        parsers = {'component': (parseComponent, gotComponent)}
+        self.parseFromTable(node, parsers)
+        return ret
+
+    def _parseFlow(self, node):
+        # <flow name="...">
+        #   <component>
+        #   ...
+        # </flow>
+        # "name" cannot be atmosphere or manager
+        name, = self.parseAttributes(node, ('name',))
+        if name == 'atmosphere':
+            raise errors.ConfigError("<flow> cannot have 'atmosphere' as name")
+        if name == 'manager':
+            raise errors.ConfigError("<flow> cannot have 'manager' as name")
+
+        components = []
+        def parseComponent(node):
+            return self.parseComponent(node, name, True, True)
+        parsers = {'component': (parseComponent, components.append)}
+        self.parseFromTable(node, parsers)
+
+        # handle master clock selection; probably should be done in the
+        # manager in persistent "flow" objects rather than here in the
+        # config
+        masters = [x for x in components if x.config['clock-master']]
+        if len(masters) > 1:
+            raise errors.ConfigError("Multiple clock masters in flow %s: %r"
+                              % (name, masters))
+
+        need_sync = [(x.defs.getClockPriority(), x) for x in components
+                     if x.defs.getNeedsSynchronization()]
+        need_sync.sort()
+        need_sync = [x[1] for x in need_sync]
+
+        if need_sync:
+            if masters:
+                master = masters[0]
+            else:
+                master = need_sync[-1]
+
+            masterAvatarId = master.config['avatarId']
+            self.info("Setting %s as clock master" % masterAvatarId)
+
+            for c in need_sync:
+                c.config['clock-master'] = masterAvatarId
+        elif masters:
+            self.info('master clock specified, but no synchronization '
+                      'necessary -- ignoring')
+            masters[0].config['clock-master'] = None
+
+        return ConfigEntryFlow(name, components)
+
+    # FIXME: remove, this is only used by the tests
+    def getComponentEntries(self):
+        """
+        Get all component entries from both atmosphere and all flows
+        from the configuration.
+
+        @rtype: dictionary of /parent/name -> L{ConfigEntryComponent}
+        """
+        entries = {}
+        if self.atmosphere and self.atmosphere.components:
+            for c in self.atmosphere.components.values():
+                path = common.componentId('atmosphere', c.name)
+                entries[path] = c
+
+        for flowEntry in self.flows:
+            for c in flowEntry.components.values():
+                path = common.componentId(c.parent, c.name)
+                entries[path] = c
+
+        return entries
+
+
+# FIXME: manager config and flow configs are currently conflated in the
+# planet config files; need to separate.
+class ManagerConfigParser(FlumotionConfigParser):
+    """
+    I parse manager configuration out of a planet configuration file.
+
+    @ivar manager:    A L{ConfigEntryManager} containing options for the manager
+                      section, filled in at construction time.
+    """
+    logCategory = 'config'
+
+    MANAGER_SOCKETS = \
+        ['flumotion.component.plugs.adminaction.AdminAction',
+         'flumotion.component.plugs.lifecycle.ManagerLifecycle',
+         'flumotion.component.plugs.identity.IdentityProvider']
+
+    def __init__(self, file):
+        FlumotionConfigParser.__init__(self, file)
+
+        # the base config: host, port, etc
+        self.manager = None
+
+        # the bouncer ConfigEntryComponent
+        self.bouncer = None
+
+        self.plugs = {}
+        for socket in self.MANAGER_SOCKETS:
+            self.plugs[socket] = []
+
+        self._parseParameters()
+
+    def _parseParameters(self):
+        root = self.doc.documentElement
+        if not root.nodeName == 'planet':
+            raise errors.ConfigError("unexpected root node': %s" % 
+                (root.nodeName,))
+
+        parsers = {'atmosphere': (_ignore, _ignore),
+                   'flow': (_ignore, _ignore),
+                   'manager': (lambda n: self._parseManagerWithoutRegistry(n),
+                               lambda v: setattr(self, 'manager', v))}
+        self.parseFromTable(root, parsers)
+
+    def _parseManagerWithoutRegistry(self, node):
+        # We parse without asking for a registry so the registry doesn't
+        # verify before knowing the debug level
+        name, = self.parseAttributes(node, (), ('name',))
+        ret = ConfigEntryManager(name, None, None, None, None, None,
+                                 None, self.plugs)
+
+        def simpleparse(proc):
+            return lambda node: self.parseTextNode(node, proc)
+        def recordval(k):
+            def record(v):
+                if getattr(ret, k):
+                    raise errors.ConfigError('duplicate %s: %s'
+                                      % (k, getattr(ret, k)))
+                setattr(ret, k, v)
+            return record
+        def enum(*allowed):
+            def eparse(v):
+                v = str(v)
+                if v not in allowed:
+                    raise errors.ConfigError('unknown value %s (should be '
+                                      'one of %r)' % (v, allowed))
+                return v
+            return eparse
+
+        parsers = {'host': (simpleparse(str), recordval('host')),
+                   'port': (simpleparse(int), recordval('port')),
+                   'transport': (simpleparse(enum('tcp', 'ssl')),
+                                 recordval('transport')),
+                   'certificate': (simpleparse(str), recordval('certificate')),
+                   'component': (_ignore, _ignore),
+                   'plugs': (_ignore, _ignore),
+                   'debug': (simpleparse(str), recordval('fludebug'))}
+        self.parseFromTable(node, parsers)
+        return ret
+
+    def _parseManagerWithRegistry(self, node):
+        def parsecomponent(node):
+            return self.parseComponent(node, 'manager', False, False)
+        def gotcomponent(val):
+            if self.bouncer is not None:
+                raise errors.ConfigError('can only have one bouncer '
+                                  '(%s is superfluous)' % (val.name,))
+            # FIXME: assert that it is a bouncer !
+            self.bouncer = val
+        def parseplugs(node):
+            return fluconfig.buildPlugsSet(self.parsePlugs(node),
+                                 self.MANAGER_SOCKETS)
+        def gotplugs(newplugs):
+            for socket in self.plugs:
+                self.plugs[socket].extend(newplugs[socket])
+
+        parsers = {'host': (_ignore, _ignore),
+                   'port': (_ignore, _ignore),
+                   'transport': (_ignore, _ignore),
+                   'certificate': (_ignore, _ignore),
+                   'component': (parsecomponent, gotcomponent),
+                   'plugs': (parseplugs, gotplugs),
+                   'debug': (_ignore, _ignore)}
+        self.parseFromTable(node, parsers)
+
+    def parseBouncerAndPlugs(self):
+        # <planet>
+        #     <manager>?
+        #     <atmosphere>*
+        #     <flow>*
+        # </planet>
+        root = self.doc.documentElement
+        if not root.nodeName == 'planet':
+            raise errors.ConfigError("unexpected root node': %s" % 
+                (root.nodeName,))
+
+        parsers = {'atmosphere': (_ignore, _ignore),
+                   'flow': (_ignore, _ignore),
+                   'manager': (self._parseManagerWithRegistry, _ignore)}
+        self.parseFromTable(root, parsers)
+
+    def unlink(self):
+        self.doc.unlink()
+        self.doc = None
+
+def exportPlanetXml(p):
+    from flumotion.common.fxml import SXML
+    X = SXML()
+
+    def serialise(propVal):
+        if isinstance(propVal, tuple): # fractions are our only tuple type
+            return "%d/%d" % propVal
+        return propVal
+
+    def component(c):
+        concat = lambda lists: reduce(list.__add__, lists, [])
+        C = c.get('config')
+        return ([X.component(name=c.get('name'),
+                             type=c.get('type'),
+                             label=C.get('label', c.get('name')),
+                             worker=c.get('workerRequested'),
+                             project=C['project'],
+                             version=common.versionTupleToString(C['version']))]
+                + [[X.eater(name=name)]
+                   + [[X.feed(alias=alias), feedId]
+                      for feedId, alias in feeders]
+                   for name, feeders in  C['eater'].items()]
+                + [[X.property(name=name), serialise(value)]
+                   for name, value in C['properties'].items()]
+                + [[X.clock_master(),
+                    C['clock-master'] == C['avatarId'] and 'true' or 'false']]
+                + [[X.plugs()]
+                   + concat([[[X.plug(socket=socket, type=plug['type'])]
+                              + [[X.property(name=name), value]
+                                 for name, value in plug['properties'].items()]
+                              for plug in plugs]
+                             for socket, plugs in C['plugs'].items()])]
+                + [[X.virtual_feed(name=name, real=real)]
+                   for name, real in C['virtual-feeds'].items()])
+
+    def flow(f):
+        return ([X.flow(name=f.get('name'))]
+                 + [component(c) for c in f.get('components')])
+
+    def atmosphere(a):
+        return ([X.atmosphere()]
+                + [component(c) for c in a.get('components')])
+
+    def planet(p):
+        return ([X.planet(name=p.get('name')),
+                 atmosphere(p.get('atmosphere'))]
+                + [flow(f) for f in p.get('flows')])
+    return fxml.sxml2unicode(planet(p))
+

Modified: flumotion/trunk/flumotion/manager/main.py
==============================================================================
--- flumotion/trunk/flumotion/manager/main.py	(original)
+++ flumotion/trunk/flumotion/manager/main.py	Fri Dec 28 13:04:37 2007
@@ -30,8 +30,8 @@
 
 from twisted.internet import reactor, error
 
-from flumotion.manager import manager
-from flumotion.common import log, config, common, errors, setup
+from flumotion.manager import manager, config
+from flumotion.common import log, common, errors, setup
 from flumotion.configure import configure
 from flumotion.common import server
 from flumotion.common.options import OptionGroup, OptionParser

Modified: flumotion/trunk/flumotion/manager/manager.py
==============================================================================
--- flumotion/trunk/flumotion/manager/manager.py	(original)
+++ flumotion/trunk/flumotion/manager/manager.py	Fri Dec 28 13:04:37 2007
@@ -41,12 +41,12 @@
 from twisted.cred import portal
 from zope.interface import implements
 
-from flumotion.common import config, errors, interfaces, log, registry
+from flumotion.common import errors, interfaces, log, registry
 from flumotion.common import planet, common, dag, messages, reflectcall, server
 from flumotion.common.identity import RemoteIdentity, LocalIdentity
 from flumotion.common.planet import moods
 from flumotion.configure import configure
-from flumotion.manager import admin, component, worker, base
+from flumotion.manager import admin, component, worker, base, config
 from flumotion.twisted import checkers
 from flumotion.twisted import portal as fportal
 
@@ -524,7 +524,7 @@
             mid += '-%s' % file
         try:
             self.clearMessage(mid)
-            conf = config.FlumotionConfigXML(file)
+            conf = config.PlanetConfigParser(file)
             conf.parse()
             return self._loadComponentConfiguration(conf, identity)
         except errors.ConfigError, e:

Modified: flumotion/trunk/flumotion/test/Makefile.am
==============================================================================
--- flumotion/trunk/flumotion/test/Makefile.am	(original)
+++ flumotion/trunk/flumotion/test/Makefile.am	Fri Dec 28 13:04:37 2007
@@ -15,6 +15,7 @@
 	test.xml				\
 	bouncertest.py				\
 	test_admin_admin.py			\
+	test_admin_config.py			\
 	test_admin_multi.py			\
 	test_bundle.py				\
 	test_checkers.py			\

Added: flumotion/trunk/flumotion/test/test_admin_config.py
==============================================================================
--- (empty file)
+++ flumotion/trunk/flumotion/test/test_admin_config.py	Fri Dec 28 13:04:37 2007
@@ -0,0 +1,80 @@
+# -*- Mode: Python; test-case-name:flumotion.test.test_config -*-
+# vi:si:et:sw=4:sts=4:ts=4
+#
+# Flumotion - a streaming media server
+# Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com).
+# All rights reserved.
+
+# This file may be distributed and/or modified under the terms of
+# the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+# This file is distributed without any warranty; without even the implied
+# warranty of merchantability or fitness for a particular purpose.
+# See "LICENSE.GPL" in the source distribution for more information.
+
+# Licensees having purchased or holding a valid Flumotion Advanced
+# Streaming Server license may use this file in accordance with the
+# Flumotion Advanced Streaming Server Commercial License Agreement.
+# See "LICENSE.Flumotion" in the source distribution for more information.
+
+# Headers in this file shall remain intact.
+
+__version__ = "$Rev: 6049 $"
+
+from StringIO import StringIO
+
+from flumotion.common import errors
+from flumotion.common import testsuite
+from flumotion.admin import config
+
+def AdminConfig(sockets, string):
+    f = StringIO(string)
+    conf = config.AdminConfigParser(sockets, f)
+    f.close()
+    return conf
+
+class AdminConfigTest(testsuite.TestCase):
+    def testMinimal(self):
+        doc = ('<admin>'
+               '<plugs>'
+               '</plugs>'
+               '</admin>')
+        parser = AdminConfig((), doc)
+        self.failUnless(parser.plugs == {}, 'expected empty plugset')
+
+    def testMinimal2(self):
+        doc = ('<admin>'
+               '<plugs>'
+               '</plugs>'
+               '</admin>')
+        parser = AdminConfig((), doc)
+        self.failUnless(parser.plugs == {}, 'expected empty plugset')
+
+    def testMinimal3(self):
+        doc = ('<admin>'
+               '<plugs>'
+               '</plugs>'
+               '</admin>')
+        parser = AdminConfig(('foo.bar',), doc)
+        self.failUnless(parser.plugs == {'foo.bar':[]}, parser.plugs)
+
+    def testUnknownPlug(self):
+        doc = ('<admin>'
+               '<plugs>'
+               '<plug type="plugdoesnotexist" socket="foo.bar">'
+               '</plug>'
+               '</plugs>'
+               '</admin>')
+        self.assertRaises(errors.UnknownPlugError,
+                          lambda: AdminConfig(('foo.bar',), doc))
+
+    def testUnknownSocket(self):
+        doc = ('<admin>'
+               '<plugs>'
+               '<plug type="frobulator" socket="baz">'
+               '</plug>'
+               '</plugs>'
+               '</admin>')
+        self.assertRaises(errors.ConfigError,
+                          lambda: AdminConfig(('foo.bar',), doc))
+

Modified: flumotion/trunk/flumotion/test/test_config.py
==============================================================================
--- flumotion/trunk/flumotion/test/test_config.py	(original)
+++ flumotion/trunk/flumotion/test/test_config.py	Fri Dec 28 13:04:37 2007
@@ -23,8 +23,9 @@
 
 from StringIO import StringIO
 
-from flumotion.common import config, registry, errors
+from flumotion.common import registry, errors
 from flumotion.common import testsuite
+from flumotion.manager import config
 
 regchunk = """
 <registry>
@@ -119,7 +120,7 @@
 reg = registry.getRegistry()
 reg.addFromString(regchunk)
 
-def ConfigXML(string, parser=config.FlumotionConfigXML):
+def ConfigXML(string, parser=config.PlanetConfigParser):
     f = StringIO(string)
     conf = parser(f)
     f.close()
@@ -149,7 +150,7 @@
                      {'default': [('foo:bar', 'default'),
                                   ('baz', 'alias')]})
         assertRaises('test-component-with-multiple-eater',
-                     [], config.ConfigError)
+                     [], errors.ConfigError)
         assertEaters('test-component-with-multiple-eater',
                      [(None, 'foo:bar')],
                      {'default': [('foo:bar', 'default')]})
@@ -161,10 +162,10 @@
 
     def testParseWrongConfig(self):
         conf = ConfigXML('<somethingorother/>')
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
     def testParseWrongSyntax(self):
-        self.assertRaises(config.ConfigError,
+        self.assertRaises(errors.ConfigError,
             ConfigXML, 'planet/>')
 
     def testParseComponentNoWorker(self):
@@ -175,7 +176,7 @@
                  <component name="component-name" type="test-component"/>
                </atmosphere>
              </planet>""")
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
         conf = ConfigXML(
              """
              <planet>
@@ -184,7 +185,7 @@
                             worker=""/>
                </atmosphere>
              </planet>""")
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
     def testParseAtmosphere(self):
         conf = ConfigXML(
@@ -214,7 +215,7 @@
              </planet>"""
         conf = ConfigXML(xml)
         self.failUnless(conf)
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
     def testParseComponent(self):
         conf = ConfigXML(
@@ -382,9 +383,9 @@
         xml = '<planet><bad-node/></planet>'
         conf = ConfigXML(xml)
         self.failUnless(conf)
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
-        self.assertRaises(config.ConfigError, ManagerConfigXML, xml)
+        self.assertRaises(errors.ConfigError, ManagerConfigXML, xml)
 
     def testParseComponentError(self):
         xml = """<planet>
@@ -403,7 +404,7 @@
             </planet>"""
         conf = ConfigXML(xml)
         self.failUnless(conf)
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
         xml = """<planet>
               <flow name="default">
@@ -412,7 +413,7 @@
             </planet>"""
         conf = ConfigXML(xml)
         self.failUnless(conf)
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
         # Specify a source for a component with no eaters
         xml = """<planet>
@@ -424,7 +425,7 @@
             </planet>"""
         conf = ConfigXML(xml)
         self.failUnless(conf)
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
     def testParseFlowError(self):
         xml = """<planet>
@@ -434,7 +435,7 @@
             </planet>"""
         conf = ConfigXML(xml)
         self.failUnless(conf)
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
         xml = """<planet>
               <flow name="manager">
@@ -443,7 +444,7 @@
             </planet>"""
         conf = ConfigXML(xml)
         self.failUnless(conf)
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
         xml = """<planet>
               <flow name="atmosphere">
@@ -452,7 +453,7 @@
             </planet>"""
         conf = ConfigXML(xml)
         self.failUnless(conf)
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
         xml = """<planet>
               <flow name="wrongcomponentnode">
@@ -461,7 +462,7 @@
             </planet>"""
         conf = ConfigXML(xml)
         self.failUnless(conf)
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
 
     def testParseManagerError(self):
@@ -471,14 +472,14 @@
             </manager></planet>"""
         conf = ManagerConfigXML(xml)
         self.failUnless(conf)
-        self.assertRaises(config.ConfigError, conf.parseBouncerAndPlugs)
+        self.assertRaises(errors.ConfigError, conf.parseBouncerAndPlugs)
 
         xml = """<planet>
                <manager name="aname">
                  <port>notanint</port>
                </manager>
              </planet>"""
-        self.assertRaises(config.ConfigError,
+        self.assertRaises(errors.ConfigError,
             ManagerConfigXML, xml)
 
         xml = """<planet>
@@ -486,7 +487,7 @@
                  <transport>notatransport</transport>
                </manager>
              </planet>"""
-        self.assertRaises(config.ConfigError,
+        self.assertRaises(errors.ConfigError,
             ManagerConfigXML, xml)
 
         xml = """<planet>
@@ -494,7 +495,7 @@
                  <notanode/>
                </manager>
              </planet>"""
-        self.assertRaises(config.ConfigError,
+        self.assertRaises(errors.ConfigError,
             ManagerConfigXML, xml)
 
     def testParseProperties(self):
@@ -810,7 +811,7 @@
              </planet>""")
         self.failIf(planet.flows)
 
-        self.assertRaises(config.ConfigError, planet.parse)
+        self.assertRaises(errors.ConfigError, planet.parse)
 
     def testGetComponentEntries(self):
         conf = ConfigXML(
@@ -868,7 +869,7 @@
               </flow>
             </planet>
             """)
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
     def testParseComponentsWithSource(self):
         conf = ConfigXML(
@@ -942,7 +943,7 @@
               </flow>
             </planet>
             """)
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
     def testParseComponentsWithMultipleEater(self):
         conf = ConfigXML(
@@ -1047,12 +1048,12 @@
              </planet>
              """
         conf = ConfigXML(xml)
-        self.assertRaises(config.ConfigError, conf.parse)
+        self.assertRaises(errors.ConfigError, conf.parse)
 
     def testVirtualFeeds(self):
         def assertFail(s):
             conf = ConfigXML(s)
-            self.assertRaises(config.ConfigError, conf.parse)
+            self.assertRaises(errors.ConfigError, conf.parse)
         def assertPass(s, feeds):
             conf = ConfigXML(s)
             conf.parse()
@@ -1110,57 +1111,6 @@
              """, {'valid:name': 'default'})
         
 
-def AdminConfig(sockets, string):
-    f = StringIO(string)
-    conf = config.AdminConfigParser(sockets, f)
-    f.close()
-    return conf
-
-class AdminConfigTest(testsuite.TestCase):
-    def testMinimal(self):
-        doc = ('<admin>'
-               '<plugs>'
-               '</plugs>'
-               '</admin>')
-        parser = AdminConfig((), doc)
-        self.failUnless(parser.plugs == {}, 'expected empty plugset')
-
-    def testMinimal2(self):
-        doc = ('<admin>'
-               '<plugs>'
-               '</plugs>'
-               '</admin>')
-        parser = AdminConfig((), doc)
-        self.failUnless(parser.plugs == {}, 'expected empty plugset')
-
-    def testMinimal3(self):
-        doc = ('<admin>'
-               '<plugs>'
-               '</plugs>'
-               '</admin>')
-        parser = AdminConfig(('foo.bar',), doc)
-        self.failUnless(parser.plugs == {'foo.bar':[]}, parser.plugs)
-
-    def testUnknownPlug(self):
-        doc = ('<admin>'
-               '<plugs>'
-               '<plug type="plugdoesnotexist" socket="foo.bar">'
-               '</plug>'
-               '</plugs>'
-               '</admin>')
-        self.assertRaises(errors.UnknownPlugError,
-                          lambda: AdminConfig(('foo.bar',), doc))
-
-    def testUnknownSocket(self):
-        doc = ('<admin>'
-               '<plugs>'
-               '<plug type="frobulator" socket="baz">'
-               '</plug>'
-               '</plugs>'
-               '</admin>')
-        self.assertRaises(config.ConfigError,
-                          lambda: AdminConfig(('foo.bar',), doc))
-
 class TestDictDiff(testsuite.TestCase):
     def assertOND(self, d1, d2, old, new, diff):
         o, n, d = config.dictDiff(d1, d2)

Modified: flumotion/trunk/po/POTFILES.in
==============================================================================
--- flumotion/trunk/po/POTFILES.in	(original)
+++ flumotion/trunk/po/POTFILES.in	Fri Dec 28 13:04:37 2007
@@ -1,4 +1,5 @@
 flumotion/manager/admin.py
+flumotion/manager/config.py
 flumotion/manager/worker.py
 flumotion/manager/main.py
 flumotion/manager/base.py
@@ -297,6 +298,7 @@
 flumotion/test/test_common_avltree.py
 flumotion/test/test_component_playlist.py
 flumotion/test/test_config.py
+flumotion/test/test_admin_config.py
 flumotion/test/test_saltsha256.py
 flumotion/test/test_ui_fgtk.py
 flumotion/test/test_wizard.py
@@ -328,6 +330,7 @@
 flumotion/test/test_admin_multi.py
 flumotion/test/test_htpasswdcrypt.py
 flumotion/admin/multi.py
+flumotion/admin/config.py
 flumotion/admin/connections.py
 flumotion/admin/admin.py
 flumotion/admin/text/admin_text.py


More information about the flumotion-commit mailing list