package org.zstack.network.service.virtualrouter.vip;
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.cloudbus.CloudBusCallBack;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.timeout.ApiTimeoutManager;
import org.zstack.core.workflow.FlowChainBuilder;
import org.zstack.header.core.Completion;
import org.zstack.header.core.ReturnValueCompletion;
import org.zstack.header.core.workflow.*;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.OperationFailureException;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.message.Message;
import org.zstack.header.message.MessageReply;
import org.zstack.header.network.l3.L3Network;
import org.zstack.header.network.l3.L3NetworkInventory;
import org.zstack.header.network.l3.L3NetworkVO;
import org.zstack.header.vm.*;
import org.zstack.network.service.vip.*;
import org.zstack.network.service.virtualrouter.*;
import org.zstack.network.service.virtualrouter.vyos.VyosConstants;
import org.zstack.utils.DebugUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import static org.zstack.core.Platform.operr;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.codehaus.groovy.runtime.InvokerHelper.asList;
/**
* Created by xing5 on 2016/11/20.
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class VirtualRouterVipBaseBackend extends VipBaseBackend {
private static final CLogger logger = Utils.getLogger(VirtualRouterVipBaseBackend.class);
@Autowired
protected VirtualRouterManager vrMgr;
@Autowired
private ApiTimeoutManager apiTimeoutManager;
public VirtualRouterVipBaseBackend(VipVO self) {
super(self);
}
@Override
protected void releaseVipOnBackend(Completion completion) {
final VirtualRouterVipVO vrvip = dbf.findByUuid(self.getUuid(), VirtualRouterVipVO.class);
if (vrvip == null) {
completion.success();
return;
}
if (vrvip.getVirtualRouterVmUuid() == null) {
// the vr has been deleted
dbf.remove(vrvip);
completion.success();
return;
}
final VirtualRouterVmVO vrvo = dbf.findByUuid(vrvip.getVirtualRouterVmUuid(), VirtualRouterVmVO.class);
if (vrvo.getState() != VmInstanceState.Running) {
// vr will sync when becomes Running
dbf.remove(vrvip);
completion.success();
return;
}
releaseVipOnVirtualRouterVm(VirtualRouterVmInventory.valueOf(vrvo), asList(getSelfInventory()), new Completion(completion) {
@Override
public void success() {
logger.debug(String.format("successfully released vip[uuid:%s, name:%s, ip:%s] on virtual router vm[uuid:%s]",
self.getUuid(), self.getName(), self.getIp(), vrvo.getUuid()));
dbf.removeByPrimaryKey(vrvip.getUuid(), vrvip.getClass());
completion.success();
}
@Override
public void fail(ErrorCode errorCode) {
logger.warn(errorCode.toString());
// NOTE: we don't know if the failure happens before or after the VIP deleted on the real device,
// for example, a message timeout failure happens after the VIP gets really deleted, but an internal error
// may happen before so. In both cases, we delete the database reference here so next time the backend
// will try to apply the VIP again. It's virtualrouter/vyos's responsibility to succeed if a VIP is applied
// while it exists
dbf.removeByPrimaryKey(vrvip.getUuid(), vrvip.getClass());
completion.fail(errorCode);
}
});
}
private String getOwnerMac(VirtualRouterVmInventory vr, VipInventory vip) {
for (VmNicInventory nic : vr.getVmNics()) {
if (nic.getL3NetworkUuid().equals(vip.getL3NetworkUuid())) {
return nic.getMac();
}
}
throw new CloudRuntimeException(String.format("virtual router vm[uuid:%s] has no nic on l3Network[uuid:%s] for vip[uuid:%s, ip:%s]",
vr.getUuid(), vip.getL3NetworkUuid(), vip.getUuid(), vip.getIp()));
}
private void releaseVipOnVirtualRouterVm(final VirtualRouterVmInventory vr, List<VipInventory> vips, final Completion completion) {
final List<VirtualRouterCommands.VipTO> tos = new ArrayList<VirtualRouterCommands.VipTO>(vips.size());
for (VipInventory vip : vips) {
String mac = getOwnerMac(vr, vip);
VirtualRouterCommands.VipTO to = VirtualRouterCommands.VipTO.valueOf(vip, mac);
tos.add(to);
}
VirtualRouterCommands.RemoveVipCmd cmd = new VirtualRouterCommands.RemoveVipCmd();
cmd.setVips(tos);
VirtualRouterAsyncHttpCallMsg msg = new VirtualRouterAsyncHttpCallMsg();
msg.setPath(VirtualRouterConstant.VR_REMOVE_VIP);
msg.setCommand(cmd);
msg.setCommandTimeout(apiTimeoutManager.getTimeout(cmd.getClass(), "30m"));
msg.setVmInstanceUuid(vr.getUuid());
bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, vr.getUuid());
bus.send(msg, new CloudBusCallBack(completion) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
completion.fail(reply.getError());
return;
}
VirtualRouterAsyncHttpCallReply re = reply.castReply();
VirtualRouterCommands.RemoveVipRsp ret = re.toResponse(VirtualRouterCommands.RemoveVipRsp.class);
if (ret.isSuccess()) {
completion.success();
} else {
ErrorCode err = operr("failed to remove vip%s, because %s", tos, ret.getError());
completion.fail(err);
}
}
});
}
public void createVipOnVirtualRouterVm(final VirtualRouterVmInventory vr, List<VipInventory> vips, final Completion completion) {
final List<VirtualRouterCommands.VipTO> tos = new ArrayList<VirtualRouterCommands.VipTO>(vips.size());
for (VipInventory vip : vips) {
String mac = getOwnerMac(vr, vip);
VirtualRouterCommands.VipTO to = VirtualRouterCommands.VipTO.valueOf(vip, mac);
tos.add(to);
}
VirtualRouterCommands.CreateVipCmd cmd = new VirtualRouterCommands.CreateVipCmd();
cmd.setVips(tos);
VirtualRouterAsyncHttpCallMsg msg = new VirtualRouterAsyncHttpCallMsg();
msg.setVmInstanceUuid(vr.getUuid());
msg.setCommand(cmd);
msg.setCommandTimeout(apiTimeoutManager.getTimeout(cmd.getClass(), "30m"));
msg.setPath(VirtualRouterConstant.VR_CREATE_VIP);
bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, vr.getUuid());
bus.send(msg, new CloudBusCallBack(completion) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
completion.fail(reply.getError());
return;
}
VirtualRouterAsyncHttpCallReply re = reply.castReply();
VirtualRouterCommands.CreateVipRsp ret = re.toResponse(VirtualRouterCommands.CreateVipRsp.class);
if (!ret.isSuccess()) {
ErrorCode err = operr("failed to create vip%s on virtual router[uuid:%s], because %s", tos, vr.getUuid(), ret.getError());
completion.fail(err);
} else {
completion.success();
}
}
});
}
@Override
protected void acquireVipOnBackend(Completion completion) {
VirtualRouterVipVO vipvo = dbf.findByUuid(self.getUuid(), VirtualRouterVipVO.class);
if (vipvo != null) {
SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class);
q.select(VmInstanceVO_.state);
q.add(VmInstanceVO_.uuid, SimpleQuery.Op.EQ, vipvo.getVirtualRouterVmUuid());
VmInstanceState vrState = q.findValue();
if (VmInstanceState.Running != vrState) {
completion.fail(operr("virtual router[uuid:%s, state:%s] is not running",
vipvo.getVirtualRouterVmUuid(), vrState));
} else {
completion.success();
}
return;
}
FlowChain chain = FlowChainBuilder.newSimpleFlowChain();
chain.setName(String.format("prepare-vr-for-vip-%s-%s", self.getUuid(), self.getIp()));
chain.then(new NoRollbackFlow() {
@Override
public void run(final FlowTrigger trigger, final Map data) {
DebugUtils.Assert(self.getPeerL3NetworkUuid() != null, "peerL3NetworkUuid cannot be null");
VirtualRouterStruct s = new VirtualRouterStruct();
s.setL3Network(L3NetworkInventory.valueOf(dbf.findByUuid(self.getPeerL3NetworkUuid(), L3NetworkVO.class)));
String appVmType;
if (self.getServiceProvider().equals(VirtualRouterConstant.VIRTUAL_ROUTER_PROVIDER_TYPE)) {
appVmType = VirtualRouterConstant.VIRTUAL_ROUTER_VM_TYPE;
} else if (self.getServiceProvider().equals(VyosConstants.VYOS_ROUTER_PROVIDER_TYPE)) {
appVmType = VyosConstants.VYOS_VM_TYPE;
} else {
throw new CloudRuntimeException(String.format("unknown network service provider type[%s]", self.getServiceProvider()));
}
s.setApplianceVmType(appVmType);
s.setProviderType(self.getServiceProvider());
s.setOfferingValidator(offering -> {
if (!offering.getPublicNetworkUuid().equals(self.getL3NetworkUuid())) {
throw new OperationFailureException(operr("found a virtual router offering[uuid:%s] for L3Network[uuid:%s] in zone[uuid:%s]; however, the network's public network[uuid:%s] is not the same to VIP[uuid:%s]'s; you may need to use system tag" +
" guestL3Network::l3NetworkUuid to specify a particular virtual router offering for the L3Network",
offering.getUuid(), s.getL3Network().getUuid(), s.getL3Network().getZoneUuid(),
self.getL3NetworkUuid(), self.getUuid()));
}
});
vrMgr.acquireVirtualRouterVm(s, new ReturnValueCompletion<VirtualRouterVmInventory>(trigger){
@Override
public void success(VirtualRouterVmInventory returnValue) {
data.put(VirtualRouterConstant.Param.VR.toString(), returnValue);
trigger.next();
}
@Override
public void fail(ErrorCode errorCode) {
trigger.fail(errorCode);
}
});
}
}).then(new NoRollbackFlow() {
@Override
public void run(final FlowTrigger trigger, Map data) {
final VirtualRouterVmInventory vr = (VirtualRouterVmInventory) data.get(VirtualRouterConstant.Param.VR.toString());
createVipOnVirtualRouterVm(vr, Arrays.asList(getSelfInventory()), new Completion(trigger) {
@Override
public void success() {
trigger.next();
}
@Override
public void fail(ErrorCode errorCode) {
trigger.fail(errorCode);
}
});
}
}).done(new FlowDoneHandler(completion) {
@Override
public void handle(Map data) {
final VirtualRouterVmInventory vr = (VirtualRouterVmInventory) data.get(VirtualRouterConstant.Param.VR.toString());
if (!dbf.isExist(self.getUuid(), VirtualRouterVipVO.class)) {
VirtualRouterVipVO vrvip = new VirtualRouterVipVO();
vrvip.setUuid(self.getUuid());
vrvip.setVirtualRouterVmUuid(vr.getUuid());
dbf.persist(vrvip);
}
completion.success();
}
}).error(new FlowErrorHandler(completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
completion.fail(errCode);
}
}).start();
}
@Override
protected void handleBackendSpecificMessage(Message msg) {
bus.dealWithUnknownMessage(msg);
}
}