When developing Abiquo v2.6, simplicity and functionality was the focus and the workflow acceptance tool has achieved just that.
The new workflow feature will allow you to create your own tools to interact with Abiquo processes at the very same moment they are being executed. In short, during certain Abiquo tasks, Abiquo will send an HTTP request with the task details to a configured endpoint. The task details will contain the links to accept or decline the task. It’s as simple as that.

  • Virtualmachine
    • deploy
    • undeploy
  • Virtualapp
    • deploy
    • undeploy
  • Virtualmachine
    • reconfigure

At Abiquo we wanted to create a tool to demonstrate the power of this functionality. This was the reasoning behind creating the workflow acceptance tool. With this tool, every time a workflow event is generated, an email is sent to the users who are responsible for the enterprise. In this email they can easily choose whether to; accept or decline the task the user has requested.

To create the web tool, we used Python and the Python lightweight “web” framework. The other Python eggs required are:

  • requests
  • humanize
  • inspect

Let’s start with the structure of the tool. With “web”, the lightweight HTTP server for Python, we created a web server that responds to:

  • /callback: This is the link that will be called by Abiquo when a workflow task is generated
  • /accept?task=<task>: This link will make Abiquo continue with the pending task. For virtual machine deploy/undeploy or reconfiguration
  • /decline?task=<task>: This link will make Abiquo cancel the pending task. For virtual machine deploy/undeploy or reconfiguration
  • /multiple?action=<action>&tasks=<commaseparated tasks>: This link will make Abiquo continue or cancel a pending task. For virtualapp deploy/undeploy.

Here is part of the webserver as an example. The callback function will receive the post data from the HTTP call and send it to the handler to be processed:

[code lang=”python”] import web
import task_handler as handler
import ConfigParser

config = ConfigParser.ConfigParser()
config.read(‘workflow.cfg’)

bind_address = config.get(‘server’, ‘bind_address’)
bind_port = int(config.get(‘server’, ‘bind_port’))

class WorkflowApplication(web.application):
def run(self, port=8080, *middleware):
func = self.wsgifunc(*middleware)
return web.httpserver.runsimple(func, (bind_address, bind_port))

task.initialize()
urls = (
‘/callback’, ‘callback’,
‘/accept’, ‘accept’,
‘/decline’, ‘decline’,
‘/multiple’, ‘multiple’
)

app = WorkflowApplication(urls, globals())

class callback:
def POST(self):
data = web.data()
handler.new_tasks(data)
[/code]

So, let’s see an example of what information we will receive about a single virtual machine deployment:

