Simple XMLRPC Server

In between messing with this site, working on a new Django based application, and work I’ve been toying with Python’s XMLRPC abilities on the server side. As a system administrator I’m typically rather lazy, like, really lazy. Well, maybe it’s not so much lazy, as I just hate being bothered with the incredibly trivial items in life. For instance, restarting Apache when it goes down, or adding a user, these are trivial things. Granted, they must be done at times, and they’re just part of your average sys admins life unfortunately. Still, grabbing a password, logging into an intermediate server, logging in to the end server, then sudoing to the root user, just so I can kick Apache or something equally simple just seems like so much work for so little pay off!

This basically all came into play when I was working on this site, and would push my local changes to the code via git to my server. Then I would rsync my changes from my git checkout, into the doc root of my site. (Yes I know I could just make my git repo the doc root of my site, but if I go mucking around, and revert a commit, I’d rather it not hose my site, k?) Now I could use a tool like Fabric, but at the time I wasn’t aware of such a tool. I was oblivious apparently! Besides, that and Capistrano just seemed like overkill for what I was doing anyways. Hence, I built myself a little XMLRPC Server, and it was quite simple. The documentation from Python.org is actually just about perfect for what I was wanting to pull off. First, we just create a simplistic XMLRPC Server:


from SimpleXMLRPCServer import SimpleXMLRPCServer

from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler

import subprocess, server_status

class RequestHandler(SimpleXMLRPCRequestHandler):
rpc_paths = ('/RPC2',)

def init_functions():
server.register_instance(server_status.server_status())

if __name__ == "__main__":

''' Create server '''
server = SimpleXMLRPCServer(("localhost", 8000),
requestHandler=RequestHandler)
server.register_introspection_functions()

''' Register our remote functions '''
init_functions()

''' Run the server's main loop '''
server.serve_forever()

Stepping through line by line here we create a request handler, and register an instance of “server_status” which is small module I built (we’ll come back to that). I also created the main function, so that you can import this into another project if needed. In there is were we create our simple little server, tell it which port to listen on, and which request handler we should use. Then we register are internal (introspection) functions, such as listing all available methods. Then we call init_functions, which is the function we defined at the top, which registers our custom module. Finally, we tell the server to serve forever (or until it’s killed).

In the server_status module we have the following:


import subprocess

class server_status:
""" A collection of commands used to check server statuses """

def general_disk(self):
""" Just provides the output of df -h """
process = subprocess.Popen(["df -h"], shell=True, stdout=subprocess.PIPE)
temp = process.stdout.read()
return temp

def detailed_disk(self, path):
""" runs du -sh /path/* """
process = subprocess.Popen(["du -sh %s/*" % path], shell=True, stdout=subprocess.PIPE)
temp = process.stdout.read()
return temp

def list_path(self, path, args = "-l"):
""" runs an ls -args path """
listpath = subprocess.Popen(["ls %s %s" % (args,path)], shell=True, stdout=subprocess.PIPE)
temp = listpath.stdout.read()
return temp

def pgrep(self, grepper):
""" Checks for a process running on a server """
process = subprocess.Popen(["ps -ef | grep -v grep | grep %s" % grepper],
shell=True, stdout=subprocess.PIPE)
temp = process.stdout.read()
return temp

All that we have here are simple subprocess calls to the system to find out information (this is all *nix based if you’re curious). This allows me to check for a variety of system stats such as disk usage, and look for running processes. This is wrapped in the server_status class, which we created an instance of, and registered with our simple XMLRPC server. You could create just about any functions you wanted in our server_status class, such as a function to control Apache like so:


def apache(self, command):
""" Controls Apache """
process = subprocess.Popen(["/etc/init.d/apache2 %s" % command], shell=True,
stdout=subprocess.PIPE)
temp = process.stdout.read()
return temp

You could also feel free to create a multitude of classes, just as long as you properly register them with your XMLRPC server instance. So we have our server status and xmlrpc modules, and we can now run our server like so:

python xmlrpcserver.py

Afterwards it’ll start listening on the host and port that you defined earlier. Now we just need to connect from the client. Typically, since I want an interactive shell, I just use ‘ipython’, but you could certainly write yourself a quick and dirty command line based application (or GUI if you so desire) to perform quick functions for you. Here I’ll just be providing the quick and dirty interactive use:


In [1]: import xmlrpclib

In [2]: s = xmlrpclib.ServerProxy('http://localhost:8000')

In [3]: print s.system.listMethods()
['apache', 'detailed_disk', 'general_disk', 'list_path', 'pgrep', 'system.listMethods',
'system.methodHelp', 'system.methodSignature', 'top_grab']

In [4]: print s.apache("status")
* Apache is running (pid 7576).

We import xmlrpclib, we create a server proxy instance connecting to our simple server, and then we’re able to start getting some work done! First I list the available methods, so we know what we have at our disposal. You’ll see the functions we created in our server_status module, as well as some internal functions that we registered as well. Finally, we run our Apache command with the “status” option, and it return the output of that command. What if I wanted to restart the Apache instance?


In [5]: print s.apache("restart")
* Restarting web server apache2
...fail!

Fail?! Why fail?! Well, it failed because I’m running the XMLRPC server as a lower level user that doesn’t have access to restart Apache at will. If the server was running as root, this wouldn’t have been an issue. We could adjust our functions to leverage sudo, with some custom sudo options in /etc/sudoers, but that’s another tutorial for another day. Hopefully someone found this useful. If nothing else, it’s at least somewhere where I can find it from now on :)

Comments

X
This is quite old, I hope you can answer me two questions:

1. Why the import of subprocess on the RPC server code?

2. Can the functions go in the same file that the RPC and use subprocess? I'm unable to detach a subprocess from RPC server, but if launched through os.system() it works… but holding the server.