package org.zstack.storage.primary.smp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.compute.vm.ImageBackupStorageSelector;
import org.zstack.core.cloudbus.CloudBusCallBack;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
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.core.*;
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.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.kvm.*;
import org.zstack.storage.backup.sftp.GetSftpBackupStorageDownloadCredentialMsg;
import org.zstack.storage.backup.sftp.GetSftpBackupStorageDownloadCredentialReply;
import org.zstack.storage.backup.sftp.SftpBackupStorageConstant;
import org.zstack.storage.primary.PrimaryStorageCapacityUpdater;
import org.zstack.storage.primary.PrimaryStoragePathMaker;
import org.zstack.storage.primary.PrimaryStoragePhysicalCapacityManager;
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 javax.persistence.TypedQuery;
import java.io.File;
import java.util.*;
/**
* Created by xing5 on 2016/3/26.
*/
public class KvmBackend extends HypervisorBackend {
private static final CLogger logger = Utils.getLogger(KvmBackend.class);
@Autowired
protected ApiTimeoutManager timeoutManager;
@Autowired
protected PrimaryStorageOverProvisioningManager ratioMgr;
@Autowired
protected PrimaryStoragePhysicalCapacityManager physicalCapacityMgr;
@Autowired
protected PluginRegistry pluginRgty;
public static class AgentCmd {
public String mountPoint;
}
public static class AgentRsp {
public boolean success = true;
public String error;
public Long totalCapacity;
public Long availableCapacity;
}
public static class ConnectCmd extends AgentCmd {
public String uuid;
}
@ApiTimeout(apiClasses = {APICreateVmInstanceMsg.class})
public static class CreateVolumeFromCacheCmd extends AgentCmd {
public String templatePathInCache;
public String installPath;
public String volumeUuid;
}
public static class DeleteBitsCmd extends AgentCmd {
public String path;
}
@ApiTimeout(apiClasses = {
APICreateRootVolumeTemplateFromRootVolumeMsg.class,
APICreateDataVolumeTemplateFromVolumeMsg.class
})
public static class CreateTemplateFromVolumeCmd extends AgentCmd {
public String installPath;
public String volumePath;
}
public static class SftpUploadBitsCmd extends AgentCmd {
public String primaryStorageInstallPath;
public String backupStorageInstallPath;
public String hostname;
public String username;
public String sshKey;
public int sshPort;
}
@ApiTimeout(apiClasses = {APIAddImageMsg.class})
public static class SftpDownloadBitsCmd extends AgentCmd {
public String sshKey;
public int sshPort;
public String hostname;
public String username;
public String backupStorageInstallPath;
public String primaryStorageInstallPath;
}
public static class RevertVolumeFromSnapshotCmd extends AgentCmd {
public String snapshotInstallPath;
}
public static class RevertVolumeFromSnapshotRsp extends AgentRsp {
@Validation
public String newVolumeInstallPath;
}
@ApiTimeout(apiClasses = {APICreateDataVolumeFromVolumeSnapshotMsg.class})
public static class MergeSnapshotCmd extends AgentCmd {
public String volumeUuid;
public String snapshotInstallPath;
public String workspaceInstallPath;
}
public static class MergeSnapshotRsp extends AgentRsp {
public long actualSize;
public long size;
}
@ApiTimeout(apiClasses = {APICreateDataVolumeFromVolumeSnapshotMsg.class, APIDeleteVolumeSnapshotMsg.class})
public static class OfflineMergeSnapshotCmd extends AgentCmd {
public String srcPath;
public String destPath;
public boolean fullRebase;
}
public static class CreateEmptyVolumeCmd extends AgentCmd {
public String installPath;
public long size;
public String name;
public String volumeUuid;
}
public static class CheckBitsCmd extends AgentCmd {
public String path;
}
public static class CheckBitsRsp extends AgentRsp {
public boolean existing;
}
public static class GetVolumeSizeCmd extends AgentCmd {
public String volumeUuid;
public String installPath;
}
public static class GetVolumeSizeRsp extends AgentRsp {
public Long actualSize;
public Long size;
}
public static final String CONNECT_PATH = "/sharedmountpointprimarystorage/connect";
public static final String CREATE_VOLUME_FROM_CACHE_PATH = "/sharedmountpointprimarystorage/createrootvolume";
public static final String DELETE_BITS_PATH = "/sharedmountpointprimarystorage/bits/delete";
public static final String CREATE_TEMPLATE_FROM_VOLUME_PATH = "/sharedmountpointprimarystorage/createtemplatefromvolume";
public static final String UPLOAD_BITS_TO_SFTP_BACKUPSTORAGE_PATH = "/sharedmountpointprimarystorage/sftp/upload";
public static final String DOWNLOAD_BITS_FROM_SFTP_BACKUPSTORAGE_PATH = "/sharedmountpointprimarystorage/sftp/download";
public static final String REVERT_VOLUME_FROM_SNAPSHOT_PATH = "/sharedmountpointprimarystorage/volume/revertfromsnapshot";
public static final String MERGE_SNAPSHOT_PATH = "/sharedmountpointprimarystorage/snapshot/merge";
public static final String OFFLINE_MERGE_SNAPSHOT_PATH = "/sharedmountpointprimarystorage/snapshot/offlinemerge";
public static final String CREATE_EMPTY_VOLUME_PATH = "/sharedmountpointprimarystorage/volume/createempty";
public static final String CHECK_BITS_PATH = "/sharedmountpointprimarystorage/bits/check";
public static final String GET_VOLUME_SIZE_PATH = "/sharedmountpointprimarystorage/volume/getsize";
public KvmBackend(PrimaryStorageVO self) {
super(self);
}
private List<String> findConnectedHostByClusterUuid(String clusterUuid, boolean exceptionOnNotFound) {
SimpleQuery<HostVO> q = dbf.createQuery(HostVO.class);
q.select(HostVO_.uuid);
q.add(HostVO_.clusterUuid, Op.EQ, clusterUuid);
q.add(HostVO_.status, Op.EQ, HostStatus.Connected);
List<String> hostUuids = q.listValue();
if (hostUuids.isEmpty() && exceptionOnNotFound) {
throw new OperationFailureException(operr("no connected host found in the cluster[uuid:%s]", clusterUuid));
}
return hostUuids;
}
protected <T extends AgentRsp> void httpCall(String path, final String hostUuid, AgentCmd cmd, final Class<T> rspType, final ReturnValueCompletion<T> completion) {
httpCall(path, hostUuid, cmd, false, rspType, completion);
}
private <T extends AgentRsp> void httpCall(String path, final String hostUuid, AgentCmd cmd, boolean noCheckStatus, final Class<T> rspType, final ReturnValueCompletion<T> completion) {
cmd.mountPoint = self.getMountPath();
KVMHostAsyncHttpCallMsg msg = new KVMHostAsyncHttpCallMsg();
msg.setHostUuid(hostUuid);
msg.setPath(path);
msg.setNoStatusCheck(noCheckStatus);
msg.setCommand(cmd);
msg.setCommandTimeout(timeoutManager.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();
final T rsp = r.toResponse(rspType);
if (!rsp.success) {
completion.fail(operr(rsp.error));
return;
}
if (rsp.totalCapacity != null && rsp.availableCapacity != null) {
new PrimaryStorageCapacityUpdater(self.getUuid()).run(new PrimaryStorageCapacityUpdaterRunnable() {
@Override
public PrimaryStorageCapacityVO call(PrimaryStorageCapacityVO cap) {
if (cap.getAvailableCapacity() == 0) {
cap.setAvailableCapacity(rsp.availableCapacity);
}
cap.setTotalCapacity(rsp.totalCapacity);
cap.setTotalPhysicalCapacity(rsp.totalCapacity);
cap.setAvailablePhysicalCapacity(rsp.availableCapacity);
return cap;
}
});
}
completion.success(rsp);
}
});
}
private void connect(String hostUuid, final Completion completion) {
ConnectCmd cmd = new ConnectCmd();
cmd.uuid = self.getUuid();
cmd.mountPoint = self.getMountPath();
httpCall(CONNECT_PATH, hostUuid, cmd, true, AgentRsp.class, new ReturnValueCompletion<AgentRsp>(completion) {
@Override
public void success(AgentRsp rsp) {
completion.success();
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
@Override
public void attachHook(final String clusterUuid, final Completion completion) {
connectByClusterUuid(clusterUuid, completion);
}
@Override
void handle(InstantiateVolumeOnPrimaryStorageMsg msg, ReturnValueCompletion<InstantiateVolumeOnPrimaryStorageReply> completion) {
if (msg instanceof InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg) {
createRootVolume((InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg) msg, completion);
} else {
createEmptyVolume(msg.getVolume(), msg.getDestHost().getUuid(), completion);
}
}
public String makeRootVolumeInstallUrl(VolumeInventory vol) {
return PathUtil.join(self.getMountPath(), PrimaryStoragePathMaker.makeRootVolumeInstallPath(vol));
}
public String makeDataVolumeInstallUrl(String volUuid) {
return PathUtil.join(self.getMountPath(), PrimaryStoragePathMaker.makeDataVolumeInstallPath(volUuid));
}
public String makeCachedImageInstallUrl(ImageInventory iminv) {
return PathUtil.join(self.getMountPath(), PrimaryStoragePathMaker.makeCachedImageInstallPath(iminv));
}
public String makeCachedImageInstallUrlFromImageUuidForTemplate(String imageUuid) {
return PathUtil.join(self.getMountPath(), PrimaryStoragePathMaker.makeCachedImageInstallPathFromImageUuidForTemplate(imageUuid));
}
public String makeTemplateFromVolumeInWorkspacePath(String imageUuid) {
return PathUtil.join(self.getMountPath(), "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.getMountPath(),
PrimaryStoragePathMaker.makeImageFromSnapshotWorkspacePath(imageUuid),
String.format("%s.qcow2", imageUuid)
);
}
class ImageCache {
ImageInventory image;
String backupStorageUuid;
String primaryStorageInstallPath;
String backupStorageInstallPath;
void download(final ReturnValueCompletion<String> completion) {
DebugUtils.Assert(image != null, "image cannot be null");
DebugUtils.Assert(backupStorageUuid != null, "backup storage 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-shared-mountpoint-storage-%s-cache", image.getUuid(), self.getUuid());
}
private void doDownload(final SyncTaskChain chain) {
FlowChain fchain = FlowChainBuilder.newShareFlowChain();
fchain.setName(String.format("download-image-%s-to-shared-mountpoint-storage-%s-cache",
image.getUuid(), self.getUuid()));
fchain.then(new ShareFlow() {
@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.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;
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 NoRollbackFlow() {
String __name__ = "download";
@Override
public void run(final FlowTrigger trigger, Map data) {
BackupStorageKvmDownloader downloader = getBackupStorageKvmDownloader(backupStorageUuid);
downloader.downloadBits(backupStorageInstallPath, primaryStorageInstallPath, 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");
vo.setInstallUrl(primaryStorageInstallPath);
dbf.persist(vo);
logger.debug(String.format("downloaded image[uuid:%s, name:%s] to the image cache of local shared mount point storage[uuid: %s, installPath: %s]",
image.getUuid(), image.getName(), self.getUuid(), primaryStorageInstallPath));
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());
String fullPath = q.findValue();
if (fullPath == null) {
doDownload(chain);
return;
}
CheckBitsCmd cmd = new CheckBitsCmd();
cmd.path = primaryStorageInstallPath;
new Do().go(CHECK_BITS_PATH, cmd, CheckBitsRsp.class, new ReturnValueCompletion<AgentRsp>(completion, chain) {
@Override
public void success(AgentRsp returnValue) {
CheckBitsRsp rsp = (CheckBitsRsp) returnValue;
if (rsp.existing) {
completion.success(primaryStorageInstallPath);
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());
ImageCacheVO vo = q.find();
IncreasePrimaryStorageCapacityMsg imsg = new IncreasePrimaryStorageCapacityMsg();
imsg.setDiskSize(vo.getSize());
imsg.setPrimaryStorageUuid(vo.getPrimaryStorageUuid());
bus.makeTargetServiceIdByResourceUuid(imsg, PrimaryStorageConstant.SERVICE_ID, vo.getPrimaryStorageUuid());
bus.send(imsg);
dbf.remove(vo);
doDownload(chain);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
chain.next();
}
});
}
@Override
public String getName() {
return getSyncSignature();
}
});
}
}
private void createEmptyVolume(final VolumeInventory volume, String hostUuid, final ReturnValueCompletion<InstantiateVolumeOnPrimaryStorageReply> completion) {
final CreateEmptyVolumeCmd cmd = new CreateEmptyVolumeCmd();
cmd.installPath = VolumeType.Root.toString().equals(volume.getType()) ? makeRootVolumeInstallUrl(volume) : makeDataVolumeInstallUrl(volume.getUuid());
cmd.name = volume.getName();
cmd.size = volume.getSize();
cmd.volumeUuid = volume.getUuid();
new Do(hostUuid).go(CREATE_EMPTY_VOLUME_PATH, cmd, new ReturnValueCompletion<AgentRsp>(completion) {
@Override
public void success(AgentRsp returnValue) {
InstantiateVolumeOnPrimaryStorageReply reply = new InstantiateVolumeOnPrimaryStorageReply();
volume.setInstallPath(cmd.installPath);
volume.setFormat(VolumeConstant.VOLUME_FORMAT_QCOW2);
reply.setVolume(volume);
completion.success(reply);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
private void createRootVolume(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(), completion);
return;
}
final VolumeInventory volume = msg.getVolume();
final String hostUuid = msg.getDestHost().getUuid();
FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName(String.format("kvm-smp-storage-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.backupStorageUuid = ispec.getSelectedBackupStorage().getBackupStorageUuid();
cache.backupStorageInstallPath = ispec.getSelectedBackupStorage().getInstallPath();
cache.primaryStorageInstallPath = pathInCache;
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.installPath = installPath;
cmd.templatePathInCache = pathInCache;
cmd.volumeUuid = volume.getUuid();
httpCall(CREATE_VOLUME_FROM_CACHE_PATH, hostUuid, cmd, AgentRsp.class, new ReturnValueCompletion<AgentRsp>(trigger) {
@Override
public void success(AgentRsp rsp) {
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);
volume.setFormat(VolumeConstant.VOLUME_FORMAT_QCOW2);
reply.setVolume(volume);
completion.success(reply);
}
});
error(new FlowErrorHandler(completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
completion.fail(errCode);
}
});
}
}).start();
}
@Transactional(readOnly = true)
private List<String> findConnectedHosts(int num) {
String sql = "select h.uuid from HostVO h, PrimaryStorageClusterRefVO ref where ref.clusterUuid = h.clusterUuid and" +
" ref.primaryStorageUuid = :psUuid and h.status = :status and h.hypervisorType = :htype";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("psUuid", self.getUuid());
q.setParameter("status", HostStatus.Connected);
q.setParameter("htype", KVMConstant.KVM_HYPERVISOR_TYPE);
q.setMaxResults(num);
List<String> hostUuids = q.getResultList();
Collections.shuffle(hostUuids);
return hostUuids;
}
private String findConnectedHost() {
List<String> huuids = findConnectedHosts(50);
if (huuids.isEmpty()) {
throw new OperationFailureException(operr("cannot find any connected host to perform the operation"));
}
return huuids.get(0);
}
class Do {
private List<String> hostUuids;
private List<ErrorCode> errors = new ArrayList<ErrorCode>();
public Do(String huuid) {
hostUuids = new ArrayList<String>();
hostUuids.add(huuid);
}
public Do() {
hostUuids = findConnectedHosts(50);
if (hostUuids.isEmpty()) {
throw new OperationFailureException(operr("cannot find any connected host to perform the operation, it seems all KVM hosts" +
" in the clusters attached with the shared mount point storage[uuid:%s] are disconnected",
self.getUuid()));
}
}
void go(String path, AgentCmd cmd, ReturnValueCompletion<AgentRsp> completion) {
go(path, cmd, AgentRsp.class, completion);
}
void go(String path, AgentCmd cmd, Class rspType, ReturnValueCompletion<AgentRsp> completion) {
doCommand(hostUuids.iterator(), path, cmd, rspType, completion);
}
private void doCommand(final Iterator<String> it, final String path, final AgentCmd cmd, final Class rspType, final ReturnValueCompletion<AgentRsp> completion) {
if (!it.hasNext()) {
completion.fail(errf.stringToOperationError("an operation failed on all hosts", errors));
return;
}
final String hostUuid = it.next();
httpCall(path, hostUuid, cmd, rspType, new ReturnValueCompletion<AgentRsp>(completion) {
@Override
public void success(AgentRsp rsp) {
completion.success(rsp);
}
@Override
public void fail(ErrorCode errorCode) {
if (!errorCode.isError(HostErrors.OPERATION_FAILURE_GC_ELIGIBLE)) {
completion.fail(errorCode);
return;
}
errors.add(errorCode);
logger.warn(String.format("failed to do the command[%s] on the kvm host[uuid:%s], %s, try next one",
cmd.getClass(), hostUuid, errorCode));
doCommand(it, path, cmd, rspType, completion);
}
});
}
}
@Override
void handle(DeleteVolumeOnPrimaryStorageMsg msg, final ReturnValueCompletion<DeleteVolumeOnPrimaryStorageReply> completion) {
deleteBits(msg.getVolume().getInstallPath(), new Completion(completion) {
@Override
public void success() {
DeleteVolumeOnPrimaryStorageReply reply = new DeleteVolumeOnPrimaryStorageReply();
completion.success(reply);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
@Override
void handle(final DownloadDataVolumeToPrimaryStorageMsg msg, final ReturnValueCompletion<DownloadDataVolumeToPrimaryStorageReply> completion) {
final String installPath = makeDataVolumeInstallUrl(msg.getVolumeUuid());
BackupStorageKvmDownloader downloader = getBackupStorageKvmDownloader(msg.getBackupStorageRef().getBackupStorageUuid());
downloader.downloadBits(msg.getBackupStorageRef().getInstallPath(), installPath, 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) {
deleteBits(msg.getInstallPath(), 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();
ImageCache cache = new ImageCache();
cache.image = ispec.getInventory();
cache.primaryStorageInstallPath = makeCachedImageInstallUrl(ispec.getInventory());
cache.backupStorageUuid = ispec.getSelectedBackupStorage().getBackupStorageUuid();
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
void handle(TakeSnapshotMsg msg, final ReturnValueCompletion<TakeSnapshotReply> completion) {
final VolumeSnapshotInventory sp = msg.getStruct().getCurrent();
VolumeInventory vol = VolumeInventory.valueOf(dbf.findByUuid(sp.getVolumeUuid(), VolumeVO.class));
final String hostUuid = findConnectedHost();
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);
completion.success(ret);
}
});
}
@Override
void handle(DeleteSnapshotOnPrimaryStorageMsg msg, final ReturnValueCompletion<DeleteSnapshotOnPrimaryStorageReply> completion) {
deleteBits(msg.getSnapshot().getPrimaryStorageInstallPath(), new Completion(completion) {
@Override
public void success() {
DeleteSnapshotOnPrimaryStorageReply reply = new DeleteSnapshotOnPrimaryStorageReply();
completion.success(reply);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
@Override
void handle(RevertVolumeFromSnapshotOnPrimaryStorageMsg msg, final ReturnValueCompletion<RevertVolumeFromSnapshotOnPrimaryStorageReply> completion) {
VolumeSnapshotInventory sp = msg.getSnapshot();
RevertVolumeFromSnapshotCmd cmd = new RevertVolumeFromSnapshotCmd();
cmd.snapshotInstallPath = sp.getPrimaryStorageInstallPath();
new Do().go(REVERT_VOLUME_FROM_SNAPSHOT_PATH, cmd, RevertVolumeFromSnapshotRsp.class, new ReturnValueCompletion<AgentRsp>(completion) {
@Override
public void success(AgentRsp returnValue) {
RevertVolumeFromSnapshotRsp rsp = (RevertVolumeFromSnapshotRsp) returnValue;
RevertVolumeFromSnapshotOnPrimaryStorageReply reply = new RevertVolumeFromSnapshotOnPrimaryStorageReply();
reply.setNewVolumeInstallPath(rsp.newVolumeInstallPath);
completion.success(reply);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
@Override
void handle(ReInitRootVolumeFromTemplateOnPrimaryStorageMsg msg, final ReturnValueCompletion<ReInitRootVolumeFromTemplateOnPrimaryStorageReply> completion) {
RevertVolumeFromSnapshotCmd cmd = new RevertVolumeFromSnapshotCmd();
cmd.snapshotInstallPath = makeCachedImageInstallUrlFromImageUuidForTemplate(msg.getVolume().getRootImageUuid());
new Do().go(REVERT_VOLUME_FROM_SNAPSHOT_PATH, cmd, RevertVolumeFromSnapshotRsp.class, new ReturnValueCompletion<AgentRsp>(completion) {
@Override
public void success(AgentRsp returnValue) {
RevertVolumeFromSnapshotRsp rsp = (RevertVolumeFromSnapshotRsp) returnValue;
ReInitRootVolumeFromTemplateOnPrimaryStorageReply reply = new ReInitRootVolumeFromTemplateOnPrimaryStorageReply();
reply.setNewVolumeInstallPath(rsp.newVolumeInstallPath);
completion.success(reply);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
@Override
void handle(CreateVolumeFromVolumeSnapshotOnPrimaryStorageMsg msg, final ReturnValueCompletion<CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply> completion) {
final String installPath = makeDataVolumeInstallUrl(msg.getVolumeUuid());
VolumeSnapshotInventory latest = msg.getSnapshot();
MergeSnapshotCmd cmd = new MergeSnapshotCmd();
cmd.volumeUuid = latest.getVolumeUuid();
cmd.snapshotInstallPath = latest.getPrimaryStorageInstallPath();
cmd.workspaceInstallPath = installPath;
new Do().go(MERGE_SNAPSHOT_PATH, cmd, MergeSnapshotRsp.class, new ReturnValueCompletion<AgentRsp>(completion) {
@Override
public void success(AgentRsp returnValue) {
MergeSnapshotRsp rsp = (MergeSnapshotRsp) returnValue;
CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply reply = new CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply();
reply.setActualSize(rsp.actualSize);
reply.setInstallPath(installPath);
reply.setSize(rsp.size);
completion.success(reply);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
void handle(MergeVolumeSnapshotOnPrimaryStorageMsg msg, final ReturnValueCompletion<MergeVolumeSnapshotOnPrimaryStorageReply> completion) {
boolean offline = true;
VolumeInventory volume = msg.getTo();
VolumeSnapshotInventory sp = msg.getFrom();
String hostUuid = null;
if (volume.getVmInstanceUuid() != null) {
SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class);
q.select(VmInstanceVO_.state, VmInstanceVO_.hostUuid);
q.add(VmInstanceVO_.uuid, Op.EQ, volume.getVmInstanceUuid());
Tuple t = q.findTuple();
VmInstanceState state = t.get(0, VmInstanceState.class);
hostUuid = t.get(1, String.class);
if (state != VmInstanceState.Stopped && state != VmInstanceState.Running) {
throw new OperationFailureException(operr("the volume[uuid;%s] is attached to a VM[uuid:%s] which is in state of %s, cannot do the snapshot merge",
volume.getUuid(), volume.getVmInstanceUuid(), state));
}
offline = (state == VmInstanceState.Stopped);
}
final MergeVolumeSnapshotOnPrimaryStorageReply reply = new MergeVolumeSnapshotOnPrimaryStorageReply();
if (offline) {
OfflineMergeSnapshotCmd cmd = new OfflineMergeSnapshotCmd();
cmd.fullRebase = msg.isFullRebase();
cmd.srcPath = sp.getPrimaryStorageInstallPath();
cmd.destPath = volume.getInstallPath();
new Do().go(OFFLINE_MERGE_SNAPSHOT_PATH, cmd, new ReturnValueCompletion<AgentRsp>(completion) {
@Override
public void success(AgentRsp returnValue) {
completion.success(reply);
}
@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 r) {
if (r.isSuccess()) {
completion.success(reply);
} else {
completion.fail(r.getError());
}
}
});
}
}
@Override
void downloadImageToCache(ImageInventory img, 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()
));
}
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.primaryStorageInstallPath = makeCachedImageInstallUrl(img);
cache.backupStorageUuid = bsUuid;
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 deleteBits(String path, final Completion completion) {
DeleteBitsCmd cmd = new DeleteBitsCmd();
cmd.path = path;
new Do().go(DELETE_BITS_PATH, cmd, new ReturnValueCompletion<AgentRsp>(completion) {
@Override
public void success(AgentRsp rsp) {
completion.success();
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
@Override
void handle(final CreateTemplateFromVolumeOnPrimaryStorageMsg msg, final ReturnValueCompletion<CreateTemplateFromVolumeOnPrimaryStorageReply> completion) {
final CreateTemplateFromVolumeOnPrimaryStorageReply reply = new CreateTemplateFromVolumeOnPrimaryStorageReply();
final VolumeInventory volume = msg.getVolumeInventory();
final ImageInventory image = msg.getImageInventory();
FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName(String.format("create-template-%s-from-volume-%s", image.getUuid(), volume.getUuid()));
chain.then(new ShareFlow() {
String temporaryTemplatePath = makeTemplateFromVolumeInWorkspacePath(msg.getImageInventory().getUuid());
String backupStorageInstallPath;
@Override
public void setup() {
flow(new Flow() {
String __name__ = "create-temporary-template";
boolean success = false;
@Override
public void run(final FlowTrigger trigger, Map data) {
CreateTemplateFromVolumeCmd cmd = new CreateTemplateFromVolumeCmd();
cmd.volumePath = volume.getInstallPath();
cmd.installPath = temporaryTemplatePath;
new Do().go(CREATE_TEMPLATE_FROM_VOLUME_PATH, cmd, new ReturnValueCompletion<AgentRsp>(trigger) {
@Override
public void success(AgentRsp returnValue) {
success = true;
trigger.next();
}
@Override
public void fail(ErrorCode errorCode) {
trigger.fail(errorCode);
}
});
}
@Override
public void rollback(FlowRollback trigger, Map data) {
if (success) {
deleteBits(temporaryTemplatePath, new Completion(trigger) {
@Override
public void success() {
// pass
}
@Override
public void fail(ErrorCode errorCode) {
//TODO GC
logger.warn(String.format("failed to delete %s, %s", temporaryTemplatePath, errorCode));
}
});
}
trigger.rollback();
}
});
flow(new NoRollbackFlow() {
String __name__ = "upload-to-backup-storage";
@Override
public void run(final FlowTrigger trigger, Map data) {
BackupStorageAskInstallPathMsg bmsg = new BackupStorageAskInstallPathMsg();
bmsg.setBackupStorageUuid(msg.getBackupStorageUuid());
bmsg.setImageMediaType(image.getMediaType());
bmsg.setImageUuid(image.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();
BackupStorageKvmUploader uploader = getBackupStorageKvmUploader(msg.getBackupStorageUuid());
uploader.uploadBits(msg.getImageInventory().getUuid(), backupStorageInstallPath, temporaryTemplatePath, new ReturnValueCompletion<String>(trigger) {
@Override
public void success(String bsPath) {
backupStorageInstallPath = bsPath;
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(FlowTrigger trigger, Map data) {
deleteBits(temporaryTemplatePath, new Completion(trigger) {
@Override
public void success() {
// pass
}
@Override
public void fail(ErrorCode errorCode) {
//TODO: GC
logger.warn(String.format("failed to delete %s on shared mount point primary storage[uuid: %s], %s; need a cleanup", temporaryTemplatePath, self.getUuid(), errorCode));
}
});
trigger.next();
}
});
done(new FlowDoneHandler(completion) {
@Override
public void handle(Map data) {
reply.setFormat(volume.getFormat());
reply.setTemplateBackupStorageInstallPath(backupStorageInstallPath);
completion.success(reply);
}
});
error(new FlowErrorHandler(completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
completion.fail(errCode);
}
});
}
}).start();
}
@Override
void handle(UploadBitsToBackupStorageMsg msg, final ReturnValueCompletion<UploadBitsToBackupStorageReply> completion) {
SftpBackupStorageKvmUploader uploader = new SftpBackupStorageKvmUploader(msg.getBackupStorageUuid());
uploader.uploadBits(null, msg.getBackupStorageInstallPath(), msg.getPrimaryStorageInstallPath(), new ReturnValueCompletion<String>(completion) {
@Override
public void success(String bsPath) {
UploadBitsToBackupStorageReply reply = new UploadBitsToBackupStorageReply();
reply.setBackupStorageInstallPath(bsPath);
completion.success(reply);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
class SftpBackupStorageKvmDownloader extends BackupStorageKvmDownloader {
private final String bsUuid;
public SftpBackupStorageKvmDownloader(String backupStorageUuid) {
bsUuid = backupStorageUuid;
}
@Override
public void downloadBits(final String bsPath, final String psPath, final Completion completion) {
GetSftpBackupStorageDownloadCredentialMsg gmsg = new GetSftpBackupStorageDownloadCredentialMsg();
gmsg.setBackupStorageUuid(bsUuid);
bus.makeTargetServiceIdByResourceUuid(gmsg, BackupStorageConstant.SERVICE_ID, bsUuid);
bus.send(gmsg, new CloudBusCallBack(completion) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
completion.fail(reply.getError());
return;
}
final GetSftpBackupStorageDownloadCredentialReply greply = reply.castReply();
SftpDownloadBitsCmd cmd = new SftpDownloadBitsCmd();
cmd.hostname = greply.getHostname();
cmd.username = greply.getUsername();
cmd.sshKey = greply.getSshKey();
cmd.sshPort = greply.getSshPort();
cmd.backupStorageInstallPath = bsPath;
cmd.primaryStorageInstallPath = psPath;
new Do().go(DOWNLOAD_BITS_FROM_SFTP_BACKUPSTORAGE_PATH, cmd, new ReturnValueCompletion<AgentRsp>(completion) {
@Override
public void success(AgentRsp returnValue) {
completion.success();
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
});
}
}
class SftpBackupStorageKvmUploader extends BackupStorageKvmUploader {
private final String bsUuid;
public SftpBackupStorageKvmUploader(String backupStorageUuid) {
bsUuid = backupStorageUuid;
}
@Override
public void uploadBits(final String imageUuid, final String bsPath, final String psPath, final ReturnValueCompletion<String> completion) {
GetSftpBackupStorageDownloadCredentialMsg gmsg = new GetSftpBackupStorageDownloadCredentialMsg();
gmsg.setBackupStorageUuid(bsUuid);
bus.makeTargetServiceIdByResourceUuid(gmsg, BackupStorageConstant.SERVICE_ID, bsUuid);
bus.send(gmsg, new CloudBusCallBack(completion) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
completion.fail(reply.getError());
return;
}
final GetSftpBackupStorageDownloadCredentialReply r = reply.castReply();
SftpUploadBitsCmd cmd = new SftpUploadBitsCmd();
cmd.primaryStorageInstallPath = psPath;
cmd.backupStorageInstallPath = bsPath;
cmd.hostname = r.getHostname();
cmd.username = r.getUsername();
cmd.sshKey = r.getSshKey();
cmd.sshPort = r.getSshPort();
new Do().go(UPLOAD_BITS_TO_SFTP_BACKUPSTORAGE_PATH, cmd, new ReturnValueCompletion<AgentRsp>(completion) {
@Override
public void success(AgentRsp returnValue) {
completion.success(bsPath);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
});
}
}
private BackupStorageKvmDownloader getBackupStorageKvmDownloader(String backupStorageUuid) {
SimpleQuery<BackupStorageVO> q = dbf.createQuery(BackupStorageVO.class);
q.select(BackupStorageVO_.type);
q.add(BackupStorageVO_.uuid, Op.EQ, backupStorageUuid);
String bsType = q.findValue();
if (SftpBackupStorageConstant.SFTP_BACKUP_STORAGE_TYPE.equals(bsType)) {
return new SftpBackupStorageKvmDownloader(backupStorageUuid);
} else {
for (BackupStorageKvmFactory f : pluginRgty.getExtensionList(BackupStorageKvmFactory.class)) {
if (bsType.equals(f.getBackupStorageType())) {
return f.createDownloader(getSelfInventory(), backupStorageUuid);
}
}
throw new CloudRuntimeException(String.format("cannot find any BackupStorageKvmFactory for the type[%s]", bsType));
}
}
private BackupStorageKvmUploader getBackupStorageKvmUploader(String backupStorageUuid) {
SimpleQuery<BackupStorageVO> q = dbf.createQuery(BackupStorageVO.class);
q.select(BackupStorageVO_.type);
q.add(BackupStorageVO_.uuid, Op.EQ, backupStorageUuid);
String bsType = q.findValue();
if (bsType == null) {
throw new OperationFailureException(operr("cannot find backup storage[uuid:%s]", backupStorageUuid));
}
if (SftpBackupStorageConstant.SFTP_BACKUP_STORAGE_TYPE.equals(bsType)) {
return new SftpBackupStorageKvmUploader(backupStorageUuid);
} else {
for (BackupStorageKvmFactory f : pluginRgty.getExtensionList(BackupStorageKvmFactory.class)) {
if (bsType.equals(f.getBackupStorageType())) {
return f.createUploader(getSelfInventory(), backupStorageUuid);
}
}
throw new CloudRuntimeException(String.format("cannot find any BackupStorageKvmFactory for the type[%s]", bsType));
}
}
@Override
void handleHypervisorSpecificMessage(SMPPrimaryStorageHypervisorSpecificMessage msg) {
if (msg instanceof InitKvmHostMsg) {
handle((InitKvmHostMsg) msg);
} else {
bus.dealWithUnknownMessage((Message) msg);
}
}
@Override
void connectByClusterUuid(final String clusterUuid, final Completion completion) {
List<String> huuids = findConnectedHostByClusterUuid(clusterUuid, false);
if (huuids.isEmpty()) {
// no host in the cluster
completion.success();
return;
}
class Result {
List<ErrorCode> errorCodes = new ArrayList<ErrorCode>();
List<String> huuids = new ArrayList<String>();
}
final Result ret = new Result();
final AsyncLatch latch = new AsyncLatch(huuids.size(), new NoErrorCompletion(completion) {
@Override
public void done() {
if (!ret.errorCodes.isEmpty()) {
String mountPathErrorInfo = "Can't find mount path on ";
for(String hostUuid : ret.huuids) {
mountPathErrorInfo += String.format("host[uuid:%s] ", hostUuid);
}
completion.fail(errf.stringToOperationError(String.format("unable to connect the shared mount point storage[uuid:%s, name:%s] to" +
" the cluster[uuid:%s], %s", self.getUuid(), self.getName(), clusterUuid, mountPathErrorInfo), ret.errorCodes));
} else {
completion.success();
}
}
});
for (String huuid : huuids) {
connect(huuid, new Completion(latch) {
@Override
public void success() {
latch.ack();
}
@Override
public void fail(ErrorCode errorCode) {
ret.errorCodes.add(errorCode);
ret.huuids.add(huuid);
latch.ack();
}
});
}
}
@Override
void handle(SyncVolumeSizeOnPrimaryStorageMsg msg, final ReturnValueCompletion<SyncVolumeSizeOnPrimaryStorageReply> completion) {
String hostUuid = findConnectedHost();
final GetVolumeSizeCmd cmd = new GetVolumeSizeCmd();
cmd.installPath = msg.getInstallPath();
cmd.volumeUuid = msg.getVolumeUuid();
cmd.mountPoint = self.getMountPath();
new KvmCommandSender(hostUuid).send(cmd, GET_VOLUME_SIZE_PATH, new KvmCommandFailureChecker() {
@Override
public ErrorCode getError(KvmResponseWrapper wrapper) {
GetVolumeSizeRsp rsp = wrapper.getResponse(GetVolumeSizeRsp.class);
return rsp.success ? null : operr(rsp.error);
}
}, new ReturnValueCompletion<KvmResponseWrapper>(completion) {
@Override
public void success(KvmResponseWrapper returnValue) {
SyncVolumeSizeOnPrimaryStorageReply reply = new SyncVolumeSizeOnPrimaryStorageReply();
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(CreateTemporaryVolumeFromSnapshotMsg msg, final ReturnValueCompletion<CreateTemporaryVolumeFromSnapshotReply> completion) {
final String installPath = makeTemplateFromVolumeInWorkspacePath(msg.getTemporaryVolumeUuid());
VolumeSnapshotInventory latest = msg.getSnapshot();
MergeSnapshotCmd cmd = new MergeSnapshotCmd();
cmd.volumeUuid = latest.getVolumeUuid();
cmd.snapshotInstallPath = latest.getPrimaryStorageInstallPath();
cmd.workspaceInstallPath = installPath;
new Do().go(MERGE_SNAPSHOT_PATH, cmd, MergeSnapshotRsp.class, new ReturnValueCompletion<AgentRsp>(completion) {
@Override
public void success(AgentRsp rsp) {
CreateTemporaryVolumeFromSnapshotReply reply = new CreateTemporaryVolumeFromSnapshotReply();
reply.setInstallPath(installPath);
MergeSnapshotRsp mrsp = (MergeSnapshotRsp) rsp;
reply.setSize(mrsp.size);
reply.setActualSize(mrsp.actualSize);
completion.success(reply);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
@Override
void handle(BackupVolumeSnapshotFromPrimaryStorageToBackupStorageMsg msg, final ReturnValueCompletion<BackupVolumeSnapshotFromPrimaryStorageToBackupStorageReply> completion) {
VolumeSnapshotInventory sinv = msg.getSnapshot();
String bsUuid = msg.getBackupStorage().getUuid();
// Get the backup storage install path
BackupStorageAskInstallPathMsg bmsg = new BackupStorageAskInstallPathMsg();
bmsg.setImageMediaType(VolumeSnapshotVO.class.getSimpleName());
bmsg.setBackupStorageUuid(msg.getBackupStorage().getUuid());
bmsg.setImageUuid(sinv.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();
BackupStorageKvmUploader uploader = getBackupStorageKvmUploader(bsUuid);
uploader.uploadBits(null, installPath, sinv.getPrimaryStorageInstallPath(), new ReturnValueCompletion<String>(completion) {
@Override
public void success(String bsPath) {
BackupVolumeSnapshotFromPrimaryStorageToBackupStorageReply reply = new BackupVolumeSnapshotFromPrimaryStorageToBackupStorageReply();
reply.setBackupStorageInstallPath(bsPath);
completion.success(reply);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
private void handle(final InitKvmHostMsg msg) {
final InitKvmHostReply reply = new InitKvmHostReply();
connect(msg.getHostUuid(), new Completion(msg) {
@Override
public void success() {
bus.reply(msg, reply);
}
@Override
public void fail(ErrorCode errorCode) {
reply.setError(errorCode);
bus.reply(msg, reply);
}
});
}
}