[PATCH] Simplify our modules by auto-detecting them and registering their handlers

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Dear Lords of Func,

This is a pretty significant change to the way our modules are currently
written.  The result is that it makes writing modules much more simple, by
removing all hard-coded method definitions and register_rpc methods.

Some brief testing on my end shows it to work fine, but still needs more
testing to ensure that we don't regress.

If no one has any problems with it, I can commit it.

luke

===

- Auto-detect and load all FuncModules.  This obsoletes the need to have our
  modules define a register_rpc method.
- Use introspection in our FuncModule to auto-register all method handlers
  that do not being with an underscore.  This obsoletes the need to
  hardcode methods in our modules.
- Remove all __init__ methods from our modules, along with register_rpc
- Modify the func-create-module script to reflect these changes.  Note that
  doing 'from modules import func_module' is no longer supported in our modules,
  do to some interesting path issues with our auto-detection code.  Supported
  methods are now:
      'import func_module' or 'from func.minion.modules import func_module'

diff --git a/func/minion/module_loader.py b/func/minion/module_loader.py
index 6fb2a6b..f8f8f42 100755
--- a/func/minion/module_loader.py
+++ b/func/minion/module_loader.py
@@ -19,10 +19,11 @@ import sys
 from gettext import gettext
 _ = gettext
 
-
 from func import logger
 logger = logger.Logger().logger
 
+from inspect import isclass
+from func.minion.modules import func_module
 
 def module_walker(topdir):
     module_files = []
@@ -80,13 +81,15 @@ def load_modules(blacklist=None):
             continue
 
         try:
+            # Auto-detect and load all FuncModules
             blip =  __import__("modules.%s" % ( mod_imp_name), globals(), locals(), [mod_imp_name])
-            if not hasattr(blip, "register_rpc"):
-                errmsg = _("%(module_path)s%(modname)s module not a proper module")
-                logger.warning(errmsg % {'module_path': module_file_path, 'modname':mod_imp_name})
-                bad_mods[mod_imp_name] = True
-                continue
-            mods[mod_imp_name] = blip
+            for obj in dir(blip):
+                attr = getattr(blip, obj)
+                if isclass(attr):
+                    if issubclass(attr, func_module.FuncModule):
+                        logger.debug("Loading %s module" % attr)
+                        mods[mod_imp_name] = attr()
+
         except ImportError, e:
             # A module that raises an ImportError is (for now) simply not loaded.
             errmsg = _("Could not load %s module: %s")
diff --git a/func/minion/modules/command.py b/func/minion/modules/command.py
index 3329927..9b93de5 100644
--- a/func/minion/modules/command.py
+++ b/func/minion/modules/command.py
@@ -13,19 +13,11 @@
 Abitrary command execution module for func.
 """
 
-from modules import func_module
-
+import func_module
 import sub_process
 
 class Command(func_module.FuncModule):
 
-    def __init__(self):
-        self.methods = {
-                "run" : self.run,
-                "exists" : self.exists, 
-        }
-        func_module.FuncModule.__init__(self)
-
     def run(self, command):
         """
         Runs a command, returning the return code, stdout, and stderr as a tuple.
@@ -46,7 +38,3 @@ class Command(func_module.FuncModule):
         if os.access(command, os.X_OK):
             return True
         return False
-
-
-methods = Command()
-register_rpc = methods.register_rpc
\ No newline at end of file
diff --git a/func/minion/modules/copyfile.py b/func/minion/modules/copyfile.py
index 6c81098..85001f8 100644
--- a/func/minion/modules/copyfile.py
+++ b/func/minion/modules/copyfile.py
@@ -14,7 +14,7 @@ import os
 import time
 import shutil
 
-from modules import func_module
+import func_module
 
 
 
@@ -22,15 +22,6 @@ from modules import func_module
 class CopyFile(func_module.FuncModule):
     version = "0.0.1"
     api_version = "0.0.2"
