Skip to content

Commit f2001fe

Browse files
author
haidong.pang
committed
<fix>[storage]: reclaim referenced reimage chains
Keep an old root out of reimage trash while downstream snapshot references still depend on it. Distinguish the reimage old root by rootInstallUrl during reference cleanup. After the last downstream reference disappears, delete the chain once from leaf to root, sync capacity, and remove matching legacy reimage trash. Resolves: ZSTAC-81222 Change-Id: I500146d6898405f4df0cc18a1d8a4aaa94fee49d
1 parent 79ca089 commit f2001fe

5 files changed

Lines changed: 166 additions & 5 deletions

File tree

header/src/main/java/org/zstack/header/storage/snapshot/reference/VolumeSnapshotReferenceTreeVO_.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class VolumeSnapshotReferenceTreeVO_ extends ResourceVO_ {
1212
public static volatile SingularAttribute<VolumeSnapshotReferenceTreeVO, String> rootVolumeSnapshotUuid;
1313
public static volatile SingularAttribute<VolumeSnapshotReferenceTreeVO, String> rootVolumeUuid;
1414
public static volatile SingularAttribute<VolumeSnapshotReferenceTreeVO, String> rootVolumeSnapshotTreeUuid;
15-
public static volatile SingularAttribute<VolumeSnapshotReferenceTreeVO, String> rootVolumeSnapshotInstallUrl;
15+
public static volatile SingularAttribute<VolumeSnapshotReferenceTreeVO, String> rootInstallUrl;
1616

1717
public static volatile SingularAttribute<VolumeSnapshotReferenceTreeVO, String> primaryStorageUuid;
1818
public static volatile SingularAttribute<VolumeSnapshotReferenceTreeVO, String> hostUuid;

storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorage.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.zstack.resourceconfig.ResourceConfigFacade;
5757
import org.zstack.storage.backup.BackupStorageSystemTags;
5858
import org.zstack.storage.primary.*;
59+
import org.zstack.storage.snapshot.reference.VolumeSnapshotReferenceUtils;
5960
import org.zstack.storage.volume.VolumeSystemTags;
6061
import org.zstack.utils.Utils;
6162
import org.zstack.utils.gson.JSONObjectUtil;
@@ -1704,6 +1705,11 @@ public void fail(ErrorCode errorCode) {
17041705

17051706
protected void handle(DeleteVolumeChainOnPrimaryStorageMsg msg) {
17061707
DeleteVolumeChainOnPrimaryStorageReply reply = new DeleteVolumeChainOnPrimaryStorageReply();
1708+
if (CollectionUtils.isEmpty(msg.getInstallPaths())) {
1709+
bus.reply(msg, reply);
1710+
return;
1711+
}
1712+
17071713
new While<>(msg.getInstallPaths()).each((installPath, compl) -> {
17081714
// TODO use trash instead, make sure only one snapshot for link clone.
17091715
controller.deleteVolumeAndSnapshot(installPath, new Completion(compl) {
@@ -1722,11 +1728,32 @@ public void fail(ErrorCode errorCode) {
17221728
@Override
17231729
public void done(ErrorCodeList errorCodeList) {
17241730
if (!errorCodeList.getCauses().isEmpty()) {
1725-
reply.setError(errorCodeList.getCauses().get(0));
1731+
logger.warn(String.format("failed to delete volume chain paths %s: %s",
1732+
msg.getInstallPaths(), errorCodeList.getCauses().get(0)));
17261733
bus.reply(msg, reply);
17271734
} else {
1735+
finishDeleteVolumeChain(msg, reply);
1736+
}
1737+
}
1738+
});
1739+
}
1740+
1741+
private void finishDeleteVolumeChain(DeleteVolumeChainOnPrimaryStorageMsg msg,
1742+
DeleteVolumeChainOnPrimaryStorageReply reply) {
1743+
SyncPrimaryStorageCapacityMsg smsg = new SyncPrimaryStorageCapacityMsg();
1744+
smsg.setPrimaryStorageUuid(self.getUuid());
1745+
bus.makeTargetServiceIdByResourceUuid(smsg, PrimaryStorageConstant.SERVICE_ID, self.getUuid());
1746+
bus.send(smsg, new CloudBusCallBack(msg) {
1747+
@Override
1748+
public void run(MessageReply r) {
1749+
if (!r.isSuccess()) {
1750+
logger.warn(String.format("failed to sync primary storage[uuid:%s] capacity after deleting volume chain %s: %s",
1751+
self.getUuid(), msg.getInstallPaths(), r.getError()));
17281752
bus.reply(msg, reply);
1753+
return;
17291754
}
1755+
1756+
bus.reply(msg, reply);
17301757
}
17311758
});
17321759
}
@@ -2099,7 +2126,8 @@ public void fail(ErrorCode errorCode) {
20992126
@Override
21002127
public boolean skip(Map data) {
21012128
return controller.reportCapabilities().getSnapshotCapability()
2102-
.getPlacementType() != VolumeSnapshotCapability.VolumeSnapshotPlacementType.INTERNAL;
2129+
.getPlacementType() != VolumeSnapshotCapability.VolumeSnapshotPlacementType.INTERNAL
2130+
|| VolumeSnapshotReferenceUtils.isVolumeDirectlyReferenceByOthers(msg.getVolume());
21032131
}
21042132

21052133
@Override

storage/src/main/java/org/zstack/storage/snapshot/reference/VolumeSnapshotReferenceTreeBase.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ private void deleteSnapshotRefLeaf(DeleteVolumeSnapshotReferenceLeafMsg msg, Com
113113
boolean rootDeleted = msg.getLeaf().getParentId() == null && !Q.New(VolumeVO.class)
114114
.eq(VolumeVO_.uuid, msg.getLeaf().getVolumeUuid())
115115
.eq(VolumeVO_.primaryStorageUuid, msg.getTree().getPrimaryStorageUuid())
116+
.eq(VolumeVO_.installPath, self.getRootInstallUrl())
116117
.isExists();
117118
String endPath = rootDeleted ? self.getRootInstallUrl() : msg.getLeaf().getVolumeSnapshotInstallUrl();
118119
String startPath = msg.getLeaf().getDirectSnapshotInstallUrl();

storage/src/main/java/org/zstack/storage/snapshot/reference/VolumeSnapshotReferenceUtils.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ protected VolumeSnapshotReferenceTreeVO scripts() {
258258
VolumeSnapshotReferenceTreeVO tree = Q.New(VolumeSnapshotReferenceTreeVO.class)
259259
.eq(VolumeSnapshotReferenceTreeVO_.rootVolumeUuid, baseSnapshot.getVolumeUuid())
260260
.eq(VolumeSnapshotReferenceTreeVO_.primaryStorageUuid, baseSnapshot.getPrimaryStorageUuid())
261+
.eq(VolumeSnapshotReferenceTreeVO_.rootInstallUrl, vol.getInstallPath())
261262
.find();
262263
if (tree != null) {
263264
return tree;
@@ -494,7 +495,17 @@ protected void scripts() {
494495
}
495496

496497
private static void deleteBitsOnPs(VolumeSnapshotReferenceTreeVO treeVO, VolumeSnapshotReferenceVO ref) {
497-
List<VolumeSnapshotReferenceVO> treeRefs = Q.New(VolumeSnapshotReferenceVO.class).eq(VolumeSnapshotReferenceVO_.treeUuid, treeVO.getUuid()).list();
498+
List<VolumeSnapshotReferenceVO> treeRefs;
499+
if (ref.getParentId() == null) {
500+
treeRefs = Q.New(VolumeSnapshotReferenceVO.class)
501+
.in(VolumeSnapshotReferenceVO_.treeUuid, Q.New(VolumeSnapshotReferenceTreeVO.class)
502+
.select(VolumeSnapshotReferenceTreeVO_.uuid)
503+
.eq(VolumeSnapshotReferenceTreeVO_.primaryStorageUuid, treeVO.getPrimaryStorageUuid())
504+
.eq(VolumeSnapshotReferenceTreeVO_.rootInstallUrl, treeVO.getRootInstallUrl()))
505+
.list();
506+
} else {
507+
treeRefs = Q.New(VolumeSnapshotReferenceVO.class).eq(VolumeSnapshotReferenceVO_.treeUuid, treeVO.getUuid()).list();
508+
}
498509

499510
List<VolumeSnapshotReferenceInventory> otherLeafs;
500511
if (ref.getParentId() == null) {
@@ -532,7 +543,8 @@ private static void deleteSnapshotRefLeafInTree(VolumeSnapshotReferenceTreeVO tr
532543
boolean backingVolumeDeletedInDb = SQL.New("select vol.uuid from VolumeVO vol, VolumeSnapshotReferenceTreeVO tree" +
533544
" where vol.uuid = :volUuid" +
534545
" and tree.uuid = :treeUuid" +
535-
" and vol.primaryStorageUuid = tree.primaryStorageUuid", String.class)
546+
" and vol.primaryStorageUuid = tree.primaryStorageUuid" +
547+
" and vol.installPath = tree.rootInstallUrl", String.class)
536548
.param("volUuid", ref.getVolumeUuid())
537549
.param("treeUuid", ref.getTreeUuid())
538550
.find() == null;

test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsPrimaryStorageCase.groovy

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@ import org.zstack.core.cloudbus.EventCallback
66
import org.zstack.core.cloudbus.EventFacade
77
import org.zstack.core.db.DatabaseFacade
88
import org.zstack.core.db.Q
9+
import org.zstack.core.trash.TrashType
910
import org.zstack.header.errorcode.ErrorCode
1011
import org.zstack.header.errorcode.OperationFailureException
12+
import org.zstack.header.core.trash.InstallPathRecycleVO
13+
import org.zstack.header.core.trash.InstallPathRecycleVO_
1114
import org.zstack.header.message.MessageReply
15+
import org.zstack.header.storage.backup.UploadImageToRemoteTargetMsg
16+
import org.zstack.header.storage.backup.UploadImageToRemoteTargetReply
1217
import org.zstack.header.storage.primary.GetVolumeBackingChainFromPrimaryStorageMsg
1318
import org.zstack.header.storage.primary.GetVolumeBackingChainFromPrimaryStorageReply
1419
import org.zstack.header.storage.primary.PrimaryStorageConstant
20+
import org.zstack.header.storage.snapshot.VolumeSnapshotVO
21+
import org.zstack.header.storage.snapshot.VolumeSnapshotVO_
22+
import org.zstack.header.storage.snapshot.reference.VolumeSnapshotReferenceVO
23+
import org.zstack.header.storage.snapshot.reference.VolumeSnapshotReferenceVO_
1524
import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageMsg
1625
import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageReply
1726
import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO
@@ -40,8 +49,10 @@ import org.zstack.storage.zbs.Config
4049
import org.zstack.storage.zbs.ZbsAgentUrl
4150
import org.zstack.storage.zbs.ZbsConstants
4251
import org.zstack.storage.zbs.ZbsGlobalProperty
52+
import org.zstack.storage.zbs.ZbsHelper
4353
import org.zstack.storage.zbs.ZbsPrimaryStorageMdsBase
4454
import org.zstack.storage.zbs.ZbsStorageController
55+
import org.zstack.storage.volume.VolumeSystemTags
4556
import org.zstack.test.integration.storage.StorageTest
4657
import org.zstack.testlib.EnvSpec
4758
import org.zstack.testlib.HttpError
@@ -194,6 +205,7 @@ class ZbsPrimaryStorageCase extends SubCase {
194205
testAttachPrimaryStorageFailsWhenActivatingHeartbeatVolumeFails()
195206
testMdsConnectFailed()
196207
testLifecycle()
208+
testReimageReferencedRootCleanup()
197209
testDataVolumeLifecycle()
198210
testMdsPing()
199211
testCheckHostStorageConnection()
@@ -756,6 +768,114 @@ class ZbsPrimaryStorageCase extends SubCase {
756768
}
757769
}
758770

771+
void testReimageReferencedRootCleanup() {
772+
attachPrimaryStorageToCluster {
773+
primaryStorageUuid = ps.uuid
774+
clusterUuid = cluster.uuid
775+
}
776+
777+
env.message(UploadImageToRemoteTargetMsg.class) { UploadImageToRemoteTargetMsg msg, CloudBus bus ->
778+
bus.reply(msg, new UploadImageToRemoteTargetReply())
779+
}
780+
781+
def instanceOffering = env.inventoryByName("instanceOffering") as InstanceOfferingInventory
782+
def image = env.inventoryByName("image") as ImageInventory
783+
def l3 = env.inventoryByName("l3") as L3NetworkInventory
784+
def vm = createVmInstance {
785+
name = "reimage-referenced-root"
786+
imageUuid = image.uuid
787+
l3NetworkUuids = [l3.uuid]
788+
instanceOfferingUuid = instanceOffering.uuid
789+
} as VmInstanceInventory
790+
def oldRoot = queryVolume {
791+
conditions = ["uuid=${vm.rootVolumeUuid}"]
792+
}[0] as VolumeInventory
793+
def snapshot = createVolumeSnapshot {
794+
name = "referenced-root-snapshot"
795+
volumeUuid = oldRoot.uuid
796+
} as VolumeSnapshotInventory
797+
def child = createDataVolumeFromVolumeSnapshot {
798+
name = "referenced-root-child"
799+
volumeSnapshotUuid = snapshot.uuid
800+
systemTags = [VolumeSystemTags.FAST_CREATE.tagFormat]
801+
} as VolumeInventory
802+
803+
assert Q.New(VolumeSnapshotReferenceVO.class)
804+
.eq(VolumeSnapshotReferenceVO_.referenceVolumeUuid, child.uuid).isExists() :
805+
"FAST_CREATE child must retain the backing reference: childUuid=${child.uuid}"
806+
807+
List<String> deleteAttempts = Collections.synchronizedList(new ArrayList<>())
808+
env.simulator(ZbsStorageController.QUERY_VOLUME_PATH) { HttpEntity<String> e, EnvSpec spec ->
809+
def cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.QueryVolumeCmd.class)
810+
def rsp = new ZbsStorageController.QueryVolumeRsp()
811+
rsp.size = oldRoot.size
812+
rsp.actualSize = oldRoot.actualSize
813+
if (ZbsHelper.normalizeToZbsPath(cmd.path) == snapshot.primaryStorageInstallPath) {
814+
rsp.parentUri = oldRoot.installPath
815+
}
816+
return rsp
817+
}
818+
env.simulator(ZbsStorageController.DELETE_VOLUME_PATH) { HttpEntity<String> e, EnvSpec spec ->
819+
def cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.DeleteVolumeCmd.class)
820+
String path = ZbsHelper.normalizeToZbsPath(cmd.path)
821+
deleteAttempts.add(path)
822+
return new ZbsStorageController.DeleteVolumeRsp()
823+
}
824+
825+
stopVmInstance {
826+
uuid = vm.uuid
827+
}
828+
reimageVmInstance {
829+
vmInstanceUuid = vm.uuid
830+
}
831+
832+
assert !deleteAttempts.contains(oldRoot.installPath) :
833+
"reimage must not delete an old root with downstream references: oldRoot=${oldRoot.installPath} attempts=${deleteAttempts}"
834+
assert !Q.New(InstallPathRecycleVO.class)
835+
.eq(InstallPathRecycleVO_.storageUuid, ps.uuid)
836+
.eq(InstallPathRecycleVO_.installPath, oldRoot.installPath)
837+
.eq(InstallPathRecycleVO_.trashType, TrashType.ReimageVolume.toString())
838+
.isExists() : "reimage must not create trash for referenced old root: oldRoot=${oldRoot.installPath}"
839+
840+
destroyVmInstance {
841+
uuid = vm.uuid
842+
}
843+
expungeVmInstance {
844+
uuid = vm.uuid
845+
}
846+
assert !Q.New(VolumeSnapshotVO.class).eq(VolumeSnapshotVO_.volumeUuid, oldRoot.uuid).isExists() :
847+
"old root snapshots must be removed after VM expunge: volumeUuid=${oldRoot.uuid}"
848+
assert Q.New(VolumeSnapshotReferenceVO.class)
849+
.eq(VolumeSnapshotReferenceVO_.referenceVolumeUuid, child.uuid).isExists() :
850+
"VM expunge must retain the FAST_CREATE child reference: childUuid=${child.uuid}"
851+
852+
int cleanupStartIndex = deleteAttempts.size()
853+
deleteDataVolume {
854+
uuid = child.uuid
855+
}
856+
expungeDataVolume {
857+
uuid = child.uuid
858+
}
859+
860+
retryInSecs {
861+
List<String> cleanupAttempts = deleteAttempts.drop(cleanupStartIndex)
862+
int snapshotIndex = cleanupAttempts.lastIndexOf(snapshot.primaryStorageInstallPath)
863+
int oldRootIndex = cleanupAttempts.indexOf(oldRoot.installPath)
864+
assert snapshotIndex >= 0 && oldRootIndex > snapshotIndex :
865+
"cleanup must delete leaf-to-root: snapshot=${snapshot.primaryStorageInstallPath} oldRoot=${oldRoot.installPath} attempts=${cleanupAttempts}"
866+
}
867+
868+
assert !Q.New(VolumeSnapshotReferenceVO.class)
869+
.eq(VolumeSnapshotReferenceVO_.referenceVolumeUuid, child.uuid).isExists() :
870+
"reference must be removed after child expunge: childUuid=${child.uuid}"
871+
872+
detachPrimaryStorageFromCluster {
873+
primaryStorageUuid = ps.uuid
874+
clusterUuid = cluster.uuid
875+
}
876+
env.cleanSimulatorHandlers()
877+
}
878+
759879
void testMdsPing() {
760880
ExternalPrimaryStorageCanonicalEvent.AddonInfoChangedData data = null
761881
long count = 0

0 commit comments

Comments
 (0)