package org.zstack.network.service.lb;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
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.exception.CloudRuntimeException;
import org.zstack.header.message.APICreateMessage;
import org.zstack.header.message.APIMessage;
import org.zstack.network.service.vip.VipVO;
import org.zstack.network.service.vip.VipVO_;
import org.zstack.tag.PatternedSystemTag;
import org.zstack.utils.DebugUtils;
import static org.zstack.core.Platform.argerr;
import static org.zstack.core.Platform.operr;
import javax.persistence.TypedQuery;
import java.util.List;
import static org.zstack.utils.CollectionDSL.e;
import static org.zstack.utils.CollectionDSL.map;
/**
* Created by frank on 8/12/2015.
*/
public class LoadBalancerApiInterceptor implements ApiMessageInterceptor {
@Autowired
private DatabaseFacade dbf;
@Autowired
private ErrorFacade errf;
@Override
public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionException {
if (msg instanceof APIDeleteLoadBalancerListenerMsg) {
validate((APIDeleteLoadBalancerListenerMsg)msg);
} else if (msg instanceof APICreateLoadBalancerListenerMsg) {
validate((APICreateLoadBalancerListenerMsg) msg);
} else if (msg instanceof APIAddVmNicToLoadBalancerMsg) {
validate((APIAddVmNicToLoadBalancerMsg) msg);
} else if (msg instanceof APICreateLoadBalancerMsg) {
validate((APICreateLoadBalancerMsg) msg);
} else if (msg instanceof APIRemoveVmNicFromLoadBalancerMsg) {
validate((APIRemoveVmNicFromLoadBalancerMsg) msg);
} else if (msg instanceof APIGetCandidateVmNicsForLoadBalancerMsg) {
validate((APIGetCandidateVmNicsForLoadBalancerMsg) msg);
}
return msg;
}
private void validate(APIGetCandidateVmNicsForLoadBalancerMsg msg) {
SimpleQuery<LoadBalancerListenerVO> lq = dbf.createQuery(LoadBalancerListenerVO.class);
lq.select(LoadBalancerListenerVO_.loadBalancerUuid);
lq.add(LoadBalancerListenerVO_.uuid, Op.EQ, msg.getListenerUuid());
String lbuuid = lq.findValue();
msg.setLoadBalancerUuid(lbuuid);
}
private void validate(APIRemoveVmNicFromLoadBalancerMsg msg) {
SimpleQuery<LoadBalancerListenerVO> lq = dbf.createQuery(LoadBalancerListenerVO.class);
lq.select(LoadBalancerListenerVO_.loadBalancerUuid);
lq.add(LoadBalancerListenerVO_.uuid, Op.EQ, msg.getListenerUuid());
String lbuuid = lq.findValue();
msg.setLoadBalancerUuid(lbuuid);
SimpleQuery<LoadBalancerListenerVmNicRefVO> q = dbf.createQuery(LoadBalancerListenerVmNicRefVO.class);
q.select(LoadBalancerListenerVmNicRefVO_.vmNicUuid);
q.add(LoadBalancerListenerVmNicRefVO_.vmNicUuid, Op.IN, msg.getVmNicUuids());
q.add(LoadBalancerListenerVmNicRefVO_.listenerUuid, Op.EQ, msg.getListenerUuid());
List<String> vmNicUuids = q.listValue();
if (vmNicUuids.isEmpty()) {
APIRemoveVmNicFromLoadBalancerEvent evt = new APIRemoveVmNicFromLoadBalancerEvent(msg.getId());
evt.setInventory(LoadBalancerInventory.valueOf(dbf.findByUuid(lbuuid, LoadBalancerVO.class)));
throw new StopRoutingException();
}
}
private void validate(APICreateLoadBalancerMsg msg) {
SimpleQuery<VipVO> q = dbf.createQuery(VipVO.class);
q.select(VipVO_.useFor);
q.add(VipVO_.uuid, Op.EQ, msg.getVipUuid());
String useFor = q.findValue();
if (LoadBalancerConstants.LB_NETWORK_SERVICE_TYPE_STRING.equals(useFor)) {
SimpleQuery<LoadBalancerVO> lq = dbf.createQuery(LoadBalancerVO.class);
lq.select(LoadBalancerVO_.uuid);
lq.add(LoadBalancerVO_.vipUuid, Op.EQ, msg.getVipUuid());
String lbuuid = lq.findValue();
throw new ApiMessageInterceptionException(argerr("the vip[uuid:%s] is occupied by another load balancer[uuid:%s]", msg.getVipUuid(), lbuuid));
}
if (useFor != null) {
throw new ApiMessageInterceptionException(argerr("the vip[uuid:%s] is occupied by another service[%s]", msg.getVipUuid(), useFor));
}
}
@Transactional(readOnly = true)
private void validate(APIAddVmNicToLoadBalancerMsg msg) {
String sql = "select nic.l3NetworkUuid from VmNicVO nic where nic.uuid in (:uuids) group by nic.l3NetworkUuid";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("uuids", msg.getVmNicUuids());
List<String> l3Uuids = q.getResultList();
DebugUtils.Assert(!l3Uuids.isEmpty(), "cannot find the l3Network");
if (l3Uuids.size() > 1) {
throw new ApiMessageInterceptionException(argerr("vm nics[uuids:%s] are not on the same L3 network. they are on L3 networks[uuids:%s]", msg.getVmNicUuids(), l3Uuids));
}
String l3Uuid = l3Uuids.get(0);
sql = "select ref.l3NetworkUuid from NetworkServiceL3NetworkRefVO ref where ref.l3NetworkUuid = :uuid and ref.networkServiceType = :ntype";
q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("uuid", l3Uuid);
q.setParameter("ntype", LoadBalancerConstants.LB_NETWORK_SERVICE_TYPE_STRING);
if (q.getResultList().isEmpty()) {
throw new ApiMessageInterceptionException(operr("the L3 network[uuid:%s] of the vm nics has no network service[%s] enabled", l3Uuid, LoadBalancerConstants.LB_NETWORK_SERVICE_TYPE_STRING));
}
sql = "select ref.vmNicUuid from LoadBalancerListenerVmNicRefVO ref where ref.vmNicUuid in (:nicUuids) and ref.listenerUuid = :uuid";
q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("nicUuids", msg.getVmNicUuids());
q.setParameter("uuid", msg.getListenerUuid());
List<String> existingNics = q.getResultList();
if (!existingNics.isEmpty()) {
throw new ApiMessageInterceptionException(operr("the vm nics[uuid:%s] are already on the load balancer listener[uuid:%s]", existingNics, msg.getListenerUuid()));
}
sql = "select l.loadBalancerUuid from LoadBalancerListenerVO l where l.uuid = :uuid";
q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("uuid", msg.getListenerUuid());
msg.setLoadBalancerUuid(q.getSingleResult());
}
private boolean hasTag(APICreateMessage msg, PatternedSystemTag tag) {
if (msg.getSystemTags() == null) {
return false;
}
for (String t : msg.getSystemTags()) {
if (tag.isMatch(t)) {
return true;
}
}
return false;
}
private void insertTagIfNotExisting(APICreateMessage msg, PatternedSystemTag tag, String value) {
if (!hasTag(msg, tag)) {
msg.addSystemTag(value);
}
}
private void validate(APICreateLoadBalancerListenerMsg msg) {
if (msg.getInstancePort() == null) {
msg.setInstancePort(msg.getLoadBalancerPort());
}
if (msg.getProtocol() == null) {
msg.setProtocol(LoadBalancerConstants.LB_PROTOCOL_TCP);
}
insertTagIfNotExisting(
msg, LoadBalancerSystemTags.CONNECTION_IDLE_TIMEOUT,
LoadBalancerSystemTags.CONNECTION_IDLE_TIMEOUT.instantiateTag(
map(e(LoadBalancerSystemTags.CONNECTION_IDLE_TIMEOUT_TOKEN, LoadBalancerGlobalConfig.CONNECTION_IDLE_TIMEOUT.value(Long.class)))
)
);
insertTagIfNotExisting(
msg, LoadBalancerSystemTags.HEALTHY_THRESHOLD,
LoadBalancerSystemTags.HEALTHY_THRESHOLD.instantiateTag(
map(e(LoadBalancerSystemTags.HEALTHY_THRESHOLD_TOKEN, LoadBalancerGlobalConfig.HEALTHY_THRESHOLD.value(Long.class)))
)
);
insertTagIfNotExisting(
msg, LoadBalancerSystemTags.HEALTH_INTERVAL,
LoadBalancerSystemTags.HEALTH_INTERVAL.instantiateTag(
map(e(LoadBalancerSystemTags.HEALTH_INTERVAL_TOKEN, LoadBalancerGlobalConfig.HEALTH_INTERVAL.value(Long.class)))
)
);
insertTagIfNotExisting(
msg, LoadBalancerSystemTags.HEALTH_TARGET,
LoadBalancerSystemTags.HEALTH_TARGET.instantiateTag(
map(e(LoadBalancerSystemTags.HEALTH_TARGET_TOKEN, LoadBalancerGlobalConfig.HEALTH_TARGET.value()))
)
);
insertTagIfNotExisting(
msg, LoadBalancerSystemTags.HEALTH_TIMEOUT,
LoadBalancerSystemTags.HEALTH_TIMEOUT.instantiateTag(
map(e(LoadBalancerSystemTags.HEALTH_TIMEOUT_TOKEN, LoadBalancerGlobalConfig.HEALTH_TIMEOUT.value(Long.class)))
)
);
insertTagIfNotExisting(
msg, LoadBalancerSystemTags.UNHEALTHY_THRESHOLD,
LoadBalancerSystemTags.UNHEALTHY_THRESHOLD.instantiateTag(
map(e(LoadBalancerSystemTags.UNHEALTHY_THRESHOLD_TOKEN, LoadBalancerGlobalConfig.UNHEALTHY_THRESHOLD.value(Long.class)))
)
);
insertTagIfNotExisting(
msg, LoadBalancerSystemTags.MAX_CONNECTION,
LoadBalancerSystemTags.MAX_CONNECTION.instantiateTag(
map(e(LoadBalancerSystemTags.MAX_CONNECTION_TOKEN, LoadBalancerGlobalConfig.MAX_CONNECTION.value(Long.class)))
)
);
insertTagIfNotExisting(
msg, LoadBalancerSystemTags.BALANCER_ALGORITHM,
LoadBalancerSystemTags.BALANCER_ALGORITHM.instantiateTag(
map(e(LoadBalancerSystemTags.BALANCER_ALGORITHM_TOKEN, LoadBalancerGlobalConfig.BALANCER_ALGORITHM.value()))
)
);
SimpleQuery<LoadBalancerListenerVO> q = dbf.createQuery(LoadBalancerListenerVO.class);
q.select(LoadBalancerListenerVO_.uuid);
q.add(LoadBalancerListenerVO_.loadBalancerPort, Op.EQ, msg.getLoadBalancerPort());
q.add(LoadBalancerListenerVO_.loadBalancerUuid, Op.EQ, msg.getLoadBalancerUuid());
String luuid = q.findValue();
if (luuid != null) {
throw new ApiMessageInterceptionException(argerr("conflict loadBalancerPort[%s], a listener[uuid:%s] has used that port", msg.getLoadBalancerPort(), luuid));
}
q = dbf.createQuery(LoadBalancerListenerVO.class);
q.select(LoadBalancerListenerVO_.uuid);
q.add(LoadBalancerListenerVO_.instancePort, Op.EQ, msg.getInstancePort());
q.add(LoadBalancerListenerVO_.loadBalancerUuid, Op.EQ, msg.getLoadBalancerUuid());
luuid = q.findValue();
if (luuid != null) {
throw new ApiMessageInterceptionException(argerr("conflict instancePort[%s], a listener[uuid:%s] has used that port", msg.getInstancePort(), luuid));
}
}
private void validate(APIDeleteLoadBalancerListenerMsg msg) {
SimpleQuery<LoadBalancerListenerVO> q = dbf.createQuery(LoadBalancerListenerVO.class);
q.select(LoadBalancerListenerVO_.loadBalancerUuid);
q.add(LoadBalancerListenerVO_.uuid, Op.EQ, msg.getUuid());
String lbUuid = q.findValue();
if (lbUuid == null) {
throw new CloudRuntimeException(String.format("cannot find load balancer uuid of LoadBalancerListenerVO[uuid:%s]", msg.getUuid()));
}
msg.setLoadBalancerUuid(lbUuid);
}
}