package org.zstack.network.service.virtualrouter.eip; import org.springframework.beans.factory.annotation.Autowired; import org.zstack.appliancevm.ApplianceVmFacade; import org.zstack.appliancevm.ApplianceVmFirewallProtocol; import org.zstack.appliancevm.ApplianceVmFirewallRuleInventory; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusCallBack; 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.core.timeout.ApiTimeoutManager; import org.zstack.core.workflow.FlowChainBuilder; import org.zstack.header.core.Completion; import org.zstack.header.core.ReturnValueCompletion; import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.message.MessageReply; import org.zstack.header.network.l3.L3NetworkInventory; import org.zstack.header.network.l3.L3NetworkVO; import org.zstack.header.vm.VmInstanceConstant; import org.zstack.header.vm.VmInstanceState; import org.zstack.header.vm.VmNicInventory; import org.zstack.network.service.eip.EipBackend; import org.zstack.network.service.eip.EipStruct; import org.zstack.network.service.virtualrouter.*; import org.zstack.network.service.virtualrouter.VirtualRouterCommands.CreateEipRsp; import org.zstack.network.service.virtualrouter.VirtualRouterCommands.RemoveEipRsp; import org.zstack.utils.CollectionUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.Function; import org.zstack.utils.logging.CLogger; import java.util.Arrays; import java.util.List; import java.util.Map; import static org.zstack.core.Platform.operr; /** */ public class VirtualRouterEipBackend extends AbstractVirtualRouterBackend implements EipBackend { private static final CLogger logger = Utils.getLogger(VirtualRouterEipBackend.class); @Autowired protected DatabaseFacade dbf; @Autowired protected CloudBus bus; @Autowired protected ErrorFacade errf; @Autowired private ApplianceVmFacade asf; @Autowired private ApiTimeoutManager apiTimeoutManager; private List<ApplianceVmFirewallRuleInventory> getFirewallRules(EipStruct struct) { ApplianceVmFirewallRuleInventory tcp = new ApplianceVmFirewallRuleInventory(); tcp.setProtocol(ApplianceVmFirewallProtocol.tcp.toString()); tcp.setDestIp(struct.getVip().getIp()); tcp.setStartPort(0); tcp.setEndPort(65535); ApplianceVmFirewallRuleInventory udp = new ApplianceVmFirewallRuleInventory(); udp.setProtocol(ApplianceVmFirewallProtocol.udp.toString()); udp.setDestIp(struct.getVip().getIp()); udp.setStartPort(0); udp.setEndPort(65535); return Arrays.asList(tcp, udp); } private void applyEip(final VirtualRouterVmInventory vr, final EipStruct struct, final Completion completion) { FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("apply-eip-%s-vr-%s", struct.getEip().getUuid(), vr.getUuid())); chain.then(new Flow() { @Override public void run(final FlowTrigger trigger, Map data) { asf.openFirewall(vr.getUuid(), struct.getVip().getL3NetworkUuid(), getFirewallRules(struct), new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } @Override public void rollback(final FlowRollback trigger, Map data) { asf.removeFirewall(vr.getUuid(), struct.getVip().getL3NetworkUuid(), getFirewallRules(struct), new Completion(trigger) { @Override public void success() { trigger.rollback(); } @Override public void fail(ErrorCode errorCode) { logger.warn(String.format("failed to remove firewall rules on virtual router[uuid:%s, l3Network uuid:%s], %s", vr.getUuid(), struct.getVip().getL3NetworkUuid(), errorCode)); trigger.rollback(); } }); } }).then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { EipTO to = new EipTO(); String priMac = CollectionUtils.find(vr.getVmNics(), new Function<String, VmNicInventory>() { @Override public String call(VmNicInventory arg) { if (arg.getL3NetworkUuid().equals(struct.getNic().getL3NetworkUuid())) { return arg.getMac(); } return null; } }); to.setPrivateMac(priMac); to.setVipIp(struct.getVip().getIp()); to.setGuestIp(struct.getNic().getIp()); to.setSnatInboundTraffic(struct.isSnatInboundTraffic()); VirtualRouterCommands.CreateEipCmd cmd = new VirtualRouterCommands.CreateEipCmd(); cmd.setEip(to); VirtualRouterAsyncHttpCallMsg msg = new VirtualRouterAsyncHttpCallMsg(); msg.setCheckStatus(true); msg.setPath(VirtualRouterConstant.VR_CREATE_EIP); msg.setCommand(cmd); msg.setCommandTimeout(apiTimeoutManager.getTimeout(cmd.getClass(), "30m")); msg.setVmInstanceUuid(vr.getUuid()); bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, vr.getUuid()); bus.send(msg, new CloudBusCallBack(completion) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { trigger.fail(reply.getError()); return; } VirtualRouterAsyncHttpCallReply re = reply.castReply(); CreateEipRsp ret = re.toResponse(CreateEipRsp.class); if (ret.isSuccess()) { trigger.next(); } else { trigger.fail(operr("failed to create eip[uuid:%s, name:%s, ip:%s] for vm nic[uuid:%s] on virtual router[uuid:%s], %s", struct.getEip().getUuid(), struct.getEip().getName(), struct.getVip().getIp(), struct.getNic().getUuid(), vr.getUuid(), ret.getError())); } } }); } }).done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { String info = String.format("successfully created eip[uuid:%s, name:%s, ip:%s] for vm nic[uuid:%s] on virtual router[uuid:%s]", struct.getEip().getUuid(), struct.getEip().getName(), struct.getVip().getIp(), struct.getNic().getUuid(), vr.getUuid()); new VirtualRouterRoleManager().makeEipRole(vr.getUuid()); logger.debug(info); completion.success(); } }).error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); } }).start(); } @Override public void applyEip(final EipStruct struct, final Completion completion) { L3NetworkVO l3vo = dbf.findByUuid(struct.getNic().getL3NetworkUuid(), L3NetworkVO.class); final L3NetworkInventory l3inv = L3NetworkInventory.valueOf(l3vo); VirtualRouterStruct s = new VirtualRouterStruct(); 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 EIP[uuid:%s]'s; you may need to use system tag" + " guestL3Network::l3NetworkUuid to specify a particular virtual router offering for the L3Network", offering.getUuid(), l3inv.getUuid(), l3inv.getZoneUuid(), struct.getVip().getL3NetworkUuid(), struct.getEip().getUuid())); } } }); s.setL3Network(l3inv); acquireVirtualRouterVm(s, new ReturnValueCompletion<VirtualRouterVmInventory>(completion) { @Override public void success(final VirtualRouterVmInventory vr) { applyEip(vr, struct, new Completion(completion) { @Override public void success() { SimpleQuery<VirtualRouterEipRefVO> q = dbf.createQuery(VirtualRouterEipRefVO.class); q.add(VirtualRouterEipRefVO_.eipUuid, Op.EQ, struct.getEip().getUuid()); if (!q.isExists()) { // if virtual router is stopped outside zstack (e.g. the host rebbot) // database will still have VirtualRouterEipRefVO for this EIP. // in this case, don't create the record again VirtualRouterEipRefVO ref = new VirtualRouterEipRefVO(); ref.setEipUuid(struct.getEip().getUuid()); ref.setVirtualRouterVmUuid(vr.getUuid()); dbf.persist(ref); } completion.success(); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override public void fail(ErrorCode errorCode) { completion.fail(errorCode); } }); } @Override public void revokeEip(final EipStruct struct, final Completion completion) { SimpleQuery<VirtualRouterEipRefVO> q = dbf.createQuery(VirtualRouterEipRefVO.class); q.add(VirtualRouterEipRefVO_.eipUuid, SimpleQuery.Op.EQ, struct.getEip().getUuid()); final VirtualRouterEipRefVO ref = q.find(); if (ref == null) { // vr may have been deleted completion.success(); return; } VirtualRouterVmVO vrvo = dbf.findByUuid(ref.getVirtualRouterVmUuid(), VirtualRouterVmVO.class); if (vrvo.getState() != VmInstanceState.Running) { // rule will be synced when vr state changes to Running dbf.remove(ref); completion.success(); return; } final VirtualRouterVmInventory vr = VirtualRouterVmInventory.valueOf(vrvo); //TODO: add GC final FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("revoke-eip-%s-vr-%s", struct.getEip().getUuid(), vr.getUuid())); chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { VirtualRouterCommands.RemoveEipCmd cmd = new VirtualRouterCommands.RemoveEipCmd(); EipTO to = new EipTO(); String priMac = CollectionUtils.find(vr.getVmNics(), new Function<String, VmNicInventory>() { @Override public String call(VmNicInventory arg) { if (arg.getL3NetworkUuid().equals(struct.getNic().getL3NetworkUuid())) { return arg.getMac(); } return null; } }); to.setPrivateMac(priMac); to.setSnatInboundTraffic(struct.isSnatInboundTraffic()); to.setVipIp(struct.getVip().getIp()); to.setGuestIp(struct.getNic().getIp()); cmd.setEip(to); VirtualRouterAsyncHttpCallMsg msg = new VirtualRouterAsyncHttpCallMsg(); msg.setVmInstanceUuid(vr.getUuid()); msg.setCommand(cmd); msg.setCommandTimeout(apiTimeoutManager.getTimeout(cmd.getClass(), "30m")); msg.setCheckStatus(true); msg.setPath(VirtualRouterConstant.VR_REMOVE_EIP); bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, vr.getUuid()); bus.send(msg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { trigger.setError(reply.getError()); } else { VirtualRouterAsyncHttpCallReply re = reply.castReply(); RemoveEipRsp ret = re.toResponse(RemoveEipRsp.class); if (!ret.isSuccess()) { ErrorCode err = operr("failed to remove eip[uuid:%s, name:%s, ip:%s] for vm nic[uuid:%s] on virtual router[uuid:%s], %s", struct.getEip().getUuid(), struct.getEip().getName(), struct.getVip().getIp(), struct.getNic().getUuid(), vr.getUuid(), ret.getError()); trigger.setError(err); } } trigger.next(); } }); } }).then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { asf.removeFirewall(vr.getUuid(), struct.getVip().getL3NetworkUuid(), getFirewallRules(struct), new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { logger.warn(String.format("failed to remove firewall rules on virtual router[uuid:%s, l3Network uuid:%s], %s", vr.getUuid(), struct.getVip().getL3NetworkUuid(), errorCode)); trigger.next(); } }); } }).done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { String info = String.format("successfully removed eip[uuid:%s, name:%s, ip:%s] for vm nic[uuid:%s] on virtual router[uuid:%s]", struct.getEip().getUuid(), struct.getEip().getName(), struct.getVip().getIp(), struct.getNic().getUuid(), vr.getUuid()); logger.debug(info); dbf.remove(ref); completion.success(); } }).error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { // We need to remove the 'ref' record, otherwise the next time when // deleting EIP is requested, we will get ConstraintViolationException. dbf.remove(ref); completion.fail(errCode); } }).start(); } @Override public String getNetworkServiceProviderType() { return VirtualRouterConstant.VIRTUAL_ROUTER_PROVIDER_TYPE; } }