package org.zstack.storage.primary.local;
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.db.DatabaseFacade;
import org.zstack.header.storage.primary.PrimaryStorageCapacityUpdaterRunnable;
import org.zstack.header.storage.primary.PrimaryStorageCapacityVO;
import org.zstack.header.storage.primary.PrimaryStorageOverProvisioningManager;
import org.zstack.storage.primary.PrimaryStorageCapacityUpdater;
import org.zstack.utils.DebugUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import javax.persistence.LockModeType;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by frank on 1/23/2016.
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class LocalStorageCapacityRecalculator {
private static final CLogger logger = Utils.getLogger(LocalStorageCapacityRecalculator.class);
@Autowired
private DatabaseFacade dbf;
@Autowired
private PrimaryStorageOverProvisioningManager ratioMgr;
@Transactional
public LocalStorageCapacityRecalculator calculateByHostUuids(String psUuid, List<String> huuids) {
DebugUtils.Assert(!huuids.isEmpty(), "hostUuids cannot be empty");
Map<String, Long> hostCap = new HashMap<>();
// count volume
String sql = "select sum(vol.size), ref.hostUuid" +
" from VolumeVO vol, LocalStorageResourceRefVO ref" +
" where vol.primaryStorageUuid = :psUuid" +
" and vol.uuid = ref.resourceUuid" +
" and ref.primaryStorageUuid = vol.primaryStorageUuid" +
" and ref.hostUuid in (:huuids)" +
" group by ref.hostUuid";
TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
q.setParameter("psUuid", psUuid);
q.setParameter("huuids", huuids);
List<Tuple> ts = q.getResultList();
for (Tuple t : ts) {
if (t.get(0, Long.class) == null) {
// no volume
continue;
}
long cap = t.get(0, Long.class);
String hostUuid = t.get(1, String.class);
hostCap.put(hostUuid, ratioMgr.calculateByRatio(psUuid, cap));
}
// count snapshot
sql = "select sum(snapshot.size), ref.hostUuid" +
" from VolumeSnapshotVO snapshot, LocalStorageResourceRefVO ref" +
" where snapshot.primaryStorageUuid = :psUuid" +
" and snapshot.uuid = ref.resourceUuid" +
" and ref.primaryStorageUuid = snapshot.primaryStorageUuid" +
" and ref.hostUuid in (:huuids)" +
" group by ref.hostUuid";
TypedQuery<Tuple> snapshotTypeQuery = dbf.getEntityManager().createQuery(sql, Tuple.class);
snapshotTypeQuery.setParameter("psUuid", psUuid);
snapshotTypeQuery.setParameter("huuids", huuids);
List<Tuple> snapshotList = snapshotTypeQuery.getResultList();
for (Tuple t : snapshotList) {
if (t.get(0, Long.class) == null) {
// no snpashot
continue;
}
long cap = t.get(0, Long.class);
String huuid = t.get(1, String.class);
Long ncap = hostCap.get(huuid);
ncap = ncap == null ? cap : ncap + cap;
hostCap.put(huuid, ncap);
}
// count imageCache
for (String huuid : huuids) {
// note: templates in image cache are physical size
// do not calculate over provisioning for them
sql = "select sum(i.size)" +
" from ImageCacheVO i" +
" where i.installUrl like :mark" +
" and i.primaryStorageUuid = :psUuid" +
" group by i.primaryStorageUuid";
TypedQuery<Long> iq = dbf.getEntityManager().createQuery(sql, Long.class);
iq.setParameter("psUuid", psUuid);
iq.setParameter("mark", String.format("%%hostUuid://%s%%", huuid));
List<Long> is = iq.getResultList();
if (!is.isEmpty()) {
Long isize = is.get(0);
Long ncap = hostCap.get(huuid);
ncap = ncap == null ? isize : ncap + isize;
hostCap.put(huuid, ncap);
} else {
// if the host has no volume and image cache
// set its used capacity to zero
Long ncap = hostCap.get(huuid);
ncap = ncap == null ? 0 : ncap;
hostCap.put(huuid, ncap);
}
}
for (Map.Entry<String, Long> e : hostCap.entrySet()) {
String hostUuid = e.getKey();
long used = e.getValue();
String sqlLocalStorageHostRefVO = "select ref" +
" from LocalStorageHostRefVO ref" +
" where hostUuid = :hostUuid" +
" and primaryStorageUuid = :primaryStorageUuid";
TypedQuery<LocalStorageHostRefVO> query = dbf.getEntityManager().
createQuery(sqlLocalStorageHostRefVO, LocalStorageHostRefVO.class);
query.setParameter("hostUuid", hostUuid);
query.setParameter("primaryStorageUuid", psUuid);
LocalStorageHostRefVO ref;
List<LocalStorageHostRefVO> localStorageHostRefVOS = query.setLockMode(LockModeType.PESSIMISTIC_WRITE).getResultList();
if(localStorageHostRefVOS.size() > 0){
ref = localStorageHostRefVOS.get(0);
}else{
break;
}
long old = ref.getAvailableCapacity();
long avail = ref.getTotalCapacity() - used - ref.getSystemUsedCapacity();
ref.setAvailableCapacity(avail);
dbf.getEntityManager().merge(ref);
logger.debug(String.format("re-calculated available capacity[before:%s, now: %s] of host[uuid:%s]" +
" of the local storage[uuid:%s] with over-provisioning ratio[%s]",
old, avail, hostUuid, psUuid, ratioMgr.getRatio(psUuid)));
}
return this;
}
@Transactional
public LocalStorageCapacityRecalculator calculateByPrimaryStorageUuid(String psUuid) {
String sql = "select ref.hostUuid" +
" from LocalStorageHostRefVO ref" +
" where ref.primaryStorageUuid = :psUuid";
TypedQuery<String> hq = dbf.getEntityManager().createQuery(sql, String.class);
hq.setParameter("psUuid", psUuid);
List<String> huuids = hq.getResultList();
if (huuids != null && !huuids.isEmpty()) {
calculateByHostUuids(psUuid, huuids);
}else{
// when ps is detached, LocalStorageHostRefVO is empty, need reset ps capacity
calculateTotalCapacity(psUuid);
}
return this;
}
@Transactional
public LocalStorageCapacityRecalculator calculateTotalCapacity(String psUuid) {
String sql = "select sum(ref.totalCapacity)," +
" sum(ref.availableCapacity)," +
" sum(ref.totalPhysicalCapacity)," +
" sum(ref.availablePhysicalCapacity)" +
" from LocalStorageHostRefVO ref" +
" where ref.primaryStorageUuid = :psUuid";
TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
q.setParameter("psUuid", psUuid);
Tuple ts = q.getSingleResult();
final long totalCapacity;
final long availableCapacity;
final long totalPhysicalCapacity;
final long availablePhysicalCapacity;
PrimaryStorageCapacityUpdater pupdater = new PrimaryStorageCapacityUpdater(psUuid);
if (ts != null) {
totalCapacity = ts.get(0) == null ? 0 : ts.get(0, Long.class);
availableCapacity = ts.get(1) == null ? 0 : ts.get(1, Long.class);
totalPhysicalCapacity = ts.get(2) == null ? 0 : ts.get(2, Long.class);
availablePhysicalCapacity = ts.get(3) == null ? 0 : ts.get(3, Long.class);
} else {
totalCapacity = 0;
availableCapacity = 0;
totalPhysicalCapacity = 0;
availablePhysicalCapacity = 0;
}
pupdater.run(new PrimaryStorageCapacityUpdaterRunnable() {
@Override
public PrimaryStorageCapacityVO call(PrimaryStorageCapacityVO cap) {
cap.setTotalCapacity(totalCapacity);
cap.setAvailableCapacity(availableCapacity);
cap.setTotalPhysicalCapacity(totalPhysicalCapacity);
cap.setAvailablePhysicalCapacity(availablePhysicalCapacity);
return cap;
}
});
return this;
}
}