Dependency Injection pattern in vRO or how to add vNIC to VM in vRA as a custom day 2 action
Today, we’re going to implement a day 2 action for vRA deployment. Custom day 2 actions are a great feature of vRA. They allow us to add almost anything. We will use this feature to add and connect a new vNIC to the deployed VM. We will support both Standard and Distributed switches and all available types of vNICs. Additionally, we’ll use native JavaScript, but we’ll employ more advanced techniques to make our code more professional. To do this, we’ll create a networking class that covers all the network parts. The idea is to create a workflow that will be triggered by the deployment and will call the action element, which will take care of creating and configuring a new network card.
To the VSCode.
Action element
Classes in ES5
In vRO, action elements are JavaScript functions wrapped by vRO. However, these are not regular functions but Immediately-Invoked Function Expressions (IIFE), which run as soon as they are defined. We define an IIFE function inside parentheses and add () to execute that function.
Several preparation steps are needed to add a new network interface card (NIC) to the VM, all related to networking. Therefore, it would be reasonable to create a networking class. It’s important to note that vRO’s JavaScript engine is Rhino, and its current version supports JavaScript version 5.1, which doesn’t support classes natively. Nonetheless, some techniques can help bridge this gap.
More details about classes in Javascript ES5 can be found here.
Network Class
Let’s create a new action element called virtualNetworkManagement
and a new class called VirtualNetworkManagement
. This class is a function that utilizes the prototype feature of the JS. To create a NIC, we need three preparation steps (methods):
- createVirtualDeviceConfigSpec - generate a new config spec for a new device (NIC). The operation will be ADD, so we want to add a NIC.
- createVirtualDeviceConnectInfo - defines the connectivity properties of the NIC. All arguments in that method are booleans.
- createVirtualEthernetAdapter - defines the type of the NIC.
adapterType
is a string that will be passed from the custom form selected by the user.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var VirtualNetworkManagement = /** @class */ (function () {
VirtualNetworkManagement.prototype.createVirtualDeviceConfigSpec = function (
operation
) {
var vmConfigSpec = new VcVirtualDeviceConfigSpec();
vmConfigSpec.operation = operation;
return vmConfigSpec;
};
VirtualNetworkManagement.prototype.createVirtualDeviceConnectInfo = function (
allowGuestControl,
connected,
startConnected
) {
var connectInfo = new VcVirtualDeviceConnectInfo();
connectInfo.allowGuestControl = allowGuestControl;
connectInfo.connected = connected;
connectInfo.startConnected = startConnected;
return connectInfo;
};
VirtualNetworkManagement.prototype.createVirtualEthernetAdapter = function (
adapterType
) {
switch (adapterType) {
case "E1000":
return new VcVirtualE1000();
case "E1000e":
return new VcVirtualE1000e();
case "Vmxnet":
return new VcVirtualVmxnet();
case "Vmxnet2":
return new VcVirtualVmxnet2();
case "Vmxnet3":
return new VcVirtualVmxnet3();
default:
throw new Error("Unknown adapter type: ".concat(adapterType));
}
};
return VirtualNetworkManagement;
})();
All these are required to create a new vNIC. Now, we’re ready to add a vNIC to the VM. We’ll need another method in our class called addVnicToSwitch to do so.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
VirtualNetworkManagement.prototype.addVnicToSwitch = function (
vm,
switchType,
adapterType
) {
var configSpec = new VcVirtualMachineConfigSpec();
var vmConfigSpecs = [];
// Create virtual device config spec for adding a new device
var vmDeviceConfigSpec = this.createVirtualDeviceConfigSpec(
VcVirtualDeviceConfigSpecOperation.add
);
// Create connection info for port group
var connectInfo = this.createVirtualDeviceConnectInfo(true, true, true);
// Create virtual ethernet adapter based on adapter type
var vNetwork = this.createVirtualEthernetAdapter(adapterType);
if (!vNetwork) throw new Error("Failed to create VirtualEthernetAdapter");
vNetwork.backing = switchType;
vNetwork.unitNumber = 0;
vNetwork.addressType = "Generated";
vNetwork.wakeOnLanEnabled = true;
vNetwork.connectable = connectInfo;
// Add the configured virtual ethernet adapter to device specs
vmDeviceConfigSpec.device = vNetwork;
vmConfigSpecs.push(vmDeviceConfigSpec);
configSpec.deviceChange = vmConfigSpecs;
System.log("Reconfiguring the virtual machine to add new vNIC");
try {
var task = vm.reconfigVM_Task(configSpec);
System.getModule("com.vmware.library.vc.basic").vim3WaitTaskEnd(
task,
true,
2
);
} catch (error) {
throw new Error("Failed to create vNIC");
}
};
Dependency Injection Pattern
The most interesting aspect of this method is a variable called switchType
(line 17 in addVnicToSwitch
method above). Both Standard and Distributed switches share almost identical configurations for all the provided code except for the backing part. For the Standard vSwitch, the vNetwork.backing
must be VcVirtualEthernetCardLegacyNetworkBackingInfo
, while for the Distributed vSwitch, it must be VcVirtualEthernetCardDistributedVirtualPortBackingInfo
.
Since we want to support connections for both Standard and Distributed switches, we must find an elegant way to replace only the switchType
variable. Otherwise, we would need to duplicate our code just because of one line of code. This is where the Dependency Injection pattern in JavaScript comes into play. A well-explained example can be found here.
We will implement a light version of this pattern, focusing on its core functionality, which can be extended as needed. The idea is to create external classes that prepare the Distributed and Standard switch objects and inject them into the main addVnicToSwitch
method as a switchType
variable. By doing this, we decouple the object’s construction (SwitchPortConnection
in our case for both types of switches) from the function it is called. Therefore, we are going to write another three classes:
- The
StandardVirtualSwitchPortConnection
class is preparing the backing of the standard switch.
1
2
3
4
5
6
7
8
9
10
11
12
13
var StandardVirtualSwitchPortConnection = /** @class */ (function () {
function StandardVirtualSwitchPortConnection() {}
StandardVirtualSwitchPortConnection.prototype.createStandardVirtualSwitchPortConnection =
function (standardNetworkGroup) {
if (!standardNetworkGroup)
throw new Error("'standardNetworkGroup' argument is required");
var backingInfo = new VcVirtualEthernetCardLegacyNetworkBackingInfo();
backingInfo.useAutoDetect = true;
backingInfo.deviceName = standardNetworkGroup.name;
return backingInfo;
};
return StandardVirtualSwitchPortConnection;
})();
- The
DistributedVirtualPortBackingInfo
andDistributedVirtualSwitchPortConnection
, which are preparing the baking of the distributed switch.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var DistributedVirtualPortBackingInfo = /** @class */ (function () {
function DistributedVirtualPortBackingInfo() {}
DistributedVirtualPortBackingInfo.prototype.createVirtualEthernetCardDistributedVirtualPortBackingInfo =
function (port) {
if (!port) throw new Error("'Port' argument is required");
var backingInfoDvs =
new VcVirtualEthernetCardDistributedVirtualPortBackingInfo();
backingInfoDvs.port = port;
return backingInfoDvs;
};
return DistributedVirtualPortBackingInfo;
})();
exports.DistributedVirtualPortBackingInfo = DistributedVirtualPortBackingInfo;
//########################################################################
var DistributedVirtualSwitchPortConnection = /** @class */ (function () {
function DistributedVirtualSwitchPortConnection() {}
DistributedVirtualSwitchPortConnection.prototype.createDistributedVirtualSwitchPortConnection =
function (portGroup) {
if (!portGroup) throw new Error("'portGroup' argument is required");
var distributedPortConnection =
new VcDistributedVirtualSwitchPortConnection();
var distributedVirtualSwitch = VcPlugin.convertToVimManagedObject(
portGroup,
portGroup.config.distributedVirtualSwitch
);
distributedPortConnection.switchUuid = distributedVirtualSwitch.uuid;
distributedPortConnection.portgroupKey = portGroup.key;
return distributedPortConnection;
};
return DistributedVirtualSwitchPortConnection;
})();
Usually, the class’ constructor is used to utilize the DI pattern. The constructor is receiving the dependency, which wants to eject. This function is used as a constructor for
VirtualNetworkManagement
class, because ES5 does not support constructors.
1 2 3 function VirtualNetworkManagement(switchType) { this.switchType = switchType; }
Workflow
The workflow is quite simple. We call the virtualNetworkManagement()
class and execute its methods based on the given inputs. In this process, we apply dependency injection (DI) depending on the type of vSwitch provided. After selecting the switch, we prepare it and pass it to the addVnicToSwitch
method, which handles the task. Cool, isn’t it?
Let’s start the workflow and see what we have.
There are two ways to provide a predefined list of adapter types: hardcode it in the custom form or use an action element as an external action. The second one if a best practice. Therefore, we create a new, simple action element that returns an array of strings.
As a result, we can see this array in a dropdown list.
Switch type input has two predefined values as well.
Based on what switch type is selected, a different port group appears. Of course, both standard and distributed portgroups are searchable fields.
vRA Day 2 action
Lets add new Resource Action. The resource type will be a Cloud.vSphere.Machine
, because we want to add a vNIC to the VM. The workflow will be our ‘Add NIC to VM’ workflow.
Don’t forget to enable ‘Activate’ option. Otherwise, our day 2 action will no be visible in the UI.
All the workflow’s inputs will be automatically added to the Property Binding section below.
One of the important things to do when we have an input of type VC:VirtualMachine
is properly binding it. When this day 2 action will run, the vRA should know to which VM this should be applied. To do so, the auto-discovery process will run. This is represented by a built-in action element called findVcVmByVcAndVmUuid
, which will find the VM and return it as an object. Therefore, changing the default binding "in request" to "with binding action" is essential.
Now, we can open one of the deployments in vRA, select the cloud vsphere machine in the canvas, click on the ACTIONS button and see a new “Add vNIC” action.
Select the adapter type.
And select the portgroup based on the virtual switch type.
Summary
VMware Aria Automation and VMware Aria Orchestrator provide an excellent tandem for automating almost anything (XaaS? 😉). And now, we can extend it even further and bring different design patterns into it.
Any feedback is highly appreciated.
Source Code
The source code with the unit tests can be found here and here.
The vRO package is also available here and the external ECMASCRIPT package here.
All packages should be imported