root/eddie/trunk/doc/dev_guide.txt

Revision 547, 12.0 kB (checked in by chris, 6 years ago)

Updated docs with version 0.30 changes (forgot to do this at release time, oops).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 The EDDIE Tool Developer's Guide
2 (c) Chris Miles 2002
3
4 The information contained in this guide is for persons managing, maintaining,
5 or modifying EDDIE code, including developers of directives, data collectors or
6 actions.
7
8
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:
183         fs='/var/log'
184         rule='pctused > 50'
185         action=email("root", "Filesystem %(mountpt)s is over 50%% full")
186
187
188
189 *** Data Collectors ***
190
191 Data collectors are the OS-specific classes and modules which collect common
192 system information for directives such as PROC, FS, SYS, etc.  Not all
193 directives need to use these data collectors, many directives are coded to
194 collect data or perform tests internally.  These are primarily to provide a
195 common set of data collection modules which are automatically loaded based
196 on the current operating system and architecture.
197
198 Data Collector creation has been designed to be relatively easy.  A base-class
199 is provided which does most of the work, handles data-caching and refreshing,
200 locking for thread-safety and provides most of the public methods.
201
202 The main steps are as follows:
203  - Create OS or architecture-specific lib directory if required;
204  - Create module if an appropriate module does not already exist;
205  - Create new data collector class definition.
206
207 ** Create Lib Directory **
208
209 If a lib directory does not already exist for the required operating system
210 or architecture (e.g., Linux, SunOS, SunOS/sun4m) in the eddie/lib/ directory
211 then a new one must be created.  The search order for architecture specific
212 modules is:
213
214     1. eddie/lib/<osname>/<osver>/<osarch>
215     2. eddie/lib/<osname>/<osarch>/<osver>
216     3. eddie/lib/<osname>/<osver>
217     4. eddie/lib/<osname>/<osarch>
218     5. eddie/lib/<osname>
219
220 where the variable directory names are taken from uname as follows:
221
222     <osname> = uname -s
223     <osver>  = uname -r
224     <osarch> = uname -m
225
226 This allows the same modules to exist for multiple operating systems and the
227 correct module will be imported as required.  If different modules are needed
228 for different OS versions or architectures of the same operating system, they
229 can be placed in the relevent subdirectory, and will be loaded automatically.
230
231
232 ** Create Module **
233
234 If an appropriate module file is not already available, a new one should be
235 created in the relevent directory (see ** Create Lib Directory ** above).
236 The module must import the datacollect module, which contains the base
237 DataCollect class definition.
238
239 * Data Collector Class *
240
241 A new class should be created which should be sub-class the
242 datacollect.DataCollect class.  The constructor method only has to
243 call the base-class constructor.
244
245 E.g.,
246
247     class mydata(datacollect.DataCollect):
248         def __init__(self):
249             apply( datacollect.DataCollect.__init__, (self,) )
250
251
252 * collectData method *
253
254 The only other method a data collector class must have is the collectData
255 method.  This is where the data is collected from the system and added
256 to dictionaries or lists in the self.data container class.  The self.data
257 class currently has no special function other than to provide a container
258 for data.  If only a single dictionary of name/values is required, the
259 self.data.datahash dictionary should be used.  The built-in getHash method
260 defaults to this dictionary, although the name of another dictionary can be
261 passed instead.  Similarly, a getList method is also available to retrieve
262 copies of any lists.
263
264 The getHash and getList methods should be the only method used to access
265 the dictionaries and lists in self.data as they provide thread-safety be
266 locking access to the data, and they make sure to return copies of the
267 data to prevent the data changing while being accessed.
268
269 collecData is only called after the current thread has acquired the data lock
270 so it is safe to manipulate the data structures as required in this method.
271
272 E.g.,
273
274     def collectData(self):
275         """
276         Collect some data.
277         """
278
279         self.data.datahash = {}         # reset dictionary
280         self.data.datahash['data1'] = 3
281         self.data.datahash['data2'] = 3.1415
282         self.data.datahash['data3'] = 'pi'
283
284
285
286 *** Actions ***
287
288 TODO
289
290
291
292 *** Logging ***
293
294 EDDIE logs to the logfile specified by LOGFILE in the config.  It uses the
295 log() function in the log.py module, and calls are of the form:
296     log.log( log_string, log_level )
297
298 log_string should be formatted like:
299     "<module_name>method_name(): log_text"
300 where module_name is the name of the current module, excluding ".py", e.g.,
301 "directive" for a logged message in the directive.py module;
302 method_name is the name of the current method or function.
303 For example, a logged message in the directive module by the docheck() method
304 of the FS class would look like:
305     log.log( "<directive>FS.docheck(): rule '%s' was false, calling doAction()" % (self.args.rul e), 6 )
306
307 log_level defines the severity level of the log message, so the verbosity of
308 logs can be set by the user with the LOGLEVEL config setting, and where the
309 levels are defined as:
310     1 - critical errors where the software cannot continue
311     2 - serious errors where the software can attempt to recover or continue
312     3 - errors forcing the current thread to die prematurely
313     4 - errors where the current directive cannot continue and will not be re-scheduled
314     5 - warnings/info or errors not serious enough for the above levels
315     6 - action output
316     7 - directive output (for debugging directive checks etc)
317     8 - config parsing output (generally for debugging config problems)
318     9 - extra verbose debugging messages
319
320
Note: See TracBrowser for help on using the browser.