From d925bedb69a2332051ff669b09662e95aa2e1c04 Mon Sep 17 00:00:00 2001 From: junxuan Date: Fri, 6 Aug 2021 03:48:37 -0400 Subject: [PATCH 01/17] add integration test for clone --- test/integration/smoke/test_vm_life_cycle.py | 21 ++++++++++++++++++++ tools/marvin/marvin/lib/base.py | 11 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index 61b3a22a6c8e..731256946889 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -876,6 +876,27 @@ def test_11_destroy_vm_and_volumes(self): self.assertEqual(Volume.list(self.apiclient, id=vol1.id), None, "List response contains records when it should not") + @attr(tags = ["devcloud", "advanced", "smoke", "basic", "sg"], required_hardware="false") + def test_12_clone_vm_and_volumes(self): + small_disk_offering = DiskOffering.list(self.apiclient, name='Small')[0]; + small_virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["small"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.small_offering.id,) + vol1 = Volume.create( + self.apiclient, + self.services, + account=self.account.name, + diskofferingid=small_disk_offering.id, + domainid=self.account.domainid, + zoneid=self.zone.id + ) + small_virtual_machine.attach_volume(self.apiclient, vol1) + self.debug("Clone VM - ID: %s" % small_virtual_machine.id) + small_virtual_machine.clone(self.apiclient) + self.assertEqual(VirtualMachine.list(self.apiclient, id=small_virtual_machine.id), None, "List response contains records when it should not") class TestSecuredVmMigration(cloudstackTestCase): diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 916af64d96cc..25b460fca9a0 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -744,6 +744,17 @@ def reboot(self, apiclient, forced=None): if response[0] == FAIL: raise Exception(response[1]) + def clone(self, apiclient, forced=None): + """"Clone the instance""" + cmd = cloneVirtualMachine.cloneVirtualMachineCmd() + cmd.id = self.id + if forced: + cmd.forced =forced + apiclient.cloneVirtualMachine(cmd) + response = self.getState(apiclient, VirtualMachine.RUNNING) + if (response[0] == FAIL): + raise Exception(response[1]) + def recover(self, apiclient): """Recover the instance""" cmd = recoverVirtualMachine.recoverVirtualMachineCmd() From cdbbd607c8eb53a72aed12bbb1b583f0e62f27e5 Mon Sep 17 00:00:00 2001 From: junxuan Date: Fri, 6 Aug 2021 12:56:38 -0400 Subject: [PATCH 02/17] add clone tag to test --- test/integration/smoke/test_vm_life_cycle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index 731256946889..4e35367314e9 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -876,7 +876,7 @@ def test_11_destroy_vm_and_volumes(self): self.assertEqual(Volume.list(self.apiclient, id=vol1.id), None, "List response contains records when it should not") - @attr(tags = ["devcloud", "advanced", "smoke", "basic", "sg"], required_hardware="false") + @attr(tags = ["clone", "devcloud", "advanced", "smoke", "basic", "sg"], required_hardware="false") def test_12_clone_vm_and_volumes(self): small_disk_offering = DiskOffering.list(self.apiclient, name='Small')[0]; small_virtual_machine = VirtualMachine.create( From 6af4d28569796ae14af402b39d2a513572e7dfc4 Mon Sep 17 00:00:00 2001 From: junxuan Date: Fri, 6 Aug 2021 13:07:30 -0400 Subject: [PATCH 03/17] fix the virtualmachine id attribute --- tools/marvin/marvin/lib/base.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 25b460fca9a0..5d97229407cc 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -744,12 +744,10 @@ def reboot(self, apiclient, forced=None): if response[0] == FAIL: raise Exception(response[1]) - def clone(self, apiclient, forced=None): + def clone(self, apiclient): """"Clone the instance""" cmd = cloneVirtualMachine.cloneVirtualMachineCmd() - cmd.id = self.id - if forced: - cmd.forced =forced + cmd.virtualmachineid = self.id apiclient.cloneVirtualMachine(cmd) response = self.getState(apiclient, VirtualMachine.RUNNING) if (response[0] == FAIL): From a743d20820741d7d6c0ac73a14e86f36cd466121 Mon Sep 17 00:00:00 2001 From: junxuan Date: Sat, 7 Aug 2021 03:19:29 -0400 Subject: [PATCH 04/17] fix the integration target --- test/integration/smoke/test_vm_life_cycle.py | 6 +++--- tools/marvin/marvin/lib/base.py | 16 ++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index 4e35367314e9..bc8a1a1d8e24 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -876,7 +876,7 @@ def test_11_destroy_vm_and_volumes(self): self.assertEqual(Volume.list(self.apiclient, id=vol1.id), None, "List response contains records when it should not") - @attr(tags = ["clone", "devcloud", "advanced", "smoke", "basic", "sg"], required_hardware="false") + @attr(tags = ["clone","devcloud", "advanced", "smoke", "basic", "sg"], required_hardware="false") def test_12_clone_vm_and_volumes(self): small_disk_offering = DiskOffering.list(self.apiclient, name='Small')[0]; small_virtual_machine = VirtualMachine.create( @@ -895,8 +895,8 @@ def test_12_clone_vm_and_volumes(self): ) small_virtual_machine.attach_volume(self.apiclient, vol1) self.debug("Clone VM - ID: %s" % small_virtual_machine.id) - small_virtual_machine.clone(self.apiclient) - self.assertEqual(VirtualMachine.list(self.apiclient, id=small_virtual_machine.id), None, "List response contains records when it should not") + clone_response = small_virtual_machine.clone(self.apiclient, small_virtual_machine) + self.assertEqual(VirtualMachine.list(self.apiclient, id=clone_response.id), None, "List response contains records when it should not") class TestSecuredVmMigration(cloudstackTestCase): diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 5d97229407cc..401cce825537 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -744,14 +744,18 @@ def reboot(self, apiclient, forced=None): if response[0] == FAIL: raise Exception(response[1]) - def clone(self, apiclient): + def clone(self, apiclient, vm): """"Clone the instance""" cmd = cloneVirtualMachine.cloneVirtualMachineCmd() - cmd.virtualmachineid = self.id - apiclient.cloneVirtualMachine(cmd) - response = self.getState(apiclient, VirtualMachine.RUNNING) - if (response[0] == FAIL): - raise Exception(response[1]) + cmd.virtualmachineid = vm.id + response = apiclient.cloneVirtualMachine(cmd) + temp = self.id + self.id = response.id + state = self.getState(apiclient, VirtualMachine.RUNNING) + self.id = temp + if (state[0] == FAIL): + raise Exception(state[1]) + return response def recover(self, apiclient): """Recover the instance""" From 1194b637235c63b1ac4155d70875f8e593527103 Mon Sep 17 00:00:00 2001 From: junxuan Date: Sat, 7 Aug 2021 21:22:40 -0400 Subject: [PATCH 05/17] debug msg and vm id logged --- test/integration/smoke/test_vm_life_cycle.py | 6 +++++- tools/marvin/marvin/lib/base.py | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index bc8a1a1d8e24..bd65e0763730 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -895,7 +895,11 @@ def test_12_clone_vm_and_volumes(self): ) small_virtual_machine.attach_volume(self.apiclient, vol1) self.debug("Clone VM - ID: %s" % small_virtual_machine.id) - clone_response = small_virtual_machine.clone(self.apiclient, small_virtual_machine) + try: + clone_response = small_virtual_machine.clone(self.apiclient, small_virtual_machine) + except Exception as e: + self.debug("Clone --" + str(e)) + raise e self.assertEqual(VirtualMachine.list(self.apiclient, id=clone_response.id), None, "List response contains records when it should not") class TestSecuredVmMigration(cloudstackTestCase): diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 401cce825537..9b34cd68ab60 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -748,6 +748,8 @@ def clone(self, apiclient, vm): """"Clone the instance""" cmd = cloneVirtualMachine.cloneVirtualMachineCmd() cmd.virtualmachineid = vm.id + if vm.id is None: + cmd.virtualmachineid = self.id response = apiclient.cloneVirtualMachine(cmd) temp = self.id self.id = response.id From 0521448939196b9cdb0e6cfcb21d8f115e8ad8b9 Mon Sep 17 00:00:00 2001 From: junxuan Date: Sat, 7 Aug 2021 23:31:15 -0400 Subject: [PATCH 06/17] fix the size integration test --- test/integration/smoke/test_vm_life_cycle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index bd65e0763730..cd0b440e1e03 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -884,7 +884,8 @@ def test_12_clone_vm_and_volumes(self): self.services["small"], accountid=self.account.name, domainid=self.account.domainid, - serviceofferingid=self.small_offering.id,) + serviceofferingid=self.small_offering.id, + rootdisksize=100,) vol1 = Volume.create( self.apiclient, self.services, From de15e9d687f7416997961a7a154c2b97911c017d Mon Sep 17 00:00:00 2001 From: junxuan Date: Sat, 7 Aug 2021 23:38:03 -0400 Subject: [PATCH 07/17] fix the resource limit check --- test/integration/smoke/test_vm_life_cycle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index cd0b440e1e03..217cc2c5076a 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -885,7 +885,7 @@ def test_12_clone_vm_and_volumes(self): accountid=self.account.name, domainid=self.account.domainid, serviceofferingid=self.small_offering.id, - rootdisksize=100,) + rootdisksize=1,) vol1 = Volume.create( self.apiclient, self.services, From 76f9869177965d1fc71b7c81aa292472bfeb7735 Mon Sep 17 00:00:00 2001 From: junxuan Date: Sat, 7 Aug 2021 23:45:23 -0400 Subject: [PATCH 08/17] fix min root size --- test/integration/smoke/test_vm_life_cycle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index 217cc2c5076a..4b8bbaa5c510 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -885,7 +885,7 @@ def test_12_clone_vm_and_volumes(self): accountid=self.account.name, domainid=self.account.domainid, serviceofferingid=self.small_offering.id, - rootdisksize=1,) + rootdisksize=2,) vol1 = Volume.create( self.apiclient, self.services, From dee440681d16c8c444aa088922438877efd2957a Mon Sep 17 00:00:00 2001 From: junxuan Date: Sun, 8 Aug 2021 00:27:15 -0400 Subject: [PATCH 09/17] fix size error when the template is not healthy --- .../main/java/com/cloud/template/TemplateManagerImpl.java | 6 ++++++ test/integration/smoke/test_vm_life_cycle.py | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java index 188a06766b09..2bc2be2dfd48 100755 --- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java @@ -1813,6 +1813,12 @@ public VirtualMachineTemplate createPrivateTemplate(CloneVMCmd cmd) throws Cloud s_logger.info("successfully created the template with Id: " + templateId); finalTmpProduct = _tmpltDao.findById(templateId); TemplateDataStoreVO srcTmpltStore = _tmplStoreDao.findByStoreTemplate(store.getId(), templateId); + try { + srcTmpltStore.getSize(); + } catch (NullPointerException e) { + srcTmpltStore.setSize(0L); + _tmplStoreDao.update(srcTmpltStore.getId(), srcTmpltStore); + } UsageEventVO usageEvent = new UsageEventVO(EventTypes.EVENT_TEMPLATE_CREATE, finalTmpProduct.getAccountId(), zoneId, finalTmpProduct.getId(), finalTmpProduct.getName(), null, finalTmpProduct.getSourceTemplateId(), srcTmpltStore.getPhysicalSize(), finalTmpProduct.getSize()); diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index 4b8bbaa5c510..bd65e0763730 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -884,8 +884,7 @@ def test_12_clone_vm_and_volumes(self): self.services["small"], accountid=self.account.name, domainid=self.account.domainid, - serviceofferingid=self.small_offering.id, - rootdisksize=2,) + serviceofferingid=self.small_offering.id,) vol1 = Volume.create( self.apiclient, self.services, From b1ad52cec266e15e33e79771f3418985b6fa74c3 Mon Sep 17 00:00:00 2001 From: junxuan Date: Sun, 8 Aug 2021 01:59:33 -0400 Subject: [PATCH 10/17] fix the permission issue --- server/src/main/java/com/cloud/vm/UserVmManagerImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index f0a680827e60..a5bec03aaf5f 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -4635,6 +4635,7 @@ private VolumeVO saveDataDiskVolumeFromSnapShot(final Account owner, final Boole public Optional cloneVirtualMachine(CloneVMCmd cmd, VolumeApiService volumeService, SnapshotApiService snapshotService) throws ResourceUnavailableException, ConcurrentOperationException, CloudRuntimeException, InsufficientCapacityException, ResourceAllocationException { long vmId = cmd.getEntityId(); UserVmVO curVm = _vmDao.findById(vmId); + Account curVmAccount = _accountDao.findById(curVm.getAccountId()); // create and attach data disk long targetClonedVmId = cmd.getId(); Account caller = CallContext.current().getCallingAccount(); @@ -4669,7 +4670,7 @@ public Optional cloneVirtualMachine(CloneVMCmd cmd, VolumeApiService vol DataCenterVO dataCenter = _dcDao.findById(zoneId); String volumeName = snapshotEntity.getName() + "-DataDisk-Volume"; VolumeVO parentVolume = _volsDao.findByIdIncludingRemoved(snapshotEntity.getVolumeId()); - newDatadisk = saveDataDiskVolumeFromSnapShot(caller, true, zoneId, + newDatadisk = saveDataDiskVolumeFromSnapShot(curVmAccount, true, zoneId, diskOfferingId, provisioningType, size, minIops, maxIops, parentVolume, volumeName, _uuidMgr.generateUuid(Volume.class, null), new HashMap<>()); VolumeVO volumeEntity = (VolumeVO) volumeService.cloneDataVolume(cmd, snapshotEntity.getId(), newDatadisk); createdVolumes.add(volumeEntity); From 94015718b9ecc42eb230799bc84fe4c4078d8342 Mon Sep 17 00:00:00 2001 From: junxuan Date: Sun, 8 Aug 2021 02:18:19 -0400 Subject: [PATCH 11/17] finish the assertion of clone vm integration test --- test/integration/smoke/test_vm_life_cycle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index bd65e0763730..979bb5467c5f 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -900,7 +900,7 @@ def test_12_clone_vm_and_volumes(self): except Exception as e: self.debug("Clone --" + str(e)) raise e - self.assertEqual(VirtualMachine.list(self.apiclient, id=clone_response.id), None, "List response contains records when it should not") + self.assertTrue(VirtualMachine.list(self.apiclient, id=clone_response.id) is not None, "vm id should be populated") class TestSecuredVmMigration(cloudstackTestCase): From 0cafec91eda99eca74c5ce041adf027c1204466c Mon Sep 17 00:00:00 2001 From: junxuan Date: Sun, 8 Aug 2021 02:27:22 -0400 Subject: [PATCH 12/17] migrate clone vm to seperate class cycle test --- test/integration/smoke/test_vm_life_cycle.py | 152 +++++++++++++++---- 1 file changed, 126 insertions(+), 26 deletions(-) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index 979bb5467c5f..81940aa9e466 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -876,32 +876,6 @@ def test_11_destroy_vm_and_volumes(self): self.assertEqual(Volume.list(self.apiclient, id=vol1.id), None, "List response contains records when it should not") - @attr(tags = ["clone","devcloud", "advanced", "smoke", "basic", "sg"], required_hardware="false") - def test_12_clone_vm_and_volumes(self): - small_disk_offering = DiskOffering.list(self.apiclient, name='Small')[0]; - small_virtual_machine = VirtualMachine.create( - self.apiclient, - self.services["small"], - accountid=self.account.name, - domainid=self.account.domainid, - serviceofferingid=self.small_offering.id,) - vol1 = Volume.create( - self.apiclient, - self.services, - account=self.account.name, - diskofferingid=small_disk_offering.id, - domainid=self.account.domainid, - zoneid=self.zone.id - ) - small_virtual_machine.attach_volume(self.apiclient, vol1) - self.debug("Clone VM - ID: %s" % small_virtual_machine.id) - try: - clone_response = small_virtual_machine.clone(self.apiclient, small_virtual_machine) - except Exception as e: - self.debug("Clone --" + str(e)) - raise e - self.assertTrue(VirtualMachine.list(self.apiclient, id=clone_response.id) is not None, "vm id should be populated") - class TestSecuredVmMigration(cloudstackTestCase): @classmethod @@ -1968,3 +1942,129 @@ def test_01_vapps_vm_cycle(self): cmd = destroyVirtualMachine.destroyVirtualMachineCmd() cmd.id = vm.id self.apiclient.destroyVirtualMachine(cmd) + +class TestCloneVM(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestVMLifeCycle, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.hypervisor = testClient.getHypervisorInfo() + + # Get Zone, Domain and templates + domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + + # if local storage is enabled, alter the offerings to use localstorage + # this step is needed for devcloud + if cls.zone.localstorageenabled == True: + cls.services["service_offerings"]["tiny"]["storagetype"] = 'local' + cls.services["service_offerings"]["small"]["storagetype"] = 'local' + cls.services["service_offerings"]["medium"]["storagetype"] = 'local' + + template = get_suitable_test_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"], + cls.hypervisor + ) + if template == FAILED: + assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] + + # Set Zones and disk offerings + cls.services["small"]["zoneid"] = cls.zone.id + cls.services["small"]["template"] = template.id + + cls.services["iso1"]["zoneid"] = cls.zone.id + + # Create VMs, NAT Rules etc + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=domain.id + ) + + cls.small_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["small"] + ) + + cls.medium_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["medium"] + ) + # create small and large virtual machines + cls.small_virtual_machine = VirtualMachine.create( + cls.apiclient, + cls.services["small"], + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.small_offering.id, + mode=cls.services["mode"] + ) + cls.medium_virtual_machine = VirtualMachine.create( + cls.apiclient, + cls.services["small"], + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.medium_offering.id, + mode=cls.services["mode"] + ) + cls.virtual_machine = VirtualMachine.create( + cls.apiclient, + cls.services["small"], + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.small_offering.id, + mode=cls.services["mode"] + ) + cls._cleanup = [ + cls.small_offering, + cls.medium_offering, + cls.account + ] + + @classmethod + def tearDownClass(cls): + super(TestVMLifeCycle, cls).tearDownClass() + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + + def tearDown(self): + try: + # Clean up, terminate the created ISOs + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags = ["clone","devcloud", "advanced", "smoke", "basic", "sg"], required_hardware="false") + def test_clone_vm_and_volumes(self): + small_disk_offering = DiskOffering.list(self.apiclient, name='Small')[0]; + small_virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["small"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.small_offering.id,) + vol1 = Volume.create( + self.apiclient, + self.services, + account=self.account.name, + diskofferingid=small_disk_offering.id, + domainid=self.account.domainid, + zoneid=self.zone.id + ) + small_virtual_machine.attach_volume(self.apiclient, vol1) + self.debug("Clone VM - ID: %s" % small_virtual_machine.id) + try: + clone_response = small_virtual_machine.clone(self.apiclient, small_virtual_machine) + except Exception as e: + self.debug("Clone --" + str(e)) + raise e + self.assertTrue(VirtualMachine.list(self.apiclient, id=clone_response.id) is not None, "vm id should be populated") \ No newline at end of file From 43d00b306cb26e8c6d75dbbd9ac26480ea5aacec Mon Sep 17 00:00:00 2001 From: junxuan Date: Sun, 8 Aug 2021 02:30:26 -0400 Subject: [PATCH 13/17] fix typo --- test/integration/smoke/test_vm_life_cycle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index 81940aa9e466..d853324c1ef0 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -1947,7 +1947,7 @@ class TestCloneVM(cloudstackTestCase): @classmethod def setUpClass(cls): - testClient = super(TestVMLifeCycle, cls).getClsTestClient() + testClient = super(TestCloneVM, cls).getClsTestClient() cls.apiclient = testClient.getApiClient() cls.services = testClient.getParsedTestDataConfig() cls.hypervisor = testClient.getHypervisorInfo() @@ -2028,7 +2028,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - super(TestVMLifeCycle, cls).tearDownClass() + super(TestCloneVM, cls).tearDownClass() def setUp(self): self.apiclient = self.testClient.getApiClient() From 13bb0a590a2b9b67b7125cefe635ee7de2a3ab68 Mon Sep 17 00:00:00 2001 From: junxuan Date: Thu, 12 Aug 2021 23:52:10 -0400 Subject: [PATCH 14/17] refactor the clone to service layer --- .../main/java/com/cloud/storage/Snapshot.java | 1 + .../com/cloud/storage/VolumeApiService.java | 4 +-- .../main/java/com/cloud/vm/UserVmService.java | 1 + .../api/command/user/vm/CloneVMCmd.java | 36 +++---------------- .../cloud/storage/VolumeApiServiceImpl.java | 7 +--- .../java/com/cloud/vm/UserVmManagerImpl.java | 31 ++++++++++++++-- 6 files changed, 39 insertions(+), 41 deletions(-) diff --git a/api/src/main/java/com/cloud/storage/Snapshot.java b/api/src/main/java/com/cloud/storage/Snapshot.java index 64917fe64213..9dc7d45b036e 100644 --- a/api/src/main/java/com/cloud/storage/Snapshot.java +++ b/api/src/main/java/com/cloud/storage/Snapshot.java @@ -27,6 +27,7 @@ public interface Snapshot extends ControlledEntity, Identity, InternalIdentity, StateObject { public enum Type { MANUAL, RECURRING, TEMPLATE, HOURLY, DAILY, WEEKLY, MONTHLY, INTERNAL; + // New types should be defined after INTERNAL, and change the max value private int max = 8; public void setMax(int max) { diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java index c9a5139043f6..f8ff9c885f95 100644 --- a/api/src/main/java/com/cloud/storage/VolumeApiService.java +++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java @@ -94,7 +94,7 @@ public interface VolumeApiService { Volume detachVolumeViaDestroyVM(long vmId, long volumeId); - Volume cloneDataVolume(CloneVMCmd cmd, long snapshotId, Volume volume) throws StorageUnavailableException; + Volume cloneDataVolume(long vmId, long snapshotId, Volume volume) throws StorageUnavailableException; Volume detachVolumeFromVM(DetachVolumeCmd cmd); @@ -105,7 +105,7 @@ Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account acc Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, String customId, long owner, String chainInfo); - Volume attachVolumeToVm(CloneVMCmd cmd, Long volumeId, Long deviceId); + Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId); /** * Extracts the volume to a particular location. diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java index da8c2373ca79..4eb006c90e04 100644 --- a/api/src/main/java/com/cloud/vm/UserVmService.java +++ b/api/src/main/java/com/cloud/vm/UserVmService.java @@ -100,6 +100,7 @@ public interface UserVmService { void checkCloneCondition(CloneVMCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException, ResourceAllocationException; + void prepareCloneVirtualMachine(CloneVMCmd cmd) throws ResourceAllocationException, InsufficientCapacityException, ResourceUnavailableException; /** * Resets the password of a virtual machine. * diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CloneVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CloneVMCmd.java index b5e388dbb5e4..3ea51897cfb4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CloneVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CloneVMCmd.java @@ -12,27 +12,18 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.acl.SecurityChecker.AccessType; -import org.apache.cloudstack.api.ACL; -import org.apache.cloudstack.api.APICommand; -import org.apache.cloudstack.api.ApiCommandJobType; -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.BaseAsyncCreateCustomIdCmd; -import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.ResponseObject; -import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.*; import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.UserVmResponse; -//import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext; import org.apache.log4j.Logger; import java.util.Optional; -@APICommand(name = "cloneVirtualMachine", responseObject = UserVmResponse.class, description = "clone a virtual VM in full clone mode", - responseView = ResponseObject.ResponseView.Restricted, requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, entityType = {VirtualMachine.class}) -public class CloneVMCmd extends BaseAsyncCreateCustomIdCmd implements UserCmd { +@APICommand(name = "cloneVirtualMachine", responseObject = UserVmResponse.class, description = "clone a virtual VM", + responseView = ResponseObject.ResponseView.Restricted, requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, entityType = {VirtualMachine.class}, since="4.16.0") +public class CloneVMCmd extends BaseAsyncCreateCmd implements UserCmd { public static final Logger s_logger = Logger.getLogger(CloneVMCmd.class.getName()); private static final String s_name = "clonevirtualmachineresponse"; @@ -102,19 +93,7 @@ public void setTemporaryTemlateId(long tempId) { public void create() throws ResourceAllocationException { try { _userVmService.checkCloneCondition(this); - VirtualMachineTemplate template = _templateService.createPrivateTemplateRecord(this, _accountService.getAccount(getEntityOwnerId()), _volumeService); - if (template == null) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "failed to create a template to db"); - } - s_logger.info("The template id recorded is: " + template.getId()); - setTemporaryTemlateId(template.getId()); - _templateService.createPrivateTemplate(this); - UserVm vmRecord = _userVmService.recordVirtualMachineToDB(this); - if (vmRecord == null) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "unable to record a new VM to db!"); - } - setEntityId(vmRecord.getId()); - setEntityUuid(vmRecord.getUuid()); + _userVmService.prepareCloneVirtualMachine(this); } catch (ResourceUnavailableException | InsufficientCapacityException e) { s_logger.warn("Exception: ", e); @@ -126,11 +105,6 @@ public void create() throws ResourceAllocationException { throw new ServerApiException(e.getErrorCode(), e.getDescription()); } catch (CloudRuntimeException e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); - } finally { - if (getTemporarySnapShotId() != null) { - _snapshotService.deleteSnapshot(getTemporarySnapShotId()); - s_logger.warn("clearing the temporary snapshot: " + getTemporarySnapShotId()); - } } } diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 59d0e3aed920..f692a9bf03d9 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -910,8 +910,7 @@ public VolumeVO createVolume(CreateVolumeCmd cmd) { } @Override - public Volume cloneDataVolume(CloneVMCmd cmd, long snapshotId, Volume volume) throws StorageUnavailableException { - long vmId = cmd.getEntityId(); + public Volume cloneDataVolume(long vmId, long snapshotId, Volume volume) throws StorageUnavailableException { return createVolumeFromSnapshot((VolumeVO) volume, snapshotId, vmId); } @@ -1706,10 +1705,6 @@ private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long device } @Override - public Volume attachVolumeToVm(CloneVMCmd cmd, Long volumeId, Long deviceId) { - return attachVolumeToVM(cmd.getEntityId(), volumeId, deviceId); - } - public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId) { Account caller = CallContext.current().getCallingAccount(); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index a5bec03aaf5f..7382c894a95e 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -532,6 +532,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private BackupDao backupDao; @Inject private BackupManager backupManager; + @Inject + private SnapshotApiService _snapshotService; private ScheduledExecutorService _executor = null; private ScheduledExecutorService _vmIpFetchExecutor = null; @@ -4630,6 +4632,30 @@ private VolumeVO saveDataDiskVolumeFromSnapShot(final Account owner, final Boole }); } + @Override + public void prepareCloneVirtualMachine(CloneVMCmd cmd) throws ResourceAllocationException, ResourceUnavailableException, InsufficientCapacityException { + try { + VirtualMachineTemplate template = _tmplService.createPrivateTemplateRecord(cmd, _accountService.getAccount(cmd.getEntityOwnerId()), _volumeService); + if (template == null) { + throw new CloudRuntimeException("failed to create a template to db"); + } + s_logger.info("The template id recorded is: " + template.getId()); + cmd.setTemporaryTemlateId(template.getId()); + _tmplService.createPrivateTemplate(cmd); + UserVm vmRecord = recordVirtualMachineToDB(cmd); + if (vmRecord == null) { + throw new CloudRuntimeException("Unable to record the VM to DB!"); + } + cmd.setEntityUuid(vmRecord.getUuid()); + cmd.setEntityId(vmRecord.getId()); + } finally { + if (cmd.getTemporarySnapShotId() != null) { + _snapshotService.deleteSnapshot(cmd.getTemporarySnapShotId()); + s_logger.warn("clearing the temporary snapshot: " + cmd.getTemporarySnapShotId()); + } + } + } + @Override @ActionEvent(eventType = EventTypes.EVENT_VM_CLONE, eventDescription = "clone vm", async = true) public Optional cloneVirtualMachine(CloneVMCmd cmd, VolumeApiService volumeService, SnapshotApiService snapshotService) throws ResourceUnavailableException, ConcurrentOperationException, CloudRuntimeException, InsufficientCapacityException, ResourceAllocationException { @@ -4672,12 +4698,13 @@ public Optional cloneVirtualMachine(CloneVMCmd cmd, VolumeApiService vol VolumeVO parentVolume = _volsDao.findByIdIncludingRemoved(snapshotEntity.getVolumeId()); newDatadisk = saveDataDiskVolumeFromSnapShot(curVmAccount, true, zoneId, diskOfferingId, provisioningType, size, minIops, maxIops, parentVolume, volumeName, _uuidMgr.generateUuid(Volume.class, null), new HashMap<>()); - VolumeVO volumeEntity = (VolumeVO) volumeService.cloneDataVolume(cmd, snapshotEntity.getId(), newDatadisk); + VolumeVO volumeEntity = (VolumeVO) volumeService.cloneDataVolume(cmd.getEntityId(), snapshotEntity.getId(), newDatadisk); createdVolumes.add(volumeEntity); } for (VolumeVO createdVol : createdVolumes) { - volumeService.attachVolumeToVm(cmd, createdVol.getId(), createdVol.getDeviceId()); +// volumeService.attachVolumeToVm(cmd, createdVol.getId(), createdVol.getDeviceId()); + volumeService.attachVolumeToVM(cmd.getEntityId(), createdVol.getId(), createdVol.getDeviceId()); } } catch (CloudRuntimeException e){ s_logger.warn("data disk process failed during clone, clearing the temporary resources..."); From 8b552628e5d5f8622aa52f39a3b5e9926a9d5005 Mon Sep 17 00:00:00 2001 From: junxuan Date: Thu, 12 Aug 2021 23:56:01 -0400 Subject: [PATCH 15/17] fix checkstyle --- .../main/java/com/cloud/storage/VolumeApiService.java | 1 - .../cloudstack/api/command/user/vm/CloneVMCmd.java | 10 +++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java index f8ff9c885f95..5eb93ca9556c 100644 --- a/api/src/main/java/com/cloud/storage/VolumeApiService.java +++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java @@ -22,7 +22,6 @@ import java.util.Map; import com.cloud.exception.StorageUnavailableException; -import org.apache.cloudstack.api.command.user.vm.CloneVMCmd; import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd; import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CloneVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CloneVMCmd.java index 3ea51897cfb4..af45c6ac810d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CloneVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CloneVMCmd.java @@ -12,7 +12,15 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.acl.SecurityChecker.AccessType; -import org.apache.cloudstack.api.*; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandJobType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.UserVmResponse; From 56f4d1ca8f6f19cd435deb3af04042e5f0a7c540 Mon Sep 17 00:00:00 2001 From: junxuan Date: Thu, 12 Aug 2021 23:59:01 -0400 Subject: [PATCH 16/17] server style fix: --- .../src/main/java/com/cloud/storage/VolumeApiServiceImpl.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index f692a9bf03d9..ed978fc57649 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -32,8 +32,6 @@ import java.util.concurrent.ExecutionException; import javax.inject.Inject; - -import org.apache.cloudstack.api.command.user.vm.CloneVMCmd; import com.cloud.api.query.dao.ServiceOfferingJoinDao; import com.cloud.api.query.vo.ServiceOfferingJoinVO; import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; From 4efd6b639f34eeffb67256739e58cd92c712c35f Mon Sep 17 00:00:00 2001 From: junxuan Date: Fri, 13 Aug 2021 00:06:19 -0400 Subject: [PATCH 17/17] fix custom id option --- server/src/main/java/com/cloud/vm/UserVmManagerImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 7382c894a95e..1d74bd3dee50 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -5783,18 +5783,18 @@ public UserVm recordVirtualMachineToDB(CloneVMCmd cmd) throws ConcurrentOperatio if (dataCenter.getNetworkType() == NetworkType.Basic) { vmResult = createBasicSecurityGroupVirtualMachine(dataCenter, serviceOffering, template, securityGroupIdList, curAccount, hostName, displayName, diskOfferingId, size, group, hypervisorType, cmd.getHttpMethod(), userData, sshKeyPair, ipToNetoworkMap, addr, isDisplayVM, keyboard, affinityGroupIdList, - curVm.getDetails() == null ? new HashMap<>() : curVm.getDetails(), cmd.getCustomId(), new HashMap<>(), + curVm.getDetails() == null ? new HashMap<>() : curVm.getDetails(), null, new HashMap<>(), null, new HashMap<>(), dynamicScalingEnabled); } else { if (dataCenter.isSecurityGroupEnabled()) { vmResult = createAdvancedSecurityGroupVirtualMachine(dataCenter, serviceOffering, template, networkIds, securityGroupIdList, curAccount, hostName, displayName, diskOfferingId, size, group, hypervisorType, cmd.getHttpMethod(), userData, sshKeyPair, ipToNetoworkMap, addr, isDisplayVM, keyboard, - affinityGroupIdList, curVm.getDetails() == null ? new HashMap<>() : curVm.getDetails(), cmd.getCustomId(), new HashMap<>(), + affinityGroupIdList, curVm.getDetails() == null ? new HashMap<>() : curVm.getDetails(), null, new HashMap<>(), null, new HashMap<>(), dynamicScalingEnabled); } else { vmResult = createAdvancedVirtualMachine(dataCenter, serviceOffering, template, networkIds, curAccount, hostName, displayName, diskOfferingId, size, group, hypervisorType, cmd.getHttpMethod(), userData, sshKeyPair, ipToNetoworkMap, addr, isDisplayVM, keyboard, affinityGroupIdList, curVm.getDetails() == null ? new HashMap<>() : curVm.getDetails(), - cmd.getCustomId(), new HashMap<>(), null, new HashMap<>(), dynamicScalingEnabled); + null, new HashMap<>(), null, new HashMap<>(), dynamicScalingEnabled); } } } catch (CloudRuntimeException e) {