-    
-    
-
-    def __init__(self):
-        self.methods = {
-                "copyfile" : self.copyfile,
-                "checksum" : self.checksum
-        }
-        func_module.FuncModule.__init__(self)
 
     def _checksum_blob(self, blob):
         CHUNK=2**16
@@ -118,8 +109,3 @@ class CopyFile(func_module.FuncModule):
             #XXX logger output here
             return False
         return True
-
-
-
-methods = CopyFile()
-register_rpc = methods.register_rpc
diff --git a/func/minion/modules/filetracker.py b/func/minion/modules/filetracker.py
index 0aa4a49..8deb382 100644
--- a/func/minion/modules/filetracker.py
+++ b/func/minion/modules/filetracker.py
@@ -16,7 +16,7 @@
 ##
 
 # func modules
-from modules import func_module
+import func_module
 
 # other modules
 from stat import *
@@ -30,17 +30,6 @@ CONFIG_FILE='/etc/func/modules/filetracker.conf'
 
 class FileTracker(func_module.FuncModule):
 
-    def __init__(self):
-        self.methods = {
-            "track"       : self.track,
-            "untrack"     : self.untrack,
-            "info"        : self.inventory,
-            "inventory"   : self.inventory,
-        }
-        func_module.FuncModule.__init__(self)
-
-    #==========================================================
-
     def __load(self):
         """
         Parse file and return data structure.
@@ -145,7 +134,7 @@ class FileTracker(func_module.FuncModule):
             gid = filestat[ST_GID]
             if not os.path.isdir(file_name) and checksum_enabled:
                 sum_handle = open(file_name)
-                hash = self.sumfile(sum_handle)
+                hash = self.__sumfile(sum_handle)
                 sum_handle.close()
             else:
                 hash = "N/A"
@@ -185,7 +174,7 @@ class FileTracker(func_module.FuncModule):
 
     #==========================================================
 
-    def sumfile(self, fobj):
+    def __sumfile(self, fobj):
         """
         Returns an md5 hash for an object with read() method.
         credit: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266486
@@ -198,6 +187,3 @@ class FileTracker(func_module.FuncModule):
                 break
             m.update(d)
         return m.hexdigest()
-
-methods = FileTracker()
-register_rpc = methods.register_rpc
diff --git a/func/minion/modules/func_module.py b/func/minion/modules/func_module.py
index 5965e24..c911b91 100644
--- a/func/minion/modules/func_module.py
+++ b/func/minion/modules/func_module.py
@@ -10,6 +10,7 @@
 ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 ##
 
+import inspect
 
 from func import logger
 from func.config import read_config
@@ -34,7 +35,7 @@ class FuncModule(object):
             "module_api_version" : self.__module_api_version,
             "module_description" : self.__module_description,
             "list_methods"       : self.__list_methods
-            }
+        }
 
     def __init_log(self):
         log = logger.Logger()
@@ -45,8 +46,11 @@ class FuncModule(object):
         # can get clobbbered by subclass versions
         for meth in self.__base_methods:
             handlers["%s.%s" % (module_name, meth)] = self.__base_methods[meth]
-        for meth in self.methods:
-            handlers["%s.%s" % (module_name,meth)] = self.methods[meth]
+
+        # register all methods that don't start with an underscore
+        for attr in dir(self):
+            if inspect.ismethod(getattr(self, attr)) and attr[0] != '_':
+                handlers["%s.%s" % (module_name, attr)] = getattr(self, attr)
 
     def __list_methods(self):
         return self.methods.keys() + self.__base_methods.keys()
@@ -59,7 +63,3 @@ class FuncModule(object):
 
     def __module_description(self):
         return self.description
-
-
-methods = FuncModule()
-register_rpc = methods.register_rpc
diff --git a/func/minion/modules/hardware.py b/func/minion/modules/hardware.py
index acf5988..5136228 100644
--- a/func/minion/modules/hardware.py
+++ b/func/minion/modules/hardware.py
@@ -20,18 +20,11 @@ import sys
 
 # our modules
 import sub_process
