package org.zstack.network.service.portforwarding; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.zstack.core.Platform; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.MessageSafe; import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.Q; import org.zstack.core.db.SQLBatch; import org.zstack.core.db.SimpleQuery; import org.zstack.core.db.SimpleQuery.Op; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.core.workflow.FlowChainBuilder; import org.zstack.core.workflow.ShareFlow; import org.zstack.header.AbstractService; import org.zstack.header.apimediator.ApiMessageInterceptionException; import org.zstack.header.core.Completion; import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.SysErrors; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.identity.IdentityErrors; import org.zstack.header.identity.Quota; import org.zstack.header.identity.Quota.QuotaOperator; import org.zstack.header.identity.Quota.QuotaPair; import org.zstack.header.identity.ReportQuotaExtensionPoint; import org.zstack.header.message.APIMessage; import org.zstack.header.message.Message; import org.zstack.header.message.NeedQuotaCheckMessage; import org.zstack.header.network.l3.L3NetworkInventory; import org.zstack.header.network.l3.L3NetworkVO; import org.zstack.header.network.service.NetworkServiceProviderType; import org.zstack.header.network.service.NetworkServiceType; import org.zstack.header.query.AddExpandedQueryExtensionPoint; import org.zstack.header.query.ExpandedQueryAliasStruct; import org.zstack.header.query.ExpandedQueryStruct; import org.zstack.header.vm.*; import org.zstack.identity.AccountManager; import org.zstack.identity.QuotaUtil; import org.zstack.network.service.NetworkServiceManager; import org.zstack.network.service.vip.*; import org.zstack.tag.TagManager; import org.zstack.utils.CollectionUtils; import org.zstack.utils.DebugUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.ForEachFunction; import org.zstack.utils.gson.JSONObjectUtil; import org.zstack.utils.logging.CLogger; import javax.persistence.Tuple; import javax.persistence.TypedQuery; import java.util.*; import static java.util.Arrays.asList; import static org.zstack.utils.CollectionDSL.list; public class PortForwardingManagerImpl extends AbstractService implements PortForwardingManager, VipReleaseExtensionPoint, AddExpandedQueryExtensionPoint, ReportQuotaExtensionPoint { private static CLogger logger = Utils.getLogger(PortForwardingManagerImpl.class); @Autowired private CloudBus bus; @Autowired private DatabaseFacade dbf; @Autowired private PluginRegistry pluginRgty; @Autowired private NetworkServiceManager nwServiceMgr; @Autowired private TagManager tagMgr; @Autowired private AccountManager acntMgr; @Autowired private ErrorFacade errf; @Autowired private VipManager vipMgr; private Map<String, PortForwardingBackend> backends = new HashMap<String, PortForwardingBackend>(); private List<AttachPortForwardingRuleExtensionPoint> attachRuleExts = new ArrayList<AttachPortForwardingRuleExtensionPoint>(); private List<RevokePortForwardingRuleExtensionPoint> revokeRuleExts = new ArrayList<RevokePortForwardingRuleExtensionPoint>(); @Override public String getId() { return bus.makeLocalServiceId(PortForwardingConstant.SERVICE_ID); } @Override @MessageSafe public void handleMessage(Message msg) { if (msg instanceof APIMessage) { handleApiMessage((APIMessage) msg); } else { handleLocalMessage(msg); } } private void handleLocalMessage(Message msg) { if (msg instanceof PortForwardingRuleDeletionMsg) { handle((PortForwardingRuleDeletionMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } private void removePortforwardingRule(final Iterator<String> it, final Completion completion) { if (!it.hasNext()) { completion.success(); return; } String uuid = it.next(); removePortforwardingRule(uuid, new Completion(completion) { @Override public void success() { removePortforwardingRule(it, completion); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } private void handle(final PortForwardingRuleDeletionMsg msg) { final PortForwardingRuleDeletionReply reply = new PortForwardingRuleDeletionReply(); removePortforwardingRule(msg.getRuleUuids().iterator(), new Completion(msg) { @Override public void success() { bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } private void handleApiMessage(APIMessage msg) { if (msg instanceof APICreatePortForwardingRuleMsg) { handle((APICreatePortForwardingRuleMsg) msg); } else if (msg instanceof APIDeletePortForwardingRuleMsg) { handle((APIDeletePortForwardingRuleMsg) msg); } else if (msg instanceof APIListPortForwardingRuleMsg) { handle((APIListPortForwardingRuleMsg) msg); } else if (msg instanceof APIAttachPortForwardingRuleMsg) { handle((APIAttachPortForwardingRuleMsg) msg); } else if (msg instanceof APIDetachPortForwardingRuleMsg) { handle((APIDetachPortForwardingRuleMsg) msg); } else if (msg instanceof APIChangePortForwardingRuleStateMsg) { handle((APIChangePortForwardingRuleStateMsg) msg); } else if (msg instanceof APIGetPortForwardingAttachableVmNicsMsg) { handle((APIGetPortForwardingAttachableVmNicsMsg) msg); } else if (msg instanceof APIUpdatePortForwardingRuleMsg) { handle((APIUpdatePortForwardingRuleMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } private void handle(APIUpdatePortForwardingRuleMsg msg) { boolean update = false; PortForwardingRuleVO vo = dbf.findByUuid(msg.getUuid(), PortForwardingRuleVO.class); if (msg.getName() != null) { vo.setName(msg.getName()); update = true; } if (msg.getDescription() != null) { vo.setDescription(msg.getDescription()); update = true; } if (update) { vo = dbf.updateAndRefresh(vo); } APIUpdatePortForwardingRuleEvent evt = new APIUpdatePortForwardingRuleEvent(msg.getId()); evt.setInventory(PortForwardingRuleInventory.valueOf(vo)); bus.publish(evt); } @Transactional(readOnly = true) private List<VmNicInventory> getAttachableVmNics(String ruleUuid) { String sql = "select l3.zoneUuid, vip.uuid, vip.peerL3NetworkUuid from L3NetworkVO l3, VipVO vip, PortForwardingRuleVO rule where vip.l3NetworkUuid = l3.uuid and vip.uuid = rule.vipUuid and rule.uuid = :ruleUuid"; TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class); q.setParameter("ruleUuid", ruleUuid); Tuple t = q.getSingleResult(); String zoneUuid = t.get(0, String.class); String vipUuid = t.get(1, String.class); String vipPeerL3Uuid = t.get(2, String.class); List<String> l3Uuids; if (vipPeerL3Uuid == null) { sql = "select l3.uuid from L3NetworkVO l3, VipVO vip, NetworkServiceL3NetworkRefVO ref where l3.system = :system and l3.uuid != vip.l3NetworkUuid and l3.uuid = ref.l3NetworkUuid and ref.networkServiceType = :nsType and l3.zoneUuid = :zoneUuid and vip.uuid = :vipUuid"; TypedQuery<String> l3q = dbf.getEntityManager().createQuery(sql, String.class); l3q.setParameter("vipUuid", vipUuid); l3q.setParameter("system", false); l3q.setParameter("zoneUuid", zoneUuid); l3q.setParameter("nsType", PortForwardingConstant.PORTFORWARDING_NETWORK_SERVICE_TYPE); l3Uuids = l3q.getResultList(); } else { l3Uuids = asList(vipPeerL3Uuid); } if (l3Uuids.isEmpty()) { return new ArrayList<>(); } sql = "select pf.privatePortStart, pf.privatePortEnd, pf.protocolType from PortForwardingRuleVO pf where pf.uuid = :ruleUuid"; q = dbf.getEntityManager().createQuery(sql, Tuple.class); q.setParameter("ruleUuid", ruleUuid); t = q.getSingleResult(); int sport = t.get(0, Integer.class); int eport = t.get(1, Integer.class); PortForwardingProtocolType protocol = t.get(2, PortForwardingProtocolType.class); sql = "select nic from VmNicVO nic, VmInstanceVO vm where nic.l3NetworkUuid in (:l3Uuids)" + " and nic.vmInstanceUuid = vm.uuid and vm.type = :vmType and vm.state in (:vmStates)" + " and nic.uuid not in (select pf.vmNicUuid from PortForwardingRuleVO pf" + " where pf.protocolType = :protocol and pf.vmNicUuid is not null and" + " ((pf.privatePortStart >= :sport and pf.privatePortStart <= :eport) or" + " (pf.privatePortStart <= :sport and :sport <= pf.privatePortEnd)))" + " and nic.uuid not in (select pf1.vmNicUuid from PortForwardingRuleVO pf1 where pf1.vipUuid != :vipUuid" + " and pf1.vmNicUuid is not null)"; TypedQuery<VmNicVO> nq = dbf.getEntityManager().createQuery(sql, VmNicVO.class); nq.setParameter("l3Uuids", l3Uuids); nq.setParameter("vmType", VmInstanceConstant.USER_VM_TYPE); nq.setParameter("vmStates", asList(VmInstanceState.Running, VmInstanceState.Stopped)); nq.setParameter("sport", sport); nq.setParameter("eport", eport); nq.setParameter("protocol", protocol); nq.setParameter("vipUuid", vipUuid); List<VmNicVO> nics = nq.getResultList(); return VmNicInventory.valueOf(nics); } private void handle(APIGetPortForwardingAttachableVmNicsMsg msg) { APIGetPortForwardingAttachableVmNicsReply reply = new APIGetPortForwardingAttachableVmNicsReply(); reply.setInventories(getAttachableVmNics(msg.getRuleUuid())); bus.reply(msg, reply); } private void handle(APIChangePortForwardingRuleStateMsg msg) { PortForwardingRuleVO vo = dbf.findByUuid(msg.getUuid(), PortForwardingRuleVO.class); vo.setState(vo.getState().nextState(PortForwardingRuleStateEvent.valueOf(msg.getStateEvent()))); vo = dbf.updateAndRefresh(vo); APIChangePortForwardingRuleStateEvent evt = new APIChangePortForwardingRuleStateEvent(msg.getId()); evt.setInventory(PortForwardingRuleInventory.valueOf(vo)); bus.publish(evt); } private void handle(APIDetachPortForwardingRuleMsg msg) { final APIDetachPortForwardingRuleEvent evt = new APIDetachPortForwardingRuleEvent(msg.getId()); final PortForwardingRuleVO vo = dbf.findByUuid(msg.getUuid(), PortForwardingRuleVO.class); PortForwardingRuleInventory inv = PortForwardingRuleInventory.valueOf(vo); final PortForwardingStruct struct = makePortForwardingStruct(inv); struct.setReleaseVmNicInfoWhenDetaching(true); final NetworkServiceProviderType providerType = nwServiceMgr.getTypeOfNetworkServiceProviderForService(struct.getGuestL3Network().getUuid(), NetworkServiceType.PortForwarding); detachPortForwardingRule(struct, providerType.toString(), new Completion(msg) { @Override public void success() { PortForwardingRuleVO prvo = dbf.reload(vo); evt.setInventory(PortForwardingRuleInventory.valueOf(prvo)); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { evt.setError(errorCode); bus.publish(evt); } }); } @Transactional private VmInstanceState getVmStateFromVmNicUuid(String vmNicUuid) { String sql = "select vm.state from VmInstanceVO vm, VmNicVO nic where vm.uuid = nic.vmInstanceUuid and nic.uuid = :nicuuid"; TypedQuery<VmInstanceState> q = dbf.getEntityManager().createQuery(sql, VmInstanceState.class); q.setParameter("nicuuid", vmNicUuid); return q.getSingleResult(); } private void handle(final APIAttachPortForwardingRuleMsg msg) { final APIAttachPortForwardingRuleEvent evt = new APIAttachPortForwardingRuleEvent(msg.getId()); PortForwardingRuleVO vo = dbf.findByUuid(msg.getRuleUuid(), PortForwardingRuleVO.class); VmNicVO nicvo = dbf.findByUuid(msg.getVmNicUuid(), VmNicVO.class); vo.setVmNicUuid(nicvo.getUuid()); vo.setGuestIp(nicvo.getIp()); final PortForwardingRuleVO prvo = dbf.updateAndRefresh(vo); final PortForwardingRuleInventory inv = PortForwardingRuleInventory.valueOf(prvo); VmInstanceState vmState = getVmStateFromVmNicUuid(msg.getVmNicUuid()); if (VmInstanceState.Running != vmState) { //TODO: dirty fix that should be refixed in 1.9 https://github.com/zxwing/premium/issues/1496 SimpleQuery<VipVO> q = dbf.createQuery(VipVO.class); q.add(VipVO_.uuid, Op.EQ, vo.getVipUuid()); VipVO vip = q.find(); if (vip.getPeerL3NetworkUuid() == null) { vip.setPeerL3NetworkUuid(nicvo.getL3NetworkUuid()); dbf.update(vip); } evt.setInventory(inv); bus.publish(evt); return; } final PortForwardingStruct struct = makePortForwardingStruct(inv); final NetworkServiceProviderType providerType = nwServiceMgr.getTypeOfNetworkServiceProviderForService(struct.getGuestL3Network().getUuid(), NetworkServiceType.PortForwarding); attachPortForwardingRule(struct, providerType.toString(), new Completion(msg) { @Override public void success() { evt.setInventory(inv); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { prvo.setVmNicUuid(null); prvo.setGuestIp(null); dbf.update(prvo); evt.setError(errorCode); bus.publish(evt); } }); } private void handle(APIListPortForwardingRuleMsg msg) { List<PortForwardingRuleVO> vos = dbf.listByApiMessage(msg, PortForwardingRuleVO.class); List<PortForwardingRuleInventory> invs = PortForwardingRuleInventory.valueOf(vos); APIListPortForwardingRuleReply reply = new APIListPortForwardingRuleReply(); reply.setInventories(invs); bus.reply(msg, reply); } private boolean isNeedRemoveVip(PortForwardingRuleInventory inv) { SimpleQuery q = dbf.createQuery(PortForwardingRuleVO.class); q.add(PortForwardingRuleVO_.vipUuid, Op.EQ, inv.getVipUuid()); q.add(PortForwardingRuleVO_.vmNicUuid, Op.NOT_NULL); return q.count() == 1; } private void removePortforwardingRule(String ruleUuid, final Completion complete) { final PortForwardingRuleVO vo = dbf.findByUuid(ruleUuid, PortForwardingRuleVO.class); final PortForwardingRuleInventory inv = PortForwardingRuleInventory.valueOf(vo); if (vo.getVmNicUuid() == null) { new Vip(vo.getVipUuid()).release(false, new Completion(complete) { @Override public void success() { dbf.remove(vo); complete.success(); } @Override public void fail(ErrorCode errorCode) { complete.fail(errorCode); } }); return; } final PortForwardingStruct struct = makePortForwardingStruct(inv); final NetworkServiceProviderType providerType = nwServiceMgr.getTypeOfNetworkServiceProviderForService(struct.getGuestL3Network().getUuid(), NetworkServiceType.PortForwarding); for (RevokePortForwardingRuleExtensionPoint extp : revokeRuleExts) { try { extp.preRevokePortForwardingRule(inv, providerType); } catch (PortForwardingException e) { String err = String.format("unable to revoke port forwarding rule[uuid:%s]", inv.getUuid()); logger.warn(err, e); complete.fail(errf.throwableToOperationError(e)); return; } } CollectionUtils.safeForEach(revokeRuleExts, new ForEachFunction<RevokePortForwardingRuleExtensionPoint>() { @Override public void run(RevokePortForwardingRuleExtensionPoint extp) { extp.beforeRevokePortForwardingRule(inv, providerType); } }); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.then(new ShareFlow() { @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "delete-portforwarding-rule"; @Override public void run(FlowTrigger trigger, Map data) { PortForwardingBackend bkd = getPortForwardingBackend(providerType); bkd.revokePortForwardingRule(struct, new Completion(trigger) { @Override public void success() { logger.debug(String.format("successfully detached %s", struct.toString())); dbf.remove(vo); trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "release-vip-if-no-rules"; @Override public void run(FlowTrigger trigger, Map data) { if (Q.New(PortForwardingRuleVO.class).eq(PortForwardingRuleVO_.vipUuid, inv.getVipUuid()) .notNull(PortForwardingRuleVO_.vmNicUuid).isExists()) { trigger.next(); return; } // no port forwarding rules use this VIP, release it new Vip(vo.getVipUuid()).release(new Completion(trigger) { @Override public void success() { logger.debug(String.format("released VIP[uuid:%s] from port forwarding", vo.getVipUuid())); trigger.next(); } @Override public void fail(ErrorCode errorCode) { //TODO: GC this VIP logger.warn(errorCode.toString()); trigger.next(); } }); } }); done(new FlowDoneHandler(complete) { @Override public void handle(Map data) { CollectionUtils.safeForEach(revokeRuleExts, new ForEachFunction<RevokePortForwardingRuleExtensionPoint>() { @Override public void run(RevokePortForwardingRuleExtensionPoint extp) { extp.afterRevokePortForwardingRule(inv, providerType); } }); logger.debug(String.format("successfully revoked port forwarding rule[uuid:%s]", inv.getUuid())); complete.success(); } }); error(new FlowErrorHandler(complete) { @Override public void handle(ErrorCode errCode, Map data) { CollectionUtils.safeForEach(revokeRuleExts, new ForEachFunction<RevokePortForwardingRuleExtensionPoint>() { @Override public void run(RevokePortForwardingRuleExtensionPoint extp) { extp.failToRevokePortForwardingRule(inv, providerType); } }); logger.warn(String.format("failed to revoke port forwarding rule[uuid:%s] because %s", inv.getUuid(), errCode)); complete.fail(errCode); } }); } }).start(); } private void handle(APIDeletePortForwardingRuleMsg msg) { final APIDeletePortForwardingRuleEvent evt = new APIDeletePortForwardingRuleEvent(msg.getId()); removePortforwardingRule(msg.getUuid(), new Completion(msg) { @Override public void success() { bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { evt.setError(errorCode); bus.publish(evt); } }); } private void handle(APICreatePortForwardingRuleMsg msg) { final APICreatePortForwardingRuleEvent evt = new APICreatePortForwardingRuleEvent(msg.getId()); int vipPortEnd = msg.getVipPortEnd() == null ? msg.getVipPortStart() : msg.getVipPortEnd(); int privatePortEnd = msg.getPrivatePortEnd() == null ? msg.getPrivatePortStart() : msg.getPrivatePortEnd(); VipVO vip = dbf.findByUuid(msg.getVipUuid(), VipVO.class); final PortForwardingRuleVO vo = new PortForwardingRuleVO(); if (msg.getResourceUuid() != null) { vo.setUuid(msg.getResourceUuid()); } else { vo.setUuid(Platform.getUuid()); } vo.setName(msg.getName()); vo.setDescription(msg.getDescription()); vo.setState(PortForwardingRuleState.Enabled); vo.setAllowedCidr(msg.getAllowedCidr()); vo.setVipUuid(vip.getUuid()); vo.setVipIp(vip.getIp()); vo.setVipPortStart(msg.getVipPortStart()); vo.setVipPortEnd(vipPortEnd); vo.setPrivatePortEnd(privatePortEnd); vo.setPrivatePortStart(msg.getPrivatePortStart()); vo.setProtocolType(PortForwardingProtocolType.valueOf(msg.getProtocolType())); new SQLBatch() { @Override protected void scripts() { persist(vo); acntMgr.createAccountResourceRef(msg.getSession().getAccountUuid(), vo.getUuid(), PortForwardingRuleVO.class); tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), PortForwardingRuleVO.class.getSimpleName()); } }.execute(); VipInventory vipInventory = VipInventory.valueOf(vip); if (msg.getVmNicUuid() == null) { Vip v = new Vip(vipInventory.getUuid()); v.setServiceProvider(vipInventory.getServiceProvider()); v.setPeerL3NetworkUuid(vipInventory.getPeerL3NetworkUuid()); v.setUseFor(PortForwardingConstant.PORTFORWARDING_NETWORK_SERVICE_TYPE); v.acquire(false, new Completion(msg) { @Override public void success() { evt.setInventory(PortForwardingRuleInventory.valueOf(vo)); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { evt.setError(errorCode); bus.publish(evt); } }); return; } VmNicVO vmNic = dbf.findByUuid(msg.getVmNicUuid(), VmNicVO.class); SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class); q.select(VmInstanceVO_.state); q.add(VmInstanceVO_.uuid, Op.EQ, vmNic.getVmInstanceUuid()); VmInstanceState vmState = q.findValue(); if (VmInstanceState.Running != vmState) { Vip v = new Vip(vipInventory.getUuid()); v.setServiceProvider(vipInventory.getServiceProvider()); v.setPeerL3NetworkUuid(vipInventory.getPeerL3NetworkUuid()); v.setUseFor(PortForwardingConstant.PORTFORWARDING_NETWORK_SERVICE_TYPE); v.acquire(false, new Completion(msg) { @Override public void success() { evt.setInventory(PortForwardingRuleInventory.valueOf(vo)); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { evt.setError(errorCode); bus.publish(evt); } }); return; } final NetworkServiceProviderType providerType = nwServiceMgr.getTypeOfNetworkServiceProviderForService(vmNic.getL3NetworkUuid(), NetworkServiceType.PortForwarding); vo.setVmNicUuid(vmNic.getUuid()); vo.setGuestIp(vmNic.getIp()); PortForwardingRuleVO pvo = dbf.updateAndRefresh(vo); final PortForwardingRuleInventory ruleInv = PortForwardingRuleInventory.valueOf(pvo); for (AttachPortForwardingRuleExtensionPoint extp : attachRuleExts) { try { extp.preAttachPortForwardingRule(ruleInv, providerType); } catch (PortForwardingException e) { String err = String.format("unable to create port forwarding rule, extension[%s] refused it because %s", extp.getClass().getName(), e.getMessage()); logger.warn(err, e); evt.setError(errf.instantiateErrorCode(SysErrors.CREATE_RESOURCE_ERROR, err)); bus.publish(evt); return; } } CollectionUtils.safeForEach(attachRuleExts, new ForEachFunction<AttachPortForwardingRuleExtensionPoint>() { @Override public void run(AttachPortForwardingRuleExtensionPoint extp) { extp.beforeAttachPortForwardingRule(ruleInv, providerType); } }); final PortForwardingStruct struct = makePortForwardingStruct(ruleInv); attachPortForwardingRule(struct, providerType.toString(), new Completion(msg) { @Override public void success() { CollectionUtils.safeForEach(attachRuleExts, new ForEachFunction<AttachPortForwardingRuleExtensionPoint>() { @Override public void run(AttachPortForwardingRuleExtensionPoint extp) { extp.afterAttachPortForwardingRule(ruleInv, providerType); } }); evt.setInventory(ruleInv); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { CollectionUtils.safeForEach(attachRuleExts, extp -> extp.failToAttachPortForwardingRule(ruleInv, providerType)); logger.debug(String.format("failed to create port forwarding rule %s, because %s", JSONObjectUtil.toJsonString(ruleInv), errorCode)); dbf.remove(vo); evt.setError(errf.instantiateErrorCode(SysErrors.CREATE_RESOURCE_ERROR, errorCode)); bus.publish(evt); } }); } private void populateExtensions() { for (PortForwardingBackend extp : pluginRgty.getExtensionList(PortForwardingBackend.class)) { PortForwardingBackend old = backends.get(extp.getProviderType().toString()); if (old != null) { throw new CloudRuntimeException(String.format("duplicate PortForwardingBackend[%s, %s] for type[%s]", extp.getClass().getName(), old.getClass().getName(), extp.getProviderType())); } backends.put(extp.getProviderType().toString(), extp); } attachRuleExts = pluginRgty.getExtensionList(AttachPortForwardingRuleExtensionPoint.class); revokeRuleExts = pluginRgty.getExtensionList(RevokePortForwardingRuleExtensionPoint.class); } @Override public boolean start() { populateExtensions(); return true; } @Override public boolean stop() { return true; } public PortForwardingBackend getPortForwardingBackend(NetworkServiceProviderType nspType) { return getPortForwardingBackend(nspType.toString()); } @Override public String getVipUse() { return PortForwardingConstant.PORTFORWARDING_NETWORK_SERVICE_TYPE; } private void releaseServicesOnVip(final Iterator<PortForwardingRuleVO> it, final Completion completion) { if (!it.hasNext()) { completion.success(); return; } final PortForwardingRuleVO rule = it.next(); if (rule.getVmNicUuid() == null) { dbf.remove(rule); completion.success(); return; } PortForwardingStruct struct = makePortForwardingStruct(PortForwardingRuleInventory.valueOf(rule)); final NetworkServiceProviderType providerType = nwServiceMgr.getTypeOfNetworkServiceProviderForService(struct.getGuestL3Network().getUuid(), NetworkServiceType.PortForwarding); PortForwardingBackend bkd = getPortForwardingBackend(providerType); bkd.revokePortForwardingRule(struct, new Completion(completion) { @Override public void success() { dbf.remove(rule); releaseServicesOnVip(it, completion); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override public void releaseServicesOnVip(VipInventory vip, Completion complete) { SimpleQuery<PortForwardingRuleVO> q = dbf.createQuery(PortForwardingRuleVO.class); q.add(PortForwardingRuleVO_.vipUuid, Op.EQ, vip.getUuid()); List<PortForwardingRuleVO> rules = q.list(); releaseServicesOnVip(rules.iterator(), complete); } private PortForwardingStruct makePortForwardingStruct(PortForwardingRuleInventory rule) { VipVO vipvo = dbf.findByUuid(rule.getVipUuid(), VipVO.class); L3NetworkVO vipL3vo = dbf.findByUuid(vipvo.getL3NetworkUuid(), L3NetworkVO.class); VmNicVO nic = dbf.findByUuid(rule.getVmNicUuid(), VmNicVO.class); L3NetworkVO guestL3vo = dbf.findByUuid(nic.getL3NetworkUuid(), L3NetworkVO.class); PortForwardingStruct struct = new PortForwardingStruct(); struct.setRule(rule); struct.setVip(VipInventory.valueOf(vipvo)); struct.setGuestIp(nic.getIp()); struct.setGuestMac(nic.getMac()); struct.setGuestL3Network(L3NetworkInventory.valueOf(guestL3vo)); struct.setSnatInboundTraffic(PortForwardingGlobalConfig.SNAT_INBOUND_TRAFFIC.value(Boolean.class)); struct.setVipL3Network(L3NetworkInventory.valueOf(vipL3vo)); return struct; } @Override public PortForwardingBackend getPortForwardingBackend(String providerType) { PortForwardingBackend bkd = backends.get(providerType); DebugUtils.Assert(bkd != null, String.format("cannot find PortForwardingBackend[type:%s]", providerType)); return bkd; } @Override public void attachPortForwardingRule(PortForwardingStruct struct, String providerType, final Completion completion) { FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("attach-portforwarding-%s-vm-nic-%s", struct.getRule().getUuid(), struct.getRule().getVmNicUuid())); chain.then(new ShareFlow() { @Override public void setup() { flow(new Flow() { String __name__ = "prepare-vip"; boolean s = false; @Override public void run(FlowTrigger trigger, Map data) { Vip vip = new Vip(struct.getVip().getUuid()); vip.setPeerL3NetworkUuid(struct.getGuestL3Network().getUuid()); vip.setServiceProvider(providerType); vip.setUseFor(PortForwardingConstant.PORTFORWARDING_NETWORK_SERVICE_TYPE); vip.acquire(new Completion(trigger) { @Override public void success() { s = true; trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } @Override public void rollback(FlowRollback trigger, Map data) { if (!s) { trigger.rollback(); return; } new Vip(struct.getVip().getUuid()).release(new Completion(trigger) { @Override public void success() { trigger.rollback(); } @Override public void fail(ErrorCode errorCode) { //TODO add GC logger.warn(errorCode.toString()); trigger.rollback(); } }); } }); flow(new NoRollbackFlow() { String __name__ = "attach-portfowarding-rule"; @Override public void run(FlowTrigger trigger, Map data) { PortForwardingBackend bkd = getPortForwardingBackend(providerType); bkd.applyPortForwardingRule(struct, new Completion(trigger) { @Override public void success() { logger.debug(String.format("successfully attached %s", struct.toString())); trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); 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 detachPortForwardingRule(final PortForwardingStruct struct, String providerType, final Completion completion) { FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("detach-portforwarding-%s-vm-nic-%s", struct.getRule().getUuid(), struct.getRule().getVmNicUuid())); chain.then(new ShareFlow() { @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "detach-portforwarding-rule"; @Override public void run(FlowTrigger trigger, Map data) { PortForwardingBackend bkd = getPortForwardingBackend(providerType); bkd.revokePortForwardingRule(struct, new Completion(trigger) { @Override public void success() { logger.debug(String.format("successfully detached %s", struct.toString())); trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "delete-vip-from-backend-if-no-rules"; @Override public void run(FlowTrigger trigger, Map data) { SimpleQuery<PortForwardingRuleVO> q = dbf.createQuery(PortForwardingRuleVO.class); q.add(PortForwardingRuleVO_.vipUuid, Op.EQ, struct.getVip().getUuid()); q.add(PortForwardingRuleVO_.vmNicUuid, Op.NOT_NULL); q.add(PortForwardingRuleVO_.uuid, Op.NOT_EQ, struct.getRule().getUuid()); if (q.isExists()) { trigger.next(); return; } new Vip(struct.getVip().getUuid()).deleteFromBackend(new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { if (struct.isReleaseVmNicInfoWhenDetaching()) { PortForwardingRuleVO vo = dbf.findByUuid(struct.getRule().getUuid(), PortForwardingRuleVO.class); vo.setVmNicUuid(null); vo.setGuestIp(null); dbf.updateAndRefresh(vo); } completion.success(); } }); error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); } }); } }).start(); } @Override public List<ExpandedQueryStruct> getExpandedQueryStructs() { List<ExpandedQueryStruct> structs = new ArrayList<ExpandedQueryStruct>(); ExpandedQueryStruct struct = new ExpandedQueryStruct(); struct.setInventoryClassToExpand(VmNicInventory.class); struct.setExpandedField("portForwarding"); struct.setInventoryClass(PortForwardingRuleInventory.class); struct.setForeignKey("uuid"); struct.setExpandedInventoryKey("vmNicUuid"); structs.add(struct); struct = new ExpandedQueryStruct(); struct.setInventoryClassToExpand(VipInventory.class); struct.setExpandedField("portForwarding"); struct.setInventoryClass(PortForwardingRuleInventory.class); struct.setForeignKey("uuid"); struct.setExpandedInventoryKey("vipUuid"); structs.add(struct); return structs; } @Override public List<ExpandedQueryAliasStruct> getExpandedQueryAliasesStructs() { return null; } @Override public List<Quota> reportQuota() { QuotaOperator checker = new QuotaOperator() { @Override public void checkQuota(APIMessage msg, Map<String, QuotaPair> pairs) { if (!new QuotaUtil().isAdminAccount(msg.getSession().getAccountUuid())) { if (msg instanceof APICreatePortForwardingRuleMsg) { check((APICreatePortForwardingRuleMsg) msg, pairs); } } } @Override public void checkQuota(NeedQuotaCheckMessage msg, Map<String, QuotaPair> pairs) { } @Override public List<Quota.QuotaUsage> getQuotaUsageByAccount(String accountUuid) { Quota.QuotaUsage usage = new Quota.QuotaUsage(); usage.setName(PortForwardingConstant.QUOTA_PF_NUM); usage.setUsed(getUsedPf(accountUuid)); return list(usage); } @Transactional(readOnly = true) private long getUsedPf(String accountUuid) { String sql = "select count(pf) from PortForwardingRuleVO pf, AccountResourceRefVO ref where pf.uuid = ref.resourceUuid" + " and ref.accountUuid = :auuid and ref.resourceType = :rtype"; TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class); q.setParameter("auuid", accountUuid); q.setParameter("rtype", PortForwardingRuleVO.class.getSimpleName()); Long pfn = q.getSingleResult(); pfn = pfn == null ? 0 : pfn; return pfn; } private void check(APICreatePortForwardingRuleMsg msg, Map<String, QuotaPair> pairs) { long pfNum = pairs.get(PortForwardingConstant.QUOTA_PF_NUM).getValue(); long pfn = getUsedPf(msg.getSession().getAccountUuid()); if (pfn + 1 > pfNum) { throw new ApiMessageInterceptionException(errf.instantiateErrorCode(IdentityErrors.QUOTA_EXCEEDING, String.format("quota exceeding. The account[uuid: %s] exceeds a quota[name: %s, value: %s]", msg.getSession().getAccountUuid(), PortForwardingConstant.QUOTA_PF_NUM, pfNum) )); } } }; Quota quota = new Quota(); quota.setOperator(checker); quota.addMessageNeedValidation(APICreatePortForwardingRuleMsg.class); QuotaPair p = new QuotaPair(); p.setName(PortForwardingConstant.QUOTA_PF_NUM); p.setValue(20); quota.addPair(p); return list(quota); } }