feat:merge router features from 1.5.2-Hoxton.SR9. (#232)

pull/234/head
Haotian Zhang 3 years ago committed by GitHub
parent fc4bd10564
commit 3a0f50af38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,4 +2,5 @@
---
- [Add metadata transfer example.](https://github.com/Tencent/spring-cloud-tencent/pull/210)
- [feat:merge features from 1.5.2-Hoxton.SR9 except router.](https://github.com/Tencent/spring-cloud-tencent/pull/226)
- [feat:merge features from 1.5.2-Hoxton.SR9 except router.](https://github.com/Tencent/spring-cloud-tencent/pull/226)
- [feat:merge router features from 1.5.2-Hoxton.SR9.](https://github.com/Tencent/spring-cloud-tencent/pull/232)

@ -19,6 +19,10 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-metadata-transfer</artifactId>
</dependency>
<!-- Spring Cloud Tencent dependencies end -->
<!-- Polaris dependencies start -->
@ -26,7 +30,51 @@
<groupId>com.tencent.polaris</groupId>
<artifactId>router-rule</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-metadata</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-nearby</artifactId>
</dependency>
<!-- Polaris dependencies end -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

@ -0,0 +1,62 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.CollectionUtils;
/**
* the context for router.
*
*@author lepdou 2022-05-17
*/
public class PolarisRouterContext {
/**
* the label for rule router.
*/
public static final String RULE_ROUTER_LABELS = "ruleRouter";
/**
* transitive labels.
*/
public static final String TRANSITIVE_LABELS = "transitive";
private Map<String, Map<String, String>> labels;
public Map<String, String> getLabels(String labelType) {
if (CollectionUtils.isEmpty(labels)) {
return Collections.emptyMap();
}
Map<String, String> subLabels = labels.get(labelType);
if (CollectionUtils.isEmpty(subLabels)) {
return Collections.emptyMap();
}
return Collections.unmodifiableMap(subLabels);
}
public void setLabels(String labelType, Map<String, String> subLabels) {
if (this.labels == null) {
this.labels = new HashMap<>();
}
labels.put(labelType, subLabels);
}
}

@ -0,0 +1,205 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.pojo.PolarisServiceInstance;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils;
import com.tencent.cloud.polaris.router.config.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerRequest;
import com.tencent.polaris.api.exception.ErrorCode;
import com.tencent.polaris.api.exception.PolarisException;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInfo;
import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.plugins.router.metadata.MetadataRouter;
import com.tencent.polaris.plugins.router.nearby.NearbyRouter;
import com.tencent.polaris.plugins.router.rule.RuleBasedRouter;
import com.tencent.polaris.router.api.core.RouterAPI;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse;
import reactor.core.publisher.Flux;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultRequestContext;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
import org.springframework.cloud.loadbalancer.core.DelegatingServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import org.springframework.util.CollectionUtils;
/**
* Service routing entrance.
*
* Rule routing needs to rely on request parameters for server filtering.
* The interface cannot obtain the context object of the request granularity,
* so the routing capability cannot be achieved through ServerListFilter.
*
* And {@link PolarisRouterServiceInstanceListSupplier#get(Request)} provides the ability to pass in http headers,
* so routing capabilities are implemented through IRule.
*
* @author Haotian Zhang, lepdou
*/
public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier {
private final PolarisNearByRouterProperties polarisNearByRouterProperties;
private final PolarisMetadataRouterProperties polarisMetadataRouterProperties;
private final PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties;
private final RouterAPI routerAPI;
public PolarisRouterServiceInstanceListSupplier(ServiceInstanceListSupplier delegate,
RouterAPI routerAPI,
PolarisNearByRouterProperties polarisNearByRouterProperties,
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) {
super(delegate);
this.routerAPI = routerAPI;
this.polarisNearByRouterProperties = polarisNearByRouterProperties;
this.polarisMetadataRouterProperties = polarisMetadataRouterProperties;
this.polarisRuleBasedRouterProperties = polarisRuleBasedRouterProperties;
}
@Override
public Flux<List<ServiceInstance>> get() {
throw new PolarisException(ErrorCode.INTERNAL_ERROR, "Unsupported method.");
}
@Override
public Flux<List<ServiceInstance>> get(Request request) {
// 1. get all servers
Flux<List<ServiceInstance>> allServers = getDelegate().get();
// 2. filter by router
DefaultRequestContext requestContext = (DefaultRequestContext) request.getContext();
PolarisRouterContext key = null;
if (requestContext instanceof RequestDataContext) {
key = buildRouterContext(((RequestDataContext) requestContext).getClientRequest().getHeaders());
}
else if (requestContext.getClientRequest() instanceof PolarisLoadBalancerRequest) {
key = buildRouterContext(((PolarisLoadBalancerRequest<?>) requestContext.getClientRequest()).getRequest()
.getHeaders());
}
return doRouter(allServers, key);
}
//set method to public for unit test
PolarisRouterContext buildRouterContext(HttpHeaders headers) {
Collection<String> labelHeaderValues = headers.get(RouterConstants.ROUTER_LABEL_HEADER);
if (CollectionUtils.isEmpty(labelHeaderValues)) {
return null;
}
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE));
labelHeaderValues.forEach(labelHeaderValue -> {
Map<String, String> labels = JacksonUtils.deserialize2Map(labelHeaderValue);
if (!CollectionUtils.isEmpty(labels)) {
Map<String, String> unescapeLabels = new HashMap<>(labels.size());
for (Map.Entry<String, String> entry : labels.entrySet()) {
String escapedKey = ExpressionLabelUtils.unescape(entry.getKey());
String escapedValue = ExpressionLabelUtils.unescape(entry.getValue());
unescapeLabels.put(escapedKey, escapedValue);
}
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, unescapeLabels);
}
});
return routerContext;
}
Flux<List<ServiceInstance>> doRouter(Flux<List<ServiceInstance>> allServers, PolarisRouterContext key) {
ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(allServers);
// filter instance by routers
ProcessRoutersRequest processRoutersRequest = buildProcessRoutersRequest(serviceInstances, key);
ProcessRoutersResponse processRoutersResponse = routerAPI.processRouters(processRoutersRequest);
List<ServiceInstance> filteredInstances = new ArrayList<>();
ServiceInstances filteredServiceInstances = processRoutersResponse.getServiceInstances();
for (Instance instance : filteredServiceInstances.getInstances()) {
filteredInstances.add(new PolarisServiceInstance(instance));
}
return Flux.fromIterable(Collections.singletonList(filteredInstances));
}
ProcessRoutersRequest buildProcessRoutersRequest(ServiceInstances serviceInstances, PolarisRouterContext key) {
ProcessRoutersRequest processRoutersRequest = new ProcessRoutersRequest();
processRoutersRequest.setDstInstances(serviceInstances);
// metadata router
if (polarisMetadataRouterProperties.isEnabled()) {
Map<String, String> transitiveLabels = getRouterLabels(key, PolarisRouterContext.TRANSITIVE_LABELS);
processRoutersRequest.putRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, transitiveLabels);
}
// nearby router
if (polarisNearByRouterProperties.isEnabled()) {
Map<String, String> nearbyRouterMetadata = new HashMap<>();
nearbyRouterMetadata.put(NearbyRouter.ROUTER_ENABLED, "true");
processRoutersRequest.putRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY, nearbyRouterMetadata);
}
// rule based router
// set dynamic switch for rule based router
boolean ruleBasedRouterEnabled = polarisRuleBasedRouterProperties.isEnabled();
Map<String, String> ruleRouterMetadata = new HashMap<>();
ruleRouterMetadata.put(RuleBasedRouter.ROUTER_ENABLED, String.valueOf(ruleBasedRouterEnabled));
processRoutersRequest.putRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED, ruleRouterMetadata);
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setNamespace(MetadataContext.LOCAL_NAMESPACE);
serviceInfo.setService(MetadataContext.LOCAL_SERVICE);
if (ruleBasedRouterEnabled) {
Map<String, String> ruleRouterLabels = getRouterLabels(key, PolarisRouterContext.RULE_ROUTER_LABELS);
// The label information that the rule based routing depends on
// is placed in the metadata of the source service for transmission.
// Later, can consider putting it in routerMetadata like other routers.
serviceInfo.setMetadata(ruleRouterLabels);
}
processRoutersRequest.setSourceService(serviceInfo);
return processRoutersRequest;
}
private Map<String, String> getRouterLabels(PolarisRouterContext key, String type) {
if (key != null) {
return key.getLabels(type);
}
return Collections.emptyMap();
}
}

@ -13,11 +13,20 @@
* 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.tencent.cloud.polaris.router;
/**
* Package info of router.
* Router constants.
*
* @author Haotian Zhang
*@author lepdou 2022-05-17
*/
package com.tencent.cloud.polaris.router;
public class RouterConstants {
/**
* the header of router label.
*/
public static final String ROUTER_LABEL_HEADER = "internal-router-label";
}

@ -0,0 +1,75 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RoutingProto;
import org.springframework.util.CollectionUtils;
/**
* Resolve label expressions from routing rules.
* @author lepdou 2022-05-19
*/
public class RouterRuleLabelResolver {
private final ServiceRuleManager serviceRuleManager;
public RouterRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
this.serviceRuleManager = serviceRuleManager;
}
public Set<String> getExpressionLabelKeys(String namespace, String sourceService, String dstService) {
List<RoutingProto.Route> rules = serviceRuleManager.getServiceRouterRule(namespace, sourceService, dstService);
if (CollectionUtils.isEmpty(rules)) {
return Collections.emptySet();
}
Set<String> expressionLabels = new HashSet<>();
for (RoutingProto.Route rule : rules) {
List<RoutingProto.Source> sources = rule.getSourcesList();
if (CollectionUtils.isEmpty(sources)) {
continue;
}
for (RoutingProto.Source source : sources) {
Map<String, ModelProto.MatchString> labels = source.getMetadataMap();
if (CollectionUtils.isEmpty(labels)) {
continue;
}
for (String labelKey : labels.keySet()) {
if (ExpressionLabelUtils.isExpressionLabel(labelKey)) {
expressionLabels.add(labelKey);
}
}
}
}
return expressionLabels;
}
}

