package org.zstack.storage.primary.local; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; import org.zstack.core.thread.AsyncThread; import org.zstack.storage.primary.local.LocalStorageKvmBackend.*; import org.zstack.storage.primary.local.LocalStorageKvmMigrateVmFlow.CopyBitsFromRemoteCmd; import org.zstack.storage.primary.local.LocalStorageKvmMigrateVmFlow.RebaseSnapshotBackingFilesCmd; import org.zstack.storage.primary.local.LocalStorageKvmMigrateVmFlow.VerifySnapshotChainCmd; import org.zstack.storage.primary.local.LocalStorageKvmSftpBackupStorageMediatorImpl.SftpDownloadBitsCmd; import org.zstack.storage.primary.local.LocalStorageKvmSftpBackupStorageMediatorImpl.SftpDownloadBitsRsp; import org.zstack.storage.primary.local.LocalStorageKvmSftpBackupStorageMediatorImpl.SftpUploadBitsCmd; import org.zstack.storage.primary.local.LocalStorageKvmSftpBackupStorageMediatorImpl.SftpUploadBitsRsp; import org.zstack.storage.primary.local.LocalStorageSimulatorConfig.Capacity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.SimpleQuery; import org.zstack.core.db.SimpleQuery.Op; import org.zstack.header.host.HostVO; import org.zstack.header.host.HostVO_; import org.zstack.header.rest.RESTConstant; import org.zstack.header.rest.RESTFacade; import org.zstack.utils.CollectionUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.Function; import org.zstack.utils.gson.JSONObjectUtil; import org.zstack.utils.logging.CLogger; /** * Created by frank on 7/1/2015. */ @Controller public class LocalStorageSimulator { private CLogger logger = Utils.getLogger(LocalStorageSimulator.class); @Autowired private RESTFacade restf; @Autowired private LocalStorageSimulatorConfig config; @Autowired private DatabaseFacade dbf; @AsyncThread public void reply(HttpEntity<String> entity, Object rsp) { String taskUuid = entity.getHeaders().getFirst(RESTConstant.TASK_UUID); String callbackUrl = entity.getHeaders().getFirst(RESTConstant.CALLBACK_URL); String rspBody = JSONObjectUtil.toJsonString(rsp); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setContentLength(rspBody.length()); headers.set(RESTConstant.TASK_UUID, taskUuid); HttpEntity<String> rreq = new HttpEntity<String>(rspBody, headers); restf.getRESTTemplate().exchange(callbackUrl, HttpMethod.POST, rreq, String.class); } @RequestMapping(value=LocalStorageKvmBackend.GET_QCOW2_REFERENCE, method= RequestMethod.POST) public @ResponseBody String getQcow2Reference(HttpEntity<String> entity) { GetQCOW2ReferenceCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetQCOW2ReferenceCmd.class); GetQCOW2ReferenceRsp rsp = new GetQCOW2ReferenceRsp(); config.getQCOW2ReferenceCmds.add(cmd); rsp.referencePaths = config.getQCOW2ReferenceCmdReference; reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.GET_BASE_IMAGE_PATH, method= RequestMethod.POST) public @ResponseBody String getVolumeBaseImagePath(HttpEntity<String> entity) { GetVolumeBaseImagePathCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetVolumeBaseImagePathCmd.class); GetVolumeBaseImagePathRsp rsp = new GetVolumeBaseImagePathRsp(); rsp.path = config.getVolumeBaseImagePaths.get(cmd.volumeUuid); reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.GET_BACKING_FILE_PATH, method= RequestMethod.POST) public @ResponseBody String getBackingFile(HttpEntity<String> entity) { GetBackingFileCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetBackingFileCmd.class); GetBackingFileRsp rsp = new GetBackingFileRsp(); config.getBackingFileCmds.add(cmd); rsp.backingFilePath = config.backingFilePath; rsp.size = config.backingFileSize; reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.GET_MD5_PATH, method= RequestMethod.POST) public @ResponseBody String getMd5sum(HttpEntity<String> entity) { GetMd5Cmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetMd5Cmd.class); GetMd5Rsp rsp = new GetMd5Rsp(); config.getMd5Cmds.add(cmd); rsp.md5s = CollectionUtils.transformToList(cmd.md5s, new Function<Md5TO, GetMd5TO>() { @Override public Md5TO call(GetMd5TO arg) { Md5TO to = new Md5TO(); to.md5 = arg.resourceUuid; to.path = arg.path; to.resourceUuid = arg.resourceUuid; return to; } }); reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.CHECK_MD5_PATH, method= RequestMethod.POST) public @ResponseBody String checkMd5sum(HttpEntity<String> entity) { CheckMd5sumCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CheckMd5sumCmd.class); config.checkMd5sumCmds.add(cmd); AgentResponse rsp = new AgentResponse(); if (!config.checkMd5Success) { rsp.setSuccess(false); rsp.setError("on purpose"); } reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmMigrateVmFlow.COPY_TO_REMOTE_BITS_PATH, method= RequestMethod.POST) public @ResponseBody String copyBitsFromRemote(HttpEntity<String> entity) { CopyBitsFromRemoteCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CopyBitsFromRemoteCmd.class); AgentResponse rsp = new AgentResponse(); if (config.copyBitsFromRemoteSuccess) { config.copyBitsFromRemoteCmds.add(cmd); } else { rsp.setError("on purpose"); rsp.setSuccess(false); } reply(entity, rsp); return null;} @RequestMapping(value=LocalStorageKvmMigrateVmFlow.REBASE_ROOT_VOLUME_TO_BACKING_FILE_PATH, method= RequestMethod.POST) public @ResponseBody String rebaseRootVolumeToBackingFile(HttpEntity<String> entity) { RebaseRootVolumeToBackingFileCmd cmd = JSONObjectUtil.toObject(entity.getBody(), RebaseRootVolumeToBackingFileCmd.class); config.rebaseRootVolumeToBackingFileCmds.add(cmd); reply(entity, new RebaseRootVolumeToBackingFileRsp()); return null; } @RequestMapping(value=LocalStorageKvmMigrateVmFlow.REBASE_SNAPSHOT_BACKING_FILES_PATH, method= RequestMethod.POST) public @ResponseBody String rebaseSnapshotBackingFiles(HttpEntity<String> entity) { RebaseSnapshotBackingFilesCmd cmd = JSONObjectUtil.toObject(entity.getBody(), RebaseSnapshotBackingFilesCmd.class); config.rebaseSnapshotBackingFilesCmds.add(cmd); reply(entity, new AgentResponse()); return null; } @RequestMapping(value=LocalStorageKvmMigrateVmFlow.VERIFY_SNAPSHOT_CHAIN_PATH, method= RequestMethod.POST) public @ResponseBody String verifySnapshotChain(HttpEntity<String> entity) { VerifySnapshotChainCmd cmd = JSONObjectUtil.toObject(entity.getBody(), VerifySnapshotChainCmd.class); config.verifySnapshotChainCmds.add(cmd); reply(entity, new AgentResponse()); return null; } @RequestMapping(value=LocalStorageKvmBackend.INIT_PATH, method= RequestMethod.POST) public @ResponseBody String init(HttpEntity<String> entity) { InitCmd cmd = JSONObjectUtil.toObject(entity.getBody(), InitCmd.class); config.initCmdList.add(cmd); AgentResponse rsp = new AgentResponse(); SimpleQuery<HostVO> q = dbf.createQuery(HostVO.class); q.select(HostVO_.name); q.add(HostVO_.uuid, Op.EQ, cmd.getHostUuid()); String hname = q.findValue(); Capacity c = config.capacityMap.get(hname); assert c!=null : String.format("cannot find host[name:%s] for configuring the local storage capacity", hname); rsp.setTotalCapacity(c.total); rsp.setAvailableCapacity(c.avail); reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.CHECK_BITS_PATH, method= RequestMethod.POST) public @ResponseBody String checkBits(HttpEntity<String> entity) { CheckBitsCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CheckBitsCmd.class); config.checkBitsCmds.add(cmd); CheckBitsRsp rsp = new CheckBitsRsp(); rsp.existing = config.checkBitsSuccess; reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.GET_PHYSICAL_CAPACITY_PATH, method= RequestMethod.POST) public @ResponseBody String getPhysicalCapacity(HttpEntity<String> entity) { GetPhysicalCapacityCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetPhysicalCapacityCmd.class); config.getPhysicalCapacityCmds.add(cmd); AgentResponse rsp = new AgentResponse(); SimpleQuery<HostVO> q = dbf.createQuery(HostVO.class); q.select(HostVO_.name); q.add(HostVO_.uuid, Op.EQ, cmd.getHostUuid()); String hname = q.findValue(); Capacity c = config.capacityMap.get(hname); rsp.setTotalCapacity(c.total); rsp.setAvailableCapacity(c.avail); reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.CREATE_EMPTY_VOLUME_PATH, method= RequestMethod.POST) public @ResponseBody String createEmptyVolume(HttpEntity<String> entity) { CreateEmptyVolumeCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CreateEmptyVolumeCmd.class); config.createEmptyVolumeCmds.add(cmd); CreateEmptyVolumeRsp rsp = new CreateEmptyVolumeRsp(); reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.CREATE_VOLUME_FROM_CACHE_PATH, method= RequestMethod.POST) public @ResponseBody String createVolumeFromCache(HttpEntity<String> entity) { CreateVolumeFromCacheCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CreateVolumeFromCacheCmd.class); config.createVolumeFromCacheCmds.add(cmd); CreateVolumeFromCacheRsp rsp = new CreateVolumeFromCacheRsp(); reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.DELETE_BITS_PATH, method= RequestMethod.POST) public @ResponseBody String delete(HttpEntity<String> entity) { DeleteBitsCmd cmd = JSONObjectUtil.toObject(entity.getBody(), DeleteBitsCmd.class); synchronized (config) { config.deleteBitsCmds.add(cmd); } DeleteBitsRsp rsp = new DeleteBitsRsp(); reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.DELETE_DIR_PATH, method= RequestMethod.POST) public @ResponseBody String deleteDir(HttpEntity<String> entity) { DeleteBitsCmd cmd = JSONObjectUtil.toObject(entity.getBody(), DeleteBitsCmd.class); synchronized (config) { config.deleteDirCmds.add(cmd); } DeleteBitsRsp rsp = new DeleteBitsRsp(); reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmSftpBackupStorageMediatorImpl.DOWNLOAD_BIT_PATH, method= RequestMethod.POST) public @ResponseBody String download(HttpEntity<String> entity) { SftpDownloadBitsCmd cmd = JSONObjectUtil.toObject(entity.getBody(), SftpDownloadBitsCmd.class); config.downloadBitsCmds.add(cmd); reply(entity, new SftpDownloadBitsRsp()); return null; } @RequestMapping(value=LocalStorageKvmSftpBackupStorageMediatorImpl.UPLOAD_BIT_PATH, method= RequestMethod.POST) public @ResponseBody String upload(HttpEntity<String> entity) { SftpUploadBitsCmd cmd = JSONObjectUtil.toObject(entity.getBody(), SftpUploadBitsCmd.class); config.uploadBitsCmds.add(cmd); reply(entity, new SftpUploadBitsRsp()); return null; } @RequestMapping(value=LocalStorageKvmBackend.CREATE_TEMPLATE_FROM_VOLUME, method= RequestMethod.POST) public @ResponseBody String createTemplateFromVolume(HttpEntity<String> entity) { CreateTemplateFromVolumeCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CreateTemplateFromVolumeCmd.class); config.createTemplateFromVolumeCmds.add(cmd); reply(entity, new CreateTemplateFromVolumeRsp()); return null; } @RequestMapping(value=LocalStorageKvmBackend.REVERT_SNAPSHOT_PATH, method= RequestMethod.POST) public @ResponseBody String revertSnapshot(HttpEntity<String> entity) { RevertVolumeFromSnapshotCmd cmd = JSONObjectUtil.toObject(entity.getBody(), RevertVolumeFromSnapshotCmd.class); config.revertVolumeFromSnapshotCmds.add(cmd); RevertVolumeFromSnapshotRsp rsp = new RevertVolumeFromSnapshotRsp(); rsp.setNewVolumeInstallPath("/new/path"); reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.MERGE_AND_REBASE_SNAPSHOT_PATH, method= RequestMethod.POST) public @ResponseBody String rebaseAndMergeSnapshot(HttpEntity<String> entity) { RebaseAndMergeSnapshotsCmd cmd = JSONObjectUtil.toObject(entity.getBody(), RebaseAndMergeSnapshotsCmd.class); config.rebaseAndMergeSnapshotsCmds.add(cmd); RebaseAndMergeSnapshotsRsp rsp = new RebaseAndMergeSnapshotsRsp(); Long size = config.snapshotToVolumeSize.get(cmd.getVolumeUuid()); rsp.setSize(size == null ? 0 : size); Long asize = config.snapshotToVolumeActualSize.get(cmd.getVolumeUuid()); rsp.setActualSize(asize == null ? 0 : asize); reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.MERGE_SNAPSHOT_PATH, method= RequestMethod.POST) public @ResponseBody String mergeSnapshot(HttpEntity<String> entity) { MergeSnapshotCmd cmd = JSONObjectUtil.toObject(entity.getBody(), MergeSnapshotCmd.class); config.mergeSnapshotCmds.add(cmd); MergeSnapshotRsp rsp = new MergeSnapshotRsp(); Long size = config.snapshotToVolumeSize.get(cmd.getVolumeUuid()); rsp.setSize(size == null ? 0 : size); Long asize = config.snapshotToVolumeActualSize.get(cmd.getVolumeUuid()); rsp.setActualSize(asize == null ? 0 : asize); reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.GET_VOLUME_SIZE, method= RequestMethod.POST) public @ResponseBody String getVolumeActualSize(HttpEntity<String> entity) { GetVolumeSizeCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetVolumeSizeCmd.class); GetVolumeSizeRsp rsp = new GetVolumeSizeRsp(); config.getVolumeSizeCmds.add(cmd); Long asize = config.getVolumeSizeCmdActualSize.get(cmd.volumeUuid); rsp.actualSize = asize == null ? 0 : asize; Long size = config.getVolumeSizeCmdSize.get(cmd.volumeUuid); rsp.size = size == null ? 0 : size; reply(entity, rsp); return null; } @RequestMapping(value=LocalStorageKvmBackend.OFFLINE_MERGE_PATH, method= RequestMethod.POST) public @ResponseBody String offlineMerge(HttpEntity<String> entity) { OfflineMergeSnapshotCmd cmd = JSONObjectUtil.toObject(entity.getBody(), OfflineMergeSnapshotCmd.class); config.offlineMergeSnapshotCmds.add(cmd); OfflineMergeSnapshotRsp rsp = new OfflineMergeSnapshotRsp(); reply(entity, rsp); return null; } @ExceptionHandler(Exception.class) public ModelAndView handleAllException(Exception ex) { logger.warn(ex.getMessage(), ex); ModelAndView model = new ModelAndView("error/generic_error"); model.addObject("errMsg", ex.getMessage()); return model; } }