summaryrefslogtreecommitdiffstats
path: root/pyfirstaidkit/plugins.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyfirstaidkit/plugins.py')
-rw-r--r--pyfirstaidkit/plugins.py352
1 files changed, 352 insertions, 0 deletions
diff --git a/pyfirstaidkit/plugins.py b/pyfirstaidkit/plugins.py
new file mode 100644
index 0000000..8d892c8
--- /dev/null
+++ b/pyfirstaidkit/plugins.py
@@ -0,0 +1,352 @@
+# First Aid Kit - diagnostic and repair tool for Linux
+# Copyright (C) 2007 Martin Sivak <msivak@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+from configuration import Config
+from returns import *
+from errors import *
+
+import FirstAidKit
+from log import Logger
+
+import imp
+import os
+import subprocess
+from cStringIO import StringIO
+
+class Flow(dict):
+ def __init__(self, rules, description="", *args, **kwargs):
+ self.description = description
+ dict.__init__(self, rules, *args, **kwargs)
+
+class Plugin(object):
+ #
+ # Some information vars.
+ #
+ name = "Plugin"
+ version = "0.0.0"
+ author = "nobody"
+ #
+ # Dictionary that holds all the flows. The keys for each flow is its
+ # name. The flow will be addressed by this name. The plugin developer
+ # Can add as many flows as he wants. The developer must use the instance.
+ # obj._flows["name"] = SomeFlow. Be aware that you can overwirhte
+ # previously added flows. This class attribute has to be overriden by
+ # each plugin.
+ #
+ flows = {}
+
+ #
+ # The initial and final states are here to give more flexibilty to the
+ # Development process. All flows will start and end with these two
+ # Variables.
+ #
+ initial = 0
+ final = 1
+
+ #
+ # The flow to use with the automated repair mode
+ #
+
+ default_flow = "defflow"
+
+ #
+ # This is the default flow that all classes deriving from plugin must
+ # have. As the initial state has no return value it will be indexed
+ # with the parent of all ReturnValue classes.
+ #
+ _defflows = {}
+ _defflows["defflow"] = Flow({
+ initial : {ReturnValue: "prepare"},
+ "prepare" : {ReturnValueTrue: "diagnose"},
+ "diagnose" : {ReturnValueTrue: "clean", ReturnValueFalse: "backup"},
+ "backup" : {ReturnValueTrue: "fix", ReturnValueFalse: "clean"},
+ "fix" : {ReturnValueTrue: "clean", ReturnValueFalse: "restore"},
+ "restore" : {ReturnValueTrue: "clean", ReturnValueFalse: "clean"},
+ "clean" : {ReturnValueTrue: final}
+ }, description="The default, fully automated, fixing sequence")
+
+ def __init__(self, flow, reporting, dependencies):
+ """ Initialize the instance.
+
+ flow -- Name of the flow to be used with this instance.
+ reporting -- object used to report information to the user
+ dependencies -- object encapsulating the inter-plugin dependency API (require, provide)
+
+ The flow is defined in the __init__ so we don't have to worry about changing it.
+ """
+ self._reporting = reporting
+ self._dependencies = dependencies
+
+ self.provide = dependencies.provide
+ self.require = dependencies.require
+
+ #
+ # state we are in.
+ #
+ self._state = Plugin.initial
+
+ #
+ # Used to hold the return value of the functions in the class.
+ #
+ self._result = None #edge from the state we are in
+
+ #
+ # Choose the flow for the instance.
+ #
+ self.defineFlow(flow)
+
+ def call(self, step):
+ """call one step from plugin"""
+ self._result = None #mark new unfinished step
+ self._state = step
+ return getattr(self, step)()
+
+ @classmethod
+ def info(cls):
+ """Returns tuple (Plugin name, Plugin version, Plugin author)"""
+ return (cls.name, cls.version, cls.author)
+
+ #
+ # The flow functions.
+ #
+ def defineFlow(self, flow):
+ """Defines the current flow to name.
+
+ flow -- Name of the flow
+ This function is to be called from the __init__ only. There will be the flows defined by the
+ Plugin class and the flows defined by the actual plugin. We will first search the Plugin
+ class and then the plugin itself for the name.
+ """
+ #
+ # The flow that will be used for the instance.
+ #
+ if flow in Plugin._defflows.keys():
+ self.cflow = Plugin._defflows[flow]
+ elif flow in self.__class__.flows.keys():
+ self.cflow = self.__class__.flows[flow]
+ else:
+ raise InvalidFlowNameException(flow)
+
+ @classmethod
+ def getFlows(cls):
+ """Return a set with the names of all possible flows."""
+ fatherf = Plugin._defflows.keys()
+ pluginf = cls.flows.keys()
+ return set(fatherf+pluginf)
+
+ @classmethod
+ def getFlow(cls, name):
+ """Return a Flow object associated with provided name"""
+ if cls.flows.has_key(name):
+ return cls.flows[name]
+ else:
+ return Plugin._defflows[name]
+
+ #dependency stuff
+ @classmethod
+ def getDeps(cls):
+ """Return list of conditions required to be set before automated run can be done"""
+ return set()
+
+ #methods available only for instance, see interpreter.py and dependency stuff there
+ #def require(self, id)
+ #def provide(self, id)
+
+ #list of all actions provided
+ def actions(self):
+ """Returns list of available actions"""
+ return set(["prepare", "backup", "diagnose", "describe", "fix", "restore", "clean"])
+
+ def nextstate(self, state=None, result=None):
+ """Returns next state when analizing self._state, self._result and the self.cflow in automode.
+
+ state -- Name of hte function.
+ result -- The return value of the previous function
+ We do not check for validity of the key in the self.cflow. If key is invalid, function will
+ Traceback. When self._state = self.final the function will traceback. This situation must
+ be handled outside this function. If an automatica iteration is needed that avoids the
+ necesity to address the self.final state, use __iter__ and next.
+ """
+ # If any of the vals are missing, we default to the current ones.
+ if state is None or result is None:
+ state=self._state
+ result=self._result
+ # The self.initial state does not have any return code.
+ # It will only work with the ReturnValue.
+ try:
+ if state == self.initial:
+ self._state = self.cflow[self.initial][ReturnValue]
+ else:
+ self._state = self.cflow[state][result]
+ return self._state
+ except KeyError:
+ raise InvalidFlowStateException(self.cflow)
+
+ #
+ #iterate protocol allows us to use loops
+ #
+ def __iter__(self):
+ self._state = self.initial
+ self._result = None
+ return self
+
+ def next(self):
+ """Iteration function.
+
+ Will return (self._state, self._result). The function that was executed and the return value.
+ """
+ func = self.nextstate()
+ if func == self.final:
+ raise StopIteration()
+ else:
+ # Execute the function.
+ getattr(self, func)()
+ return (self._state, self._result)
+
+ #
+ #default (mandatory) plugin actions
+ #
+ def prepare(self):
+ """Initial actions.
+
+ All the actions that must be done before the execution of any plugin function.
+ This function generaly addresses things that are global to the plugin.
+ """
+ #We want these functions to be overridden by the plugin developer.
+ if self.__class__ is Plugin:
+ Logger.warning("Clean is an abstract method, it should be used as such.")
+
+ def clean(self):
+ """Final actions.
+
+ All the actions that must be done after the exection of all plugin functions.
+ This function generaly addresses things that are global and need to be closed
+ off, like file descriptos, or mounted partitions....
+ """
+ #We want these functions to be overridden by the plugin developer.
+ if self.__class__ is Plugin:
+ Logger.warning("Clean is an abstract method, it should be used as such.")
+
+ def backup(self):
+ """Gather important information needed for restore."""
+ #We want these functions to be overridden by the plugin developer.
+ if self.__class__ is Plugin:
+ Logger.warning("Clean is an abstract method, it should be used as such.")
+
+ def restore(self):
+ """Try to restore the previous state described in backup."""
+ #We want these functions to be overridden by the plugin developer.
+ if self.__class__ is Plugin:
+ Logger.warning("Clean is an abstract method, it should be used as such.")
+
+ def diagnose(self):
+ """Diagnose the situation."""
+ #We want these functions to be overridden by the plugin developer.
+ if self.__class__ is Plugin:
+ Logger.warning("Clean is an abstract method, it should be used as such.")
+
+ def fix(self):
+ """Try to fix whatever is wrong in the system."""
+ #We want these functions to be overridden by the plugin developer.
+ if self.__class__ is Plugin:
+ Logger.warning("Clean is an abstract method, it should be used as such.")
+
+class PluginSystem(object):
+ """Encapsulate all plugin detection and import stuff"""
+
+ def __init__(self, reporting, dependencies, config = Config):
+ self._path = Config.plugin.path
+ self._reporting = reporting
+ self._deps = dependencies
+ self._plugins = {}
+
+ #create list of potential modules in the path
+ importlist = set()
+ for f in os.listdir(self._path):
+ fullpath = os.path.join(self._path, f)
+ Logger.debug("Processing file: %s", f)
+ if os.path.isdir(fullpath) and os.path.isfile(os.path.join(self._path, f, "__init__.py")):
+ importlist.add(f)
+ Logger.debug("Adding python module (directory): %s", f)
+ elif os.path.isfile(fullpath) and (f[-3:]==".so" or f[-3:]==".py"):
+ importlist.add(f[:-3])
+ Logger.debug("Adding python module (file): %s", f)
+ elif os.path.isfile(fullpath) and (f[-4:]==".pyc" or f[-4:]==".pyo"):
+ importlist.add(f[:-4])
+ Logger.debug("Adding python module (compiled): %s", f)
+
+ #try to import the modules as FirstAidKit.plugins.modulename
+ for m in importlist:
+ if m in Config.plugin._list("disabled"):
+ continue
+
+ imp.acquire_lock()
+ try:
+ Logger.debug("Importing module %s from %s", m, self._path)
+ moduleinfo = imp.find_module(m, [self._path])
+ module = imp.load_module(".".join([FirstAidKit.__name__, m]), *moduleinfo)
+ Logger.debug("... OK")
+ finally:
+ imp.release_lock()
+
+ self._plugins[m] = module
+
+ def list(self):
+ """Return the list of imported plugins"""
+ return self._plugins.keys()
+
+ def autorun(self, plugin, flow = None, dependencies = True):
+ """Perform automated run of plugin with condition checking
+returns - True if conditions are fully satisfied
+ False if there is something missing
+ exception when some other error happens"""
+
+ pklass = self._plugins[plugin].get_plugin() #get top level class of plugin
+ Logger.info("Plugin information...")
+ Logger.info("name:%s , version:%s , author:%s " % pklass.info())
+
+ flows = pklass.getFlows()
+ Logger.info("Provided flows : %s " % flows)
+ if flow==None:
+ flowName = pklass.default_flow
+ else:
+ flowName = flow
+
+ Logger.info("Using %s flow" % flowName)
+ if flowName not in flows:
+ Logger.error("Flow %s does not exist in plugin %s", flowName, plugin)
+ raise InvalidFlowNameException(d)
+
+ if dependencies:
+ deps = pklass.getDeps()
+ Logger.info("depends on: %s" % (", ".join(deps),))
+ for d in deps:
+ if not self._deps.require(d):
+ Logger.info("depends on usatisfied condition: %s" % (d,))
+ return False
+
+ p = pklass(flowName, reporting = self._reporting, dependencies = self._deps)
+ for (step, rv) in p: #autorun all the needed steps
+ Logger.info("Running step %s in plugin %s ...", step, plugin)
+ Logger.info("%s is current step and %s is result of that step." % (step, rv))
+
+ return True
+
+ def getplugin(self, plugin):
+ """Get instance of plugin, so we can call the steps manually"""
+ return self._plugins[plugin].get_plugin()
+