@ -0,0 +1,96 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.config;
import com.tencent.cloud.polaris.router.PolarisRouterServiceInstanceListSupplier;
import com.tencent.polaris.router.api.core.RouterAPI;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
/**
* Auto configuration for ribbon components.
* @author lepdou 2022-05-17
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class LoadBalancerConfiguration {
/**
* Order of reactive discovery service instance supplier.
*/
private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;
@Configuration
@ConditionalOnReactiveDiscoveryEnabled
@Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER)
static class PolarisReactiveSupportConfiguration {
@Bean
@ConditionalOnBean(ReactiveDiscoveryClient.class)
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "polaris")
public ServiceInstanceListSupplier polarisRouterDiscoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context,
RouterAPI routerAPI,
PolarisNearByRouterProperties polarisNearByRouterProperties,
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) {
return new PolarisRouterServiceInstanceListSupplier(
ServiceInstanceListSupplier.builder().withDiscoveryClient().build(context),
routerAPI,
polarisNearByRouterProperties,
polarisMetadataRouterProperties,
polarisRuleBasedRouterProperties);
}
}
@Configuration
@ConditionalOnBlockingDiscoveryEnabled
@Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER + 1)
static class PolarisBlockingSupportConfiguration {
@Bean
@ConditionalOnBean(DiscoveryClient.class)
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "polaris")
public ServiceInstanceListSupplier polarisRouterDiscoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context,
RouterAPI routerAPI,
PolarisNearByRouterProperties polarisNearByRouterProperties,
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) {
return new PolarisRouterServiceInstanceListSupplier(
ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().build(context),
routerAPI,
polarisNearByRouterProperties,
polarisMetadataRouterProperties,
polarisRuleBasedRouterProperties);
}
}
}

@ -0,0 +1,46 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* the configuration for metadata router.
* @author lepdou 2022-05-23
*/
@ConfigurationProperties(prefix = "spring.cloud.polaris.router.metadata-router")
public class PolarisMetadataRouterProperties {
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public String toString() {
return "PolarisMetadataRouterProperties{" +
"enabled=" + enabled +
'}';
}
}

@ -0,0 +1,47 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* the configuration for nearby router.
*
* @author lepdou 2022-05-23
*/
@ConfigurationProperties(prefix = "spring.cloud.polaris.router.nearby-router")
public class PolarisNearByRouterProperties {
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public String toString() {
return "PolarisNearByRouterProperties{" +
"enabled=" + enabled +
'}';
}
}

@ -0,0 +1,48 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* the configuration for rule based router.
*
* @author lepdou 2022-05-23
*/
@ConfigurationProperties(prefix = "spring.cloud.polaris.router.rule-router")
public class PolarisRuleBasedRouterProperties {
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public String toString() {
return "PolarisNearByRouterProperties{" +
"enabled=" + enabled +
'}';
}
}

@ -0,0 +1,66 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.config;
import java.util.List;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.feign.RouterLabelFeignInterceptor;
import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerBeanPostProcessor;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.lang.Nullable;
import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
/**
* router module auto configuration.
*
*@author lepdou 2022-05-11
*/
@Configuration
@LoadBalancerClients(defaultConfiguration = LoadBalancerConfiguration.class)
@Import({PolarisNearByRouterProperties.class, PolarisMetadataRouterProperties.class, PolarisRuleBasedRouterProperties.class})
public class RouterAutoConfiguration {
@Bean
public RouterLabelFeignInterceptor routerLabelInterceptor(@Nullable List<RouterLabelResolver> routerLabelResolvers,
MetadataLocalProperties metadataLocalProperties,
RouterRuleLabelResolver routerRuleLabelResolver) {
return new RouterLabelFeignInterceptor(routerLabelResolvers, metadataLocalProperties, routerRuleLabelResolver);
}
@Bean
@Order(HIGHEST_PRECEDENCE)
public PolarisLoadBalancerBeanPostProcessor polarisLoadBalancerBeanPostProcessor() {
return new PolarisLoadBalancerBeanPostProcessor();
}
@Bean
public RouterRuleLabelResolver routerRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
return new RouterRuleLabelResolver(serviceRuleManager);
}
}

@ -0,0 +1,83 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.feign;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import feign.RequestTemplate;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
/**
* Resolve rule expression label from feign request.
* @author lepdou 2022-05-20
*/
public class FeignExpressionLabelUtils {
public static Map<String, String> resolve(RequestTemplate request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> labels = new HashMap<>();
for (String labelKey : labelKeys) {
if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_HEADER_PREFIX)) {
String headerKey = ExpressionLabelUtils.parseHeaderKey(labelKey);
if (StringUtils.isBlank(headerKey)) {
continue;
}
labels.put(labelKey, getHeaderValue(request, headerKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_QUERY_PREFIX)) {
String queryKey = ExpressionLabelUtils.parseQueryKey(labelKey);
if (StringUtils.isBlank(queryKey)) {
continue;
}
labels.put(labelKey, getQueryValue(request, queryKey));
}
else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_METHOD, labelKey)) {
labels.put(labelKey, request.method());
}
else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_URI, labelKey)) {
URI uri = URI.create(request.request().url());
labels.put(labelKey, uri.getPath());
}
}
return labels;
}
public static String getHeaderValue(RequestTemplate request, String key) {
Map<String, Collection<String>> headers = request.headers();
return ExpressionLabelUtils.getFirstValue(headers, key);
}
public static String getQueryValue(RequestTemplate request, String key) {
return ExpressionLabelUtils.getFirstValue(request.queries(), key);
}
}

@ -0,0 +1,134 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.feign;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.util.CollectionUtils;
/**
* Resolver labels from request.
*
*@author lepdou 2022-05-12
*/
public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered {
private static final Logger LOGGER = LoggerFactory.getLogger(RouterLabelFeignInterceptor.class);
private final List<RouterLabelResolver> routerLabelResolvers;
private final MetadataLocalProperties metadataLocalProperties;
private final RouterRuleLabelResolver routerRuleLabelResolver;
public RouterLabelFeignInterceptor(List<RouterLabelResolver> routerLabelResolvers,
MetadataLocalProperties metadataLocalProperties,
RouterRuleLabelResolver routerRuleLabelResolver) {
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
routerLabelResolvers.sort(Comparator.comparingInt(Ordered::getOrder));
this.routerLabelResolvers = routerLabelResolvers;
}
else {
this.routerLabelResolvers = null;
}
this.metadataLocalProperties = metadataLocalProperties;
this.routerRuleLabelResolver = routerRuleLabelResolver;
}
@Override
public int getOrder() {
return 0;
}
@Override
public void apply(RequestTemplate requestTemplate) {
// local service labels
Map<String, String> labels = new HashMap<>(metadataLocalProperties.getContent());
// labels from rule expression
String peerServiceName = requestTemplate.feignTarget().name();
Map<String, String> ruleExpressionLabels = getRuleExpressionLabels(requestTemplate, peerServiceName);
labels.putAll(ruleExpressionLabels);
// labels from request
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
routerLabelResolvers.forEach(resolver -> {
try {
Map<String, String> customResolvedLabels = resolver.resolve(requestTemplate);
if (!CollectionUtils.isEmpty(customResolvedLabels)) {
labels.putAll(customResolvedLabels);
}
}
catch (Throwable t) {
LOGGER.error("[SCT][Router] revoke RouterLabelResolver occur some exception. ", t);
}
});
}
// labels from downstream
Map<String, String> transitiveLabels = MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
labels.putAll(transitiveLabels);
// Because when the label is placed in RequestTemplate.header,
// RequestTemplate will parse the header according to the regular, which conflicts with the expression.
// Avoid conflicts by escaping.
Map<String, String> escapeLabels = new HashMap<>(labels.size());
for (Map.Entry<String, String> entry : labels.entrySet()) {
String escapedKey = ExpressionLabelUtils.escape(entry.getKey());
String escapedValue = ExpressionLabelUtils.escape(entry.getValue());
escapeLabels.put(escapedKey, escapedValue);
}
// pass label by header
if (escapeLabels.size() == 0) {
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER);
return;
}
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER, JacksonUtils.serialize2Json(escapeLabels));
}
private Map<String, String> getRuleExpressionLabels(RequestTemplate requestTemplate, String peerService) {
Set<String> labelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE,
MetadataContext.LOCAL_SERVICE, peerService);
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
return FeignExpressionLabelUtils.resolve(requestTemplate, labelKeys);
}
}

@ -0,0 +1,65 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.resttemplate;
import java.util.List;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.BeanFactoryUtils;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
/**
* Replace LoadBalancerInterceptor with PolarisLoadBalancerInterceptor.
* PolarisLoadBalancerInterceptor can pass routing context information.
*
*@author lepdou 2022-05-18
*/
public class PolarisLoadBalancerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private BeanFactory factory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.factory = beanFactory;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof LoadBalancerInterceptor) {
LoadBalancerRequestFactory requestFactory = this.factory.getBean(LoadBalancerRequestFactory.class);
LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class);
List<RouterLabelResolver> routerLabelResolvers = BeanFactoryUtils.getBeans(factory, RouterLabelResolver.class);
MetadataLocalProperties metadataLocalProperties = this.factory.getBean(MetadataLocalProperties.class);
RouterRuleLabelResolver routerRuleLabelResolver = this.factory.getBean(RouterRuleLabelResolver.class);
return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory,
routerLabelResolvers, metadataLocalProperties, routerRuleLabelResolver);
}
return bean;
}
}

