package org.zstack.network.service.lb; 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.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.OperationFailureException; 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.query.AddExpandedQueryExtensionPoint; import org.zstack.header.query.ExpandedQueryAliasStruct; import org.zstack.header.query.ExpandedQueryStruct; import org.zstack.header.tag.AbstractSystemTagOperationJudger; import org.zstack.header.tag.SystemTagInventory; import org.zstack.header.tag.SystemTagValidator; import org.zstack.header.vm.VmNicInventory; import org.zstack.identity.AccountManager; import org.zstack.identity.QuotaUtil; import org.zstack.network.service.vip.Vip; import org.zstack.network.service.vip.VipInventory; import org.zstack.network.service.vip.VipVO; import org.zstack.tag.TagManager; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; import static org.zstack.core.Platform.argerr; import static org.zstack.core.Platform.operr; import javax.persistence.TypedQuery; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.zstack.utils.CollectionDSL.list; /** * Created by frank on 8/8/2015. */ public class LoadBalancerManagerImpl extends AbstractService implements LoadBalancerManager, AddExpandedQueryExtensionPoint, ReportQuotaExtensionPoint { private static final CLogger logger = Utils.getLogger(LoadBalancerManagerImpl.class); @Autowired private CloudBus bus; @Autowired private DatabaseFacade dbf; @Autowired private AccountManager acntMgr; @Autowired private ErrorFacade errf; @Autowired private PluginRegistry pluginRgty; @Autowired private TagManager tagMgr; private Map<String, LoadBalancerBackend> backends = new HashMap<String, LoadBalancerBackend>(); @Override @MessageSafe public void handleMessage(Message msg) { if (msg instanceof LoadBalancerMessage) { passThrough((LoadBalancerMessage) msg); } else if (msg instanceof APIMessage) { handleApiMessage((APIMessage) msg); } else { handleLocalMessage(msg); } } private void passThrough(LoadBalancerMessage msg) { LoadBalancerVO vo = dbf.findByUuid(msg.getLoadBalancerUuid(), LoadBalancerVO.class); if (vo == null) { throw new OperationFailureException(operr("cannot find the load balancer[uuid:%s]", msg.getLoadBalancerUuid())); } LoadBalancerBase base = new LoadBalancerBase(vo); base.handleMessage((Message) msg); } protected void handleLocalMessage(Message msg) { bus.dealWithUnknownMessage(msg); } protected void handleApiMessage(APIMessage msg) { if (msg instanceof APICreateLoadBalancerMsg) { handle((APICreateLoadBalancerMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } private void handle(final APICreateLoadBalancerMsg msg) { final APICreateLoadBalancerEvent evt = new APICreateLoadBalancerEvent(msg.getId()); final VipInventory vip = VipInventory.valueOf(dbf.findByUuid(msg.getVipUuid(), VipVO.class)); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("create-lb-%s", msg.getName())); chain.then(new ShareFlow() { LoadBalancerVO vo; @Override public void setup() { flow(new Flow() { String __name__ = "acquire-vip"; boolean s = false; @Override public void run(FlowTrigger trigger, Map data) { Vip v = new Vip(vip.getUuid()); v.setUseFor(LoadBalancerConstants.LB_NETWORK_SERVICE_TYPE_STRING); v.acquire(false, 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(vip.getUuid()).release(false, 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__ = "write-to-db"; @Override public void run(FlowTrigger trigger, Map data) { vo = new LoadBalancerVO(); vo.setName(msg.getName()); vo.setUuid(msg.getResourceUuid() == null ? Platform.getUuid() : msg.getResourceUuid()); vo.setDescription(msg.getDescription()); vo.setVipUuid(msg.getVipUuid()); vo.setState(LoadBalancerState.Enabled); vo = dbf.persistAndRefresh(vo); acntMgr.createAccountResourceRef(msg.getSession().getAccountUuid(), vo.getUuid(), LoadBalancerVO.class); tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), LoadBalancerVO.class.getSimpleName()); trigger.next(); } }); done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { evt.setInventory(LoadBalancerInventory.valueOf(dbf.reload(vo))); bus.publish(evt); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { evt.setError(errCode); bus.publish(evt); } }); } }).start(); } @Override public String getId() { return bus.makeLocalServiceId(LoadBalancerConstants.SERVICE_ID); } @Override public boolean start() { for (LoadBalancerBackend bkd : pluginRgty.getExtensionList(LoadBalancerBackend.class)) { LoadBalancerBackend old = backends.get(bkd.getNetworkServiceProviderType()); if (old != null) { throw new CloudRuntimeException(String.format("duplicate LoadBalancerBackend[%s, %s]", old.getClass(), bkd.getNetworkServiceProviderType())); } backends.put(bkd.getNetworkServiceProviderType(), bkd); } prepareSystemTags(); return true; } private void prepareSystemTags() { AbstractSystemTagOperationJudger judger = new AbstractSystemTagOperationJudger() { @Override public void tagPreDeleted(SystemTagInventory tag) { throw new OperationFailureException(operr("cannot delete the system tag[%s]. The load balancer plugin relies on it, you can only update it", tag.getTag())); } }; LoadBalancerSystemTags.BALANCER_ALGORITHM.installJudger(judger); LoadBalancerSystemTags.HEALTHY_THRESHOLD.installJudger(judger); LoadBalancerSystemTags.MAX_CONNECTION.installJudger(judger); LoadBalancerSystemTags.HEALTH_INTERVAL.installJudger(judger); LoadBalancerSystemTags.HEALTH_TARGET.installJudger(judger); LoadBalancerSystemTags.CONNECTION_IDLE_TIMEOUT.installJudger(judger); LoadBalancerSystemTags.HEALTH_TIMEOUT.installJudger(judger); LoadBalancerSystemTags.UNHEALTHY_THRESHOLD.installJudger(judger); LoadBalancerSystemTags.BALANCER_ALGORITHM.installValidator(new SystemTagValidator() { @Override public void validateSystemTag(String resourceUuid, Class resourceType, String systemTag) { String algorithm = LoadBalancerSystemTags.BALANCER_ALGORITHM.getTokenByTag(systemTag, LoadBalancerSystemTags.BALANCER_ALGORITHM_TOKEN); if (!LoadBalancerConstants.BALANCE_ALGORITHMS.contains(algorithm)) { throw new OperationFailureException(argerr("invalid balance algorithm[%s], valid algorithms are %s", algorithm, LoadBalancerConstants.BALANCE_ALGORITHMS)); } } }); LoadBalancerSystemTags.UNHEALTHY_THRESHOLD.installValidator(new SystemTagValidator() { @Override public void validateSystemTag(String resourceUuid, Class resourceType, String systemTag) { String s = LoadBalancerSystemTags.UNHEALTHY_THRESHOLD.getTokenByTag(systemTag, LoadBalancerSystemTags.UNHEALTHY_THRESHOLD_TOKEN); try { Long.valueOf(s); } catch (NumberFormatException e) { throw new OperationFailureException(argerr("invalid unhealthy threshold[%s], %s is not a number", systemTag, s)); } } }); LoadBalancerSystemTags.HEALTHY_THRESHOLD.installValidator(new SystemTagValidator() { @Override public void validateSystemTag(String resourceUuid, Class resourceType, String systemTag) { String s = LoadBalancerSystemTags.HEALTHY_THRESHOLD.getTokenByTag(systemTag, LoadBalancerSystemTags.HEALTHY_THRESHOLD_TOKEN); try { Long.valueOf(s); } catch (NumberFormatException e) { throw new OperationFailureException(argerr("invalid healthy threshold[%s], %s is not a number", systemTag, s)); } } }); LoadBalancerSystemTags.HEALTH_TIMEOUT.installValidator(new SystemTagValidator() { @Override public void validateSystemTag(String resourceUuid, Class resourceType, String systemTag) { String s = LoadBalancerSystemTags.HEALTH_TIMEOUT.getTokenByTag(systemTag, LoadBalancerSystemTags.HEALTH_TIMEOUT_TOKEN); try { Long.valueOf(s); } catch (NumberFormatException e) { throw new OperationFailureException(argerr("invalid healthy timeout[%s], %s is not a number", systemTag, s)); } } }); LoadBalancerSystemTags.CONNECTION_IDLE_TIMEOUT.installValidator(new SystemTagValidator() { @Override public void validateSystemTag(String resourceUuid, Class resourceType, String systemTag) { String s = LoadBalancerSystemTags.CONNECTION_IDLE_TIMEOUT.getTokenByTag(systemTag, LoadBalancerSystemTags.CONNECTION_IDLE_TIMEOUT_TOKEN); try { Long.valueOf(s); } catch (NumberFormatException e) { throw new OperationFailureException(argerr("invalid connection idle timeout[%s], %s is not a number", systemTag, s)); } } }); LoadBalancerSystemTags.HEALTH_INTERVAL.installValidator(new SystemTagValidator() { @Override public void validateSystemTag(String resourceUuid, Class resourceType, String systemTag) { String s = LoadBalancerSystemTags.HEALTH_INTERVAL.getTokenByTag(systemTag, LoadBalancerSystemTags.HEALTH_INTERVAL_TOKEN); try { Long.valueOf(s); } catch (NumberFormatException e) { throw new OperationFailureException(argerr("invalid health check interval[%s], %s is not a number", systemTag, s)); } } }); LoadBalancerSystemTags.MAX_CONNECTION.installValidator(new SystemTagValidator() { @Override public void validateSystemTag(String resourceUuid, Class resourceType, String systemTag) { String s = LoadBalancerSystemTags.MAX_CONNECTION.getTokenByTag(systemTag, LoadBalancerSystemTags.MAX_CONNECTION_TOKEN); try { Long.valueOf(s); } catch (NumberFormatException e) { throw new OperationFailureException(argerr("invalid max connection[%s], %s is not a number", systemTag, s)); } } }); LoadBalancerSystemTags.HEALTH_TARGET.installValidator(new SystemTagValidator() { @Override public void validateSystemTag(String resourceUuid, Class resourceType, String systemTag) { String target = LoadBalancerSystemTags.HEALTH_TARGET.getTokenByTag(systemTag, LoadBalancerSystemTags.HEALTH_TARGET_TOKEN); String[] ts = target.split(":"); if (ts.length != 2) { throw new OperationFailureException(argerr("invalid health target[%s], the format is targetCheckProtocol:port, for example, tcp:default", systemTag)); } String protocol = ts[0]; if (!LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCOLS.contains(protocol)) { throw new OperationFailureException(argerr("invalid health target[%s], the target checking protocol[%s] is invalid, valid protocols are %s", systemTag, protocol, LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCOLS)); } String port = ts[1]; if (!"default".equals(port)) { try { int p = Integer.valueOf(port); if (p < 1 || p > 65535) { throw new OperationFailureException(argerr("invalid invalid health target[%s], port[%s] is not in the range of [1, 65535]", systemTag, port)); } } catch (NumberFormatException e) { throw new OperationFailureException(argerr("invalid invalid health target[%s], port[%s] is not a number", systemTag, port)); } } } }); } @Override public boolean stop() { return true; } @Override public LoadBalancerBackend getBackend(String providerType) { LoadBalancerBackend bkd = backends.get(providerType); if (bkd == null) { throw new CloudRuntimeException(String.format("cannot find LoadBalancerBackend[provider type:%s]", providerType)); } return bkd; } @Override public List<ExpandedQueryStruct> getExpandedQueryStructs() { List<ExpandedQueryStruct> structs = new ArrayList<ExpandedQueryStruct>(); ExpandedQueryStruct s = new ExpandedQueryStruct(); s.setExpandedField("loadBalancerListenerRef"); s.setHidden(true); s.setForeignKey("uuid"); s.setExpandedInventoryKey("vmNicUuid"); s.setInventoryClassToExpand(VmNicInventory.class); s.setInventoryClass(LoadBalancerListenerVmNicRefInventory.class); structs.add(s); s = new ExpandedQueryStruct(); s.setInventoryClassToExpand(VipInventory.class); s.setExpandedField("loadBalancer"); s.setInventoryClass(LoadBalancerInventory.class); s.setForeignKey("uuid"); s.setExpandedInventoryKey("vipUuid"); structs.add(s); return structs; } @Override public List<ExpandedQueryAliasStruct> getExpandedQueryAliasesStructs() { List<ExpandedQueryAliasStruct> structs = new ArrayList<ExpandedQueryAliasStruct>(); ExpandedQueryAliasStruct s = new ExpandedQueryAliasStruct(); s.setInventoryClass(VmNicInventory.class); s.setAlias("loadBalancerListener"); s.setExpandedField("loadBalancerListenerRef.listener"); structs.add(s); return structs; } @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 APICreateLoadBalancerMsg) { check((APICreateLoadBalancerMsg) 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(LoadBalancerConstants.QUOTA_LOAD_BALANCER_NUM); usage.setUsed(getUsedLb(accountUuid)); return list(usage); } @Transactional(readOnly = true) private long getUsedLb(String accountUuid) { String sql = "select count(lb) from LoadBalancerVO lb, AccountResourceRefVO ref where ref.resourceUuid = lb.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", LoadBalancerVO.class.getSimpleName()); Long en = q.getSingleResult(); en = en == null ? 0 : en; return en; } private void check(APICreateLoadBalancerMsg msg, Map<String, QuotaPair> pairs) { long lbNum = pairs.get(LoadBalancerConstants.QUOTA_LOAD_BALANCER_NUM).getValue(); long en = getUsedLb(msg.getSession().getAccountUuid()); if (en + 1 > lbNum) { 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(), LoadBalancerConstants.QUOTA_LOAD_BALANCER_NUM, lbNum) )); } } }; Quota quota = new Quota(); quota.addMessageNeedValidation(APICreateLoadBalancerMsg.class); quota.setOperator(checker); QuotaPair p = new QuotaPair(); p.setName(LoadBalancerConstants.QUOTA_LOAD_BALANCER_NUM); p.setValue(20); quota.addPair(p); return list(quota); } }