package org.zstack.storage.snapshot;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.cascade.CascadeConstant;
import org.zstack.core.cascade.CascadeFacade;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.CloudBusCallBack;
import org.zstack.core.cloudbus.CloudBusListCallBack;
import org.zstack.core.cloudbus.MessageSafe;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.Q;
import org.zstack.core.db.SQLBatch;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.core.thread.ChainTask;
import org.zstack.core.thread.SyncTaskChain;
import org.zstack.core.thread.ThreadFacade;
import org.zstack.core.workflow.FlowChainBuilder;
import org.zstack.core.workflow.ShareFlow;
import org.zstack.header.core.Completion;
import org.zstack.header.core.NoErrorCompletion;
import org.zstack.header.core.NopeCompletion;
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.image.ImageInventory;
import org.zstack.header.image.ImageVO;
import org.zstack.header.message.APIDeleteMessage;
import org.zstack.header.message.APIMessage;
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.*;
import org.zstack.header.storage.snapshot.CreateTemplateFromVolumeSnapshotExtensionPoint.ParamIn;
import org.zstack.header.storage.snapshot.CreateTemplateFromVolumeSnapshotExtensionPoint.ParamOut;
import org.zstack.header.storage.snapshot.CreateTemplateFromVolumeSnapshotExtensionPoint.WorkflowTemplate;
import org.zstack.header.storage.snapshot.VolumeSnapshotStatus.StatusEvent;
import org.zstack.header.storage.snapshot.VolumeSnapshotTree.SnapshotLeaf;
import org.zstack.header.vm.VmInstanceState;
import org.zstack.header.vm.VmInstanceVO;
import org.zstack.header.vm.VmInstanceVO_;
import org.zstack.header.volume.VolumeFormat;
import org.zstack.header.volume.VolumeInventory;
import org.zstack.header.volume.VolumeVO;
import org.zstack.storage.primary.PrimaryStorageCapacityUpdater;
import org.zstack.utils.CollectionUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.function.Function;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.message.OperationChecker;
import static org.zstack.core.Platform.operr;
import javax.persistence.Query;
import java.util.*;
import java.util.stream.Collectors;
import static org.zstack.utils.CollectionDSL.e;
/**
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class VolumeSnapshotTreeBase {
protected static OperationChecker allowedStatus = new OperationChecker(true);
private static CLogger logger = Utils.getLogger(VolumeSnapshotTreeBase.class);
static {
allowedStatus.addState(VolumeSnapshotStatus.Ready,
VolumeSnapshotDeletionMsg.class.getName(),
BackupVolumeSnapshotMsg.class.getName(),
CreateDataVolumeFromVolumeSnapshotMsg.class.getName(),
CreateTemplateFromVolumeSnapshotMsg.class.getName(),
VolumeSnapshotBackupStorageDeletionMsg.class.getName(),
APIRevertVolumeFromSnapshotMsg.class.getName(),
APIDeleteVolumeSnapshotFromBackupStorageMsg.class.getName(),
APIBackupVolumeSnapshotMsg.class.getName()
);
allowedStatus.addState(VolumeSnapshotStatus.Deleting,
VolumeSnapshotDeletionMsg.class.getName());
allowedStatus.addState(VolumeSnapshotStatus.Creating,
VolumeSnapshotDeletionMsg.class.getName());
}
protected VolumeSnapshotVO currentRoot;
protected SnapshotLeaf currentLeaf;
protected VolumeSnapshotTree fullTree;
protected String syncSignature;
@Autowired
private CloudBus bus;
@Autowired
private ThreadFacade thdf;
@Autowired
private DatabaseFacade dbf;
@Autowired
private ErrorFacade errf;
@Autowired
private CascadeFacade casf;
@Autowired
private PluginRegistry pluginRgty;
@Autowired
private PrimaryStorageOverProvisioningManager psRaitoMgr;
public VolumeSnapshotTreeBase(VolumeSnapshotVO vo, boolean syncOnVolume) {
currentRoot = vo;
if (syncOnVolume) {
syncSignature = String.format("volume.snapshot.volume.%s", currentRoot.getVolumeUuid());
} else {
syncSignature = String.format("volume.snapshot.tree.%s", currentRoot.getTreeUuid());
}
}
private ErrorCode isOperationAllowed(Message msg) {
if (allowedStatus.isOperationAllowed(msg.getClass().getName(), currentRoot.getStatus().toString())) {
return null;
} else {
return errf.instantiateErrorCode(VolumeSnapshotErrors.NOT_IN_CORRECT_STATE,
String.format("snapshot[uuid:%s, name:%s]'s status[%s] is not allowed for message[%s], allowed status%s",
currentRoot.getUuid(), currentRoot.getName(), currentRoot.getStatus(), msg.getClass().getName(), allowedStatus.getStatesForOperation(msg.getClass().getName())));
}
}
private void refreshVO() {
VolumeSnapshotVO vo = dbf.reload(currentRoot);
if (vo == null) {
throw new OperationFailureException(operr("cannot find volume snapshot[uuid:%s, name:%s], it may have been deleted by previous operation",
currentRoot.getUuid(), currentRoot.getName()));
}
currentRoot = vo;
buildFullSnapshotTree();
currentLeaf = fullTree.findSnapshot(new Function<Boolean, VolumeSnapshotInventory>() {
@Override
public Boolean call(VolumeSnapshotInventory arg) {
return arg.getUuid().equals(currentRoot.getUuid());
}
});
}
private VolumeSnapshotInventory getSelfInventory() {
return VolumeSnapshotInventory.valueOf(currentRoot);
}
private void buildFullSnapshotTree() {
SimpleQuery<VolumeSnapshotVO> q = dbf.createQuery(VolumeSnapshotVO.class);
q.add(VolumeSnapshotVO_.treeUuid, SimpleQuery.Op.EQ, currentRoot.getTreeUuid());
List<VolumeSnapshotVO> vos = q.list();
fullTree = VolumeSnapshotTree.fromVOs(vos);
}
@MessageSafe
public void handleMessage(Message msg) {
if (msg instanceof APIMessage) {
handleApiMessage((APIMessage) msg);
} else {
handleLocalMessage(msg);
}
}
private void handleLocalMessage(Message msg) {
if (msg instanceof CreateTemplateFromVolumeSnapshotMsg) {
handle((CreateTemplateFromVolumeSnapshotMsg) msg);
} else if (msg instanceof CreateDataVolumeFromVolumeSnapshotMsg) {
handle((CreateDataVolumeFromVolumeSnapshotMsg) msg);
} else if (msg instanceof VolumeSnapshotDeletionMsg) {
handle((VolumeSnapshotDeletionMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
}
private void handle(final VolumeSnapshotDeletionMsg msg) {
thdf.chainSubmit(new ChainTask(msg) {
@Override
public String getSyncSignature() {
return syncSignature;
}
@Override
public void run(final SyncTaskChain chain) {
deletion(msg, new NoErrorCompletion(chain) {
@Override
public void done() {
chain.next();
}
});
}
@Override
public String getName() {
return String.format("delete-volume-snapshot-%s", currentRoot.getUuid());
}
});
}
private void deletion(final VolumeSnapshotDeletionMsg msg, final NoErrorCompletion completion) {
final VolumeSnapshotDeletionReply reply = new VolumeSnapshotDeletionReply();
try {
refreshVO();
} catch (OperationFailureException e) {
// the snapshot has been deleted
bus.reply(msg, reply);
completion.done();
return;
}
ErrorCode err = isOperationAllowed(msg);
if (err != null) {
reply.setError(err);
bus.reply(msg, reply);
completion.done();
return;
}
FlowChain chain = FlowChainBuilder.newSimpleFlowChain();
chain.setName(String.format("delete-snapshot-%s", currentRoot.getUuid()));
chain.allowEmptyFlow();
boolean ancestorOfLatest = false;
for (VolumeSnapshotInventory inv : currentLeaf.getDescendants()) {
if (inv.isLatest()) {
ancestorOfLatest = true;
break;
}
}
chain.then(new Flow() {
String __name__ = String.format("change-volume-snapshot-status-%s", VolumeSnapshotStatus.Deleting);
public void run(final FlowTrigger trigger, Map data) {
changeStatusOfSnapshots(StatusEvent.delete, currentLeaf.getDescendants(), new Completion(trigger) {
@Override
public void success() {
trigger.next();
}
@Override
public void fail(ErrorCode errorCode) {
trigger.fail(errorCode);
}
});
}
@Override
public void rollback(final FlowRollback trigger, Map data) {
changeStatusOfSnapshots(StatusEvent.ready, currentLeaf.getDescendants(), new Completion(trigger) {
@Override
public void success() {
trigger.rollback();
}
@Override
public void fail(ErrorCode errorCode) {
trigger.rollback();
}
});
}
});
if (!msg.isVolumeDeletion()) {
// this deletion is caused by snapshot deletion, check if merge need
SimpleQuery<VolumeSnapshotTreeVO> tq = dbf.createQuery(VolumeSnapshotTreeVO.class);
tq.select(VolumeSnapshotTreeVO_.current);
tq.add(VolumeSnapshotTreeVO_.uuid, Op.EQ, currentRoot.getTreeUuid());
Boolean onCurrentTree = tq.findValue();
boolean needMerge = onCurrentTree && ancestorOfLatest && currentRoot.getPrimaryStorageUuid() != null && VolumeSnapshotConstant.HYPERVISOR_SNAPSHOT_TYPE.toString().equals(currentRoot.getType());
if (needMerge) {
chain.then(new NoRollbackFlow() {
String __name__ = "merge-volume-snapshots-to-volume";
@Override
public void run(final FlowTrigger trigger, Map data) {
MergeVolumeSnapshotOnPrimaryStorageMsg mmsg = new MergeVolumeSnapshotOnPrimaryStorageMsg();
VolumeSnapshotInventory from = currentLeaf.getParent() == null ? currentLeaf.getInventory() : currentLeaf.getParent().getInventory();
mmsg.setFrom(from);
VolumeVO vol = dbf.findByUuid(currentRoot.getVolumeUuid(), VolumeVO.class);
mmsg.setTo(VolumeInventory.valueOf(vol));
mmsg.setFullRebase(currentLeaf.getParent() == null);
bus.makeTargetServiceIdByResourceUuid(mmsg, PrimaryStorageConstant.SERVICE_ID, currentRoot.getPrimaryStorageUuid());
bus.send(mmsg, new CloudBusCallBack(trigger) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
trigger.fail(reply.getError());
} else {
trigger.next();
}
}
});
}
});
}
chain.then(new NoRollbackFlow() {
String __name__ = "delete-volume-snapshots-from-backup-storage";
@Override
public void run(final FlowTrigger trigger, Map data) {
final List<VolumeSnapshotBackupStorageDeletionMsg> dmsgs = makeVolumeSnapshotBackupStorageDeletionMsg(null);
if (dmsgs.isEmpty()) {
trigger.next();
return;
}
bus.send(dmsgs, VolumeSnapshotGlobalConfig.SNAPSHOT_DELETE_PARALLELISM_DEGREE.value(Integer.class), new CloudBusListCallBack(trigger) {
@Override
public void run(List<MessageReply> replies) {
for (MessageReply r : replies) {
if (!r.isSuccess()) {
VolumeSnapshotBackupStorageDeletionMsg dmsg = dmsgs.get(replies.indexOf(r));
logger.warn(String.format("failed to delete snapshot[uuid:%s] on backup storage[uuids: %s], the backup storage should cleanup",
dmsg.getSnapshotUuid(), dmsg.getBackupStorageUuids()));
}
}
trigger.next();
}
});
}
});
}
chain.then(new NoRollbackFlow() {
String __name__ = "delete-volume-snapshots-from-primary-storage";
@Override
public void run(final FlowTrigger trigger, Map data) {
final List<VolumeSnapshotPrimaryStorageDeletionMsg> pmsgs = CollectionUtils.transformToList(currentLeaf.getDescendants(), new Function<VolumeSnapshotPrimaryStorageDeletionMsg, VolumeSnapshotInventory>() {
@Override
public VolumeSnapshotPrimaryStorageDeletionMsg call(VolumeSnapshotInventory arg) {
if (arg.getPrimaryStorageUuid() == null) {
return null;
}
VolumeSnapshotPrimaryStorageDeletionMsg pmsg = new VolumeSnapshotPrimaryStorageDeletionMsg();
pmsg.setUuid(arg.getUuid());
bus.makeTargetServiceIdByResourceUuid(pmsg, VolumeSnapshotConstant.SERVICE_ID, arg.getPrimaryStorageUuid());
return pmsg;
}
});
if (pmsgs.isEmpty()) {
trigger.next();
return;
}
bus.send(pmsgs, VolumeSnapshotGlobalConfig.SNAPSHOT_DELETE_PARALLELISM_DEGREE.value(Integer.class), new CloudBusListCallBack(trigger) {
@Override
public void run(List<MessageReply> replies) {
for (MessageReply r : replies) {
if (!r.isSuccess()) {
VolumeSnapshotPrimaryStorageDeletionMsg pmsg = pmsgs.get(replies.indexOf(r));
logger.warn(String.format("failed to delete snapshot[uuid:%s] on primary storage[uuid:%s], the primary storage should cleanup",
pmsg.getSnapshotUuid(), currentRoot.getPrimaryStorageUuid()));
}
}
trigger.next();
}
});
}
});
final boolean finalAncestorOfLatest = ancestorOfLatest;
chain.done(new FlowDoneHandler(msg, completion) {
@Override
public void handle(Map data) {
if (msg.isVolumeDeletion()) {
new Runnable() {
@Override
@Transactional
public void run() {
String sql = "update VolumeSnapshotTreeVO tree set tree.volumeUuid = NULL where tree.volumeUuid = :volUuid";
Query q = dbf.getEntityManager().createQuery(sql);
q.setParameter("volUuid", currentRoot.getVolumeUuid());
q.executeUpdate();
sql = "update VolumeSnapshotVO s set s.volumeUuid = NULL where s.volumeUuid = :volUuid";
q = dbf.getEntityManager().createQuery(sql);
q.setParameter("volUuid", currentRoot.getVolumeUuid());
q.executeUpdate();
}
}.run();
}
if (!msg.isVolumeDeletion() && finalAncestorOfLatest && currentRoot.getParentUuid() != null) {
// reset latest
VolumeSnapshotVO vo = dbf.findByUuid(currentRoot.getParentUuid(), VolumeSnapshotVO.class);
vo.setLatest(true);
dbf.update(vo);
logger.debug(String.format("reset latest snapshot of tree[uuid:%s] to snapshot[uuid:%s]",
currentRoot.getTreeUuid(), currentRoot.getParentUuid()));
}
if (!cleanup()) {
changeStatusOfSnapshots(StatusEvent.ready, currentLeaf.getDescendants(), new Completion(msg, completion) {
@Override
public void success() {
bus.reply(msg, reply);
completion.done();
}
@Override
public void fail(ErrorCode errorCode) {
reply.setError(errorCode);
bus.reply(msg, reply);
completion.done();
}
});
} else {
bus.reply(msg, reply);
completion.done();
}
}
}).error(new FlowErrorHandler(msg, completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
reply.setError(errCode);
bus.reply(msg, reply);
completion.done();
}
}).start();
}
private void handle(final CreateDataVolumeFromVolumeSnapshotMsg msg) {
thdf.chainSubmit(new ChainTask(msg) {
@Override
public String getSyncSignature() {
return syncSignature;
}
@Override
public void run(final SyncTaskChain chain) {
createDataVolume(msg, new NoErrorCompletion(chain) {
@Override
public void done() {
chain.next();
}
});
}
@Override
public String getName() {
return String.format("create-data-volume-from-snapshot-%s", currentRoot.getUuid());
}
});
}
private void createDataVolume(final CreateDataVolumeFromVolumeSnapshotMsg msg, final NoErrorCompletion completion) {
final CreateDataVolumeFromVolumeSnapshotReply reply = new CreateDataVolumeFromVolumeSnapshotReply();
refreshVO();
ErrorCode err = isOperationAllowed(msg);
if (err != null) {
reply.setError(err);
bus.reply(msg, reply);
completion.done();
return;
}
FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName(String.format("create-data-volume-from-snapshot-%s", currentRoot.getUuid()));
chain.then(new ShareFlow() {
String installPath;
long size;
long actualSize;
@Override
public void setup() {
flow(new Flow() {
String __name__ = "create-data-volume-on-primary-storage";
public void run(final FlowTrigger trigger, Map data) {
CreateVolumeFromVolumeSnapshotOnPrimaryStorageMsg cmsg = new CreateVolumeFromVolumeSnapshotOnPrimaryStorageMsg();
cmsg.setSnapshot(currentLeaf.getInventory());
cmsg.setVolumeUuid(msg.getVolume().getUuid());
cmsg.setPrimaryStorageUuid(currentRoot.getPrimaryStorageUuid());
bus.makeTargetServiceIdByResourceUuid(cmsg, PrimaryStorageConstant.SERVICE_ID, cmsg.getPrimaryStorageUuid());
bus.send(cmsg, new CloudBusCallBack(trigger) {
@Override
public void run(MessageReply reply) {
if (reply.isSuccess()) {
CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply cr = reply.castReply();
installPath = cr.getInstallPath();
actualSize = cr.getActualSize();
size = cr.getSize();
trigger.next();
} else {
trigger.fail(reply.getError());
}
}
});
}
@Override
public void rollback(FlowRollback trigger, Map data) {
if (installPath != null) {
DeleteBitsOnPrimaryStorageMsg dmsg = new DeleteBitsOnPrimaryStorageMsg();
dmsg.setHypervisorType(VolumeFormat.getMasterHypervisorTypeByVolumeFormat(getSelfInventory().getFormat()).toString());
dmsg.setPrimaryStorageUuid(currentRoot.getPrimaryStorageUuid());
dmsg.setInstallPath(installPath);
dmsg.setBitsType(VolumeVO.class.getSimpleName());
dmsg.setBitsUuid(msg.getVolumeUuid());
bus.makeTargetServiceIdByResourceUuid(dmsg, PrimaryStorageConstant.SERVICE_ID, dmsg.getPrimaryStorageUuid());
bus.send(dmsg);
}
trigger.rollback();
}
});
flow(new NoRollbackFlow() {
String __name__ = "reserve-capacity-on-primary-storage";
@Override
public void run(FlowTrigger trigger, Map data) {
long requiredSize = psRaitoMgr.calculateByRatio(currentRoot.getPrimaryStorageUuid(), size);
PrimaryStorageCapacityUpdater updater = new PrimaryStorageCapacityUpdater(currentRoot.getPrimaryStorageUuid());
updater.reserve(requiredSize);
trigger.next();
}
});
done(new FlowDoneHandler(msg, completion) {
@Override
public void handle(Map data) {
VolumeInventory inv = msg.getVolume();
inv.setInstallPath(installPath);
inv.setSize(size);
inv.setPrimaryStorageUuid(currentRoot.getPrimaryStorageUuid());
inv.setFormat(currentRoot.getFormat());
reply.setActualSize(actualSize);
reply.setInventory(inv);
bus.reply(msg, reply);
}
});
error(new FlowErrorHandler(msg, completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
reply.setError(errCode);
bus.reply(msg, reply);
}
});
Finally(new FlowFinallyHandler(msg, completion) {
@Override
public void Finally() {
completion.done();
}
});
}
}).start();
}
private void handle(final CreateTemplateFromVolumeSnapshotMsg msg) {
thdf.chainSubmit(new ChainTask(msg) {
@Override
public String getSyncSignature() {
return syncSignature;
}
@Override
public void run(final SyncTaskChain chain) {
createTemplate(msg, new NoErrorCompletion(chain) {
@Override
public void done() {
chain.next();
}
});
}
@Override
public String getName() {
return String.format("create-template-from-volume-snapshot-%s", currentRoot.getUuid());
}
});
}
private void changeStatusOfSnapshots(final StatusEvent evt, final List<VolumeSnapshotInventory> snapshots, final Completion completion) {
List<ChangeVolumeSnapshotStatusMsg> msgs = CollectionUtils.transformToList(snapshots, new Function<ChangeVolumeSnapshotStatusMsg, VolumeSnapshotInventory>() {
@Override
public ChangeVolumeSnapshotStatusMsg call(VolumeSnapshotInventory arg) {
ChangeVolumeSnapshotStatusMsg msg = new ChangeVolumeSnapshotStatusMsg();
msg.setEvent(evt.toString());
msg.setSnapshotUuid(arg.getUuid());
bus.makeLocalServiceId(msg, VolumeSnapshotConstant.SERVICE_ID);
return msg;
}
});
bus.send(msgs, new CloudBusListCallBack(completion) {
@Override
public void run(List<MessageReply> replies) {
ErrorCode err = null;
VolumeSnapshotInventory failSnapshot = null;
for (MessageReply r : replies) {
if (!r.isSuccess()) {
err = r.getError();
failSnapshot = snapshots.get(replies.indexOf(r));
break;
}
}
if (err != null) {
completion.fail(operr("failed to change status of volume snapshot[uuid:%s, name:%s] by status event[%s]",
failSnapshot.getUuid(), failSnapshot.getName(), evt).causedBy(err));
} else {
completion.success();
}
}
});
}
private void createTemplate(final CreateTemplateFromVolumeSnapshotMsg msg, final NoErrorCompletion completion) {
final CreateTemplateFromVolumeSnapshotReply reply = new CreateTemplateFromVolumeSnapshotReply();
refreshVO();
final ErrorCode err = isOperationAllowed(msg);
if (err != null) {
reply.setError(err);
bus.reply(msg, reply);
completion.done();
return;
}
SimpleQuery<PrimaryStorageVO> q = dbf.createQuery(PrimaryStorageVO.class);
q.select(PrimaryStorageVO_.type);
q.add(PrimaryStorageVO_.uuid, Op.EQ, currentRoot.getPrimaryStorageUuid());
String psType = q.findValue();
CreateTemplateFromVolumeSnapshotExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, CreateTemplateFromVolumeSnapshotExtensionPoint.class);
if (ext == null) {
throw new CloudRuntimeException(String.format("the primary storage[type:%s] doesn't implement CreateTemplateFromVolumeSnapshotExtensionPoint", psType));
}
ParamIn paramIn = new ParamIn();
paramIn.setPrimaryStorageUuid(currentRoot.getPrimaryStorageUuid());
paramIn.setSnapshot(currentLeaf.getInventory());
paramIn.setImage(ImageInventory.valueOf(dbf.findByUuid(msg.getImageUuid(), ImageVO.class)));
WorkflowTemplate workflowTemplate = ext.createTemplateFromVolumeSnapshot(paramIn);
ParamOut paramOut = new ParamOut();
FlowChain chain = FlowChainBuilder.newSimpleFlowChain();
chain.setName(String.format("create-template-from-snapshot-%s-%s", currentLeaf.getInventory().getName(),
currentLeaf.getUuid()));
chain.putData(
e(ParamIn.class, paramIn),
e(ParamOut.class, paramOut)
);
chain.then(workflowTemplate.getCreateTemporaryTemplate());
chain.then(new Flow() {
String __name__ = "allocate-backup-storage";
String allocateBackupStorageUuid;
Long allocatedSize;
@Override
public void run(FlowTrigger trigger, Map data) {
ParamOut out = (ParamOut) data.get(ParamOut.class);
allocatedSize = out.getActualSize();
AllocateBackupStorageMsg amsg = new AllocateBackupStorageMsg();
amsg.setBackupStorageUuid(msg.getBackupStorageUuid());
amsg.setSize(allocatedSize);
bus.makeLocalServiceId(amsg, BackupStorageConstant.SERVICE_ID);
bus.send(amsg, new CloudBusCallBack(trigger) {
@Override
public void run(MessageReply reply) {
if (reply.isSuccess()) {
allocateBackupStorageUuid = msg.getBackupStorageUuid();
paramIn.setBackupStorageUuid(allocateBackupStorageUuid);
trigger.next();
} else {
trigger.fail(reply.getError());
}
}
});
}
@Override
public void rollback(FlowRollback trigger, Map data) {
if (allocateBackupStorageUuid != null) {
ReturnBackupStorageMsg rmsg = new ReturnBackupStorageMsg();
rmsg.setSize(allocatedSize);
rmsg.setBackupStorageUuid(allocateBackupStorageUuid);
bus.makeLocalServiceId(rmsg, BackupStorageConstant.SERVICE_ID);
bus.send(rmsg);
}
trigger.rollback();
}
});
chain.then(workflowTemplate.getUploadToBackupStorage());
chain.then(workflowTemplate.getDeleteTemporaryTemplate());
chain.done(new FlowDoneHandler(msg, completion) {
@Override
public void handle(Map data) {
ParamOut out = (ParamOut) data.get(ParamOut.class);
reply.setActualSize(out.getActualSize());
reply.setSize(out.getSize());
reply.setBackupStorageInstallPath(out.getBackupStorageInstallPath());
reply.setBackupStorageUuid(paramIn.getBackupStorageUuid());
bus.reply(msg, reply);
}
}).error(new FlowErrorHandler(msg, completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
reply.setError(errCode);
bus.reply(msg, reply);
}
}).Finally(new FlowFinallyHandler(msg, completion) {
@Override
public void Finally() {
completion.done();
}
}).start();
}
private void handleApiMessage(APIMessage msg) {
if (msg instanceof APIDeleteVolumeSnapshotMsg) {
handle((APIDeleteVolumeSnapshotMsg) msg);
} else if (msg instanceof APIRevertVolumeFromSnapshotMsg) {
handle((APIRevertVolumeFromSnapshotMsg) msg);
} else if (msg instanceof APIBackupVolumeSnapshotMsg) {
handle((APIBackupVolumeSnapshotMsg) msg);
} else if (msg instanceof APIDeleteVolumeSnapshotFromBackupStorageMsg) {
handle((APIDeleteVolumeSnapshotFromBackupStorageMsg) msg);
} else if (msg instanceof APIUpdateVolumeSnapshotMsg) {
handle((APIUpdateVolumeSnapshotMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
}
private void handle(APIUpdateVolumeSnapshotMsg msg) {
VolumeSnapshotVO self = dbf.findByUuid(msg.getUuid(), VolumeSnapshotVO.class);
boolean update = false;
if (msg.getName() != null) {
self.setName(msg.getName());
update = true;
}
if (msg.getDescription() != null) {
self.setDescription(msg.getDescription());
update = true;
}
if (update) {
self = dbf.updateAndRefresh(self);
}
APIUpdateVolumeSnapshotEvent evt = new APIUpdateVolumeSnapshotEvent(msg.getId());
evt.setInventory(VolumeSnapshotInventory.valueOf(self));
bus.publish(evt);
}
private boolean cleanup() {
class Ret {
boolean value;
}
Ret ret = new Ret();
new SQLBatch() {
@Override
protected void scripts() {
String psUuid = q(VolumeSnapshotVO.class)
.select(VolumeSnapshotVO_.primaryStorageUuid)
.eq(VolumeSnapshotVO_.uuid, currentRoot.getUuid())
.findValue();
if (psUuid != null) {
ret.value = false;
return;
}
// the snapshot is on neither primary storage, delete it and descendants
List<String> uuids = currentLeaf.getDescendants().stream().map(VolumeSnapshotInventory::getUuid).collect(Collectors.toList());
if (!uuids.isEmpty()) {
sql(VolumeSnapshotVO.class).in(VolumeSnapshotVO_.uuid, uuids).hardDelete();
}
if (!q(VolumeSnapshotVO.class).eq(VolumeSnapshotVO_.treeUuid, currentRoot.getTreeUuid()).isExists()) {
logger.debug(String.format("volume snapshot tree[uuid:%s] has no leaf, delete it", currentRoot.getTreeUuid()));
sql(VolumeSnapshotTreeVO.class).eq(VolumeSnapshotTreeVO_.uuid, currentRoot.getTreeUuid()).hardDelete();
}
ret.value = true;
}
}.execute();
return ret.value;
}
private List<VolumeSnapshotBackupStorageDeletionMsg> makeVolumeSnapshotBackupStorageDeletionMsg(List<String> bsUuids) {
List<VolumeSnapshotBackupStorageDeletionMsg> msgs = new ArrayList<VolumeSnapshotBackupStorageDeletionMsg>();
List<String> allMyBsUuids = CollectionUtils.transformToList(currentRoot.getBackupStorageRefs(), new Function<String, VolumeSnapshotBackupStorageRefVO>() {
@Override
public String call(VolumeSnapshotBackupStorageRefVO arg) {
return arg.getBackupStorageUuid();
}
});
if (allMyBsUuids.isEmpty()) {
return msgs;
}
if (bsUuids == null || bsUuids.containsAll(allMyBsUuids)) {
// delete me and all my descendants
for (VolumeSnapshotInventory inv : currentLeaf.getDescendants()) {
List<String> buuids = CollectionUtils.transformToList(inv.getBackupStorageRefs(), new Function<String, VolumeSnapshotBackupStorageRefInventory>() {
@Override
public String call(VolumeSnapshotBackupStorageRefInventory arg) {
return arg.getBackupStorageUuid();
}
});
VolumeSnapshotBackupStorageDeletionMsg msg = new VolumeSnapshotBackupStorageDeletionMsg();
msg.setBackupStorageUuids(buuids);
msg.setSnapshotUuid(inv.getUuid());
bus.makeLocalServiceId(msg, VolumeSnapshotConstant.SERVICE_ID);
msgs.add(msg);
}
} else {
// delete me only
VolumeSnapshotBackupStorageDeletionMsg msg = new VolumeSnapshotBackupStorageDeletionMsg();
msg.setSnapshotUuid(currentRoot.getUuid());
msg.setBackupStorageUuids(bsUuids);
bus.makeLocalServiceId(msg, VolumeSnapshotConstant.SERVICE_ID);
msgs.add(msg);
}
return msgs;
}
private void handle(final APIDeleteVolumeSnapshotFromBackupStorageMsg msg) {
thdf.chainSubmit(new ChainTask(msg) {
@Override
public String getSyncSignature() {
return syncSignature;
}
@Override
public void run(final SyncTaskChain chain) {
deleteOnBackupStorage(msg, new NoErrorCompletion(chain) {
@Override
public void done() {
chain.next();
}
});
}
@Override
public String getName() {
return String.format("delete-volume-snapshot-%s-on-backup-storage", currentRoot.getUuid());
}
});
}
private void deleteOnBackupStorage(final APIDeleteVolumeSnapshotFromBackupStorageMsg msg, final NoErrorCompletion completion) {
final APIDeleteVolumeSnapshotFromBackupStorageEvent evt = new APIDeleteVolumeSnapshotFromBackupStorageEvent(msg.getId());
refreshVO();
final ErrorCode err = isOperationAllowed(msg);
if (err != null) {
evt.setError(err);
bus.publish(evt);
completion.done();
return;
}
FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName(String.format("delete-volume-snapshot-%s-from-backup-stroage", currentRoot.getUuid()));
chain.then(new ShareFlow() {
@Override
public void setup() {
flow(new NoRollbackFlow() {
String __name__ = "delete-volume-snapshot-from-backup-storage";
@Override
public void run(final FlowTrigger trigger, Map data) {
List<VolumeSnapshotBackupStorageDeletionMsg> msgs = makeVolumeSnapshotBackupStorageDeletionMsg(msg.getBackupStorageUuids());
bus.send(msgs, 1, new CloudBusListCallBack(trigger) {
@Override
public void run(List<MessageReply> replies) {
//delete would always succeed
trigger.next();
}
});
}
});
done(new FlowDoneHandler(msg, completion) {
@Override
public void handle(Map data) {
if (!cleanup()) {
currentRoot = dbf.reload(currentRoot);
evt.setInventory(getSelfInventory());
}
bus.publish(evt);
completion.done();
}
});
error(new FlowErrorHandler(msg, completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
evt.setError(errCode);
bus.publish(evt);
completion.done();
}
});
}
}).start();
}
private void handle(final APIBackupVolumeSnapshotMsg msg) {
thdf.chainSubmit(new ChainTask(msg) {
@Override
public String getSyncSignature() {
return syncSignature;
}
@Override
public void run(final SyncTaskChain chain) {
backup(msg, new NoErrorCompletion(chain) {
@Override
public void done() {
chain.next();
}
});
}
@Override
public String getName() {
return String.format("backup-volume-snapshot-%s", msg.getUuid());
}
});
}
private void backup(final APIBackupVolumeSnapshotMsg msg, final NoErrorCompletion completion) {
final APIBackupVolumeSnapshotEvent evt = new APIBackupVolumeSnapshotEvent(msg.getId());
refreshVO();
final ErrorCode err = isOperationAllowed(msg);
if (err != null) {
evt.setError(err);
bus.publish(evt);
completion.done();
return;
}
class Info {
VolumeSnapshotInventory snapshot;
String backupStorageUuid;
BackupStorageInventory destBackupStorage;
boolean backupSuccess;
}
final String requiredBsUuid = msg.getBackupStorageUuid();
final List<Info> needBackup = new ArrayList<Info>();
currentLeaf.walkUp(new Function<Boolean, VolumeSnapshotInventory>() {
@Override
public Boolean call(VolumeSnapshotInventory arg) {
Info info = new Info();
info.snapshot = arg;
if (arg.getUuid().equals(currentRoot.getUuid()) && requiredBsUuid != null) {
info.backupStorageUuid = requiredBsUuid;
needBackup.add(info);
return false;
}
if (arg.getBackupStorageRefs().isEmpty()) {
needBackup.add(info);
}
return false;
}
});
if (needBackup.isEmpty()) {
completion.done();
return;
}
FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName(String.format("backup-volume-snapshot-%s", currentRoot.getUuid()));
chain.then(new ShareFlow() {
@Override
public void setup() {
flow(new Flow() {
String __name__ = "allocate-backup-storage";
private void allocateBackupStorage(final Iterator<Info> it, final FlowTrigger trigger) {
if (!it.hasNext()) {
trigger.next();
return;
}
final Info info = it.next();
AllocateBackupStorageMsg amsg = new AllocateBackupStorageMsg();
amsg.setBackupStorageUuid(info.backupStorageUuid);
amsg.setSize(info.snapshot.getSize());
bus.makeLocalServiceId(amsg, BackupStorageConstant.SERVICE_ID);
bus.send(amsg, new CloudBusCallBack(trigger) {
@Override
public void run(MessageReply reply) {
if (reply.isSuccess()) {
AllocateBackupStorageReply re = reply.castReply();
info.destBackupStorage = re.getInventory();
allocateBackupStorage(it, trigger);
} else {
trigger.fail(reply.getError());
}
}
});
}
@Override
public void run(FlowTrigger trigger, Map data) {
allocateBackupStorage(needBackup.iterator(), trigger);
}
@Override
public void rollback(FlowRollback trigger, Map data) {
for (Info info : needBackup) {
if (info.destBackupStorage != null) {
ReturnBackupStorageMsg rmsg = new ReturnBackupStorageMsg();
rmsg.setBackupStorageUuid(info.destBackupStorage.getUuid());
rmsg.setSize(info.snapshot.getSize());
bus.makeTargetServiceIdByResourceUuid(rmsg, BackupStorageConstant.SERVICE_ID, info.destBackupStorage.getUuid());
bus.send(rmsg);
}
}
trigger.rollback();
}
});
flow(new Flow() {
String __name__ = "backing-up";
@Override
public void run(final FlowTrigger trigger, Map data) {
List<BackupVolumeSnapshotMsg> bmsgs = CollectionUtils.transformToList(needBackup, new Function<BackupVolumeSnapshotMsg, Info>() {
@Override
public BackupVolumeSnapshotMsg call(Info arg) {
BackupVolumeSnapshotMsg bmsg = new BackupVolumeSnapshotMsg();
bmsg.setBackupStorage(arg.destBackupStorage);
bmsg.setSnapshotUuid(arg.snapshot.getUuid());
bus.makeTargetServiceIdByResourceUuid(bmsg, VolumeSnapshotConstant.SERVICE_ID, arg.snapshot.getUuid());
return bmsg;
}
});
bus.send(bmsgs, VolumeSnapshotGlobalConfig.SNAPSHOT_BACKUP_PARALLELISM_DEGREE.value(Integer.class), new CloudBusListCallBack(trigger) {
@Override
public void run(List<MessageReply> replies) {
ErrorCode err = null;
for (MessageReply r : replies) {
if (r.isSuccess()) {
Info info = needBackup.get(replies.indexOf(r));
info.backupSuccess = true;
logger.debug(String.format("successfully backed up volume snapshot[uuid:%s] on backup storage[uuid:%s]",
info.snapshot.getUuid(), info.destBackupStorage.getUuid()));
} else {
err = r.getError();
}
}
if (err != null) {
trigger.fail(err);
} else {
trigger.next();
}
}
});
}
@Override
public void rollback(final FlowRollback trigger, Map data) {
List<VolumeSnapshotBackupStorageDeletionMsg> dmsgs = CollectionUtils.transformToList(needBackup, new Function<VolumeSnapshotBackupStorageDeletionMsg, Info>() {
@Override
public VolumeSnapshotBackupStorageDeletionMsg call(Info arg) {
if (arg.backupSuccess) {
VolumeSnapshotBackupStorageDeletionMsg dmsg = new VolumeSnapshotBackupStorageDeletionMsg();
dmsg.setSnapshotUuid(arg.snapshot.getUuid());
dmsg.setBackupStorageUuids(Arrays.asList(arg.destBackupStorage.getUuid()));
bus.makeTargetServiceIdByResourceUuid(dmsg, VolumeSnapshotConstant.SERVICE_ID, arg.snapshot.getUuid());
return dmsg;
}
return null;
}
});
if (dmsgs.isEmpty()) {
trigger.rollback();
return;
}
bus.send(dmsgs, VolumeSnapshotGlobalConfig.SNAPSHOT_BACKUP_PARALLELISM_DEGREE.value(Integer.class), new CloudBusListCallBack(trigger) {
@Override
public void run(List<MessageReply> replies) {
trigger.rollback();
}
});
}
});
done(new FlowDoneHandler(msg, completion) {
@Override
public void handle(Map data) {
refreshVO();
evt.setInventory(getSelfInventory());
bus.publish(evt);
completion.done();
}
});
error(new FlowErrorHandler(msg, completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
evt.setError(errCode);
bus.publish(evt);
completion.done();
}
});
}
}).start();
}
private void handle(final APIRevertVolumeFromSnapshotMsg msg) {
thdf.chainSubmit(new ChainTask(msg) {
@Override
public String getSyncSignature() {
return syncSignature;
}
@Override
public void run(final SyncTaskChain chain) {
revert(msg, new NoErrorCompletion(chain) {
@Override
public void done() {
chain.next();
}
});
}
@Override
public String getName() {
return String.format("revert-volume-%s-from-snapshot-%s", currentRoot.getVolumeUuid(), currentRoot.getUuid());
}
});
}
private void revert(final APIRevertVolumeFromSnapshotMsg msg, final NoErrorCompletion completion) {
final APIRevertVolumeFromSnapshotEvent evt = new APIRevertVolumeFromSnapshotEvent(msg.getId());
refreshVO();
final ErrorCode err = isOperationAllowed(msg);
if (err != null) {
evt.setError(err);
bus.publish(evt);
completion.done();
return;
}
FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName(String.format("revert-volume-%s-from-snapshot-%s", currentRoot.getVolumeUuid(), currentRoot.getUuid()));
chain.then(new ShareFlow() {
String newVolumeInstallPath;
VolumeVO volume = dbf.findByUuid(currentRoot.getVolumeUuid(), VolumeVO.class);
VolumeInventory volumeInventory = VolumeInventory.valueOf(volume);
@Override
public void setup() {
flow(new NoRollbackFlow() {
String __name__ = "revert-volume-from-volume-snapshot-on-primary-storage";
@Override
public void run(final FlowTrigger trigger, Map data) {
RevertVolumeFromSnapshotOnPrimaryStorageMsg rmsg = new RevertVolumeFromSnapshotOnPrimaryStorageMsg();
rmsg.setSnapshot(getSelfInventory());
rmsg.setVolume(volumeInventory);
if (rmsg.getVolume().getVmInstanceUuid() != null) {
SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class);
q.select(VmInstanceVO_.state);
q.add(VmInstanceVO_.uuid, Op.EQ, rmsg.getVolume().getVmInstanceUuid());
VmInstanceState state = q.findValue();
if (state != VmInstanceState.Stopped) {
throw new OperationFailureException(operr("unable to reset volume[uuid:%s] to snapshot[uuid:%s]," +
" the vm[uuid:%s] volume attached to is not in Stopped state," +
" current state is %s",
rmsg.getVolume().getUuid(),
rmsg.getSnapshot().getUuid(),
rmsg.getVolume().getVmInstanceUuid(), state));
}
}
bus.makeTargetServiceIdByResourceUuid(rmsg, PrimaryStorageConstant.SERVICE_ID, volumeInventory.getPrimaryStorageUuid());
bus.send(rmsg, new CloudBusCallBack(trigger) {
@Override
public void run(MessageReply reply) {
if (reply.isSuccess()) {
RevertVolumeFromSnapshotOnPrimaryStorageReply re = (RevertVolumeFromSnapshotOnPrimaryStorageReply) reply;
newVolumeInstallPath = re.getNewVolumeInstallPath();
trigger.next();
} else {
trigger.fail(reply.getError());
}
}
});
}
});
done(new FlowDoneHandler(msg, completion) {
@Transactional
private void updateLatest() {
String sql = "update VolumeSnapshotVO s" +
" set s.latest = false" +
" where s.latest = true" +
" and s.treeUuid = :treeUuid";
Query q = dbf.getEntityManager().createQuery(sql);
q.setParameter("treeUuid", currentRoot.getTreeUuid());
q.executeUpdate();
currentRoot.setLatest(true);
dbf.getEntityManager().merge(currentRoot);
sql = "update VolumeSnapshotTreeVO tree" +
" set tree.current = false" +
" where tree.current = true" +
" and tree.volumeUuid = :volUuid";
q = dbf.getEntityManager().createQuery(sql);
q.setParameter("volUuid", currentRoot.getVolumeUuid());
q.executeUpdate();
sql = "update VolumeSnapshotTreeVO tree" +
" set tree.current = true" +
" where tree.uuid = :treeUuid";
q = dbf.getEntityManager().createQuery(sql);
q.setParameter("treeUuid", currentRoot.getTreeUuid());
q.executeUpdate();
}
@Override
public void handle(Map data) {
volume.setInstallPath(newVolumeInstallPath);
dbf.update(volume);
updateLatest();
bus.publish(evt);
completion.done();
}
});
error(new FlowErrorHandler(msg, completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
logger.warn(String.format("failed to restore volume[uuid:%s] to snapshot[uuid:%s, name:%s], %s",
volumeInventory.getUuid(), currentRoot.getUuid(), currentRoot.getName(), errCode));
evt.setError(errCode);
bus.publish(evt);
completion.done();
}
});
}
}).start();
}
private void handle(final APIDeleteVolumeSnapshotMsg msg) {
final APIDeleteVolumeSnapshotEvent evt = new APIDeleteVolumeSnapshotEvent(msg.getId());
final String issuer = VolumeSnapshotVO.class.getSimpleName();
final List<VolumeSnapshotInventory> ctx = Arrays.asList(getSelfInventory());
FlowChain chain = FlowChainBuilder.newSimpleFlowChain();
chain.setName(String.format("delete-snapshot-%s", msg.getUuid()));
if (msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Permissive) {
chain.then(new NoRollbackFlow() {
@Override
public void run(final FlowTrigger trigger, Map data) {
casf.asyncCascade(CascadeConstant.DELETION_CHECK_CODE, issuer, ctx, new Completion(trigger) {
@Override
public void success() {
trigger.next();
}
@Override
public void fail(ErrorCode errorCode) {
trigger.fail(errorCode);
}
});
}
}).then(new NoRollbackFlow() {
@Override
public void run(final FlowTrigger trigger, Map data) {
casf.asyncCascade(CascadeConstant.DELETION_DELETE_CODE, issuer, ctx, new Completion(trigger) {
@Override
public void success() {
trigger.next();
}
@Override
public void fail(ErrorCode errorCode) {
trigger.fail(errorCode);
}
});
}
});
} else {
chain.then(new NoRollbackFlow() {
@Override
public void run(final FlowTrigger trigger, Map data) {
casf.asyncCascade(CascadeConstant.DELETION_FORCE_DELETE_CODE, issuer, ctx, new Completion(trigger) {
@Override
public void success() {
trigger.next();
}
@Override
public void fail(ErrorCode errorCode) {
trigger.fail(errorCode);
}
});
}
});
}
chain.done(new FlowDoneHandler(msg) {
@Override
public void handle(Map data) {
casf.asyncCascadeFull(CascadeConstant.DELETION_CLEANUP_CODE, issuer, ctx, new NopeCompletion());
bus.publish(evt);
}
}).error(new FlowErrorHandler(msg) {
@Override
public void handle(ErrorCode errCode, Map data) {
evt.setError(errf.instantiateErrorCode(SysErrors.DELETE_RESOURCE_ERROR, errCode));
bus.publish(evt);
}
}).start();
}
}