Skip to content

Commit 3d81ec4

Browse files
author
Gabriel Beims Bräscher
committed
Allow KVM VM live migration with ROOT volume on file
- Add JUnit tests
1 parent c6fbf56 commit 3d81ec4

File tree

4 files changed

+417
-17
lines changed

4 files changed

+417
-17
lines changed

engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
3131
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
3232
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
33-
import org.apache.commons.lang.StringUtils;
3433
import org.springframework.stereotype.Component;
3534

3635
import com.cloud.agent.api.Answer;
@@ -108,11 +107,12 @@ protected String generateDestPath(VirtualMachineTO vmTO, VolumeVO srcVolume, Hos
108107
throw new CloudRuntimeException(String.format("Unable to modify target volume on the host [host id:%s, name:%s]", destHost.getId(), destHost.getName()));
109108
}
110109

111-
String libvirtDestImgsPath = StringUtils.EMPTY;
110+
String libvirtDestImgsPath = null;
112111
if (rootImageProvisioningAnswer instanceof CreateAnswer) {
113-
libvirtDestImgsPath = ((CreateAnswer)rootImageProvisioningAnswer).getVolume().getName() + File.separator;
112+
libvirtDestImgsPath = ((CreateAnswer)rootImageProvisioningAnswer).getVolume().getName();
114113
}
115-
return libvirtDestImgsPath + destVolumeInfo.getUuid();
114+
// File.getAbsolutePath is used to keep the file separator as it should be and eliminate a verification to check if exists a file separator in the last character of libvirtDestImgsPath.
115+
return new File(libvirtDestImgsPath, destVolumeInfo.getUuid()).getAbsolutePath();
116116
}
117117

