package org.zstack.storage.snapshot; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusCallBack; import org.zstack.core.cloudbus.MessageSafe; import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.workflow.*; import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.message.Message; import org.zstack.header.message.MessageReply; import org.zstack.header.storage.backup.*; import org.zstack.header.storage.primary.*; import org.zstack.header.storage.snapshot.*; import org.zstack.header.storage.snapshot.VolumeSnapshotStatus.StatusEvent; import org.zstack.utils.CollectionUtils; import org.zstack.utils.DebugUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.ForEachFunction; import org.zstack.utils.function.Function; import org.zstack.utils.logging.CLogger; import java.util.*; /** */ @Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) public class VolumeSnapshotBase implements VolumeSnapshot { private static final CLogger logger = Utils.getLogger(VolumeSnapshotBase.class); @Autowired private CloudBus bus; @Autowired private DatabaseFacade dbf; @Autowired private PluginRegistry pluginRgty; protected VolumeSnapshotVO self; protected VolumeSnapshotInventory getSelfInventory() { return VolumeSnapshotInventory.valueOf(self); } public VolumeSnapshotBase(VolumeSnapshotVO self) { this.self = self; } @Override @MessageSafe public void handleMessage(Message msg) { handleLocalMessage(msg); } private void handleLocalMessage(Message msg) { if (msg instanceof VolumeSnapshotPrimaryStorageDeletionMsg) { handle((VolumeSnapshotPrimaryStorageDeletionMsg) msg); } else if (msg instanceof BackupVolumeSnapshotMsg) { handle((BackupVolumeSnapshotMsg) msg); } else if (msg instanceof VolumeSnapshotBackupStorageDeletionMsg) { handle((VolumeSnapshotBackupStorageDeletionMsg) msg); } else if (msg instanceof ChangeVolumeSnapshotStatusMsg) { handle((ChangeVolumeSnapshotStatusMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } private void handle(ChangeVolumeSnapshotStatusMsg msg) { StatusEvent evt = StatusEvent.valueOf(msg.getEvent()); changeStatus(evt); ChangeVolumeSnapshotStatusReply reply = new ChangeVolumeSnapshotStatusReply(); bus.reply(msg, reply); } private void handle(final VolumeSnapshotBackupStorageDeletionMsg msg) { final VolumeSnapshotBackupStorageDeletionReply reply = new VolumeSnapshotBackupStorageDeletionReply(); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("delete-volume-snapshot-%s-from-backup-storage", self.getUuid())); for (final String bsUuid : msg.getBackupStorageUuids()) { final String installPath = CollectionUtils.find(self.getBackupStorageRefs(), new Function<String, VolumeSnapshotBackupStorageRefVO>() { @Override public String call(VolumeSnapshotBackupStorageRefVO arg) { return arg.getBackupStorageUuid().equals(bsUuid) ? arg.getInstallPath() : null; } }); DebugUtils.Assert(installPath!=null, String.format("why installPath is NULL for snapshot[uuid:%s] on backupStorage[uuid:%s]???", self.getUuid(), bsUuid)); chain.then(new ShareFlow() { boolean success; @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = String.format("delete-on-backup-storage-%s", bsUuid); @Override public void run(final FlowTrigger trigger, Map data) { DeleteBitsOnBackupStorageMsg dmsg = new DeleteBitsOnBackupStorageMsg(); dmsg.setInstallPath(installPath); dmsg.setBackupStorageUuid(bsUuid); bus.makeTargetServiceIdByResourceUuid(dmsg, BackupStorageConstant.SERVICE_ID, bsUuid); bus.send(dmsg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { success = reply.isSuccess(); if (!success) { logger.warn(String.format("failed to delete snapshot[%s] on backup storage[uuid:%s], %s. the backup storage must clean up", self.getUuid(), bsUuid, reply.getError())); } VolumeSnapshotBackupStorageRefVO ref = CollectionUtils.find(self.getBackupStorageRefs(), new Function<VolumeSnapshotBackupStorageRefVO, VolumeSnapshotBackupStorageRefVO>() { @Override public VolumeSnapshotBackupStorageRefVO call(VolumeSnapshotBackupStorageRefVO arg) { return arg.getBackupStorageUuid().equals(bsUuid) ? arg : null; } }); dbf.remove(ref); trigger.next(); } }); } }); flow(new NoRollbackFlow() { String __name__ = String.format("return-capacity-to-backup-storage-%s", bsUuid); @Override public void run(final FlowTrigger trigger, Map data) { if (!success) { logger.warn(String.format("failed to delete volume snapshot[uuid:%s] from backup storage[uuid:%s], skip to return capacity[%s bytes]", self.getUuid(), bsUuid, self.getSize())); trigger.next(); return; } ReturnBackupStorageMsg rmsg = new ReturnBackupStorageMsg(); rmsg.setSize(self.getSize()); rmsg.setBackupStorageUuid(bsUuid); bus.makeTargetServiceIdByResourceUuid(rmsg, BackupStorageConstant.SERVICE_ID, bsUuid); bus.send(rmsg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { logger.warn(String.format("failed to return capacity to backup storage[uuid:%s] for volume snapshot[uuid:%s, size:%s], %s", bsUuid, self.getUuid(), self.getSize(), reply.getError())); } trigger.next(); } }); } }); } }); } chain.done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { bus.reply(msg, reply); } }).error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { bus.reply(msg, reply); } }).start(); } private void handle(final BackupVolumeSnapshotMsg msg) { final BackupVolumeSnapshotReply reply = new BackupVolumeSnapshotReply(); BackupVolumeSnapshotFromPrimaryStorageToBackupStorageMsg bmsg = new BackupVolumeSnapshotFromPrimaryStorageToBackupStorageMsg(); bmsg.setPrimaryStorageUuid(self.getPrimaryStorageUuid()); bmsg.setBackupStorage(msg.getBackupStorage()); bmsg.setSnapshot(getSelfInventory()); bus.makeTargetServiceIdByResourceUuid(bmsg, PrimaryStorageConstant.SERVICE_ID, self.getPrimaryStorageUuid()); bus.send(bmsg, new CloudBusCallBack(msg) { @Override public void run(MessageReply rsp) { if (!rsp.isSuccess()) { reply.setError(rsp.getError()); } else { BackupVolumeSnapshotFromPrimaryStorageToBackupStorageReply r = rsp.castReply(); VolumeSnapshotBackupStorageRefVO ref = new VolumeSnapshotBackupStorageRefVO(); ref.setInstallPath(r.getBackupStorageInstallPath()); ref.setVolumeSnapshotUuid(self.getUuid()); ref.setBackupStorageUuid(msg.getBackupStorage().getUuid()); dbf.persist(ref); } bus.reply(msg, reply); } }); } private void changeStatus(VolumeSnapshotStatus.StatusEvent event) { self.setStatus(self.getStatus().nextState(event)); dbf.update(self); } private void handle(final VolumeSnapshotPrimaryStorageDeletionMsg msg) { final VolumeSnapshotInventory sp = getSelfInventory(); for (VolumeSnapshotPreDeleteExtensionPoint ext : pluginRgty.getExtensionList(VolumeSnapshotPreDeleteExtensionPoint.class)) { ext.volumeSnapshotPreDeleteExtensionPoint(sp); } CollectionUtils.safeForEach(pluginRgty.getExtensionList(VolumeSnapshotBeforeDeleteExtensionPoint.class), new ForEachFunction<VolumeSnapshotBeforeDeleteExtensionPoint>() { @Override public void run(VolumeSnapshotBeforeDeleteExtensionPoint arg) { arg.volumeSnapshotBeforeDeleteExtensionPoint(sp); } }); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("delete-volume-snapshot-%s-on-primary-storage", self.getUuid())); chain.then(new ShareFlow() { private void finish() { CollectionUtils.safeForEach(pluginRgty.getExtensionList(VolumeSnapshotAfterDeleteExtensionPoint.class), new ForEachFunction<VolumeSnapshotAfterDeleteExtensionPoint>() { @Override public void run(VolumeSnapshotAfterDeleteExtensionPoint arg) { arg.volumeSnapshotAfterDeleteExtensionPoint(sp); } }); VolumeSnapshotPrimaryStorageDeletionReply dreply = new VolumeSnapshotPrimaryStorageDeletionReply(); self.setPrimaryStorageInstallPath(null); self.setPrimaryStorageUuid(null); dbf.update(self); bus.reply(msg, dreply); } @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "delete-on-primary-storage"; @Override public void run(final FlowTrigger trigger, Map data) { DeleteSnapshotOnPrimaryStorageMsg dmsg = new DeleteSnapshotOnPrimaryStorageMsg(); dmsg.setSnapshot(getSelfInventory()); bus.makeTargetServiceIdByResourceUuid(dmsg, PrimaryStorageConstant.SERVICE_ID, self.getPrimaryStorageUuid()); bus.send(dmsg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (reply.isSuccess()) { trigger.setError(reply.getError()); } trigger.next(); } }); } }); flow(new NoRollbackFlow() { String __name__ = "return-capacity-to-primary-storage"; @Override public void run(FlowTrigger trigger, Map data) { IncreasePrimaryStorageCapacityMsg imsg = new IncreasePrimaryStorageCapacityMsg(); imsg.setDiskSize(self.getSize()); imsg.setNoOverProvisioning(true); imsg.setPrimaryStorageUuid(self.getPrimaryStorageUuid()); bus.makeTargetServiceIdByResourceUuid(imsg, PrimaryStorageConstant.SERVICE_ID, imsg.getPrimaryStorageUuid()); bus.send(imsg); trigger.next(); } }); done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { finish(); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { // TODO GC logger.warn(String.format("failed to delete snapshot[uuid:%s, name:%s] on primary storage[uuid:%s], the primary storage should cleanup", self.getUuid(), self.getName(), self.getPrimaryStorageUuid())); finish(); } }); } }).start(); } }