Introduction
Our intent is to provide an easy way to make the OpenStack API compatible with any existing cloud products. This is accomplished through a series of drivers that are specific to each cloud provider.
If you’re reading this, we assume you’re interested in adding OpenStack API compatible driver to an existing public or private cloud provider.
Before You Start
Here are a few things to take into account before creating a new driver:
- Jumpgate was written primarily for Python 2.7/Python 3.3. Your drivers will need to work with these versions as well.
- Drivers are created as a series of objects for the Falcon framework. You should be familiar with both Falcon and REST APIs in general.
- You need to be familiar with the expected OpenStack API JSON. Jumpgate provides the endpoint mappings for you, but doesn’t handle the building of valid responses.
Once you have these things, you’re ready to begin creating your driver.
Creating Your Driver
You have many places where you could begin creating your first driver, but we’ve found starting with the compute index and the Identity driver (Keystone) to be the easiest. To give you the general idea, we’re going to cover how to create a couple endpoints. From there, you can use the SoftLayer driver as a further example of other endpoints, should you need it.
Note that there are no restrictions on how you create your driver as long as you make it work with the Falcon framework. You are free to use whatever libraries, tools, and folder layout you are most comfortable with. For consistency, we’ll use the same style as the SoftLayer driver, but you are not required to do this for your driver.
The Starting Point
The first step in creating your driver is deciding where to create it. You can create it almost anywhere as long as:
- It will be in your Python path
- It can be imported by Jumpgate
We’ll start by creating a small compute driver that implements the index endpoint (/v2/
).
Next, we’re going to create a __init__.py within that directory. Here, you could jam all of your code into it, but that’s going to get extremely clunky for some projects (such as Nova Compute). And since Jumpgate loads this file, we’re going to be importing classes from other modules instead.
To start, make this the contents of your __init__.py file:
Let’s look at each section. First we import the IndexV2 class. This is the actual driver class we’ll be developing for the /v2 endpoint. We’ll cover it in a moment.
Next, we implement a setup_routes()
method which takes two arguments: app
and disp
. app
is an instance of jumpgate.api.Jumpgate which is likely the only instance. This enables us to attach global hooks (before and after) and gives us a clean way to access jumpgate configuration. disp
is an instance of jumpgate.common.dispatcher.Dispatcher
. The Dispatcher.set_handler()
method takes two arguments: The endpoint name and the responder object for to handle that endpoint. Each endpoint that OpenStack supports has a unique name so that you can refer to it without having to know exactly what the URL is. You can either open up the dispatcher to get a list of all of the endpoint names or check out our docs on GitHub to get a list there. The handler is an instance of IndexV2 that we imported earlier.
The Index Endpoint
Now that the driver created, we need to build a response handler. As noted above, we’re starting with the v2_index endpoint, which corresponds to the /v2 path. If you refer to the OpenStack API docs, you’ll find a /v2 endpoint for multiple APIs. The one we’re going to concern ourselves with right now is within the Compute API. If you read the details for the section, you’ll find out that it doesn’t need a request body and the response JSON is straightforward. We could copy the document from the docs exactly and have a valid response, but there are a couple problems with this:
- It has URLs in it
- It doesn’t represent the functionality our driver actually supports.
When implementing things like the index endpoint (and the tokens endpoint later), it’s extremely important to remember that the output is dependent upon what your driver actually supports. In this case, we’re not going to worry about v3 support and we’re going to add in v1 support (to make using Horizon easier).
To start, create the index.py where our IndexV2 class will reside. Start by putting the following within the index.py file:
Response handlers are plain objects and don’t need to inherit from any particular class or interface. Per the Compute API documentation, we know that this endpoint handles the GET verb, so we create an on_get()
function. This is how the Falcon framework handles responses. The contents of the function are what we’re going to do to serve this endpoint. This should look very similar to the sample within the API docs, though you’ll see we’ve added the v1 support as we discussed and we’re not hardcoding URLs.
Because dispatchers handle endpoints, they also know how to build URLs. This is handy because it provides a level of abstraction between your driver and the OpenStack API itself so that if something changed in the future or Jumpgate switched hosts, you shouldn’t need to change any of your driver code. To get the URL for a particular endpoint, call the get_endpoint_url()
method on the dispatcher and pass in the Falcon request object and the identifier for the endpoint. If the endpoint’s URL has variables within it (as a lot of the Nova compute endpoints do), you pass them in as keyword arguments. The only exception to this is the tenant ID, which we’ll discuss later. Each dispatcher only knows about its own endpoints (they’re contained as properties of the object), so you need to use the appropriate one when building your endpoint URL.
The very last thing the function does is assign a body to the response object. This should conform to the expected format within the OpenStack API documentation. Assuming you provide a valid Python dictionary, Jumpgate will automatically JSON encode it for you. Note that the default status code is 200. If you need to assign a different status code, you should refer to the Falcon docs or look at the examples within the SoftLayer driver.
The Tokens Endpoint
The other endpoint example we’re going to provide is the v2_tokens endpoint within the Keystone Identity API. This endpoint is important because every OpenStack tool will first try to authenticate to Keystone before doing anything else, so if you don’t have this, you may have problems. It also has several other interesting examples for a driver that make it worth discussing even if you’re not planning on using Keystone.
As with the index driver, we first need to create a few things. We’ll do it in a larger batch this time:
Create the __init__.py file
This should look familiar to you from the index example earlier. Next, create the tokens.py file where the TokensV2 class will live.
This is the starting point for the driver. If you refer to the Identity API documentation, you’ll see that the /v2.0/tokens endpoint responds to POST, so we’ve created an on_post()
method. Next, we pull the body out of the request stream. After that, we should authenticate the user. The implementation of this is going to be specific to your API, but hopefully you know how to authenticate someone. We’re going to assume that you’ve successfully authenticated the person and put information about him into a dictionary called user, information about his tenant account into a dictionary called account and a string which represents enough information to represent an authenticated session called token. From there, we just need to build the response body based upon what the driver supports and what the API expects.
You’ll notice that this is a lot smaller than what you get back from a native OpenStack Keystone call and that’s because we’re not going to support many modules right now. As you add more drivers, you’ll want to update this dictionary. Lastly, as before, we assign it to the response body and we’re done.
Configuring
Now that we’ve built a couple drivers, we need to tell Jumpgate to use them. This is done by modifying the jumpgate.conf file in the root of the installation directory. By default, Jumpgate uses the OpenStack passthrough drivers. What we want to do instead is use our drivers for the index and identity. Open up the jumpgate.conf file. It should look something like this:
The file is in standard ConfigParser format and should be easy to follow. All we need to do is replace the driver line for both Compute and Identity so that it uses the module path for our drivers instead.
Next Steps
At this point, you have the basics of creating a driver. Now, it’s just a matter of expanding the functionality. Where you go next is up to you and what your goals are. Regardless of what you create next, here are a few things to help you to be more successful.
- Use Horizon in debug mode to test your functionality. Horizon provides a good, standard GUI for interacting with OpenStack and will give you a list of target endpoints to prioritize when implementing your drivers.
- If Horizon is too broad for you, you can also use the various CLI tools provided by Nova and other modules for the same purpose. Just add the
-debug
flag. - Check out the included SoftLayer drivers. We don’t have full OpenStack compatibility yet, but we do have a very usable subset of commands implemented.
Useful Tools
Building any compatibility driver is going to be a large amount of work for any provider, so we’ve included a few things to hopefully make the process easier.
- Within the jumpgate.common directory, there are several libraries for providing common, reusable functionality for things like error handling, formatting, and nested dictionary management. If you find yourself using something else repeatedly, please let us know so that we can include it in the common toolset.
- The dispatcher includes a full set of before and after request hooks that allow you to perform common actions immediately prior to or after acting upon a request. This can allow you to centralize some common functionality. For example, the SoftLayer driver uses it to automatically set the
tenant_id variable
on routes that need it. All you have to do is set thetenant_id property
within the request’s environment dictionary and the dispatcher will automatically include it. - The dispatcher objects include a method called
get_unused_endpoints()
that will provide a list of all endpoints the dispatcher knows about that you haven’t attached handlers to. If you want to get an idea of your coverage.
Compatibility
Each section below includes the following compatibility references for the SoftLayer driver:
- Identity (Keystone)
- Compute (Nova)
- Images (Glance)
- Block Storage (Cinder)
- Network (Quantum)
Identity
This table includes compatibility references for Identity (Keystone).
Verb | Endpoint | Available | Notes |
---|---|---|---|
POST | v2.0/tokens | Yes | |
GET | v2.0/tokens/{tokenId} | Yes | |
DELETE | v2.0/tokens/{tokenId} | Mocked | |
GET | v2.0/tokens/tenants | Yes | |
GET | v2.0/tenants | Yes | |
GET | v3 | Yes | |
POST | v3/auth/tokens | Yes | |
GET | v3/tokens/{token_id} | Yes | |
POST | v3/tokens/{token_id} | Yes | |
DELETE | v3/tokens/{token_id | Mocked |
Compute
This table includes compatibility references for Compute (Nova).
Verb | Endpoint | Available | Notes |
---|---|---|---|
GET | / | Yes | |
GET | v2 | Yes | |
GET | v2/{tenant_id}/extensions | Yes | |
GET | v2/{tenant_id}/extensions/{alias} | Yes | |
GET | v2/{tenant_id}/limits | Mocked | Hardcoded to mocked data. This would effect Horizon's neat graphical displays. |
GET | v2/{tenant_id}/servers | Yes | Missing: public network only, direct IP allocation |
POST | v2/{tenant_id}/servers | Yes | |
GET | v2/{tenant_id}/servers/detail | Yes | |
GET | v2/{tenant_id}/servers/{server_id} | Yes | |
PUT | v2/{tenant_id}/servers/{server_id} | Yes | |
DELETE | v2/{tenant_id}/servers/{server_id} | Yes | |
GET | v2/{tenant_id}/servers/{server_id}/ips | Yes | |
GET | v2/{tenant_id}/servers/{server_id}/ips/{network_label} | Yes | |
POST | v2/{tenant_id}/servers/{server_id}/action | Yes | Missing: suspend/unsuspend/console log/change password/resize/confirmResize/revertResize |
GET | v2/{tenant_id}/servers/{server_id}/os-instance-actions | Yes | |
GET | v2/{tenant_id}/servers/{server_id}/os-instance-actions/{action_id} | Yes | |
GET | v2/{tenant_id}/images | Yes | |
POST | v2/{tenant_id}/images | Yes | Only works with SL Object Storage URLs |
GET | v2/{tenant_id}/images/detail | Yes | |
HEAD | v2/{tenant_id}/images/{image_id} | Yes | |
GET | v2/{tenant_id}/images/{image_id} | Yes | |
DELETE | v2/{tenant_id}/images/{image_id} | Yes | |
GET | v2/{tenant_id}/flavors | Yes | |
GET | v2/{tenant_id}/flavors/detail | Yes | |
GET | v2/{tenant_id}/flavors/{flavor_id} | Yes | |
GET | v2/{tenant_id}/flavors/{flavor_id}/os-extra_specs | Yes | |
GET | v2/{tenant_id}/flavors/{flavor_id}/os-extra_specs/{key_id} | Yes | |
GET | v2/{tenant_id}/os-availability-zone | Yes | |
GET | v2/{tenant_id}/os-availability-zone/detail | Yes | |
GET | v2/{tenant_id}/os-floating-ips-dns | Yes | |
GET | v2/{tenant_id}/os-floating-ips-dns/{domain}/entries/{entry} | Yes | |
PUT | v2/{tenant_id}/os-floating-ips-dns/{domain}/entries/{entry} | Yes | |
DELETE | v2/{tenant_id}/os-floating-ips-dns/{domain}/entries/{entry} | Yes | |
GET | v2/{tenant_id}/os-keypairs | Yes | Private keys are not stored by SoftLayer |
POST | v2/{tenant_id}/os-keypairs | Yes | Private keys are not stored by SoftLayer |
GET | v2/{tenant_id}/os-keypairs/{keypair_name} | Yes | Private keys are not stored by SoftLayer |
POST | v2/{tenant_id}/os-keypairs/{keypair_name} | Yes | Private keys are not stored by SoftLayer |
GET | v2/{tenant_id}/os-tenant-networks | Yes | |
GET | v2/{tenant_id}/os-tenant-networks/{network_id} | Yes | |
GET | v2/{tenant_id}/os-networks | Yes | |
GET | v2/{tenant_id}/os-networks/{network_id} | Yes | |
GET | v2/{tenant_id}/os-simple-tenant-usage/{target_id} | Yes | |
GET | v2/{tenant_id}/servers/{insance_id}/os-volume_attachments/{volume_id} | Yes | |
DELETE | v2/{tenant_id}/servers/{insance_id}/os-volume_attachments/{volume_id} | Yes |
Images
This table includes compatibility references for Images (Glance).
Verb | Endpoint | Available | Notes |
---|---|---|---|
GET | v2/schemas/images | Mocked | |
GET | v2/schemas/image | Mocked | |
GET | v2/images | Yes | |
GET | v2/images/{image_id} | Yes | |
DELETE | v2/images/{image_id} | Yes |
Block Storage
This table includes compatibility references for Block Storage (Cinder).
Verb | Endpoint | Available | Notes |
---|---|---|---|
GET | v1/{tenant_id}/types | Yes | |
GET | v1/{tenant_id}/volumes | Yes | |
POST | v1/{tenant_id}/volumes | Yes | |
GET | v1/{tenant_id}/volumes/{volume_id} | Yes | |
DELETE | v1/{tenant_id}/volumes/{volume_id} | Yes | |
GET | v1/{tenant_id}/volumes/detail | Yes |
Network
This table includes compatibility references for Network (Quantum).
Verb | Endpoint | Available | Notes |
---|---|---|---|
GET | v1/{tenant_id}/networks | Yes | |
GET | v1/{tenant_id}/networks/{network_id} | Yes | |
GET | v1/{tenant_id}/subnets | Yes | |
GET | v1/{tenant_id}/subnets/{subnet_id} | Yes | |
GET | v1/{tenant_id}/extensions | Yes |