[FOR #6301] [TASK 1] Integrate this workflow to Nacos 2.0, replace the Selector in 1.x. (#6680)

* [ISSUE #6301] replace the Selector in 1.x.

* [ISSUE #6301] remove the annotation of API(get all selector types).
This commit is contained in:
brotherlu-xcq 2021-08-18 10:00:54 +08:00 committed by GitHub
parent 6a273c9451
commit 9670627724
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 925 additions and 575 deletions

View File

@ -15,10 +15,11 @@
* *
*/ */
package com.alibaba.nacos.api.naming.selector; package com.alibaba.nacos.api.selector;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.selector.context.CmdbContext; import com.alibaba.nacos.api.selector.context.CmdbContext;
import java.util.List; import java.util.List;
@ -33,6 +34,34 @@ import static com.alibaba.nacos.api.common.Constants.Naming.CMDB_CONTEXT_TYPE;
*/ */
public abstract class AbstractCmdbSelector<T extends Instance> implements Selector<List<T>, CmdbContext<T>, String> { public abstract class AbstractCmdbSelector<T extends Instance> implements Selector<List<T>, CmdbContext<T>, String> {
/**
* the labels expression.
*/
protected String expression;
public String getExpression() {
return expression;
}
public void setExpression(String expression) {
this.expression = expression;
}
@Override
public Selector<List<T>, CmdbContext<T>, String> parse(String expression) throws NacosException {
this.expression = expression;
doParse(expression);
return this;
}
/**
* The real parse logic implement by sub class.
*
* @param expression expression.
* @throws NacosException parse failed exception.
*/
protected abstract void doParse(String expression) throws NacosException;
@Override @Override
public List<T> select(CmdbContext<T> context) { public List<T> select(CmdbContext<T> context) {
return doSelect(context); return doSelect(context);

View File

@ -15,7 +15,7 @@
* *
*/ */
package com.alibaba.nacos.api.naming.selector; package com.alibaba.nacos.api.selector;
import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.exception.NacosException;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
@ -36,17 +36,17 @@ import java.io.Serializable;
* @author chenglu * @author chenglu
* @date 2021-07-09 21:24 * @date 2021-07-09 21:24
*/ */
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION, property = "type") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface Selector<R, C, D> extends Serializable { public interface Selector<R, C, E> extends Serializable {
/** /**
* parse the selector, build the inner info which used by {@link #select(Object)}. * parse the selector, build the inner info which used by {@link #select(Object)}.
* *
* @param condition condition. * @param expression expression.
* @return selector. * @return selector.
* @throws NacosException parse failed exception. * @throws NacosException parse failed exception.
*/ */
Selector<R, C, D> parse(D condition) throws NacosException; Selector<R, C, E> parse(E expression) throws NacosException;
/** /**
* select the target result. * select the target result.

View File

@ -15,16 +15,17 @@
* *
*/ */
package com.alibaba.nacos.api.naming.selector.context; package com.alibaba.nacos.api.selector.context;
import com.alibaba.nacos.api.cmdb.pojo.Entity; import com.alibaba.nacos.api.cmdb.pojo.Entity;
import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.selector.Selector;
import java.util.List; import java.util.List;
/** /**
* The CMDB context is given by the {@link SelectorContextBuilder#build(Object, Object)} and used for the * The CMDB context is given by the {@link SelectorContextBuilder#build(Object, Object)} and used for the
* {@link com.alibaba.nacos.api.naming.selector.Selector#select(Object)}. * {@link Selector#select(Object)}.
* *
* @author chenglu * @author chenglu
* @date 2021-07-09 21:31 * @date 2021-07-09 21:31

View File

@ -15,10 +15,12 @@
* *
*/ */
package com.alibaba.nacos.api.naming.selector.context; package com.alibaba.nacos.api.selector.context;
import com.alibaba.nacos.api.selector.Selector;
/** /**
* The {@link SelectorContextBuilder} mainly for provide the context for {@link com.alibaba.nacos.api.naming.selector.Selector#select(Object)}. * The {@link SelectorContextBuilder} mainly for provide the context for {@link Selector#select(Object)}.
* It provides {@link #build(Object, Object)} method for build context. And also provide {@link #getContextType()} for get the contextType. * It provides {@link #build(Object, Object)} method for build context. And also provide {@link #getContextType()} for get the contextType.
* *
* @author chenglu * @author chenglu
@ -27,12 +29,12 @@ package com.alibaba.nacos.api.naming.selector.context;
public interface SelectorContextBuilder<T, C, P> { public interface SelectorContextBuilder<T, C, P> {
/** /**
* build the context for {@link com.alibaba.nacos.api.naming.selector.Selector#select(Object)}. The user must provide consumer and provider. * build the context for {@link Selector#select(Object)}. The user must provide consumer and provider.
* we provide {@link CmdbContext} for user default who want to use the {@link com.alibaba.nacos.api.naming.pojo.Instance}'s CMDB info. * we provide {@link CmdbContext} for user default who want to use the {@link com.alibaba.nacos.api.naming.pojo.Instance}'s CMDB info.
* *
* @param consumer consumer who launch the select. * @param consumer consumer who launch the select.
* @param provider the provides who are selected by consumer. * @param provider the provides who are selected by consumer.
* @return selectorContext use by {@link com.alibaba.nacos.api.naming.selector.Selector#select(Object)}. * @return selectorContext use by {@link Selector#select(Object)}.
*/ */
T build(C consumer, P provider); T build(C consumer, P provider);

View File

@ -20,11 +20,15 @@ import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.CommonParams; import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.api.naming.utils.NamingUtils;
import com.alibaba.nacos.api.selector.SelectorType; import com.alibaba.nacos.api.selector.Selector;
import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.annotation.Secured;
import com.alibaba.nacos.auth.common.ActionTypes; import com.alibaba.nacos.auth.common.ActionTypes;
import com.alibaba.nacos.common.model.RestResult;
import com.alibaba.nacos.common.model.RestResultUtils;
import com.alibaba.nacos.common.utils.IoUtils; import com.alibaba.nacos.common.utils.IoUtils;
import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.common.utils.NumberUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.core.cluster.ServerMemberManager; import com.alibaba.nacos.core.cluster.ServerMemberManager;
import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.core.utils.WebUtils;
import com.alibaba.nacos.naming.core.Service; import com.alibaba.nacos.naming.core.Service;
@ -38,14 +42,11 @@ import com.alibaba.nacos.naming.core.v2.upgrade.UpgradeJudgement;
import com.alibaba.nacos.naming.misc.Loggers; import com.alibaba.nacos.naming.misc.Loggers;
import com.alibaba.nacos.naming.misc.UtilsAndCommons; import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.naming.pojo.Subscriber;
import com.alibaba.nacos.naming.selector.LabelSelector;
import com.alibaba.nacos.naming.selector.NoneSelector; import com.alibaba.nacos.naming.selector.NoneSelector;
import com.alibaba.nacos.naming.selector.Selector; import com.alibaba.nacos.naming.selector.SelectorManager;
import com.alibaba.nacos.naming.web.NamingResourceParser; import com.alibaba.nacos.naming.web.NamingResourceParser;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.common.utils.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -61,7 +62,8 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Objects;
import java.util.Optional;
/** /**
* Service operation controller. * Service operation controller.
@ -90,6 +92,9 @@ public class ServiceController {
@Autowired @Autowired
private UpgradeJudgement upgradeJudgement; private UpgradeJudgement upgradeJudgement;
@Autowired
private SelectorManager selectorManager;
/** /**
* Create a new service. This API will create a persistence service. * Create a new service. This API will create a persistence service.
* *
@ -364,26 +369,33 @@ public class ServiceController {
} }
} }
private Selector parseSelector(String selectorJsonString) throws Exception { /**
* Get all {@link Selector} types.
*
* @return {@link Selector} types.
*/
@GetMapping("/selector/types")
public RestResult<List<String>> listSelectorTypes() {
return RestResultUtils.success(selectorManager.getAllSelectorTypes());
}
private Selector parseSelector(String selectorJsonString) throws Exception {
if (StringUtils.isBlank(selectorJsonString)) { if (StringUtils.isBlank(selectorJsonString)) {
return new NoneSelector(); return new NoneSelector();
} }
JsonNode selectorJson = JacksonUtils.toObj(URLDecoder.decode(selectorJsonString, "UTF-8")); JsonNode selectorJson = JacksonUtils.toObj(URLDecoder.decode(selectorJsonString, "UTF-8"));
switch (SelectorType.valueOf(selectorJson.get("type").asText())) { String type = Optional.ofNullable(selectorJson.get("type"))
case none: .orElseThrow(() -> new NacosException(NacosException.INVALID_PARAM, "not match any type of selector!"))
return new NoneSelector(); .asText();
case label: String expression = Optional.ofNullable(selectorJson.get("expression"))
String expression = selectorJson.get("expression").asText(); .map(JsonNode::asText)
Set<String> labels = LabelSelector.parseExpression(expression); .orElse(null);
LabelSelector labelSelector = new LabelSelector(); Selector selector = selectorManager.parseSelector(type, expression);
labelSelector.setExpression(expression); if (Objects.isNull(selector)) {
labelSelector.setLabels(labels);
return labelSelector;
default:
throw new NacosException(NacosException.INVALID_PARAM, "not match any type of selector!"); throw new NacosException(NacosException.INVALID_PARAM, "not match any type of selector!");
} }
return selector;
} }
private ServiceOperator getServiceOperator() { private ServiceOperator getServiceOperator() {

View File

@ -22,11 +22,13 @@ import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ServiceInfo; import com.alibaba.nacos.api.naming.pojo.ServiceInfo;
import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.api.naming.utils.NamingUtils;
import com.alibaba.nacos.api.selector.SelectorType; import com.alibaba.nacos.api.selector.Selector;
import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.annotation.Secured;
import com.alibaba.nacos.auth.common.ActionTypes; import com.alibaba.nacos.auth.common.ActionTypes;
import com.alibaba.nacos.common.utils.ConvertUtils; import com.alibaba.nacos.common.utils.ConvertUtils;
import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.common.utils.NumberUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.core.utils.WebUtils;
import com.alibaba.nacos.naming.core.InstanceOperator; import com.alibaba.nacos.naming.core.InstanceOperator;
import com.alibaba.nacos.naming.core.InstanceOperatorClientImpl; import com.alibaba.nacos.naming.core.InstanceOperatorClientImpl;
@ -44,16 +46,14 @@ import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import com.alibaba.nacos.naming.monitor.MetricsMonitor; import com.alibaba.nacos.naming.monitor.MetricsMonitor;
import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.naming.pojo.Subscriber;
import com.alibaba.nacos.naming.pojo.instance.HttpRequestInstanceBuilder; import com.alibaba.nacos.naming.pojo.instance.HttpRequestInstanceBuilder;
import com.alibaba.nacos.naming.selector.LabelSelector;
import com.alibaba.nacos.naming.selector.NoneSelector; import com.alibaba.nacos.naming.selector.NoneSelector;
import com.alibaba.nacos.naming.selector.Selector; import com.alibaba.nacos.naming.selector.SelectorManager;
import com.alibaba.nacos.naming.web.CanDistro; import com.alibaba.nacos.naming.web.CanDistro;
import com.alibaba.nacos.naming.web.NamingResourceParser; import com.alibaba.nacos.naming.web.NamingResourceParser;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import com.alibaba.nacos.common.utils.StringUtils; import org.springframework.beans.factory.annotation.Autowired;
import com.alibaba.nacos.common.utils.NumberUtils;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -70,6 +70,8 @@ import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -107,6 +109,9 @@ public class UpgradeOpsController {
private final UpgradeJudgement upgradeJudgement; private final UpgradeJudgement upgradeJudgement;
@Autowired
private SelectorManager selectorManager;
public UpgradeOpsController(SwitchDomain switchDomain, ServiceManager serviceManager, public UpgradeOpsController(SwitchDomain switchDomain, ServiceManager serviceManager,
ServiceOperatorV1Impl serviceOperatorV1, ServiceOperatorV2Impl serviceOperatorV2, ServiceOperatorV1Impl serviceOperatorV1, ServiceOperatorV2Impl serviceOperatorV2,
InstanceOperatorServiceImpl instanceServiceV1, InstanceOperatorClientImpl instanceServiceV2, InstanceOperatorServiceImpl instanceServiceV1, InstanceOperatorClientImpl instanceServiceV2,
@ -366,25 +371,22 @@ public class UpgradeOpsController {
} }
private Selector parseSelector(String selectorJsonString) throws Exception { private Selector parseSelector(String selectorJsonString) throws Exception {
if (StringUtils.isBlank(selectorJsonString)) { if (StringUtils.isBlank(selectorJsonString)) {
return new NoneSelector(); return new NoneSelector();
} }
JsonNode selectorJson = JacksonUtils.toObj(URLDecoder.decode(selectorJsonString, "UTF-8")); JsonNode selectorJson = JacksonUtils.toObj(URLDecoder.decode(selectorJsonString, "UTF-8"));
switch (SelectorType.valueOf(selectorJson.get("type").asText())) { String type = Optional.ofNullable(selectorJson.get("type"))
case none: .orElseThrow(() -> new NacosException(NacosException.INVALID_PARAM, "not match any type of selector!"))
return new NoneSelector(); .asText();
case label: String expression = Optional.ofNullable(selectorJson.get("expression"))
String expression = selectorJson.get("expression").asText(); .map(JsonNode::asText)
Set<String> labels = LabelSelector.parseExpression(expression); .orElse(null);
LabelSelector labelSelector = new LabelSelector(); Selector selector = selectorManager.parseSelector(type, expression);
labelSelector.setExpression(expression); if (Objects.isNull(selector)) {
labelSelector.setLabels(labels);
return labelSelector;
default:
throw new NacosException(NacosException.INVALID_PARAM, "not match any type of selector!"); throw new NacosException(NacosException.INVALID_PARAM, "not match any type of selector!");
} }
return selector;
} }

View File

@ -22,6 +22,7 @@ import com.alibaba.nacos.api.naming.PreservedMetadataKeys;
import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ServiceInfo; import com.alibaba.nacos.api.naming.pojo.ServiceInfo;
import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.naming.core.v2.upgrade.doublewrite.execute.InstanceUpgradeHelper; import com.alibaba.nacos.naming.core.v2.upgrade.doublewrite.execute.InstanceUpgradeHelper;
import com.alibaba.nacos.naming.healthcheck.RsInfo; import com.alibaba.nacos.naming.healthcheck.RsInfo;
import com.alibaba.nacos.naming.misc.Loggers; import com.alibaba.nacos.naming.misc.Loggers;
@ -35,9 +36,9 @@ import com.alibaba.nacos.naming.push.v1.ClientInfo;
import com.alibaba.nacos.naming.push.v1.DataSource; import com.alibaba.nacos.naming.push.v1.DataSource;
import com.alibaba.nacos.naming.push.v1.NamingSubscriberServiceV1Impl; import com.alibaba.nacos.naming.push.v1.NamingSubscriberServiceV1Impl;
import com.alibaba.nacos.naming.push.v1.PushClient; import com.alibaba.nacos.naming.push.v1.PushClient;
import com.alibaba.nacos.naming.selector.SelectorManager;
import com.alibaba.nacos.naming.utils.InstanceUtil; import com.alibaba.nacos.naming.utils.InstanceUtil;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -75,6 +76,8 @@ public class InstanceOperatorServiceImpl implements InstanceOperator {
private final InstanceUpgradeHelper instanceUpgradeHelper; private final InstanceUpgradeHelper instanceUpgradeHelper;
private final SelectorManager selectorManager;
private DataSource pushDataSource = new DataSource() { private DataSource pushDataSource = new DataSource() {
@Override @Override
@ -98,12 +101,13 @@ public class InstanceOperatorServiceImpl implements InstanceOperator {
public InstanceOperatorServiceImpl(ServiceManager serviceManager, SwitchDomain switchDomain, public InstanceOperatorServiceImpl(ServiceManager serviceManager, SwitchDomain switchDomain,
UdpPushService pushService, NamingSubscriberServiceV1Impl subscriberServiceV1, UdpPushService pushService, NamingSubscriberServiceV1Impl subscriberServiceV1,
InstanceUpgradeHelper instanceUpgradeHelper) { InstanceUpgradeHelper instanceUpgradeHelper, SelectorManager selectorManager) {
this.serviceManager = serviceManager; this.serviceManager = serviceManager;
this.switchDomain = switchDomain; this.switchDomain = switchDomain;
this.pushService = pushService; this.pushService = pushService;
this.subscriberServiceV1 = subscriberServiceV1; this.subscriberServiceV1 = subscriberServiceV1;
this.instanceUpgradeHelper = instanceUpgradeHelper; this.instanceUpgradeHelper = instanceUpgradeHelper;
this.selectorManager = selectorManager;
} }
@Override @Override
@ -193,7 +197,7 @@ public class InstanceOperatorServiceImpl implements InstanceOperator {
// filter ips using selector: // filter ips using selector:
if (service.getSelector() != null && StringUtils.isNotBlank(clientIP)) { if (service.getSelector() != null && StringUtils.isNotBlank(clientIP)) {
srvedIps = service.getSelector().select(clientIP, srvedIps); srvedIps = selectorManager.select(service.getSelector(), clientIP, srvedIps);
} }
if (CollectionUtils.isEmpty(srvedIps)) { if (CollectionUtils.isEmpty(srvedIps)) {

View File

@ -17,8 +17,10 @@
package com.alibaba.nacos.naming.core; package com.alibaba.nacos.naming.core;
import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.selector.Selector;
import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.common.utils.MD5Utils; import com.alibaba.nacos.common.utils.MD5Utils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.naming.consistency.KeyBuilder; import com.alibaba.nacos.naming.consistency.KeyBuilder;
import com.alibaba.nacos.naming.consistency.RecordListener; import com.alibaba.nacos.naming.consistency.RecordListener;
import com.alibaba.nacos.naming.core.v2.upgrade.doublewrite.delay.DoubleWriteEventListener; import com.alibaba.nacos.naming.core.v2.upgrade.doublewrite.delay.DoubleWriteEventListener;
@ -31,14 +33,12 @@ import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import com.alibaba.nacos.naming.pojo.Record; import com.alibaba.nacos.naming.pojo.Record;
import com.alibaba.nacos.naming.push.UdpPushService; import com.alibaba.nacos.naming.push.UdpPushService;
import com.alibaba.nacos.naming.selector.NoneSelector; import com.alibaba.nacos.naming.selector.NoneSelector;
import com.alibaba.nacos.naming.selector.Selector;
import com.alibaba.nacos.sys.utils.ApplicationUtils; import com.alibaba.nacos.sys.utils.ApplicationUtils;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.ListUtils; import org.apache.commons.collections.ListUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;

View File

@ -16,8 +16,8 @@
package com.alibaba.nacos.naming.core.v2.metadata; package com.alibaba.nacos.naming.core.v2.metadata;
import com.alibaba.nacos.api.selector.Selector;
import com.alibaba.nacos.naming.selector.NoneSelector; import com.alibaba.nacos.naming.selector.NoneSelector;
import com.alibaba.nacos.naming.selector.Selector;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map; import java.util.Map;
@ -44,7 +44,7 @@ public class ServiceMetadata implements Serializable {
private float protectThreshold = 0.0F; private float protectThreshold = 0.0F;
/** /**
* Type of {@link com.alibaba.nacos.naming.selector.Selector}. * Type of {@link Selector}.
*/ */
private Selector selector = new NoneSelector(); private Selector selector = new NoneSelector();

View File

@ -1,131 +0,0 @@
/*
* Copyright 1999-2021 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.alibaba.nacos.naming.selector;
import com.alibaba.nacos.api.cmdb.pojo.Entity;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.selector.AbstractCmdbSelector;
import com.alibaba.nacos.api.naming.selector.Selector;
import com.alibaba.nacos.api.naming.selector.context.CmdbContext;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* The implement of {@link LabelSelector} at new version. The main logic is same with {@link LabelSelector}.
* The {@link CmdbLabelSelector} will return the instances labels in {@link #labels} and providers' label value is same with consumer.
* If none matched, then will return all providers instead of.
*
* @author chenglu
* @date 2021-07-16 16:26
*/
public class CmdbLabelSelector<T extends Instance> extends AbstractCmdbSelector<T> {
private static final String TYPE = "label";
/**
* {@link Entity} labels key.
*/
private Set<String> labels;
/**
* the labels expression.
*/
private String expression;
public Set<String> getLabels() {
return labels;
}
public void setLabels(Set<String> labels) {
this.labels = labels;
}
public String getExpression() {
return expression;
}
public void setExpression(String expression) {
this.expression = expression;
}
@Override
protected List<T> doSelect(CmdbContext<T> context) {
if (CollectionUtils.isEmpty(labels)) {
return context.getProviders()
.stream()
.map(CmdbContext.CmdbInstance::getInstance)
.collect(Collectors.toList());
}
CmdbContext.CmdbInstance<T> consumer = context.getConsumer();
Map<String, String> consumerLabels = Optional.ofNullable(consumer.getEntity())
.map(Entity::getLabels)
.orElse(Collections.emptyMap());
// filter the instance if consumer and providers' label values equals.
List<T> result = context.getProviders()
.stream()
.filter(ci -> {
Entity providerEntity = ci.getEntity();
if (Objects.isNull(providerEntity)) {
return false;
}
Map<String, String> providerLabels = Optional.ofNullable(ci.getEntity().getLabels())
.orElse(Collections.emptyMap());
return labels.stream()
.allMatch(label -> {
String consumerLabelValue = consumerLabels.get(label);
if (StringUtils.isBlank(consumerLabelValue)) {
return false;
}
return Objects.equals(consumerLabelValue, providerLabels.get(label));
});
})
.map(CmdbContext.CmdbInstance::getInstance)
.collect(Collectors.toList());
// if none match, then return all providers.
if (CollectionUtils.isEmpty(result)) {
return context.getProviders()
.stream()
.map(CmdbContext.CmdbInstance::getInstance)
.collect(Collectors.toList());
}
return result;
}
@Override
public Selector<List<T>, CmdbContext<T>, String> parse(String condition) throws NacosException {
this.labels = LabelSelector.ExpressionInterpreter.parseExpression(condition);
this.expression = condition;
return this;
}
@Override
public String getType() {
return TYPE;
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 1999-2018 Alibaba Group Holding Ltd. * Copyright 1999-2021 Alibaba Group Holding Ltd.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -12,81 +12,46 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package com.alibaba.nacos.naming.selector; package com.alibaba.nacos.naming.selector;
import com.alibaba.nacos.api.cmdb.pojo.PreservedEntityTypes; import com.alibaba.nacos.api.cmdb.pojo.Entity;
import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.selector.ExpressionSelector; import com.alibaba.nacos.api.selector.AbstractCmdbSelector;
import com.alibaba.nacos.api.selector.SelectorType; import com.alibaba.nacos.api.selector.context.CmdbContext;
import com.alibaba.nacos.cmdb.service.CmdbReader; import com.alibaba.nacos.common.utils.CollectionUtils;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.sys.utils.ApplicationUtils;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.naming.selector.interpreter.ExpressionInterpreter;
import java.util.ArrayList; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
/** /**
* A selector to implement a so called same-label-prior rule for service discovery. * The implement of {@link com.alibaba.nacos.naming.selector.v1.LabelSelector} at new version.
* <h2>Backgroup</h2> * The main logic is same with {@link com.alibaba.nacos.naming.selector.v1.LabelSelector}.
* Consider service providers are deployed in two sites i.e. site A and site B, and consumers of this service provider * The {@link LabelSelector} will return the instances labels in {@link #labels} and providers' label value is same with consumer.
* are also deployed in site A and site B. So the consumers may want to visit the service provider in current site, thus * If none matched, then will return all providers instead of.
* consumers in site A visit service providers in site A and consumers in site B visit service providers in site B. This
* is quite useful to reduce the transfer delay of RPC. This is called same-site-prior strategy.
* <h2>Same Label Prior</h2>
* The same-site-prior strategy covers many circumstances in large companies and we can abstract it to a higher level
* strategy: same-label-prior.
* *
* <p>So the idea is that presumed we have built a self-defined or integrated a third-party idc CMDB which stores all * @author chenglu
* the labels of all IPs. Then we can filter provider IPs by the consumer IP and we only return the providers who have * @date 2021-07-16 16:26
* the same label values with consumer. We can define the labels we want to include in the comparison.
*
* <p>If no provider has the same label value with the consumer, we fall back to give all providers to the consumer.
* Note that this fallback strategy may also be abstracted in future to introduce more kinds of behaviors.
*
* @author nkorange
* @see CmdbReader
* @since 0.7.0
*/ */
@Deprecated public class LabelSelector<T extends Instance> extends AbstractCmdbSelector<T> {
@JsonTypeInfo(use = Id.NAME, property = "type")
public class LabelSelector extends ExpressionSelector implements Selector {
private static final long serialVersionUID = -7381912003505096093L; private static final String TYPE = "label";
/** /**
* The labels relevant to this the selector. * {@link Entity} labels key.
*
* @see com.alibaba.nacos.api.cmdb.pojo.Label
*/ */
private Set<String> labels; private Set<String> labels;
private static final Set<String> SUPPORTED_INNER_CONNCETORS = new HashSet<>();
private static final Set<String> SUPPORTED_OUTER_CONNCETORS = new HashSet<>();
private static final String CONSUMER_PREFIX = "CONSUMER.label.";
private static final String PROVIDER_PREFIX = "PROVIDER.label.";
private static final char CEQUAL = '=';
private static final char CAND = '&';
static {
SUPPORTED_INNER_CONNCETORS.add(String.valueOf(CEQUAL));
SUPPORTED_OUTER_CONNCETORS.add(String.valueOf(CAND));
JacksonUtils.registerSubtype(LabelSelector.class, SelectorType.label.name());
}
public Set<String> getLabels() { public Set<String> getLabels() {
return labels; return labels;
} }
@ -95,197 +60,58 @@ public class LabelSelector extends ExpressionSelector implements Selector {
this.labels = labels; this.labels = labels;
} }
public LabelSelector() { @Override
super(); protected List<T> doSelect(CmdbContext<T> context) {
if (CollectionUtils.isEmpty(labels)) {
return context.getProviders()
.stream()
.map(CmdbContext.CmdbInstance::getInstance)
.collect(Collectors.toList());
} }
CmdbContext.CmdbInstance<T> consumer = context.getConsumer();
Map<String, String> consumerLabels = Optional.ofNullable(consumer.getEntity())
.map(Entity::getLabels)
.orElse(Collections.emptyMap());
private CmdbReader getCmdbReader() { // filter the instance if consumer and providers' label values equals.
return ApplicationUtils.getBean(CmdbReader.class); List<T> result = context.getProviders()
.stream()
.filter(ci -> {
Entity providerEntity = ci.getEntity();
if (Objects.isNull(providerEntity)) {
return false;
} }
Map<String, String> providerLabels = Optional.ofNullable(ci.getEntity().getLabels())
.orElse(Collections.emptyMap());
return labels.stream()
.allMatch(label -> {
String consumerLabelValue = consumerLabels.get(label);
if (StringUtils.isBlank(consumerLabelValue)) {
return false;
}
return Objects.equals(consumerLabelValue, providerLabels.get(label));
});
})
.map(CmdbContext.CmdbInstance::getInstance)
.collect(Collectors.toList());
public static Set<String> parseExpression(String expression) throws NacosException { // if none match, then return all providers.
return ExpressionInterpreter.parseExpression(expression); if (CollectionUtils.isEmpty(result)) {
return context.getProviders()
.stream()
.map(CmdbContext.CmdbInstance::getInstance)
.collect(Collectors.toList());
}
return result;
} }
@Override @Override
public <T extends Instance> List<T> select(String consumer, List<T> providers) { protected void doParse(String expression) throws NacosException {
if (labels.isEmpty()) { this.labels = ExpressionInterpreter.parseExpression(expression);
return providers;
} }
List<T> instanceList = new ArrayList<>(); @Override
for (T instance : providers) { public String getType() {
return TYPE;
boolean matched = true;
for (String labelName : getLabels()) {
String consumerLabelValue = getCmdbReader()
.queryLabel(consumer, PreservedEntityTypes.ip.name(), labelName);
if (StringUtils.isNotBlank(consumerLabelValue) && !StringUtils.equals(consumerLabelValue,
getCmdbReader().queryLabel(instance.getIp(), PreservedEntityTypes.ip.name(), labelName))) {
matched = false;
break;
}
}
if (matched) {
instanceList.add(instance);
}
}
if (instanceList.isEmpty()) {
return providers;
}
return instanceList;
}
/**
* Expression interpreter for label selector.
*
* <p>For now it supports very limited set of syntax rules.
*/
public static class ExpressionInterpreter {
/**
* Parse the label expression.
*
* <p>Currently we support the very single type of expression:
* <pre>
* consumer.labelA = provider.labelA & consumer.labelB = provider.labelB
* </pre>
* Later we will implement a interpreter to parse this expression in a standard LL parser way.
*
* @param expression the label expression to parse
* @return collection of labels
*/
public static Set<String> parseExpression(String expression) throws NacosException {
if (StringUtils.isBlank(expression)) {
return new HashSet<>();
}
expression = StringUtils.deleteWhitespace(expression);
List<String> elements = getTerms(expression);
Set<String> gotLabels = new HashSet<>();
int index = 0;
index = checkInnerSyntax(elements, index);
if (index == -1) {
throw new NacosException(NacosException.INVALID_PARAM, "parse expression failed!");
}
gotLabels.add(elements.get(index++).split(PROVIDER_PREFIX)[1]);
while (index < elements.size()) {
index = checkOuterSyntax(elements, index);
if (index >= elements.size()) {
return gotLabels;
}
if (index == -1) {
throw new NacosException(NacosException.INVALID_PARAM, "parse expression failed!");
}
gotLabels.add(elements.get(index++).split(PROVIDER_PREFIX)[1]);
}
return gotLabels;
}
public static List<String> getTerms(String expression) {
List<String> terms = new ArrayList<>();
Set<Character> characters = new HashSet<>();
characters.add(CEQUAL);
characters.add(CAND);
char[] chars = expression.toCharArray();
int lastIndex = 0;
for (int index = 0; index < chars.length; index++) {
char ch = chars[index];
if (characters.contains(ch)) {
terms.add(expression.substring(lastIndex, index));
terms.add(expression.substring(index, index + 1));
index++;
lastIndex = index;
}
}
terms.add(expression.substring(lastIndex, chars.length));
return terms;
}
private static int skipEmpty(List<String> elements, int start) {
while (start < elements.size() && StringUtils.isBlank(elements.get(start))) {
start++;
}
return start;
}
private static int checkOuterSyntax(List<String> elements, int start) {
int index = start;
index = skipEmpty(elements, index);
if (index >= elements.size()) {
return index;
}
if (!SUPPORTED_OUTER_CONNCETORS.contains(elements.get(index++))) {
return -1;
}
return checkInnerSyntax(elements, index);
}
private static int checkInnerSyntax(List<String> elements, int start) {
int index = start;
index = skipEmpty(elements, index);
if (index >= elements.size()) {
return -1;
}
if (!elements.get(index).startsWith(CONSUMER_PREFIX)) {
return -1;
}
final String labelConsumer = elements.get(index++).split(CONSUMER_PREFIX)[1];
index = skipEmpty(elements, index);
if (index >= elements.size()) {
return -1;
}
if (!SUPPORTED_INNER_CONNCETORS.contains(elements.get(index++))) {
return -1;
}
index = skipEmpty(elements, index);
if (index >= elements.size()) {
return -1;
}
if (!elements.get(index).startsWith(PROVIDER_PREFIX)) {
return -1;
}
final String labelProvider = elements.get(index).split(PROVIDER_PREFIX)[1];
if (!labelConsumer.equals(labelProvider)) {
return -1;
}
return index;
}
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 1999-2018 Alibaba Group Holding Ltd. * Copyright 1999-2021 Alibaba Group Holding Ltd.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -12,36 +12,46 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package com.alibaba.nacos.naming.selector; package com.alibaba.nacos.naming.selector;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.selector.SelectorType; import com.alibaba.nacos.api.selector.Selector;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import java.util.List; import java.util.List;
/** /**
* Selector with no filtering. * Selector with no filtering. The logic is same to {@link com.alibaba.nacos.naming.selector.v1.NoneSelector}.
* *
* @author nkorange * @author chenglu
* @since 0.7.0 * @date 2021-08-04 13:28
*/ */
@Deprecated public class NoneSelector<T extends Instance> implements Selector<List<T>, List<T>, String> {
@JsonTypeInfo(use = Id.NAME, property = "type")
public class NoneSelector extends com.alibaba.nacos.api.selector.NoneSelector implements Selector {
private static final long serialVersionUID = -3752116616221930677L; private static final String CONTEXT_TYPE = "NONE";
static { private static final String TYPE = "none";
JacksonUtils.registerSubtype(NoneSelector.class, SelectorType.none.name());
@Override
public Selector<List<T>, List<T>, String> parse(String condition) throws NacosException {
return this;
} }
@Override @Override
public <T extends Instance> List<T> select(String consumer, List<T> providers) { public List<T> select(List<T> context) {
return providers; return context;
}
@Override
public String getType() {
return TYPE;
}
@Override
public String getContextType() {
return CONTEXT_TYPE;
} }
} }

View File

@ -17,11 +17,13 @@
package com.alibaba.nacos.naming.selector; package com.alibaba.nacos.naming.selector;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.selector.Selector; import com.alibaba.nacos.api.selector.Selector;
import com.alibaba.nacos.api.naming.selector.context.SelectorContextBuilder; import com.alibaba.nacos.api.selector.context.SelectorContextBuilder;
import com.alibaba.nacos.common.spi.NacosServiceLoader; import com.alibaba.nacos.common.spi.NacosServiceLoader;
import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.naming.misc.Loggers; import com.alibaba.nacos.naming.misc.Loggers;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -34,6 +36,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import static com.alibaba.nacos.api.exception.NacosException.SERVER_ERROR;
/** /**
* {@link SelectorManager} work on init {@link Selector#parse(Object)}, execute {@link Selector#select(Object)} and maintain * {@link SelectorManager} work on init {@link Selector#parse(Object)}, execute {@link Selector#select(Object)} and maintain
* the type of {@link Selector} and {@link SelectorContextBuilder}. * the type of {@link Selector} and {@link SelectorContextBuilder}.
@ -125,7 +129,10 @@ public class SelectorManager {
* @param condition the condition provide for {@link Selector#parse(Object)}. * @param condition the condition provide for {@link Selector#parse(Object)}.
* @return {@link Selector}. * @return {@link Selector}.
*/ */
public Selector parseSelector(String type, String condition) { public Selector parseSelector(String type, String condition) throws NacosException {
if (StringUtils.isBlank(type)) {
return null;
}
Class<? extends Selector> clazz = selectorTypes.get(type); Class<? extends Selector> clazz = selectorTypes.get(type);
if (Objects.isNull(clazz)) { if (Objects.isNull(clazz)) {
return null; return null;
@ -136,8 +143,8 @@ public class SelectorManager {
return selector; return selector;
} catch (Exception e) { } catch (Exception e) {
Loggers.SRV_LOG.warn("[SelectorManager] Parse Selector failed, type: {}, condition: {}.", type, condition, e); Loggers.SRV_LOG.warn("[SelectorManager] Parse Selector failed, type: {}, condition: {}.", type, condition, e);
throw new NacosException(SERVER_ERROR, "Selector parses failed: " + e.getMessage());
} }
return null;
} }
/** /**
@ -157,7 +164,12 @@ public class SelectorManager {
Loggers.SRV_LOG.info("[SelectorManager] cannot find the contextBuilder of type {}.", selector.getType()); Loggers.SRV_LOG.info("[SelectorManager] cannot find the contextBuilder of type {}.", selector.getType());
return providers; return providers;
} }
try {
Object context = selectorContextBuilder.build(consumerIp, providers); Object context = selectorContextBuilder.build(consumerIp, providers);
return (List<T>) selector.select(context); return (List<T>) selector.select(context);
} catch (Exception e) {
Loggers.SRV_LOG.warn("[SelectorManager] execute select failed, will return all providers.", e);
return providers;
}
} }
} }

View File

@ -20,8 +20,8 @@ package com.alibaba.nacos.naming.selector.context;
import com.alibaba.nacos.api.cmdb.pojo.Entity; import com.alibaba.nacos.api.cmdb.pojo.Entity;
import com.alibaba.nacos.api.cmdb.pojo.PreservedEntityTypes; import com.alibaba.nacos.api.cmdb.pojo.PreservedEntityTypes;
import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.selector.context.CmdbContext; import com.alibaba.nacos.api.selector.context.CmdbContext;
import com.alibaba.nacos.api.naming.selector.context.SelectorContextBuilder; import com.alibaba.nacos.api.selector.context.SelectorContextBuilder;
import com.alibaba.nacos.cmdb.service.CmdbReader; import com.alibaba.nacos.cmdb.service.CmdbReader;
import com.alibaba.nacos.sys.utils.ApplicationUtils; import com.alibaba.nacos.sys.utils.ApplicationUtils;

View File

@ -0,0 +1,45 @@
/*
* Copyright 1999-2021 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.alibaba.nacos.naming.selector.context;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.selector.context.SelectorContextBuilder;
import java.util.List;
/**
* The {@link NoneSelectorContextBuilder} will return the provider as context for the {@link com.alibaba.nacos.api.selector.Selector}
* which doesn't need any other resource.
*
* @author chenglu
* @date 2021-08-04 13:31
*/
public class NoneSelectorContextBuilder<T extends Instance> implements SelectorContextBuilder<List<T>, String, List<T>> {
private static final String CONTEXT_TYPE = "NONE";
@Override
public List<T> build(String consumer, List<T> provider) {
return provider;
}
@Override
public String getContextType() {
return CONTEXT_TYPE;
}
}

View File

@ -0,0 +1,194 @@
/*
* Copyright 1999-2021 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.alibaba.nacos.naming.selector.interpreter;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.common.utils.StringUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Expression interpreter for label selector.
*
* <p>For now it supports very limited set of syntax rules.
*
* @author nokrange
*/
public class ExpressionInterpreter {
private static final Set<String> SUPPORTED_INNER_CONNCETORS = new HashSet<>();
private static final Set<String> SUPPORTED_OUTER_CONNCETORS = new HashSet<>();
private static final String CONSUMER_PREFIX = "CONSUMER.label.";
private static final String PROVIDER_PREFIX = "PROVIDER.label.";
private static final char CEQUAL = '=';
private static final char CAND = '&';
static {
SUPPORTED_INNER_CONNCETORS.add(String.valueOf(CEQUAL));
SUPPORTED_OUTER_CONNCETORS.add(String.valueOf(CAND));
}
/**
* Parse the label expression.
*
* <p>Currently we support the very single type of expression:
* <pre>
* consumer.labelA = provider.labelA & consumer.labelB = provider.labelB
* </pre>
* Later we will implement a interpreter to parse this expression in a standard LL parser way.
*
* @param expression the label expression to parse
* @return collection of labels
*/
public static Set<String> parseExpression(String expression) throws NacosException {
if (StringUtils.isBlank(expression)) {
return new HashSet<>();
}
expression = StringUtils.deleteWhitespace(expression);
List<String> elements = getTerms(expression);
Set<String> gotLabels = new HashSet<>();
int index = 0;
index = checkInnerSyntax(elements, index);
if (index == -1) {
throw new NacosException(NacosException.INVALID_PARAM, "parse expression failed!");
}
gotLabels.add(elements.get(index++).split(PROVIDER_PREFIX)[1]);
while (index < elements.size()) {
index = checkOuterSyntax(elements, index);
if (index >= elements.size()) {
return gotLabels;
}
if (index == -1) {
throw new NacosException(NacosException.INVALID_PARAM, "parse expression failed!");
}
gotLabels.add(elements.get(index++).split(PROVIDER_PREFIX)[1]);
}
return gotLabels;
}
public static List<String> getTerms(String expression) {
List<String> terms = new ArrayList<>();
Set<Character> characters = new HashSet<>();
characters.add(CEQUAL);
characters.add(CAND);
char[] chars = expression.toCharArray();
int lastIndex = 0;
for (int index = 0; index < chars.length; index++) {
char ch = chars[index];
if (characters.contains(ch)) {
terms.add(expression.substring(lastIndex, index));
terms.add(expression.substring(index, index + 1));
index++;
lastIndex = index;
}
}
terms.add(expression.substring(lastIndex, chars.length));
return terms;
}
private static int skipEmpty(List<String> elements, int start) {
while (start < elements.size() && StringUtils.isBlank(elements.get(start))) {
start++;
}
return start;
}
private static int checkOuterSyntax(List<String> elements, int start) {
int index = start;
index = skipEmpty(elements, index);
if (index >= elements.size()) {
return index;
}
if (!SUPPORTED_OUTER_CONNCETORS.contains(elements.get(index++))) {
return -1;
}
return checkInnerSyntax(elements, index);
}
private static int checkInnerSyntax(List<String> elements, int start) {
int index = start;
index = skipEmpty(elements, index);
if (index >= elements.size()) {
return -1;
}
if (!elements.get(index).startsWith(CONSUMER_PREFIX)) {
return -1;
}
final String labelConsumer = elements.get(index++).split(CONSUMER_PREFIX)[1];
index = skipEmpty(elements, index);
if (index >= elements.size()) {
return -1;
}
if (!SUPPORTED_INNER_CONNCETORS.contains(elements.get(index++))) {
return -1;
}
index = skipEmpty(elements, index);
if (index >= elements.size()) {
return -1;
}
if (!elements.get(index).startsWith(PROVIDER_PREFIX)) {
return -1;
}
final String labelProvider = elements.get(index).split(PROVIDER_PREFIX)[1];
if (!labelConsumer.equals(labelProvider)) {
return -1;
}
return index;
}
}

View File

@ -0,0 +1,292 @@
/*
* Copyright 1999-2021 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.alibaba.nacos.naming.selector.v1;
import com.alibaba.nacos.api.cmdb.pojo.PreservedEntityTypes;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.selector.ExpressionSelector;
import com.alibaba.nacos.api.selector.SelectorType;
import com.alibaba.nacos.cmdb.service.CmdbReader;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.sys.utils.ApplicationUtils;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.alibaba.nacos.common.utils.StringUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A selector to implement a so called same-label-prior rule for service discovery.
* <h2>Backgroup</h2>
* Consider service providers are deployed in two sites i.e. site A and site B, and consumers of this service provider
* are also deployed in site A and site B. So the consumers may want to visit the service provider in current site, thus
* consumers in site A visit service providers in site A and consumers in site B visit service providers in site B. This
* is quite useful to reduce the transfer delay of RPC. This is called same-site-prior strategy.
* <h2>Same Label Prior</h2>
* The same-site-prior strategy covers many circumstances in large companies and we can abstract it to a higher level
* strategy: same-label-prior.
*
* <p>So the idea is that presumed we have built a self-defined or integrated a third-party idc CMDB which stores all
* the labels of all IPs. Then we can filter provider IPs by the consumer IP and we only return the providers who have
* the same label values with consumer. We can define the labels we want to include in the comparison.
*
* <p>If no provider has the same label value with the consumer, we fall back to give all providers to the consumer.
* Note that this fallback strategy may also be abstracted in future to introduce more kinds of behaviors.
*
* @author nkorange
* @see CmdbReader
* @since 0.7.0
*/
@Deprecated
@JsonTypeInfo(use = Id.NAME, property = "type")
public class LabelSelector extends ExpressionSelector implements Selector {
private static final long serialVersionUID = -7381912003505096093L;
/**
* The labels relevant to this the selector.
*
* @see com.alibaba.nacos.api.cmdb.pojo.Label
*/
private Set<String> labels;
private static final Set<String> SUPPORTED_INNER_CONNCETORS = new HashSet<>();
private static final Set<String> SUPPORTED_OUTER_CONNCETORS = new HashSet<>();
private static final String CONSUMER_PREFIX = "CONSUMER.label.";
private static final String PROVIDER_PREFIX = "PROVIDER.label.";
private static final char CEQUAL = '=';
private static final char CAND = '&';
static {
SUPPORTED_INNER_CONNCETORS.add(String.valueOf(CEQUAL));
SUPPORTED_OUTER_CONNCETORS.add(String.valueOf(CAND));
JacksonUtils.registerSubtype(LabelSelector.class, SelectorType.label.name());
}
public Set<String> getLabels() {
return labels;
}
public void setLabels(Set<String> labels) {
this.labels = labels;
}
public LabelSelector() {
super();
}
private CmdbReader getCmdbReader() {
return ApplicationUtils.getBean(CmdbReader.class);
}
public static Set<String> parseExpression(String expression) throws NacosException {
return ExpressionInterpreter.parseExpression(expression);
}
@Override
public <T extends Instance> List<T> select(String consumer, List<T> providers) {
if (labels.isEmpty()) {
return providers;
}
List<T> instanceList = new ArrayList<>();
for (T instance : providers) {
boolean matched = true;
for (String labelName : getLabels()) {
String consumerLabelValue = getCmdbReader()
.queryLabel(consumer, PreservedEntityTypes.ip.name(), labelName);
if (StringUtils.isNotBlank(consumerLabelValue) && !StringUtils.equals(consumerLabelValue,
getCmdbReader().queryLabel(instance.getIp(), PreservedEntityTypes.ip.name(), labelName))) {
matched = false;
break;
}
}
if (matched) {
instanceList.add(instance);
}
}
if (instanceList.isEmpty()) {
return providers;
}
return instanceList;
}
/**
* Expression interpreter for label selector.
*
* <p>For now it supports very limited set of syntax rules.
*/
public static class ExpressionInterpreter {
/**
* Parse the label expression.
*
* <p>Currently we support the very single type of expression:
* <pre>
* consumer.labelA = provider.labelA & consumer.labelB = provider.labelB
* </pre>
* Later we will implement a interpreter to parse this expression in a standard LL parser way.
*
* @param expression the label expression to parse
* @return collection of labels
*/
public static Set<String> parseExpression(String expression) throws NacosException {
if (StringUtils.isBlank(expression)) {
return new HashSet<>();
}
expression = StringUtils.deleteWhitespace(expression);
List<String> elements = getTerms(expression);
Set<String> gotLabels = new HashSet<>();
int index = 0;
index = checkInnerSyntax(elements, index);
if (index == -1) {
throw new NacosException(NacosException.INVALID_PARAM, "parse expression failed!");
}
gotLabels.add(elements.get(index++).split(PROVIDER_PREFIX)[1]);
while (index < elements.size()) {
index = checkOuterSyntax(elements, index);
if (index >= elements.size()) {
return gotLabels;
}
if (index == -1) {
throw new NacosException(NacosException.INVALID_PARAM, "parse expression failed!");
}
gotLabels.add(elements.get(index++).split(PROVIDER_PREFIX)[1]);
}
return gotLabels;
}
public static List<String> getTerms(String expression) {
List<String> terms = new ArrayList<>();
Set<Character> characters = new HashSet<>();
characters.add(CEQUAL);
characters.add(CAND);
char[] chars = expression.toCharArray();
int lastIndex = 0;
for (int index = 0; index < chars.length; index++) {
char ch = chars[index];
if (characters.contains(ch)) {
terms.add(expression.substring(lastIndex, index));
terms.add(expression.substring(index, index + 1));
index++;
lastIndex = index;
}
}
terms.add(expression.substring(lastIndex, chars.length));
return terms;
}
private static int skipEmpty(List<String> elements, int start) {
while (start < elements.size() && StringUtils.isBlank(elements.get(start))) {
start++;
}
return start;
}
private static int checkOuterSyntax(List<String> elements, int start) {
int index = start;
index = skipEmpty(elements, index);
if (index >= elements.size()) {
return index;
}
if (!SUPPORTED_OUTER_CONNCETORS.contains(elements.get(index++))) {
return -1;
}
return checkInnerSyntax(elements, index);
}
private static int checkInnerSyntax(List<String> elements, int start) {
int index = start;
index = skipEmpty(elements, index);
if (index >= elements.size()) {
return -1;
}
if (!elements.get(index).startsWith(CONSUMER_PREFIX)) {
return -1;
}
final String labelConsumer = elements.get(index++).split(CONSUMER_PREFIX)[1];
index = skipEmpty(elements, index);
if (index >= elements.size()) {
return -1;
}
if (!SUPPORTED_INNER_CONNCETORS.contains(elements.get(index++))) {
return -1;
}
index = skipEmpty(elements, index);
if (index >= elements.size()) {
return -1;
}
if (!elements.get(index).startsWith(PROVIDER_PREFIX)) {
return -1;
}
final String labelProvider = elements.get(index).split(PROVIDER_PREFIX)[1];
if (!labelConsumer.equals(labelProvider)) {
return -1;
}
return index;
}
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 1999-2021 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.alibaba.nacos.naming.selector.v1;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.selector.SelectorType;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import java.util.List;
/**
* Selector with no filtering.
*
* @author nkorange
* @since 0.7.0
*/
@Deprecated
@JsonTypeInfo(use = Id.NAME, property = "type")
public class NoneSelector extends com.alibaba.nacos.api.selector.NoneSelector implements Selector {
private static final long serialVersionUID = -3752116616221930677L;
static {
JacksonUtils.registerSubtype(NoneSelector.class, SelectorType.none.name());
}
@Override
public <T extends Instance> List<T> select(String consumer, List<T> providers) {
return providers;
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 1999-2018 Alibaba Group Holding Ltd. * Copyright 1999-2021 Alibaba Group Holding Ltd.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -12,9 +12,10 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package com.alibaba.nacos.naming.selector; package com.alibaba.nacos.naming.selector.v1;
import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.Instance;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;

View File

@ -15,4 +15,5 @@
# #
# #
com.alibaba.nacos.naming.selector.CmdbLabelSelector com.alibaba.nacos.naming.selector.LabelSelector
com.alibaba.nacos.naming.selector.NoneSelector

View File

@ -16,3 +16,4 @@
# #
com.alibaba.nacos.naming.selector.context.CmdbSelectorContextBuilder com.alibaba.nacos.naming.selector.context.CmdbSelectorContextBuilder
com.alibaba.nacos.naming.selector.context.NoneSelectorContextBuilder

View File

@ -16,9 +16,9 @@
package com.alibaba.nacos.naming.core.v2.metadata; package com.alibaba.nacos.naming.core.v2.metadata;
import com.alibaba.nacos.api.selector.Selector;
import com.alibaba.nacos.naming.selector.LabelSelector; import com.alibaba.nacos.naming.selector.LabelSelector;
import com.alibaba.nacos.naming.selector.NoneSelector; import com.alibaba.nacos.naming.selector.NoneSelector;
import com.alibaba.nacos.naming.selector.Selector;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

View File

@ -1,51 +0,0 @@
/*
* Copyright 1999-2021 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.alibaba.nacos.naming.selector;
import com.alibaba.nacos.api.naming.selector.Selector;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* {@link CmdbLabelSelector} unit test.
*
* @author chenglu
* @date 2021-07-16 17:41
*/
public class CmdbLabelSelectorTest {
private SelectorManager selectorManager;
@Before
public void setUp() {
selectorManager = new SelectorManager();
selectorManager.init();
}
@Test
public void testParseSelector() {
Selector selector = selectorManager.parseSelector("label", "CONSUMER.label.A=PROVIDER.label.A &CONSUMER.label.B=PROVIDER.label.B");
Assert.assertTrue(selector instanceof CmdbLabelSelector);
CmdbLabelSelector cmdbLabelSelector = (CmdbLabelSelector) selector;
Assert.assertEquals(2, cmdbLabelSelector.getLabels().size());
Assert.assertTrue(cmdbLabelSelector.getLabels().contains("A"));
Assert.assertTrue(cmdbLabelSelector.getLabels().contains("B"));
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 1999-2018 Alibaba Group Holding Ltd. * Copyright 1999-2021 Alibaba Group Holding Ltd.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -12,32 +12,41 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package com.alibaba.nacos.naming.selector; package com.alibaba.nacos.naming.selector;
import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.api.selector.Selector;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.util.List; /**
import java.util.Set; * {@link LabelSelector} unit test.
*
* @author chenglu
* @date 2021-07-16 17:41
*/
public class LabelSelectorTest { public class LabelSelectorTest {
private String expression = "CONSUMER.label.A=PROVIDER.label.A &CONSUMER.label.B=PROVIDER.label.B"; private SelectorManager selectorManager;
@Before
public void setUp() {
selectorManager = new SelectorManager();
selectorManager.init();
}
@Test @Test
public void parseExpression() throws NacosException { public void testParseSelector() throws NacosException {
expression = StringUtils.deleteWhitespace(expression); Selector selector = selectorManager.parseSelector("label", "CONSUMER.label.A=PROVIDER.label.A &CONSUMER.label.B=PROVIDER.label.B");
List<String> terms = LabelSelector.ExpressionInterpreter.getTerms(expression); Assert.assertTrue(selector instanceof LabelSelector);
Assert.assertEquals(7, terms.size());
Set<String> parseLables = LabelSelector.parseExpression(expression);
Assert.assertEquals(2, parseLables.size());
String[] labs = parseLables.toArray(new String[] {});
Assert.assertEquals("A", labs[0]);
Assert.assertEquals("B", labs[1]);
}
LabelSelector labelSelector = (LabelSelector) selector;
Assert.assertEquals(2, labelSelector.getLabels().size());
Assert.assertTrue(labelSelector.getLabels().contains("A"));
Assert.assertTrue(labelSelector.getLabels().contains("B"));
}
} }

View File

@ -19,8 +19,8 @@ package com.alibaba.nacos.naming.selector;
import com.alibaba.nacos.api.cmdb.pojo.Entity; import com.alibaba.nacos.api.cmdb.pojo.Entity;
import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.selector.context.CmdbContext; import com.alibaba.nacos.api.selector.context.CmdbContext;
import com.alibaba.nacos.api.naming.selector.context.SelectorContextBuilder; import com.alibaba.nacos.api.selector.context.SelectorContextBuilder;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;

View File

@ -17,10 +17,10 @@
package com.alibaba.nacos.naming.selector; package com.alibaba.nacos.naming.selector;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.selector.AbstractCmdbSelector; import com.alibaba.nacos.api.selector.AbstractCmdbSelector;
import com.alibaba.nacos.api.naming.selector.Selector; import com.alibaba.nacos.api.selector.context.CmdbContext;
import com.alibaba.nacos.api.naming.selector.context.CmdbContext;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -73,11 +73,10 @@ public class MockSelector extends AbstractCmdbSelector<Instance> {
} }
@Override @Override
public Selector<List<Instance>, CmdbContext<Instance>, String> parse(String condition) { protected void doParse(String expression) throws NacosException {
String[] keyValues = condition.split("="); String[] keyValues = expression.split("=");
key = keyValues[0]; key = keyValues[0];
value = keyValues[1]; value = keyValues[1];
return this;
} }
@Override @Override

View File

@ -17,8 +17,9 @@
package com.alibaba.nacos.naming.selector; package com.alibaba.nacos.naming.selector;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.selector.Selector; import com.alibaba.nacos.api.selector.Selector;
import com.alibaba.nacos.consistency.SerializeFactory; import com.alibaba.nacos.consistency.SerializeFactory;
import com.alibaba.nacos.consistency.Serializer; import com.alibaba.nacos.consistency.Serializer;
import org.junit.Assert; import org.junit.Assert;
@ -51,7 +52,7 @@ public class SelectorManagerTest {
} }
@Test @Test
public void testParseSelector() { public void testParseSelector() throws NacosException {
Selector selector = selectorManager.parseSelector("mock", "key=value"); Selector selector = selectorManager.parseSelector("mock", "key=value");
Assert.assertTrue(selector instanceof MockSelector); Assert.assertTrue(selector instanceof MockSelector);
@ -59,7 +60,7 @@ public class SelectorManagerTest {
} }
@Test @Test
public void testSelect() { public void testSelect() throws NacosException {
Selector selector = selectorManager.parseSelector("mock", "key=value"); Selector selector = selectorManager.parseSelector("mock", "key=value");
Instance instance = new Instance(); Instance instance = new Instance();
instance.setIp("2.2.2.2"); instance.setIp("2.2.2.2");

View File

@ -0,0 +1,43 @@
/*
* Copyright 1999-2021 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.alibaba.nacos.naming.selector.v1;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.common.utils.StringUtils;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import java.util.Set;
public class LabelSelectorTest {
private String expression = "CONSUMER.label.A=PROVIDER.label.A &CONSUMER.label.B=PROVIDER.label.B";
@Test
public void parseExpression() throws NacosException {
expression = StringUtils.deleteWhitespace(expression);
List<String> terms = LabelSelector.ExpressionInterpreter.getTerms(expression);
Assert.assertEquals(7, terms.size());
Set<String> parseLables = LabelSelector.parseExpression(expression);
Assert.assertEquals(2, parseLables.size());
String[] labs = parseLables.toArray(new String[] {});
Assert.assertEquals("A", labs[0]);
Assert.assertEquals("B", labs[1]);
}
}