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):
163 return (self.name, self.mac, self.active, self.addrs)
167 def get_network_devices():
168 p = subprocess.Popen(('ip', '-o', 'link'), stdout=subprocess.PIPE,
170 string = p.stdout.read()
173 devices = string.splitlines()
176 if dev.find('link/ether') != -1:
177 i = dev.find('link/ether')
178 mac = dev[i+11 : i+11+17]
180 j = dev.find(':', i+1)
182 d[name] = Device(name,mac,False)
183 elif dev.find('link/ppp') != -1:
184 i = dev.find('link/ppp')
185 mac = '00:00:00:00:00:00'
187 j = dev.find(':', i+1)
189 d[name] = Device(name,mac,True)
190 #since the device is ppp, get the address and peer
192 p = subprocess.Popen(('ip', '-o', 'addr', 'show', name), stdout=subprocess.PIPE,
193 close_fds=True, stderr=subprocess.PIPE)
194 string = p.stdout.read()
196 addrs = string.splitlines()
197 inet = addrs[1].find('inet')
198 peer = addrs[1].find('peer')
199 bar = addrs[1].find('/')
200 from_addr = addrs[1][inet+5 : peer-1]
201 to_addr = addrs[1][peer+5 : bar]
202 d[name].addrs[from_addr] = Address(from_addr,24, peer=to_addr)
208 p = subprocess.Popen(('ip', '-o', 'addr'), stdout=subprocess.PIPE,
211 def call(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
212 stderr=subprocess.PIPE, close_fds=True, universal_newlines=True,
214 log.debug(u'call(%r)', command)
216 log.debug(u'call: not really executing, DEBUG mode')
219 subprocess.check_call(command, stdin=stdin, stdout=stdout,
220 stderr=stderr, close_fds=close_fds,
221 universal_newlines=universal_newlines, **kw)
223 log.debug(u'call: Execution error %r', e)
224 raise ExecutionError(command, e)
227 r"""Persistent([attrs[, dir[, ext]]]) -> Persistent.
229 This is a helper class to inherit from to automatically handle data
230 persistence using pickle.
232 The variables attributes to persist (attrs), and the pickle directory (dir)
233 and file extension (ext) can be defined by calling the constructor or in a
234 more declarative way as class attributes, like:
236 class TestHandler(Persistent):
237 _persistent_attrs = ('some_attr', 'other_attr')
238 _persistent_dir = 'persistent-data'
239 _persistent_ext = '.pickle'
241 The default dir is '.' and the default extension is '.pkl'. There are no
242 default variables, and they should be specified as string if a single
243 attribute should be persistent or as a tuple of strings if they are more.
244 The strings should be the attribute names to be persisted. For each
245 attribute a separated pickle file is generated in the pickle directory.
247 You can call _dump() and _load() to write and read the data respectively.
249 # TODO implement it using metaclasses to add the handlers method by demand
250 # (only for specifieds commands).
252 _persistent_attrs = ()
253 _persistent_dir = '.'
254 _persistent_ext = '.pkl'
256 def __init__(self, attrs=None, dir=None, ext=None):
257 r"Initialize the object, see the class documentation for details."
258 if attrs is not None:
259 self._persistent_attrs = attrs
261 self._persistent_dir = dir
263 self._persistent_ext = ext
266 r"_dump() -> None :: Dump all persistent data to pickle files."
267 if isinstance(self._persistent_attrs, basestring):
268 self._persistent_attrs = (self._persistent_attrs,)
269 for attrname in self._persistent_attrs:
270 self._dump_attr(attrname)
273 r"_load() -> None :: Load all persistent data from pickle files."
274 if isinstance(self._persistent_attrs, basestring):
275 self._persistent_attrs = (self._persistent_attrs,)
276 for attrname in self._persistent_attrs:
277 self._load_attr(attrname)
279 def _dump_attr(self, attrname):
280 r"_dump_attr() -> None :: Dump a specific variable to a pickle file."
281 fname = self._pickle_filename(attrname)
282 attr = getattr(self, attrname)
283 log.debug(u'Persistent._dump_attr(%r) -> file=%r, value=%r',
284 attrname, fname, attr)
285 f = file(self._pickle_filename(attrname), 'wb')
286 pickle.dump(attr, f, 2)
289 def _load_attr(self, attrname):
290 r"_load_attr() -> object :: Load a specific pickle file."
291 fname = self._pickle_filename(attrname)
293 attr = pickle.load(f)
294 log.debug(u'Persistent._load_attr(%r) -> file=%r, value=%r',
295 attrname, fname, attr)
296 setattr(self, attrname, attr)
299 def _pickle_filename(self, name):
300 r"_pickle_filename() -> string :: Construct a pickle filename."
301 return path.join(self._persistent_dir, name) + self._persistent_ext
303 class Restorable(Persistent):
304 r"""Restorable([defaults]) -> Restorable.
306 This is a helper class to inherit from that provides a nice _restore()
307 method to restore the persistent data if any, or load some nice defaults
310 The defaults can be defined by calling the constructor or in a more
311 declarative way as class attributes, like:
313 class TestHandler(Restorable):
314 _persistent_attrs = ('some_attr', 'other_attr')
315 _restorable_defaults = dict(
316 some_attr = 'some_default',
317 other_attr = 'other_default')
319 The defaults is a dictionary, very coupled with the _persistent_attrs
320 attribute inherited from Persistent. The defaults keys should be the
321 values from _persistent_attrs, and the values the default values.
323 The _restore() method returns True if the data was restored successfully
324 or False if the defaults were loaded (in case you want to take further
325 actions). If a _write_config method if found, it's executed when a restore
328 # TODO implement it using metaclasses to add the handlers method by demand
329 # (only for specifieds commands).
331 _restorable_defaults = dict()
333 def __init__(self, defaults=None):
334 r"Initialize the object, see the class documentation for details."
335 if defaults is not None:
336 self._restorable_defaults = defaults
339 r"_restore() -> bool :: Restore persistent data or create a default."
340 log.debug(u'Restorable._restore()')
342 log.debug(u'Restorable._restore: trying to load...')
344 log.debug(u'Restorable._restore: load OK')
347 log.debug(u'Restorable._restore: load FAILED, making defaults: %r',
348 self._restorable_defaults)
349 for (k, v) in self._restorable_defaults.items():
351 # TODO tener en cuenta servicios que hay que levantar y los que no
352 if hasattr(self, 'commit'):
353 log.debug(u'Restorable._restore: commit() found, commiting...')
356 log.debug(u'Restorable._restore: dumping new defaults...')
358 if hasattr(self, '_write_config'):
359 log.debug(u'Restorable._restore: _write_config() found, '
360 u'writing new config...')
365 r"""ConfigWriter([initd_name[, initd_dir]]) -> ConfigWriter.
367 This is a helper class to inherit from to automatically handle
368 configuration generation. Mako template system is used for configuration
371 The configuration filenames, the generated configuration files directory
372 and the templates directory can be defined by calling the constructor or
373 in a more declarative way as class attributes, like:
375 class TestHandler(ConfigWriter):
376 _config_writer_files = ('base.conf', 'custom.conf')
377 _config_writer_cfg_dir = {
378 'base.conf': '/etc/service',
379 'custom.conf': '/etc/service/conf.d',
381 _config_writer_tpl_dir = 'templates'
383 The generated configuration files directory defaults to '.' and the
384 templates directory to 'templates'. _config_writer_files has no default and
385 must be specified in either way. It can be string or a tuple if more than
386 one configuration file must be generated. _config_writer_cfg_dir could be a
387 dict mapping which file should be stored in which directory, or a single
388 string if all the config files should go to the same directory.
390 The template filename and the generated configuration filename are both the
391 same (so if you want to generate some /etc/config, you should have some
392 templates/config template). That's why _config_writer_cfg_dir and
393 _config_writer_tpl_dir can't be the same. This is not true for very
394 specific cases where _write_single_config() is used.
396 When you write your Handler, you should call _config_build_templates() in
397 you Handler constructor to build the templates.
399 To write the configuration files, you must use the _write_config() method.
400 To know what variables to replace in the template, you have to provide a
401 method called _get_config_vars(tamplate_name), which should return a
402 dictionary of variables to pass to the template system to be replaced in
403 the template for the configuration file 'config_file'.
405 # TODO implement it using metaclasses to add the handlers method by demand
406 # (only for specifieds commands).
408 _config_writer_files = ()
409 _config_writer_cfg_dir = '.'
410 _config_writer_tpl_dir = 'templates'
412 def __init__(self, files=None, cfg_dir=None, tpl_dir=None):
413 r"Initialize the object, see the class documentation for details."
414 if files is not None:
415 self._config_writer_files = files
416 if cfg_dir is not None:
417 self._config_writer_cfg_dir = cfg_dir
418 if tpl_dir is not None:
419 self._config_writer_tpl_dir = tpl_dir
420 self._config_build_templates()
422 def _config_build_templates(self):
423 r"_config_writer_templates() -> None :: Build the template objects."
424 log.debug(u'ConfigWriter.build_templates()')
425 if isinstance(self._config_writer_files, basestring):
426 self._config_writer_files = (self._config_writer_files,)
427 if not hasattr(self, '_config_writer_templates') \
428 or not self._config_writer_templates:
429 self._config_writer_templates = dict()
430 for t in self._config_writer_files:
431 f = path.join(self._config_writer_tpl_dir, t)
432 self._config_writer_templates[t] = Template(filename=f)
434 def _render_config(self, template_name, vars=None):
435 r"""_render_config(template_name[, config_filename[, vars]]).
437 Render a single config file using the template 'template_name'. If
438 vars is specified, it's used as the dictionary with the variables
439 to replace in the templates, if not, it looks for a
440 _get_config_vars() method to get it.
443 if hasattr(self, '_get_config_vars'):
444 vars = self._get_config_vars(template_name)
448 vars = vars(template_name)
449 log.debug(u'ConfigWriter._render_config: rendering template %r with '
450 u'vars=%r', template_name, vars)
451 return self._config_writer_templates[template_name].render(**vars)
453 def _get_config_path(self, template_name, config_filename=None):
454 r"Get a complete configuration path."
455 if not config_filename:
456 config_filename = template_name
457 if isinstance(self._config_writer_cfg_dir, basestring):
458 return path.join(self._config_writer_cfg_dir, config_filename)
459 return path.join(self._config_writer_cfg_dir[template_name],
462 def _write_single_config(self, template_name, config_filename=None, vars=None):
463 r"""_write_single_config(template_name[, config_filename[, vars]]).
465 Write a single config file using the template 'template_name'. If no
466 config_filename is specified, the config filename will be the same as
467 the 'template_name' (but stored in the generated config files
468 directory). If it's specified, the generated config file is stored in
469 the file called 'config_filename' (also in the generated files
470 directory). If vars is specified, it's used as the dictionary with the
471 variables to replace in the templates, if not, it looks for a
472 _get_config_vars() method to get it.
475 if hasattr(self, '_get_config_vars'):
476 vars = self._get_config_vars(template_name)
480 vars = vars(template_name)
481 fname = self._get_config_path(template_name, config_filename)
483 ctx = Context(f, **vars)
484 log.debug(u'ConfigWriter._write_single_config: rendering template '
485 u'%r with vars=%r to file %r', template_name, vars, fname)
486 self._config_writer_templates[template_name].render_context(ctx)
489 def _write_config(self):
490 r"_write_config() -> None :: Generate all the configuration files."
491 for t in self._config_writer_files:
492 self._write_single_config(t)
495 class ServiceHandler(Handler, Restorable):
496 r"""ServiceHandler([start[, stop[, restart[, reload]]]]) -> ServiceHandler.
498 This is a helper class to inherit from to automatically handle services
499 with start, stop, restart, reload actions.
501 The actions can be defined by calling the constructor with all the
502 parameters or in a more declarative way as class attributes, like:
504 class TestHandler(ServiceHandler):
505 _service_start = ('command', 'start')
506 _service_stop = ('command', 'stop')
507 _service_restart = ('command', 'restart')
508 _service_reload = 'reload-command'
510 Commands are executed without using the shell, that's why they are specified
511 as tuples (where the first element is the command and the others are the
512 command arguments). If only a command is needed (without arguments) a single
513 string can be specified.
515 All commands must be specified.
517 # TODO implement it using metaclasses to add the handlers method by demand
518 # (only for specifieds commands).
520 def __init__(self, start=None, stop=None, restart=None, reload=None):
521 r"Initialize the object, see the class documentation for details."
522 log.debug(u'ServiceHandler(%r, %r, %r, %r)', start, stop, restart,
524 for (name, action) in dict(start=start, stop=stop, restart=restart,
525 reload=reload).items():
526 if action is not None:
527 attr_name = '_service_%s' % name
528 log.debug(u'ServiceHandler: using %r as %s', attr_name, action)
529 setattr(self, attr_name, action)
530 self._persistent_attrs = list(self._persistent_attrs)
531 self._persistent_attrs.append('_service_running')
532 if '_service_running' not in self._restorable_defaults:
533 self._restorable_defaults['_service_running'] = False
534 log.debug(u'ServiceHandler: restoring service configuration...')
536 if self._service_running:
537 log.debug(u'ServiceHandler: service was running, starting it...')
538 self._service_running = False
541 @handler(u'Start the service.')
543 r"start() -> None :: Start the service."
544 log.debug(u'ServiceHandler.start()')
545 if not self._service_running:
546 log.debug(u'ServiceHandler.start: not running, starting it...')
547 if callable(self._service_start):
548 self._service_start()
550 call(self._service_start)
551 self._service_running = True
552 self._dump_attr('_service_running')
554 @handler(u'Stop the service.')
556 log.debug(u'ServiceHandler.stop()')
557 r"stop() -> None :: Stop the service."
558 if self._service_running:
559 log.debug(u'ServiceHandler.stop: running, stoping it...')
560 if callable(self._service_stop):
563 call(self._service_stop)
564 self._service_running = False
565 self._dump_attr('_service_running')
567 @handler(u'Restart the service.')
569 r"restart() -> None :: Restart the service."
570 log.debug(u'ServiceHandler.restart()')
571 if callable(self._service_restart):
572 self._service_restart()
574 call(self._service_restart)
575 self._service_running = True
576 self._dump_attr('_service_running')
578 @handler(u'Reload the service config (without restarting, if possible).')
580 r"reload() -> None :: Reload the configuration of the service."
581 log.debug(u'ServiceHandler.reload()')
582 if self._service_running:
583 log.debug(u'ServiceHandler.reload: running, reloading...')
584 if callable(self._service_reload):
585 self._service_reload()
587 call(self._service_reload)
589 @handler(u'Tell if the service is running.')
591 r"reload() -> None :: Reload the configuration of the service."
592 log.debug(u'ServiceHandler.running() -> %r', self._service_running)
593 if self._service_running:
598 class RestartHandler(Handler):
599 r"""RestartHandler() -> RestartHandler :: Provides generic restart command.
601 This is a helper class to inherit from to automatically add a restart
602 command that first stop the service and then starts it again (using start
603 and stop commands respectively).
606 @handler(u'Restart the service (alias to stop + start).')
608 r"restart() -> None :: Restart the service calling stop() and start()."
609 log.debug(u'RestartHandler.restart()')
613 class ReloadHandler(Handler):
614 r"""ReloadHandler() -> ReloadHandler :: Provides generic reload command.
616 This is a helper class to inherit from to automatically add a reload
617 command that calls restart.
620 @handler(u'Reload the service config (alias to restart).')
622 r"reload() -> None :: Reload the configuration of the service."
623 log.debug(u'ReloadHandler.reload()')
624 if hasattr(self, '_service_running') and self._service_running:
625 log.debug(u'ReloadHandler.reload: running, reloading...')
628 class InitdHandler(ServiceHandler):
629 # TODO update docs, declarative style is depracated
630 r"""InitdHandler([initd_name[, initd_dir]]) -> InitdHandler.
632 This is a helper class to inherit from to automatically handle services
633 with start, stop, restart, reload actions using a /etc/init.d like script.
635 The name and directory of the script can be defined by calling the
636 constructor or in a more declarative way as class attributes, like:
638 class TestHandler(ServiceHandler):
639 _initd_name = 'some-service'
640 _initd_dir = '/usr/local/etc/init.d'
642 The default _initd_dir is '/etc/init.d', _initd_name has no default and
643 must be specified in either way.
645 Commands are executed without using the shell.
647 # TODO implement it using metaclasses to add the handlers method by demand
648 # (only for specifieds commands).
650 _initd_dir = '/etc/init.d'
652 def __init__(self, initd_name=None, initd_dir=None):
653 r"Initialize the object, see the class documentation for details."
654 log.debug(u'InitdHandler(%r, %r)', initd_name, initd_dir)
655 if initd_name is not None:
656 self._initd_name = initd_name
657 if initd_dir is not None:
658 self._initd_dir = initd_dir
660 for action in ('start', 'stop', 'restart', 'reload'):
661 actions[action] = (path.join(self._initd_dir, self._initd_name),
663 ServiceHandler.__init__(self, **actions)
665 def handle_timer(self):
667 log.debug(u'InitdHandler.handle_timer(): self=%r', self)
668 p = subprocess.Popen(('pgrep', '-f', self._initd_name),
669 stdout=subprocess.PIPE)
670 pid = p.communicate()[0]
671 if p.returncode == 0 and len(pid) > 0:
672 log.debug(u'InitdHandler.handle_timer: pid present, running')
673 self._service_running = True
675 log.debug(u'InitdHandler.handle_timer: pid absent, NOT running')
676 self._service_running = False
678 class TransactionalHandler(Handler):
679 r"""Handle command transactions providing a commit and rollback commands.
681 This is a helper class to inherit from to automatically handle
682 transactional handlers, which have commit and rollback commands.
684 The handler should provide a reload() method (see ServiceHandler and
685 InitdHandler for helper classes to provide this) which will be called
686 when a commit command is issued (if a reload() command is present).
687 The persistent data will be written too (if a _dump() method is provided,
688 see Persistent and Restorable for that), and the configuration files
689 will be generated (if a _write_config method is present, see ConfigWriter).
691 # TODO implement it using metaclasses to add the handlers method by demand
692 # (only for specifieds commands).
694 @handler(u'Commit the changes (reloading the service, if necessary).')
696 r"commit() -> None :: Commit the changes and reload the service."
697 log.debug(u'TransactionalHandler.commit()')
698 if hasattr(self, '_dump'):
699 log.debug(u'TransactionalHandler.commit: _dump() present, '
703 if hasattr(self, '_write_config'):
704 log.debug(u'TransactionalHandler.commit: _write_config() present, '
705 u'writing config...')
706 unchanged = self._write_config()
707 if not unchanged and hasattr(self, 'reload'):
708 log.debug(u'TransactionalHandler.commit: reload() present, and'
709 u'configuration changed, reloading...')
712 @handler(u'Discard all the uncommited changes.')
714 r"rollback() -> None :: Discard the changes not yet commited."
715 log.debug(u'TransactionalHandler.reload()')
716 if hasattr(self, '_load'):
717 log.debug(u'TransactionalHandler.reload: _load() present, loading'
718 u'pickled values...')
721 class ParametersHandler(Handler):
722 r"""ParametersHandler([attr]) -> ParametersHandler.
724 This is a helper class to inherit from to automatically handle
725 service parameters, providing set, get, list and show commands.
727 The attribute that holds the parameters can be defined by calling the
728 constructor or in a more declarative way as class attributes, like:
730 class TestHandler(ServiceHandler):
731 _parameters_attr = 'some_attr'
733 The default is 'params' and it should be a dictionary.
735 # TODO implement it using metaclasses to add the handlers method by demand
736 # (only for specifieds commands).
738 _parameters_attr = 'params'
740 def __init__(self, attr=None):
741 r"Initialize the object, see the class documentation for details."
742 log.debug(u'ParametersHandler(%r)', attr)
744 self._parameters_attr = attr
746 @handler(u'Set a service parameter.')
747 def set(self, param, value):
748 r"set(param, value) -> None :: Set a service parameter."
749 log.debug(u'ParametersHandler.set(%r, %r)', param, value)
750 if not param in self.params:
751 log.debug(u'ParametersHandler.set: parameter not found')
752 raise ParameterNotFoundError(param)
753 self.params[param] = value
754 if hasattr(self, '_update'):
755 log.debug(u'ParametersHandler.set: _update found, setting to True')
758 @handler(u'Get a service parameter.')
759 def get(self, param):
760 r"get(param) -> None :: Get a service parameter."
761 log.debug(u'ParametersHandler.get(%r)', param)
762 if not param in self.params:
763 log.debug(u'ParametersHandler.get: parameter not found')
764 raise ParameterNotFoundError(param)
765 return self.params[param]
767 @handler(u'List all available service parameters.')
769 r"list() -> tuple :: List all the parameter names."
770 log.debug(u'ParametersHandler.list()')
771 return self.params.keys()
773 @handler(u'Get all service parameters, with their values.')
775 r"show() -> (key, value) tuples :: List all the parameters."
776 log.debug(u'ParametersHandler.show()')
777 return self.params.items()
779 class SubHandler(Handler):
780 r"""SubHandler(parent) -> SubHandler instance :: Handles subcommands.
782 This is a helper class to build sub handlers that needs to reference the
785 parent - Parent Handler object.
788 def __init__(self, parent):
789 r"Initialize the object, see the class documentation for details."
790 log.debug(u'SubHandler(%r)', parent)
793 class ContainerSubHandler(SubHandler):
794 r"""ContainerSubHandler(parent) -> ContainerSubHandler instance.
796 This is a helper class to implement ListSubHandler and DictSubHandler. You
797 should not use it directly.
799 The container attribute to handle and the class of objects that it
800 contains can be defined by calling the constructor or in a more declarative
801 way as class attributes, like:
803 class TestHandler(ContainerSubHandler):
804 _cont_subhandler_attr = 'some_cont'
805 _cont_subhandler_class = SomeClass
807 This way, the parent's some_cont attribute (self.parent.some_cont)
808 will be managed automatically, providing the commands: add, update,
809 delete, get and show. New items will be instances of SomeClass,
810 which should provide a cmp operator to see if the item is on the
811 container and an update() method, if it should be possible to modify
812 it. If SomeClass has an _add, _update or _delete attribute, it set
813 them to true when the item is added, updated or deleted respectively
814 (in case that it's deleted, it's not removed from the container,
815 but it's not listed either).
818 def __init__(self, parent, attr=None, cls=None):
819 r"Initialize the object, see the class documentation for details."
820 log.debug(u'ContainerSubHandler(%r, %r, %r)', parent, attr, cls)
823 self._cont_subhandler_attr = attr
825 self._cont_subhandler_class = cls
827 def _attr(self, attr=None):
829 return getattr(self.parent, self._cont_subhandler_attr)
830 setattr(self.parent, self._cont_subhandler_attr, attr)
833 if isinstance(self._attr(), dict):
834 return dict([(k, i) for (k, i) in self._attr().items()
835 if not hasattr(i, '_delete') or not i._delete])
836 return [i for i in self._attr()
837 if not hasattr(i, '_delete') or not i._delete]
839 @handler(u'Add a new item')
840 def add(self, *args, **kwargs):
841 r"add(...) -> None :: Add an item to the list."
842 log.debug(u'ContainerSubHandler.add(%r, %r)', args, kwargs)
843 item = self._cont_subhandler_class(*args, **kwargs)
844 if hasattr(item, '_add'):
845 log.debug(u'ContainerSubHandler.add: _add found, setting to True')
848 if isinstance(self._attr(), dict):
849 key = item.as_tuple()[0]
850 # do we have the same item? then raise an error
851 if key in self._vattr():
852 log.debug(u'ContainerSubHandler.add: allready exists')
853 raise ItemAlreadyExistsError(item)
854 # do we have the same item, but logically deleted? then update flags
855 if key in self._attr():
856 log.debug(u'ContainerSubHandler.add: was deleted, undeleting it')
858 if not isinstance(self._attr(), dict):
859 index = self._attr().index(item)
860 if hasattr(item, '_add'):
861 self._attr()[index]._add = False
862 if hasattr(item, '_delete'):
863 self._attr()[index]._delete = False
864 else: # it's *really* new
865 if isinstance(self._attr(), dict):
866 self._attr()[key] = item
868 self._attr().append(item)
870 @handler(u'Update an item')
871 def update(self, index, *args, **kwargs):
872 r"update(index, ...) -> None :: Update an item of the container."
873 log.debug(u'ContainerSubHandler.update(%r, %r, %r)',
875 # TODO make it right with metaclasses, so the method is not created
876 # unless the update() method really exists.
877 if not isinstance(self._attr(), dict):
878 index = int(index) # TODO validation
879 if not hasattr(self._cont_subhandler_class, 'update'):
880 log.debug(u'ContainerSubHandler.update: update() not found, '
881 u"can't really update, raising command not found")
882 raise CommandNotFoundError(('update',))
884 item = self._vattr()[index]
885 item.update(*args, **kwargs)
886 if hasattr(item, '_update'):
887 log.debug(u'ContainerSubHandler.update: _update found, '
891 log.debug(u'ContainerSubHandler.update: item not found')
892 raise ItemNotFoundError(index)
894 @handler(u'Delete an item')
895 def delete(self, index):
896 r"delete(index) -> None :: Delete an item of the container."
897 log.debug(u'ContainerSubHandler.delete(%r)', index)
898 if not isinstance(self._attr(), dict):
899 index = int(index) # TODO validation
901 item = self._vattr()[index]
902 if hasattr(item, '_delete'):
903 log.debug(u'ContainerSubHandler.delete: _delete found, '
907 del self._attr()[index]
910 log.debug(u'ContainerSubHandler.delete: item not found')
911 raise ItemNotFoundError(index)
913 @handler(u'Remove all items (use with care).')
915 r"clear() -> None :: Delete all items of the container."
916 log.debug(u'ContainerSubHandler.clear()')
917 # FIXME broken really, no _delete attribute is setted :S
918 if isinstance(self._attr(), dict):
923 @handler(u'Get information about an item')
924 def get(self, index):
925 r"get(index) -> item :: List all the information of an item."
926 log.debug(u'ContainerSubHandler.get(%r)', index)
927 if not isinstance(self._attr(), dict):
928 index = int(index) # TODO validation
930 return self._vattr()[index]
932 log.debug(u'ContainerSubHandler.get: item not found')
933 raise ItemNotFoundError(index)
935 @handler(u'Get information about all items')
937 r"show() -> list of items :: List all the complete items information."
938 log.debug(u'ContainerSubHandler.show()')
939 if isinstance(self._attr(), dict):
940 return self._attr().values()
943 class ListSubHandler(ContainerSubHandler):
944 r"""ListSubHandler(parent) -> ListSubHandler instance.
946 ContainerSubHandler holding lists. See ComposedSubHandler documentation
950 @handler(u'Get how many items are in the list')
952 r"len() -> int :: Get how many items are in the list."
953 log.debug(u'ListContainerSubHandler.len()')
954 return len(self._vattr())
956 class DictSubHandler(ContainerSubHandler):
957 r"""DictSubHandler(parent) -> DictSubHandler instance.
959 ContainerSubHandler holding dicts. See ComposedSubHandler documentation
963 @handler(u'List all the items by key')
965 r"list() -> tuple :: List all the item keys."
966 log.debug(u'DictContainerSubHandler.list()')
967 return self._attr().keys()
969 class ComposedSubHandler(SubHandler):
970 r"""ComposedSubHandler(parent) -> ComposedSubHandler instance.
972 This is a helper class to implement ListComposedSubHandler and
973 DictComposedSubHandler. You should not use it directly.
975 This class is usefull when you have a parent that has a dict (cont)
976 that stores some object that has an attribute (attr) with a list or
977 a dict of objects of some class. In that case, this class provides
978 automated commands to add, update, delete, get and show that objects.
979 This commands takes the cont (key of the dict for the object holding
980 the attr), and an index for access the object itself (in the attr
983 The container object (cont) that holds a containers, the attribute of
984 that object that is the container itself, and the class of the objects
985 that it contains can be defined by calling the constructor or in a
986 more declarative way as class attributes, like:
988 class TestHandler(ComposedSubHandler):
989 _comp_subhandler_cont = 'some_cont'
990 _comp_subhandler_attr = 'some_attr'
991 _comp_subhandler_class = SomeClass
993 This way, the parent's some_cont attribute (self.parent.some_cont)
994 will be managed automatically, providing the commands: add, update,
995 delete, get and show for manipulating a particular instance that holds
996 of SomeClass. For example, updating an item at the index 5 is the same
997 (simplified) as doing parent.some_cont[cont][5].update().
998 SomeClass should provide a cmp operator to see if the item is on the
999 container and an update() method, if it should be possible to modify
1000 it. If SomeClass has an _add, _update or _delete attribute, it set
1001 them to true when the item is added, updated or deleted respectively
1002 (in case that it's deleted, it's not removed from the container,
1003 but it's not listed either). If the container objects
1004 (parent.some_cont[cont]) has an _update attribute, it's set to True
1005 when any add, update or delete command is executed.
1008 def __init__(self, parent, cont=None, attr=None, cls=None):
1009 r"Initialize the object, see the class documentation for details."
1010 log.debug(u'ComposedSubHandler(%r, %r, %r, %r)',
1011 parent, cont, attr, cls)
1012 self.parent = parent
1013 if cont is not None:
1014 self._comp_subhandler_cont = cont
1015 if attr is not None:
1016 self._comp_subhandler_attr = attr
1018 self._comp_subhandler_class = cls
1021 return getattr(self.parent, self._comp_subhandler_cont)
1023 def _attr(self, cont, attr=None):
1025 return getattr(self._cont()[cont], self._comp_subhandler_attr)
1026 setattr(self._cont()[cont], self._comp_subhandler_attr, attr)
1028 def _vattr(self, cont):
1029 if isinstance(self._attr(cont), dict):
1030 return dict([(k, i) for (k, i) in self._attr(cont).items()
1031 if not hasattr(i, '_delete') or not i._delete])
1032 return [i for i in self._attr(cont)
1033 if not hasattr(i, '_delete') or not i._delete]
1035 @handler(u'Add a new item')
1036 def add(self, cont, *args, **kwargs):
1037 r"add(cont, ...) -> None :: Add an item to the list."
1038 log.debug(u'ComposedSubHandler.add(%r, %r, %r)', cont, args, kwargs)
1039 if not cont in self._cont():
1040 log.debug(u'ComposedSubHandler.add: container not found')
1041 raise ContainerNotFoundError(cont)
1042 item = self._comp_subhandler_class(*args, **kwargs)
1043 if hasattr(item, '_add'):
1044 log.debug(u'ComposedSubHandler.add: _add found, setting to True')
1047 if isinstance(self._attr(cont), dict):
1048 key = item.as_tuple()[0]
1049 # do we have the same item? then raise an error
1050 if key in self._vattr(cont):
1051 log.debug(u'ComposedSubHandler.add: allready exists')
1052 raise ItemAlreadyExistsError(item)
1053 # do we have the same item, but logically deleted? then update flags
1054 if key in self._attr(cont):
1055 log.debug(u'ComposedSubHandler.add: was deleted, undeleting it')
1057 if not isinstance(self._attr(cont), dict):
1058 index = self._attr(cont).index(item)
1059 if hasattr(item, '_add'):
1060 self._attr(cont)[index]._add = False
1061 if hasattr(item, '_delete'):
1062 self._attr(cont)[index]._delete = False
1063 else: # it's *really* new
1064 if isinstance(self._attr(cont), dict):
1065 self._attr(cont)[key] = item
1067 self._attr(cont).append(item)
1068 if hasattr(self._cont()[cont], '_update'):
1069 log.debug(u"ComposedSubHandler.add: container's _update found, "
1071 self._cont()[cont]._update = True
1073 @handler(u'Update an item')
1074 def update(self, cont, index, *args, **kwargs):
1075 r"update(cont, index, ...) -> None :: Update an item of the container."
1076 # TODO make it right with metaclasses, so the method is not created
1077 # unless the update() method really exists.
1078 log.debug(u'ComposedSubHandler.update(%r, %r, %r, %r)',
1079 cont, index, args, kwargs)
1080 if not cont in self._cont():
1081 log.debug(u'ComposedSubHandler.add: container not found')
1082 raise ContainerNotFoundError(cont)
1083 if not isinstance(self._attr(cont), dict):
1084 index = int(index) # TODO validation
1085 if not hasattr(self._comp_subhandler_class, 'update'):
1086 log.debug(u'ComposedSubHandler.update: update() not found, '
1087 u"can't really update, raising command not found")
1088 raise CommandNotFoundError(('update',))
1090 item = self._vattr(cont)[index]
1091 item.update(*args, **kwargs)
1092 if hasattr(item, '_update'):
1093 log.debug(u'ComposedSubHandler.update: _update found, '
1096 if hasattr(self._cont()[cont], '_update'):
1097 log.debug(u"ComposedSubHandler.add: container's _update found, "
1099 self._cont()[cont]._update = True
1101 log.debug(u'ComposedSubHandler.update: item not found')
1102 raise ItemNotFoundError(index)
1104 @handler(u'Delete an item')
1105 def delete(self, cont, index):
1106 r"delete(cont, index) -> None :: Delete an item of the container."
1107 log.debug(u'ComposedSubHandler.delete(%r, %r)', cont, index)
1108 if not cont in self._cont():
1109 log.debug(u'ComposedSubHandler.add: container not found')
1110 raise ContainerNotFoundError(cont)
1111 if not isinstance(self._attr(cont), dict):
1112 index = int(index) # TODO validation
1114 item = self._vattr(cont)[index]
1115 if hasattr(item, '_delete'):
1116 log.debug(u'ComposedSubHandler.delete: _delete found, '
1120 del self._attr(cont)[index]
1121 if hasattr(self._cont()[cont], '_update'):
1122 log.debug(u"ComposedSubHandler.add: container's _update found, "
1124 self._cont()[cont]._update = True
1127 log.debug(u'ComposedSubHandler.delete: item not found')
1128 raise ItemNotFoundError(index)
1130 @handler(u'Remove all items (use with care).')
1131 def clear(self, cont):
1132 r"clear(cont) -> None :: Delete all items of the container."
1133 # FIXME broken really, no item or container _delete attribute is
1135 log.debug(u'ComposedSubHandler.clear(%r)', cont)
1136 if not cont in self._cont():
1137 log.debug(u'ComposedSubHandler.add: container not found')
1138 raise ContainerNotFoundError(cont)
1139 if isinstance(self._attr(cont), dict):
1140 self._attr(cont).clear()
1142 self._attr(cont, list())
1144 @handler(u'Get information about an item')
1145 def get(self, cont, index):
1146 r"get(cont, index) -> item :: List all the information of an item."
1147 log.debug(u'ComposedSubHandler.get(%r, %r)', cont, index)
1148 if not cont in self._cont():
1149 log.debug(u'ComposedSubHandler.add: container not found')
1150 raise ContainerNotFoundError(cont)
1151 if not isinstance(self._attr(cont), dict):
1152 index = int(index) # TODO validation
1154 return self._vattr(cont)[index]
1156 log.debug(u'ComposedSubHandler.get: item not found')
1157 raise ItemNotFoundError(index)
1159 @handler(u'Get information about all items')
1160 def show(self, cont):
1161 r"show(cont) -> list of items :: List all the complete items information."
1162 log.debug(u'ComposedSubHandler.show(%r)', cont)
1163 if not cont in self._cont():
1164 log.debug(u'ComposedSubHandler.add: container not found')
1165 raise ContainerNotFoundError(cont)
1166 if isinstance(self._attr(cont), dict):
1167 return self._attr(cont).values()
1168 return self._vattr(cont)
1170 class ListComposedSubHandler(ComposedSubHandler):
1171 r"""ListComposedSubHandler(parent) -> ListComposedSubHandler instance.
1173 ComposedSubHandler holding lists. See ComposedSubHandler documentation
1177 @handler(u'Get how many items are in the list')
1178 def len(self, cont):
1179 r"len(cont) -> int :: Get how many items are in the list."
1180 log.debug(u'ListComposedSubHandler.len(%r)', cont)
1181 if not cont in self._cont():
1182 raise ContainerNotFoundError(cont)
1183 return len(self._vattr(cont))
1185 class DictComposedSubHandler(ComposedSubHandler):
1186 r"""DictComposedSubHandler(parent) -> DictComposedSubHandler instance.
1188 ComposedSubHandler holding dicts. See ComposedSubHandler documentation
1192 @handler(u'List all the items by key')
1193 def list(self, cont):
1194 r"list(cont) -> tuple :: List all the item keys."
1195 log.debug(u'DictComposedSubHandler.list(%r)', cont)
1196 if not cont in self._cont():
1197 raise ContainerNotFoundError(cont)
1198 return self._attr(cont).keys()
1201 if __name__ == '__main__':
1203 logging.basicConfig(
1204 level = logging.DEBUG,
1205 format = '%(asctime)s %(levelname)-8s %(message)s',
1206 datefmt = '%H:%M:%S',
1212 class STestHandler1(ServiceHandler):
1213 _service_start = ('service', 'start')
1214 _service_stop = ('service', 'stop')
1215 _service_restart = ('ls', '/')
1216 _service_reload = ('cp', '/la')
1217 class STestHandler2(ServiceHandler):
1219 ServiceHandler.__init__(self, 'cmd-start', 'cmd-stop',
1220 'cmd-restart', 'cmd-reload')
1221 class ITestHandler1(InitdHandler):
1222 _initd_name = 'test1'
1223 class ITestHandler2(InitdHandler):
1225 InitdHandler.__init__(self, 'test2', '/usr/local/etc/init.d')
1233 print h.__class__.__name__
1236 except ExecutionError, e:
1240 except ExecutionError, e:
1244 except ExecutionError, e:
1248 except ExecutionError, e:
1253 print 'PTestHandler'
1254 class PTestHandler(Persistent):
1255 _persistent_attrs = 'vars'
1257 self.vars = dict(a=1, b=2)
1275 print 'RTestHandler'
1276 class RTestHandler(Restorable):
1277 _persistent_attrs = 'vars'
1278 _restorable_defaults = dict(vars=dict(a=1, b=2))
1290 print 'CTestHandler'
1292 os.mkdir('templates')
1293 f = file('templates/config', 'w')
1294 f.write('Hello, ${name}! You are ${what}.')
1297 print file('templates/config').read()
1298 class CTestHandler(ConfigWriter):
1299 _config_writer_files = 'config'
1301 self._config_build_templates()
1302 def _get_config_vars(self, config_file):
1303 return dict(name='you', what='a parrot')
1307 print file('config').read()
1309 os.unlink('templates/config')
1310 os.rmdir('templates')
1313 print get_network_devices()