-from modules import func_module
+import func_module
 
 # =================================
 
 class HardwareModule(func_module.FuncModule):
-    def __init__(self):
-        self.methods = {
-            "info"      : self.info,
-            "inventory" : self.inventory,       # for func-inventory
-            "hal_info"  : self.hal_info
-        }
-        func_module.FuncModule.__init__(self)
 
     def hal_info(self):
         """
@@ -131,6 +124,3 @@ def hw_info(with_devices=True):
         })
 
     return data
-
-methods = HardwareModule()
-register_rpc = methods.register_rpc
diff --git a/func/minion/modules/mount.py b/func/minion/modules/mount.py
index e8e41ce..1b4671d 100644
--- a/func/minion/modules/mount.py
+++ b/func/minion/modules/mount.py
@@ -13,18 +13,10 @@
 ##
 
 import sub_process, os
-from modules import func_module
+import func_module
 
 
 class MountModule(func_module.FuncModule):
-    def __init__(self):
-        self.methods = {
-            "list": self.list,
-            "mount": self.mount,
-            "umount": self.umount,
-            "inventory": self.inventory,
-        }
-        func_module.FuncModule.__init__(self)
 
     def list(self):
         cmd = sub_process.Popen(["/bin/cat", "/proc/mounts"], executable="/bin/cat", stdout=sub_process.PIPE, shell=False)
@@ -86,7 +78,3 @@ class MountModule(func_module.FuncModule):
 
     def inventory(self, flatten=True):
         return self.list()
-
-
-methods = MountModule()
-register_rpc = methods.register_rpc
diff --git a/func/minion/modules/nagios-check.py b/func/minion/modules/nagios-check.py
index a902762..80889ce 100644
--- a/func/minion/modules/nagios-check.py
+++ b/func/minion/modules/nagios-check.py
@@ -13,18 +13,11 @@
 Abitrary command execution module for func.
 """
 
-from modules import func_module
-
+import func_module
 import sub_process
 
 class Nagios(func_module.FuncModule):
 
-    def __init__(self):
-        self.methods = {
-                "run" : self.run
-        }
-        func_module.FuncModule.__init__(self)
-
     def run(self, check_command):
         """
         Runs a nagios check returning the return code, stdout, and stderr as a tuple.
@@ -35,8 +28,3 @@ class Nagios(func_module.FuncModule):
         cmdref = sub_process.Popen(command.split(),stdout=sub_process.PIPE,stderr=sub_process.PIPE, shell=False)
         data = cmdref.communicate()
         return (cmdref.returncode, data[0], data[1])
-
-methods = Nagios()
-register_rpc = methods.register_rpc
-
-
diff --git a/func/minion/modules/process.py b/func/minion/modules/process.py
index 92c6d6a..18b5abe 100644
--- a/func/minion/modules/process.py
+++ b/func/minion/modules/process.py
@@ -18,19 +18,11 @@ import sub_process
 import codes
 
 # our modules
-from modules import func_module
+import func_module
 
 # =================================
 
 class ProcessModule(func_module.FuncModule):
-    def __init__(self):
-        self.methods = {
-            "info"    : self.info,
-            "kill"    : self.kill,
-            "pkill"   : self.pkill,
-            "mem"     : self.mem
-        }
-        func_module.FuncModule.__init__(self)
 
     def info(self,flags="-auxh"):
         """
@@ -208,6 +200,3 @@ class ProcessModule(func_module.FuncModule):
         # example killall("thunderbird","-9")
         rc = sub_process.call(["/usr/bin/pkill", name, level], executable="/usr/bin/pkill", shell=False)
         return rc
-
-methods = ProcessModule()
-register_rpc = methods.register_rpc
diff --git a/func/minion/modules/reboot.py b/func/minion/modules/reboot.py
index 8772b8f..0af78f9 100644
--- a/func/minion/modules/reboot.py
+++ b/func/minion/modules/reboot.py
@@ -8,22 +8,10 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-
-from modules import func_module
-
+import func_module
 import sub_process
 
 class Reboot(func_module.FuncModule):
 
