feat:merge features from 1.5.x-Hoxton.SR9. (#250)
parent
a656d9ca7f
commit
ff25ca6975
@ -1,4 +1,5 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
---
|
---
|
||||||
|
|
||||||
- [Add metadata transfer example.](https://github.com/Tencent/spring-cloud-tencent/pull/211)
|
- [Add metadata transfer example.](https://github.com/Tencent/spring-cloud-tencent/pull/210)
|
||||||
|
- [feat:merge features from 1.5.x-Hoxton.SR9.](https://github.com/Tencent/spring-cloud-tencent/pull/250)
|
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* 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.metadata.core;
|
||||||
|
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* resolve custom transitive metadata from request.
|
||||||
|
*@author lepdou 2022-05-20
|
||||||
|
*/
|
||||||
|
public class CustomTransitiveMetadataResolver {
|
||||||
|
|
||||||
|
private static final String TRANSITIVE_HEADER_PREFIX = "X-SCT-Metadata-Transitive-";
|
||||||
|
private static final int TRANSITIVE_HEADER_PREFIX_LENGTH = TRANSITIVE_HEADER_PREFIX.length();
|
||||||
|
|
||||||
|
public static Map<String, String> resolve(ServerWebExchange exchange) {
|
||||||
|
Map<String, String> result = new HashMap<>();
|
||||||
|
|
||||||
|
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||||
|
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(key) &&
|
||||||
|
StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX)
|
||||||
|
&& !CollectionUtils.isEmpty(entry.getValue())) {
|
||||||
|
|
||||||
|
String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH);
|
||||||
|
result.put(sourceKey, entry.getValue().get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> resolve(HttpServletRequest request) {
|
||||||
|
Map<String, String> result = new HashMap<>();
|
||||||
|
|
||||||
|
Enumeration<String> headers = request.getHeaderNames();
|
||||||
|
while (headers.hasMoreElements()) {
|
||||||
|
String key = headers.nextElement();
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(key) &&
|
||||||
|
StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX)
|
||||||
|
&& StringUtils.isNotBlank(request.getHeader(key))) {
|
||||||
|
|
||||||
|
String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH);
|
||||||
|
result.put(sourceKey, request.getHeader(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -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.circuitbreaker;
|
||||||
|
|
||||||
|
import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignBeanPostProcessor;
|
||||||
|
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
|
||||||
|
import com.tencent.polaris.api.core.ConsumerAPI;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link PolarisFeignClientAutoConfiguration}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
public class PolarisFeignClientAutoConfigurationTest {
|
||||||
|
|
||||||
|
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||||
|
.withConfiguration(
|
||||||
|
AutoConfigurations.of(
|
||||||
|
PolarisContextAutoConfiguration.class,
|
||||||
|
PolarisFeignClientAutoConfiguration.class))
|
||||||
|
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultInitialization() {
|
||||||
|
this.contextRunner.run(context -> {
|
||||||
|
assertThat(context).hasSingleBean(ConsumerAPI.class);
|
||||||
|
assertThat(context).hasSingleBean(PolarisFeignBeanPostProcessor.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* 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.circuitbreaker.feign;
|
||||||
|
|
||||||
|
import com.tencent.polaris.api.core.ConsumerAPI;
|
||||||
|
import feign.Client;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
|
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||||
|
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||||
|
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||||
|
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link PolarisFeignBeanPostProcessor}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
public class PolarisFeignBeanPostProcessorTest {
|
||||||
|
|
||||||
|
private PolarisFeignBeanPostProcessor polarisFeignBeanPostProcessor;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
ConsumerAPI consumerAPI = mock(ConsumerAPI.class);
|
||||||
|
|
||||||
|
polarisFeignBeanPostProcessor = new PolarisFeignBeanPostProcessor(consumerAPI);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostProcessBeforeInitialization() {
|
||||||
|
BeanFactory beanFactory = mock(BeanFactory.class);
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
Class<?> clazz = invocation.getArgument(0);
|
||||||
|
if (clazz.equals(BlockingLoadBalancerClient.class)) {
|
||||||
|
return mock(BlockingLoadBalancerClient.class);
|
||||||
|
}
|
||||||
|
if (clazz.equals(LoadBalancerProperties.class)) {
|
||||||
|
return mock(LoadBalancerProperties.class);
|
||||||
|
}
|
||||||
|
if (clazz.equals(LoadBalancerClientFactory.class)) {
|
||||||
|
return mock(LoadBalancerClientFactory.class);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}).when(beanFactory).getBean(any(Class.class));
|
||||||
|
polarisFeignBeanPostProcessor.setBeanFactory(beanFactory);
|
||||||
|
|
||||||
|
// isNeedWrap(bean) == false
|
||||||
|
Object bean1 = new Object();
|
||||||
|
Object bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean1, "bean1");
|
||||||
|
assertThat(bean).isNotInstanceOfAny(
|
||||||
|
PolarisFeignClient.class,
|
||||||
|
PolarisFeignBlockingLoadBalancerClient.class);
|
||||||
|
|
||||||
|
// bean instanceOf Client.class
|
||||||
|
Client bean2 = mock(Client.class);
|
||||||
|
bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean2, "bean2");
|
||||||
|
assertThat(bean).isInstanceOf(PolarisFeignClient.class);
|
||||||
|
|
||||||
|
// bean instanceOf FeignBlockingLoadBalancerClient.class
|
||||||
|
FeignBlockingLoadBalancerClient bean3 = mock(FeignBlockingLoadBalancerClient.class);
|
||||||
|
doReturn(mock(Client.class)).when(bean3).getDelegate();
|
||||||
|
bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean3, "bean3");
|
||||||
|
assertThat(bean).isInstanceOf(PolarisFeignBlockingLoadBalancerClient.class);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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.circuitbreaker.feign;
|
||||||
|
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link PolarisFeignBlockingLoadBalancerClient}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
public class PolarisFeignBlockingLoadBalancerClientTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConstructor() {
|
||||||
|
try {
|
||||||
|
new PolarisFeignBlockingLoadBalancerClient(null, null, null, null);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
Assertions.fail("Exception encountered.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
|
||||||
|
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler;
|
||||||
|
import com.tencent.cloud.polaris.extend.consul.ConsulContextProperties;
|
||||||
|
import com.tencent.polaris.api.core.ConsumerAPI;
|
||||||
|
import com.tencent.polaris.api.core.ProviderAPI;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link DiscoveryPropertiesAutoConfiguration}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
public class DiscoveryPropertiesAutoConfigurationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultInitialization() {
|
||||||
|
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner().withConfiguration(
|
||||||
|
AutoConfigurations.of(PolarisContextAutoConfiguration.class,
|
||||||
|
DiscoveryPropertiesAutoConfiguration.class));
|
||||||
|
applicationContextRunner.run(context -> {
|
||||||
|
assertThat(context).hasSingleBean(DiscoveryPropertiesAutoConfiguration.class);
|
||||||
|
assertThat(context).hasSingleBean(PolarisDiscoveryProperties.class);
|
||||||
|
assertThat(context).hasSingleBean(ConsulContextProperties.class);
|
||||||
|
assertThat(context).hasSingleBean(ProviderAPI.class);
|
||||||
|
assertThat(context).hasSingleBean(ConsumerAPI.class);
|
||||||
|
assertThat(context).hasSingleBean(PolarisDiscoveryHandler.class);
|
||||||
|
assertThat(context).hasSingleBean(DiscoveryConfigModifier.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInit() {
|
||||||
|
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner().withConfiguration(
|
||||||
|
AutoConfigurations.of(PolarisContextAutoConfiguration.class,
|
||||||
|
TestConfiguration.class,
|
||||||
|
DiscoveryPropertiesAutoConfiguration.class))
|
||||||
|
.withPropertyValues("spring.cloud.polaris.discovery.register=false")
|
||||||
|
.withPropertyValues("spring.cloud.consul.discovery.register=false")
|
||||||
|
.withPropertyValues("spring.cloud.consul.discovery.enabled=false");
|
||||||
|
applicationContextRunner.run(context -> {
|
||||||
|
assertThat(context).hasSingleBean(DiscoveryPropertiesAutoConfiguration.class);
|
||||||
|
DiscoveryPropertiesAutoConfiguration discoveryPropertiesAutoConfiguration = context.getBean(DiscoveryPropertiesAutoConfiguration.class);
|
||||||
|
assertThat(discoveryPropertiesAutoConfiguration.isRegisterEnabled()).isFalse();
|
||||||
|
assertThat(discoveryPropertiesAutoConfiguration.isDiscoveryEnabled()).isFalse();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class TestConfiguration {
|
||||||
|
@Bean
|
||||||
|
public PolarisDiscoveryProperties polarisDiscoveryProperties() {
|
||||||
|
PolarisDiscoveryProperties polarisDiscoveryProperties = new PolarisDiscoveryProperties();
|
||||||
|
polarisDiscoveryProperties.setEnabled(false);
|
||||||
|
return polarisDiscoveryProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ConsulContextProperties consulContextProperties() {
|
||||||
|
ConsulContextProperties consulContextProperties = new ConsulContextProperties();
|
||||||
|
consulContextProperties.setEnabled(true);
|
||||||
|
return consulContextProperties;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
|
||||||
|
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link DiscoveryPropertiesBootstrapAutoConfiguration}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
public class DiscoveryPropertiesBootstrapAutoConfigurationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultInitialization() {
|
||||||
|
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner().withConfiguration(
|
||||||
|
AutoConfigurations.of(PolarisContextAutoConfiguration.class,
|
||||||
|
DiscoveryPropertiesBootstrapAutoConfiguration.class))
|
||||||
|
.withPropertyValues("spring.cloud.polaris.enabled=true");
|
||||||
|
applicationContextRunner.run(context -> {
|
||||||
|
assertThat(context).hasSingleBean(DiscoveryPropertiesBootstrapAutoConfiguration.class);
|
||||||
|
assertThat(context).hasSingleBean(DiscoveryPropertiesAutoConfiguration.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
|
||||||
|
import static com.tencent.polaris.test.common.Consts.PORT;
|
||||||
|
import static com.tencent.polaris.test.common.Consts.PROVIDER_TOKEN;
|
||||||
|
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link PolarisDiscoveryProperties}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
public class PolarisDiscoveryPropertiesTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAndSet() {
|
||||||
|
PolarisDiscoveryProperties polarisDiscoveryProperties = new PolarisDiscoveryProperties();
|
||||||
|
|
||||||
|
// HeartbeatEnabled
|
||||||
|
polarisDiscoveryProperties.setHeartbeatEnabled(true);
|
||||||
|
assertThat(polarisDiscoveryProperties.isHeartbeatEnabled()).isTrue();
|
||||||
|
|
||||||
|
// Namespace
|
||||||
|
polarisDiscoveryProperties.setNamespace(NAMESPACE_TEST);
|
||||||
|
assertThat(polarisDiscoveryProperties.getNamespace()).isEqualTo(NAMESPACE_TEST);
|
||||||
|
|
||||||
|
// Weight
|
||||||
|
polarisDiscoveryProperties.setWeight(10);
|
||||||
|
assertThat(polarisDiscoveryProperties.getWeight()).isEqualTo(10);
|
||||||
|
|
||||||
|
// Service
|
||||||
|
polarisDiscoveryProperties.setService(SERVICE_PROVIDER);
|
||||||
|
assertThat(polarisDiscoveryProperties.getService()).isEqualTo(SERVICE_PROVIDER);
|
||||||
|
|
||||||
|
// Enabled
|
||||||
|
polarisDiscoveryProperties.setEnabled(true);
|
||||||
|
assertThat(polarisDiscoveryProperties.isEnabled()).isTrue();
|
||||||
|
|
||||||
|
// RegisterEnabled
|
||||||
|
polarisDiscoveryProperties.setRegisterEnabled(true);
|
||||||
|
assertThat(polarisDiscoveryProperties.isRegisterEnabled()).isTrue();
|
||||||
|
|
||||||
|
// Token
|
||||||
|
polarisDiscoveryProperties.setToken(PROVIDER_TOKEN);
|
||||||
|
assertThat(polarisDiscoveryProperties.getToken()).isEqualTo(PROVIDER_TOKEN);
|
||||||
|
|
||||||
|
// Version
|
||||||
|
polarisDiscoveryProperties.setVersion("1.0.0");
|
||||||
|
assertThat(polarisDiscoveryProperties.getVersion()).isEqualTo("1.0.0");
|
||||||
|
|
||||||
|
// HTTP
|
||||||
|
polarisDiscoveryProperties.setProtocol("HTTP");
|
||||||
|
assertThat(polarisDiscoveryProperties.getProtocol()).isEqualTo("HTTP");
|
||||||
|
|
||||||
|
// Port
|
||||||
|
polarisDiscoveryProperties.setPort(PORT);
|
||||||
|
assertThat(polarisDiscoveryProperties.getPort()).isEqualTo(PORT);
|
||||||
|
|
||||||
|
// HealthCheckUrl
|
||||||
|
polarisDiscoveryProperties.setHealthCheckUrl("/health");
|
||||||
|
assertThat(polarisDiscoveryProperties.getHealthCheckUrl()).isEqualTo("/health");
|
||||||
|
|
||||||
|
// ServiceListRefreshInterval
|
||||||
|
polarisDiscoveryProperties.setServiceListRefreshInterval(1000L);
|
||||||
|
assertThat(polarisDiscoveryProperties.getServiceListRefreshInterval()).isEqualTo(1000L);
|
||||||
|
|
||||||
|
assertThat(polarisDiscoveryProperties.toString())
|
||||||
|
.isEqualTo("PolarisDiscoveryProperties{"
|
||||||
|
+ "namespace='Test'"
|
||||||
|
+ ", service='java_provider_test'"
|
||||||
|
+ ", token='19485a7674294e3c88dba293373c1534'"
|
||||||
|
+ ", weight=10, version='1.0.0'"
|
||||||
|
+ ", protocol='HTTP'"
|
||||||
|
+ ", port=9091"
|
||||||
|
+ ", enabled=true"
|
||||||
|
+ ", registerEnabled=true"
|
||||||
|
+ ", heartbeatEnabled=true"
|
||||||
|
+ ", healthCheckUrl='/health'"
|
||||||
|
+ ", serviceListRefreshInterval=1000}");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* 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.discovery.refresh;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import com.tencent.polaris.api.pojo.DefaultInstance;
|
||||||
|
import com.tencent.polaris.api.pojo.ServiceEventKey;
|
||||||
|
import com.tencent.polaris.api.pojo.ServiceInfo;
|
||||||
|
import com.tencent.polaris.api.pojo.ServiceKey;
|
||||||
|
import com.tencent.polaris.client.pojo.ServiceInstancesByProto;
|
||||||
|
import com.tencent.polaris.client.pojo.ServicesByProto;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
|
||||||
|
import static com.tencent.polaris.test.common.Consts.HOST;
|
||||||
|
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
|
||||||
|
import static com.tencent.polaris.test.common.Consts.PORT;
|
||||||
|
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doNothing;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link PolarisServiceStatusChangeListener}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
public class PolarisServiceStatusChangeListenerTest {
|
||||||
|
|
||||||
|
private ApplicationEventPublisher publisher;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
publisher = mock(ApplicationEventPublisher.class);
|
||||||
|
doNothing().when(publisher).publishEvent(any(ApplicationEvent.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnResourceUpdated() {
|
||||||
|
PolarisServiceStatusChangeListener polarisServiceStatusChangeListener = new PolarisServiceStatusChangeListener();
|
||||||
|
polarisServiceStatusChangeListener.setApplicationEventPublisher(publisher);
|
||||||
|
|
||||||
|
// Service update event
|
||||||
|
ServiceEventKey serviceUpdateEventKey = new ServiceEventKey(new ServiceKey(NAMESPACE_TEST, SERVICE_PROVIDER), ServiceEventKey.EventType.SERVICE);
|
||||||
|
ServiceInfo serviceInfo = new ServiceInfo();
|
||||||
|
serviceInfo.setNamespace(NAMESPACE_TEST);
|
||||||
|
serviceInfo.setService(SERVICE_PROVIDER);
|
||||||
|
// Need update
|
||||||
|
ServicesByProto oldServices = new ServicesByProto(Collections.emptyList());
|
||||||
|
ServicesByProto newServices = new ServicesByProto(Collections.singletonList(serviceInfo));
|
||||||
|
polarisServiceStatusChangeListener.onResourceUpdated(serviceUpdateEventKey, oldServices, newServices);
|
||||||
|
verify(publisher, times(1)).publishEvent(any(ApplicationEvent.class));
|
||||||
|
// No need update
|
||||||
|
oldServices = new ServicesByProto(Collections.singletonList(serviceInfo));
|
||||||
|
newServices = new ServicesByProto(Collections.singletonList(serviceInfo));
|
||||||
|
polarisServiceStatusChangeListener.onResourceUpdated(serviceUpdateEventKey, oldServices, newServices);
|
||||||
|
verify(publisher, times(1)).publishEvent(any(ApplicationEvent.class));
|
||||||
|
|
||||||
|
|
||||||
|
// Instance update event
|
||||||
|
ServiceEventKey instanceUpdateEventKey = new ServiceEventKey(new ServiceKey(NAMESPACE_TEST, SERVICE_PROVIDER), ServiceEventKey.EventType.INSTANCE);
|
||||||
|
DefaultInstance instance = new DefaultInstance();
|
||||||
|
instance.setNamespace(NAMESPACE_TEST);
|
||||||
|
instance.setService(SERVICE_PROVIDER);
|
||||||
|
instance.setHost(HOST);
|
||||||
|
instance.setPort(PORT);
|
||||||
|
try {
|
||||||
|
Field instances = ServiceInstancesByProto.class.getDeclaredField("instances");
|
||||||
|
instances.setAccessible(true);
|
||||||
|
|
||||||
|
// Need update
|
||||||
|
ServiceInstancesByProto oldInstances = new ServiceInstancesByProto();
|
||||||
|
instances.set(oldInstances, Collections.emptyList());
|
||||||
|
ServiceInstancesByProto newInstances = new ServiceInstancesByProto();
|
||||||
|
instances.set(newInstances, Collections.singletonList(instance));
|
||||||
|
polarisServiceStatusChangeListener.onResourceUpdated(serviceUpdateEventKey, oldInstances, newInstances);
|
||||||
|
verify(publisher, times(2)).publishEvent(any(ApplicationEvent.class));
|
||||||
|
|
||||||
|
// No need update
|
||||||
|
oldInstances = new ServiceInstancesByProto();
|
||||||
|
instances.set(oldInstances, Collections.singletonList(instance));
|
||||||
|
newInstances = new ServiceInstancesByProto();
|
||||||
|
instances.set(newInstances, Collections.singletonList(instance));
|
||||||
|
polarisServiceStatusChangeListener.onResourceUpdated(serviceUpdateEventKey, oldInstances, newInstances);
|
||||||
|
verify(publisher, times(2)).publishEvent(any(ApplicationEvent.class));
|
||||||
|
}
|
||||||
|
catch (NoSuchFieldException | IllegalAccessException e) {
|
||||||
|
Assertions.fail("Exception encountered.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* 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.extend.consul;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.tencent.polaris.client.api.SDKContext;
|
||||||
|
import com.tencent.polaris.factory.config.global.ServerConnectorConfigImpl;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import static com.tencent.polaris.plugins.connector.common.constant.ConsulConstant.MetadataMapKey.INSTANCE_ID_KEY;
|
||||||
|
import static com.tencent.polaris.plugins.connector.common.constant.ConsulConstant.MetadataMapKey.IP_ADDRESS_KEY;
|
||||||
|
import static com.tencent.polaris.plugins.connector.common.constant.ConsulConstant.MetadataMapKey.PREFER_IP_ADDRESS_KEY;
|
||||||
|
import static com.tencent.polaris.plugins.connector.common.constant.ConsulConstant.MetadataMapKey.SERVICE_NAME_KEY;
|
||||||
|
import static com.tencent.polaris.test.common.Consts.HOST;
|
||||||
|
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link ConsulContextProperties}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(classes = ConsulContextPropertiesTest.TestApplication.class)
|
||||||
|
@ActiveProfiles("test")
|
||||||
|
public class ConsulContextPropertiesTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConsulContextProperties consulContextProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SDKContext sdkContext;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultInitialization() {
|
||||||
|
assertThat(consulContextProperties).isNotNull();
|
||||||
|
assertThat(consulContextProperties.isEnabled()).isTrue();
|
||||||
|
assertThat(consulContextProperties.getHost()).isEqualTo("127.0.0.1");
|
||||||
|
assertThat(consulContextProperties.getPort()).isEqualTo(8500);
|
||||||
|
assertThat(consulContextProperties.isRegister()).isTrue();
|
||||||
|
assertThat(consulContextProperties.isDiscoveryEnabled()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testModify() {
|
||||||
|
assertThat(sdkContext).isNotNull();
|
||||||
|
com.tencent.polaris.api.config.Configuration configuration = sdkContext.getConfig();
|
||||||
|
List<ServerConnectorConfigImpl> serverConnectorConfigs = configuration.getGlobal().getServerConnectors();
|
||||||
|
Map<String, String> metadata = null;
|
||||||
|
for (ServerConnectorConfigImpl serverConnectorConfig : serverConnectorConfigs) {
|
||||||
|
if (serverConnectorConfig.getId().equals("consul")) {
|
||||||
|
metadata = serverConnectorConfig.getMetadata();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertThat(metadata).isNotNull();
|
||||||
|
assertThat(metadata.get(SERVICE_NAME_KEY)).isEqualTo(SERVICE_PROVIDER);
|
||||||
|
assertThat(metadata.get(INSTANCE_ID_KEY)).isEqualTo("ins-test");
|
||||||
|
assertThat(metadata.get(PREFER_IP_ADDRESS_KEY)).isEqualTo("true");
|
||||||
|
assertThat(metadata.get(IP_ADDRESS_KEY)).isEqualTo(HOST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
protected static class TestApplication {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* 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.registry;
|
||||||
|
|
||||||
|
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
|
||||||
|
import org.springframework.cloud.client.serviceregistry.Registration;
|
||||||
|
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
|
||||||
|
import static com.tencent.polaris.test.common.Consts.PORT;
|
||||||
|
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.nullable;
|
||||||
|
import static org.mockito.Mockito.doNothing;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link PolarisAutoServiceRegistration}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class PolarisAutoServiceRegistrationTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ServiceRegistry<Registration> serviceRegistry;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private AutoServiceRegistrationProperties autoServiceRegistrationProperties;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PolarisDiscoveryProperties polarisDiscoveryProperties;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Environment environment;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PolarisRegistration registration;
|
||||||
|
|
||||||
|
private PolarisAutoServiceRegistration polarisAutoServiceRegistration;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
doReturn(polarisDiscoveryProperties).when(registration).getPolarisProperties();
|
||||||
|
|
||||||
|
doNothing().when(serviceRegistry).register(nullable(Registration.class));
|
||||||
|
|
||||||
|
polarisAutoServiceRegistration =
|
||||||
|
new PolarisAutoServiceRegistration(serviceRegistry, autoServiceRegistrationProperties, registration);
|
||||||
|
|
||||||
|
doReturn(environment).when(applicationContext).getEnvironment();
|
||||||
|
polarisAutoServiceRegistration.setApplicationContext(applicationContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegister() {
|
||||||
|
doReturn(false).when(registration).isRegisterEnabled();
|
||||||
|
try {
|
||||||
|
polarisAutoServiceRegistration.register();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
doReturn(true).when(registration).isRegisterEnabled();
|
||||||
|
doReturn(-1).when(registration).getPort();
|
||||||
|
try {
|
||||||
|
polarisAutoServiceRegistration.register();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
doReturn(PORT).when(registration).getPort();
|
||||||
|
try {
|
||||||
|
polarisAutoServiceRegistration.register();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetManagementRegistration() {
|
||||||
|
assertThat(polarisAutoServiceRegistration.getManagementRegistration()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegisterManagement() {
|
||||||
|
doReturn(false).when(registration).isRegisterEnabled();
|
||||||
|
try {
|
||||||
|
polarisAutoServiceRegistration.registerManagement();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
doReturn(true).when(registration).isRegisterEnabled();
|
||||||
|
try {
|
||||||
|
polarisAutoServiceRegistration.registerManagement();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAppName() {
|
||||||
|
doReturn("application").when(environment).getProperty(anyString(), anyString());
|
||||||
|
assertThat(polarisAutoServiceRegistration.getAppName()).isEqualTo("application");
|
||||||
|
|
||||||
|
doReturn(SERVICE_PROVIDER).when(polarisDiscoveryProperties).getService();
|
||||||
|
assertThat(polarisAutoServiceRegistration.getAppName()).isEqualTo(SERVICE_PROVIDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* 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.registry;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.tencent.cloud.common.metadata.StaticMetadataManager;
|
||||||
|
import com.tencent.cloud.polaris.DiscoveryPropertiesAutoConfiguration;
|
||||||
|
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
|
||||||
|
import com.tencent.polaris.api.config.Configuration;
|
||||||
|
import com.tencent.polaris.api.config.global.APIConfig;
|
||||||
|
import com.tencent.polaris.api.config.global.GlobalConfig;
|
||||||
|
import com.tencent.polaris.client.api.SDKContext;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import static com.tencent.polaris.test.common.Consts.HOST;
|
||||||
|
import static com.tencent.polaris.test.common.Consts.PORT;
|
||||||
|
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link PolarisRegistration}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class PolarisRegistrationTest {
|
||||||
|
|
||||||
|
private PolarisRegistration polarisRegistration;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
// mock DiscoveryPropertiesAutoConfiguration
|
||||||
|
DiscoveryPropertiesAutoConfiguration discoveryPropertiesAutoConfiguration =
|
||||||
|
mock(DiscoveryPropertiesAutoConfiguration.class);
|
||||||
|
doReturn(true).when(discoveryPropertiesAutoConfiguration).isRegisterEnabled();
|
||||||
|
|
||||||
|
// mock PolarisDiscoveryProperties
|
||||||
|
PolarisDiscoveryProperties polarisDiscoveryProperties = mock(PolarisDiscoveryProperties.class);
|
||||||
|
doReturn(SERVICE_PROVIDER).when(polarisDiscoveryProperties).getService();
|
||||||
|
doReturn(PORT).when(polarisDiscoveryProperties).getPort();
|
||||||
|
doReturn("http").when(polarisDiscoveryProperties).getProtocol();
|
||||||
|
|
||||||
|
// mock SDKContext
|
||||||
|
APIConfig apiConfig = mock(APIConfig.class);
|
||||||
|
doReturn(HOST).when(apiConfig).getBindIP();
|
||||||
|
GlobalConfig globalConfig = mock(GlobalConfig.class);
|
||||||
|
doReturn(apiConfig).when(globalConfig).getAPI();
|
||||||
|
Configuration configuration = mock(Configuration.class);
|
||||||
|
doReturn(globalConfig).when(configuration).getGlobal();
|
||||||
|
SDKContext polarisContext = mock(SDKContext.class);
|
||||||
|
doReturn(configuration).when(polarisContext).getConfig();
|
||||||
|
|
||||||
|
// mock StaticMetadataManager
|
||||||
|
StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class);
|
||||||
|
doReturn(Collections.singletonMap("key1", "value1")).when(staticMetadataManager).getMergedStaticMetadata();
|
||||||
|
doReturn(Collections.singletonMap("key2", "value2")).when(staticMetadataManager).getLocationMetadata();
|
||||||
|
|
||||||
|
polarisRegistration = new PolarisRegistration(
|
||||||
|
discoveryPropertiesAutoConfiguration, polarisDiscoveryProperties, polarisContext, staticMetadataManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetServiceId() {
|
||||||
|
assertThat(polarisRegistration.getServiceId()).isEqualTo(SERVICE_PROVIDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetHost() {
|
||||||
|
assertThat(polarisRegistration.getHost()).isEqualTo(HOST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetPort() {
|
||||||
|
assertThat(polarisRegistration.getPort()).isEqualTo(PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsSecure() {
|
||||||
|
assertThat(polarisRegistration.isSecure()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetUri() {
|
||||||
|
assertThat(polarisRegistration.getUri().toString()).isEqualTo("http://" + HOST + ":" + PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMetadata() {
|
||||||
|
Map<String, String> metadata = polarisRegistration.getMetadata();
|
||||||
|
assertThat(metadata).isNotNull();
|
||||||
|
assertThat(metadata).isNotEmpty();
|
||||||
|
assertThat(metadata.size()).isEqualTo(2);
|
||||||
|
assertThat(metadata.get("key1")).isEqualTo("value1");
|
||||||
|
assertThat(metadata.get("key2")).isEqualTo("value2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetPolarisProperties() {
|
||||||
|
assertThat(polarisRegistration.getPolarisProperties()).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsRegisterEnabled() {
|
||||||
|
assertThat(polarisRegistration.isRegisterEnabled()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToString() {
|
||||||
|
System.out.println(polarisRegistration);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import org.assertj.core.util.Maps;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.web.server.LocalServerPort;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link OkHttpUtil}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = OkHttpUtilTest.TestApplication.class)
|
||||||
|
public class OkHttpUtilTest {
|
||||||
|
|
||||||
|
@LocalServerPort
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGet() {
|
||||||
|
assertThat(OkHttpUtil.get("http://localhost:" + port + "/test", Maps.newHashMap("key", "value"))).isTrue();
|
||||||
|
assertThat(OkHttpUtil.get("http://localhost:" + port + "/error", Maps.newHashMap("key", "value"))).isFalse();
|
||||||
|
assertThat(OkHttpUtil.get("http://localhost:55555/error", Maps.newHashMap("key", "value"))).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@RestController
|
||||||
|
static class TestApplication {
|
||||||
|
@GetMapping("/test")
|
||||||
|
public String test() {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
server:
|
||||||
|
port: 48084
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: java_provider_test
|
||||||
|
cloud:
|
||||||
|
polaris:
|
||||||
|
address: grpc://127.0.0.1:8091
|
||||||
|
namespace: Test
|
||||||
|
enabled: true
|
||||||
|
discovery:
|
||||||
|
enabled: true
|
||||||
|
register: true
|
||||||
|
consul:
|
||||||
|
port: 8500
|
||||||
|
host: 127.0.0.1
|
||||||
|
enabled: true
|
||||||
|
discovery:
|
||||||
|
enabled: true
|
||||||
|
register: true
|
||||||
|
instance-id: ins-test
|
||||||
|
service-name: ${spring.application.name}
|
||||||
|
ip-address: 127.0.0.1
|
||||||
|
prefer-ip-address: true
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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.ratelimit;
|
||||||
|
|
||||||
|
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.RateLimitProto;
|
||||||
|
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* resolve labels from rate limit rule.
|
||||||
|
*
|
||||||
|
*@author lepdou 2022-05-13
|
||||||
|
*/
|
||||||
|
public class RateLimitRuleLabelResolver {
|
||||||
|
|
||||||
|
private final ServiceRuleManager serviceRuleManager;
|
||||||
|
|
||||||
|
public RateLimitRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
|
||||||
|
this.serviceRuleManager = serviceRuleManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getExpressionLabelKeys(String namespace, String service) {
|
||||||
|
RateLimitProto.RateLimit rateLimitRule = serviceRuleManager.getServiceRateLimitRule(namespace, service);
|
||||||
|
if (rateLimitRule == null) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RateLimitProto.Rule> rules = rateLimitRule.getRulesList();
|
||||||
|
if (CollectionUtils.isEmpty(rules)) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> expressionLabels = new HashSet<>();
|
||||||
|
for (RateLimitProto.Rule rule : rules) {
|
||||||
|
Map<String, ModelProto.MatchString> labels = rule.getLabelsMap();
|
||||||
|
if (CollectionUtils.isEmpty(labels)) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
for (String key : labels.keySet()) {
|
||||||
|
if (ExpressionLabelUtils.isExpressionLabel(key)) {
|
||||||
|
expressionLabels.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expressionLabels;
|
||||||
|
}
|
||||||
|
}
|
@ -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.ratelimit.config;
|
||||||
|
|
||||||
|
import com.tencent.cloud.common.constant.ContextConstant;
|
||||||
|
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
|
||||||
|
import com.tencent.cloud.polaris.context.PolarisConfigModifier;
|
||||||
|
import com.tencent.polaris.factory.config.ConfigurationImpl;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Autoconfiguration of rate limit at bootstrap phase.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@ConditionalOnPolarisEnabled
|
||||||
|
@ConditionalOnProperty(name = "spring.cloud.polaris.ratelimit.enabled", matchIfMissing = true)
|
||||||
|
public class PolarisRateLimitBootstrapConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PolarisRateLimitProperties polarisRateLimitProperties() {
|
||||||
|
return new PolarisRateLimitProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RateLimitConfigModifier rateLimitConfigModifier(PolarisRateLimitProperties polarisRateLimitProperties) {
|
||||||
|
return new RateLimitConfigModifier(polarisRateLimitProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config modifier for rate limit.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
public static class RateLimitConfigModifier implements PolarisConfigModifier {
|
||||||
|
|
||||||
|
private PolarisRateLimitProperties polarisRateLimitProperties;
|
||||||
|
|
||||||
|
public RateLimitConfigModifier(PolarisRateLimitProperties polarisRateLimitProperties) {
|
||||||
|
this.polarisRateLimitProperties = polarisRateLimitProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void modify(ConfigurationImpl configuration) {
|
||||||
|
// Update MaxQueuingTime.
|
||||||
|
configuration.getProvider().getRateLimit()
|
||||||
|
.setMaxQueuingTime(polarisRateLimitProperties.getMaxQueuingTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return ContextConstant.ModifierOrder.CIRCUIT_BREAKER_ORDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +1,4 @@
|
|||||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||||
com.tencent.cloud.polaris.ratelimit.config.RateLimitConfiguration
|
com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitConfiguration
|
||||||
|
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
|
||||||
|
com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitBootstrapConfiguration
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* 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.ratelimit;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.google.protobuf.StringValue;
|
||||||
|
import com.tencent.cloud.polaris.context.ServiceRuleManager;
|
||||||
|
import com.tencent.polaris.client.pb.ModelProto;
|
||||||
|
import com.tencent.polaris.client.pb.RateLimitProto;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link RateLimitRuleLabelResolver}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class RateLimitRuleLabelResolverTest {
|
||||||
|
|
||||||
|
private ServiceRuleManager serviceRuleManager;
|
||||||
|
|
||||||
|
private RateLimitRuleLabelResolver rateLimitRuleLabelResolver;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
serviceRuleManager = mock(ServiceRuleManager.class);
|
||||||
|
when(serviceRuleManager.getServiceRateLimitRule(any(), anyString())).thenAnswer(invocationOnMock -> {
|
||||||
|
String serviceName = invocationOnMock.getArgument(1).toString();
|
||||||
|
if (serviceName.equals("TestApp1")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if (serviceName.equals("TestApp2")) {
|
||||||
|
return RateLimitProto.RateLimit.newBuilder().build();
|
||||||
|
}
|
||||||
|
else if (serviceName.equals("TestApp3")) {
|
||||||
|
RateLimitProto.Rule rule = RateLimitProto.Rule.newBuilder().build();
|
||||||
|
return RateLimitProto.RateLimit.newBuilder().addRules(rule).build();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ModelProto.MatchString matchString = ModelProto.MatchString.newBuilder()
|
||||||
|
.setType(ModelProto.MatchString.MatchStringType.EXACT)
|
||||||
|
.setValue(StringValue.of("value"))
|
||||||
|
.setValueType(ModelProto.MatchString.ValueType.TEXT).build();
|
||||||
|
RateLimitProto.Rule rule = RateLimitProto.Rule.newBuilder()
|
||||||
|
.putLabels("${http.method}", matchString).build();
|
||||||
|
return RateLimitProto.RateLimit.newBuilder().addRules(rule).build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rateLimitRuleLabelResolver = new RateLimitRuleLabelResolver(serviceRuleManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetExpressionLabelKeys() {
|
||||||
|
// rateLimitRule == null
|
||||||
|
String serviceName = "TestApp1";
|
||||||
|
Set<String> labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
|
||||||
|
assertThat(labelKeys).isEmpty();
|
||||||
|
|
||||||
|
// CollectionUtils.isEmpty(rules)
|
||||||
|
serviceName = "TestApp2";
|
||||||
|
labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
|
||||||
|
assertThat(labelKeys).isEmpty();
|
||||||
|
|
||||||
|
// CollectionUtils.isEmpty(labels)
|
||||||
|
serviceName = "TestApp3";
|
||||||
|
labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
|
||||||
|
assertThat(labelKeys).isEmpty();
|
||||||
|
|
||||||
|
// Has labels
|
||||||
|
serviceName = "TestApp4";
|
||||||
|
labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
|
||||||
|
assertThat(labelKeys).isNotEmpty();
|
||||||
|
assertThat(labelKeys).contains("${http.method}");
|
||||||
|
}
|
||||||
|
}
|
@ -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.ratelimit.config;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link PolarisRateLimitBootstrapConfiguration}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
public class PolarisRateLimitBootstrapConfigurationTest {
|
||||||
|
|
||||||
|
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||||
|
.withConfiguration(
|
||||||
|
AutoConfigurations.of(PolarisRateLimitBootstrapConfiguration.class))
|
||||||
|
.withPropertyValues("spring.cloud.polaris.ratelimit.enabled=true");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultInitialization() {
|
||||||
|
this.contextRunner.run(context -> {
|
||||||
|
assertThat(context).hasSingleBean(PolarisRateLimitProperties.class);
|
||||||
|
assertThat(context).hasSingleBean(PolarisRateLimitBootstrapConfiguration.RateLimitConfigModifier.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* 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.ratelimit.config;
|
||||||
|
|
||||||
|
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
|
||||||
|
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
|
||||||
|
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter;
|
||||||
|
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter;
|
||||||
|
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||||
|
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link PolarisRateLimitConfiguration}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
public class PolarisRateLimitConfigurationTest {
|
||||||
|
|
||||||
|
private ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner();
|
||||||
|
|
||||||
|
private WebApplicationContextRunner webApplicationContextRunner = new WebApplicationContextRunner();
|
||||||
|
|
||||||
|
private ReactiveWebApplicationContextRunner reactiveWebApplicationContextRunner = new ReactiveWebApplicationContextRunner();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoWebApplication() {
|
||||||
|
this.applicationContextRunner
|
||||||
|
.withConfiguration(AutoConfigurations.of(
|
||||||
|
PolarisContextAutoConfiguration.class,
|
||||||
|
PolarisRateLimitProperties.class,
|
||||||
|
PolarisRateLimitConfiguration.class))
|
||||||
|
.run(context -> {
|
||||||
|
assertThat(context).hasSingleBean(LimitAPI.class);
|
||||||
|
assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class);
|
||||||
|
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.QuotaCheckFilterConfig.class);
|
||||||
|
assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class);
|
||||||
|
assertThat(context).doesNotHaveBean(FilterRegistrationBean.class);
|
||||||
|
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.MetadataReactiveFilterConfig.class);
|
||||||
|
assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testServletWebApplication() {
|
||||||
|
this.webApplicationContextRunner
|
||||||
|
.withConfiguration(AutoConfigurations.of(PolarisContextAutoConfiguration.class,
|
||||||
|
PolarisRateLimitProperties.class,
|
||||||
|
PolarisRateLimitConfiguration.class))
|
||||||
|
.run(context -> {
|
||||||
|
assertThat(context).hasSingleBean(LimitAPI.class);
|
||||||
|
assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class);
|
||||||
|
assertThat(context).hasSingleBean(PolarisRateLimitConfiguration.QuotaCheckFilterConfig.class);
|
||||||
|
assertThat(context).hasSingleBean(QuotaCheckServletFilter.class);
|
||||||
|
assertThat(context).hasSingleBean(FilterRegistrationBean.class);
|
||||||
|
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.MetadataReactiveFilterConfig.class);
|
||||||
|
assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReactiveWebApplication() {
|
||||||
|
this.reactiveWebApplicationContextRunner
|
||||||
|
.withConfiguration(AutoConfigurations.of(PolarisContextAutoConfiguration.class,
|
||||||
|
PolarisRateLimitProperties.class,
|
||||||
|
PolarisRateLimitConfiguration.class))
|
||||||
|
.run(context -> {
|
||||||
|
assertThat(context).hasSingleBean(LimitAPI.class);
|
||||||
|
assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class);
|
||||||
|
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.QuotaCheckFilterConfig.class);
|
||||||
|
assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class);
|
||||||
|
assertThat(context).doesNotHaveBean(FilterRegistrationBean.class);
|
||||||
|
assertThat(context).hasSingleBean(PolarisRateLimitConfiguration.MetadataReactiveFilterConfig.class);
|
||||||
|
assertThat(context).hasSingleBean(QuotaCheckReactiveFilter.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 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.ratelimit.config;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link PolarisRateLimitProperties}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
public class PolarisRateLimitPropertiesTest {
|
||||||
|
|
||||||
|
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||||
|
.withConfiguration(AutoConfigurations.of(PolarisRateLimitPropertiesAutoConfiguration.class, PolarisRateLimitProperties.class))
|
||||||
|
.withPropertyValues("spring.cloud.polaris.ratelimit.rejectRequestTips=xxx")
|
||||||
|
.withPropertyValues("spring.cloud.polaris.ratelimit.rejectRequestTipsFilePath=/index.html")
|
||||||
|
.withPropertyValues("spring.cloud.polaris.ratelimit.rejectHttpCode=419")
|
||||||
|
.withPropertyValues("spring.cloud.polaris.ratelimit.maxQueuingTime=500");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultInitialization() {
|
||||||
|
this.contextRunner.run(context -> {
|
||||||
|
PolarisRateLimitProperties polarisRateLimitProperties = context.getBean(PolarisRateLimitProperties.class);
|
||||||
|
assertThat(polarisRateLimitProperties.getRejectRequestTips()).isEqualTo("xxx");
|
||||||
|
assertThat(polarisRateLimitProperties.getRejectRequestTipsFilePath()).isEqualTo("/index.html");
|
||||||
|
assertThat(polarisRateLimitProperties.getRejectHttpCode()).isEqualTo(419);
|
||||||
|
assertThat(polarisRateLimitProperties.getMaxQueuingTime()).isEqualTo(500L);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
static class PolarisRateLimitPropertiesAutoConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* 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.ratelimit.filter;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.tencent.cloud.common.metadata.MetadataContext;
|
||||||
|
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
|
||||||
|
import com.tencent.cloud.common.util.ExpressionLabelUtils;
|
||||||
|
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
|
||||||
|
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
|
||||||
|
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
|
||||||
|
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
|
||||||
|
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
|
||||||
|
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
|
||||||
|
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
|
||||||
|
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
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.Mono;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
|
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||||
|
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import org.springframework.web.server.WebFilterChain;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.fail;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anySet;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link QuotaCheckReactiveFilter}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
@SpringBootTest(classes = QuotaCheckReactiveFilterTest.TestApplication.class, properties = {
|
||||||
|
"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"
|
||||||
|
})
|
||||||
|
public class QuotaCheckReactiveFilterTest {
|
||||||
|
|
||||||
|
private PolarisRateLimiterLabelReactiveResolver labelResolver = exchange -> Collections.singletonMap("ReactiveResolver", "ReactiveResolver");
|
||||||
|
|
||||||
|
private QuotaCheckReactiveFilter quotaCheckReactiveFilter;
|
||||||
|
|
||||||
|
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
|
||||||
|
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeClass() {
|
||||||
|
expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class);
|
||||||
|
when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())).thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver"));
|
||||||
|
|
||||||
|
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
|
||||||
|
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
|
||||||
|
.thenReturn("unit-test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterClass() {
|
||||||
|
mockedApplicationContextAwareUtils.close();
|
||||||
|
expressionLabelUtilsMockedStatic.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MetadataContext.LOCAL_NAMESPACE = "TEST";
|
||||||
|
|
||||||
|
LimitAPI limitAPI = mock(LimitAPI.class);
|
||||||
|
when(limitAPI.getQuota(any(QuotaRequest.class))).thenAnswer(invocationOnMock -> {
|
||||||
|
String serviceName = ((QuotaRequest) invocationOnMock.getArgument(0)).getService();
|
||||||
|
if (serviceName.equals("TestApp1")) {
|
||||||
|
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "QuotaResultOk"));
|
||||||
|
}
|
||||||
|
else if (serviceName.equals("TestApp2")) {
|
||||||
|
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
|
||||||
|
}
|
||||||
|
else if (serviceName.equals("TestApp3")) {
|
||||||
|
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new QuotaResponse(new QuotaResult(null, 0, null));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
|
||||||
|
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips");
|
||||||
|
polarisRateLimitProperties.setRejectHttpCode(419);
|
||||||
|
|
||||||
|
RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class);
|
||||||
|
when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.EMPTY_SET);
|
||||||
|
|
||||||
|
this.quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetOrder() {
|
||||||
|
assertThat(this.quotaCheckReactiveFilter.getOrder()).isEqualTo(RateLimitConstant.FILTER_ORDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInit() {
|
||||||
|
quotaCheckReactiveFilter.init();
|
||||||
|
try {
|
||||||
|
Field rejectTips = QuotaCheckReactiveFilter.class.getDeclaredField("rejectTips");
|
||||||
|
rejectTips.setAccessible(true);
|
||||||
|
assertThat(rejectTips.get(quotaCheckReactiveFilter)).isEqualTo("RejectRequestTips");
|
||||||
|
}
|
||||||
|
catch (NoSuchFieldException | IllegalAccessException e) {
|
||||||
|
fail("Exception encountered.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetRuleExpressionLabels() {
|
||||||
|
try {
|
||||||
|
Method getCustomResolvedLabels = QuotaCheckReactiveFilter.class.getDeclaredMethod("getCustomResolvedLabels", ServerWebExchange.class);
|
||||||
|
getCustomResolvedLabels.setAccessible(true);
|
||||||
|
|
||||||
|
// Mock request
|
||||||
|
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/test").build();
|
||||||
|
ServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||||
|
|
||||||
|
// labelResolver != null
|
||||||
|
Map<String, String> result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange);
|
||||||
|
assertThat(result.size()).isEqualTo(1);
|
||||||
|
assertThat(result.get("ReactiveResolver")).isEqualTo("ReactiveResolver");
|
||||||
|
|
||||||
|
// throw exception
|
||||||
|
PolarisRateLimiterLabelReactiveResolver exceptionLabelResolver = new PolarisRateLimiterLabelReactiveResolver() {
|
||||||
|
@Override
|
||||||
|
public Map<String, String> resolve(ServerWebExchange exchange) {
|
||||||
|
throw new RuntimeException("Mock exception.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(null, exceptionLabelResolver, null, null);
|
||||||
|
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange);
|
||||||
|
assertThat(result.size()).isEqualTo(0);
|
||||||
|
|
||||||
|
// labelResolver == null
|
||||||
|
quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(null, null, null, null);
|
||||||
|
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange);
|
||||||
|
assertThat(result.size()).isEqualTo(0);
|
||||||
|
|
||||||
|
getCustomResolvedLabels.setAccessible(false);
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
fail("Exception encountered.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilter() {
|
||||||
|
// Create mock WebFilterChain
|
||||||
|
WebFilterChain webFilterChain = serverWebExchange -> Mono.empty();
|
||||||
|
|
||||||
|
// Mock request
|
||||||
|
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/test").build();
|
||||||
|
ServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||||
|
|
||||||
|
quotaCheckReactiveFilter.init();
|
||||||
|
|
||||||
|
// Pass
|
||||||
|
MetadataContext.LOCAL_SERVICE = "TestApp1";
|
||||||
|
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
|
||||||
|
|
||||||
|
// Unirate waiting 1000ms
|
||||||
|
MetadataContext.LOCAL_SERVICE = "TestApp2";
|
||||||
|
long startTimestamp = System.currentTimeMillis();
|
||||||
|
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
|
||||||
|
assertThat(System.currentTimeMillis() - startTimestamp).isGreaterThanOrEqualTo(1000L);
|
||||||
|
|
||||||
|
// Rate limited
|
||||||
|
MetadataContext.LOCAL_SERVICE = "TestApp3";
|
||||||
|
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
|
||||||
|
ServerHttpResponse response = exchange.getResponse();
|
||||||
|
assertThat(response.getRawStatusCode()).isEqualTo(419);
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE);
|
||||||
|
|
||||||
|
// Exception
|
||||||
|
MetadataContext.LOCAL_SERVICE = "TestApp4";
|
||||||
|
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
protected static class TestApplication {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* 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.ratelimit.filter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import com.tencent.cloud.common.metadata.MetadataContext;
|
||||||
|
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
|
||||||
|
import com.tencent.cloud.common.util.ExpressionLabelUtils;
|
||||||
|
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
|
||||||
|
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
|
||||||
|
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
|
||||||
|
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
|
||||||
|
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
|
||||||
|
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
|
||||||
|
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
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 org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.fail;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anySet;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link QuotaCheckServletFilter}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
@SpringBootTest(classes = QuotaCheckServletFilterTest.TestApplication.class, properties = {
|
||||||
|
"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"
|
||||||
|
})
|
||||||
|
public class QuotaCheckServletFilterTest {
|
||||||
|
|
||||||
|
private PolarisRateLimiterLabelServletResolver labelResolver = exchange -> Collections.singletonMap("ServletResolver", "ServletResolver");
|
||||||
|
|
||||||
|
private QuotaCheckServletFilter quotaCheckServletFilter;
|
||||||
|
|
||||||
|
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
|
||||||
|
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic;
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeClass() {
|
||||||
|
expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class);
|
||||||
|
when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())).thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver"));
|
||||||
|
|
||||||
|
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
|
||||||
|
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
|
||||||
|
.thenReturn("unit-test");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterClass() throws Exception {
|
||||||
|
mockedApplicationContextAwareUtils.close();
|
||||||
|
expressionLabelUtilsMockedStatic.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MetadataContext.LOCAL_NAMESPACE = "TEST";
|
||||||
|
|
||||||
|
LimitAPI limitAPI = mock(LimitAPI.class);
|
||||||
|
when(limitAPI.getQuota(any(QuotaRequest.class))).thenAnswer(invocationOnMock -> {
|
||||||
|
String serviceName = ((QuotaRequest) invocationOnMock.getArgument(0)).getService();
|
||||||
|
if (serviceName.equals("TestApp1")) {
|
||||||
|
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "QuotaResultOk"));
|
||||||
|
}
|
||||||
|
else if (serviceName.equals("TestApp2")) {
|
||||||
|
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
|
||||||
|
}
|
||||||
|
else if (serviceName.equals("TestApp3")) {
|
||||||
|
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new QuotaResponse(new QuotaResult(null, 0, null));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
|
||||||
|
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips");
|
||||||
|
polarisRateLimitProperties.setRejectHttpCode(419);
|
||||||
|
|
||||||
|
RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class);
|
||||||
|
when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.EMPTY_SET);
|
||||||
|
|
||||||
|
this.quotaCheckServletFilter = new QuotaCheckServletFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInit() {
|
||||||
|
quotaCheckServletFilter.init();
|
||||||
|
try {
|
||||||
|
Field rejectTips = QuotaCheckServletFilter.class.getDeclaredField("rejectTips");
|
||||||
|
rejectTips.setAccessible(true);
|
||||||
|
assertThat(rejectTips.get(quotaCheckServletFilter)).isEqualTo("RejectRequestTips");
|
||||||
|
}
|
||||||
|
catch (NoSuchFieldException | IllegalAccessException e) {
|
||||||
|
fail("Exception encountered.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetRuleExpressionLabels() {
|
||||||
|
try {
|
||||||
|
Method getCustomResolvedLabels = QuotaCheckServletFilter.class.getDeclaredMethod("getCustomResolvedLabels", HttpServletRequest.class);
|
||||||
|
getCustomResolvedLabels.setAccessible(true);
|
||||||
|
|
||||||
|
// Mock request
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
|
||||||
|
// labelResolver != null
|
||||||
|
Map<String, String> result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request);
|
||||||
|
assertThat(result.size()).isEqualTo(1);
|
||||||
|
assertThat(result.get("ServletResolver")).isEqualTo("ServletResolver");
|
||||||
|
|
||||||
|
// throw exception
|
||||||
|
PolarisRateLimiterLabelServletResolver exceptionLabelResolver = request1 -> {
|
||||||
|
throw new RuntimeException("Mock exception.");
|
||||||
|
};
|
||||||
|
quotaCheckServletFilter = new QuotaCheckServletFilter(null, exceptionLabelResolver, null, null);
|
||||||
|
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request);
|
||||||
|
assertThat(result.size()).isEqualTo(0);
|
||||||
|
|
||||||
|
// labelResolver == null
|
||||||
|
quotaCheckServletFilter = new QuotaCheckServletFilter(null, null, null, null);
|
||||||
|
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request);
|
||||||
|
assertThat(result.size()).isEqualTo(0);
|
||||||
|
|
||||||
|
getCustomResolvedLabels.setAccessible(false);
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
fail("Exception encountered.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoFilterInternal() {
|
||||||
|
// Create mock FilterChain
|
||||||
|
FilterChain filterChain = (servletRequest, servletResponse) -> {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock request
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
quotaCheckServletFilter.init();
|
||||||
|
try {
|
||||||
|
// Pass
|
||||||
|
MetadataContext.LOCAL_SERVICE = "TestApp1";
|
||||||
|
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
|
||||||
|
|
||||||
|
// Unirate waiting 1000ms
|
||||||
|
MetadataContext.LOCAL_SERVICE = "TestApp2";
|
||||||
|
long startTimestamp = System.currentTimeMillis();
|
||||||
|
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
|
||||||
|
assertThat(System.currentTimeMillis() - startTimestamp).isGreaterThanOrEqualTo(1000L);
|
||||||
|
|
||||||
|
// Rate limited
|
||||||
|
MetadataContext.LOCAL_SERVICE = "TestApp3";
|
||||||
|
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
|
||||||
|
assertThat(response.getStatus()).isEqualTo(419);
|
||||||
|
assertThat(response.getContentAsString()).isEqualTo("RejectRequestTips");
|
||||||
|
|
||||||
|
|
||||||
|
// Exception
|
||||||
|
MetadataContext.LOCAL_SERVICE = "TestApp4";
|
||||||
|
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
|
||||||
|
}
|
||||||
|
catch (ServletException | IOException e) {
|
||||||
|
fail("Exception encountered.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
protected static class TestApplication {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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.ratelimit.utils;
|
||||||
|
|
||||||
|
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
|
||||||
|
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
|
||||||
|
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
|
||||||
|
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
|
||||||
|
import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link QuotaCheckUtils}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class QuotaCheckUtilsTest {
|
||||||
|
|
||||||
|
private LimitAPI limitAPI;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
limitAPI = mock(LimitAPI.class);
|
||||||
|
when(limitAPI.getQuota(any(QuotaRequest.class))).thenAnswer(invocationOnMock -> {
|
||||||
|
String serviceName = ((QuotaRequest) invocationOnMock.getArgument(0)).getService();
|
||||||
|
if (serviceName.equals("TestApp1")) {
|
||||||
|
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "QuotaResultOk"));
|
||||||
|
}
|
||||||
|
else if (serviceName.equals("TestApp2")) {
|
||||||
|
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
|
||||||
|
}
|
||||||
|
else if (serviceName.equals("TestApp3")) {
|
||||||
|
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new RuntimeException("Mock exception.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetQuota() {
|
||||||
|
// Pass
|
||||||
|
String serviceName = "TestApp1";
|
||||||
|
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
|
||||||
|
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
|
||||||
|
assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
|
||||||
|
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk");
|
||||||
|
|
||||||
|
// Unirate waiting 1000ms
|
||||||
|
serviceName = "TestApp2";
|
||||||
|
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
|
||||||
|
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
|
||||||
|
assertThat(quotaResponse.getWaitMs()).isEqualTo(1000);
|
||||||
|
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk");
|
||||||
|
|
||||||
|
// Rate limited
|
||||||
|
serviceName = "TestApp3";
|
||||||
|
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
|
||||||
|
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultLimited);
|
||||||
|
assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
|
||||||
|
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultLimited");
|
||||||
|
|
||||||
|
// Exception
|
||||||
|
serviceName = "TestApp4";
|
||||||
|
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
|
||||||
|
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
|
||||||
|
assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
|
||||||
|
assertThat(quotaResponse.getInfo()).isEqualTo("get quota failed");
|
||||||
|
}
|
||||||
|
}
|
@ -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.ratelimit.utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.tencent.cloud.common.util.ResourceFileUtils;
|
||||||
|
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.QUOTA_LIMITED_INFO;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link RateLimitUtils}.
|
||||||
|
*
|
||||||
|
* @author Haotian Zhang
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class RateLimitUtilsTest {
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeClass() throws IOException {
|
||||||
|
mockStatic(ResourceFileUtils.class);
|
||||||
|
when(ResourceFileUtils.readFile(anyString())).thenAnswer(invocation -> {
|
||||||
|
String rejectFilePath = invocation.getArgument(0).toString();
|
||||||
|
if (rejectFilePath.equals("exception.html")) {
|
||||||
|
throw new IOException("Mock exceptions");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "RejectRequestTips";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetRejectTips() {
|
||||||
|
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
|
||||||
|
|
||||||
|
// RejectRequestTips
|
||||||
|
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips");
|
||||||
|
assertThat(RateLimitUtils.getRejectTips(polarisRateLimitProperties)).isEqualTo("RejectRequestTips");
|
||||||
|
|
||||||
|
// RejectRequestTipsFilePath
|
||||||
|
polarisRateLimitProperties.setRejectRequestTips(null);
|
||||||
|
polarisRateLimitProperties.setRejectRequestTipsFilePath("reject-tips.html");
|
||||||
|
assertThat(RateLimitUtils.getRejectTips(polarisRateLimitProperties)).isEqualTo("RejectRequestTips");
|
||||||
|
|
||||||
|
// RejectRequestTipsFilePath with Exception
|
||||||
|
polarisRateLimitProperties.setRejectRequestTips(null);
|
||||||
|
polarisRateLimitProperties.setRejectRequestTipsFilePath("exception.html");
|
||||||
|
assertThat(RateLimitUtils.getRejectTips(polarisRateLimitProperties)).isEqualTo(QUOTA_LIMITED_INFO);
|
||||||
|
}
|
||||||
|
}
|
@ -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,210 @@
|
|||||||
|
/*
|
||||||
|
* 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.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
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.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 org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
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 static final Logger LOGGER = LoggerFactory.getLogger(PolarisRouterServiceInstanceListSupplier.class);
|
||||||
|
|
||||||
|
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 -> {
|
||||||
|
try {
|
||||||
|
Map<String, String> labels = JacksonUtils.deserialize2Map(URLDecoder.decode(labelHeaderValue, "UTF-8"));
|
||||||
|
if (!CollectionUtils.isEmpty(labels)) {
|
||||||
|
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (UnsupportedEncodingException e) {
|
||||||
|
LOGGER.error("Decode header[{}] failed.", labelHeaderValue, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -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,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.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.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)
|
||||||
|
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)
|
||||||
|
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,132 @@
|
|||||||
|
/*
|
||||||
|
* 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.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
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.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);
|
||||||
|
|
||||||
|
// pass label by header
|
||||||
|
if (labels.size() == 0) {
|
||||||
|
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String headerMetadataStr = URLEncoder.encode(JacksonUtils.serialize2Json(labels), "UTF-8");
|
||||||
|
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER, headerMetadataStr);
|
||||||
|
}
|
||||||
|
catch (UnsupportedEncodingException e) {
|
||||||
|
LOGGER.error("Set header failed.", e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,156 @@
|
|||||||
|
/*
|
||||||
|
* 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.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
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);
|
||||||
|
|
||||||
|
// pass label by header
|
||||||
|
if (labels.size() == 0) {
|
||||||
|
request.getHeaders().set(RouterConstants.ROUTER_LABEL_HEADER, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String headerMetadataStr = URLEncoder.encode(JacksonUtils.serialize2Json(labels), "UTF-8");
|
||||||
|
request.getHeaders().set(RouterConstants.ROUTER_LABEL_HEADER, headerMetadataStr);
|
||||||
|
}
|
||||||
|
catch (UnsupportedEncodingException e) {
|
||||||
|
LOGGER.error("Set header failed.", e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,135 @@
|
|||||||
|
/*
|
||||||
|
* 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.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
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.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() throws UnsupportedEncodingException {
|
||||||
|
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 = JacksonUtils.deserialize2Map(URLDecoder.decode(value, "UTF-8"));
|
||||||
|
|
||||||
|
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}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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,231 @@
|
|||||||
|
/*
|
||||||
|
* 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.net.URLDecoder;
|
||||||
|
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(URLDecoder.decode(request.getHeaders()
|
||||||
|
.get(RouterConstants.ROUTER_LABEL_HEADER).get(0), "UTF-8"));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue