package org.zstack.compute.allocator;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.config.GlobalConfigVO;
import org.zstack.core.config.GlobalConfigVO_;
import org.zstack.core.db.Q;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.header.allocator.*;
import org.zstack.header.core.ReturnValueCompletion;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.OperationFailureException;
import org.zstack.header.host.HostInventory;
import org.zstack.header.host.HostVO;
import org.zstack.header.vm.VmInstanceInventory;
import org.zstack.utils.DebugUtils;
import org.zstack.utils.SizeUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.gson.JSONObjectUtil;
import org.zstack.utils.logging.CLogger;
import java.util.*;
/**
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class HostAllocatorChain implements HostAllocatorTrigger, HostAllocatorStrategy {
private static final CLogger logger = Utils.getLogger(HostAllocatorChain.class);
private HostAllocatorSpec allocationSpec;
private String name;
private List<AbstractHostAllocatorFlow> flows;
private Iterator<AbstractHostAllocatorFlow> it;
private ErrorCode errorCode;
private List<HostVO> result = null;
private boolean isDryRun;
private ReturnValueCompletion<HostInventory> completion;
private ReturnValueCompletion<List<HostInventory>> dryRunCompletion;
private AbstractHostAllocatorFlow lastFlow;
private HostAllocationPaginationInfo paginationInfo;
private Set<String> seriesErrorWhenPagination = new HashSet<String>();
private MarshalResultFunction marshalResultFunction;
@Autowired
private ErrorFacade errf;
@Autowired
private PluginRegistry pluginRgty;
@Autowired
private HostCapacityOverProvisioningManager ratioMgr;
public HostAllocatorSpec getAllocationSpec() {
return allocationSpec;
}
public void setAllocationSpec(HostAllocatorSpec allocationSpec) {
this.allocationSpec = allocationSpec;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<AbstractHostAllocatorFlow> getFlows() {
return flows;
}
public void setFlows(List<AbstractHostAllocatorFlow> flows) {
this.flows = flows;
}
void reserveCapacity(final String hostUuid, final long requestCpu, final long requestMemory) {
HostCapacityUpdater updater = new HostCapacityUpdater(hostUuid);
String reservedMemoryOfGlobalConfig = Q.New(GlobalConfigVO.class).select(GlobalConfigVO_.value).eq(GlobalConfigVO_.name,"reservedMemory").findValue();
updater.run(new HostCapacityUpdaterRunnable() {
@Override
public HostCapacityVO call(HostCapacityVO cap) {
long availCpu = cap.getAvailableCpu() - requestCpu;
if (availCpu < 0) {
throw new UnableToReserveHostCapacityException(
String.format("no enough CPU[%s] on the host[uuid:%s]", requestCpu, hostUuid));
}
cap.setAvailableCpu(availCpu);
long availMemory = cap.getAvailableMemory() - ratioMgr.calculateMemoryByRatio(hostUuid, requestMemory);
if (availMemory - SizeUtils.sizeStringToBytes(reservedMemoryOfGlobalConfig) <= 0) {
throw new UnableToReserveHostCapacityException(
String.format("no enough memory[%s] on the host[uuid:%s]", requestMemory, hostUuid));
}
cap.setAvailableMemory(availMemory);
return cap;
}
});
}
protected void marshalResult() {
if (marshalResultFunction != null) {
marshalResultFunction.marshal(result);
}
}
private void done() {
if (result == null) {
if (isDryRun) {
if (HostAllocatorError.NO_AVAILABLE_HOST.toString().equals(errorCode.getCode())) {
dryRunCompletion.success(new ArrayList<HostInventory>());
} else {
dryRunCompletion.fail(errorCode);
}
} else {
completion.fail(errorCode);
}
return;
}
// in case a wrong flow returns an empty result set
if (result.isEmpty()) {
if (isDryRun) {
dryRunCompletion.fail(errf.instantiateErrorCode(HostAllocatorError.NO_AVAILABLE_HOST,
"host allocation flow doesn't indicate any details"));
} else {
completion.fail(errf.instantiateErrorCode(HostAllocatorError.NO_AVAILABLE_HOST,
"host allocation flow doesn't indicate any details"));
}
return;
}
if (isDryRun) {
dryRunCompletion.success(HostInventory.valueOf(result));
return;
}
marshalResult();
try {
for (HostVO h : result) {
try {
reserveCapacity(h.getUuid(), allocationSpec.getCpuCapacity(), allocationSpec.getMemoryCapacity());
logger.debug(String.format("[Host Allocation]: successfully reserved cpu[%s], memory[%s bytes] on host[uuid:%s] for vm[uuid:%s]",
allocationSpec.getCpuCapacity(), allocationSpec.getMemoryCapacity(), h.getUuid(),
allocationSpec.getVmInstance().getUuid()));
completion.success(HostInventory.valueOf(h));
return;
} catch (UnableToReserveHostCapacityException e) {
logger.debug(String.format("[Host Allocation]: %s on host[uuid:%s]. try next one",
e.getMessage(), h.getUuid()));
}
}
if (paginationInfo != null) {
logger.debug("[Host Allocation]: unable to reserve cpu/memory on all candidate hosts; because of pagination is enabled, allocation will start over");
seriesErrorWhenPagination.add(String.format("{unable to reserve cpu[%s], memory[%s bytes] on all candidate hosts}",
allocationSpec.getCpuCapacity(), allocationSpec.getMemoryCapacity()));
startOver();
} else {
completion.fail(errf.instantiateErrorCode(HostAllocatorError.NO_AVAILABLE_HOST,
"reservation on cpu/memory failed on all candidates host"));
}
} catch (Throwable t) {
logger.debug(t.getClass().getName(), t);
completion.fail(errf.throwableToInternalError(t));
}
}
private void startOver() {
it = flows.iterator();
result = null;
runFlow(it.next());
}
private void runFlow(AbstractHostAllocatorFlow flow) {
try {
lastFlow = flow;
flow.setCandidates(result);
flow.setSpec(allocationSpec);
flow.setTrigger(this);
flow.setPaginationInfo(paginationInfo);
flow.allocate();
} catch (OperationFailureException ofe) {
if (ofe.getErrorCode().getCode().equals(HostAllocatorConstant.PAGINATION_INTERMEDIATE_ERROR.getCode())) {
logger.debug(String.format("[Host Allocation]: intermediate failure; " +
"because of pagination, will start over allocation again; " +
"current pagination info %s; failure details: %s",
JSONObjectUtil.toJsonString(paginationInfo), ofe.getErrorCode().getDetails()));
seriesErrorWhenPagination.add(String.format("{%s}", ofe.getErrorCode().getDetails()));
startOver();
} else {
fail(ofe.getErrorCode());
}
} catch (Throwable t) {
logger.warn("unhandled throwable", t);
completion.fail(errf.throwableToInternalError(t));
}
}
private void start() {
for (HostAllocatorPreStartExtensionPoint processor : pluginRgty.getExtensionList(HostAllocatorPreStartExtensionPoint.class)) {
processor.beforeHostAllocatorStart(allocationSpec, flows);
}
if (HostAllocatorGlobalConfig.USE_PAGINATION.value(Boolean.class)) {
paginationInfo = new HostAllocationPaginationInfo();
paginationInfo.setLimit(HostAllocatorGlobalConfig.PAGINATION_LIMIT.value(Integer.class));
}
it = flows.iterator();
DebugUtils.Assert(it.hasNext(), "can not run an empty host allocation chain");
runFlow(it.next());
}
private void allocate(ReturnValueCompletion<HostInventory> completion) {
isDryRun = false;
this.completion = completion;
start();
}
private void dryRun(ReturnValueCompletion<List<HostInventory>> completion) {
isDryRun = true;
this.dryRunCompletion = completion;
start();
}
@Override
public void next(List<HostVO> candidates) {
DebugUtils.Assert(candidates != null, "cannot pass null to next() method");
DebugUtils.Assert(!candidates.isEmpty(), "cannot pass empty candidates to next() method");
result = candidates;
VmInstanceInventory vm = allocationSpec.getVmInstance();
logger.debug(String.format("[Host Allocation]: flow[%s] successfully found %s candidate hosts for vm[uuid:%s, name:%s]",
lastFlow.getClass().getName(), result.size(), vm.getUuid(), vm.getName()));
if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder("[Host Allocation Details]:");
for (HostVO vo : result) {
sb.append(String.format("\ncandidate host[name:%s, uuid:%s, zoneUuid:%s, clusterUuid:%s, hypervisorType:%s]",
vo.getName(), vo.getUuid(), vo.getZoneUuid(), vo.getClusterUuid(), vo.getHypervisorType()));
}
logger.trace(sb.toString());
}
if (it.hasNext()) {
runFlow(it.next());
return;
}
done();
}
@Override
public void skip() {
logger.debug(String.format("[Host Allocation]: flow[%s] asks to skip itself, we are running to the next flow",
lastFlow.getClass()));
if (it.hasNext()) {
runFlow(it.next());
return;
}
done();
}
@Override
public int indexOfFlow(AbstractHostAllocatorFlow flow) {
return flows.indexOf(flow);
}
private void fail(ErrorCode errorCode) {
result = null;
if (seriesErrorWhenPagination.isEmpty()) {
logger.debug(String.format("[Host Allocation] flow[%s] failed to allocate host; %s",
lastFlow.getClass().getName(), errorCode.getDetails()));
this.errorCode = errorCode;
} else {
String err = String.format("unable to allocate hosts; due to pagination is enabled, " +
"there might be several allocation failures happened before;" +
" the error list is %s", seriesErrorWhenPagination);
logger.debug(err);
this.errorCode = errf.instantiateErrorCode(HostAllocatorError.NO_AVAILABLE_HOST, err);
}
done();
}
@Override
public void allocate(HostAllocatorSpec spec, ReturnValueCompletion<HostInventory> completion) {
this.allocationSpec = spec;
allocate(completion);
}
@Override
public void dryRun(HostAllocatorSpec spec, ReturnValueCompletion<List<HostInventory>> completion) {
this.allocationSpec = spec;
dryRun(completion);
}
@Override
public void setMarshalResultFunction(MarshalResultFunction func) {
marshalResultFunction = func;
}
}