package org.zstack.network.service.lb;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.CloudBusCallBack;
import org.zstack.core.cloudbus.CloudBusListCallBack;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.core.workflow.FlowChainBuilder;
import org.zstack.header.core.Completion;
import org.zstack.header.core.NoErrorCompletion;
import org.zstack.header.core.workflow.*;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.message.MessageReply;
import org.zstack.header.message.NeedReplyMessage;
import org.zstack.header.network.service.NetworkServiceType;
import org.zstack.header.vm.VmInstanceConstant.VmOperation;
import org.zstack.header.vm.VmInstanceSpec;
import org.zstack.header.vm.VmNicInventory;
import org.zstack.network.service.AbstractNetworkServiceExtension;
import org.zstack.network.service.vip.VipInventory;
import org.zstack.network.service.vip.VipReleaseExtensionPoint;
import org.zstack.utils.CollectionUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.function.Function;
import org.zstack.utils.logging.CLogger;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Created by frank on 8/13/2015.
*/
public class LoadBalancerExtension extends AbstractNetworkServiceExtension implements VipReleaseExtensionPoint {
private static final CLogger logger = Utils.getLogger(LoadBalancerExtension.class);
@Autowired
private DatabaseFacade dbf;
@Autowired
private CloudBus bus;
@Override
public NetworkServiceType getNetworkServiceType() {
return LoadBalancerConstants.LB_NETWORK_SERVICE_TYPE;
}
@Transactional(readOnly = true)
private List<Tuple> getLbTuple(VmInstanceSpec servedVm) {
String sql = "select l.uuid, l.loadBalancerUuid, ref.vmNicUuid from LoadBalancerListenerVmNicRefVO ref, LoadBalancerListenerVO l where ref.listenerUuid = l.uuid" +
" and ref.vmNicUuid in (:nicUuids)";
TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
q.setParameter("nicUuids", CollectionUtils.transformToList(servedVm.getDestNics(), new Function<String, VmNicInventory>() {
@Override
public String call(VmNicInventory arg) {
return arg.getUuid();
}
}));
return q.getResultList();
}
@Override
public void applyNetworkService(final VmInstanceSpec servedVm, Map<String, Object> data, final Completion completion) {
List<Tuple> ts = getLbTuple(servedVm);
if (ts.isEmpty()) {
completion.success();
return;
}
Map<String, LoadBalancerActiveVmNicMsg> m = new HashMap<String, LoadBalancerActiveVmNicMsg>();
for (Tuple t : ts) {
String listenerUuid = t.get(0, String.class);
String lbUuid = t.get(1, String.class);
String nicUuid = t.get(2, String.class);
LoadBalancerActiveVmNicMsg msg = m.get(listenerUuid);
if (msg == null) {
msg = new LoadBalancerActiveVmNicMsg();
msg.setLoadBalancerUuid(lbUuid);
msg.setListenerUuid(listenerUuid);
msg.setVmNicUuids(new ArrayList<String>());
bus.makeTargetServiceIdByResourceUuid(msg, LoadBalancerConstants.SERVICE_ID, lbUuid);
m.put(listenerUuid, msg);
}
msg.getVmNicUuids().add(nicUuid);
}
FlowChain chain = FlowChainBuilder.newSimpleFlowChain();
chain.setName(String.format("active-nic-for-vm-%s-on-lb", servedVm.getVmInventory().getUuid()));
for (final LoadBalancerActiveVmNicMsg msg : m.values()) {
chain.then(new Flow() {
boolean s = false;
@Override
public void run(final FlowTrigger trigger, Map data) {
bus.send(msg, new CloudBusCallBack(trigger) {
@Override
public void run(MessageReply reply) {
if (reply.isSuccess()) {
s = true;
trigger.next();
} else {
trigger.fail(reply.getError());
}
}
});
}
@Override
public void rollback(final FlowRollback trigger, Map data) {
if (!s) {
LoadBalancerDeactiveVmNicMsg dmsg = new LoadBalancerDeactiveVmNicMsg();
dmsg.setLoadBalancerUuid(msg.getLoadBalancerUuid());
dmsg.setListenerUuid(msg.getListenerUuid());
dmsg.setVmNicUuids(msg.getVmNicUuids());
bus.makeTargetServiceIdByResourceUuid(dmsg, LoadBalancerConstants.SERVICE_ID, msg.getLoadBalancerUuid());
bus.send(dmsg, new CloudBusCallBack(trigger) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
//TODO: add GC
logger.warn(String.format("failed to deactive vm nics[uuids: %s] on the load balancer[uuid:%s]", msg.getVmNicUuids(), msg.getLoadBalancerUuid()));
}
}
});
}
trigger.rollback();
}
});
}
chain.done(new FlowDoneHandler(completion) {
@Override
public void handle(Map data) {
completion.success();
}
}).error(new FlowErrorHandler(completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
completion.fail(errCode);
}
}).start();
}
@Override
public void releaseNetworkService(VmInstanceSpec servedVm, Map<String, Object> data, final NoErrorCompletion completion) {
List<Tuple> ts = getLbTuple(servedVm);
if (ts.isEmpty()) {
completion.done();
return;
}
class Triplet {
String lbUuid;
String listenerUuid;
List<String> vmNicUuids;
}
Map<String, Triplet> mt = new HashMap<String, Triplet>();
for (Tuple t : ts) {
String listenerUuid = t.get(0, String.class);
Triplet tr = mt.get(listenerUuid);
if (tr == null) {
tr = new Triplet();
tr.listenerUuid = listenerUuid;
tr.lbUuid = t.get(1, String.class);
tr.vmNicUuids = new ArrayList<String>();
mt.put(listenerUuid, tr);
}
tr.vmNicUuids.add(t.get(2, String.class));
}
List<NeedReplyMessage> msgs = new ArrayList<NeedReplyMessage>();
if (servedVm.getCurrentVmOperation() == VmOperation.Destroy) {
msgs.addAll(CollectionUtils.transformToList(mt.entrySet(), new Function<NeedReplyMessage, Entry<String, Triplet>>() {
@Override
public NeedReplyMessage call(Entry<String, Triplet> arg) {
LoadBalancerRemoveVmNicMsg msg = new LoadBalancerRemoveVmNicMsg();
msg.setVmNicUuids(arg.getValue().vmNicUuids);
msg.setListenerUuid(arg.getValue().listenerUuid);
msg.setLoadBalancerUuid(arg.getValue().lbUuid);
bus.makeTargetServiceIdByResourceUuid(msg, LoadBalancerConstants.SERVICE_ID, arg.getKey());
return msg;
}
}));
} else {
msgs.addAll(CollectionUtils.transformToList(mt.entrySet(), new Function<LoadBalancerDeactiveVmNicMsg, Entry<String, Triplet>>() {
@Override
public LoadBalancerDeactiveVmNicMsg call(Entry<String, Triplet> arg) {
LoadBalancerDeactiveVmNicMsg msg = new LoadBalancerDeactiveVmNicMsg();
msg.setVmNicUuids(arg.getValue().vmNicUuids);
msg.setListenerUuid(arg.getValue().listenerUuid);
msg.setLoadBalancerUuid(arg.getValue().lbUuid);
bus.makeTargetServiceIdByResourceUuid(msg, LoadBalancerConstants.SERVICE_ID, arg.getKey());
return msg;
}
}));
}
bus.send(msgs, new CloudBusListCallBack(completion) {
@Override
public void run(List<MessageReply> replies) {
for (MessageReply r : replies) {
if (!r.isSuccess()) {
//TODO
logger.warn(String.format("failed to deactive a vm nic on lb, need a cleanup, %s", r.getError()));
}
}
completion.done();
}
});
}
@Override
public String getVipUse() {
return LoadBalancerConstants.LB_NETWORK_SERVICE_TYPE_STRING;
}
@Override
public void releaseServicesOnVip(VipInventory vip, final Completion completion) {
SimpleQuery<LoadBalancerVO> q = dbf.createQuery(LoadBalancerVO.class);
q.select(LoadBalancerVO_.uuid);
q.add(LoadBalancerVO_.vipUuid, Op.EQ, vip.getUuid());
String lbUuid = q.findValue();
if (lbUuid == null) {
completion.success();
return;
}
DeleteLoadBalancerOnlyMsg msg = new DeleteLoadBalancerOnlyMsg();
msg.setLoadBalancerUuid(lbUuid);
bus.makeTargetServiceIdByResourceUuid(msg, LoadBalancerConstants.SERVICE_ID, lbUuid);
bus.send(msg, new CloudBusCallBack(completion) {
@Override
public void run(MessageReply reply) {
if (reply.isSuccess()) {
completion.success();
} else {
completion.fail(reply.getError());
}
}
});
}
}