package org.zstack.storage.volume;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.Platform;
import org.zstack.core.cloudbus.*;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.config.GlobalConfig;
import org.zstack.core.config.GlobalConfigUpdateExtensionPoint;
import org.zstack.core.db.*;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.core.scheduler.SchedulerFacade;
import org.zstack.core.thread.CancelablePeriodicTask;
import org.zstack.core.thread.ThreadFacade;
import org.zstack.core.workflow.FlowChainBuilder;
import org.zstack.core.workflow.ShareFlow;
import org.zstack.header.AbstractService;
import org.zstack.header.configuration.DiskOfferingVO;
import org.zstack.header.core.scheduler.SchedulerVO;
import org.zstack.header.core.scheduler.SchedulerVO_;
import org.zstack.header.core.workflow.*;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.OperationFailureException;
import org.zstack.header.identity.AccountResourceRefInventory;
import org.zstack.header.identity.ResourceOwnerAfterChangeExtensionPoint;
import org.zstack.header.image.*;
import org.zstack.header.managementnode.ManagementNodeReadyExtensionPoint;
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.Message;
import org.zstack.header.message.MessageReply;
import org.zstack.header.search.SearchOp;
import org.zstack.header.storage.backup.BackupStorageState;
import org.zstack.header.storage.backup.BackupStorageStatus;
import org.zstack.header.storage.primary.*;
import org.zstack.header.storage.snapshot.*;
import org.zstack.header.vm.VmInstanceVO;
import org.zstack.header.volume.*;
import org.zstack.header.volume.APIGetVolumeFormatReply.VolumeFormatReplyStruct;
import org.zstack.header.volume.VolumeDeletionPolicyManager.VolumeDeletionPolicy;
import org.zstack.identity.AccountManager;
import org.zstack.search.SearchQuery;
import org.zstack.tag.TagManager;
import org.zstack.utils.CollectionUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.function.Function;
import org.zstack.utils.gson.JSONObjectUtil;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.path.PathUtil;
import static org.zstack.core.Platform.operr;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class VolumeManagerImpl extends AbstractService implements VolumeManager, ManagementNodeReadyExtensionPoint,
VolumeDeletionExtensionPoint, VolumeBeforeExpungeExtensionPoint, RecoverDataVolumeExtensionPoint,
ResourceOwnerAfterChangeExtensionPoint {
private static final CLogger logger = Utils.getLogger(VolumeManagerImpl.class);
@Autowired
private CloudBus bus;
@Autowired
private DatabaseFacade dbf;
@Autowired
private DbEntityLister dl;
@Autowired
private AccountManager acntMgr;
@Autowired
private ErrorFacade errf;
@Autowired
private TagManager tagMgr;
@Autowired
private ThreadFacade thdf;
@Autowired
private ResourceDestinationMaker destMaker;
@Autowired
private VolumeDeletionPolicyManager deletionPolicyMgr;
@Autowired
private EventFacade evtf;
@Autowired
private SchedulerFacade schedulerFacade;
@Autowired
private PluginRegistry pluginRgty;
private Future<Void> volumeExpungeTask;
private void passThrough(VolumeMessage vmsg) {
Message msg = (Message) vmsg;
VolumeVO vo = dbf.findByUuid(vmsg.getVolumeUuid(), VolumeVO.class);
if (vo == null) {
bus.replyErrorByMessageType(msg, String.format("Cannot find volume[uuid:%s], it may have been deleted", vmsg.getVolumeUuid()));
return;
}
List<VolumeFactory> l = pluginRgty.getExtensionList(VolumeFactory.class);
if (!l.isEmpty()) {
VolumeBase volumeBase = l.get(0).makeVolumeBase(vo);
volumeBase.handleMessage(msg);
} else {
VolumeBase volumeBase = new VolumeBase(vo);
volumeBase.handleMessage(msg);
}
}
@Override
@MessageSafe
public void handleMessage(Message msg) {
if (msg instanceof VolumeMessage) {
passThrough((VolumeMessage) msg);
} else if (msg instanceof APIMessage) {
handleApiMessage((APIMessage) msg);
} else {
handleLocalMessage(msg);
}
}
private void handleLocalMessage(Message msg) {
if (msg instanceof CreateVolumeMsg) {
handle((CreateVolumeMsg) msg);
} else if (msg instanceof VolumeReportPrimaryStorageCapacityUsageMsg) {
handle((VolumeReportPrimaryStorageCapacityUsageMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
}
@Transactional(readOnly = true)
private void handle(VolumeReportPrimaryStorageCapacityUsageMsg msg) {
String sql = "select sum(vol.size) from VolumeVO vol where vol.primaryStorageUuid = :prUuid and vol.status = :status";
TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class);
q.setParameter("prUuid", msg.getPrimaryStorageUuid());
q.setParameter("status", VolumeStatus.Ready);
Long size = q.getSingleResult();
VolumeReportPrimaryStorageCapacityUsageReply reply = new VolumeReportPrimaryStorageCapacityUsageReply();
reply.setUsedCapacity(size == null ? 0 : size);
bus.reply(msg, reply);
}
private VolumeInventory createVolume(CreateVolumeMsg msg) {
VolumeVO vo = new VolumeVO();
vo.setUuid(Platform.getUuid());
vo.setRootImageUuid(msg.getRootImageUuid());
vo.setDescription(msg.getDescription());
vo.setName(msg.getName());
vo.setPrimaryStorageUuid(msg.getPrimaryStorageUuid());
vo.setSize(msg.getSize());
vo.setVmInstanceUuid(msg.getVmInstanceUuid());
vo.setFormat(msg.getFormat());
vo.setStatus(VolumeStatus.NotInstantiated);
vo.setType(VolumeType.valueOf(msg.getVolumeType()));
vo.setDiskOfferingUuid(msg.getDiskOfferingUuid());
if (vo.getType() == VolumeType.Root) {
vo.setDeviceId(0);
}
VolumeVO finalVo = vo;
vo = new SQLBatchWithReturn<VolumeVO>() {
@Override
protected VolumeVO scripts() {
dbf.getEntityManager().persist(finalVo);
dbf.getEntityManager().flush();
dbf.getEntityManager().refresh(finalVo);
acntMgr.createAccountResourceRef(msg.getAccountUuid(), finalVo.getUuid(), VolumeVO.class);
return finalVo;
}
}.execute();
new FireVolumeCanonicalEvent().fireVolumeStatusChangedEvent(null, VolumeInventory.valueOf(vo));
VolumeInventory inv = VolumeInventory.valueOf(vo);
logger.debug(String.format("successfully created volume[uuid:%s, name:%s, type:%s, vm uuid:%s",
inv.getUuid(), inv.getName(), inv.getType(), inv.getVmInstanceUuid()));
return inv;
}
private void handle(CreateVolumeMsg msg) {
VolumeInventory inv = createVolume(msg);
CreateVolumeReply reply = new CreateVolumeReply();
reply.setInventory(inv);
bus.reply(msg, reply);
}
private void handle(APICreateDataVolumeFromVolumeSnapshotMsg msg) {
final APICreateDataVolumeFromVolumeSnapshotEvent evt = new APICreateDataVolumeFromVolumeSnapshotEvent(msg.getId());
final VolumeVO vo = new VolumeVO();
if (msg.getResourceUuid() != null) {
vo.setUuid(msg.getResourceUuid());
} else {
vo.setUuid(Platform.getUuid());
}
vo.setName(msg.getName());
vo.setDescription(msg.getDescription());
vo.setState(VolumeState.Enabled);
vo.setStatus(VolumeStatus.Creating);
vo.setType(VolumeType.Data);
vo.setSize(0);
VolumeVO vvo = new SQLBatchWithReturn<VolumeVO>() {
@Override
protected VolumeVO scripts() {
persist(vo);
reload(vo);
acntMgr.createAccountResourceRef(msg.getSession().getAccountUuid(), vo.getUuid(), VolumeVO.class);
tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), VolumeVO.class.getSimpleName());
return vo;
}
}.execute();
new FireVolumeCanonicalEvent().fireVolumeStatusChangedEvent(null, VolumeInventory.valueOf(vvo));
SimpleQuery<VolumeSnapshotVO> sq = dbf.createQuery(VolumeSnapshotVO.class);
sq.select(VolumeSnapshotVO_.volumeUuid, VolumeSnapshotVO_.treeUuid);
sq.add(VolumeSnapshotVO_.uuid, Op.EQ, msg.getVolumeSnapshotUuid());
Tuple t = sq.findTuple();
String volumeUuid = t.get(0, String.class);
String treeUuid = t.get(1, String.class);
CreateDataVolumeFromVolumeSnapshotMsg cmsg = new CreateDataVolumeFromVolumeSnapshotMsg();
cmsg.setVolumeUuid(volumeUuid);
cmsg.setTreeUuid(treeUuid);
cmsg.setUuid(msg.getVolumeSnapshotUuid());
cmsg.setVolume(VolumeInventory.valueOf(vo));
cmsg.setPrimaryStorageUuid(msg.getPrimaryStorageUuid());
String resourceUuid = volumeUuid != null ? volumeUuid : treeUuid;
bus.makeTargetServiceIdByResourceUuid(cmsg, VolumeSnapshotConstant.SERVICE_ID, resourceUuid);
bus.send(cmsg, new CloudBusCallBack(msg) {
@Override
public void run(MessageReply reply) {
if (reply.isSuccess()) {
CreateDataVolumeFromVolumeSnapshotReply cr = reply.castReply();
VolumeInventory inv = cr.getInventory();
vo.setSize(inv.getSize());
vo.setActualSize(cr.getActualSize());
vo.setInstallPath(inv.getInstallPath());
vo.setStatus(VolumeStatus.Ready);
vo.setPrimaryStorageUuid(inv.getPrimaryStorageUuid());
vo.setFormat(inv.getFormat());
VolumeVO vvo = dbf.updateAndRefresh(vo);
new FireVolumeCanonicalEvent().fireVolumeStatusChangedEvent(VolumeStatus.Creating, VolumeInventory.valueOf(vvo));
evt.setInventory(VolumeInventory.valueOf(vvo));
} else {
evt.setError(reply.getError());
}
bus.publish(evt);
}
});
}
private void handleApiMessage(APIMessage msg) {
if (msg instanceof APICreateDataVolumeMsg) {
handle((APICreateDataVolumeMsg) msg);
} else if (msg instanceof APIListVolumeMsg) {
handle((APIListVolumeMsg) msg);
} else if (msg instanceof APISearchVolumeMsg) {
handle((APISearchVolumeMsg) msg);
} else if (msg instanceof APIGetVolumeMsg) {
handle((APIGetVolumeMsg) msg);
} else if (msg instanceof APICreateDataVolumeFromVolumeSnapshotMsg) {
handle((APICreateDataVolumeFromVolumeSnapshotMsg) msg);
} else if (msg instanceof APICreateDataVolumeFromVolumeTemplateMsg) {
handle((APICreateDataVolumeFromVolumeTemplateMsg) msg);
} else if (msg instanceof APIGetVolumeFormatMsg) {
handle((APIGetVolumeFormatMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
}
private void handle(APIGetVolumeFormatMsg msg) {
List<VolumeFormatReplyStruct> structs = new ArrayList<VolumeFormatReplyStruct>();
for (VolumeFormat format : VolumeFormat.getAllFormats()) {
structs.add(new VolumeFormatReplyStruct(format));
}
APIGetVolumeFormatReply reply = new APIGetVolumeFormatReply();
reply.setFormats(structs);
bus.reply(msg, reply);
}
private void handle(final APICreateDataVolumeFromVolumeTemplateMsg msg) {
final APICreateDataVolumeFromVolumeTemplateEvent evt = new APICreateDataVolumeFromVolumeTemplateEvent(msg.getId());
final ImageVO template = dbf.findByUuid(msg.getImageUuid(), ImageVO.class);
final VolumeVO vol = new VolumeVO();
vol.setUuid(msg.getResourceUuid() == null ? Platform.getUuid() : msg.getResourceUuid());
vol.setName(msg.getName());
vol.setDescription(msg.getDescription());
vol.setFormat(template.getFormat());
vol.setSize(template.getSize());
vol.setActualSize(template.getActualSize());
vol.setRootImageUuid(template.getUuid());
vol.setStatus(VolumeStatus.Creating);
vol.setState(VolumeState.Enabled);
vol.setType(VolumeType.Data);
vol.setPrimaryStorageUuid(msg.getPrimaryStorageUuid());
VolumeVO vvo = new SQLBatchWithReturn<VolumeVO>() {
@Override
protected VolumeVO scripts() {
persist(vol);
reload(vol);
acntMgr.createAccountResourceRef(msg.getSession().getAccountUuid(), vol.getUuid(), VolumeVO.class);
tagMgr.createTagsFromAPICreateMessage(msg, vol.getUuid(), VolumeVO.class.getSimpleName());
return vol;
}
}.execute();
new FireVolumeCanonicalEvent().fireVolumeStatusChangedEvent(null, VolumeInventory.valueOf(vvo));
FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName(String.format("create-data-volume-from-template-%s", template.getUuid()));
chain.then(new ShareFlow() {
ImageBackupStorageRefVO targetBackupStorageRef;
PrimaryStorageInventory targetPrimaryStorage;
String primaryStorageInstallPath;
String volumeFormat;
@Override
public void setup() {
flow(new NoRollbackFlow() {
String __name__ = "select-backup-storage";
@Override
@Transactional(readOnly = true)
public void run(FlowTrigger trigger, Map data) {
List<String> bsUuids = CollectionUtils.transformToList(template.getBackupStorageRefs(), new Function<String, ImageBackupStorageRefVO>() {
@Override
public String call(ImageBackupStorageRefVO arg) {
return ImageStatus.Deleted.equals(arg.getStatus()) ? null : arg.getBackupStorageUuid();
}
});
if (bsUuids.isEmpty()) {
throw new OperationFailureException(operr("the image[uuid:%s, name:%s] has been deleted on all backup storage", template.getUuid(), template.getName()));
}
String sql = "select bs.uuid from BackupStorageVO bs, BackupStorageZoneRefVO zref, PrimaryStorageVO ps where zref.zoneUuid = ps.zoneUuid and bs.status = :bsStatus and bs.state = :bsState and ps.uuid = :psUuid and zref.backupStorageUuid = bs.uuid and bs.uuid in (:bsUuids)";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("psUuid", msg.getPrimaryStorageUuid());
q.setParameter("bsStatus", BackupStorageStatus.Connected);
q.setParameter("bsState", BackupStorageState.Enabled);
q.setParameter("bsUuids", bsUuids);
bsUuids = q.getResultList();
if (bsUuids.isEmpty()) {
trigger.fail(operr("cannot find a backup storage on which the image[uuid:%s] is that satisfies all conditions of: 1. has state Enabled 2. has status Connected. 3 has attached to zone in which primary storage[uuid:%s] is",
template.getUuid(), msg.getPrimaryStorageUuid()));
return;
}
final String bsUuid = bsUuids.get(0);
targetBackupStorageRef = CollectionUtils.find(template.getBackupStorageRefs(), new Function<ImageBackupStorageRefVO, ImageBackupStorageRefVO>() {
@Override
public ImageBackupStorageRefVO call(ImageBackupStorageRefVO arg) {
return arg.getBackupStorageUuid().equals(bsUuid) ? arg : null;
}
});
trigger.next();
}
});
flow(new Flow() {
String __name__ = "allocate-primary-storage";
@Override
public void run(final FlowTrigger trigger, Map data) {
AllocatePrimaryStorageMsg amsg = new AllocatePrimaryStorageMsg();
amsg.setSize(template.getSize());
amsg.setPurpose(PrimaryStorageAllocationPurpose.DownloadImage.toString());
amsg.setRequiredPrimaryStorageUuid(msg.getPrimaryStorageUuid());
amsg.setRequiredHostUuid(msg.getHostUuid());
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 {
targetPrimaryStorage = ((AllocatePrimaryStorageReply) reply).getPrimaryStorageInventory();
trigger.next();
}
}
});
}
@Override
public void rollback(FlowRollback trigger, Map data) {
if (targetPrimaryStorage != null) {
IncreasePrimaryStorageCapacityMsg imsg = new IncreasePrimaryStorageCapacityMsg();
imsg.setDiskSize(template.getSize());
imsg.setPrimaryStorageUuid(targetPrimaryStorage.getUuid());
bus.makeTargetServiceIdByResourceUuid(imsg, PrimaryStorageConstant.SERVICE_ID, targetPrimaryStorage.getUuid());
bus.send(imsg);
}
trigger.rollback();
}
});
flow(new Flow() {
String __name__ = "download-data-volume-template-to-primary-storage";
@Override
public void run(final FlowTrigger trigger, Map data) {
DownloadDataVolumeToPrimaryStorageMsg dmsg = new DownloadDataVolumeToPrimaryStorageMsg();
dmsg.setPrimaryStorageUuid(targetPrimaryStorage.getUuid());
dmsg.setVolumeUuid(vol.getUuid());
dmsg.setBackupStorageRef(ImageBackupStorageRefInventory.valueOf(targetBackupStorageRef));
dmsg.setImage(ImageInventory.valueOf(template));
dmsg.setHostUuid(msg.getHostUuid());
bus.makeTargetServiceIdByResourceUuid(dmsg, PrimaryStorageConstant.SERVICE_ID, targetPrimaryStorage.getUuid());
bus.send(dmsg, new CloudBusCallBack(trigger) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
trigger.fail(reply.getError());
} else {
DownloadDataVolumeToPrimaryStorageReply r = reply.castReply();
primaryStorageInstallPath = r.getInstallPath();
volumeFormat = r.getFormat();
trigger.next();
}
}
});
}
@Override
public void rollback(FlowRollback trigger, Map data) {
if (primaryStorageInstallPath != null) {
DeleteBitsOnPrimaryStorageMsg delMsg = new DeleteBitsOnPrimaryStorageMsg();
delMsg.setInstallPath(PathUtil.parentFolder(primaryStorageInstallPath));
delMsg.setBitsUuid(vol.getUuid());
delMsg.setBitsType(VolumeVO.class.getSimpleName());
delMsg.setFolder(true);
delMsg.setPrimaryStorageUuid(targetPrimaryStorage.getUuid());
delMsg.setHypervisorType(VolumeFormat.getMasterHypervisorTypeByVolumeFormat(vol.getFormat()).toString());
bus.makeTargetServiceIdByResourceUuid(delMsg, PrimaryStorageConstant.SERVICE_ID, targetPrimaryStorage.getUuid());
bus.send(delMsg);
}
trigger.rollback();
}
});
done(new FlowDoneHandler(msg) {
@Override
public void handle(Map data) {
vol.setInstallPath(primaryStorageInstallPath);
vol.setStatus(VolumeStatus.Ready);
if (volumeFormat != null) {
vol.setFormat(volumeFormat);
}
VolumeVO vo = dbf.updateAndRefresh(vol);
new FireVolumeCanonicalEvent().fireVolumeStatusChangedEvent(VolumeStatus.Creating, VolumeInventory.valueOf(vo));
evt.setInventory(VolumeInventory.valueOf(vo));
bus.publish(evt);
}
});
error(new FlowErrorHandler(msg) {
@Override
public void handle(ErrorCode errCode, Map data) {
evt.setError(errCode);
bus.publish(evt);
}
});
}
}).start();
}
private void handle(APIGetVolumeMsg msg) {
SearchQuery<VolumeInventory> q = new SearchQuery(VolumeInventory.class);
q.addAccountAsAnd(msg);
q.add("uuid", SearchOp.AND_EQ, msg.getUuid());
List<VolumeInventory> invs = q.list();
APIGetVolumeReply reply = new APIGetVolumeReply();
if (!invs.isEmpty()) {
reply.setInventory(JSONObjectUtil.toJsonString(invs.get(0)));
}
bus.reply(msg, reply);
}
private void handle(APISearchVolumeMsg msg) {
SearchQuery<VolumeInventory> q = SearchQuery.create(msg, VolumeInventory.class);
q.addAccountAsAnd(msg);
String res = q.listAsString();
APISearchVolumeReply reply = new APISearchVolumeReply();
reply.setContent(res);
bus.reply(msg, reply);
}
private void handle(APIListVolumeMsg msg) {
List<VolumeVO> vos = dl.listByApiMessage(msg, VolumeVO.class);
List<VolumeInventory> invs = VolumeInventory.valueOf(vos);
APIListVolumeReply reply = new APIListVolumeReply();
reply.setInventories(invs);
bus.reply(msg, reply);
}
private void handle(APICreateDataVolumeMsg msg) {
APICreateDataVolumeEvent evt = new APICreateDataVolumeEvent(msg.getId());
DiskOfferingVO dvo = dbf.findByUuid(msg.getDiskOfferingUuid(), DiskOfferingVO.class);
VolumeVO vo = new VolumeVO();
if (msg.getResourceUuid() != null) {
vo.setUuid(msg.getResourceUuid());
} else {
vo.setUuid(Platform.getUuid());
}
vo.setDiskOfferingUuid(dvo.getUuid());
vo.setDescription(msg.getDescription());
vo.setName(msg.getName());
vo.setSize(dvo.getDiskSize());
vo.setActualSize(0L);
vo.setType(VolumeType.Data);
vo.setStatus(VolumeStatus.NotInstantiated);
if (msg.hasSystemTag(VolumeSystemTags.SHAREABLE.getTagFormat())) {
vo.setShareable(true);
}
VolumeVO finalVo1 = vo;
vo = new SQLBatchWithReturn<VolumeVO>() {
@Override
protected VolumeVO scripts() {
dbf.getEntityManager().persist(finalVo1);
dbf.getEntityManager().flush();
dbf.getEntityManager().refresh(finalVo1);
acntMgr.createAccountResourceRef(msg.getSession().getAccountUuid(), finalVo1.getUuid(), VolumeVO.class);
tagMgr.createTagsFromAPICreateMessage(msg, finalVo1.getUuid(), VolumeVO.class.getSimpleName());
return finalVo1;
}
}.execute();
if (msg.getPrimaryStorageUuid() == null) {
new FireVolumeCanonicalEvent().fireVolumeStatusChangedEvent(null, VolumeInventory.valueOf(vo));
VolumeInventory inv = VolumeInventory.valueOf(vo);
evt.setInventory(inv);
logger.debug(String.format("Successfully created data volume[name:%s, uuid:%s, size:%s]", inv.getName(), inv.getUuid(), inv.getSize()));
bus.publish(evt);
return;
}
InstantiateVolumeMsg imsg = new InstantiateVolumeMsg();
imsg.setVolumeUuid(vo.getUuid());
imsg.setPrimaryStorageUuid(msg.getPrimaryStorageUuid());
imsg.setSystemTags(msg.getSystemTags());
imsg.setUserTags(msg.getUserTags());
bus.makeTargetServiceIdByResourceUuid(imsg, VolumeConstant.SERVICE_ID, vo.getUuid());
VolumeVO finalVo = vo;
bus.send(imsg, new CloudBusCallBack(msg) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
dbf.remove(finalVo);
evt.setError(reply.getError());
} else {
evt.setInventory(((InstantiateVolumeReply) reply).getVolume());
}
bus.publish(evt);
}
});
}
@Override
public String getId() {
return bus.makeLocalServiceId(VolumeConstant.SERVICE_ID);
}
@Override
public boolean start() {
VolumeGlobalConfig.VOLUME_EXPUNGE_INTERVAL.installUpdateExtension(new GlobalConfigUpdateExtensionPoint() {
@Override
public void updateGlobalConfig(GlobalConfig oldConfig, GlobalConfig newConfig) {
startExpungeTask();
}
});
VolumeGlobalConfig.VOLUME_EXPUNGE_PERIOD.installUpdateExtension(new GlobalConfigUpdateExtensionPoint() {
@Override
public void updateGlobalConfig(GlobalConfig oldConfig, GlobalConfig newConfig) {
startExpungeTask();
}
});
VolumeGlobalConfig.VOLUME_DELETION_POLICY.installUpdateExtension(new GlobalConfigUpdateExtensionPoint() {
@Override
public void updateGlobalConfig(GlobalConfig oldConfig, GlobalConfig newConfig) {
startExpungeTask();
}
});
pluginRgty.saveExtensionAsMap(InstantiateDataVolumeOnCreationExtensionPoint.class, new Function<Object, InstantiateDataVolumeOnCreationExtensionPoint>() {
@Override
public Object call(InstantiateDataVolumeOnCreationExtensionPoint arg) {
return arg.getPrimaryStorageTypeForInstantiateDataVolumeOnCreationExtensionPoint();
}
});
{
List<VolumeFactory> exts = pluginRgty.getExtensionList(
VolumeFactory.class);
if (exts.size() > 1) {
throw new OperationFailureException(operr("there should not be more than one %s implementation.",
VolumeFactory.class.getSimpleName()));
}
}
return true;
}
@Override
public boolean stop() {
return true;
}
private synchronized void startExpungeTask() {
if (volumeExpungeTask != null) {
volumeExpungeTask.cancel(true);
}
volumeExpungeTask = thdf.submitCancelablePeriodicTask(new CancelablePeriodicTask() {
private List<Tuple> getDeletedVolumeManagedByUs() {
int qun = 1000;
SimpleQuery q = dbf.createQuery(VolumeVO.class);
q.add(VolumeVO_.status, Op.EQ, VolumeStatus.Deleted);
q.add(VolumeVO_.type, Op.EQ, VolumeType.Data);
long amount = q.count();
int times = (int) (amount / qun) + (amount % qun != 0 ? 1 : 0);
int start = 0;
List<Tuple> ret = new ArrayList<Tuple>();
for (int i = 0; i < times; i++) {
q = dbf.createQuery(VolumeVO.class);
q.select(VolumeVO_.uuid, VolumeVO_.lastOpDate);
q.add(VolumeVO_.status, Op.EQ, VolumeStatus.Deleted);
q.add(VolumeVO_.type, Op.EQ, VolumeType.Data);
q.setLimit(qun);
q.setStart(start);
List<Tuple> lst = q.listTuple();
start += qun;
for (Tuple t : lst) {
String uuid = t.get(0, String.class);
if (!destMaker.isManagedByUs(uuid)) {
continue;
}
ret.add(t);
}
}
return ret;
}
@Override
public boolean run() {
List<Tuple> vols = getDeletedVolumeManagedByUs();
if (vols.isEmpty()) {
logger.debug("[Volume Expunging Task]: no volume to expunge");
return false;
}
Timestamp current = dbf.getCurrentSqlTime();
for (final Tuple v : vols) {
final String uuid = v.get(0, String.class);
Timestamp date = v.get(1, Timestamp.class);
long end = date.getTime() + TimeUnit.SECONDS.toMillis(VolumeGlobalConfig.VOLUME_EXPUNGE_PERIOD.value(Long.class));
if (current.getTime() >= end) {
VolumeDeletionPolicy deletionPolicy = deletionPolicyMgr.getDeletionPolicy(uuid);
if (deletionPolicy == VolumeDeletionPolicy.Never) {
logger.debug(String.format("the deletion policy of the volume[uuid:%s] is Never, don't expunge it",
uuid));
continue;
}
ExpungeVolumeMsg msg = new ExpungeVolumeMsg();
msg.setVolumeUuid(uuid);
bus.makeTargetServiceIdByResourceUuid(msg, VolumeConstant.SERVICE_ID, uuid);
bus.send(msg, new CloudBusCallBack(null) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
logger.warn(String.format("failed to expunge the volume[uuid:%s], %s", uuid, reply.getError()));
} else {
logger.debug(String.format("successfully expunged the volume [uuid:%s]", uuid));
}
}
});
}
}
return false;
}
@Override
public TimeUnit getTimeUnit() {
return TimeUnit.SECONDS;
}
@Override
public long getInterval() {
return VolumeGlobalConfig.VOLUME_EXPUNGE_INTERVAL.value(Long.class);
}
@Override
public String getName() {
return "expunging-volume-task";
}
});
logger.debug(String.format("volume expunging task starts [period: %s seconds, interval: %s seconds]",
VolumeGlobalConfig.VOLUME_EXPUNGE_PERIOD.value(Long.class),
VolumeGlobalConfig.VOLUME_EXPUNGE_INTERVAL.value(Long.class)));
}
@Override
public void managementNodeReady() {
startExpungeTask();
}
public void preDeleteVolume(VolumeInventory volume) {
}
public void beforeDeleteVolume(VolumeInventory volume) {
SimpleQuery<SchedulerVO> q = dbf.createQuery(SchedulerVO.class);
q.add(SchedulerVO_.targetResourceUuid, Op.EQ, volume.getUuid());
q.select(SchedulerVO_.uuid);
List<String> uuids = q.listValue();
for (String uuid : uuids) {
schedulerFacade.pauseSchedulerJob(uuid);
}
}
public void afterDeleteVolume(VolumeInventory volume) {
}
public void failedToDeleteVolume(VolumeInventory volume, ErrorCode errorCode) {
}
public void volumeBeforeExpunge(VolumeInventory volume) {
logger.debug(String.format("will delete scheduler before expunge volume %s", volume.getUuid()));
SimpleQuery<SchedulerVO> q = dbf.createQuery(SchedulerVO.class);
q.add(SchedulerVO_.targetResourceUuid, Op.EQ, volume.getUuid());
q.select(SchedulerVO_.uuid);
List<String> uuids = q.listValue();
for (String uuid : uuids) {
schedulerFacade.deleteSchedulerJob(uuid);
}
}
public void preRecoverDataVolume(VolumeInventory volume) {
}
public void beforeRecoverDataVolume(VolumeInventory volume) {
}
public void afterRecoverDataVolume(VolumeInventory volume) {
}
@Override
public void resourceOwnerAfterChange(AccountResourceRefInventory ref, String newOwnerUuid) {
if (!VmInstanceVO.class.getSimpleName().equals(ref.getResourceType())) {
return;
}
changeVolumeOwner(ref, newOwnerUuid);
}
private void changeVolumeOwner(AccountResourceRefInventory ref, String newOwnerUuid) {
SimpleQuery<VolumeVO> q = dbf.createQuery(VolumeVO.class);
q.select(VolumeVO_.uuid);
q.add(VolumeVO_.vmInstanceUuid, Op.EQ, ref.getResourceUuid());
List<String> uuids = q.listValue();
for (String uuid : uuids) {
acntMgr.changeResourceOwner(uuid, newOwnerUuid);
}
}
}