-    def __init__(self):
-        self.methods = {
-                "reboot" : self.reboot
-        }
-        func_module.FuncModule.__init__(self)
-
     def reboot(self, when='now', message=''):
         return sub_process.call(["/sbin/shutdown", '-r', when, message])
-
-
-methods = Reboot()
-register_rpc = methods.register_rpc
diff --git a/func/minion/modules/rpms.py b/func/minion/modules/rpms.py
index 901a9d6..f73e9db 100644
--- a/func/minion/modules/rpms.py
+++ b/func/minion/modules/rpms.py
@@ -8,18 +8,11 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-from modules import func_module
+import func_module
 import rpm
 
 class RpmModule(func_module.FuncModule):
 
-    def __init__(self):
-        self.methods = {
-           "inventory" : self.inventory,
-           "info"      : self.inventory
-        }
-        func_module.FuncModule.__init__(self)
-
     def inventory(self, flatten=True):
         """
         Returns information on all installed packages.
@@ -43,6 +36,3 @@ class RpmModule(func_module.FuncModule):
             else:
                 results.append([name,epoch,version,release,arch])
         return results
-
-methods = RpmModule()
-register_rpc = methods.register_rpc
diff --git a/func/minion/modules/service.py b/func/minion/modules/service.py
index d088907..d1170a8 100644
--- a/func/minion/modules/service.py
+++ b/func/minion/modules/service.py
@@ -13,26 +13,13 @@
 ##
 
 import codes
-from modules import func_module
+import func_module
 
 import sub_process
 import os
 
 class Service(func_module.FuncModule):
 
-    def __init__(self):
-        self.methods = {
-            "start"       : self.start,
-            "stop"        : self.stop,
-            "restart"     : self.restart,
-            "reload"      : self.reload,
-            "status"      : self.status,
-            "get_enabled" : self.get_enabled,
-            "get_running" : self.get_running,
-            "inventory"   : self.inventory,
-        }
-        func_module.FuncModule.__init__(self)
-
     def __command(self, service_name, command):
 
         filename = os.path.join("/etc/rc.d/init.d/",service_name)
@@ -95,6 +82,3 @@ class Service(func_module.FuncModule):
                 tokens = line.split()
                 results.append((tokens[0], tokens[-1].replace("...","")))
         return results
-
-methods = Service()
-register_rpc = methods.register_rpc
diff --git a/func/minion/modules/smart.py b/func/minion/modules/smart.py
index e85b90c..ff7afd9 100644
--- a/func/minion/modules/smart.py
+++ b/func/minion/modules/smart.py
@@ -17,17 +17,11 @@
 import sub_process
 
 # our modules
-from modules import func_module
+import func_module
 
 # =================================
 
 class SmartModule(func_module.FuncModule):
-    def __init__(self):
-        self.methods = {
-            "info"         : self.info,
-            "inventory"    : self.info,  # for func-inventory
-        }
-        func_module.FuncModule.__init__(self)
 
     def info(self,flags="-q onecheck"):
         """
@@ -47,6 +41,3 @@ class SmartModule(func_module.FuncModule):
             results.append(x)
 
         return (cmd.returncode, results)
-
-methods = SmartModule()
-register_rpc = methods.register_rpc
diff --git a/func/minion/modules/snmp.py b/func/minion/modules/snmp.py
index 3144144..374821c 100644
--- a/func/minion/modules/snmp.py
+++ b/func/minion/modules/snmp.py
@@ -13,19 +13,12 @@
 Abitrary command execution module for func.
 """
 
-from modules import func_module
-
+import func_module
 import sub_process
 base_snmp_command = '/usr/bin/snmpget -v2c -Ov -OQ'
 
 class Snmp(func_module.FuncModule):
 
-    def __init__(self):
-        self.methods = {
-                "get" : self.get
-        }
-        func_module.FuncModule.__init__(self)
-
     def get(self, oid, rocommunity, hostname='localhost'):
         """
         Runs an snmpget on a specific oid returns the output of the call.
