@@ -6,12 +6,21 @@ import org.zstack.core.cloudbus.EventCallback
66import org.zstack.core.cloudbus.EventFacade
77import org.zstack.core.db.DatabaseFacade
88import org.zstack.core.db.Q
9+ import org.zstack.core.trash.TrashType
910import org.zstack.header.errorcode.ErrorCode
1011import org.zstack.header.errorcode.OperationFailureException
12+ import org.zstack.header.core.trash.InstallPathRecycleVO
13+ import org.zstack.header.core.trash.InstallPathRecycleVO_
1114import org.zstack.header.message.MessageReply
15+ import org.zstack.header.storage.backup.UploadImageToRemoteTargetMsg
16+ import org.zstack.header.storage.backup.UploadImageToRemoteTargetReply
1217import org.zstack.header.storage.primary.GetVolumeBackingChainFromPrimaryStorageMsg
1318import org.zstack.header.storage.primary.GetVolumeBackingChainFromPrimaryStorageReply
1419import 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_
1524import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageMsg
1625import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageReply
1726import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO
@@ -40,8 +49,10 @@ import org.zstack.storage.zbs.Config
4049import org.zstack.storage.zbs.ZbsAgentUrl
4150import org.zstack.storage.zbs.ZbsConstants
4251import org.zstack.storage.zbs.ZbsGlobalProperty
52+ import org.zstack.storage.zbs.ZbsHelper
4353import org.zstack.storage.zbs.ZbsPrimaryStorageMdsBase
4454import org.zstack.storage.zbs.ZbsStorageController
55+ import org.zstack.storage.volume.VolumeSystemTags
4556import org.zstack.test.integration.storage.StorageTest
4657import org.zstack.testlib.EnvSpec
4758import 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