package org.zstack.network.service.eip; 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.*; 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.core.Completion; import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.identity.*; import org.zstack.header.identity.Quota.QuotaOperator; import org.zstack.header.identity.Quota.QuotaPair; 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.UsedIpInventory; import org.zstack.header.network.service.NetworkServiceProviderType; 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.Utils; import org.zstack.utils.function.Function; import org.zstack.utils.logging.CLogger; import static org.zstack.core.Platform.err; import static org.zstack.core.Platform.operr; import javax.persistence.Tuple; import javax.persistence.TypedQuery; import java.util.*; import java.util.stream.Collectors; import static org.zstack.utils.CollectionDSL.list; /** */ public class EipManagerImpl extends AbstractService implements EipManager, VipReleaseExtensionPoint, AddExpandedQueryExtensionPoint, ReportQuotaExtensionPoint, VmPreAttachL3NetworkExtensionPoint, VmIpChangedExtensionPoint, ResourceOwnerAfterChangeExtensionPoint { private static final CLogger logger = Utils.getLogger(EipManagerImpl.class); @Autowired private CloudBus bus; @Autowired private DatabaseFacade dbf; @Autowired private PluginRegistry pluginRgty; @Autowired private NetworkServiceManager nwServiceMgr; @Autowired private AccountManager acntMgr; @Autowired private VipManager vipMgr; @Autowired private TagManager tagMgr; @Autowired private ErrorFacade errf; private Map<String, EipBackend> backends = new HashMap<>(); @Override @MessageSafe public void handleMessage(Message msg) { if (msg instanceof APIMessage) { handleApiMessage((APIMessage) msg); } else { handleLocalMessage(msg); } } private void handleLocalMessage(Message msg) { bus.dealWithUnknownMessage(msg); } private void handleApiMessage(APIMessage msg) { if (msg instanceof APICreateEipMsg) { handle((APICreateEipMsg) msg); } else if (msg instanceof APIDeleteEipMsg) { handle((APIDeleteEipMsg) msg); } else if (msg instanceof APIAttachEipMsg) { handle((APIAttachEipMsg) msg); } else if (msg instanceof APIDetachEipMsg) { handle((APIDetachEipMsg) msg); } else if (msg instanceof APIChangeEipStateMsg) { handle((APIChangeEipStateMsg) msg); } else if (msg instanceof APIGetEipAttachableVmNicsMsg) { handle((APIGetEipAttachableVmNicsMsg) msg); } else if (msg instanceof APIUpdateEipMsg) { handle((APIUpdateEipMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } private void handle(APIUpdateEipMsg msg) { EipVO vo = dbf.findByUuid(msg.getUuid(), EipVO.class); boolean update = false; 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); } APIUpdateEipEvent evt = new APIUpdateEipEvent(msg.getId()); evt.setInventory(EipInventory.valueOf(vo)); bus.publish(evt); } @Transactional(readOnly = true) private List<VmNicInventory> getAttachableVmNicForEip(String eipUuid, String vipUuid) { String zoneUuid; String providerType; String peerL3NetworkUuid = null; if (eipUuid != null) { Tuple t = SQL.New("select l3.zoneUuid, vip.uuid, vip.peerL3NetworkUuid" + " from L3NetworkVO l3, VipVO vip, EipVO eip" + " where l3.uuid = vip.l3NetworkUuid" + " and eip.vipUuid = vip.uuid" + " and eip.uuid = :eipUuid", Tuple.class) .param("eipUuid", eipUuid) .find(); zoneUuid = t.get(0, String.class); vipUuid = t.get(1, String.class); peerL3NetworkUuid = t.get(2, String.class); providerType = SQL.New("select v.serviceProvider from VipVO v, EipVO e where e.vipUuid = v.uuid" + " and e.uuid = :euuid").param("euuid", eipUuid).find(); } else { String sql = "select l3.zoneUuid" + " from L3NetworkVO l3, VipVO vip" + " where l3.uuid = vip.l3NetworkUuid" + " and vip.uuid = :vipUuid"; TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class); q.setParameter("vipUuid", vipUuid); zoneUuid = q.getSingleResult(); providerType = SQL.New("select v.serviceProvider from VipVO v where v.uuid = :uuid") .param("uuid", vipUuid).find(); } List<String> l3Uuids; if (providerType != null) { // the eip is created on the backend l3Uuids = SQL.New("select l3.uuid" + " from L3NetworkVO l3, VipVO vip, NetworkServiceL3NetworkRefVO ref, NetworkServiceProviderVO np" + " 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" + " and np.uuid = ref.networkServiceProviderUuid" + " and np.type = :npType") .param("system", false) .param("zoneUuid", zoneUuid) .param("nsType", EipConstant.EIP_NETWORK_SERVICE_TYPE) .param("npType", providerType) .param("vipUuid", vipUuid) .list(); } else { // the eip is not created on the backend l3Uuids = SQL.New("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") .param("system", false) .param("zoneUuid", zoneUuid) .param("nsType", EipConstant.EIP_NETWORK_SERVICE_TYPE) .param("vipUuid", vipUuid) .list(); } if (peerL3NetworkUuid != null) { String finalPeerL3NetworkUuid = peerL3NetworkUuid; l3Uuids = l3Uuids.stream().filter(l -> l.equals(finalPeerL3NetworkUuid)).collect(Collectors.toList()); } if (l3Uuids.isEmpty()) { return new ArrayList<>(); } List<VmNicVO> nics = SQL.New("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) " + // IP = null means the VM is just recovered without any IP allocated " and nic.ip is not null") .param("l3Uuids", l3Uuids) .param("vmType", VmInstanceConstant.USER_VM_TYPE) .param("vmStates", Arrays.asList(VmInstanceState.Running, VmInstanceState.Stopped)) .list(); return VmNicInventory.valueOf(nics); } private void handle(APIGetEipAttachableVmNicsMsg msg) { APIGetEipAttachableVmNicsReply reply = new APIGetEipAttachableVmNicsReply(); List<VmNicInventory> nics = getAttachableVmNicForEip(msg.getEipUuid(), msg.getVipUuid()); reply.setInventories(nics); bus.reply(msg, reply); } private void handle(APIChangeEipStateMsg msg) { EipVO eip = dbf.findByUuid(msg.getUuid(), EipVO.class); eip.setState(eip.getState().nextState(EipStateEvent.valueOf(msg.getStateEvent()))); eip = dbf.updateAndRefresh(eip); APIChangeEipStateEvent evt = new APIChangeEipStateEvent(msg.getId()); evt.setInventory(EipInventory.valueOf(eip)); bus.publish(evt); } private void handle(APIDetachEipMsg msg) { final APIDetachEipEvent evt = new APIDetachEipEvent(msg.getId()); final EipVO vo = dbf.findByUuid(msg.getUuid(), EipVO.class); VmNicVO nicvo = dbf.findByUuid(vo.getVmNicUuid(), VmNicVO.class); VmNicInventory nicInventory = VmNicInventory.valueOf(nicvo); VipVO vipvo = dbf.findByUuid(vo.getVipUuid(), VipVO.class); VipInventory vipInventory = VipInventory.valueOf(vipvo); NetworkServiceProviderType providerType = nwServiceMgr. getTypeOfNetworkServiceProviderForService(nicInventory.getL3NetworkUuid(), EipConstant.EIP_TYPE); EipStruct struct = new EipStruct(); struct.setVip(vipInventory); struct.setNic(nicInventory); struct.setEip(EipInventory.valueOf(vo)); struct.setSnatInboundTraffic(EipGlobalConfig.SNAT_INBOUND_TRAFFIC.value(Boolean.class)); detachEipAndUpdateDb(struct, providerType.toString(), new Completion(msg) { @Override public void success() { evt.setInventory(EipInventory.valueOf(dbf.reload(vo))); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { evt.setError(errorCode); bus.publish(evt); } }); } private void handle(final APIAttachEipMsg msg) { final APIAttachEipEvent evt = new APIAttachEipEvent(msg.getId()); final EipVO vo = dbf.findByUuid(msg.getEipUuid(), EipVO.class); VmNicVO nicvo = dbf.findByUuid(msg.getVmNicUuid(), VmNicVO.class); final VmNicInventory nicInventory = VmNicInventory.valueOf(nicvo); VipVO vipvo = dbf.findByUuid(vo.getVipUuid(), VipVO.class); VipInventory vipInventory = VipInventory.valueOf(vipvo); SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class); q.select(VmInstanceVO_.state); q.add(VmInstanceVO_.uuid, SimpleQuery.Op.EQ, nicvo.getVmInstanceUuid()); VmInstanceState state = q.findValue(); if (VmInstanceState.Running != state) { vo.setVmNicUuid(nicInventory.getUuid()); vo.setGuestIp(nicvo.getIp()); EipVO evo = dbf.updateAndRefresh(vo); evt.setInventory(EipInventory.valueOf(evo)); bus.publish(evt); return; } NetworkServiceProviderType providerType = nwServiceMgr.getTypeOfNetworkServiceProviderForService( nicInventory.getL3NetworkUuid(), EipConstant.EIP_TYPE); EipStruct struct = new EipStruct(); struct.setNic(nicInventory); struct.setVip(vipInventory); struct.setEip(EipInventory.valueOf(vo)); struct.setSnatInboundTraffic(EipGlobalConfig.SNAT_INBOUND_TRAFFIC.value(Boolean.class)); attachEip(struct, providerType.toString(), new Completion(msg) { @Override public void success() { vo.setVmNicUuid(nicInventory.getUuid()); vo.setGuestIp(nicInventory.getIp()); EipVO evo = dbf.updateAndRefresh(vo); evt.setInventory(EipInventory.valueOf(evo)); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { evt.setError(errorCode); bus.publish(evt); } }); } private void handle(APIDeleteEipMsg msg) { final APIDeleteEipEvent evt = new APIDeleteEipEvent(msg.getId()); final EipVO vo = dbf.findByUuid(msg.getUuid(), EipVO.class); VipVO vipvo = dbf.findByUuid(vo.getVipUuid(), VipVO.class); VipInventory vipInventory = VipInventory.valueOf(vipvo); if (vo.getVmNicUuid() == null) { new Vip(vipvo.getUuid()).release(new Completion(msg) { @Override public void success() { dbf.remove(vo); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { evt.setError(errorCode); bus.publish(evt); } }); return; } VmNicVO nicvo = dbf.findByUuid(vo.getVmNicUuid(), VmNicVO.class); VmNicInventory nicInventory = VmNicInventory.valueOf(nicvo); EipStruct struct = new EipStruct(); struct.setNic(nicInventory); struct.setVip(vipInventory); struct.setEip(EipInventory.valueOf(vo)); struct.setSnatInboundTraffic(EipGlobalConfig.SNAT_INBOUND_TRAFFIC.value(Boolean.class)); NetworkServiceProviderType providerType = nwServiceMgr. getTypeOfNetworkServiceProviderForService(nicInventory.getL3NetworkUuid(), EipConstant.EIP_TYPE); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("delete-eip-vmNic-%s-vip-%s", nicvo.getUuid(), vipvo.getUuid())); chain.then(new ShareFlow() { @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "delete-eip-from-backend"; @Override public void run(FlowTrigger trigger, Map data) { boolean callBackend = Q.New(VmInstanceVO.class).select(VmInstanceVO_.state) .eq(VmInstanceVO_.uuid, nicvo.getVmInstanceUuid()).findValue() == VmInstanceState.Running; if (!callBackend) { logger.warn(String.format("the vm[uuid:%s] is not running, no need to delete the EIP[uuid:%s] from the backend", nicvo.getVmInstanceUuid(), struct.getEip().getUuid())); trigger.next(); return; } EipBackend bkd = getEipBackend(providerType.toString()); bkd.revokeEip(struct, new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { //TODO: add GC instead of failing the API logger.warn(String.format("failed to detach eip[uuid:%s, ip:%s, vm nic uuid:%s] on service provider[%s], service provider will garbage collect. %s", struct.getEip().getUuid(), struct.getVip().getIp(), struct.getNic().getUuid(), providerType, errorCode)); trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "release-vip"; @Override public void run(FlowTrigger trigger, Map data) { new Vip(vipInventory.getUuid()).release(new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { dbf.remove(vo); bus.publish(evt); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { evt.setError(errCode); bus.publish(evt); } }); } }).start(); } private void handle(APICreateEipMsg msg) { final APICreateEipEvent evt = new APICreateEipEvent(msg.getId()); EipVO vo = new EipVO(); if (msg.getResourceUuid() != null) { vo.setUuid(msg.getResourceUuid()); } else { vo.setUuid(Platform.getUuid()); } vo.setName(msg.getName()); vo.setDescription(msg.getDescription()); vo.setVipUuid(msg.getVipUuid()); SimpleQuery<VipVO> vipq = dbf.createQuery(VipVO.class); vipq.select(VipVO_.ip); vipq.add(VipVO_.uuid, Op.EQ, msg.getVipUuid()); String vipIp = vipq.findValue(); vo.setVipIp(vipIp); vo.setVmNicUuid(msg.getVmNicUuid()); vo.setState(EipState.Enabled); EipVO finalVo1 = vo; vo = new SQLBatchWithReturn<EipVO>() { @Override protected EipVO scripts() { persist(finalVo1); reload(finalVo1); acntMgr.createAccountResourceRef(msg.getSession().getAccountUuid(), finalVo1.getUuid(), EipVO.class); tagMgr.createTagsFromAPICreateMessage(msg, finalVo1.getUuid(), EipVO.class.getSimpleName()); return finalVo1; } }.execute(); VipVO vipvo = dbf.findByUuid(msg.getVipUuid(), VipVO.class); final VipInventory vipInventory = VipInventory.valueOf(vipvo); if (vo.getVmNicUuid() == null) { EipVO finalVo = vo; Vip vip = new Vip(vipvo.getUuid()); vip.setUseFor(EipConstant.EIP_NETWORK_SERVICE_TYPE); vip.acquire(false, new Completion(msg) { @Override public void success() { evt.setInventory(EipInventory.valueOf(finalVo)); logger.debug(String.format("successfully created eip[uuid:%s, name:%s] on vip[uuid:%s, ip:%s]", finalVo.getUuid(), finalVo.getName(), vipInventory.getUuid(), vipInventory.getIp())); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { evt.setError(errorCode); bus.publish(evt); } }); return; } VmNicVO nicvo = dbf.findByUuid(msg.getVmNicUuid(), VmNicVO.class); vo.setGuestIp(nicvo.getIp()); vo = dbf.updateAndRefresh(vo); final EipInventory retinv = EipInventory.valueOf(vo); final VmNicInventory nicInventory = VmNicInventory.valueOf(nicvo); SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class); q.select(VmInstanceVO_.state); q.add(VmInstanceVO_.uuid, SimpleQuery.Op.EQ, nicvo.getVmInstanceUuid()); VmInstanceState state = q.findValue(); if (state != VmInstanceState.Running) { EipVO finalVo = vo; new Vip(vipvo.getUuid()).acquire(false, new Completion(msg) { @Override public void success() { evt.setInventory(EipInventory.valueOf(finalVo)); logger.debug(String.format("successfully created eip[uuid:%s, name:%s] on vip[uuid:%s, ip:%s]", finalVo.getUuid(), finalVo.getName(), vipInventory.getUuid(), vipInventory.getIp())); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { evt.setError(errorCode); bus.publish(evt); } }); return; } final EipVO fevo = vo; EipStruct struct = new EipStruct(); struct.setEip(EipInventory.valueOf(vo)); struct.setSnatInboundTraffic(EipGlobalConfig.SNAT_INBOUND_TRAFFIC.value(Boolean.class)); struct.setNic(nicInventory); struct.setVip(vipInventory); NetworkServiceProviderType providerType = nwServiceMgr.getTypeOfNetworkServiceProviderForService(nicInventory.getL3NetworkUuid(), EipConstant.EIP_TYPE); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("create-eip-vmNic-%s-vip-%s", msg.getVmNicUuid(), msg.getVipUuid())); 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(vipInventory.getUuid()); vip.setServiceProvider(providerType.toString()); vip.setUseFor(EipConstant.EIP_NETWORK_SERVICE_TYPE); vip.setPeerL3NetworkUuid(nicInventory.getL3NetworkUuid()); 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; } Vip vip = new Vip(vipInventory.getUuid()); vip.release(new Completion(trigger) { @Override public void success() { trigger.rollback(); } @Override public void fail(ErrorCode errorCode) { logger.warn(errorCode.toString()); trigger.rollback(); } }); } }); flow(new NoRollbackFlow() { String __name__ = "create-eip-on-backend"; @Override public void run(FlowTrigger trigger, Map data) { EipBackend bkd = getEipBackend(providerType.toString()); bkd.applyEip(struct, new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { evt.setInventory(retinv); logger.debug(String.format("successfully created eip[uuid:%s, name:%s] on vip[uuid:%s] for vm nic[uuid:%s]", retinv.getUuid(), retinv.getName(), vipInventory.getUuid(), nicInventory.getUuid())); bus.publish(evt); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { evt.setError(errCode); dbf.remove(fevo); logger.debug(String.format("failed to create eip[uuid:%s, name:%s] on vip[uuid:%s] for vm nic[uuid:%s], %s", retinv.getUuid(), retinv.getName(), vipInventory.getUuid(), nicInventory.getUuid(), errCode)); bus.publish(evt); } }); } }).start(); } @Override public String getId() { return bus.makeLocalServiceId(EipConstant.SERVICE_ID); } private void populateExtensions() { for (EipBackend ext : pluginRgty.getExtensionList(EipBackend.class)) { EipBackend old = backends.get(ext.getNetworkServiceProviderType()); if (old != null) { throw new CloudRuntimeException(String.format("duplicate EipBackend[%s,%s] for type[%s]", old.getClass().getName(), ext.getClass().getName(), ext.getNetworkServiceProviderType())); } backends.put(ext.getNetworkServiceProviderType(), ext); } } @Override public boolean start() { populateExtensions(); return true; } @Override public boolean stop() { return true; } @Override public EipBackend getEipBackend(String providerType) { EipBackend bkd = backends.get(providerType); if (bkd == null) { throw new CloudRuntimeException(String.format("cannot find EipBackend for type[%s]", providerType)); } return bkd; } private void detachEip(final EipStruct struct, final String providerType, final boolean updateDb, final Completion completion) { VmNicInventory nic = struct.getNic(); final EipInventory eip = struct.getEip(); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("detach-eip-%s-vmNic-%s", eip.getUuid(), nic.getUuid())); chain.then(new ShareFlow() { @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "delete-eip-from-backend"; @Override public void run(FlowTrigger trigger, Map data) { EipBackend bkd = getEipBackend(providerType); bkd.revokeEip(struct, new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { //TODO add GC instead of failing the API logger.warn(String.format("failed to detach eip[uuid:%s, ip:%s, vm nic uuid:%s] on service provider[%s], service provider will garbage collect. %s", struct.getEip().getUuid(), struct.getVip().getIp(), struct.getNic().getUuid(), providerType, errorCode)); trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "delete-vip-from-backend"; @Override public void run(FlowTrigger trigger, Map data) { new Vip(eip.getVipUuid()).deleteFromBackend(new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); if (updateDb) { flow(new NoRollbackFlow() { String __name__ = "udpate-eip"; @Override public void run(FlowTrigger trigger, Map data) { UpdateQuery q = UpdateQuery.New(EipVO.class); q.condAnd(EipVO_.uuid, Op.EQ, eip.getUuid()); q.set(EipVO_.vmNicUuid, null); q.set(EipVO_.guestIp, null); q.update(); trigger.next(); } }); } 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 detachEip(EipStruct struct, String providerType, final Completion completion) { detachEip(struct, providerType, false, completion); } @Override public void detachEipAndUpdateDb(EipStruct struct, String providerType, Completion completion) { detachEip(struct, providerType, true, completion); } @Override public void attachEip(final EipStruct struct, final String providerType, final Completion completion) { final EipInventory eip = struct.getEip(); final VmNicInventory nic = struct.getNic(); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("attach-eip-%s-vmNic-%s", eip.getUuid(), nic.getUuid())); chain.then(new ShareFlow() { @Override public void setup() { flow(new Flow() { boolean s = false; String __name__ = "acquire-vip"; @Override public void run(FlowTrigger trigger, Map data) { Vip vip = new Vip(struct.getVip().getUuid()); vip.setServiceProvider(providerType); vip.setUseFor(EipConstant.EIP_NETWORK_SERVICE_TYPE); vip.setPeerL3NetworkUuid(nic.getL3NetworkUuid()); 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; } Vip vip = new Vip(struct.getVip().getUuid()); vip.release(new Completion(trigger) { @Override public void success() { trigger.rollback(); } @Override public void fail(ErrorCode errorCode) { logger.warn(errorCode.toString()); trigger.rollback(); } }); } }); flow(new NoRollbackFlow() { String __name__ = "create-eip-on-backend"; @Override public void run(FlowTrigger trigger, Map data) { EipBackend bkd = getEipBackend(providerType); bkd.applyEip(struct, 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) { completion.success(); } }); error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); } }); } }).start(); } @Override public String getVipUse() { return EipConstant.EIP_NETWORK_SERVICE_TYPE; } @Override public void releaseServicesOnVip(VipInventory vip, final Completion completion) { SimpleQuery<EipVO> eq = dbf.createQuery(EipVO.class); eq.add(EipVO_.vipUuid, SimpleQuery.Op.EQ, vip.getUuid()); final EipVO vo = eq.find(); if (vo.getVmNicUuid() == null) { dbf.remove(vo); completion.success(); return; } VmNicVO nicvo = dbf.findByUuid(vo.getVmNicUuid(), VmNicVO.class); VmNicInventory nicInventory = VmNicInventory.valueOf(nicvo); SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class); q.select(VmInstanceVO_.state); q.add(VmInstanceVO_.uuid, SimpleQuery.Op.EQ, nicvo.getVmInstanceUuid()); VmInstanceState state = q.findValue(); if (VmInstanceState.Stopped == state) { dbf.remove(vo); completion.success(); return; } NetworkServiceProviderType providerType = nwServiceMgr.getTypeOfNetworkServiceProviderForService(nicInventory.getL3NetworkUuid(), EipConstant.EIP_TYPE); EipStruct struct = new EipStruct(); struct.setVip(vip); struct.setNic(nicInventory); struct.setEip(EipInventory.valueOf(vo)); struct.setSnatInboundTraffic(EipGlobalConfig.SNAT_INBOUND_TRAFFIC.value(Boolean.class)); EipBackend bkd = getEipBackend(providerType.toString()); bkd.revokeEip(struct, new Completion(completion) { @Override public void success() { dbf.remove(vo); completion.success(); } @Override public void fail(ErrorCode errorCode) { logger.warn(String.format("failed to detach eip[uuid:%s, ip:%s, vm nic uuid:%s] on service provider[%s], service provider will garbage collect. %s", struct.getEip().getUuid(), struct.getVip().getIp(), struct.getNic().getUuid(), providerType, errorCode)); completion.fail(errorCode); } }); } @Override public List<ExpandedQueryStruct> getExpandedQueryStructs() { List<ExpandedQueryStruct> structs = new ArrayList<>(); ExpandedQueryStruct struct = new ExpandedQueryStruct(); struct.setInventoryClassToExpand(VmNicInventory.class); struct.setExpandedField("eip"); struct.setInventoryClass(EipInventory.class); struct.setForeignKey("uuid"); struct.setExpandedInventoryKey("vmNicUuid"); structs.add(struct); struct = new ExpandedQueryStruct(); struct.setInventoryClassToExpand(VipInventory.class); struct.setExpandedField("eip"); struct.setInventoryClass(EipInventory.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 APICreateEipMsg) { check((APICreateEipMsg) msg, pairs); } else if (msg instanceof APIChangeResourceOwnerMsg) { check((APIChangeResourceOwnerMsg) msg, pairs); } } else { if (msg instanceof APIChangeResourceOwnerMsg) { check((APIChangeResourceOwnerMsg) 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(EipConstant.QUOTA_EIP_NUM); usage.setUsed(getUsedEipNum(accountUuid)); return list(usage); } @Transactional(readOnly = true) private long getUsedEipNum(String accountUuid) { String sql = "select count(eip)" + " from EipVO eip, AccountResourceRefVO ref" + " where ref.resourceUuid = eip.uuid" + " and ref.accountUuid = :auuid" + " and ref.resourceType = :rtype"; TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class); q.setParameter("auuid", accountUuid); q.setParameter("rtype", EipVO.class.getSimpleName()); Long usedEipNum = q.getSingleResult(); usedEipNum = usedEipNum == null ? 0 : usedEipNum; return usedEipNum; } @Transactional(readOnly = true) private long getVmEipNum(String vmUuid) { String sql = "select count(eip)" + " from EipVO eip, VmNicVO vmnic" + " where vmnic.vmInstanceUuid = :vmuuid" + " and vmnic.uuid = eip.vmNicUuid"; TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class); q.setParameter("vmuuid", vmUuid); Long vmEipNum = q.getSingleResult(); vmEipNum = vmEipNum == null ? 0 : vmEipNum; return vmEipNum; } @Transactional(readOnly = true) private void check(APICreateEipMsg msg, Map<String, QuotaPair> pairs) { String currentAccountUuid = msg.getSession().getAccountUuid(); String resourceTargetOwnerAccountUuid = msg.getSession().getAccountUuid(); long eipNumQuota = pairs.get(EipConstant.QUOTA_EIP_NUM).getValue(); long usedEipNum = getUsedEipNum(msg.getSession().getAccountUuid()); long askedEipNum = 1; QuotaUtil.QuotaCompareInfo quotaCompareInfo; quotaCompareInfo = new QuotaUtil.QuotaCompareInfo(); quotaCompareInfo.currentAccountUuid = currentAccountUuid; quotaCompareInfo.resourceTargetOwnerAccountUuid = resourceTargetOwnerAccountUuid; quotaCompareInfo.quotaName = EipConstant.QUOTA_EIP_NUM; quotaCompareInfo.quotaValue = eipNumQuota; quotaCompareInfo.currentUsed = usedEipNum; quotaCompareInfo.request = askedEipNum; new QuotaUtil().CheckQuota(quotaCompareInfo); } @Transactional(readOnly = true) private void check(APIChangeResourceOwnerMsg msg, Map<String, Quota.QuotaPair> pairs) { String currentAccountUuid = msg.getSession().getAccountUuid(); String resourceTargetOwnerAccountUuid = msg.getAccountUuid(); if (new QuotaUtil().isAdminAccount(resourceTargetOwnerAccountUuid)) { return; } SimpleQuery<AccountResourceRefVO> q = dbf.createQuery(AccountResourceRefVO.class); q.add(AccountResourceRefVO_.resourceUuid, Op.EQ, msg.getResourceUuid()); AccountResourceRefVO accResRefVO = q.find(); if (accResRefVO.getResourceType().equals(VmInstanceVO.class.getSimpleName())) { long eipNumQuota = pairs.get(EipConstant.QUOTA_EIP_NUM).getValue(); long usedEipNum = getUsedEipNum(resourceTargetOwnerAccountUuid); long askedEipNum = getVmEipNum(msg.getResourceUuid()); QuotaUtil.QuotaCompareInfo quotaCompareInfo; quotaCompareInfo = new QuotaUtil.QuotaCompareInfo(); quotaCompareInfo.currentAccountUuid = currentAccountUuid; quotaCompareInfo.resourceTargetOwnerAccountUuid = resourceTargetOwnerAccountUuid; quotaCompareInfo.quotaName = EipConstant.QUOTA_EIP_NUM; quotaCompareInfo.quotaValue = eipNumQuota; quotaCompareInfo.currentUsed = usedEipNum; quotaCompareInfo.request = askedEipNum; new QuotaUtil().CheckQuota(quotaCompareInfo); } } }; Quota quota = new Quota(); quota.addMessageNeedValidation(APICreateEipMsg.class); quota.addMessageNeedValidation(APIChangeResourceOwnerMsg.class); quota.setOperator(checker); QuotaPair p = new QuotaPair(); p.setName(EipConstant.QUOTA_EIP_NUM); p.setValue(20); quota.addPair(p); return list(quota); } @Override public void vmPreAttachL3Network(final VmInstanceInventory vm, final L3NetworkInventory l3) { final List<String> nicUuids = CollectionUtils.transformToList(vm.getVmNics(), new Function<String, VmNicInventory>() { @Override public String call(VmNicInventory arg) { return arg.getUuid(); } }); if (nicUuids.isEmpty()) { return; } new Runnable() { @Override @Transactional(readOnly = true) public void run() { String sql = "select count(*)" + " from EipVO eip, VipVO vip" + " where eip.vipUuid = vip.uuid" + " and vip.l3NetworkUuid = :l3Uuid" + " and eip.vmNicUuid in (:nicUuids)"; TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class); q.setParameter("l3Uuid", l3.getUuid()); q.setParameter("nicUuids", nicUuids); Long count = q.getSingleResult(); if (count > 0) { throw new OperationFailureException(operr("unable to attach the L3 network[uuid:%s, name:%s] to the vm[uuid:%s, name:%s]," + " because the L3 network is providing EIP to one of the vm's nic", l3.getUuid(), l3.getName(), vm.getUuid(), vm.getName())); } } }.run(); } @Override public void vmIpChanged(VmInstanceInventory vm, VmNicInventory nic, UsedIpInventory oldIp, UsedIpInventory newIp) { SimpleQuery<EipVO> q = dbf.createQuery(EipVO.class); q.add(EipVO_.vmNicUuid, Op.EQ, nic.getUuid()); EipVO eip = q.find(); if (eip == null) { return; } eip.setGuestIp(newIp.getIp()); dbf.update(eip); logger.debug(String.format("update the EIP[uuid:%s, name:%s]'s guest IP from %s to %s for the nic[uuid:%s]", eip.getUuid(), eip.getName(), oldIp.getIp(), newIp.getIp(), nic.getUuid())); } @Override public void resourceOwnerAfterChange(AccountResourceRefInventory ref, String newOwnerUuid) { if (!VmInstanceVO.class.getSimpleName().equals(ref.getResourceType())) { return; } changeEipOwner(ref, newOwnerUuid); } @Transactional private void changeEipOwner(AccountResourceRefInventory ref, String newOwnerUuid) { String sql = "select eip.uuid" + " from VmInstanceVO vm, VmNicVO nic, EipVO eip" + " where vm.uuid = nic.vmInstanceUuid" + " and nic.uuid = eip.vmNicUuid" + " and vm.uuid = :uuid"; TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class); q.setParameter("uuid", ref.getResourceUuid()); List<String> eipUuids = q.getResultList(); if (eipUuids.isEmpty()) { logger.debug(String.format("Vm[uuid:%s] doesn't have any eip, there is no need to change owner of eip.", ref.getResourceUuid())); return; } for (String uuid : eipUuids) { acntMgr.changeResourceOwner(uuid, newOwnerUuid); } } }