package org.zstack.storage.primary.local; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.zstack.compute.vm.ImageBackupStorageSelector; import org.zstack.core.cloudbus.CloudBusCallBack; import org.zstack.core.cloudbus.CloudBusListCallBack; import org.zstack.core.cloudbus.MessageSafe; import org.zstack.core.db.Q; import org.zstack.core.db.SimpleQuery; import org.zstack.core.db.SimpleQuery.Op; import org.zstack.core.thread.AsyncThread; import org.zstack.core.thread.ChainTask; import org.zstack.core.thread.SyncTaskChain; import org.zstack.core.timeout.ApiTimeoutManager; import org.zstack.core.workflow.FlowChainBuilder; import org.zstack.core.workflow.ShareFlow; import org.zstack.header.HasThreadContext; import org.zstack.header.cluster.ClusterInventory; import org.zstack.header.core.ApiTimeout; import org.zstack.header.core.Completion; import org.zstack.header.core.ReturnValueCompletion; import org.zstack.header.core.validation.Validation; 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.image.*; import org.zstack.header.image.ImageConstant.ImageMediaType; import org.zstack.header.message.Message; import org.zstack.header.message.MessageReply; import org.zstack.header.rest.RESTFacade; import org.zstack.header.storage.backup.*; import org.zstack.header.storage.primary.*; import org.zstack.header.storage.snapshot.APIDeleteVolumeSnapshotMsg; import org.zstack.header.storage.snapshot.VolumeSnapshotConstant; import org.zstack.header.storage.snapshot.VolumeSnapshotInventory; import org.zstack.header.storage.snapshot.VolumeSnapshotVO; import org.zstack.header.vm.APICreateVmInstanceMsg; 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.*; import org.zstack.identity.AccountManager; import org.zstack.kvm.*; import org.zstack.storage.primary.PrimaryStoragePathMaker; import org.zstack.storage.primary.local.LocalStorageKvmMigrateVmFlow.CopyBitsFromRemoteCmd; import org.zstack.storage.primary.local.MigrateBitsStruct.ResourceInfo; 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.operr; import javax.persistence.Tuple; import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import static org.zstack.core.progress.ProgressReportService.taskProgress; import static org.zstack.utils.CollectionDSL.list; /** * Created by frank on 6/30/2015. */ public class LocalStorageKvmBackend extends LocalStorageHypervisorBackend { private final static CLogger logger = Utils.getLogger(LocalStorageKvmBackend.class); @Autowired private AccountManager acntMgr; @Autowired private LocalStorageFactory localStorageFactory; @Autowired private ApiTimeoutManager timeoutMgr; @Autowired private RESTFacade restf; public static class AgentCommand { public String uuid; public String storagePath; } public static class AgentResponse { private Long totalCapacity; private Long availableCapacity; private boolean success = true; private String error; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getError() { return error; } public void setError(String error) { this.error = error; } public Long getTotalCapacity() { return totalCapacity; } public void setTotalCapacity(Long totalCapacity) { this.totalCapacity = totalCapacity; } public Long getAvailableCapacity() { return availableCapacity; } public void setAvailableCapacity(Long availableCapacity) { this.availableCapacity = availableCapacity; } } public static class InitCmd extends AgentCommand { private String path; private String hostUuid; public String getHostUuid() { return hostUuid; } public void setHostUuid(String hostUuid) { this.hostUuid = hostUuid; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } } public static class CreateEmptyVolumeCmd extends AgentCommand { private String installUrl; private long size; private String accountUuid; private String name; private String volumeUuid; private String backingFile; public String getBackingFile() { return backingFile; } public void setBackingFile(String backingFile) { this.backingFile = backingFile; } public String getInstallUrl() { return installUrl; } public void setInstallUrl(String installUrl) { this.installUrl = installUrl; } public long getSize() { return size; } public void setSize(long size) { this.size = size; } public String getAccountUuid() { return accountUuid; } public void setAccountUuid(String accountUuid) { this.accountUuid = accountUuid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getVolumeUuid() { return volumeUuid; } public void setVolumeUuid(String volumeUuid) { this.volumeUuid = volumeUuid; } } public static class CreateEmptyVolumeRsp extends AgentResponse { } public static class GetPhysicalCapacityCmd extends AgentCommand { private String hostUuid; public String getHostUuid() { return hostUuid; } public void setHostUuid(String hostUuid) { this.hostUuid = hostUuid; } } @ApiTimeout(apiClasses = {APICreateVmInstanceMsg.class}) public static class CreateVolumeFromCacheCmd extends AgentCommand { private String templatePathInCache; private String installUrl; private String volumeUuid; public String getTemplatePathInCache() { return templatePathInCache; } public void setTemplatePathInCache(String templatePathInCache) { this.templatePathInCache = templatePathInCache; } public String getInstallUrl() { return installUrl; } public void setInstallUrl(String installUrl) { this.installUrl = installUrl; } public String getVolumeUuid() { return volumeUuid; } public void setVolumeUuid(String volumeUuid) { this.volumeUuid = volumeUuid; } } public static class CreateVolumeFromCacheRsp extends AgentResponse { } @ApiTimeout(apiClasses = { APICreateRootVolumeTemplateFromRootVolumeMsg.class, APICreateDataVolumeTemplateFromVolumeMsg.class, APICreateDataVolumeFromVolumeSnapshotMsg.class, APILocalStorageMigrateVolumeMsg.class, APIDeleteVolumeSnapshotMsg.class, }) public static class DeleteBitsCmd extends AgentCommand { private String hostUuid; private String path; private String username; public String getHostUuid() { return hostUuid; } public void setHostUuid(String hostUuid) { this.hostUuid = hostUuid; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } } public static class DeleteBitsRsp extends AgentResponse { } @ApiTimeout(apiClasses = { APICreateRootVolumeTemplateFromRootVolumeMsg.class, APICreateDataVolumeTemplateFromVolumeMsg.class }) public static class CreateTemplateFromVolumeCmd extends AgentCommand { private String installPath; private String volumePath; public String getInstallPath() { return installPath; } public void setInstallPath(String installPath) { this.installPath = installPath; } public String getVolumePath() { return volumePath; } public void setVolumePath(String rootVolumePath) { this.volumePath = rootVolumePath; } } public static class CreateTemplateFromVolumeRsp extends AgentResponse { } public static class RevertVolumeFromSnapshotCmd extends AgentCommand { private String snapshotInstallPath; public String getSnapshotInstallPath() { return snapshotInstallPath; } public void setSnapshotInstallPath(String snapshotInstallPath) { this.snapshotInstallPath = snapshotInstallPath; } } public static class RevertVolumeFromSnapshotRsp extends AgentResponse { @Validation private String newVolumeInstallPath; public String getNewVolumeInstallPath() { return newVolumeInstallPath; } public void setNewVolumeInstallPath(String newVolumeInstallPath) { this.newVolumeInstallPath = newVolumeInstallPath; } } @ApiTimeout(apiClasses = {APIDeleteVolumeSnapshotMsg.class}) public static class MergeSnapshotCmd extends AgentCommand { private String volumeUuid; private String snapshotInstallPath; private String workspaceInstallPath; public String getVolumeUuid() { return volumeUuid; } public void setVolumeUuid(String volumeUuid) { this.volumeUuid = volumeUuid; } public String getSnapshotInstallPath() { return snapshotInstallPath; } public void setSnapshotInstallPath(String snapshotInstallPath) { this.snapshotInstallPath = snapshotInstallPath; } public String getWorkspaceInstallPath() { return workspaceInstallPath; } public void setWorkspaceInstallPath(String workspaceInstallPath) { this.workspaceInstallPath = workspaceInstallPath; } } public static class MergeSnapshotRsp extends AgentResponse { private long size; private long actualSize; public long getActualSize() { return actualSize; } public void setActualSize(long actualSize) { this.actualSize = actualSize; } public long getSize() { return size; } public void setSize(long size) { this.size = size; } } @ApiTimeout(apiClasses = {APICreateRootVolumeTemplateFromVolumeSnapshotMsg.class}) public static class RebaseAndMergeSnapshotsCmd extends AgentCommand { private String volumeUuid; private List<String> snapshotInstallPaths; private String workspaceInstallPath; public String getVolumeUuid() { return volumeUuid; } public void setVolumeUuid(String volumeUuid) { this.volumeUuid = volumeUuid; } public List<String> getSnapshotInstallPaths() { return snapshotInstallPaths; } public void setSnapshotInstallPaths(List<String> snapshotInstallPaths) { this.snapshotInstallPaths = snapshotInstallPaths; } public String getWorkspaceInstallPath() { return workspaceInstallPath; } public void setWorkspaceInstallPath(String workspaceInstallPath) { this.workspaceInstallPath = workspaceInstallPath; } } public static class RebaseAndMergeSnapshotsRsp extends AgentResponse { private long size; private long actualSize; public long getActualSize() { return actualSize; } public void setActualSize(long actualSize) { this.actualSize = actualSize; } public long getSize() { return size; } public void setSize(long size) { this.size = size; } } @ApiTimeout(apiClasses = {APIDeleteVolumeSnapshotMsg.class}) public static class OfflineMergeSnapshotCmd extends AgentCommand { private String srcPath; private String destPath; private boolean fullRebase; public boolean isFullRebase() { return fullRebase; } public void setFullRebase(boolean fullRebase) { this.fullRebase = fullRebase; } public String getSrcPath() { return srcPath; } public void setSrcPath(String srcPath) { this.srcPath = srcPath; } public String getDestPath() { return destPath; } public void setDestPath(String destPath) { this.destPath = destPath; } } public static class OfflineMergeSnapshotRsp extends AgentResponse { } public static class CheckBitsCmd extends AgentCommand { public String path; public String username; } public static class CheckBitsRsp extends AgentResponse { public boolean existing; } public static class RebaseRootVolumeToBackingFileCmd extends AgentCommand { public String backingFilePath; public String rootVolumePath; } public static class RebaseRootVolumeToBackingFileRsp extends AgentResponse { } public static class GetMd5TO { public String resourceUuid; public String path; } @ApiTimeout(apiClasses = {APILocalStorageMigrateVolumeMsg.class}) public static class GetMd5Cmd extends AgentCommand implements HasThreadContext { public List<GetMd5TO> md5s; public String sendCommandUrl; public String volumeUuid; public String stage; } public static class Md5TO { public String resourceUuid; public String path; public String md5; } public static class GetMd5Rsp extends AgentResponse { public List<Md5TO> md5s; } @ApiTimeout(apiClasses = {APILocalStorageMigrateVolumeMsg.class}) public static class CheckMd5sumCmd extends AgentCommand implements HasThreadContext { public List<Md5TO> md5s; public String sendCommandUrl; public String volumeUuid; public String stage; } @ApiTimeout(apiClasses = {APILocalStorageMigrateVolumeMsg.class}) public static class GetBackingFileCmd extends AgentCommand { public String path; public String volumeUuid; } public static class GetBackingFileRsp extends AgentResponse { public String backingFilePath; public Long size; } public static class GetVolumeBaseImagePathCmd extends AgentCommand { public String volumeUuid; public String installPath; } public static class GetVolumeBaseImagePathRsp extends AgentResponse { public String path; } public static class GetVolumeSizeCmd extends AgentCommand { public String volumeUuid; public String installPath; } public static class GetVolumeSizeRsp extends AgentResponse { public long actualSize; public long size; } public static class GetQCOW2ReferenceCmd extends AgentCommand { public String path; public String searchingDir; } public static class GetQCOW2ReferenceRsp extends AgentResponse { List<String> referencePaths; } public static final String INIT_PATH = "/localstorage/init"; public static final String GET_PHYSICAL_CAPACITY_PATH = "/localstorage/getphysicalcapacity"; public static final String CREATE_EMPTY_VOLUME_PATH = "/localstorage/volume/createempty"; public static final String CREATE_VOLUME_FROM_CACHE_PATH = "/localstorage/volume/createvolumefromcache"; public static final String DELETE_BITS_PATH = "/localstorage/delete"; public static final String DELETE_DIR_PATH = "/localstorage/deletedir"; public static final String CHECK_BITS_PATH = "/localstorage/checkbits"; public static final String CREATE_TEMPLATE_FROM_VOLUME = "/localstorage/volume/createtemplate"; public static final String REVERT_SNAPSHOT_PATH = "/localstorage/snapshot/revert"; public static final String MERGE_SNAPSHOT_PATH = "/localstorage/snapshot/merge"; public static final String MERGE_AND_REBASE_SNAPSHOT_PATH = "/localstorage/snapshot/mergeandrebase"; public static final String OFFLINE_MERGE_PATH = "/localstorage/snapshot/offlinemerge"; public static final String GET_MD5_PATH = "/localstorage/getmd5"; public static final String CHECK_MD5_PATH = "/localstorage/checkmd5"; public static final String GET_BACKING_FILE_PATH = "/localstorage/volume/getbackingfile"; public static final String GET_VOLUME_SIZE = "/localstorage/volume/getsize"; public static final String GET_BASE_IMAGE_PATH = "/localstorage/volume/getbaseimagepath"; public static final String GET_QCOW2_REFERENCE = "/localstorage/getqcow2reference"; public LocalStorageKvmBackend(PrimaryStorageVO self) { super(self); } public String makeRootVolumeInstallUrl(VolumeInventory vol) { return PathUtil.join(self.getUrl(), PrimaryStoragePathMaker.makeRootVolumeInstallPath(vol)); } public String makeDataVolumeInstallUrl(String volUuid) { return PathUtil.join(self.getUrl(), PrimaryStoragePathMaker.makeDataVolumeInstallPath(volUuid)); } public String makeCachedImageInstallUrl(ImageInventory iminv) { return PathUtil.join(self.getUrl(), PrimaryStoragePathMaker.makeCachedImageInstallPath(iminv)); } public String makeCachedImageInstallUrlFromImageUuidForTemplate(String imageUuid) { return PathUtil.join(self.getUrl(), PrimaryStoragePathMaker.makeCachedImageInstallPathFromImageUuidForTemplate(imageUuid)); } public String makeTemplateFromVolumeInWorkspacePath(String imageUuid) { return PathUtil.join(self.getUrl(), "templateWorkspace", String.format("image-%s", imageUuid), String.format("%s.qcow2", imageUuid)); } public String makeSnapshotInstallPath(VolumeInventory vol, VolumeSnapshotInventory snapshot) { String volPath; if (VolumeType.Data.toString().equals(vol.getType())) { volPath = makeDataVolumeInstallUrl(vol.getUuid()); } else { volPath = makeRootVolumeInstallUrl(vol); } File volDir = new File(volPath).getParentFile(); return PathUtil.join(volDir.getAbsolutePath(), "snapshots", String.format("%s.qcow2", snapshot.getUuid())); } public String makeSnapshotWorkspacePath(String imageUuid) { return PathUtil.join( self.getUrl(), PrimaryStoragePathMaker.makeImageFromSnapshotWorkspacePath(imageUuid), String.format("%s.qcow2", imageUuid) ); } @Override void syncPhysicalCapacityInCluster(List<ClusterInventory> clusters, final ReturnValueCompletion<PhysicalCapacityUsage> completion) { List<String> clusterUuids = CollectionUtils.transformToList(clusters, new Function<String, ClusterInventory>() { @Override public String call(ClusterInventory arg) { return arg.getUuid(); } }); final PhysicalCapacityUsage ret = new PhysicalCapacityUsage(); SimpleQuery<HostVO> q = dbf.createQuery(HostVO.class); q.select(HostVO_.uuid); q.add(HostVO_.clusterUuid, Op.IN, clusterUuids); final List<String> hostInClusterUuids = q.listValue(); if (hostInClusterUuids.isEmpty()) { completion.success(ret); return; } SimpleQuery<LocalStorageHostRefVO> sq = dbf.createQuery(LocalStorageHostRefVO.class); sq.select(LocalStorageHostRefVO_.hostUuid); sq.add(LocalStorageHostRefVO_.primaryStorageUuid, Op.EQ, self.getUuid()); sq.add(LocalStorageHostRefVO_.hostUuid, Op.IN, hostInClusterUuids); final List<String> hostUuids = sq.listValue(); if (hostUuids.isEmpty()) { completion.success(ret); return; } List<KVMHostAsyncHttpCallMsg> msgs = CollectionUtils.transformToList(hostUuids, new Function<KVMHostAsyncHttpCallMsg, String>() { @Override public KVMHostAsyncHttpCallMsg call(String arg) { GetPhysicalCapacityCmd cmd = new GetPhysicalCapacityCmd(); cmd.setHostUuid(arg); cmd.storagePath = self.getUrl(); KVMHostAsyncHttpCallMsg msg = new KVMHostAsyncHttpCallMsg(); msg.setHostUuid(arg); msg.setCommand(cmd); msg.setCommandTimeout(timeoutMgr.getTimeout(cmd.getClass(), "5m")); msg.setPath(GET_PHYSICAL_CAPACITY_PATH); bus.makeTargetServiceIdByResourceUuid(msg, HostConstant.SERVICE_ID, arg); return msg; } }); bus.send(msgs, new CloudBusListCallBack(completion) { @Override public void run(List<MessageReply> replies) { for (MessageReply reply : replies) { String hostUuid = hostUuids.get(replies.indexOf(reply)); if (!reply.isSuccess()) { logger.warn(String.format("cannot get the physical capacity of local storage on the host[uuid:%s], %s", hostUuid, reply.getError())); continue; } KVMHostAsyncHttpCallReply r = reply.castReply(); AgentResponse rsp = r.toResponse(AgentResponse.class); if (!rsp.isSuccess()) { logger.warn(String.format("cannot get the physical capacity of local storage on the host[uuid:%s], %s", hostUuid, rsp.getError())); continue; } ret.totalPhysicalSize += rsp.getTotalCapacity(); ret.availablePhysicalSize += rsp.getAvailableCapacity(); } completion.success(ret); } }); } protected <T extends AgentResponse> void httpCall(String path, final String hostUuid, AgentCommand cmd, final Class<T> rspType, final ReturnValueCompletion<T> completion) { httpCall(path, hostUuid, cmd, false, rspType, completion); } protected <T extends AgentResponse> void httpCall(String path, final String hostUuid, AgentCommand cmd, boolean noCheckStatus, final Class<T> rspType, final ReturnValueCompletion<T> completion) { cmd.uuid = self.getUuid(); cmd.storagePath = self.getUrl(); KVMHostAsyncHttpCallMsg msg = new KVMHostAsyncHttpCallMsg(); msg.setHostUuid(hostUuid); msg.setPath(path); msg.setNoStatusCheck(noCheckStatus); msg.setCommand(cmd); msg.setCommandTimeout(timeoutMgr.getTimeout(cmd.getClass(), "5m")); bus.makeTargetServiceIdByResourceUuid(msg, HostConstant.SERVICE_ID, hostUuid); bus.send(msg, new CloudBusCallBack(completion) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { completion.fail(reply.getError()); return; } KVMHostAsyncHttpCallReply r = reply.castReply(); T rsp = r.toResponse(rspType); if (!rsp.isSuccess()) { completion.fail(operr(rsp.getError())); return; } if (rsp.getTotalCapacity() != null && rsp.getAvailableCapacity() != null) { new LocalStorageCapacityUpdater().updatePhysicalCapacityByKvmAgentResponse(self.getUuid(), hostUuid, rsp); } completion.success(rsp); } }); } @Override protected void handle(final InstantiateVolumeOnPrimaryStorageMsg msg, final ReturnValueCompletion<InstantiateVolumeOnPrimaryStorageReply> completion) { if (msg instanceof InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg) { createRootVolume((InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg) msg, completion); } else { createEmptyVolume(msg.getVolume(), msg.getDestHost().getUuid(), new ReturnValueCompletion<String>(completion) { @Override public void success(String returnValue) { InstantiateVolumeOnPrimaryStorageReply r = new InstantiateVolumeOnPrimaryStorageReply(); VolumeInventory vol = msg.getVolume(); vol.setInstallPath(returnValue); vol.setFormat(VolumeConstant.VOLUME_FORMAT_QCOW2); r.setVolume(vol); completion.success(r); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } } private void createEmptyVolume(final VolumeInventory volume, final String hostUuid, final ReturnValueCompletion<String> completion) { createEmptyVolume(volume, hostUuid, null, completion); } private void createEmptyVolume(final VolumeInventory volume, final String hostUuid, final String backingFile, final ReturnValueCompletion<String> completion) { final CreateEmptyVolumeCmd cmd = new CreateEmptyVolumeCmd(); cmd.setAccountUuid(acntMgr.getOwnerAccountUuidOfResource(volume.getUuid())); if (VolumeType.Root.toString().equals(volume.getType())) { cmd.setInstallUrl(makeRootVolumeInstallUrl(volume)); } else { cmd.setInstallUrl(makeDataVolumeInstallUrl(volume.getUuid())); } cmd.setName(volume.getName()); cmd.setSize(volume.getSize()); cmd.setVolumeUuid(volume.getUuid()); cmd.setBackingFile(backingFile); httpCall(CREATE_EMPTY_VOLUME_PATH, hostUuid, cmd, CreateEmptyVolumeRsp.class, new ReturnValueCompletion<CreateEmptyVolumeRsp>(completion) { @Override public void success(CreateEmptyVolumeRsp returnValue) { completion.success(cmd.getInstallUrl()); } @Override public void fail(ErrorCode errorCode) { completion.fail(operr("unable to create an empty volume[uuid:%s, name:%s] on the kvm host[uuid:%s]", volume.getUuid(), volume.getName(), hostUuid).causedBy(errorCode)); } }); } private String getHostUuidByResourceUuid(String resUuid, String resType) { SimpleQuery<LocalStorageResourceRefVO> q = dbf.createQuery(LocalStorageResourceRefVO.class); q.select(LocalStorageResourceRefVO_.hostUuid); q.add(LocalStorageResourceRefVO_.primaryStorageUuid, Op.EQ, self.getUuid()); q.add(LocalStorageResourceRefVO_.resourceUuid, Op.EQ, resUuid); String hostUuid = q.findValue(); if (hostUuid == null) { throw new CloudRuntimeException(String.format("resource[uuid:%s, type:%s] is not any on any host of local primary storage[uuid:%s]", resUuid, resType, self.getUuid())); } return hostUuid; } public static class CacheInstallPath { public String fullPath; public String hostUuid; public String installPath; public CacheInstallPath disassemble() { DebugUtils.Assert(fullPath != null, "fullPath cannot be null"); String[] pair = fullPath.split(";"); DebugUtils.Assert(pair.length == 2, String.format("invalid cache path %s", fullPath)); installPath = pair[0].replaceFirst("file://", ""); hostUuid = pair[1].replaceFirst("hostUuid://", ""); return this; } public String makeFullPath() { DebugUtils.Assert(installPath != null, "installPath cannot be null"); DebugUtils.Assert(hostUuid != null, "hostUuid cannot be null"); fullPath = String.format("file://%s;hostUuid://%s", installPath, hostUuid); return fullPath; } } class ImageCache { ImageInventory image; BackupStorageInventory backupStorage; String hostUuid; String primaryStorageInstallPath; String backupStorageInstallPath; void download(final ReturnValueCompletion<String> completion) { DebugUtils.Assert(image != null, "image cannot be null"); DebugUtils.Assert(backupStorage != null, "backup storage cannot be null"); DebugUtils.Assert(hostUuid != null, "host uuid cannot be null"); DebugUtils.Assert(primaryStorageInstallPath != null, "primaryStorageInstallPath cannot be null"); DebugUtils.Assert(backupStorageInstallPath != null, "backupStorageInstallPath cannot be null"); thdf.chainSubmit(new ChainTask(completion) { @Override public String getSyncSignature() { return String.format("download-image-%s-to-localstorage-%s-cache-host-%s", image.getUuid(), self.getUuid(), hostUuid); } private void doDownload(final SyncTaskChain chain) { taskProgress("Download the image[%s] to the image cache", image.getName()); FlowChain fchain = FlowChainBuilder.newShareFlowChain(); fchain.setName(String.format("download-image-%s-to-local-storage-%s-cache-host-%s", image.getUuid(), self.getUuid(), hostUuid)); fchain.then(new ShareFlow() { String psUuid; @Override public void setup() { flow(new Flow() { String __name__ = "allocate-primary-storage"; boolean s = false; @Override public void run(final FlowTrigger trigger, Map data) { AllocatePrimaryStorageMsg amsg = new AllocatePrimaryStorageMsg(); amsg.setRequiredPrimaryStorageUuid(self.getUuid()); amsg.setRequiredHostUuid(hostUuid); amsg.setSize(image.getActualSize()); amsg.setPurpose(PrimaryStorageAllocationPurpose.DownloadImage.toString()); amsg.setNoOverProvisioning(true); bus.makeLocalServiceId(amsg, PrimaryStorageConstant.SERVICE_ID); bus.send(amsg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (reply.isSuccess()) { s = true; AllocatePrimaryStorageReply r = reply.castReply(); psUuid = r.getPrimaryStorageInventory().getUuid(); trigger.next(); } else { trigger.fail(reply.getError()); } } }); } @Override public void rollback(FlowRollback trigger, Map data) { if (s) { IncreasePrimaryStorageCapacityMsg imsg = new IncreasePrimaryStorageCapacityMsg(); imsg.setDiskSize(image.getActualSize()); imsg.setNoOverProvisioning(true); imsg.setPrimaryStorageUuid(self.getUuid()); bus.makeLocalServiceId(imsg, PrimaryStorageConstant.SERVICE_ID); bus.send(imsg); } trigger.rollback(); } }); flow(new Flow() { String __name__ = "allocate-capacity-on-host"; @Override public void run(FlowTrigger trigger, Map data) { reserveCapacityOnHost(hostUuid, image.getActualSize(), psUuid); trigger.next(); } @Override public void rollback(FlowRollback trigger, Map data) { returnStorageCapacityToHost(hostUuid, image.getActualSize()); trigger.rollback(); } }); flow(new NoRollbackFlow() { String __name__ = "download"; @Override public void run(final FlowTrigger trigger, Map data) { LocalStorageBackupStorageMediator m = localStorageFactory.getBackupStorageMediator(KVMConstant.KVM_HYPERVISOR_TYPE, backupStorage.getType()); m.downloadBits(getSelfInventory(), backupStorage, backupStorageInstallPath, primaryStorageInstallPath, hostUuid, new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); done(new FlowDoneHandler(completion, chain) { @Override public void handle(Map data) { ImageCacheVO vo = new ImageCacheVO(); vo.setState(ImageCacheState.ready); vo.setMediaType(ImageMediaType.valueOf(image.getMediaType())); vo.setImageUuid(image.getUuid()); vo.setPrimaryStorageUuid(self.getUuid()); vo.setSize(image.getActualSize()); vo.setMd5sum("not calculated"); CacheInstallPath path = new CacheInstallPath(); path.installPath = primaryStorageInstallPath; path.hostUuid = hostUuid; vo.setInstallUrl(path.makeFullPath()); dbf.persist(vo); logger.debug(String.format("downloaded image[uuid:%s, name:%s] to the image cache of local primary storage[uuid: %s, installPath: %s] on host[uuid: %s]", image.getUuid(), image.getName(), self.getUuid(), primaryStorageInstallPath, hostUuid)); completion.success(primaryStorageInstallPath); chain.next(); } }); error(new FlowErrorHandler(completion, chain) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); chain.next(); } }); } }).start(); } @Override public void run(final SyncTaskChain chain) { SimpleQuery<ImageCacheVO> q = dbf.createQuery(ImageCacheVO.class); q.select(ImageCacheVO_.installUrl); q.add(ImageCacheVO_.primaryStorageUuid, Op.EQ, self.getUuid()); q.add(ImageCacheVO_.imageUuid, Op.EQ, image.getUuid()); q.add(ImageCacheVO_.installUrl, Op.LIKE, String.format("%%hostUuid://%s%%", hostUuid)); String fullPath = q.findValue(); if (fullPath == null) { doDownload(chain); return; } CacheInstallPath path = new CacheInstallPath(); path.fullPath = fullPath; final String installPath = path.disassemble().installPath; CheckBitsCmd cmd = new CheckBitsCmd(); cmd.path = installPath; httpCall(CHECK_BITS_PATH, hostUuid, cmd, CheckBitsRsp.class, new ReturnValueCompletion<CheckBitsRsp>(completion, chain) { @Override public void success(CheckBitsRsp rsp) { if (rsp.existing) { logger.debug(String.format("found image[uuid: %s, name: %s] in the image cache of local primary storage[uuid:%s, installPath: %s]", image.getUuid(), image.getName(), self.getUuid(), installPath)); completion.success(installPath); chain.next(); return; } // the image is removed on the host // delete the cache object and re-download it SimpleQuery<ImageCacheVO> q = dbf.createQuery(ImageCacheVO.class); q.add(ImageCacheVO_.primaryStorageUuid, Op.EQ, self.getUuid()); q.add(ImageCacheVO_.imageUuid, Op.EQ, image.getUuid()); q.add(ImageCacheVO_.installUrl, Op.LIKE, String.format("%%hostUuid://%s%%", hostUuid)); ImageCacheVO cvo = q.find(); IncreasePrimaryStorageCapacityMsg rmsg = new IncreasePrimaryStorageCapacityMsg(); rmsg.setDiskSize(cvo.getSize()); rmsg.setPrimaryStorageUuid(cvo.getPrimaryStorageUuid()); bus.makeTargetServiceIdByResourceUuid(rmsg, PrimaryStorageConstant.SERVICE_ID, cvo.getPrimaryStorageUuid()); bus.send(rmsg); returnStorageCapacityToHost(hostUuid, cvo.getSize()); dbf.remove(cvo); doDownload(chain); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); chain.next(); } }); } @Override public String getName() { return getSyncSignature(); } }); } } private void createRootVolume(final InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg msg, final ReturnValueCompletion<InstantiateVolumeOnPrimaryStorageReply> completion) { final ImageSpec ispec = msg.getTemplateSpec(); final ImageInventory image = ispec.getInventory(); if (!ImageMediaType.RootVolumeTemplate.toString().equals(image.getMediaType())) { createEmptyVolume(msg.getVolume(), msg.getDestHost().getUuid(), new ReturnValueCompletion<String>(completion) { @Override public void success(String returnValue) { InstantiateVolumeOnPrimaryStorageReply r = new InstantiateVolumeOnPrimaryStorageReply(); VolumeInventory vol = msg.getVolume(); vol.setInstallPath(returnValue); vol.setFormat(VolumeConstant.VOLUME_FORMAT_QCOW2); r.setVolume(vol); completion.success(r); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); return; } SimpleQuery<BackupStorageVO> q = dbf.createQuery(BackupStorageVO.class); q.add(BackupStorageVO_.uuid, Op.EQ, ispec.getSelectedBackupStorage().getBackupStorageUuid()); BackupStorageVO bs = q.find(); final BackupStorageInventory bsInv = BackupStorageInventory.valueOf(bs); final VolumeInventory volume = msg.getVolume(); final String hostUuid = msg.getDestHost().getUuid(); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("kvm-localstorage-create-root-volume-from-image-%s", image.getUuid())); chain.then(new ShareFlow() { String pathInCache = makeCachedImageInstallUrl(image); String installPath; @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "download-image-to-cache"; @Override public void run(final FlowTrigger trigger, Map data) { ImageCache cache = new ImageCache(); cache.backupStorage = bsInv; cache.backupStorageInstallPath = ispec.getSelectedBackupStorage().getInstallPath(); cache.primaryStorageInstallPath = pathInCache; cache.hostUuid = hostUuid; cache.image = image; cache.download(new ReturnValueCompletion<String>(trigger) { @Override public void success(String returnValue) { pathInCache = returnValue; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "create-template-from-cache"; @Override public void run(final FlowTrigger trigger, Map data) { installPath = makeRootVolumeInstallUrl(volume); CreateVolumeFromCacheCmd cmd = new CreateVolumeFromCacheCmd(); cmd.setInstallUrl(installPath); cmd.setTemplatePathInCache(pathInCache); cmd.setVolumeUuid(volume.getUuid()); httpCall(CREATE_VOLUME_FROM_CACHE_PATH, hostUuid, cmd, CreateVolumeFromCacheRsp.class, new ReturnValueCompletion<CreateVolumeFromCacheRsp>(trigger) { @Override public void success(CreateVolumeFromCacheRsp returnValue) { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { InstantiateVolumeOnPrimaryStorageReply reply = new InstantiateVolumeOnPrimaryStorageReply(); volume.setInstallPath(installPath); reply.setVolume(volume); completion.success(reply); } }); error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); } }); } }).start(); } @Override public void deleteBits(final String path, final String hostUuid, final Completion completion) { deleteBits(path, hostUuid, false, completion); } public void deleteBits(final String path, final String hostUuid, boolean dir, final Completion completion) { DeleteBitsCmd cmd = new DeleteBitsCmd(); cmd.setPath(path); cmd.setHostUuid(hostUuid); String deletePath = dir ? DELETE_DIR_PATH : DELETE_BITS_PATH; httpCall(deletePath, hostUuid, cmd, DeleteBitsRsp.class, new ReturnValueCompletion<DeleteBitsRsp>(completion) { @Override public void success(DeleteBitsRsp returnValue) { if(returnValue.getAvailableCapacity() == null || returnValue.getTotalCapacity() == null){ logger.warn("Deleting bits is successful, " + "but getting capacity is failed, " + "Please check if the storage has been detach from cluster"); } completion.success(); } @Override public void fail(ErrorCode errorCode) { if (!errorCode.isError(HostErrors.OPERATION_FAILURE_GC_ELIGIBLE)) { completion.fail(errorCode); return; } LocalStorageDeleteBitsGC gc = new LocalStorageDeleteBitsGC(); gc.isDir = dir; gc.primaryStorageUuid = self.getUuid(); gc.hostUuid = hostUuid; gc.installPath = path; gc.NAME = String.format("gc-local-storage-%s-delete-bits-on-host-%s", self.getUuid(), hostUuid); gc.submit(); completion.success(); } }); } @Override void handle(final DeleteVolumeOnPrimaryStorageMsg msg, final ReturnValueCompletion<DeleteVolumeOnPrimaryStorageReply> completion) { final DeleteVolumeOnPrimaryStorageReply dreply = new DeleteVolumeOnPrimaryStorageReply(); final String hostUuid = getHostUuidByResourceUuid(msg.getVolume().getUuid(), VolumeVO.class.getSimpleName()); deleteBits(msg.getVolume().getInstallPath(), hostUuid, new Completion(completion) { @Override public void success() { completion.success(dreply); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(final DownloadDataVolumeToPrimaryStorageMsg msg, final ReturnValueCompletion<DownloadDataVolumeToPrimaryStorageReply> completion) { BackupStorageVO bsvo = dbf.findByUuid(msg.getBackupStorageRef().getBackupStorageUuid(), BackupStorageVO.class); LocalStorageBackupStorageMediator m = localStorageFactory.getBackupStorageMediator(KVMConstant.KVM_HYPERVISOR_TYPE, bsvo.getType()); final String installPath = makeDataVolumeInstallUrl(msg.getVolumeUuid()); m.downloadBits(getSelfInventory(), BackupStorageInventory.valueOf(bsvo), msg.getBackupStorageRef().getInstallPath(), installPath, msg.getHostUuid(), new Completion(completion) { @Override public void success() { DownloadDataVolumeToPrimaryStorageReply reply = new DownloadDataVolumeToPrimaryStorageReply(); reply.setFormat(msg.getImage().getFormat()); reply.setInstallPath(installPath); completion.success(reply); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(DeleteBitsOnPrimaryStorageMsg msg, final ReturnValueCompletion<DeleteBitsOnPrimaryStorageReply> completion) { String hostUuid = getHostUuidByResourceUuid(msg.getBitsUuid(), msg.getBitsType()); deleteBits(PathUtil.parentFolder(msg.getInstallPath()), hostUuid, true, new Completion(completion) { @Override public void success() { DeleteBitsOnPrimaryStorageReply reply = new DeleteBitsOnPrimaryStorageReply(); completion.success(reply); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(DownloadIsoToPrimaryStorageMsg msg, final ReturnValueCompletion<DownloadIsoToPrimaryStorageReply> completion) { ImageSpec ispec = msg.getIsoSpec(); SimpleQuery<BackupStorageVO> q = dbf.createQuery(BackupStorageVO.class); q.add(BackupStorageVO_.uuid, Op.EQ, ispec.getSelectedBackupStorage().getBackupStorageUuid()); BackupStorageVO bsvo = q.find(); BackupStorageInventory bsinv = BackupStorageInventory.valueOf(bsvo); ImageCache cache = new ImageCache(); cache.image = ispec.getInventory(); cache.hostUuid = msg.getDestHostUuid(); cache.primaryStorageInstallPath = makeCachedImageInstallUrl(ispec.getInventory()); cache.backupStorage = bsinv; cache.backupStorageInstallPath = ispec.getSelectedBackupStorage().getInstallPath(); cache.download(new ReturnValueCompletion<String>(completion) { @Override public void success(String returnValue) { DownloadIsoToPrimaryStorageReply reply = new DownloadIsoToPrimaryStorageReply(); reply.setInstallPath(returnValue); completion.success(reply); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(DeleteIsoFromPrimaryStorageMsg msg, ReturnValueCompletion<DeleteIsoFromPrimaryStorageReply> completion) { // The ISO is in the image cache, no need to delete it DeleteIsoFromPrimaryStorageReply reply = new DeleteIsoFromPrimaryStorageReply(); completion.success(reply); } @Override protected void handle(InitPrimaryStorageOnHostConnectedMsg msg, final ReturnValueCompletion<PhysicalCapacityUsage> completion) { InitCmd cmd = new InitCmd(); cmd.setHostUuid(msg.getHostUuid()); cmd.setPath(self.getUrl()); httpCall(INIT_PATH, msg.getHostUuid(), cmd, true, AgentResponse.class, new ReturnValueCompletion<AgentResponse>(completion) { @Override public void success(AgentResponse rsp) { PhysicalCapacityUsage usage = new PhysicalCapacityUsage(); usage.totalPhysicalSize = rsp.getTotalCapacity(); usage.availablePhysicalSize = rsp.getAvailableCapacity(); completion.success(usage); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(final TakeSnapshotMsg msg, final String hostUuid, final ReturnValueCompletion<TakeSnapshotReply> completion) { final VolumeSnapshotInventory sp = msg.getStruct().getCurrent(); VolumeInventory vol = VolumeInventory.valueOf(dbf.findByUuid(sp.getVolumeUuid(), VolumeVO.class)); TakeSnapshotOnHypervisorMsg hmsg = new TakeSnapshotOnHypervisorMsg(); hmsg.setHostUuid(hostUuid); hmsg.setVmUuid(vol.getVmInstanceUuid()); hmsg.setVolume(vol); hmsg.setSnapshotName(msg.getStruct().getCurrent().getUuid()); hmsg.setFullSnapshot(msg.getStruct().isFullSnapshot()); String installPath = makeSnapshotInstallPath(vol, sp); hmsg.setInstallPath(installPath); bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid); bus.send(hmsg, new CloudBusCallBack(completion) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { completion.fail(reply.getError()); return; } TakeSnapshotOnHypervisorReply treply = (TakeSnapshotOnHypervisorReply) reply; sp.setSize(treply.getSize()); sp.setPrimaryStorageUuid(self.getUuid()); sp.setPrimaryStorageInstallPath(treply.getSnapshotInstallPath()); sp.setType(VolumeSnapshotConstant.HYPERVISOR_SNAPSHOT_TYPE.toString()); TakeSnapshotReply ret = new TakeSnapshotReply(); ret.setNewVolumeInstallPath(treply.getNewVolumeInstallPath()); ret.setInventory(sp); reserveCapacityOnHost(hostUuid, sp.getSize(), self.getUuid()); completion.success(ret); } }); } @Override void handle(final DeleteSnapshotOnPrimaryStorageMsg msg, final String hostUuid, final ReturnValueCompletion<DeleteSnapshotOnPrimaryStorageReply> completion) { final DeleteSnapshotOnPrimaryStorageReply reply = new DeleteSnapshotOnPrimaryStorageReply(); deleteBits(msg.getSnapshot().getPrimaryStorageInstallPath(), hostUuid, new Completion(completion) { @Override public void success() { completion.success(reply); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(RevertVolumeFromSnapshotOnPrimaryStorageMsg msg, String hostUuid, final ReturnValueCompletion<RevertVolumeFromSnapshotOnPrimaryStorageReply> completion) { VolumeSnapshotInventory sp = msg.getSnapshot(); RevertVolumeFromSnapshotCmd cmd = new RevertVolumeFromSnapshotCmd(); cmd.setSnapshotInstallPath(sp.getPrimaryStorageInstallPath()); httpCall(REVERT_SNAPSHOT_PATH, hostUuid, cmd, RevertVolumeFromSnapshotRsp.class, new ReturnValueCompletion<RevertVolumeFromSnapshotRsp>(completion) { @Override public void success(RevertVolumeFromSnapshotRsp rsp) { RevertVolumeFromSnapshotOnPrimaryStorageReply ret = new RevertVolumeFromSnapshotOnPrimaryStorageReply(); ret.setNewVolumeInstallPath(rsp.getNewVolumeInstallPath()); completion.success(ret); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(ReInitRootVolumeFromTemplateOnPrimaryStorageMsg msg, String hostUuid, final ReturnValueCompletion<ReInitRootVolumeFromTemplateOnPrimaryStorageReply> completion) { RevertVolumeFromSnapshotCmd cmd = new RevertVolumeFromSnapshotCmd(); cmd.setSnapshotInstallPath(makeCachedImageInstallUrlFromImageUuidForTemplate(msg.getVolume().getRootImageUuid())); httpCall(REVERT_SNAPSHOT_PATH, hostUuid, cmd, RevertVolumeFromSnapshotRsp.class, new ReturnValueCompletion<RevertVolumeFromSnapshotRsp>(completion) { @Override public void success(RevertVolumeFromSnapshotRsp rsp) { ReInitRootVolumeFromTemplateOnPrimaryStorageReply ret = new ReInitRootVolumeFromTemplateOnPrimaryStorageReply(); ret.setNewVolumeInstallPath(rsp.getNewVolumeInstallPath()); completion.success(ret); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(BackupVolumeSnapshotFromPrimaryStorageToBackupStorageMsg msg, String hostUuid, final ReturnValueCompletion<BackupVolumeSnapshotFromPrimaryStorageToBackupStorageReply> completion) { VolumeSnapshotInventory sp = msg.getSnapshot(); LocalStorageBackupStorageMediator m = localStorageFactory.getBackupStorageMediator(KVMConstant.KVM_HYPERVISOR_TYPE, msg.getBackupStorage().getType()); BackupStorageAskInstallPathMsg bmsg = new BackupStorageAskInstallPathMsg(); bmsg.setImageMediaType(VolumeSnapshotVO.class.getSimpleName()); bmsg.setBackupStorageUuid(msg.getBackupStorage().getUuid()); bmsg.setImageUuid(sp.getUuid()); bus.makeTargetServiceIdByResourceUuid(bmsg, BackupStorageConstant.SERVICE_ID, msg.getBackupStorage().getUuid()); MessageReply br = bus.call(bmsg); if (!br.isSuccess()) { completion.fail(br.getError()); return; } final String installPath = ((BackupStorageAskInstallPathReply) br).getInstallPath(); m.uploadBits(null, getSelfInventory(), msg.getBackupStorage(), installPath, sp.getPrimaryStorageInstallPath(), hostUuid, new ReturnValueCompletion<String>(completion) { @Override public void success(String installPath) { BackupVolumeSnapshotFromPrimaryStorageToBackupStorageReply reply = new BackupVolumeSnapshotFromPrimaryStorageToBackupStorageReply(); reply.setBackupStorageInstallPath(installPath); completion.success(reply); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(CreateVolumeFromVolumeSnapshotOnPrimaryStorageMsg msg, String hostUuid, final ReturnValueCompletion<CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply> completion) { final CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply reply = new CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply(); final String installPath = makeDataVolumeInstallUrl(msg.getVolumeUuid()); VolumeSnapshotInventory sp = msg.getSnapshot(); MergeSnapshotCmd cmd = new MergeSnapshotCmd(); cmd.setVolumeUuid(sp.getVolumeUuid()); cmd.setSnapshotInstallPath(sp.getPrimaryStorageInstallPath()); cmd.setWorkspaceInstallPath(installPath); httpCall(MERGE_SNAPSHOT_PATH, hostUuid, cmd, MergeSnapshotRsp.class, new ReturnValueCompletion<MergeSnapshotRsp>(completion) { @Override public void success(MergeSnapshotRsp rsp) { reply.setActualSize(rsp.actualSize); reply.setSize(rsp.size); reply.setInstallPath(installPath); completion.success(reply); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(MergeVolumeSnapshotOnPrimaryStorageMsg msg, String hostUuid, final ReturnValueCompletion<MergeVolumeSnapshotOnPrimaryStorageReply> completion) { boolean offline = true; VolumeInventory volume = msg.getTo(); VolumeSnapshotInventory sp = msg.getFrom(); if (volume.getVmInstanceUuid() != null) { SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class); q.select(VmInstanceVO_.state); q.add(VmInstanceVO_.uuid, Op.EQ, volume.getVmInstanceUuid()); VmInstanceState state = q.findValue(); offline = (state == VmInstanceState.Stopped); } final MergeVolumeSnapshotOnPrimaryStorageReply ret = new MergeVolumeSnapshotOnPrimaryStorageReply(); if (offline) { OfflineMergeSnapshotCmd cmd = new OfflineMergeSnapshotCmd(); cmd.setFullRebase(msg.isFullRebase()); cmd.setSrcPath(sp.getPrimaryStorageInstallPath()); cmd.setDestPath(volume.getInstallPath()); httpCall(OFFLINE_MERGE_PATH, hostUuid, cmd, OfflineMergeSnapshotRsp.class, new ReturnValueCompletion<OfflineMergeSnapshotRsp>(completion) { @Override public void success(OfflineMergeSnapshotRsp returnValue) { completion.success(ret); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } else { MergeVolumeSnapshotOnKvmMsg kmsg = new MergeVolumeSnapshotOnKvmMsg(); kmsg.setFullRebase(msg.isFullRebase()); kmsg.setHostUuid(hostUuid); kmsg.setFrom(sp); kmsg.setTo(volume); bus.makeTargetServiceIdByResourceUuid(kmsg, HostConstant.SERVICE_ID, hostUuid); bus.send(kmsg, new CloudBusCallBack(completion) { @Override public void run(MessageReply reply) { if (reply.isSuccess()) { completion.success(ret); } else { completion.fail(reply.getError()); } } }); } } @Override void handle(LocalStorageCreateEmptyVolumeMsg msg, final ReturnValueCompletion<LocalStorageCreateEmptyVolumeReply> completion) { createEmptyVolume(msg.getVolume(), msg.getHostUuid(), msg.getBackingFile(), new ReturnValueCompletion<String>(completion) { @Override public void success(String returnValue) { LocalStorageCreateEmptyVolumeReply reply = new LocalStorageCreateEmptyVolumeReply(); completion.success(reply); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(LocalStorageDirectlyDeleteBitsMsg msg, String hostUuid, final ReturnValueCompletion<LocalStorageDirectlyDeleteBitsReply> completion) { deleteBits(msg.getPath(), hostUuid, new Completion(completion) { @Override public void success() { completion.success(new LocalStorageDirectlyDeleteBitsReply()); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(CreateTemporaryVolumeFromSnapshotMsg msg, final String hostUuid, final ReturnValueCompletion<CreateTemporaryVolumeFromSnapshotReply> completion) { final String workSpaceInstallPath = makeSnapshotWorkspacePath(msg.getImageUuid()); VolumeSnapshotInventory sp = msg.getSnapshot(); MergeSnapshotCmd cmd = new MergeSnapshotCmd(); cmd.setVolumeUuid(sp.getVolumeUuid()); cmd.setSnapshotInstallPath(sp.getPrimaryStorageInstallPath()); cmd.setWorkspaceInstallPath(workSpaceInstallPath); httpCall(MERGE_SNAPSHOT_PATH, hostUuid, cmd, MergeSnapshotRsp.class, new ReturnValueCompletion<MergeSnapshotRsp>(completion) { @Override public void success(MergeSnapshotRsp rsp) { CreateTemporaryVolumeFromSnapshotReply reply = new CreateTemporaryVolumeFromSnapshotReply(); reply.setInstallPath(workSpaceInstallPath); reply.setSize(rsp.size); reply.setActualSize(rsp.actualSize); reply.setHostUuid(hostUuid); completion.success(reply); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(SyncVolumeSizeOnPrimaryStorageMsg msg, String hostUuid, final ReturnValueCompletion<SyncVolumeSizeOnPrimaryStorageReply> completion) { final SyncVolumeSizeOnPrimaryStorageReply reply = new SyncVolumeSizeOnPrimaryStorageReply(); GetVolumeSizeCmd cmd = new GetVolumeSizeCmd(); cmd.installPath = msg.getInstallPath(); cmd.volumeUuid = msg.getVolumeUuid(); cmd.storagePath = Q.New(PrimaryStorageVO.class) .eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()) .select(PrimaryStorageVO_.url) .findValue(); KvmCommandSender sender = new KvmCommandSender(hostUuid); sender.send(cmd, GET_VOLUME_SIZE, new KvmCommandFailureChecker() { @Override public ErrorCode getError(KvmResponseWrapper wrapper) { GetVolumeSizeRsp rsp = wrapper.getResponse(GetVolumeSizeRsp.class); return rsp.isSuccess() ? null : operr(rsp.getError()); } }, new ReturnValueCompletion<KvmResponseWrapper>(completion) { @Override public void success(KvmResponseWrapper returnValue) { GetVolumeSizeRsp rsp = returnValue.getResponse(GetVolumeSizeRsp.class); reply.setActualSize(rsp.actualSize); reply.setSize(rsp.size); completion.success(reply); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(final UploadBitsFromLocalStorageToBackupStorageMsg msg, String hostUuid, final ReturnValueCompletion<UploadBitsFromLocalStorageToBackupStorageReply> completion) { final BackupStorageVO bs = dbf.findByUuid(msg.getBackupStorageUuid(), BackupStorageVO.class); BackupStorageInventory bsinv = BackupStorageInventory.valueOf(bs); LocalStorageBackupStorageMediator m = localStorageFactory.getBackupStorageMediator(KVMConstant.KVM_HYPERVISOR_TYPE, bs.getType()); m.uploadBits(null, getSelfInventory(), bsinv, msg.getBackupStorageInstallPath(), msg.getPrimaryStorageInstallPath(), hostUuid, new ReturnValueCompletion<String>(completion) { @Override public void success(String installPath) { UploadBitsFromLocalStorageToBackupStorageReply reply = new UploadBitsFromLocalStorageToBackupStorageReply(); reply.setBackupStorageInstallPath(installPath); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { logger.warn(String.format("failed to upload template[%s] from local primary storage[uuid: %s] to the backup storage[uuid: %s, path: %s]", msg.getPrimaryStorageInstallPath(), self.getUuid(), bs.getUuid(), msg.getBackupStorageInstallPath())); completion.fail(errorCode); } }); } @Override void handle(final GetVolumeRootImageUuidFromPrimaryStorageMsg msg, String hostUuid, final ReturnValueCompletion<GetVolumeRootImageUuidFromPrimaryStorageReply> completion) { GetVolumeBaseImagePathCmd cmd = new GetVolumeBaseImagePathCmd(); cmd.installPath = msg.getVolume().getInstallPath(); cmd.volumeUuid = msg.getVolume().getUuid(); cmd.storagePath = Q.New(PrimaryStorageVO.class) .eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()) .select(PrimaryStorageVO_.url) .findValue(); new KvmCommandSender(hostUuid).send(cmd, GET_BASE_IMAGE_PATH, new KvmCommandFailureChecker() { @Override public ErrorCode getError(KvmResponseWrapper w) { GetVolumeBaseImagePathRsp rsp = w.getResponse(GetVolumeBaseImagePathRsp.class); return rsp.isSuccess() ? null : operr(rsp.getError()); } }, new ReturnValueCompletion<KvmResponseWrapper>(completion) { @Override public void success(KvmResponseWrapper w) { GetVolumeBaseImagePathRsp rsp = w.getResponse(GetVolumeBaseImagePathRsp.class); String rootImageUuid = new File(rsp.path).getName().split("\\.")[0]; GetVolumeRootImageUuidFromPrimaryStorageReply reply = new GetVolumeRootImageUuidFromPrimaryStorageReply(); reply.setImageUuid(rootImageUuid); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override @MessageSafe void handleHypervisorSpecificMessage(LocalStorageHypervisorSpecificMessage msg) { bus.dealWithUnknownMessage((Message) msg); } @Override void downloadImageToCache(ImageInventory img, String hostUuid, final ReturnValueCompletion<String> completion) { ImageBackupStorageSelector selector = new ImageBackupStorageSelector(); selector.setZoneUuid(self.getZoneUuid()); selector.setImageUuid(img.getUuid()); final String bsUuid = selector.select(); if (bsUuid == null) { throw new OperationFailureException(operr( "the image[uuid:%s, name: %s] is not available to download on any backup storage:\n" + "1. check if image is in status of Deleted\n" + "2. check if the backup storage on which the image is shown as Ready is attached to the zone[uuid:%s]", img.getUuid(), img.getName(), self.getZoneUuid())); } BackupStorageInventory bs = BackupStorageInventory.valueOf(dbf.findByUuid(bsUuid, BackupStorageVO.class)); ImageBackupStorageRefInventory ref = CollectionUtils.find(img.getBackupStorageRefs(), new Function<ImageBackupStorageRefInventory, ImageBackupStorageRefInventory>() { @Override public ImageBackupStorageRefInventory call(ImageBackupStorageRefInventory arg) { return arg.getBackupStorageUuid().equals(bsUuid) ? arg : null; } }); final ImageCache cache = new ImageCache(); cache.image = img; cache.hostUuid = hostUuid; cache.primaryStorageInstallPath = makeCachedImageInstallUrl(img); cache.backupStorage = bs; cache.backupStorageInstallPath = ref.getInstallPath(); cache.download(new ReturnValueCompletion<String>(completion) { @Override public void success(String returnValue) { completion.success(cache.primaryStorageInstallPath); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override void handle(final LocalStorageDeleteImageCacheOnPrimaryStorageMsg msg, String hostUuid, final ReturnValueCompletion<DeleteImageCacheOnPrimaryStorageReply> completion) { FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("clean-up-image-cache-on-local-storage-%s", self.getUuid())); chain.then(new ShareFlow() { @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "ensure-image-is-not-referenced"; @Override public void run(final FlowTrigger trigger, Map data) { GetQCOW2ReferenceCmd cmd = new GetQCOW2ReferenceCmd(); cmd.searchingDir = self.getUrl(); cmd.path = msg.getInstallPath(); cmd.storagePath = Q.New(PrimaryStorageVO.class) .eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()) .select(PrimaryStorageVO_.url) .findValue(); new KvmCommandSender(msg.getHostUuid()).send(cmd, GET_QCOW2_REFERENCE, new KvmCommandFailureChecker() { @Override public ErrorCode getError(KvmResponseWrapper wrapper) { GetQCOW2ReferenceRsp rsp = wrapper.getResponse(GetQCOW2ReferenceRsp.class); return rsp.isSuccess() ? null : operr(rsp.getError()); } }, new ReturnValueCompletion<KvmResponseWrapper>(trigger) { @Override public void success(KvmResponseWrapper w) { GetQCOW2ReferenceRsp rsp = w.getResponse(GetQCOW2ReferenceRsp.class); if (rsp.referencePaths == null || rsp.referencePaths.isEmpty()) { trigger.next(); } else { trigger.fail(errf.stringToInternalError(String.format("[THIS IS A BUG NEEDED TO BE FIXED RIGHT NOW, PLEASE REPORT TO US ASAP] the image cache file[%s] is still referenced by" + " below QCOW2 files:\n%s", msg.getInstallPath(), StringUtils.join(rsp.referencePaths, "\n")))); } } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { deleteBits(PathUtil.parentFolder(msg.getInstallPath()), msg.getHostUuid(), true, new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { DeleteImageCacheOnPrimaryStorageReply reply = new DeleteImageCacheOnPrimaryStorageReply(); completion.success(reply); } }); error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); } }); } }).start(); } @Override public List<Flow> createMigrateBitsVolumeFlow(final MigrateBitsStruct struct) { List<Flow> flows = new ArrayList<Flow>(); SimpleQuery<KVMHostVO> q = dbf.createQuery(KVMHostVO.class); q.select(KVMHostVO_.managementIp, KVMHostVO_.username, KVMHostVO_.password, KVMHostVO_.port); q.add(KVMHostVO_.uuid, Op.EQ, struct.getDestHostUuid()); Tuple t = q.findTuple(); final String mgmtIp = t.get(0, String.class); final String username = t.get(1, String.class); final String password = t.get(2, String.class); final int port = t.get(3, Integer.class); class Context { GetMd5Rsp getMd5Rsp; String backingFilePath; String rootVolumeUuid; Long backingFileSize; String backingFileMd5; ImageVO image; Boolean hasbackingfile = false; } final Context context = new Context(); if (VolumeType.Root.toString().equals(struct.getVolume().getType())) { final boolean downloadImage; String imageUuid = struct.getVolume().getRootImageUuid(); if (imageUuid != null) { context.image = dbf.findByUuid(imageUuid, ImageVO.class); downloadImage = !(context.image == null || context.image.getMediaType() == ImageMediaType.ISO || context.image.getStatus() == ImageStatus.Deleted); } else { downloadImage = false; } if (downloadImage) { flows.add(new NoRollbackFlow() { String __name__ = "download-base-image-to-dst-host"; @Override public void run(final FlowTrigger trigger, Map data) { downloadImageToCache(ImageInventory.valueOf(context.image), struct.getDestHostUuid(), new ReturnValueCompletion<String>(trigger) { @Override public void success(String returnValue) { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); } else { context.hasbackingfile = true; flows.add(new NoRollbackFlow() { String __name__ = "get-backing-file-of-root-volume"; @Override public void run(final FlowTrigger trigger, Map data) { GetBackingFileCmd cmd = new GetBackingFileCmd(); cmd.path = struct.getVolume().getInstallPath(); cmd.volumeUuid = struct.getVolume().getUuid(); httpCall(GET_BACKING_FILE_PATH, struct.getSrcHostUuid(), cmd, GetBackingFileRsp.class, new ReturnValueCompletion<GetBackingFileRsp>(trigger) { @Override public void success(GetBackingFileRsp rsp) { context.backingFilePath = rsp.backingFilePath; context.backingFileSize = rsp.size; context.rootVolumeUuid = cmd.volumeUuid; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flows.add(new Flow() { String __name__ = "reserve-capacity-for-backing-file-on-dst-host"; boolean s = false; @Override public void run(FlowTrigger trigger, Map data) { if (context.backingFilePath == null) { logger.debug("no backing file, skip this flow"); trigger.next(); return; } reserveCapacityOnHost(struct.getDestHostUuid(), context.backingFileSize, self.getUuid()); s = true; trigger.next(); } @Override public void rollback(FlowRollback trigger, Map data) { if (s) { returnStorageCapacityToHost(struct.getDestHostUuid(), context.backingFileSize); } trigger.rollback(); } }); flows.add(new NoRollbackFlow() { String __name__ = "get-md5-of-backing-file"; @Override public void run(final FlowTrigger trigger, Map data) { if (context.backingFilePath == null) { logger.debug("no backing file, skip this flow"); trigger.next(); return; } GetMd5Cmd cmd = new GetMd5Cmd(); GetMd5TO to = new GetMd5TO(); to.resourceUuid = "backing-file"; to.path = context.backingFilePath; cmd.md5s = list(to); cmd.volumeUuid = struct.getVolume().getUuid(); cmd.stage = PrimaryStorageConstant.MIGRATE_VOLUME_BACKING_FILE_GET_MD5_STAGE; httpCall(GET_MD5_PATH, struct.getSrcHostUuid(), cmd, false, GetMd5Rsp.class, new ReturnValueCompletion<GetMd5Rsp>(trigger) { @Override public void success(GetMd5Rsp rsp) { context.backingFileMd5 = rsp.md5s.get(0).md5; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flows.add(new Flow() { String __name__ = "migrate-backing-file"; boolean s = false; private void migrate(final FlowTrigger trigger) { // sync here for migrating multiple volumes having the same backing file thdf.chainSubmit(new ChainTask(trigger) { @Override public String getSyncSignature() { return String.format("migrate-backing-file-%s-to-host-%s", context.backingFilePath, struct.getDestHostUuid()); } @Override public void run(final SyncTaskChain chain) { final CopyBitsFromRemoteCmd cmd = new CopyBitsFromRemoteCmd(); cmd.dstIp = mgmtIp; cmd.dstUsername = username; cmd.dstPassword = password; cmd.dstPort = port; cmd.paths = list(context.backingFilePath); cmd.uuid = context.rootVolumeUuid; cmd.stage = PrimaryStorageConstant.MIGRATE_VOLUME_BACKING_FILE_COPY_STAGE; httpCall(LocalStorageKvmMigrateVmFlow.COPY_TO_REMOTE_BITS_PATH, struct.getSrcHostUuid(), cmd, false, AgentResponse.class, new ReturnValueCompletion<AgentResponse>(trigger, chain) { @Override public void success(AgentResponse rsp) { s = true; trigger.next(); chain.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); chain.next(); } }); } @Override public String getName() { return getSyncSignature(); } }); } @Override public void run(final FlowTrigger trigger, Map data) { if (context.backingFilePath == null) { logger.debug("no backing file, skip this flow"); trigger.next(); return; } checkIfExistOnDst(new ReturnValueCompletion<Boolean>(trigger) { @Override public void success(Boolean existing) { if (existing) { // DO NOT set success = true here, otherwise the rollback // will delete the backing file which belongs to others on the dst host logger.debug(String.format("found %s on the dst host[uuid:%s], don't copy it", context.backingFilePath, struct.getDestHostUuid())); trigger.next(); } else { migrate(trigger); } } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } private void checkIfExistOnDst(final ReturnValueCompletion<Boolean> completion) { CheckBitsCmd cmd = new CheckBitsCmd(); cmd.path = context.backingFilePath; cmd.username = username; httpCall(CHECK_BITS_PATH, struct.getDestHostUuid(), cmd, CheckBitsRsp.class, new ReturnValueCompletion<CheckBitsRsp>(completion) { @Override public void success(CheckBitsRsp rsp) { completion.success(rsp.existing); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override public void rollback(FlowRollback trigger, Map data) { if (s) { deleteBits(context.backingFilePath, struct.getDestHostUuid(), new Completion(null) { @Override public void success() { // ignore } @Override public void fail(ErrorCode errorCode) { //TODO add GC logger.warn(String.format("failed to delete %s on the host[uuid:%s], %s", struct.getDestHostUuid(), context.backingFilePath, errorCode)); } }); } trigger.rollback(); } }); flows.add(new NoRollbackFlow() { String __name__ = "check-md5-of-backing-file-on-dst-host"; @Override public void run(final FlowTrigger trigger, Map data) { if (context.backingFilePath == null) { logger.debug("no backing file, skip this flow"); trigger.next(); return; } Md5TO to = new Md5TO(); to.resourceUuid = "backing-file"; to.path = context.backingFilePath; to.md5 = context.backingFileMd5; CheckMd5sumCmd cmd = new CheckMd5sumCmd(); cmd.md5s = list(to); cmd.volumeUuid = struct.getVolume().getUuid(); cmd.stage = PrimaryStorageConstant.MIGRATE_VOLUME_BACKING_FILE_CHECK_MD5_STAGE; httpCall(CHECK_MD5_PATH, struct.getDestHostUuid(), cmd, false, AgentResponse.class, new ReturnValueCompletion<AgentResponse>(trigger) { @Override public void success(AgentResponse returnValue) { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); } } flows.add(new NoRollbackFlow() { String __name__ = "get-md5-on-src-host"; @Override public void run(final FlowTrigger trigger, Map data) { GetMd5Cmd cmd = new GetMd5Cmd(); cmd.sendCommandUrl = restf.getSendCommandUrl(); cmd.volumeUuid = struct.getVolume().getUuid(); if (context.hasbackingfile) { cmd.stage = PrimaryStorageConstant.MIGRATE_VOLUME_AFTER_BACKING_FILE_GET_MD5_STAGE; } else { cmd.stage = PrimaryStorageConstant.MIGRATE_VOLUME_GET_MD5_STAGE; } cmd.md5s = CollectionUtils.transformToList(struct.getInfos(), new Function<GetMd5TO, ResourceInfo>() { @Override public GetMd5TO call(ResourceInfo arg) { GetMd5TO to = new GetMd5TO(); to.path = arg.getPath(); to.resourceUuid = arg.getResourceRef().getResourceUuid(); return to; } }); httpCall(GET_MD5_PATH, struct.getSrcHostUuid(), cmd, false, GetMd5Rsp.class, new ReturnValueCompletion<GetMd5Rsp>(trigger) { @Override public void success(GetMd5Rsp rsp) { context.getMd5Rsp = rsp; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flows.add(new Flow() { String __name__ = "migrate-bits-to-dst-host"; List<String> migrated; @Override public void run(final FlowTrigger trigger, Map data) { final CopyBitsFromRemoteCmd cmd = new CopyBitsFromRemoteCmd(); cmd.dstIp = mgmtIp; cmd.dstUsername = username; cmd.dstPassword = password; cmd.dstPort = port; cmd.sendCommandUrl = restf.getSendCommandUrl(); if(context.hasbackingfile) { cmd.stage = PrimaryStorageConstant.MIGRATE_VOLUME_AFTER_BACKING_FILE_COPY_STAGE; } else { cmd.stage = PrimaryStorageConstant.MIGRATE_VOLUME_COPY_STAGE; } cmd.paths = CollectionUtils.transformToList(struct.getInfos(), new Function<String, ResourceInfo>() { @Override public String call(ResourceInfo arg) { return arg.getPath(); } }); cmd.uuid = struct.getInfos().get(0).getResourceRef().getResourceUuid(); httpCall(LocalStorageKvmMigrateVmFlow.COPY_TO_REMOTE_BITS_PATH, struct.getSrcHostUuid(), cmd, false, AgentResponse.class, new ReturnValueCompletion<AgentResponse>(trigger) { @Override public void success(AgentResponse rsp) { migrated = cmd.paths; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } @Override public void rollback(FlowRollback trigger, Map data) { if (migrated != null) { new Runnable() { @Override @AsyncThread public void run() { doDelete(migrated.iterator()); } private void doDelete(final Iterator<String> it) { if (!it.hasNext()) { return; } final String path = it.next(); deleteBits(path, struct.getDestHostUuid(), new Completion(trigger) { @Override public void success() { doDelete(it); } @Override public void fail(ErrorCode errorCode) { //TODO add GC logger.warn(String.format("failed to delete %s on the host[uuid:%s], %s", path, struct.getDestHostUuid(), errorCode)); doDelete(it); } }); } }.run(); } trigger.rollback(); } }); flows.add(new NoRollbackFlow() { String __name__ = "check-md5-on-dst"; @Override public void run(final FlowTrigger trigger, Map data) { CheckMd5sumCmd cmd = new CheckMd5sumCmd(); cmd.sendCommandUrl = restf.getSendCommandUrl(); cmd.md5s = context.getMd5Rsp.md5s; cmd.volumeUuid = struct.getVolume().getUuid(); if (context.hasbackingfile) { cmd.stage = PrimaryStorageConstant.MIGRATE_VOLUME_AFTER_BACKING_FILE_CHECK_MD5_STAGE; } else { cmd.stage = PrimaryStorageConstant.MIGRATE_VOLUME_CHECK_MD5_STAGE; } httpCall(CHECK_MD5_PATH, struct.getDestHostUuid(), cmd, false, AgentResponse.class, new ReturnValueCompletion<AgentResponse>(trigger) { @Override public void success(AgentResponse rsp) { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); return flows; } @Override public void detachHook(String clusterUuid, final Completion completion) { SimpleQuery<HostVO> q = dbf.createQuery(HostVO.class); q.select(HostVO_.uuid); q.add(HostVO_.clusterUuid, Op.EQ, clusterUuid); final List<String> hostUuids = q.listValue(); if (hostUuids.isEmpty()) { completion.success(); return; } SimpleQuery<LocalStorageHostRefVO> refq = dbf.createQuery(LocalStorageHostRefVO.class); refq.add(LocalStorageHostRefVO_.primaryStorageUuid, Op.EQ, self.getUuid()); refq.add(LocalStorageHostRefVO_.hostUuid, Op.IN, hostUuids); List<LocalStorageHostRefVO> refs = refq.list(); if (!refs.isEmpty()) { dbf.removeCollection(refs, LocalStorageHostRefVO.class); long total = 0; long avail = 0; long pt = 0; long pa = 0; long su = 0; for (LocalStorageHostRefVO ref : refs) { total += ref.getTotalCapacity(); avail += ref.getAvailableCapacity(); pt += ref.getTotalPhysicalCapacity(); pa += ref.getAvailablePhysicalCapacity(); su += ref.getSystemUsedCapacity(); } // after detaching, total capacity on those hosts should be deducted // from both total and available capacity of the primary storage decreaseCapacity(total, avail, pt, pa, su); } completion.success(); } @Override public void attachHook(String clusterUuid, final Completion completion) { // get all hosts of cluster SimpleQuery<HostVO> q = dbf.createQuery(HostVO.class); q.select(HostVO_.uuid); q.add(HostVO_.clusterUuid, Op.EQ, clusterUuid); final List<String> hostUuids = q.listValue(); if (hostUuids.isEmpty()) { completion.success(); return; } // make init msg for each host List<KVMHostAsyncHttpCallMsg> msgs = CollectionUtils.transformToList(hostUuids, new Function<KVMHostAsyncHttpCallMsg, String>() { @Override public KVMHostAsyncHttpCallMsg call(String arg) { InitCmd cmd = new InitCmd(); cmd.uuid = self.getUuid(); cmd.path = self.getUrl(); cmd.hostUuid = arg; KVMHostAsyncHttpCallMsg msg = new KVMHostAsyncHttpCallMsg(); msg.setCommand(cmd); msg.setCommandTimeout(timeoutMgr.getTimeout(cmd.getClass(), "5m")); msg.setPath(INIT_PATH); msg.setHostUuid(arg); bus.makeTargetServiceIdByResourceUuid(msg, HostConstant.SERVICE_ID, arg); return msg; } }); bus.send(msgs, new CloudBusListCallBack(completion) { @Override public void run(List<MessageReply> replies) { long total = 0; long avail = 0; long systemUsed = 0; List<LocalStorageHostRefVO> refs = new ArrayList<>(); for (MessageReply reply : replies) { String hostUuid = hostUuids.get(replies.indexOf(reply)); if (!reply.isSuccess()) { logger.warn(String.format("cannot get the physical capacity of local storage on the host[uuid:%s], %s", hostUuid, reply.getError())); continue; } KVMHostAsyncHttpCallReply r = reply.castReply(); AgentResponse rsp = r.toResponse(AgentResponse.class); if (!rsp.isSuccess()) { logger.warn(String.format("cannot get the physical capacity of local storage on the host[uuid:%s], %s", hostUuid, rsp.getError())); continue; } { SimpleQuery<LocalStorageHostRefVO> sq = dbf.createQuery(LocalStorageHostRefVO.class); sq.add(LocalStorageHostRefVO_.primaryStorageUuid, Op.EQ, self.getUuid()); sq.add(LocalStorageHostRefVO_.hostUuid, Op.EQ, hostUuid); if (sq.isExists()) { logger.debug(String.format("host[uuid :%s] is already in the local primary storage[uuid: %s]", hostUuid, self.getUuid())); continue; } } total += rsp.getTotalCapacity(); avail += rsp.getAvailableCapacity(); systemUsed += (rsp.getTotalCapacity() - rsp.getAvailableCapacity()); LocalStorageHostRefVO ref = new LocalStorageHostRefVO(); ref.setPrimaryStorageUuid(self.getUuid()); ref.setHostUuid(hostUuid); ref.setAvailablePhysicalCapacity(rsp.getAvailableCapacity()); ref.setAvailableCapacity(rsp.getAvailableCapacity()); ref.setTotalCapacity(rsp.getTotalCapacity()); ref.setTotalPhysicalCapacity(rsp.getTotalCapacity()); ref.setSystemUsedCapacity(rsp.getTotalCapacity() - rsp.getAvailableCapacity()); refs.add(ref); } dbf.persistCollection(refs); increaseCapacity(total, avail, total, avail, systemUsed); completion.success(); } }); } @Override protected void handle(final CreateTemplateFromVolumeOnPrimaryStorageMsg msg) { final LocalStorageResourceRefVO ref = Q.New(LocalStorageResourceRefVO.class) .eq(LocalStorageResourceRefVO_.resourceUuid, msg.getVolumeInventory().getUuid()) .find(); final CreateTemplateFromVolumeOnPrimaryStorageReply reply = new CreateTemplateFromVolumeOnPrimaryStorageReply(); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("create-image-%s-from-volume-%s", msg.getImageInventory().getUuid(), msg.getVolumeInventory().getUuid())); chain.then(new ShareFlow() { String temporaryTemplatePath = makeTemplateFromVolumeInWorkspacePath(msg.getImageInventory().getUuid()); String backupStorageInstallPath; @Override public void setup() { flow(new Flow() { String __name__ = "reserve-capacity-on-the-host-for-template"; long requiredSize = ratioMgr.calculateByRatio(self.getUuid(), msg.getVolumeInventory().getSize()); @Override public void run(FlowTrigger trigger, Map data) { reserveCapacityOnHost(ref.getHostUuid(), requiredSize, ref.getPrimaryStorageUuid()); trigger.next(); } @Override public void rollback(FlowRollback trigger, Map data) { returnStorageCapacityToHost(ref.getHostUuid(), requiredSize); trigger.rollback(); } }); flow(new Flow() { String __name__ = "create-temporary-template"; @Override public void run(final FlowTrigger trigger, Map data) { CreateTemplateFromVolumeCmd cmd = new CreateTemplateFromVolumeCmd(); cmd.setInstallPath(temporaryTemplatePath); cmd.setVolumePath(msg.getVolumeInventory().getInstallPath()); httpCall(CREATE_TEMPLATE_FROM_VOLUME, ref.getHostUuid(), cmd, false, CreateTemplateFromVolumeRsp.class, new ReturnValueCompletion<CreateTemplateFromVolumeRsp>(trigger) { @Override public void success(CreateTemplateFromVolumeRsp rsp) { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } @Override public void rollback(final FlowRollback trigger, Map data) { deleteBits(temporaryTemplatePath, ref.getHostUuid(), new Completion(trigger) { @Override public void success() { trigger.rollback(); } @Override public void fail(ErrorCode errorCode) { logger.warn(String.format("failed to delete %s on primary storage[uuid: %s], %s; continue to rollback", temporaryTemplatePath, self.getUuid(), errorCode)); trigger.rollback(); } }); } }); flow(new NoRollbackFlow() { String __name__ = "upload-template-to-backup-storage"; @Override public void run(final FlowTrigger trigger, Map data) { BackupStorageAskInstallPathMsg bmsg = new BackupStorageAskInstallPathMsg(); bmsg.setBackupStorageUuid(msg.getBackupStorageUuid()); bmsg.setImageMediaType(msg.getImageInventory().getMediaType()); bmsg.setImageUuid(msg.getImageInventory().getUuid()); bus.makeTargetServiceIdByResourceUuid(bmsg, BackupStorageConstant.SERVICE_ID, msg.getBackupStorageUuid()); MessageReply br = bus.call(bmsg); if (!br.isSuccess()) { trigger.fail(br.getError()); return; } backupStorageInstallPath = ((BackupStorageAskInstallPathReply) br).getInstallPath(); BackupStorageVO bsvo = dbf.findByUuid(msg.getBackupStorageUuid(), BackupStorageVO.class); LocalStorageBackupStorageMediator m = localStorageFactory.getBackupStorageMediator(KVMConstant.KVM_HYPERVISOR_TYPE, bsvo.getType()); m.uploadBits(null, getSelfInventory(), BackupStorageInventory.valueOf(bsvo), backupStorageInstallPath, temporaryTemplatePath, ref.getHostUuid(), new ReturnValueCompletion<String>(trigger) { @Override public void success(String installPath) { backupStorageInstallPath = installPath; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "delete-temporary-template-on-primary-storage"; @Override public void run(final FlowTrigger trigger, Map data) { deleteBits(temporaryTemplatePath, ref.getHostUuid(), new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { //TODO: add GC logger.warn(String.format("failed to delete %s on local primary storage[uuid: %s], %s; need a cleanup", temporaryTemplatePath, self.getUuid(), errorCode)); trigger.next(); } }); } }); flow(new NoRollbackFlow() { String __name__ = "return-capacity-of-temporary-template-to-host"; @Override public void run(FlowTrigger trigger, Map data) { returnStorageCapacityToHost(ref.getHostUuid(), msg.getVolumeInventory().getSize()); trigger.next(); } }); done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { reply.setFormat(msg.getVolumeInventory().getFormat()); reply.setTemplateBackupStorageInstallPath(backupStorageInstallPath); bus.reply(msg, reply); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { reply.setError(errCode); bus.reply(msg, reply); } }); } }).start(); } }