package org.zstack.compute.vm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.CoreGlobalProperty;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.EventFacade;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.thread.SyncTask;
import org.zstack.core.thread.ThreadFacade;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.vm.*;
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 org.zstack.utils.CollectionDSL.list;
/**
*/
public abstract class VmTracer {
private static final CLogger logger = Utils.getLogger(VmTracer.class);
@Autowired
private CloudBus bus;
@Autowired
private DatabaseFacade dbf;
@Autowired
private ThreadFacade thdf;
@Autowired
private EventFacade evtf;
private class Tracer {
String hostUuid;
Map<String, VmInstanceState> hostSideStates;
Map<String, VmInstanceState> mgmtSideStates;
@Transactional(readOnly = true)
private void buildManagementServerSideVmStates() {
mgmtSideStates = new HashMap<String, VmInstanceState>();
String sql = "select vm.uuid, vm.state from VmInstanceVO vm where vm.hostUuid = :huuid or (vm.hostUuid is null and vm.lastHostUuid = :huuid)" +
" and vm.state not in (:vmstates)";
TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
q.setParameter("huuid", hostUuid);
q.setParameter("vmstates", list(VmInstanceState.Destroyed, VmInstanceState.Destroying));
List<Tuple> ts = q.getResultList();
for (Tuple t : ts) {
mgmtSideStates.put(t.get(0, String.class), t.get(1, VmInstanceState.class));
}
}
private void checkFromHostSide() {
for (Map.Entry<String, VmInstanceState> e : hostSideStates.entrySet()) {
String vmUuid = e.getKey();
VmInstanceState actualState = e.getValue();
VmInstanceState expectedState = mgmtSideStates.get(vmUuid);
if (expectedState == null) {
// an anonymous vm showing on this host
handleAnonymousVm(vmUuid, actualState, expectedState);
} else if (actualState != expectedState) {
// vm state changed on host side
handleStateChangeOnHostSide(vmUuid, actualState, expectedState);
}
}
}
private void handleStateChangeOnHostSide(final String vmUuid, final VmInstanceState actualState, VmInstanceState expected) {
VmStateChangedOnHostMsg msg = new VmStateChangedOnHostMsg();
msg.setVmStateAtTracingMoment(expected);
msg.setVmInstanceUuid(vmUuid);
msg.setStateOnHost(actualState);
msg.setHostUuid(hostUuid);
bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, vmUuid);
bus.send(msg);
}
private void handleAnonymousVm(final String vmUuid, final VmInstanceState actualState, VmInstanceState expected) {
final VmInstanceVO vm = dbf.findByUuid(vmUuid, VmInstanceVO.class);
if (vm == null) {
logger.debug(String.format("[Vm Tracer] detects stranger vm[identity:%s, state:%s]", vmUuid, actualState));
VmTracerCanonicalEvents.StrangerVmFoundData data = new VmTracerCanonicalEvents.StrangerVmFoundData();
data.setVmIdentity(vmUuid);
data.setVmState(actualState);
data.setHostUuid(hostUuid);
evtf.fire(VmTracerCanonicalEvents.STRANGER_VM_FOUND_PATH, data);
return;
}
handleStateChangeOnHostSide(vmUuid, actualState, expected);
}
private void checkFromManagementServerSide() {
// from mgmt server we only check missing vm, vm state change has been updated by host side check
for (Map.Entry<String, VmInstanceState> e : mgmtSideStates.entrySet()) {
String vmUuid = e.getKey();
VmInstanceState expectedState = e.getValue();
if (expectedState != VmInstanceState.Stopped && !hostSideStates.containsKey(vmUuid)) {
handleMissingVm(vmUuid, expectedState);
}
}
}
private void handleMissingVm(final String vmUuid, final VmInstanceState expectedState) {
VmStateChangedOnHostMsg msg = new VmStateChangedOnHostMsg();
msg.setVmStateAtTracingMoment(expectedState);
msg.setHostUuid(hostUuid);
msg.setVmInstanceUuid(vmUuid);
msg.setStateOnHost(VmInstanceState.Stopped);
bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, vmUuid);
bus.send(msg);
}
void trace() {
buildManagementServerSideVmStates();
checkFromHostSide();
checkFromManagementServerSide();
}
}
protected void reportVmState(final String hostUuid, final Map<String, VmInstanceState> vmStates) {
for (VmInstanceState state : vmStates.values()) {
if (state != VmInstanceState.Running && state != VmInstanceState.Stopped && state != VmInstanceState.Paused) {
throw new CloudRuntimeException(String.format("host can only report vm state as Running, Stopped or Paused, got %s", state));
}
}
if (!CoreGlobalProperty.VM_TRACER_ON) {
logger.debug(String.format("vm tracer is off, skip reporting vm state on host[uuid:%s]", hostUuid));
return;
}
thdf.syncSubmit(new SyncTask<Object>() {
@Override
public String getSyncSignature() {
return String.format("trace-vm-state-on-host-%s", hostUuid);
}
@Override
public int getSyncLevel() {
return 1;
}
@Override
public String getName() {
return String.format("trace-vm-state-on-host-%s", hostUuid);
}
@Override
public Object call() throws Exception {
Tracer t = new Tracer();
t.hostUuid = hostUuid;
t.hostSideStates = vmStates;
t.trace();
return null;
}
});
}
}