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