package org.zstack.storage.primary.nfs; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.zstack.core.asyncbatch.AsyncBatchRunner; import org.zstack.core.asyncbatch.LoopAsyncBatch; import org.zstack.core.cloudbus.AutoOffEventCallback; import org.zstack.core.cloudbus.CloudBusCallBack; import org.zstack.core.cloudbus.EventFacade; import org.zstack.core.db.Q; import org.zstack.core.db.SimpleQuery; import org.zstack.core.db.SimpleQuery.Op; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.core.notification.N; import org.zstack.core.thread.SyncTask; import org.zstack.core.workflow.FlowChainBuilder; import org.zstack.core.workflow.ShareFlow; import org.zstack.header.cluster.ClusterVO; import org.zstack.header.cluster.ClusterVO_; import org.zstack.header.core.*; import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.host.*; import org.zstack.header.host.HostCanonicalEvents.HostStatusChangedData; import org.zstack.header.image.ImageConstant.ImageMediaType; import org.zstack.header.image.ImageInventory; import org.zstack.header.message.Message; import org.zstack.header.message.MessageReply; import org.zstack.header.storage.backup.BackupStorageInventory; import org.zstack.header.storage.backup.BackupStorageType; import org.zstack.header.storage.backup.BackupStorageVO; import org.zstack.header.storage.backup.BackupStorageVO_; import org.zstack.header.storage.primary.*; import org.zstack.header.storage.primary.VolumeSnapshotCapability.VolumeSnapshotArrangementType; import org.zstack.header.storage.snapshot.VolumeSnapshotConstant; import org.zstack.header.storage.snapshot.VolumeSnapshotInventory; import org.zstack.header.vm.VmInstanceSpec.ImageSpec; import org.zstack.header.vm.VmInstanceState; import org.zstack.header.vm.VmInstanceVO; import org.zstack.header.vm.VmInstanceVO_; import org.zstack.header.volume.VolumeConstant; import org.zstack.header.volume.VolumeFormat; import org.zstack.header.volume.VolumeInventory; import org.zstack.header.volume.VolumeVO; import org.zstack.kvm.KVMConstant; import org.zstack.storage.primary.PrimaryStorageBase; import org.zstack.storage.primary.PrimaryStorageCapacityUpdater; import org.zstack.storage.primary.PrimaryStoragePathMaker; import org.zstack.utils.CollectionUtils; import org.zstack.utils.DebugUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.Function; import org.zstack.utils.logging.CLogger; import org.zstack.utils.path.PathUtil; import static org.zstack.core.Platform.err; import static org.zstack.core.Platform.operr; import javax.persistence.Tuple; import javax.persistence.TypedQuery; import java.io.File; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; public class NfsPrimaryStorage extends PrimaryStorageBase { private static final CLogger logger = Utils.getLogger(NfsPrimaryStorage.class); @Autowired private NfsPrimaryStorageFactory factory; @Autowired private ErrorFacade errf; @Autowired private NfsPrimaryStorageManager nfsMgr; @Autowired private EventFacade evtf; @Autowired private NfsPrimaryStorageImageCacheCleaner imageCacheCleaner; public NfsPrimaryStorage(PrimaryStorageVO vo) { super(vo); } @Override protected void handleLocalMessage(Message msg) { if (msg instanceof PrimaryStorageRemoveCachedImageMsg) { handle((PrimaryStorageRemoveCachedImageMsg) msg); } else if (msg instanceof TakeSnapshotMsg) { handle((TakeSnapshotMsg) msg); } else if (msg instanceof BackupVolumeSnapshotFromPrimaryStorageToBackupStorageMsg) { handle((BackupVolumeSnapshotFromPrimaryStorageToBackupStorageMsg) msg); } else if (msg instanceof CreateVolumeFromVolumeSnapshotOnPrimaryStorageMsg) { handle((CreateVolumeFromVolumeSnapshotOnPrimaryStorageMsg) msg); } else if (msg instanceof CreateTemporaryVolumeFromSnapshotMsg) { handle((CreateTemporaryVolumeFromSnapshotMsg) msg); } else if (msg instanceof UploadBitsToBackupStorageMsg) { handle((UploadBitsToBackupStorageMsg) msg); } else if (msg instanceof GetVolumeRootImageUuidFromPrimaryStorageMsg) { handle((GetVolumeRootImageUuidFromPrimaryStorageMsg) msg); } else if (msg instanceof DeleteImageCacheOnPrimaryStorageMsg) { handle((DeleteImageCacheOnPrimaryStorageMsg) msg); } else if (msg instanceof NfsRecalculatePrimaryStorageCapacityMsg) { handle((NfsRecalculatePrimaryStorageCapacityMsg) msg); } else { super.handleLocalMessage(msg); } } protected void updateMountPoint(PrimaryStorageVO vo, String newUrl, Completion completion) { String oldUrl = self.getUrl(); SimpleQuery<PrimaryStorageClusterRefVO> q = dbf.createQuery(PrimaryStorageClusterRefVO.class); q.select(PrimaryStorageClusterRefVO_.clusterUuid); q.add(PrimaryStorageClusterRefVO_.primaryStorageUuid, Op.EQ, self.getUuid()); List<String> cuuids = q.listValue(); if (cuuids.isEmpty()) { vo.setUrl(newUrl); dbf.update(vo); completion.success(); } PrimaryStorageInventory psinv = getSelfInventory(); new LoopAsyncBatch<String>(completion) { @Override protected Collection<String> collect() { return cuuids; } @Override protected AsyncBatchRunner forEach(String item) { return new AsyncBatchRunner() { @Override public void run(NoErrorCompletion completion) { NfsPrimaryStorageBackend bkd = getBackendByClusterUuid(item); bkd.updateMountPoint(psinv, item, oldUrl, newUrl, new Completion(completion) { @Override public void success() { completion.done(); } @Override public void fail(ErrorCode errorCode) { logger.warn(String.format("failed to update the nfs[uuid:%s, name:%s] mount point" + " from %s to %s in the cluster[uuid:%s], %s", self.getUuid(), self.getName(), oldUrl, newUrl, item, errorCode)); errors.add(errorCode); completion.done(); } }); } }; } @Override protected void done() { if (errors.size() == cuuids.size()){ completion.fail(errors.get(0)); }else { vo.setUrl(newUrl); dbf.update(vo); completion.success(); } } }.start(); } @Override 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; } if (msg.getUrl() != null && !self.getUrl().equals(msg.getUrl())){ updateMountPoint(self, msg.getUrl(), new Completion(completion) { @Override public void success() { self.setUrl(msg.getUrl()); completion.success(self); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); return; } completion.success(update? self: null); } @Override protected void handle(APICleanUpImageCacheOnPrimaryStorageMsg msg) { APICleanUpImageCacheOnPrimaryStorageEvent evt = new APICleanUpImageCacheOnPrimaryStorageEvent(msg.getId()); imageCacheCleaner.cleanup(msg.getUuid()); bus.publish(evt); } private void handle(final DeleteImageCacheOnPrimaryStorageMsg msg) { NfsPrimaryStorageBackend bkd = getUsableBackend(); if (bkd == null) { throw new OperationFailureException(operr("cannot find usable backend")); } DeleteBitsOnPrimaryStorageMsg dmsg = new DeleteBitsOnPrimaryStorageMsg(); dmsg.setFolder(true); dmsg.setHypervisorType(bkd.getHypervisorType().toString()); dmsg.setInstallPath(new File(msg.getInstallPath()).getParent()); dmsg.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); bus.makeTargetServiceIdByResourceUuid(dmsg, PrimaryStorageConstant.SERVICE_ID, msg.getPrimaryStorageUuid()); bus.send(dmsg, new CloudBusCallBack(msg) { @Override public void run(MessageReply reply) { DeleteImageCacheOnPrimaryStorageReply r = new DeleteImageCacheOnPrimaryStorageReply(); r.setSuccess(reply.isSuccess()); if (reply.getError() != null) { r.setError(reply.getError()); } bus.reply(msg, r); } }); } private void handle(final GetVolumeRootImageUuidFromPrimaryStorageMsg msg) { NfsPrimaryStorageBackend bkd = getUsableBackend(); if (bkd == null) { throw new OperationFailureException(operr("no usable backend found")); } bkd.handle(getSelfInventory(), msg, new ReturnValueCompletion<GetVolumeRootImageUuidFromPrimaryStorageReply>(msg) { @Override public void success(GetVolumeRootImageUuidFromPrimaryStorageReply reply) { bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { GetVolumeRootImageUuidFromPrimaryStorageReply reply = new GetVolumeRootImageUuidFromPrimaryStorageReply(); reply.setError(errorCode); bus.reply(msg, reply); } }); } private void handle(final UploadBitsToBackupStorageMsg msg) { NfsPrimaryStorageBackend bkd = getBackend(HypervisorType.valueOf(msg.getHypervisorType())); bkd.handle(getSelfInventory(), msg, new ReturnValueCompletion<UploadBitsToBackupStorageReply>(msg) { @Override public void success(UploadBitsToBackupStorageReply returnValue) { bus.reply(msg, returnValue); } @Override public void fail(ErrorCode errorCode) { UploadBitsToBackupStorageReply reply = new UploadBitsToBackupStorageReply(); reply.setError(errorCode); bus.reply(msg, reply); } }); } private void handle(final CreateTemporaryVolumeFromSnapshotMsg msg) { NfsPrimaryStorageBackend bkd = getBackend(HypervisorType.valueOf(msg.getHypervisorType())); bkd.handle(getSelfInventory(), msg, new ReturnValueCompletion<CreateTemporaryVolumeFromSnapshotReply>(msg) { @Override public void success(CreateTemporaryVolumeFromSnapshotReply returnValue) { bus.reply(msg, returnValue); } @Override public void fail(ErrorCode errorCode) { CreateTemporaryVolumeFromSnapshotReply reply = new CreateTemporaryVolumeFromSnapshotReply(); reply.setError(errorCode); bus.reply(msg, reply); } }); } @Override public void deleteHook() { List<String> cuuids = CollectionUtils.transformToList(self.getAttachedClusterRefs(), new Function<String, PrimaryStorageClusterRefVO>() { @Override public String call(PrimaryStorageClusterRefVO arg) { return arg.getClusterUuid(); } }); for (String cuuid : cuuids) { NfsPrimaryStorageBackend backend = getBackendByClusterUuid(cuuid); try { backend.detachFromCluster(getSelfInventory(), cuuid); } catch (NfsPrimaryStorageException e) { logger.warn(String.format("failed to detach the nfs primary storage[uuid: %s] from the cluster[uuid: %s]", self.getUuid(), cuuid)); } } } protected void handle(final MergeVolumeSnapshotOnPrimaryStorageMsg msg) { final MergeVolumeSnapshotOnPrimaryStorageReply reply = new MergeVolumeSnapshotOnPrimaryStorageReply(); VolumeSnapshotInventory snapshot = msg.getFrom(); VolumeInventory volume = msg.getTo(); final NfsPrimaryStorageBackend backend = getBackend(nfsMgr.findHypervisorTypeByImageFormatAndPrimaryStorageUuid(snapshot.getFormat(), self.getUuid())); backend.mergeSnapshotToVolume(getSelfInventory(), snapshot, volume, msg.isFullRebase(), 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(final CreateVolumeFromVolumeSnapshotOnPrimaryStorageMsg msg) { final NfsPrimaryStorageBackend backend = getBackend(nfsMgr.findHypervisorTypeByImageFormatAndPrimaryStorageUuid(msg.getSnapshot().getFormat(), self.getUuid())); backend.handle(getSelfInventory(), msg, new ReturnValueCompletion<CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply>(msg) { @Override public void success(CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply returnValue) { bus.reply(msg, returnValue); } @Override public void fail(ErrorCode errorCode) { CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply reply = new CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply(); reply.setError(errorCode); bus.reply(msg, reply); } }); } private void handle(final BackupVolumeSnapshotFromPrimaryStorageToBackupStorageMsg msg) { BackupStorageInventory bs = msg.getBackupStorage(); VolumeSnapshotInventory sinv = msg.getSnapshot(); NfsPrimaryToBackupStorageMediator mediator = factory.getPrimaryToBackupStorageMediator( BackupStorageType.valueOf(bs.getType()), nfsMgr.findHypervisorTypeByImageFormatAndPrimaryStorageUuid(sinv.getFormat(), self.getUuid()) ); final BackupVolumeSnapshotFromPrimaryStorageToBackupStorageReply reply = new BackupVolumeSnapshotFromPrimaryStorageToBackupStorageReply(); final String installPath = mediator.makeVolumeSnapshotInstallPath(bs.getUuid(), sinv.getUuid()); mediator.uploadBits(null, getSelfInventory(), bs, installPath, sinv.getPrimaryStorageInstallPath(), new ReturnValueCompletion<String>(msg) { @Override public void success(String installPath) { reply.setBackupStorageInstallPath(installPath); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } protected void handle(final RevertVolumeFromSnapshotOnPrimaryStorageMsg msg) { final RevertVolumeFromSnapshotOnPrimaryStorageReply reply = new RevertVolumeFromSnapshotOnPrimaryStorageReply(); HostInventory destHost = factory.getConnectedHostForOperation(PrimaryStorageInventory.valueOf(self)); if (destHost == null) { reply.setError(operr("no host in Connected status to which nfs primary storage[uuid:%s, name:%s] attached" + " found to revert volume[uuid:%s] to snapshot[uuid:%s, name:%s]", self.getUuid(), self.getName(), msg.getVolume().getUuid(), msg.getSnapshot().getUuid(), msg.getSnapshot().getName())); bus.reply(msg, reply); return; } NfsPrimaryStorageBackend bkd = getBackend(nfsMgr.findHypervisorTypeByImageFormatAndPrimaryStorageUuid(msg.getSnapshot().getFormat(), self.getUuid())); bkd.revertVolumeFromSnapshot(msg.getSnapshot(), msg.getVolume(), destHost, new ReturnValueCompletion<String>(msg) { @Override public void success(String returnValue) { reply.setNewVolumeInstallPath(returnValue); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } protected void handle(final ReInitRootVolumeFromTemplateOnPrimaryStorageMsg msg) { final ReInitRootVolumeFromTemplateOnPrimaryStorageReply reply = new ReInitRootVolumeFromTemplateOnPrimaryStorageReply(); HostInventory destHost = factory.getConnectedHostForOperation(PrimaryStorageInventory.valueOf(self)); if (destHost == null) { reply.setError(operr("no host in Connected status to which nfs primary storage[uuid:%s, name:%s] attached" + " found to revert volume[uuid:%s] to image[uuid:%s]", self.getUuid(), self.getName(), msg.getVolume().getUuid(), msg.getVolume().getRootImageUuid())); bus.reply(msg, reply); return; } NfsPrimaryStorageBackend bkd = getBackend(nfsMgr.findHypervisorTypeByImageFormatAndPrimaryStorageUuid( VolumeConstant.VOLUME_FORMAT_QCOW2, self.getUuid()) ); bkd.resetRootVolumeFromImage(msg.getVolume(), destHost, new ReturnValueCompletion<String>(msg) { @Override public void success(String returnValue) { reply.setNewVolumeInstallPath(returnValue); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } @Override protected void handle(final DeleteSnapshotOnPrimaryStorageMsg msg) { final DeleteSnapshotOnPrimaryStorageReply reply = new DeleteSnapshotOnPrimaryStorageReply(); final VolumeSnapshotInventory sinv = msg.getSnapshot(); final NfsPrimaryStorageBackend bkd = getBackend(nfsMgr.findHypervisorTypeByImageFormatAndPrimaryStorageUuid(sinv.getFormat(), self.getUuid())); bkd.delete(getSelfInventory(), sinv.getPrimaryStorageInstallPath(), new Completion(msg) { @Override public void success() { bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { NfsDeleteVolumeSnapshotGC gc = new NfsDeleteVolumeSnapshotGC(); gc.NAME = String.format("gc-nfs-%s-snapshot-%s", self.getUuid(), sinv.getUuid()); gc.snapshot = sinv; gc.primaryStorageUuid = self.getUuid(); gc.hypervisorType = bkd.getHypervisorType().toString(); gc.submit(NfsPrimaryStorageGlobalConfig.GC_INTERVAL.value(Long.class), TimeUnit.SECONDS); N.New(PrimaryStorageVO.class, self.getUuid()).warn_("NFS primary storage[uuid:%s] failed to delete a volume snapshot[uuid:%s], %s. A GC" + " job[uuid:%s] is scheduled to cleanup it in the interval of %s seconds", self.getUuid(), sinv.getUuid(), errorCode, NfsPrimaryStorageGlobalConfig.GC_INTERVAL.value(Long.class)); bus.reply(msg, reply); } }); } private void handle(final TakeSnapshotMsg msg) { final TakeSnapshotReply reply = new TakeSnapshotReply(); String volumeUuid = msg.getStruct().getCurrent().getVolumeUuid(); VolumeVO vol = dbf.findByUuid(volumeUuid, VolumeVO.class); String huuid = null; if (vol.getVmInstanceUuid() != null) { SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class); q.select(VmInstanceVO_.state, VmInstanceVO_.hostUuid, VmInstanceVO_.lastHostUuid); q.add(VmInstanceVO_.uuid, Op.EQ, vol.getVmInstanceUuid()); Tuple t = q.findTuple(); VmInstanceState vmState = t.get(0, VmInstanceState.class); String hostUuid = t.get(1, String.class); String lastHostUuid = t.get(2, String.class); if (vmState != VmInstanceState.Running && vmState != VmInstanceState.Stopped) { ErrorCode err = operr("vm[uuid:%s] is not Running or Stopped, current state is %s", vol.getVmInstanceUuid(), vmState); reply.setError(err); bus.reply(msg, reply); return; } huuid = VmInstanceState.Running == vmState ? hostUuid : lastHostUuid; } else { HostInventory host = factory.getConnectedHostForOperation(getSelfInventory()); huuid = host.getUuid(); } VolumeInventory volInv = VolumeInventory.valueOf(vol); TakeSnapshotOnHypervisorMsg hmsg = new TakeSnapshotOnHypervisorMsg(); hmsg.setHostUuid(huuid); hmsg.setVmUuid(vol.getVmInstanceUuid()); hmsg.setVolume(volInv); hmsg.setSnapshotName(msg.getStruct().getCurrent().getUuid()); hmsg.setFullSnapshot(msg.getStruct().isFullSnapshot()); final String installPath = NfsPrimaryStorageKvmHelper.makeKvmSnapshotInstallPath(getSelfInventory(), volInv, msg.getStruct().getCurrent()); hmsg.setInstallPath(installPath); bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, huuid); bus.send(hmsg, new CloudBusCallBack(msg) { @Override public void run(MessageReply ret) { if (ret.isSuccess()) { TakeSnapshotOnHypervisorReply treply = (TakeSnapshotOnHypervisorReply) ret; VolumeSnapshotInventory inv = msg.getStruct().getCurrent(); inv.setSize(treply.getSize()); inv.setPrimaryStorageUuid(self.getUuid()); inv.setPrimaryStorageInstallPath(treply.getSnapshotInstallPath()); inv.setType(VolumeSnapshotConstant.HYPERVISOR_SNAPSHOT_TYPE.toString()); reply.setNewVolumeInstallPath(treply.getNewVolumeInstallPath()); reply.setInventory(inv); } else { reply.setError(ret.getError()); } bus.reply(msg, reply); } }); } private void handle(PrimaryStorageRemoveCachedImageMsg msg) { if (self.getAttachedClusterRefs().isEmpty()) { PrimaryStorageRemoveCachedImageReply reply = new PrimaryStorageRemoveCachedImageReply(); reply.setError(operr("primary storage[uuid:%s] doesn't attach to any cluster", self.getUuid())); bus.reply(msg, reply); return; } PrimaryStorageClusterRefVO ref = self.getAttachedClusterRefs().iterator().next(); ClusterVO cluster = dbf.findByUuid(ref.getClusterUuid(), ClusterVO.class); getBackend(HypervisorType.valueOf(cluster.getHypervisorType())).deleteImageCache(msg.getInventory()); } @Transactional(readOnly = true) private NfsPrimaryStorageBackend getUsableBackend() { List<String> cuuids = CollectionUtils.transformToList(self.getAttachedClusterRefs(), new Function<String, PrimaryStorageClusterRefVO>() { @Override public String call(PrimaryStorageClusterRefVO arg) { return arg.getClusterUuid(); } }); if (cuuids.isEmpty()) { return null; } String sql = "select cluster.uuid from ClusterVO cluster, HostVO host where cluster.uuid = host.clusterUuid and host.status = :hostStatus and cluster.uuid in (:cuuids)"; TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class); q.setParameter("hostStatus", HostStatus.Connected); q.setParameter("cuuids", cuuids); cuuids = q.getResultList(); if (cuuids.isEmpty()) { return null; } return getBackendByClusterUuid(cuuids.get(0)); } private NfsPrimaryStorageBackend getBackendByClusterUuid(String clusterUuid) { SimpleQuery<ClusterVO> query = dbf.createQuery(ClusterVO.class); query.select(ClusterVO_.hypervisorType); query.add(ClusterVO_.uuid, Op.EQ, clusterUuid); String hvType = query.findValue(); return getBackend(HypervisorType.valueOf(hvType)); } private NfsPrimaryStorageBackend getBackend(HypervisorType hvType) { return factory.getHypervisorBackend(hvType); } @Override public void attachHook(String clusterUuid, Completion completion) { NfsPrimaryStorageBackend backend = getBackendByClusterUuid(clusterUuid); backend.attachToCluster(PrimaryStorageInventory.valueOf(self), clusterUuid, new ReturnValueCompletion<Boolean>(completion) { @Override public void success(Boolean ret) { if(ret){ changeStatus(PrimaryStorageStatus.Connected); } completion.success(); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override public void detachHook(String clusterUuid, Completion completion) { NfsPrimaryStorageBackend backend = getBackendByClusterUuid(clusterUuid); try { backend.detachFromCluster(PrimaryStorageInventory.valueOf(self), clusterUuid); completion.success(); } catch (NfsPrimaryStorageException e) { completion.fail(errf.throwableToOperationError(e)); } } private void handle(final InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg msg) throws PrimaryStorageException { final InstantiateVolumeOnPrimaryStorageReply reply = new InstantiateVolumeOnPrimaryStorageReply(); final ImageSpec ispec = msg.getTemplateSpec(); SimpleQuery<BackupStorageVO> q = dbf.createQuery(BackupStorageVO.class); q.select(BackupStorageVO_.type); q.add(BackupStorageVO_.uuid, Op.EQ, ispec.getSelectedBackupStorage().getBackupStorageUuid()); final String bsType = q.findValue(); final VolumeInventory volume = msg.getVolume(); final ImageInventory image = ispec.getInventory(); if (ImageMediaType.RootVolumeTemplate.toString().equals(image.getMediaType())) { FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("create-root-volume-from-image-%s", image.getUuid())); chain.then(new ShareFlow() { PrimaryStorageInventory primaryStorage = getSelfInventory(); ImageCacheInventory imageCache; String volumeInstallPath; @Override public void setup() { flow(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { NfsDownloadImageToCacheJob job = new NfsDownloadImageToCacheJob(); job.setImage(ispec); job.setPrimaryStorage(primaryStorage); jobf.execute(NfsPrimaryStorageKvmHelper.makeDownloadImageJobName(image, primaryStorage), NfsPrimaryStorageKvmHelper.makeJobOwnerName(primaryStorage), job, new ReturnValueCompletion<ImageCacheInventory>(trigger) { @Override public void success(ImageCacheInventory returnValue) { imageCache = returnValue; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }, ImageCacheInventory.class); } }); flow(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { NfsPrimaryToBackupStorageMediator mediator = factory.getPrimaryToBackupStorageMediator( BackupStorageType.valueOf(bsType), nfsMgr.findHypervisorTypeByImageFormatAndPrimaryStorageUuid(image.getFormat(), self.getUuid()) ); mediator.createVolumeFromImageCache(primaryStorage, imageCache, volume, new ReturnValueCompletion<String>(trigger) { @Override public void success(String returnValue) { volumeInstallPath = returnValue; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { volume.setInstallPath(volumeInstallPath); volume.setFormat(image.getFormat()); reply.setVolume(volume); bus.reply(msg, reply); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { reply.setError(errCode); bus.reply(msg, reply); } }); } }).start(); } else { createEmptyVolume(msg); } } private void createEmptyVolume(final InstantiateVolumeOnPrimaryStorageMsg msg) { NfsPrimaryStorageBackend backend; if (msg.getDestHost() != null) { backend = getBackend(HypervisorType.valueOf(msg.getDestHost().getHypervisorType())); } else { backend = getUsableBackend(); if (backend == null) { throw new OperationFailureException(operr("the NFS primary storage[uuid:%s, name:%s] cannot find any usable host to" + " create the data volume[uuid:%s, name:%s]", self.getUuid(), self.getName(), msg.getVolume().getUuid(), msg.getVolume().getName())); } } VolumeInventory vol = msg.getVolume(); final InstantiateVolumeOnPrimaryStorageReply reply = new InstantiateVolumeOnPrimaryStorageReply(); backend.instantiateVolume(PrimaryStorageInventory.valueOf(self), vol, new ReturnValueCompletion<VolumeInventory>(msg) { @Override public void success(VolumeInventory returnValue) { reply.setVolume(returnValue); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); reply.setSuccess(false); bus.reply(msg, reply); } }); } @Override protected void handle(InstantiateVolumeOnPrimaryStorageMsg msg) { try { if (msg.getClass() == InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg.class) { handle((InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg) msg); } else { createEmptyVolume(msg); } } catch (PrimaryStorageException e) { logger.warn(e.getMessage(), e); InstantiateVolumeOnPrimaryStorageReply reply = new InstantiateVolumeOnPrimaryStorageReply(); reply.setError(errf.throwableToOperationError(e)); bus.reply(msg, reply); } } @Override protected void handle(final DeleteVolumeOnPrimaryStorageMsg msg) { final DeleteVolumeOnPrimaryStorageReply reply = new DeleteVolumeOnPrimaryStorageReply(); final VolumeInventory vol = msg.getVolume(); final NfsPrimaryStorageBackend backend = getBackend(nfsMgr.findHypervisorTypeByImageFormatAndPrimaryStorageUuid(vol.getFormat(), self.getUuid())); String volumeFolder = PathUtil.parentFolder(vol.getInstallPath()); backend.deleteFolder(getSelfInventory(), volumeFolder, new Completion(msg) { @Override public void success() { logger.debug(String.format("successfully delete volume[uuid:%s, installPath:%s] on nfs primary storage[uuid:%s]", vol.getUuid(), vol.getInstallPath(), self.getUuid())); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { NfsDeleteVolumeGC gc = new NfsDeleteVolumeGC(); gc.NAME = String.format("gc-nfs-%s-volume-%s", self.getUuid(), vol.getUuid()); gc.primaryStorageUuid = self.getUuid(); gc.hypervisorType = backend.getHypervisorType().toString(); gc.volume = vol; gc.submit(NfsPrimaryStorageGlobalConfig.GC_INTERVAL.value(Long.class), TimeUnit.SECONDS); bus.reply(msg, reply); } }); } @Override protected void handle(final CreateTemplateFromVolumeOnPrimaryStorageMsg msg) { final CreateTemplateFromVolumeOnPrimaryStorageReply reply = new CreateTemplateFromVolumeOnPrimaryStorageReply(); final VolumeInventory volume = msg.getVolumeInventory(); BackupStorageVO bsvo = dbf.findByUuid(msg.getBackupStorageUuid(), BackupStorageVO.class); final BackupStorageInventory bsinv = BackupStorageInventory.valueOf(bsvo); final PrimaryStorageInventory pinv = getSelfInventory(); final ImageInventory image = msg.getImageInventory(); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("create-template-from-volume-%s-on-nfs-primary-storage-%s", volume.getUuid(), self.getUuid())); chain.then(new ShareFlow() { String templatePrimaryStorageInstallPath; String templateBackupStorageInstallPath; @Override public void setup() { flow(new Flow() { NfsPrimaryStorageBackend bkd = getBackend(nfsMgr.findHypervisorTypeByImageFormatAndPrimaryStorageUuid(volume.getFormat(), self.getUuid())); @Override public void run(final FlowTrigger trigger, Map data) { bkd.createTemplateFromVolume(pinv, volume, image, new ReturnValueCompletion<String>(trigger) { @Override public void success(String returnValue) { templatePrimaryStorageInstallPath = returnValue; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } @Override public void rollback(FlowRollback trigger, Map data) { if (templatePrimaryStorageInstallPath != null) { bkd.delete(pinv, templatePrimaryStorageInstallPath, new NopeCompletion()); } trigger.rollback(); } }); flow(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { NfsPrimaryToBackupStorageMediator mediator = factory.getPrimaryToBackupStorageMediator( BackupStorageType.valueOf(bsinv.getType()), nfsMgr.findHypervisorTypeByImageFormatAndPrimaryStorageUuid(volume.getFormat(), self.getUuid()) ); DebugUtils.Assert(!ImageMediaType.ISO.toString().equals(image.getMediaType()), String.format("how can this happen? creating an template from an ISO????")); templateBackupStorageInstallPath = ImageMediaType.RootVolumeTemplate.toString().equals(image.getMediaType()) ? mediator.makeRootVolumeTemplateInstallPath(bsinv.getUuid(), image.getUuid()) : mediator.makeDataVolumeTemplateInstallPath(bsinv.getUuid(), image.getUuid()); mediator.uploadBits(msg.getImageInventory().getUuid(), pinv, bsinv, templateBackupStorageInstallPath, templatePrimaryStorageInstallPath, new ReturnValueCompletion<String>(trigger) { @Override public void success(String installPath) { templateBackupStorageInstallPath = installPath; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { reply.setTemplateBackupStorageInstallPath(templateBackupStorageInstallPath); bus.reply(msg, reply); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { reply.setError(errCode); bus.reply(msg, reply); } }); } }).start(); } @Override protected void handle(final DownloadDataVolumeToPrimaryStorageMsg msg) { final DownloadDataVolumeToPrimaryStorageReply reply = new DownloadDataVolumeToPrimaryStorageReply(); final String installPath = PathUtil.join(self.getMountPath(), PrimaryStoragePathMaker.makeDataVolumeInstallPath(msg.getVolumeUuid())); BackupStorageVO bsvo = dbf.findByUuid(msg.getBackupStorageRef().getBackupStorageUuid(), BackupStorageVO.class); NfsPrimaryToBackupStorageMediator mediator = factory.getPrimaryToBackupStorageMediator( BackupStorageType.valueOf(bsvo.getType()), nfsMgr.findHypervisorTypeByImageFormatAndPrimaryStorageUuid(msg.getImage().getFormat(), self.getUuid()) ); mediator.downloadBits(getSelfInventory(), BackupStorageInventory.valueOf(bsvo), msg.getBackupStorageRef().getInstallPath(), installPath, new Completion(msg) { @Override public void success() { reply.setInstallPath(installPath); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } @Override protected void handle(final DeleteBitsOnPrimaryStorageMsg msg) { final DeleteBitsOnPrimaryStorageReply reply = new DeleteBitsOnPrimaryStorageReply(); NfsPrimaryStorageBackend bkd = getBackend(HypervisorType.valueOf(msg.getHypervisorType())); if (msg.isFolder()) { bkd.deleteFolder(getSelfInventory(), msg.getInstallPath(), new Completion(msg) { @Override public void success() { bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } else { bkd.delete(getSelfInventory(), msg.getInstallPath(), new Completion(msg) { @Override public void success() { bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } } @Override protected void handle(final DownloadIsoToPrimaryStorageMsg msg) { final DownloadIsoToPrimaryStorageReply reply = new DownloadIsoToPrimaryStorageReply(); final PrimaryStorageInventory pinv = getSelfInventory(); NfsDownloadImageToCacheJob job = new NfsDownloadImageToCacheJob(); job.setPrimaryStorage(pinv); job.setImage(msg.getIsoSpec()); final ImageInventory img = msg.getIsoSpec().getInventory(); jobf.execute(NfsPrimaryStorageKvmHelper.makeDownloadImageJobName(msg.getIsoSpec().getInventory(), pinv), NfsPrimaryStorageKvmHelper.makeJobOwnerName(pinv), job, new ReturnValueCompletion<ImageCacheInventory>(msg) { @Override public void success(ImageCacheInventory returnValue) { logger.debug(String.format("successfully downloaded iso[uuid:%s, name:%s] from backup storage[uuid:%s] to primary storage[uuid:%s, name:%s], path in cache: %s", img.getUuid(), img.getName(), msg.getIsoSpec().getSelectedBackupStorage().getBackupStorageUuid(), pinv.getUuid(), pinv.getName(), returnValue.getInstallUrl())); reply.setInstallPath(returnValue.getInstallUrl()); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { String err = String.format("failed to downloaded iso[uuid:%s, name:%s] from backup storage[uuid:%s] to primary storage[uuid:%s, name:%s]", img.getUuid(), img.getName(), msg.getIsoSpec().getSelectedBackupStorage().getBackupStorageUuid(), pinv.getUuid(), pinv.getName()); logger.warn(err); reply.setError(errorCode); bus.reply(msg, reply); } }, ImageCacheInventory.class); } @Override protected void handle(DeleteIsoFromPrimaryStorageMsg msg) { DeleteIsoFromPrimaryStorageReply reply = new DeleteIsoFromPrimaryStorageReply(); bus.reply(msg, reply); } @Override protected void handle(AskVolumeSnapshotCapabilityMsg msg) { VolumeSnapshotCapability capability = new VolumeSnapshotCapability(); HypervisorType hvType = VolumeFormat.getMasterHypervisorTypeByVolumeFormat(msg.getVolume().getFormat()); if (hvType.toString().equals(KVMConstant.KVM_HYPERVISOR_TYPE)) { capability.setArrangementType(VolumeSnapshotArrangementType.CHAIN); capability.setSupport(true); } else { capability.setSupport(false); } AskVolumeSnapshotCapabilityReply reply = new AskVolumeSnapshotCapabilityReply(); reply.setCapability(capability); bus.reply(msg, reply); } @Override protected void handle(final SyncVolumeSizeOnPrimaryStorageMsg msg) { NfsPrimaryStorageBackend backend = getUsableBackend(); if (backend == null) { throw new OperationFailureException(operr("the NFS primary storage[uuid:%s, name:%s] cannot find hosts in attached clusters to perform the operation", self.getUuid(), self.getName())); } backend.handle(getSelfInventory(), msg, new ReturnValueCompletion<SyncVolumeSizeOnPrimaryStorageReply>(msg) { @Override public void success(SyncVolumeSizeOnPrimaryStorageReply reply) { bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { SyncVolumeSizeOnPrimaryStorageReply reply = new SyncVolumeSizeOnPrimaryStorageReply(); reply.setError(errorCode); bus.reply(msg, reply); } }); } protected void handle(NfsRecalculatePrimaryStorageCapacityMsg msg) { if (msg.isRelease()) { doReleasePrimaryStorageCapacity(); } else { RecalculatePrimaryStorageCapacityMsg rmsg = new RecalculatePrimaryStorageCapacityMsg(); rmsg.setPrimaryStorageUuid(self.getUuid()); bus.makeLocalServiceId(rmsg, PrimaryStorageConstant.SERVICE_ID); bus.send(rmsg); } } private void doReleasePrimaryStorageCapacity() { PrimaryStorageCapacityUpdater updater = new PrimaryStorageCapacityUpdater(self.getUuid()); updater.run(new PrimaryStorageCapacityUpdaterRunnable() { @Override public PrimaryStorageCapacityVO call(PrimaryStorageCapacityVO cap) { cap.setAvailableCapacity(0L); cap.setAvailablePhysicalCapacity(0L); cap.setSystemUsedCapacity(0L); cap.setTotalPhysicalCapacity(0L); cap.setTotalCapacity(0L); return cap; } }); } protected void hookToKVMHostConnectedEventToChangeStatusToConnected() { // hook on host connected event to reconnect the primary storage once there is // a host connected in attached clusters evtf.onLocal(HostCanonicalEvents.HOST_STATUS_CHANGED_PATH, new AutoOffEventCallback() { { uniqueIdentity = String.format("connect-nfs-%s-when-host-connected", self.getUuid()); } @Override protected boolean run(Map tokens, Object data) { HostStatusChangedData d = (HostStatusChangedData) data; if (!HostStatus.Connected.toString().equals(d.getNewStatus())) { return false; } if (!KVMConstant.KVM_HYPERVISOR_TYPE.equals(d.getInventory().getHypervisorType())) { return false; } self = dbf.reload(self); if (self.getStatus() == PrimaryStorageStatus.Connected) { return true; } if (!self.getAttachedClusterRefs().stream() .anyMatch(ref -> ref.getClusterUuid().equals(d.getInventory().getClusterUuid()))) { return false; } FutureCompletion future = new FutureCompletion(null); ConnectParam p = new ConnectParam(); p.setNewAdded(false); connectHook(p, future); future.await(); if (!future.isSuccess()) { N.New(PrimaryStorageVO.class, self.getUuid()).warn_("unable to reconnect the primary storage[uuid:%s, name:%s], %s", self.getUuid(), self.getName(), future.getErrorCode()); } return future.isSuccess(); } }); } @Override protected void connectHook(ConnectParam param, final Completion completion) { final NfsPrimaryStorageBackend backend = getUsableBackend(); if (backend == null) { if (!param.isNewAdded()) { hookToKVMHostConnectedEventToChangeStatusToConnected(); } // the nfs primary storage has not been attached to any clusters, or no connected hosts completion.fail(errf.instantiateErrorCode(PrimaryStorageErrors.DISCONNECTED, String.format("the NFS primary storage[uuid:%s, name:%s] has not attached to any clusters, or no hosts in the" + " attached clusters are connected", self.getUuid(), self.getName()) )); return; } FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("reconnect-nfs-primary-storage-%s", self.getUuid())); chain.then(new ShareFlow() { @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "ping"; @Override public void run(final FlowTrigger trigger, Map data) { backend.ping(getSelfInventory(), new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "remount"; @Override public void run(final FlowTrigger trigger, Map data) { SimpleQuery<PrimaryStorageClusterRefVO> q = dbf.createQuery(PrimaryStorageClusterRefVO.class); q.select(PrimaryStorageClusterRefVO_.clusterUuid); q.add(PrimaryStorageClusterRefVO_.primaryStorageUuid, Op.EQ, self.getUuid()); List<String> cuuids = q.listValue(); if (cuuids.isEmpty()) { trigger.next(); return; } PrimaryStorageInventory inv = getSelfInventory(); new LoopAsyncBatch<String>(trigger) { boolean success; @Override protected Collection<String> collect() { return cuuids; } @Override protected AsyncBatchRunner forEach(String cuuid) { return new AsyncBatchRunner() { @Override public void run(NoErrorCompletion completion) { NfsPrimaryStorageBackend bkd = getBackendByClusterUuid(cuuid); bkd.remount(inv, cuuid, new Completion(completion) { @Override public void success() { success = true; completion.done(); } @Override public void fail(ErrorCode errorCode) { errors.add(errorCode); completion.done(); } }); } }; } @Override protected void done() { if (success) { self = dbf.reload(self); trigger.next(); } else { trigger.fail(errf.stringToOperationError(String.format("unable to connect the" + "NFS primary storage[uuid:%s, name:%s]", self.getUuid(), self.getName()), errors)); } } }.start(); } }); done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { completion.success(); } }); error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); } }); } }).start(); } @Override protected void pingHook(Completion completion) { NfsPrimaryStorageBackend bkd = getUsableBackend(); if (bkd == null) { hookToKVMHostConnectedEventToChangeStatusToConnected(); // the nfs primary storage has not been attached to any clusters, or no connected hosts completion.fail(operr("the NFS primary storage[uuid:%s, name:%s] has not attached to any clusters, or no hosts in the" + " attached clusters are connected", self.getUuid(), self.getName())); } else { bkd.ping(getSelfInventory(), completion); } } @Override protected void syncPhysicalCapacity(ReturnValueCompletion<PhysicalCapacityUsage> completion) { NfsPrimaryStorageBackend backend = getUsableBackend(); if (backend == null) { PhysicalCapacityUsage usage = new PhysicalCapacityUsage(); usage.availablePhysicalSize = 0; usage.totalPhysicalSize = 0; completion.success(usage); } else { backend.getPhysicalCapacity(getSelfInventory(), completion); } } }