From 23c9c26d270ff766133e7aeebffc99a35633ef41 Mon Sep 17 00:00:00 2001
From: Luke Macken <lmacken@redhat.com>
Date: Tue, 15 Jan 2008 17:42:05 -0500
Subject: [PATCH] Simplify our modules by auto-detecting them and registering their handlers

- 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

