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.db.DatabaseFacade;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.header.core.workflow.FlowTrigger;
import org.zstack.header.core.workflow.NoRollbackFlow;
import org.zstack.header.errorcode.OperationFailureException;
import org.zstack.header.storage.primary.*;
import org.zstack.header.storage.primary.PrimaryStorageConstant.AllocatorParams;
import org.zstack.utils.CollectionUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.function.Function;
import org.zstack.utils.logging.CLogger;
import static org.zstack.core.Platform.operr;
import javax.persistence.TypedQuery;
import java.util.*;
/**
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class PrimaryStorageMainAllocatorFlow extends NoRollbackFlow {
private static CLogger logger = Utils.getLogger(PrimaryStorageMainAllocatorFlow.class);
@Autowired
protected DatabaseFacade dbf;
@Autowired
protected ErrorFacade errf;
@Autowired
protected PrimaryStorageOverProvisioningManager ratioMgr;
private class Result {
List<PrimaryStorageVO> result;
String error;
}
@Transactional(readOnly = true)
private Result allocate(Map data) {
PrimaryStorageAllocationSpec spec = (PrimaryStorageAllocationSpec) data.get(AllocatorParams.SPEC);
TypedQuery<PrimaryStorageVO> query;
String errorInfo;
if (spec.getRequiredPrimaryStorageUuid() != null) {
String sql = "select pri" +
" from PrimaryStorageVO pri" +
" where pri.state = :priState" +
" and pri.status = :status" +
" and pri.uuid = :priUuid";
query = dbf.getEntityManager().createQuery(sql, PrimaryStorageVO.class);
query.setParameter("priState", PrimaryStorageState.Enabled);
query.setParameter("status", PrimaryStorageStatus.Connected);
query.setParameter("priUuid", spec.getRequiredPrimaryStorageUuid());
errorInfo = String.format("required primary storage[uuid:%s] cannot satisfy conditions[state:%s, status:%s, size:%s]",
spec.getRequiredPrimaryStorageUuid(), PrimaryStorageState.Enabled, PrimaryStorageStatus.Connected, spec.getSize());
} else if (spec.getRequiredHostUuid() != null) {
String sql = "select pri" +
" from PrimaryStorageVO pri, PrimaryStorageClusterRefVO ref, HostVO host" +
" where host.uuid = :huuid" +
" and host.clusterUuid = ref.clusterUuid" +
" and ref.primaryStorageUuid = pri.uuid" +
" and pri.status = :status" +
" and pri.state = :priState";
query = dbf.getEntityManager().createQuery(sql, PrimaryStorageVO.class);
query.setParameter("huuid", spec.getRequiredHostUuid());
query.setParameter("priState", PrimaryStorageState.Enabled);
query.setParameter("status", PrimaryStorageStatus.Connected);
errorInfo = String.format("cannot find primary storage satisfying conditions" +
"[attached to cluster having host:%s, state:%s, status: %s, available capacity > %s",
spec.getRequiredHostUuid(), PrimaryStorageState.Enabled, PrimaryStorageStatus.Connected, spec.getSize());
} else if (spec.getRequiredClusterUuids() != null && !spec.getRequiredClusterUuids().isEmpty()) {
String sql = "select pri" +
" from PrimaryStorageVO pri, PrimaryStorageClusterRefVO ref, ClusterVO cluster" +
" where cluster.uuid = ref.clusterUuid" +
" and ref.primaryStorageUuid = pri.uuid" +
" and pri.status = :status" +
" and pri.state = :priState" +
" and cluster.uuid in (:clusterUuids)";
query = dbf.getEntityManager().createQuery(sql, PrimaryStorageVO.class);
query.setParameter("clusterUuids", spec.getRequiredClusterUuids());
query.setParameter("status", PrimaryStorageStatus.Connected);
query.setParameter("priState", PrimaryStorageState.Enabled);
errorInfo = String.format("cannot find primary storage satisfying conditions" +
"[attached to clusters:%s, state:%s, status:%s, available capacity > %s",
spec.getRequiredClusterUuids(), PrimaryStorageState.Enabled, PrimaryStorageStatus.Connected, spec.getSize());
} else if (spec.getRequiredZoneUuid() != null) {
String sql = "select pri" +
" from PrimaryStorageVO pri" +
" where pri.zoneUuid = :zoneUuid" +
" and pri.status = :status" +
" and pri.state = :priState";
query = dbf.getEntityManager().createQuery(sql, PrimaryStorageVO.class);
query.setParameter("priState", PrimaryStorageState.Enabled);
query.setParameter("status", PrimaryStorageStatus.Connected);
query.setParameter("zoneUuid", spec.getRequiredZoneUuid());
errorInfo = String.format("cannot find primary storage satisfying conditions[in zone:%s, state:%s, status:%s, available capacity > %s",
spec.getRequiredZoneUuid(), PrimaryStorageState.Enabled, PrimaryStorageStatus.Connected, spec.getSize());
} else {
String sql = "select pri" +
" from PrimaryStorageVO pri" +
" where pri.status = :status" +
" and pri.state = :priState";
query = dbf.getEntityManager().createQuery(sql, PrimaryStorageVO.class);
query.setParameter("priState", PrimaryStorageState.Enabled);
query.setParameter("status", PrimaryStorageStatus.Connected);
errorInfo = String.format("cannot find primary storage satisfying conditions[state:%s, status:%s, available capacity > %s",
PrimaryStorageState.Enabled, PrimaryStorageStatus.Connected, spec.getSize());
}
List<PrimaryStorageVO> vos = query.getResultList();
if (spec.getRequiredPrimaryStorageTypes() != null && !spec.getRequiredPrimaryStorageTypes().isEmpty()) {
Iterator<PrimaryStorageVO> it = vos.iterator();
while (it.hasNext()) {
PrimaryStorageVO psvo = it.next();
if (!spec.getRequiredPrimaryStorageTypes().contains(psvo.getType())) {
logger.debug(String.format("the primary storage[name:%s, uuid:%s, type:%s] is not in required primary storage types[%s]," +
" remove it", psvo.getName(), psvo.getUuid(), psvo.getType(), spec.getRequiredPrimaryStorageTypes()));
it.remove();
}
}
}
List<PrimaryStorageVO> res = new ArrayList<>();
if (PrimaryStorageAllocationPurpose.CreateNewVm.toString().equals(spec.getPurpose())) {
res.addAll(considerImageCache(spec, vos));
} else {
for (PrimaryStorageVO vo : vos) {
if (ratioMgr.calculatePrimaryStorageAvailableCapacityByRatio(vo.getUuid(),
vo.getCapacity().getAvailableCapacity()) > spec.getSize()) {
res.add(vo);
}
}
}
Result ret = new Result();
ret.error = errorInfo;
ret.result = res;
return ret;
}
@Transactional(readOnly = true)
private Collection<? extends PrimaryStorageVO> considerImageCache(PrimaryStorageAllocationSpec spec, List<PrimaryStorageVO> vos) {
List<PrimaryStorageVO> res = new ArrayList<>();
if (vos.isEmpty()) {
return res;
}
List<String> psUuids = CollectionUtils.transformToList(vos, new Function<String, PrimaryStorageVO>() {
@Override
public String call(PrimaryStorageVO arg) {
return arg.getUuid();
}
});
String sql = "select i.primaryStorageUuid from ImageCacheVO i where i.primaryStorageUuid in (:psUuids) and i.imageUuid = :iuuid";
TypedQuery<String> iq = dbf.getEntityManager().createQuery(sql, String.class);
iq.setParameter("psUuids", psUuids);
iq.setParameter("iuuid", spec.getImageUuid());
List<String> hasImagePrimaryStorage = iq.getResultList();
sql = "select i.actualSize from ImageVO i where i.uuid = :uuid";
TypedQuery<Long> sq = dbf.getEntityManager().createQuery(sql, Long.class);
sq.setParameter("uuid", spec.getImageUuid());
long imageSize = sq.getSingleResult();
for (PrimaryStorageVO vo : vos) {
long requiredSize = ratioMgr.calculateByRatio(vo.getUuid(), spec.getSize());
if (!hasImagePrimaryStorage.contains(vo.getUuid())) {
requiredSize += imageSize;
}
if (vo.getCapacity().getAvailableCapacity() >= requiredSize) {
res.add(vo);
}
}
return res;
}
@Override
public void run(FlowTrigger trigger, Map data) {
Result ret = allocate(data);
if (ret.result.isEmpty()) {
throw new OperationFailureException(operr(ret.error));
}
data.put(AllocatorParams.CANDIDATES, ret.result);
trigger.next();
}
}