@@ -39,8 +32,3 @@ class Snmp(func_module.FuncModule):
     #def walk(self, oid, rocommunity):
 
     #def table(self, oid, rocommunity):
-    
-methods = Snmp()
-register_rpc = methods.register_rpc
-
-
diff --git a/func/minion/modules/test.py b/func/minion/modules/test.py
index 24af03e..6718fed 100644
--- a/func/minion/modules/test.py
+++ b/func/minion/modules/test.py
@@ -1,17 +1,10 @@
-from modules import func_module
+import func_module
 import time
 
 class Test(func_module.FuncModule):
     version = "11.11.11"
     api_version = "0.0.1"
     description = "Just a very simple example module"
-    def __init__(self):
-        self.methods = {
-            "add":  self.add,
-            "ping": self.ping,
-            "sleep": self.sleep
-        }
-        func_module.FuncModule.__init__(self)
 
     def add(self, numb1, numb2):
         return numb1 + numb2
@@ -27,6 +20,3 @@ class Test(func_module.FuncModule):
         t = int(t)
         time.sleep(t)
         return time.time()
-
-methods = Test()
-register_rpc = methods.register_rpc
diff --git a/func/minion/modules/virt.py b/func/minion/modules/virt.py
index ba888ec..3671172 100644
--- a/func/minion/modules/virt.py
+++ b/func/minion/modules/virt.py
@@ -119,31 +119,8 @@ class FuncLibvirtConnection(object):
 
 class Virt(func_module.FuncModule):
 
-
-    def __init__(self):
-
-        """
-        Constructor.  Register methods and make them available.
-        """
-
-        self.methods = {
-            "install"   : self.install,
-            "shutdown"  : self.shutdown,
-            "destroy"   : self.destroy,
-            "start"     : self.create,
-            "pause"     : self.pause,
-            "unpause"   : self.unpause,
-            "delete"    : self.undefine,
-            "status"    : self.get_status,
-	    "info"      : self.info,
-	    "inventory" : self.info,   # for func-inventory
-            "list_vms"  : self.list_vms,
-        }
-
-        func_module.FuncModule.__init__(self)
-
-    def get_conn(self):
-	self.conn = FuncLibvirtConnection()
+    def __get_conn(self):
+        self.conn = FuncLibvirtConnection()
         return self.conn
 
     def state(self):
@@ -176,7 +153,7 @@ class Virt(func_module.FuncModule):
 
 
     def list_vms(self):
-        self.conn = self.get_conn()
+        self.conn = self.__get_conn()
         vms = self.conn.find_vm(-1)
         results = []
         for x in vms:
@@ -195,7 +172,7 @@ class Virt(func_module.FuncModule):
         # Example:
         # install("bootserver.example.org", "fc7webserver", True)
 
-        conn = self.get_conn()
+        conn = self.__get_conn()
 
         if conn is None:
             raise codes.FuncException("no connection")
@@ -227,7 +204,7 @@ class Virt(func_module.FuncModule):
         Make the machine with the given vmid stop running.
         Whatever that takes.
         """
-        self.get_conn()
+        self.__get_conn()
         self.conn.shutdown(vmid)
         return 0
 
@@ -237,7 +214,7 @@ class Virt(func_module.FuncModule):
         """
         Pause the machine with the given vmid.
         """
-        self.get_conn()
+        self.__get_conn()
         self.conn.suspend(vmid)
         return 0
 
@@ -248,7 +225,7 @@ class Virt(func_module.FuncModule):
         Unpause the machine with the given vmid.
         """
 
-        self.get_conn()
+        self.__get_conn()
         self.conn.resume(vmid)
         return 0
 
@@ -258,7 +235,7 @@ class Virt(func_module.FuncModule):
         """
         Start the machine via the given mac address.
         """
-        self.get_conn()
+        self.__get_conn()
         self.conn.create(vmid)
         return 0
 
