package org.zstack.network.service.virtualrouter.lb;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SQLBatch;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.core.workflow.FlowChainBuilder;
import org.zstack.core.workflow.ShareFlow;
import org.zstack.header.core.Completion;
import org.zstack.header.core.workflow.*;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.vm.VmNicInventory;
import org.zstack.header.vm.VmNicVO;
import org.zstack.header.vm.VmNicVO_;
import org.zstack.network.service.lb.*;
import org.zstack.network.service.vip.VipInventory;
import org.zstack.network.service.vip.VipVO;
import org.zstack.network.service.vip.VipVO_;
import org.zstack.network.service.virtualrouter.*;
import org.zstack.network.service.virtualrouter.VirtualRouterConstant.Param;
import org.zstack.network.service.virtualrouter.vip.VirtualRouterVipBackend;
import org.zstack.utils.CollectionUtils;
import org.zstack.utils.function.Function;
import javax.annotation.Nullable;
import javax.persistence.TypedQuery;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
/**
* Created by frank on 8/17/2015.
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class VirtualRouterSyncLbOnStartFlow implements Flow {
@Autowired
private VirtualRouterManager vrMgr;
@Autowired
private DatabaseFacade dbf;
@Autowired
@Qualifier("VirtualRouterLoadBalancerBackend")
private VirtualRouterLoadBalancerBackend bkd;
@Autowired
@Qualifier("VirtualRouterVipBackend")
protected VirtualRouterVipBackend vipExt;
private LoadBalancerStruct makeStruct(LoadBalancerVO vo) {
LoadBalancerStruct struct = new LoadBalancerStruct();
struct.setLb(LoadBalancerInventory.valueOf(vo));
List<String> activeNicUuids = new ArrayList<String>();
for (LoadBalancerListenerVO l : vo.getListeners()) {
activeNicUuids.addAll(CollectionUtils.transformToList(l.getVmNicRefs(), new Function<String, LoadBalancerListenerVmNicRefVO>() {
@Override
public String call(LoadBalancerListenerVmNicRefVO arg) {
return arg.getStatus() == LoadBalancerVmNicStatus.Active || arg.getStatus() == LoadBalancerVmNicStatus.Pending ? arg.getVmNicUuid() : null;
}
}));
}
if (activeNicUuids.isEmpty()) {
struct.setVmNics(new HashMap<String, VmNicInventory>());
} else {
SimpleQuery<VmNicVO> nq = dbf.createQuery(VmNicVO.class);
nq.add(VmNicVO_.uuid, Op.IN, activeNicUuids);
List<VmNicVO> nicvos = nq.list();
Map<String, VmNicInventory> m = new HashMap<String, VmNicInventory>();
for (VmNicVO n : nicvos) {
m.put(n.getUuid(), VmNicInventory.valueOf(n));
}
struct.setVmNics(m);
}
struct.setListeners(LoadBalancerListenerInventory.valueOf(vo.getListeners()));
return struct;
}
@Override
public void run(final FlowTrigger outterTrigger, final Map data) {
final VirtualRouterVmInventory vr = (VirtualRouterVmInventory) data.get(VirtualRouterConstant.Param.VR.toString());
final VmNicInventory guestNic = vr.getGuestNic();
if (!vrMgr.isL3NetworkNeedingNetworkServiceByVirtualRouter(guestNic.getL3NetworkUuid(), LoadBalancerConstants.LB_NETWORK_SERVICE_TYPE_STRING)) {
outterTrigger.next();
return;
}
if (VirtualRouterSystemTags.DEDICATED_ROLE_VR.hasTag(vr.getUuid()) && !VirtualRouterSystemTags.VR_LB_ROLE.hasTag(vr.getUuid())) {
outterTrigger.next();
return;
}
new VirtualRouterRoleManager().makeLoadBalancerRole(vr.getUuid());
Collection<LoadBalancerVO> lbs = new Callable<List<LoadBalancerVO>>() {
@Override
@Transactional(readOnly = true)
public List<LoadBalancerVO> call() {
String sql = "select lb from LoadBalancerVO lb, LoadBalancerListenerVO l, LoadBalancerListenerVmNicRefVO lref, VmNicVO nic, L3NetworkVO l3" +
" where lb.uuid = l.loadBalancerUuid and l.uuid = lref.listenerUuid and lref.vmNicUuid = nic.uuid and nic.l3NetworkUuid = l3.uuid" +
" and l3.uuid = :l3uuid and lb.state = :state and lb.uuid not in (select t.resourceUuid from SystemTagVO t" +
" where t.tag = :tag and t.resourceType = :rtype)";
TypedQuery<LoadBalancerVO> vq = dbf.getEntityManager().createQuery(sql, LoadBalancerVO.class);
if (!data.containsKey(Param.IS_NEW_CREATED.toString())) {
// start/reboot the vr, handle the case that it is the separate lb vr
sql = "select ref.loadBalancerUuid from VirtualRouterLoadBalancerRefVO ref where ref.virtualRouterVmUuid = :vruuid";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("vruuid", vr.getUuid());
List<String> lbuuids = q.getResultList();
if (!lbuuids.isEmpty()) {
sql = "select lb from LoadBalancerVO lb, LoadBalancerListenerVO l, LoadBalancerListenerVmNicRefVO lref, VmNicVO nic, L3NetworkVO l3" +
" where lb.uuid = l.loadBalancerUuid and l.uuid = lref.listenerUuid and lref.vmNicUuid = nic.uuid and nic.l3NetworkUuid = l3.uuid" +
" and l3.uuid = :l3uuid and lb.state = :state and lb.uuid not in (select t.resourceUuid from SystemTagVO t" +
" where t.tag = :tag and t.resourceType = :rtype and t.resourceUuid not in (:mylbs))";
vq = dbf.getEntityManager().createQuery(sql, LoadBalancerVO.class);
vq.setParameter("mylbs", lbuuids);
}
}
vq.setParameter("tag", LoadBalancerSystemTags.SEPARATE_VR.getTagFormat());
vq.setParameter("rtype", LoadBalancerVO.class.getSimpleName());
vq.setParameter("state", LoadBalancerState.Enabled);
vq.setParameter("l3uuid", guestNic.getL3NetworkUuid());
return vq.getResultList();
}
}.call();
if (lbs.isEmpty()) {
outterTrigger.next();
return;
}
Map<String, LoadBalancerVO> tmp = new HashMap<>();
lbs.forEach(vo -> tmp.put(String.format("%s-%s", vo.getUuid(), vr.getUuid()), vo));
lbs = tmp.values();
FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName(String.format("sync-lb-on-vr-%s", vr.getUuid()));
Collection<LoadBalancerVO> finalLbs = lbs;
chain.then(new ShareFlow() {
@Override
public void setup() {
flow(new NoRollbackFlow() {
String __name__ = "create-vip-for-lbs";
private void createVip(final Iterator<VipInventory> it, final FlowTrigger trigger) {
if (!it.hasNext()) {
trigger.next();
return;
}
vipExt.acquireVipOnVirtualRouterVm(vr, it.next(), new Completion(trigger) {
@Override
public void success() {
createVip(it, trigger);
}
@Override
public void fail(ErrorCode errorCode) {
trigger.fail(errorCode);
}
});
}
@Override
public void run(final FlowTrigger trigger, Map data) {
SimpleQuery<VipVO> q = dbf.createQuery(VipVO.class);
q.add(VipVO_.uuid, Op.IN, CollectionUtils.transformToList(finalLbs, new Function<String, LoadBalancerVO>() {
@Override
public String call(LoadBalancerVO arg) {
return arg.getVipUuid();
}
}));
List<VipVO> vipvos = q.list();
createVip(VipInventory.valueOf(vipvos).iterator(), trigger);
}
});
flow(new NoRollbackFlow() {
String __name__ = "create-lbs";
@Override
public void run(final FlowTrigger trigger, final Map data) {
List<LoadBalancerStruct> structs = new ArrayList<LoadBalancerStruct>();
for (LoadBalancerVO vo : finalLbs) {
structs.add(makeStruct(vo));
}
bkd.syncOnStart(vr, structs, new Completion(trigger) {
@Override
public void success() {
List<VirtualRouterLoadBalancerRefVO> refs = new ArrayList<>();
new SQLBatch() {
@Override
protected void scripts() {
for (LoadBalancerVO vo : finalLbs) {
if (!q(VirtualRouterLoadBalancerRefVO.class)
.eq(VirtualRouterLoadBalancerRefVO_.loadBalancerUuid, vo.getUuid())
.eq(VirtualRouterLoadBalancerRefVO_.virtualRouterVmUuid, vr.getUuid()).isExists()) {
VirtualRouterLoadBalancerRefVO ref = new VirtualRouterLoadBalancerRefVO();
ref.setLoadBalancerUuid(vo.getUuid());
ref.setVirtualRouterVmUuid(vr.getUuid());
ref = persist(ref);
refs.add(ref);
}
}
}
}.execute();
data.put(VirtualRouterSyncLbOnStartFlow.class, refs);
trigger.next();
}
@Override
public void fail(ErrorCode errorCode) {
trigger.fail(errorCode);
}
});
}
});
done(new FlowDoneHandler(outterTrigger) {
@Override
public void handle(Map data) {
outterTrigger.next();
}
});
error(new FlowErrorHandler(outterTrigger) {
@Override
public void handle(ErrorCode errCode, Map data) {
outterTrigger.fail(errCode);
}
});
}
}).start();
}
@Override
public void rollback(FlowRollback trigger, Map data) {
List<VirtualRouterLoadBalancerRefVO> refs = (List<VirtualRouterLoadBalancerRefVO>) data.get(VirtualRouterSyncLbOnStartFlow.class);
if (refs != null) {
dbf.removeCollection(refs, VirtualRouterLoadBalancerRefVO.class);
}
trigger.rollback();
}
}