package org.zstack.storage.snapshot;
import org.springframework.beans.factory.annotation.Autowired;
import org.zstack.core.cascade.AbstractAsyncCascadeExtension;
import org.zstack.core.cascade.CascadeAction;
import org.zstack.core.cascade.CascadeConstant;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.CloudBusListCallBack;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.header.core.Completion;
import org.zstack.header.message.MessageReply;
import org.zstack.header.message.NeedReplyMessage;
import org.zstack.header.storage.snapshot.*;
import org.zstack.header.volume.VolumeConstant;
import org.zstack.header.volume.VolumeDeletionPolicyManager.VolumeDeletionPolicy;
import org.zstack.header.volume.VolumeDeletionStruct;
import org.zstack.header.volume.VolumeVO;
import org.zstack.utils.CollectionUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.function.Function;
import org.zstack.utils.logging.CLogger;
import javax.persistence.Tuple;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
*/
public class VolumeSnapshotCascadeExtension extends AbstractAsyncCascadeExtension {
private static final CLogger logger = Utils.getLogger(VolumeSnapshotCascadeExtension.class);
@Autowired
private DatabaseFacade dbf;
@Autowired
private CloudBus bus;
private static final String NAME = VolumeSnapshotVO.class.getSimpleName();
@Override
public void asyncCascade(CascadeAction action, Completion completion) {
if (action.isActionCode(CascadeConstant.DELETION_CHECK_CODE)) {
handleDeletionCheck(action, completion);
} else if (action.isActionCode(CascadeConstant.DELETION_DELETE_CODE, CascadeConstant.DELETION_FORCE_DELETE_CODE)) {
handleDeletion(action, completion);
} else if (action.isActionCode(CascadeConstant.DELETION_CLEANUP_CODE)) {
handleDeletionCleanup(action, completion);
} else {
completion.success();
}
}
private void handleDeletionCleanup(CascadeAction action, Completion completion) {
dbf.eoCleanup(VolumeSnapshotVO.class);
completion.success();
}
private NeedReplyMessage makeMsg(final String suuid, boolean volumeDeletion) {
SimpleQuery<VolumeSnapshotVO> sq = dbf.createQuery(VolumeSnapshotVO.class);
sq.select(VolumeSnapshotVO_.volumeUuid, VolumeSnapshotVO_.treeUuid);
sq.add(VolumeSnapshotVO_.uuid, Op.EQ, suuid);
Tuple t = sq.findTuple();
String volumeUuid = t.get(0, String.class);
String treeUuid = t.get(1, String.class);
VolumeSnapshotDeletionMsg msg = new VolumeSnapshotDeletionMsg();
msg.setSnapshotUuid(suuid);
msg.setTreeUuid(treeUuid);
msg.setVolumeUuid(volumeUuid);
msg.setVolumeDeletion(volumeDeletion);
String resourceUuid = volumeUuid != null ? volumeUuid : treeUuid;
bus.makeTargetServiceIdByResourceUuid(msg, VolumeSnapshotConstant.SERVICE_ID, resourceUuid);
if (volumeUuid == null) {
return msg;
}
// To delete a volume snapshot, we need first to be synchronized in the volume queue.
VolumeSnapshotOverlayMsg omsg = new VolumeSnapshotOverlayMsg();
omsg.setMessage(msg);
omsg.setVolumeUuid(volumeUuid);
bus.makeTargetServiceIdByResourceUuid(omsg, VolumeConstant.SERVICE_ID, volumeUuid);
return omsg;
}
private void handleDeletion(final CascadeAction action, final Completion completion) {
final List<NeedReplyMessage> msgs = new ArrayList<>();
if (VolumeVO.class.getSimpleName().equals(action.getParentIssuer())) {
List<VolumeDeletionStruct> vols = action.getParentIssuerContext();
for (VolumeDeletionStruct vol : vols) {
msgs.addAll(handleVolumeDeletion(vol));
}
} else if (VolumeSnapshotVO.class.getSimpleName().equals(action.getParentIssuer())) {
List<VolumeSnapshotInventory> sinvs = action.getParentIssuerContext();
for (VolumeSnapshotInventory sinv : sinvs) {
msgs.add(handleSnapshotDeletion(sinv));
}
}
if (msgs.isEmpty()) {
completion.success();
return;
}
bus.send(msgs, new CloudBusListCallBack(completion) {
@Override
public void run(List<MessageReply> replies) {
if (!action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE)) {
for (MessageReply r : replies) {
if (!r.isSuccess()) {
completion.fail(r.getError());
return;
}
}
}
completion.success();
}
});
}
private NeedReplyMessage handleSnapshotDeletion(VolumeSnapshotInventory sinv) {
return makeMsg(sinv.getUuid(), false);
}
private List<NeedReplyMessage> handleVolumeDeletion(VolumeDeletionStruct vol) {
if (!VolumeDeletionPolicy.Direct.toString().equals(vol.getDeletionPolicy())) {
return new ArrayList<>();
}
List<NeedReplyMessage> ret = new ArrayList<>();
SimpleQuery<VolumeSnapshotTreeVO> cq = dbf.createQuery(VolumeSnapshotTreeVO.class);
cq.select(VolumeSnapshotTreeVO_.uuid);
cq.add(VolumeSnapshotTreeVO_.volumeUuid, Op.EQ, vol.getInventory().getUuid());
List<String> cuuids = cq.listValue();
for (String cuuid : cuuids) {
// deleting full snapshot of chain will cause whole chain to be deleted
SimpleQuery<VolumeSnapshotVO> q = dbf.createQuery(VolumeSnapshotVO.class);
q.select(VolumeSnapshotVO_.uuid);
q.add(VolumeSnapshotVO_.treeUuid, Op.EQ, cuuid);
q.add(VolumeSnapshotVO_.parentUuid, Op.NULL);
q.add(VolumeSnapshotVO_.type, Op.EQ, VolumeSnapshotConstant.HYPERVISOR_SNAPSHOT_TYPE.toString());
String suuid = q.findValue();
if (suuid == null) {
// this is a storage snapshot, don't delete it on primary storage
continue;
}
ret.add(makeMsg(suuid, true));
}
return ret;
}
private void handleDeletionCheck(CascadeAction action, Completion completion) {
completion.success();
}
@Override
public List<String> getEdgeNames() {
return Arrays.asList(VolumeVO.class.getSimpleName());
}
@Override
public String getCascadeResourceName() {
return NAME;
}
private List<VolumeSnapshotInventory> fromAction(CascadeAction action) {
List<VolumeSnapshotInventory> ret = null;
if (VolumeVO.class.getSimpleName().equals(action.getParentIssuer())) {
List<VolumeDeletionStruct> vols = action.getParentIssuerContext();
List<String> volUuids = CollectionUtils.transformToList(vols, new Function<String, VolumeDeletionStruct>() {
@Override
public String call(VolumeDeletionStruct arg) {
return arg.getInventory().getUuid();
}
});
if (volUuids.isEmpty()) {
return null;
}
SimpleQuery<VolumeSnapshotVO> q = dbf.createQuery(VolumeSnapshotVO.class);
q.add(VolumeSnapshotVO_.volumeUuid, Op.IN, volUuids);
List<VolumeSnapshotVO> vos = q.list();
if (!vos.isEmpty()) {
ret = VolumeSnapshotInventory.valueOf(vos);
}
} else if (NAME.equals(action.getParentIssuer())) {
ret = action.getParentIssuerContext();
}
return ret;
}
@Override
public CascadeAction createActionForChildResource(CascadeAction action) {
if (CascadeConstant.DELETION_CODES.contains(action.getActionCode())) {
List<VolumeSnapshotInventory> invs = fromAction(action);
if (invs != null) {
return action.copy().setParentIssuer(NAME).setParentIssuerContext(invs);
}
}
return null;
}
}