@ -0,0 +1,157 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.resttemplate;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor capabilities.
* Parses the label from the request and puts it into the RouterContext for routing.
*
*@author lepdou 2022-05-18
*/
public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisLoadBalancerInterceptor.class);
private final LoadBalancerClient loadBalancer;
private final LoadBalancerRequestFactory requestFactory;
private final List<RouterLabelResolver> routerLabelResolvers;
private final MetadataLocalProperties metadataLocalProperties;
private final RouterRuleLabelResolver routerRuleLabelResolver;
public PolarisLoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory,
List<RouterLabelResolver> routerLabelResolvers,
MetadataLocalProperties metadataLocalProperties,
RouterRuleLabelResolver routerRuleLabelResolver) {
super(loadBalancer, requestFactory);
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
this.metadataLocalProperties = metadataLocalProperties;
this.routerRuleLabelResolver = routerRuleLabelResolver;
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
routerLabelResolvers.sort(Comparator.comparingInt(Ordered::getOrder));
this.routerLabelResolvers = routerLabelResolvers;
}
else {
this.routerLabelResolvers = null;
}
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String peerServiceName = originalUri.getHost();
Assert.state(peerServiceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
setLabelsToHeaders(request, body, peerServiceName);
return this.loadBalancer.execute(peerServiceName,
new PolarisLoadBalancerRequest<>(request, this.requestFactory.createRequest(request, body, execution)));
}
void setLabelsToHeaders(HttpRequest request, byte[] body, String peerServiceName) {
// local service labels
Map<String, String> labels = new HashMap<>(metadataLocalProperties.getContent());
// labels from rule expression
Map<String, String> ruleExpressionLabels = getExpressionLabels(request, peerServiceName);
if (!CollectionUtils.isEmpty(ruleExpressionLabels)) {
labels.putAll(ruleExpressionLabels);
}
// labels from request
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
routerLabelResolvers.forEach(resolver -> {
try {
Map<String, String> customResolvedLabels = resolver.resolve(request, body);
if (!CollectionUtils.isEmpty(customResolvedLabels)) {
labels.putAll(customResolvedLabels);
}
}
catch (Throwable t) {
LOGGER.error("[SCT][Router] revoke RouterLabelResolver occur some exception. ", t);
}
});
}
// labels from downstream
Map<String, String> transitiveLabels = MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
labels.putAll(transitiveLabels);
// Because when the label is placed in RequestTemplate.header,
// RequestTemplate will parse the header according to the regular, which conflicts with the expression.
// Avoid conflicts by escaping.
Map<String, String> escapeLabels = new HashMap<>(labels.size());
for (Map.Entry<String, String> entry : labels.entrySet()) {
String escapedKey = ExpressionLabelUtils.escape(entry.getKey());
String escapedValue = ExpressionLabelUtils.escape(entry.getValue());
escapeLabels.put(escapedKey, escapedValue);
}
// pass label by header
if (escapeLabels.size() == 0) {
request.getHeaders().set(RouterConstants.ROUTER_LABEL_HEADER, null);
return;
}
request.getHeaders().set(RouterConstants.ROUTER_LABEL_HEADER, JacksonUtils.serialize2Json(escapeLabels));
}
private Map<String, String> getExpressionLabels(HttpRequest request, String peerServiceName) {
Set<String> labelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE,
MetadataContext.LOCAL_SERVICE, peerServiceName);
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
return ExpressionLabelUtils.resolve(request, labelKeys);
}
}

@ -0,0 +1,52 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.resttemplate;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.http.HttpRequest;
/**
* Wrapper of {@link LoadBalancerRequest}.
*
* @author Haotian Zhang
*/
public class PolarisLoadBalancerRequest<T> implements LoadBalancerRequest<T> {
private HttpRequest request;
private LoadBalancerRequest<T> delegate;
public PolarisLoadBalancerRequest(HttpRequest request, LoadBalancerRequest<T> delegate) {
this.request = request;
this.delegate = delegate;
}
@Override
public T apply(ServiceInstance instance) throws Exception {
return delegate.apply(instance);
}
public HttpRequest getRequest() {
return request;
}
public LoadBalancerRequest<T> getDelegate() {
return delegate;
}
}

@ -0,0 +1,49 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.spi;
import java.util.Map;
import feign.RequestTemplate;
import org.springframework.core.Ordered;
import org.springframework.http.HttpRequest;
/**
* The spi for resolving labels from request.
*
* @author lepdou 2022-05-11
*/
public interface RouterLabelResolver extends Ordered {
/**
* resolve labels from feign request.
* @param requestTemplate the feign request.
* @return resolved labels
*/
Map<String, String> resolve(RequestTemplate requestTemplate);
/**
* resolve labels from rest template request.
* @param request the rest template request.
* @param body the rest template request body.
* @return resolved labels
*/
Map<String, String> resolve(HttpRequest request, byte[] body);
}

@ -0,0 +1,22 @@
{
"properties": [
{
"name": "spring.cloud.polaris.router.metadata-router.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for metadata router."
},
{
"name": "spring.cloud.polaris.router.nearby-router.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for near by router."
},
{
"name": "spring.cloud.polaris.router.rule-router.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for rule based router."
}
]
}

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.polaris.router.config.RouterAutoConfiguration

@ -0,0 +1,64 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router;
import java.util.HashMap;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
/**
* test for {@link PolarisRouterContext}
*
*@author lepdou 2022-05-26
*/
public class PolarisRouterContextTest {
@Test
public void testNormalGetterSetter() {
Map<String, String> labels = new HashMap<>();
labels.put("k1", "v1");
labels.put("k2", "v2");
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels);
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size());
Assert.assertEquals(2, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size());
Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k1"));
Assert.assertEquals("v2", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k2"));
Assert.assertNull(routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k3"));
}
@Test
public void testSetNull() {
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, null);
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size());
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size());
}
@Test
public void testGetEmptyRouterContext() {
PolarisRouterContext routerContext = new PolarisRouterContext();
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size());
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size());
}
}

@ -0,0 +1,248 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.pojo.PolarisServiceInstance;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties;
import com.tencent.cloud.polaris.router.config.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.PolarisRuleBasedRouterProperties;
import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.pojo.DefaultServiceInstances;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.plugins.router.metadata.MetadataRouter;
import com.tencent.polaris.plugins.router.nearby.NearbyRouter;
import com.tencent.polaris.plugins.router.rule.RuleBasedRouter;
import com.tencent.polaris.router.api.core.RouterAPI;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import reactor.core.publisher.Flux;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
/**
* test for {@link PolarisRouterServiceInstanceListSupplier}
*@author lepdou 2022-05-26
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisRouterServiceInstanceListSupplierTest {
private static AtomicBoolean initTransitiveMetadata = new AtomicBoolean(false);
@Mock
private ServiceInstanceListSupplier delegate;
@Mock
private PolarisLoadBalancerProperties polarisLoadBalancerProperties;
@Mock
private PolarisNearByRouterProperties polarisNearByRouterProperties;
@Mock
private PolarisMetadataRouterProperties polarisMetadataRouterProperties;
@Mock
private PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties;
@Mock
private RouterAPI routerAPI;
private String testNamespace = "testNamespace";
private String testCallerService = "testCallerService";
private String testCalleeService = "testCalleeService";
@Test
public void testBuildMetadataRouteRequest() {
when(polarisMetadataRouterProperties.isEnabled()).thenReturn(true);
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn(testCallerService);
setTransitiveMetadata();
PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier(
delegate, routerAPI, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties);
ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA);
Assert.assertEquals(1, routerMetadata.size());
Assert.assertEquals(0, request.getRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY).size());
Assert.assertEquals(1, request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED).size());
Assert.assertEquals("false", request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED)
.get(RuleBasedRouter.ROUTER_ENABLED));
}
}
@Test
public void testBuildNearbyRouteRequest() {
when(polarisNearByRouterProperties.isEnabled()).thenReturn(true);
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn(testCallerService);
setTransitiveMetadata();
PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier(
delegate, routerAPI, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties);
ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY);
Assert.assertEquals(0, request.getRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA).size());
Assert.assertEquals(1, routerMetadata.size());
Assert.assertEquals("true", routerMetadata.get(NearbyRouter.ROUTER_ENABLED));
Assert.assertEquals(1, request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED).size());
Assert.assertEquals("false", request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED)
.get(RuleBasedRouter.ROUTER_ENABLED));
}
}
@Test
public void testBuildRuleBasedRouteRequest() {
when(polarisRuleBasedRouterProperties.isEnabled()).thenReturn(true);
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())).
thenReturn(testCallerService);
setTransitiveMetadata();
PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier(
delegate, routerAPI, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties);
ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED);
Assert.assertEquals(1, routerMetadata.size());
Assert.assertEquals(0, request.getRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA).size());
Assert.assertEquals(0, request.getRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY).size());
Assert.assertEquals(1, request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED).size());
Assert.assertEquals("true", request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED)
.get(RuleBasedRouter.ROUTER_ENABLED));
}
}
@Test
public void testRouter() {
when(polarisRuleBasedRouterProperties.isEnabled()).thenReturn(true);
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn(testCallerService);
setTransitiveMetadata();
PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier(
delegate, routerAPI, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties);
ProcessRoutersResponse assembleResponse = assembleProcessRoutersResponse();
when(routerAPI.processRouters(any())).thenReturn(assembleResponse);
Flux<List<ServiceInstance>> servers = compositeRule.doRouter(assembleServers(), assembleRouterContext());
Assert.assertEquals(assembleResponse.getServiceInstances().getInstances().size(),
servers.toStream().mapToLong(List::size).sum());
}
}
private void setTransitiveMetadata() {
if (initTransitiveMetadata.compareAndSet(false, true)) {
// mock transitive metadata
MetadataContext metadataContext = Mockito.mock(MetadataContext.class);
try (MockedStatic<MetadataContextHolder> mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class)) {
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
}
}
}
private ServiceInstances assembleServiceInstances() {
ServiceKey serviceKey = new ServiceKey(testNamespace, testCalleeService);
List<Instance> instances = new LinkedList<>();
instances.add(new DefaultInstance());
instances.add(new DefaultInstance());
instances.add(new DefaultInstance());
instances.add(new DefaultInstance());
instances.add(new DefaultInstance());
return new DefaultServiceInstances(serviceKey, instances);
}
private PolarisRouterContext assembleRouterContext() {
PolarisRouterContext routerContext = new PolarisRouterContext();
Map<String, String> transitiveLabels = new HashMap<>();
transitiveLabels.put("k1", "v1");
Map<String, String> routerLabels = new HashMap<>();
routerLabels.put("k2", "v2");
routerLabels.put("k3", "v3");
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels);
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, routerLabels);
return routerContext;
}
private ProcessRoutersResponse assembleProcessRoutersResponse() {
return new ProcessRoutersResponse(assembleServiceInstances());
}
private Flux<List<ServiceInstance>> assembleServers() {
ServiceInstances serviceInstances = assembleServiceInstances();
List<ServiceInstance> servers = new ArrayList<>();
for (Instance instance : serviceInstances.getInstances()) {
servers.add(new PolarisServiceInstance(instance));
}
return Flux.fromIterable(Collections.singletonList(servers));
}
}

@ -0,0 +1,93 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Lists;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RoutingProto;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
/**
* test for {@link RouterRuleLabelResolver}
*@author lepdou 2022-05-26
*/
@RunWith(MockitoJUnitRunner.class)
public class RouterRuleLabelResolverTest {
@Mock
private ServiceRuleManager serviceRuleManager;
private final String testNamespace = "testNamespace";
private final String testSourceService = "sourceService";
private final String testDstService = "dstService";
@Test
public void test() {
Map<String, ModelProto.MatchString> labels = new HashMap<>();
ModelProto.MatchString matchString = ModelProto.MatchString.getDefaultInstance();
String validKey1 = "${http.header.uid}";
String validKey2 = "${http.query.name}";
String validKey3 = "${http.method}";
String validKey4 = "${http.uri}";
String invalidKey = "${http.expression.wrong}";
labels.put(validKey1, matchString);
labels.put(validKey2, matchString);
labels.put(validKey3, matchString);
labels.put(validKey4, matchString);
labels.put(invalidKey, matchString);
RoutingProto.Source source1 = RoutingProto.Source.newBuilder().putAllMetadata(labels).build();
RoutingProto.Source source2 = RoutingProto.Source.newBuilder().putAllMetadata(labels).build();
RoutingProto.Source source3 = RoutingProto.Source.newBuilder().putAllMetadata(new HashMap<>()).build();
List<RoutingProto.Route> routes = new LinkedList<>();
RoutingProto.Route route = RoutingProto.Route.newBuilder()
.addAllSources(Lists.newArrayList(source1, source2, source3))
.build();
routes.add(route);
when(serviceRuleManager.getServiceRouterRule(testNamespace, testSourceService, testDstService)).thenReturn(routes);
RouterRuleLabelResolver resolver = new RouterRuleLabelResolver(serviceRuleManager);
Set<String> resolvedExpressionLabelKeys = resolver.getExpressionLabelKeys(testNamespace, testSourceService, testDstService);
Assert.assertNotNull(resolvedExpressionLabelKeys);
Assert.assertEquals(4, resolvedExpressionLabelKeys.size());
Assert.assertTrue(resolvedExpressionLabelKeys.contains(validKey1));
Assert.assertTrue(resolvedExpressionLabelKeys.contains(validKey2));
Assert.assertTrue(resolvedExpressionLabelKeys.contains(validKey3));
Assert.assertTrue(resolvedExpressionLabelKeys.contains(validKey4));
Assert.assertFalse(resolvedExpressionLabelKeys.contains(invalidKey));
}
}