[code lang=”xml” light=”true”] <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tasks>
<task>
<link rel="self" href="cloud/virtualdatacenters/3/virtualappliances/9/virtualmachines/59/tasks/d8113ffd-c3d9-4834-8b3c-1b51736c9b8b"/>
<link rel="parent" href="cloud/virtualdatacenters/3/virtualappliances/9/virtualmachines/59/tasks"/>
<link rel="target" href="cloud/virtualdatacenters/3/virtualappliances/9/virtualmachines/59"/>
<link rel="user" href="admin/enterprises/1/users/1"/>
<link rel="cancel" href="cloud/virtualdatacenters/3/virtualappliances/9/virtualmachines/59/tasks/d8113ffd-c3d9-4834-8b3c-1b51736c9b8b/action/cancel"/>
<link rel="continue" href="cloud/virtualdatacenters/3/virtualappliances/9/virtualmachines/59/tasks/d8113ffd-c3d9-4834-8b3c-1b51736c9b8b/action/continue"/>
<taskId>d8113ffd-c3d9-4834-8b3c-1b51736c9b8b</taskId>
<userId>1</userId>
<type>DEPLOY</type>
<ownerId>59</ownerId>
<state>QUEUEING</state>
<creationTimestamp>1372234045</creationTimestamp>
<timestamp>0</timestamp>
<jobs>
<job>
<id>d8113ffd-c3d9-4834-8b3c-1b51736c9b8b.1efe029a-77ef-42b3-bb25-fdfdab2714e9</id>
<parentTaskId>d8113ffd-c3d9-4834-8b3c-1b51736c9b8b</parentTaskId>
<type>SCHEDULE</type>
<description>Deploy task’s schedule on virtual machine with id 59</description>
<state>PENDING</state>
<rollbackState>UNKNOWN</rollbackState>
<creationTimestamp>1372234045</creationTimestamp>
<timestamp>0</timestamp>
</job>
<job>
<id>d8113ffd-c3d9-4834-8b3c-1b51736c9b8b.44126724-940e-4a81-976f-f1377aa11c33</id>
<parentTaskId>d8113ffd-c3d9-4834-8b3c-1b51736c9b8b</parentTaskId>
<type>CONFIGURE</type>
<description>Deploy task’s configure on virtual machine with id 59</description>
<state>PENDING</state>
<rollbackState>UNKNOWN</rollbackState>
<creationTimestamp>1372234045</creationTimestamp>
<timestamp>0</timestamp>
</job>
<job>
<id>d8113ffd-c3d9-4834-8b3c-1b51736c9b8b.9f070573-14e9-4f2c-9cc0-750366a60014</id>
<parentTaskId>d8113ffd-c3d9-4834-8b3c-1b51736c9b8b</parentTaskId>
<type>POWER_ON</type>
<description>Deploy task’s power on on virtual machine with id 59</description>
<state>PENDING</state>
<rollbackState>UNKNOWN</rollbackState>
<creationTimestamp>1372234045</creationTimestamp>
<timestamp>0</timestamp>
</job>
</jobs>
</task>
</tasks>
[/code]

Now let’s analyze it. First there are some API links related to the objects involved (virtual machine, task, user and accept/decline task links). Next, there are some fields that explain the task details, such as the type of task, the user who executed it and so on. After this, the overall task broken down into different jobs (in this case, schedule, configure and power on). Of course, you are able to accept (or decline) the whole task, you can’t just work with some of the jobs.

Once the task request reaches the server, we parse the XML, store the information in a database, and send a notification about the task to the enterprise managers. The role that defines who to send the email to is configurable. In the email we will send an HTML with the accept/cancel links. The following image shows a full example of the email sent.

If we then click, for example, on an Accept button we will then start the process to accept this virtual machine. This will deactivate the task in the database and notify Abiquo that the task must be accepted and then notify the user about the update. This is the piece of code that notifies Abiquo that we want to accept the task:

[code lang=”python”]

def accept(task):

# Prepare the URL accept link and send the post call to Abiquo.
url = config.get(‘abiquo’, ‘api_location’) + "/" + task[‘rel_continue’]   data = json.dumps({})
try:
r = requests.post(url, data, auth=(config.get(‘abiquo’, ‘api_username’),config.get(‘abiquo’, ‘api_password’)))
except:
print "There was an error comunicating with the Abiquo API."

[/code]

Once this has been done, the user will be sent this email below informing them that their task has been processed:

The class to accept this task (which accepts the links from the buttons) looks like this:

[code lang=”python”] class accept:
def GET(self):
web.header(‘Content-Type’, ‘text/html’)
get_var = web.input(task = ‘task’)
response = handler.accept_task(web.websafe(get_var.task), False)
if response[‘code’] == ‘404’:
web.notfound()
if response[‘code’] == ‘410’:
web.gone()
return response[‘html’] [/code]

We simply get the data task ID from the GET HTTP call and send it to the handler. Then, we send the HTML from the handler response, with the proper error code.

  • [200] Accept / Cancel worked fine
  • [404] The task ID cannot be found in the database
  • [410] The task ID exists in database, but it has already been processed.

Essentially, that’s it. As you can see, workflow enables you to easily create tools to run in the midst of Abiquo processes.

You can get the full code of the workflow acceptance tool from:

https://github.com/abiquo/workflow-client