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