diff --git a/CHANGELOG.md b/CHANGELOG.md index fcb751959..772273f0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,3 +26,7 @@ - [Use jdk constants instead of magic variables](https://github.com/Tencent/spring-cloud-tencent/pull/362) - [Refator JacksonUtils and JacksonUtilsTest](https://github.com/Tencent/spring-cloud-tencent/pull/366) - [Feature: support actuator for sct core components](https://github.com/Tencent/spring-cloud-tencent/pull/370) +- [docs: Fix javadoc
error](https://github.com/Tencent/spring-cloud-tencent/pull/375) +- [docs: Update Readme.md](https://github.com/Tencent/spring-cloud-tencent/pull/381) +- [docs:optimize example](https://github.com/Tencent/spring-cloud-tencent/pull/386) +- [Feature: support spring cloud gateway routers](https://github.com/Tencent/spring-cloud-tencent/pull/388) diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java index 6acbf2336..142b41bed 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java @@ -38,7 +38,7 @@ public @interface PolarisConfigKVFileChangeListener { /** * The keys interested in the listener, will only be notified if any of the interested keys is changed. - *
+ *

* If neither of {@code interestedKeys} and {@code interestedKeyPrefixes} is specified then the {@code listener} will be notified when any key is changed. * @return interested keys in the listener */ @@ -49,7 +49,7 @@ public @interface PolarisConfigKVFileChangeListener { * The prefixes will simply be used to determine whether the {@code listener} should be notified or not using {@code changedKey.startsWith(prefix)}. * e.g. "spring." means that {@code listener} is interested in keys that starts with "spring.", such as "spring.banner", "spring.jpa", etc. * and "application" means that {@code listener} is interested in keys that starts with "application", such as "applicationName", "application.port", etc. - *
+ *

* If neither of {@code interestedKeys} and {@code interestedKeyPrefixes} is specified then the {@code listener} will be notified when whatever key is changed. * @return interested key-prefixed in the listener */ diff --git a/spring-cloud-starter-tencent-polaris-router/pom.xml b/spring-cloud-starter-tencent-polaris-router/pom.xml index 147451b5f..e21ca43f8 100644 --- a/spring-cloud-starter-tencent-polaris-router/pom.xml +++ b/spring-cloud-starter-tencent-polaris-router/pom.xml @@ -52,6 +52,20 @@ true + + + org.springframework.cloud + spring-cloud-gateway-server + true + + + + org.springframework.boot + spring-boot-starter-webflux + true + + + org.springframework.boot spring-boot-starter-test diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/FeignAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/FeignAutoConfiguration.java new file mode 100644 index 000000000..89cea90d5 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/FeignAutoConfiguration.java @@ -0,0 +1,44 @@ +/* + * 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.router.RouterRuleLabelResolver; +import com.tencent.cloud.polaris.router.feign.RouterLabelFeignInterceptor; +import com.tencent.cloud.polaris.router.spi.RouterLabelResolver; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.lang.Nullable; + +@Configuration +@ConditionalOnClass(name = {"feign.RequestInterceptor"}) +public class FeignAutoConfiguration { + + @Bean + public RouterLabelFeignInterceptor routerLabelInterceptor(@Nullable List routerLabelResolvers, + MetadataLocalProperties metadataLocalProperties, + RouterRuleLabelResolver routerRuleLabelResolver) { + return new RouterLabelFeignInterceptor(routerLabelResolvers, metadataLocalProperties, routerRuleLabelResolver); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java index a9e1a54df..ef765fbdd 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java @@ -18,21 +18,17 @@ 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 com.tencent.cloud.polaris.router.scg.PolarisLoadBalancerClientBeanPostProcessor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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; @@ -47,16 +43,17 @@ import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE; public class RouterAutoConfiguration { @Bean - public RouterLabelFeignInterceptor routerLabelInterceptor(@Nullable List routerLabelResolvers, - MetadataLocalProperties metadataLocalProperties, - RouterRuleLabelResolver routerRuleLabelResolver) { - return new RouterLabelFeignInterceptor(routerLabelResolvers, metadataLocalProperties, routerRuleLabelResolver); + @Order(HIGHEST_PRECEDENCE) + @ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor") + public PolarisLoadBalancerBeanPostProcessor polarisLoadBalancerBeanPostProcessor() { + return new PolarisLoadBalancerBeanPostProcessor(); } @Bean @Order(HIGHEST_PRECEDENCE) - public PolarisLoadBalancerBeanPostProcessor polarisLoadBalancerBeanPostProcessor() { - return new PolarisLoadBalancerBeanPostProcessor(); + @ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter") + public PolarisLoadBalancerClientBeanPostProcessor polarisLoadBalancerClientBeanPostProcessor() { + return new PolarisLoadBalancerClientBeanPostProcessor(); } @Bean diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientBeanPostProcessor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientBeanPostProcessor.java new file mode 100644 index 000000000..ca45878da --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientBeanPostProcessor.java @@ -0,0 +1,68 @@ +/* + * 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.scg; + +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.LoadBalancerProperties; +import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties; +import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; + +/** + * Replaced ReactiveLoadBalancerClientFilter with PolarisReactiveLoadBalancerClientFilter during creating bean phase. + *@author lepdou 2022-06-20 + */ +public class PolarisLoadBalancerClientBeanPostProcessor 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 { + // Support spring cloud gateway router. + // Replaces the default LoadBalancerClientFilter implementation and returns a custom PolarisLoadBalancerClientFilter + if (bean instanceof ReactiveLoadBalancerClientFilter) { + LoadBalancerClientFactory loadBalancerClientFactory = this.factory.getBean(LoadBalancerClientFactory.class); + GatewayLoadBalancerProperties gatewayLoadBalancerProperties = this.factory.getBean(GatewayLoadBalancerProperties.class); + LoadBalancerProperties loadBalancerProperties = this.factory.getBean(LoadBalancerProperties.class); + List routerLabelResolvers = BeanFactoryUtils.getBeans(factory, RouterLabelResolver.class); + MetadataLocalProperties metadataLocalProperties = this.factory.getBean(MetadataLocalProperties.class); + RouterRuleLabelResolver routerRuleLabelResolver = this.factory.getBean(RouterRuleLabelResolver.class); + + return new PolarisReactiveLoadBalancerClientFilter( + loadBalancerClientFactory, gatewayLoadBalancerProperties, loadBalancerProperties, + metadataLocalProperties, routerRuleLabelResolver, routerLabelResolvers); + } + return bean; + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilter.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilter.java new file mode 100644 index 000000000..b7cb97807 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilter.java @@ -0,0 +1,264 @@ +/* + * 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.scg; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +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 reactor.core.publisher.Mono; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.CompletionContext; +import org.springframework.cloud.client.loadbalancer.DefaultRequest; +import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle; +import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator; +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools; +import org.springframework.cloud.client.loadbalancer.Request; +import org.springframework.cloud.client.loadbalancer.RequestData; +import org.springframework.cloud.client.loadbalancer.RequestDataContext; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.client.loadbalancer.ResponseData; +import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter; +import org.springframework.cloud.gateway.support.DelegatingServiceInstance; +import org.springframework.cloud.gateway.support.NotFoundException; +import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; +import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.CollectionUtils; +import org.springframework.web.server.ServerWebExchange; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; + +/** + * ReactiveLoadBalancerClientFilter does not have the ability to pass route labels, so it is replaced with PolarisReactiveLoadBalancerClientFilter. + * The passed route labels are used in {@link com.tencent.cloud.polaris.router.PolarisRouterServiceInstanceListSupplier}. + *@author lepdou 2022-06-20 + */ +public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter { + private static final Logger log = LoggerFactory.getLogger(PolarisReactiveLoadBalancerClientFilter.class); + + private final LoadBalancerClientFactory clientFactory; + private final GatewayLoadBalancerProperties gatewayLoadBalancerProperties; + private final LoadBalancerProperties loadBalancerProperties; + private final MetadataLocalProperties metadataLocalProperties; + private final RouterRuleLabelResolver routerRuleLabelResolver; + private final List routerLabelResolvers; + + public PolarisReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, + GatewayLoadBalancerProperties gatewayLoadBalancerProperties, + LoadBalancerProperties loadBalancerProperties, + MetadataLocalProperties metadataLocalProperties, + RouterRuleLabelResolver routerRuleLabelResolver, + List routerLabelResolvers) { + super(clientFactory, gatewayLoadBalancerProperties, loadBalancerProperties); + + this.clientFactory = clientFactory; + this.gatewayLoadBalancerProperties = gatewayLoadBalancerProperties; + this.loadBalancerProperties = loadBalancerProperties; + this.metadataLocalProperties = metadataLocalProperties; + this.routerRuleLabelResolver = routerRuleLabelResolver; + this.routerLabelResolvers = routerLabelResolvers; + } + + /** + * Copied from ReactiveLoadBalancerClientFilter, and create new RequestData for passing router labels. + */ + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); + String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR); + if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) { + return chain.filter(exchange); + } + // preserve the original url + addOriginalRequestUrl(exchange, url); + + if (log.isTraceEnabled()) { + log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url); + } + + URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); + String serviceId = requestUri.getHost(); + Set supportedLifecycleProcessors = LoadBalancerLifecycleValidator + .getSupportedLifecycleProcessors(clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class), + RequestDataContext.class, ResponseData.class, ServiceInstance.class); + + // Pass route tags through http headers + HttpHeaders routerHttpHeaders = genRouterHttpHeaders(exchange, serviceId); + + ServerHttpRequest request = exchange.getRequest(); + RequestData requestData = new RequestData(request.getMethod(), request.getURI(), routerHttpHeaders, + new HttpHeaders(), new HashMap<>()); + DefaultRequest lbRequest = new DefaultRequest<>(new RequestDataContext( + requestData, getHint(serviceId, loadBalancerProperties.getHint()))); + + return choose(lbRequest, serviceId, supportedLifecycleProcessors).doOnNext(response -> { + + if (!response.hasServer()) { + supportedLifecycleProcessors.forEach(lifecycle -> lifecycle + .onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, response))); + throw NotFoundException.create(gatewayLoadBalancerProperties.isUse404(), + "Unable to find instance for " + url.getHost()); + } + + ServiceInstance retrievedInstance = response.getServer(); + + URI uri = exchange.getRequest().getURI(); + + // if the `lb:` mechanism was used, use `` as the default, + // if the loadbalancer doesn't provide one. + String overrideScheme = retrievedInstance.isSecure() ? "https" : "http"; + if (schemePrefix != null) { + overrideScheme = url.getScheme(); + } + + DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(retrievedInstance, + overrideScheme); + + URI requestUrl = reconstructURI(serviceInstance, uri); + + if (log.isTraceEnabled()) { + log.trace("LoadBalancerClientFilter url chosen: " + requestUrl); + } + exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); + exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, response); + supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, response)); + }).then(chain.filter(exchange)) + .doOnError(throwable -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle + .onComplete(new CompletionContext( + CompletionContext.Status.FAILED, throwable, lbRequest, + exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR))))) + .doOnSuccess(aVoid -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle + .onComplete(new CompletionContext( + CompletionContext.Status.SUCCESS, lbRequest, + exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR), + new ResponseData(exchange.getResponse(), new RequestData(exchange.getRequest())))))); + } + + protected URI reconstructURI(ServiceInstance serviceInstance, URI original) { + return LoadBalancerUriTools.reconstructURI(serviceInstance, original); + } + + private Mono> choose(Request lbRequest, String serviceId, + Set supportedLifecycleProcessors) { + ReactorLoadBalancer loadBalancer = this.clientFactory.getInstance(serviceId, + ReactorServiceInstanceLoadBalancer.class); + if (loadBalancer == null) { + throw new NotFoundException("No loadbalancer available for " + serviceId); + } + supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest)); + return loadBalancer.choose(lbRequest); + } + + // no actual used + private String getHint(String serviceId, Map hints) { + String defaultHint = hints.getOrDefault("default", "default"); + String hintPropertyValue = hints.get(serviceId); + return hintPropertyValue != null ? hintPropertyValue : defaultHint; + } + + // In order to be consistent with feign and restTemplate, + // the router label is passed through the http header uniformly instead of the original hint mechanism. + HttpHeaders genRouterHttpHeaders(ServerWebExchange exchange, String peerServiceName) { + HttpHeaders headers = new HttpHeaders(); + headers.add(RouterConstants.ROUTER_LABEL_HEADER, genRouterHint(exchange, peerServiceName)); + return headers; + } + + private String genRouterHint(ServerWebExchange exchange, String peerServiceName) { + Map routerLabels = genRouterLabels(exchange, peerServiceName); + String encodedLabelsContent; + try { + encodedLabelsContent = URLEncoder.encode(JacksonUtils.serialize2Json(routerLabels), StandardCharsets.UTF_8.name()); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException("unsupported charset exception " + StandardCharsets.UTF_8.name()); + } + return encodedLabelsContent; + } + + private Map genRouterLabels(ServerWebExchange exchange, String peerServiceName) { + // local service labels + Map labels = new HashMap<>(metadataLocalProperties.getContent()); + + // labels from rule expression + Map ruleExpressionLabels = getExpressionLabels(exchange, peerServiceName); + if (!CollectionUtils.isEmpty(ruleExpressionLabels)) { + labels.putAll(ruleExpressionLabels); + } + + // labels from request + if (!CollectionUtils.isEmpty(routerLabelResolvers)) { + routerLabelResolvers.forEach(resolver -> { + try { + Map customResolvedLabels = resolver.resolve(exchange); + if (!CollectionUtils.isEmpty(customResolvedLabels)) { + labels.putAll(customResolvedLabels); + } + } + catch (Throwable t) { + log.error("[SCT][Router] revoke RouterLabelResolver occur some exception. ", t); + } + }); + } + + // labels from downstream + Map transitiveLabels = MetadataContextHolder.get() + .getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); + labels.putAll(transitiveLabels); + + return labels; + } + + private Map getExpressionLabels(ServerWebExchange exchange, String peerServiceName) { + Set labelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE, + MetadataContext.LOCAL_SERVICE, peerServiceName); + + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + return ExpressionLabelUtils.resolve(exchange, labelKeys); + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterLabelResolver.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterLabelResolver.java index 99a6b5bf3..d885581ca 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterLabelResolver.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterLabelResolver.java @@ -25,6 +25,7 @@ import feign.RequestTemplate; import org.springframework.core.Ordered; import org.springframework.http.HttpRequest; +import org.springframework.web.server.ServerWebExchange; /** * The spi for resolving labels from request. @@ -51,4 +52,13 @@ public interface RouterLabelResolver extends Ordered { default Map resolve(HttpRequest request, byte[] body) { return Collections.emptyMap(); } + + /** + * resolve labels from server web exchange. + * @param exchange the server web exchange. + * @return resolved labels + */ + default Map resolve(ServerWebExchange exchange) { + return Collections.emptyMap(); + } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/resources/META-INF/spring.factories b/spring-cloud-starter-tencent-polaris-router/src/main/resources/META-INF/spring.factories index d33dcea7f..3b20deb56 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-starter-tencent-polaris-router/src/main/resources/META-INF/spring.factories @@ -1,2 +1,3 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - com.tencent.cloud.polaris.router.config.RouterAutoConfiguration + com.tencent.cloud.polaris.router.config.RouterAutoConfiguration,\ + com.tencent.cloud.polaris.router.config.FeignAutoConfiguration diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientBeanPostProcessorTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientBeanPostProcessorTest.java new file mode 100644 index 000000000..35e91ca73 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientBeanPostProcessorTest.java @@ -0,0 +1,100 @@ +/* + * 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.scg; + +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.LoadBalancerProperties; +import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties; +import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; + +import static org.mockito.Mockito.when; + + +/** + * Test for ${@link PolarisLoadBalancerClientBeanPostProcessor} + *@author lepdou 2022-07-04 + */ +@RunWith(MockitoJUnitRunner.class) +public class PolarisLoadBalancerClientBeanPostProcessorTest { + + @Mock + private BeanFactory beanFactory; + @Mock + private LoadBalancerClientFactory loadBalancerClientFactory; + @Mock + private GatewayLoadBalancerProperties gatewayLoadBalancerProperties; + @Mock + private LoadBalancerProperties loadBalancerProperties; + @Mock + private MetadataLocalProperties metadataLocalProperties; + @Mock + private RouterRuleLabelResolver routerRuleLabelResolver; + + @Test + public void testWrapReactiveLoadBalancerClientFilter() { + when(beanFactory.getBean(LoadBalancerClientFactory.class)).thenReturn(loadBalancerClientFactory); + when(beanFactory.getBean(GatewayLoadBalancerProperties.class)).thenReturn(gatewayLoadBalancerProperties); + when(beanFactory.getBean(LoadBalancerProperties.class)).thenReturn(loadBalancerProperties); + when(beanFactory.getBean(MetadataLocalProperties.class)).thenReturn(metadataLocalProperties); + when(beanFactory.getBean(RouterRuleLabelResolver.class)).thenReturn(routerRuleLabelResolver); + + try (MockedStatic mockedBeanFactoryUtils = Mockito.mockStatic(BeanFactoryUtils.class)) { + mockedBeanFactoryUtils.when(() -> BeanFactoryUtils.getBeans(beanFactory, RouterLabelResolver.class)) + .thenReturn(null); + + ReactiveLoadBalancerClientFilter reactiveLoadBalancerClientFilter = new ReactiveLoadBalancerClientFilter( + loadBalancerClientFactory, gatewayLoadBalancerProperties, loadBalancerProperties); + + PolarisLoadBalancerClientBeanPostProcessor processor = new PolarisLoadBalancerClientBeanPostProcessor(); + processor.setBeanFactory(beanFactory); + + Object bean = processor.postProcessBeforeInitialization(reactiveLoadBalancerClientFilter, ""); + + Assert.assertTrue(bean instanceof PolarisReactiveLoadBalancerClientFilter); + } + } + + @Test + public void testNotWrapLoadBalancerInterceptor() { + PolarisLoadBalancerClientBeanPostProcessor processor = new PolarisLoadBalancerClientBeanPostProcessor(); + processor.setBeanFactory(beanFactory); + + OtherBean otherBean = new OtherBean(); + Object bean = processor.postProcessBeforeInitialization(otherBean, ""); + Assert.assertFalse(bean instanceof PolarisReactiveLoadBalancerClientFilter); + Assert.assertTrue(bean instanceof OtherBean); + } + + static class OtherBean { + + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilterTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilterTest.java new file mode 100644 index 000000000..1ed4813fd --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilterTest.java @@ -0,0 +1,145 @@ +/* + * 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.scg; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +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.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.util.CollectionUtils; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +/** + * Test for ${@link PolarisReactiveLoadBalancerClientFilter} + *@author lepdou 2022-07-04 + */ +@RunWith(MockitoJUnitRunner.class) +public class PolarisReactiveLoadBalancerClientFilterTest { + + private static final String callerService = "callerService"; + private static final String calleeService = "calleeService"; + private static MockedStatic mockedApplicationContextAwareUtils; + private static MockedStatic mockedMetadataContextHolder; + + @Mock + private MetadataLocalProperties metadataLocalProperties; + @Mock + private RouterLabelResolver routerLabelResolver; + @Mock + private RouterRuleLabelResolver routerRuleLabelResolver; + @Mock + private LoadBalancerClientFactory loadBalancerClientFactory; + @Mock + private GatewayLoadBalancerProperties gatewayLoadBalancerProperties; + @Mock + private LoadBalancerProperties loadBalancerProperties; + + @BeforeClass + public static void beforeClass() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn(callerService); + + MetadataContext metadataContext = Mockito.mock(MetadataContext.class); + + // mock transitive metadata + Map transitiveLabels = new HashMap<>(); + transitiveLabels.put("t1", "v1"); + transitiveLabels.put("t2", "v2"); + when(metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)).thenReturn(transitiveLabels); + + mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class); + mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext); + } + + @AfterClass + public static void afterClass() { + mockedApplicationContextAwareUtils.close(); + mockedMetadataContextHolder.close(); + } + + @Test + public void testGenRouterHttpHeaders() throws UnsupportedEncodingException { + PolarisReactiveLoadBalancerClientFilter filter = new PolarisReactiveLoadBalancerClientFilter(loadBalancerClientFactory, + gatewayLoadBalancerProperties, loadBalancerProperties, metadataLocalProperties, routerRuleLabelResolver, + Lists.newArrayList(routerLabelResolver)); + + Map localMetadata = new HashMap<>(); + localMetadata.put("env", "blue"); + when(metadataLocalProperties.getContent()).thenReturn(localMetadata); + + Set expressionLabelKeys = Sets.newHashSet("${http.header.k1}", "${http.query.userid}"); + when(routerRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString(), anyString())).thenReturn(expressionLabelKeys); + + MockServerHttpRequest request = MockServerHttpRequest.get("/" + calleeService + "/users") + .header("k1", "v1") + .queryParam("userid", "zhangsan") + .build(); + MockServerWebExchange webExchange = new MockServerWebExchange.Builder(request).build(); + + Map customMetadata = new HashMap<>(); + customMetadata.put("k2", "v2"); + when(routerLabelResolver.resolve(webExchange)).thenReturn(customMetadata); + + HttpHeaders headers = filter.genRouterHttpHeaders(webExchange, calleeService); + + Assert.assertNotNull(headers); + List routerHeaders = headers.get(RouterConstants.ROUTER_LABEL_HEADER); + Assert.assertFalse(CollectionUtils.isEmpty(routerHeaders)); + + Map routerLabels = JacksonUtils.deserialize2Map(URLDecoder.decode(routerHeaders.get(0), StandardCharsets.UTF_8.name())); + Assert.assertEquals("v1", routerLabels.get("${http.header.k1}")); + Assert.assertEquals("zhangsan", routerLabels.get("${http.query.userid}")); + Assert.assertEquals("blue", routerLabels.get("env")); + Assert.assertEquals("v1", routerLabels.get("t1")); + Assert.assertEquals("v2", routerLabels.get("t2")); + } +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceA.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceA.java index 1c96db6c9..3db7df8f3 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceA.java +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceA.java @@ -35,7 +35,7 @@ import org.springframework.web.client.RestTemplate; public class ServiceA { public static void main(String[] args) { - SpringApplication.run(ServiceA.class); + SpringApplication.run(ServiceA.class, args); } @Bean @@ -43,5 +43,4 @@ public class ServiceA { public RestTemplate restTemplate() { return new RestTemplate(); } - } diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java index 3ba9901a6..73fc55bd3 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java @@ -63,5 +63,4 @@ public class ServiceAController { .getForEntity("http://polaris-circuitbreaker-example-b/example/service/b/info", String.class); return entity.getBody(); } - } diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceB.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceB.java index 7354da537..c8c32bf24 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceB.java +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceB.java @@ -29,7 +29,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; public class ServiceB { public static void main(String[] args) { - SpringApplication.run(ServiceB.class); + SpringApplication.run(ServiceB.class, args); } - } diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java index 67540c2c9..99b8410d8 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java @@ -18,7 +18,6 @@ package com.tencent.cloud.polaris.circuitbreaker.example; -import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -32,21 +31,12 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/example/service/b") public class ServiceBController { - @Value("${is-throw-runtime-exception:#{false}}") - private boolean isThrowRuntimeException; - /** * Get service information. * @return service information */ @GetMapping("/info") public String info() { - if (isThrowRuntimeException) { - throw new RuntimeException("failed for call my service"); - } - else { - return "hello world ! I'm a service B1"; - } + return "hello world ! I'm a service B1"; } - } diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/resources/bootstrap.yml index 4aba50500..3805dfa5e 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/resources/bootstrap.yml @@ -8,4 +8,3 @@ spring: address: grpc://183.47.111.80:8091 namespace: default enabled: true -is-throw-runtime-exception: false diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceB2.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceB2.java index 4aacc0d22..fb6bdc687 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceB2.java +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceB2.java @@ -30,7 +30,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; public class ServiceB2 { public static void main(String[] args) { - SpringApplication.run(ServiceB2.class); + SpringApplication.run(ServiceB2.class, args); } - } diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceBController.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceBController.java index 7e4bb9b4c..83dc45303 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceBController.java +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceBController.java @@ -18,9 +18,10 @@ package com.tencent.cloud.polaris.ciruitbreaker.example; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; /** @@ -32,21 +33,13 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/example/service/b") public class ServiceBController { - @Value("${is-throw-runtime-exception:#{false}}") - private boolean isThrowRuntimeException; - /** * Get service information. * @return service information */ @GetMapping("/info") + @ResponseStatus(value = HttpStatus.BAD_GATEWAY, reason = "failed for call my service") public String info() { - if (isThrowRuntimeException) { - throw new RuntimeException("failed for call my service"); - } - else { - return "hello world ! I'm a service B2"; - } + return "failed for call service B2"; } - } diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/resources/bootstrap.yml index b150a5424..f3720f09c 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/resources/bootstrap.yml @@ -8,4 +8,3 @@ spring: address: grpc://183.47.111.80:8091 namespace: default enabled: true -is-throw-runtime-exception: true diff --git a/spring-cloud-tencent-examples/polaris-config-example/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-config-example/src/main/resources/bootstrap.yml index 84e44d4aa..1f257a787 100644 --- a/spring-cloud-tencent-examples/polaris-config-example/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-config-example/src/main/resources/bootstrap.yml @@ -17,4 +17,4 @@ management: web: exposure: include: - - polaris-config \ No newline at end of file + - polaris-config diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/README.md b/spring-cloud-tencent-examples/polaris-gateway-example/README.md index 43bd680f6..b1bb35671 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/README.md +++ b/spring-cloud-tencent-examples/polaris-gateway-example/README.md @@ -2,7 +2,7 @@ ## Example Introduction -This example shows how to use ```spring-cloud-tencent-polaris-gateway`` in Spring Cloud project for its features. +This example shows how to use ```spring-cloud-tencent-polaris-gateway``` in Spring Cloud project for its features. This example contains ```gateway-zuul-service```, ```gateway-scg-service``` and ```gateway-callee-service```. ```gateway-zuul-service``` and ```gateway-scg-service``` invoke ```gateway-callee-service```. diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/pom.xml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/pom.xml new file mode 100644 index 000000000..842363af9 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/pom.xml @@ -0,0 +1,28 @@ + + + + polaris-gateway-example + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + gateway-callee-service2 + Spring Cloud Starter Tencent Polaris Gateway Callee Example + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-discovery + + + + org.springframework.boot + spring-boot-starter-web + + + + diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/src/main/java/com/tencent/cloud/polaris/gateway/example/callee/GatewayCalleeApplication.java b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/src/main/java/com/tencent/cloud/polaris/gateway/example/callee/GatewayCalleeApplication.java new file mode 100644 index 000000000..4e5e816bd --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/src/main/java/com/tencent/cloud/polaris/gateway/example/callee/GatewayCalleeApplication.java @@ -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.gateway.example.callee; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Gateway callee application. + * + * @author Haotian Zhang + */ +@SpringBootApplication +public class GatewayCalleeApplication { + + public static void main(String[] args) { + SpringApplication.run(GatewayCalleeApplication.class, args); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/src/main/java/com/tencent/cloud/polaris/gateway/example/callee/GatewayCalleeController.java b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/src/main/java/com/tencent/cloud/polaris/gateway/example/callee/GatewayCalleeController.java new file mode 100644 index 000000000..eae571921 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/src/main/java/com/tencent/cloud/polaris/gateway/example/callee/GatewayCalleeController.java @@ -0,0 +1,71 @@ +/* + * 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.gateway.example.callee; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; + +import com.tencent.cloud.common.constant.MetadataConstant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Gateway callee controller. + * + * @author Haotian Zhang + */ +@RestController +@RequestMapping("/gateway/example/callee") +public class GatewayCalleeController { + + private static Logger LOG = LoggerFactory.getLogger(GatewayCalleeController.class); + + @Value("${server.port:0}") + private int port; + + /** + * Get information of callee. + * @return information of callee + */ + @RequestMapping("/info") + public String info() { + LOG.info("Gateway Example Callee [{}] is called.", port); + return String.format("Gateway Example Callee [%s] is called.", port); + } + + /** + * Get metadata in HTTP header. + * + * @param metadataStr metadata string + * @return metadata in HTTP header + * @throws UnsupportedEncodingException encoding exception + */ + @RequestMapping("/echo") + public String echoHeader(@RequestHeader(MetadataConstant.HeaderName.CUSTOM_METADATA) String metadataStr) + throws UnsupportedEncodingException { + LOG.info(URLDecoder.decode(metadataStr, StandardCharsets.UTF_8.name())); + return URLDecoder.decode(metadataStr, StandardCharsets.UTF_8.name()); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..f124251c8 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/src/main/resources/bootstrap.yml @@ -0,0 +1,10 @@ +server: + session-timeout: 1800 + port: 48082 +spring: + application: + name: GatewayCalleeService + cloud: + polaris: + address: grpc://183.47.111.80:8091 + namespace: default diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/pom.xml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/pom.xml index 8b8b50bf6..b6643b49d 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/pom.xml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/pom.xml @@ -26,7 +26,7 @@ com.tencent.cloud - spring-cloud-starter-tencent-metadata-transfer + spring-cloud-starter-tencent-polaris-router @@ -39,4 +39,4 @@ spring-cloud-loadbalancer - \ No newline at end of file + diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml index 5d949573b..55f03dfa3 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml @@ -9,14 +9,10 @@ spring: metadata: content: a: 1 - transitive: - - a polaris: address: grpc://183.47.111.80:8091 namespace: default enabled: true - discovery: - service-list-refresh-interval: 1000 gateway: discovery: locator: diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/pom.xml b/spring-cloud-tencent-examples/polaris-gateway-example/pom.xml index 2607823df..e67b8a8dd 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/pom.xml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/pom.xml @@ -17,6 +17,7 @@ gateway-scg-service gateway-callee-service + gateway-callee-service2 @@ -34,4 +35,4 @@ - \ No newline at end of file + diff --git a/spring-cloud-tencent-examples/polaris-router-example/router-caller-service/src/main/java/com/tencent/cloud/polaris/router/example/CustomRouterLabelResolver.java b/spring-cloud-tencent-examples/polaris-router-example/router-caller-service/src/main/java/com/tencent/cloud/polaris/router/example/CustomRouterLabelResolver.java index 815b2baff..bd289abb9 100644 --- a/spring-cloud-tencent-examples/polaris-router-example/router-caller-service/src/main/java/com/tencent/cloud/polaris/router/example/CustomRouterLabelResolver.java +++ b/spring-cloud-tencent-examples/polaris-router-example/router-caller-service/src/main/java/com/tencent/cloud/polaris/router/example/CustomRouterLabelResolver.java @@ -58,7 +58,6 @@ public class CustomRouterLabelResolver implements RouterLabelResolver { return labels; } - @Override public int getOrder() { return 0; diff --git a/spring-cloud-tencent-polaris-loadbalancer/pom.xml b/spring-cloud-tencent-polaris-loadbalancer/pom.xml index cfec39a05..da560c430 100644 --- a/spring-cloud-tencent-polaris-loadbalancer/pom.xml +++ b/spring-cloud-tencent-polaris-loadbalancer/pom.xml @@ -49,6 +49,24 @@ spring-boot-starter-test test + + + org.mockito + mockito-inline + test + + + + org.mockito + mockito-core + test + + + + net.bytebuddy + byte-buddy + test + - \ No newline at end of file + diff --git a/spring-cloud-tencent-polaris-loadbalancer/src/main/java/com/tencent/cloud/polaris/loadbalancer/LoadBalancerUtils.java b/spring-cloud-tencent-polaris-loadbalancer/src/main/java/com/tencent/cloud/polaris/loadbalancer/LoadBalancerUtils.java index 0199d0322..fd4a511d5 100644 --- a/spring-cloud-tencent-polaris-loadbalancer/src/main/java/com/tencent/cloud/polaris/loadbalancer/LoadBalancerUtils.java +++ b/spring-cloud-tencent-polaris-loadbalancer/src/main/java/com/tencent/cloud/polaris/loadbalancer/LoadBalancerUtils.java @@ -18,7 +18,9 @@ package com.tencent.cloud.polaris.loadbalancer; +import java.util.Collections; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import com.tencent.cloud.common.metadata.MetadataContext; diff --git a/spring-cloud-tencent-polaris-loadbalancer/src/test/java/com/tencent/cloud/polaris/loadbalancer/LoadBalancerUtilsTest.java b/spring-cloud-tencent-polaris-loadbalancer/src/test/java/com/tencent/cloud/polaris/loadbalancer/LoadBalancerUtilsTest.java new file mode 100644 index 000000000..28b62fdd6 --- /dev/null +++ b/spring-cloud-tencent-polaris-loadbalancer/src/test/java/com/tencent/cloud/polaris/loadbalancer/LoadBalancerUtilsTest.java @@ -0,0 +1,106 @@ +/* + * 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.ArrayList; +import java.util.List; + +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.polaris.api.pojo.Instance; +import com.tencent.polaris.api.pojo.ServiceInstances; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import reactor.core.publisher.Flux; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; + +import static org.mockito.ArgumentMatchers.anyString; + +/** + * Test for ${@link LoadBalancerUtils}. + *@author lepdou 2022-07-04 + */ +@RunWith(MockitoJUnitRunner.class) +public class LoadBalancerUtilsTest { + + private static MockedStatic mockedApplicationContextAwareUtils; + private static MockedStatic mockedMetadataContextHolder; + + private static final String testNamespaceAndService = "testNamespaceAndService"; + + @BeforeClass + public static void beforeClass() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn(testNamespaceAndService); + + MetadataContext metadataContext = Mockito.mock(MetadataContext.class); + mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class); + mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext); + } + + @AfterClass + public static void afterClass() { + mockedApplicationContextAwareUtils.close(); + mockedMetadataContextHolder.close(); + } + + @Test + public void testTransferEmptyInstances() { + ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(Flux.empty()); + Assert.assertNotNull(serviceInstances.getInstances()); + Assert.assertEquals(0, serviceInstances.getInstances().size()); + } + + @Test + public void testTransferNotEmptyInstances() { + int instanceSize = 100; + + List instances = new ArrayList<>(); + for (int i = 0; i < instanceSize; i++) { + instances.add(new DefaultServiceInstance("ins" + i, testNamespaceAndService, "127.0.0." + i, + 8080, false)); + } + + ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(Flux.just(instances)); + + Assert.assertNotNull(serviceInstances.getInstances()); + Assert.assertEquals(instanceSize, serviceInstances.getInstances().size()); + + List polarisInstances = serviceInstances.getInstances(); + for (int i = 0; i < instanceSize; i++) { + Instance instance = polarisInstances.get(i); + Assert.assertEquals(testNamespaceAndService, instance.getNamespace()); + Assert.assertEquals(testNamespaceAndService, instance.getService()); + Assert.assertEquals("ins" + i, instance.getId()); + Assert.assertEquals("127.0.0." + i, instance.getHost()); + Assert.assertEquals(8080, instance.getPort()); + Assert.assertEquals(100, instance.getWeight()); + } + } +}