1 # vim: set encoding=utf-8 et sw=4 sts=4 :
4 from mako.template import Template
5 from mako.runtime import Context
8 import cPickle as pickle
11 import logging ; log = logging.getLogger('pymin.services.util')
13 from pymin.dispatcher import Handler, handler, HandlerError, \
15 from pymin.seqtools import Sequence
20 __ALL__ = ('Error', 'ExecutionError', 'ItemError', 'ItemAlreadyExistsError',
21 'ItemNotFoundError', 'ContainerError', 'ContainerNotFoundError',
22 'call', 'get_network_devices', 'Persistent', 'Restorable',
23 'ConfigWriter', 'ServiceHandler', 'RestartHandler',
24 'ReloadHandler', 'InitdHandler', 'SubHandler', 'DictSubHandler',
25 'ListSubHandler', 'ComposedSubHandler', 'ListComposedSubHandler',
26 'DictComposedSubHandler', 'Device','Address')
28 class Error(HandlerError):
30 Error(message) -> Error instance :: Base ServiceHandler exception class.
32 All exceptions raised by the ServiceHandler inherits from this one, so
33 you can easily catch any ServiceHandler exception.
35 message - A descriptive error message.
39 class ExecutionError(Error):
41 ExecutionError(command, error) -> ExecutionError instance.
43 Error executing a command.
45 command - Command that was tried to execute.
47 error - Error received when trying to execute the command.
50 def __init__(self, command, error):
51 r"Initialize the object. See class documentation for more info."
52 self.command = command
55 def __unicode__(self):
56 command = self.command
57 if not isinstance(self.command, basestring):
58 command = ' '.join(command)
59 return "Can't execute command %s: %s" % (command, self.error)
61 class ParameterError(Error, KeyError):
63 ParameterError(paramname) -> ParameterError instance
65 This is the base exception for all DhcpHandler parameters related errors.
68 def __init__(self, paramname):
69 r"Initialize the object. See class documentation for more info."
70 self.message = 'Parameter error: "%s"' % paramname
72 class ParameterNotFoundError(ParameterError):
74 ParameterNotFoundError(paramname) -> ParameterNotFoundError instance
76 This exception is raised when trying to operate on a parameter that doesn't
80 def __init__(self, paramname):
81 r"Initialize the object. See class documentation for more info."
82 self.message = 'Parameter not found: "%s"' % paramname
84 class ItemError(Error, KeyError):
86 ItemError(key) -> ItemError instance.
88 This is the base exception for all item related errors.
91 def __init__(self, key):
92 r"Initialize the object. See class documentation for more info."
93 self.message = u'Item error: "%s"' % key
95 class ItemAlreadyExistsError(ItemError):
97 ItemAlreadyExistsError(key) -> ItemAlreadyExistsError instance.
99 This exception is raised when trying to add an item that already exists.
102 def __init__(self, key):
103 r"Initialize the object. See class documentation for more info."
104 self.message = u'Item already exists: "%s"' % key
106 class ItemNotFoundError(ItemError):
108 ItemNotFoundError(key) -> ItemNotFoundError instance.
110 This exception is raised when trying to operate on an item that doesn't
114 def __init__(self, key):
115 r"Initialize the object. See class documentation for more info."
116 self.message = u'Item not found: "%s"' % key
118 class ContainerError(Error, KeyError):
120 ContainerError(key) -> ContainerError instance.
122 This is the base exception for all container related errors.
125 def __init__(self, key):
126 r"Initialize the object. See class documentation for more info."
127 self.message = u'Container error: "%s"' % key
129 class ContainerNotFoundError(ContainerError):
131 ContainerNotFoundError(key) -> ContainerNotFoundError instance.
133 This exception is raised when trying to operate on an container that
137 def __init__(self, key):
138 r"Initialize the object. See class documentation for more info."
139 self.message = u'Container not found: "%s"' % key
141 class Address(Sequence):
142 def __init__(self, ip, netmask, broadcast=None, peer=None):
144 self.netmask = netmask
145 self.broadcast = broadcast
147 def update(self, netmask=None, broadcast=None):
148 if netmask is not None: self.netmask = netmask
149 if broadcast is not None: self.broadcast = broadcast
151 return (self.ip, self.netmask, self.broadcast, self.peer)
154 class Device(Sequence):
155 def __init__(self, name, mac, ppp):
162 return (self.name, self.mac, self.addrs)
166 def get_network_devices():
167 p = subprocess.Popen(('ip', '-o', 'link'), stdout=subprocess.PIPE,
169 string = p.stdout.read()
172 devices = string.splitlines()
175 if dev.find('link/ether') != -1:
176 i = dev.find('link/ether')
177 mac = dev[i+11 : i+11+17]
179 j = dev.find(':', i+1)
181 d[name] = Device(name,mac,False)
182 elif dev.find('link/ppp') != -1:
183 i = dev.find('link/ppp')
184 mac = '00:00:00:00:00:00'
186 j = dev.find(':', i+1)
188 d[name] = Device(name,mac,True)
189 #since the device is ppp, get the address and peer
191 p = subprocess.Popen(('ip', '-o', 'addr', 'show', name), stdout=subprocess.PIPE,
192 close_fds=True, stderr=subprocess.PIPE)
193 string = p.stdout.read()
195 addrs = string.splitlines()
196 inet = addrs[1].find('inet')
197 peer = addrs[1].find('peer')
198 bar = addrs[1].find('/')
199 from_addr = addrs[1][inet+5 : peer-1]
200 to_addr = addrs[1][peer+5 : bar]
201 d[name].addrs[from_addr] = Address(from_addr,24, peer=to_addr)
207 p = subprocess.Popen(('ip', '-o', 'addr'), stdout=subprocess.PIPE,
210 def call(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
211 stderr=subprocess.PIPE, close_fds=True, universal_newlines=True,
213 log.debug(u'call(%r)', command)
215 log.debug(u'call: not really executing, DEBUG mode')
218 subprocess.check_call(command, stdin=stdin, stdout=stdout,
219 stderr=stderr, close_fds=close_fds,
220 universal_newlines=universal_newlines, **kw)
222 log.debug(u'call: Execution error %r', e)
223 raise ExecutionError(command, e)
226 r"""Persistent([attrs[, dir[, ext]]]) -> Persistent.
228 This is a helper class to inherit from to automatically handle data
229 persistence using pickle.
231 The variables attributes to persist (attrs), and the pickle directory (dir)
232 and file extension (ext) can be defined by calling the constructor or in a
233 more declarative way as class attributes, like:
235 class TestHandler(Persistent):
236 _persistent_attrs = ('some_attr', 'other_attr')
237 _persistent_dir = 'persistent-data'
238 _persistent_ext = '.pickle'
240 The default dir is '.' and the default extension is '.pkl'. There are no
241 default variables, and they should be specified as string if a single
242 attribute should be persistent or as a tuple of strings if they are more.
243 The strings should be the attribute names to be persisted. For each
244 attribute a separated pickle file is generated in the pickle directory.
246 You can call _dump() and _load() to write and read the data respectively.
248 # TODO implement it using metaclasses to add the handlers method by demand
249 # (only for specifieds commands).
251 _persistent_attrs = ()
252 _persistent_dir = '.'
253 _persistent_ext = '.pkl'
255 def __init__(self, attrs=None, dir=None, ext=None):
256 r"Initialize the object, see the class documentation for details."
257 if attrs is not None:
258 self._persistent_attrs = attrs
260 self._persistent_dir = dir
262 self._persistent_ext = ext
265 r"_dump() -> None :: Dump all persistent data to pickle files."
266 if isinstance(self._persistent_attrs, basestring):
267 self._persistent_attrs = (self._persistent_attrs,)
268 for attrname in self._persistent_attrs:
269 self._dump_attr(attrname)
272 r"_load() -> None :: Load all persistent data from pickle files."
273 if isinstance(self._persistent_attrs, basestring):
274 self._persistent_attrs = (self._persistent_attrs,)
275 for attrname in self._persistent_attrs:
276 self._load_attr(attrname)
278 def _dump_attr(self, attrname):
279 r"_dump_attr() -> None :: Dump a specific variable to a pickle file."
280 fname = self._pickle_filename(attrname)
281 attr = getattr(self, attrname)
282 log.debug(u'Persistent._dump_attr(%r) -> file=%r, value=%r',
283 attrname, fname, attr)
284 f = file(self._pickle_filename(attrname), 'wb')
285 pickle.dump(attr, f, 2)
288 def _load_attr(self, attrname):
289 r"_load_attr() -> object :: Load a specific pickle file."
290 fname = self._pickle_filename(attrname)
292 attr = pickle.load(f)
293 log.debug(u'Persistent._load_attr(%r) -> file=%r, value=%r',
294 attrname, fname, attr)
295 setattr(self, attrname, attr)
298 def _pickle_filename(self, name):
299 r"_pickle_filename() -> string :: Construct a pickle filename."
300 return path.join(self._persistent_dir, name) + self._persistent_ext
302 class Restorable(Persistent):
303 r"""Restorable([defaults]) -> Restorable.
305 This is a helper class to inherit from that provides a nice _restore()
306 method to restore the persistent data if any, or load some nice defaults
309 The defaults can be defined by calling the constructor or in a more
310 declarative way as class attributes, like:
312 class TestHandler(Restorable):
313 _persistent_attrs = ('some_attr', 'other_attr')
314 _restorable_defaults = dict(
315 some_attr = 'some_default',
316 other_attr = 'other_default')
318 The defaults is a dictionary, very coupled with the _persistent_attrs
319 attribute inherited from Persistent. The defaults keys should be the
320 values from _persistent_attrs, and the values the default values.
322 The _restore() method returns True if the data was restored successfully
323 or False if the defaults were loaded (in case you want to take further
324 actions). If a _write_config method if found, it's executed when a restore
327 # TODO implement it using metaclasses to add the handlers method by demand
328 # (only for specifieds commands).
330 _restorable_defaults = dict()
332 def __init__(self, defaults=None):
333 r"Initialize the object, see the class documentation for details."
334 if defaults is not None:
335 self._restorable_defaults = defaults
338 r"_restore() -> bool :: Restore persistent data or create a default."
339 log.debug(u'Restorable._restore()')
341 log.debug(u'Restorable._restore: trying to load...')
343 log.debug(u'Restorable._restore: load OK')
346 log.debug(u'Restorable._restore: load FAILED, making defaults: %r',
347 self._restorable_defaults)
348 for (k, v) in self._restorable_defaults.items():
350 # TODO tener en cuenta servicios que hay que levantar y los que no
351 if hasattr(self, 'commit'):
352 log.debug(u'Restorable._restore: commit() found, commiting...')
355 log.debug(u'Restorable._restore: dumping new defaults...')
357 if hasattr(self, '_write_config'):
358 log.debug(u'Restorable._restore: _write_config() found, '
359 u'writing new config...')
364 r"""ConfigWriter([initd_name[, initd_dir]]) -> ConfigWriter.
366 This is a helper class to inherit from to automatically handle
367 configuration generation. Mako template system is used for configuration
370 The configuration filenames, the generated configuration files directory
371 and the templates directory can be defined by calling the constructor or
372 in a more declarative way as class attributes, like:
374 class TestHandler(ConfigWriter):
375 _config_writer_files = ('base.conf', 'custom.conf')
376 _config_writer_cfg_dir = {
377 'base.conf': '/etc/service',
378 'custom.conf': '/etc/service/conf.d',
380 _config_writer_tpl_dir = 'templates'
382 The generated configuration files directory defaults to '.' and the
383 templates directory to 'templates'. _config_writer_files has no default and
384 must be specified in either way. It can be string or a tuple if more than
385 one configuration file must be generated. _config_writer_cfg_dir could be a
386 dict mapping which file should be stored in which directory, or a single
387 string if all the config files should go to the same directory.
389 The template filename and the generated configuration filename are both the
390 same (so if you want to generate some /etc/config, you should have some
391 templates/config template). That's why _config_writer_cfg_dir and
392 _config_writer_tpl_dir can't be the same. This is not true for very
393 specific cases where _write_single_config() is used.
395 When you write your Handler, you should call _config_build_templates() in
396 you Handler constructor to build the templates.
398 To write the configuration files, you must use the _write_config() method.
399 To know what variables to replace in the template, you have to provide a
400 method called _get_config_vars(tamplate_name), which should return a
401 dictionary of variables to pass to the template system to be replaced in
402 the template for the configuration file 'config_file'.
404 # TODO implement it using metaclasses to add the handlers method by demand
405 # (only for specifieds commands).
407 _config_writer_files = ()
408 _config_writer_cfg_dir = '.'
409 _config_writer_tpl_dir = 'templates'
411 def __init__(self, files=None, cfg_dir=None, tpl_dir=None):
412 r"Initialize the object, see the class documentation for details."
413 if files is not None:
414 self._config_writer_files = files
415 if cfg_dir is not None:
416 self._config_writer_cfg_dir = cfg_dir
417 if tpl_dir is not None:
418 self._config_writer_tpl_dir = tpl_dir
419 self._config_build_templates()
421 def _config_build_templates(self):
422 r"_config_writer_templates() -> None :: Build the template objects."
423 log.debug(u'ConfigWriter.build_templates()')
424 if isinstance(self._config_writer_files, basestring):
425 self._config_writer_files = (self._config_writer_files,)
426 if not hasattr(self, '_config_writer_templates') \
427 or not self._config_writer_templates:
428 self._config_writer_templates = dict()
429 for t in self._config_writer_files:
430 f = path.join(self._config_writer_tpl_dir, t)
431 self._config_writer_templates[t] = Template(filename=f)
433 def _render_config(self, template_name, vars=None):
434 r"""_render_config(template_name[, config_filename[, vars]]).
436 Render a single config file using the template 'template_name'. If
437 vars is specified, it's used as the dictionary with the variables
438 to replace in the templates, if not, it looks for a
439 _get_config_vars() method to get it.
442 if hasattr(self, '_get_config_vars'):
443 vars = self._get_config_vars(template_name)
447 vars = vars(template_name)
448 log.debug(u'ConfigWriter._render_config: rendering template %r with '
449 u'vars=%r', template_name, vars)
450 return self._config_writer_templates[template_name].render(**vars)
452 def _get_config_path(self, template_name, config_filename=None):
453 r"Get a complete configuration path."
454 if not config_filename:
455 config_filename = template_name
456 if isinstance(self._config_writer_cfg_dir, basestring):
457 return path.join(self._config_writer_cfg_dir, config_filename)
458 return path.join(self._config_writer_cfg_dir[template_name],
461 def _write_single_config(self, template_name, config_filename=None, vars=None):
462 r"""_write_single_config(template_name[, config_filename[, vars]]).
464 Write a single config file using the template 'template_name'. If no
465 config_filename is specified, the config filename will be the same as
466 the 'template_name' (but stored in the generated config files
467 directory). If it's specified, the generated config file is stored in
468 the file called 'config_filename' (also in the generated files
469 directory). If vars is specified, it's used as the dictionary with the
470 variables to replace in the templates, if not, it looks for a
471 _get_config_vars() method to get it.
474 if hasattr(self, '_get_config_vars'):
475 vars = self._get_config_vars(template_name)
479 vars = vars(template_name)
480 fname = self._get_config_path(template_name, config_filename)
482 ctx = Context(f, **vars)
483 log.debug(u'ConfigWriter._write_single_config: rendering template '
484 u'%r with vars=%r to file %r', template_name, vars, fname)
485 self._config_writer_templates[template_name].render_context(ctx)
488 def _write_config(self):
489 r"_write_config() -> None :: Generate all the configuration files."
490 for t in self._config_writer_files:
491 self._write_single_config(t)
494 class ServiceHandler(Handler, Restorable):
495 r"""ServiceHandler([start[, stop[, restart[, reload]]]]) -> ServiceHandler.
497 This is a helper class to inherit from to automatically handle services
498 with start, stop, restart, reload actions.
500 The actions can be defined by calling the constructor with all the
501 parameters or in a more declarative way as class attributes, like:
503 class TestHandler(ServiceHandler):
504 _service_start = ('command', 'start')
505 _service_stop = ('command', 'stop')
506 _service_restart = ('command', 'restart')
507 _service_reload = 'reload-command'
509 Commands are executed without using the shell, that's why they are specified
510 as tuples (where the first element is the command and the others are the
511 command arguments). If only a command is needed (without arguments) a single
512 string can be specified.
514 All commands must be specified.
516 # TODO implement it using metaclasses to add the handlers method by demand
517 # (only for specifieds commands).
519 def __init__(self, start=None, stop=None, restart=None, reload=None):
520 r"Initialize the object, see the class documentation for details."
521 log.debug(u'ServiceHandler(%r, %r, %r, %r)', start, stop, restart,
523 for (name, action) in dict(start=start, stop=stop, restart=restart,
524 reload=reload).items():
525 if action is not None:
526 attr_name = '_service_%s' % name
527 log.debug(u'ServiceHandler: using %r as %s', attr_name, action)
528 setattr(self, attr_name, action)
529 self._persistent_attrs = list(self._persistent_attrs)
530 self._persistent_attrs.append('_service_running')
531 if '_service_running' not in self._restorable_defaults:
532 self._restorable_defaults['_service_running'] = False
533 log.debug(u'ServiceHandler: restoring service configuration...')
535 if self._service_running:
536 log.debug(u'ServiceHandler: service was running, starting it...')
537 self._service_running = False
540 @handler(u'Start the service.')
542 r"start() -> None :: Start the service."
543 log.debug(u'ServiceHandler.start()')
544 if not self._service_running:
545 log.debug(u'ServiceHandler.start: not running, starting it...')
546 if callable(self._service_start):
547 self._service_start()
549 call(self._service_start)
550 self._service_running = True
551 self._dump_attr('_service_running')
553 @handler(u'Stop the service.')
555 log.debug(u'ServiceHandler.stop()')
556 r"stop() -> None :: Stop the service."
557 if self._service_running:
558 log.debug(u'ServiceHandler.stop: running, stoping it...')
559 if callable(self._service_stop):
562 call(self._service_stop)
563 self._service_running = False
564 self._dump_attr('_service_running')
566 @handler(u'Restart the service.')
568 r"restart() -> None :: Restart the service."
569 log.debug(u'ServiceHandler.restart()')
570 if callable(self._service_restart):
571 self._service_restart()
573 call(self._service_restart)
574 self._service_running = True
575 self._dump_attr('_service_running')
577 @handler(u'Reload the service config (without restarting, if possible).')
579 r"reload() -> None :: Reload the configuration of the service."
580 log.debug(u'ServiceHandler.reload()')
581 if self._service_running:
582 log.debug(u'ServiceHandler.reload: running, reloading...')
583 if callable(self._service_reload):
584 self._service_reload()
586 call(self._service_reload)
588 @handler(u'Tell if the service is running.')
590 r"reload() -> None :: Reload the configuration of the service."
591 log.debug(u'ServiceHandler.running() -> %r', self._service_running)
592 if self._service_running:
597 class RestartHandler(Handler):
598 r"""RestartHandler() -> RestartHandler :: Provides generic restart command.
600 This is a helper class to inherit from to automatically add a restart
601 command that first stop the service and then starts it again (using start
602 and stop commands respectively).
605 @handler(u'Restart the service (alias to stop + start).')
607 r"restart() -> None :: Restart the service calling stop() and start()."
608 log.debug(u'RestartHandler.restart()')
612 class ReloadHandler(Handler):
613 r"""ReloadHandler() -> ReloadHandler :: Provides generic reload command.
615 This is a helper class to inherit from to automatically add a reload
616 command that calls restart.
619 @handler(u'Reload the service config (alias to restart).')
621 r"reload() -> None :: Reload the configuration of the service."
622 log.debug(u'ReloadHandler.reload()')
623 if hasattr(self, '_service_running') and self._service_running:
624 log.debug(u'ReloadHandler.reload: running, reloading...')
627 class InitdHandler(ServiceHandler):
628 # TODO update docs, declarative style is depracated
629 r"""InitdHandler([initd_name[, initd_dir]]) -> InitdHandler.
631 This is a helper class to inherit from to automatically handle services
632 with start, stop, restart, reload actions using a /etc/init.d like script.
634 The name and directory of the script can be defined by calling the
635 constructor or in a more declarative way as class attributes, like:
637 class TestHandler(ServiceHandler):
638 _initd_name = 'some-service'
639 _initd_dir = '/usr/local/etc/init.d'
641 The default _initd_dir is '/etc/init.d', _initd_name has no default and
642 must be specified in either way.
644 Commands are executed without using the shell.
646 # TODO implement it using metaclasses to add the handlers method by demand
647 # (only for specifieds commands).
649 _initd_dir = '/etc/init.d'
651 def __init__(self, initd_name=None, initd_dir=None):
652 r"Initialize the object, see the class documentation for details."
653 log.debug(u'InitdHandler(%r, %r)', initd_name, initd_dir)
654 if initd_name is not None:
655 self._initd_name = initd_name
656 if initd_dir is not None:
657 self._initd_dir = initd_dir
659 for action in ('start', 'stop', 'restart', 'reload'):
660 actions[action] = (path.join(self._initd_dir, self._initd_name),
662 ServiceHandler.__init__(self, **actions)
664 def handle_timer(self):
666 log.debug(u'InitdHandler.handle_timer(): self=%r', self)
667 p = subprocess.Popen(('pgrep', '-f', self._initd_name),
668 stdout=subprocess.PIPE)
669 pid = p.communicate()[0]
670 if p.returncode == 0 and len(pid) > 0:
671 log.debug(u'InitdHandler.handle_timer: pid present, running')
672 self._service_running = True
674 log.debug(u'InitdHandler.handle_timer: pid absent, NOT running')
675 self._service_running = False
677 class TransactionalHandler(Handler):
678 r"""Handle command transactions providing a commit and rollback commands.
680 This is a helper class to inherit from to automatically handle
681 transactional handlers, which have commit and rollback commands.
683 The handler should provide a reload() method (see ServiceHandler and
684 InitdHandler for helper classes to provide this) which will be called
685 when a commit command is issued (if a reload() command is present).
686 The persistent data will be written too (if a _dump() method is provided,
687 see Persistent and Restorable for that), and the configuration files
688 will be generated (if a _write_config method is present, see ConfigWriter).
690 # TODO implement it using metaclasses to add the handlers method by demand
691 # (only for specifieds commands).
693 @handler(u'Commit the changes (reloading the service, if necessary).')
695 r"commit() -> None :: Commit the changes and reload the service."
696 log.debug(u'TransactionalHandler.commit()')
697 if hasattr(self, '_dump'):
698 log.debug(u'TransactionalHandler.commit: _dump() present, '
702 if hasattr(self, '_write_config'):
703 log.debug(u'TransactionalHandler.commit: _write_config() present, '
704 u'writing config...')
705 unchanged = self._write_config()
706 if not unchanged and hasattr(self, 'reload'):
707 log.debug(u'TransactionalHandler.commit: reload() present, and'
708 u'configuration changed, reloading...')
711 @handler(u'Discard all the uncommited changes.')
713 r"rollback() -> None :: Discard the changes not yet commited."
714 log.debug(u'TransactionalHandler.reload()')
715 if hasattr(self, '_load'):
716 log.debug(u'TransactionalHandler.reload: _load() present, loading'
717 u'pickled values...')
720 class ParametersHandler(Handler):
721 r"""ParametersHandler([attr]) -> ParametersHandler.
723 This is a helper class to inherit from to automatically handle
724 service parameters, providing set, get, list and show commands.
726 The attribute that holds the parameters can be defined by calling the
727 constructor or in a more declarative way as class attributes, like:
729 class TestHandler(ServiceHandler):
730 _parameters_attr = 'some_attr'
732 The default is 'params' and it should be a dictionary.
734 # TODO implement it using metaclasses to add the handlers method by demand
735 # (only for specifieds commands).
737 _parameters_attr = 'params'
739 def __init__(self, attr=None):
740 r"Initialize the object, see the class documentation for details."
741 log.debug(u'ParametersHandler(%r)', attr)
743 self._parameters_attr = attr
745 @handler(u'Set a service parameter.')
746 def set(self, param, value):
747 r"set(param, value) -> None :: Set a service parameter."
748 log.debug(u'ParametersHandler.set(%r, %r)', param, value)
749 if not param in self.params:
750 log.debug(u'ParametersHandler.set: parameter not found')
751 raise ParameterNotFoundError(param)
752 self.params[param] = value
753 if hasattr(self, '_update'):
754 log.debug(u'ParametersHandler.set: _update found, setting to True')
757 @handler(u'Get a service parameter.')
758 def get(self, param):
759 r"get(param) -> None :: Get a service parameter."
760 log.debug(u'ParametersHandler.get(%r)', param)
761 if not param in self.params:
762 log.debug(u'ParametersHandler.get: parameter not found')
763 raise ParameterNotFoundError(param)
764 return self.params[param]
766 @handler(u'List all available service parameters.')
768 r"list() -> tuple :: List all the parameter names."
769 log.debug(u'ParametersHandler.list()')
770 return self.params.keys()
772 @handler(u'Get all service parameters, with their values.')
774 r"show() -> (key, value) tuples :: List all the parameters."
775 log.debug(u'ParametersHandler.show()')
776 return self.params.items()
778 class SubHandler(Handler):
779 r"""SubHandler(parent) -> SubHandler instance :: Handles subcommands.
781 This is a helper class to build sub handlers that needs to reference the
784 parent - Parent Handler object.
787 def __init__(self, parent):
788 r"Initialize the object, see the class documentation for details."
789 log.debug(u'SubHandler(%r)', parent)
792 class ContainerSubHandler(SubHandler):
793 r"""ContainerSubHandler(parent) -> ContainerSubHandler instance.
795 This is a helper class to implement ListSubHandler and DictSubHandler. You
796 should not use it directly.
798 The container attribute to handle and the class of objects that it
799 contains can be defined by calling the constructor or in a more declarative
800 way as class attributes, like:
802 class TestHandler(ContainerSubHandler):
803 _cont_subhandler_attr = 'some_cont'
804 _cont_subhandler_class = SomeClass
806 This way, the parent's some_cont attribute (self.parent.some_cont)
807 will be managed automatically, providing the commands: add, update,
808 delete, get and show. New items will be instances of SomeClass,
809 which should provide a cmp operator to see if the item is on the
810 container and an update() method, if it should be possible to modify
811 it. If SomeClass has an _add, _update or _delete attribute, it set
812 them to true when the item is added, updated or deleted respectively
813 (in case that it's deleted, it's not removed from the container,
814 but it's not listed either).
817 def __init__(self, parent, attr=None, cls=None):
818 r"Initialize the object, see the class documentation for details."
819 log.debug(u'ContainerSubHandler(%r, %r, %r)', parent, attr, cls)
822 self._cont_subhandler_attr = attr
824 self._cont_subhandler_class = cls
826 def _attr(self, attr=None):
828 return getattr(self.parent, self._cont_subhandler_attr)
829 setattr(self.parent, self._cont_subhandler_attr, attr)
832 if isinstance(self._attr(), dict):
833 return dict([(k, i) for (k, i) in self._attr().items()
834 if not hasattr(i, '_delete') or not i._delete])
835 return [i for i in self._attr()
836 if not hasattr(i, '_delete') or not i._delete]
838 @handler(u'Add a new item')
839 def add(self, *args, **kwargs):
840 r"add(...) -> None :: Add an item to the list."
841 log.debug(u'ContainerSubHandler.add(%r, %r)', args, kwargs)
842 item = self._cont_subhandler_class(*args, **kwargs)
843 if hasattr(item, '_add'):
844 log.debug(u'ContainerSubHandler.add: _add found, setting to True')
847 if isinstance(self._attr(), dict):
848 key = item.as_tuple()[0]
849 # do we have the same item? then raise an error
850 if key in self._vattr():
851 log.debug(u'ContainerSubHandler.add: allready exists')
852 raise ItemAlreadyExistsError(item)
853 # do we have the same item, but logically deleted? then update flags
854 if key in self._attr():
855 log.debug(u'ContainerSubHandler.add: was deleted, undeleting it')
857 if not isinstance(self._attr(), dict):
858 index = self._attr().index(item)
859 if hasattr(item, '_add'):
860 self._attr()[index]._add = False
861 if hasattr(item, '_delete'):
862 self._attr()[index]._delete = False
863 else: # it's *really* new
864 if isinstance(self._attr(), dict):
865 self._attr()[key] = item
867 self._attr().append(item)
869 @handler(u'Update an item')
870 def update(self, index, *args, **kwargs):
871 r"update(index, ...) -> None :: Update an item of the container."
872 log.debug(u'ContainerSubHandler.update(%r, %r, %r)',
874 # TODO make it right with metaclasses, so the method is not created
875 # unless the update() method really exists.
876 if not isinstance(self._attr(), dict):
877 index = int(index) # TODO validation
878 if not hasattr(self._cont_subhandler_class, 'update'):
879 log.debug(u'ContainerSubHandler.update: update() not found, '
880 u"can't really update, raising command not found")
881 raise CommandNotFoundError(('update',))
883 item = self._vattr()[index]
884 item.update(*args, **kwargs)
885 if hasattr(item, '_update'):
886 log.debug(u'ContainerSubHandler.update: _update found, '
890 log.debug(u'ContainerSubHandler.update: item not found')
891 raise ItemNotFoundError(index)
893 @handler(u'Delete an item')
894 def delete(self, index):
895 r"delete(index) -> None :: Delete an item of the container."
896 log.debug(u'ContainerSubHandler.delete(%r)', index)
897 if not isinstance(self._attr(), dict):
898 index = int(index) # TODO validation
900 item = self._vattr()[index]
901 if hasattr(item, '_delete'):
902 log.debug(u'ContainerSubHandler.delete: _delete found, '
906 del self._attr()[index]
909 log.debug(u'ContainerSubHandler.delete: item not found')
910 raise ItemNotFoundError(index)
912 @handler(u'Remove all items (use with care).')
914 r"clear() -> None :: Delete all items of the container."
915 log.debug(u'ContainerSubHandler.clear()')
916 # FIXME broken really, no _delete attribute is setted :S
917 if isinstance(self._attr(), dict):
922 @handler(u'Get information about an item')
923 def get(self, index):
924 r"get(index) -> item :: List all the information of an item."
925 log.debug(u'ContainerSubHandler.get(%r)', index)
926 if not isinstance(self._attr(), dict):
927 index = int(index) # TODO validation
929 return self._vattr()[index]
931 log.debug(u'ContainerSubHandler.get: item not found')
932 raise ItemNotFoundError(index)
934 @handler(u'Get information about all items')
936 r"show() -> list of items :: List all the complete items information."
937 log.debug(u'ContainerSubHandler.show()')
938 if isinstance(self._attr(), dict):
939 return self._attr().values()
942 class ListSubHandler(ContainerSubHandler):
943 r"""ListSubHandler(parent) -> ListSubHandler instance.
945 ContainerSubHandler holding lists. See ComposedSubHandler documentation
949 @handler(u'Get how many items are in the list')
951 r"len() -> int :: Get how many items are in the list."
952 log.debug(u'ListContainerSubHandler.len()')
953 return len(self._vattr())
955 class DictSubHandler(ContainerSubHandler):
956 r"""DictSubHandler(parent) -> DictSubHandler instance.
958 ContainerSubHandler holding dicts. See ComposedSubHandler documentation
962 @handler(u'List all the items by key')
964 r"list() -> tuple :: List all the item keys."
965 log.debug(u'DictContainerSubHandler.list()')
966 return self._attr().keys()
968 class ComposedSubHandler(SubHandler):
969 r"""ComposedSubHandler(parent) -> ComposedSubHandler instance.
971 This is a helper class to implement ListComposedSubHandler and
972 DictComposedSubHandler. You should not use it directly.
974 This class is usefull when you have a parent that has a dict (cont)
975 that stores some object that has an attribute (attr) with a list or
976 a dict of objects of some class. In that case, this class provides
977 automated commands to add, update, delete, get and show that objects.
978 This commands takes the cont (key of the dict for the object holding
979 the attr), and an index for access the object itself (in the attr
982 The container object (cont) that holds a containers, the attribute of
983 that object that is the container itself, and the class of the objects
984 that it contains can be defined by calling the constructor or in a
985 more declarative way as class attributes, like:
987 class TestHandler(ComposedSubHandler):
988 _comp_subhandler_cont = 'some_cont'
989 _comp_subhandler_attr = 'some_attr'
990 _comp_subhandler_class = SomeClass
992 This way, the parent's some_cont attribute (self.parent.some_cont)
993 will be managed automatically, providing the commands: add, update,
994 delete, get and show for manipulating a particular instance that holds
995 of SomeClass. For example, updating an item at the index 5 is the same
996 (simplified) as doing parent.some_cont[cont][5].update().
997 SomeClass should provide a cmp operator to see if the item is on the
998 container and an update() method, if it should be possible to modify
999 it. If SomeClass has an _add, _update or _delete attribute, it set
1000 them to true when the item is added, updated or deleted respectively
1001 (in case that it's deleted, it's not removed from the container,
1002 but it's not listed either). If the container objects
1003 (parent.some_cont[cont]) has an _update attribute, it's set to True
1004 when any add, update or delete command is executed.
1007 def __init__(self, parent, cont=None, attr=None, cls=None):
1008 r"Initialize the object, see the class documentation for details."
1009 log.debug(u'ComposedSubHandler(%r, %r, %r, %r)',
1010 parent, cont, attr, cls)
1011 self.parent = parent
1012 if cont is not None:
1013 self._comp_subhandler_cont = cont
1014 if attr is not None:
1015 self._comp_subhandler_attr = attr
1017 self._comp_subhandler_class = cls
1020 return getattr(self.parent, self._comp_subhandler_cont)
1022 def _attr(self, cont, attr=None):
1024 return getattr(self._cont()[cont], self._comp_subhandler_attr)
1025 setattr(self._cont()[cont], self._comp_subhandler_attr, attr)
1027 def _vattr(self, cont):
1028 if isinstance(self._attr(cont), dict):
1029 return dict([(k, i) for (k, i) in self._attr(cont).items()
1030 if not hasattr(i, '_delete') or not i._delete])
1031 return [i for i in self._attr(cont)
1032 if not hasattr(i, '_delete') or not i._delete]
1034 @handler(u'Add a new item')
1035 def add(self, cont, *args, **kwargs):
1036 r"add(cont, ...) -> None :: Add an item to the list."
1037 log.debug(u'ComposedSubHandler.add(%r, %r, %r)', cont, args, kwargs)
1038 if not cont in self._cont():
1039 log.debug(u'ComposedSubHandler.add: container not found')
1040 raise ContainerNotFoundError(cont)
1041 item = self._comp_subhandler_class(*args, **kwargs)
1042 if hasattr(item, '_add'):
1043 log.debug(u'ComposedSubHandler.add: _add found, setting to True')
1046 if isinstance(self._attr(cont), dict):
1047 key = item.as_tuple()[0]
1048 # do we have the same item? then raise an error
1049 if key in self._vattr(cont):
1050 log.debug(u'ComposedSubHandler.add: allready exists')
1051 raise ItemAlreadyExistsError(item)
1052 # do we have the same item, but logically deleted? then update flags
1053 if key in self._attr(cont):
1054 log.debug(u'ComposedSubHandler.add: was deleted, undeleting it')
1056 if not isinstance(self._attr(cont), dict):
1057 index = self._attr(cont).index(item)
1058 if hasattr(item, '_add'):
1059 self._attr(cont)[index]._add = False
1060 if hasattr(item, '_delete'):
1061 self._attr(cont)[index]._delete = False
1062 else: # it's *really* new
1063 if isinstance(self._attr(cont), dict):
1064 self._attr(cont)[key] = item
1066 self._attr(cont).append(item)
1067 if hasattr(self._cont()[cont], '_update'):
1068 log.debug(u"ComposedSubHandler.add: container's _update found, "
1070 self._cont()[cont]._update = True
1072 @handler(u'Update an item')
1073 def update(self, cont, index, *args, **kwargs):
1074 r"update(cont, index, ...) -> None :: Update an item of the container."
1075 # TODO make it right with metaclasses, so the method is not created
1076 # unless the update() method really exists.
1077 log.debug(u'ComposedSubHandler.update(%r, %r, %r, %r)',
1078 cont, index, args, kwargs)
1079 if not cont in self._cont():
1080 log.debug(u'ComposedSubHandler.add: container not found')
1081 raise ContainerNotFoundError(cont)
1082 if not isinstance(self._attr(cont), dict):
1083 index = int(index) # TODO validation
1084 if not hasattr(self._comp_subhandler_class, 'update'):
1085 log.debug(u'ComposedSubHandler.update: update() not found, '
1086 u"can't really update, raising command not found")
1087 raise CommandNotFoundError(('update',))
1089 item = self._vattr(cont)[index]
1090 item.update(*args, **kwargs)
1091 if hasattr(item, '_update'):
1092 log.debug(u'ComposedSubHandler.update: _update found, '
1095 if hasattr(self._cont()[cont], '_update'):
1096 log.debug(u"ComposedSubHandler.add: container's _update found, "
1098 self._cont()[cont]._update = True
1100 log.debug(u'ComposedSubHandler.update: item not found')
1101 raise ItemNotFoundError(index)
1103 @handler(u'Delete an item')
1104 def delete(self, cont, index):
1105 r"delete(cont, index) -> None :: Delete an item of the container."
1106 log.debug(u'ComposedSubHandler.delete(%r, %r)', cont, index)
1107 if not cont in self._cont():
1108 log.debug(u'ComposedSubHandler.add: container not found')
1109 raise ContainerNotFoundError(cont)
1110 if not isinstance(self._attr(cont), dict):
1111 index = int(index) # TODO validation
1113 item = self._vattr(cont)[index]
1114 if hasattr(item, '_delete'):
1115 log.debug(u'ComposedSubHandler.delete: _delete found, '
1119 del self._attr(cont)[index]
1120 if hasattr(self._cont()[cont], '_update'):
1121 log.debug(u"ComposedSubHandler.add: container's _update found, "
1123 self._cont()[cont]._update = True
1126 log.debug(u'ComposedSubHandler.delete: item not found')
1127 raise ItemNotFoundError(index)
1129 @handler(u'Remove all items (use with care).')
1130 def clear(self, cont):
1131 r"clear(cont) -> None :: Delete all items of the container."
1132 # FIXME broken really, no item or container _delete attribute is
1134 log.debug(u'ComposedSubHandler.clear(%r)', cont)
1135 if not cont in self._cont():
1136 log.debug(u'ComposedSubHandler.add: container not found')
1137 raise ContainerNotFoundError(cont)
1138 if isinstance(self._attr(cont), dict):
1139 self._attr(cont).clear()
1141 self._attr(cont, list())
1143 @handler(u'Get information about an item')
1144 def get(self, cont, index):
1145 r"get(cont, index) -> item :: List all the information of an item."
1146 log.debug(u'ComposedSubHandler.get(%r, %r)', cont, index)
1147 if not cont in self._cont():
1148 log.debug(u'ComposedSubHandler.add: container not found')
1149 raise ContainerNotFoundError(cont)
1150 if not isinstance(self._attr(cont), dict):
1151 index = int(index) # TODO validation
1153 return self._vattr(cont)[index]
1155 log.debug(u'ComposedSubHandler.get: item not found')
1156 raise ItemNotFoundError(index)
1158 @handler(u'Get information about all items')
1159 def show(self, cont):
1160 r"show(cont) -> list of items :: List all the complete items information."
1161 log.debug(u'ComposedSubHandler.show(%r)', cont)
1162 if not cont in self._cont():
1163 log.debug(u'ComposedSubHandler.add: container not found')
1164 raise ContainerNotFoundError(cont)
1165 if isinstance(self._attr(cont), dict):
1166 return self._attr(cont).values()
1167 return self._vattr(cont)
1169 class ListComposedSubHandler(ComposedSubHandler):
1170 r"""ListComposedSubHandler(parent) -> ListComposedSubHandler instance.
1172 ComposedSubHandler holding lists. See ComposedSubHandler documentation
1176 @handler(u'Get how many items are in the list')
1177 def len(self, cont):
1178 r"len(cont) -> int :: Get how many items are in the list."
1179 log.debug(u'ListComposedSubHandler.len(%r)', cont)
1180 if not cont in self._cont():
1181 raise ContainerNotFoundError(cont)
1182 return len(self._vattr(cont))
1184 class DictComposedSubHandler(ComposedSubHandler):
1185 r"""DictComposedSubHandler(parent) -> DictComposedSubHandler instance.
1187 ComposedSubHandler holding dicts. See ComposedSubHandler documentation
1191 @handler(u'List all the items by key')
1192 def list(self, cont):
1193 r"list(cont) -> tuple :: List all the item keys."
1194 log.debug(u'DictComposedSubHandler.list(%r)', cont)
1195 if not cont in self._cont():
1196 raise ContainerNotFoundError(cont)
1197 return self._attr(cont).keys()
1200 if __name__ == '__main__':
1202 logging.basicConfig(
1203 level = logging.DEBUG,
1204 format = '%(asctime)s %(levelname)-8s %(message)s',
1205 datefmt = '%H:%M:%S',
1211 class STestHandler1(ServiceHandler):
1212 _service_start = ('service', 'start')
1213 _service_stop = ('service', 'stop')
1214 _service_restart = ('ls', '/')
1215 _service_reload = ('cp', '/la')
1216 class STestHandler2(ServiceHandler):
1218 ServiceHandler.__init__(self, 'cmd-start', 'cmd-stop',
1219 'cmd-restart', 'cmd-reload')
1220 class ITestHandler1(InitdHandler):
1221 _initd_name = 'test1'
1222 class ITestHandler2(InitdHandler):
1224 InitdHandler.__init__(self, 'test2', '/usr/local/etc/init.d')
1232 print h.__class__.__name__
1235 except ExecutionError, e:
1239 except ExecutionError, e:
1243 except ExecutionError, e:
1247 except ExecutionError, e:
1252 print 'PTestHandler'
1253 class PTestHandler(Persistent):
1254 _persistent_attrs = 'vars'
1256 self.vars = dict(a=1, b=2)
1274 print 'RTestHandler'
1275 class RTestHandler(Restorable):
1276 _persistent_attrs = 'vars'
1277 _restorable_defaults = dict(vars=dict(a=1, b=2))
1289 print 'CTestHandler'
1291 os.mkdir('templates')
1292 f = file('templates/config', 'w')
1293 f.write('Hello, ${name}! You are ${what}.')
1296 print file('templates/config').read()
1297 class CTestHandler(ConfigWriter):
1298 _config_writer_files = 'config'
1300 self._config_build_templates()
1301 def _get_config_vars(self, config_file):
1302 return dict(name='you', what='a parrot')
1306 print file('config').read()
1308 os.unlink('templates/config')
1309 os.rmdir('templates')
1312 print get_network_devices()