package org.zstack.network.service.virtualrouter.portforwarding;
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.core.workflow.FlowChain;
import org.zstack.core.workflow.FlowChainBuilder;
import org.zstack.header.core.workflow.FlowDoneHandler;
import org.zstack.header.core.workflow.FlowErrorHandler;
import org.zstack.header.Component;
import org.zstack.header.core.Completion;
import org.zstack.header.core.ReturnValueCompletion;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.OperationFailureException;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.network.service.NetworkServiceProviderType;
import org.zstack.header.vm.VmInstanceState;
import org.zstack.header.vm.VmNicInventory;
import org.zstack.identity.AccountManager;
import org.zstack.network.service.portforwarding.PortForwardingBackend;
import org.zstack.network.service.portforwarding.PortForwardingRuleInventory;
import org.zstack.network.service.portforwarding.PortForwardingStruct;
import org.zstack.network.service.virtualrouter.*;
import org.zstack.utils.CollectionUtils;
import org.zstack.utils.DebugUtils;
import org.zstack.utils.function.Function;
import static org.zstack.core.Platform.operr;
import javax.persistence.TypedQuery;
import java.util.*;
public class VirtualRouterPortForwardingBackend extends AbstractVirtualRouterBackend implements PortForwardingBackend, Component {
@Autowired
protected DatabaseFacade dbf;
@Autowired
protected CloudBus bus;
@Autowired
protected ErrorFacade errf;
@Autowired
protected AccountManager acntMgr;
private List<String> applyPortForwardingRuleElements;
private FlowChainBuilder applyRuleChainBuilder;
private FlowChainBuilder releaseRuleChainBuilder;
private List<String> releasePortForwardingRuleElements;
private PortForwardingRuleTO makePortForwardingRuleTO(final PortForwardingStruct struct, VirtualRouterVmInventory vr) {
String privateMac = CollectionUtils.find(vr.getVmNics(), new Function<String, VmNicInventory>() {
@Override
public String call(VmNicInventory arg) {
if (arg.getL3NetworkUuid().equals(struct.getGuestL3Network().getUuid())) {
return arg.getMac();
}
return null;
}
});
DebugUtils.Assert(privateMac!=null, String.format("cannot find guest nic[l3NetworkUuid:%s] on virtual router[uuid:%s, name:%s]",
struct.getGuestL3Network().getUuid(), vr.getUuid(), vr.getName()));
PortForwardingRuleTO to = new PortForwardingRuleTO();
to.setAllowedCidr(struct.getRule().getAllowedCidr());
to.setPrivateIp(struct.getGuestIp());
to.setPrivateMac(privateMac);
to.setPrivatePortEnd(struct.getRule().getPrivatePortEnd());
to.setPrivatePortStart(struct.getRule().getPrivatePortStart());
to.setVipIp(struct.getVip().getIp());
to.setVipPortEnd(struct.getRule().getVipPortEnd());
to.setSnatInboundTraffic(struct.isSnatInboundTraffic());
to.setVipPortStart(struct.getRule().getVipPortStart());
to.setProtocolType(struct.getRule().getProtocolType());
return to;
}
@Transactional(readOnly = true)
private VirtualRouterVmInventory findRunningVirtualRouterForRule(String ruleUuid) {
String sql = "select vr from VirtualRouterPortForwardingRuleRefVO ref, VirtualRouterVmVO vr where ref.virtualRouterVmUuid = vr.uuid and ref.uuid = :ruleUuid and vr.state = :vrState";
TypedQuery<VirtualRouterVmVO> q = dbf.getEntityManager().createQuery(sql, VirtualRouterVmVO.class);
q.setParameter("ruleUuid", ruleUuid);
q.setParameter("vrState", VmInstanceState.Running);
q.setMaxResults(1);
List<VirtualRouterVmVO> vrs = q.getResultList();
if (vrs.isEmpty()) {
return null;
} else {
return VirtualRouterVmInventory.valueOf(vrs.get(0));
}
}
private void buildWorkFlowsBuilder() {
try{
applyRuleChainBuilder = FlowChainBuilder.newBuilder().setFlowClassNames(applyPortForwardingRuleElements).construct();
releaseRuleChainBuilder = FlowChainBuilder.newBuilder().setFlowClassNames(releasePortForwardingRuleElements).construct();
} catch (Exception e) {
throw new CloudRuntimeException(e);
}
}
public void setApplyPortForwardingRuleElements(List<String> applyPortForwardingRuleElements) {
this.applyPortForwardingRuleElements = applyPortForwardingRuleElements;
}
public void setReleasePortForwardingRuleElements(List<String> releasePortForwardingRuleElements) {
this.releasePortForwardingRuleElements = releasePortForwardingRuleElements;
}
@Override
public boolean start() {
buildWorkFlowsBuilder();
return true;
}
@Override
public boolean stop() {
return true;
}
@Override
public NetworkServiceProviderType getProviderType() {
return VirtualRouterConstant.PROVIDER_TYPE;
}
private void applyRule(final Iterator<PortForwardingStruct> it, final Completion completion) {
if (!it.hasNext()) {
completion.success();
return;
}
final PortForwardingStruct struct = it.next();
VirtualRouterStruct s = new VirtualRouterStruct();
s.setL3Network(struct.getGuestL3Network());
s.setOfferingValidator(new VirtualRouterOfferingValidator() {
@Override
public void validate(VirtualRouterOfferingInventory offering) throws OperationFailureException {
if (!offering.getPublicNetworkUuid().equals(struct.getVip().getL3NetworkUuid())) {
throw new OperationFailureException(operr("found a virtual router offering[uuid:%s] for L3Network[uuid:%s] in zone[uuid:%s]; however, the network's public network[uuid:%s] is not the same to PortForwarding rule[uuid:%s]'s; you may need to use system tag" +
" guestL3Network::l3NetworkUuid to specify a particular virtual router offering for the L3Network", offering.getUuid(), struct.getGuestL3Network().getUuid(), struct.getGuestL3Network().getZoneUuid(), struct.getVip().getL3NetworkUuid(), struct.getRule().getUuid()));
}
}
});
acquireVirtualRouterVm(s, new ReturnValueCompletion<VirtualRouterVmInventory>(completion) {
@Override
public void success(final VirtualRouterVmInventory vr) {
applyRule(struct, vr, new Completion(completion) {
@Override
public void success() {
new VirtualRouterRoleManager().makePortForwardingRole(vr.getUuid());
applyRule(it, completion);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
private void applyRule(final PortForwardingStruct struct, final VirtualRouterVmInventory vr, final Completion completion) {
final PortForwardingRuleTO to = makePortForwardingRuleTO(struct, vr);
FlowChain chain = applyRuleChainBuilder.build();
chain.setName(String.format("vr-apply-port-forwarding-rule-%s", struct.getRule().getUuid()));
chain.getData().put(VirtualRouterConstant.VR_RESULT_VM, vr);
chain.getData().put(VirtualRouterConstant.VR_PORT_FORWARDING_RULE, to);
chain.getData().put(VirtualRouterConstant.VR_VIP, struct.getVip());
chain.done(new FlowDoneHandler(completion) {
@Override
public void handle(Map data) {
SimpleQuery<VirtualRouterPortForwardingRuleRefVO> q = dbf.createQuery(VirtualRouterPortForwardingRuleRefVO.class);
q.add(VirtualRouterPortForwardingRuleRefVO_.uuid, Op.EQ, struct.getRule().getUuid());
if (!q.isExists()) {
// if virtual router is stopped outside zstack (e.g. the host reboot)
// database will still have VirtualRouterPortForwardingRuleRefVO for this PF rule.
// in this case, don't create the record again
VirtualRouterPortForwardingRuleRefVO ref = new VirtualRouterPortForwardingRuleRefVO();
ref.setUuid(struct.getRule().getUuid());
ref.setVirtualRouterVmUuid(vr.getUuid());
ref.setVipUuid(struct.getVip().getUuid());
dbf.persist(ref);
}
completion.success();
}
}).error(new FlowErrorHandler(completion) {
public void handle(ErrorCode errCode, Map data) {
completion.fail(errCode);
}
}).start();
}
@Override
public void applyPortForwardingRule(PortForwardingStruct struct, Completion completion) {
PortForwardingRuleInventory rule = struct.getRule();
if ((rule.getVipPortStart() != rule.getPrivatePortStart() || rule.getVipPortEnd() != rule.getPrivatePortEnd()) && (rule.getVipPortStart() != rule.getVipPortEnd()) && (rule.getPrivatePortStart() != rule.getPrivatePortEnd())) {
throw new OperationFailureException(operr("virtual router doesn't support port forwarding range redirection, the vipPortStart must be equals to privatePortStart and vipPortEnd must be equals to privatePortEnd;" +
"but this rule rule has a mismatching range: vip port[%s, %s], private port[%s, %s]", rule.getVipPortStart(), rule.getVipPortEnd(), rule.getPrivatePortStart(), rule.getPrivatePortEnd()));
}
applyRule(Arrays.asList(struct).iterator(), completion);
}
private void revokeRule(final PortForwardingStruct struct, final Completion completion) {
VirtualRouterVmInventory vr = findRunningVirtualRouterForRule(struct.getRule().getUuid());
if (vr == null) {
// vr is either destroyed or not running
// no need to do anything in each case, if vr is not running, rules will get synced once it gets running
completion.success();
return;
}
PortForwardingRuleTO to = makePortForwardingRuleTO(struct, vr);
FlowChain chain = releaseRuleChainBuilder.build();
chain.setName(String.format("vr-release-port-forwarding-rule-%s", struct.getRule().getUuid()));
Map<String, Object> ctx = new HashMap<String, Object>();
ctx.put(VirtualRouterConstant.VR_RESULT_VM, vr);
ctx.put(VirtualRouterConstant.VR_PORT_FORWARDING_RULE, to);
ctx.put(VirtualRouterConstant.VR_VIP_L3NETWORK, struct.getVip().getL3NetworkUuid());
chain.setData(ctx).done(new FlowDoneHandler(completion) {
@Override
public void handle(Map data) {
dbf.removeByPrimaryKey(struct.getRule().getUuid(), VirtualRouterPortForwardingRuleRefVO.class);
completion.success();
}
}).error(new FlowErrorHandler(completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
completion.fail(errCode);
}
}).start();
}
@Override
public void revokePortForwardingRule(PortForwardingStruct struct, Completion completion) {
revokeRule(struct, completion);
}
}