feat: support lossless online/offline (#1254)

pull/1262/head
andrew shan 3 months ago committed by GitHub
parent bed48cb841
commit f148662930
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,3 +1,5 @@
# Change Log
---
- [fix: fix RouterLabelRestTemplateInterceptor add response headers exception with httpclient5.](https://github.com/Tencent/spring-cloud-tencent/pull/1239)
- [feat: support lossless online and offline](https://github.com/Tencent/spring-cloud-tencent/pull/1254)

@ -25,10 +25,10 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.OkHttpUtil;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler;
import com.tencent.cloud.polaris.util.OkHttpUtil;
import com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatProperties;
import com.tencent.polaris.api.config.global.StatReporterConfig;
import com.tencent.polaris.api.core.ProviderAPI;
@ -176,7 +176,7 @@ public class PolarisServiceRegistry implements ServiceRegistry<PolarisRegistrati
public void deregister(PolarisRegistration registration) {
LOGGER.info("De-registering from Polaris Server now...");
if (StringUtils.isEmpty(registration.getServiceId())) {
if (StringUtils.isEmpty(registration.getServiceId()) || !PolarisSDKContextManager.isRegistered) {
LOGGER.warn("No dom to de-register for polaris client...");
return;
}
@ -191,6 +191,8 @@ public class PolarisServiceRegistry implements ServiceRegistry<PolarisRegistrati
try {
ProviderAPI providerClient = polarisSDKContextManager.getProviderAPI();
providerClient.deRegister(deRegisterRequest);
PolarisSDKContextManager.isRegistered = false;
LOGGER.info("De-registration finished.");
}
catch (Exception e) {
LOGGER.error("ERR_POLARIS_DEREGISTER, de-register failed...{},", registration, e);
@ -199,8 +201,6 @@ public class PolarisServiceRegistry implements ServiceRegistry<PolarisRegistrati
if (null != heartbeatExecutor) {
heartbeatExecutor.shutdown();
}
LOGGER.info("De-registration finished.");
PolarisSDKContextManager.isRegistered = false;
}
}
@ -238,21 +238,14 @@ public class PolarisServiceRegistry implements ServiceRegistry<PolarisRegistrati
public void heartbeat(InstanceHeartbeatRequest heartbeatRequest) {
heartbeatExecutor.scheduleWithFixedDelay(() -> {
try {
String healthCheckEndpoint = polarisDiscoveryProperties.getHealthCheckUrl();
// If the health check passes, the heartbeat will be reported.
// If it does not pass, the heartbeat will not be reported.
if (!healthCheckEndpoint.startsWith("/")) {
healthCheckEndpoint = "/" + healthCheckEndpoint;
}
String healthCheckUrl = String.format("http://%s:%s%s", heartbeatRequest.getHost(),
heartbeatRequest.getPort(), healthCheckEndpoint);
Map<String, String> headers = new HashMap<>(1);
headers.put(HttpHeaders.USER_AGENT, "polaris");
if (!OkHttpUtil.get(healthCheckUrl, headers)) {
if (!OkHttpUtil.checkUrl(heartbeatRequest.getHost(), heartbeatRequest.getPort(),
polarisDiscoveryProperties.getHealthCheckUrl(), headers)) {
LOGGER.error("backend service health check failed. health check endpoint = {}",
healthCheckEndpoint);
polarisDiscoveryProperties.getHealthCheckUrl());
return;
}

@ -172,6 +172,11 @@ public class OrderConstant {
*/
public static Integer STAT_REPORTER_ORDER = 1;
/**
* Order of lossless configuration modifier.
*/
public static Integer LOSSLESS_ORDER = 2;
/**
* Order of service contract configuration modifier.
*/

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.util;
package com.tencent.cloud.common.util;
import java.io.BufferedReader;
import java.io.InputStreamReader;
@ -87,4 +87,12 @@ public final class OkHttpUtil {
}
return false;
}
public static boolean checkUrl(String host, Integer port, String endpoint, Map<String, String> headers) {
if (!endpoint.startsWith("/")) {
endpoint = "/" + endpoint;
}
String checkUrl = String.format("http://%s:%s%s", host, port, endpoint);
return get(checkUrl, headers);
}
}

@ -0,0 +1,78 @@
/*
* 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.common.util;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
/**
* Utils for URLDecoder/URLEncoder.
*
* @author Shedfree Wu
*/
public final class UrlUtils {
private static final Logger LOG = LoggerFactory.getLogger(UrlUtils.class);
private UrlUtils() {
}
public static String decode(String s) {
return decode(s, UTF_8);
}
public static String decode(String s, String enc) {
if (!StringUtils.hasText(s)) {
return s;
}
try {
return URLDecoder.decode(s, enc);
}
catch (UnsupportedEncodingException e) {
LOG.warn("Runtime system does not support {} coding. s:{}, msg:{}", enc, s, e.getMessage());
// return original string
return s;
}
}
public static String encode(String s) {
return encode(s, UTF_8);
}
public static String encode(String s, String enc) {
if (!StringUtils.hasText(s)) {
return s;
}
try {
return URLEncoder.encode(s, enc);
}
catch (UnsupportedEncodingException e) {
LOG.warn("Runtime system does not support {} coding. s:{}, msg:{}", enc, s, e.getMessage());
// return original string
return s;
}
}
}

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.util;
package com.tencent.cloud.common.util;
import org.assertj.core.util.Maps;
import org.junit.jupiter.api.Test;
@ -37,7 +37,13 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Haotian Zhang
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = OkHttpUtilTest.TestApplication.class, properties = {"spring.application.name=test", "spring.cloud.polaris.discovery.register=false"})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = OkHttpUtilTest.TestApplication.class,
properties = {
"spring.application.name=test",
"spring.cloud.polaris.discovery.register=false",
"spring.cloud.gateway.enabled=false"
})
public class OkHttpUtilTest {
@LocalServerPort
@ -46,6 +52,8 @@ public class OkHttpUtilTest {
@Test
public void testGet() {
assertThat(OkHttpUtil.get("http://localhost:" + port + "/test", Maps.newHashMap("key", "value"))).isTrue();
assertThat(OkHttpUtil.checkUrl("localhost", port, "/test", Maps.newHashMap("key", "value"))).isTrue();
assertThat(OkHttpUtil.checkUrl("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();
}

@ -0,0 +1,68 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.common.util;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Utils for {@link UrlUtils}.
*
* @author Shedfree Wu
*/
public class UrlUtilsTest {
@Test
public void testEncodeDecode1() {
String expectEncodeValue = "a%2Fb";
String origin = "a/b";
String encode1 = UrlUtils.encode(origin);
assertThat(expectEncodeValue).isEqualTo(encode1);
// encode twice is different
String encode2 = UrlUtils.encode(encode1);
assertThat(encode1).isNotEqualTo(encode2);
// test decode
assertThat(origin).isEqualTo(UrlUtils.decode(encode1));
}
@Test
public void testEncodeDecode2() {
String origin = null;
String encode1 = UrlUtils.encode(origin);
assertThat(encode1).isNull();
origin = "";
encode1 = UrlUtils.encode(origin);
assertThat(encode1).isEqualTo(origin);
}
@Test
public void testError() {
String origin = "a/b";
String encode = UrlUtils.encode(origin, "error-enc");
assertThat(encode).isEqualTo(origin);
encode = "a%2Fb";
String decode = UrlUtils.decode(encode, "error-enc");
assertThat(decode).isEqualTo(encode);
}
}

@ -83,6 +83,11 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-gateway-plugin</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-lossless-plugin</artifactId>
</dependency>
</dependencies>
<build>

@ -73,7 +73,7 @@
<revision>1.14.0-2023.0.0-SNAPSHOT</revision>
<!-- Dependencies -->
<polaris.version>1.15.0</polaris.version>
<polaris.version>1.15.3-SNAPSHOT</polaris.version>
<guava.version>32.0.1-jre</guava.version>
<springdoc.version>2.2.0</springdoc.version>
<mocktio.version>4.9.0</mocktio.version>
@ -182,6 +182,12 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-lossless-plugin</artifactId>
<version>${revision}</version>
</dependency>
<!-- third part framework dependencies -->
<dependency>
<groupId>com.google.guava</groupId>

@ -0,0 +1,63 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>lossless-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>lossless-callee-service</artifactId>
<name>Spring Cloud Starter Tencent Lossless Callee Service Example</name>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-lossless-plugin</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,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.lossless.callee;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import com.tencent.cloud.common.constant.MetadataConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
@RestController
@RequestMapping("/lossless/callee")
public class LosslessCalleeController {
private static final Logger LOG = LoggerFactory.getLogger(LosslessCalleeController.class);
@Value("${lossless.healthy.delay.second:0}")
private int healthyDelay;
private final AtomicBoolean calledHealthyEndpoint = new AtomicBoolean(false);
private final AtomicInteger healthy = new AtomicInteger(0);
@GetMapping("/health")
public ResponseEntity<String> health() {
if (healthy.get() == 1) {
return new ResponseEntity<>("OK", HttpStatus.OK);
}
else {
if (calledHealthyEndpoint.compareAndSet(false, true)) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
if (healthyDelay > 0) {
try {
Thread.sleep(healthyDelay * 1000L);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
healthy.set(1);
}
});
thread.start();
}
return new ResponseEntity<>("NOK", HttpStatus.SERVICE_UNAVAILABLE);
}
}
/**
* Get metadata in HTTP query.
*
* @param metadataStr metadata string
* @return metadata in HTTP header
* @throws UnsupportedEncodingException encoding exception
*/
@RequestMapping("/echo")
public String echoHeader(@RequestHeader(MetadataConstant.HeaderName.CUSTOM_METADATA) String metadataStr)
throws UnsupportedEncodingException {
LOG.info(URLDecoder.decode(metadataStr, UTF_8));
metadataStr = URLDecoder.decode(metadataStr, UTF_8);
return metadataStr;
}
}

@ -0,0 +1,29 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.lossless.callee;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LosslessCalleeService {
public static void main(String[] args) {
SpringApplication.run(LosslessCalleeService.class, args);
}
}

@ -0,0 +1,36 @@
server:
port: 48090
spring:
application:
name: LosslessCalleeService
cloud:
polaris:
address: grpc://119.91.66.223:8091
namespace: default
enabled: true
discovery:
enabled: true
register: true
contract:
exposure: true
report:
enabled: true
stat:
enabled: true
port: 28084
lossless:
enabled: true
healthCheckPath: /lossless/callee/health
healthCheckInterval: 5000
lossless:
healthy:
delay:
second: 20
management:
endpoints:
web:
exposure:
include:
- polaris-discovery
- polaris-ratelimit
- polaris-config

@ -0,0 +1,69 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>lossless-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.tencent.polaris</groupId>
<artifactId>lossless-nacos-callee-service</artifactId>
<name>Spring Cloud Starter Tencent Lossless Nacos Callee Service Example</name>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2023.0.0.0-RC1</version>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-discovery-adapter-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-lossless-plugin</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,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.lossless.nacos.callee;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import com.tencent.cloud.common.constant.MetadataConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
@RestController
@RequestMapping("/lossless/nacos/callee")
public class LosslessNacosCalleeController {
private static final Logger LOG = LoggerFactory.getLogger(LosslessNacosCalleeController.class);
@Value("${lossless.healthy.delay.second:0}")
private int healthyDelay;
private final AtomicBoolean calledHealthyEndpoint = new AtomicBoolean(false);
private final AtomicInteger healthy = new AtomicInteger(0);
@GetMapping("/health")
public ResponseEntity<String> health() {
if (healthy.get() == 1) {
return new ResponseEntity<>("OK", HttpStatus.OK);
}
else {
if (calledHealthyEndpoint.compareAndSet(false, true)) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
if (healthyDelay > 0) {
try {
Thread.sleep(healthyDelay * 1000L);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
healthy.set(1);
}
});
thread.start();
}
return new ResponseEntity<>("NOK", HttpStatus.SERVICE_UNAVAILABLE);
}
}
/**
* Get metadata in HTTP query.
*
* @param metadataStr metadata string
* @return metadata in HTTP header
* @throws UnsupportedEncodingException encoding exception
*/
@RequestMapping("/echo")
public String echoHeader(@RequestHeader(MetadataConstant.HeaderName.CUSTOM_METADATA) String metadataStr)
throws UnsupportedEncodingException {
LOG.info(URLDecoder.decode(metadataStr, UTF_8));
metadataStr = URLDecoder.decode(metadataStr, UTF_8);
return metadataStr;
}
}

@ -0,0 +1,31 @@
/*
* 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.lossless.nacos.callee;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LosslessNacosCalleeService {
public static void main(String[] args) {
SpringApplication.run(LosslessNacosCalleeService.class, args);
}
}

@ -0,0 +1,28 @@
server:
port: 48091
spring:
application:
name: LosslessNacosCalleeService
cloud:
nacos:
discovery:
server-addr: 9.134.5.52:8848
enabled: true
namespace: "test1"
polaris:
lossless:
enabled: true
healthCheckPath: /lossless/nacos/callee/health
healthCheckInterval: 5000
lossless:
healthy:
delay:
second: 20
management:
endpoints:
web:
exposure:
include:
- polaris-discovery
- polaris-ratelimit
- polaris-config

@ -0,0 +1,25 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>spring-cloud-tencent-examples</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modules>
<module>lossless-callee-service</module>
<module>lossless-nacos-callee-service</module>
</modules>
<modelVersion>4.0.0</modelVersion>
<artifactId>lossless-example</artifactId>
<packaging>pom</packaging>
<name>Spring Cloud Starter Tencent Lossless Example</name>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
</dependencies>
</project>

@ -22,6 +22,7 @@
<module>polaris-router-featureenv-example</module>
<module>polaris-config-data-example</module>
<module>quickstart-example</module>
<module>lossless-example</module>
</modules>
<properties>

@ -18,6 +18,7 @@
<module>spring-cloud-tencent-featureenv-plugin</module>
<module>spring-cloud-tencent-gateway-plugin</module>
<module>spring-cloud-starter-tencent-discovery-adapter-plugin</module>
<module>spring-cloud-tencent-lossless-plugin</module>
</modules>
</project>

@ -29,8 +29,8 @@
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2022.0.0.0-RC1</version>
<scope>test</scope>
<version>2023.0.0.0-RC1</version>
<optional>true</optional>
</dependency>
<dependency>

@ -19,8 +19,10 @@
package com.tencent.cloud.plugin.discovery.adapter.config;
import com.tencent.cloud.plugin.discovery.adapter.transformer.NacosInstanceTransformer;
import com.tencent.cloud.plugin.discovery.adapter.transformer.NacosRegistrationTransformer;
import com.tencent.cloud.polaris.router.config.LoadBalancerConfiguration;
import com.tencent.cloud.rpc.enhancement.transformer.InstanceTransformer;
import com.tencent.cloud.rpc.enhancement.transformer.RegistrationTransformer;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -47,4 +49,11 @@ public class NacosDiscoveryAdapterAutoConfiguration {
return new NacosInstanceTransformer();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = "com.alibaba.cloud.nacos.registry.NacosRegistration")
public RegistrationTransformer registrationTransformer() {
return new NacosRegistrationTransformer();
}
}

@ -44,6 +44,7 @@ public class NacosInstanceTransformer implements InstanceTransformer {
);
String nacosInstanceId = serviceInstance.getMetadata().get("nacos.instanceId");
instance.setId(nacosInstanceId);
}
}

@ -0,0 +1,43 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.plugin.discovery.adapter.transformer;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.registry.NacosRegistration;
import com.tencent.cloud.rpc.enhancement.transformer.RegistrationTransformer;
import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.utils.StringUtils;
import org.springframework.cloud.client.serviceregistry.Registration;
public class NacosRegistrationTransformer implements RegistrationTransformer {
@Override
public void transformCustom(DefaultInstance instance, Registration registration) {
if (registration instanceof NacosRegistration nacosRegistration) {
NacosDiscoveryProperties nacosDiscoveryProperties = nacosRegistration.getNacosDiscoveryProperties();
String namespace = nacosDiscoveryProperties.getNamespace();
if (StringUtils.isBlank(namespace)) {
namespace = "default";
}
instance.setNamespace(namespace);
}
}
}

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tencent-plugin-starters</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-tencent-lossless-plugin</artifactId>
<name>Spring Cloud Tencent Lossless Plugin</name>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-context</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-commons</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>lossless-register</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>lossless-deregister</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-test-mock-discovery</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

@ -0,0 +1,104 @@
/*
* 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.plugin.lossless;
import com.tencent.cloud.plugin.lossless.config.LosslessProperties;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.cloud.rpc.enhancement.transformer.RegistrationTransformer;
import com.tencent.polaris.api.pojo.BaseInstance;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
/**
* Intercept for of register and deregister.
*
* @author Shedfree Wu
*/
@Aspect
public class LosslessRegistryAspect {
private ServiceRegistry<Registration> serviceRegistry;
private Registration registration;
private LosslessProperties losslessProperties;
private PolarisSDKContextManager polarisSDKContextManager;
private RegistrationTransformer registrationTransformer;
private PolarisContextProperties properties;
public LosslessRegistryAspect(ServiceRegistry<Registration> serviceRegistry, Registration registration,
PolarisContextProperties properties, LosslessProperties losslessProperties,
PolarisSDKContextManager polarisSDKContextManager, RegistrationTransformer registrationTransformer) {
this.serviceRegistry = serviceRegistry;
this.registration = registration;
this.losslessProperties = losslessProperties;
this.polarisSDKContextManager = polarisSDKContextManager;
this.registrationTransformer = registrationTransformer;
this.properties = properties;
}
@Pointcut("execution(public * org.springframework.cloud.client.serviceregistry.ServiceRegistry.register(..))")
public void registerPointcut() {
}
@Pointcut("execution(public * org.springframework.cloud.client.serviceregistry.ServiceRegistry.deregister(..))")
public void deregisterPointcut() {
}
@Around("registerPointcut()")
public Object invokeRegister(ProceedingJoinPoint joinPoint) throws Throwable {
// web started, get port from registration
BaseInstance instance = SpringCloudLosslessActionProvider.getBaseInstance(registration, registrationTransformer);
Runnable registerAction = () -> executeJoinPoint(joinPoint);
SpringCloudLosslessActionProvider losslessActionProvider =
new SpringCloudLosslessActionProvider(serviceRegistry, registration, losslessProperties, registerAction);
polarisSDKContextManager.getLosslessAPI().setLosslessActionProvider(instance, losslessActionProvider);
polarisSDKContextManager.getLosslessAPI().losslessRegister(instance);
// return void
return null;
}
@Around("deregisterPointcut()")
public Object invokeDeregister(ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}
public void executeJoinPoint(ProceedingJoinPoint joinPoint) {
try {
joinPoint.proceed();
}
catch (Throwable e) {
throw new RuntimeException(e);
}
}
}

@ -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.plugin.lossless;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.common.util.OkHttpUtil;
import com.tencent.cloud.plugin.lossless.config.LosslessProperties;
import com.tencent.cloud.rpc.enhancement.transformer.RegistrationTransformer;
import com.tencent.polaris.api.plugin.lossless.InstanceProperties;
import com.tencent.polaris.api.plugin.lossless.LosslessActionProvider;
import com.tencent.polaris.api.pojo.BaseInstance;
import com.tencent.polaris.api.utils.StringUtils;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.http.HttpHeaders;
/**
* LosslessActionProvider for Spring Cloud.
*
* @author Shedfree Wu
*/
public class SpringCloudLosslessActionProvider implements LosslessActionProvider {
private ServiceRegistry<Registration> serviceRegistry;
private LosslessProperties losslessProperties;
private Runnable originalRegisterAction;
private Registration registration;
public SpringCloudLosslessActionProvider(ServiceRegistry<Registration> serviceRegistry, Registration registration,
LosslessProperties losslessProperties, Runnable originalRegisterAction) {
this.serviceRegistry = serviceRegistry;
this.registration = registration;
this.losslessProperties = losslessProperties;
this.originalRegisterAction = originalRegisterAction;
}
@Override
public String getName() {
return "spring-cloud";
}
@Override
public void doRegister(InstanceProperties instanceProperties) {
// use lambda to do original register
originalRegisterAction.run();
}
@Override
public void doDeregister() {
serviceRegistry.deregister(registration);
}
/**
* Check whether health check is enable.
* @return true: register after passing doHealthCheck, false: register after delayRegisterInterval.
*/
@Override
public boolean isEnableHealthCheck() {
return StringUtils.isNotBlank(losslessProperties.getHealthCheckPath());
}
@Override
public boolean doHealthCheck() {
Map<String, String> headers = new HashMap<>(1);
headers.put(HttpHeaders.USER_AGENT, "polaris");
return OkHttpUtil.checkUrl("localhost", registration.getPort(),
losslessProperties.getHealthCheckPath(), headers);
}
public static BaseInstance getBaseInstance(Registration registration, RegistrationTransformer registrationTransformer) {
return registrationTransformer.transform(registration);
}
}

@ -0,0 +1,54 @@
/*
* 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.plugin.lossless.config;
import com.tencent.cloud.plugin.lossless.LosslessRegistryAspect;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.cloud.rpc.enhancement.transformer.RegistrationTransformer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Autoconfiguration of lossless.
*
* @author Shedfree Wu
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisEnabled
@Import(LosslessPropertiesAutoConfiguration.class)
public class LosslessAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LosslessRegistryAspect losslessRegistryAspect(
ServiceRegistry serviceRegistry, Registration registration, PolarisContextProperties properties,
LosslessProperties losslessProperties, PolarisSDKContextManager polarisSDKContextManager,
RegistrationTransformer registrationTransformer) {
return new LosslessRegistryAspect(serviceRegistry, registration, properties, losslessProperties,
polarisSDKContextManager, registrationTransformer);
}
}

@ -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.plugin.lossless.config;
import java.util.Objects;
import com.tencent.cloud.common.constant.OrderConstant.Modifier;
import com.tencent.cloud.polaris.context.PolarisConfigModifier;
import com.tencent.polaris.factory.config.ConfigurationImpl;
import com.tencent.polaris.factory.config.provider.LosslessConfigImpl;
/**
* Config modifier for lossless.
*
* @author Shedfree Wu
*/
public class LosslessConfigModifier implements PolarisConfigModifier {
private final LosslessProperties losslessProperties;
public LosslessConfigModifier(LosslessProperties losslessProperties) {
this.losslessProperties = losslessProperties;
}
@Override
public void modify(ConfigurationImpl configuration) {
if (losslessProperties.isEnabled()) {
LosslessConfigImpl losslessConfig = (LosslessConfigImpl) configuration.getProvider().getLossless();
losslessConfig.setEnable(true);
losslessConfig.setPort(losslessProperties.getPort());
if (Objects.nonNull(losslessProperties.getDelayRegisterInterval())) {
losslessConfig.setDelayRegisterInterval(losslessProperties.getDelayRegisterInterval());
}
if (Objects.nonNull(losslessProperties.getHealthCheckInterval())) {
losslessConfig.setHealthCheckInterval(losslessProperties.getHealthCheckInterval());
}
}
}
@Override
public int getOrder() {
return Modifier.LOSSLESS_ORDER;
}
}

@ -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.plugin.lossless.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("spring.cloud.polaris.lossless")
public class LosslessProperties {
private boolean enabled = true;
private int port = 28080;
private String healthCheckPath;
private Long delayRegisterInterval;
private Long healthCheckInterval;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getHealthCheckPath() {
return healthCheckPath;
}
public void setHealthCheckPath(String healthCheckPath) {
this.healthCheckPath = healthCheckPath;
}
public Long getDelayRegisterInterval() {
return delayRegisterInterval;
}
public void setDelayRegisterInterval(Long delayRegisterInterval) {
this.delayRegisterInterval = delayRegisterInterval;
}
public Long getHealthCheckInterval() {
return healthCheckInterval;
}
public void setHealthCheckInterval(Long healthCheckInterval) {
this.healthCheckInterval = healthCheckInterval;
}
}

@ -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.plugin.lossless.config;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Autoconfiguration of lossless properties.
*
* @author Shedfree Wu
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisEnabled
@EnableConfigurationProperties(LosslessProperties.class)
public class LosslessPropertiesAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LosslessConfigModifier losslessConfigModifier(LosslessProperties losslessProperties) {
return new LosslessConfigModifier(losslessProperties);
}
}

@ -0,0 +1,35 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.lossless.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* BootstrapConfiguration of lossless properties.
*
* @author Shedfree Wu
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty("spring.cloud.polaris.enabled")
@Import(LosslessPropertiesAutoConfiguration.class)
public class LosslessPropertiesBootstrapConfiguration {
}

@ -0,0 +1,10 @@
{
"properties": [
{
"name": "spring.cloud.polaris.lossless.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for lossless plugin."
}
]
}

@ -0,0 +1,2 @@
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.plugin.lossless.config.LosslessPropertiesBootstrapConfiguration

@ -0,0 +1,91 @@
/*
* 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.plugin.lossless;
import com.tencent.cloud.plugin.lossless.config.LosslessConfigModifier;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.polaris.api.config.provider.LosslessConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link LosslessConfigModifier}.
*
* @author Shedfree Wu
*/
public class LosslessConfigModifierTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(TestApplication.class))
.withPropertyValues("spring.cloud.nacos.discovery.enabled=false")
.withPropertyValues("spring.cloud.polaris.enabled=true")
.withPropertyValues("spring.cloud.polaris.lossless.enabled=true")
.withPropertyValues("spring.cloud.polaris.lossless.port=20000")
.withPropertyValues("spring.cloud.polaris.lossless.healthCheckPath=/xxx")
.withPropertyValues("spring.cloud.polaris.lossless.delayRegisterInterval=10")
.withPropertyValues("spring.cloud.polaris.lossless.healthCheckInterval=5")
.withPropertyValues("spring.application.name=test")
.withPropertyValues("spring.cloud.gateway.enabled=false");
private final ApplicationContextRunner disabledContextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(TestApplication.class))
.withPropertyValues("spring.cloud.nacos.discovery.enabled=false")
.withPropertyValues("spring.cloud.polaris.enabled=true")
.withPropertyValues("spring.cloud.polaris.lossless.enabled=false")
.withPropertyValues("spring.application.name=test")
.withPropertyValues("spring.cloud.gateway.enabled=false");
@BeforeEach
void setUp() {
PolarisSDKContextManager.innerDestroy();
}
@Test
void testModify() {
contextRunner.run(context -> {
PolarisSDKContextManager polarisSDKContextManager = context.getBean(PolarisSDKContextManager.class);
LosslessConfig losslessConfig = polarisSDKContextManager.getSDKContext().
getConfig().getProvider().getLossless();
assertThat(losslessConfig.getHost()).isEqualTo("0.0.0.0");
assertThat(losslessConfig.getPort()).isEqualTo(20000);
assertThat(losslessConfig.getDelayRegisterInterval()).isEqualTo(10);
assertThat(losslessConfig.getHealthCheckInterval()).isEqualTo(5);
});
}
@Test
void testDisabled() {
disabledContextRunner.run(context -> {
PolarisSDKContextManager polarisSDKContextManager = context.getBean(PolarisSDKContextManager.class);
LosslessConfig losslessConfig = polarisSDKContextManager.getSDKContext().
getConfig().getProvider().getLossless();
assertThat(losslessConfig.isEnable()).isFalse();
});
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -0,0 +1,40 @@
/*
* 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.plugin.lossless;
import com.tencent.cloud.plugin.lossless.config.LosslessProperties;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link LosslessProperties}.
*
* @author Shedfree Wu
*/
public class LosslessPropertiesTest {
@Test
void testGetAndSet() {
LosslessProperties polarisStatProperties = new LosslessProperties();
// healthCheckPath
polarisStatProperties.setHealthCheckPath("/xxx");
assertThat(polarisStatProperties.getHealthCheckPath()).isEqualTo("/xxx");
}
}

@ -0,0 +1,191 @@
/*
* 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.plugin.lossless;
import java.util.Collections;
import com.tencent.cloud.common.util.OkHttpUtil;
import com.tencent.cloud.plugin.lossless.config.LosslessAutoConfiguration;
import com.tencent.cloud.plugin.lossless.config.LosslessPropertiesBootstrapConfiguration;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryAutoConfiguration;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClientConfiguration;
import com.tencent.cloud.polaris.registry.PolarisRegistration;
import com.tencent.cloud.polaris.registry.PolarisServiceRegistry;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.test.mock.discovery.NamingServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationUtils;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
/**
* Test for {@link LosslessRegistryAspect}.
*
* @author Shedfree Wu
*/
public class LosslessRegistryAspectTest {
private static String NAMESPACE_TEST = "Test";
private static String SERVICE_PROVIDER = "java_provider_test";
private static String HOST = "127.0.0.1";
private static int APPLICATION_PORT = 19091;
private static int LOSSLESS_PORT_1 = 28083;
private static NamingServer namingServer;
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
LosslessAutoConfiguration.class,
LosslessPropertiesBootstrapConfiguration.class,
PolarisContextAutoConfiguration.class,
PolarisPropertiesConfiguration.class,
PolarisDiscoveryClientConfiguration.class,
PolarisDiscoveryAutoConfiguration.class))
.withPropertyValues("spring.cloud.nacos.discovery.enabled=false")
.withPropertyValues("spring.cloud.polaris.lossless.delayRegisterInterval=5000")
.withPropertyValues("spring.cloud.polaris.lossless.healthCheckPath=")
.withPropertyValues("spring.cloud.polaris.lossless.port=" + LOSSLESS_PORT_1)
.withPropertyValues("spring.application.name=" + SERVICE_PROVIDER)
.withPropertyValues("server.port=" + APPLICATION_PORT)
.withPropertyValues("spring.cloud.polaris.localIpAddress=" + HOST)
.withPropertyValues("spring.cloud.polaris.localPort=" + APPLICATION_PORT)
.withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081")
.withPropertyValues("spring.cloud.polaris.discovery.namespace=" + NAMESPACE_TEST)
.withPropertyValues("spring.cloud.polaris.discovery.token=xxxxxx");
private final WebApplicationContextRunner contextRunner2 = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
LosslessAutoConfiguration.class,
LosslessPropertiesBootstrapConfiguration.class,
PolarisContextAutoConfiguration.class,
PolarisPropertiesConfiguration.class,
PolarisDiscoveryClientConfiguration.class,
PolarisDiscoveryAutoConfiguration.class))
.withPropertyValues("spring.cloud.nacos.discovery.enabled=false")
.withPropertyValues("spring.cloud.polaris.lossless.healthCheckInterval=1000")
.withPropertyValues("spring.cloud.polaris.lossless.healthCheckPath=/test")
.withPropertyValues("spring.cloud.polaris.lossless.port=28082")
.withPropertyValues("spring.application.name=" + SERVICE_PROVIDER)
.withPropertyValues("server.port=" + APPLICATION_PORT)
.withPropertyValues("spring.cloud.polaris.localIpAddress=" + HOST)
.withPropertyValues("spring.cloud.polaris.localPort=" + APPLICATION_PORT)
.withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081")
.withPropertyValues("spring.cloud.polaris.discovery.namespace=" + NAMESPACE_TEST)
.withPropertyValues("spring.cloud.polaris.discovery.token=xxxxxx");
@BeforeAll
static void beforeAll() throws Exception {
namingServer = NamingServer.startNamingServer(10081);
// add service
namingServer.getNamingService().addService(new ServiceKey(NAMESPACE_TEST, SERVICE_PROVIDER));
}
@AfterAll
static void afterAll() {
if (null != namingServer) {
namingServer.terminate();
}
}
@BeforeEach
void setUp() {
PolarisSDKContextManager.innerDestroy();
}
@Test
public void testRegister() {
this.contextRunner.run(context -> {
AbstractAutoServiceRegistration autoServiceRegistration = context.getBean(AbstractAutoServiceRegistration.class);
assertThatCode(() -> {
AutoServiceRegistrationUtils.register(autoServiceRegistration);
}).doesNotThrowAnyException();
Thread.sleep(1000);
// before register online status is false
assertThatCode(() -> {
assertThat(OkHttpUtil.checkUrl(HOST, LOSSLESS_PORT_1, "/online", Collections.EMPTY_MAP)).isFalse();
}).doesNotThrowAnyException();
// delay register after 5s
Thread.sleep(5000);
PolarisServiceRegistry registry = context.getBean(PolarisServiceRegistry.class);
PolarisRegistration registration = context.getBean(PolarisRegistration.class);
assertThatCode(() -> {
assertThat(registry.getStatus(registration)).isEqualTo("DOWN");
}).doesNotThrowAnyException();
assertThatCode(() -> {
assertThat(OkHttpUtil.checkUrl(HOST, LOSSLESS_PORT_1, "/online", Collections.EMPTY_MAP)).isTrue();
}).doesNotThrowAnyException();
assertThatCode(() -> {
assertThat(OkHttpUtil.checkUrl(HOST, LOSSLESS_PORT_1, "/offline", Collections.EMPTY_MAP)).isTrue();
}).doesNotThrowAnyException();
assertThatCode(() -> {
AutoServiceRegistrationUtils.deRegister(autoServiceRegistration);
}).doesNotThrowAnyException();
assertThatCode(() -> {
assertThat(registry.getStatus(registration)).isEqualTo("DOWN");
}).doesNotThrowAnyException();
});
}
@Test
public void testRegister2() {
this.contextRunner2.run(context -> {
AbstractAutoServiceRegistration autoServiceRegistration = context.getBean(AbstractAutoServiceRegistration.class);
assertThatCode(() -> {
AutoServiceRegistrationUtils.register(autoServiceRegistration);
}).doesNotThrowAnyException();
Thread.sleep(2000);
assertThatCode(() -> {
AutoServiceRegistrationUtils.deRegister(autoServiceRegistration);
}).doesNotThrowAnyException();
});
}
@Configuration
@EnableAutoConfiguration
static class PolarisPropertiesConfiguration {
}
}

