From 1c02c21cab1068827eb5d83380d016f6f9b131b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Szczepa=C5=84ski?= Date: Thu, 14 Jun 2018 11:05:50 +0200 Subject: [PATCH] Prevent CVM from deleting all VMs on startup Use vrouter_uuid variable in VM and VMI services Move _can_delete_from_vnc up to parent Service class Split sync_vms into two methods and change the order of execution Change-Id: Iff4bba6f821f45e0b0a595e931926215d0346cc2 Depends-On: Ib4b8106828f4e254bf64129a2a7346c38a1d57ad Closes-Bug: #1776850 --- cvm/__main__.py | 3 +- cvm/clients.py | 5 ++++ cvm/controllers.py | 3 +- cvm/services.py | 62 ++++++++++++++++++------------------------ tests/test_services.py | 54 +++++++++++++++++------------------- 5 files changed, 60 insertions(+), 67 deletions(-) diff --git a/cvm/__main__.py b/cvm/__main__.py index 23506c4..b797098 100644 --- a/cvm/__main__.py +++ b/cvm/__main__.py @@ -55,7 +55,8 @@ def main(args): vcenter_api_client=vcenter_api_client, vnc_api_client=vnc_api_client, vrouter_api_client=VRouterAPIClient(), - database=database + database=database, + esxi_api_client=esxi_api_client ) vm_renamed_handler = VmRenamedHandler(vm_service, vmi_service) vm_reconfigured_handler = VmReconfiguredHandler(vm_service, vmi_service) diff --git a/cvm/clients.py b/cvm/clients.py index 990708b..ec1dd20 100644 --- a/cvm/clients.py +++ b/cvm/clients.py @@ -12,6 +12,7 @@ VNC_VCENTER_DEFAULT_SG, VNC_VCENTER_DEFAULT_SG_FQN, VNC_VCENTER_IPAM, VNC_VCENTER_IPAM_FQN, VNC_VCENTER_PROJECT) +from cvm.models import find_vrouter_uuid logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -134,6 +135,10 @@ def read_vm_properties(self, vmware_vm): prop_set = object_set[0].propSet return {prop.name: prop.val for prop in prop_set} + def read_vrouter_uuid(self): + host = self._datacenter.hostFolder.childEntity[0].host[0] + return find_vrouter_uuid(host) + class VCenterAPIClient(VSphereAPIClient): def __init__(self, vcenter_cfg): diff --git a/cvm/controllers.py b/cvm/controllers.py index 891a8f4..c564163 100644 --- a/cvm/controllers.py +++ b/cvm/controllers.py @@ -17,8 +17,9 @@ def __init__(self, vm_service, vn_service, vmi_service, handlers): def initialize_database(self): logger.info('Initializing database...') self._vn_service.sync_vns() - self._vm_service.sync_vms() + self._vm_service.get_vms_from_vmware() self._vmi_service.sync_vmis() + self._vm_service.delete_unused_vms_in_vnc() def handle_update(self, update_set): logger.info('Handling ESXi update.') diff --git a/cvm/services.py b/cvm/services.py index d18aab0..4fa977b 100644 --- a/cvm/services.py +++ b/cvm/services.py @@ -10,18 +10,35 @@ class Service(object): - def __init__(self, vnc_api_client, database): + def __init__(self, vnc_api_client, database, esxi_api_client=None, vcenter_api_client=None): self._vnc_api_client = vnc_api_client self._database = database + self._esxi_api_client = esxi_api_client + self._vcenter_api_client = vcenter_api_client self._project = self._vnc_api_client.read_or_create_project() self._default_security_group = self._vnc_api_client.read_or_create_security_group() self._ipam = self._vnc_api_client.read_or_create_ipam() + if self._esxi_api_client: + self._vrouter_uuid = esxi_api_client.read_vrouter_uuid() + + def _can_delete_from_vnc(self, vnc_obj): + if vnc_obj.get_type() == 'virtual-machine': + existing_obj = self._vnc_api_client.read_vm(vnc_obj.uuid) + if vnc_obj.get_type() == 'virtual-machine-interface': + existing_obj = self._vnc_api_client.read_vmi(vnc_obj.uuid) + existing_obj_vrouter_uuid = next(pair.value + for pair in existing_obj.get_annotations().key_value_pair + if pair.key == 'vrouter-uuid') + if existing_obj_vrouter_uuid == self._vrouter_uuid: + return True + logger.error('%s %s is managed by vRouter %s and cannot be deleted from VNC.', + vnc_obj.get_type(), vnc_obj.name, existing_obj_vrouter_uuid) + return False class VirtualMachineService(Service): def __init__(self, esxi_api_client, vnc_api_client, database): - super(VirtualMachineService, self).__init__(vnc_api_client, database) - self._esxi_api_client = esxi_api_client + super(VirtualMachineService, self).__init__(vnc_api_client, database, esxi_api_client=esxi_api_client) def update(self, vmware_vm): vm_properties = self._esxi_api_client.read_vm_properties(vmware_vm) @@ -46,16 +63,12 @@ def _add_property_filter_for_vm(self, vm_model, filters): property_filter = self._esxi_api_client.add_filter(vm_model.vmware_vm, filters) vm_model.property_filter = property_filter - def sync_vms(self): - self._get_vms_from_vmware() - self._delete_unused_vms_in_vnc() - - def _get_vms_from_vmware(self): + def get_vms_from_vmware(self): vmware_vms = self._esxi_api_client.get_all_vms() for vmware_vm in vmware_vms: self.update(vmware_vm) - def _delete_unused_vms_in_vnc(self): + def delete_unused_vms_in_vnc(self): vnc_vms = self._vnc_api_client.get_all_vms() for vnc_vm in vnc_vms: vm_model = self._database.get_vm_model_by_uuid(vnc_vm.uuid) @@ -75,18 +88,6 @@ def remove_vm(self, name): vm_model.destroy_property_filter() return vm_model - def _can_delete_from_vnc(self, vnc_vm): - existing_vm = self._vnc_api_client.read_vm(vnc_vm.uuid) - vrouter_uuid = next(pair.value - for pair in vnc_vm.get_annotations().key_value_pair - if pair.key == 'vrouter-uuid') - if any([pair.key == 'vrouter-uuid' and pair.value == vrouter_uuid - for pair in existing_vm.get_annotations().key_value_pair]): - return True - logger.error('Virtual Machine %s is managed by vRouter %s and cannot be deleted from VNC.', - vnc_vm.name, vrouter_uuid) - return False - def set_tools_running_status(self, vmware_vm, value): vm_model = self._database.get_vm_model_by_uuid(vmware_vm.config.instanceUuid) if not vm_model: @@ -122,10 +123,11 @@ def sync_vns(self): class VirtualMachineInterfaceService(Service): - def __init__(self, vcenter_api_client, vnc_api_client, vrouter_api_client, database): - super(VirtualMachineInterfaceService, self).__init__(vnc_api_client, database) + def __init__(self, vcenter_api_client, vnc_api_client, vrouter_api_client, database, esxi_api_client=None): + super(VirtualMachineInterfaceService, self).__init__(vnc_api_client, database, + esxi_api_client=esxi_api_client, + vcenter_api_client=vcenter_api_client) self.vrouter_api_client = vrouter_api_client - self._vcenter_api_client = vcenter_api_client def sync_vmis(self): self._create_new_vmis() @@ -228,18 +230,6 @@ def _restore_vlan_id(self, vmi_model): self._vcenter_api_client.restore_vlan_id(vmi_model.vn_model.dvs_name, vmi_model.port_key) vmi_model.clear_vlan_id() - def _can_delete_from_vnc(self, vnc_vmi): - existing_vmi = self._vnc_api_client.read_vmi(vnc_vmi.uuid) - vrouter_uuid = next(pair.value - for pair in vnc_vmi.get_annotations().key_value_pair - if pair.key == 'vrouter-uuid') - if any([pair.key == 'vrouter-uuid' and pair.value == vrouter_uuid - for pair in existing_vmi.get_annotations().key_value_pair]): - return True - logger.error('Virtual Machine Interface %s is managed by vRouter %s and cannot be deleted from VNC.', - vnc_vmi.display_name, vrouter_uuid) - return False - def _delete_vrouter_port(self, vmi_model): if vmi_model.vrouter_port_added: logger.info('Deleting vRouter port for %s...', vmi_model.display_name) diff --git a/tests/test_services.py b/tests/test_services.py index eb2eeb9..e48e52a 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -82,7 +82,7 @@ def test_sync_vms(self): self.esxi_api_client.get_all_vms.return_value = [self.vmware_vm] self.vnc_client.get_all_vms.return_value = [] - self.vm_service.sync_vms() + self.vm_service.get_vms_from_vmware() self.database.save.assert_called_once() self.vnc_client.update_or_create_vm.assert_called_once() @@ -93,7 +93,7 @@ def test_sync_no_vms(self): self.esxi_api_client.get_all_vms.return_value = [] self.vnc_client.get_all_vms.return_value = [] - self.vm_service.sync_vms() + self.vm_service.get_vms_from_vmware() self.database.save.assert_not_called() self.vnc_client.update_vm.assert_not_called() @@ -106,9 +106,8 @@ def test_delete_unused_vms(self): with patch('cvm.services.VirtualMachineService._can_delete_from_vnc') as can_delete: can_delete.return_value = True - self.vm_service.sync_vms() + self.vm_service.delete_unused_vms_in_vnc() - self.database.save.assert_not_called() self.vnc_client.delete_vm.assert_called_once_with(vnc_vm) def test_remove_vm(self): @@ -486,13 +485,16 @@ def test_read_no_ipam(self): class TestCanDeleteFromVnc(TestCase): def setUp(self): self.vnc_api_client = Mock() - self.vm_service = VirtualMachineService(None, self.vnc_api_client, None) - self.vmi_service = VirtualMachineInterfaceService(None, self.vnc_api_client, None, None) + esxi_api_client = Mock() + esxi_api_client.read_vrouter_uuid.return_value = 'vrouter_uuid_1' + self.vm_service = VirtualMachineService(esxi_api_client, self.vnc_api_client, None) + self.vmi_service = VirtualMachineInterfaceService(esxi_api_client, self.vnc_api_client, + None, None, esxi_api_client=esxi_api_client) def test_vnc_vm_true(self): - vnc_vm = Mock() - vnc_vm.get_annotations.return_value = KeyValuePairs( - [KeyValuePair('vrouter-uuid', 'vrouter_uuid_1')]) + vnc_vm = vnc_api.VirtualMachine('VM', vnc_api.Project()) + vnc_vm.set_annotations(KeyValuePairs( + [KeyValuePair('vrouter-uuid', 'vrouter_uuid_1')])) self.vnc_api_client.read_vm.return_value = vnc_vm result = self.vm_service._can_delete_from_vnc(vnc_vm) @@ -500,22 +502,19 @@ def test_vnc_vm_true(self): self.assertTrue(result) def test_vnc_vm_false(self): - vnc_vm_1 = Mock() - vnc_vm_1.get_annotations.return_value = KeyValuePairs( - [KeyValuePair('vrouter-uuid', 'vrouter_uuid_1')]) - vnc_vm_2 = Mock() - vnc_vm_2.get_annotations.return_value = KeyValuePairs( - [KeyValuePair('vrouter-uuid', 'vrouter_uuid_2')]) - self.vnc_api_client.read_vm.return_value = vnc_vm_1 + vnc_vm = vnc_api.VirtualMachine('VM', vnc_api.Project()) + vnc_vm.set_annotations(KeyValuePairs( + [KeyValuePair('vrouter-uuid', 'vrouter_uuid_2')])) + self.vnc_api_client.read_vm.return_value = vnc_vm - result = self.vm_service._can_delete_from_vnc(vnc_vm_2) + result = self.vm_service._can_delete_from_vnc(vnc_vm) self.assertFalse(result) def test_vnc_vmi_true(self): - vnc_vmi = Mock() - vnc_vmi.get_annotations.return_value = KeyValuePairs( - [KeyValuePair('vrouter-uuid', 'vrouter_uuid_1')]) + vnc_vmi = vnc_api.VirtualMachineInterface('VMI', vnc_api.Project()) + vnc_vmi.set_annotations(KeyValuePairs( + [KeyValuePair('vrouter-uuid', 'vrouter_uuid_1')])) self.vnc_api_client.read_vmi.return_value = vnc_vmi result = self.vmi_service._can_delete_from_vnc(vnc_vmi) @@ -523,14 +522,11 @@ def test_vnc_vmi_true(self): self.assertTrue(result) def test_vnc_vmi_false(self): - vnc_vmi_1 = Mock() - vnc_vmi_1.get_annotations.return_value = KeyValuePairs( - [KeyValuePair('vrouter-uuid', 'vrouter_uuid_1')]) - vnc_vmi_2 = Mock() - vnc_vmi_2.get_annotations.return_value = KeyValuePairs( - [KeyValuePair('vrouter-uuid', 'vrouter_uuid_2')]) - self.vnc_api_client.read_vmi.return_value = vnc_vmi_1 - - result = self.vmi_service._can_delete_from_vnc(vnc_vmi_2) + vnc_vmi = vnc_api.VirtualMachineInterface('VMI', vnc_api.Project()) + vnc_vmi.set_annotations(KeyValuePairs( + [KeyValuePair('vrouter-uuid', 'vrouter_uuid_2')])) + self.vnc_api_client.read_vmi.return_value = vnc_vmi + + result = self.vmi_service._can_delete_from_vnc(vnc_vmi) self.assertFalse(result)