package org.zstack.storage.primary;
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.cloudbus.CloudBus;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.storage.primary.*;
import org.zstack.header.volume.VolumeStatus;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.Arrays.asList;
/**
* Created by AlanJager on 2017/4/25.
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class PrimaryStorageCapacityRecalculator {
private static CLogger logger = Utils.getLogger(PrimaryStorageCapacityRecalculator.class);
@Autowired
private DatabaseFacade dbf;
@Autowired
private CloudBus bus;
@Autowired
private ErrorFacade errf;
@Autowired
private PrimaryStorageOverProvisioningManager ratioMgr;
@Autowired
private PluginRegistry pluginRgty;
public List<String> psUuids;
private Map<String, RecalculatePrimaryStorageCapacityExtensionPoint> recalculateCapacityExtensions = new HashMap<>();
public PrimaryStorageCapacityRecalculator() {
for (RecalculatePrimaryStorageCapacityExtensionPoint ext : pluginRgty.getExtensionList(RecalculatePrimaryStorageCapacityExtensionPoint.class)) {
RecalculatePrimaryStorageCapacityExtensionPoint old = recalculateCapacityExtensions.get(ext.getPrimaryStorageTypeForRecalculateCapacityExtensionPoint());
if (old != null) {
throw new CloudRuntimeException(
String.format("duplicate RecalculatePrimaryStorageCapacityExtensionPoint[%s, %s] for type[%s]",
ext.getClass().getName(), old.getClass().getName(),
old.getPrimaryStorageTypeForRecalculateCapacityExtensionPoint()));
}
recalculateCapacityExtensions.put(ext.getPrimaryStorageTypeForRecalculateCapacityExtensionPoint(), ext);
}
}
public void recalculate() {
if (psUuids.isEmpty()) {
return;
}
final Map<String, Long> psCap = new HashMap<>();
new Runnable() {
@Override
@Transactional(readOnly = true)
public void run() {
// calculate all volume size
{
String sql = "select sum(vol.size), vol.primaryStorageUuid" +
" from VolumeVO vol" +
" where vol.primaryStorageUuid in (:psUuids)" +
" and vol.status in (:volStatus)" +
" group by vol.primaryStorageUuid";
TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
q.setParameter("psUuids", psUuids);
List<VolumeStatus> needCountVolumeStates = asList(VolumeStatus.Creating, VolumeStatus.Ready, VolumeStatus.Deleted);
q.setParameter("volStatus", needCountVolumeStates);
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 psUuid = t.get(1, String.class);
psCap.put(psUuid, ratioMgr.calculateByRatio(psUuid, cap));
}
}
// calculate all image cache size
{
String sql = "select sum(i.size), i.primaryStorageUuid" +
" from ImageCacheVO i" +
" where i.primaryStorageUuid in (:psUuids)" +
" group by i.primaryStorageUuid";
TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
q.setParameter("psUuids", psUuids);
List<Tuple> ts = q.getResultList();
for (Tuple t : ts) {
if (t.get(0, Long.class) == null) {
// no image cache
continue;
}
// templates in image cache are physical size
// do not calculate over-provisioning
long cap = t.get(0, Long.class);
String psUuid = t.get(1, String.class);
Long ncap = psCap.get(psUuid);
ncap = ncap == null ? cap : ncap + cap;
psCap.put(psUuid, ncap);
}
}
// calculate all snapshot size
{
String sql = "select sum(snapshot.size), snapshot.primaryStorageUuid" +
" from VolumeSnapshotVO snapshot" +
" where snapshot.primaryStorageUuid in (:psUuids)" +
" group by snapshot.primaryStorageUuid";
TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
q.setParameter("psUuids", psUuids);
List<Tuple> ts = q.getResultList();
for (Tuple t : ts) {
if (t.get(0, Long.class) == null) {
// no snapshot
continue;
}
long cap = t.get(0, Long.class);
String psUuid = t.get(1, String.class);
Long ncap = psCap.get(psUuid);
ncap = ncap == null ? cap : ncap + cap;
psCap.put(psUuid, ncap);
}
}
}
}.run();
if (psCap.isEmpty()) {
// the primary storage is empty
for (String psUuid : psUuids) {
new Runnable() {
@Override
@Transactional
public void run() {
String sql = "select ps.type" +
" from PrimaryStorageVO ps" +
" where ps.uuid = :psUuid";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("psUuid", psUuid);
String type = q.getSingleResult();
RecalculatePrimaryStorageCapacityExtensionPoint ext = recalculateCapacityExtensions.get(type);
RecalculatePrimaryStorageCapacityStruct struct = new RecalculatePrimaryStorageCapacityStruct();
struct.setPrimaryStorageUuid(psUuid);
if (ext != null) {
ext.beforeRecalculatePrimaryStorageCapacity(struct);
}
PrimaryStorageCapacityUpdater updater = new PrimaryStorageCapacityUpdater(psUuid);
updater.run(new PrimaryStorageCapacityUpdaterRunnable() {
@Override
public PrimaryStorageCapacityVO call(PrimaryStorageCapacityVO cap) {
cap.setAvailableCapacity(cap.getAvailablePhysicalCapacity());
logger.debug(String.format("re-calculated available capacity of the primary storage" +
"[uuid:%s] with over-provisioning ratio[%s]",
psUuid, ratioMgr.getRatio(psUuid)));
return cap;
}
});
if (ext != null) {
ext.afterRecalculatePrimaryStorageCapacity(struct);
}
}
}.run();
}
} else {
// there are volumes/images on the primary storage, re-calculate the available capacity
for (final Map.Entry<String, Long> e : psCap.entrySet()) {
final String psUuid = e.getKey();
final long used = e.getValue();
new Runnable() {
@Override
@Transactional
public void run() {
String sql = "select ps.type" +
" from PrimaryStorageVO ps" +
" where ps.uuid = :psUuid";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("psUuid", psUuid);
String type = q.getSingleResult();
RecalculatePrimaryStorageCapacityExtensionPoint ext = recalculateCapacityExtensions.get(type);
RecalculatePrimaryStorageCapacityStruct struct = new RecalculatePrimaryStorageCapacityStruct();
struct.setPrimaryStorageUuid(psUuid);
if (ext != null) {
ext.beforeRecalculatePrimaryStorageCapacity(struct);
}
PrimaryStorageCapacityUpdater updater = new PrimaryStorageCapacityUpdater(psUuid);
updater.run(new PrimaryStorageCapacityUpdaterRunnable() {
@Override
public PrimaryStorageCapacityVO call(PrimaryStorageCapacityVO cap) {
long before = cap.getAvailableCapacity();
long now = cap.getTotalCapacity()
- used
- (cap.getSystemUsedCapacity() == null ? 0 : cap.getSystemUsedCapacity());
cap.setAvailableCapacity(now);
logger.debug(String.format("re-calculated available capacity of the primary storage" +
"[uuid:%s, before:%s, now:%s] with over-provisioning ratio[%s]",
psUuid, before, now, ratioMgr.getRatio(psUuid)));
return cap;
}
});
if (ext != null) {
ext.afterRecalculatePrimaryStorageCapacity(struct);
}
}
}.run();
}
}
}
}