How to write a python module for the MMC agent¶
Creating a Python module¶
Each MMC agent module must be located in the $PYTHONPATH/site-packages/mmc/plugins directory.
When the MMC agent starts, it looks for all Python modules in this path, and tries to activate them.
Each MMC Python module must declare a function call “activate”. This function should make all needed tests that ensures the module will works. This function returns True if all the tests are OK, else False. In the later case, the MMC agent will give up on this module, and won’t export it on the network.
The following method must also be implemented
- getVersion: must return the MMC version of the Python module, which is the same then the MDS version number
- getApiVersion: must return the Python module API number
- getApiRevision: must return the SVN revision number
Here is a MMC Python module skeleton. For example /usr/lib/python2.5/site-packages/mmc/plugins/modulename/__init__.py:
VERSION = "2.0.0" APIVERSION = "4:1:3" REVISION = int("$Rev$".split(':').strip(' $')) def getVersion(): return VERSION def getApiVersion(): return APIVERSION def getRevision(): return REVISION def activate(): return True
A MMC Python module is in the Python language terminology a “package”. So making a __init__.py file is required to make Python treats a directory as containing a package. Please read this section from the Python language tutorial to know more about Python packages system.
Python module configuration file¶
The module configuration file must be located into the /etc/mmc/plugins/module_name.ini file.
The configuration file should be read using a PluginConfig class instance. This class inherits from the ConfigParser class.
This configuration file must at least contains a “main” section with the “disable” option, telling if the module is disabled or not:
[main] disable = 0
If the configuration file doesn’t exist, or doesn’t have the “disable” option, the module is by default considered as disabled.
from mmc.support.config import PluginConfig, ConfigException class ModulenameConfig(PluginConfig): def setDefault(self): """ Set good default for the module if a parameter is missing the configuration file. This function is called in the class constructor, so what you set here will be overwritten by the readConf method. """ PluginConfig.setDefault(self) self.confOption = "option1" # ... def readConf(self): """ Read the configuration file using the ConfigParser API. The PluginConfig.readConf reads the "disable" option of the "main" section. """ PluginConfig.readConf(self) self.confOption = self.get("sectionname", "optionname") # ... def check(self): """ Check the values set in the configuration file. Must be implemented by the subclass. ConfigException is raised with a corresponding error string if a check fails. """ if not self.confOption: raise ConfigException("Conf error") def activate(): # Get module config from "/etc/mmc/plugins/module_name.ini" config = ModulenameConfig("module_name") ... return True
Exporting Python module API¶
All methods defined in the Python module are exported by the MMC agent, and can be directy called using XML-RPC.
def activate(): return True # Module attribute can't be exported with XML-RPC value = 1234 # This method will be exported def func1(arg1A, arg1B): # ... return SomeClass().func1(arg1A, arg1B) # This method will be exported too def func2(arg2A, arg2B): # ... return SomeClass().func2(arg2A, arg2B) # Class can't be exported with XML-RPC ! class SomeClass: def func1(self, argA, argB): # ... return "xxx" def func2(self, argA, argB): # ... return "zzz"
How to launch shell commands inside a Python module¶
As the MMC agent is written on top of Python Twisted, you can’t use the dedicated standard Python modules (like commands or popen) to run shell commands. You must use the Twisted API, and write ProcessProtocol classes.
But we provide simple ProcessProtocol based functions to run a process, and get its outputs.
In blocking mode, if we start a shell command, the twisted server will loop until a process terminates. Blocking mode should not be used for functions that can be called by XML-RPC, because they will completely block the server. The server won’t process other requests until the blocking code is terminated.
But when using the MMC API in command line, it’s simpler to use the blocking mode.
Here is an example:
# Import the shLaunch method from mmc.support.mmctools import shLaunch # Run "ls -l" # shLaunch returns once the shell command terminates proc = shLaunch("ls -l") # Return shell command exit code print proc.exitCode # Return shell command stdout print proc.out # Return shell command stderr print proc.err
Non blocking mode¶
Non blocking-mode should be used when a method called by XML-RPC may block. Basically, the method should not return the result, but a Deferred object attached to a callback corresponding to the result. The twisted reactor will process the deferred, send the result to the callback, and the callback will finally return the wanted result.
Here is an example:
# Import the shLaunchDeferred method from mmc.support.mmctools import shLaunchDeferred def runLs(): def cb(shprocess): # The callback just return the process outputs return shprocess.exitCode, shprocess.out, shprocess.err d = shLaunchDeferred("ls -l") # shLaunchDeferred returns a Deferred() object # We add the cb function as a callback d.addCallback(cb) # We return the Deferred() object return d
For more explanation about Python Twisted and Deferred objects, please read this page.
To use the runLs function in a python script, without the XML-RPC server:
from twisted.internet import reactor, defer from xxx import runLs def printResult(ret): print ret reactor.stop() d = runLs() # runLs returns a deferred object, we add a callback that is just printing the result d.addCallback(printResult) reactor.run()