Introduction
Continuing the vRO deep dive series and this is the 4th part. This will be discussing log handling, exception handling and failing back.
Feel free to revisit the previous deep dive series:
Log Handling
vRO provides 3 types of log handling:
- Log
- Warning
- Error
Let’s do some exercise and see the difference between them.
First of all, create a workflow calling it what you would like to (I called it “hi”) and navigate to the Log tab under Schema. You will find 7 default loggings:
For the starters, let’s try System log out. Drag and drop the element in like the following:
Edit the System log and navigate to Scripting. You will find an auto-generated script “System.log(text);”.
Let’s take a look at System warning and error as well, drag and drop others like the following screenshot:
Edit the System warning and it will show you a similar script as System log, “System.warn(text);”
Pretty much identical for System error, “System.error(text);”
Let’s run the workflow and see how they look differ on the vRO logging tab. Create an attribute called attrText and the value to be “Different types of logging”.
For each element, finish up the visual binding like the following:
Time to run the workflow. Execute and it will present 3 different types of logging:
- Log in a normal font
- Warning in bold
- Error in red and bold
One thing I would like to highlight before moving on is that you do not need to use the above element in order to do logging. Within a scriptable task, you could always use System.log(text), System.warn(text) or System.error(text). Will go a bit deeper into this shortly.
Time for a proper exercise! For the next, I will be going through creating a network adapter on a virtual machine with different types of network adapters.
First of all, remove all 3 logging elements made previously and drag and drop a scriptable task. Navigate to the Scripting tab and paste the following script in:
System.log("Adding a virtual NIC to this VM with portgroup: " + Portgroup.name); // Create connectable info for network var connectInfo = new VcVirtualDeviceConnectInfo(); connectInfo.connected = true; connectInfo.startConnected = true; connectInfo.allowGuestControl = false; // Create Network BackingInfo var netBackingInfo = new VcVirtualEthernetCardDistributedVirtualPortBackingInfo(); netBackingInfo.port = new VcDistributedVirtualSwitchPortConnection(); netBackingInfo.port.switchUuid = Portgroup.config.distributedVirtualSwitch.uuid; netBackingInfo.port.portgroupKey = Portgroup.key; // Create VirtualNetwork if (Type.toUpperCase() === "VMXNET3") { vNetwork = new VcVirtualVmxnet3(); } else if(Type.toUpperCase() === "E1000E") { vNetwork = new VcVirtualE1000e(); } vNetwork.backing = netBackingInfo; vNetwork.addressType = "Generated"; vNetwork.connectable = connectInfo; // Create Network ConfigSpec var deviceConfigSpec = new VcVirtualDeviceConfigSpec(); deviceConfigSpec.device = vNetwork; deviceConfigSpec.operation = VcVirtualDeviceConfigSpecOperation.add; var configSpec = new VcVirtualMachineConfigSpec(); var configSpecArray = new Array(); configSpecArray.push(deviceConfigSpec); configSpec.deviceChange = configSpecArray; // Run the task Task = VM.reconfigVM_Task(configSpec);
Once the script is in place, create three inputs as per the following screenshot:
As depicted above, the workflow will require 3 inputs from the end users; virtual machine, portgroup and type. For the type, instead of asking the end user to manually type the input in, it would be better to pre-define a list and let them select one. This would make it easier for both workflow developers and users.
Go back to the General tab, change the type of the attribute attrTypeList to Array of string and add VMXNET3 and E1000E:
Once done, go to Presentation tab, select Type, choose “Predefined list of elements” and link it to pre created attribute attrTypeList.
Lastly, finish off the Visual Binding:
All done! Let’s run the workflow to ensure it does the job. For the next example, I will be choosing a portgroup with VLAN1153 and VMXNET3 network adapter.
Submit the request. Once the workflow completes, see if VMXNET 3 network adapter with portgroup VLAN1153 has added:
Let’s create another one with VLAN1153 in E1000E type. Submit another request:
Now we are confident that the workflow creates a portgroup as intended, it’s time to go through the logging. What I will be showing shortly is:
- Selecting VMXNET3 = informational message will be logged
- Selecting E1000E = warning message will be logged
Go back to the scriptable task and modify like the following:
Old
// Create VirtualNetwork if (Type.toUpperCase() === "VMXNET3") { vNetwork = new VcVirtualVmxnet3(); } else if(Type.toUpperCase() === "E1000E") { vNetwork = new VcVirtualE1000e(); }
New
// Create VirtualNetwork if (Type.toUpperCase() === "VMXNET3") { vNetwork = new VcVirtualVmxnet3(); System.log("VMXNET3 Virtual Adapter will be created"); } else if(Type.toUpperCase() === "E1000E") { vNetwork = new VcVirtualE1000e(); System.warn("VMXNET3 is recommended, but E1000E will be created"); }
Have a look at the example runs below:
- 1 x VLAN997 VMXNET3 portgroup
- 1 x VLAN997 E100E portgroup
This is how informational or warning message could be used. For the error message, it will be discussed in the next topic “Exception Handling”.
Exception Handling
An exception handling in the programming world is defined as
Exception handling is the process of responding to the occurrence, during computation, of exceptions – anomalous or exceptional conditions requiring special processing – often changing the normal flow of program execution
Why would you need to do exception handling? The reason is simple, when something never expected happens, information of the exception has to be handled, e.g. error message logged, locate where it happened…etc, which will allow easier troubleshooting for the developers.
The best way to catch an exception is to use try & catch:
try { ...... script } catch (e) { System.error(e); }
Whatever fails in try statement, the error message will be saved to the variable e and then logged as an error message.
Will be continuing with the example used in the previous section. Edit the scriptable task and replace the whole lines with the following script:
Script
try { System.log("Adding a virtual NIC to this VM with portgroup: " + Portgroup.name); // Create connectable info for network var connectInfo = new VcVirtualDeviceConnectInfo(); connectInfo.connected = true; connectInfo.startConnected = true; connectInfo.allowGuestControl = false; // Create Network BackingInfo var netBackingInfo = new VcVirtualEthernetCardDistributedVirtualPortBackingInfo(); netBackingInfo.port = new VcDistributedVirtualSwitchPortConnection(); netBackingInfo.port.switchUuid = Portgroup.config.distributedVirtualSwitch.uuid; netBackingInfo.port.portgroupKey = Portgroup.key; // Create VirtualNetwork if (Type.toUpperCase() === "VMXNET3") { vNetwork = new VcVirtualVmxnet3(); System.log("VMXNET3 Virtual Adapter will be created"); } else if(Type.toUpperCase() === "E1000E") { vNetwork = new VcVirtualE1000e(); System.warn("VMXNET3 is recommended, but E1000E will be created"); } vNetwork.backing = netBackingInfo; vNetwork.addressType = "Generated"; vNetwork.connectable = connectInfo; // Create Network ConfigSpec var deviceConfigSpec = new VcVirtualDeviceConfigSpec(); deviceConfigSpec.device = vNetwork; deviceConfigSpec.operation = VcVirtualDeviceConfigSpecOperation.add; var configSpec = new VcVirtualMachineConfigSpec(); var configSpecArray = new Array(); configSpecArray.push(deviceConfigSpec); configSpec.deviceChange = configSpecArray; // Run the task Task = VM.reconfigVM_Task(configSpec); } catch (e) { System.error(e); }
Causing an exception with this workflow is quite simple, just pick a portgroup that the virtual machine doesn’t have access to. In this example, VLAN23 was chosen:
Since VLAN23 portgroup doesn’t exist, it now presents an error message saying “vNetwork is not defined”. One odd thing here is that looking at history of the workflow, it says the workflow was successful. Why is this? It’s because the exception was caught but wasn’t thrown properly, i.e. the workflow was not terminated. The way of throwing an exception is using “throw” statement. Add the throw statement like the following:
Old
} catch (e) { System.error(e); }
} catch (errorCode) { System.error(errorCode); throw errorCode; }
Once the change is made, the error will be thrown when an exception happens. One thing to consider is, where will it be thrown to? The exception must be handled once it is thrown.
Go back to the Schema and under the Generic tab, you will be able to find “Throw Exception” element.
Throw exception element could be treated as End workflow but the difference is, the workflow was successful or failed.
Drag and drop the Throw exception element on the Scriptable task and a dotted red arrow will be created.
Doing this will automatically create an attribute called errorCode. The purpose of this attribute is to catch any exception was thrown and forward it to the Throw exception element. This is why the variable e has been modified to errorCode earlier.
Saving the workflow will cause an error saying “Exception binding was not set”. This is because the workflow expects the scriptable task to bind the error message as an attribute. Edit the scriptable task, navigate to Exception and bind it to errorCode:
Also, do the visual binding like below:
Let’s run the workflow again choosing VLAN23. Unlike the previous example, now it will end in the throw exception state (the Throw exception element is highlighted).
This is how you handle exceptions and will be moving onto fail-back process.
Fail-back
In my opinion, fail-back is one of the important aspects when developing a workflow. Continuing with the example above, let’s say there are 3 more tasks after creating a network adapter which I will be calling X, Y and Z and if task Y fails, what would you do to the network adapter created in the previous stage? The answer is obvious, it has to be removed, you don’t want the users to run the workflow multiple times ending up with 10 identical network adapters on a single virtual machine.
Once done, drag and drop 3 scriptable tasks between Create Network Adapter scriptable task and End element and call them Task X, Task Y and Task Z accordingly. Make sure you bind errorCode as the exception variable across 3 scriptable tasks and also, hover over and drag red arrow to Throw element.
When task X,Y or Z fails, we want to delete the network adapter created. To do this, simply drag and drop a scriptable task between Task X and the Throw element and call it Remove Network Adapter:
Paste the following script into Remove Network Adapter scriptable task:
try { System.error("An error has occurred, removing the created network adapter: " + attrDevice); var deviceConfigSpec = new VcVirtualDeviceConfigSpec(); deviceConfigSpec.device = attrDevice; deviceConfigSpec.operation = VcVirtualDeviceConfigSpecOperation.remove; var configSpec = new VcVirtualMachineConfigSpec(); var configSpecArray = new Array(); configSpecArray.push(deviceConfigSpec); configSpec.deviceChange = configSpecArray; Task = VM.reconfigVM_Task(configSpec); } catch (e) { errorCode = e; throw errorCode; }
And also, finish off the Visual Binding:
In
Out
All done, let’s run and see the fail-back works. The following depicts the virtual machine has two network adapters, VLAN444 and VLAN1153:
Let’s intentionally throw an exception in Task Y. Do the Visual Binding for errorCode and type the following in the scripting field:
errorCode = "This is intentional"; System.error(errorCode); throw errorCode;
Let’s run it!
It has thrown an exception at the Task Y and looking at how many network adapters the virtual machine has, you will see that the network adapter wasn’t created. Fail-back was successful!
Wrap-Up
Hope this blog post was helpful and as always, feel free to contact me for any clarifications or help.
The next series will cover “Introduction to Relationship Between vRO & vRA” which is a big topic, stay tuned! 😀