From 8a9d68fcfeff8172e644f9aa08a390880c225125 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 12 May 2022 18:17:54 +0530 Subject: [PATCH 1/3] test: add test for importUnmanagedInstance Signed-off-by: Abhishek Kumar --- test/integration/smoke/test_vm_life_cycle.py | 34 ++++++++++++++++ tools/marvin/marvin/lib/base.py | 42 ++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index 8626a3dcaff7..756089be56aa 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -1671,6 +1671,15 @@ def setUp(self): self.network ] + def tearDown(self): + try: + #Clean up, terminate the created templates + cleanup_resources(self.apiclient, self.cleanup) + + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + @attr(tags=["advanced", "advancedns", "smoke", "sg"], required_hardware="false") @skipTestIf("hypervisorNotSupported") def test_01_unmanage_vm_cycle(self): @@ -1752,6 +1761,31 @@ def test_01_unmanage_vm_cycle(self): "PowerOn", "Unmanaged VM is still running" ) + unmanaged_vm_nic = unmanaged_vm.nic[0] + nicnetworklist = [{}] + nicnetworklist[0]["nic"] = unmanaged_vm_nic.id + nicnetworklist[0]["network"] = self.network.id + nicipaddresslist = [{}] + nicipaddresslist[0]["nic"] = unmanaged_vm_nic.id + nicipaddresslist[0]["ip4Address"] = "auto" + importedVMService = { + "nicnetworklist": nicnetworklist, + "nicipaddresslist": nicipaddresslist + } + imported_vm = VirtualMachine.importUnmanagedInstance( + clusterid=clusterid, + name=vm_instance_name, + serviceofferingid=self.small_offering.id, + service=importedVMService, + templateid=self.template.id) + + self.assertEqual( + self.small_offering.id, + imported_vm.serviceofferingid, + "Imported VM service offering is different" + ) + + self.cleanup.append(imported_vm) class TestVAppsVM(cloudstackTestCase): diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 5b38c655011f..1039744271b6 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -1038,6 +1038,48 @@ def listUnmanagedInstances(cls, apiclient, clusterid, name = None): cmd.name = name return apiclient.listUnmanagedInstances(cmd) + @classmethod + def importUnmanagedInstance(cls, apiclient, clusterid, name, serviceofferingid, services, templateid=None + account=None, domainid=None, projectid=None, migrateallowed=None, forced=None): + """Import an unmanaged VM (currently VMware only)""" + cmd = importUnmanagedInstance.importUnmanagedInstanceCmd() + cmd.clusterid = clusterid + cmd.name = name + cmd.serviceofferingid = serviceofferingid + if templateid: + cmd.templateid = account + elif "templateid" in services: + cmd.templateid = services["templateid"] + if account: + cmd.account = account + elif "account" in services: + cmd.account = services["account"] + if domainid: + cmd.domainid = domainid + elif "domainid" in services: + cmd.domainid = services["domainid"] + if projectid: + cmd.projectid = projectid + elif "projectid" in services: + cmd.projectid = services["projectid"] + if migrateallowed: + cmd.migrateallowed = migrateallowed + elif "migrateallowed" in services: + cmd.migrateallowed = services["migrateallowed"] + if forced: + cmd.forced = forced + elif "forced" in services: + cmd.forced = services["forced"] + if "details" in services: + cmd.details = services["details"] + if "datadiskofferinglist" in services: + cmd.datadiskofferinglist = services["datadiskofferinglist"] + if "nicnetworklist" in services: + cmd.nicnetworklist = services["nicnetworklist"] + if "nicipaddresslist" in services: + cmd.nicnetworklist = services["nicipaddresslist"] + return apiclient.importUnmanagedInstance(cmd) + class Volume: """Manage Volume Life cycle From 71553e36ad1c2bbcd8247dc94c7fd0d00dc3b678 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 13 May 2022 13:39:52 +0530 Subject: [PATCH 2/3] refactor Signed-off-by: Abhishek Kumar --- test/integration/smoke/test_vm_life_cycle.py | 186 ------------- .../test_vm_lifecycle_unmanage_import.py | 245 ++++++++++++++++++ tools/marvin/marvin/lib/base.py | 11 +- tools/marvin/marvin/lib/vcenter.py | 35 ++- 4 files changed, 280 insertions(+), 197 deletions(-) create mode 100644 test/integration/smoke/test_vm_lifecycle_unmanage_import.py diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index 756089be56aa..0581a8b67c9b 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -26,8 +26,6 @@ updateConfiguration, migrateVirtualMachine, migrateVirtualMachineWithVolume, - unmanageVirtualMachine, - listUnmanagedInstances, listNics, listVolumes) from marvin.lib.utils import * @@ -49,7 +47,6 @@ get_suitable_test_template, get_test_ovf_templates, list_hosts, - list_virtual_machines, get_vm_vapp_configs) from marvin.codes import FAILED, PASS from nose.plugins.attrib import attr @@ -1605,189 +1602,6 @@ def test_02_migrate_VM_with_two_data_disks(self): "HostID not as expected") -class TestUnmanageVM(cloudstackTestCase): - - @classmethod - def setUpClass(cls): - testClient = super(TestUnmanageVM, cls).getClsTestClient() - cls.apiclient = testClient.getApiClient() - cls.services = testClient.getParsedTestDataConfig() - cls.hypervisor = testClient.getHypervisorInfo() - cls._cleanup = [] - - # Get Zone, Domain and templates - cls.domain = get_domain(cls.apiclient) - cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) - cls.services['mode'] = cls.zone.networktype - cls.template = get_suitable_test_template( - cls.apiclient, - cls.zone.id, - cls.services["ostype"], - cls.hypervisor - ) - if cls.template == FAILED: - assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] - - cls.hypervisorNotSupported = cls.hypervisor.lower() != "vmware" - - cls.services["small"]["zoneid"] = cls.zone.id - cls.services["small"]["template"] = cls.template.id - - cls.account = Account.create( - cls.apiclient, - cls.services["account"], - domainid=cls.domain.id - ) - - cls.small_offering = ServiceOffering.create( - cls.apiclient, - cls.services["service_offerings"]["small"] - ) - - cls.network_offering = NetworkOffering.create( - cls.apiclient, - cls.services["l2-network_offering"], - ) - cls.network_offering.update(cls.apiclient, state='Enabled') - - cls._cleanup = [ - cls.small_offering, - cls.network_offering, - cls.account - ] - - def setUp(self): - self.apiclient = self.testClient.getApiClient() - self.services["network"]["networkoffering"] = self.network_offering.id - - self.network = Network.create( - self.apiclient, - self.services["l2-network"], - zoneid=self.zone.id, - networkofferingid=self.network_offering.id - ) - - self.cleanup = [ - self.network - ] - - def tearDown(self): - try: - #Clean up, terminate the created templates - cleanup_resources(self.apiclient, self.cleanup) - - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - @attr(tags=["advanced", "advancedns", "smoke", "sg"], required_hardware="false") - @skipTestIf("hypervisorNotSupported") - def test_01_unmanage_vm_cycle(self): - """ - Test the following: - 1. Deploy VM - 2. Unmanage VM - 3. Verify VM is not listed in CloudStack - 4. Verify VM is listed as part of the unmanaged instances - 5. Import VM - 6. Destroy VM - """ - - # 1 - Deploy VM - self.virtual_machine = VirtualMachine.create( - self.apiclient, - self.services["virtual_machine"], - templateid=self.template.id, - serviceofferingid=self.small_offering.id, - networkids=self.network.id, - zoneid=self.zone.id - ) - vm_id = self.virtual_machine.id - vm_instance_name = self.virtual_machine.instancename - hostid = self.virtual_machine.hostid - hosts = Host.list( - self.apiclient, - id=hostid - ) - host = hosts[0] - clusterid = host.clusterid - - list_vm = list_virtual_machines( - self.apiclient, - id=vm_id - ) - self.assertEqual( - isinstance(list_vm, list), - True, - "Check if virtual machine is present" - ) - vm_response = list_vm[0] - - self.assertEqual( - vm_response.state, - "Running", - "VM state should be running after deployment" - ) - - # 2 - Unmanage VM from CloudStack - self.virtual_machine.unmanage(self.apiclient) - - list_vm = list_virtual_machines( - self.apiclient, - id=vm_id - ) - - self.assertEqual( - list_vm, - None, - "VM should not be listed" - ) - - unmanaged_vms = VirtualMachine.listUnmanagedInstances( - self.apiclient, - clusterid=clusterid, - name=vm_instance_name - ) - - self.assertEqual( - len(unmanaged_vms), - 1, - "Unmanaged VMs matching instance name list size is 1" - ) - - unmanaged_vm = unmanaged_vms[0] - self.assertEqual( - unmanaged_vm.powerstate, - "PowerOn", - "Unmanaged VM is still running" - ) - unmanaged_vm_nic = unmanaged_vm.nic[0] - nicnetworklist = [{}] - nicnetworklist[0]["nic"] = unmanaged_vm_nic.id - nicnetworklist[0]["network"] = self.network.id - nicipaddresslist = [{}] - nicipaddresslist[0]["nic"] = unmanaged_vm_nic.id - nicipaddresslist[0]["ip4Address"] = "auto" - importedVMService = { - "nicnetworklist": nicnetworklist, - "nicipaddresslist": nicipaddresslist - } - imported_vm = VirtualMachine.importUnmanagedInstance( - clusterid=clusterid, - name=vm_instance_name, - serviceofferingid=self.small_offering.id, - service=importedVMService, - templateid=self.template.id) - - self.assertEqual( - self.small_offering.id, - imported_vm.serviceofferingid, - "Imported VM service offering is different" - ) - - self.cleanup.append(imported_vm) - - class TestVAppsVM(cloudstackTestCase): @classmethod diff --git a/test/integration/smoke/test_vm_lifecycle_unmanage_import.py b/test/integration/smoke/test_vm_lifecycle_unmanage_import.py new file mode 100644 index 000000000000..ea76a7ee4280 --- /dev/null +++ b/test/integration/smoke/test_vm_lifecycle_unmanage_import.py @@ -0,0 +1,245 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +""" BVT tests for Virtual Machine Life Cycle - Unmanage - Import +""" +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import * +from marvin.lib.base import (Account, + ServiceOffering, + VirtualMachine, + Host, + Network, + NetworkOffering, + VirtualMachine) +from marvin.lib.common import (get_domain, + get_zone, + get_suitable_test_template) +from marvin.codes import FAILED +from nose.plugins.attrib import attr +from marvin.lib.decoratorGenerators import skipTestIf +from marvin.lib.vcenter import Vcenter + +_multiprocess_shared_ = True + +class TestUnmanageVM(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestUnmanageVM, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.hypervisor = testClient.getHypervisorInfo() + cls._cleanup = [] + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + cls.template = get_suitable_test_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"], + cls.hypervisor + ) + if cls.template == FAILED: + assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] + + cls.hypervisorNotSupported = cls.hypervisor.lower() != "vmware" + + cls.services["small"]["zoneid"] = cls.zone.id + cls.services["small"]["template"] = cls.template.id + + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + + cls.small_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["small"] + ) + cls._cleanup.append(cls.small_offering) + + cls.network_offering = NetworkOffering.create( + cls.apiclient, + cls.services["l2-network_offering"], + ) + cls._cleanup.append(cls.network_offering) + cls.network_offering.update(cls.apiclient, state='Enabled') + + @classmethod + def tearDownClass(cls): + super(TestUnmanageVM, cls).tearDownClass() + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.services["network"]["networkoffering"] = self.network_offering.id + self.network = Network.create( + self.apiclient, + self.services["l2-network"], + zoneid=self.zone.id, + networkofferingid=self.network_offering.id + ) + self.cleanup = [ + self.network + ] + self.unmanaged_instance = None + + ''' + Fetch vmware datacenter login details + ''' + def get_vmware_dc_config(self, zone_id): + zid = self.dbclient.execute("select id from data_center where uuid='%s';" % + zone_id) + vmware_dc_id = self.dbclient.execute( + "select vmware_data_center_id from vmware_data_center_zone_map where zone_id='%s';" % + zid[0]) + vmware_dc_config = self.dbclient.execute( + "select vcenter_host, username, password from vmware_data_center where id = '%s';" % vmware_dc_id[0]) + + return vmware_dc_config + + def delete_vcenter_vm(self, vm_name): + config = self.get_vmware_dc_config(self.zone.id) + vc_object = Vcenter(config[0][0], config[0][1], 'P@ssword123') + vc_object.delete_vm(vm_name) + + def tearDown(self): + if self.unmanaged_instance is not None: + try: + self.delete_vcenter_vm(self.unmanaged_instance) + except Exception as e: + print("Warning: Exception during cleaning up vCenter VM: %s : %s" % (self.unmanaged_instance, e)) + super(TestUnmanageVM, self).tearDown() + + def check_vm_state(self, vm_id): + list_vm = VirtualMachine.list( + self.apiclient, + id=vm_id + ) + self.assertEqual( + isinstance(list_vm, list), + True, + "Check if virtual machine is present" + ) + vm_response = list_vm[0] + self.assertEqual( + vm_response.state, + "Running", + "VM state should be running after deployment" + ) + return vm_response + + @attr(tags=["advanced", "advancedns", "smoke", "sg"], required_hardware="false") + @skipTestIf("hypervisorNotSupported") + def test_01_unmanage_vm_cycle(self): + """ + Test the following: + 1. Deploy VM + 2. Unmanage VM + 3. Verify VM is not listed in CloudStack + 4. Verify VM is listed as part of the unmanaged instances + 5. Import VM + 6. Destroy VM + """ + + # 1 - Deploy VM + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + serviceofferingid=self.small_offering.id, + networkids=self.network.id, + zoneid=self.zone.id + ) + vm_id = self.virtual_machine.id + vm_instance_name = self.virtual_machine.instancename + hostid = self.virtual_machine.hostid + hosts = Host.list( + self.apiclient, + id=hostid + ) + host = hosts[0] + clusterid = host.clusterid + self.check_vm_state(vm_id) + # 2 - Unmanage VM from CloudStack + self.virtual_machine.unmanage(self.apiclient) + self.unmanaged_instance = vm_instance_name + # 3 - Verify VM is not listed in CloudStack + list_vm = VirtualMachine.list( + self.apiclient, + id=vm_id + ) + self.assertEqual( + list_vm, + None, + "VM should not be listed" + ) + # 4 - Verify VM is listed as part of the unmanaged instances + unmanaged_vms = VirtualMachine.listUnmanagedInstances( + self.apiclient, + clusterid=clusterid, + name=vm_instance_name + ) + self.assertEqual( + len(unmanaged_vms), + 1, + "Unmanaged VMs matching instance name list size is 1" + ) + unmanaged_vm = unmanaged_vms[0] + self.assertEqual( + unmanaged_vm.powerstate, + "PowerOn", + "Unmanaged VM is still running" + ) + # 5 - Import VM + unmanaged_vm_nic = unmanaged_vm.nic[0] + nicnetworklist = [{}] + nicnetworklist[0]["nic"] = unmanaged_vm_nic.id + nicnetworklist[0]["network"] = self.network.id + nicipaddresslist = [{}] + if self.network.type == "Isolated": + nicipaddresslist[0]["nic"] = unmanaged_vm_nic.id + nicipaddresslist[0]["ip4Address"] = "auto" + import_vm_service = { + "nicnetworklist": nicnetworklist, + "nicipaddresslist": nicipaddresslist + } + self.imported_vm = VirtualMachine.importUnmanagedInstance( + self.apiclient, + clusterid=clusterid, + name=vm_instance_name, + serviceofferingid=self.small_offering.id, + services=import_vm_service, + templateid=self.template.id) + self.cleanup.append(self.imported_vm) + self.unmanaged_instance = None + self.assertEqual( + self.small_offering.id, + self.imported_vm.serviceofferingid, + "Imported VM service offering is different, expected: %s, actual: %s" % (self.small_offering.id, self.imported_vm.serviceofferingid) + ) + self.assertEqual( + self.template.id, + self.imported_vm.templateid, + "Imported VM template is different, expected: %s, actual: %s" % (self.template.id, self.imported_vm.templateid) + ) + self.check_vm_state(self.imported_vm.id) diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 1039744271b6..6567d944759e 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -1039,15 +1039,15 @@ def listUnmanagedInstances(cls, apiclient, clusterid, name = None): return apiclient.listUnmanagedInstances(cmd) @classmethod - def importUnmanagedInstance(cls, apiclient, clusterid, name, serviceofferingid, services, templateid=None - account=None, domainid=None, projectid=None, migrateallowed=None, forced=None): + def importUnmanagedInstance(cls, apiclient, clusterid, name, serviceofferingid, services, templateid=None, + account=None, domainid=None, projectid=None, migrateallowed=None, forced=None): """Import an unmanaged VM (currently VMware only)""" cmd = importUnmanagedInstance.importUnmanagedInstanceCmd() cmd.clusterid = clusterid cmd.name = name cmd.serviceofferingid = serviceofferingid if templateid: - cmd.templateid = account + cmd.templateid = templateid elif "templateid" in services: cmd.templateid = services["templateid"] if account: @@ -1077,8 +1077,9 @@ def importUnmanagedInstance(cls, apiclient, clusterid, name, serviceofferingid, if "nicnetworklist" in services: cmd.nicnetworklist = services["nicnetworklist"] if "nicipaddresslist" in services: - cmd.nicnetworklist = services["nicipaddresslist"] - return apiclient.importUnmanagedInstance(cmd) + cmd.nicipaddresslist = services["nicipaddresslist"] + virtual_machine = apiclient.importUnmanagedInstance(cmd) + return VirtualMachine(virtual_machine.__dict__, services) class Volume: diff --git a/tools/marvin/marvin/lib/vcenter.py b/tools/marvin/marvin/lib/vcenter.py index 8b793ee7ee28..6019c2d4293e 100644 --- a/tools/marvin/marvin/lib/vcenter.py +++ b/tools/marvin/marvin/lib/vcenter.py @@ -143,20 +143,24 @@ def parse_details(self, obj, vimtype): parsedObject['name'] = obj.name return parsedObject - def _get_obj(self, vimtype, name=None): + def _get_obj(self, vimtype, name=None, parse=True): """ Get the vsphere object associated with a given text name """ - obj = None content = self.service_instance.RetrieveContent() container = content.viewManager.CreateContainerView(content.rootFolder, vimtype, True) + result = [] for c in container.view: if name is not None: if c.name == name: - obj = c - return [self.parse_details(obj, vimtype)] + result.append(c) + break else: - return [self.parse_details(c, vimtype) for c in container.view] + result.append(c) + container.Destroy() + if parse: + return [self.parse_details(c, vimtype) for c in result] + return result def get_dvswitches(self, name=None): """ @@ -186,12 +190,31 @@ def get_dvportgroups(self, name=None): def get_vms(self, name=None): """ - :param name: + Get VMs in vCenter + :param name: Name of the VM in vCenter :return: """ vms = self._get_obj([vim.VirtualMachine], name) return vms + + def delete_vm(self, name): + """ + Deletes a VM in vCenter + :param name: Name of the VM in vCenter + :return: + """ + vms = self._get_obj([vim.VirtualMachine], name, False) + if type(vms) is not list or len(vms) != 1: + return False + vm = vms[0] + print("Deleting VM with name: %s; vm: %s" % (name, vm)) + task = vm.PowerOffVM_Task() + self.wait_for_task(task) + task = vm.Destroy_Task() + self.wait_for_task(task) + return True + def get_clusters(self, dc, clus=None): """ :param dc: From 6f58570bcfa6722f871d77be9d18f9fc3f03391e Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Sat, 14 May 2022 22:05:37 +0530 Subject: [PATCH 3/3] fix test Signed-off-by: Abhishek Kumar --- tools/marvin/marvin/lib/vcenter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/marvin/marvin/lib/vcenter.py b/tools/marvin/marvin/lib/vcenter.py index 6019c2d4293e..39a4ec100b9d 100644 --- a/tools/marvin/marvin/lib/vcenter.py +++ b/tools/marvin/marvin/lib/vcenter.py @@ -158,6 +158,8 @@ def _get_obj(self, vimtype, name=None, parse=True): else: result.append(c) container.Destroy() + if len(result) == 0: + return None if parse: return [self.parse_details(c, vimtype) for c in result] return result