Making ZXTM more RAD with Jython Extensions

Jython.org Logo As is well documented here on the KnowlegeHub, we support the use of Java extensions to manipulate traffic. One of the great things about supporting "Java" is that this really means supporting the JVM platform... which, in turn, means we support any language that will run on the JVM and can access the Java libraries. Declan's article on writing extensions in Scala is a recent example exploiting this fact.

Java isn't my first choice of languages, especially when it comes to web development. And while I think Scala is cool, I prefer not to have to compile my web code. My language of choice in this sphere is Python; and thanks to the Jython project we can write our extensions in Python!

(Note: If you're particularly impatient, or already an expert, there's a checklist at the end of this article summarising the steps required to get Jython running on ZXTM.)

Jython comes with a servlet named PyServlet which will work with ZXTM out-of-the-box, this is a good place to start. First let us quickly set up our Jython environment, working Java support is a prerequisite of course. (If you're not already familiar with ZXTM Java extensions I recommend reviewing the Java Development Guide available from the Diagnose > Technical Support section of the ZXTM UI, you can also find the ZXTM 5.1 documentation here. It is also worth reading over some of our other KnowledgeHub articles on the topic.)

  1. Grab Jython 2.5 (if there is now a more recent version I expect it will work fine as well)
  2. Jython comes in the form of a .jar installer, on Linux I recommend using the CLI install variant:
    java -jar jython_installer-2.5.0.jar -c
  3. Install to a path of your choosing, I've used: /space/jython
  4. Upload /space/jython/jython.jar to your Java Extensions Catalog

In the Java Extensions Catalog you can now see a couple of extensions provided by jython.jar, including org.python.util.PyServlet. By default this servlet maps URLs ending in .py to local Python files which it will compile and cache. Set up a test HTTP Virtual Server (I've created a test one called "Jython" tied to the "discard" pool), and add the following request rule to it:

if (string.endsWith(http.getPath(), ".py")) {
   java.run( "org.python.util.PyServlet" );
}

The rule avoids errors by only invoking the extension for .py files. (Though if you invoke PyServlet for a .py file that doesn't exist you will get a nasty NullPointerException stack trace in your event log.)

Next, create a file called Hello.py containing the following code:

from javax.servlet.http import HttpServlet

class Hello(HttpServlet):
   def doGet(self, request, response):
      toClient = response.getWriter()
      response.setContentType ("text/html")
      toClient.println("<html><head><title>Hello from Python</title>" +
         "<body><h1 style='color:green;'>Hello from Python!</h1></body></html>")

Upload Hello.py to your ZXTM's Java Extensions Catalog. Now if you visit the file Hello.py at the URL of your VirtualServer you should see the message "Hello from Python!" Hey, we didn't have to compile anything! Your event log will have some messages about processing .jar files, this only happens on first invocation. You will also get a warning in your event log every time you visit Hello.py:

WARN java/* servleterror Servlet org.python.util.PyServlet: Unknown attribute (pyservlet)

This is just because ZXTM doesn't set up or use this particular attribute. We'll ignore the warning for now, and get rid of it when we tailor PyServlet to be more convenient for ZXTM use. The servlet will also have created some some extra files in your Java Libraries & Data Catalog under a top-level directory WEB-INF. It is possible to change the location used for these files, we'll get back to that in a moment.

All quite neat so far, but the icing on the cake is yet to come. If you open up $ZEUSHOME/conf/jars/Hello.py in your favourite text editor and change the message to "Hello <em>again</em> from Python!" and refresh your browser you'll notice that the new message comes straight through. This is because the PyServlet code checks the .py file, if it is newer than the cached bytecode it will re-interpret and re-cache it. We now have somewhat-more-rapid application development for ZXTM extensions. Bringing together the excellence of Python and the extensiveness of Java libraries.

However, what I really want to do is use TrafficScript at the top level to tell PyServlet which Python servlet to run. This will require a little tweaking of the code. We want to get rid of the code that resolves .py file from the request URI and replace it with code that uses an argument passed in by TrafficScript. While we're at it, we'll make it non-HTTP-specific and add some other small tweaks. The changes are documented in comments in the code, which you can download here: ZeusPyServlet.java

You can download my compiled version if you want to avoid compiling the code yourself. Alternatively, to compile the ZXTM version of the servlet and pop the two classes into a single convenient .jar file execute the following commands.

javac -cp $ZEUSHOME/zxtm/lib/servlet.jar:/space/jython/jython.jar ZeusPyServlet.java
jar -cvf ZeusPyServlet.jar ZeusPyServlet.class ZeusPyServletCacheEntry.class

(Adjusting paths to suit your environment if necessary.)

Upload the ZeusPyServlet.jar file to your ZXTM's Java Extensions Catalog. You should now have a ZeusPyServlet extension available. Change your TrafficScript rule to load this new extension and provide an appropriate argument.

if (string.endsWith(http.getPath(), ".py")) {
    java.run( "ZeusPyServlet", "Hello.py" );
}

Now visiting Hello.py works just as before. In fact, visiting any URL that ends in .py will now generate the same result as visiting Hello.py. We have complete control over what Python code is executed from our TrafficScript rule, much more convenient.

If you continue hacking from this point you'll soon find that we're missing core parts of python with the setup described so far. For example adding import md5 to your servlet code will break the servlet, you'd see this in your ZXTM Event Log:

WARN servlets/ZeusPyServlet Servlet threw exception javax.servlet.ServletException:
           Exception during init of /opt/zeus/zws/zxtm/conf/jars/ServerStatus.py
WARN  Java: Traceback (most recent call last):
WARN  Java:    File "/opt/zeus/zws/zxtm/conf/jars/ServerStatus.py", line 2, in <module>
WARN  Java:      import md5
WARN  Java: ImportError: No module named md5

This is because the class files for the core Python libraries are not included in jython.jar. To get a fully functioning Jython we need to tell ZeusPyServlet where Jython is installed. To do this you must have Jython installed on the same machine as the ZXTM software, and then you just have to set a configuration parameter for the servlet, in summary:

  1. Install Jython on your ZXTM machine, I've installed mine to /space/jython
  2. In Catalogs > Java > ZeusPyServlet add some parameters:
    • Parameter: python_home, Value: /space/jython (or wherever you have installed Jython)
    • Parameter: debug, Value: none required (this is optional, it will turn on some potentially useful debug messages)
  3. Back in Catalogs > Java you can now delete all the WEB-INF files, now that Jython knows where it is installed it doesn't need this
  4. Go to System > Traffic Managers and click the 'Restart Java Runner ...' button, then confim the restart (this ensures no bad state is cached)

Now your Jython should be fully functional, here's a script for you to try that uses MD5 functionality from both Python and Java. Just replace the content of Hello.py with the following code.

from javax.servlet.http import HttpServlet
from java.security import MessageDigest
from md5 import md5

class Hello(HttpServlet):
   def doGet(self, request, response):
      toClient = response.getWriter()
      response.setContentType ("text/html")
      htmlOut = "<html><head><title>Hello from Python</title><body>"
      htmlOut += "<h1>Hello from Python!</h1>"

      # try a Python md5
      htmlOut += "<h2>Python MD5 of 'foo': %s</h2>" % md5("foo").hexdigest()

      # try a Java md5
      htmlOut += "<h2>Java MD5 of 'foo': "
      jmd5 = MessageDigest.getInstance("MD5")
      digest = jmd5.digest("foo")
      for byte in digest:
         htmlOut += "%02x" % (byte & 0xFF)
      htmlOut += "</h2>" 

      # yes, the Zeus attributes are available
      htmlOut += "<h2>VS: %s</h2>" % request.getAttribute("virtualserver")

      htmlOut += "</body></html>"
      toClient.println(htmlOut)

The expected output when you visit a .py URL is roughly (the styling will be a bit different):

Hello from Python!
Python MD5 of 'foo': acbd18db4cc2f85cedef654fccc4a4d8
Java MD5 of 'foo': acbd18db4cc2f85cedef654fccc4a4d8
VS: Jython

An important point to realise about Jython is that beyond the usual core Python APIs you cannot expect all the 3rd party Python libraries out there to "just work". Non-core Python modules compiled from C (and any modules that depend on such modules) are the main issue here. For example the popular Numeric package will not work with Jython. Not to worry though, there are often pure-Python alternatives. Don't forget that you have all Java libraries available too; and even special Java libraries designed to extend Python-like APIs to Jyhon such as JNumeric, a Jython equivalent to Numeric. There's more information on the Jython wiki. I recommend reading through all the FAQs as a starting point. It is perhaps best to think of Jython as a language which gives you the neatness of Python syntax and the Python core with the utility of the massive collection of Java APIs out there.

Lets do something more interesting to finish off. The ZXTM Java and TrafficScript APIs don't offer comprehensive control of the software, but we have a SOAP API which does.

In theory one would expect Jython should give us several approaches to SOAP, some examples: Apache AXIS from Java, Python's SOAPpy, or Python's ZSI. In practice it doesn't seem to be practical to work with ZSI or SOAPpy, I had trouble getting either of these working. The most straightforward solution turns out to be AXIS. The following example will interface with ZXTM via SOAP from Jython using AXIS. I'm going to assume some pre-knowledge of Java to avoid delving into configuration details. My example ZXTM host system is Ubuntu Linux with the openjdk-6-jre package installed, and Jython installed in /space/jython.

Start out by setting up AXIS support as described here: Accessing ZXTM's Control API from Java

Once this is done you should have installed AXIS, JavaMail, and JAF .jar files in your ZXTM's Java Extensions Catalog.

This full list of files in the catalog relating to this article is shown in the screenshot below.

Jython Java Catalog Screenshot

I've ported the Java extension from the ZXTM ServerStatus: Java Extensions and the ZXTM Control API article to Python. I've written a fairly direct line-for-line translation of the original Java code so as to provide a good side-by-side comparison of the implementations. The output should be essentially identical. To use this Python extension:

  1. Download ServerStatus.py
  2. Upload ServerStatus.py to your ZXTM's Java Extensions Catalog
  3. Create a Java trusted keystore containing your ZXTM's admin server certificate (if you're using a self-signed certificate):
    1. SSH to your ZXTM, and execute:
    2. openssl x509 -in $ZEUSHOME/admin/etc/admin.public -out /tmp/admin.crt
    3. keytool -import -file /tmp/admin.crt -noprompt -storepass abcdef \
      -keystore $ZEUSHOME/zxtm/conf/jars/java.keystore
    4. Update 2009-10-27: Finally, ensure that you set the path to the java.keystore file correctly in the ServerStatus.py __init__ function.
  4. Adjust your Jython TrafficScript rule to load "ServerStatus.py" instead of "Hello.py"
  5. Visit the same URL as before
  6. You should be prompted for login details, provide the username and password of your ZXTM's "admin" user
  7. You should now see information about your ZXTM!

(If you don't see server status information check the ZXTM Event Log to see if there are any errors.)

Here's a sample of the code from our Jython implementation of the ServerStatus servlet:

   def doGet( self, req, res ):
      try:
         userPass = req.getRemoteUserAndPassword()
         if userPass == None:
            raise ServletException("No authentication details")

         # username is userPass[0], password is userPass[1]
         admin = "https://%s:%s@localhost:9090/soap" % \
            (userPass[0], userPass[1])
         smil = SystemMachineInfoLocator()
         smil.setSystemMachineInfoPortEndpointAddress( admin )
         smip = smil.getSystemMachineInfoPort()

         # will throw an exception if admin username and password are incorrect
         smip.getProductVersion()

         result = self.generateReport( admin )
         res.setContentType( "text/html" )
         out = res.getWriter()
         out.println( "<h1>System Status</h1>" )
         out.println( result )

      except Exception, e:
         res.setHeader("WWW-Authenticate", "Basic realm=\"ZXTM Server Status\"")
         res.setHeader("Content-Type", "text/html")
         res.setStatus(401)
         message = \
             "<html>" + \
             "<head><title>Unauthorized</title></head>" + \
             "<body>" + \
             "<h2>Unauthorized - please log in</h2>" + \
             "<p>Please log in with the Admin username and password</p>" + \
             "<p>Error: " + e.toString() + "</p>" + \
             "</body>" + \
             "</html>"
         out = res.getWriter()
         out.println( message )

And here's the equivalent functionality from the original Java code.

   public void doGet( HttpServletRequest req, HttpServletResponse res )
      throws ServletException, IOException
   {
      try {
         ZXTMHttpServletRequest zreq = (ZXTMHttpServletRequest)req;

         String[] userPass = zreq.getRemoteUserAndPassword();
         if( userPass == null ) throw new Exception( "No Authentication details" );

         // Username is userPass[0], password is userPass[1]
         String admin = "https://"+userPass[0]+":"+userPass[1]+"@localhost:9090/soap";

         SystemMachineInfoLocator smil = new SystemMachineInfoLocator();
         smil.setSystemMachineInfoPortEndpointAddress( admin );
         SystemMachineInfoPort smip = smil.getSystemMachineInfoPort();

         // will throw an exception if admin username and passwd are incorrect
         smip.getProductVersion();

         // No exceptions thrown... must have been successful ;-)
         String result = generateReport( admin );

         res.setContentType( "text/html" );

         PrintWriter out = res.getWriter();
         out.println( "<h1>System Status</h1>" );
         out.println( result );
      } catch( Exception e ) {
           res.setHeader( "WWW-Authenticate", "Basic realm=\"ZXTM Server Status\"" );
           res.setHeader( "Content-Type", "text/html" );
           res.setStatus( 401 );

           String message =
               "<html>" +
               "<head><title>Unauthorized</title></head>" +
               "<body>" +
               "<h2>Unauthorized - please log in</h2>" +
               "<p>Please log in with the Admin username and password</p>" +
               "<p>Error: " + e.toString() + "</p>" +
               "</body>" +
               "</html>";

           PrintWriter out = res.getWriter();
           out.println( message );
       }
   }

So that's it, the examples in this article should be enough to get you started writing Jython extensions for processing ZXTM traffic. You don't have to worry about compiling them. Making ZXTM even more RAD!

Of course, the compile-and-cache model used by this ZeusPyServlet extension could be applied to other JVM languages too. Languages such as Scala, JRuby, and even Java itself.

Jython on ZXTM Checklist

For the really impatient you can try simply running through the checklist below to get Jython working on your Zeus traffic manager:

  1. Download the Jython 2.5 Installer
  2. Install Jython 2.5 on your ZXTM host machine with:
           java -jar jython_installer-2.5.0.jar -c

    Install to a location of your choice, which we'll call $JYTHONHOME

  3. Copy jython.jar to ZXTM's configuration:
          cp $JYTHONHOME/jython.jar $ZEUSHOME/zxtm/conf/jars/
  4. Download the following items:

  5. Unpack all of these into some working directory of your choice
  6. Upload the following files to your ZXTM's Java Catalog (file paths based on the versions I've downloaded):

    • axis-1_4/lib/axis-ant.jar
    • axis-1_4/lib/axis.jar
    • axis-1_4/lib/commons-discovery-0.2.jar
    • axis-1_4/lib/commons-logging-1.0.4.jar
    • axis-1_4/lib/jaxrpc.jar
    • axis-1_4/lib/saaj.jar
    • axis-1_4/lib/wsdl4j-1.5.1.jar
    • javamail-1.4.1/mail.jar
    • jaf-1.0.2/activation.jar
    • ZeusPyServlet/ZeusPyServlet.jar
    • ZeusPyServlet/ZXTM-API.jar
    • ZeusPyServlet/ServerStatus.py (optional sample servlet)

    An alternative to uploading them is to copy them directly to $ZEUSHOME/zxtm/conf/jars/

  7. Visit Catalogs > Java > ZeusPyServlet in the ZXTM admin interface and add a parameter to the servlet:
    Parameter Name: python_home
    Value: $JYTHONHOME
    Where $JYTHONHOME is replaced by the actual path to the Jython install location on your ZXTM host machine (see step 2)
  8. To get the optional sample servlet create the following TrafficScript rule:

          if (string.endsWith(http.getPath(), ".py")) {
              java.run( "ZeusPyServlet", "ServerStatus.py" );
          }

    Update 2009-10-27: Now set up the SSL trust store if you are using a self-signed SSL certificate by SSHing to the ZXTM host machine, setting $ZEUSHOME and then running:

          openssl x509 -in $ZEUSHOME/admin/etc/admin.public -out /tmp/admin.crt
          keytool -import -file /tmp/admin.crt -noprompt -storepass abcdef \
                -keystore $ZEUSHOME/zxtm/conf/jars/java.keystore

    After doing this the path to the java.keystore file must be set correctly in the ServerStatus.py file.

  9. Visiting any URL ending in .py at the VirtualServer should now return ZXTM status information
  10. For more detail see the full article above
Yvan Seth [Zeus Dev Team] 31 July 2009 Bookmark with del.icio.us Post this article to Digg Post this article to reddit Post this article to Facebook Tweet this article  
Leave a comment ...
Your email address will not be displayed.
Your URL will be displayed.
This public messageboard is not a forum for technical support. To report technical support problems, please contact our dedicated Support team using the instructions at the bottom of this page.
Options:
 
(Line breaks become <br />)
(Set cookies for name, email & url)

Recently...

Other Resources