@@ -269,7 +246,7 @@ class Virt(func_module.FuncModule):
         Pull the virtual power from the virtual domain, giving it virtually no
         time to virtually shut down.
         """
-        self.get_conn()
+        self.__get_conn()
         self.conn.destroy(vmid)
         return 0
 
@@ -281,7 +258,7 @@ class Virt(func_module.FuncModule):
         by deleting the disk image and it's configuration file.
         """
 
-        self.get_conn()
+        self.__get_conn()
         self.conn.undefine(vmid)
         return 0
 
@@ -292,9 +269,5 @@ class Virt(func_module.FuncModule):
         Return a state suitable for server consumption.  Aka, codes.py values, not XM output.
         """
 
-        self.get_conn()
+        self.__get_conn()
         return self.conn.get_status(vmid)
-
-
-methods = Virt()
-register_rpc = methods.register_rpc
diff --git a/func/minion/modules/yumcmd.py b/func/minion/modules/yumcmd.py
index b3976fb..8bf760c 100644
--- a/func/minion/modules/yumcmd.py
+++ b/func/minion/modules/yumcmd.py
@@ -8,8 +8,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-
-from modules import func_module
+import func_module
 
 import yum
 from yum import repos
@@ -22,13 +21,6 @@ class DummyCallback(object):
 
 class Yum(func_module.FuncModule):
 
-    def __init__(self):
-        self.methods = {
-                "update" : self.update,
-                "check_update" : self.check_update
-        }
-        func_module.FuncModule.__init__(self)
-
     def update(self):
         # XXX support updating specific rpms
         ayum = yum.YumBase()
@@ -52,7 +44,3 @@ class Yum(func_module.FuncModule):
         ayum.doTsSetup()
         ayum.repos.enableRepo(repo)
         return map(str, ayum.doPackageLists('updates').updates)
-
-
-methods = Yum()
-register_rpc = methods.register_rpc
diff --git a/scripts/func-create-module b/scripts/func-create-module
index afb4d09..7cbf9eb 100755
--- a/scripts/func-create-module
+++ b/scripts/func-create-module
@@ -10,9 +10,7 @@
 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
 TEMPLATE = """\
-from modules import func_module
-# Add your imports here
-import sub_process
+import func_module
 
 class %s(func_module.FuncModule):
 
@@ -21,17 +19,7 @@ class %s(func_module.FuncModule):
     api_version = "0.0.1"
     description = "%s"
 
-    def __init__(self):
-        self.methods = {
 %s
-        }
-        func_module.FuncModule.__init__(self)
-
-%s
-
-
-methods = %s()
-register_rpc = methods.register_rpc
 """
 
 
@@ -40,12 +28,9 @@ def populate_template(module_name, desc, methods):
     Makes the method strings and populates the template.
     """
     actual_methods = ""
-    method_str_dict = ""
     for method in methods:
-        method_str_dict += '            "%s": self.%s,\n' % (method, method)
-        actual_methods += "    def self.%s(self):\n        pass\n\n" % method
-    return TEMPLATE % (module_name, desc, 
-                       method_str_dict[:-1], actual_methods[:-2], module_name)
+        actual_methods += "    def %s(self):\n        pass\n\n" % method
+    return TEMPLATE % (module_name, desc, actual_methods[:-2])
 
 
 if __name__ == '__main__':
@@ -63,4 +48,4 @@ if __name__ == '__main__':
     file_obj = open(file_name, "w")
     file_obj.write(populate_template(module_name, desc, methods))
     file_obj.close()
-    print "Your module is ready to be hacked on. Wrote out to %s." % file_name
\ No newline at end of file
+    print "Your module is ready to be hacked on. Wrote out to %s." % file_name
-- 
1.5.3.3

_______________________________________________
Func-list mailing list
Func-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/func-list

[Index of Archives]     [Fedora Users]     [Linux Networking]     [Fedora Legacy List]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]

  Powered by Linux