From b15c202ee5a1464d5725823980b3b13ed7883927 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 17 May 2022 19:48:45 +0530 Subject: [PATCH] test: add test for importUnmanagedInstance (#6385) * test: add test for importUnmanagedInstance Signed-off-by: Abhishek Kumar * refactor Signed-off-by: Abhishek Kumar * fix test Signed-off-by: Abhishek Kumar --- test/integration/smoke/test_vm_life_cycle.py | 152 ----------- .../test_vm_lifecycle_unmanage_import.py | 245 ++++++++++++++++++ tools/marvin/marvin/lib/base.py | 43 +++ tools/marvin/marvin/lib/vcenter.py | 37 ++- 4 files changed, 319 insertions(+), 158 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 8626a3dcaff..0581a8b67c9 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -26,8 +26,6 @@ from marvin.cloudstackAPI import (recoverVirtualMachine, updateConfiguration, migrateVirtualMachine, migrateVirtualMachineWithVolume, - unmanageVirtualMachine, - listUnmanagedInstances, listNics, listVolumes) from marvin.lib.utils import * @@ -49,7 +47,6 @@ from marvin.lib.common import (get_domain, 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,155 +1602,6 @@ class TestKVMLiveMigration(cloudstackTestCase): "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 - ] - - @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" - ) - - 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 00000000000..ea76a7ee428 --- /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 5b38c655011..6567d944759 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -1038,6 +1038,49 @@ class VirtualMachine: 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 = templateid + 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.nicipaddresslist = services["nicipaddresslist"] + virtual_machine = apiclient.importUnmanagedInstance(cmd) + return VirtualMachine(virtual_machine.__dict__, services) + class Volume: """Manage Volume Life cycle diff --git a/tools/marvin/marvin/lib/vcenter.py b/tools/marvin/marvin/lib/vcenter.py index 8b793ee7ee2..39a4ec100b9 100644 --- a/tools/marvin/marvin/lib/vcenter.py +++ b/tools/marvin/marvin/lib/vcenter.py @@ -143,20 +143,26 @@ class Vcenter(): 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 len(result) == 0: + return None + if parse: + return [self.parse_details(c, vimtype) for c in result] + return result def get_dvswitches(self, name=None): """ @@ -186,12 +192,31 @@ class Vcenter(): 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: