In my previous post, we saw how to extend wallaby by writing Ruby classes that use a client library to extend the wallaby shell. If you’re comfortable with Ruby, this is a great way to build functionality on top of the wallaby API in an idiomatic way. However, Python programmers shouldn’t have to learn Ruby just to interact with wallaby.
The wallaby source repository now includes a Python client library to interact with a wallaby service. This library does a few things to make your life easier:
Raw QMF object proxies are wrapped in Python client objects.
Object references returned by QMF methods are automatically wrapped by client objects.
QMF methods that return error statuses will raise exceptions in the client library.
Client classes include docstrings for QMF methods.
See below for a transcript illustrating these features. Note that you’ll need to have the following packages installed to use the Python wallaby client library: Red Hat Enterprise MRG 1.3 (or the python-qmf package from a recent Fedora release) and Wallaby 0.9.18 or later (0.10.0 to access all of the features exposed through the client library).
Example interaction with the Wallaby Python client library.
fromqmf.consoleimportSessionimportwallaby# create a new console objectconsole=Session()# connect to the broker (on localhost:5672, by default)console.addBroker()# => Broker connected at: localhost:5672# find the QMF object for the wallaby serviceraw_store,=console.getObjects(_class="Store")# wrap it up in a client objectstore=wallaby.Store(raw_store,console)# now, interact with it!node=store.addNode("barney.local.")feature=store.addFeature("Example feature")param=store.addParam("EXAMPLE_PARAM")# most "options" arguments are indeed optionalfeature.modifyParams("ADD",{"EXAMPLE_PARAM":"example value"})store.getDefaultGroup().modifyFeatures("ADD",["Example feature"])node.getConfig()# => {u'WALLABY_CONFIG_VERSION': u'0', u'EXAMPLE_PARAM': u'example value'}# What does the activateConfiguration method return?help(store.activateConfiguration)# Help on method activateConfiguration in module wallaby:# # activateConfiguration(self) method of wallaby.Store instance# Returns a tuple consisting of:# * A map containing an explanation of why the configuration isn't valid, or an empty map if the configuration was successfully activated.# * A set of warnings encountered during configuration activation.store.activateConfiguration()# => ({}, [])node.getConfig()# => {u'WALLABY_CONFIG_VERSION': u'1292513031757418', u'EXAMPLE_PARAM': u'example value'}# try something that will cause an errorfeature=store.addFeature("Example feature")# Traceback (most recent call last):# File "<stdin>", line 2, in <module># File "wallaby.py", line 404, in addFeature# raise ClientError(result.status, result.text)# wallaby.ClientError: (67109121, u'Feature name Example feature is already taken')
The most recent few releases of the Wallaby configuration management service have included some great new features: wallaby console can now be used as an interpreter for shebang scripts), wallaby inventory now supports limiting the nodes it lists with expressive constraints, the wallaby http-server command provides access to node configurations over HTTP, and the wallaby command and its subcommands feature many minor functional and aesthetic improvements. One feature that I’m particularly excited about is under the hood: there is now an API for extending the wallaby shell with your own subcommands. In this post, we’ll look at how to make a new wallaby subcommand.
First, make sure you’re running wallaby 0.9.23 or later. If you’re running Fedora, you can simply git clone the repository, check out the v0.9.23 tag, and run rake rpms to generate installable packages.
Next, you’ll want to have a directory in which to put your custom wallaby shell commands. I recommend putting them somewhere in your home directory. (For the purposes of this discussion, we’ll assume that you’re using ~/.wallaby.) The wallaby command will find and attempt to load every ruby file in this directory that begins with cmd_, so you probably don’t want it to be writable by anyone other than you. Once you have this directory set up, set WALLABY_COMMAND_DIR in your environment to this directory:
export WALLABY_COMMAND_DIR=${HOME}/.wallaby
We’ll start by making a fairly straightforward command that simply prints out the API version supported by the wallaby service. To create a new wallaby command, use wallaby new-command:
The -d option allows us to provide a description for the new command (viz., what would show up if we typed wallaby help commands). The -D option allows us to specify a directory in which to create the new command file. (wallaby new-command supports several other options; you can use wallaby help new-command for additional documentation.) After executing this command, we’ll have a documented template file for a new wallaby command, called cmd_api_version.rb, installed in our WALLABY_COMMAND_DIR. It will look something like this:
We can now run wallaby help commands and verify that our command shows up. Sure enough, it does:
We can even run wallaby api-version, although it won’t do anything yet. To make it do something useful, we’re going to edit the act method defined for us in the ApiVersion class. We could do something very simple, like insert puts store.apiVersionNumber before return 0, but it would be nice to allow a user to format the API version number as he or she sees fit, in order to use the result of our command in other scripts.
To let the user supply a format string, we’ll need to add a command-line option, which requires us to edit the init_option_parser method. This method must return an OptionParser object; if you haven’t used Ruby’s option parser class, read up on its documentation. For this example, though, the changes we have to make are pretty minor.
After we’ve added a command-line option and a body for the act method, our cmd_api_version.rb file will look more like this:
Running this command without arguments will return a string like The wallaby service is running version 20100915 of the Wallaby API. If you supply a format string, you can get simply the raw number (with --format '%s') or something more suited to your taste. (Note that this example script doesn’t do any sanity- or error-checking of the format string, which you’d certainly want to do if you were going to put your command into production.)
We’ll look at a more interesting example wallaby shell command and some of the issues that more interesting commands raise in a future post.
wallaby inventory is a useful command for quickly checking up on the health of your pool and answering certain kinds of questions: Which nodes have checked in recently? Which nodes have checked in without being explicitly configured? Which nodes have never checked in?
Recently, I added constraint support to wallaby inventory. That is, instead of listing all nodes, you can choose to list only nodes for which a given Ruby expression is true. These constraints can be simple, like last_checkin < 1.hour_ago (which will find all nodes that have checked in more than one hour ago), or more complex. They are only limited by Ruby’s $SAFE execution: among other things, they cannot modify running classes, perform file I/O, fork new processes, or, because of how QMF is implemented, invoke QMF methods on the node object. However, every QMF property of the node object is available to wallaby inventory constraints. The transcript below provides some examples running against a wallaby agent loaded up with test data.
In some environments, users may wish to use Wallaby to serve configurations to nodes that can’t reach the Qpid broker that the Wallaby agent is running against. Some users may also want to write simple scripts that access current or historical configuration data without writing a QMF console. The wallaby http-server command, which is available beginning with wallaby-0.9.19, offers a solution for such users. It provides a read-only web service gateway to a node’s configuration: the last activated configuration, a particular version, and the node’s current configuration in the store (which may not yet have been activated).
wallaby http-server requires that the Sinatra framework is installed. If you are building RPMs for wallaby, wallaby-http-server will be built as a separate package, and only if you’re building from Fedora 12 or later. If you’re installing from source, simply gem install sinatra before running the HTTP server; if you’ve already installed a wallaby package on a non-Fedora system, you can simply install cmd_http_server.rb from the source repository into your wallaby shell commands directory. (In the future, we expect to package wallaby-http-server for Red Hat Enterprise Linux as well, thus simplifying this process.)
The transcript below shows the HTTP server’s built-in help, listing all of the methods it supports (as of wallaby-0.9.20, which will be released later today). Please let us know if you can think of other API methods that would be useful to expose over the Wallaby HTTP server.
One of the main benefits of using Wallaby for configuration is the remote-access API. Because the API is comprehensive and usable from any language with a QMF binding (including C++, Java, Python, and Ruby), it provides developers and users with the means to build tools, policies, templating systems, and one-off scripts on top of Wallaby.
If you’re writing a Wallaby API client in Ruby, you can use a couple of nice features to ease development: the Wallaby client library, which presents a more polished interface than dealing with raw QMF queries and calls, and the wallaby console utility, which connects to a specified broker and gives you a Ruby REPL that has a global variable pointing to a store client — perfect for casual, interactive experimentation.
Use the same global command line options for wallaby console as you’d use for any other wallaby subcommand: specifying host, port, and authentication information for the broker. Once you’ve started it up, you’ll be dropped into a Ruby prompt where Wallaby::store is a reference to a client proxy object for your Wallaby store. You can then inspect or modify any entity that the store knows about; the store object has accessors called nodes, features, parameters, etc., that return arrays of each kind of object. Then you can invoke methods on each object. The transcript below shows a session in which the user inspects some details of a node in a trivial pool:
In the future, it will be easier to extend the wallaby command with your own subcommands. For now, however, wallaby console provides a great way to interact with the Wallaby API.
Wallaby provides a great way to manage Condor configurations, and if you’re just starting out with Condor, it’s easy to do things the Wallaby way from the start. However, many installations have their own systems for managing Condor features and configuration files — perhaps with separate files representing common, node-specific, and feature-specific configurations. In this post, we’ll talk about wallaby feature-import, a command that lets you import Condor configuration-file snippets into the Wallaby database as Wallaby features, complete with metadata and relationships with other features.
The basic syntax of the file you’ll pass to wallaby feature-import is the same as the basic syntax of a Condor configuration file; in fact, you can import many Condor configuration files without modification. (Only the NAME = VALUE syntax is supported at this time; you can’t use the NAME = +VALUE syntax for DC_DAEMON_LIST.) However, there are some special directives that you can pass to the configuration file as comments:
#name NAME
Indicates that this feature should be named NAME. (Note that you must supply a name for the feature, either on the command line, via -n NAME, or in the file itself.)
#includes FEATURE
Indicates that this feature should include FEATURE, which must exist in Wallaby.
#dependson FEATURE
Indicates that this feature should depend upon FEATURE, which must exist in Wallaby.
#default PARAM
Indicates that this feature should include PARAM, and set it to its default value.
Because Condor ignores these comments, it is possible to use the same file both as a Condor configuration file and as a Wallaby feature description. (Other comments will be ignored by wallaby feature-import, but they may generate harmless warnings.) wallaby feature-import will automatically create any parameters referenced in the file that don’t already exist in Wallaby’s database; this can be a great time-saver for migrating to Wallaby from a custom configuration-management system.
The following example file is a feature that defines eight startds on a single node:
We had a great time at Condor Week 2010 and are excited to announce Wallaby! If you weren’t there, you can check out slides from our talk. Whether you were there or not, please subscribe to our syndication feed to keep up to date with new Wallaby developments, including prerelease packages, tutorials, Wallaby API tools, and official releases.