| 9 | | Logging |
| | 9 | *** The Directive API: Creating new Directives *** |
| | 10 | |
| | 11 | The Directive API has been designed so that new directives can be added with |
| | 12 | a minimal amount of work. The summary of the steps required are: |
| | 13 | |
| | 14 | - Create new Python module in the "lib/common/Directives/" directory; |
| | 15 | - Sub-class the Directive base-class, creating a new directive class |
| | 16 | definition; |
| | 17 | - Create directive-specific methods, overriding some of the base-class |
| | 18 | methods. |
| | 19 | |
| | 20 | |
| | 21 | ** Create New Module ** |
| | 22 | |
| | 23 | Eddie directives are defined in Python modules in the "lib/common/Directives/" |
| | 24 | directory of the Eddie tree. If an appropriate module is not already available |
| | 25 | for the new directive, a new one must be created. E.g., |
| | 26 | $ vi /opt/eddie/lib/common/Directives/mydirec.py |
| | 27 | |
| | 28 | At the very least the new module must import the 'directive' module. |
| | 29 | The directive module contains the Directive base-class definition, along with |
| | 30 | a few other useful definitions (like exceptions). |
| | 31 | |
| | 32 | If logging is required (it usually is) the 'log' module should be imported. |
| | 33 | See *** Logging *** in this document for more details on logging. E.g., |
| | 34 | import directive, log |
| | 35 | |
| | 36 | |
| | 37 | ** Create New Directive Class ** |
| | 38 | |
| | 39 | The new directive class definition should have the actual directive name, and |
| | 40 | it should be created as a sub-class of the Directive class. E.g., |
| | 41 | class MYFS(directive.Directive): |
| | 42 | """ |
| | 43 | This is my new directive. |
| | 44 | It allows filesystem checks to be performed. |
| | 45 | """ |
| | 46 | |
| | 47 | * constructor method * |
| | 48 | |
| | 49 | The constructor method must call the base-class constructor explicitly, |
| | 50 | define any required data-collectors (optional) and perform any other |
| | 51 | directive-specific initialization if necessary. |
| | 52 | E.g., |
| | 53 | def __init__(self, toklist): |
| | 54 | self.need_collectors = ( ('df','dfList'), ) |
| | 55 | apply( directive.Directive.__init__, (self, toklist) ) |
| | 56 | |
| | 57 | The toklist variable contains token information provided by the config |
| | 58 | parser and contains the name of the directive class along with |
| | 59 | (optionally) the user-defined name of the directive instance. |
| | 60 | |
| | 61 | If any data-collectors are required by this directive, the need_collectors |
| | 62 | attribute must be set as a tuple of (module,class) pairs. If any of |
| | 63 | these required data-collectors cannot be loaded at directive initialization |
| | 64 | time, config parsing will halt at that point and an error will be displayed. |
| | 65 | See *** Data Collectors *** for more information. |
| | 66 | |
| | 67 | * tokenparser method * |
| | 68 | |
| | 69 | The tokenparser method must be created for the directive class. It is |
| | 70 | used to parse all the directive arguments and perform any final setup |
| | 71 | tasks. It should begin by calling the base-class tokenparser method |
| | 72 | which actually does the parsing of tokens and building a container of |
| | 73 | valid arguments. Common directive arguments are then set before returning |
| | 74 | control back to the current tokenparser method. This method should |
| | 75 | check the existence of required directive-specific arguments, perform |
| | 76 | any checking of argument values (type-checking, etc), set default action |
| | 77 | variables (see *** Actions ***) and finally set a unique instance ID. |
| | 78 | E.g., |
| | 79 | def tokenparser(self, toklist, toktypes, indent): |
| | 80 | """ |
| | 81 | Parse directive arguments. |
| | 82 | """ |
| | 83 | |
| | 84 | apply( directive.Directive.tokenparser, (self, toklist, toktypes, indent) ) |
| | 85 | |
| | 86 | # test required arguments |
| | 87 | try: |
| | 88 | self.args.fsname |
| | 89 | except AttributeError: |
| | 90 | raise ParseFailure, "Filesystem name (fsname) not specified" |
| | 91 | |
| | 92 | try: |
| | 93 | self.args.rule |
| | 94 | except AttributeError: |
| | 95 | raise ParseFailure, "Rule not specified" |
| | 96 | |
| | 97 | # Set any directive-specific variables |
| | 98 | self.defaultVarDict['rule'] = self.args.rule |
| | 99 | |
| | 100 | # define the unique ID |
| | 101 | if self.ID == None: |
| | 102 | self.ID = '%s.FS.%s' % (log.hostname,self.args.rule) |
| | 103 | self.state.ID = self.ID |
| | 104 | |
| | 105 | * getData method * |
| | 106 | |
| | 107 | The base-class handles all the grunt work when a directive is run: |
| | 108 | including handling re-checks (numchecks argument > 1); calling the |
| | 109 | directive-specific method to fetch the data (getData method); |
| | 110 | evaluating the rule using the data as the environment; setting the |
| | 111 | directive state (ok, fail, etc) depending on the value of the rule |
| | 112 | evaluation; calling actions appropriately; and submitting the |
| | 113 | directive back in the scheduler queue. |
| | 114 | |
| | 115 | The main part that the new directive designer needs to worry about |
| | 116 | is the part that fetches the data. This is performed in the getData |
| | 117 | method which must be defined by each directive sub-class. |
| | 118 | |
| | 119 | The getData method must do whatever is necessary to fetch the |
| | 120 | directive-specific data (which could be as simple as calling |
| | 121 | a data-collector - see *** Data Collectors ***) and returning |
| | 122 | the data as a dictionary of name/values. |
| | 123 | |
| | 124 | The data dictionary will be used as the environment to evaluate |
| | 125 | the rule in (each data element will be a variable available for |
| | 126 | the rule). The data will also be be inserted into the action |
| | 127 | variable dictionary (see *** Actions ***) for use by action calls |
| | 128 | and message strings, etc. |
| | 129 | |
| | 130 | If getData encounters a critical error where the directive cannot |
| | 131 | continue, and should not be re-scheduled again, it should raise |
| | 132 | a directive.DirectiveError exception. |
| | 133 | |
| | 134 | If the directive is functioning fine, but the rule should not |
| | 135 | be evaluated this time for some reason, getData should return |
| | 136 | the None object. The directive check will end immediately and |
| | 137 | the directive will be re-scheduled as normal. |
| | 138 | |
| | 139 | E.g., |
| | 140 | |
| | 141 | def getData(self): |
| | 142 | """ |
| | 143 | Called by Directive docheck() method to fetch the data required for |
| | 144 | evaluating the directive rule. |
| | 145 | """ |
| | 146 | |
| | 147 | # Get filesystem statistics from dfList data-collector |
| | 148 | df = self.data_collectors['df.dfList'][self.args.fsname] |
| | 149 | if df == None: |
| | 150 | # Raise an error if given filesystem is invalid |
| | 151 | log.log( "<directive>FS.docheck(): Error, filesystem not found '%s'" % (self.args.fsname), 4 ) |
| | 152 | raise directive.DirectiveError, "Error, filesystem not found '%s'" % (self.args.fsname) |
| | 153 | else: |
| | 154 | # Return dictionary of filesystem stats |
| | 155 | return df.getHash() |
| | 156 | |
| | 157 | ** Optional Methods ** |
| | 158 | |
| | 159 | There are a few more methods which can be defined for a directive |
| | 160 | if they are necessary. |
| | 161 | |
| | 162 | * addVariables * |
| | 163 | |
| | 164 | If any action variables need to be added after the rule has been evaluated |
| | 165 | (but before actions are called) an addVariables method can be defined to |
| | 166 | do just this. |
| | 167 | |
| | 168 | * postAction * |
| | 169 | |
| | 170 | If any post-action processing needs to be performed, before the directive |
| | 171 | is re-scheduled, it can be done in a postAction method. |
| | 172 | |
| | 173 | ** Test New Directive Class ** |
| | 174 | |
| | 175 | Once the above steps have been completed, the new directive should be ready for |
| | 176 | use. EDDIE will automatically pick it up. A test rule can be added to the |
| | 177 | configuration and EDDIE restarted. Look for errors or exceptions in the log |
| | 178 | file to see if there are problems. |
| | 179 | |
| | 180 | E.g., |
| | 181 | |
| | 182 | MYFS test: fsname='/var/log' |
| | 183 | rule='capac > 50' |
| | 184 | action='email("root", "Filesystem %(mountpt)s is over 50%% full")' |
| | 185 | |
| | 186 | |
| | 187 | *** Data Collectors *** |
| | 188 | |
| | 189 | TODO |
| | 190 | |
| | 191 | |
| | 192 | |
| | 193 | *** Actions *** |
| | 194 | |
| | 195 | TODO |
| | 196 | |
| | 197 | |
| | 198 | |
| | 199 | *** Logging *** |