package org.zstack.storage.primary;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.CloudBusCallBack;
import org.zstack.core.cloudbus.ResourceDestinationMaker;
import org.zstack.core.config.GlobalConfig;
import org.zstack.core.config.GlobalConfigUpdateExtensionPoint;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.thread.PeriodicTask;
import org.zstack.core.thread.SyncTask;
import org.zstack.core.thread.ThreadFacade;
import org.zstack.header.message.MessageReply;
import org.zstack.header.storage.primary.DeleteImageCacheOnPrimaryStorageMsg;
import org.zstack.header.storage.primary.ImageCacheShadowVO;
import org.zstack.header.storage.primary.ImageCacheVO;
import org.zstack.header.storage.primary.PrimaryStorageConstant;
import org.zstack.header.volume.VolumeType;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import javax.persistence.TypedQuery;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* Created by xing5 on 2016/7/18.
*/
public abstract class ImageCacheCleaner {
private static final CLogger logger = Utils.getLogger(ImageCacheCleaner.class);
@Autowired
protected DatabaseFacade dbf;
@Autowired
protected ThreadFacade thdf;
@Autowired
protected CloudBus bus;
@Autowired
protected ResourceDestinationMaker destMaker;
protected Future<Void> gcThread;
protected abstract String getPrimaryStorageType();
protected void startGC() {
cleanupIntervalConfig().installUpdateExtension(new GlobalConfigUpdateExtensionPoint() {
@Override
public void updateGlobalConfig(GlobalConfig oldConfig, GlobalConfig newConfig) {
if (gcThread != null) {
gcThread.cancel(true);
}
startGCThread();
}
});
startGCThread();
}
protected GlobalConfig cleanupIntervalConfig() {
return PrimaryStorageGlobalConfig.IMAGE_CACHE_GARBAGE_COLLECTOR_INTERVAL;
}
public void cleanup() {
cleanup(null);
}
public void cleanup(String psUuid) {
ImageCacheCleaner self = this;
thdf.syncSubmit(new SyncTask<Void>() {
@Override
public Void call() throws Exception {
doCleanup(psUuid);
return null;
}
@Override
public String getName() {
return getSyncSignature();
}
@Override
public String getSyncSignature() {
return self.getClass().getName();
}
@Override
public int getSyncLevel() {
return 1;
}
});
}
protected void doCleanup(String psUuid) {
List<ImageCacheShadowVO> shadowVOs = createShadowImageCacheVOs(psUuid);
if (shadowVOs == null || shadowVOs.isEmpty()) {
return;
}
for (final ImageCacheShadowVO vo : shadowVOs) {
if (!destMaker.isManagedByUs(vo.getImageUuid())) {
continue;
}
DeleteImageCacheOnPrimaryStorageMsg msg = new DeleteImageCacheOnPrimaryStorageMsg();
msg.setImageUuid(vo.getImageUuid());
msg.setInstallPath(vo.getInstallUrl());
msg.setPrimaryStorageUuid(vo.getPrimaryStorageUuid());
bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, vo.getPrimaryStorageUuid());
bus.send(msg, new CloudBusCallBack(null) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
logger.warn(String.format("failed to delete the stale image cache[%s] on the primary storage[%s], %s," +
"will re-try later", vo.getInstallUrl(), vo.getPrimaryStorageUuid(), reply.getError()));
return;
}
logger.debug(String.format("successfully deleted the stale image cache[%s] on the primary storage[%s]",
vo.getInstallUrl(), vo.getPrimaryStorageUuid()));
dbf.remove(vo);
}
});
}
}
private void startGCThread() {
logger.debug(String.format("%s starts with the interval %s secs", this.getClass().getSimpleName(), PrimaryStorageGlobalConfig.IMAGE_CACHE_GARBAGE_COLLECTOR_INTERVAL.value(Long.class)));
gcThread = thdf.submitPeriodicTask(new PeriodicTask() {
@Override
public TimeUnit getTimeUnit() {
return TimeUnit.SECONDS;
}
@Override
public long getInterval() {
return cleanupIntervalConfig().value(Long.class);
}
@Override
public String getName() {
return "image-cache-cleanup-thread";
}
@Override
public void run() {
cleanup();
}
});
}
@Transactional
protected List<Long> getStaleImageCacheIds(String psUuid) {
String sql;
if (psUuid == null) {
sql = "select count(*) from VolumeVO vol, PrimaryStorageVO pri where vol.primaryStorageUuid = pri.uuid" +
" and vol.type = :volType and vol.rootImageUuid is null and pri.type = :psType";
} else {
sql = "select count(*) from VolumeVO vol, PrimaryStorageVO pri where vol.primaryStorageUuid = pri.uuid" +
" and vol.type = :volType and vol.rootImageUuid is null and pri.type = :psType and pri.uuid = :psUuid";
}
TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class);
q.setParameter("volType", VolumeType.Root);
q.setParameter("psType", getPrimaryStorageType());
if (psUuid != null) {
q.setParameter("psUuid", psUuid);
}
Long count = q.getSingleResult();
if (count != 0) {
logger.warn(String.format("found %s volumes on the primary storage[type:%s] has NULL rootImageUuid. Please do following:\n" +
"1. zstack-ctl stop_node\n" +
"2. zstack-ctl start_node -DfixImageCacheUuid=true -DrootVolumeFindMissingImageUuid=true\n" +
"to fix the problem. For the data safety, we won't clean the image cache of the primary storage", count, getPrimaryStorageType()));
return null;
}
if (psUuid == null) {
sql = "select c.id from ImageCacheVO c, PrimaryStorageVO pri, ImageEO i where c.primaryStorageUuid = pri.uuid and i.uuid = c.imageUuid and i.deleted is not null and pri.type = :ptype";
} else {
sql = "select c.id from ImageCacheVO c, PrimaryStorageVO pri, ImageEO i where c.primaryStorageUuid = pri.uuid and i.uuid = c.imageUuid and i.deleted is not null and pri.type = :ptype and pri.uuid = :psUuid";
}
TypedQuery<Long> cq = dbf.getEntityManager().createQuery(sql, Long.class);
cq.setParameter("ptype", getPrimaryStorageType());
if (psUuid != null) {
cq.setParameter("psUuid", psUuid);
}
List<Long> deleted = cq.getResultList();
if (psUuid == null) {
sql = "select c.id from ImageCacheVO c, PrimaryStorageVO pri where c.imageUuid not in (select img.uuid from ImageVO img) and" +
" c.primaryStorageUuid = pri.uuid and pri.type = :psType";
} else {
sql = "select c.id from ImageCacheVO c, PrimaryStorageVO pri where c.imageUuid not in (select img.uuid from ImageVO img) and" +
" c.primaryStorageUuid = pri.uuid and pri.type = :psType and pri.uuid = :psUuid";
}
cq = dbf.getEntityManager().createQuery(sql, Long.class);
cq.setParameter("psType", getPrimaryStorageType());
if (psUuid != null) {
cq.setParameter("psUuid", psUuid);
}
deleted.addAll(cq.getResultList());
if (deleted.isEmpty()) {
return null;
}
return deleted;
}
@Transactional
protected List<ImageCacheShadowVO> createShadowImageCacheVOsForNewDeletedAndOld(String psUuid) {
List<Long> staleImageCacheIds = getStaleImageCacheIds(psUuid);
if (staleImageCacheIds == null || staleImageCacheIds.isEmpty()) {
return null;
}
String sql = "select c from ImageCacheVO c where c.imageUuid not in (select vol.rootImageUuid from VolumeVO vol where vol.rootImageUuid is not null) and c.id in (:ids)";
TypedQuery<ImageCacheVO> cq = dbf.getEntityManager().createQuery(sql, ImageCacheVO.class);
cq.setParameter("ids", staleImageCacheIds);
List<ImageCacheVO> stale = cq.getResultList();
if (stale.isEmpty()) {
return null;
}
logger.debug(String.format("found %s stale images in cache on the primary storage[type:%s], they are about to be cleaned up",
stale.size(), getPrimaryStorageType()));
for (ImageCacheVO vo : stale) {
dbf.getEntityManager().persist(new ImageCacheShadowVO(vo));
dbf.getEntityManager().remove(vo);
}
sql = "select s from ImageCacheShadowVO s, PrimaryStorageVO p where p.uuid = s.primaryStorageUuid and p.type = :ptype";
TypedQuery<ImageCacheShadowVO> sq = dbf.getEntityManager().createQuery(sql, ImageCacheShadowVO.class);
sq.setParameter("ptype", getPrimaryStorageType());
return sq.getResultList();
}
@Transactional
protected List<ImageCacheShadowVO> createShadowImageCacheVOs(String psUuid) {
List<ImageCacheShadowVO> newDeletedAndOld = createShadowImageCacheVOsForNewDeletedAndOld(psUuid);
if (newDeletedAndOld == null) {
// no new deleted images, let's check if there any old that failed to be deleted last time
String sql = "select s from ImageCacheShadowVO s, PrimaryStorageVO p where p.uuid = s.primaryStorageUuid and p.type = :ptype";
TypedQuery<ImageCacheShadowVO> sq = dbf.getEntityManager().createQuery(sql, ImageCacheShadowVO.class);
sq.setParameter("ptype", getPrimaryStorageType());
return sq.getResultList();
} else {
return newDeletedAndOld;
}
}
}