Changeset 28 for trunk/confloader.py

Show
Ignore:
Timestamp:
26/11/07 20:14:17 (4 years ago)
Author:
daedalus
Message:

* Refactored code to permit more flexible templating of changes. You can now

define a <changetemplate/> in 95% the same way as a <change/>, and then
refer to a <changetemplate/> when defining a <change/> to allow you to
combine change templates as a change flow, using namespaces and iterators
for each one.

* Some work on manual bailout in authoritarian mode, but it isn't completely

bailing out immediately yet, due to some complexities in the deferred chains
that I haven't quite unravelled.

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • trunk/confloader.py

    r27 r28  
    1515from device import Device 
    1616from change import CHANGE_STATE 
     17import util 
    1718 
    1819from twisted.internet import defer, reactor 
     
    128129        self.provisioners = {} 
    129130        self.devices = {} 
     131        self.change_templates = {} 
    130132        self.changes = {} 
    131133        self.iterators = {} 
     
    163165                    del self.devices[dev] 
    164166                    pass 
    165  
    166     def oldloader(self): 
    167         # A new provisioner 
    168         if name == 'provisioner': 
    169             log.debug("Creating new provisioner") 
    170             self.add_provisioner(element) 
    171  
    172         elif name == 'device': 
    173             log.debug("Creating new device") 
    174             self.add_device(element) 
    175  
    176         elif name == 'change': 
    177             log.debug("Creating new change") 
    178             self.add_change(element) 
    179  
    180         elif name == 'namespace': 
    181             log.debug("Creating new namespace") 
    182             self.add_namespace(element) 
    183  
    184         elif name == 'config': 
    185             # We've finished the config 
    186             #self.global_namespace = self.namespace_stack.pop() 
    187  
    188             # Add some global elements to the namespace 
    189             element.namespace['ALL_TARGETS'] = [ x for x in self.devices.keys() ] 
    190  
    191             self.global_namespace = element.namespace 
    192  
    193  
    194 ##         elif name == 'dependencies': 
    195 ##             # We've finish a list of dependency elements 
    196 ##             log.info("Finished loaded dependencies.") 
    197 ##             self.dependency_tree = element 
    198167             
    199168    def parse(self, configfile): 
     
    203172        self.tree = etree.parse(configfile) 
    204173 
     174        # Process xincludes 
     175        try: 
     176            self.tree.xinclude() 
     177        except etree.XIncludeError: 
     178            log.error("XInclude of a file failed.") 
     179            log.error("Use external tool such as xmllint to figure out why.") 
     180            log.error("Sorry, but lxml.etree won't tell me exactly what went wrong.") 
     181            raise 
     182             
    205183        # add the global namespace 
    206184        try: 
     
    209187        except IndexError: 
    210188            log.warn("Cannot find global namespace") 
    211             self.global_namespace = None 
     189            self.global_namespace = {} 
    212190 
    213191        # Add all my node types, in the order specified in the list 
     
    216194            'provisioner', 
    217195            'device', 
     196            'changetemplate', 
    218197            'change', 
    219             'dependencies', 
     198#            'dependencies', 
    220199            ]: 
    221200 
     
    333312        log.debug("my ip addr is: %s", device.ipaddress) 
    334313        self.devices[device_name] = device 
    335          
     314 
     315    def add_changetemplate(self, node): 
     316        """ 
     317        Add a change template to my list of available templates. 
     318        A change template is a change object that may not have 
     319        all of its parameters set yet. 
     320        """ 
     321        log.debug("Adding change template...") 
     322        # Create the change template object 
     323        change_tmpl = self.create_change_object(node) 
     324         
     325        # Set any namespaces 
     326        self.set_change_namespace(change_tmpl, node) 
     327 
     328        # Set change parameters that are common to all changes 
     329        self.set_change_params(change_tmpl, node) 
     330         
     331        # Set the optional change iterator 
     332        self.set_change_iterator(change_tmpl, node) 
     333         
     334        # Add an optional onfail mode 
     335        self.set_change_onfail(change_tmpl, node) 
     336 
     337        self.change_templates[change_tmpl.name] = change_tmpl 
     338 
     339        return change_tmpl 
     340 
    336341    def add_change(self, node): 
    337342        """ 
    338         Add a change to my configuration based on the parsed element. 
     343        When adding a change, check for some extra options such as a 
     344        change template, which will cause this change to be based on 
     345        an existing change template. 
     346        """ 
     347        log.debug("Adding change...") 
     348        try: 
     349            tmpl_name = node.attrib['template'] 
     350            log.debug("Change is based on a template: '%s'", tmpl_name) 
     351            # Do template based processing 
     352            try: 
     353                template = self.change_templates[tmpl_name] 
     354            except KeyError: 
     355                raise ValueError("Change template '%s' is not defined" % tmpl_name) 
     356 
     357            change = template.copy() 
     358            # Make sure we set the change name to the actual change, 
     359            # not the name of the template 
     360            change.name = node.attrib['name'] 
     361 
     362            # Now do non-templated specific processing 
     363            self.set_change_namespace(change, node) 
     364            self.set_change_params(change, node) 
     365            self.set_change_iterator(change, node) 
     366            self.set_change_onfail(change, node) 
     367             
     368        except KeyError: 
     369            # Use the same processing stream as a template, with the 
     370            # assumption that all the required parameters have been 
     371            # defined. If they haven't, the change will fail. 
     372            change = self.add_change_template(node) 
     373            pass 
     374         
     375        # If the change doesn't have a provisioner, use the first one 
     376        # in our config by default. 
     377        if getattr(change, 'provisioner', None) is None: 
     378            log.info("Change '%s' has no provisioner. Using default.", change.name ) 
     379            # FIXME: This should find the first *compatible* provisioner, 
     380            # rather than just the first one. 
     381            change.provisioner = self.provisioners.values()[0] 
     382            pass 
     383         
     384        self.changes[change.name] = change 
     385        self.pending_changes.append( change ) 
     386 
     387    def create_change_object(self, node): 
     388        """ 
     389        Create a change object from a node. 
     390        This may be used as a change template, or a regular change. 
    339391        """ 
    340392        # Change type is dynamic, as it supports the loading 
     
    343395        change_name = node.attrib['name'] 
    344396 
    345         log.debug("Attempting to create a '%s' change", change_klass) 
     397        log.debug("Creating '%s' change", change_klass) 
    346398 
    347399        try: 
     
    356408 
    357409        log.debug("created change '%s': %s", change_name, change) 
    358  
    359         # Add any namespaces 
     410        return change 
     411 
     412    def set_change_namespace(self, change, node): 
     413        """ 
     414        Add a namespace to a change or change template 
     415        """ 
    360416        ns = node.find('namespace') 
    361417        if ns is None: 
     
    364420            self.add_namespace(ns, change) 
    365421 
    366         # Add change attributes that are common to all changes 
     422    def set_change_provisioner(self, change, node): 
     423        # which provisioner to use for the change 
     424        try: 
     425            provname = node.attrib['provisioner'] 
     426        except KeyError: 
     427            return 
     428         
     429        provname = util.substituteVariables(provname, change.namespace) 
     430        try: 
     431            change.provisioner = self.provisioners[provname] 
     432            log.debug("change '%s' will use provisioner '%s'", change, provname) 
     433        except KeyError: 
     434            raise KeyError("Provisioner named '%s' is not defined" % provname) 
     435 
     436    def set_change_params(self, change, node): 
     437        """ 
     438        Set common change parameters, defined in subnodes. 
     439        """ 
    367440        for subnode in node.xpath('*'): 
    368             # which provisioner to use for the change 
    369             if subnode.tag == 'useprov': 
    370                 provname = subnode.text 
    371                 provname = provname % change.namespace 
    372                 change.provisioner = self.provisioners[provname] 
    373                 log.debug("change '%s' will use provisioner '%s'", change, provname) 
    374                 pass 
    375  
    376441            # a target device for the change 
    377             elif subnode.tag == 'target': 
     442            if subnode.tag == 'target': 
    378443                targetname = subnode.text 
    379444 
     
    385450                else: 
    386451                    # perform variable substitution with global namespace 
    387                     #targetname = self.apply_namespaces(targetname) 
    388452                    log.debug("my namespace: %s", change.namespace) 
    389                     targetname = targetname % change.namespace 
    390                      
     453                    targetname = util.substituteVariables(targetname, change.namespace) 
    391454                    dev = self.devices[targetname] 
    392455                    log.debug("current target list: %s", change.devices) 
     
    395458                pass 
    396459 
    397             elif subnode.tag == 'prereq': 
    398                 changename = subnode.text 
     460            elif subnode.tag == 'depends': 
     461                changename = subnode.attrib['on'] 
    399462                try: 
    400463                    change.pre_requisites.append(self.changes[changename]) 
     
    444507                pass 
    445508            pass 
    446  
    447         # Set the optional change iterator 
     509        pass 
     510 
     511    def set_change_iterator(self, change, node): 
     512        """ 
     513        Set an optional change iterator 
     514        """ 
    448515        try: 
    449516            itername = node.attrib['iterator'] 
     
    453520        if itername is not None: 
    454521            # Use namespace substitution for the itername to allow templating 
    455             itername = itername % change.namespace 
     522            itername = util.substituteVariables(itername, change.namespace) 
    456523            try: 
    457524                change.iterator = self.iterators[itername] 
     
    460527                log.error("Iterator '%s' is not defined", itername) 
    461528                raise 
    462  
    463         # Add an optional onfail mode 
     529            pass 
     530        pass 
     531 
     532    def set_change_onfail(self, change, node): 
     533        """ 
     534        Attempt to add an 'onfail' mode for the change, if defined 
     535        """ 
    464536        log.debug("Checking for 'onfail' attribute...") 
    465537        try: 
     
    482554            pass 
    483555 
    484         # If the change doesn't have a provisioner, use the first one 
    485         # in our config by default. 
    486         if getattr(change, 'provisioner', None) is None: 
    487             change.provisioner = self.provisioners.values()[0] 
    488          
    489         self.changes[change_name] = change 
    490         self.pending_changes.append( change ) 
    491556 
    492557    def add_namespace(self, node, item=None): 
     
    598663            raise ValueError("change_complete() cannot handle change state: %s", change.state) 
    599664 
    600     def apply_namespaces(self, string): 
    601         """ 
    602         Apply namespaces mid parse. 
    603         Deprecated. 
    604         """ 
    605         # Find the first namespace in the element stack, and use it, 
    606         # which will implicitly apply its parents. 
    607 ##         tempstack = self.element_stack 
    608 ##         tempstack.reverse() 
    609 ##         for elem in tempstack: 
    610 ##             if getattr(elem, 'namespace', None) is not None: 
    611 ##                 log.debug("applying namespace: %s", elem.namespace) 
    612 ##                 string = string % elem.namespace 
    613 ##                 break 
    614 ##             pass 
    615         log.warn("Inline namespace usage not yet supported.") 
     665    def _apply_namespace(self, string, namespace): 
     666        """ 
     667        Apply a namespace to a string 
     668        Deprecated 
     669        """ 
     670        if namespace is not None: 
     671            string = string % namespace 
     672 
    616673        return string 
    617674