package org.zstack.network.service.eip;
import org.apache.commons.net.util.SubnetUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.header.apimediator.ApiMessageInterceptionException;
import org.zstack.header.apimediator.ApiMessageInterceptor;
import org.zstack.header.apimediator.StopRoutingException;
import org.zstack.header.errorcode.SysErrors;
import org.zstack.header.message.APIMessage;
import org.zstack.header.vm.VmNicVO;
import org.zstack.header.vm.VmNicVO_;
import org.zstack.network.service.vip.VipState;
import org.zstack.network.service.vip.VipVO;
import static org.zstack.core.Platform.argerr;
import static org.zstack.core.Platform.operr;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import java.util.concurrent.Callable;
/**
*/
public class EipApiInterceptor implements ApiMessageInterceptor {
@Autowired
private DatabaseFacade dbf;
@Autowired
private ErrorFacade errf;
@Autowired
private CloudBus bus;
private void setServiceId(APIMessage msg) {
if (msg instanceof EipMessage) {
EipMessage emsg = (EipMessage) msg;
bus.makeTargetServiceIdByResourceUuid(msg, EipConstant.SERVICE_ID, emsg.getEipUuid());
}
}
@Override
public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionException {
if (msg instanceof APICreateEipMsg) {
validate((APICreateEipMsg) msg);
} else if (msg instanceof APIDeleteEipMsg) {
validate((APIDeleteEipMsg) msg);
} else if (msg instanceof APIDetachEipMsg) {
validate((APIDetachEipMsg) msg);
} else if (msg instanceof APIAttachEipMsg) {
validate((APIAttachEipMsg) msg);
} else if (msg instanceof APIGetEipAttachableVmNicsMsg) {
validate((APIGetEipAttachableVmNicsMsg) msg);
}
setServiceId(msg);
return msg;
}
private void validate(APIGetEipAttachableVmNicsMsg msg) {
if (msg.getVipUuid() == null && msg.getEipUuid() == null) {
throw new ApiMessageInterceptionException(argerr("either eipUuid or vipUuid must be set"));
}
if (msg.getEipUuid() != null) {
SimpleQuery<EipVO> q = dbf.createQuery(EipVO.class);
q.select(EipVO_.state, EipVO_.vmNicUuid);
q.add(EipVO_.uuid, Op.EQ, msg.getEipUuid());
Tuple t = q.findTuple();
EipState state = t.get(0, EipState.class);
if (state != EipState.Enabled) {
throw new ApiMessageInterceptionException(operr("eip[uuid:%s] is not in state of Enabled, cannot get attachable vm nic", msg.getEipUuid()));
}
String vmNicUuid = t.get(1, String.class);
if (vmNicUuid != null) {
throw new ApiMessageInterceptionException(operr("eip[uuid:%s] has attached to vm nic[uuid:%s], cannot get attachable vm nic", msg.getEipUuid(), vmNicUuid));
}
}
}
private void validate(final APIAttachEipMsg msg) {
SimpleQuery<EipVO> q = dbf.createQuery(EipVO.class);
q.select(EipVO_.state, EipVO_.vmNicUuid, EipVO_.vipIp);
q.add(EipVO_.uuid, Op.EQ, msg.getEipUuid());
Tuple t = q.findTuple();
String vmNicUuid = t.get(1, String.class);
if (vmNicUuid != null) {
throw new ApiMessageInterceptionException(operr("eip[uuid:%s] has attached to another vm nic[uuid:%s], can't attach again",
msg.getEipUuid(), vmNicUuid));
}
EipState state = t.get(0, EipState.class);
if (state != EipState.Enabled) {
throw new ApiMessageInterceptionException(operr("eip[uuid: %s] can only be attached when state is %s, current state is %s",
msg.getEipUuid(), EipState.Enabled, state));
}
String vipIp = t.get(2, String.class);
isVipInVmNicSubnet(vipIp, msg.getVmNicUuid());
VipVO vip = new Callable<VipVO>() {
@Override
@Transactional(readOnly = true)
public VipVO call() {
String sql = "select vip" +
" from VipVO vip, EipVO eip" +
" where vip.uuid = eip.vipUuid" +
" and eip.uuid = :eipUuid";
TypedQuery<VipVO> q = dbf.getEntityManager().createQuery(sql, VipVO.class);
q.setParameter("eipUuid", msg.getEipUuid());
return q.getSingleResult();
}
}.call();
VmNicVO nic = dbf.findByUuid(msg.getVmNicUuid(), VmNicVO.class);
if (nic.getL3NetworkUuid().equals(vip.getL3NetworkUuid())) {
throw new ApiMessageInterceptionException(argerr("guest l3Network of vm nic[uuid:%s] and vip l3Network of EIP[uuid:%s] are the same network",
msg.getVmNicUuid(), msg.getEipUuid()));
}
// check if the vm already has a network where the vip comes
checkIfVmAlreadyHasVipNetwork(nic.getVmInstanceUuid(), vip);
}
private void validate(APIDetachEipMsg msg) {
SimpleQuery<EipVO> q = dbf.createQuery(EipVO.class);
q.select(EipVO_.vmNicUuid);
q.add(EipVO_.uuid, Op.EQ, msg.getUuid());
String vmNicUuid = q.findValue();
if (vmNicUuid == null) {
throw new ApiMessageInterceptionException(operr("eip[uuid:%s] has not attached to any vm nic", msg.getUuid()));
}
}
private void validate(APIDeleteEipMsg msg) {
if (!dbf.isExist(msg.getUuid(), EipVO.class)) {
APIDeleteEipEvent evt = new APIDeleteEipEvent(msg.getId());
bus.publish(evt);
throw new StopRoutingException();
}
}
private void isVipInVmNicSubnet(String eipIp, String vmNicUuid) {
SimpleQuery<VmNicVO> q = dbf.createQuery(VmNicVO.class);
q.select(VmNicVO_.gateway, VmNicVO_.netmask);
q.add(VmNicVO_.uuid, Op.EQ, vmNicUuid);
Tuple t = q.findTuple();
String gw = t.get(0, String.class);
String netmask = t.get(1, String.class);
SubnetUtils sub = new SubnetUtils(gw, netmask);
if (sub.getInfo().isInRange(eipIp)) {
throw new ApiMessageInterceptionException(operr("overlap public and private subnets. The subnet of EIP[%s] is an overlap with the subnet[%s/%s]" +
" of the VM nic[uuid:%s].", eipIp, gw, netmask, vmNicUuid));
}
}
@Transactional(readOnly = true)
private void checkIfVmAlreadyHasVipNetwork(String vmUuid, VipVO vip) {
String sql = "select count(*) from VmNicVO nic, VmInstanceVO vm where nic.vmInstanceUuid = vm.uuid" +
" and vm.uuid = :vmUuid and nic.l3NetworkUuid = :vipL3Uuid";
TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class);
q.setParameter("vmUuid", vmUuid);
q.setParameter("vipL3Uuid", vip.getL3NetworkUuid());
Long c = q.getSingleResult();
if (c > 0) {
throw new ApiMessageInterceptionException(argerr("the vm[uuid:%s] that the EIP is about to attach is already on the public network[uuid:%s] from which" +
" the vip[uuid:%s, name:%s, ip:%s] comes", vmUuid, vip.getL3NetworkUuid(), vip.getUuid(), vip.getName(), vip.getIp()));
}
}
private void validate(APICreateEipMsg msg) {
VipVO vip = dbf.findByUuid(msg.getVipUuid(), VipVO.class);
if (vip.getUseFor() != null) {
throw new ApiMessageInterceptionException(operr("vip[uuid:%s] has been occupied other network service entity[%s]", msg.getVipUuid(), vip.getUseFor()));
}
if (vip.getState() != VipState.Enabled) {
throw new ApiMessageInterceptionException(operr("vip[uuid:%s] is not in state[%s], current state is %s", msg.getVipUuid(), VipState.Enabled, vip.getState()));
}
if (msg.getVmNicUuid() != null) {
isVipInVmNicSubnet(vip.getIp(), msg.getVmNicUuid());
SimpleQuery<VmNicVO> nicq = dbf.createQuery(VmNicVO.class);
nicq.add(VmNicVO_.uuid, Op.EQ, msg.getVmNicUuid());
VmNicVO nic = nicq.find();
if (nic.getL3NetworkUuid().equals(vip.getL3NetworkUuid())) {
throw new ApiMessageInterceptionException(argerr("guest l3Network of vm nic[uuid:%s] and vip l3Network of vip[uuid: %s] are the same network", msg.getVmNicUuid(), msg.getVipUuid()));
}
// check if the vm already has a network where the vip comes
checkIfVmAlreadyHasVipNetwork(nic.getVmInstanceUuid(), vip);
}
}
}