118118
/**
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.cloudstack.storage.motion;
20+
21+
import static org.mockito.MockitoAnnotations.initMocks;
22+
23+
import java.util.HashMap;
24+
import java.util.Map;
25+
26+
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
27+
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
28+
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
29+
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
30+
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
31+
import org.apache.cloudstack.storage.datastore.PrimaryDataStoreImpl;
32+
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
33+
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
34+
import org.apache.cloudstack.storage.volume.VolumeObject;
35+
import org.junit.Assert;
36+
import org.junit.Before;
37+
import org.junit.Test;
38+
import org.junit.runner.RunWith;
39+
import org.mockito.InjectMocks;
40+
import org.mockito.Mock;
41+
import org.mockito.Mockito;
42+
import org.mockito.Spy;
43+
import org.mockito.runners.MockitoJUnitRunner;
44+
45+
import com.cloud.agent.AgentManager;
46+
import com.cloud.agent.api.MigrateCommand;
47+
import com.cloud.agent.api.storage.CreateAnswer;
48+
import com.cloud.agent.api.storage.CreateCommand;
49+
import com.cloud.agent.api.to.VirtualMachineTO;
50+
import com.cloud.agent.api.to.VolumeTO;
51+
import com.cloud.host.HostVO;
52+
import com.cloud.hypervisor.Hypervisor.HypervisorType;
53+
import com.cloud.storage.DataStoreRole;
54+
import com.cloud.storage.DiskOfferingVO;
55+
import com.cloud.storage.Storage;
56+
import com.cloud.storage.Storage.StoragePoolType;
57+
import com.cloud.storage.Volume;
58+
import com.cloud.storage.VolumeVO;
59+
import com.cloud.storage.dao.DiskOfferingDao;
60+
import com.cloud.vm.DiskProfile;
61+
62+
@RunWith(MockitoJUnitRunner.class)
63+
public class KvmNonManagedStorageSystemDataMotionTest {
64+
65+
@Mock
66+
private PrimaryDataStoreDao _storagePoolDao;
67+
68+
@Mock
69+
private TemplateDataFactory tmplFactory;
70+
71+
@Mock
72+
private AgentManager _agentMgr;
73+
74+
@Mock
75+
private DiskOfferingDao _diskOfferingDao;
76+
77+
@Spy
78+
@InjectMocks
79+
private KvmNonManagedStorageDataMotionStrategy strategy;
80+
81+
@Before
82+
public void setUp() throws Exception {
83+
initMocks(strategy);
84+
}
85+
86+
@Test
87+
public void canHandleTestExpectHypervisorStrategyForKvm() {
88+
canHandleExpectCantHandle(HypervisorType.KVM, 1, StrategyPriority.HYPERVISOR);
89+
}
90+
91+
@Test
92+
public void canHandleTestExpectCantHandle() {
93+
HypervisorType[] hypervisorTypeArray = HypervisorType.values();
94+
for (int i = 0; i < hypervisorTypeArray.length; i++) {
95+
HypervisorType ht = hypervisorTypeArray[i];
96+
if (ht.equals(HypervisorType.KVM)) {
97+
continue;
98+
}
99+
canHandleExpectCantHandle(ht, 0, StrategyPriority.CANT_HANDLE);
100+
}
101+
}
102+
103+
private void canHandleExpectCantHandle(HypervisorType hypervisorType, int times, StrategyPriority expectedStrategyPriority) {
104+
HostVO srcHost = new HostVO("sourceHostUuid");
105+
srcHost.setHypervisorType(hypervisorType);
106+
Mockito.doReturn(StrategyPriority.HYPERVISOR).when(strategy).internalCanHandle(new HashMap<>());
107+
108+
StrategyPriority strategyPriority = strategy.canHandle(new HashMap<>(), srcHost, new HostVO("destHostUuid"));
109+
110+
Mockito.verify(strategy, Mockito.times(times)).internalCanHandle(new HashMap<>());
111+
Assert.assertEquals(expectedStrategyPriority, strategyPriority);
112+
}
113+
114+
@Test
115+
public void internalCanHandleTestNonManaged() {
116+
StoragePoolType[] storagePoolTypeArray = StoragePoolType.values();
117+
for (int i = 0; i < storagePoolTypeArray.length; i++) {
118+
Map<VolumeInfo, DataStore> volumeMap = configureTestInternalCanHandle(false, storagePoolTypeArray[i]);
119+
StrategyPriority strategyPriority = strategy.internalCanHandle(volumeMap);
120+
if (storagePoolTypeArray[i] == StoragePoolType.Filesystem) {
121+
Assert.assertEquals(StrategyPriority.HYPERVISOR, strategyPriority);
122+
} else {
123+
Assert.assertEquals(StrategyPriority.CANT_HANDLE, strategyPriority);
124+
}
125+
}
126+
}
127+
128+
@Test
129+
public void internalCanHandleTestIsManaged() {
130+
StoragePoolType[] storagePoolTypeArray = StoragePoolType.values();
131+
for (int i = 0; i < storagePoolTypeArray.length; i++) {
132+
Map<VolumeInfo, DataStore> volumeMap = configureTestInternalCanHandle(true, storagePoolTypeArray[i]);
133+
StrategyPriority strategyPriority = strategy.internalCanHandle(volumeMap);
134+
Assert.assertEquals(StrategyPriority.CANT_HANDLE, strategyPriority);
135+
}
136+
}
137+
138+
private Map<VolumeInfo, DataStore> configureTestInternalCanHandle(boolean isManagedStorage, StoragePoolType storagePoolType) {
139+
VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
140+
Mockito.doReturn(0l).when(volumeInfo).getPoolId();
141+
DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
142+
Mockito.doReturn(0l).when(ds).getId();
143+
144+
Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
145+
volumeMap.put(volumeInfo, ds);
146+
147+
StoragePoolVO storagePool = Mockito.spy(new StoragePoolVO());
148+
Mockito.doReturn(storagePoolType).when(storagePool).getPoolType();
149+
150+
Mockito.doReturn(storagePool).when(_storagePoolDao).findById(0l);
151+
Mockito.doReturn(isManagedStorage).when(storagePool).isManaged();
152+
return volumeMap;
153+
}
154+
155+
@Test
156+
public void getTemplateUuidTestTemplateIdNotNull() {
157+
String expectedTemplateUuid = prepareTestGetTemplateUuid();
158+
String templateUuid = strategy.getTemplateUuid(0l);
159+
Assert.assertEquals(expectedTemplateUuid, templateUuid);
160+
}
161+
162+
@Test
163+
public void getTemplateUuidTestTemplateIdNull() {
164+
prepareTestGetTemplateUuid();
165+
String templateUuid = strategy.getTemplateUuid(null);
166+
Assert.assertEquals(null, templateUuid);
167+
}
168+
169+
private String prepareTestGetTemplateUuid() {
170+
TemplateInfo templateImage = Mockito.mock(TemplateInfo.class);
171+
String expectedTemplateUuid = "template uuid";
172+
Mockito.when(templateImage.getUuid()).thenReturn(expectedTemplateUuid);
173+
Mockito.doReturn(templateImage).when(tmplFactory).getTemplate(0l, DataStoreRole.Image);
174+
return expectedTemplateUuid;
175+
}
176+
177+
@Test
178+
public void configureMigrateDiskInfoTest() {
179+
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
180+
Mockito.doReturn("volume path").when(srcVolumeInfo).getPath();
181+
MigrateCommand.MigrateDiskInfo migrateDiskInfo = strategy.configureMigrateDiskInfo(srcVolumeInfo, "destPath");
182+
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.FILE, migrateDiskInfo.getDiskType());
183+
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.QCOW2, migrateDiskInfo.getDriverType());
184+
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.FILE, migrateDiskInfo.getSource());
185+
Assert.assertEquals("destPath", migrateDiskInfo.getSourceText());
186+
Assert.assertEquals("volume path", migrateDiskInfo.getSerialNumber());
187+
}
188+
189+
@Test
190+
public void generateDestPathTest() {
191+
String uuid = "f3d49ecc-870c-475a-89fa-fd0124420a9b";
192+
String destPath = "/var/lib/libvirt/images/";
193+
194+
VirtualMachineTO vmTO = Mockito.mock(VirtualMachineTO.class);
195+
Mockito.when(vmTO.getName()).thenReturn("vmName");
196+
197+
VolumeVO srcVolume = Mockito.spy(new VolumeVO("name", 0l, 0l, 0l, 0l, 0l, "folder", "path", Storage.ProvisioningType.THIN, 0l, Volume.Type.ROOT));
198+
StoragePoolVO destStoragePool = Mockito.spy(new StoragePoolVO());
199+
200+
VolumeInfo destVolumeInfo = Mockito.spy(new VolumeObject());
201+
Mockito.doReturn(0l).when(destVolumeInfo).getTemplateId();
202+
Mockito.doReturn(0l).when(destVolumeInfo).getId();
203+
Mockito.doReturn(Volume.Type.ROOT).when(destVolumeInfo).getVolumeType();
204+
Mockito.doReturn("name").when(destVolumeInfo).getName();
205+
Mockito.doReturn(0l).when(destVolumeInfo).getSize();
206+
Mockito.doReturn(uuid).when(destVolumeInfo).getUuid();
207+
208+
DiskOfferingVO diskOffering = Mockito.spy(new DiskOfferingVO());
209+
Mockito.doReturn(0l).when(diskOffering).getId();
210+
Mockito.doReturn(diskOffering).when(_diskOfferingDao).findById(0l);
211+
DiskProfile diskProfile = Mockito.spy(new DiskProfile(destVolumeInfo, diskOffering, HypervisorType.KVM));
212+
213+
String templateUuid = Mockito.doReturn("templateUuid").when(strategy).getTemplateUuid(0l);
214+
CreateCommand rootImageProvisioningCommand = new CreateCommand(diskProfile, templateUuid, destStoragePool, true);
215+
CreateAnswer createAnswer = Mockito.spy(new CreateAnswer(rootImageProvisioningCommand, "details"));
216+
Mockito.doReturn(true).when(createAnswer).getResult();
217+
218+
VolumeTO volumeTo = Mockito.mock(VolumeTO.class);
219+
Mockito.doReturn(destPath).when(volumeTo).getName();
220+
Mockito.doReturn(volumeTo).when(createAnswer).getVolume();
221+
222+
Mockito.doReturn(createAnswer).when(_agentMgr).easySend(0l, rootImageProvisioningCommand);
223+
224+
String generatedDestPath = strategy.generateDestPath(vmTO, srcVolume, new HostVO("sourceHostUuid"), destStoragePool, destVolumeInfo);
225+
226+
Assert.assertEquals(destPath + uuid, generatedDestPath);
227+
}
228+
}

0 commit comments

Comments
 (0)