Skip to content

Commit 08132ac

Browse files
authored
Fix restore VM with allocated root disk (#8977)
* Fix restore VM with allocated root disk * Add e2e test for restore vm * Add more checks for e2e test
1 parent 80a8b80 commit 08132ac

File tree

4 files changed

+124
-7
lines changed

4 files changed

+124
-7
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ jobs:
5454
smoke/test_deploy_vm_with_userdata
5555
smoke/test_deploy_vms_in_parallel
5656
smoke/test_deploy_vms_with_varied_deploymentplanners
57+
smoke/test_restore_vm
5758
smoke/test_diagnostics
5859
smoke/test_direct_download
5960
smoke/test_disk_offerings

server/src/main/java/com/cloud/vm/UserVmManagerImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7846,7 +7846,7 @@ public UserVm restoreVirtualMachine(final Account caller, final long vmId, final
78467846
DiskOffering diskOffering = rootDiskOfferingId != null ? _diskOfferingDao.findById(rootDiskOfferingId) : null;
78477847
for (VolumeVO root : rootVols) {
78487848
if ( !Volume.State.Allocated.equals(root.getState()) || newTemplateId != null ) {
7849-
_volumeService.validateDestroyVolume(root, caller, expunge, false);
7849+
_volumeService.validateDestroyVolume(root, caller, Volume.State.Allocated.equals(root.getState()) || expunge, false);
78507850
final UserVmVO userVm = vm;
78517851
Pair<UserVmVO, Volume> vmAndNewVol = Transaction.execute(new TransactionCallbackWithException<Pair<UserVmVO, Volume>, CloudRuntimeException>() {
78527852
@Override
@@ -7909,7 +7909,7 @@ public Pair<UserVmVO, Volume> doInTransaction(final TransactionStatus status) th
79097909

79107910
// Detach, destroy and create the usage event for the old root volume.
79117911
_volsDao.detachVolume(root.getId());
7912-
_volumeService.destroyVolume(root.getId(), caller, expunge, false);
7912+
_volumeService.destroyVolume(root.getId(), caller, Volume.State.Allocated.equals(root.getState()) || expunge, false);
79137913

79147914
// For VMware hypervisor since the old root volume is replaced by the new root volume, force expunge old root volume if it has been created in storage
79157915
if (vm.getHypervisorType() == HypervisorType.VMware) {
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
""" P1 tests for Scaling up Vm
18+
"""
19+
# Import Local Modules
20+
from marvin.cloudstackTestCase import cloudstackTestCase
21+
from marvin.lib.base import (VirtualMachine, Volume, ServiceOffering, Template)
22+
from marvin.lib.common import (get_zone, get_domain)
23+
from nose.plugins.attrib import attr
24+
25+
_multiprocess_shared_ = True
26+
27+
28+
class TestRestoreVM(cloudstackTestCase):
29+
30+
@classmethod
31+
def setUpClass(cls):
32+
testClient = super(TestRestoreVM, cls).getClsTestClient()
33+
cls.apiclient = testClient.getApiClient()
34+
cls.services = testClient.getParsedTestDataConfig()
35+
36+
# Get Zone, Domain and templates
37+
cls.domain = get_domain(cls.apiclient)
38+
cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
39+
cls.hypervisor = testClient.getHypervisorInfo()
40+
cls.services['mode'] = cls.zone.networktype
41+
42+
cls.services["virtual_machine"]["zoneid"] = cls.zone.id
43+
44+
cls.service_offering = ServiceOffering.create(cls.apiclient, cls.services["service_offering"])
45+
46+
cls.template_t1 = Template.register(cls.apiclient, cls.services["test_templates"][
47+
cls.hypervisor.lower() if cls.hypervisor.lower() != 'simulator' else 'xenserver'],
48+
zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower())
49+
50+
cls.template_t2 = Template.register(cls.apiclient, cls.services["test_templates"][
51+
cls.hypervisor.lower() if cls.hypervisor.lower() != 'simulator' else 'xenserver'],
52+
zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower())
53+
54+
cls._cleanup = [cls.service_offering, cls.template_t1, cls.template_t2]
55+
56+
@classmethod
57+
def tearDownClass(cls):
58+
super(TestRestoreVM, cls).tearDownClass()
59+
return
60+
61+
@attr(tags=["advanced", "basic"], required_hardware="false")
62+
def test_01_restore_vm(self):
63+
"""Test restore virtual machine
64+
"""
65+
# create a virtual machine
66+
virtual_machine = VirtualMachine.create(self.apiclient, self.services["virtual_machine"], zoneid=self.zone.id,
67+
templateid=self.template_t1.id,
68+
serviceofferingid=self.service_offering.id)
69+
self._cleanup.append(virtual_machine)
70+
71+
root_vol = Volume.list(self.apiclient, virtualmachineid=virtual_machine.id)[0]
72+
self.assertEqual(root_vol.state, 'Ready', "Volume should be in Ready state")
73+
self.assertEqual(root_vol.size, self.template_t1.size, "Size of volume and template should match")
74+
75+
virtual_machine.restore(self.apiclient, self.template_t2.id)
76+
restored_vm = VirtualMachine.list(self.apiclient, id=virtual_machine.id)[0]
77+
self.assertEqual(restored_vm.state, 'Running', "VM should be in a running state")
78+
self.assertEqual(restored_vm.templateid, self.template_t2.id, "VM's template after restore is incorrect")
79+
root_vol = Volume.list(self.apiclient, virtualmachineid=restored_vm.id)[0]
80+
self.assertEqual(root_vol.state, 'Ready', "Volume should be in Ready state")
81+
self.assertEqual(root_vol.size, self.template_t2.size, "Size of volume and template should match")
82+
83+
@attr(tags=["advanced", "basic"], required_hardware="false")
84+
def test_02_restore_vm_allocated_root(self):
85+
"""Test restore virtual machine with root disk in allocated state
86+
"""
87+
# create a virtual machine with allocated root disk by setting startvm=False
88+
virtual_machine = VirtualMachine.create(self.apiclient, self.services["virtual_machine"], zoneid=self.zone.id,
89+
templateid=self.template_t1.id,
90+
serviceofferingid=self.service_offering.id,
91+
startvm=False)
92+
self._cleanup.append(virtual_machine)
93+
root_vol = Volume.list(self.apiclient, virtualmachineid=virtual_machine.id)[0]
94+
self.assertEqual(root_vol.state, 'Allocated', "Volume should be in Allocated state")
95+
self.assertEqual(root_vol.size, self.template_t1.size, "Size of volume and template should match")
96+
97+
virtual_machine.restore(self.apiclient, self.template_t2.id)
98+
restored_vm = VirtualMachine.list(self.apiclient, id=virtual_machine.id)[0]
99+
self.assertEqual(restored_vm.state, 'Stopped', "Check the state of VM")
100+
self.assertEqual(restored_vm.templateid, self.template_t2.id, "Check the template of VM")
101+
102+
root_vol = Volume.list(self.apiclient, virtualmachineid=restored_vm.id)[0]
103+
self.assertEqual(root_vol.state, 'Allocated', "Volume should be in Allocated state")
104+
self.assertEqual(root_vol.size, self.template_t2.size, "Size of volume and template should match")
105+
106+
virtual_machine.start(self.apiclient)
107+
root_vol = Volume.list(self.apiclient, virtualmachineid=restored_vm.id)[0]
108+
self.assertEqual(root_vol.state, 'Ready', "Volume should be in Ready state")

ui/src/views/compute/ReinstallVm.vue

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
:items="templates"
3737
:selected="tabKey"
3838
:loading="loading.templates"
39-
:preFillContent="resource.templateid"
39+
:preFillContent="dataPrefill"
4040
:key="templateKey"
4141
@handle-search-filter="($event) => fetchAllTemplates($event)"
4242
@update-template-iso="updateFieldValue"
@@ -61,7 +61,7 @@
6161
:zoneId="resource.zoneId"
6262
:value="diskOffering ? diskOffering.id : ''"
6363
:loading="loading.diskOfferings"
64-
:preFillContent="resource.diskofferingid"
64+
:preFillContent="dataPrefill"
6565
:isIsoSelected="false"
6666
:isRootDiskOffering="true"
6767
@on-selected-disk-size="onSelectDiskSize"
@@ -170,7 +170,11 @@ export default {
170170
],
171171
diskOffering: {},
172172
diskOfferingCount: 0,
173-
templateKey: 0
173+
templateKey: 0,
174+
dataPrefill: {
175+
templateid: this.resource.templateid,
176+
diskofferingid: this.resource.diskofferingid
177+
}
174178
}
175179
},
176180
beforeCreate () {
@@ -192,8 +196,10 @@ export default {
192196
},
193197
handleSubmit () {
194198
const params = {
195-
virtualmachineid: this.resource.id,
196-
templateid: this.templateid
199+
virtualmachineid: this.resource.id
200+
}
201+
if (this.templateid) {
202+
params.templateid = this.templateid
197203
}
198204
if (this.overrideDiskOffering) {
199205
params.diskofferingid = this.diskOffering.id
@@ -285,9 +291,11 @@ export default {
285291
},
286292
onSelectDiskSize (rowSelected) {
287293
this.diskOffering = rowSelected
294+
this.dataPrefill.diskofferingid = rowSelected.id
288295
},
289296
updateFieldValue (input, value) {
290297
this[input] = value
298+
this.dataPrefill[input] = value
291299
}
292300
}
293301
}

0 commit comments

Comments
 (0)