package org.zstack.network.service.virtualrouter;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.zstack.appliancevm.*;
import org.zstack.appliancevm.ApplianceVmConstant.Params;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.core.thread.ChainTask;
import org.zstack.core.thread.SyncTaskChain;
import org.zstack.header.core.Completion;
import org.zstack.header.core.workflow.*;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.OperationFailureException;
import org.zstack.header.errorcode.SysErrors;
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.Message;
import org.zstack.header.rest.JsonAsyncRESTCallback;
import org.zstack.header.rest.RESTFacade;
import org.zstack.header.vm.VmInstanceInventory;
import org.zstack.header.vm.VmInstanceState;
import org.zstack.network.service.virtualrouter.VirtualRouterCommands.PingCmd;
import org.zstack.network.service.virtualrouter.VirtualRouterCommands.PingRsp;
import org.zstack.network.service.virtualrouter.VirtualRouterConstant.Param;
import static org.zstack.core.Platform.operr;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class VirtualRouter extends ApplianceVmBase {
static {
allowedOperations.addState(VmInstanceState.Running, APIReconnectVirtualRouterMsg.class.getName());
allowedOperations.addState(VmInstanceState.Running, ReconnectVirtualRouterVmMsg.class.getName());
}
@Autowired
protected VirtualRouterManager vrMgr;
@Autowired
protected RESTFacade restf;
@Autowired
protected ErrorFacade errf;
protected VirtualRouterVmInventory vr;
public VirtualRouter(ApplianceVmVO vo) {
super(vo);
}
public VirtualRouter(VirtualRouterVmVO vo) {
super(vo);
vr = new VirtualRouterVmInventory(vo);
}
@Override
protected VmInstanceInventory getSelfInventory() {
return VirtualRouterVmInventory.valueOf(getSelf());
}
@Override
protected List<Flow> getPostCreateFlows() {
return vrMgr.getPostCreateFlows();
}
@Override
protected List<Flow> getPostStartFlows() {
return vrMgr.getPostStartFlows();
}
@Override
protected List<Flow> getPostStopFlows() {
return vrMgr.getPostStopFlows();
}
@Override
protected List<Flow> getPostRebootFlows() {
return vrMgr.getPostRebootFlows();
}
@Override
protected List<Flow> getPostDestroyFlows() {
return vrMgr.getPostDestroyFlows();
}
@Override
protected List<Flow> getPostMigrateFlows() {
return vrMgr.getPostMigrateFlows();
}
protected FlowChain getReconnectChain() {
return vrMgr.getReconnectFlowChain();
}
@Override
protected void handleApiMessage(APIMessage msg) {
if (msg instanceof APIReconnectVirtualRouterMsg) {
handle((APIReconnectVirtualRouterMsg) msg);
} else {
super.handleApiMessage(msg);
}
}
@Override
protected void handleLocalMessage(Message msg) {
if (msg instanceof VirtualRouterAsyncHttpCallMsg) {
handle((VirtualRouterAsyncHttpCallMsg) msg);
} else if (msg instanceof ReconnectVirtualRouterVmMsg) {
handle((ReconnectVirtualRouterVmMsg) msg);
} else if (msg instanceof PingVirtualRouterVmMsg) {
handle((PingVirtualRouterVmMsg) msg);
} else {
super.handleLocalMessage(msg);
}
}
private void handle(final PingVirtualRouterVmMsg msg) {
thdf.chainSubmit(new ChainTask(msg) {
@Override
public String getSyncSignature() {
return syncThreadName;
}
@Override
public void run(final SyncTaskChain chain) {
final PingVirtualRouterVmReply reply = new PingVirtualRouterVmReply();
if (VmInstanceState.Running != self.getState() || ApplianceVmStatus.Connecting == getSelf().getStatus()) {
reply.setDoReconnect(false);
bus.reply(msg, reply);
chain.next();
return;
}
PingCmd cmd = new PingCmd();
cmd.setUuid(self.getUuid());
restf.asyncJsonPost(buildUrl(vr.getManagementNic().getIp(), VirtualRouterConstant.VR_PING), cmd, new JsonAsyncRESTCallback<PingRsp>(msg, chain) {
@Override
public void fail(ErrorCode err) {
reply.setDoReconnect(true);
reply.setConnected(false);
logger.warn(String.format("failed to ping the virtual router vm[uuid:%s], %s. We will reconnect it soon", self.getUuid(), reply.getError()));
bus.reply(msg, reply);
chain.next();
}
@Override
public void success(PingRsp ret) {
reply.setDoReconnect(true);
if (!ret.isSuccess()) {
logger.warn(String.format("failed to ping the virtual router vm[uuid:%s], %s. We will reconnect it soon", self.getUuid(), ret.getError()));
reply.setConnected(false);
} else {
boolean connected = self.getUuid().equals(ret.getUuid());
if (!connected) {
logger.warn(String.format("a signature lost on the virtual router vm[uuid:%s] changed, it's probably caused by the agent restart. We will issue a reconnect soon", self.getUuid()));
}
reply.setConnected(connected);
}
bus.reply(msg, reply);
chain.next();
}
@Override
public Class<PingRsp> getReturnClass() {
return PingRsp.class;
}
});
}
@Override
public String getName() {
return "ping-virtual-router";
}
});
}
private void handle(final ReconnectVirtualRouterVmMsg msg) {
thdf.chainSubmit(new ChainTask(msg) {
@Override
public String getSyncSignature() {
return syncThreadName;
}
@Override
public void run(final SyncTaskChain chain) {
final ReconnectVirtualRouterVmReply reply = new ReconnectVirtualRouterVmReply();
refreshVO();
ErrorCode allowed = validateOperationByState(msg, self.getState(), SysErrors.OPERATION_ERROR);
if (allowed != null) {
reply.setError(allowed);
bus.reply(msg, reply);
chain.next();
return;
}
reconnect(new Completion(msg, chain) {
@Override
public void success() {
bus.reply(msg, reply);
chain.next();
}
@Override
public void fail(ErrorCode errorCode) {
reply.setError(errorCode);
bus.reply(msg, reply);
chain.next();
}
});
}
@Override
public String getName() {
return String.format("reconnect-virtual-router-%s", self.getUuid());
}
});
}
protected String buildUrl(String mgmtIp, String path) {
return vrMgr.buildUrl(mgmtIp, path);
}
private void handle(final VirtualRouterAsyncHttpCallMsg msg) {
thdf.chainSubmit(new ChainTask(msg) {
@Override
public String getSyncSignature() {
return String.format("%s-commands", syncThreadName);
}
@Override
public void run(final SyncTaskChain chain) {
refreshVO();
final VirtualRouterAsyncHttpCallReply reply = new VirtualRouterAsyncHttpCallReply();
if (msg.isCheckStatus() && getSelf().getState() != VmInstanceState.Running) {
throw new OperationFailureException(operr("the virtual router[name:%s, uuid:%s, current state:%s] is not running," +
"and cannot perform required operation. Please retry your operation later once it is running", self.getName(), self.getUuid(), self.getState()));
}
if (msg.isCheckStatus() && getSelf().getStatus() != ApplianceVmStatus.Connected) {
throw new OperationFailureException(operr("virtual router[uuid:%s] is in status of %s that cannot make http call to %s",
self.getUuid(), getSelf().getStatus(), msg.getPath()));
}
restf.asyncJsonPost(buildUrl(vr.getManagementNic().getIp(), msg.getPath()), msg.getCommand(), new JsonAsyncRESTCallback<LinkedHashMap>(msg, chain) {
@Override
public void fail(ErrorCode err) {
reply.setError(err);
bus.reply(msg, reply);
chain.next();
}
@Override
public void success(LinkedHashMap ret) {
reply.setResponse(ret);
bus.reply(msg, reply);
chain.next();
}
@Override
public Class<LinkedHashMap> getReturnClass() {
return LinkedHashMap.class;
}
}, TimeUnit.SECONDS, msg.getCommandTimeout());
}
@Override
protected int getSyncLevel() {
return vrMgr.getParallelismDegree(self.getUuid());
}
@Override
public String getName() {
return getSyncSignature();
}
});
}
private void handle(final APIReconnectVirtualRouterMsg msg) {
thdf.chainSubmit(new ChainTask(msg) {
@Override
public String getSyncSignature() {
return syncThreadName;
}
@Override
public void run(final SyncTaskChain chain) {
final APIReconnectVirtualRouterEvent evt = new APIReconnectVirtualRouterEvent(msg.getId());
refreshVO();
ErrorCode allowed = validateOperationByState(msg, self.getState(), SysErrors.OPERATION_ERROR);
if (allowed != null) {
evt.setError(allowed);
bus.publish(evt);
chain.next();
return;
}
reconnect(new Completion(msg, chain) {
@Override
public void success() {
evt.setInventory((ApplianceVmInventory) getSelfInventory());
bus.publish(evt);
chain.next();
}
@Override
public void fail(ErrorCode errorCode) {
evt.setError(errorCode);
bus.publish(evt);
chain.next();
}
});
}
@Override
public String getName() {
return String.format("reconnect-virtual-router-%s", self.getUuid());
}
});
}
private void reconnect(final Completion completion) {
FlowChain chain = getReconnectChain();
chain.setName(String.format("reconnect-virtual-router-%s", self.getUuid()));
chain.getData().put(VirtualRouterConstant.Param.VR.toString(), vr);
chain.getData().put(Param.IS_RECONNECT.toString(), Boolean.TRUE.toString());
chain.getData().put(Params.isReconnect.toString(), Boolean.TRUE.toString());
chain.getData().put(Params.managementNicIp.toString(), vr.getManagementNic().getIp());
chain.getData().put(Params.applianceVmUuid.toString(), self.getUuid());
SimpleQuery<ApplianceVmFirewallRuleVO> q = dbf.createQuery(ApplianceVmFirewallRuleVO.class);
q.add(ApplianceVmFirewallRuleVO_.applianceVmUuid, Op.EQ, getSelf().getUuid());
List<ApplianceVmFirewallRuleVO> vos = q.list();
List<ApplianceVmFirewallRuleInventory> rules = ApplianceVmFirewallRuleInventory.valueOf(vos);
chain.getData().put(ApplianceVmConstant.Params.applianceVmFirewallRules.toString(), rules);
chain.insert(new Flow() {
String __name__ = "change-appliancevm-status-to-connecting";
@Override
public void run(FlowTrigger trigger, Map data) {
getSelf().setStatus(ApplianceVmStatus.Connecting);
self = dbf.updateAndRefresh(self);
trigger.next();
}
@Override
public void rollback(FlowRollback trigger, Map data) {
self = dbf.reload(self);
getSelf().setStatus(ApplianceVmStatus.Disconnected);
self = dbf.updateAndRefresh(self);
trigger.rollback();
}
}).then(new NoRollbackFlow() {
String __name__ = "change-appliancevm-status-to-connected";
@Override
public void run(FlowTrigger trigger, Map data) {
getSelf().setStatus(ApplianceVmStatus.Connected);
self = dbf.updateAndRefresh(self);
trigger.next();
}
}).done(new FlowDoneHandler(completion) {
@Override
public void handle(Map data) {
self = dbf.reload(self);
completion.success();
}
}).error(new FlowErrorHandler(completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
completion.fail(errCode);
}
}).start();
}
}