A love letter to vRO’s LockingSystem

Oh LockingSystem, how do I love thee? Let me count the infinite ways.

The LockingSystem scriptable objects have been around since at least the vCO 5.5 days. In later revisions VMware put a few demo workflows to show how you could take advantage of it, but for the most part it’s not talked about too much. I’ll say this though – when you really start building complex workflows with many interlocking systems, LockingSystem saves lives.

A use case that comes almost immediately to mind is that of vRA and provisioning a large number of machines at once. A notable difference could be that in this environment, we do not use the internal IPAM, but rather the Infoblox IPAM system and the Event Broker to allocate an IP in the PRE BuildingMachine phase.
In a situation where 20-30 VMs are requested for a build, you want to make sure there are no concurrency problems where multiple VMs get the same IP address allocated, right?

Thankfully implementing the LockingSystem is extremely easy, but there are few things to keep in mind to make sure it is implemented well.

The process you wish to lock should be 1 or more separate workflows
In the example above, I have created a separate workflow that calls Infoblox IPAM via the REST API. It takes a couple of inputs, and finds the next available IP on the correct subnet, allocates it, and then outputs the network information that I want for VM customization.

A high level workflow schema for LockingSystem.

Above is a very high-level schema of what a typical implementation of LockingSystem would look like.
Notice that if the nested workflow fails, you will want to ensure the lock is removed! Alternatively you could bind errors to continue ahead without an exception, though this gives you more flexibility to recover gracefully.

For the first element in the use case detailed above, it could be as simple as this code:
LockingSystem.lockAndWait("vRA-IP-Allocation",workflow.id)

The first parameter in the lockAndWait method is an arbitrary name. This is a global value, so if you have other workflows that may call this same nested one, it is worth considering having a naming schema for your locks.
The second parameter is the current workflow token ID. I typically like to see which workflow token has the lock if I need to, but this is also a very easy way to lock a workflow without excessive binding of attributes. Alternatives to use would be the workflow runner’s account name if needed.

Similarly, it is simple to unlock the workflow so that the next process in line can proceed.
LockingSystem.unlock("vRA-IP-Allocation",workflow.id)

Adjusting LockingSystem concurrency
A single unique locking mechanism is nice, but what if you want to tune the concurrency throughput? This would help limit requests to an external system, plugin, or API so that more than one flow can run, but not overwhelm it. Thankfully the LockingSystem has methods that can help you do this, with a little bit of setup.

Pick a line, any line
Let’s say you wanted to be able to limit your number of locks to a maximum of 5, because the plugin or external system can get overwhelmed.
You can request a lock between 1 and 5, such as “vRA-Machine-Build-1” through “vRA-Machine-Build-5.” All you have to do is generate that suffix number, either randomly or through a controlled loop.

A simple random inclusive number function can be found below, and it is probably useful enough to simply make into a vRO Action for future use!
In this sample, your return value would be a number type, and your two inputs, min and max would also be number type.
// Reference code from Mozilla
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min

Then in your Locking scriptable task, you can call the function or action to return a value from 1 to 5 (or whatever values you want!) and append it.

var lane = System.getModule("us.nsfv.shared.locking").getRandomNumber(1,5)
LockingSystem.lockAndWait("vRA-Machine-Build-"+lane,workflow.id)

Find the first open lane
While the random inclusive number (or in this case, the “lane”) can solve the concurrency, you may want there to be more intelligence – i.e. have the LockingSystem get the first available, regardless of which lane it is. There is a simple method simply called .lock() that will return true/false if you successfully acquire a lock of that value.
Thus, you can simply loop through the available locks and wait for one to be true.

for(i=1;i<=5;i++) {
var lockAcquired = LockingSystem.lock("vRA-Machine-Build-"+i,workflow.id);
if(!lockAcquired) {
// no luck getting this one
} else {
// lock acquired, output the lock ID to an attribute so it can be unlocked later.
System.log("Lock "+i+" acquired!");
var attributeLock = "vRA-Machine-Build-"+i;

}
// reset the loop if you've reached the end
if(i == 5) {
i = 0;
}
}

When this loop runs, it will stay essentially in an infinite loop until a lock is freed up, at which point it will exit and continue with whatever process was locked, until it is unlocked afterward.

The functions and scripts above are a good starting point in tuning your vRO workflow concurrency. I hope these help you as much as they have helped me out!

Leave a Reply