]> git.llucax.com Git - software/pymin.git/blob - pymin/services/util.py
Add a new helper class for services: ListSubHandler.
[software/pymin.git] / pymin / services / util.py
1 # vim: set encoding=utf-8 et sw=4 sts=4 :
2
3 import subprocess
4 from mako.template import Template
5 from mako.runtime import Context
6 from os import path
7 try:
8     import cPickle as pickle
9 except ImportError:
10     import pickle
11
12 from pymin.dispatcher import Handler, handler, HandlerError, \
13                                 CommandNotFoundError
14
15 #DEBUG = False
16 DEBUG = True
17
18 __ALL__ = ('ServiceHandler', 'InitdHandler', 'SubHandler', 'DictSubHandler',
19             'ListSubHandler', 'Persistent', 'ConfigWriter', 'Error',
20             'ReturnNot0Error', 'ExecutionError', 'ItemError',
21             'ItemAlreadyExistsError', 'ItemNotFoundError', 'call')
22
23 class Error(HandlerError):
24     r"""
25     Error(message) -> Error instance :: Base ServiceHandler exception class.
26
27     All exceptions raised by the ServiceHandler inherits from this one, so
28     you can easily catch any ServiceHandler exception.
29
30     message - A descriptive error message.
31     """
32     pass
33
34 class ReturnNot0Error(Error):
35     r"""
36     ReturnNot0Error(return_value) -> ReturnNot0Error instance.
37
38     A command didn't returned the expected 0 return value.
39
40     return_value - Return value returned by the command.
41     """
42
43     def __init__(self, return_value):
44         r"Initialize the object. See class documentation for more info."
45         self.return_value = return_value
46
47     def __unicode__(self):
48         return 'The command returned %d' % self.return_value
49
50 class ExecutionError(Error):
51     r"""
52     ExecutionError(command, error) -> ExecutionError instance.
53
54     Error executing a command.
55
56     command - Command that was tried to execute.
57
58     error - Error received when trying to execute the command.
59     """
60
61     def __init__(self, command, error):
62         r"Initialize the object. See class documentation for more info."
63         self.command = command
64         self.error = error
65
66     def __unicode__(self):
67         command = self.command
68         if not isinstance(self.command, basestring):
69             command = ' '.join(command)
70         return "Can't execute command %s: %s" % (command, self.error)
71
72 class ParameterError(Error, KeyError):
73     r"""
74     ParameterError(paramname) -> ParameterError instance
75
76     This is the base exception for all DhcpHandler parameters related errors.
77     """
78
79     def __init__(self, paramname):
80         r"Initialize the object. See class documentation for more info."
81         self.message = 'Parameter error: "%s"' % paramname
82
83 class ParameterNotFoundError(ParameterError):
84     r"""
85     ParameterNotFoundError(paramname) -> ParameterNotFoundError instance
86
87     This exception is raised when trying to operate on a parameter that doesn't
88     exists.
89     """
90
91     def __init__(self, paramname):
92         r"Initialize the object. See class documentation for more info."
93         self.message = 'Parameter not found: "%s"' % paramname
94
95 class ItemError(Error, KeyError):
96     r"""
97     ItemError(key) -> ItemError instance.
98
99     This is the base exception for all item related errors.
100     """
101
102     def __init__(self, key):
103         r"Initialize the object. See class documentation for more info."
104         self.message = u'Item error: "%s"' % key
105
106 class ItemAlreadyExistsError(ItemError):
107     r"""
108     ItemAlreadyExistsError(key) -> ItemAlreadyExistsError instance.
109
110     This exception is raised when trying to add an item that already exists.
111     """
112
113     def __init__(self, key):
114         r"Initialize the object. See class documentation for more info."
115         self.message = u'Item already exists: "%s"' % key
116
117 class ItemNotFoundError(ItemError):
118     r"""
119     ItemNotFoundError(key) -> ItemNotFoundError instance
120
121     This exception is raised when trying to operate on an item that doesn't
122     exists.
123     """
124
125     def __init__(self, key):
126         r"Initialize the object. See class documentation for more info."
127         self.message = u'Item not found: "%s"' % key
128
129
130 def call(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
131             stderr=subprocess.PIPE, close_fds=True, universal_newlines=True,
132             **kw):
133     if DEBUG:
134         if not isinstance(command, basestring):
135             command = ' '.join(command)
136         print 'Executing command:', command
137         return
138     try:
139         r = subprocess.call(command, stdin=stdin, stdout=stdout, stderr=stderr,
140                                 universal_newlines=universal_newlines,
141                                 close_fds=close_fds, **kw)
142     except Exception, e:
143         raise ExecutionError(command, e)
144     if r is not 0:
145         raise ExecutionError(command, ReturnNot0Error(r))
146
147 class Persistent:
148     r"""Persistent([attrs[, dir[, ext]]]) -> Persistent.
149
150     This is a helper class to inherit from to automatically handle data
151     persistence using pickle.
152
153     The variables attributes to persist (attrs), and the pickle directory (dir)
154     and file extension (ext) can be defined by calling the constructor or in a
155     more declarative way as class attributes, like:
156
157     class TestHandler(Persistent):
158         _persistent_attrs = ('some_attr', 'other_attr')
159         _persistent_dir = 'persistent-data'
160         _persistent_ext = '.pickle'
161
162     The default dir is '.' and the default extension is '.pkl'. There are no
163     default variables, and they should be specified as string if a single
164     attribute should be persistent or as a tuple of strings if they are more.
165     The strings should be the attribute names to be persisted. For each
166     attribute a separated pickle file is generated in the pickle directory.
167
168     You can call _dump() and _load() to write and read the data respectively.
169     """
170     # TODO implement it using metaclasses to add the handlers method by demand
171     # (only for specifieds commands).
172
173     _persistent_attrs = ()
174     _persistent_dir = '.'
175     _persistent_ext = '.pkl'
176
177     def __init__(self, attrs=None, dir=None, ext=None):
178         r"Initialize the object, see the class documentation for details."
179         if attrs is not None:
180             self._persistent_attrs = attrs
181         if dir is not None:
182             self._persistent_dir = dir
183         if ext is not None:
184             self._persistent_ext = ext
185
186     def _dump(self):
187         r"_dump() -> None :: Dump all persistent data to pickle files."
188         if isinstance(self._persistent_attrs, basestring):
189             self._persistent_attrs = (self._persistent_attrs,)
190         for attrname in self._persistent_attrs:
191             self._dump_attr(attrname)
192
193     def _load(self):
194         r"_load() -> None :: Load all persistent data from pickle files."
195         if isinstance(self._persistent_attrs, basestring):
196             self._persistent_attrs = (self._persistent_attrs,)
197         for attrname in self._persistent_attrs:
198             self._load_attr(attrname)
199
200     def _dump_attr(self, attrname):
201         r"_dump_attr() -> None :: Dump a specific variable to a pickle file."
202         f = file(self._pickle_filename(attrname), 'wb')
203         pickle.dump(getattr(self, attrname), f, 2)
204         f.close()
205
206     def _load_attr(self, attrname):
207         r"_load_attr() -> object :: Load a specific pickle file."
208         f = file(self._pickle_filename(attrname))
209         setattr(self, attrname, pickle.load(f))
210         f.close()
211
212     def _pickle_filename(self, name):
213         r"_pickle_filename() -> string :: Construct a pickle filename."
214         return path.join(self._persistent_dir, name) + self._persistent_ext
215
216 class Restorable(Persistent):
217     r"""Restorable([defaults]) -> Restorable.
218
219     This is a helper class to inherit from that provides a nice _restore()
220     method to restore the persistent data if any, or load some nice defaults
221     if not.
222
223     The defaults can be defined by calling the constructor or in a more
224     declarative way as class attributes, like:
225
226     class TestHandler(Restorable):
227         _persistent_attrs = ('some_attr', 'other_attr')
228         _restorable_defaults = dict(
229                 some_attr = 'some_default',
230                 other_attr = 'other_default')
231
232     The defaults is a dictionary, very coupled with the _persistent_attrs
233     attribute inherited from Persistent. The defaults keys should be the
234     values from _persistent_attrs, and the values the default values.
235
236     The _restore() method returns True if the data was restored successfully
237     or False if the defaults were loaded (in case you want to take further
238     actions). If a _write_config method if found, it's executed when a restore
239     fails too.
240     """
241     # TODO implement it using metaclasses to add the handlers method by demand
242     # (only for specifieds commands).
243
244     _restorable_defaults = dict()
245
246     def __init__(self, defaults=None):
247         r"Initialize the object, see the class documentation for details."
248         if defaults is not None:
249             self._restorable_defaults = defaults
250
251     def _restore(self):
252         r"_restore() -> bool :: Restore persistent data or create a default."
253         try:
254             self._load()
255             # TODO tener en cuenta servicios que hay que levantar y los que no
256             if hasattr(self, 'commit'): # TODO deberia ser reload y/o algo para comandos
257                 self.commit()
258             return True
259         except IOError:
260             for (k, v) in self._restorable_defaults.items():
261                 setattr(self, k, v)
262             # TODO tener en cuenta servicios que hay que levantar y los que no
263             if hasattr(self, 'commit'):
264                 self.commit()
265                 return False
266             self._dump()
267             if hasattr(self, '_write_config'):
268                 self._write_config()
269             if hasattr(self, 'reload'):
270                 self.reload()
271             return False
272
273 class ConfigWriter:
274     r"""ConfigWriter([initd_name[, initd_dir]]) -> ConfigWriter.
275
276     This is a helper class to inherit from to automatically handle
277     configuration generation. Mako template system is used for configuration
278     files generation.
279
280     The configuration filenames, the generated configuration files directory
281     and the templates directory can be defined by calling the constructor or
282     in a more declarative way as class attributes, like:
283
284     class TestHandler(ConfigWriter):
285         _config_writer_files = ('base.conf', 'custom.conf')
286         _config_writer_cfg_dir = '/etc/service'
287         _config_writer_tpl_dir = 'templates'
288
289     The generated configuration files directory defaults to '.' and the
290     templates directory to 'templates'. _config_writer_files has no default and
291     must be specified in either way. It can be string or a tuple if more than
292     one configuration file must be generated.
293
294     The template filename and the generated configuration filename are both the
295     same (so if you want to generate some /etc/config, you should have some
296     templates/config template). That's why _config_writer_cfg_dir and
297     _config_writer_tpl_dir can't be the same.
298
299     When you write your Handler, you should call _config_build_templates() in
300     you Handler constructor to build the templates.
301
302     To write the configuration files, you must use the _write_config() method.
303     To know what variables to replace in the template, you have to provide a
304     method called _get_config_vars(tamplate_name), which should return a
305     dictionary of variables to pass to the template system to be replaced in
306     the template for the configuration file 'config_file'.
307     """
308     # TODO implement it using metaclasses to add the handlers method by demand
309     # (only for specifieds commands).
310
311     _config_writer_files = ()
312     _config_writer_cfg_dir = '.'
313     _config_writer_tpl_dir = 'templates'
314
315     def __init__(self, files=None, cfg_dir=None, tpl_dir=None):
316         r"Initialize the object, see the class documentation for details."
317         if files is not None:
318             self._config_writer_files = files
319         if cfg_dir is not None:
320             self._config_writer_cfg_dir = cfg_dir
321         if tpl_dir is not None:
322             self._config_writer_tpl_dir = tpl_dir
323         self._config_build_templates()
324
325     def _config_build_templates(self):
326         r"_config_writer_templates() -> None :: Build the template objects."
327         if isinstance(self._config_writer_files, basestring):
328             self._config_writer_files = (self._config_writer_files,)
329         if not hasattr(self, '_config_writer_templates') \
330                                         or not self._config_writer_templates:
331             self._config_writer_templates = dict()
332             for t in self._config_writer_files:
333                 f = path.join(self._config_writer_tpl_dir, t)
334                 self._config_writer_templates[t] = Template(filename=f)
335
336     def _render_config(self, template_name, vars=None):
337         r"""_render_config(template_name[, config_filename[, vars]]).
338
339         Render a single config file using the template 'template_name'. If
340         vars is specified, it's used as the dictionary with the variables
341         to replace in the templates, if not, it looks for a
342         _get_config_vars() method to get it.
343         """
344         if vars is None:
345             if hasattr(self, '_get_config_vars'):
346                 vars = self._get_config_vars(template_name)
347             else:
348                 vars = dict()
349         elif callable(vars):
350             vars = vars(template_name)
351         return self._config_writer_templates[template_name].render(**vars)
352
353     def _write_single_config(self, template_name, config_filename=None, vars=None):
354         r"""_write_single_config(template_name[, config_filename[, vars]]).
355
356         Write a single config file using the template 'template_name'. If no
357         config_filename is specified, the config filename will be the same as
358         the 'template_name' (but stored in the generated config files
359         directory). If it's specified, the generated config file is stored in
360         the file called 'config_filename' (also in the generated files
361         directory). If vars is specified, it's used as the dictionary with the
362         variables to replace in the templates, if not, it looks for a
363         _get_config_vars() method to get it.
364         """
365         if not config_filename:
366             config_filename = template_name
367         if vars is None:
368             if hasattr(self, '_get_config_vars'):
369                 vars = self._get_config_vars(template_name)
370             else:
371                 vars = dict()
372         elif callable(vars):
373             vars = vars(template_name)
374         f = file(path.join(self._config_writer_cfg_dir, config_filename), 'w')
375         ctx = Context(f, **vars)
376         self._config_writer_templates[template_name].render_context(ctx)
377         f.close()
378
379     def _write_config(self):
380         r"_write_config() -> None :: Generate all the configuration files."
381         for t in self._config_writer_files:
382             self._write_single_config(t)
383
384
385 class ServiceHandler(Handler):
386     r"""ServiceHandler([start[, stop[, restart[, reload]]]]) -> ServiceHandler.
387
388     This is a helper class to inherit from to automatically handle services
389     with start, stop, restart, reload actions.
390
391     The actions can be defined by calling the constructor with all the
392     parameters or in a more declarative way as class attributes, like:
393
394     class TestHandler(ServiceHandler):
395         _service_start = ('command', 'start')
396         _service_stop = ('command', 'stop')
397         _service_restart = ('command', 'restart')
398         _service_reload = 'reload-command'
399
400     Commands are executed without using the shell, that's why they are specified
401     as tuples (where the first element is the command and the others are the
402     command arguments). If only a command is needed (without arguments) a single
403     string can be specified.
404
405     All commands must be specified.
406     """
407     # TODO implement it using metaclasses to add the handlers method by demand
408     # (only for specifieds commands).
409
410     def __init__(self, start=None, stop=None, restart=None, reload=None):
411         r"Initialize the object, see the class documentation for details."
412         for (name, action) in dict(start=start, stop=stop, restart=restart,
413                                                     reload=reload).items():
414             if action is not None:
415                 setattr(self, '_service_%s' % name, action)
416
417     @handler(u'Start the service.')
418     def start(self):
419         r"start() -> None :: Start the service."
420         call(self._service_start)
421
422     @handler(u'Stop the service.')
423     def stop(self):
424         r"stop() -> None :: Stop the service."
425         call(self._service_stop)
426
427     @handler(u'Restart the service.')
428     def restart(self):
429         r"restart() -> None :: Restart the service."
430         call(self._service_restart)
431
432     @handler(u'Reload the service config (without restarting, if possible).')
433     def reload(self):
434         r"reload() -> None :: Reload the configuration of the service."
435         call(self._service_reload)
436
437 class InitdHandler(Handler):
438     r"""InitdHandler([initd_name[, initd_dir]]) -> InitdHandler.
439
440     This is a helper class to inherit from to automatically handle services
441     with start, stop, restart, reload actions using a /etc/init.d like script.
442
443     The name and directory of the script can be defined by calling the
444     constructor or in a more declarative way as class attributes, like:
445
446     class TestHandler(ServiceHandler):
447         _initd_name = 'some-service'
448         _initd_dir = '/usr/local/etc/init.d'
449
450     The default _initd_dir is '/etc/init.d', _initd_name has no default and
451     must be specified in either way.
452
453     Commands are executed without using the shell.
454     """
455     # TODO implement it using metaclasses to add the handlers method by demand
456     # (only for specifieds commands).
457
458     _initd_dir = '/etc/init.d'
459
460     def __init__(self, initd_name=None, initd_dir=None):
461         r"Initialize the object, see the class documentation for details."
462         if initd_name is not None:
463             self._initd_name = initd_name
464         if initd_dir is not None:
465             self._initd_dir = initd_dir
466
467     @handler(u'Start the service.')
468     def start(self):
469         r"start() -> None :: Start the service."
470         call((path.join(self._initd_dir, self._initd_name), 'start'))
471
472     @handler(u'Stop the service.')
473     def stop(self):
474         r"stop() -> None :: Stop the service."
475         call((path.join(self._initd_dir, self._initd_name), 'stop'))
476
477     @handler(u'Restart the service.')
478     def restart(self):
479         r"restart() -> None :: Restart the service."
480         call((path.join(self._initd_dir, self._initd_name), 'restart'))
481
482     @handler(u'Reload the service config (without restarting, if possible).')
483     def reload(self):
484         r"reload() -> None :: Reload the configuration of the service."
485         call((path.join(self._initd_dir, self._initd_name), 'reload'))
486
487 class TransactionalHandler(Handler):
488     r"""Handle command transactions providing a commit and rollback commands.
489
490     This is a helper class to inherit from to automatically handle
491     transactional handlers, which have commit and rollback commands.
492
493     The handler should provide a reload() method (see ServiceHandler and
494     InitdHandler for helper classes to provide this) which will be called
495     when a commit command is issued (if a reload() command is present).
496     The persistent data will be written too (if a _dump() method is provided,
497     see Persistent and Restorable for that), and the configuration files
498     will be generated (if a _write_config method is present, see ConfigWriter).
499     """
500     # TODO implement it using metaclasses to add the handlers method by demand
501     # (only for specifieds commands).
502
503     @handler(u'Commit the changes (reloading the service, if necessary).')
504     def commit(self):
505         r"commit() -> None :: Commit the changes and reload the service."
506         if hasattr(self, '_dump'):
507             self._dump()
508         if hasattr(self, '_write_config'):
509             self._write_config()
510         if hasattr(self, 'reload'):
511             self.reload()
512
513     @handler(u'Discard all the uncommited changes.')
514     def rollback(self):
515         r"rollback() -> None :: Discard the changes not yet commited."
516         if hasattr(self, '_load'):
517             self._load()
518
519 class ParametersHandler(Handler):
520     r"""ParametersHandler([attr]) -> ParametersHandler.
521
522     This is a helper class to inherit from to automatically handle
523     service parameters, providing set, get, list and show commands.
524
525     The attribute that holds the parameters can be defined by calling the
526     constructor or in a more declarative way as class attributes, like:
527
528     class TestHandler(ServiceHandler):
529         _parameters_attr = 'some_attr'
530
531     The default is 'params' and it should be a dictionary.
532     """
533     # TODO implement it using metaclasses to add the handlers method by demand
534     # (only for specifieds commands).
535
536     _parameters_attr = 'params'
537
538     def __init__(self, attr=None):
539         r"Initialize the object, see the class documentation for details."
540         if attr is not None:
541             self._parameters_attr = attr
542
543     @handler(u'Set a service parameter.')
544     def set(self, param, value):
545         r"set(param, value) -> None :: Set a service parameter."
546         if not param in self.params:
547             raise ParameterNotFoundError(param)
548         self.params[param] = value
549
550     @handler(u'Get a service parameter.')
551     def get(self, param):
552         r"get(param) -> None :: Get a service parameter."
553         if not param in self.params:
554             raise ParameterNotFoundError(param)
555         return self.params[param]
556
557     @handler(u'List all available service parameters.')
558     def list(self):
559         r"list() -> tuple :: List all the parameter names."
560         return self.params.keys()
561
562     @handler(u'Get all service parameters, with their values.')
563     def show(self):
564         r"show() -> (key, value) tuples :: List all the parameters."
565         return self.params.items()
566
567 class SubHandler(Handler):
568     r"""SubHandler(parent) -> SubHandler instance :: Handles subcommands.
569
570     This is a helper class to build sub handlers that needs to reference the
571     parent handler.
572
573     parent - Parent Handler object.
574     """
575
576     def __init__(self, parent):
577         r"Initialize the object, see the class documentation for details."
578         self.parent = parent
579
580 class ListSubHandler(SubHandler):
581     r"""ListSubHandler(parent) -> ListSubHandler instance.
582
583     This is a helper class to inherit from to automatically handle subcommands
584     that operates over a list parent attribute.
585
586     The list attribute to handle and the class of objects that it contains can
587     be defined by calling the constructor or in a more declarative way as
588     class attributes, like:
589
590     class TestHandler(ListSubHandler):
591         _list_subhandler_attr = 'some_list'
592         _list_subhandler_class = SomeClass
593
594     This way, the parent's some_list attribute (self.parent.some_list) will be
595     managed automatically, providing the commands: add, update, delete, get,
596     list and show. New items will be instances of SomeClass, which should
597     provide a cmp operator to see if the item is on the list and an update()
598     method, if it should be possible to modify it.
599     """
600
601     def __init__(self, parent, attr=None, cls=None):
602         r"Initialize the object, see the class documentation for details."
603         self.parent = parent
604         if attr is not None:
605             self._list_subhandler_attr = attr
606         if cls is not None:
607             self._list_subhandler_class = cls
608
609     def _list(self):
610         return getattr(self.parent, self._list_subhandler_attr)
611
612     @handler(u'Add a new item')
613     def add(self, *args, **kwargs):
614         r"add(...) -> None :: Add an item to the list."
615         item = self._list_subhandler_class(*args, **kwargs)
616         if item in self._list():
617             raise ItemAlreadyExistsError(item)
618         self._list().append(item)
619
620     @handler(u'Update an item')
621     def update(self, index, *args, **kwargs):
622         r"update(index, ...) -> None :: Update an item of the list."
623         # TODO make it right with metaclasses, so the method is not created
624         # unless the update() method really exists.
625         # TODO check if the modified item is the same of an existing one
626         index = int(index) # TODO validation
627         if not hasattr(self._list_subhandler_class, 'update'):
628             raise CommandNotFoundError(('update',))
629         try:
630             self._list()[index].update(*args, **kwargs)
631         except IndexError:
632             raise ItemNotFoundError(index)
633
634     @handler(u'Delete an item')
635     def delete(self, index):
636         r"delete(index) -> None :: Delete an item of the list."
637         index = int(index) # TODO validation
638         try:
639             return self._list().pop(index)
640         except IndexError:
641             raise ItemNotFoundError(index)
642
643     @handler(u'Get information about an item')
644     def get(self, index):
645         r"get(index) -> Host :: List all the information of an item."
646         index = int(index) # TODO validation
647         try:
648             return self._list()[index]
649         except IndexError:
650             raise ItemNotFoundError(index)
651
652     @handler(u'Get how many items are in the list')
653     def len(self):
654         r"len() -> int :: Get how many items are in the list."
655         return len(self._list())
656
657     @handler(u'Get information about all items')
658     def show(self):
659         r"show() -> list of Hosts :: List all the complete items information."
660         return self._list()
661
662 class DictSubHandler(SubHandler):
663     r"""DictSubHandler(parent) -> DictSubHandler instance.
664
665     This is a helper class to inherit from to automatically handle subcommands
666     that operates over a dict parent attribute.
667
668     The dict attribute to handle and the class of objects that it contains can
669     be defined by calling the constructor or in a more declarative way as
670     class attributes, like:
671
672     class TestHandler(DictSubHandler):
673         _dict_subhandler_attr = 'some_dict'
674         _dict_subhandler_class = SomeClass
675
676     This way, the parent's some_dict attribute (self.parent.some_dict) will be
677     managed automatically, providing the commands: add, update, delete, get,
678     list and show. New items will be instances of SomeClass, which should
679     provide a constructor with at least the key value and an update() method,
680     if it should be possible to modify it.
681     """
682
683     def __init__(self, parent, attr=None, cls=None):
684         r"Initialize the object, see the class documentation for details."
685         self.parent = parent
686         if attr is not None:
687             self._dict_subhandler_attr = attr
688         if cls is not None:
689             self._dict_subhandler_class = cls
690
691     def _dict(self):
692         return getattr(self.parent, self._dict_subhandler_attr)
693
694     @handler(u'Add a new item')
695     def add(self, key, *args, **kwargs):
696         r"add(key, ...) -> None :: Add an item to the dict."
697         item = self._dict_subhandler_class(key, *args, **kwargs)
698         if key in self._dict():
699             raise ItemAlreadyExistsError(key)
700         self._dict()[key] = item
701
702     @handler(u'Update an item')
703     def update(self, key, *args, **kwargs):
704         r"update(key, ...) -> None :: Update an item of the dict."
705         # TODO make it right with metaclasses, so the method is not created
706         # unless the update() method really exists.
707         if not hasattr(self._dict_subhandler_class, 'update'):
708             raise CommandNotFoundError(('update',))
709         if not key in self._dict():
710             raise ItemNotFoundError(key)
711         self._dict()[key].update(*args, **kwargs)
712
713     @handler(u'Delete an item')
714     def delete(self, key):
715         r"delete(key) -> None :: Delete an item of the dict."
716         if not key in self._dict():
717             raise ItemNotFoundError(key)
718         del self._dict()[key]
719
720     @handler(u'Get information about an item')
721     def get(self, key):
722         r"get(key) -> Host :: List all the information of an item."
723         if not key in self._dict():
724             raise ItemNotFoundError(key)
725         return self._dict()[key]
726
727     @handler(u'List all the items by key')
728     def list(self):
729         r"list() -> tuple :: List all the item keys."
730         return self._dict().keys()
731
732     @handler(u'Get information about all items')
733     def show(self):
734         r"show() -> list of Hosts :: List all the complete items information."
735         return self._dict().values()
736
737
738 if __name__ == '__main__':
739
740     # Execution tests
741     class STestHandler1(ServiceHandler):
742         _service_start = ('service', 'start')
743         _service_stop = ('service', 'stop')
744         _service_restart = ('ls', '/')
745         _service_reload = ('cp', '/la')
746     class STestHandler2(ServiceHandler):
747         def __init__(self):
748             ServiceHandler.__init__(self, 'cmd-start', 'cmd-stop',
749                                         'cmd-restart', 'cmd-reload')
750     class ITestHandler1(InitdHandler):
751         _initd_name = 'test1'
752     class ITestHandler2(InitdHandler):
753         def __init__(self):
754             InitdHandler.__init__(self, 'test2', '/usr/local/etc/init.d')
755     handlers = [
756         STestHandler1(),
757         STestHandler2(),
758         ITestHandler1(),
759         ITestHandler2(),
760     ]
761     for h in handlers:
762         print h.__class__.__name__
763         try:
764             h.start()
765         except ExecutionError, e:
766             print e
767         try:
768             h.stop()
769         except ExecutionError, e:
770             print e
771         try:
772             h.restart()
773         except ExecutionError, e:
774             print e
775         try:
776             h.reload()
777         except ExecutionError, e:
778             print e
779         print
780
781     # Persistent test
782     print 'PTestHandler'
783     class PTestHandler(Persistent):
784         _persistent_attrs = 'vars'
785         def __init__(self):
786             self.vars = dict(a=1, b=2)
787     h = PTestHandler()
788     print h.vars
789     h._dump()
790     h.vars['x'] = 100
791     print h.vars
792     h._load()
793     print h.vars
794     h.vars['x'] = 100
795     h._dump()
796     print h.vars
797     del h.vars['x']
798     print h.vars
799     h._load()
800     print h.vars
801     print
802
803     # Restorable test
804     print 'RTestHandler'
805     class RTestHandler(Restorable):
806         _persistent_attrs = 'vars'
807         _restorable_defaults = dict(vars=dict(a=1, b=2))
808         def __init__(self):
809             self._restore()
810     h = RTestHandler()
811     print h.vars
812     h.vars['x'] = 100
813     h._dump()
814     h = RTestHandler()
815     print h.vars
816     print
817
818     # ConfigWriter test
819     print 'CTestHandler'
820     import os
821     os.mkdir('templates')
822     f = file('templates/config', 'w')
823     f.write('Hello, ${name}! You are ${what}.')
824     f.close()
825     print 'template:'
826     print file('templates/config').read()
827     class CTestHandler(ConfigWriter):
828         _config_writer_files = 'config'
829         def __init__(self):
830             self._config_build_templates()
831         def _get_config_vars(self, config_file):
832             return dict(name='you', what='a parrot')
833     h = CTestHandler()
834     h._write_config()
835     print 'config:'
836     print file('config').read()
837     os.unlink('config')
838     os.unlink('templates/config')
839     os.rmdir('templates')
840     print
841