From 791be8eb08fd0b9ad554c5ee5de3e7f288a4960f Mon Sep 17 00:00:00 2001 From: Krzysztof Gromadzki Date: Thu, 24 May 2018 13:30:55 +0200 Subject: [PATCH] Handler for VmReconfiguredEvent Add VmReconfiguredHandler to VmwareController handlers Delete and create VMI on VMIs network update Refactored asserting VMI model state Change-Id: Iba22674eb4de98f557faae6a4f2d3a7b906c91e6 Closes-Bug: #1774661 --- .gitignore | 3 + cvm/__main__.py | 6 +- cvm/controllers.py | 44 +++++- cvm/models.py | 32 +++-- cvm/services.py | 44 +++--- tests/test_events.py | 318 ++++++++++++++++++++++++++++++++--------- tests/test_services.py | 12 +- 7 files changed, 344 insertions(+), 115 deletions(-) diff --git a/.gitignore b/.gitignore index 89ec87e..0bf532b 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,6 @@ config.yaml # idea files .idea/ + +# pytest +.pytest_cache/ \ No newline at end of file diff --git a/cvm/__main__.py b/cvm/__main__.py index 4ea4b53..077ebe6 100644 --- a/cvm/__main__.py +++ b/cvm/__main__.py @@ -8,7 +8,7 @@ import cvm.constants as const from cvm.clients import (ESXiAPIClient, VCenterAPIClient, VNCAPIClient, VRouterAPIClient) -from cvm.controllers import VmRenamedHandler, VmwareController +from cvm.controllers import VmRenamedHandler, VmReconfiguredHandler, VmwareController from cvm.database import Database from cvm.monitors import VMwareMonitor from cvm.services import (VirtualMachineInterfaceService, @@ -56,7 +56,9 @@ def main(): database=database ) vm_renamed_handler = VmRenamedHandler(vm_service, vmi_service) - vmware_controller = VmwareController(vm_service, vn_service, vmi_service, [vm_renamed_handler]) + vm_reconfigured_handler = VmReconfiguredHandler(vm_service, vmi_service) + handlers = [vm_renamed_handler, vm_reconfigured_handler] + vmware_controller = VmwareController(vm_service, vn_service, vmi_service, handlers) vmware_monitor = VMwareMonitor(esxi_api_client, vmware_controller) greenlets = [ diff --git a/cvm/controllers.py b/cvm/controllers.py index f88e839..96002b1 100644 --- a/cvm/controllers.py +++ b/cvm/controllers.py @@ -1,3 +1,4 @@ +from abc import abstractmethod, ABCMeta import logging from pyVmomi import vim, vmodl # pylint: disable=no-name-in-module @@ -50,7 +51,6 @@ def _handle_event(self, event): vim.event.VmCreatedEvent, vim.event.VmClonedEvent, vim.event.VmDeployedEvent, - vim.event.VmReconfiguredEvent, vim.event.VmMacChangedEvent, vim.event.VmMacAssignedEvent, vim.event.DrsVmMigratedEvent, @@ -91,10 +91,8 @@ def _handle_net_change(self, nic_infos): self._vmi_service.update_nic(nic_info) -class VmRenamedHandler(object): - def __init__(self, vm_service, vmi_service): - self._vm_service = vm_service - self._vmi_service = vmi_service +class AbstractEventHandler(object): + __metaclass__ = ABCMeta def handle_update(self, update_set): for property_filter_update in update_set.filterSet: @@ -107,10 +105,44 @@ def _handle_change(self, property_change): value = getattr(property_change, 'val', None) if value: if name.startswith('latestPage'): - if isinstance(value, vim.event.VmRenamedEvent): + if isinstance(value, self.EVENTS): self._handle_event(value) + @abstractmethod + def _handle_event(self, event): + pass + + +class VmRenamedHandler(AbstractEventHandler): + EVENTS = (vim.event.VmRenamedEvent,) + + def __init__(self, vm_service, vmi_service): + self._vm_service = vm_service + self._vmi_service = vmi_service + def _handle_event(self, event): vmware_vm = event.vm.vm self._vm_service.rename_vm(vmware_vm) self._vmi_service.rename_vmis(vmware_vm) + + +class VmReconfiguredHandler(AbstractEventHandler): + EVENTS = (vim.event.VmReconfiguredEvent,) + + def __init__(self, vm_service, vmi_service): + self._vm_service = vm_service + self._vmi_service = vmi_service + + def _handle_event(self, event): + logger.info('Detected VmReconfiguredEvent') + vmware_vm = event.vm.vm + for device_spec in event.configSpec.deviceChange: + device = device_spec.device + if isinstance(device, vim.vm.device.VirtualVmxnet3): + logger.info('Detected VmReconfiguredEvent with %s device', type(device)) + mac_address = device.macAddress + portgroup_key = device.backing.port.portgroupKey + self._vm_service.update_vm_models_interface(vmware_vm, mac_address, portgroup_key) + self._vmi_service.update_vmis_vn(vmware_vm, mac_address, portgroup_key) + else: + logger.info('Detected VmReconfiguredEvent with unsupported %s device', type(device)) diff --git a/cvm/models.py b/cvm/models.py index df9fddc..97f3d46 100644 --- a/cvm/models.py +++ b/cvm/models.py @@ -93,6 +93,9 @@ def rename(self, vmware_vm): self.vmware_vm = vmware_vm self.vm_properties['name'] = vmware_vm.name + def update_interface_portgroup_key(self, mac_address, portgroup_key): + self.interfaces[mac_address] = portgroup_key + def _read_interfaces(self): try: return {device.macAddress: device.backing.port.portgroupKey @@ -187,6 +190,7 @@ def __init__(self, vm_model, vn_model, parent, security_group): self.vn_model = vn_model self.mac_address = find_vm_mac_address(self.vm_model.vmware_vm, self.vn_model.key) self.ip_address = None + self.port_key = None self.vlan_id = None self.security_group = security_group self.vnc_vmi = None @@ -201,23 +205,21 @@ def uuid(self): def display_name(self): return 'vmi-{}-{}'.format(self.vn_model.name, self.vm_model.name) - @property - def port_key(self): - return find_vmi_port_key(self.vm_model.vmware_vm, self.mac_address) + def refresh_port_key(self): + self.port_key = find_vmi_port_key(self.vm_model.vmware_vm, self.mac_address) def to_vnc(self): - if not self.vnc_vmi: - self.vnc_vmi = VirtualMachineInterface(name=self.uuid, - display_name=self.display_name, - parent_obj=self.parent, - id_perms=ID_PERMS) - self.vnc_vmi.set_uuid(self.uuid) - self.vnc_vmi.add_virtual_machine(self.vm_model.vnc_vm) - self.vnc_vmi.set_virtual_network(self.vn_model.vnc_vn) - self.vnc_vmi.set_virtual_machine_interface_mac_addresses(MacAddressesType([self.mac_address])) - self.vnc_vmi.set_port_security_enabled(True) - self.vnc_vmi.set_security_group(self.security_group) - return self.vnc_vmi + vnc_vmi = VirtualMachineInterface(name=self.uuid, + display_name=self.display_name, + parent_obj=self.parent, + id_perms=ID_PERMS) + vnc_vmi.set_uuid(self.uuid) + vnc_vmi.add_virtual_machine(self.vm_model.vnc_vm) + vnc_vmi.set_virtual_network(self.vn_model.vnc_vn) + vnc_vmi.set_virtual_machine_interface_mac_addresses(MacAddressesType([self.mac_address])) + vnc_vmi.set_port_security_enabled(True) + vnc_vmi.set_security_group(self.security_group) + return vnc_vmi def construct_instance_ip(self): if not self._should_construct_instance_ip(): diff --git a/cvm/services.py b/cvm/services.py index 9597722..1268aca 100644 --- a/cvm/services.py +++ b/cvm/services.py @@ -84,6 +84,10 @@ def rename_vm(self, vmware_vm): self._vnc_api_client.update_or_create_vm(vm_model.vnc_vm) self._database.save(vm_model) + def update_vm_models_interface(self, vmware_vm, mac_address, portgroup_key): + vm_model = self._database.get_vm_model_by_uuid(vmware_vm.config.instanceUuid) + vm_model.update_interface_portgroup_key(mac_address, portgroup_key) + class VirtualNetworkService(Service): def __init__(self, vcenter_api_client, vnc_api_client, database): @@ -122,32 +126,34 @@ def update_vmis_for_vm_model(self, vm_model): existing_vmi_models = {vmi_model.mac_address: vmi_model for vmi_model in self._database.get_vmi_models_by_vm_uuid(vm_model.uuid)} for mac_address, portgroup_key in vm_model.interfaces.iteritems(): - vmi_model = existing_vmi_models.pop(mac_address, None) - vn_model = self._database.get_vn_model_by_key(portgroup_key) - if vmi_model: - self._update_vmis_vn(vmi_model, vn_model) - else: - vmi_model = VirtualMachineInterfaceModel( - vm_model, - vn_model, - self._project, - self._default_security_group - ) - self._create_or_update(vmi_model) + existing_vmi_models.pop(mac_address, None) + self.update_vmis_vn(vm_model.vmware_vm, mac_address, portgroup_key) for unused_vmi_model in existing_vmi_models.values(): self._delete(unused_vmi_model) - def _update_vmis_vn(self, vmi_model, vn_model): - if vmi_model.vn_model == vn_model: - return - vmi_model.clear_vlan_id() - vmi_model.vn_model = vn_model - self._vnc_api_client.delete_instance_ip(vmi_model.vnc_instance_ip) + def update_vmis_vn(self, vmware_vm, mac_address, portgroup_key): + vmi_model = self._database.get_vmi_model_by_uuid(VirtualMachineInterfaceModel.get_uuid(mac_address)) + vn_model = self._database.get_vn_model_by_key(portgroup_key) + vm_model = self._database.get_vm_model_by_uuid(vmware_vm.config.instanceUuid) + if not vmi_model: + vmi_model = VirtualMachineInterfaceModel( + vm_model, + vn_model, + self._project, + self._default_security_group + ) + if vmi_model.vn_model != vn_model: + with self._vcenter_api_client: + self._vcenter_api_client.restore_vlan_id(vmi_model.vn_model.dvs_name, vmi_model.port_key) + vmi_model.clear_vlan_id() + vmi_model.vn_model = vn_model + self._delete(vmi_model) + self._create_or_update(vmi_model) def _create_or_update(self, vmi_model): with self._vcenter_api_client: - self._vcenter_api_client.restore_vlan_id(vmi_model.vn_model.dvs_name, vmi_model.port_key) + vmi_model.refresh_port_key() vmi_model.acquire_vlan_id() self._vcenter_api_client.set_vlan_id(vmi_model.vn_model.dvs_name, vmi_model.port_key, vmi_model.vlan_id) self._vnc_api_client.update_or_create_vmi(vmi_model.to_vnc()) diff --git a/tests/test_events.py b/tests/test_events.py index 9d399a6..5b37960 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -1,43 +1,77 @@ +# pylint: disable=redefined-outer-name + import pytest from mock import Mock from pyVmomi import vim, vmodl # pylint: disable=no-name-in-module -from vnc_api.vnc_api import InstanceIp, Project, VirtualNetwork +from vnc_api import vnc_api +from vnc_api.vnc_api import Project, VirtualNetwork -from cvm.controllers import VmRenamedHandler, VmwareController +from cvm.controllers import (VmReconfiguredHandler, VmRenamedHandler, + VmwareController) from cvm.database import Database from cvm.models import VirtualNetworkModel, VlanIdPool -from cvm.services import VirtualMachineInterfaceService, VirtualMachineService +from cvm.services import (VirtualMachineInterfaceService, + VirtualMachineService, VirtualNetworkService) -@pytest.fixture() -def vnc_vn_1(): - vnc_vn = VirtualNetwork(name='DPG1', parent=Project()) +def create_ipam(): + return vnc_api.NetworkIpam( + name='IPAM', + parent_obj=vnc_api.Project() + ) + + +def create_vnc_vn(name, uuid): + vnc_vn = VirtualNetwork(name=name, parent=Project()) + vnc_vn.set_uuid(uuid) + vnc_vn.set_network_ipam(create_ipam(), None) return vnc_vn -@pytest.fixture() -def vn_model_1(vnc_vn_1): +def create_vn_model(vnc_vn, portgroup_key, portgroup_name): dpg = Mock() - dpg.key = 'dportgroup-1' - dpg.name = 'DPG1' + dpg.key = portgroup_key + dpg.name = portgroup_name dvs = Mock() dpg.config.distributedVirtualSwitch = dvs dvs.FetchDVPorts.return_value = [] - return VirtualNetworkModel(dpg, vnc_vn_1, VlanIdPool(0, 100)) + return VirtualNetworkModel(dpg, vnc_vn, VlanIdPool(0, 100)) + + +@pytest.fixture() +def vnc_vn_1(): + return create_vnc_vn(name='DPG1', uuid='vnc_vn_uuid_1') -@pytest.fixture(scope='module') +@pytest.fixture() +def vnc_vn_2(): + return create_vnc_vn(name='DPG2', uuid='vnc_vn_uuid_2') + + +@pytest.fixture() +def vn_model_1(vnc_vn_1): + return create_vn_model(vnc_vn=vnc_vn_1, + portgroup_key='dvportgroup-1', portgroup_name='DPG1') + + +@pytest.fixture() +def vn_model_2(vnc_vn_2): + return create_vn_model(vnc_vn=vnc_vn_2, + portgroup_key='dvportgroup-2', portgroup_name='DPG2') + + +@pytest.fixture() def vmware_vm_1(): vmware_vm = Mock() vmware_vm.summary.runtime.host.vm = [] vmware_vm.config.instanceUuid = '12345678-1234-1234-1234-123456789012' backing = Mock(spec=vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo) - backing.port = Mock(portgroupKey='dportgroup-1', portKey='10') + backing.port = Mock(portgroupKey='dvportgroup-1', portKey='10') vmware_vm.config.hardware.device = [Mock(backing=backing, macAddress='11:11:11:11:11:11')] return vmware_vm -@pytest.fixture(scope='module') +@pytest.fixture() def vm_properties_1(): return { 'config.instanceUuid': '12345678-1234-1234-1234-123456789012', @@ -45,7 +79,7 @@ def vm_properties_1(): } -@pytest.fixture(scope='module') +@pytest.fixture() def vm_properties_renamed(): return { 'config.instanceUuid': '12345678-1234-1234-1234-123456789012', @@ -53,17 +87,12 @@ def vm_properties_renamed(): } -@pytest.fixture(scope='module') -def instance_ip(): - instance_ip = InstanceIp() +def assign_ip_to_instance_ip(instance_ip): instance_ip.set_instance_ip_address('192.168.100.5') return instance_ip -@pytest.fixture(scope='module') -def vm_created_update(vmware_vm_1): - event = Mock(spec=vim.event.VmCreatedEvent()) - event.vm.vm = vmware_vm_1 +def wrap_into_update_set(event): change = vmodl.query.PropertyCollector.Change() change.name = 'latestPage' change.val = event @@ -77,29 +106,40 @@ def vm_created_update(vmware_vm_1): return update_set -@pytest.fixture(scope='module') +@pytest.fixture() +def vm_created_update(vmware_vm_1): + event = Mock(spec=vim.event.VmCreatedEvent()) + event.vm.vm = vmware_vm_1 + return wrap_into_update_set(event) + + +@pytest.fixture() def vm_renamed_update(vmware_vm_1): event = Mock(spec=vim.event.VmRenamedEvent()) vmware_vm_1.name = 'VM1-renamed' event.vm.vm = vmware_vm_1 - change = vmodl.query.PropertyCollector.Change() - change.name = 'latestPage' - change.val = event - update_set = vmodl.query.PropertyCollector.UpdateSet() - filter_update = vmodl.query.PropertyCollector.FilterUpdate() - object_update = vmodl.query.PropertyCollector.ObjectUpdate() - object_update.changeSet = [change] - object_set = [object_update] - filter_update.objectSet = object_set - update_set.filterSet = [filter_update] - return update_set + return wrap_into_update_set(event) @pytest.fixture() -def vnc_api_client(instance_ip): +def vm_reconfigure_update(vmware_vm_1): + event = Mock(spec=vim.event.VmReconfiguredEvent()) + event.vm.vm = vmware_vm_1 + port = Mock(spec=vim.dvs.PortConnection()) + port.portgroupKey = 'dvportgroup-2' + device = Mock(spec=vim.vm.device.VirtualVmxnet3()) + device.backing.port = port + device.macAddress = '11:11:11:11:11:11' + device_spec = Mock(spec=vim.vm.device.VirtualDeviceSpec(), device=device) + event.configSpec.deviceChange = [device_spec] + return wrap_into_update_set(event) + + +@pytest.fixture() +def vnc_api_client(): vnc_client = Mock() vnc_client.read_or_create_project.return_value = Project() - vnc_client.create_and_read_instance_ip.return_value = instance_ip + vnc_client.create_and_read_instance_ip.side_effect = assign_ip_to_instance_ip return vnc_client @@ -114,13 +154,62 @@ def vcenter_api_client(): @pytest.fixture() def esxi_api_client(vm_properties_1): - esxi_api_client = Mock() - esxi_api_client.read_vm_properties.return_value = vm_properties_1 - return esxi_api_client + esxi_client = Mock() + esxi_client.read_vm_properties.return_value = vm_properties_1 + return esxi_client + + +def assert_vmi_model_state(vmi_model, mac_address=None, ip_address=None, + vlan_id=None, display_name=None, vn_model=None, vm_model=None): + if mac_address is not None: + assert vmi_model.mac_address == mac_address + if ip_address is not None: + assert vmi_model.vnc_instance_ip.instance_ip_address == ip_address + if vlan_id is not None: + assert vmi_model.vlan_id == vlan_id + if display_name is not None: + assert vmi_model.display_name == display_name + if vn_model is not None: + assert vmi_model.vn_model == vn_model + if vm_model is not None: + assert vmi_model.vm_model == vm_model + + +def assert_vm_model_state(vm_model, uuid=None, name=None, has_interfaces=None): + if uuid is not None: + assert vm_model.uuid == uuid + if name is not None: + assert vm_model.name == name + if has_interfaces is None: + has_interfaces = {} + for mac_address, portgroup_key in has_interfaces.items(): + assert mac_address in vm_model.interfaces + assert vm_model.interfaces[mac_address] == portgroup_key + + +def assert_vnc_vmi_state(vnc_vmi, mac_address=None, vnc_vm_uuid=None, vnc_vn_uuid=None): + if mac_address is not None: + assert vnc_vmi.get_virtual_machine_interface_mac_addresses().mac_address == [mac_address] + if vnc_vm_uuid is not None: + assert vnc_vm_uuid in [ref['uuid'] for ref in vnc_vmi.get_virtual_machine_refs()] + if vnc_vn_uuid is not None: + assert vnc_vn_uuid in [ref['uuid'] for ref in vnc_vmi.get_virtual_network_refs()] + + +def assert_vnc_vm_state(vnc_vm, uuid=None, name=None): + if uuid is not None: + assert vnc_vm.uuid == uuid + if name is not None: + assert vnc_vm.name == name + + +def reserve_vlan_ids(vn_model, vlan_ids): + for vlan_id in vlan_ids: + vn_model.vlan_id_pool.reserve(vlan_id) def test_vm_created(vcenter_api_client, vn_model_1, vm_created_update, - esxi_api_client, vnc_api_client): + esxi_api_client, vnc_api_client, vnc_vn_1): vrouter_api_client = Mock() database = Database() vm_service = VirtualMachineService(esxi_api_client, vnc_api_client, database) @@ -132,8 +221,7 @@ def test_vm_created(vcenter_api_client, vn_model_1, vm_created_update, database.save(vn_model_1) # Some vlan ids should be already reserved - vn_model_1.vlan_id_pool.reserve(0) - vn_model_1.vlan_id_pool.reserve(1) + reserve_vlan_ids(vn_model_1, [0, 1]) # A new update containing VmCreatedEvent arrives and is being handled by the controller controller.handle_update(vm_created_update) @@ -142,39 +230,43 @@ def test_vm_created(vcenter_api_client, vn_model_1, vm_created_update, # - in VNC: vnc_api_client.update_or_create_vm.assert_called_once() vnc_vm = vnc_api_client.update_or_create_vm.call_args[0][0] - assert vnc_vm.uuid == '12345678-1234-1234-1234-123456789012' - assert vnc_vm.name == '12345678-1234-1234-1234-123456789012' + assert_vnc_vm_state(vnc_vm, uuid='12345678-1234-1234-1234-123456789012', + name='12345678-1234-1234-1234-123456789012') # - in Database: vm_model = database.get_vm_model_by_uuid('12345678-1234-1234-1234-123456789012') - assert vm_model.uuid == '12345678-1234-1234-1234-123456789012' - assert vm_model.name == 'VM1' + assert_vm_model_state(vm_model, uuid='12345678-1234-1234-1234-123456789012', name='VM1') # Check if VMI Model has been saved properly: # - in VNC vnc_api_client.update_or_create_vmi.assert_called_once() vnc_vmi = vnc_api_client.update_or_create_vmi.call_args[0][0] - assert vnc_vmi.get_virtual_machine_interface_mac_addresses().mac_address == ['11:11:11:11:11:11'] - assert vnc_vm.uuid in [ref['uuid'] for ref in vnc_vmi.get_virtual_machine_refs()] + assert_vnc_vmi_state(vnc_vmi, mac_address='11:11:11:11:11:11', + vnc_vm_uuid=vnc_vm.uuid, vnc_vn_uuid=vnc_vn_1.uuid) # - in Database vmi_model = database.get_all_vmi_models()[0] - assert vmi_model.display_name == 'vmi-DPG1-VM1' # Check if VMI Model's Instance IP has been created in VNC: vnc_api_client.create_and_read_instance_ip.assert_called_once() # Check if VMI's vRouter Port has been added: - vmi_model = vrouter_api_client.add_port.call_args[0][0] - assert vmi_model.vm_model == vm_model - assert vmi_model.vn_model == vn_model_1 - assert vmi_model.mac_address == '11:11:11:11:11:11' - assert vmi_model.vnc_instance_ip.instance_ip_address == '192.168.100.5' - assert vmi_model.vlan_id == 2 + vrouter_api_client.add_port.assert_called_once_with(vmi_model) # Check if VLAN ID has been set using VLAN Override vcenter_api_client.set_vlan_id.assert_called_once_with(vmi_model.vn_model.dvs_name, '10', 2) + # Check inner VMI model state + assert_vmi_model_state( + vmi_model, + mac_address='11:11:11:11:11:11', + ip_address='192.168.100.5', + vlan_id=2, + display_name='vmi-DPG1-VM1', + vn_model=vn_model_1, + vm_model=vm_model + ) + def test_vm_renamed(vcenter_api_client, vn_model_1, vm_created_update, esxi_api_client, vm_renamed_update, @@ -196,8 +288,7 @@ def test_vm_renamed(vcenter_api_client, vn_model_1, vm_created_update, database.save(vn_model_1) # Some vlan ids should be already reserved - vn_model_1.vlan_id_pool.reserve(0) - vn_model_1.vlan_id_pool.reserve(1) + reserve_vlan_ids(vn_model_1, [0, 1]) # A new update containing VmCreatedEvent arrives and is being handled by the controller controller.handle_update(vm_created_update) @@ -210,35 +301,122 @@ def test_vm_renamed(vcenter_api_client, vn_model_1, vm_created_update, # - in VNC: assert vnc_api_client.update_or_create_vmi.call_count == 2 vnc_vm = vnc_api_client.update_or_create_vm.call_args[0][0] - assert vnc_vm.uuid == '12345678-1234-1234-1234-123456789012' - assert vnc_vm.name == '12345678-1234-1234-1234-123456789012' + assert_vnc_vm_state(vnc_vm, uuid='12345678-1234-1234-1234-123456789012', + name='12345678-1234-1234-1234-123456789012') # - in Database: vm_model = database.get_vm_model_by_uuid('12345678-1234-1234-1234-123456789012') - assert vm_model.uuid == '12345678-1234-1234-1234-123456789012' - assert vm_model.name == 'VM1-renamed' + assert_vm_model_state(vm_model, uuid='12345678-1234-1234-1234-123456789012', name='VM1-renamed') # Check if VMI Model has been saved properly: # - in VNC assert vnc_api_client.update_or_create_vmi.call_count == 2 vnc_vmi = vnc_api_client.update_or_create_vmi.call_args[0][0] - assert vnc_vmi.get_virtual_machine_interface_mac_addresses().mac_address == ['11:11:11:11:11:11'] - assert vnc_vm.uuid in [ref['uuid'] for ref in vnc_vmi.get_virtual_machine_refs()] + assert_vnc_vmi_state(vnc_vmi, mac_address='11:11:11:11:11:11', vnc_vm_uuid=vnc_vm.uuid) # - in Database vmi_model = database.get_all_vmi_models()[0] - assert vmi_model.display_name == 'vmi-DPG1-VM1-renamed' # Check if VMI Model's Instance IP has been created in VNC: vnc_api_client.create_and_read_instance_ip.assert_called_once() # Check if VMI's vRouter Port has been added: - vmi_model = vrouter_api_client.add_port.call_args[0][0] - assert vmi_model.vm_model == vm_model - assert vmi_model.vn_model == vn_model_1 - assert vmi_model.mac_address == '11:11:11:11:11:11' - assert vmi_model.vnc_instance_ip.instance_ip_address == '192.168.100.5' - assert vmi_model.vlan_id == 2 + vrouter_api_client.add_port.called_with(vmi_model) + assert vrouter_api_client.add_port.call_count == 2 # Check if VLAN ID has been set using VLAN Override vcenter_api_client.set_vlan_id.assert_called_once_with(vmi_model.vn_model.dvs_name, '10', 2) + + # Check inner VMI model state + assert_vmi_model_state( + vmi_model, + mac_address='11:11:11:11:11:11', + ip_address='192.168.100.5', + vlan_id=2, + display_name='vmi-DPG1-VM1-renamed', + vn_model=vn_model_1, + vm_model=vm_model + ) + + +def test_vm_reconfigured(vcenter_api_client, vn_model_1, vn_model_2, vm_created_update, + esxi_api_client, vm_reconfigure_update, vnc_api_client, vnc_vn_2, + vmware_vm_1): + vrouter_api_client = Mock() + database = Database() + vm_service = VirtualMachineService(esxi_api_client, vnc_api_client, database) + vn_service = VirtualNetworkService(vcenter_api_client, vnc_api_client, database) + vmi_service = VirtualMachineInterfaceService( + vcenter_api_client, + vnc_api_client, + vrouter_api_client, + database + ) + vm_reconfigure_handler = VmReconfiguredHandler(vm_service, vmi_service) + controller = VmwareController(vm_service, vn_service, vmi_service, [vm_reconfigure_handler]) + + # Virtual Networks are already created for us and after synchronization, + # their models are stored in our database + database.save(vn_model_1) + database.save(vn_model_2) + + # Some vlan ids should be already reserved + reserve_vlan_ids(vn_model_1, [0, 1]) + reserve_vlan_ids(vn_model_2, [0, 1, 2, 3]) + + # A new update containing VmCreatedEvent arrives and is being handled by the controller + controller.handle_update(vm_created_update) + old_vmi_model = database.get_vmi_models_by_vm_uuid('12345678-1234-1234-1234-123456789012')[0] + old_instance_ip = old_vmi_model.vnc_instance_ip + + # After a portgroup is changed, the port key is also changed + vmware_vm_1.config.hardware.device[0].backing.port.portKey = '11' + + # Then VmReconfiguredEvent is being handled + controller.handle_update(vm_reconfigure_update) + + # Check if VM Model has been saved properly in Database: + vm_model = database.get_vm_model_by_uuid('12345678-1234-1234-1234-123456789012') + assert_vm_model_state(vm_model, has_interfaces={'11:11:11:11:11:11': 'dvportgroup-2'}) + + # Check that VM was not updated in VNC except VM create event + vnc_api_client.update_or_create_vm.assert_called_once() + + # Check if VMI Model has been saved properly: + + # - in Database + vmi_models = database.get_vmi_models_by_vm_uuid('12345678-1234-1234-1234-123456789012') + assert len(vmi_models) == 1 + vmi_model = vmi_models[0] + + # - in VNC + vnc_api_client.delete_vmi.assert_called_once_with(vmi_model.uuid) + assert vnc_api_client.update_or_create_vmi.call_count == 2 + vnc_vmi = vnc_api_client.update_or_create_vmi.call_args[0][0] + assert_vnc_vmi_state(vnc_vmi, mac_address='11:11:11:11:11:11', vnc_vn_uuid=vnc_vn_2.uuid) + + # Check if VMI Model's Instance IP has been updated in VNC: + vnc_api_client.delete_instance_ip.assert_called_once_with(old_instance_ip.uuid) + assert vnc_api_client.create_and_read_instance_ip.call_count == 2 + new_instance_ip = vmi_model.vnc_instance_ip + assert vnc_api_client.create_and_read_instance_ip.call_args[0][0] == new_instance_ip + assert vnc_vn_2.uuid in [ref['uuid'] for ref in new_instance_ip.get_virtual_network_refs()] + + # Check if VMI's vRouter Port has been updated: + vrouter_api_client.delete_port.assert_called_once_with(vmi_model.uuid) + assert vrouter_api_client.add_port.call_count == 2 + assert vrouter_api_client.add_port.call_args[0][0] == vmi_model + + # Check if VLAN ID has been set using VLAN Override + assert vcenter_api_client.set_vlan_id.call_count == 2 + assert vcenter_api_client.set_vlan_id.call_args[0] == (vmi_model.vn_model.dvs_name, '11', 4) + + # Check inner VMI model state + assert_vmi_model_state( + vmi_model, + mac_address='11:11:11:11:11:11', + ip_address='192.168.100.5', + vlan_id=4, + display_name='vmi-DPG2-VM1', + vn_model=vn_model_2 + ) diff --git a/tests/test_services.py b/tests/test_services.py index 3876628..6b04b93 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -172,7 +172,8 @@ def setUp(self): def test_create_vmis_proper_vm_dpg(self): """ A new VMI is being created with proper VM/DPG pair. """ - other_vn_model = create_vn_model(name='DPortGroup', key='dportgroup-51') + self.database.save(self.vm_model) + other_vn_model = create_vn_model(name='DPortGroup', key='dportgroup-51', uuid='uuid_2') self.database.save(other_vn_model) self.vmi_service.update_vmis_for_vm_model(self.vm_model) @@ -183,7 +184,8 @@ def test_create_vmis_proper_vm_dpg(self): self.assertEqual(self.vn_model, saved_vmi.vn_model) self.vrouter_api_client.add_port.assert_called_once_with(saved_vmi) self.vrouter_api_client.enable_port.assert_called_once_with(saved_vmi.uuid) - self.vnc_client.update_or_create_vmi.assert_called_once_with(saved_vmi.to_vnc()) + vnc_vmi = self.vnc_client.update_or_create_vmi.call_args[0][0] + self.assertIn(self.vn_model.uuid, [ref['uuid'] for ref in vnc_vmi.get_virtual_network_refs()]) self.assertTrue(saved_vmi.vrouter_port_added) def test_no_update_for_no_dpgs(self): @@ -202,6 +204,9 @@ def test_update_existing_vmi(self): self.database.save(second_vn_model) vmi_model = VirtualMachineInterfaceModel(self.vm_model, self.vn_model, vnc_api.Project(), vnc_api.SecurityGroup()) + vnc_instance_ip = Mock() + vnc_instance_ip.uuid = 'uuid' + vmi_model.vnc_instance_ip = vnc_instance_ip vmi_model.vrouter_port_added = True self.database.save(vmi_model) self.vm_model.interfaces['c8:5b:76:53:0f:f5'] = 'dportgroup-51' @@ -213,7 +218,8 @@ def test_update_existing_vmi(self): saved_vmi = self.database.get_all_vmi_models()[0] self.assertEqual(self.vm_model, saved_vmi.vm_model) self.assertEqual(second_vn_model, saved_vmi.vn_model) - self.vnc_client.update_or_create_vmi.assert_called_once_with(saved_vmi.to_vnc()) + vnc_vmi = self.vnc_client.update_or_create_vmi.call_args[0][0] + self.assertIn(second_vn_model.uuid, [ref['uuid'] for ref in vnc_vmi.get_virtual_network_refs()]) self.assertTrue(saved_vmi.vrouter_port_added) self.vrouter_api_client.delete_port.assert_called_once_with(vmi_model.uuid) self.vrouter_api_client.add_port.assert_called_once()