@ -0,0 +1,140 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.feign;
import java.util.HashMap;
import java.util.Map;
import com.google.common.collect.Sets;
import feign.Request;
import feign.RequestTemplate;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.util.StringUtils;
/**
* Test for {@link FeignExpressionLabelUtils}
*@author lepdou 2022-05-26
*/
public class FeignExpressionLabelUtilsTest {
@Test
public void testGetHeaderLabel() {
String headerKey = "uid";
String headerValue = "1000";
String headerKey2 = "teacher.age";
String headerValue2 = "1000";
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.header(headerKey, headerValue);
requestTemplate.header(headerKey2, headerValue2);
String labelKey1 = "${http.header.uid}";
String labelKey2 = "${http.header.name}";
String labelKey3 = "${http.headername}";
String labelKey4 = "${http.header.}";
String labelKey5 = "${http.header.teacher.age}";
Map<String, String> result = FeignExpressionLabelUtils.resolve(requestTemplate,
Sets.newHashSet(labelKey1, labelKey2, labelKey3, labelKey4, labelKey5));
Assert.assertFalse(result.isEmpty());
Assert.assertEquals(headerValue, result.get(labelKey1));
Assert.assertEquals(headerValue2, result.get(labelKey5));
Assert.assertTrue(StringUtils.isEmpty(result.get(labelKey2)));
Assert.assertTrue(StringUtils.isEmpty(result.get(labelKey3)));
Assert.assertTrue(StringUtils.isEmpty(result.get(labelKey4)));
}
@Test
public void testGetQueryLabel() {
String headerKey = "uid";
String headerValue = "1000";
String headerKey2 = "teacher.age";
String headerValue2 = "1000";
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.query(headerKey, headerValue);
requestTemplate.query(headerKey2, headerValue2);
String labelKey1 = "${http.query.uid}";
String labelKey2 = "${http.query.name}";
String labelKey3 = "${http.queryname}";
String labelKey4 = "${http.query.}";
String labelKey5 = "${http.query.teacher.age}";
Map<String, String> result = FeignExpressionLabelUtils.resolve(requestTemplate,
Sets.newHashSet(labelKey1, labelKey2, labelKey3, labelKey4, labelKey5));
Assert.assertFalse(result.isEmpty());
Assert.assertEquals(headerValue, result.get(labelKey1));
Assert.assertEquals(headerValue2, result.get(labelKey5));
Assert.assertTrue(StringUtils.isEmpty(result.get(labelKey2)));
Assert.assertTrue(StringUtils.isEmpty(result.get(labelKey3)));
Assert.assertTrue(StringUtils.isEmpty(result.get(labelKey4)));
}
@Test
public void testGetMethod() {
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.method(Request.HttpMethod.GET);
String labelKey1 = "${http.method}";
Map<String, String> result = FeignExpressionLabelUtils.resolve(requestTemplate,
Sets.newHashSet(labelKey1));
Assert.assertFalse(result.isEmpty());
Assert.assertEquals("GET", result.get(labelKey1));
}
@Test
public void testGetUri() {
String uri = "/user/get";
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.uri(uri);
requestTemplate.method(Request.HttpMethod.GET);
requestTemplate.target("http://localhost");
requestTemplate = requestTemplate.resolve(new HashMap<>());
String labelKey1 = "${http.uri}";
Map<String, String> result = FeignExpressionLabelUtils.resolve(requestTemplate,
Sets.newHashSet(labelKey1));
Assert.assertFalse(result.isEmpty());
Assert.assertEquals(uri, result.get(labelKey1));
}
@Test
public void testGetUri2() {
String uri = "/";
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.uri(uri);
requestTemplate.method(Request.HttpMethod.GET);
requestTemplate.target("http://localhost");
requestTemplate = requestTemplate.resolve(new HashMap<>());
String labelKey1 = "${http.uri}";
Map<String, String> result = FeignExpressionLabelUtils.resolve(requestTemplate,
Sets.newHashSet(labelKey1));
Assert.assertFalse(result.isEmpty());
Assert.assertEquals(uri, result.get(labelKey1));
}
}

@ -0,0 +1,142 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.feign;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import feign.RequestTemplate;
import feign.Target;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
/**
* test for {@link RouterLabelFeignInterceptor}
* @author lepdou 2022-05-26
*/
@RunWith(MockitoJUnitRunner.class)
public class RouterLabelFeignInterceptorTest {
@Mock
private MetadataLocalProperties metadataLocalProperties;
@Mock
private RouterRuleLabelResolver routerRuleLabelResolver;
@Mock
private RouterLabelResolver routerLabelResolver;
@Test
public void testResolveRouterLabel() {
RouterLabelFeignInterceptor routerLabelFeignInterceptor = new RouterLabelFeignInterceptor(
Collections.singletonList(routerLabelResolver),
metadataLocalProperties, routerRuleLabelResolver);
// mock request template
RequestTemplate requestTemplate = new RequestTemplate();
String headerUidKey = "uid";
String headerUidValue = "1000";
requestTemplate.header(headerUidKey, headerUidValue);
String peerService = "peerService";
Target.EmptyTarget<Object> target = Target.EmptyTarget.create(Object.class, peerService);
requestTemplate.feignTarget(target);
// mock ApplicationContextAwareUtils#getProperties
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
String testService = "callerService";
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn(testService);
MetadataContext metadataContext = Mockito.mock(MetadataContext.class);
// mock transitive metadata
Map<String, String> transitiveLabels = new HashMap<>();
transitiveLabels.put("k1", "v1");
transitiveLabels.put("k2", "v22");
when(metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)).thenReturn(transitiveLabels);
// mock MetadataContextHolder#get
try (MockedStatic<MetadataContextHolder> mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class)) {
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
// mock custom resolved labels from request
Map<String, String> customResolvedLabels = new HashMap<>();
customResolvedLabels.put("k2", "v2");
customResolvedLabels.put("k3", "v3");
when(routerLabelResolver.resolve(requestTemplate)).thenReturn(customResolvedLabels);
// mock expression rule labels
Set<String> expressionKeys = new HashSet<>();
expressionKeys.add("${http.header.uid}");
expressionKeys.add("${http.header.name}");
when(routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE,
MetadataContext.LOCAL_SERVICE, peerService)).thenReturn(expressionKeys);
// mock local metadata
Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("k3", "v31");
localMetadata.put("k4", "v4");
when(metadataLocalProperties.getContent()).thenReturn(localMetadata);
routerLabelFeignInterceptor.apply(requestTemplate);
Collection<String> routerLabels = requestTemplate.headers().get(RouterConstants.ROUTER_LABEL_HEADER);
Assert.assertNotNull(routerLabels);
for (String value : routerLabels) {
Map<String, String> labels = unescape(JacksonUtils.deserialize2Map(value));
Assert.assertEquals("v1", labels.get("k1"));
Assert.assertEquals("v22", labels.get("k2"));
Assert.assertEquals("v3", labels.get("k3"));
Assert.assertEquals("v4", labels.get("k4"));
Assert.assertEquals(headerUidValue, labels.get("${http.header.uid}"));
Assert.assertEquals("", labels.get("${http.header.name}"));
}
}
}
}
private Map<String, String> unescape(Map<String, String> labels) {
Map<String, String> result = new HashMap<>();
for (Map.Entry<String, String> entry : labels.entrySet()) {
result.put(ExpressionLabelUtils.unescape(entry.getKey()), ExpressionLabelUtils.unescape(entry.getValue()));
}
return result;
}
}

