package org.zstack.storage.fusionstor.primary; import org.springframework.beans.factory.annotation.Autowired; import org.zstack.core.Platform; import org.zstack.core.cloudbus.CloudBusCallBack; 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.thread.ThreadFacade; import org.zstack.core.timeout.ApiTimeoutManager; import org.zstack.core.workflow.FlowChainBuilder; import org.zstack.core.workflow.ShareFlow; import org.zstack.header.cluster.ClusterVO; import org.zstack.header.cluster.ClusterVO_; import org.zstack.header.core.*; import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.errorcode.SysErrors; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.host.HostStatus; import org.zstack.header.host.HostVO; import org.zstack.header.host.HostVO_; import org.zstack.header.image.APICreateDataVolumeTemplateFromVolumeMsg; import org.zstack.header.image.APICreateRootVolumeTemplateFromRootVolumeMsg; import org.zstack.header.image.APICreateRootVolumeTemplateFromVolumeSnapshotMsg; import org.zstack.header.image.ImageConstant.ImageMediaType; import org.zstack.header.image.ImageInventory; import org.zstack.header.message.APIMessage; 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.primary.VolumeSnapshotCapability.VolumeSnapshotArrangementType; 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.storage.snapshot.VolumeSnapshotVO_; import org.zstack.header.vm.VmInstanceSpec.ImageSpec; import org.zstack.header.volume.*; import org.zstack.kvm.*; import org.zstack.kvm.KvmSetupSelfFencerExtensionPoint.KvmSetupSelfFencerParam; 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.fusionstor.*; import org.zstack.storage.fusionstor.FusionstorMonBase.PingResult; import org.zstack.storage.fusionstor.backup.FusionstorBackupStorageVO; import org.zstack.storage.fusionstor.backup.FusionstorBackupStorageVO_; import org.zstack.storage.primary.PrimaryStorageBase; 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.gson.JSONObjectUtil; import org.zstack.utils.logging.CLogger; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static org.zstack.core.Platform.operr; import static org.zstack.utils.CollectionDSL.list; /** * Created by frank on 7/28/2015. */ public class FusionstorPrimaryStorageBase extends PrimaryStorageBase { private static final CLogger logger = Utils.getLogger(FusionstorPrimaryStorageBase.class); @Autowired private RESTFacade restf; @Autowired private ThreadFacade thdf; @Autowired private ApiTimeoutManager timeoutMgr; class ReconnectMonLock { AtomicBoolean hold = new AtomicBoolean(false); boolean lock() { return hold.compareAndSet(false, true); } void unlock() { hold.set(false); } } ReconnectMonLock reconnectMonLock = new ReconnectMonLock(); public static class AgentCommand { String fsId; String uuid; public String getFsId() { return fsId; } public void setFsId(String fsId) { this.fsId = fsId; } public String getUuid() { return uuid; } public void setUuid(String uuid) { this.uuid = uuid; } } public static class AgentResponse { String error; boolean success = true; Long totalCapacity; Long availableCapacity; public String getError() { return error; } public void setError(String error) { this.error = error; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } 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 GetVolumeSizeCmd extends AgentCommand { public String volumeUuid; public String installPath; } public static class GetVolumeSizeRsp extends AgentResponse { public Long size; public Long actualSize; } public static class Pool { String name; boolean predefined; } public static class InitCmd extends AgentCommand { List<Pool> pools; List<String> monHostnames; List<String> sshUsernames; List<String> sshPasswords; String fusionstorType; public List<Pool> getPools() { return pools; } public void setPools(List<Pool> pools) { this.pools = pools; } } public static class InitRsp extends AgentResponse { String fsid; String userKey; public String getUserKey() { return userKey; } public void setUserKey(String userKey) { this.userKey = userKey; } public String getFsid() { return fsid; } public void setFsid(String fsid) { this.fsid = fsid; } } public static class CreateEmptyVolumeCmd extends AgentCommand { String installPath; long size; public String getInstallPath() { return installPath; } public void setInstallPath(String installPath) { this.installPath = installPath; } public long getSize() { return size; } public void setSize(long size) { this.size = size; } } public static class CreateEmptyVolumeRsp extends AgentResponse { } public static class DeleteCmd extends AgentCommand { String installPath; public String getInstallPath() { return installPath; } public void setInstallPath(String installPath) { this.installPath = installPath; } } public static class DeleteRsp extends AgentResponse { } public static class CloneCmd extends AgentCommand { String srcPath; String dstPath; public String getSrcPath() { return srcPath; } public void setSrcPath(String srcPath) { this.srcPath = srcPath; } public String getDstPath() { return dstPath; } public void setDstPath(String dstPath) { this.dstPath = dstPath; } } public static class CloneRsp extends AgentResponse { } public static class FlattenCmd extends AgentCommand { String path; public String getPath() { return path; } public void setPath(String path) { this.path = path; } } public static class FlattenRsp extends AgentResponse { } public static class SftpDownloadCmd extends AgentCommand { String sshKey; String hostname; String username; int sshPort; String backupStorageInstallPath; String primaryStorageInstallPath; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getSshPort() { return sshPort; } public void setSshPort(int sshPort) { this.sshPort = sshPort; } public String getSshKey() { return sshKey; } public void setSshKey(String sshKey) { this.sshKey = sshKey; } public String getHostname() { return hostname; } public void setHostname(String hostname) { this.hostname = hostname; } public String getBackupStorageInstallPath() { return backupStorageInstallPath; } public void setBackupStorageInstallPath(String backupStorageInstallPath) { this.backupStorageInstallPath = backupStorageInstallPath; } public String getPrimaryStorageInstallPath() { return primaryStorageInstallPath; } public void setPrimaryStorageInstallPath(String primaryStorageInstallPath) { this.primaryStorageInstallPath = primaryStorageInstallPath; } } public static class SftpDownloadRsp extends AgentResponse { } @ApiTimeout(apiClasses = { APICreateRootVolumeTemplateFromRootVolumeMsg.class, APICreateDataVolumeTemplateFromVolumeMsg.class }) public static class SftpUpLoadCmd extends AgentCommand { String primaryStorageInstallPath; String backupStorageInstallPath; String hostname; String username; String sshKey; int sshPort; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getSshPort() { return sshPort; } public void setSshPort(int sshPort) { this.sshPort = sshPort; } public String getPrimaryStorageInstallPath() { return primaryStorageInstallPath; } public void setPrimaryStorageInstallPath(String primaryStorageInstallPath) { this.primaryStorageInstallPath = primaryStorageInstallPath; } public String getBackupStorageInstallPath() { return backupStorageInstallPath; } public void setBackupStorageInstallPath(String backupStorageInstallPath) { this.backupStorageInstallPath = backupStorageInstallPath; } public String getHostname() { return hostname; } public void setHostname(String hostname) { this.hostname = hostname; } public String getSshKey() { return sshKey; } public void setSshKey(String sshKey) { this.sshKey = sshKey; } } public static class SftpUploadRsp extends AgentResponse { } @ApiTimeout(apiClasses = {APICreateVolumeSnapshotMsg.class}) public static class CreateSnapshotCmd extends AgentCommand { boolean skipOnExisting; String snapshotPath; String volumeUuid; public String getVolumeUuid() { return volumeUuid; } public void setVolumeUuid(String volumeUuid) { this.volumeUuid = volumeUuid; } public boolean isSkipOnExisting() { return skipOnExisting; } public void setSkipOnExisting(boolean skipOnExisting) { this.skipOnExisting = skipOnExisting; } public String getSnapshotPath() { return snapshotPath; } public void setSnapshotPath(String snapshotPath) { this.snapshotPath = snapshotPath; } } public static class CreateSnapshotRsp extends AgentResponse { Long size; 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; } } public static class DeleteSnapshotCmd extends AgentCommand { String snapshotPath; public String getSnapshotPath() { return snapshotPath; } public void setSnapshotPath(String snapshotPath) { this.snapshotPath = snapshotPath; } } public static class DeleteSnapshotRsp extends AgentResponse { } public static class ProtectSnapshotCmd extends AgentCommand { String snapshotPath; boolean ignoreError; public boolean isIgnoreError() { return ignoreError; } public void setIgnoreError(boolean ignoreError) { this.ignoreError = ignoreError; } public String getSnapshotPath() { return snapshotPath; } public void setSnapshotPath(String snapshotPath) { this.snapshotPath = snapshotPath; } } public static class ProtectSnapshotRsp extends AgentResponse { } public static class UnprotectedSnapshotCmd extends AgentCommand { String snapshotPath; public String getSnapshotPath() { return snapshotPath; } public void setSnapshotPath(String snapshotPath) { this.snapshotPath = snapshotPath; } } public static class UnprotectedSnapshotRsp extends AgentResponse { } @ApiTimeout(apiClasses = { APICreateRootVolumeTemplateFromRootVolumeMsg.class, APICreateDataVolumeTemplateFromVolumeMsg.class, APICreateDataVolumeFromVolumeSnapshotMsg.class, APICreateRootVolumeTemplateFromVolumeSnapshotMsg.class }) public static class CpCmd extends AgentCommand { String resourceUuid; String srcPath; String dstPath; } public static class CpRsp extends AgentResponse { Long size; Long actualSize; } public static class RollbackSnapshotCmd extends AgentCommand { String snapshotPath; public String getSnapshotPath() { return snapshotPath; } public void setSnapshotPath(String snapshotPath) { this.snapshotPath = snapshotPath; } } public static class RollbackSnapshotRsp extends AgentResponse { } public static class CreateKvmSecretCmd extends KVMAgentCommands.AgentCommand { String userKey; String uuid; public String getUserKey() { return userKey; } public void setUserKey(String userKey) { this.userKey = userKey; } public String getUuid() { return uuid; } public void setUuid(String uuid) { this.uuid = uuid; } } public static class CreateKvmSecretRsp extends AgentResponse { } public static class DeletePoolCmd extends AgentCommand { List<String> poolNames; public List<String> getPoolNames() { return poolNames; } public void setPoolNames(List<String> poolNames) { this.poolNames = poolNames; } } public static class DeletePoolRsp extends AgentResponse { } public static class KvmSetupSelfFencerCmd extends AgentCommand { public String heartbeatImagePath; public String hostUuid; public long interval; public int maxAttempts; public int storageCheckerTimeout; public String userKey; public List<String> monUrls; } public static class GetFactsCmd extends AgentCommand { public String monUuid; } public static class GetFactsRsp extends AgentResponse { public String fsid; } public static final String INIT_PATH = "/fusionstor/primarystorage/init"; public static final String CREATE_VOLUME_PATH = "/fusionstor/primarystorage/volume/createempty"; public static final String DELETE_PATH = "/fusionstor/primarystorage/delete"; public static final String CLONE_PATH = "/fusionstor/primarystorage/volume/clone"; public static final String FLATTEN_PATH = "/fusionstor/primarystorage/volume/flatten"; public static final String SFTP_DOWNLOAD_PATH = "/fusionstor/primarystorage/sftpbackupstorage/download"; public static final String SFTP_UPLOAD_PATH = "/fusionstor/primarystorage/sftpbackupstorage/upload"; public static final String CREATE_SNAPSHOT_PATH = "/fusionstor/primarystorage/snapshot/create"; public static final String DELETE_SNAPSHOT_PATH = "/fusionstor/primarystorage/snapshot/delete"; public static final String PROTECT_SNAPSHOT_PATH = "/fusionstor/primarystorage/snapshot/protect"; public static final String ROLLBACK_SNAPSHOT_PATH = "/fusionstor/primarystorage/snapshot/rollback"; public static final String UNPROTECT_SNAPSHOT_PATH = "/fusionstor/primarystorage/snapshot/unprotect"; public static final String CP_PATH = "/fusionstor/primarystorage/volume/cp"; public static final String DELETE_POOL_PATH = "/fusionstor/primarystorage/deletepool"; public static final String GET_VOLUME_SIZE_PATH = "/fusionstor/primarystorage/getvolumesize"; public static final String KVM_HA_SETUP_SELF_FENCER = "/ha/fusionstor/setupselffencer"; public static final String GET_FACTS = "/fusionstor/primarystorage/facts"; private final Map<String, BackupStorageMediator> backupStorageMediators = new HashMap<String, BackupStorageMediator>(); { backupStorageMediators.put(SftpBackupStorageConstant.SFTP_BACKUP_STORAGE_TYPE, new SftpBackupStorageMediator()); backupStorageMediators.put(FusionstorConstants.FUSIONSTOR_BACKUP_STORAGE_TYPE, new FusionstorBackupStorageMediator()); } abstract class MediatorParam { } class DownloadParam extends MediatorParam { ImageSpec image; String installPath; } class UploadParam extends MediatorParam { ImageInventory image; String primaryStorageInstallPath; String backupStorageInstallPath; } abstract class BackupStorageMediator { BackupStorageInventory backupStorage; MediatorParam param; protected void checkParam() { DebugUtils.Assert(backupStorage != null, "backupStorage cannot be null"); DebugUtils.Assert(param != null, "param cannot be null"); } abstract void download(ReturnValueCompletion<String> completion); abstract void upload(ReturnValueCompletion<String> completion); abstract boolean deleteWhenRollabackDownload(); } class SftpBackupStorageMediator extends BackupStorageMediator { private void getSftpCredentials(final ReturnValueCompletion<GetSftpBackupStorageDownloadCredentialReply> completion) { GetSftpBackupStorageDownloadCredentialMsg gmsg = new GetSftpBackupStorageDownloadCredentialMsg(); gmsg.setBackupStorageUuid(backupStorage.getUuid()); bus.makeTargetServiceIdByResourceUuid(gmsg, BackupStorageConstant.SERVICE_ID, backupStorage.getUuid()); bus.send(gmsg, new CloudBusCallBack(completion) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { completion.fail(reply.getError()); } else { completion.success((GetSftpBackupStorageDownloadCredentialReply) reply); } } }); } @Override void download(final ReturnValueCompletion<String> completion) { checkParam(); final DownloadParam dparam = (DownloadParam) param; FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("download-image-from-sftp-%s-to-fusionstor-%s", backupStorage.getUuid(), self.getUuid())); chain.then(new ShareFlow() { String sshkey; int sshport; String sftpHostname; String username; @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "get-sftp-credentials"; @Override public void run(final FlowTrigger trigger, Map data) { getSftpCredentials(new ReturnValueCompletion<GetSftpBackupStorageDownloadCredentialReply>(trigger) { @Override public void success(GetSftpBackupStorageDownloadCredentialReply greply) { sshkey = greply.getSshKey(); sftpHostname = greply.getHostname(); username = greply.getUsername(); sshport = greply.getSshPort(); trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "download-image"; @Override public void run(final FlowTrigger trigger, Map data) { SftpDownloadCmd cmd = new SftpDownloadCmd(); cmd.backupStorageInstallPath = dparam.image.getSelectedBackupStorage().getInstallPath(); cmd.hostname = sftpHostname; cmd.username = username; cmd.sshKey = sshkey; cmd.sshPort = sshport; cmd.primaryStorageInstallPath = dparam.installPath; httpCall(SFTP_DOWNLOAD_PATH, cmd, SftpDownloadRsp.class, new ReturnValueCompletion<SftpDownloadRsp>(trigger) { @Override public void success(SftpDownloadRsp returnValue) { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { completion.success(dparam.installPath); } }); error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); } }); } }).start(); } @Override void upload(final ReturnValueCompletion<String> completion) { checkParam(); final UploadParam uparam = (UploadParam) param; FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("upload-image-fusionstor-%s-to-sftp-%s", self.getUuid(), backupStorage.getUuid())); chain.then(new ShareFlow() { String sshKey; String hostname; String username; int sshPort; String backupStorageInstallPath; @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "get-sftp-credentials"; @Override public void run(final FlowTrigger trigger, Map data) { getSftpCredentials(new ReturnValueCompletion<GetSftpBackupStorageDownloadCredentialReply>(trigger) { @Override public void success(GetSftpBackupStorageDownloadCredentialReply returnValue) { sshKey = returnValue.getSshKey(); hostname = returnValue.getHostname(); username = returnValue.getUsername(); sshPort = returnValue.getSshPort(); trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "get-backup-storage-install-path"; @Override public void run(final FlowTrigger trigger, Map data) { BackupStorageAskInstallPathMsg msg = new BackupStorageAskInstallPathMsg(); msg.setBackupStorageUuid(backupStorage.getUuid()); msg.setImageUuid(uparam.image.getUuid()); msg.setImageMediaType(uparam.image.getMediaType()); bus.makeTargetServiceIdByResourceUuid(msg, BackupStorageConstant.SERVICE_ID, backupStorage.getUuid()); bus.send(msg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { trigger.fail(reply.getError()); } else { backupStorageInstallPath = ((BackupStorageAskInstallPathReply) reply).getInstallPath(); trigger.next(); } } }); } }); flow(new NoRollbackFlow() { String __name__ = "upload-to-backup-storage"; @Override public void run(final FlowTrigger trigger, Map data) { SftpUpLoadCmd cmd = new SftpUpLoadCmd(); cmd.setBackupStorageInstallPath(backupStorageInstallPath); cmd.setHostname(hostname); cmd.setUsername(username); cmd.setSshKey(sshKey); cmd.setSshPort(sshPort); cmd.setPrimaryStorageInstallPath(uparam.primaryStorageInstallPath); httpCall(SFTP_UPLOAD_PATH, cmd, SftpUploadRsp.class, new ReturnValueCompletion<SftpUploadRsp>(trigger) { @Override public void success(SftpUploadRsp returnValue) { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { completion.success(backupStorageInstallPath); } }); error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); } }); } }).start(); } @Override boolean deleteWhenRollabackDownload() { return true; } } class FusionstorBackupStorageMediator extends BackupStorageMediator { protected void checkParam() { super.checkParam(); SimpleQuery<FusionstorBackupStorageVO> q = dbf.createQuery(FusionstorBackupStorageVO.class); q.select(FusionstorBackupStorageVO_.fsid); q.add(FusionstorBackupStorageVO_.uuid, Op.EQ, backupStorage.getUuid()); String bsFsid = q.findValue(); if (!getSelf().getFsid().equals(bsFsid)) { throw new OperationFailureException(errf.stringToOperationError( String.format("the backup storage[uuid:%s, name:%s, fsid:%s] is not in the same fusionstor cluster" + " with the primary storage[uuid:%s, name:%s, fsid:%s]", backupStorage.getUuid(), backupStorage.getName(), bsFsid, self.getUuid(), self.getName(), getSelf().getFsid()) )); } } @Override void download(final ReturnValueCompletion<String> completion) { checkParam(); final DownloadParam dparam = (DownloadParam) param; if (ImageMediaType.DataVolumeTemplate.toString().equals(dparam.image.getInventory().getMediaType())) { CpCmd cmd = new CpCmd(); cmd.srcPath = dparam.image.getSelectedBackupStorage().getInstallPath(); cmd.dstPath = dparam.installPath; httpCall(CP_PATH, cmd, CpRsp.class, new ReturnValueCompletion<CpRsp>(completion) { @Override public void success(CpRsp returnValue) { completion.success(dparam.installPath); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } else { completion.success(dparam.image.getSelectedBackupStorage().getInstallPath()); } } @Override void upload(final ReturnValueCompletion<String> completion) { checkParam(); final UploadParam uparam = (UploadParam) param; FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("upload-image-fusionstor-%s-to-fusionstor-%s", self.getUuid(), backupStorage.getUuid())); chain.then(new ShareFlow() { String backupStorageInstallPath; @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "get-backup-storage-install-path"; @Override public void run(final FlowTrigger trigger, Map data) { BackupStorageAskInstallPathMsg msg = new BackupStorageAskInstallPathMsg(); msg.setBackupStorageUuid(backupStorage.getUuid()); msg.setImageUuid(uparam.image.getUuid()); msg.setImageMediaType(uparam.image.getMediaType()); bus.makeTargetServiceIdByResourceUuid(msg, BackupStorageConstant.SERVICE_ID, backupStorage.getUuid()); bus.send(msg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { trigger.fail(reply.getError()); } else { backupStorageInstallPath = ((BackupStorageAskInstallPathReply) reply).getInstallPath(); trigger.next(); } } }); } }); flow(new NoRollbackFlow() { String __name__ = "cp-to-the-image"; @Override public void run(final FlowTrigger trigger, Map data) { CpCmd cmd = new CpCmd(); cmd.srcPath = uparam.primaryStorageInstallPath; cmd.dstPath = backupStorageInstallPath; httpCall(CP_PATH, cmd, CpRsp.class, new ReturnValueCompletion<CpRsp>(trigger) { @Override public void success(CpRsp returnValue) { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { completion.success(backupStorageInstallPath); } }); error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); } }); } }).start(); } @Override boolean deleteWhenRollabackDownload() { return false; } } private BackupStorageMediator getBackupStorageMediator(String bsUuid) { BackupStorageVO bsvo = dbf.findByUuid(bsUuid, BackupStorageVO.class); BackupStorageMediator mediator = backupStorageMediators.get(bsvo.getType()); if (mediator == null) { throw new CloudRuntimeException(String.format("cannot find BackupStorageMediator for type[%s]", bsvo.getType())); } mediator.backupStorage = BackupStorageInventory.valueOf(bsvo); return mediator; } private String makeRootVolumeInstallPath(String volUuid) { return String.format("fusionstor://%s/%s", getSelf().getRootVolumePoolName(), volUuid); } private String makeDataVolumeInstallPath(String volUuid) { return String.format("fusionstor://%s/%s", getSelf().getDataVolumePoolName(), volUuid); } private String makeResetImageRootVolumeInstallPath(String volUuid) { return String.format("fusionstor://%s/reset-image-%s-%s", getSelf().getRootVolumePoolName(), volUuid, System.currentTimeMillis()); } private String makeCacheInstallPath(String uuid) { return String.format("fusionstor://%s/%s", getSelf().getImageCachePoolName(), uuid); } public FusionstorPrimaryStorageBase(PrimaryStorageVO self) { super(self); } protected FusionstorPrimaryStorageVO getSelf() { return (FusionstorPrimaryStorageVO) self; } protected FusionstorPrimaryStorageInventory getSelfInventory() { return FusionstorPrimaryStorageInventory.valueOf(getSelf()); } private void createEmptyVolume(final InstantiateVolumeOnPrimaryStorageMsg msg) { final CreateEmptyVolumeCmd cmd = new CreateEmptyVolumeCmd(); cmd.installPath = VolumeType.Root.toString().equals(msg.getVolume().getType()) ? makeRootVolumeInstallPath(msg.getVolume().getUuid()) : makeDataVolumeInstallPath(msg.getVolume().getUuid()); cmd.size = msg.getVolume().getSize(); final InstantiateVolumeOnPrimaryStorageReply reply = new InstantiateVolumeOnPrimaryStorageReply(); httpCall(CREATE_VOLUME_PATH, cmd, CreateEmptyVolumeRsp.class, new ReturnValueCompletion<CreateEmptyVolumeRsp>(msg) { @Override public void fail(ErrorCode err) { reply.setError(err); bus.reply(msg, reply); } @Override public void success(CreateEmptyVolumeRsp ret) { VolumeInventory vol = msg.getVolume(); vol.setInstallPath(cmd.getInstallPath()); vol.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); reply.setVolume(vol); bus.reply(msg, reply); } }); } @Override protected void handle(final InstantiateVolumeOnPrimaryStorageMsg msg) { if (msg instanceof InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg) { createVolumeFromTemplate((InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg) msg); } else { createEmptyVolume(msg); } } class DownloadToCache { ImageSpec image; private void doDownload(final ReturnValueCompletion<ImageCacheVO> completion) { SimpleQuery<ImageCacheVO> q = dbf.createQuery(ImageCacheVO.class); q.add(ImageCacheVO_.imageUuid, Op.EQ, image.getInventory().getUuid()); q.add(ImageCacheVO_.primaryStorageUuid, Op.EQ, self.getUuid()); ImageCacheVO cache = q.find(); if (cache != null) { completion.success(cache); return; } final FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("prepare-image-cache-fusionstor-%s", self.getUuid())); chain.then(new ShareFlow() { String cachePath; String snapshotPath; @Override public void setup() { flow(new Flow() { String __name__ = "allocate-primary-storage-capacity-for-image-cache"; boolean s = false; @Override public void run(final FlowTrigger trigger, Map data) { AllocatePrimaryStorageMsg amsg = new AllocatePrimaryStorageMsg(); amsg.setRequiredPrimaryStorageUuid(self.getUuid()); amsg.setSize(image.getInventory().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()) { trigger.fail(reply.getError()); } else { s = true; trigger.next(); } } }); } @Override public void rollback(FlowRollback trigger, Map data) { if (s) { IncreasePrimaryStorageCapacityMsg imsg = new IncreasePrimaryStorageCapacityMsg(); imsg.setNoOverProvisioning(true); imsg.setPrimaryStorageUuid(self.getUuid()); imsg.setDiskSize(image.getInventory().getActualSize()); bus.makeLocalServiceId(imsg, PrimaryStorageConstant.SERVICE_ID); bus.send(imsg); } trigger.rollback(); } }); flow(new Flow() { String __name__ = "download-from-backup-storage"; boolean deleteOnRollback; @Override public void run(final FlowTrigger trigger, Map data) { DownloadParam param = new DownloadParam(); param.image = image; param.installPath = makeCacheInstallPath(image.getInventory().getUuid()); BackupStorageMediator mediator = getBackupStorageMediator(image.getSelectedBackupStorage().getBackupStorageUuid()); mediator.param = param; deleteOnRollback = mediator.deleteWhenRollabackDownload(); mediator.download(new ReturnValueCompletion<String>(trigger) { @Override public void success(String path) { cachePath = path; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } @Override public void rollback(FlowRollback trigger, Map data) { if (deleteOnRollback && cachePath != null) { DeleteCmd cmd = new DeleteCmd(); cmd.installPath = cachePath; httpCall(DELETE_PATH, cmd, DeleteRsp.class, new ReturnValueCompletion<DeleteRsp>(null) { @Override public void success(DeleteRsp returnValue) { logger.debug(String.format("successfully deleted %s", cachePath)); } @Override public void fail(ErrorCode errorCode) { //TODO logger.warn(String.format("unable to delete %s, %s. Need a cleanup", cachePath, errorCode)); } }); } trigger.rollback(); } }); flow(new Flow() { String __name__ = "create-snapshot"; boolean needCleanup = false; @Override public void run(final FlowTrigger trigger, Map data) { snapshotPath = String.format("%s@%s", cachePath, image.getInventory().getUuid()); CreateSnapshotCmd cmd = new CreateSnapshotCmd(); cmd.skipOnExisting = true; cmd.snapshotPath = snapshotPath; httpCall(CREATE_SNAPSHOT_PATH, cmd, CreateSnapshotRsp.class, new ReturnValueCompletion<CreateSnapshotRsp>(trigger) { @Override public void success(CreateSnapshotRsp returnValue) { needCleanup = true; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } @Override public void rollback(FlowRollback trigger, Map data) { if (needCleanup) { DeleteSnapshotCmd cmd = new DeleteSnapshotCmd(); cmd.snapshotPath = snapshotPath; httpCall(DELETE_SNAPSHOT_PATH, cmd, DeleteSnapshotRsp.class, new ReturnValueCompletion<DeleteSnapshotRsp>(null) { @Override public void success(DeleteSnapshotRsp returnValue) { logger.debug(String.format("successfully deleted the snapshot %s", snapshotPath)); } @Override public void fail(ErrorCode errorCode) { //TODO logger.warn(String.format("unable to delete the snapshot %s, %s. Need a cleanup", snapshotPath, errorCode)); } }); } trigger.rollback(); } }); flow(new NoRollbackFlow() { String __name__ = "protect-snapshot"; @Override public void run(final FlowTrigger trigger, Map data) { ProtectSnapshotCmd cmd = new ProtectSnapshotCmd(); cmd.snapshotPath = snapshotPath; cmd.ignoreError = true; httpCall(PROTECT_SNAPSHOT_PATH, cmd, ProtectSnapshotRsp.class, new ReturnValueCompletion<ProtectSnapshotRsp>(trigger) { @Override public void success(ProtectSnapshotRsp returnValue) { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { ImageCacheVO cvo = new ImageCacheVO(); cvo.setMd5sum("not calculated"); cvo.setSize(image.getInventory().getActualSize()); cvo.setInstallUrl(snapshotPath); cvo.setImageUuid(image.getInventory().getUuid()); cvo.setPrimaryStorageUuid(self.getUuid()); cvo.setMediaType(ImageMediaType.valueOf(image.getInventory().getMediaType())); cvo.setState(ImageCacheState.ready); cvo = dbf.persistAndRefresh(cvo); completion.success(cvo); } }); error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); } }); } }).start(); } void download(final ReturnValueCompletion<ImageCacheVO> completion) { thdf.chainSubmit(new ChainTask(completion) { @Override public String getSyncSignature() { return String.format("fusionstor-p-%s-download-image-%s", self.getUuid(), image.getInventory().getUuid()); } @Override public void run(final SyncTaskChain chain) { doDownload(new ReturnValueCompletion<ImageCacheVO>(chain) { @Override public void success(ImageCacheVO returnValue) { completion.success(returnValue); chain.next(); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); chain.next(); } }); } @Override public String getName() { return getSyncSignature(); } }); } } private void createVolumeFromTemplate(final InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg msg) { final ImageInventory img = msg.getTemplateSpec().getInventory(); final InstantiateVolumeOnPrimaryStorageReply reply = new InstantiateVolumeOnPrimaryStorageReply(); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("create-root-volume-%s", msg.getVolume().getUuid())); chain.then(new ShareFlow() { String cloneInstallPath; String volumePath = makeRootVolumeInstallPath(msg.getVolume().getUuid()); ImageCacheVO cache; @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "download-image-to-cache"; @Override public void run(final FlowTrigger trigger, Map data) { DownloadToCache downloadToCache = new DownloadToCache(); downloadToCache.image = msg.getTemplateSpec(); downloadToCache.download(new ReturnValueCompletion<ImageCacheVO>(trigger) { @Override public void success(ImageCacheVO returnValue) { cloneInstallPath = returnValue.getInstallUrl(); cache = returnValue; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "clone-image"; @Override public void run(final FlowTrigger trigger, Map data) { CloneCmd cmd = new CloneCmd(); cmd.srcPath = cloneInstallPath; cmd.dstPath = volumePath; httpCall(CLONE_PATH, cmd, CloneRsp.class, new ReturnValueCompletion<CloneRsp>(trigger) { @Override public void fail(ErrorCode err) { trigger.fail(err); } @Override public void success(CloneRsp ret) { trigger.next(); } }); } }); done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { VolumeInventory vol = msg.getVolume(); vol.setInstallPath(volumePath); vol.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); reply.setVolume(vol); ImageCacheVolumeRefVO ref = new ImageCacheVolumeRefVO(); ref.setImageCacheId(cache.getId()); ref.setPrimaryStorageUuid(self.getUuid()); ref.setVolumeUuid(vol.getUuid()); dbf.persist(ref); bus.reply(msg, reply); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { reply.setError(errCode); bus.reply(msg, reply); } }); } }).start(); } @Override protected void handle(final DeleteVolumeOnPrimaryStorageMsg msg) { DeleteCmd cmd = new DeleteCmd(); cmd.installPath = msg.getVolume().getInstallPath(); final DeleteVolumeOnPrimaryStorageReply reply = new DeleteVolumeOnPrimaryStorageReply(); httpCall(DELETE_PATH, cmd, DeleteRsp.class, new ReturnValueCompletion<DeleteRsp>(msg) { @Override public void fail(ErrorCode err) { reply.setError(err); bus.reply(msg, reply); } @Override public void success(DeleteRsp ret) { bus.reply(msg, reply); } }); } @Override protected void handle(final CreateTemplateFromVolumeOnPrimaryStorageMsg msg) { final CreateTemplateFromVolumeOnPrimaryStorageReply reply = new CreateTemplateFromVolumeOnPrimaryStorageReply(); BackupStorageMediator mediator = getBackupStorageMediator(msg.getBackupStorageUuid()); UploadParam param = new UploadParam(); param.image = msg.getImageInventory(); param.primaryStorageInstallPath = msg.getVolumeInventory().getInstallPath(); mediator.param = param; mediator.upload(new ReturnValueCompletion<String>(msg) { @Override public void success(String returnValue) { reply.setTemplateBackupStorageInstallPath(returnValue); reply.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } @Override protected void handle(final DownloadDataVolumeToPrimaryStorageMsg msg) { final DownloadDataVolumeToPrimaryStorageReply reply = new DownloadDataVolumeToPrimaryStorageReply(); BackupStorageMediator mediator = getBackupStorageMediator(msg.getBackupStorageRef().getBackupStorageUuid()); ImageSpec spec = new ImageSpec(); spec.setInventory(msg.getImage()); spec.setSelectedBackupStorage(msg.getBackupStorageRef()); DownloadParam param = new DownloadParam(); param.image = spec; param.installPath = makeDataVolumeInstallPath(msg.getVolumeUuid()); mediator.param = param; mediator.download(new ReturnValueCompletion<String>(msg) { @Override public void success(String returnValue) { reply.setInstallPath(returnValue); reply.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } @Override protected void handle(final DeleteBitsOnPrimaryStorageMsg msg) { DeleteCmd cmd = new DeleteCmd(); cmd.installPath = msg.getInstallPath(); final DeleteBitsOnPrimaryStorageReply reply = new DeleteBitsOnPrimaryStorageReply(); httpCall(DELETE_PATH, cmd, DeleteRsp.class, new ReturnValueCompletion<DeleteRsp>(msg) { @Override public void fail(ErrorCode err) { reply.setError(err); bus.reply(msg, reply); } @Override public void success(DeleteRsp ret) { bus.reply(msg, reply); } }); } @Override protected void handle(final DownloadIsoToPrimaryStorageMsg msg) { final DownloadIsoToPrimaryStorageReply reply = new DownloadIsoToPrimaryStorageReply(); DownloadToCache downloadToCache = new DownloadToCache(); downloadToCache.image = msg.getIsoSpec(); downloadToCache.download(new ReturnValueCompletion<ImageCacheVO>(msg) { @Override public void success(ImageCacheVO returnValue) { reply.setInstallPath(returnValue.getInstallUrl()); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } @Override protected void handle(DeleteIsoFromPrimaryStorageMsg msg) { DeleteIsoFromPrimaryStorageReply reply = new DeleteIsoFromPrimaryStorageReply(); bus.reply(msg, reply); } @Override protected void handle(AskVolumeSnapshotCapabilityMsg msg) { AskVolumeSnapshotCapabilityReply reply = new AskVolumeSnapshotCapabilityReply(); VolumeSnapshotCapability cap = new VolumeSnapshotCapability(); cap.setSupport(true); cap.setArrangementType(VolumeSnapshotArrangementType.INDIVIDUAL); reply.setCapability(cap); bus.reply(msg, reply); } @Override protected void handle(final SyncVolumeSizeOnPrimaryStorageMsg msg) { final SyncVolumeSizeOnPrimaryStorageReply reply = new SyncVolumeSizeOnPrimaryStorageReply(); final VolumeVO vol = dbf.findByUuid(msg.getVolumeUuid(), VolumeVO.class); String installPath = vol.getInstallPath(); GetVolumeSizeCmd cmd = new GetVolumeSizeCmd(); cmd.fsId = getSelf().getFsid(); cmd.uuid = self.getUuid(); cmd.volumeUuid = msg.getVolumeUuid(); cmd.installPath = installPath; httpCall(GET_VOLUME_SIZE_PATH, cmd, GetVolumeSizeRsp.class, new ReturnValueCompletion<GetVolumeSizeRsp>(msg) { @Override public void success(GetVolumeSizeRsp rsp) { // current fusionstor has no way to get actual size long asize = rsp.actualSize == null ? vol.getActualSize() : rsp.actualSize; reply.setActualSize(asize); reply.setSize(rsp.size); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } protected <T extends AgentResponse> void httpCall(final String path, final AgentCommand cmd, final Class<T> retClass, final ReturnValueCompletion<T> callback) { cmd.setUuid(self.getUuid()); cmd.setFsId(getSelf().getFsid()); final List<FusionstorPrimaryStorageMonBase> mons = new ArrayList<FusionstorPrimaryStorageMonBase>(); for (FusionstorPrimaryStorageMonVO monvo : getSelf().getMons()) { if (monvo.getStatus() == MonStatus.Connected) { mons.add(new FusionstorPrimaryStorageMonBase(monvo)); } } if (mons.isEmpty()) { throw new OperationFailureException(errf.stringToOperationError( String.format("all fusionstor mons of primary storage[uuid:%s] are not in Connected state", self.getUuid()) )); } Collections.shuffle(mons); class HttpCaller { Iterator<FusionstorPrimaryStorageMonBase> it = mons.iterator(); List<ErrorCode> errorCodes = new ArrayList<ErrorCode>(); void call() { if (!it.hasNext()) { callback.fail(errf.stringToOperationError( String.format("all mons failed to execute http call[%s], errors are %s", path, JSONObjectUtil.toJsonString(errorCodes)) )); return; } FusionstorPrimaryStorageMonBase base = it.next(); base.httpCall(path, cmd, retClass, new ReturnValueCompletion<T>(callback) { @Override public void success(T ret) { if (!ret.success) { callback.fail(errf.stringToOperationError(ret.error)); return; } if (!(cmd instanceof InitCmd)) { updateCapacityIfNeeded(ret); } callback.success(ret); } @Override public void fail(ErrorCode errorCode) { errorCodes.add(errorCode); call(); } }); } } new HttpCaller().call(); } private void updateCapacityIfNeeded(AgentResponse rsp) { if (rsp.totalCapacity != null && rsp.availableCapacity != null) { new FusionstorCapacityUpdater().update(getSelf().getFsid(), rsp.totalCapacity, rsp.availableCapacity); } } private void connect(final boolean newAdded, final Completion completion) { final List<FusionstorPrimaryStorageMonBase> mons = CollectionUtils.transformToList(getSelf().getMons(), new Function<FusionstorPrimaryStorageMonBase, FusionstorPrimaryStorageMonVO>() { @Override public FusionstorPrimaryStorageMonBase call(FusionstorPrimaryStorageMonVO arg) { return new FusionstorPrimaryStorageMonBase(arg); } }); class Connector { List<ErrorCode> errorCodes = new ArrayList<ErrorCode>(); Iterator<FusionstorPrimaryStorageMonBase> it = mons.iterator(); void connect(final FlowTrigger trigger) { if (!it.hasNext()) { if (errorCodes.size() == mons.size()) { trigger.fail(errf.stringToOperationError( String.format("unable to connect to the fusionstor primary storage[uuid:%s]. Failed to connect all fusionstor mons. Errors are %s", self.getUuid(), JSONObjectUtil.toJsonString(errorCodes)) )); } else { // reload because mon status changed PrimaryStorageVO vo = dbf.reload(self); if (vo == null) { if (newAdded){ if (!getSelf().getMons().isEmpty()) { dbf.removeCollection(getSelf().getMons(), FusionstorPrimaryStorageMonVO.class); } } trigger.fail(operr("fusionstor primary storage[uuid:%s] may have been deleted.", self.getUuid())); } else { self = vo; trigger.next(); } } return; } final FusionstorPrimaryStorageMonBase base = it.next(); base.connect(new Completion(trigger) { @Override public void success() { connect(trigger); } @Override public void fail(ErrorCode errorCode) { errorCodes.add(errorCode); if (newAdded) { // the mon fails to connect, remove it dbf.remove(base.getSelf()); } connect(trigger); } }); } } FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("connect-fusionstor-primary-storage-%s", self.getUuid())); chain.then(new ShareFlow() { @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "connect-monitor"; @Override public void run(FlowTrigger trigger, Map data) { new Connector().connect(trigger); } }); flow(new NoRollbackFlow() { String __name__ = "check-mon-integrity"; @Override public void run(final FlowTrigger trigger, Map data) { final Map<String, String> fsids = new HashMap<String, String>(); final List<FusionstorPrimaryStorageMonBase> mons = CollectionUtils.transformToList(getSelf().getMons(), new Function<FusionstorPrimaryStorageMonBase, FusionstorPrimaryStorageMonVO>() { @Override public FusionstorPrimaryStorageMonBase call(FusionstorPrimaryStorageMonVO arg) { return arg.getStatus() == MonStatus.Connected ? new FusionstorPrimaryStorageMonBase(arg) : null; } }); DebugUtils.Assert(!mons.isEmpty(), "how can be no connected MON !!!???"); final AsyncLatch latch = new AsyncLatch(mons.size(), new NoErrorCompletion(trigger) { @Override public void done() { Set<String> set = new HashSet<String>(); set.addAll(fsids.values()); if (set.size() != 1) { StringBuilder sb = new StringBuilder("the fsid returned by mons are mismatching, it seems the mons belong to different fusionstor clusters:\n"); for (FusionstorPrimaryStorageMonBase mon : mons) { String fsid = fsids.get(mon.getSelf().getUuid()); sb.append(String.format("%s (mon ip) --> %s (fsid)\n", mon.getSelf().getHostname(), fsid)); } throw new OperationFailureException(errf.stringToOperationError(sb.toString())); } // check if there is another fusionstor setup having the same fsid String fsId = set.iterator().next(); SimpleQuery<FusionstorPrimaryStorageVO> q = dbf.createQuery(FusionstorPrimaryStorageVO.class); q.add(FusionstorPrimaryStorageVO_.fsid, Op.EQ, fsId); q.add(FusionstorPrimaryStorageVO_.uuid, Op.NOT_EQ, self.getUuid()); FusionstorPrimaryStorageVO otherFusion = q.find(); if (otherFusion != null) { throw new OperationFailureException(errf.stringToOperationError( String.format("there is another Fusionstor primary storage[name:%s, uuid:%s] with the same" + " FSID[%s], you cannot add the same Fusionstor setup as two different primary storage", otherFusion.getName(), otherFusion.getUuid(), fsId) )); } trigger.next(); } }); for (final FusionstorPrimaryStorageMonBase mon : mons) { GetFactsCmd cmd = new GetFactsCmd(); cmd.uuid = self.getUuid(); cmd.monUuid = mon.getSelf().getUuid(); mon.httpCall(GET_FACTS, cmd, GetFactsRsp.class, new ReturnValueCompletion<GetFactsRsp>(latch) { @Override public void success(GetFactsRsp rsp) { if (!rsp.success) { // one mon cannot get the facts, directly error out trigger.fail(errf.stringToOperationError(rsp.error)); return; } fsids.put(mon.getSelf().getUuid(), rsp.fsid); latch.ack(); } @Override public void fail(ErrorCode errorCode) { // one mon cannot get the facts, directly error out trigger.fail(errorCode); } }); } } }); flow(new NoRollbackFlow() { String __name__ = "init"; @Override public void run(final FlowTrigger trigger, Map data) { InitCmd cmd = new InitCmd(); List<Pool> pools = new ArrayList<Pool>(); List<String> monHostnames = new ArrayList<String>(); List<String> sshUsernames = new ArrayList<String>(); List<String> sshPasswords = new ArrayList<String>(); final List<FusionstorPrimaryStorageMonBase> mons = CollectionUtils.transformToList(getSelf().getMons(), new Function<FusionstorPrimaryStorageMonBase, FusionstorPrimaryStorageMonVO>() { @Override public FusionstorPrimaryStorageMonBase call(FusionstorPrimaryStorageMonVO arg) { return new FusionstorPrimaryStorageMonBase(arg); } }); for (FusionstorPrimaryStorageMonBase mon : mons) { monHostnames.add(mon.getSelf().getHostname()); sshUsernames.add(mon.getSelf().getSshUsername()); sshPasswords.add(mon.getSelf().getSshPassword()); } Pool p = new Pool(); p.name = getSelf().getImageCachePoolName(); p.predefined = FusionstorSystemTags.PREDEFINED_PRIMARY_STORAGE_IMAGE_CACHE_POOL.hasTag(self.getUuid()); pools.add(p); p = new Pool(); p.name = getSelf().getRootVolumePoolName(); p.predefined = FusionstorSystemTags.PREDEFINED_PRIMARY_STORAGE_ROOT_VOLUME_POOL.hasTag(self.getUuid()); pools.add(p); p = new Pool(); p.name = getSelf().getDataVolumePoolName(); p.predefined = FusionstorSystemTags.PREDEFINED_PRIMARY_STORAGE_DATA_VOLUME_POOL.hasTag(self.getUuid()); pools.add(p); cmd.pools = pools; cmd.monHostnames = monHostnames; cmd.sshUsernames = sshUsernames; cmd.sshPasswords = sshPasswords; cmd.fusionstorType = FusionstorGlobalProperty.FUSIONSTOR_PRIMARY_STORAGE_TYPE; httpCall(INIT_PATH, cmd, InitRsp.class, new ReturnValueCompletion<InitRsp>(trigger) { @Override public void fail(ErrorCode err) { trigger.fail(err); } @Override public void success(InitRsp ret) { if (getSelf().getFsid() == null) { getSelf().setFsid(ret.fsid); } getSelf().setUserKey(ret.userKey); self = dbf.updateAndRefresh(self); FusionstorCapacityUpdater updater = new FusionstorCapacityUpdater(); updater.update(ret.fsid, ret.totalCapacity, ret.availableCapacity, true); trigger.next(); } }); } }); done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { completion.success(); } }); error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { if (newAdded) { PrimaryStorageVO vo = dbf.reload(self); if (vo != null) { self = vo; } if (!getSelf().getMons().isEmpty()) { dbf.removeCollection(getSelf().getMons(), FusionstorPrimaryStorageMonVO.class); } } completion.fail(errCode); } }); } }).start(); } @Override protected void connectHook(ConnectParam param, final Completion completion) { connect(param.isNewAdded(), completion); } @Override protected void pingHook(final Completion completion) { final List<FusionstorPrimaryStorageMonBase> mons = CollectionUtils.transformToList(getSelf().getMons(), new Function<FusionstorPrimaryStorageMonBase, FusionstorPrimaryStorageMonVO>() { @Override public FusionstorPrimaryStorageMonBase call(FusionstorPrimaryStorageMonVO arg) { return new FusionstorPrimaryStorageMonBase(arg); } }); final List<ErrorCode> errors = new ArrayList<ErrorCode>(); class Ping { private AtomicBoolean replied = new AtomicBoolean(false); @AsyncThread private void reconnectMon(final FusionstorPrimaryStorageMonBase mon, boolean delay) { if (!FusionstorGlobalConfig.PRIMARY_STORAGE_MON_AUTO_RECONNECT.value(Boolean.class)) { logger.debug(String.format("do not reconnect the fusionstor primary storage mon[uuid:%s] as the global config[%s] is set to false", self.getUuid(), FusionstorGlobalConfig.PRIMARY_STORAGE_MON_AUTO_RECONNECT.getCanonicalName())); return; } // there has been a reconnect in process if (!reconnectMonLock.lock()) { logger.debug(String.format("ignore this call, reconnect fusionstor primary storage mon[uuid:%s] is in process", self.getUuid())); return; } final NoErrorCompletion releaseLock = new NoErrorCompletion() { @Override public void done() { reconnectMonLock.unlock(); } }; try { if (delay) { try { TimeUnit.SECONDS.sleep(FusionstorGlobalConfig.PRIMARY_STORAGE_MON_RECONNECT_DELAY.value(Long.class)); } catch (InterruptedException e) { e.printStackTrace(); } } mon.connect(new Completion(releaseLock) { @Override public void success() { logger.debug(String.format("successfully reconnected the mon[uuid:%s] of the fusionstor primary" + " storage[uuid:%s, name:%s]", mon.getSelf().getUuid(), self.getUuid(), self.getName())); releaseLock.done(); } @Override public void fail(ErrorCode errorCode) { //TODO logger.warn(String.format("failed to reconnect the mon[uuid:%s] of the fusionstor primary" + " storage[uuid:%s, name:%s], %s", mon.getSelf().getUuid(), self.getUuid(), self.getName(), errorCode)); releaseLock.done(); } }); } catch (Throwable t) { releaseLock.done(); logger.warn(t.getMessage(), t); } } void ping() { // this is only called when all mons are disconnected final AsyncLatch latch = new AsyncLatch(mons.size(), new NoErrorCompletion() { @Override public void done() { if (!replied.compareAndSet(false, true)) { return; } ErrorCode err = errf.stringToOperationError(String.format("failed to ping the fusionstor primary storage[uuid:%s, name:%s]", self.getUuid(), self.getName()), errors); completion.fail(err); } }); for (final FusionstorPrimaryStorageMonBase mon : mons) { mon.ping(new ReturnValueCompletion<PingResult>(latch) { private void thisMonIsDown(ErrorCode err) { //TODO logger.warn(String.format("cannot ping mon[uuid:%s] of the fusionstor primary storage[uuid:%s, name:%s], %s", mon.getSelf().getUuid(), self.getUuid(), self.getName(), err)); errors.add(err); mon.changeStatus(MonStatus.Disconnected); reconnectMon(mon, true); latch.ack(); } @Override public void success(PingResult res) { if (res.success) { // as long as there is one mon working, the primary storage works pingSuccess(); if (mon.getSelf().getStatus() == MonStatus.Disconnected) { reconnectMon(mon, false); } } else if (res.operationFailure) { // as long as there is one mon saying the fusionstor not working, the primary storage goes down logger.warn(String.format("the fusionstor primary storage[uuid:%s, name:%s] is down, as one mon[uuid:%s] reports" + " an operation failure[%s]", self.getUuid(), self.getName(), mon.getSelf().getUuid(), res.error)); ErrorCode errorCode = errf.stringToOperationError(res.error); errors.add(errorCode); primaryStorageDown(); } else { // this mon is down(success == false, operationFailure == false), but the primary storage may still work as other mons may work ErrorCode errorCode = errf.stringToOperationError(res.error); thisMonIsDown(errorCode); } } @Override public void fail(ErrorCode errorCode) { thisMonIsDown(errorCode); } }); } } // this is called once a mon return an operation failure private void primaryStorageDown() { if (!replied.compareAndSet(false, true)) { return; } // set all mons to be disconnected for (FusionstorPrimaryStorageMonBase base : mons) { base.changeStatus(MonStatus.Disconnected); } ErrorCode err = errf.stringToOperationError(String.format("failed to ping the fusionstor primary storage[uuid:%s, name:%s]", self.getUuid(), self.getName()), errors); completion.fail(err); } private void pingSuccess() { if (!replied.compareAndSet(false, true)) { return; } completion.success(); } } new Ping().ping(); } @Override protected void syncPhysicalCapacity(ReturnValueCompletion<PhysicalCapacityUsage> completion) { PrimaryStorageCapacityVO cap = dbf.findByUuid(self.getUuid(), PrimaryStorageCapacityVO.class); PhysicalCapacityUsage usage = new PhysicalCapacityUsage(); usage.availablePhysicalSize = cap.getAvailablePhysicalCapacity(); usage.totalPhysicalSize = cap.getTotalPhysicalCapacity(); completion.success(usage); } @Override protected void handle(APIReconnectPrimaryStorageMsg msg) { final APIReconnectPrimaryStorageEvent evt = new APIReconnectPrimaryStorageEvent(msg.getId()); self.setStatus(PrimaryStorageStatus.Connecting); dbf.update(self); connect(false, new Completion(msg) { @Override public void success() { self = dbf.reload(self); self.setStatus(PrimaryStorageStatus.Connected); self = dbf.updateAndRefresh(self); evt.setInventory(getSelfInventory()); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { self = dbf.reload(self); self.setStatus(PrimaryStorageStatus.Disconnected); self = dbf.updateAndRefresh(self); evt.setError(errorCode); bus.publish(evt); } }); } @Override protected void handleApiMessage(APIMessage msg) { if (msg instanceof APIAddMonToFusionstorPrimaryStorageMsg) { handle((APIAddMonToFusionstorPrimaryStorageMsg) msg); } else if (msg instanceof APIRemoveMonFromFusionstorPrimaryStorageMsg) { handle((APIRemoveMonFromFusionstorPrimaryStorageMsg) msg); } else if (msg instanceof APIUpdateFusionstorPrimaryStorageMonMsg) { handle((APIUpdateFusionstorPrimaryStorageMonMsg)msg); } else { super.handleApiMessage(msg); } } private void handle(APIUpdateFusionstorPrimaryStorageMonMsg msg) { final APIUpdateMonToFusionstorPrimaryStorageEvent evt = new APIUpdateMonToFusionstorPrimaryStorageEvent(msg.getId()); FusionstorPrimaryStorageMonVO monvo = dbf.findByUuid(msg.getMonUuid(), FusionstorPrimaryStorageMonVO.class); if (msg.getHostname() != null) { monvo.setHostname(msg.getHostname()); } if (msg.getMonPort() != null && msg.getMonPort() > 0 && msg.getMonPort() <= 65535) { monvo.setMonPort(msg.getMonPort()); } if (msg.getSshPort() != null && msg.getSshPort() > 0 && msg.getSshPort() <= 65535) { monvo.setSshPort(msg.getSshPort()); } if (msg.getSshUsername() != null) { monvo.setSshUsername(msg.getSshUsername()); } if (msg.getSshPassword() != null) { monvo.setSshPassword(msg.getSshPassword()); } dbf.update(monvo); evt.setInventory(FusionstorPrimaryStorageInventory.valueOf((dbf.reload(getSelf())))); bus.publish(evt); } private void handle(APIRemoveMonFromFusionstorPrimaryStorageMsg msg) { APIRemoveMonFromFusionstorPrimaryStorageEvent evt = new APIRemoveMonFromFusionstorPrimaryStorageEvent(msg.getId()); SimpleQuery<FusionstorPrimaryStorageMonVO> q = dbf.createQuery(FusionstorPrimaryStorageMonVO.class); q.add(FusionstorPrimaryStorageMonVO_.hostname, Op.IN, msg.getMonHostnames()); List<FusionstorPrimaryStorageMonVO> vos = q.list(); dbf.removeCollection(vos, FusionstorPrimaryStorageMonVO.class); evt.setInventory(FusionstorPrimaryStorageInventory.valueOf(dbf.reload(getSelf()))); bus.publish(evt); } private void handle(final APIAddMonToFusionstorPrimaryStorageMsg msg) { final APIAddMonToFusionstorPrimaryStorageEvent evt = new APIAddMonToFusionstorPrimaryStorageEvent(msg.getId()); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("add-mon-fusionstor-primary-storage-%s", self.getUuid())); chain.then(new ShareFlow() { List<FusionstorPrimaryStorageMonVO> monVOs = new ArrayList<FusionstorPrimaryStorageMonVO>(); @Override public void setup() { flow(new Flow() { String __name__ = "create-mon-in-db"; @Override public void run(FlowTrigger trigger, Map data) { for (String url : msg.getMonUrls()) { FusionstorPrimaryStorageMonVO monvo = new FusionstorPrimaryStorageMonVO(); MonUri uri = new MonUri(url); monvo.setUuid(Platform.getUuid()); monvo.setStatus(MonStatus.Connecting); monvo.setHostname(uri.getHostname()); monvo.setMonPort(uri.getMonPort()); monvo.setSshPort(uri.getSshPort()); monvo.setSshUsername(uri.getSshUsername()); monvo.setSshPassword(uri.getSshPassword()); monvo.setPrimaryStorageUuid(self.getUuid()); monVOs.add(monvo); } dbf.persistCollection(monVOs); trigger.next(); } @Override public void rollback(FlowRollback trigger, Map data) { dbf.removeCollection(monVOs, FusionstorPrimaryStorageMonVO.class); trigger.rollback(); } }); flow(new NoRollbackFlow() { String __name__ = "connect-mons"; @Override public void run(final FlowTrigger trigger, Map data) { List<FusionstorPrimaryStorageMonBase> bases = CollectionUtils.transformToList(monVOs, new Function<FusionstorPrimaryStorageMonBase, FusionstorPrimaryStorageMonVO>() { @Override public FusionstorPrimaryStorageMonBase call(FusionstorPrimaryStorageMonVO arg) { return new FusionstorPrimaryStorageMonBase(arg); } }); final List<ErrorCode> errorCodes = new ArrayList<ErrorCode>(); final AsyncLatch latch = new AsyncLatch(bases.size(), new NoErrorCompletion(trigger) { @Override public void done() { if (!errorCodes.isEmpty()) { trigger.fail(errf.instantiateErrorCode(SysErrors.OPERATION_ERROR, "unable to connect mons", errorCodes)); } else { trigger.next(); } } }); for (FusionstorPrimaryStorageMonBase base : bases) { base.connect(new Completion(trigger) { @Override public void success() { latch.ack(); } @Override public void fail(ErrorCode errorCode) { // one fails, all fail errorCodes.add(errorCode); latch.ack(); } }); } } }); flow(new NoRollbackFlow() { String __name__ = "check-mon-integrity"; @Override public void run(final FlowTrigger trigger, Map data) { List<FusionstorPrimaryStorageMonBase> bases = CollectionUtils.transformToList(monVOs, new Function<FusionstorPrimaryStorageMonBase, FusionstorPrimaryStorageMonVO>() { @Override public FusionstorPrimaryStorageMonBase call(FusionstorPrimaryStorageMonVO arg) { return new FusionstorPrimaryStorageMonBase(arg); } }); final List<ErrorCode> errors = new ArrayList<ErrorCode>(); final AsyncLatch latch = new AsyncLatch(bases.size(), new NoErrorCompletion(trigger) { @Override public void done() { // one fail, all fail if (!errors.isEmpty()) { trigger.fail(errf.instantiateErrorCode(SysErrors.OPERATION_ERROR, "unable to add mon to fusionstor primary storage", errors)); } else { trigger.next(); } } }); for (final FusionstorPrimaryStorageMonBase base : bases) { GetFactsCmd cmd = new GetFactsCmd(); cmd.uuid = self.getUuid(); cmd.monUuid = base.getSelf().getUuid(); base.httpCall(GET_FACTS, cmd, GetFactsRsp.class, new ReturnValueCompletion<GetFactsRsp>(latch) { @Override public void success(GetFactsRsp rsp) { if (!rsp.isSuccess()) { errors.add(errf.stringToOperationError(rsp.getError())); } else { String fsid = rsp.fsid; if (!getSelf().getFsid().equals(fsid)) { errors.add(errf.stringToOperationError( String.format("the mon[ip:%s] returns a fsid[%s] different from the current fsid[%s] of the cep cluster," + "are you adding a mon not belonging to current cluster mistakenly?", base.getSelf().getHostname(), fsid, getSelf().getFsid()) )); } } latch.ack(); } @Override public void fail(ErrorCode errorCode) { errors.add(errorCode); latch.ack(); } }); } } }); done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { evt.setInventory(FusionstorPrimaryStorageInventory.valueOf(dbf.reload(getSelf()))); bus.publish(evt); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { evt.setError(errCode); bus.publish(evt); } }); } }).start(); } @Override protected void handleLocalMessage(Message msg) { if (msg instanceof TakeSnapshotMsg) { handle((TakeSnapshotMsg) msg); } else if (msg instanceof CreateVolumeFromVolumeSnapshotOnPrimaryStorageMsg) { handle((CreateVolumeFromVolumeSnapshotOnPrimaryStorageMsg) msg); } else if (msg instanceof BackupVolumeSnapshotFromPrimaryStorageToBackupStorageMsg) { handle((BackupVolumeSnapshotFromPrimaryStorageToBackupStorageMsg) msg); } else if (msg instanceof CreateKvmSecretMsg) { handle((CreateKvmSecretMsg) msg); } else if (msg instanceof UploadBitsToBackupStorageMsg) { handle((UploadBitsToBackupStorageMsg) msg); } else if (msg instanceof SetupSelfFencerOnKvmHostMsg) { handle((SetupSelfFencerOnKvmHostMsg) msg); } else { super.handleLocalMessage(msg); } } private void handle(final SetupSelfFencerOnKvmHostMsg msg) { KvmSetupSelfFencerParam param = msg.getParam(); KvmSetupSelfFencerCmd cmd = new KvmSetupSelfFencerCmd(); cmd.uuid = self.getUuid(); cmd.fsId = getSelf().getFsid(); cmd.hostUuid = param.getHostUuid(); cmd.interval = param.getInterval(); cmd.maxAttempts = param.getMaxAttempts(); cmd.storageCheckerTimeout = param.getStorageCheckerTimeout(); cmd.userKey = getSelf().getUserKey(); cmd.heartbeatImagePath = String.format("%s/fusionstor-primary-storage-%s-heartbeat-file", getSelf().getRootVolumePoolName(), self.getUuid()); cmd.monUrls = CollectionUtils.transformToList(getSelf().getMons(), new Function<String, FusionstorPrimaryStorageMonVO>() { @Override public String call(FusionstorPrimaryStorageMonVO arg) { return String.format("%s:%s", arg.getHostname(), arg.getMonPort()); } }); final SetupSelfFencerOnKvmHostReply reply = new SetupSelfFencerOnKvmHostReply(); new KvmCommandSender(param.getHostUuid()).send(cmd, KVM_HA_SETUP_SELF_FENCER, new KvmCommandFailureChecker() { @Override public ErrorCode getError(KvmResponseWrapper wrapper) { AgentResponse rsp = wrapper.getResponse(AgentResponse.class); return rsp.isSuccess() ? null : errf.stringToOperationError(rsp.getError()); } }, new ReturnValueCompletion<KvmResponseWrapper>(msg) { @Override public void success(KvmResponseWrapper wrapper) { bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } private void handle(final UploadBitsToBackupStorageMsg msg) { SimpleQuery<BackupStorageVO> q = dbf.createQuery(BackupStorageVO.class); q.select(BackupStorageVO_.type); q.add(BackupStorageVO_.uuid, Op.EQ, msg.getBackupStorageUuid()); String bsType = q.findValue(); if (!FusionstorConstants.FUSIONSTOR_BACKUP_STORAGE_TYPE.equals(bsType)) { throw new OperationFailureException(errf.stringToOperationError( String.format("unable to upload bits to the backup storage[type:%s], we only support FUSIONSTOR", bsType) )); } final UploadBitsToBackupStorageReply reply = new UploadBitsToBackupStorageReply(); CpCmd cmd = new CpCmd(); cmd.fsId = getSelf().getFsid(); cmd.srcPath = msg.getPrimaryStorageInstallPath(); cmd.dstPath = msg.getBackupStorageInstallPath(); httpCall(CP_PATH, cmd, CpRsp.class, new ReturnValueCompletion<CpRsp>(msg) { @Override public void success(CpRsp rsp) { bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } private void handle(final CreateKvmSecretMsg msg) { final CreateKvmSecretReply reply = new CreateKvmSecretReply(); createSecretOnKvmHosts(msg.getHostUuids(), new Completion(msg) { @Override public void success() { bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } private void handle(BackupVolumeSnapshotFromPrimaryStorageToBackupStorageMsg msg) { BackupVolumeSnapshotFromPrimaryStorageToBackupStorageReply reply = new BackupVolumeSnapshotFromPrimaryStorageToBackupStorageReply(); reply.setError(errf.stringToOperationError("backing up snapshots to backup storage is a depreciated feature, which will be removed in future version")); bus.reply(msg, reply); } private void handle(final CreateVolumeFromVolumeSnapshotOnPrimaryStorageMsg msg) { final CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply reply = new CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply(); final String volPath = makeDataVolumeInstallPath(msg.getVolumeUuid()); VolumeSnapshotInventory sp = msg.getSnapshot(); CpCmd cmd = new CpCmd(); cmd.resourceUuid = msg.getSnapshot().getVolumeUuid(); cmd.srcPath = sp.getPrimaryStorageInstallPath(); cmd.dstPath = volPath; httpCall(CP_PATH, cmd, CpRsp.class, new ReturnValueCompletion<CpRsp>(msg) { @Override public void success(CpRsp rsp) { reply.setInstallPath(volPath); reply.setSize(rsp.size); // current fusionstor has no way to get the actual size long asize = rsp.actualSize == null ? 1 : rsp.actualSize; reply.setActualSize(asize); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } protected void handle(final RevertVolumeFromSnapshotOnPrimaryStorageMsg msg) { final RevertVolumeFromSnapshotOnPrimaryStorageReply reply = new RevertVolumeFromSnapshotOnPrimaryStorageReply(); final FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("revert-volume-[uuid:%s]-from-snapshot-[uuid:%s]-on-fusionstor-primary-storage", msg.getVolume().getUuid(), msg.getSnapshot().getUuid())); chain.then(new ShareFlow() { @Override public void setup() { String originalVolumePath = msg.getVolume().getInstallPath(); // get volume path from snapshot path, just split @ String volumePath = msg.getSnapshot().getPrimaryStorageInstallPath().split("@")[0]; flow(new NoRollbackFlow() { @Override public void run(FlowTrigger trigger, Map data) { RollbackSnapshotCmd cmd = new RollbackSnapshotCmd(); cmd.snapshotPath = msg.getSnapshot().getPrimaryStorageInstallPath(); httpCall(ROLLBACK_SNAPSHOT_PATH, cmd, RollbackSnapshotRsp.class, new ReturnValueCompletion<RollbackSnapshotRsp>(msg) { @Override public void success(RollbackSnapshotRsp returnValue) { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "delete-origin-root-volume-which-has-no-snapshot"; @Override public void run(FlowTrigger trigger, Map data) { SimpleQuery<VolumeSnapshotVO> sq = dbf.createQuery(VolumeSnapshotVO.class); sq.add(VolumeSnapshotVO_.primaryStorageInstallPath, Op.LIKE, String.format("%s%%", originalVolumePath)); sq.count(); if (sq.count() == 0) { DeleteCmd cmd = new DeleteCmd(); cmd.installPath = originalVolumePath; httpCall(DELETE_PATH, cmd, DeleteRsp.class, new ReturnValueCompletion<DeleteRsp>(null) { @Override public void success(DeleteRsp returnValue) { logger.debug(String.format("successfully deleted %s", originalVolumePath)); } @Override public void fail(ErrorCode errorCode) { //TODO GC logger.warn(String.format("unable to delete %s, %s. Need a cleanup", originalVolumePath, errorCode)); } }); } trigger.next(); } }); done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { reply.setNewVolumeInstallPath(volumePath); bus.reply(msg, reply); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { reply.setError(errCode); bus.reply(msg, reply); } }); } }).start(); } protected void handle(final ReInitRootVolumeFromTemplateOnPrimaryStorageMsg msg) { final ReInitRootVolumeFromTemplateOnPrimaryStorageReply reply = new ReInitRootVolumeFromTemplateOnPrimaryStorageReply(); final FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("reimage-vm-root-volume-%s", msg.getVolume().getUuid())); chain.then(new ShareFlow() { String cloneInstallPath; String originalVolumePath = msg.getVolume().getInstallPath(); String volumePath = makeResetImageRootVolumeInstallPath(msg.getVolume().getUuid()); ImageCacheVO cache; @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "clone-image"; @Override public void run(final FlowTrigger trigger, Map data) { SimpleQuery<ImageCacheVO> sq = dbf.createQuery(ImageCacheVO.class); sq.add(ImageCacheVO_.imageUuid, Op.EQ, msg.getVolume().getRootImageUuid()); sq.add(ImageCacheVO_.primaryStorageUuid, Op.EQ, msg.getPrimaryStorageUuid()); ImageCacheVO ivo = sq.find(); CloneCmd cmd = new CloneCmd(); cmd.srcPath = ivo.getInstallUrl(); cmd.dstPath = volumePath; httpCall(CLONE_PATH, cmd, CloneRsp.class, new ReturnValueCompletion<CloneRsp>(trigger) { @Override public void fail(ErrorCode err) { trigger.fail(err); } @Override public void success(CloneRsp ret) { trigger.next(); } }); } }); flow(new NoRollbackFlow() { String __name__ = "delete-origin-root-volume-which-has-no-snapshot"; @Override public void run(FlowTrigger trigger, Map data) { SimpleQuery<VolumeSnapshotVO> sq = dbf.createQuery(VolumeSnapshotVO.class); sq.add(VolumeSnapshotVO_.primaryStorageInstallPath, Op.LIKE, String.format("%s%%", originalVolumePath)); sq.count(); if (sq.count() == 0) { DeleteCmd cmd = new DeleteCmd(); cmd.installPath = originalVolumePath; httpCall(DELETE_PATH, cmd, DeleteRsp.class, new ReturnValueCompletion<DeleteRsp>(null) { @Override public void success(DeleteRsp returnValue) { logger.debug(String.format("successfully deleted %s", originalVolumePath)); } @Override public void fail(ErrorCode errorCode) { //TODO GC logger.warn(String.format("unable to delete %s, %s. Need a cleanup", originalVolumePath, errorCode)); } }); } trigger.next(); } }); done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { reply.setNewVolumeInstallPath(volumePath); bus.reply(msg, reply); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { reply.setError(errCode); bus.reply(msg, reply); } }); } }).start(); } @Override protected void handle(final DeleteSnapshotOnPrimaryStorageMsg msg) { final DeleteSnapshotOnPrimaryStorageReply reply = new DeleteSnapshotOnPrimaryStorageReply(); DeleteSnapshotCmd cmd = new DeleteSnapshotCmd(); cmd.snapshotPath = msg.getSnapshot().getPrimaryStorageInstallPath(); httpCall(DELETE_SNAPSHOT_PATH, cmd, DeleteSnapshotRsp.class, new ReturnValueCompletion<DeleteSnapshotRsp>(msg) { @Override public void success(DeleteSnapshotRsp returnValue) { bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } protected void handle(MergeVolumeSnapshotOnPrimaryStorageMsg msg) { MergeVolumeSnapshotOnPrimaryStorageReply reply = new MergeVolumeSnapshotOnPrimaryStorageReply(); bus.reply(msg, reply); } private void handle(final TakeSnapshotMsg msg) { final TakeSnapshotReply reply = new TakeSnapshotReply(); final VolumeSnapshotInventory sp = msg.getStruct().getCurrent(); SimpleQuery<VolumeVO> q = dbf.createQuery(VolumeVO.class); q.select(VolumeVO_.installPath); q.add(VolumeVO_.uuid, Op.EQ, sp.getVolumeUuid()); String volumePath = q.findValue(); final String spPath = String.format("%s@%s", volumePath, sp.getUuid()); CreateSnapshotCmd cmd = new CreateSnapshotCmd(); cmd.volumeUuid = sp.getVolumeUuid(); cmd.snapshotPath = spPath; httpCall(CREATE_SNAPSHOT_PATH, cmd, CreateSnapshotRsp.class, new ReturnValueCompletion<CreateSnapshotRsp>(msg) { @Override public void success(CreateSnapshotRsp rsp) { // current fusionstor has no way to get actual size long asize = rsp.getActualSize() == null ? 1 : rsp.getActualSize(); sp.setSize(asize); sp.setPrimaryStorageUuid(self.getUuid()); sp.setPrimaryStorageInstallPath(spPath); sp.setType(VolumeSnapshotConstant.STORAGE_SNAPSHOT_TYPE.toString()); sp.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); reply.setInventory(sp); bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } @Override public void attachHook(String clusterUuid, Completion completion) { SimpleQuery<ClusterVO> q = dbf.createQuery(ClusterVO.class); q.select(ClusterVO_.hypervisorType); q.add(ClusterVO_.uuid, Op.EQ, clusterUuid); String hvType = q.findValue(); if (KVMConstant.KVM_HYPERVISOR_TYPE.equals(hvType)) { attachToKvmCluster(clusterUuid, completion); } else { completion.success(); } } private void createSecretOnKvmHosts(List<String> hostUuids, final Completion completion) { completion.success(); } private void attachToKvmCluster(String clusterUuid, Completion completion) { 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()) { completion.success(); } else { createSecretOnKvmHosts(hostUuids, completion); } } @Override public void deleteHook() { if (FusionstorGlobalConfig.PRIMARY_STORAGE_DELETE_POOL.value(Boolean.class)) { DeletePoolCmd cmd = new DeletePoolCmd(); cmd.poolNames = list(getSelf().getImageCachePoolName(), getSelf().getDataVolumePoolName(), getSelf().getRootVolumePoolName()); FutureReturnValueCompletion completion = new FutureReturnValueCompletion(null); httpCall(DELETE_POOL_PATH, cmd, DeletePoolRsp.class, completion); completion.await(TimeUnit.MINUTES.toMillis(30)); if (!completion.isSuccess()) { throw new OperationFailureException(completion.getErrorCode()); } } dbf.removeCollection(getSelf().getMons(), FusionstorPrimaryStorageMonVO.class); } }