feat:merge features from 1.5.2-Hoxton.SR9. (#223)
parent
7ba0fe8e95
commit
8e0a35159f
@ -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.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory;
|
||||
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
|
||||
|
||||
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(CachingSpringLoadBalancerFactory.class)) {
|
||||
return mock(CachingSpringLoadBalancerFactory.class);
|
||||
}
|
||||
if (clazz.equals(SpringClientFactory.class)) {
|
||||
return mock(SpringClientFactory.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,
|
||||
PolarisLoadBalancerFeignClient.class);
|
||||
|
||||
// bean instanceOf Client.class
|
||||
Client bean2 = mock(Client.class);
|
||||
bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean2, "bean2");
|
||||
assertThat(bean).isInstanceOf(PolarisFeignClient.class);
|
||||
|
||||
// bean instanceOf LoadBalancerFeignClient.class
|
||||
LoadBalancerFeignClient bean3 = mock(LoadBalancerFeignClient.class);
|
||||
doReturn(mock(Client.class)).when(bean3).getDelegate();
|
||||
bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean3, "bean3");
|
||||
assertThat(bean).isInstanceOf(PolarisLoadBalancerFeignClient.class);
|
||||
}
|
||||
}
|
@ -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,44 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
|
||||
*
|
||||
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.polaris.ribbon;
|
||||
|
||||
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 PolarisDiscoveryRibbonAutoConfiguration}.
|
||||
*
|
||||
* @author Haotian Zhang
|
||||
*/
|
||||
public class PolarisDiscoveryRibbonAutoConfigurationTest {
|
||||
|
||||
private ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner();
|
||||
|
||||
@Test
|
||||
public void testDefaultInitialization() {
|
||||
this.applicationContextRunner
|
||||
.withConfiguration(AutoConfigurations.of(PolarisDiscoveryRibbonAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context).hasSingleBean(PolarisDiscoveryRibbonAutoConfiguration.class);
|
||||
});
|
||||
}
|
||||
}
|
@ -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
|
||||
@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=\
|
||||
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,219 @@
|
||||
/*
|
||||
* 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.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 static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
|
||||
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic;
|
||||
private PolarisRateLimiterLabelReactiveResolver labelResolver = exchange -> Collections.singletonMap("ReactiveResolver", "ReactiveResolver");
|
||||
private QuotaCheckReactiveFilter quotaCheckReactiveFilter;
|
||||
|
||||
@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.getStatusCode().value()).isEqualTo(419);
|
||||
|
||||
// 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,222 @@
|
||||
/*
|
||||
* 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.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
|
||||
import com.netflix.loadbalancer.AvailabilityFilteringRule;
|
||||
import com.netflix.loadbalancer.BestAvailableRule;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.RandomRule;
|
||||
import com.netflix.loadbalancer.RetryRule;
|
||||
import com.netflix.loadbalancer.RoundRobinRule;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.WeightedResponseTimeRule;
|
||||
import com.netflix.loadbalancer.ZoneAvoidanceRule;
|
||||
import com.tencent.cloud.common.metadata.MetadataContext;
|
||||
import com.tencent.cloud.common.pojo.PolarisServer;
|
||||
import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils;
|
||||
import com.tencent.cloud.polaris.loadbalancer.PolarisWeightedRule;
|
||||
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.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.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* Service routing entrance.
|
||||
*
|
||||
* Rule routing needs to rely on request parameters for server filtering,
|
||||
* and {@link com.netflix.loadbalancer.ServerListFilter#getFilteredListOfServers(List)}
|
||||
* The interface cannot obtain the context object of the request granularity,
|
||||
* so the routing capability cannot be achieved through ServerListFilter.
|
||||
*
|
||||
* And {@link com.netflix.loadbalancer.IRule#choose(Object)} provides the ability to pass in context parameters,
|
||||
* so routing capabilities are implemented through IRule.
|
||||
*
|
||||
* @author Haotian Zhang, lepdou
|
||||
*/
|
||||
public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule {
|
||||
|
||||
final static String STRATEGY_RANDOM = "random";
|
||||
final static String STRATEGY_ROUND_ROBIN = "roundRobin";
|
||||
final static String STRATEGY_WEIGHT = "polarisWeighted";
|
||||
final static String STRATEGY_RETRY = "retry";
|
||||
final static String STRATEGY_RESPONSE_TIME_WEIGHTED = "responseTimeWeighted";
|
||||
final static String STRATEGY_BEST_AVAILABLE = "bestAvailable";
|
||||
final static String STRATEGY_ZONE_AVOIDANCE = "zoneAvoidance";
|
||||
final static String STRATEGY_AVAILABILITY_FILTERING = "availabilityFilteringRule";
|
||||
|
||||
private final PolarisLoadBalancerProperties loadBalancerProperties;
|
||||
private final PolarisNearByRouterProperties polarisNearByRouterProperties;
|
||||
private final PolarisMetadataRouterProperties polarisMetadataRouterProperties;
|
||||
private final PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties;
|
||||
private final RouterAPI routerAPI;
|
||||
|
||||
private final AbstractLoadBalancerRule delegateRule;
|
||||
|
||||
public PolarisLoadBalancerCompositeRule(RouterAPI routerAPI,
|
||||
PolarisLoadBalancerProperties polarisLoadBalancerProperties,
|
||||
PolarisNearByRouterProperties polarisNearByRouterProperties,
|
||||
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
|
||||
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties,
|
||||
IClientConfig iClientConfig) {
|
||||
this.routerAPI = routerAPI;
|
||||
this.polarisNearByRouterProperties = polarisNearByRouterProperties;
|
||||
this.loadBalancerProperties = polarisLoadBalancerProperties;
|
||||
this.polarisMetadataRouterProperties = polarisMetadataRouterProperties;
|
||||
this.polarisRuleBasedRouterProperties = polarisRuleBasedRouterProperties;
|
||||
|
||||
delegateRule = getRule();
|
||||
delegateRule.initWithNiwsConfig(iClientConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initWithNiwsConfig(IClientConfig clientConfig) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Server choose(Object key) {
|
||||
// 1. get all servers
|
||||
List<Server> allServers = getLoadBalancer().getReachableServers();
|
||||
if (CollectionUtils.isEmpty(allServers)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. filter by router
|
||||
List<Server> serversAfterRouter = doRouter(allServers, key);
|
||||
|
||||
// 3. filter by load balance.
|
||||
// A LoadBalancer needs to be regenerated for each request,
|
||||
// because the list of servers may be different after filtered by router
|
||||
ILoadBalancer loadBalancer = new SimpleLoadBalancer();
|
||||
loadBalancer.addServers(serversAfterRouter);
|
||||
delegateRule.setLoadBalancer(loadBalancer);
|
||||
|
||||
return delegateRule.choose(key);
|
||||
}
|
||||
|
||||
List<Server> doRouter(List<Server> allServers, Object key) {
|
||||
ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(allServers);
|
||||
|
||||
// filter instance by routers
|
||||
ProcessRoutersRequest processRoutersRequest = buildProcessRoutersRequest(serviceInstances, key);
|
||||
|
||||
ProcessRoutersResponse processRoutersResponse = routerAPI.processRouters(processRoutersRequest);
|
||||
|
||||
List<Server> filteredInstances = new ArrayList<>();
|
||||
ServiceInstances filteredServiceInstances = processRoutersResponse.getServiceInstances();
|
||||
for (Instance instance : filteredServiceInstances.getInstances()) {
|
||||
filteredInstances.add(new PolarisServer(serviceInstances, instance));
|
||||
}
|
||||
return filteredInstances;
|
||||
}
|
||||
|
||||
ProcessRoutersRequest buildProcessRoutersRequest(ServiceInstances serviceInstances, Object 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(Object key, String type) {
|
||||
if (key instanceof PolarisRouterContext) {
|
||||
return ((PolarisRouterContext) key).getLabels(type);
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
public AbstractLoadBalancerRule getRule() {
|
||||
String loadBalanceStrategy = loadBalancerProperties.getStrategy();
|
||||
if (StringUtils.isEmpty(loadBalanceStrategy)) {
|
||||
return new ZoneAvoidanceRule();
|
||||
}
|
||||
switch (loadBalanceStrategy) {
|
||||
case STRATEGY_RANDOM:
|
||||
return new RandomRule();
|
||||
case STRATEGY_WEIGHT:
|
||||
return new PolarisWeightedRule(routerAPI);
|
||||
case STRATEGY_RETRY:
|
||||
return new RetryRule();
|
||||
case STRATEGY_RESPONSE_TIME_WEIGHTED:
|
||||
return new WeightedResponseTimeRule();
|
||||
case STRATEGY_BEST_AVAILABLE:
|
||||
return new BestAvailableRule();
|
||||
case STRATEGY_ROUND_ROBIN:
|
||||
return new RoundRobinRule();
|
||||
case STRATEGY_AVAILABILITY_FILTERING:
|
||||
return new AvailabilityFilteringRule();
|
||||
case STRATEGY_ZONE_AVOIDANCE:
|
||||
default:
|
||||
return new ZoneAvoidanceRule();
|
||||
}
|
||||
}
|
||||
}
|
@ -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,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,73 @@
|
||||
/*
|
||||
* 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.List;
|
||||
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
|
||||
/**
|
||||
* Simple load balancer only for getting and setting servers.
|
||||
*
|
||||
*@author lepdou 2022-05-17
|
||||
*/
|
||||
public class SimpleLoadBalancer implements ILoadBalancer {
|
||||
private List<Server> servers;
|
||||
|
||||
@Override
|
||||
public void addServers(List<Server> newServers) {
|
||||
this.servers = newServers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Server chooseServer(Object key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markServerDown(Server server) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Server> getServerList(boolean availableOnly) {
|
||||
if (servers == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.unmodifiableList(servers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Server> getReachableServers() {
|
||||
if (servers == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.unmodifiableList(servers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Server> getAllServers() {
|
||||
if (servers == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.unmodifiableList(servers);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
|
||||
*
|
||||
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.polaris.router.config;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.tencent.cloud.polaris.router.feign.PolarisFeignLoadBalancer;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* configuration for feign component.
|
||||
*
|
||||
*@author lepdou 2022-05-16
|
||||
*/
|
||||
@Configuration
|
||||
public class FeignConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public PolarisFeignLoadBalancer polarisFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
|
||||
ServerIntrospector serverIntrospector) {
|
||||
return new PolarisFeignLoadBalancer(lb, clientConfig, serverIntrospector);
|
||||
}
|
||||
}
|
@ -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,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 com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.IRule;
|
||||
import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties;
|
||||
import com.tencent.cloud.polaris.router.PolarisLoadBalancerCompositeRule;
|
||||
import com.tencent.polaris.router.api.core.RouterAPI;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Auto configuration for ribbon components.
|
||||
* @author lepdou 2022-05-17
|
||||
*/
|
||||
@Configuration
|
||||
public class RibbonConfiguration {
|
||||
|
||||
@Bean
|
||||
public IRule polarisLoadBalancerCompositeRule(RouterAPI routerAPI,
|
||||
PolarisLoadBalancerProperties polarisLoadBalancerProperties,
|
||||
PolarisNearByRouterProperties polarisNearByRouterProperties,
|
||||
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
|
||||
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties,
|
||||
IClientConfig iClientConfig) {
|
||||
return new PolarisLoadBalancerCompositeRule(routerAPI, polarisLoadBalancerProperties,
|
||||
polarisNearByRouterProperties, polarisMetadataRouterProperties,
|
||||
polarisRuleBasedRouterProperties, iClientConfig);
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.PolarisCachingSpringLoadBalanceFactory;
|
||||
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.netflix.ribbon.RibbonClients;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
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
|
||||
@RibbonClients(defaultConfiguration = {FeignConfiguration.class, RibbonConfiguration.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
|
||||
public PolarisCachingSpringLoadBalanceFactory polarisCachingSpringLoadBalanceFactory(SpringClientFactory factory) {
|
||||
return new PolarisCachingSpringLoadBalanceFactory(factory);
|
||||
}
|
||||
|
||||
@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,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.router.feign;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory;
|
||||
import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
/**
|
||||
* Extends CachingSpringLoadBalancerFactory to be able to create PolarisFeignLoadBalance.
|
||||
*
|
||||
*@author lepdou 2022-05-16
|
||||
*/
|
||||
public class PolarisCachingSpringLoadBalanceFactory extends CachingSpringLoadBalancerFactory {
|
||||
|
||||
private final Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
public PolarisCachingSpringLoadBalanceFactory(SpringClientFactory factory) {
|
||||
super(factory);
|
||||
}
|
||||
|
||||
public PolarisCachingSpringLoadBalanceFactory(SpringClientFactory factory,
|
||||
LoadBalancedRetryFactory loadBalancedRetryPolicyFactory) {
|
||||
super(factory, loadBalancedRetryPolicyFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeignLoadBalancer create(String clientName) {
|
||||
FeignLoadBalancer client = this.cache.get(clientName);
|
||||
if (client != null) {
|
||||
return client;
|
||||
}
|
||||
|
||||
IClientConfig config = this.factory.getClientConfig(clientName);
|
||||
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
|
||||
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
|
||||
|
||||
FeignLoadBalancer loadBalancer = new PolarisFeignLoadBalancer(lb, config, serverIntrospector);
|
||||
|
||||
//There is a concurrency problem here.
|
||||
//When the concurrency is high, it may cause a service to create multiple FeignLoadBalancers.
|
||||
//But there is no concurrency control in CachingSpringLoadBalancerFactory,
|
||||
//so no locks will be added here for the time being
|
||||
cache.putIfAbsent(clientName, loadBalancer);
|
||||
|
||||
return loadBalancer;
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
|
||||
*
|
||||
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.polaris.router.feign;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.reactive.LoadBalancerCommand;
|
||||
import com.tencent.cloud.common.metadata.MetadataContext;
|
||||
import com.tencent.cloud.common.metadata.MetadataContextHolder;
|
||||
import com.tencent.cloud.common.util.ExpressionLabelUtils;
|
||||
import com.tencent.cloud.common.util.JacksonUtils;
|
||||
import com.tencent.cloud.polaris.router.PolarisRouterContext;
|
||||
import com.tencent.cloud.polaris.router.RouterConstants;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* In order to pass router context for {@link com.tencent.cloud.polaris.router.PolarisLoadBalancerCompositeRule}.
|
||||
*
|
||||
*@author lepdou 2022-05-16
|
||||
*/
|
||||
public class PolarisFeignLoadBalancer extends FeignLoadBalancer {
|
||||
|
||||
public PolarisFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector) {
|
||||
super(lb, clientConfig, serverIntrospector);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customizeLoadBalancerCommandBuilder(RibbonRequest request, IClientConfig config,
|
||||
LoadBalancerCommand.Builder<RibbonResponse> builder) {
|
||||
Map<String, Collection<String>> headers = request.getRequest().headers();
|
||||
|
||||
PolarisRouterContext routerContext = buildRouterContext(headers);
|
||||
|
||||
builder.withServerLocator(routerContext);
|
||||
}
|
||||
|
||||
//set method to public for unit test
|
||||
PolarisRouterContext buildRouterContext(Map<String, Collection<String>> headers) {
|
||||
Collection<String> labelHeaderValues = headers.get(RouterConstants.ROUTER_LABEL_HEADER);
|
||||
|
||||
if (CollectionUtils.isEmpty(labelHeaderValues)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PolarisRouterContext routerContext = new PolarisRouterContext();
|
||||
|
||||
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get()
|
||||
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE));
|
||||
|
||||
labelHeaderValues.forEach(labelHeaderValue -> {
|
||||
Map<String, String> labels = JacksonUtils.deserialize2Map(labelHeaderValue);
|
||||
if (!CollectionUtils.isEmpty(labels)) {
|
||||
Map<String, String> unescapeLabels = new HashMap<>(labels.size());
|
||||
for (Map.Entry<String, String> entry : labels.entrySet()) {
|
||||
String escapedKey = ExpressionLabelUtils.unescape(entry.getKey());
|
||||
String escapedValue = ExpressionLabelUtils.unescape(entry.getValue());
|
||||
unescapeLabels.put(escapedKey, escapedValue);
|
||||
}
|
||||
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, unescapeLabels);
|
||||
}
|
||||
});
|
||||
|
||||
return routerContext;
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
|
||||
*
|
||||
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.polaris.router.feign;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.tencent.cloud.common.metadata.MetadataContext;
|
||||
import com.tencent.cloud.common.metadata.MetadataContextHolder;
|
||||
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
|
||||
import com.tencent.cloud.common.util.ExpressionLabelUtils;
|
||||
import com.tencent.cloud.common.util.JacksonUtils;
|
||||
import com.tencent.cloud.polaris.router.RouterConstants;
|
||||
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
|
||||
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Resolver labels from request.
|
||||
*
|
||||
*@author lepdou 2022-05-12
|
||||
*/
|
||||
public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RouterLabelFeignInterceptor.class);
|
||||
|
||||
private final List<RouterLabelResolver> routerLabelResolvers;
|
||||
private final MetadataLocalProperties metadataLocalProperties;
|
||||
private final RouterRuleLabelResolver routerRuleLabelResolver;
|
||||
|
||||
public RouterLabelFeignInterceptor(List<RouterLabelResolver> routerLabelResolvers,
|
||||
MetadataLocalProperties metadataLocalProperties,
|
||||
RouterRuleLabelResolver routerRuleLabelResolver) {
|
||||
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
|
||||
routerLabelResolvers.sort(Comparator.comparingInt(Ordered::getOrder));
|
||||
this.routerLabelResolvers = routerLabelResolvers;
|
||||
}
|
||||
else {
|
||||
this.routerLabelResolvers = null;
|
||||
}
|
||||
this.metadataLocalProperties = metadataLocalProperties;
|
||||
this.routerRuleLabelResolver = routerRuleLabelResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate) {
|
||||
// local service labels
|
||||
Map<String, String> labels = new HashMap<>(metadataLocalProperties.getContent());
|
||||
|
||||
// labels from rule expression
|
||||
String peerServiceName = requestTemplate.feignTarget().name();
|
||||
Map<String, String> ruleExpressionLabels = getRuleExpressionLabels(requestTemplate, peerServiceName);
|
||||
labels.putAll(ruleExpressionLabels);
|
||||
|
||||
// labels from request
|
||||
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
|
||||
routerLabelResolvers.forEach(resolver -> {
|
||||
try {
|
||||
Map<String, String> customResolvedLabels = resolver.resolve(requestTemplate);
|
||||
if (!CollectionUtils.isEmpty(customResolvedLabels)) {
|
||||
labels.putAll(customResolvedLabels);
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
LOGGER.error("[SCT][Router] revoke RouterLabelResolver occur some exception. ", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// labels from downstream
|
||||
Map<String, String> transitiveLabels = MetadataContextHolder.get()
|
||||
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
|
||||
labels.putAll(transitiveLabels);
|
||||
|
||||
// Because when the label is placed in RequestTemplate.header,
|
||||
// RequestTemplate will parse the header according to the regular, which conflicts with the expression.
|
||||
// Avoid conflicts by escaping.
|
||||
Map<String, String> escapeLabels = new HashMap<>(labels.size());
|
||||
for (Map.Entry<String, String> entry : labels.entrySet()) {
|
||||
String escapedKey = ExpressionLabelUtils.escape(entry.getKey());
|
||||
String escapedValue = ExpressionLabelUtils.escape(entry.getValue());
|
||||
escapeLabels.put(escapedKey, escapedValue);
|
||||
}
|
||||
|
||||
// pass label by header
|
||||
if (escapeLabels.size() == 0) {
|
||||
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER);
|
||||
return;
|
||||
}
|
||||
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER, JacksonUtils.serialize2Json(escapeLabels));
|
||||
}
|
||||
|
||||
private Map<String, String> getRuleExpressionLabels(RequestTemplate requestTemplate, String peerService) {
|
||||
Set<String> labelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE,
|
||||
MetadataContext.LOCAL_SERVICE, peerService);
|
||||
|
||||
if (CollectionUtils.isEmpty(labelKeys)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
return FeignExpressionLabelUtils.resolve(requestTemplate, labelKeys);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
|
||||
*
|
||||
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.polaris.router.resttemplate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
|
||||
import com.tencent.cloud.common.util.BeanFactoryUtils;
|
||||
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
|
||||
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
|
||||
|
||||
/**
|
||||
* Replace LoadBalancerInterceptor with PolarisLoadBalancerInterceptor.
|
||||
* PolarisLoadBalancerInterceptor can pass routing context information.
|
||||
*
|
||||
*@author lepdou 2022-05-18
|
||||
*/
|
||||
public class PolarisLoadBalancerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
|
||||
|
||||
private BeanFactory factory;
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.factory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof LoadBalancerInterceptor) {
|
||||
LoadBalancerRequestFactory requestFactory = this.factory.getBean(LoadBalancerRequestFactory.class);
|
||||
LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class);
|
||||
List<RouterLabelResolver> routerLabelResolvers = BeanFactoryUtils.getBeans(factory, RouterLabelResolver.class);
|
||||
MetadataLocalProperties metadataLocalProperties = this.factory.getBean(MetadataLocalProperties.class);
|
||||
RouterRuleLabelResolver routerRuleLabelResolver = this.factory.getBean(RouterRuleLabelResolver.class);
|
||||
|
||||
return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory,
|
||||
routerLabelResolvers, metadataLocalProperties, routerRuleLabelResolver);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue