Package flumotion :: Package manager :: Module config
[hide private]

Source Code for Module flumotion.manager.config

  1  # -*- Mode: Python; test-case-name: flumotion.test.test_config -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3  # 
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007,2008 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 21   
 22  """ 
 23  parsing of manager configuration files 
 24  """ 
 25   
 26  from flumotion.common import log, errors, common, registry 
 27  from flumotion.common import config as fluconfig 
 28  from flumotion.common.xmlwriter import cmpComponentType, XMLWriter 
 29  from flumotion.configure import configure 
 30   
 31  __version__ = "$Rev: 6981 $" 
 32   
 33   
34 -def _ignore(*args):
35 pass
36
37 -def upgradeEaters(conf):
38 def parseFeedId(feedId): 39 if feedId.find(':') == -1: 40 return "%s:default" % feedId 41 else: 42 return feedId
43 44 eaterConfig = conf.get('eater', {}) 45 sourceConfig = conf.get('source', []) 46 if eaterConfig == {} and sourceConfig != []: 47 eaters = registry.getRegistry().getComponent( 48 conf.get('type')).getEaters() 49 eatersDict = {} 50 eatersTuple = [(None, parseFeedId(s)) for s in sourceConfig] 51 eatersDict = buildEatersDict(eatersTuple, eaters) 52 conf['eater'] = eatersDict 53 54 if sourceConfig: 55 sources = [] 56 for s in sourceConfig: 57 sources.append(parseFeedId(s)) 58 conf['source'] = sources 59
60 -def upgradeAliases(conf):
61 eaters = dict(conf.get('eater', {})) # a copy 62 concat = lambda lists: reduce(list.__add__, lists, []) 63 if not reduce(lambda x,y: y and isinstance(x, tuple), 64 concat(eaters.values()), 65 True): 66 for eater in eaters: 67 aliases = [] 68 feeders = eaters[eater] 69 for i in range(len(feeders)): 70 val = feeders[i] 71 if isinstance(val, tuple): 72 feedId, alias = val 73 aliases.append(val[1]) 74 else: 75 feedId = val 76 alias = eater 77 while alias in aliases: 78 log.warning('config', "Duplicate alias %s for " 79 "eater %s, uniquifying", alias, eater) 80 alias += '-bis' 81 aliases.append(alias) 82 feeders[i] = (feedId, val) 83 conf['eater'] = eaters
84 85 UPGRADERS = [upgradeEaters, upgradeAliases] 86 CURRENT_VERSION = len(UPGRADERS) 87
88 -def buildEatersDict(eatersList, eaterDefs):
89 """Build a eaters dict suitable for forming part of a component 90 config. 91 92 @param eatersList: List of eaters. For example, 93 [('default', 'othercomp:feeder', 'foo')] says 94 that our eater 'default' will be fed by the feed 95 identified by the feedId 'othercomp:feeder', and 96 that it has the alias 'foo'. Alias is optional. 97 @type eatersList: List of (eaterName, feedId, eaterAlias?) 98 @param eaterDefs: The set of allowed and required eaters 99 @type eaterDefs: List of 100 L{flumotion.common.registry.RegistryEntryEater} 101 @returns: Dict of eaterName => [(feedId, eaterAlias)] 102 """ 103 def parseEaterTuple(tup): 104 def parse(eaterName, feedId, eaterAlias=None): 105 if eaterAlias is None: 106 eaterAlias = eaterName 107 return (eaterName, feedId, eaterAlias)
108 return parse(*tup) 109 110 eaters = {} 111 for eater, feedId, alias in [parseEaterTuple(t) for t in eatersList]: 112 if eater is None: 113 if not eaterDefs: 114 raise errors.ConfigError( 115 "Feed %r cannot be connected, component has no eaters" % 116 (feedId,)) 117 # cope with old <source> entries 118 eater = eaterDefs[0].getName() 119 if alias is None: 120 alias = eater 121 feeders = eaters.get(eater, []) 122 if feedId in feeders: 123 raise errors.ConfigError("Already have a feedId %s eating from %s" % 124 (feedId, eater)) 125 while alias in [a for f, a in feeders]: 126 log.warning('config', "Duplicate alias %s for eater %s, " 127 "uniquifying", alias, eater) 128 alias += '-bis' 129 130 feeders.append((feedId, alias)) 131 eaters[eater] = feeders 132 for e in eaterDefs: 133 eater = e.getName() 134 if e.getRequired() and not eater in eaters: 135 raise errors.ConfigError("Component wants to eat on %s," 136 " but no feeders specified." 137 % (e.getName(),)) 138 if not e.getMultiple() and len(eaters.get(eater, [])) > 1: 139 raise errors.ConfigError("Component does not support multiple " 140 "sources feeding %s (%r)" 141 % (eater, eaters[eater])) 142 aliases = reduce(list.__add__, 143 [[x[1] for x in tups] for tups in eaters.values()], 144 []) 145 # FIXME: Python 2.3 has no sets 146 # if len(aliases) != len(set(aliases): 147 while aliases: 148 alias = aliases.pop() 149 if alias in aliases: 150 raise errors.ConfigError("Duplicate alias: %s" % (alias,)) 151 152 return eaters 153 154
155 -def buildVirtualFeeds(feedPairs, feeders):
156 """Build a virtual feeds dict suitable for forming part of a 157 component config. 158 159 @param feedPairs: List of virtual feeds, as name-feederName pairs. For 160 example, [('bar:baz', 'qux')] defines one 161 virtual feed 'bar:baz', which is provided by 162 the component's 'qux' feed. 163 @type feedPairs: List of (feedId, feedName) -- both strings. 164 @param feeders: The feeders exported by this component, from the 165 registry. 166 @type feeders: List of str. 167 """ 168 ret = {} 169 for virtual, real in feedPairs: 170 if real not in feeders: 171 raise errors.ConfigError('virtual feed maps to unknown feeder: ' 172 '%s -> %s' % (virtual, real)) 173 try: 174 common.parseFeedId(virtual) 175 except: 176 raise errors.ConfigError('virtual feed name not a valid feedId: %s' 177 % (virtual,)) 178 ret[virtual] = real 179 return ret
180
181 -def dictDiff(old, new, onlyOld=None, onlyNew=None, diff=None, 182 keyBase=None):
183 """Compute the difference between two config dicts. 184 185 @returns: 3 tuple: (onlyOld, onlyNew, diff) where: 186 onlyOld is a list of (key, value), representing key-value 187 pairs that are only in old; 188 onlyNew is a list of (key, value), representing key-value 189 pairs that are only in new; 190 diff is a list of (key, oldValue, newValue), representing 191 keys with different values in old and new; and 192 key is a tuple of strings representing the recursive key 193 to get to a value. For example, ('foo', 'bar') represents 194 the value d['foo']['bar'] on a dict d. 195 """ 196 # key := tuple of strings 197 198 if onlyOld is None: 199 onlyOld = [] # key, value 200 onlyNew = [] # key, value 201 diff = [] # key, oldvalue, newvalue 202 keyBase = () 203 204 for k in old: 205 key = (keyBase + (k,)) 206 if k not in new: 207 onlyOld.append((key, old[k])) 208 elif old[k] != new[k]: 209 if isinstance(old[k], dict) and isinstance(new[k], dict): 210 dictDiff(old[k], new[k], onlyOld, onlyNew, diff, key) 211 else: 212 diff.append((key, old[k], new[k])) 213 214 for k in new: 215 key = (keyBase + (k,)) 216 if k not in old: 217 onlyNew.append((key, new[k])) 218 219 return onlyOld, onlyNew, diff
220
221 -def dictDiffMessageString((old, new, diff), oldLabel='old', 222 newLabel='new'):
223 def ref(label, k): 224 return "%s%s: '%s'" % (label, 225 ''.join(["[%r]" % (subk,) 226 for subk in k[:-1]]), 227 k[-1])
228 229 out = [] 230 for k, v in old: 231 out.append('Only in %s = %r' % (ref(oldLabel, k), v)) 232 for k, v in new: 233 out.append('Only in %s = %r' % (ref(newLabel, k), v)) 234 for k, oldv, newv in diff: 235 out.append('Value mismatch:') 236 out.append(' %s = %r' % (ref(oldLabel, k), oldv)) 237 out.append(' %s = %r' % (ref(newLabel, k), newv)) 238 return '\n'.join(out) 239
240 -class ConfigEntryComponent(log.Loggable):
241 "I represent a <component> entry in a planet config file" 242 nice = 0 243 logCategory = 'config' 244 245 __pychecker__ = 'maxargs=13' 246
247 - def __init__(self, name, parent, type, label, propertyList, plugList, 248 worker, eatersList, isClockMaster, project, version, 249 virtualFeeds=None):
250 self.name = name 251 self.parent = parent 252 self.type = type 253 self.label = label 254 self.worker = worker 255 self.defs = registry.getRegistry().getComponent(self.type) 256 try: 257 self.config = self._buildConfig(propertyList, plugList, 258 eatersList, isClockMaster, 259 project, version, 260 virtualFeeds) 261 except errors.ConfigError, e: 262 # reuse the original exception? 263 e.args = ("While parsing component %s: %s" 264 % (name, log.getExceptionMessage(e)),) 265 raise
266
267 - def _buildVersionTuple(self, version):
268 if version is None: 269 return configure.versionTuple 270 elif isinstance(version, tuple): 271 assert len(version) == 4 272 return version 273 elif isinstance(version, str): 274 try: 275 def parse(maj, min, mic, nan=0): 276 return maj, min, mic, nan
277 return parse(*map(int, version.split('.'))) 278 except: 279 raise errors.ConfigError("<component> version not parseable") 280 raise errors.ConfigError("<component> version not parseable")
281
282 - def _buildConfig(self, propertyList, plugsList, eatersList, 283 isClockMaster, project, version, virtualFeeds):
284 """ 285 Build a component configuration dictionary. 286 """ 287 # clock-master should be either an avatar id or None. 288 # It can temporarily be set to True, and the flow parsing 289 # code will change it to the avatar id or None. 290 config = {'name': self.name, 291 'parent': self.parent, 292 'type': self.type, 293 'config-version': CURRENT_VERSION, 294 'avatarId': common.componentId(self.parent, self.name), 295 'project': project or configure.PACKAGE, 296 'version': self._buildVersionTuple(version), 297 'clock-master': isClockMaster or None, 298 'feed': self.defs.getFeeders(), 299 'properties': fluconfig.buildPropertyDict(propertyList, 300 self.defs.getProperties()), 301 'plugs': fluconfig.buildPlugsSet(plugsList, 302 self.defs.getSockets()), 303 'eater': buildEatersDict(eatersList, 304 self.defs.getEaters()), 305 'source': [tup[1] for tup in eatersList], 306 'virtual-feeds': buildVirtualFeeds(virtualFeeds or [], 307 self.defs.getFeeders())} 308 309 if self.label: 310 # only add a label attribute if it was specified 311 config['label'] = self.label 312 313 if not config['source']: 314 # preserve old behavior 315 del config['source'] 316 # FIXME: verify that config['project'] matches the defs 317 318 return config
319
320 - def getType(self):
321 return self.type
322
323 - def getLabel(self):
324 return self.label
325
326 - def getName(self):
327 return self.name
328
329 - def getParent(self):
330 return self.parent
331
332 - def getConfigDict(self):
333 return self.config
334
335 - def getWorker(self):
336 return self.worker
337
338 -class ConfigEntryFlow:
339 "I represent a <flow> entry in a planet config file"
340 - def __init__(self, name, components):
341 self.name = name 342 self.components = {} 343 for c in components: 344 if c.name in self.components: 345 raise errors.ConfigError( 346 'flow %s already has component named %s' % (name, c.name)) 347 self.components[c.name] = c
348
349 -class ConfigEntryManager:
350 "I represent a <manager> entry in a planet config file"
351 - def __init__(self, name, host, port, transport, certificate, bouncer, 352 fludebug, plugs):
353 self.name = name 354 self.host = host 355 self.port = port 356 self.transport = transport 357 self.certificate = certificate 358 self.bouncer = bouncer 359 self.fludebug = fludebug 360 self.plugs = plugs
361
362 -class ConfigEntryAtmosphere:
363 "I represent a <atmosphere> entry in a planet config file"
364 - def __init__(self):
365 self.components = {}
366
367 - def __len__(self):
368 return len(self.components)
369
370 -class FlumotionConfigParser(fluconfig.BaseConfigParser):
371 """ 372 This is a base class for parsing planet configuration files (both manager 373 and flow files). 374 """ 375 logCategory = 'config' 376
377 - def _parseFeedId(self, feedId):
378 if feedId.find(':') == -1: 379 return "%s:default" % feedId 380 else: 381 return feedId
382
383 - def _parseVirtualFeed(self, node):
384 # <virtual-feed name="foo" real="bar"/> 385 name, real = self.parseAttributes(node, ('name', 'real')) 386 # assert no content 387 self.parseFromTable(node, {}) 388 return name, real
389
390 - def parseComponent(self, node, parent, isFeedComponent, 391 needsWorker):
392 """ 393 Parse a <component></component> block. 394 395 @rtype: L{ConfigEntryComponent} 396 """ 397 # <component name="..." type="..." label="..."? worker="..."? 398 # project="..."? version="..."?> 399 # <source>...</source>* 400 # <eater name="...">...</eater>* 401 # <property name="name">value</property>* 402 # <clock-master>...</clock-master>? 403 # <plugs>...</plugs>* 404 # <virtual-feed name="foo" real="bar"/>* 405 # </component> 406 407 attrs = self.parseAttributes(node, ('name', 'type'), 408 ('label', 'worker', 'project', 'version',)) 409 name, componentType, label, worker, project, version = attrs 410 if needsWorker and not worker: 411 raise errors.ConfigError('component %s does not specify the worker ' 412 'that it is to run on' % (name,)) 413 elif worker and not needsWorker: 414 raise errors.ConfigError('component %s specifies a worker to run ' 415 'on, but does not need a worker' % (name,)) 416 417 properties = [] 418 plugs = [] 419 eaters = [] 420 clockmasters = [] 421 sources = [] 422 virtual_feeds = [] 423 424 def parseBool(node): 425 return self.parseTextNode(node, common.strToBool)
426 parsers = {'property': (self._parseProperty, properties.append), 427 'compound-property': (self._parseCompoundProperty, 428 properties.append), 429 'plugs': (self.parsePlugs, plugs.extend)} 430 431 if isFeedComponent: 432 parsers.update({'eater': (self._parseEater, eaters.extend), 433 'clock-master': (parseBool, clockmasters.append), 434 'source': (self._parseSource, sources.append), 435 'virtual-feed': (self._parseVirtualFeed, 436 virtual_feeds.append)}) 437 438 self.parseFromTable(node, parsers) 439 440 if len(clockmasters) == 0: 441 isClockMaster = None 442 elif len(clockmasters) == 1: 443 isClockMaster = clockmasters[0] 444 else: 445 raise errors.ConfigError("Only one <clock-master> node allowed") 446 447 for feedId in sources: 448 # map old <source> nodes to new <eater> nodes 449 eaters.append((None, feedId)) 450 451 return ConfigEntryComponent(name, parent, componentType, label, 452 properties, plugs, worker, eaters, 453 isClockMaster, project, version, 454 virtual_feeds)
455
456 - def _parseSource(self, node):
457 return self._parseFeedId(self.parseTextNode(node))
458
459 - def _parseFeed(self, node):
460 alias, = self.parseAttributes(node, (), ('alias',)) 461 feedId = self._parseFeedId(self.parseTextNode(node)) 462 return feedId, alias
463
464 - def _parseEater(self, node):
465 # <eater name="eater-name"> 466 # <feed alias="foo"?>feeding-component:feed-name</feed>* 467 # </eater> 468 name, = self.parseAttributes(node, ('name',)) 469 feeds = [] 470 parsers = {'feed': (self._parseFeed, feeds.append)} 471 self.parseFromTable(node, parsers) 472 if len(feeds) == 0: 473 # we have an eater node with no feeds 474 raise errors.ConfigError( 475 "Eater node %s with no <feed> nodes, is not allowed" % (name,)) 476 return [(name, feedId, alias) for feedId, alias in feeds]
477
478 -class PlanetConfigParser(FlumotionConfigParser):
479 """ 480 I represent a planet configuration file for Flumotion. 481 482 @ivar atmosphere: A L{ConfigEntryAtmosphere}, filled in when parse() is 483 called. 484 @ivar flows: A list of L{ConfigEntryFlow}, filled in when parse() is 485 called. 486 """ 487 logCategory = 'config' 488
489 - def __init__(self, file):
490 FlumotionConfigParser.__init__(self, file) 491 492 self.flows = [] 493 self.atmosphere = ConfigEntryAtmosphere()
494
495 - def parse(self):
496 # <planet> 497 # <manager>? 498 # <atmosphere>* 499 # <flow>* 500 # </planet> 501 root = self.doc.documentElement 502 if root.nodeName != 'planet': 503 raise errors.ConfigError("unexpected root node': %s" % 504 (root.nodeName,)) 505 506 parsers = {'atmosphere': (self._parseAtmosphere, 507 self.atmosphere.components.update), 508 'flow': (self._parseFlow, 509 self.flows.append), 510 'manager': (_ignore, _ignore)} 511 self.parseFromTable(root, parsers) 512 self.doc.unlink() 513 self.doc = None
514 515
516 - def _parseAtmosphere(self, node):
517 # <atmosphere> 518 # <component> 519 # ... 520 # </atmosphere> 521 ret = {} 522 def parseComponent(node): 523 return self.parseComponent(node, 'atmosphere', False, True)
524 def gotComponent(comp): 525 ret[comp.name] = comp
526 parsers = {'component': (parseComponent, gotComponent)} 527 self.parseFromTable(node, parsers) 528 return ret 529
530 - def _parseFlow(self, node):
531 # <flow name="..."> 532 # <component> 533 # ... 534 # </flow> 535 # "name" cannot be atmosphere or manager 536 name, = self.parseAttributes(node, ('name',)) 537 if name == 'atmosphere': 538 raise errors.ConfigError("<flow> cannot have 'atmosphere' as name") 539 if name == 'manager': 540 raise errors.ConfigError("<flow> cannot have 'manager' as name") 541 542 components = [] 543 def parseComponent(node): 544 return self.parseComponent(node, name, True, True)
545 parsers = {'component': (parseComponent, components.append)} 546 self.parseFromTable(node, parsers) 547 548 # handle master clock selection; probably should be done in the 549 # manager in persistent "flow" objects rather than here in the 550 # config 551 masters = [x for x in components if x.config['clock-master']] 552 if len(masters) > 1: 553 raise errors.ConfigError("Multiple clock masters in flow %s: %s" 554 % (name, ', '.join([m.name for m in masters]))) 555 556 need_sync = [(x.defs.getClockPriority(), x) for x in components 557 if x.defs.getNeedsSynchronization()] 558 need_sync.sort() 559 need_sync = [x[1] for x in need_sync] 560 561 if need_sync: 562 if masters: 563 master = masters[0] 564 else: 565 master = need_sync[-1] 566 567 masterAvatarId = master.config['avatarId'] 568 self.info("Setting %s as clock master" % masterAvatarId) 569 570 for c in need_sync: 571 c.config['clock-master'] = masterAvatarId 572 elif masters: 573 self.info('master clock specified, but no synchronization ' 574 'necessary -- ignoring') 575 masters[0].config['clock-master'] = None 576 577 return ConfigEntryFlow(name, components) 578 579 # FIXME: remove, this is only used by the tests
580 - def getComponentEntries(self):
581 """ 582 Get all component entries from both atmosphere and all flows 583 from the configuration. 584 585 @rtype: dictionary of /parent/name -> L{ConfigEntryComponent} 586 """ 587 entries = {} 588 if self.atmosphere and self.atmosphere.components: 589 for c in self.atmosphere.components.values(): 590 path = common.componentId('atmosphere', c.name) 591 entries[path] = c 592 593 for flowEntry in self.flows: 594 for c in flowEntry.components.values(): 595 path = common.componentId(c.parent, c.name) 596 entries[path] = c 597 598 return entries
599 600 601 # FIXME: manager config and flow configs are currently conflated in the 602 # planet config files; need to separate.
603 -class ManagerConfigParser(FlumotionConfigParser):
604 """ 605 I parse manager configuration out of a planet configuration file. 606 607 @ivar manager: A L{ConfigEntryManager} containing options for the manager 608 section, filled in at construction time. 609 """ 610 logCategory = 'config' 611 612 MANAGER_SOCKETS = \ 613 ['flumotion.component.plugs.adminaction.AdminAction', 614 'flumotion.component.plugs.lifecycle.ManagerLifecycle', 615 'flumotion.component.plugs.identity.IdentityProvider'] 616
617 - def __init__(self, file):
618 FlumotionConfigParser.__init__(self, file) 619 620 # the base config: host, port, etc 621 self.manager = None 622 623 # the bouncer ConfigEntryComponent 624 self.bouncer = None 625 626 self.plugs = {} 627 for socket in self.MANAGER_SOCKETS: 628 self.plugs[socket] = [] 629 630 self._parseParameters()
631
632 - def _parseParameters(self):
633 root = self.doc.documentElement 634 if not root.nodeName == 'planet': 635 raise errors.ConfigError("unexpected root node': %s" % 636 (root.nodeName,)) 637 638 parsers = {'atmosphere': (_ignore, _ignore), 639 'flow': (_ignore, _ignore), 640 'manager': (lambda n: self._parseManagerWithoutRegistry(n), 641 lambda v: setattr(self, 'manager', v))} 642 self.parseFromTable(root, parsers)
643
644 - def _parseManagerWithoutRegistry(self, node):
645 # We parse without asking for a registry so the registry doesn't 646 # verify before knowing the debug level 647 name, = self.parseAttributes(node, (), ('name',)) 648 ret = ConfigEntryManager(name, None, None, None, None, None, 649 None, self.plugs) 650 651 def simpleparse(proc): 652 return lambda node: self.parseTextNode(node, proc)
653 def recordval(k): 654 def record(v): 655 if getattr(ret, k): 656 raise errors.ConfigError('duplicate %s: %s' 657 % (k, getattr(ret, k))) 658 setattr(ret, k, v)
659 return record 660 def enum(*allowed): 661 def eparse(v): 662 v = str(v) 663 if v not in allowed: 664 raise errors.ConfigError('unknown value %s (should be ' 665 'one of %r)' % (v, allowed)) 666 return v 667 return eparse 668 669 parsers = {'host': (simpleparse(str), recordval('host')), 670 'port': (simpleparse(int), recordval('port')), 671 'transport': (simpleparse(enum('tcp', 'ssl')), 672 recordval('transport')), 673 'certificate': (simpleparse(str), recordval('certificate')), 674 'component': (_ignore, _ignore), 675 'plugs': (_ignore, _ignore), 676 'debug': (simpleparse(str), recordval('fludebug'))} 677 self.parseFromTable(node, parsers) 678 return ret 679
680 - def _parseManagerWithRegistry(self, node):
681 def parsecomponent(node): 682 return self.parseComponent(node, 'manager', False, False)
683 def gotcomponent(val): 684 if self.bouncer is not None: 685 raise errors.ConfigError('can only have one bouncer ' 686 '(%s is superfluous)' % (val.name,)) 687 # FIXME: assert that it is a bouncer ! 688 self.bouncer = val 689 def parseplugs(node): 690 return fluconfig.buildPlugsSet(self.parsePlugs(node), 691 self.MANAGER_SOCKETS) 692 def gotplugs(newplugs): 693 for socket in self.plugs: 694 self.plugs[socket].extend(newplugs[socket]) 695 696 parsers = {'host': (_ignore, _ignore), 697 'port': (_ignore, _ignore), 698 'transport': (_ignore, _ignore), 699 'certificate': (_ignore, _ignore), 700 'component': (parsecomponent, gotcomponent), 701 'plugs': (parseplugs, gotplugs), 702 'debug': (_ignore, _ignore)} 703 self.parseFromTable(node, parsers) 704
705 - def parseBouncerAndPlugs(self):
706 # <planet> 707 # <manager>? 708 # <atmosphere>* 709 # <flow>* 710 # </planet> 711 root = self.doc.documentElement 712 if not root.nodeName == 'planet': 713 raise errors.ConfigError("unexpected root node': %s" % 714 (root.nodeName,)) 715 716 parsers = {'atmosphere': (_ignore, _ignore), 717 'flow': (_ignore, _ignore), 718 'manager': (self._parseManagerWithRegistry, _ignore)} 719 self.parseFromTable(root, parsers)
720 724 725
726 -class PlanetXMLWriter(XMLWriter):
727 - def __init__(self, planetState):
728 super(PlanetXMLWriter, self).__init__() 729 self._writePlanet(planetState)
730
731 - def _writePlanet(self, planet):
732 attrs = [('name', planet.get('name'))] 733 self.pushTag('planet', attrs) 734 self._writeAtmosphere(planet.get('atmosphere')) 735 for flow in planet.get('flows'): 736 self._writeFlow(flow) 737 self.popTag()
738
739 - def _writeAtmosphere(self, atmosphere):
740 self.pushTag('atmosphere') 741 for component in atmosphere.get('components'): 742 self._writeComponent(component, isFeedComponent=False) 743 self.popTag()
744
745 - def _writeFlow(self, flow):
746 attrs = [('name', flow.get('name'))] 747 self.pushTag('flow', attrs) 748 749 # FIXME: When we can depend on Python 2.4, use 750 # sorted(flow.get('components'), 751 # cmp=cmpComponentType, 752 # key=operator.attrgetter('type')) 753 # 754 def componentSort(a, b): 755 return cmpComponentType(a.get('type'), b.get('type'))
756 components = list(flow.get('components')) 757 components.sort(cmp=componentSort) 758 for component in components: 759 self._writeComponent(component) 760 self.popTag()
761
762 - def _writeComponent(self, component, isFeedComponent=True):
763 config = component.get('config') 764 attrs = [('name', component.get('name')), 765 ('type', component.get('type')), 766 ('label', config.get('label', component.get('name'))), 767 ('worker', component.get('workerRequested')), 768 ('project', config['project']), 769 ('version', common.versionTupleToString(config['version']))] 770 self.pushTag('component', attrs) 771 for name, feeders in config['eater'].items(): 772 self._writeEater(name, feeders) 773 self._writeProperties(config['properties'].items()) 774 if isFeedComponent: 775 if config['clock-master'] == config['avatarId']: 776 value = 'true' 777 else: 778 value = 'false' 779 self.writeTag('clock-master', data=value) 780 self._writePlugs(config['plugs'].items()) 781 self._writeVirtualFeeds(config['virtual-feeds'].items()) 782 self.popTag()
783
784 - def _writeEater(self, name, feeders):
785 attrs = [('name', name)] 786 self.pushTag('eater', attrs) 787 for feedId, alias in feeders: 788 attrs = [('alias', alias)] 789 self.writeTag('feed', attrs, feedId) 790 self.popTag()
791
792 - def _writeProperties(self, properties):
793 def serialise(propVal): 794 if isinstance(propVal, tuple): # fractions are our only tuple type 795 return "%d/%d" % propVal 796 return propVal
797 for name, value in properties: 798 attrs = [('name', name)] 799 self.writeTag('property', attrs, serialise(value)) 800
801 - def _writePlugs(self, plugs):
802 self.pushTag('plugs') 803 for socket, plugs in plugs: 804 for plug in plugs: 805 self._writePlug(plug, socket) 806 self.popTag()
807
808 - def _writePlug(self, plug, socket):
809 attrs = [('socket', socket), 810 ('type', plug['type'])] 811 self.pushTag('plug', attrs) 812 self._writeProperties(plug['properties'].items()) 813 self.popTag()
814
815 - def _writeVirtualFeeds(self, virtualfeeds):
816 for name, real in virtualfeeds: 817 attrs = [('name', name), 818 ('real', real)] 819 self.writeTag('virtual-feed', attrs)
820
821 -def exportPlanetXml(p):
822 pw = PlanetXMLWriter(p) 823 return pw.getXML()
824