package org.zstack.network.service.virtualrouter.portforwarding;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.CloudBusCallBack;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.core.timeout.ApiTimeoutManager;
import org.zstack.header.core.workflow.Flow;
import org.zstack.header.core.workflow.FlowRollback;
import org.zstack.header.core.workflow.FlowTrigger;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.message.MessageReply;
import org.zstack.header.vm.VmInstanceConstant;
import org.zstack.header.vm.VmInstanceState;
import org.zstack.header.vm.VmNicInventory;
import org.zstack.network.service.portforwarding.PortForwardingConstant;
import org.zstack.network.service.portforwarding.PortForwardingGlobalConfig;
import org.zstack.network.service.portforwarding.PortForwardingRuleVO;
import org.zstack.network.service.virtualrouter.*;
import org.zstack.network.service.virtualrouter.VirtualRouterCommands.SyncPortForwardingRuleCmd;
import org.zstack.network.service.virtualrouter.VirtualRouterCommands.SyncPortForwardingRuleRsp;
import org.zstack.network.service.virtualrouter.VirtualRouterConstant.Param;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import static org.zstack.core.Platform.operr;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import java.util.*;
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class VirtualRouterSyncPortForwardingRulesOnStartFlow implements Flow {
private static CLogger logger = Utils.getLogger(VirtualRouterSyncPortForwardingRulesOnStartFlow.class);
@Autowired
private DatabaseFacade dbf;
@Autowired
private CloudBus bus;
@Autowired
private ErrorFacade errf;
@Autowired
private VirtualRouterManager vrMgr;
@Autowired
private ApiTimeoutManager apiTimeoutManager;
@Transactional
private List<PortForwardingRuleVO> findRulesForThisRouter(VirtualRouterVmInventory vr, Map<String, Object> data, boolean isNewCreated) {
if (!isNewCreated) {
String sql = "select rule from PortForwardingRuleVO rule, VirtualRouterPortForwardingRuleRefVO ref, VmNicVO nic, VmInstanceVO vm where vm.state = :vmState and nic.vmInstanceUuid = vm.uuid and rule.vmNicUuid = nic.uuid and rule.uuid = ref.uuid and ref.virtualRouterVmUuid = :vrUuid";
TypedQuery<PortForwardingRuleVO> q = dbf.getEntityManager().createQuery(sql, PortForwardingRuleVO.class);
q.setParameter("vrUuid", vr.getUuid());
q.setParameter("vmState", VmInstanceState.Running);
return q.getResultList();
} else {
VmNicInventory publicNic = vr.getPublicNic();
VmNicInventory guestNic = vr.getGuestNic();
String sql = "select rule from PortForwardingRuleVO rule, VipVO vip, VmNicVO nic, VmInstanceVO vm where vm.uuid = nic.vmInstanceUuid and vm.state = :vmState and rule.vipUuid = vip.uuid and rule.vmNicUuid = nic.uuid and vip.l3NetworkUuid = :vipL3Uuid and nic.l3NetworkUuid = :guestL3Uuid";
TypedQuery<PortForwardingRuleVO> q = dbf.getEntityManager().createQuery(sql, PortForwardingRuleVO.class);
q.setParameter("vipL3Uuid", publicNic.getL3NetworkUuid());
q.setParameter("guestL3Uuid", guestNic.getL3NetworkUuid());
q.setParameter("vmState", VmInstanceState.Running);
List<PortForwardingRuleVO> rules = q.getResultList();
if (!rules.isEmpty()) {
List<VirtualRouterPortForwardingRuleRefVO> refs = new ArrayList<VirtualRouterPortForwardingRuleRefVO>();
for (PortForwardingRuleVO rule : rules) {
VirtualRouterPortForwardingRuleRefVO ref = new VirtualRouterPortForwardingRuleRefVO();
ref.setVirtualRouterVmUuid(vr.getUuid());
ref.setVipUuid(rule.getVipUuid());
ref.setUuid(rule.getUuid());
dbf.getEntityManager().persist(ref);
refs.add(ref);
}
data.put(VirtualRouterSyncPortForwardingRulesOnStartFlow.class.getName(), refs);
}
return rules;
}
}
@Transactional(readOnly=true)
private Collection<PortForwardingRuleTO> calculateAllRules(Map<String, PortForwardingRuleVO> ruleMap, String vrUuid) {
String sql = "select rule.uuid, nic.ip, vip.ip from PortForwardingRuleVO rule, VmNicVO nic, VipVO vip where rule.vmNicUuid = nic.uuid and rule.uuid in (:ruleUuids) and vip.uuid = rule.vipUuid";
TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
q.setParameter("ruleUuids", ruleMap.keySet());
List<Tuple> privateIps = q.getResultList();
Map<String, PortForwardingRuleTO> tos = new HashMap<String, PortForwardingRuleTO>();
for (Tuple t : privateIps) {
String ruleUuid = t.get(0, String.class);
PortForwardingRuleTO to = new PortForwardingRuleTO();
to.setPrivateIp(t.get(1, String.class));
PortForwardingRuleVO ruleVO = ruleMap.get(ruleUuid);
to.setAllowedCidr(ruleVO.getAllowedCidr());
to.setPrivatePortEnd(ruleVO.getPrivatePortEnd());
to.setPrivatePortStart(ruleVO.getPrivatePortStart());
to.setVipPortEnd(ruleVO.getVipPortEnd());
to.setSnatInboundTraffic(PortForwardingGlobalConfig.SNAT_INBOUND_TRAFFIC.value(Boolean.class));
to.setVipPortStart(ruleVO.getVipPortStart());
to.setVipIp(t.get(2, String.class));
to.setProtocolType(ruleVO.getProtocolType().toString());
tos.put(ruleUuid, to);
}
assert tos.size() == ruleMap.size();
sql = "select rule.uuid, vrnic.mac from PortForwardingRuleVO rule, VmNicVO vrnic, VmNicVO nic2, ApplianceVmVO vr where vr.uuid = vrnic.vmInstanceUuid and vrnic.l3NetworkUuid = nic2.l3NetworkUuid and nic2.uuid = rule.vmNicUuid and rule.uuid in (:ruleUuids) and vr.uuid = :vrUuid";
TypedQuery<Tuple> privateMacQuery = dbf.getEntityManager().createQuery(sql, Tuple.class);
privateMacQuery.setParameter("ruleUuids", ruleMap.keySet());
privateMacQuery.setParameter("vrUuid", vrUuid);
List<Tuple> privateMacs = privateMacQuery.getResultList();
for (Tuple t: privateMacs) {
String ruleUuid = t.get(0, String.class);
PortForwardingRuleTO to = tos.get(ruleUuid);
to.setPrivateMac(t.get(1, String.class));
}
return tos.values();
}
@Override
public void run(final FlowTrigger chain, Map data) {
final VirtualRouterVmInventory vr = (VirtualRouterVmInventory) data.get(VirtualRouterConstant.Param.VR.toString());
VmNicInventory guestNic = vr.getGuestNic();
if (!vrMgr.isL3NetworkNeedingNetworkServiceByVirtualRouter(guestNic.getL3NetworkUuid(), PortForwardingConstant.PORTFORWARDING_NETWORK_SERVICE_TYPE)) {
chain.next();
return;
}
if (VirtualRouterSystemTags.DEDICATED_ROLE_VR.hasTag(vr.getUuid()) && !VirtualRouterSystemTags.VR_PORT_FORWARDING_ROLE.hasTag(vr.getUuid())) {
chain.next();
return;
}
new VirtualRouterRoleManager().makePortForwardingRole(vr.getUuid());
boolean isNewCreated = data.containsKey(Param.IS_NEW_CREATED.toString());
List<PortForwardingRuleVO> ruleVOs = findRulesForThisRouter(vr, data, isNewCreated);
if (ruleVOs.isEmpty()) {
chain.next();
return;
}
Map<String, PortForwardingRuleVO> ruleMap = new HashMap<String, PortForwardingRuleVO>(ruleVOs.size());
for (PortForwardingRuleVO rvo : ruleVOs) {
ruleMap.put(rvo.getUuid(), rvo);
}
Collection<PortForwardingRuleTO> tos = calculateAllRules(ruleMap, vr.getUuid());
List<PortForwardingRuleTO> toList = new ArrayList<PortForwardingRuleTO>(tos.size());
toList.addAll(tos);
SyncPortForwardingRuleCmd cmd = new SyncPortForwardingRuleCmd();
cmd.setRules(toList);
VirtualRouterAsyncHttpCallMsg msg = new VirtualRouterAsyncHttpCallMsg();
msg.setCommand(cmd);
msg.setCommandTimeout(apiTimeoutManager.getTimeout(cmd.getClass(), "30m"));
msg.setPath(VirtualRouterConstant.VR_SYNC_PORT_FORWARDING);
msg.setVmInstanceUuid(vr.getUuid());
bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, vr.getUuid());
bus.send(msg, new CloudBusCallBack(chain) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
chain.fail(reply.getError());
return;
}
VirtualRouterAsyncHttpCallReply re = reply.castReply();
SyncPortForwardingRuleRsp ret = re.toResponse(SyncPortForwardingRuleRsp.class);
if (ret.isSuccess()) {
String info = String.format("successfully sync port forwarding rules served by virtual router[name: %s uuid: %s]",
vr.getName(), vr.getUuid());
logger.debug(info);
chain.next();
} else {
ErrorCode err = operr("failed to sync port forwarding rules served by virtual router[name: %s, uuid: %s], because %s",
vr.getName(), vr.getUuid(), ret.getError());
chain.fail(err);
}
}
});
}
@Override
public void rollback(FlowRollback chain, Map data) {
List<VirtualRouterPortForwardingRuleRefVO> refs = (List<VirtualRouterPortForwardingRuleRefVO>) data.get(VirtualRouterSyncPortForwardingRulesOnStartFlow.class.getName());
if (refs != null) {
dbf.removeCollection(refs, VirtualRouterPortForwardingRuleRefVO.class);
}
chain.rollback();
}
}