Skip to content

Commit 7087d4d

Browse files
committed
Fix KVM incremental snapshot migration between secondary storages
1 parent 5caf6cd commit 7087d4d

12 files changed

Lines changed: 534 additions & 33 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
20+
package com.cloud.agent.api;
21+
22+
import com.cloud.utils.Pair;
23+
24+
import java.util.List;
25+
26+
public class MigrateBetweenSecondaryStoragesCommandAnswer extends Answer {
27+
28+
List<Pair<Long, String>> migratedResourcesIdAndCheckpointPath;
29+
30+
public MigrateBetweenSecondaryStoragesCommandAnswer() {
31+
}
32+
33+
public MigrateBetweenSecondaryStoragesCommandAnswer(MigrateSnapshotsBetweenSecondaryStoragesCommand cmd, boolean success, String result, List<Pair<Long, String>> migratedResourcesIdAndCheckpointPath) {
34+
super(cmd, success, result);
35+
this.migratedResourcesIdAndCheckpointPath = migratedResourcesIdAndCheckpointPath;
36+
}
37+
38+
public List<Pair<Long, String>> getMigratedResources() {
39+
return migratedResourcesIdAndCheckpointPath;
40+
}
41+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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+
//
18+
19+
package com.cloud.agent.api;
20+
21+
import com.cloud.agent.api.to.DataStoreTO;
22+
import com.cloud.agent.api.to.DataTO;
23+
24+
import java.util.List;
25+
import java.util.Set;
26+
27+
public class MigrateSnapshotsBetweenSecondaryStoragesCommand extends Command {
28+
29+
DataStoreTO srcDataStore;
30+
DataStoreTO destDataStore;
31+
List<DataTO> snapshotChain;
32+
Set<Long> snapshotsIdToMigrate;
33+
34+
public MigrateSnapshotsBetweenSecondaryStoragesCommand() {
35+
}
36+
37+
public MigrateSnapshotsBetweenSecondaryStoragesCommand(List<DataTO> snapshotChain, DataStoreTO srcDataStore, DataStoreTO destDataStore, Set<Long> snapshotsIdToMigrate) {
38+
this.srcDataStore = srcDataStore;
39+
this.destDataStore = destDataStore;
40+
this.snapshotChain = snapshotChain;
41+
this.snapshotsIdToMigrate = snapshotsIdToMigrate;
42+
}
43+
44+
@Override
45+
public boolean executeInSequence() {
46+
return false;
47+
}
48+
49+
public List<DataTO> getSnapshotChain() {
50+
return snapshotChain;
51+
}
52+
53+
public Set<Long> getSnapshotsIdToMigrate() {
54+
return snapshotsIdToMigrate;
55+
}
56+
57+
public DataStoreTO getSrcDataStore() {
58+
return srcDataStore;
59+
}
60+
61+
public DataStoreTO getDestDataStore() {
62+
return destDataStore;
63+
}
64+
}

engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotInfo.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public interface SnapshotInfo extends DataObject, Snapshot {
3030

3131
SnapshotInfo getParent();
3232

33+
List<SnapshotInfo> getParents();
34+
3335
String getPath();
3436

3537
DataStore getImageStore();
@@ -40,6 +42,8 @@ public interface SnapshotInfo extends DataObject, Snapshot {
4042

4143
List<SnapshotInfo> getChildren();
4244

45+
List<SnapshotInfo> getChildAndGrandchildren();
46+
4347
VolumeInfo getBaseVolume();
4448

4549
void addPayload(Object data);

engine/components-api/src/main/java/com/cloud/storage/StorageManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ public interface StorageManager extends StorageService {
233233
"while adding a new Secondary Storage. If the copy operation fails, the system falls back to downloading the template from the source URL.",
234234
true, ConfigKey.Scope.Zone, null);
235235

236+
ConfigKey<Integer> AgentMaxDataMigrationWaitTime = new ConfigKey<>("Advanced", Integer.class, "agent.max.data.migration.wait.time", "3600",
237+
"The maximum time (in seconds) that the secondary storage data migration command sent to the KVM Agent will be executed before a timeout occurs.", true, ConfigKey.Scope.Cluster);
238+
236239
/**
237240
* should we execute in sequence not involving any storages?
238241
* @return tru if commands should execute in sequence

engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/DataMigrationUtility.java

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.List;
2929
import java.util.Map;
3030
import java.util.Set;
31+
import java.util.stream.Collectors;
3132

3233
import javax.inject.Inject;
3334

@@ -94,7 +95,7 @@ public class DataMigrationUtility {
9495
* "Ready" "Allocated", "Destroying", "Destroyed", "Failed". If this is the case, and if the migration policy is complete,
9596
* the migration is terminated.
9697
*/
97-
public boolean filesReadyToMigrate(Long srcDataStoreId, List<TemplateDataStoreVO> templates, List<SnapshotDataStoreVO> snapshots, List<VolumeDataStoreVO> volumes) {
98+
public boolean filesReadyToMigrate(List<TemplateDataStoreVO> templates, List<SnapshotDataStoreVO> snapshots, List<VolumeDataStoreVO> volumes) {
9899
State[] validStates = {State.Ready, State.Allocated, State.Destroying, State.Destroyed, State.Failed};
99100
boolean isReady = true;
100101
for (TemplateDataStoreVO template : templates) {
@@ -116,7 +117,8 @@ private boolean filesReadyToMigrate(Long srcDataStoreId) {
116117
List<TemplateDataStoreVO> templates = templateDataStoreDao.listByStoreId(srcDataStoreId);
117118
List<SnapshotDataStoreVO> snapshots = snapshotDataStoreDao.listByStoreId(srcDataStoreId, DataStoreRole.Image);
118119
List<VolumeDataStoreVO> volumes = volumeDataStoreDao.listByStoreId(srcDataStoreId);
119-
return filesReadyToMigrate(srcDataStoreId, templates, snapshots, volumes);
120+
121+
return filesReadyToMigrate(templates, snapshots, volumes);
120122
}
121123

122124
protected void checkIfCompleteMigrationPossible(ImageStoreService.MigrationPolicy policy, Long srcDataStoreId) {
@@ -165,29 +167,40 @@ public int compare(Map.Entry<Long, Pair<Long, Long>> e1, Map.Entry<Long, Pair<Lo
165167
}
166168

167169
protected List<DataObject> getSortedValidSourcesList(DataStore srcDataStore, Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains,
168-
Map<DataObject, Pair<List<TemplateInfo>, Long>> childTemplates, List<TemplateDataStoreVO> templates, List<SnapshotDataStoreVO> snapshots) {
170+
Map<DataObject, Pair<List<TemplateInfo>, Long>> childTemplates, List<TemplateDataStoreVO> templates, List<SnapshotDataStoreVO> snapshots, Set<Long> snapshotIdsToMigrate) {
169171
List<DataObject> files = new ArrayList<>();
170172

171173
files.addAll(getAllReadyTemplates(srcDataStore, childTemplates, templates));
172-
files.addAll(getAllReadySnapshotsAndChains(srcDataStore, snapshotChains, snapshots));
174+
files.addAll(getAllReadySnapshotsAndChains(srcDataStore, snapshotChains, snapshots, snapshotIdsToMigrate));
173175

174176
files = sortFilesOnSize(files, snapshotChains);
175177

176178
return files;
177179
}
178180

179181
protected List<DataObject> getSortedValidSourcesList(DataStore srcDataStore, Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains,
180-
Map<DataObject, Pair<List<TemplateInfo>, Long>> childTemplates) {
182+
Map<DataObject, Pair<List<TemplateInfo>, Long>> childTemplates, Set<Long> snapshotIdsToMigrate) {
181183
List<DataObject> files = new ArrayList<>();
182184
files.addAll(getAllReadyTemplates(srcDataStore, childTemplates));
183-
files.addAll(getAllReadySnapshotsAndChains(srcDataStore, snapshotChains));
185+
files.addAll(getAllReadySnapshotsAndChains(srcDataStore, snapshotChains, snapshotIdsToMigrate));
184186
files.addAll(getAllReadyVolumes(srcDataStore));
185187

186188
files = sortFilesOnSize(files, snapshotChains);
187189

188190
return files;
189191
}
190192

193+
private List<SnapshotInfo> createKvmIncrementalSnapshotChain(SnapshotDataStoreVO snapshot) {
194+
List<SnapshotInfo> chain = new LinkedList<>();
195+
SnapshotInfo snapshotInfo = snapshotFactory.getSnapshot(snapshot.getSnapshotId(), snapshot.getDataStoreId(), snapshot.getRole());
196+
197+
chain.addAll(snapshotInfo.getParents());
198+
chain.add(snapshotInfo);
199+
chain.addAll(snapshotInfo.getChildAndGrandchildren());
200+
201+
return chain;
202+
}
203+
191204
protected List<DataObject> sortFilesOnSize(List<DataObject> files, Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains) {
192205
Collections.sort(files, new Comparator<DataObject>() {
193206
@Override
@@ -274,9 +287,10 @@ protected boolean shouldMigrateTemplate(TemplateDataStoreVO template, VMTemplate
274287
* for each parent snapshot and the cumulative size of the chain - this is done to ensure that all the snapshots in a chain
275288
* are migrated to the same datastore
276289
*/
277-
protected List<DataObject> getAllReadySnapshotsAndChains(DataStore srcDataStore, Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains, List<SnapshotDataStoreVO> snapshots) {
290+
protected List<DataObject> getAllReadySnapshotsAndChains(DataStore srcDataStore, Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains, List<SnapshotDataStoreVO> snapshots, Set<Long> snapshotIdsToMigrate) {
278291
List<SnapshotInfo> files = new LinkedList<>();
279292
Set<Long> idsForMigration = new HashSet<>();
293+
Set<Long> snapshotIdsAlreadyInChain = new HashSet<>();
280294

281295
for (SnapshotDataStoreVO snapshot : snapshots) {
282296
long snapshotId = snapshot.getSnapshotId();
@@ -300,13 +314,22 @@ protected List<DataObject> getAllReadySnapshotsAndChains(DataStore srcDataStore,
300314
if (snapshot.getParentSnapshotId() != 0) {
301315
continue; // The child snapshot will be migrated in the for loop below.
302316
}
303-
SnapshotInfo snap = snapshotFactory.getSnapshot(snapshotVO.getSnapshotId(), snapshot.getDataStoreId(), snapshot.getRole());
304-
if (snap == null) {
305-
logger.debug("Not migrating snapshot [{}] because we could not get its information.", snapshot);
306-
continue;
317+
318+
if (snapshotVO.getHypervisorType() == Hypervisor.HypervisorType.KVM && snapshot.getKvmCheckpointPath() != null && !snapshotIdsAlreadyInChain.contains(snapshotVO.getId())) {
319+
List<SnapshotInfo> kvmIncrementalSnapshotChain = createKvmIncrementalSnapshotChain(snapshot);
320+
SnapshotInfo parent = kvmIncrementalSnapshotChain.get(0);
321+
snapshotIdsAlreadyInChain.addAll(kvmIncrementalSnapshotChain.stream().map(DataObject::getId).collect(Collectors.toSet()));
322+
snapshotChains.put(parent, new Pair<>(kvmIncrementalSnapshotChain, getTotalChainSize(kvmIncrementalSnapshotChain.stream().filter(snapInfo -> snapshotIdsToMigrate.contains(snapInfo.getId())).collect(Collectors.toList()))));
323+
files.add(parent);
324+
} else {
325+
SnapshotInfo snap = snapshotFactory.getSnapshot(snapshotVO.getSnapshotId(), snapshot.getDataStoreId(), snapshot.getRole());
326+
if (snap == null) {
327+
logger.debug("Not migrating snapshot [{}] because we could not get its information.", snapshot);
328+
continue;
329+
}
330+
files.add(snap);
331+
idsForMigration.add(snapshotId);
307332
}
308-
files.add(snap);
309-
idsForMigration.add(snapshotId);
310333
}
311334

312335
for (SnapshotInfo parent : files) {
@@ -319,15 +342,16 @@ protected List<DataObject> getAllReadySnapshotsAndChains(DataStore srcDataStore,
319342
chain.addAll(children);
320343
}
321344
}
322-
snapshotChains.put(parent, new Pair<>(chain, getTotalChainSize(chain)));
345+
snapshotChains.putIfAbsent(parent, new Pair<>(chain, getTotalChainSize(chain)));
323346
}
324347

325348
return (List<DataObject>) (List<?>) files;
326349
}
327350

328-
protected List<DataObject> getAllReadySnapshotsAndChains(DataStore srcDataStore, Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains) {
351+
protected List<DataObject> getAllReadySnapshotsAndChains(DataStore srcDataStore, Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains, Set<Long> snapshotIdsToMigrate) {
329352
List<SnapshotDataStoreVO> snapshots = snapshotDataStoreDao.listByStoreId(srcDataStore.getId(), DataStoreRole.Image);
330-
return getAllReadySnapshotsAndChains(srcDataStore, snapshotChains, snapshots);
353+
snapshotIdsToMigrate.addAll(snapshots.stream().map(SnapshotDataStoreVO::getSnapshotId).collect(Collectors.toSet()));
354+
return getAllReadySnapshotsAndChains(srcDataStore, snapshotChains, snapshots, snapshotIdsToMigrate);
331355
}
332356

333357
protected Long getTotalChainSize(List<? extends DataObject> chain) {

0 commit comments

Comments
 (0)