@ -0,0 +1,93 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.resttemplate;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.BeanFactoryUtils;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import static org.mockito.Mockito.when;
/**
* Test for ${@link PolarisLoadBalancerBeanPostProcessor}
* @author lepdou 2022-05-26
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisLoadBalancerBeanPostProcessorTest {
@Mock
private LoadBalancerClient loadBalancerClient;
@Mock
private LoadBalancerRequestFactory loadBalancerRequestFactory;
@Mock
private MetadataLocalProperties metadataLocalProperties;
@Mock
private RouterRuleLabelResolver routerRuleLabelResolver;
@Mock
private BeanFactory beanFactory;
@Test
public void testWrapperLoadBalancerInterceptor() {
when(beanFactory.getBean(LoadBalancerRequestFactory.class)).thenReturn(loadBalancerRequestFactory);
when(beanFactory.getBean(LoadBalancerClient.class)).thenReturn(loadBalancerClient);
when(beanFactory.getBean(MetadataLocalProperties.class)).thenReturn(metadataLocalProperties);
when(beanFactory.getBean(RouterRuleLabelResolver.class)).thenReturn(routerRuleLabelResolver);
try (MockedStatic<BeanFactoryUtils> mockedBeanFactoryUtils = Mockito.mockStatic(BeanFactoryUtils.class)) {
mockedBeanFactoryUtils.when(() -> BeanFactoryUtils.getBeans(beanFactory, RouterLabelResolver.class))
.thenReturn(null);
LoadBalancerInterceptor loadBalancerInterceptor = new LoadBalancerInterceptor(loadBalancerClient, loadBalancerRequestFactory);
PolarisLoadBalancerBeanPostProcessor processor = new PolarisLoadBalancerBeanPostProcessor();
processor.setBeanFactory(beanFactory);
Object bean = processor.postProcessBeforeInitialization(loadBalancerInterceptor, "");
Assert.assertTrue(bean instanceof PolarisLoadBalancerInterceptor);
}
}
@Test
public void testNotWrapperLoadBalancerInterceptor() {
PolarisLoadBalancerBeanPostProcessor processor = new PolarisLoadBalancerBeanPostProcessor();
processor.setBeanFactory(beanFactory);
OtherBean otherBean = new OtherBean();
Object bean = processor.postProcessBeforeInitialization(otherBean, "");
Assert.assertFalse(bean instanceof PolarisLoadBalancerInterceptor);
Assert.assertTrue(bean instanceof OtherBean);
}
static class OtherBean {
}
}

@ -0,0 +1,229 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.resttemplate;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* test for {@link PolarisLoadBalancerInterceptor}
* @author lepdou 2022-05-26
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisLoadBalancerInterceptorTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<MetadataContextHolder> mockedMetadataContextHolder;
@Mock
private LoadBalancerClient loadBalancerClient;
@Mock
private LoadBalancerRequestFactory loadBalancerRequestFactory;
@Mock
private RouterLabelResolver routerLabelResolver;
@Mock
private MetadataLocalProperties metadataLocalProperties;
@Mock
private RouterRuleLabelResolver routerRuleLabelResolver;
@BeforeClass
public static void beforeClass() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("callerService");
mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class);
}
@AfterClass
public static void afterClass() {
mockedApplicationContextAwareUtils.close();
mockedMetadataContextHolder.close();
}
@Test
public void testProxyRibbonLoadBalance() throws Exception {
String callerService = "callerService";
String calleeService = "calleeService";
HttpRequest request = new MockedHttpRequest("http://" + calleeService + "/user/get");
// mock local metadata
Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("k1", "v1");
localMetadata.put("k2", "v2");
when(metadataLocalProperties.getContent()).thenReturn(localMetadata);
// mock custom resolved from request
Map<String, String> customResolvedLabels = new HashMap<>();
customResolvedLabels.put("k3", "v3");
customResolvedLabels.put("k4", "v4");
when(routerLabelResolver.resolve(request, null)).thenReturn(customResolvedLabels);
// mock expression rule labels
Set<String> expressionKeys = new HashSet<>();
expressionKeys.add("${http.method}");
expressionKeys.add("${http.uri}");
when(routerRuleLabelResolver.getExpressionLabelKeys(callerService, callerService, calleeService)).thenReturn(expressionKeys);
MetadataContext metadataContext = Mockito.mock(MetadataContext.class);
// mock transitive metadata
Map<String, String> transitiveLabels = new HashMap<>();
transitiveLabels.put("k1", "v1");
transitiveLabels.put("k2", "v22");
when(metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)).thenReturn(transitiveLabels);
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
LoadBalancerRequest<ClientHttpResponse> loadBalancerRequest = new MockedLoadBalancerRequest<>();
when(loadBalancerRequestFactory.createRequest(request, null, null)).thenReturn(loadBalancerRequest);
PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(loadBalancerClient,
loadBalancerRequestFactory, Collections.singletonList(routerLabelResolver), metadataLocalProperties, routerRuleLabelResolver);
polarisLoadBalancerInterceptor.intercept(request, null, null);
verify(metadataLocalProperties).getContent();
verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService);
verify(routerLabelResolver).resolve(request, null);
}
@Test
public void testRouterContext() throws Exception {
String callerService = "callerService";
String calleeService = "calleeService";
HttpRequest request = new MockedHttpRequest("http://" + calleeService + "/user/get");
// mock local metadata
Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("k1", "v1");
localMetadata.put("k2", "v2");
when(metadataLocalProperties.getContent()).thenReturn(localMetadata);
// mock custom resolved from request
Map<String, String> customResolvedLabels = new HashMap<>();
customResolvedLabels.put("k2", "v22");
customResolvedLabels.put("k4", "v4");
when(routerLabelResolver.resolve(request, null)).thenReturn(customResolvedLabels);
// mock expression rule labels
Set<String> expressionKeys = new HashSet<>();
expressionKeys.add("${http.method}");
expressionKeys.add("${http.uri}");
when(routerRuleLabelResolver.getExpressionLabelKeys(callerService, callerService, calleeService)).thenReturn(expressionKeys);
MetadataContext metadataContext = Mockito.mock(MetadataContext.class);
// mock transitive metadata
Map<String, String> transitiveLabels = new HashMap<>();
transitiveLabels.put("k1", "v1");
transitiveLabels.put("k2", "v22");
when(metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)).thenReturn(transitiveLabels);
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(loadBalancerClient,
loadBalancerRequestFactory, Collections.singletonList(routerLabelResolver), metadataLocalProperties, routerRuleLabelResolver);
polarisLoadBalancerInterceptor.setLabelsToHeaders(request, null, calleeService);
verify(metadataLocalProperties).getContent();
verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService);
verify(routerLabelResolver).resolve(request, null);
Map<String, String> headers = JacksonUtils.deserialize2Map(request.getHeaders()
.get(RouterConstants.ROUTER_LABEL_HEADER).get(0));
Assert.assertEquals("v1", headers.get("k1"));
Assert.assertEquals("v22", headers.get("k2"));
Assert.assertEquals("v4", headers.get("k4"));
Assert.assertEquals("GET", headers.get("##@$@##http.method}"));
Assert.assertEquals("/user/get", headers.get("##@$@##http.uri}"));
}
static class MockedLoadBalancerRequest<T> implements LoadBalancerRequest<T> {
@Override
public T apply(ServiceInstance instance) throws Exception {
return null;
}
}
static class MockedHttpRequest implements HttpRequest {
private URI uri;
private HttpHeaders httpHeaders = new HttpHeaders();
MockedHttpRequest(String url) {
this.uri = URI.create(url);
}
@Override
public String getMethodValue() {
return HttpMethod.GET.name();
}
@Override
public URI getURI() {
return uri;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
}
}

@ -22,7 +22,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -44,10 +45,10 @@ public class RouterCalleeController {
* Get information of callee.
* @return information of callee
*/
@GetMapping("/info")
public String info() {
@PostMapping("/info")
public String info(String name, @RequestBody User user) {
LOG.info("Discovery Service Callee [{}] is called.", port);
return String.format("Discovery Service Callee [%s] is called.", port);
return String.format("Discovery Service Callee [%s] is called. user = %s", port, user);
}
}

@ -0,0 +1,53 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.example;
/**
* demo object.
* @author lepdou 2022-05-12
*/
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

