package org.zstack.storage.volume; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.transaction.annotation.Transactional; import org.zstack.core.cascade.CascadeConstant; import org.zstack.core.cascade.CascadeFacade; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusCallBack; import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.SQL; import org.zstack.core.db.SimpleQuery; import org.zstack.core.db.SimpleQuery.Op; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.core.thread.ChainTask; import org.zstack.core.thread.SyncTaskChain; import org.zstack.core.thread.ThreadFacade; import org.zstack.core.workflow.FlowChainBuilder; import org.zstack.core.workflow.ShareFlow; import org.zstack.header.core.Completion; import org.zstack.header.core.NoErrorCompletion; import org.zstack.header.core.NopeCompletion; import org.zstack.header.core.ReturnValueCompletion; import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.errorcode.SysErrors; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.host.HostInventory; import org.zstack.header.host.HostVO; import org.zstack.header.image.ImageInventory; import org.zstack.header.image.ImageVO; import org.zstack.header.message.APIDeleteMessage.DeletionMode; import org.zstack.header.message.APIMessage; import org.zstack.header.message.Message; import org.zstack.header.message.MessageReply; import org.zstack.header.message.OverlayMessage; import org.zstack.header.storage.primary.*; import org.zstack.header.storage.snapshot.CreateVolumeSnapshotMsg; import org.zstack.header.storage.snapshot.CreateVolumeSnapshotReply; import org.zstack.header.storage.snapshot.VolumeSnapshotConstant; import org.zstack.header.storage.snapshot.VolumeSnapshotVO; import org.zstack.header.vm.*; import org.zstack.header.volume.*; import org.zstack.header.volume.VolumeConstant.Capability; import org.zstack.header.volume.VolumeDeletionPolicyManager.VolumeDeletionPolicy; import org.zstack.identity.AccountManager; import org.zstack.tag.TagManager; 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.logging.CLogger; import static org.zstack.core.Platform.operr; import javax.persistence.TypedQuery; import java.util.*; import static org.zstack.utils.CollectionDSL.list; /** * Created with IntelliJ IDEA. * User: frank * Time: 9:20 PM * To change this template use File | Settings | File Templates. */ @Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) public class VolumeBase implements Volume { private static final CLogger logger = Utils.getLogger(VolumeBase.class); protected String syncThreadId; protected VolumeVO self; @Autowired private CloudBus bus; @Autowired private DatabaseFacade dbf; @Autowired private ThreadFacade thdf; @Autowired private ErrorFacade errf; @Autowired private CascadeFacade casf; @Autowired private AccountManager acntMgr; @Autowired private TagManager tagMgr; @Autowired private PluginRegistry pluginRgty; @Autowired private VolumeDeletionPolicyManager deletionPolicyMgr; public VolumeBase(VolumeVO vo) { self = vo; syncThreadId = String.format("volume-%s", self.getUuid()); } protected void refreshVO() { self = dbf.reload(self); } @Override public void handleMessage(Message msg) { try { if (msg instanceof APIMessage) { handleApiMessage((APIMessage) msg); } else { handleLocalMessage(msg); } } catch (Exception e) { bus.logExceptionWithMessageDump(msg, e); bus.replyErrorByMessageType(msg, e); } } private void handleLocalMessage(Message msg) { if (msg instanceof VolumeDeletionMsg) { handle((VolumeDeletionMsg) msg); } else if (msg instanceof DeleteVolumeMsg) { handle((DeleteVolumeMsg) msg); } else if (msg instanceof VolumeCreateSnapshotMsg) { handle((VolumeCreateSnapshotMsg) msg); } else if (msg instanceof CreateDataVolumeTemplateFromDataVolumeMsg) { handle((CreateDataVolumeTemplateFromDataVolumeMsg) msg); } else if (msg instanceof ExpungeVolumeMsg) { handle((ExpungeVolumeMsg) msg); } else if (msg instanceof RecoverVolumeMsg) { handle((RecoverVolumeMsg) msg); } else if (msg instanceof SyncVolumeSizeMsg) { handle((SyncVolumeSizeMsg) msg); } else if (msg instanceof InstantiateVolumeMsg) { handle((InstantiateVolumeMsg) msg); } else if (msg instanceof OverlayMessage) { handle((OverlayMessage) msg); } else { bus.dealWithUnknownMessage(msg); } } private void handle(InstantiateVolumeMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return String.format("instantiate-volume-%s", self.getUuid()); } @Override public void run(SyncTaskChain chain) { doInstantiateVolume(msg, new NoErrorCompletion(msg, chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return "instantiate-volume"; } }); } private void doInstantiateVolume(InstantiateVolumeMsg msg, NoErrorCompletion completion) { InstantiateVolumeReply reply = new InstantiateVolumeReply(); List<PreInstantiateVolumeExtensionPoint> exts = pluginRgty.getExtensionList(PreInstantiateVolumeExtensionPoint.class); for (PreInstantiateVolumeExtensionPoint ext : exts) { ext.preInstantiateVolume(msg); } FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("instantiate-volume-%s", self.getUuid())); chain.then(new ShareFlow() { String installPath; String format; @Override public void setup() { if (!msg.isPrimaryStorageAllocated()) { flow(new Flow() { String __name__ = "allocate-primary-storage"; boolean success; @Override public void run(FlowTrigger trigger, Map data) { AllocatePrimaryStorageMsg amsg = new AllocatePrimaryStorageMsg(); amsg.setRequiredPrimaryStorageUuid(msg.getPrimaryStorageUuid()); amsg.setSize(self.getSize()); bus.makeTargetServiceIdByResourceUuid(amsg, PrimaryStorageConstant.SERVICE_ID, msg.getPrimaryStorageUuid()); bus.send(amsg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { trigger.fail(reply.getError()); } else { success = true; trigger.next(); } } }); } @Override public void rollback(FlowRollback trigger, Map data) { if (success) { IncreasePrimaryStorageCapacityMsg imsg = new IncreasePrimaryStorageCapacityMsg(); imsg.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); imsg.setDiskSize(self.getSize()); bus.makeTargetServiceIdByResourceUuid(imsg, PrimaryStorageConstant.SERVICE_ID, msg.getPrimaryStorageUuid()); bus.send(imsg); } trigger.rollback(); } }); } flow(new Flow() { String __name__ = "instantiate-volume-on-primary-storage"; boolean success; @Override public void run(FlowTrigger trigger, Map data) { SimpleQuery<PrimaryStorageVO> q = dbf.createQuery(PrimaryStorageVO.class); q.select(PrimaryStorageVO_.type); q.add(PrimaryStorageVO_.uuid, Op.EQ, msg.getPrimaryStorageUuid()); String psType = q.findValue(); InstantiateDataVolumeOnCreationExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, InstantiateDataVolumeOnCreationExtensionPoint.class); if (ext != null) { ext.instantiateDataVolumeOnCreation(msg, getSelfInventory(), new ReturnValueCompletion<VolumeInventory>(trigger) { @Override public void success(VolumeInventory ret) { success = true; installPath = ret.getInstallPath(); format = ret.getFormat(); trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } else { if (msg instanceof InstantiateRootVolumeMsg) { instantiateRootVolume((InstantiateRootVolumeMsg) msg, trigger); } else { instantiateDataVolume(msg, trigger); } } } private void instantiateRootVolume(InstantiateRootVolumeMsg msg, FlowTrigger trigger) { InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg imsg = new InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg(); imsg.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); imsg.setVolume(getSelfInventory()); if (msg.getHostUuid() != null) { imsg.setDestHost(HostInventory.valueOf(dbf.findByUuid(msg.getHostUuid(), HostVO.class))); } imsg.setSystemTags(msg.getSystemTags()); imsg.setTemplateSpec(msg.getTemplateSpec()); bus.makeTargetServiceIdByResourceUuid(imsg, PrimaryStorageConstant.SERVICE_ID, msg.getPrimaryStorageUuid()); bus.send(imsg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { trigger.fail(reply.getError()); return; } success = true; InstantiateVolumeOnPrimaryStorageReply ir = reply.castReply(); installPath = ir.getVolume().getInstallPath(); format = ir.getVolume().getFormat(); trigger.next(); } }); } private void instantiateDataVolume(InstantiateVolumeMsg msg, FlowTrigger trigger) { InstantiateVolumeOnPrimaryStorageMsg imsg = new InstantiateVolumeOnPrimaryStorageMsg(); imsg.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); imsg.setVolume(getSelfInventory()); imsg.setSystemTags(msg.getSystemTags()); if (msg.getHostUuid() != null) { imsg.setDestHost(HostInventory.valueOf(dbf.findByUuid(msg.getHostUuid(), HostVO.class))); } bus.makeTargetServiceIdByResourceUuid(imsg, PrimaryStorageConstant.SERVICE_ID, msg.getPrimaryStorageUuid()); bus.send(imsg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { trigger.fail(reply.getError()); return; } success = true; InstantiateVolumeOnPrimaryStorageReply ir = reply.castReply(); installPath = ir.getVolume().getInstallPath(); format = ir.getVolume().getFormat(); trigger.next(); } }); } @Override public void rollback(FlowRollback trigger, Map data) { if (success) { DeleteVolumeOnPrimaryStorageMsg dmsg = new DeleteVolumeOnPrimaryStorageMsg(); dmsg.setUuid(msg.getPrimaryStorageUuid()); dmsg.setVolume(getSelfInventory()); bus.makeTargetServiceIdByResourceUuid(dmsg, PrimaryStorageConstant.SERVICE_ID, msg.getPrimaryStorageUuid()); bus.send(dmsg); } trigger.rollback(); } }); done(new FlowDoneHandler(msg, completion) { @Override public void handle(Map data) { VolumeStatus oldStatus = self.getStatus(); self.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); self.setInstallPath(installPath); DebugUtils.Assert(format != null, "format cannot be null"); self.setFormat(format); self.setStatus(VolumeStatus.Ready); self = dbf.updateAndRefresh(self); VolumeInventory vol = getSelfInventory(); new FireVolumeCanonicalEvent().fireVolumeStatusChangedEvent(oldStatus, vol); reply.setVolume(vol); bus.reply(msg, reply); } }); error(new FlowErrorHandler(msg, completion) { @Override public void handle(ErrorCode errCode, Map data) { reply.setError(errCode); bus.reply(msg, reply); } }); Finally(new FlowFinallyHandler(msg, completion) { @Override public void Finally() { completion.done(); } }); } }).start(); } private void handle(OverlayMessage msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return syncThreadId; } @Override public void run(SyncTaskChain chain) { doOverlayMessage(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return "overlay-message"; } }); } private void doOverlayMessage(OverlayMessage msg, NoErrorCompletion noErrorCompletion) { bus.send(msg.getMessage(), new CloudBusCallBack(msg, noErrorCompletion) { @Override public void run(MessageReply reply) { bus.reply(msg, reply); noErrorCompletion.done(); } }); } private void handle(final SyncVolumeSizeMsg msg) { final SyncVolumeSizeReply reply = new SyncVolumeSizeReply(); syncVolumeVolumeSize(new ReturnValueCompletion<VolumeSize>(msg) { @Override public void success(VolumeSize ret) { reply.setActualSize(ret.actualSize); reply.setSize(ret.size); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } private void handle(final RecoverVolumeMsg msg) { final RecoverVolumeReply reply = new RecoverVolumeReply(); thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return syncThreadId; } @Override public void run(SyncTaskChain chain) { recoverVolume(new Completion(chain, msg) { @Override public void success() { bus.reply(msg, reply); chain.next(); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); chain.next(); } }); } @Override public String getName() { return RecoverVolumeMsg.class.getName(); } }); } private void expunge(final Completion completion) { if (self.getStatus() != VolumeStatus.Deleted) { throw new OperationFailureException(operr("the volume[uuid:%s, name:%s] is not deleted yet, can't expunge it", self.getUuid(), self.getName())); } final VolumeInventory inv = getSelfInventory(); CollectionUtils.safeForEach(pluginRgty.getExtensionList(VolumeBeforeExpungeExtensionPoint.class), new ForEachFunction<VolumeBeforeExpungeExtensionPoint>() { @Override public void run(VolumeBeforeExpungeExtensionPoint arg) { arg.volumeBeforeExpunge(inv); } }); if (self.getPrimaryStorageUuid() != null) { DeleteVolumeOnPrimaryStorageMsg dmsg = new DeleteVolumeOnPrimaryStorageMsg(); dmsg.setVolume(getSelfInventory()); dmsg.setUuid(self.getPrimaryStorageUuid()); bus.makeTargetServiceIdByResourceUuid(dmsg, PrimaryStorageConstant.SERVICE_ID, self.getPrimaryStorageUuid()); bus.send(dmsg, new CloudBusCallBack(completion) { @Override public void run(MessageReply r) { if (!r.isSuccess()) { completion.fail(r.getError()); } else { IncreasePrimaryStorageCapacityMsg msg = new IncreasePrimaryStorageCapacityMsg(); msg.setPrimaryStorageUuid(self.getPrimaryStorageUuid()); msg.setDiskSize(self.getSize()); bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, self.getPrimaryStorageUuid()); bus.send(msg); CollectionUtils.safeForEach(pluginRgty.getExtensionList(VolumeAfterExpungeExtensionPoint.class), new ForEachFunction<VolumeAfterExpungeExtensionPoint>() { @Override public void run(VolumeAfterExpungeExtensionPoint arg) { arg.volumeAfterExpunge(inv); } }); dbf.remove(self); completion.success(); } } }); } else { CollectionUtils.safeForEach(pluginRgty.getExtensionList(VolumeAfterExpungeExtensionPoint.class), new ForEachFunction<VolumeAfterExpungeExtensionPoint>() { @Override public void run(VolumeAfterExpungeExtensionPoint arg) { arg.volumeAfterExpunge(inv); } }); dbf.remove(self); completion.success(); } } private void handle(final ExpungeVolumeMsg msg) { final ExpungeVolumeReply reply = new ExpungeVolumeReply(); thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return syncThreadId; } @Override public void run(SyncTaskChain chain) { expunge(new Completion(msg, chain) { @Override public void success() { bus.reply(msg, reply); chain.next(); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); chain.next(); } }); } @Override public String getName() { return ExpungeVolumeMsg.class.getName(); } }); } private void handle(final CreateDataVolumeTemplateFromDataVolumeMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return syncThreadId; } @Override public void run(SyncTaskChain chain) { doCreateDataVolumeTemplateFromDataVolumeMsg(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return CreateDataVolumeTemplateFromDataVolumeMsg.class.getName(); } }); } private void doCreateDataVolumeTemplateFromDataVolumeMsg(CreateDataVolumeTemplateFromDataVolumeMsg msg, NoErrorCompletion noErrorCompletion) { final CreateTemplateFromVolumeOnPrimaryStorageMsg cmsg = new CreateTemplateFromVolumeOnPrimaryStorageMsg(); cmsg.setBackupStorageUuid(msg.getBackupStorageUuid()); cmsg.setImageInventory(ImageInventory.valueOf(dbf.findByUuid(msg.getImageUuid(), ImageVO.class))); cmsg.setVolumeInventory(getSelfInventory()); bus.makeTargetServiceIdByResourceUuid(cmsg, PrimaryStorageConstant.SERVICE_ID, self.getPrimaryStorageUuid()); bus.send(cmsg, new CloudBusCallBack(msg, noErrorCompletion) { @Override public void run(MessageReply r) { CreateDataVolumeTemplateFromDataVolumeReply reply = new CreateDataVolumeTemplateFromDataVolumeReply(); if (!r.isSuccess()) { reply.setError(r.getError()); } else { CreateTemplateFromVolumeOnPrimaryStorageReply creply = r.castReply(); String backupStorageInstallPath = creply.getTemplateBackupStorageInstallPath(); reply.setFormat(creply.getFormat()); reply.setInstallPath(backupStorageInstallPath); reply.setMd5sum(null); reply.setBackupStorageUuid(msg.getBackupStorageUuid()); } bus.reply(msg, reply); noErrorCompletion.done(); } }); } private void handle(final DeleteVolumeMsg msg) { final DeleteVolumeReply reply = new DeleteVolumeReply(); delete(true, VolumeDeletionPolicy.valueOf(msg.getDeletionPolicy()), msg.isDetachBeforeDeleting(), new Completion(msg) { @Override public void success() { logger.debug(String.format("deleted data volume[uuid: %s]", msg.getUuid())); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } private void deleteVolume(final VolumeDeletionMsg msg, final NoErrorCompletion completion) { final VolumeDeletionReply reply = new VolumeDeletionReply(); for (VolumeDeletionExtensionPoint extp : pluginRgty.getExtensionList(VolumeDeletionExtensionPoint.class)) { extp.preDeleteVolume(getSelfInventory()); } CollectionUtils.safeForEach(pluginRgty.getExtensionList(VolumeDeletionExtensionPoint.class), new ForEachFunction<VolumeDeletionExtensionPoint>() { @Override public void run(VolumeDeletionExtensionPoint arg) { arg.beforeDeleteVolume(getSelfInventory()); } }); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("delete-volume-%s", self.getUuid())); // for NotInstantiated Volume, no flow to execute chain.allowEmptyFlow(); chain.then(new ShareFlow() { VolumeDeletionPolicy deletionPolicy; { if (msg.getDeletionPolicy() == null) { deletionPolicy = deletionPolicyMgr.getDeletionPolicy(self.getUuid()); } else { deletionPolicy = VolumeDeletionPolicy.valueOf(msg.getDeletionPolicy()); } } @Override public void setup() { if (self.getVmInstanceUuid() != null && self.getType() == VolumeType.Data && msg.isDetachBeforeDeleting()) { flow(new NoRollbackFlow() { String __name__ = "detach-volume-from-vm"; public void run(final FlowTrigger trigger, Map data) { DetachDataVolumeFromVmMsg dmsg = new DetachDataVolumeFromVmMsg(); dmsg.setVolume(getSelfInventory()); String vmUuid; if (dmsg.getVmInstanceUuid() == null) { vmUuid = getSelfInventory().getVmInstanceUuid(); } else { vmUuid = dmsg.getVmInstanceUuid(); } dmsg.setVmInstanceUuid(vmUuid); bus.makeTargetServiceIdByResourceUuid(dmsg, VmInstanceConstant.SERVICE_ID, vmUuid); bus.send(dmsg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { self.setVmInstanceUuid(null); self = dbf.updateAndRefresh(self); trigger.next(); } }); } }); } if (deletionPolicy == VolumeDeletionPolicy.Direct) { flow(new NoRollbackFlow() { String __name__ = "delete-data-volume-from-primary-storage"; @Override public void run(final FlowTrigger trigger, Map data) { if (self.getStatus() == VolumeStatus.Ready) { DeleteVolumeOnPrimaryStorageMsg dmsg = new DeleteVolumeOnPrimaryStorageMsg(); dmsg.setVolume(getSelfInventory()); dmsg.setUuid(self.getPrimaryStorageUuid()); bus.makeTargetServiceIdByResourceUuid(dmsg, PrimaryStorageConstant.SERVICE_ID, self.getPrimaryStorageUuid()); logger.debug(String.format("Asking primary storage[uuid:%s] to remove data volume[uuid:%s]", self.getPrimaryStorageUuid(), self.getUuid())); bus.send(dmsg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { logger.warn(String.format("failed to delete volume[uuid:%s, name:%s], %s", self.getUuid(), self.getName(), reply.getError())); } trigger.next(); } }); } else { trigger.next(); } } }); } if (self.getPrimaryStorageUuid() != null && deletionPolicy == VolumeDeletionPolicy.Direct) { flow(new NoRollbackFlow() { String __name__ = "return-primary-storage-capacity"; @Override public void run(FlowTrigger trigger, Map data) { IncreasePrimaryStorageCapacityMsg imsg = new IncreasePrimaryStorageCapacityMsg(); imsg.setPrimaryStorageUuid(self.getPrimaryStorageUuid()); imsg.setDiskSize(self.getSize()); bus.makeTargetServiceIdByResourceUuid(imsg, PrimaryStorageConstant.SERVICE_ID, self.getPrimaryStorageUuid()); bus.send(imsg); trigger.next(); } }); } done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { VolumeStatus oldStatus = self.getStatus(); if (deletionPolicy == VolumeDeletionPolicy.Direct) { self.setStatus(VolumeStatus.Deleted); self = dbf.updateAndRefresh(self); new FireVolumeCanonicalEvent().fireVolumeStatusChangedEvent(oldStatus, getSelfInventory()); dbf.remove(self); } else if (deletionPolicy == VolumeDeletionPolicy.Delay) { self.setStatus(VolumeStatus.Deleted); self = dbf.updateAndRefresh(self); new FireVolumeCanonicalEvent().fireVolumeStatusChangedEvent(oldStatus, getSelfInventory()); } else if (deletionPolicy == VolumeDeletionPolicy.Never) { self.setStatus(VolumeStatus.Deleted); self = dbf.updateAndRefresh(self); new FireVolumeCanonicalEvent().fireVolumeStatusChangedEvent(oldStatus, getSelfInventory()); } else if (deletionPolicy == VolumeDeletionPolicy.DBOnly) { new FireVolumeCanonicalEvent().fireVolumeStatusChangedEvent(oldStatus, getSelfInventory()); dbf.remove(self); } else { throw new CloudRuntimeException(String.format("Invalid deletionPolicy:%s", deletionPolicy)); } CollectionUtils.safeForEach(pluginRgty.getExtensionList(VolumeDeletionExtensionPoint.class), new ForEachFunction<VolumeDeletionExtensionPoint>() { @Override public void run(VolumeDeletionExtensionPoint arg) { arg.afterDeleteVolume(getSelfInventory()); } }); bus.reply(msg, reply); } }); error(new FlowErrorHandler(msg) { @Override public void handle(final ErrorCode errCode, Map data) { CollectionUtils.safeForEach(pluginRgty.getExtensionList(VolumeDeletionExtensionPoint.class), new ForEachFunction<VolumeDeletionExtensionPoint>() { @Override public void run(VolumeDeletionExtensionPoint arg) { arg.failedToDeleteVolume(getSelfInventory(), errCode); } }); reply.setError(errCode); bus.reply(msg, reply); } }); Finally(new FlowFinallyHandler(msg) { @Override public void Finally() { completion.done(); } }); } }).start(); } private void handle(final VolumeDeletionMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return syncThreadId; } @Override public void run(final SyncTaskChain chain) { self = dbf.reload(self); if (self.getStatus() == VolumeStatus.Deleted) { // the volume has been deleted // we run into this case because the cascading framework // will send duplicate messages when deleting a vm as the cascading // framework has no knowledge about if the volume has been deleted VolumeDeletionReply reply = new VolumeDeletionReply(); bus.reply(msg, reply); chain.next(); return; } deleteVolume(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("delete-volume-%s", self.getUuid()); } }); } private void handleApiMessage(APIMessage msg) { if (msg instanceof APIChangeVolumeStateMsg) { handle((APIChangeVolumeStateMsg) msg); } else if (msg instanceof APICreateVolumeSnapshotMsg) { handle((APICreateVolumeSnapshotMsg) msg); } else if (msg instanceof APIDeleteDataVolumeMsg) { handle((APIDeleteDataVolumeMsg) msg); } else if (msg instanceof APIDetachDataVolumeFromVmMsg) { handle((APIDetachDataVolumeFromVmMsg) msg); } else if (msg instanceof APIAttachDataVolumeToVmMsg) { handle((APIAttachDataVolumeToVmMsg) msg); } else if (msg instanceof APIGetDataVolumeAttachableVmMsg) { handle((APIGetDataVolumeAttachableVmMsg) msg); } else if (msg instanceof APIUpdateVolumeMsg) { handle((APIUpdateVolumeMsg) msg); } else if (msg instanceof APIRecoverDataVolumeMsg) { handle((APIRecoverDataVolumeMsg) msg); } else if (msg instanceof APIExpungeDataVolumeMsg) { handle((APIExpungeDataVolumeMsg) msg); } else if (msg instanceof APISyncVolumeSizeMsg) { handle((APISyncVolumeSizeMsg) msg); } else if (msg instanceof APIGetVolumeCapabilitiesMsg) { handle((APIGetVolumeCapabilitiesMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } private void handle(APIGetVolumeCapabilitiesMsg msg) { APIGetVolumeCapabilitiesReply reply = new APIGetVolumeCapabilitiesReply(); Map<String, Object> ret = new HashMap<String, Object>(); if (VolumeStatus.Ready == self.getStatus()) { getPrimaryStorageCapacities(ret); } reply.setCapabilities(ret); bus.reply(msg, reply); } private void getPrimaryStorageCapacities(Map<String, Object> ret) { SimpleQuery<PrimaryStorageVO> q = dbf.createQuery(PrimaryStorageVO.class); q.select(PrimaryStorageVO_.type); q.add(PrimaryStorageVO_.uuid, Op.EQ, self.getPrimaryStorageUuid()); String type = q.findValue(); PrimaryStorageType psType = PrimaryStorageType.valueOf(type); ret.put(Capability.MigrationInCurrentPrimaryStorage.toString(), psType.isSupportVolumeMigrationInCurrentPrimaryStorage()); ret.put(Capability.MigrationToOtherPrimaryStorage.toString(), psType.isSupportVolumeMigrationToOtherPrimaryStorage()); } private void syncVolumeVolumeSize(final ReturnValueCompletion<VolumeSize> completion) { SyncVolumeSizeOnPrimaryStorageMsg smsg = new SyncVolumeSizeOnPrimaryStorageMsg(); smsg.setPrimaryStorageUuid(self.getPrimaryStorageUuid()); smsg.setVolumeUuid(self.getUuid()); smsg.setInstallPath(self.getInstallPath()); bus.makeTargetServiceIdByResourceUuid(smsg, PrimaryStorageConstant.SERVICE_ID, self.getPrimaryStorageUuid()); bus.send(smsg, new CloudBusCallBack(completion) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { completion.fail(reply.getError()); return; } SyncVolumeSizeOnPrimaryStorageReply r = reply.castReply(); self.setSize(r.getSize()); // the actual size = volume actual size + all snapshot size long snapshotSize = calculateSnapshotSize(); self.setActualSize(r.getActualSize() + snapshotSize); self = dbf.updateAndRefresh(self); VolumeSize size = new VolumeSize(); size.actualSize = self.getActualSize(); size.size = self.getSize(); completion.success(size); } @Transactional(readOnly = true) private long calculateSnapshotSize() { String sql = "select sum(sp.size) from VolumeSnapshotVO sp where sp.volumeUuid = :uuid"; TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class); q.setParameter("uuid", self.getUuid()); Long size = q.getSingleResult(); return size == null ? 0 : size; } }); } private void handle(APISyncVolumeSizeMsg msg) { final APISyncVolumeSizeEvent evt = new APISyncVolumeSizeEvent(msg.getId()); if (self.getStatus() != VolumeStatus.Ready) { evt.setInventory(getSelfInventory()); bus.publish(evt); return; } syncVolumeVolumeSize(new ReturnValueCompletion<VolumeSize>(msg) { @Override public void success(VolumeSize ret) { evt.setInventory(getSelfInventory()); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { evt.setError(errorCode); bus.publish(evt); } }); } private void handle(APIExpungeDataVolumeMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return syncThreadId; } @Override public void run(SyncTaskChain chain) { final APIExpungeDataVolumeEvent evt = new APIExpungeDataVolumeEvent(msg.getId()); expunge(new Completion(msg, chain) { @Override public void success() { bus.publish(evt); chain.next(); } @Override public void fail(ErrorCode errorCode) { evt.setError(errorCode); bus.publish(evt); chain.next(); } }); } @Override public String getName() { return msg.getClass().getName(); } }); } protected void recoverVolume(Completion completion) { final VolumeInventory vol = getSelfInventory(); List<RecoverDataVolumeExtensionPoint> exts = pluginRgty.getExtensionList(RecoverDataVolumeExtensionPoint.class); for (RecoverDataVolumeExtensionPoint ext : exts) { ext.preRecoverDataVolume(vol); } CollectionUtils.safeForEach(exts, new ForEachFunction<RecoverDataVolumeExtensionPoint>() { @Override public void run(RecoverDataVolumeExtensionPoint ext) { ext.beforeRecoverDataVolume(vol); } }); VolumeStatus oldStatus = self.getStatus(); if (self.getInstallPath() != null) { self.setStatus(VolumeStatus.Ready); } else { self.setStatus(VolumeStatus.NotInstantiated); } self = dbf.updateAndRefresh(self); new FireVolumeCanonicalEvent().fireVolumeStatusChangedEvent(oldStatus, getSelfInventory()); CollectionUtils.safeForEach(exts, new ForEachFunction<RecoverDataVolumeExtensionPoint>() { @Override public void run(RecoverDataVolumeExtensionPoint ext) { ext.afterRecoverDataVolume(vol); } }); completion.success(); } private void handle(APIRecoverDataVolumeMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return syncThreadId; } @Override public void run(SyncTaskChain chain) { final APIRecoverDataVolumeEvent evt = new APIRecoverDataVolumeEvent(msg.getId()); recoverVolume(new Completion(msg, chain) { @Override public void success() { evt.setInventory(getSelfInventory()); bus.publish(evt); chain.next(); } @Override public void fail(ErrorCode errorCode) { evt.setInventory(getSelfInventory()); evt.setError(errorCode); bus.publish(evt); chain.next(); } }); } @Override public String getName() { return msg.getClass().getName(); } }); } private void handle(APIUpdateVolumeMsg msg) { boolean update = false; if (msg.getName() != null) { self.setName(msg.getName()); update = true; } if (msg.getDescription() != null) { self.setDescription(msg.getDescription()); update = true; } if (update) { self = dbf.updateAndRefresh(self); } APIUpdateVolumeEvent evt = new APIUpdateVolumeEvent(msg.getId()); evt.setInventory(getSelfInventory()); bus.publish(evt); } @Transactional(readOnly = true) private List<VmInstanceVO> getCandidateVmForAttaching(String accountUuid) { List<String> vmUuids = acntMgr.getResourceUuidsCanAccessByAccount(accountUuid, VmInstanceVO.class); if (vmUuids != null && vmUuids.isEmpty()) { return new ArrayList<VmInstanceVO>(); } SQL sql = null; if (vmUuids == null) { // all vms if (self.getStatus() == VolumeStatus.Ready) { List<String> hvTypes = VolumeFormat.valueOf(self.getFormat()).getHypervisorTypesSupportingThisVolumeFormatInString(); sql = SQL.New("select vm " + "from VmInstanceVO vm, PrimaryStorageClusterRefVO ref, VolumeVO vol " + "where vm.state in (:vmStates) " + "and vol.uuid = :volUuid " + "and vm.hypervisorType in (:hvTypes) " + "and vm.clusterUuid = ref.clusterUuid " + "and ref.primaryStorageUuid = vol.primaryStorageUuid " + "group by vm.uuid") .param("volUuid", self.getUuid()) .param("hvTypes", hvTypes); } else if (self.getStatus() == VolumeStatus.NotInstantiated) { //not support vmtx volume temporarily, so filter ESX vm when volume is NotInstantiated. sql = SQL.New("select vm " + "from VmInstanceVO vm,PrimaryStorageClusterRefVO ref,PrimaryStorageEO ps " + "where vm.state in (:vmStates) " + "and vm.hypervisorType <> :hvType " + "and vm.clusterUuid = ref.clusterUuid " + "and ref.primaryStorageUuid = ps.uuid " + "and ps.state in (:psState) " + "group by vm.uuid") //TODO: this is a dirty fix, delete it when VMWare support DataVolume .param("hvType", "ESX") .param("psState",PrimaryStorageState.Enabled); } else { DebugUtils.Assert(false, String.format("should not reach here, volume[uuid:%s]", self.getUuid())); } } else { if (self.getStatus() == VolumeStatus.Ready) { List<String> hvTypes = VolumeFormat.valueOf(self.getFormat()).getHypervisorTypesSupportingThisVolumeFormatInString(); sql = SQL.New("select vm "+ "from VmInstanceVO vm, PrimaryStorageClusterRefVO ref, VolumeVO vol " + "where vm.uuid in (:vmUuids) " + "and vm.state in (:vmStates) " + "and vol.uuid = :volUuid " + "and vm.hypervisorType in (:hvTypes) " + "and vm.clusterUuid = ref.clusterUuid " + "and ref.primaryStorageUuid = vol.primaryStorageUuid " + "group by vm.uuid") .param("volUuid", self.getUuid()) .param("hvTypes", hvTypes); } else if (self.getStatus() == VolumeStatus.NotInstantiated) { sql = SQL.New("select vm " + "from VmInstanceVO vm,PrimaryStorageClusterRefVO ref,PrimaryStorageEO ps " + "where vm.uuid in (:vmUuids) " + "and vm.state in (:vmStates) " + "and vm.clusterUuid = ref.clusterUuid " + "and ref.primaryStorageUuid = ps.uuid " + "and ps.state in (:psState) " + "group by vm.uuid") .param("psState",PrimaryStorageState.Enabled); } else { DebugUtils.Assert(false, String.format("should not reach here, volume[uuid:%s]", self.getUuid())); } sql.param("vmUuids", vmUuids); } sql.param("vmStates", Arrays.asList(VmInstanceState.Running, VmInstanceState.Stopped)); List<VmInstanceVO> vms = sql.list(); if (vms.isEmpty()) { return vms; } VolumeInventory vol = getSelfInventory(); for (VolumeGetAttachableVmExtensionPoint ext : pluginRgty.getExtensionList(VolumeGetAttachableVmExtensionPoint.class)) { vms = ext.returnAttachableVms(vol, vms); } return vms; } private boolean volumeIsAttached(final String volumeUuid) { SimpleQuery<VolumeVO> q = dbf.createQuery(VolumeVO.class); q.select(VolumeVO_.vmInstanceUuid); q.add(VolumeVO_.uuid, Op.EQ, volumeUuid); return q.findValue() != null; } private void handle(APIGetDataVolumeAttachableVmMsg msg) { APIGetDataVolumeAttachableVmReply reply = new APIGetDataVolumeAttachableVmReply(); if (volumeIsAttached(msg.getVolumeUuid())) { reply.setInventories(VmInstanceInventory.valueOf(new ArrayList<>())); } else { reply.setInventories(VmInstanceInventory.valueOf(getCandidateVmForAttaching(msg.getSession().getAccountUuid()))); } bus.reply(msg, reply); } private void handle(final APIAttachDataVolumeToVmMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return syncThreadId; } @Override public void run(SyncTaskChain chain) { self = dbf.reload(self); self.setVmInstanceUuid(msg.getVmInstanceUuid()); self = dbf.updateAndRefresh(self); AttachDataVolumeToVmMsg amsg = new AttachDataVolumeToVmMsg(); amsg.setVolume(getSelfInventory()); amsg.setVmInstanceUuid(msg.getVmInstanceUuid()); bus.makeTargetServiceIdByResourceUuid(amsg, VmInstanceConstant.SERVICE_ID, amsg.getVmInstanceUuid()); bus.send(amsg, new CloudBusCallBack(msg, chain) { @Override public void run(MessageReply reply) { final APIAttachDataVolumeToVmEvent evt = new APIAttachDataVolumeToVmEvent(msg.getId()); self = dbf.reload(self); if (reply.isSuccess()) { AttachDataVolumeToVmReply ar = reply.castReply(); self.setVmInstanceUuid(msg.getVmInstanceUuid()); self.setFormat(VolumeFormat.getVolumeFormatByMasterHypervisorType(ar.getHypervisorType()).toString()); self = dbf.updateAndRefresh(self); evt.setInventory(getSelfInventory()); } else { self.setVmInstanceUuid(null); dbf.update(self); evt.setError(reply.getError()); } if (self.isShareable()) { self.setVmInstanceUuid(null); dbf.update(self); } bus.publish(evt); chain.next(); } }); } @Override public String getName() { return msg.getClass().getName(); } }); } private void handle(final APIDetachDataVolumeFromVmMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return syncThreadId; } @Override public void run(SyncTaskChain chain) { DetachDataVolumeFromVmMsg dmsg = new DetachDataVolumeFromVmMsg(); dmsg.setVolume(getSelfInventory()); String vmUuid; if (msg.getVmUuid() != null) { vmUuid = msg.getVmUuid(); } else { vmUuid = getSelfInventory().getVmInstanceUuid(); } dmsg.setVmInstanceUuid(vmUuid); bus.makeTargetServiceIdByResourceUuid(dmsg, VmInstanceConstant.SERVICE_ID, vmUuid); bus.send(dmsg, new CloudBusCallBack(msg, chain) { @Override public void run(MessageReply reply) { APIDetachDataVolumeFromVmEvent evt = new APIDetachDataVolumeFromVmEvent(msg.getId()); if (reply.isSuccess()) { self.setVmInstanceUuid(null); self.setDeviceId(null); self = dbf.updateAndRefresh(self); evt.setInventory(getSelfInventory()); } else { evt.setError(reply.getError()); } bus.publish(evt); chain.next(); } }); } @Override public String getName() { return msg.getClass().getName(); } }); } protected VolumeInventory getSelfInventory() { return VolumeInventory.valueOf(self); } private void delete(boolean forceDelete, final Completion completion) { delete(forceDelete, true, completion); } // don't put this in queue, it will eventually send the VolumeDeletionMsg that will be in queue private void delete(boolean forceDelete, boolean detachBeforeDeleting, final Completion completion) { delete(forceDelete, null, detachBeforeDeleting, completion); } private void delete(boolean forceDelete, VolumeDeletionPolicy deletionPolicy, boolean detachBeforeDeleting, final Completion completion) { final String issuer = VolumeVO.class.getSimpleName(); VolumeDeletionStruct struct = new VolumeDeletionStruct(); struct.setInventory(getSelfInventory()); struct.setDetachBeforeDeleting(detachBeforeDeleting); struct.setDeletionPolicy(deletionPolicy != null ? deletionPolicy.toString() : deletionPolicyMgr.getDeletionPolicy(self.getUuid()).toString()); final List<VolumeDeletionStruct> ctx = list(struct); FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName("delete-data-volume"); if (!forceDelete) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { casf.asyncCascade(CascadeConstant.DELETION_CHECK_CODE, issuer, ctx, new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }).then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { casf.asyncCascade(CascadeConstant.DELETION_DELETE_CODE, issuer, ctx, new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); } else { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { casf.asyncCascade(CascadeConstant.DELETION_FORCE_DELETE_CODE, issuer, ctx, new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); } chain.done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { casf.asyncCascadeFull(CascadeConstant.DELETION_CLEANUP_CODE, issuer, ctx, new NopeCompletion()); completion.success(); } }).error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); } }).start(); } private void handle(APIDeleteDataVolumeMsg msg) { final APIDeleteDataVolumeEvent evt = new APIDeleteDataVolumeEvent(msg.getId()); delete(msg.getDeletionMode() == DeletionMode.Enforcing, new Completion(msg) { @Override public void success() { bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { evt.setError(errf.instantiateErrorCode(SysErrors.DELETE_RESOURCE_ERROR, errorCode)); bus.publish(evt); } }); } private void handle(final VolumeCreateSnapshotMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return syncThreadId; } @Override public void run(final SyncTaskChain chain) { CreateVolumeSnapshotMsg cmsg = new CreateVolumeSnapshotMsg(); cmsg.setName(msg.getName()); cmsg.setDescription(msg.getDescription()); cmsg.setResourceUuid(msg.getResourceUuid()); cmsg.setAccountUuid(msg.getAccountUuid()); cmsg.setVolumeUuid(msg.getVolumeUuid()); bus.makeLocalServiceId(cmsg, VolumeSnapshotConstant.SERVICE_ID); bus.send(cmsg, new CloudBusCallBack(msg, chain) { @Override public void run(MessageReply reply) { VolumeCreateSnapshotReply r = new VolumeCreateSnapshotReply(); if (reply.isSuccess()) { CreateVolumeSnapshotReply creply = (CreateVolumeSnapshotReply) reply; r.setInventory(creply.getInventory()); } else { r.setError(reply.getError()); } bus.reply(msg, r); chain.next(); } }); } @Override public String getName() { return String.format("create-snapshot-for-volume-%s", self.getUuid()); } }); } private void handle(final APICreateVolumeSnapshotMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return syncThreadId; } @Override public void run(final SyncTaskChain chain) { CreateVolumeSnapshotMsg cmsg = new CreateVolumeSnapshotMsg(); cmsg.setName(msg.getName()); cmsg.setDescription(msg.getDescription()); cmsg.setResourceUuid(msg.getResourceUuid()); cmsg.setAccountUuid(msg.getSession().getAccountUuid()); cmsg.setVolumeUuid(msg.getVolumeUuid()); bus.makeLocalServiceId(cmsg, VolumeSnapshotConstant.SERVICE_ID); bus.send(cmsg, new CloudBusCallBack(msg, chain) { @Override public void run(MessageReply reply) { APICreateVolumeSnapshotEvent evt = new APICreateVolumeSnapshotEvent(msg.getId()); if (reply.isSuccess()) { CreateVolumeSnapshotReply creply = (CreateVolumeSnapshotReply) reply; evt.setInventory(creply.getInventory()); tagMgr.createTagsFromAPICreateMessage(msg, creply.getInventory().getUuid(), VolumeSnapshotVO.class.getSimpleName()); } else { evt.setError(reply.getError()); } bus.publish(evt); chain.next(); } }); } @Override public String getName() { return String.format("create-snapshot-for-volume-%s", self.getUuid()); } }); } private void handle(APIChangeVolumeStateMsg msg) { VolumeStateEvent sevt = VolumeStateEvent.valueOf(msg.getStateEvent()); if (sevt == VolumeStateEvent.enable) { self.setState(VolumeState.Enabled); } else { self.setState(VolumeState.Disabled); } self = dbf.updateAndRefresh(self); VolumeInventory inv = VolumeInventory.valueOf(self); APIChangeVolumeStateEvent evt = new APIChangeVolumeStateEvent(msg.getId()); evt.setInventory(inv); bus.publish(evt); } class VolumeSize { long size; long actualSize; } }