I have recently been doing some vmware api development work using pyvmomi and one of the issues I had is how we can unit test without a vSphere Installation. The solution is to mock the pyvmomi api but it turned out that it was a much more complex task than I initially thought, so here are my findings.

I will not go into depth of the pyvmomi api or the python mock object library as I assume anyone who will need to mock it would have the sufficient knowledge of both.

The pyvmomi wrapper class I have developed is VmwareUtil and I am using the context manager VmwareOpen to manage the lifecycle of the pyVim.connect.SmartConnectNoSSL which provides a vim.ServiceInstance. This is the root object of the vCenter/ESXi inventory.

from pyVim.connect import SmartConnectNoSSL, Disconnect
from pyVmomi import vim, vmodl

class VmwareOpen(object):

    def __init__(self, host, port, username, password):
        self._host = host
        self._port = port
        self._username = username
        self._password = password

    def __enter__(self):
        try:
            self._service_instance = SmartConnectNoSSL(host=self._host, user=self._username, pwd=self._password, port=self._port)
            return self._service_instance
        except IOError as e:
            raise e

    def __exit__(self, type, value, traceback):
        Disconnect(self._service_instance)

class VmwareUtil(object):
    
    def __init__(self, vmwarehost, vmwareport, username, password):
        self._host = vmwarehost
        self._port = vmwareport
        self._username = username
        self._password = password
    
    def _get_vm(self, identity, service_instance):
        content = service_instance.RetrieveContent()
        
        vmlist = []
        
        for child in content.rootFolder.childEntity:
            if hasattr(child, 'vmFolder'):
                datacenter = child
                vmfolder = datacenter.vmFolder
                vmlist = vmfolder.childEntity
                
        for vm in vmlist:
            if vm.summary.config.name == identity or vm.summary.config.uuid == identity:
                return vm
        
        raise Exception(identity + " not found.")
    
    def get_uuid(self, identity):
        with VmwareOpen(self._host, self._port, self._username, self._password) as service_instance:
            vm = self._get_vm(identity, service_instance)
            return vm.summary.config.uuid
    
def tryme():
    vmware_util = VmwareUtil("192.168.122.168", "443", "root", "password")
    vmuuid = vmware_util.get_uuid("test_vm_00")
    print("UUID : " + vmuuid)

if __name__ == "__main__":
    tryme()

As the simplest example I will just implement a method which will return the UUID of a Virtual Machine by passing its name.

In order to do this we first need to access the Service Instance Content vim.ServiceInstanceContent using the RetrieveContent method of the vim.ServiceInstance.

This will give us access to a set of Managed Objects in the inventory. The rootFolder is the root of the inventory and we need to look for the datacenter object vim.Datacenter. This would be the child which has the attribute vmFolder. There should be a more efficient way of doing this such as using the instanceof function but I kept the lookup code as it was in the pyvmomi community samples. The last step is to loop the children of the vmFolder and find the vm we look for based on name or uuid.

Now the hard stuff and the mocking bit.

The main issue I had is that all the pyvmomi objects are server managed so we cannot just instantiate and populate them at client side. This is why we need to mock the whole lot.


import unittest
from unittest import mock
from unittest.mock import patch

from vmware_util import VmwareUtil

from pyVim.connect import SmartConnectNoSSL, Disconnect
from pyVmomi import vim, vmodl

class TestVMwareUtil(unittest.TestCase):
    
    @mock.patch('vmware_util.VmwareOpen')
    def test_uuid(self, vmware_mock):
        
        # Initialize the Config Summary
        test_vm_00_config_summary = mock.MagicMock(spec_set=vim.vm.Summary.ConfigSummary())
        test_vm_00_config_summary.name = "test_vm_00"
        test_vm_00_config_summary.uuid = "c7a5fdbd-cdaf-9455-926a-d65c16db1809"
         
        # Initialize VM Summary 
        test_vm_00_summary = mock.MagicMock(spec_set=vim.vm.Summary())
        test_vm_00_summary.config = test_vm_00_config_summary
        
        # Initialize the Mock VM
        test_vm_00 = mock.MagicMock(spec_set=vim.VirtualMachine("vm-41"))
        test_vm_00.summary = test_vm_00_summary
        
        # Initialize the Data Center and add the Mock VM
        # Create the vmFolder       
        vm_folder_mock = mock.MagicMock(spec_set=vim.Folder("vm-folder"))
        vm_folder_mock.childType = { "vim.Folder", "vim.Virtualmachine", "vim.VirtualApp" }       
        # Add it to the Data Center and add the VM in the children
        ds_mock = mock.MagicMock(spec_set=vim.Datacenter("ds-00"))
        ds_mock.vmFolder = vm_folder_mock
        ds_mock.vmFolder.childEntity = [test_vm_00]
        
        # Initialize the Service Instance Mock and add the Data Center
        # Create the rootFolder
        root_folder_mock = mock.MagicMock(spec_set=vim.Folder("root-folder"))
        root_folder_mock.childEntity = [ds_mock]
        # Create the Service Instance and add the rootFolder
        si_mock = mock.MagicMock(spec_set=vim.ServiceInstance("si-00"))
        si_content_mock = mock.MagicMock(spec_set=vim.ServiceInstanceContent())
        si_content_mock.rootFolder = root_folder_mock
        # Mock the RetrieveContent Methos in the service instance
        si_mock.RetrieveContent = mock.MagicMock(return_value=si_content_mock)
        #si_mock.RetrieveContent.side_effect = Exception('Boom!')
        
        # Get the VmwareOpen reference
        vm_mock = vmware_mock.return_value
        # Mock the enter and exit methods
        vm_mock.__enter__ = mock.MagicMock(return_value=si_mock)
        vm_mock.__exit__ = mock.MagicMock(return_value=None)
        
        # Call the VMWare Util as normal. Any vmware_util.VmwareOpen reference within the function call will be replaced by the mocked object.
        vmware_util = VmwareUtil("", "", "", "")
        returned_uuid = vmware_util.get_uuid("test_vm_00")
         
        # Assert
        self.assertEqual('c7a5fdbd-cdaf-9455-926a-d65c16db1809', returned_uuid)


if __name__ == '__main__':
    unittest.main()

The hierarchy inside the ServiceInstanceContent is the following so we need to mock in reverve order.

.
+-- ServiceInstanceContent
    +-- rootFolder
        +-- dataCenter
            +-- vmFolder
                +-- childEntity
                    +-- VirtualMachine

The next step is to mock the RetrieveContent method and assign the mocked ServiceInstanceContent as its returned object and the last step is to mock the __enter__ method of the vmware_util.VmwareOpen class which is the context manager for the ServiceInstance. We use the mock.patch function decorator @mock.patch('vmware_util.VmwareOpen') in the test_uuid() function so every time we ran the unit test, any reference to the vmware_util.VmwareOpen will be replaced with the patched object so there will be no real call to the VMWare Hypervisor but the mocked object will be returned instead. The vmware_util.VmwareOpen reference is at with VmwareOpen(self._host, self._port, self._username, self._password) as service_instance:.

You can find the code listings in my Github

That’s it, I hope you find this useful and if you have a better way of doing it please leave a comment below.

Thank you,

Petros