@ -22,8 +22,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
@ -44,10 +46,10 @@ public class RouterCalleeController {
* Get information of callee.
* @return information of callee
*/
@GetMapping("/info")
public String info() {
@PostMapping("/info")
public String info(@RequestParam("name") String name, @RequestBody User user) {
LOG.info("Discovery Service Callee [{}] is called.", port);
return String.format("Discovery Service Callee [%s] is called.", port);
return String.format("Discovery Service Callee [%s] is called. user = %s", port, user);
}
}

@ -0,0 +1,53 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.example;
/**
* demo object.
* @author lepdou 2022-05-12
*/
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

@ -22,6 +22,12 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>

@ -0,0 +1,65 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.example;
import java.util.HashMap;
import java.util.Map;
import com.google.gson.Gson;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import feign.RequestTemplate;
import org.springframework.http.HttpRequest;
import org.springframework.stereotype.Component;
/**
*
* Customize the business tag information obtained from the request
*
*@author lepdou 2022-05-12
*/
@Component
public class CustomRouterLabelResolver implements RouterLabelResolver {
private final Gson gson = new Gson();
@Override
public Map<String, String> resolve(RequestTemplate requestTemplate) {
Map<String, String> labels = new HashMap<>();
User user = gson.fromJson(new String(requestTemplate.body()), User.class);
labels.put("user", user.getName());
return labels;
}
@Override
public Map<String, String> resolve(HttpRequest request, byte[] body) {
Map<String, String> labels = new HashMap<>();
User user = gson.fromJson(new String(body), User.class);
labels.put("user", user.getName());
return labels;
}
@Override
public int getOrder() {
return 0;
}
}

@ -19,7 +19,9 @@
package com.tencent.cloud.polaris.router.example;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
/**
* Router callee feign client.
@ -29,7 +31,7 @@ import org.springframework.web.bind.annotation.GetMapping;
@FeignClient("RouterCalleeService")
public interface RouterCalleeService {
@GetMapping("/router/service/callee/info")
String info();
@PostMapping("/router/service/callee/info")
String info(@RequestParam("name") String name, @RequestBody User user);
}

@ -21,6 +21,7 @@ package com.tencent.cloud.polaris.router.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@ -44,8 +45,11 @@ public class RouterCallerController {
* @return info
*/
@GetMapping("/feign")
public String feign() {
return routerCalleeService.info();
public String feign(@RequestParam String name) {
User user = new User();
user.setName(name);
user.setAge(18);
return routerCalleeService.info(name, user);
}
/**
@ -53,8 +57,12 @@ public class RouterCallerController {
* @return information of callee
*/
@GetMapping("/rest")
public String rest() {
return restTemplate.getForObject("http://RouterCalleeService/router/service/callee/info", String.class);
public String rest(@RequestParam String name) {
User user = new User();
user.setName(name);
user.setAge(18);
return restTemplate.postForObject(
"http://RouterCalleeService/router/service/callee/info?name={name}", user, String.class, name);
}
/**

@ -0,0 +1,45 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.example;
/**
* demo object.
* @author lepdou 2022-05-12
*/
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

@ -0,0 +1,263 @@
# Spring Cloud Polaris Gray Release Example
[English](./README.md) | 简体中文
## 项目说明
本项目演示如何使用 Spring Cloud Tencent 的路由和标签透传功能 完成 Spring Cloud 应用的全链路灰度。
## 示例架构
![](https://qcloudimg.tencent-cloud.cn/raw/488182fd3001b3e77d9450e2c8798ff3.png)
本示例请求都通过最上层网关进行分发分发的目的地主要涉及3个环境
- 灰度环境1只针对uid=1的请求放开环境标识为env=green绿色环境
- 灰度环境2只针对uid=2的请求放开环境标识为env=purple紫色环境
- 基线环境稳定的业务版本针对其他请求放开环境标识为env=blue蓝色环境
## 如何接入
### 启动网关服务
1. 添加环境变量
- 北极星服务端地址polaris_address=grpc://127.0.0.1:8091
- 可观测性PushGateway地址prometheus_address=127.0.0.1:9091
2. 启动router-grayrelease-gateway应用
- IDE直接启动找到主类 `GrayReleaseGatewayApplication`,执行 main 方法启动应用。
- 打包编译后启动:首先执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar router-grayrelease-gateway-${verion}.jar`启动应用。
3. 添加路由规则
通过往北极星接口发送以下数据为网关服务添加路由规则路由规则可以针对用户ID进行环境的分发。
````
POST /naming/v1/routings
[{
"service": "gray-release-gateway",
"namespace": "default",
"outbounds": [
{
"sources": [
{
"service": "gray-release-gateway",
"namespace": "default",
"metadata": {
"${http.header.uid}": {
"type": "EXACT",
"value": "2"
}
}
}],
"destinations": [
{
"service": "*",
"namespace": "*",
"metadata": {
"env": {
"type": "EXACT",
"value": "purple"
}
},
"priority": 0,
"weight": 100,
"isolate": false
}]
},
{
"sources": [
{
"service": "gray-release-gateway",
"namespace": "default",
"metadata": {
"${http.header.uid}": {
"type": "EXACT",
"value": "1"
}
}
}],
"destinations": [
{
"service": "*",
"namespace": "*",
"metadata": {
"env": {
"type": "EXACT",
"value": "green"
}
},
"priority": 0,
"weight": 100,
"isolate": false
}]
},
{
"sources": [
{
"service": "gray-release-gateway",
"namespace": "default",
"metadata": {
"*": {
"type": "EXACT",
"value": "*"
}
}
}],
"destinations": [
{
"service": "*",
"namespace": "*",
"metadata": {
"env": {
"type": "EXACT",
"value": "blue"
}
},
"priority": 0,
"weight": 100,
"isolate": false
}]
}
]
}]
````
路由规则也可以通过北极星控制台进行定义,最终控制台效果如下:
![](https://qcloudimg.tencent-cloud.cn/raw/28e3d734c4b73624869a5b9b7059b118.png)
### 启动Front服务
#### 启动基线环境(蓝色)
1. 添加环境变量
- 北极星服务端地址polaris_address=grpc://127.0.0.1:8091
- 可观测性PushGateway地址prometheus_address=127.0.0.1:9091
- 环境标识SCT_METADATA_CONTENT_env=blue
- 透传环境标识SCT_METADATA_CONTENT_TRANSITIVE=env
2. 启动router-grayrelease-frontend应用
- IDE直接启动找到主类 `GrayReleaseFrontApplication`,执行 main 方法启动应用。
- 打包编译后启动:首先执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar router-grayrelease-frontend-${verion}.jar`启动应用。
#### 启动灰度环境1绿色
1. 添加环境变量
- 北极星服务端地址polaris_address=grpc://127.0.0.1:8091
- 可观测性PushGateway地址prometheus_address=127.0.0.1:9091
- 环境标识SCT_METADATA_CONTENT_env=green
- 透传环境标识SCT_METADATA_CONTENT_TRANSITIVE=env
2. 启动router-grayrelease-frontend应用与前面一致
如果遇到端口冲突,可以通过-Dserver.port来指定端口
#### 启动灰度环境2紫色
1. 添加环境变量
- 北极星服务端地址polaris_address=grpc://127.0.0.1:8091
- 可观测性PushGateway地址prometheus_address=127.0.0.1:9091
- 环境标识SCT_METADATA_CONTENT_env=purple
- 透传环境标识SCT_METADATA_CONTENT_TRANSITIVE=env
2. 启动router-grayrelease-frontend应用与前面一致
#### 启动后效果
在北极星控制台可以看到gray-release-front服务下有3个节点每个节点有不同的环境标识。
![](https://qcloudimg.tencent-cloud.cn/raw/96d2bdd2fb3495f737ab278e31a4a2e7.png)
### 启动middle服务
#### 启动基线环境(蓝色)
1. 添加环境变量
- 北极星服务端地址polaris_address=grpc://127.0.0.1:8091
- 可观测性PushGateway地址prometheus_address=127.0.0.1:9091
- 环境标识SCT_METADATA_CONTENT_env=blue
- 透传环境标识SCT_METADATA_CONTENT_TRANSITIVE=env
2. 启动router-grayrelease-middle应用
- IDE直接启动找到主类 `GrayReleaseMiddleApplication`,执行 main 方法启动应用。
- 打包编译后启动:首先执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar router-grayrelease-middle-${verion}.jar`启动应用。
#### 启动灰度环境2紫色
1. 添加环境变量
- 北极星服务端地址polaris_address=grpc://127.0.0.1:8091
- 可观测性PushGateway地址prometheus_address=127.0.0.1:9091
- 环境标识SCT_METADATA_CONTENT_env=purple
- 透传环境标识SCT_METADATA_CONTENT_TRANSITIVE=env
2. 启动router-grayrelease-middle应用与前面一致
### 启动back服务
#### 启动基线环境(蓝色)
1. 添加环境变量
- 北极星服务端地址polaris_address=grpc://127.0.0.1:8091
- 可观测性PushGateway地址prometheus_address=127.0.0.1:9091
- 环境标识SCT_METADATA_CONTENT_env=blue
- 透传环境标识SCT_METADATA_CONTENT_TRANSITIVE=env
2. 启动router-grayrelease-backend应用
- IDE直接启动找到主类 `GrayReleaseBackendApplication`,执行 main 方法启动应用。
- 打包编译后启动:首先执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar router-grayrelease-backend-${verion}.jar`启动应用。
#### 启动灰度环境1绿色
1. 添加环境变量
- 北极星服务端地址polaris_address=grpc://127.0.0.1:8091
- 可观测性PushGateway地址prometheus_address=127.0.0.1:9091
- 环境标识SCT_METADATA_CONTENT_env=green
- 透传环境标识SCT_METADATA_CONTENT_TRANSITIVE=env
2. 启动router-grayrelease-backend应用与前面一致
### 测试
#### 基线环境路由
````
curl -H'uid:0' 127.0.0.1:59100/router/gray/route_rule
````
获取结果
````
gray-release-gateway -> gray-release-front[blue] -> gray-release-middle[blue] -> gray-release-back[blue]
````
#### 灰度环境1绿色路由
````
curl -H'uid:1' 127.0.0.1:59100/router/gray/route_rule
````
获取结果
````
gray-release-gateway -> gray-release-front[green] -> gray-release-middle[blue] -> gray-release-back[green]
````
#### 灰度环境2紫色路由
````
curl -H'uid:2' 127.0.0.1:59100/router/gray/route_rule
````
获取结果
````
gray-release-gateway -> gray-release-front[purple] -> gray-release-middle[purple] -> gray-release-back[blue]
````

@ -0,0 +1,262 @@
# Spring Cloud Polaris Gray Release Example
English | [简体中文](./README-zh.md)
## Project Explanation
This project shows how to use Spring Cloud Tencent route and transitive feature to do the full chain gray releasing.
## Architecture
![](https://qcloudimg.tencent-cloud.cn/raw/488182fd3001b3e77d9450e2c8798ff3.png)
Incoming requests dispatched from Gateway service to 3 environments
- gray1(match uid=1), env=green(green environment)
- gray2(match uid=2), env=purple(purple environment)
- baselinestable environment, match all other requests, env=blue(blue environment)
## How to access
### Start Gateway service
1. add environment variables
- polaris server address: polaris_address=grpc://127.0.0.1:8091
- pushgateway address: prometheus_address=127.0.0.1:9091
2. start router-grayrelease-gateway application
- Launch by IDEStart the main class `GrayReleaseGatewayApplication`.
- Launch by JarExecute `mvn clean package` to compile with jar package, then use `java -jar router-grayrelease-gateway-${verion}.jar` to launch application.
3. add the route rule
Send http request to polaris server to add the route rule, make requests dispatched to 3 environments.
````
POST /naming/v1/routings
[{
"service": "gray-release-gateway",
"namespace": "default",
"outbounds": [
{
"sources": [
{
"service": "gray-release-gateway",
"namespace": "default",
"metadata": {
"${http.header.uid}": {
"type": "EXACT",
"value": "2"
}
}
}],
"destinations": [
{
"service": "*",
"namespace": "*",
"metadata": {
"env": {
"type": "EXACT",
"value": "purple"
}
},
"priority": 0,
"weight": 100,
"isolate": false
}]
},
{
"sources": [
{
"service": "gray-release-gateway",
"namespace": "default",
"metadata": {
"${http.header.uid}": {
"type": "EXACT",
"value": "1"
}
}
}],
"destinations": [
{
"service": "*",
"namespace": "*",
"metadata": {
"env": {
"type": "EXACT",
"value": "green"
}
},
"priority": 0,
"weight": 100,
"isolate": false
}]
},
{
"sources": [
{
"service": "gray-release-gateway",
"namespace": "default",
"metadata": {
"*": {
"type": "EXACT",
"value": "*"
}
}
}],
"destinations": [
{
"service": "*",
"namespace": "*",
"metadata": {
"env": {
"type": "EXACT",
"value": "blue"
}
},
"priority": 0,
"weight": 100,
"isolate": false
}]
}
]
}]
````
The route rule can be added by polaris console:
![](https://qcloudimg.tencent-cloud.cn/raw/28e3d734c4b73624869a5b9b7059b118.png)
### Start Front service
#### Start baseline environment (blue)
1. add environment variables
- polaris server address: polaris_address=grpc://127.0.0.1:8091
- pushgateway address: prometheus_address=127.0.0.1:9091
- env tagSCT_METADATA_CONTENT_env=blue
- transitive tagSCT_METADATA_CONTENT_TRANSITIVE=env
2. start router-grayrelease-frontend application
- Launch by IDEStart the main class `GrayReleaseFrontApplication`.
- Launch by JarExecute `mvn clean package` to compile with jar package, then use `java -jar router-grayrelease-frontend-${verion}.jar` to launch application.
#### Start gray1 environment (green)
1. add environment variables
- polaris server address: polaris_address=grpc://127.0.0.1:8091
- pushgateway address: prometheus_address=127.0.0.1:9091
- env tagSCT_METADATA_CONTENT_env=green
- transitive tagSCT_METADATA_CONTENT_TRANSITIVE=env
2. start router-grayrelease-frontend application (same as previous instruction)
If port conflicted, you can specify another port by -Dserver.port
#### Start gray2 environment (purple)
1. add environment variables
- polaris server address: polaris_address=grpc://127.0.0.1:8091
- pushgateway address: prometheus_address=127.0.0.1:9091
- env tagSCT_METADATA_CONTENT_env=purple
- transitive tagSCT_METADATA_CONTENT_TRANSITIVE=env
2. start router-grayrelease-frontend application (same as previous instruction)
#### Start effective
You can find the instances with different tags in polaris console.
![](https://qcloudimg.tencent-cloud.cn/raw/96d2bdd2fb3495f737ab278e31a4a2e7.png)
### Start Middle service
#### Start baseline environment (blue)
1. add environment variables
- polaris server address: polaris_address=grpc://127.0.0.1:8091
- pushgateway address: prometheus_address=127.0.0.1:9091
- env tagSCT_METADATA_CONTENT_env=blue
- transitive tagSCT_METADATA_CONTENT_TRANSITIVE=env
2. start router-grayrelease-middle application
- Launch by IDEStart the main class `GrayReleaseMiddleApplication`.
- Launch by JarExecute `mvn clean package` to compile with jar package, then use `java -jar router-grayrelease-middle-${verion}.jar` to launch application.
#### Start gray2 environment (purple)
1. add environment variables
- polaris server address: polaris_address=grpc://127.0.0.1:8091
- pushgateway address: prometheus_address=127.0.0.1:9091
- env tagSCT_METADATA_CONTENT_env=purple
- transitive tagSCT_METADATA_CONTENT_TRANSITIVE=env
2. start router-grayrelease-middle application (same as previous instruction)
### Start Back service
#### Start baseline environment (blue)
1. add environment variables
- polaris server address: polaris_address=grpc://127.0.0.1:8091
- pushgateway address: prometheus_address=127.0.0.1:9091
- env tagSCT_METADATA_CONTENT_env=blue
- transitive tagSCT_METADATA_CONTENT_TRANSITIVE=env
2. start router-grayrelease-backend application
- Launch by IDEStart the main class `GrayReleaseBackendApplication`.
- Launch by JarExecute `mvn clean package` to compile with jar package, then use `java -jar router-grayrelease-backend-${verion}.jar` to launch application.
#### Start gray1 environment (green)
1. add environment variables
- polaris server address: polaris_address=grpc://127.0.0.1:8091
- pushgateway address: prometheus_address=127.0.0.1:9091
- env tagSCT_METADATA_CONTENT_env=green
- transitive tagSCT_METADATA_CONTENT_TRANSITIVE=env
2. start router-grayrelease-backend application (same as previous instruction)
### Test
#### Baseline routing
````
curl -H'uid:0' 127.0.0.1:59100/router/gray/route_rule
````
Got result
````
gray-release-gateway -> gray-release-front[blue] -> gray-release-middle[blue] -> gray-release-back[blue]
````
#### Green routing
````
curl -H'uid:1' 127.0.0.1:59100/router/gray/route_rule
````
Got result
````
gray-release-gateway -> gray-release-front[green] -> gray-release-middle[blue] -> gray-release-back[green]
````
#### Purple routing
````
curl -H'uid:2' 127.0.0.1:59100/router/gray/route_rule
````
Got result
````
gray-release-gateway -> gray-release-front[purple] -> gray-release-middle[purple] -> gray-release-back[blue]
````

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tencent-examples</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>polaris-router-grayrelease-example</artifactId>
<packaging>pom</packaging>
<name>Spring Cloud Tencent Polaris Router GrayRelease Example</name>
<description>Example of Spring Cloud Tencent Polaris Router GrayRelease</description>
<modules>
<module>router-grayrelease-gateway</module>
<module>router-grayrelease-frontend</module>
<module>router-grayrelease-middle</module>
<module>router-grayrelease-backend</module>
</modules>
</project>

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>polaris-router-grayrelease-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>router-grayrelease-backend</artifactId>
<dependencies>
<dependency>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
<groupId>com.tencent.cloud</groupId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,15 @@
############################################################
# Dockerfile to build polaris-java quickstart example provider
# 1. You need to build the binary from the source code,
# use `mvn clean install` to build the binary.
# 2. You need to copy the quickstart-example-provider-*.jar to this directory
# 3. Replace the ${VERSION} to the real version of the project
############################################################
FROM java:8
ADD router-grayrelease-backend-1.5.0-Hoxton.SR9-SNAPSHOT.jar /root/app.jar
ENTRYPOINT ["java","-jar","/root/app.jar"]

@ -0,0 +1,43 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.grayrelease.back;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/router/gray")
public class BackController {
@Autowired
private Environment environment;
/**
* Get information of callee.
* @return information of callee
*/
@GetMapping("/rest")
public String rest() {
String env = System.getenv("SCT_METADATA_CONTENT_env");
String appName = environment.getProperty("spring.application.name");
return appName + "[" + env + "]";
}
}

@ -0,0 +1,29 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.grayrelease.back;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GrayReleaseBackendApplication {
public static void main(String[] args) {
SpringApplication.run(GrayReleaseBackendApplication.class, args);
}
}

@ -0,0 +1,15 @@
server:
session-timeout: 1800
port: 59002
spring:
application:
name: gray-release-back
cloud:
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
logging:
level:
org.springframework.cloud.gateway: info
com.tencent.cloud.polaris: debug

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>polaris-router-grayrelease-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>router-grayrelease-frontend</artifactId>
<dependencies>
<dependency>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
<groupId>com.tencent.cloud</groupId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,15 @@
############################################################
# Dockerfile to build polaris-java quickstart example provider
# 1. You need to build the binary from the source code,
# use `mvn clean install` to build the binary.
# 2. You need to copy the quickstart-example-provider-*.jar to this directory
# 3. Replace the ${VERSION} to the real version of the project
############################################################
FROM java:8
ADD router-grayrelease-frontend-1.5.0-Hoxton.SR9-SNAPSHOT.jar /root/app.jar
ENTRYPOINT ["java","-jar","/root/app.jar"]

@ -0,0 +1,49 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.grayrelease.front;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/router/gray")
public class FrontController {
@Autowired
private Environment environment;
@Autowired
private RouterService routerService;
/**
* Get information of callee.
* @return information of callee
*/
@GetMapping("/rest")
public String rest() {
String env = System.getenv("SCT_METADATA_CONTENT_env");
String appName = environment.getProperty("spring.application.name");
String curName = appName + "[" + env + "]";
String resp = routerService.rest();
return curName + " -> " + resp;
}
}

@ -0,0 +1,33 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.grayrelease.front;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class GrayReleaseFrontApplication {
public static void main(String[] args) {
SpringApplication.run(GrayReleaseFrontApplication.class, args);
}
}

@ -0,0 +1,35 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.grayrelease.front;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Router callee feign client.
*
* @author lepdou 2022-04-06
*/
@FeignClient("gray-release-middle")
public interface RouterService {
@GetMapping("/router/gray/rest")
String rest();
}

@ -0,0 +1,15 @@
server:
session-timeout: 1800
port: 59000
spring:
application:
name: gray-release-front
cloud:
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
logging:
level:
org.springframework.cloud.gateway: info
com.tencent.cloud.polaris: debug

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>polaris-router-grayrelease-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>router-grayrelease-gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
<groupId>com.tencent.cloud</groupId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,15 @@
############################################################
# Dockerfile to build polaris-java quickstart example provider
# 1. You need to build the binary from the source code,
# use `mvn clean install` to build the binary.
# 2. You need to copy the quickstart-example-provider-*.jar to this directory
# 3. Replace the ${VERSION} to the real version of the project
############################################################
FROM java:8
ADD router-grayrelease-gateway-1.5.0-Hoxton.SR9-SNAPSHOT.jar /root/app.jar
ENTRYPOINT ["java","-jar","/root/app.jar"]

@ -0,0 +1,49 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.grayrelease.gateway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/router/gray")
public class GatewayController {
@Autowired
private Environment environment;
@Autowired
private RouterService routerService;
/**
* Get information of callee.
* @return information of callee
*/
@GetMapping("/route_rule")
public String routeRule(@RequestHeader("uid") int userId) {
String appName = environment.getProperty("spring.application.name");
String resp = routerService.restByUser(userId);
return appName + " -> " + resp;
}
}

@ -0,0 +1,33 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.grayrelease.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class GrayReleaseGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GrayReleaseGatewayApplication.class, args);
}
}

@ -0,0 +1,35 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.grayrelease.gateway;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
/**
* Router callee feign client.
*
* @author lepdou 2022-04-06
*/
@FeignClient("gray-release-front")
public interface RouterService {
@GetMapping("/router/gray/rest")
String restByUser(@RequestHeader("uid") int user);
}

@ -0,0 +1,15 @@
server:
session-timeout: 1800
port: 59100
spring:
application:
name: gray-release-gateway
cloud:
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
logging:
level:
org.springframework.cloud.gateway: info
com.tencent.cloud.polaris: debug

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>polaris-router-grayrelease-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>router-grayrelease-middle</artifactId>
<dependencies>
<dependency>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
<groupId>com.tencent.cloud</groupId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,15 @@
############################################################
# Dockerfile to build polaris-java quickstart example provider
# 1. You need to build the binary from the source code,
# use `mvn clean install` to build the binary.
# 2. You need to copy the quickstart-example-provider-*.jar to this directory
# 3. Replace the ${VERSION} to the real version of the project
############################################################
FROM java:8
ADD router-grayrelease-middle-1.5.0-Hoxton.SR9-SNAPSHOT.jar /root/app.jar
ENTRYPOINT ["java","-jar","/root/app.jar"]

@ -0,0 +1,33 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.grayrelease.middle;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class GrayReleaseMiddleApplication {
public static void main(String[] args) {
SpringApplication.run(GrayReleaseMiddleApplication.class, args);
}
}

@ -0,0 +1,49 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.grayrelease.middle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/router/gray")
public class MiddleController {
@Autowired
private Environment environment;
@Autowired
private RouterService routerService;
/**
* Get information of callee.
* @return information of callee
*/
@GetMapping("/rest")
public String rest() {
String env = System.getenv("SCT_METADATA_CONTENT_env");
String appName = environment.getProperty("spring.application.name");
String curName = appName + "[" + env + "]";
String resp = routerService.rest();
return curName + " -> " + resp;
}
}

@ -0,0 +1,35 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.router.grayrelease.middle;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Router callee feign client.
*
* @author lepdou 2022-04-06
*/
@FeignClient("gray-release-back")
public interface RouterService {
@GetMapping("/router/gray/rest")
String rest();
}

@ -0,0 +1,15 @@
server:
session-timeout: 1800
port: 59001
spring:
application:
name: gray-release-middle
cloud:
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
logging:
level:
org.springframework.cloud.gateway: info
com.tencent.cloud.polaris: debug

@ -23,6 +23,7 @@
<module>polaris-config-example</module>
<module>polaris-router-example</module>
<module>metadata-transfer-example</module>
<module>polaris-router-grayrelease-example</module>
</modules>
<properties>

@ -0,0 +1,65 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.tencent.cloud.polaris.loadbalancer;
import java.util.List;
import java.util.stream.Collectors;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.pojo.DefaultServiceInstances;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.api.pojo.ServiceKey;
import org.apache.commons.collections.CollectionUtils;
import reactor.core.publisher.Flux;
import org.springframework.cloud.client.ServiceInstance;
/**
* load balancer utils.
*
*@author lepdou 2022-05-17
*/
public class LoadBalancerUtils {
public static ServiceInstances transferServersToServiceInstances(Flux<List<ServiceInstance>> servers) {
List<Instance> instances = servers.toStream().flatMap(List::stream).map(serviceInstance -> {
DefaultInstance instance = new DefaultInstance();
instance.setNamespace(MetadataContext.LOCAL_NAMESPACE);
instance.setService(serviceInstance.getServiceId());
instance.setProtocol(serviceInstance.getScheme());
instance.setId(serviceInstance.getInstanceId());
instance.setHost(serviceInstance.getHost());
instance.setPort(serviceInstance.getPort());
instance.setWeight(100);
instance.setMetadata(serviceInstance.getMetadata());
return instance;
}).collect(Collectors.toList());
String serviceName = null;
if (CollectionUtils.isNotEmpty(instances)) {
serviceName = instances.get(0).getService();
}
ServiceKey serviceKey = new ServiceKey(MetadataContext.LOCAL_NAMESPACE, serviceName);
return new DefaultServiceInstances(serviceKey, instances);
}
}

@ -43,7 +43,6 @@ import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
@ -52,7 +51,7 @@ import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
public class PolarisLoadBalancer extends RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
public class PolarisLoadBalancer extends RoundRobinLoadBalancer {
private static final Logger log = LoggerFactory.getLogger(PolarisLoadBalancer.class);
@ -88,7 +87,7 @@ public class PolarisLoadBalancer extends RoundRobinLoadBalancer implements React
}
ServiceInstanceListSupplier supplier = supplierObjectProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get().next().map(this::getInstanceResponse);
return supplier.get(request).next().map(this::getInstanceResponse);
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> serviceInstances) {

@ -23,7 +23,6 @@ import java.util.List;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.pojo.PolarisServiceInstance;
import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.pojo.Instance;
import org.apache.commons.lang.StringUtils;
import reactor.core.publisher.Flux;
@ -64,7 +63,7 @@ public class PolarisServiceInstanceListSupplier extends DelegatingServiceInstanc
throw new IllegalStateException(
"PolarisRoutingLoadBalancer only Server with AppName or ServiceIdForDiscovery attribute");
}
List<Instance> instances = new ArrayList<>(allServers.size());
List<ServiceInstance> serviceInstances = new ArrayList<>(allServers.size());
for (ServiceInstance server : allServers) {
DefaultInstance instance = new DefaultInstance();
instance.setNamespace(MetadataContext.LOCAL_NAMESPACE);
@ -75,14 +74,9 @@ public class PolarisServiceInstanceListSupplier extends DelegatingServiceInstanc
instance.setPort(server.getPort());
instance.setWeight(100);
instance.setMetadata(server.getMetadata());
instances.add(instance);
serviceInstances.add(new PolarisServiceInstance(instance));
}
List<ServiceInstance> filteredInstances = new ArrayList<>();
for (Instance instance : instances) {
filteredInstances.add(new PolarisServiceInstance(instance));
}
return filteredInstances;
return serviceInstances;
}
}

@ -26,6 +26,7 @@ import com.tencent.polaris.router.api.core.RouterAPI;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration;
import org.springframework.context.annotation.Bean;
@ -38,6 +39,7 @@ import org.springframework.context.annotation.Configuration;
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnDiscoveryEnabled
@ConditionalOnPolarisEnabled
@ConditionalOnProperty(value = "spring.cloud.polaris.loadbalancer.enabled", matchIfMissing = true)
@AutoConfigureAfter(LoadBalancerAutoConfiguration.class)

@ -53,8 +53,11 @@ public class PolarisLoadBalancerClientConfiguration {
*/
private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;
private final static String STRATEGY_WEIGHT = "polarisWeighted";
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.cloud.polaris.loadbalancer.strategy", havingValue = STRATEGY_WEIGHT)
public ReactorLoadBalancer<ServiceInstance> polarisLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory, PolarisLoadBalancerProperties loadBalancerProperties,
RouterAPI routerAPI) {
@ -70,6 +73,7 @@ public class PolarisLoadBalancerClientConfiguration {
static class PolarisReactiveSupportConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(ReactiveDiscoveryClient.class)
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "polaris")
public ServiceInstanceListSupplier polarisRouterDiscoveryClientServiceInstanceListSupplier(
@ -86,6 +90,7 @@ public class PolarisLoadBalancerClientConfiguration {
static class PolarisBlockingSupportConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(DiscoveryClient.class)
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "polaris")
public ServiceInstanceListSupplier polarisRouterDiscoveryClientServiceInstanceListSupplier(

Loading…
Cancel
Save