@ -0,0 +1,29 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.cloud.client.serviceregistry;
public class AutoServiceRegistrationUtils {
public static void register(AbstractAutoServiceRegistration autoServiceRegistration) {
autoServiceRegistration.register();
}
public static void deRegister(AbstractAutoServiceRegistration autoServiceRegistration) {
autoServiceRegistration.deregister();
}
}

@ -23,6 +23,7 @@ import java.util.Objects;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.polaris.api.control.Destroyable;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.core.LosslessAPI;
import com.tencent.polaris.api.core.ProviderAPI;
import com.tencent.polaris.assembly.api.AssemblyAPI;
import com.tencent.polaris.assembly.factory.AssemblyAPIFactory;
@ -55,6 +56,7 @@ public class PolarisSDKContextManager {
private volatile static SDKContext sdkContext;
private volatile static ProviderAPI providerAPI;
private volatile static ConsumerAPI consumerAPI;
private volatile static LosslessAPI losslessAPI;
private volatile static RouterAPI routerAPI;
private volatile static CircuitBreakAPI circuitBreakAPI;
private volatile static LimitAPI limitAPI;
@ -81,6 +83,12 @@ public class PolarisSDKContextManager {
providerAPI = null;
}
// destroy LosslessAPI
if (Objects.nonNull(losslessAPI)) {
((AutoCloseable) losslessAPI).close();
losslessAPI = null;
}
// destroy ConsumerAPI
if (Objects.nonNull(consumerAPI)) {
((AutoCloseable) consumerAPI).close();
@ -135,6 +143,9 @@ public class PolarisSDKContextManager {
// init ProviderAPI
providerAPI = DiscoveryAPIFactory.createProviderAPIByContext(sdkContext);
// init losslessAPI
losslessAPI = DiscoveryAPIFactory.createLosslessAPIByContext(sdkContext);
// init ConsumerAPI
consumerAPI = DiscoveryAPIFactory.createConsumerAPIByContext(sdkContext);
@ -183,6 +194,11 @@ public class PolarisSDKContextManager {
return providerAPI;
}
public LosslessAPI getLosslessAPI() {
init();
return losslessAPI;
}
public ConsumerAPI getConsumerAPI() {
init();
return consumerAPI;

@ -37,6 +37,8 @@ import com.tencent.cloud.rpc.enhancement.resttemplate.PolarisLoadBalancerRequest
import com.tencent.cloud.rpc.enhancement.scg.EnhancedGatewayGlobalFilter;
import com.tencent.cloud.rpc.enhancement.transformer.InstanceTransformer;
import com.tencent.cloud.rpc.enhancement.transformer.PolarisInstanceTransformer;
import com.tencent.cloud.rpc.enhancement.transformer.PolarisRegistrationTransformer;
import com.tencent.cloud.rpc.enhancement.transformer.RegistrationTransformer;
import com.tencent.cloud.rpc.enhancement.webclient.EnhancedWebClientExchangeFilterFunction;
import com.tencent.cloud.rpc.enhancement.webclient.PolarisLoadBalancerClientRequestTransformer;
@ -47,7 +49,6 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -82,11 +83,18 @@ public class RpcEnhancementAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnMissingClass("com.alibaba.cloud.nacos.NacosServiceInstance")
@ConditionalOnClass(name = "com.tencent.cloud.common.pojo.PolarisServiceInstance")
public InstanceTransformer instanceTransformer() {
return new PolarisInstanceTransformer();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = "com.tencent.cloud.polaris.registry.PolarisRegistration")
public RegistrationTransformer registrationTransformer() {
return new PolarisRegistrationTransformer();
}
@Bean
@Lazy
public EnhancedPluginRunner enhancedFeignPluginRunner(

@ -0,0 +1,25 @@
/*
* 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.rpc.enhancement.transformer;
public class PolarisRegistrationTransformer implements RegistrationTransformer {
}

@ -0,0 +1,55 @@
/*
* 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.rpc.enhancement.transformer;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.pojo.Instance;
import org.springframework.cloud.client.serviceregistry.Registration;
/**
* RegistrationTransformer extensions to adapt 3rd registration to polaris instance.
*
* @author andrew shan
*/
public interface RegistrationTransformer {
default Instance transform(Registration registration) {
DefaultInstance instance = new DefaultInstance();
transformDefault(instance, registration);
transformCustom(instance, registration);
return instance;
}
default void transformDefault(DefaultInstance instance, Registration registration) {
instance.setNamespace(MetadataContext.LOCAL_NAMESPACE);
instance.setService(registration.getServiceId());
instance.setProtocol(registration.getScheme());
instance.setId(registration.getInstanceId());
instance.setHost(registration.getHost());
instance.setPort(registration.getPort());
instance.setMetadata(registration.getMetadata());
}
default void transformCustom(DefaultInstance instance, Registration registration) {
}
}
Loading…
Cancel
Save