I have updated this page on 22nd Jan 2019 to introduce improvements over the original Action template and module structure. I have introduced the new logger object and JSDoc documentation styles into my code.
Managing your code base in vRealize Orchestrator can be quite challenging and complex. Often, you won’t realise this until you’ve reached a point where it becomes difficult and time consuming to both organise or locate existing code that you have written. In this post, I am going to suggest ways to help you organise your code better, using methods that I have adopted with my time using vRO.
I am not suggesting this be the perfect solution, but it should provide a working standard to adopt to your own needs. I would also argue that the extra time spent getting this in place on the outset, will lead to time saved later on.
Just for reference, from this point on, I am going to refer to ‘code base‘ and ‘actions‘ interchangeably, because your vRO actions ARE your code base. Almost every single line of code you write in vRO should be in an action (I will discuss this in more detail in a later post which I will link here).
Page Contents
Actions
Here are some general principles I like to follow when creating actions: As a general rule, actions should:
- Contain small, manageable chunks of code that perform a specific task. Actions are just functions and just like any function, it should contain code that performs a specific task. If your action is doing many different tasks, then consider breaking these down into multiple, smaller actions.
- Validate inputs. I appreciate that some may debate this idea, but actions are not ‘private functions’. They are public code where you can never guarantee that the action ‘caller’ is properly validating its inputs. This is the nature of vRO, it is a ‘hub’ that has many different uses cases and scenarios for executing the same actions. I have seen dozens of cases where developers and support engineers have wasted time tracking unexpected errors;
- Be named appropriate to the task they perform. I generally like to use verbs in my action names, like, ‘getVirtualMachineNames’, ‘getVirtualMachineNetworks’ or ‘setCustomProperty’. Actions named this way will make it easier for other developers to identify what they are used for;
- Have variables declared in a single block. This will just make it easier to see what variables are being used. The data type can also be defined, but is not always necessary or as important;
- Provide consistent logging throughout. Make it so, the action almost tells the story of what is happening. Don’t go overboard, but generally a before, during and after style to logging works quite well;
- Nesting actions within an action is generally ‘OK’ but keep it to a minimum if possible. Too many nested actions can create depth that may be more difficult to maintain and troubleshoot later on. Typical use cases are ‘helper’ or ‘utility’ actions (you’ll be completely forgiven with actions used for workflow presentation as these are a pain);
- Perform singular tasks. Don’t write actions that perform plural tasks. Write the singular version first, then use a looping mechanism that re-uses the singular action (there are also ways this can be achieved with performance in mind in vRO). This way you’ll only have 1 version of the code;
- Be based on a user-defined template. Yup, I’m not crazy. Have a defined template (aka boilerplate) set out on how an action should generally look and have the team follow this. It will make code reviews far easier;
- Always use camel cased alphabetical characters (no dots);
If you adopt the above principles, you’ll have actions that will be much easier to understand, maintain and troubleshoot and everyone will be a happy bunny.
Action Template
And here is the template:
/*global param1 param2 param3*/ /** * This is an Action template that can be used as a starting point for creating * new Actions in vRealize Orchestrator. * @author Jane Smith <jsmith@example.com> * @version 0.0.0 * @function actionName * @param {data_type} param1 - param1 description. * @param {data_type} param2 - param2 description. * @param {data_type} param3 - param3 description. * @returns {data_type} return description. */ function checkParams(param_1, param_2, param_3) { var inputErrors = []; var errorMessage; if (!param_1 || typeof param_1 !== "string") { inputErrors.push(" - param_1 missing or not of type 'string'"); } if (!param_2 || typeof param_2 !== "number") { inputErrors.push(" - param_2 missing or not of type 'number'"); } if (!param_3 || !(param_3 instanceof Properties)) { inputErrors.push(" - param_3 missing or not of type 'Properties'"); } if (inputErrors.length > 0) { errorMessage = "Mandatory parameters not satisfied:\n" + inputErrors.join("\n"); log.error(errorMessage); } } /** Variable declaration block */ var logType = "Action"; var logName = "actionName"; // This must be set to the name of the action var Logger = System.getModule("com.simplygeek.library.util").logger(logType, logName); var log = new Logger(logType, logName); var itemToReturn; /** Variable declaration block */ /** Code block */ try { checkParams(param1, param2, param3); log.log("start message."); // code log.log("end message."); } catch (e) { log.error("error message.",e); } /** Code block */ return itemToReturn;
If you’re wondering about the ‘logger‘ object, you can read my post here.
Action Example
Here is a working example of an action that has been based on the template.
/*global vcacHost dataCollectionType dataCollectionEntityId*/ /** * Checks if the vRA compute resource data collection entity is enabled or disabled for the given type. * @author Gavin Stephens <gavin.stephens@simplygeek.co.uk> * @version 1.0.0 * @function getDataCollectionEnabledStatus * @param {vCACHost} vcacHost - The vCAC host. * @param {string} dataCollectionType - The data collection type i.e. inventory. * @param {string} dataCollectionEntityId - The data collection entity uuid. * @returns {boolean} returns true if the data collector is enabled. Otherwise returns false. */ function checkParams(vcacHost, dataCollectionType, dataCollectionEntityId) { var inputErrors = []; var errorMessage; if (!vcacHost || System.getObjectType(vcacHost) !== "vCAC:VCACHost") { inputErrors.push(" - vcacHost missing or not of type 'vCAC:VCACHost'"); } if (!dataCollectionType || typeof dataCollectionType !== "string") { inputErrors.push(" - dataCollectionType missing or not of type 'string'"); } if (!dataCollectionEntityId || typeof dataCollectionEntityId !== "string") { inputErrors.push(" - dataCollectionEntityId missing or not of type 'string'"); } if (inputErrors.length > 0) { errorMessage = "Mandatory parameters not satisfied:\n" + inputErrors.join("\n"); log.error(errorMessage); } } /** Variable declaration block */ var logType = "Action"; var logName = "getDataCollectionEnabledStatus"; // This must be set to the name of the action var Logger = System.getModule("com.simplygeek.library.util").logger(logType, logName); var log = new Logger(logType, logName); var dataCollectionEntity; // The data collection entity object. var dataCollectionEnabledStatus; // This will be set to true/false by the entities property value. var dataCollectionEnabled = true; // default data collection status value. /** Variable declaration block */ /** Code block */ try { checkParams(vcacHost, dataCollectionType, dataCollectionEntityId); log.log("Checking if the " + dataCollectionType + " data collection collector is enabled."); dataCollectionEntity = System.getModule("com.simplygeek.library.vcac.compute.datacollection").getDataCollectionEntityByEntityID(vcacHost, dataCollectionType, dataCollectionEntityId); dataCollectionEnabledStatus = dataCollectionEntity.getProperty("IsDisabled"); if (dataCollectionEnabledStatus) { dataCollectionEnabled = false; } log.log("Data collection enabled: " + dataCollectionType); } catch (e) { log.error("Failed to retrieve data collection collector enabled status.",e); } /** Code block */ return dataCollectionEnabled;
Modules
Modules are quite a simple topic but it’s important to get them right as they are almost impossible to change later. Modules are used to organise or group a collection of actions together by a common function.
Here are some general principles I like to follow when creating modules: As a general rule, modules should:
- Conform to a standard naming convention;
- Allow developers to easily find existing actions or where to create new actions. Failure to address this point will result in developers re-inventing the wheel by writing new actions for which may already exist;
- Always be lower case alphabetical characters using dot notation;
- Always have a utility module for storing general ‘helper’ actions i.e. an action that returns a unique array of items
So generally, if you follow a standard naming convention for your module names then you’ll be set. I have found that the following naming convention works well:
com.[company].library.[component].[interface].[parent group].[child group]
Where
[company] = company name as a single word
[component] = Examples are: NSX, vCAC, Infoblox, Activedirectory, Zerto.
[interface] – Optional – Examples are: REST, SOAP, PS. Omit this if you are using the vcenter or vcac plugins.
[parent group] = Parent group i.e. Edges, Entities, Networks, vm.
[child group] = A group containing collections of child actions that relate to the parent group.
Here is an example of what (some) of my Zerto Rest API code library looks like:
You could just create a module where the branch stops at [interface] or [parent group], but what you’ll find, is that it will become a dumping ground for dozens of actions, which will become difficult to maintain. Adding additional child groups helps break the modules up.
This creates a sense of order within your code base. It will be much easier to find actions that you may need and also easier for developers to identify where new code should be placed. When I am writing a framework for an API, I like to align the API structure with my module structure. You can also gain some performance improvements by calling a module once and execute the actions as methods.
I hope this helps provides some standards around your vRO code base. If you have suggestions, comments, etc. then please let me know as I’ll be glad to hear them and value every bit of feedback.
[…] Below is the logger action that I am using (this is the only action I have that does not conform to my template). […]
[…] vRealize Orchestrator: Standardising Modules & Actions […]
[…] vRealize Orchestrator: Standardising Modules & Actions […]