package org.zstack.storage.primary.local;
import org.apache.commons.lang.StringUtils;
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.host.HostState;
import org.zstack.header.host.HostStatus;
import org.zstack.header.storage.primary.*;
import org.zstack.header.storage.primary.PrimaryStorageConstant.AllocatorParams;
import org.zstack.storage.primary.PrimaryStoragePhysicalCapacityManager;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import static org.zstack.core.Platform.operr;
import javax.persistence.TypedQuery;
import java.util.*;
import java.util.stream.Collectors;
/**
* Created by frank on 7/1/2015.
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class LocalStorageMainAllocatorFlow extends NoRollbackFlow {
private static final CLogger logger = Utils.getLogger(LocalStorageMainAllocatorFlow.class);
@Autowired
protected DatabaseFacade dbf;
@Autowired
protected ErrorFacade errf;
@Autowired
protected PrimaryStorageOverProvisioningManager ratioMgr;
@Autowired
protected PrimaryStoragePhysicalCapacityManager physicalCapacityMgr;
private class Result {
List<PrimaryStorageVO> result;
String error;
}
@Transactional(readOnly = true)
private Result allocate(Map data) {
PrimaryStorageAllocationSpec spec = (PrimaryStorageAllocationSpec) data.get(AllocatorParams.SPEC);
TypedQuery<LocalStorageHostRefVO> query;
String errorInfo;
if (spec.getRequiredPrimaryStorageUuid() != null) {
String sql = "select ref" +
" from PrimaryStorageVO pri, LocalStorageHostRefVO ref, HostVO host" +
" where ref.primaryStorageUuid = pri.uuid" +
" and pri.state = :state" +
" and pri.status = :status" +
" and pri.uuid = :uuid" +
" and host.uuid = ref.hostUuid" +
" and host.state = :hstate" +
" and host.status = :hstatus" +
" and pri.type = :ptype";
query = dbf.getEntityManager().createQuery(sql, LocalStorageHostRefVO.class);
query.setParameter("state", PrimaryStorageState.Enabled);
query.setParameter("status", PrimaryStorageStatus.Connected);
query.setParameter("uuid", spec.getRequiredPrimaryStorageUuid());
query.setParameter("hstate", HostState.Enabled);
query.setParameter("hstatus", HostStatus.Connected);
query.setParameter("ptype", LocalStorageConstants.LOCAL_STORAGE_TYPE);
errorInfo = String.format("required local primary storage[uuid:%s] cannot satisfy conditions[state: %s, status: %s]," +
" or hosts providing the primary storage don't satisfy conditions[state: %s, status: %s, size > %s bytes]",
spec.getRequiredPrimaryStorageUuid(),
PrimaryStorageState.Enabled,
PrimaryStorageStatus.Connected,
HostState.Enabled,
HostStatus.Connected,
spec.getSize());
} else if (spec.getRequiredHostUuid() != null) {
String sql = "select lref" +
" from PrimaryStorageVO pri, PrimaryStorageClusterRefVO pref, HostVO host, LocalStorageHostRefVO lref" +
" where pri.uuid = pref.primaryStorageUuid" +
" and lref.primaryStorageUuid = pri.uuid" +
" and host.uuid = :huuid" +
" and host.uuid = lref.hostUuid" +
" and host.clusterUuid = pref.clusterUuid" +
" and pri.state = :pstate" +
" and pri.status = :pstatus" +
" and host.state = :hstate" +
" and host.status = :hstatus" +
" and pri.type = :ptype";
query = dbf.getEntityManager().createQuery(sql, LocalStorageHostRefVO.class);
query.setParameter("huuid", spec.getRequiredHostUuid());
query.setParameter("pstate", PrimaryStorageState.Enabled);
query.setParameter("pstatus", PrimaryStorageStatus.Connected);
query.setParameter("hstate", HostState.Enabled);
query.setParameter("hstatus", HostStatus.Connected);
query.setParameter("ptype", LocalStorageConstants.LOCAL_STORAGE_TYPE);
errorInfo = String.format("the required host[uuid:%s] cannot satisfy conditions[state: %s, status: %s, size > %s bytes]," +
" or doesn't belong to a local primary storage satisfying conditions[state: %s, status: %s]," +
" or its cluster doesn't attach to any local primary storage",
spec.getRequiredHostUuid(),
HostState.Enabled,
HostStatus.Connected,
spec.getSize(),
PrimaryStorageState.Enabled,
PrimaryStorageStatus.Connected);
} else if (spec.getRequiredZoneUuid() != null) {
String sql = "select ref" +
" from PrimaryStorageVO pri, LocalStorageHostRefVO ref, HostVO host" +
" where pri.uuid = ref.primaryStorageUuid" +
" and pri.state = :pstate" +
" and pri.status = :pstatus" +
" and pri.zoneUuid = :zoneUuid" +
" and pri.type = :ptype" +
" and host.uuid = ref.hostUuid" +
" and host.state = :hstate" +
" and host.status = :hstatus";
query = dbf.getEntityManager().createQuery(sql, LocalStorageHostRefVO.class);
query.setParameter("pstate", PrimaryStorageState.Enabled);
query.setParameter("pstatus", PrimaryStorageStatus.Connected);
query.setParameter("zoneUuid", spec.getRequiredZoneUuid());
query.setParameter("hstate", HostState.Enabled);
query.setParameter("hstatus", HostStatus.Connected);
query.setParameter("ptype", LocalStorageConstants.LOCAL_STORAGE_TYPE);
errorInfo = String.format("no local primary storage in zone[uuid:%s] can satisfy conditions[state: %s, status: %s]" +
" or contain hosts satisfying conditions[state: %s, status: %s, size > %s bytes]",
spec.getRequiredZoneUuid(),
PrimaryStorageState.Enabled,
PrimaryStorageStatus.Connected,
HostState.Enabled,
HostStatus.Connected,
spec.getSize());
} else {
String sql = "select ref" +
" from PrimaryStorageVO pri, LocalStorageHostRefVO ref, HostVO host" +
" where pri.uuid = ref.primaryStorageUuid" +
" and host.uuid = ref.hostUuid" +
" and pri.state = :pstate" +
" and pri.status = :pstatus" +
" and host.state = :hstate" +
" and host.status = :hstatus" +
" and pri.type = :ptype";
query = dbf.getEntityManager().createQuery(sql, LocalStorageHostRefVO.class);
query.setParameter("pstate", PrimaryStorageState.Enabled);
query.setParameter("pstatus", PrimaryStorageStatus.Connected);
query.setParameter("hstate", HostState.Enabled);
query.setParameter("hstatus", HostStatus.Connected);
query.setParameter("ptype", LocalStorageConstants.LOCAL_STORAGE_TYPE);
errorInfo = String.format("no local primary storage can satisfy conditions[state: %s, status: %s]" +
" or contain hosts satisfying conditions[state: %s, status: %s, size > %s bytes]",
PrimaryStorageState.Enabled,
PrimaryStorageStatus.Connected,
HostState.Enabled,
HostStatus.Connected,
spec.getSize());
}
List<LocalStorageHostRefVO> refs = query.getResultList();
List<LocalStorageHostRefVO> candidateHosts = new ArrayList<>();
for (LocalStorageHostRefVO ref : refs) {
if (spec.isNoOverProvisioning()) {
if (ref.getAvailableCapacity() > spec.getSize()) {
candidateHosts.add(ref);
}
} else {
if (ratioMgr.calculatePrimaryStorageAvailableCapacityByRatio(ref.getPrimaryStorageUuid(),
ref.getAvailableCapacity()) > spec.getSize()) {
candidateHosts.add(ref);
}
}
}
if (!candidateHosts.isEmpty()) {
Iterator<LocalStorageHostRefVO> it = candidateHosts.iterator();
List<String> err = new ArrayList<>();
while (it.hasNext()) {
LocalStorageHostRefVO ref = it.next();
if (!physicalCapacityMgr.checkCapacityByRatio(ref.getPrimaryStorageUuid(), ref.getTotalPhysicalCapacity(), ref.getAvailablePhysicalCapacity())) {
err.add(String.format("{the physical capacity usage of the host[uuid:%s] has exceeded the threshold[%s]}",
ref.getHostUuid(), physicalCapacityMgr.getRatio(ref.getPrimaryStorageUuid())));
it.remove();
}
}
if (candidateHosts.isEmpty()) {
errorInfo = StringUtils.join(err, ",");
}
}
Set<String> candidates = new HashSet<>();
if (!candidateHosts.isEmpty()) {
if (PrimaryStorageAllocationPurpose.CreateNewVm.toString().equals(spec.getPurpose())) {
candidates.addAll(considerImageCache(spec, candidateHosts));
} else {
candidates.addAll(candidateHosts.stream()
.map(LocalStorageHostRefVO::getPrimaryStorageUuid)
.collect(Collectors.toList())
);
}
}
List<PrimaryStorageVO> res;
if (candidates.isEmpty()) {
res = new ArrayList<>();
} else {
String sql = "select ps from PrimaryStorageVO ps where ps.uuid in (:psUuids)";
TypedQuery<PrimaryStorageVO> q = dbf.getEntityManager().createQuery(sql, PrimaryStorageVO.class);
q.setParameter("psUuids", candidates);
res = q.getResultList();
}
if (spec.getRequiredPrimaryStorageTypes() != null && !spec.getRequiredPrimaryStorageTypes().isEmpty()) {
Iterator<PrimaryStorageVO> it = res.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();
}
}
}
Result ret = new Result();
ret.error = errorInfo;
ret.result = res;
return ret;
}
private Collection<? extends String> considerImageCache(PrimaryStorageAllocationSpec spec, List<LocalStorageHostRefVO> candidateHosts) {
List<String> ret = new ArrayList<>();
String sql = "select i.size 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 (LocalStorageHostRefVO ref : candidateHosts) {
sql = "select count(i) from ImageCacheVO i where i.installUrl like :mark and i.primaryStorageUuid in (:psUuids)";
TypedQuery<Long> iq = dbf.getEntityManager().createQuery(sql, Long.class);
iq.setParameter("psUuids", ref.getPrimaryStorageUuid());
iq.setParameter("mark", String.format("%%hostUuid://%s%%", ref.getHostUuid()));
iq.setMaxResults(1);
long count = iq.getSingleResult();
if (count > 0) {
// the host has the image in cache
ret.add(ref.getPrimaryStorageUuid());
} else {
// the host doesn't has the image in cache
// we need to add the image size;
if (spec.isNoOverProvisioning()) {
if (ref.getAvailableCapacity() > spec.getSize() + imageSize) {
ret.add(ref.getPrimaryStorageUuid());
}
} else {
if (ref.getAvailableCapacity() > ratioMgr.calculateByRatio(ref.getPrimaryStorageUuid(), spec.getSize()) + imageSize) {
ret.add(ref.getPrimaryStorageUuid());
}
}
}
}
return ret;
}
@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();
}
}