package org.zstack.storage.primary; 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.cloudbus.CloudBusListCallBack; import org.zstack.core.cloudbus.EventFacade; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.Q; 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.inventory.InventoryFacade; import org.zstack.core.job.JobQueueFacade; 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.message.APIDeleteMessage; import org.zstack.header.message.APIMessage; import org.zstack.header.message.Message; import org.zstack.header.message.MessageReply; import org.zstack.header.storage.primary.*; import org.zstack.header.storage.primary.PrimaryStorageCanonicalEvent.PrimaryStorageDeletedData; import org.zstack.header.storage.primary.PrimaryStorageCanonicalEvent.PrimaryStorageStatusChangedData; import org.zstack.header.storage.snapshot.ChangeVolumeSnapshotStatusReply; import org.zstack.header.storage.snapshot.VolumeSnapshotConstant; import org.zstack.header.storage.snapshot.VolumeSnapshotReportPrimaryStorageCapacityUsageMsg; import org.zstack.header.storage.snapshot.VolumeSnapshotReportPrimaryStorageCapacityUsageReply; import org.zstack.header.vm.StopVmInstanceMsg; import org.zstack.header.vm.VmInstanceConstant; import org.zstack.header.volume.VolumeConstant; import org.zstack.header.volume.VolumeReportPrimaryStorageCapacityUsageMsg; import org.zstack.header.volume.VolumeReportPrimaryStorageCapacityUsageReply; import org.zstack.header.volume.VolumeType; import org.zstack.utils.DebugUtils; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; import static org.zstack.core.Platform.operr; import javax.persistence.LockModeType; import javax.persistence.Query; import javax.persistence.TypedQuery; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @Configurable(preConstruction = true, autowire = Autowire.BY_TYPE, dependencyCheck = true) public abstract class PrimaryStorageBase extends AbstractPrimaryStorage { private final static CLogger logger = Utils.getLogger(PrimaryStorageBase.class); protected PrimaryStorageVO self; @Autowired protected CloudBus bus; @Autowired protected DatabaseFacade dbf; @Autowired protected JobQueueFacade jobf; @Autowired protected PrimaryStorageExtensionPointEmitter extpEmitter; @Autowired protected InventoryFacade invf; @Autowired protected CascadeFacade casf; @Autowired protected ErrorFacade errf; @Autowired protected ThreadFacade thdf; @Autowired protected PrimaryStorageOverProvisioningManager ratioMgr; @Autowired protected EventFacade evtf; @Autowired protected PrimaryStoragePingTracker tracker; public static class PhysicalCapacityUsage { public long totalPhysicalSize; public long availablePhysicalSize; } public static class ConnectParam { private boolean newAdded; public boolean isNewAdded() { return newAdded; } public void setNewAdded(boolean newAdded) { this.newAdded = newAdded; } } protected abstract void handle(InstantiateVolumeOnPrimaryStorageMsg msg); protected abstract void handle(DeleteVolumeOnPrimaryStorageMsg msg); protected abstract void handle(CreateTemplateFromVolumeOnPrimaryStorageMsg msg); protected abstract void handle(DownloadDataVolumeToPrimaryStorageMsg msg); protected abstract void handle(DeleteBitsOnPrimaryStorageMsg msg); protected abstract void handle(DownloadIsoToPrimaryStorageMsg msg); protected abstract void handle(DeleteIsoFromPrimaryStorageMsg msg); protected abstract void handle(AskVolumeSnapshotCapabilityMsg msg); protected abstract void handle(SyncVolumeSizeOnPrimaryStorageMsg msg); protected abstract void handle(MergeVolumeSnapshotOnPrimaryStorageMsg msg); protected abstract void handle(DeleteSnapshotOnPrimaryStorageMsg msg); protected abstract void handle(RevertVolumeFromSnapshotOnPrimaryStorageMsg msg); protected abstract void handle(ReInitRootVolumeFromTemplateOnPrimaryStorageMsg msg); protected abstract void connectHook(ConnectParam param, Completion completion); protected abstract void pingHook(Completion completion); protected abstract void syncPhysicalCapacity(ReturnValueCompletion<PhysicalCapacityUsage> completion); public PrimaryStorageBase(PrimaryStorageVO self) { this.self = self; } protected PrimaryStorageInventory getSelfInventory() { return PrimaryStorageInventory.valueOf(self); } protected String getSyncId() { return String.format("primaryStorage-%s", self.getUuid()); } @Override public void attachHook(String clusterUuid, Completion completion) { completion.success(); } @Override public void detachHook(String clusterUuid, Completion completion) { completion.success(); } @Override public void deleteHook() { } @Override public void changeStateHook(PrimaryStorageStateEvent evt, PrimaryStorageState nextState) { } @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); } } // if new kind of storage is added , override it protected void checkImageIfNeedToDownload(DownloadIsoToPrimaryStorageMsg msg){ logger.debug("check if image exist in disabled primary storage"); if(self.getState() != PrimaryStorageState.Disabled){ return ; } if( !Q.New(ImageCacheVO.class) .eq(ImageCacheVO_.primaryStorageUuid, self.getUuid()) .eq(ImageCacheVO_.imageUuid, msg.getIsoSpec().getInventory().getUuid()) .isExists()){ throw new OperationFailureException(errf.stringToOperationError( String.format("cannot attach ISO to a primary storage[uuid:%s] which is disabled", self.getUuid()))); } } private void forbidOperationWhenPrimaryStorageDisable(String primaryStorageState) { if (primaryStorageState.equals(PrimaryStorageState.Disabled.toString())) { logger.debug("checking primary storage status whether Disabled"); String error = "Operation is not permitted when primary storage status is 'Disabled', please check primary storage status"; ErrorCode errorCode = new ErrorCode(); errorCode.setCode(PrimaryStorageErrors.ALLOCATE_ERROR.toString()); errorCode.setDetails(error); errorCode.setDescription("Operation is not permitted"); throw new OperationFailureException(errorCode); } } private void forbidOperationWhenPrimaryStorageMaintenance(String primaryStorageState) { logger.debug("checking primary storage status whether Maintenance"); if (primaryStorageState.equals(PrimaryStorageState.Maintenance.toString())) { String error = "Operation is not permitted when primary storage status is 'Maintenance', please check primary storage status"; ErrorCode errorCode = new ErrorCode(); errorCode.setCode(PrimaryStorageErrors.ALLOCATE_ERROR.toString()); errorCode.setDetails(error); errorCode.setDescription("Operation is not permitted"); throw new OperationFailureException(errorCode); } } private void checkPrimaryStatus(Message msg) { if (msg instanceof InstantiateVolumeOnPrimaryStorageMsg) { forbidOperationWhenPrimaryStorageMaintenance(self.getState().toString()); forbidOperationWhenPrimaryStorageDisable(self.getState().toString()); } else if (msg instanceof DeleteVolumeOnPrimaryStorageMsg) { forbidOperationWhenPrimaryStorageMaintenance(self.getState().toString()); } else if (msg instanceof CreateTemplateFromVolumeOnPrimaryStorageMsg) { forbidOperationWhenPrimaryStorageDisable(self.getState().toString()); forbidOperationWhenPrimaryStorageMaintenance(self.getState().toString()); } else if (msg instanceof PrimaryStorageDeletionMsg) { forbidOperationWhenPrimaryStorageMaintenance(self.getState().toString()); forbidOperationWhenPrimaryStorageDisable(self.getState().toString()); } else if (msg instanceof DownloadDataVolumeToPrimaryStorageMsg) { forbidOperationWhenPrimaryStorageDisable(self.getState().toString()); forbidOperationWhenPrimaryStorageMaintenance(self.getState().toString()); } else if (msg instanceof DeleteBitsOnPrimaryStorageMsg) { forbidOperationWhenPrimaryStorageMaintenance(self.getState().toString()); } else if (msg instanceof DeleteIsoFromPrimaryStorageMsg) { forbidOperationWhenPrimaryStorageMaintenance(self.getState().toString()); } else if (msg instanceof AskVolumeSnapshotCapabilityMsg) { forbidOperationWhenPrimaryStorageDisable(self.getState().toString()); forbidOperationWhenPrimaryStorageMaintenance(self.getState().toString()); } else if (msg instanceof MergeVolumeSnapshotOnPrimaryStorageMsg) { forbidOperationWhenPrimaryStorageMaintenance(self.getState().toString()); } else if (msg instanceof DeleteSnapshotOnPrimaryStorageMsg) { forbidOperationWhenPrimaryStorageMaintenance(self.getState().toString()); } else if (msg instanceof RevertVolumeFromSnapshotOnPrimaryStorageMsg) { forbidOperationWhenPrimaryStorageMaintenance(self.getState().toString()); } else if (msg instanceof ReInitRootVolumeFromTemplateOnPrimaryStorageMsg) { forbidOperationWhenPrimaryStorageMaintenance(self.getState().toString()); forbidOperationWhenPrimaryStorageDisable(self.getState().toString()); } } protected void handleLocalMessage(Message msg) { checkPrimaryStatus(msg); if (msg instanceof PrimaryStorageReportPhysicalCapacityMsg) { handle((PrimaryStorageReportPhysicalCapacityMsg) msg); } else if (msg instanceof RecalculatePrimaryStorageCapacityMsg) { handle((RecalculatePrimaryStorageCapacityMsg) msg); } else if (msg instanceof InstantiateVolumeOnPrimaryStorageMsg) { handle((InstantiateVolumeOnPrimaryStorageMsg) msg); } else if (msg instanceof DeleteVolumeOnPrimaryStorageMsg) { handle((DeleteVolumeOnPrimaryStorageMsg) msg); } else if (msg instanceof CreateTemplateFromVolumeOnPrimaryStorageMsg) { handleBase((CreateTemplateFromVolumeOnPrimaryStorageMsg) msg); } else if (msg instanceof PrimaryStorageDeletionMsg) { handle((PrimaryStorageDeletionMsg) msg); } else if (msg instanceof DetachPrimaryStorageFromClusterMsg) { handle((DetachPrimaryStorageFromClusterMsg) msg); } else if (msg instanceof DownloadDataVolumeToPrimaryStorageMsg) { handleBase((DownloadDataVolumeToPrimaryStorageMsg) msg); } else if (msg instanceof DeleteBitsOnPrimaryStorageMsg) { handle((DeleteBitsOnPrimaryStorageMsg) msg); } else if (msg instanceof ConnectPrimaryStorageMsg) { handle((ConnectPrimaryStorageMsg) msg); } else if (msg instanceof DownloadIsoToPrimaryStorageMsg) { handleBase((DownloadIsoToPrimaryStorageMsg) msg); } else if (msg instanceof DeleteIsoFromPrimaryStorageMsg) { handle((DeleteIsoFromPrimaryStorageMsg) msg); } else if (msg instanceof AskVolumeSnapshotCapabilityMsg) { handle((AskVolumeSnapshotCapabilityMsg) msg); } else if (msg instanceof SyncVolumeSizeOnPrimaryStorageMsg) { handle((SyncVolumeSizeOnPrimaryStorageMsg) msg); } else if (msg instanceof PingPrimaryStorageMsg) { handle((PingPrimaryStorageMsg) msg); } else if (msg instanceof ChangePrimaryStorageStatusMsg) { handle((ChangePrimaryStorageStatusMsg) msg); } else if (msg instanceof ReconnectPrimaryStorageMsg) { handle((ReconnectPrimaryStorageMsg) msg); } else if (msg instanceof RevertVolumeFromSnapshotOnPrimaryStorageMsg) { handle((RevertVolumeFromSnapshotOnPrimaryStorageMsg) msg); } else if (msg instanceof ReInitRootVolumeFromTemplateOnPrimaryStorageMsg) { handle((ReInitRootVolumeFromTemplateOnPrimaryStorageMsg) msg); } else if (msg instanceof MergeVolumeSnapshotOnPrimaryStorageMsg) { handle((MergeVolumeSnapshotOnPrimaryStorageMsg) msg); } else if (msg instanceof DeleteSnapshotOnPrimaryStorageMsg) { handle((DeleteSnapshotOnPrimaryStorageMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } protected void handle(RecalculatePrimaryStorageCapacityMsg msg) { RecalculatePrimaryStorageCapacityReply reply = new RecalculatePrimaryStorageCapacityReply(); PrimaryStorageCapacityRecalculator recalculator = new PrimaryStorageCapacityRecalculator(); recalculator.psUuids = Arrays.asList(msg.getPrimaryStorageUuid()); recalculator.recalculate(); bus.reply(msg, reply); } protected void handle(ReconnectPrimaryStorageMsg msg) { ReconnectPrimaryStorageReply reply = new ReconnectPrimaryStorageReply(); doConnect(new ConnectParam(), new Completion(msg) { @Override public void success() { bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } private void handle(ChangePrimaryStorageStatusMsg msg) { changeStatus(PrimaryStorageStatus.valueOf(msg.getStatus())); ChangeVolumeSnapshotStatusReply reply = new ChangeVolumeSnapshotStatusReply(); bus.reply(msg, reply); } private void handle(final PingPrimaryStorageMsg msg) { final PingPrimaryStorageReply reply = new PingPrimaryStorageReply(); pingHook(new Completion(msg) { @Override public void success() { if (self.getStatus() == PrimaryStorageStatus.Disconnected) { doConnect(new ConnectParam(), new NopeCompletion()); } reply.setConnected(true); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { changeStatus(PrimaryStorageStatus.Disconnected); reply.setConnected(false); reply.setError(errorCode); bus.reply(msg, reply); } }); } private void handleBase(DownloadIsoToPrimaryStorageMsg msg) { checkIfBackupStorageAttachedToMyZone(msg.getIsoSpec().getSelectedBackupStorage().getBackupStorageUuid()); checkImageIfNeedToDownload(msg); handle(msg); } private void doConnect(ConnectParam param, final Completion completion) { thdf.chainSubmit(new ChainTask(completion) { @Override public String getSyncSignature() { return String.format("reconnect-primary-storage-%s", self.getUuid()); } @Override public void run(SyncTaskChain chain) { changeStatus(PrimaryStorageStatus.Connecting); connectHook(param, new Completion(chain, completion) { @Override public void success() { RecalculatePrimaryStorageCapacityMsg rmsg = new RecalculatePrimaryStorageCapacityMsg(); rmsg.setPrimaryStorageUuid(self.getUuid()); bus.makeLocalServiceId(rmsg, PrimaryStorageConstant.SERVICE_ID); bus.send(rmsg); self = dbf.reload(self); changeStatus(PrimaryStorageStatus.Connected); logger.debug(String.format("successfully connected primary storage[uuid:%s]", self.getUuid())); tracker.track(self.getUuid()); completion.success(); chain.next(); } @Override public void fail(ErrorCode errorCode) { tracker.track(self.getUuid()); self = dbf.reload(self); changeStatus(PrimaryStorageStatus.Disconnected); logger.debug(String.format("failed to connect primary storage[uuid:%s], %s", self.getUuid(), errorCode)); completion.fail(errorCode); chain.next(); } }); } @Override public String getName() { return getSyncSignature(); } }); } private void handle(final ConnectPrimaryStorageMsg msg) { final ConnectPrimaryStorageReply reply = new ConnectPrimaryStorageReply(); ConnectParam param = new ConnectParam(); param.newAdded = msg.isNewAdded(); doConnect(param, new Completion(msg) { @Override public void success() { reply.setConnected(true); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { if (msg.isNewAdded()) { reply.setError(errorCode); } else { reply.setConnected(false); } bus.reply(msg, reply); } }); } private void handleBase(DownloadDataVolumeToPrimaryStorageMsg msg) { checkIfBackupStorageAttachedToMyZone(msg.getBackupStorageRef().getBackupStorageUuid()); handle(msg); } @Transactional(readOnly = true) private void checkIfBackupStorageAttachedToMyZone(String bsUuid) { String sql = "select bs.uuid" + " from BackupStorageVO bs, BackupStorageZoneRefVO ref" + " where bs.uuid = ref.backupStorageUuid" + " and ref.zoneUuid = :zoneUuid" + " and bs.uuid = :bsUuid"; TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class); q.setParameter("zoneUuid", self.getZoneUuid()); q.setParameter("bsUuid", bsUuid); if (q.getResultList().isEmpty()) { throw new OperationFailureException(operr("backup storage[uuid:%s] is not attached to zone[uuid:%s] the primary storage[uuid:%s] belongs to", bsUuid, self.getZoneUuid(), self.getUuid())); } } private void handleBase(CreateTemplateFromVolumeOnPrimaryStorageMsg msg) { checkIfBackupStorageAttachedToMyZone(msg.getBackupStorageUuid()); handle(msg); } private void handle(final DetachPrimaryStorageFromClusterMsg msg) { final DetachPrimaryStorageFromClusterReply reply = new DetachPrimaryStorageFromClusterReply(); thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return getSyncId(); } @Override public void run(final SyncTaskChain chain) { extpEmitter.beforeDetach(self, msg.getClusterUuid()); detachHook(msg.getClusterUuid(), new Completion(msg, chain) { @Override public void success() { self = dbf.reload(self); extpEmitter.afterDetach(self, msg.getClusterUuid()); logger.debug(String.format("successfully detached primary storage[name: %s, uuid:%s]", self.getName(), self.getUuid())); bus.reply(msg, reply); chain.next(); } @Override public void fail(ErrorCode errorCode) { extpEmitter.failToDetach(self, msg.getClusterUuid()); logger.warn(errorCode.toString()); reply.setError(errf.instantiateErrorCode(PrimaryStorageErrors.DETACH_ERROR, errorCode)); bus.reply(msg, reply); chain.next(); } }); } @Override public String getName() { return String.format("detach-primary-storage-%s-from-%s", self.getUuid(), msg.getClusterUuid()); } }); } private void handle(PrimaryStorageDeletionMsg msg) { PrimaryStorageInventory inv = PrimaryStorageInventory.valueOf(self); extpEmitter.beforeDelete(inv); deleteHook(); extpEmitter.afterDelete(inv); tracker.untrack(self.getUuid()); PrimaryStorageDeletionReply reply = new PrimaryStorageDeletionReply(); bus.reply(msg, reply); } @Transactional private void updateCapacity(long total, long avail) { PrimaryStorageCapacityVO cvo = dbf.getEntityManager().find(PrimaryStorageCapacityVO.class, self.getUuid(), LockModeType.PESSIMISTIC_WRITE); DebugUtils.Assert(cvo != null, String.format("how can there is no PrimaryStorageCapacityVO[uuid:%s]", self.getUuid())); cvo.setTotalPhysicalCapacity(total); cvo.setAvailablePhysicalCapacity(avail); dbf.getEntityManager().merge(cvo); } private void handle(PrimaryStorageReportPhysicalCapacityMsg msg) { updateCapacity(msg.getTotalCapacity(), msg.getAvailableCapacity()); bus.reply(msg, new MessageReply()); } protected void handleApiMessage(APIMessage msg) { if (msg instanceof APIDeletePrimaryStorageMsg) { handle((APIDeletePrimaryStorageMsg) msg); } else if (msg instanceof APIChangePrimaryStorageStateMsg) { handle((APIChangePrimaryStorageStateMsg) msg); } else if (msg instanceof APIAttachPrimaryStorageToClusterMsg) { handle((APIAttachPrimaryStorageToClusterMsg) msg); } else if (msg instanceof APIDetachPrimaryStorageFromClusterMsg) { handle((APIDetachPrimaryStorageFromClusterMsg) msg); } else if (msg instanceof APIReconnectPrimaryStorageMsg) { handle((APIReconnectPrimaryStorageMsg) msg); } else if (msg instanceof APIUpdatePrimaryStorageMsg) { handle((APIUpdatePrimaryStorageMsg) msg); } else if (msg instanceof APISyncPrimaryStorageCapacityMsg) { handle((APISyncPrimaryStorageCapacityMsg) msg); } else if (msg instanceof APICleanUpImageCacheOnPrimaryStorageMsg) { handle((APICleanUpImageCacheOnPrimaryStorageMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } protected void handle(APICleanUpImageCacheOnPrimaryStorageMsg msg) { throw new OperationFailureException(operr("operation not supported")); } private void handle(final APISyncPrimaryStorageCapacityMsg msg) { final APISyncPrimaryStorageCapacityEvent evt = new APISyncPrimaryStorageCapacityEvent(msg.getId()); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("sync-capacity-of-primary-storage-%s", self.getUuid())); chain.then(new ShareFlow() { Long volumeUsage; Long snapshotUsage; Long totalPhysicalSize; Long availablePhysicalSize; @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "sync-capacity-used-by-volumes"; @Override public void run(final FlowTrigger trigger, Map data) { VolumeReportPrimaryStorageCapacityUsageMsg msg = new VolumeReportPrimaryStorageCapacityUsageMsg(); msg.setPrimaryStorageUuid(self.getUuid()); bus.makeLocalServiceId(msg, VolumeConstant.SERVICE_ID); bus.send(msg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { trigger.fail(reply.getError()); return; } VolumeReportPrimaryStorageCapacityUsageReply r = reply.castReply(); volumeUsage = r.getUsedCapacity(); volumeUsage = ratioMgr.calculateByRatio(self.getUuid(), volumeUsage); trigger.next(); } }); } }); flow(new NoRollbackFlow() { String __name__ = "sync-capacity-used-by-volume-snapshots"; @Override public void run(final FlowTrigger trigger, Map data) { VolumeSnapshotReportPrimaryStorageCapacityUsageMsg msg = new VolumeSnapshotReportPrimaryStorageCapacityUsageMsg(); msg.setPrimaryStorageUuid(self.getUuid()); bus.makeLocalServiceId(msg, VolumeSnapshotConstant.SERVICE_ID); bus.send(msg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { trigger.fail(reply.getError()); return; } // note: snapshot size is physical size, // don't calculate over-provisioning here VolumeSnapshotReportPrimaryStorageCapacityUsageReply r = reply.castReply(); snapshotUsage = r.getUsedSize(); trigger.next(); } }); } }); flow(new NoRollbackFlow() { String __name__ = "sync-physical-capacity"; @Override public void run(final FlowTrigger trigger, Map data) { syncPhysicalCapacity(new ReturnValueCompletion<PhysicalCapacityUsage>(trigger) { @Override public void success(PhysicalCapacityUsage returnValue) { totalPhysicalSize = returnValue.totalPhysicalSize; availablePhysicalSize = returnValue.availablePhysicalSize; availablePhysicalSize = availablePhysicalSize < 0 ? 0 : availablePhysicalSize; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { writeToDb(); self = dbf.reload(self); evt.setInventory(getSelfInventory()); bus.publish(evt); } private void writeToDb() { PrimaryStorageCapacityUpdater updater = new PrimaryStorageCapacityUpdater(self.getUuid()); updater.run(new PrimaryStorageCapacityUpdaterRunnable() { @Override public PrimaryStorageCapacityVO call(PrimaryStorageCapacityVO cap) { long avail = cap.getTotalCapacity() - volumeUsage - snapshotUsage; cap.setAvailableCapacity(avail); cap.setAvailablePhysicalCapacity(availablePhysicalSize); cap.setTotalPhysicalCapacity(totalPhysicalSize); return cap; } }); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { evt.setError(errCode); bus.publish(evt); } }); } }).start(); } protected void updatePrimaryStorage(APIUpdatePrimaryStorageMsg msg, ReturnValueCompletion<PrimaryStorageVO> completion) { boolean update = false; if (msg.getName() != null) { self.setName(msg.getName()); update = true; } if (msg.getDescription() != null) { self.setDescription(msg.getDescription()); update = true; } completion.success(update? self : null); } private void handle(APIUpdatePrimaryStorageMsg msg) { APIUpdatePrimaryStorageEvent evt = new APIUpdatePrimaryStorageEvent(msg.getId()); updatePrimaryStorage(msg, new ReturnValueCompletion<PrimaryStorageVO>(msg) { @Override public void success(PrimaryStorageVO vo) { if (vo != null){ self = dbf.updateAndRefresh(vo); } evt.setInventory(getSelfInventory()); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { evt.setError(errorCode); bus.publish(evt); } }); } protected void changeStatus(PrimaryStorageStatus status) { if (status == self.getStatus()) { return; } PrimaryStorageStatus oldStatus = self.getStatus(); self.setStatus(status); self = dbf.updateAndRefresh(self); PrimaryStorageStatusChangedData d = new PrimaryStorageStatusChangedData(); d.setInventory(PrimaryStorageInventory.valueOf(self)); d.setPrimaryStorageUuid(self.getUuid()); d.setOldStatus(oldStatus.toString()); d.setNewStatus(status.toString()); evtf.fire(PrimaryStorageCanonicalEvent.PRIMARY_STORAGE_STATUS_CHANGED_PATH, d); logger.debug(String.format("the primary storage[uuid:%s, name:%s] changed status from %s to %s", self.getUuid(), self.getName(), oldStatus, status)); } protected void handle(APIReconnectPrimaryStorageMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return getSyncId(); } @Override public void run(SyncTaskChain chain) { final APIReconnectPrimaryStorageEvent evt = new APIReconnectPrimaryStorageEvent(msg.getId()); doConnect(new ConnectParam(), new Completion(msg, chain) { @Override public void success() { evt.setInventory(getSelfInventory()); bus.publish(evt); chain.next(); } @Override public void fail(ErrorCode errorCode) { evt.setError(errorCode); bus.publish(evt); chain.next(); } }); } @Override public String getName() { return "reconnect-primary-storage"; } }); } // don't use chainTask for this method, the sub-sequential DetachPrimaryStorageFromClusterMsg // is in the queue protected void handle(final APIDetachPrimaryStorageFromClusterMsg msg) { final APIDetachPrimaryStorageFromClusterEvent evt = new APIDetachPrimaryStorageFromClusterEvent(msg.getId()); try { extpEmitter.preDetach(self, msg.getClusterUuid()); } catch (PrimaryStorageException e) { throw new OperationFailureException(errf.instantiateErrorCode(PrimaryStorageErrors.DETACH_ERROR, e.getMessage())); } // if not, HA will allocate wrong host, rollback when API fail SimpleQuery<PrimaryStorageClusterRefVO> q = dbf.createQuery(PrimaryStorageClusterRefVO.class); q.add(PrimaryStorageClusterRefVO_.clusterUuid, Op.EQ, msg.getClusterUuid()); q.add(PrimaryStorageClusterRefVO_.primaryStorageUuid, Op.EQ, msg.getPrimaryStorageUuid()); List<PrimaryStorageClusterRefVO> refs = q.list(); dbf.removeCollection(refs, PrimaryStorageClusterRefVO.class); String issuer = PrimaryStorageVO.class.getSimpleName(); List<PrimaryStorageDetachStruct> ctx = new ArrayList<>(); PrimaryStorageDetachStruct struct = new PrimaryStorageDetachStruct(); struct.setClusterUuid(msg.getClusterUuid()); struct.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); ctx.add(struct); casf.asyncCascade(PrimaryStorageConstant.PRIMARY_STORAGE_DETACH_CODE, issuer, ctx, new Completion(msg) { @Override public void success() { self = dbf.reload(self); evt.setInventory(PrimaryStorageInventory.valueOf(self)); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { //has removed RefVO before, roll back dbf.updateAndRefresh(refs.get(0)); evt.setError(errorCode); bus.publish(evt); } }); } protected void handle(final APIAttachPrimaryStorageToClusterMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return getSyncId(); } @Override public void run(final SyncTaskChain chain) { attachCluster(msg, new NoErrorCompletion(msg, chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("attach-primary-storage-%s-to-cluster-%s", self.getUuid(), msg.getClusterUuid()); } }); } private void attachCluster(final APIAttachPrimaryStorageToClusterMsg msg, final NoErrorCompletion completion) { final APIAttachPrimaryStorageToClusterEvent evt = new APIAttachPrimaryStorageToClusterEvent(msg.getId()); try { extpEmitter.preAttach(self, msg.getClusterUuid()); } catch (PrimaryStorageException pe) { evt.setError(errf.instantiateErrorCode(PrimaryStorageErrors.ATTACH_ERROR, pe.getMessage())); bus.publish(evt); completion.done(); return; } extpEmitter.beforeAttach(self, msg.getClusterUuid()); attachHook(msg.getClusterUuid(), new Completion(msg, completion) { @Override public void success() { PrimaryStorageClusterRefVO ref = new PrimaryStorageClusterRefVO(); ref.setClusterUuid(msg.getClusterUuid()); ref.setPrimaryStorageUuid(self.getUuid()); dbf.persist(ref); self = dbf.reload(self); extpEmitter.afterAttach(self, msg.getClusterUuid()); PrimaryStorageInventory pinv = (PrimaryStorageInventory) invf.valueOf(self); evt.setInventory(pinv); logger.debug(String.format("successfully attached primary storage[name:%s, uuid:%s]", pinv.getName(), pinv.getUuid())); bus.publish(evt); completion.done(); } @Override public void fail(ErrorCode errorCode) { extpEmitter.failToAttach(self, msg.getClusterUuid()); evt.setError(errf.instantiateErrorCode(PrimaryStorageErrors.ATTACH_ERROR, errorCode)); bus.publish(evt); completion.done(); } }); } private void stopAllVms(List<String> vmUuids) { final List<StopVmInstanceMsg> msgs = new ArrayList<StopVmInstanceMsg>(); for (String vmUuid : vmUuids) { StopVmInstanceMsg msg = new StopVmInstanceMsg(); msg.setVmInstanceUuid(vmUuid); bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, vmUuid); msgs.add(msg); } bus.send(msgs, new CloudBusListCallBack(null) { @Override public void run(List<MessageReply> replies) { StringBuilder sb = new StringBuilder(); boolean success = true; for (MessageReply r : replies) { if (!r.isSuccess()) { StopVmInstanceMsg msg = msgs.get(replies.indexOf(r)); String err = String.format("\nfailed to stop vm[uuid:%s] on primary storage[uuid:%s], %s", msg.getVmInstanceUuid(), self.getUuid(), r.getError()); sb.append(err); success = false; } } if (!success) { logger.warn(sb.toString()); } } }); } protected void handle(APIChangePrimaryStorageStateMsg msg) { APIChangePrimaryStorageStateEvent evt = new APIChangePrimaryStorageStateEvent(msg.getId()); PrimaryStorageState currState = self.getState(); PrimaryStorageStateEvent event = PrimaryStorageStateEvent.valueOf(msg.getStateEvent()); PrimaryStorageState nextState = AbstractPrimaryStorage.getNextState(currState, event); try { extpEmitter.preChange(self, event); } catch (PrimaryStorageException e) { evt.setError(errf.instantiateErrorCode(SysErrors.CHANGE_RESOURCE_STATE_ERROR, e.getMessage())); bus.publish(evt); return; } extpEmitter.beforeChange(self, event); if (PrimaryStorageStateEvent.maintain == event) { logger.warn(String.format("Primary Storage %s will enter maintenance mode, ignore unknown status VMs", msg.getPrimaryStorageUuid())); List<String> vmUuids = SQL.New("select vm.uuid from VmInstanceVO vm, VolumeVO vol" + " where vol.primaryStorageUuid =:uuid and vol.vmInstanceUuid = vm.uuid and vol.type = :volType", String.class) .param("uuid", self.getUuid()).param("volType", VolumeType.Root).list(); if ( vmUuids.size() != 0 ) { stopAllVms(vmUuids); } } changeStateHook(event, nextState); self.setState(nextState); self = dbf.updateAndRefresh(self); extpEmitter.afterChange(self, event, currState); evt.setInventory(PrimaryStorageInventory.valueOf(self)); bus.publish(evt); } protected void handle(APIDeletePrimaryStorageMsg msg) { final APIDeletePrimaryStorageEvent evt = new APIDeletePrimaryStorageEvent(msg.getId()); final String issuer = PrimaryStorageVO.class.getSimpleName(); final List<PrimaryStorageInventory> ctx = PrimaryStorageInventory.valueOf(Arrays.asList(self)); self.setState(PrimaryStorageState.Deleting); self = dbf.updateAndRefresh(self); FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("delete-primary-storage-%s", msg.getUuid())); if (msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Permissive) { 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); } }); } }); } // Due to issue #1412, deleting PS asynchronously might leave VmInstanceEO in // database. Since eoCleanup() could be called before deleting VmInstanceVO. chain.then(new NoRollbackFlow() { @Override public void run(FlowTrigger trigger, Map data) { casf.asyncCascadeFull(CascadeConstant.DELETION_CLEANUP_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(msg) { @Override public void handle(Map data) { bus.publish(evt); PrimaryStorageDeletedData d = new PrimaryStorageDeletedData(); d.setPrimaryStorageUuid(self.getUuid()); d.setInventory(PrimaryStorageInventory.valueOf(self)); evtf.fire(PrimaryStorageCanonicalEvent.PRIMARY_STORAGE_DELETED_PATH, d); } }).error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { evt.setError(errf.instantiateErrorCode(SysErrors.DELETE_RESOURCE_ERROR, errCode)); bus.publish(evt); } }).start(); } // don't attach any cluster public boolean isUnmounted() { long count = Q.New(PrimaryStorageClusterRefVO.class) .eq(PrimaryStorageClusterRefVO_.primaryStorageUuid, this.self.getUuid()).count(); return count == 0; } }