Introducing the Nimbula API Python Bindings

In this post I’d like to talk about one of the ways to use Nimbula Director that hasn’t been discussed before. Currently, users can access the core functionality via the command line client, our web UI or by making HTTP calls directly against our RESTful web API. All of these methods are explained extensively in our documentation (which can be found under the references section here).

In this post I’ll be talking about a fourth method: the Nimbula API Python Bindings. This is a Python library which is distributed alongside our tools (which can be found here). It provides all the functionality available against any Nimbula entity via HTTP API calls to a Nimbula Director endpoint. Let’s look at some examples:

Setting up the environment

To use the bindings we first need to set up the environment to point at the API endpoint we would like to use. The standard way to do this is by setting the NIMBULA_API environment variable (which is also used by our CLI tools). For example:

$ export NIMBULA_API="http://api.example.com/"

Now any subsequent library calls will work against the specified endpoint. If you prefer not to use environment variables it is possible to set the API endpoint directly in code by setting the base_url property of the transport you are using, for example:

from nimbula.api import client
client.DEFAULT_TRANSPORT.base_url = "http://api.example.com/"

Hello Cloud

Now that the endpoint is set we can write our first program. In this example we log in to the cloud as the user ‘project/user’ with the password ‘p4ssw0rd’ and then get some basic details about the site:


from nimbula.api import models as m
m.User.authenticate('project/user', 'p4ssw0rd')
print m.SiteInformation.get()

On my development cluster this produces:

<SiteInformation: {'name': u'usdev150', 'idpname': u'usdev150', 'uri': None, 'licensed': True, 'version': u'2.0.1', 'fingerprint': u'06:7E:67:24:C0:38:2C:F8:6D:97:CE:D2:CC:7F:7B:FF:2C:DC:C0:C0'}>

You may notice that these are all synchronous calls which correspond with REST calls against the API.

Now that we’ve done our first example, let’s look at something more useful. In the following example, we launch two instances (a database and webserver) with two different shapes and two different images. We also set a further placement requirement that these two instances run on the same node. Finally, we wait till the instances have started and then print out their IP addresses.

from nimbula.api import models as m,client as c
from time import sleep
m.User.authenticate('project/user','p4ssw0rd')
lp = m.LaunchPlan()

web_instance = m.Instance()
db_instance = m.Instance()

web_instance.label = 'webserver'
web_instance.imagelist = '/project/webimages/webserver'
web_instance.shape = 'medium'

db_instance.label = 'database'
db_instance.imagelist = '/project/webimages/database'
db_instance.shape = 'large'

lp.instances = [web_instance, db_instance]
lp.relationships = [{ "instances":["database", "webserver" ],
                      "type":"same_node" }  ] # we want the instances to run on the same node

lp.save() # The api launch call happens here

for inst in lp.instances: # The following two lines are necessary for
    inst.bind()           # older versions of the product

def is_running(instance):
    instance.refresh()
    return instance.state == 'running'

while not all(map(is_running, lp.instances)):
    print 'Waiting for instances to start'
    sleep(5)

print 'Instances started'
print '\n'.join(['Instance label: %s , IP: %s' % (i.label, i.ip) for i in lp.instances])

Twisted transport

As I mentioned earlier, the previous examples have all been synchronous calls to the REST API, achieved using a blocking HTTP request. However our Python bindings can also make use of the Twisted library for asynchronous IO. This allows you to make API calls and register callbacks for when they complete. Twisted is a great event-based networking library which we make use of extensively for our internal services. More information can be found here.

There are two ways to make use of theTwisted transport when using our Python bindings. One way is by setting the transport directly on the client model:

from nimbula.api import client
from nimbula.api.transports.http.twisted import PersistentTwistedHTTPTransport
client.DEFAULT_TRANSPORT = PersistentTwistedHTTPTransport

The second way is by importing twisted.internet at the beginning of your script. Our Python bindings will inspect the modules imported and if Twisted is present it will make use of that.

Here is a simple example to delete all instances on the Nimbula deployment using the Twisted transport:


from twisted.internet import reactor, defer
from nimbula.api import models as m

@defer.inlineCallbacks
def delete_instances():
    yield m.User.authenticate('project/user', 'p4ssw0rd')
    instances = yield m.Instance.list()
    dl = [instance.delete() for instance in instances]
    print 'Waiting on callbacks'
    yield defer.DeferredList(dl)
    print 'Done'
    reactor.stop()

delete_instances()
reactor.run()

EC2 API: Using boto with Nimbula Director

In Nimbula Director 2.0 we introduced our AWS EC2 shim which allows users to interact with a Director deployment using any EC2 compatible tools by implementing both the SOAP and Query protocols. One of the popular AWS tools is the boto library, which provides Python bindings to the AWS API.

To use the AWS API you first need to create authentication credentials. To do this you can run:

$ nimbula-api add accesskey /project/user/mykey

This will create an access and secret key pair with the identifier /project/user/mykey. These can then be used against Director’s AWS API shim. Any commands which are then run against the cloud with these credentials will run with the permissions of the user that created the key. Once you have created the authentication credentials, using the Nimbula EC2 API through boto is simple. Here is an example of a simple boto program to print all of the instances which are running:


import boto
from boto.ec2.regioninfo import RegionInfo
hostname = "api.example.com" # the nimbula API endpoint
region = RegionInfo(name='nimbula', endpoint=hostname)
accesskey = '< your access key >'
secretkey = '< your secret key >'

connection = boto.connect_ec2(aws_access_key_id=accesskey,
    aws_secret_access_key=secretkey, is_secure=False,
    region=region, path="/aws/")
print connection.get_all_instances()

Check it out yourself!

So that should all be enough to try it out for yourself. These examples have only shown a few of the objects you can interact with, however there are many more. A great way to explore the Python bindings is with ipython and its tab completion: simply import the models module in nimbula.api.models and see which objects are available.

Alternatively you can read the source distributed alongside the tools (download here), under tools/source/.